commit e904b667c67c7a8c023fde2f281823f8d8b1a535 Author: wehub-resource-sync Date: Mon Jul 13 11:59:26 2026 +0800 chore: import upstream snapshot with attribution diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..612c1b6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,62 @@ +# PaddleOCR + +name: 🐛 Bug Report +description: Problems with PaddleOCR +body: + - type: markdown + attributes: + value: | + Thank you for submitting a PaddleOCR 🐛 Bug Report! + + - type: checkboxes + attributes: + label: 🔎 Search before asking + description: > + Please search the PaddleOCR [Docs](https://paddlepaddle.github.io/PaddleOCR/), [Issues](https://github.com/PaddlePaddle/PaddleOCR/issues) and [Discussions](https://github.com/PaddlePaddle/PaddleOCR/discussions) to see if a similar bug report already exists. + options: + - label: I have searched the PaddleOCR [Docs](https://paddlepaddle.github.io/PaddleOCR/) and found no similar bug report. + required: true + - label: I have searched the PaddleOCR [Issues](https://github.com/PaddlePaddle/PaddleOCR/issues) and found no similar bug report. + required: true + - label: I have searched the PaddleOCR [Discussions](https://github.com/PaddlePaddle/PaddleOCR/discussions) and found no similar bug report. + required: true + + - type: textarea + attributes: + label: 🐛 Bug (问题描述) + description: Provide console output with error messages and/or screenshots of the bug. (请提供详细报错信息或者截图) + placeholder: | + 💡 ProTip! Include as much information as possible (screenshots, logs, tracebacks etc.) to receive the most helpful response. + validations: + required: true + + - type: textarea + attributes: + label: 🏃‍♂️ Environment (运行环境) + description: Please specify the software and hardware you used to produce the bug. (请给出详细依赖包信息,便于复现问题) + placeholder: | + ```bash + OS macOS-13.5.2 + Environment Jupyter + Python 3.11.2 + PaddleOCR 2.8.1 + Install git + RAM 16.00 GB + CPU Apple M2 + CUDA None + ``` + validations: + required: true + + - type: textarea + attributes: + label: 🌰 Minimal Reproducible Example (最小可复现问题的Demo) + description: > + When asking a question, people will be better able to provide help if you provide code that they can easily understand and use to **reproduce** the problem. + This is referred to by community members as creating a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). (请务必提供该Demo,这样节省大家时间) + placeholder: | + ```bash + # Code to reproduce your issue here + ``` + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..e427482 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: 🙏 Q&A + url: https://github.com/PaddlePaddle/PaddleOCR/discussions/categories/q-a + about: Ask the community for help + - name: 💡 Feature requests and ideas + url: https://github.com/PaddlePaddle/PaddleOCR/discussions/categories/ideas + about: Share ideas for new features + - name: 🙌 Show and tell + url: https://github.com/PaddlePaddle/PaddleOCR/discussions/categories/show-and-tell + about: Show off something you've made diff --git a/.github/actions/detect-docs-only/action.yml b/.github/actions/detect-docs-only/action.yml new file mode 100644 index 0000000..b09905f --- /dev/null +++ b/.github/actions/detect-docs-only/action.yml @@ -0,0 +1,55 @@ +name: Detect Docs-Only Change +description: > + Output docs_only=true if every changed file in the current pull_request + matches docs/**, **/*.md, or .github/**. On push or workflow_dispatch, + always output docs_only=false. +outputs: + docs_only: + description: "true if change is docs-only, otherwise false" + value: ${{ steps.compute.outputs.docs_only }} +runs: + using: composite + steps: + - id: compute + shell: bash + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_BASE_REF: ${{ github.base_ref }} + run: | + set -euo pipefail + if [ "${GITHUB_EVENT_NAME}" != "pull_request" ]; then + echo "docs_only=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + git fetch origin "${GITHUB_BASE_REF}" --depth=1 >/dev/null 2>&1 || true + CHANGED_FILES="$(git diff --name-only "origin/${GITHUB_BASE_REF}...HEAD")" + # >>> matcher-begin + shopt -s globstar extglob nullglob + is_docs_only() { + local files="$1" + if [ -z "$files" ]; then + echo "false"; return 0 + fi + while IFS= read -r f; do + [ -z "$f" ] && continue + case "$f" in + docs/*) ;; + .github/*) ;; + *.md) ;; + *) + # also accept nested */**/*.md via case glob + case "$f" in + */*.md) ;; + *) echo "false"; return 0 ;; + esac + ;; + esac + done <<< "$files" + echo "true" + } + result="$(is_docs_only "${CHANGED_FILES}")" + if [ -n "${GITHUB_OUTPUT:-}" ] && [ "${GITHUB_OUTPUT}" != "/dev/null" ]; then + echo "docs_only=${result}" >> "$GITHUB_OUTPUT" + fi + echo "${result}" + # <<< matcher-end diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..be006de --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Keep GitHub Actions up to date with GitHub's Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + groups: + github-actions: + patterns: + - "*" # Group all Actions updates into a single larger pull request + schedule: + interval: weekly diff --git a/.github/workflows/build_publish_develop_docs.yml b/.github/workflows/build_publish_develop_docs.yml new file mode 100644 index 0000000..159f41a --- /dev/null +++ b/.github/workflows/build_publish_develop_docs.yml @@ -0,0 +1,39 @@ +name: Build/Publish Develop Docs +on: + push: + branches: + - master + - main +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + - uses: actions/setup-python@v6 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v5 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install mike mkdocs-material jieba mkdocs-git-revision-date-localized-plugin mkdocs-git-committers-plugin-2 mkdocs-static-i18n markdown-callouts + - name: Check docs GitHub source links + run: python tools/check_docs_github_links.py --repo-slug PaddlePaddle/PaddleOCR --forbidden-ref main --forbidden-ref master + - name: Resolve docs GitHub source refs + run: python tools/resolve_doc_github_refs.py --placeholder '{{PADDLEOCR_GITHUB_REF}}' --source-ref "${REF_NAME}" + env: + REF_NAME: ${{ github.ref_name }} + - run: | + git fetch origin gh-pages --depth=1 + mike deploy --push --update-aliases main latest + env: + DOCS_EDIT_URI: edit/${{ github.ref_name }}/docs/ diff --git a/.github/workflows/build_publish_release_docs.yml b/.github/workflows/build_publish_release_docs.yml new file mode 100644 index 0000000..6d91593 --- /dev/null +++ b/.github/workflows/build_publish_release_docs.yml @@ -0,0 +1,41 @@ +name: Build/Publish Release Docs +on: + push: + tags: + - v* + +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email github-actions[bot]@users.noreply.github.com + - uses: actions/setup-python@v6 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v5 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install mike mkdocs-material jieba mkdocs-git-revision-date-localized-plugin mkdocs-git-committers-plugin-2 mkdocs-static-i18n markdown-callouts + - name: Check docs GitHub source links + run: python tools/check_docs_github_links.py --repo-slug PaddlePaddle/PaddleOCR --forbidden-ref main --forbidden-ref master + - name: Resolve docs GitHub source refs + run: python tools/resolve_doc_github_refs.py --placeholder '{{PADDLEOCR_GITHUB_REF}}' --source-ref "${REF_NAME}" + env: + REF_NAME: ${{ github.ref_name }} + - run: | + git fetch origin gh-pages --depth=1 + mike deploy --push "${REF_NAME}" + + env: + REF_NAME: ${{ github.ref_name }} + DOCS_EDIT_URI: blob/${{ github.ref_name }}/docs/ diff --git a/.github/workflows/close_inactive_issues.yml b/.github/workflows/close_inactive_issues.yml new file mode 100644 index 0000000..5046e62 --- /dev/null +++ b/.github/workflows/close_inactive_issues.yml @@ -0,0 +1,23 @@ +name: Close inactive issues +on: + schedule: + - cron: "30 1 * * *" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v10 + with: + days-before-issue-stale: 90 + days-before-issue-close: 14 + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has been open for 90 days with no activity." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml new file mode 100644 index 0000000..4e074bd --- /dev/null +++ b/.github/workflows/codestyle.yml @@ -0,0 +1,41 @@ +name: PaddleOCR Code Style Check + +# NOTE: Job name `check-code-style` is the required status check context +# configured in branch protection. Do not rename without updating settings. + +on: + pull_request: {} + push: + branches: ['main', 'release/*'] + +jobs: + check-code-style: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.ref }} + fetch-depth: 0 + - id: detect + uses: ./.github/actions/detect-docs-only + - uses: actions/setup-python@v6 + if: steps.detect.outputs.docs_only != 'true' + with: + python-version: '3.10' + - name: Cache Python dependencies + if: steps.detect.outputs.docs_only != 'true' + uses: actions/cache@v5 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install Dependencies for Python + if: steps.detect.outputs.docs_only != 'true' + run: | + python -m pip install --upgrade pip + pip install "clang-format==13.0.0" + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + if: steps.detect.outputs.docs_only != 'true' + with: + extra_args: '--all-files' diff --git a/.github/workflows/docs_anchor_check.yml b/.github/workflows/docs_anchor_check.yml new file mode 100644 index 0000000..dc341db --- /dev/null +++ b/.github/workflows/docs_anchor_check.yml @@ -0,0 +1,39 @@ +name: Docs Anchor Link Check + +on: + pull_request: + paths: + - 'docs/**' + - 'mkdocs.yml' + - 'mkdocs-ci.yml' + - 'overrides/**' + +jobs: + check-anchor-links: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - uses: actions/setup-python@v6 + with: + python-version: '3.x' + + - uses: actions/cache@v5 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-mkdocs-${{ hashFiles('mkdocs.yml') }} + restore-keys: | + ${{ runner.os }}-pip-mkdocs- + + - name: Install dependencies + run: pip install mike mkdocs-material jieba mkdocs-git-revision-date-localized-plugin mkdocs-git-committers-plugin-2 mkdocs-static-i18n markdown-callouts + + - name: Resolve docs GitHub source refs + run: python tools/resolve_doc_github_refs.py --placeholder '{{PADDLEOCR_GITHUB_REF}}' --source-ref main + + - name: Check for broken anchor links + env: + ENABLE_GIT_PLUGINS: 'false' + run: mkdocs build -f mkdocs-ci.yml diff --git a/.github/workflows/link_check.yml b/.github/workflows/link_check.yml new file mode 100644 index 0000000..6b6ab34 --- /dev/null +++ b/.github/workflows/link_check.yml @@ -0,0 +1,42 @@ +name: Link Checker + +on: + repository_dispatch: + workflow_dispatch: + # push: + # branches: + # - main + # schedule: + # - cron: "00 18 * * 6" + +jobs: + linkChecker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: '3.x' + + - name: Resolve docs GitHub source refs + run: python tools/resolve_doc_github_refs.py --placeholder '{{PADDLEOCR_GITHUB_REF}}' --source-ref main + + - name: Link Checker + id: lychee + uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2 + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + with: + args: --exclude 'docs/index/*.md' 'docs/update/*.md' --verbose --no-progress --max-redirects 8 'docs/**/*.md' + format: markdown + fail: false + output: lychee/results.md + + - name: Create Issue From File + if: steps.lychee.outputs.exit_code != 0 + uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6 + with: + title: Link Checker Report + content-filepath: ./lychee/results.md + labels: report, automated issue diff --git a/.github/workflows/python_publish.yml b/.github/workflows/python_publish.yml new file mode 100644 index 0000000..67b4ca9 --- /dev/null +++ b/.github/workflows/python_publish.yml @@ -0,0 +1,39 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build==1.2.2 + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test_gpu.yml b/.github/workflows/test_gpu.yml new file mode 100644 index 0000000..e4c4654 --- /dev/null +++ b/.github/workflows/test_gpu.yml @@ -0,0 +1,117 @@ +name: PaddleOCR PR Tests GPU + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + workflow_dispatch: +env: + PR_ID: ${{ github.event.pull_request.number }} + COMMIT_ID: ${{ github.event.pull_request.head.sha || github.sha }} + work_dir: /workspace/PaddleOCR + PADDLENLP_ROOT: /workspace/PaddleOCR + TASK: paddleocr-CI-${{ github.event.pull_request.number }} + BRANCH: ${{ github.event.pull_request.base.ref }} + AGILE_COMPILE_BRANCH: ${{ github.event.pull_request.base.ref }} + DIR_NAME: ${{ github.repository }} +permissions: + contents: read + +jobs: + detect-changes: + runs-on: ubuntu-latest + outputs: + docs_only: ${{ steps.detect.outputs.docs_only }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - id: detect + uses: ./.github/actions/detect-docs-only + + test-pr-gpu-impl: + runs-on: [self-hosted, GPU-2Card-OCR] + needs: detect-changes + if: needs.detect-changes.outputs.docs_only != 'true' + steps: + - name: run test + env: + py_version: "3.10" + paddle_whl: https://paddle-qa.bj.bcebos.com/paddle-pipeline/Develop-GpuSome-LinuxCentos-Gcc82-Cuda118-Cudnn86-Trt85-Py310-CINN-Compile/latest/paddlepaddle_gpu-0.0.0-cp310-cp310-linux_x86_64.whl + docker_image: ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddle:latest-dev-cuda11.8-cudnn8.6-trt8.5-gcc82 + run: | + work_dir=$RANDOM + mkdir $work_dir + cd $work_dir + git clone --depth=1 https://github.com/PaddlePaddle/PaddleOCR.git -b main + cd PaddleOCR + if [ -n "${PR_ID}" ]; then + git fetch origin pull/${PR_ID}/head:ci_build + git checkout ci_build + else + git fetch --depth=1 origin "${COMMIT_ID}" + git checkout "${COMMIT_ID}" + fi + docker run --gpus all --rm -i --name PaddleOCR_CI_$RANDOM \ + --shm-size=128g --net=host \ + -v $PWD:/workspace -w /workspace \ + -e "py_version=${py_version}" \ + -e "paddle_whl=${paddle_whl}" \ + ${docker_image} /bin/bash -c ' + ldconfig; + nvidia-smi + df -hl + + echo ${py_version} + rm -rf run_env + mkdir run_env + ln -s $(which python${py_version}) run_env/python + ln -s $(which python${py_version}) run_env/python3 + ln -s $(which pip${py_version}) run_env/pip + export PATH=$PWD/run_env:${PATH} + + git config --global --add safe.directory /workspace + + python -m pip install paddlepaddle-gpu==3.1.0 -i https://www.paddlepaddle.org.cn/packages/stable/cu118/ + python -c "import paddle; paddle.version.show()" + python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple + python -m pip install pytest + if [ -f requirements.txt ]; then python -m pip install -r requirements.txt; fi + PADDLEX_SERIES=$(grep -oE "paddlex\[[^]]+\]>=[0-9]+\.[0-9]+" pyproject.toml | head -1 | grep -oE "[0-9]+\.[0-9]+") + if [ -z "$PADDLEX_SERIES" ]; then + echo "Failed to determine PaddleX version requirement from pyproject.toml" >&2 + exit 1 + fi + PADDLEX_BRANCH="release/${PADDLEX_SERIES}" + echo "Installing PaddleX from branch: ${PADDLEX_BRANCH}" + python -m pip install -e ".[all]" "paddlex@git+https://github.com/PaddlePaddle/PaddleX.git@${PADDLEX_BRANCH}" + python -c "import paddlex; print(f\"Installed paddlex version: {paddlex.__version__}\")" + python -m pytest --verbose tests/ + ' + + # Aggregator: produces a single check named `test-pr-gpu` so branch + # protection (which requires the literal context `test-pr-gpu`) is satisfied + # even when the heavy GPU job is skipped (e.g. docs-only changes). Without + # this, a SKIPPED conclusion on the GPU job leaves the required check + # unsatisfied and blocks merge. + test-pr-gpu: + runs-on: ubuntu-latest + needs: [detect-changes, test-pr-gpu-impl] + if: always() + steps: + - name: Verify required jobs + run: | + if [ "${{ needs.detect-changes.result }}" != "success" ]; then + echo "detect-changes did not succeed: ${{ needs.detect-changes.result }}" + exit 1 + fi + if [ "${{ needs.detect-changes.outputs.docs_only }}" = "true" ]; then + echo "Docs-only change; treating GPU tests as not required." + exit 0 + fi + if [ "${{ needs.test-pr-gpu-impl.result }}" != "success" ]; then + echo "test-pr-gpu-impl concluded with: ${{ needs.test-pr-gpu-impl.result }}" + exit 1 + fi + echo "GPU tests passed." diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..4e5c561 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,108 @@ +name: PaddleOCR PR Tests + +on: + push: + branches: ["main", "release/*"] + pull_request: + branches: ["main", "release/*"] + +permissions: + contents: read + +jobs: + detect-changes: + runs-on: ubuntu-latest + outputs: + docs_only: ${{ steps.detect.outputs.docs_only }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - id: detect + uses: ./.github/actions/detect-docs-only + + test-pr-python: + runs-on: ubuntu-latest + needs: detect-changes + if: needs.detect-changes.outputs.docs_only != 'true' + strategy: + matrix: + python-version: ["3.8", "3.9", "3.13"] + + steps: + - uses: actions/checkout@v6 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Cache dependencies + uses: actions/cache@v5 + with: + path: | + ~/.cache/pip + ~/.local/lib/python${{ matrix.python-version }}/site-packages + ~/.paddleocr/ + key: ${{ runner.os }}-dependencies-${{ matrix.python-version }}-${{ hashFiles('**/requirements.txt', 'pyproject.toml') }} + restore-keys: | + ${{ runner.os }}-dependencies- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + # On py3.8 several paddlex transitive deps require py3.9+, so only + # paddleocr[doc2md] is installable. See installation.md for the + # supported extras-by-Python-version matrix. + if [[ "${{ matrix.python-version }}" == "3.8" ]]; then + python -m pip install paddlepaddle==3.0.0 -i https://www.paddlepaddle.org.cn/packages/stable/cpu/ + PADDLEOCR_EXTRAS="[doc2md]" + else + python -m pip install paddlepaddle==3.1.0 -i https://www.paddlepaddle.org.cn/packages/stable/cpu/ + PADDLEOCR_EXTRAS="[all]" + fi + PADDLEX_SERIES=$(grep -oE 'paddlex\[[^]]+\]>=[0-9]+\.[0-9]+' pyproject.toml | head -1 | grep -oE '[0-9]+\.[0-9]+') + if [ -z "$PADDLEX_SERIES" ]; then + echo "Failed to determine PaddleX version requirement from pyproject.toml" >&2 + exit 1 + fi + + python -m pip install "paddlex>=3.7.0,<3.8.0" + PADDLEX_BRANCH="release/${PADDLEX_SERIES}" + echo "Installing PaddleX from branch: ${PADDLEX_BRANCH}" + python -m pip install -e ".${PADDLEOCR_EXTRAS}" "paddlex@git+https://github.com/PaddlePaddle/PaddleX.git@${PADDLEX_BRANCH}" + python -c "import paddlex; print(f'Installed paddlex version: {paddlex.__version__}')" + - name: Test with pytest + run: | + # Skip py38_incompatible tests on py3.8. + if [[ "${{ matrix.python-version }}" == "3.8" ]]; then + pytest --verbose tests/ -m "not resource_intensive and not py38_incompatible" + else + pytest --verbose tests/ + fi + + # Aggregator: produces a single check named `test-pr` so branch protection + # (which requires the literal context `test-pr`) is satisfied. Without this, + # the matrix above only emits `test-pr (3.x)` checks and the required + # `test-pr` context never reports. + test-pr: + runs-on: ubuntu-latest + needs: [detect-changes, test-pr-python] + if: always() + steps: + - name: Verify required jobs + run: | + if [ "${{ needs.detect-changes.result }}" != "success" ]; then + echo "detect-changes did not succeed: ${{ needs.detect-changes.result }}" + exit 1 + fi + if [ "${{ needs.detect-changes.outputs.docs_only }}" = "true" ]; then + echo "Docs-only change; skipping python tests." + exit 0 + fi + if [ "${{ needs.test-pr-python.result }}" != "success" ]; then + echo "test-pr-python concluded with: ${{ needs.test-pr-python.result }}" + exit 1 + fi + echo "All python matrix variants passed." diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7930be0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +.ipynb_checkpoints/ +*.py[cod] +*$py.class + +# C extensions +*.so + +/inference/ +/inference_results/ +/output/ +/train_data/ +/log/ + +*.DS_Store +*.vs +*.user +*~ +*.vscode +*.idea + +*.log +.clang-format +.clang_format.hook + +build/ +dist/ +*.egg-info/ + +/deploy/android_demo/app/OpenCV/ +/deploy/android_demo/app/PaddleLite/ +/deploy/android_demo/app/.cxx/ +/deploy/android_demo/app/cache/ + +test_tipc/web/models/ +test_tipc/web/node_modules/ + +.venv/ +.worktrees/ diff --git a/.lycheeignore b/.lycheeignore new file mode 100644 index 0000000..a0761f9 --- /dev/null +++ b/.lycheeignore @@ -0,0 +1,5 @@ +zhuanlan.zhihu.com/* +https://demo.doctrp.top/ +http://127.0.0.1:8001/ +http://localhost:9003 +https://rrc.cvc.uab.es/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c2c52b4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,46 @@ +exclude: ^(langchain-paddleocr/|paddleocr-js/) +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files + args: ['--maxkb=512'] + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - id: detect-private-key + - id: end-of-file-fixer + - id: trailing-whitespace + files: \.(c|cc|cxx|cpp|cu|h|hpp|hxx|py)$ +- repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.5.5 + hooks: + - id: remove-crlf + - id: remove-tabs + files: \.(c|cc|cxx|cpp|cu|h|hpp|hxx|py)$ +- repo: local + hooks: + - id: clang-format + name: clang-format + description: Format files with ClangFormat + entry: bash .clang_format.hook -i + language: system + files: \.(c|cc|cxx|cpp|cu|h|hpp|hxx|cuh|proto)$ +# For Python files +- repo: https://github.com/psf/black.git + rev: 24.10.0 + hooks: + - id: black + files: (.*\.(py|pyi|bzl)|BUILD|.*\.BUILD|WORKSPACE)$ + +# Flake8 +- repo: https://github.com/pycqa/flake8 + rev: 7.1.1 + hooks: + - id: flake8 + args: + - --count + - --select=E9,F63,F7,F82,E721 + - --show-source + - --statistics + exclude: ^benchmark/|^test_tipc/ diff --git a/.style.yapf b/.style.yapf new file mode 100644 index 0000000..4741fb4 --- /dev/null +++ b/.style.yapf @@ -0,0 +1,3 @@ +[style] +based_on_style = pep8 +column_limit = 80 diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..ac7eea8 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +www.paddleocr.ai diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..430edfa --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..5e6f4f2 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,22 @@ +prune .github +prune applications +prune benchmark +prune configs +prune deploy +prune doc +prune docs +prune overrides +prune ppocr/ext_op +prune ppocr/losses +prune ppocr/metrics +prune ppocr/modeling +prune ppocr/optimizer +prune ppstructure/docs +prune test_tipc +prune tests +exclude .clang_format.hook +exclude .gitignore +exclude .pre-commit-config.yaml +exclude .style.yapf +exclude mkdocs.yml +exclude train.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..6458242 --- /dev/null +++ b/README.md @@ -0,0 +1,316 @@ + +
+

+ Star-history +

+ + + +

Global Leading OCR Toolkit & Document AI Engine

+ +English | [简体中文](./readme/README_cn.md) | [繁體中文](./readme/README_tcn.md) | [日本語](./readme/README_ja.md) | [한국어](./readme/README_ko.md) | [Français](./readme/README_fr.md) | [Русский](./readme/README_ru.md) | [Español](./readme/README_es.md) | [العربية](./readme/README_ar.md) + + + +[![PyPI Downloads](https://static.pepy.tech/badge/paddleocr)](https://pepy.tech/projects/paddleocr) +[![Used by](https://img.shields.io/badge/Used%20by-6k%2B%20repositories-blue)](https://github.com/PaddlePaddle/PaddleOCR/network/dependents) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + +[![AI Studio](https://img.shields.io/badge/PaddleOCR-_Offiical_Website-1927BA?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAABlBMVEU2P+X///+1KuUwAAAHKklEQVR42u3dS5bjOAwEwALvf2fMavZum6IAImI7b2yYSqU+1Zb//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKCR/+fzly7rD92yVg69xh8zeLwOa5w+ZvFYHtc4ft3ykB++cOm79PAp6YO2z/Ngl4ZO5l+9+yT4QAvLqS748VF33Ylzdvzpl72f6z53YIGJ6SZdPeNHcIwOycaADdLgCSIgAIgCOAACAAykIAEAAEAAFAABCAT+WQuQVgeBqXhXQIQAAYegowLQBpbg3gZGFyAC6vgBQAMREA2/YfDPxyaDQNyTNz+3Zwn5J4ZG7PB2h0kHhi7plPCImmJwkPzO0RMa3OET0i5uGlzHFze0xcu0vE2Dq3J4U2vEPgSaHbFzPNDQAAAAAAAMBNovdw+cP/ny+uaf7w/+eYADy8kE+F4Offdjn6zZXhAXgiA78G4MNNsmnu1Xr7b3mbOL8T5Ja5bw/A35EC2LiWpzt1y9jRugBy30fLg3NvHPvnuZcC2NsCUXA/aRmA89V07Fwgt37uH8deCmBr6N44pP4UgaUATpdA7v/cMbIB8okliY65/SW5HhJ1ehPmM+8edwXgpbu4R88FayR32Y/P7oZZbOx13/Zr//ZHx27bAPnkFoyewYlbAhD3TvBobr95gaUAtr1EdNx1lgI4OcTTuR3z6+FZMEDRcu9ZCuDgGCdyGxMa4EgBRMvcjrkM7NgBZw5c0TwAUWUhZwRXA2xaya65Xa3jO2qYZ8bu2AD5w38tG5V8aZpoGN6Tz0bOfa9bceyWAciTO0jWyO1Tc5cLwJmF/JfPnXVyu3/slgHIg1n79O2O5fZv+1cHV7sC2HYqmUdHysNzX3sVkMcjUK5Gc+dMs28E5bGtm0V3gloBOP9vgZv+4sYn3RUaYFMCol5uN77g6lUApc8pWs69Zn7snS9Z9Q8G0S0AUTVUUTG3A54R1KSvo/diLAv5fKzynZeN6xogC75u93+AtBTA47OlAFSv6qY/vp3DAjD8iv2ZdFYJwKynMhTK1rInPfzaxW81LnvSgFP9KxrATaCLA3DxHpbFX31ZyNm5XRZyXG5bNkAWfP0rcrsUwOgC6NIAzgBcBiqAWwPgLrAGuGBP6jr2sifdfiJ6QQM4Bbw4AK4B3129ZSFn53ZZyA/GyFty27IBFMDFAXAG8PbyLQv5xULGPRl0K3h2AbwcgCZPhs+LD1zLnjS6AN4NwMU/DVFh7LyhASreTbvqrxdr/J4XT4Swz4FrTS+AGJ7bNbwAYkxuWzZAVljHrJfbjb9wviYXwFO/FJ8Vli4vaICsEMFyBbA3tmtsAUS0zG1c/bj4YwsZH2/+Whd0+1Nb+S7IE2sfPw4RL0XmsR8Nqvz7qFngmPHF34EqjP15AAofAkosZKPC/K6FVoeP02Ehi540NG6AK/4pYP3cLgVwXwHkDQ1QcSGb/uF4WwCmfX8u/+4vgLINcMUlQIfcLgXwXAF0+BGkpQDuuJx7/hwgpu//cWVuO3wxJOz/z8297vgYBwaIO3O7Kn+c194578ltywbIgu8fl+Z2lS+APvnLjnOv8hsgSqxjgwL4Ln9LAezaj98tgPzy7ZcC+GQzxrWxXQpgx370dm6/H7v6jaBoso5dY1swAFlwHWvfBf5pxVa93fCtdx64+1dsgCy4joWvAfPX9VoKYMs6Zse9/8Mlvv7LILlhAfKFFdsSutJXAdFkL3qlADJPrXFcXAC5KYaH586jO9mtAch9S3T0GQJ726ZWAE49kjP3rlDJuetdaL/1zeqZY9c7CRz7s0wCUPxienQBnAuAAtAAlxaAAAxfyBQABSAACkAAFIAAKAABUAACMEkKwL170oh7V8ueNLoAjgTAXWAN4BRwcABcA2oABTA4AApAAyiAwQFQABpAAQwOgALQADMWUgCuEmNyu15fSIY3gFPAiwPgFFADKIDBAVAAGkABCIACmBqAUAAaQAHMDUCMWkgBuMWw3K43F5LhDeAU8OIAuAmkARTA4AAoAA2gAARAAUwNgLvAGkABDA6Au8AaoKOJuV0vLSTDG8Ap4MUBcBNIAyiAwQFQABpAAQwOgALQAApAABTA1AC4C6wBOhqb23V+IRneAE4BLw6Aa0ANoAAGB0ABaAAFMDgACkADKAABUABTA+AusAboKATAQs4trjV+IYcfuJYCcA6gAATAQk69dFkKQANYyLkFcLIBFIDLQAVwawDsSRrAEWBwAJwCagAFMDgACkADKIDBAVAAGkABCIACmBoAzwXWAApgcADsSRrg0iNACoACEADXgAIwdCFTACykALgGFIAfl0kBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPBv/gN+IH8U6YveYgAAAABJRU5ErkJggg==&labelColor=white)](https://www.paddleocr.com) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/PaddlePaddle/PaddleOCR) +[![License](https://img.shields.io/badge/license-Apache_2.0-green)](../LICENSE) + +
+ + + + + + +**PaddleOCR converts PDF documents and images into structured, LLM-ready data (JSON/Markdown) with industry-leading accuracy. With 70k+ Stars and trusted by top-tier projects like Dify, RAGFlow, and Cherry Studio, PaddleOCR is the bedrock for building intelligent RAG and Agentic applications.** + + +## 🚀 Key Features + +### 📄 Intelligent Document Parsing (LLM-Ready) +> *Transforming messy visuals into structured data for the LLM era.* + +* **SOTA Document VLM**: Featuring **PaddleOCR-VL-1.6 (0.9B)**, the industry's leading lightweight vision-language model for document parsing. It achieves 96.3% accuracy on OmniDocBench v1.6, leads in text, formula, and table recognition, and shows significantly enhanced capabilities in ancient documents, rare characters, seals, and charts, with structured outputs in **Markdown** and **JSON** formats. +* **Structure-Aware Conversion**: Powered by **PP-StructureV3**, seamlessly convert complex PDFs and images into **Markdown** or **JSON**. Unlike the PaddleOCR-VL series models, it provides more fine-grained coordinate information, including table cell coordinates, text coordinates, and more. +* **Production-Ready Efficiency**: Achieve commercial-grade accuracy with an ultra-small footprint. Outperforms numerous closed-source solutions in public benchmarks while remaining resource-efficient for edge/cloud deployment. + +### 🔍 Universal Text Recognition (Scene OCR) +> *The global gold standard for high-speed, multilingual text spotting.* + +* **100+ Languages Supported**: Native recognition for a vast global library. **PP-OCRv6** supports 50 languages with a single unified model (Chinese, English, Japanese, and 46 Latin-script languages) — no model switching needed for multilingual documents. +* **Complex Element Mastery**: Beyond standard text recognition, we support **natural scene text spotting** across a wide range of environments, including IDs, street views, books, and industrial components +* **Performance Leap**: PP-OCRv6 achieves **+4.6% detection** and **+5.1% recognition** accuracy over PP-OCRv5, surpassing mainstream Vision-Language Models. 5.2× CPU inference speedup end-to-end. + +
+

+ PaddleOCR Architecture +

+
+ +### 🛠️ Developer-Centric Ecosystem +* **Seamless Integration**: The premier choice for the AI Agent ecosystem—deeply integrated with **Dify, RAGFlow, Pathway, and Cherry Studio**. +* **LLM Data Flywheel**: A complete pipeline to build high-quality datasets, providing a sustainable "Data Engine" for fine-tuning Large Language Models. +* **One-Click Deployment**: Supports various hardware backends (NVIDIA GPU, Intel CPU, Kunlunxin XPU, and diverse AI Accelerators). + + +## 📣 Recent updates + +### 🔥 2026.06.11: Release of PaddleOCR 3.7.0 +- PP-OCRv6 highlights: + + - **Accuracy boost**: Medium tier achieves +4.6% detection and +5.1% recognition over PP-OCRv5_server, surpassing mainstream VLMs (Qwen3-VL-235B, GPT-5.5) with only 34.5M parameters. + - **50 languages unified**: Single model covers Chinese, English, Japanese, and 46 Latin-script languages — no model switching needed. + - **Specialized scenarios**: Major improvements in digital displays, dot-matrix characters, tire prints, and industrial text recognition. + - **Faster inference**: 5.2× CPU speedup (OpenVINO), 6.1× on Apple M4 (tiny), 0.13s on A100 GPU. + - **Three tiers for all scenarios**: tiny (1.5M) / small (7.7M) / medium (34.5M) for edge, mobile, and server deployment. + - **Model availability**: All models are available on [HuggingFace](https://huggingface.co/collections/PaddlePaddle/pp-ocrv6) and [ModelScope](https://www.modelscope.cn/collections/PaddlePaddle/PP-OCRv6). + +
+2026.05.28: Release of PaddleOCR 3.6.0 + +- PaddleOCR-VL-1.6 highlights: + + - **New SOTA Accuracy**: Achieves over 96.3% on OmniDocBench v1.6, also sets new SOTA on OmniDocBench v1.5 and Real5-OmniDocBench, leading both open-source and proprietary solutions in text, formula, and table recognition. + - **Comprehensive Capability Upgrade**: Significant improvements in table, ancient document, and rare character recognition, with notably enhanced seal recognition, spotting, and chart understanding across multiple scenarios. + - **Seamless Migration**: Model architecture is fully consistent with PaddleOCR-VL-1.5, enabling zero-cost adaptation—swap and go. + - **Try it now**: Available on [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6) or our [Official Website](https://www.paddleocr.com). + +
+ +
+2026.04.21: Release of PaddleOCR 3.5.0 + +* **Flexible inference backends**: Seamlessly switch between Paddle static graph, Paddle dynamic graph, or Transformers. PaddleOCR is now deeply integrated with the Hugging Face ecosystem, and 20 major models support Transformers as the inference backend. +* **Office documents to Markdown**: Convert common document formats such as Word, Excel, and PowerPoint into Markdown. +* **DOCX export for parsed results**: The `PaddleOCR-VL` series, `PP-StructureV3`, and `PP-DocTranslation` now support exporting parsed results to DOCX for convenient viewing and editing in Microsoft Word. +* **Official browser inference SDK**: Released `PaddleOCR.js`, the official browser inference SDK that supports running `PP-OCRv5` directly in the browser. + +
+ +
+2026.01.29: Release of PaddleOCR 3.4.0 + +* PaddleOCR-VL-1.5 (SOTA 0.9B VLM): Our latest flagship model for document parsing is now live! + * **94.5% Accuracy on OmniDocBench**: Surpassing top-tier general large models and specialized document parsers. + * **Real-World Robustness**: First to introduce the **PP-DocLayoutV3** algorithm for irregular shape positioning, mastering 5 tough scenarios: *Skew, Warping, Scanning, Illumination, and Screen Photography*. + * **Capability Expansion**: Now supports **Seal Recognition**, **Text Spotting**, and expands to **111 languages** (including China’s Tibetan script and Bengali). + * **Long Document Mastery**: Supports automatic cross-page table merging and hierarchical heading identification. + * **Try it now**: Available on [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.5) or our [Official Website](https://www.paddleocr.com). + +
+ +
+2025.10.16: Release of PaddleOCR 3.3.0 + +- Released PaddleOCR-VL: + - **Model Introduction**: + - **PaddleOCR-VL** is a SOTA and resource-efficient model tailored for document parsing. Its core component is PaddleOCR-VL-0.9B, a compact yet powerful vision-language model (VLM) that integrates a NaViT-style dynamic resolution visual encoder with the ERNIE-4.5-0.3B language model to enable accurate element recognition. **This innovative model efficiently supports 109 languages and excels in recognizing complex elements (e.g., text, tables, formulas, and charts), while maintaining minimal resource consumption**. Through comprehensive evaluations on widely used public benchmarks and in-house benchmarks, PaddleOCR-VL achieves SOTA performance in both page-level document parsing and element-level recognition. It significantly outperforms existing solutions, exhibits strong competitiveness against top-tier VLMs, and delivers fast inference speeds. These strengths make it highly suitable for practical deployment in real-world scenarios. The model has been released on [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL). Everyone is welcome to download and use it! More introduction information can be found in [PaddleOCR-VL](https://www.paddleocr.ai/latest/version3.x/algorithm/PaddleOCR-VL/PaddleOCR-VL.html). + + - **Core Features**: + - **Compact yet Powerful VLM Architecture**: We present a novel vision-language model that is specifically designed for resource-efficient inference, achieving outstanding performance in element recognition. By integrating a NaViT-style dynamic high-resolution visual encoder with the lightweight ERNIE-4.5-0.3B language model, we significantly enhance the model’s recognition capabilities and decoding efficiency. This integration maintains high accuracy while reducing computational demands, making it well-suited for efficient and practical document processing applications. + - **SOTA Performance on Document Parsing**: PaddleOCR-VL achieves state-of-the-art performance in both page-level document parsing and element-level recognition. It significantly outperforms existing pipeline-based solutions and exhibiting strong competitiveness against leading vision-language models (VLMs) in document parsing. Moreover, it excels in recognizing complex document elements, such as text, tables, formulas, and charts, making it suitable for a wide range of challenging content types, including handwritten text and historical documents. This makes it highly versatile and suitable for a wide range of document types and scenarios. + - **Multilingual Support**: PaddleOCR-VL Supports 109 languages, covering major global languages, including but not limited to Chinese, English, Japanese, Latin, and Korean, as well as languages with different scripts and structures, such as Russian (Cyrillic script), Arabic, Hindi (Devanagari script), and Thai. This broad language coverage substantially enhances the applicability of our system to multilingual and globalized document processing scenarios. + +- Released PP-OCRv5 Multilingual Recognition Model: + - Improved the accuracy and coverage of Latin script recognition; added support for Cyrillic, Arabic, Devanagari, Telugu, Tamil, and other language systems, covering recognition of 109 languages. The model has only 2M parameters, and the accuracy of some models has increased by over 40% compared to the previous generation. + +
+ + +
+2025.08.21: Release of PaddleOCR 3.2.0 + +- **Significant Model Additions:** + - Introduced training, inference, and deployment for PP-OCRv5 recognition models in English, Thai, and Greek. **The PP-OCRv5 English model delivers an 11% improvement in English scenarios compared to the main PP-OCRv5 model, with the Thai and Greek recognition models achieving accuracies of 82.68% and 89.28%, respectively.** + +- **Deployment Capability Upgrades:** + - **Full support for PaddlePaddle framework versions 3.1.0 and 3.1.1.** + - **Comprehensive upgrade of the PP-OCRv5 C++ local deployment solution, now supporting both Linux and Windows, with feature parity and identical accuracy to the Python implementation.** + - **High-performance inference now supports CUDA 12, and inference can be performed using either the Paddle Inference or ONNX Runtime backends.** + - **The high-stability service-oriented deployment solution is now fully open-sourced, allowing users to customize Docker images and SDKs as required.** + - The high-stability service-oriented deployment solution also supports invocation via manually constructed HTTP requests, enabling client-side code development in any programming language. + +- **Benchmark Support:** + - **All production lines now support fine-grained benchmarking, enabling measurement of end-to-end inference time as well as per-layer and per-module latency data to assist with performance analysis. [Here's](docs/version3.x/pipeline_usage/instructions/benchmark.en.md) how to set up and use the benchmark feature.** + - **Documentation has been updated to include key metrics for commonly used configurations on mainstream hardware, such as inference latency and memory usage, providing deployment references for users.** + +- **Bug Fixes:** + - Resolved the issue of failed log saving during model training. + - Upgraded the data augmentation component for formula models for compatibility with newer versions of the albumentations dependency, and fixed deadlock warnings when using the tokenizers package in multi-process scenarios. + - Fixed inconsistencies in switch behaviors (e.g., `use_chart_parsing`) in the PP-StructureV3 configuration files compared to other pipelines. + +- **Other Enhancements:** + - **Separated core and optional dependencies. Only minimal core dependencies are required for basic text recognition; additional dependencies for document parsing and information extraction can be installed as needed.** + - **Enabled support for NVIDIA RTX 50 series graphics cards on Windows; users can refer to the [installation guide](docs/version3.x/installation.en.md) for the corresponding PaddlePaddle framework versions.** + - **PP-OCR series models now support returning single-character coordinates.** + - Added AIStudio, ModelScope, and other model download sources, allowing users to specify the source for model downloads. + - Added support for chart-to-table conversion via the PP-Chart2Table module. + - Optimized documentation descriptions to improve usability. +
+ + +[History Log](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + + +## 🚀 Quick Start + +### Step 1: Try Online +PaddleOCR official website provides interactive **Experience Center** and **APIs**—no setup required, just one click to experience. + +👉 [Visit Official Website](https://www.paddleocr.com) + +### Step 2: Local Deployment +For local usage, please refer to the following documentation based on your needs: + +- **PP-OCR Series**: See [PP-OCR Documentation](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/OCR.html) +- **PaddleOCR-VL Series**: See [PaddleOCR-VL Documentation](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PaddleOCR-VL.html) +- **PP-StructureV3**: See [PP-StructureV3 Documentation](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PP-StructureV3.html) +- **More Capabilities**: See [More Capabilities Documentation](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/pipeline_overview.html) + + +## 🧩 More Features + +- Convert models to ONNX format: [Obtaining ONNX Models](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/others/obtaining_onnx_models.html). +- Accelerate inference using engines like OpenVINO, ONNX Runtime, TensorRT, or perform inference using ONNX format models: [High-Performance Inference](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/local_inference/high_performance_inference.html). +- Accelerate inference using multi-GPU and multi-process: [Parallel Inference for Pipelines](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/instructions/parallel_inference.html). +- Integrate PaddleOCR into applications written in C++, C#, Java, etc.: [Serving](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/serving/serving.html). + +## 🔄 Quick Overview of Execution Results + +### PP-OCRv5 + +
+

+ PP-OCRv5 Demo +

+
+ + + +### PP-StructureV3 + +
+

+ PP-StructureV3 Demo +

+
+ +### PaddleOCR-VL + +
+

+ PP-StructureV3 Demo +

+
+ + +## ✨ Stay Tuned + +⭐ **Star this repository to keep up with exciting updates and new releases, including powerful OCR and document parsing capabilities!** ⭐ + +
+

+ Star-Project +

+
+ + +## 👩‍👩‍👧‍👦 Community + +
+ +| PaddlePaddle WeChat official account | Join the tech discussion group | +| :---: | :---: | +| | | +
+ + +## 😃 Awesome Projects Leveraging PaddleOCR +PaddleOCR wouldn't be where it is today without its incredible community! 💗 A massive thank you to all our longtime partners, new collaborators, and everyone who's poured their passion into PaddleOCR — whether we've named you or not. Your support fuels our fire! + +
+ +| Project Name | Description | +| ------------ | ----------- | +| [Dify](https://github.com/langgenius/dify) |Production-ready platform for agentic workflow development.| +| [RAGFlow](https://github.com/infiniflow/ragflow) |RAG engine based on deep document understanding.| +| [pathway](https://github.com/pathwaycom/pathway) |Python ETL framework for stream processing, real-time analytics, LLM pipelines, and RAG.| +| [MinerU](https://github.com/opendatalab/MinerU) |Multi-type Document to Markdown Conversion Tool| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |Free, Open-source, Batch Offline OCR Software.| +| [cherry-studio](https://github.com/CherryHQ/cherry-studio) |A desktop client that supports for multiple LLM providers.| +| [haystack](https://github.com/deepset-ai/haystack) |AI orchestration framework to build customizable, production-ready LLM applications.| +| [OmniParser](https://github.com/microsoft/OmniParser) |OmniParser: Screen Parsing tool for Pure Vision Based GUI Agent.| +| [QAnything](https://github.com/netease-youdao/QAnything) |Question and Answer based on Anything.| +| [Learn more projects](./awesome_projects.md) | [More projects based on PaddleOCR](./awesome_projects.md)| +
+ +## 👩‍👩‍👧‍👦 Contributors + +
+ + + +
+ +## 🌟 Star + +
+

+ Star-history +

+
+ + +## 📄 License +This project is released under the [Apache 2.0 license](LICENSE). + +## 🎓 Citation + +```bibtex +@misc{cui2025paddleocr30technicalreport, + title={PaddleOCR 3.0 Technical Report}, + author={Cheng Cui and Ting Sun and Manhui Lin and Tingquan Gao and Yubo Zhang and Jiaxuan Liu and Xueqing Wang and Zelun Zhang and Changda Zhou and Hongen Liu and Yue Zhang and Wenyu Lv and Kui Huang and Yichao Zhang and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2507.05595}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2507.05595}, +} + +@misc{cui2025paddleocrvlboostingmultilingualdocument, + title={PaddleOCR-VL: Boosting Multilingual Document Parsing via a 0.9B Ultra-Compact Vision-Language Model}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Handong Zheng and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2510.14528}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2510.14528}, +} + +@misc{cui2026paddleocrvl15multitask09bvlm, + title={PaddleOCR-VL-1.5: Towards a Multi-Task 0.9B VLM for Robust In-the-Wild Document Parsing}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2026}, + eprint={2601.21957}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2601.21957}, +} + +@misc{zhang2026paddleocrvl16expandingfrontierdocument, + title={PaddleOCR-VL-1.6: Expanding the Frontier of Document Parsing with Under-Optimized Region Refinement and Progressive Post-Training}, + author={Zelun Zhang and Hongen Liu and Suyin Liang and Yubo Zhang and Yiqing Xiang and Jiaxuan Liu and Ting Sun and Manhui Lin and Yue Zhang and Changda Zhou and Tingquan Gao and Cheng Cui and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2026}, + eprint={2606.03264}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2606.03264}, +} +``` diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..91d80c3 --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`PaddlePaddle/PaddleOCR` +- 原始仓库:https://github.com/PaddlePaddle/PaddleOCR +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..fe260a2 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,194 @@ +# PaddleOCR Release SOP + +[中文](./RELEASING_cn.md) + +## Scope + +This document describes the standard release process for PaddleOCR. + +## Release Types + +The current process supports the following two release types: + +- `bump patch`: for example, `3.4.0 -> 3.4.1` +- `bump minor`: for example, `3.4.x -> 3.5.0` + +For `bump major`: + +- The current process does not directly support this scenario +- A major release process should be discussed and designed separately, for example by introducing an additional branch or a new preparation flow + +## Release Principles + +- Daily development happens on `main` +- Official releases are made only from `release/X.Y` +- Official tags must use `vX.Y.Z` +- Do not create official release tags on `main` + +## Standard Release Process + +### 1. Confirm the release target + +First confirm which release line this release belongs to and what the target version is. + +Examples: + +- Release `3.4.1` on `release/3.4` +- Release `3.5.0` on `release/3.5` + +### 2. Create or switch to the release branch + +If the release line does not exist yet, create the corresponding `release/X.Y` branch from `main`. +If it already exists, switch to that branch and continue release preparation there. + +Requirements: + +- One minor release line corresponds to one fixed `release/X.Y` branch +- That branch should contain only the release content and patch fixes for that line + +### 3. Pick release content from main + +Based on the release scope, `cherry-pick` the required commits from `main` into `release/X.Y` until the release content is ready. + +Requirements: + +- Only pick what is needed for the current release +- Avoid bringing unrelated new features into the release branch +- If there are release-specific fixes on the release branch, keep them limited to the current release scope + +### 4. Complete pre-release checks + +Complete pre-release checks on `release/X.Y`. + +At minimum, this should include: + +- The current branch is correct +- The working tree is clean +- The version is as expected +- Key functionality is verified +- Required tests, builds, packaging, and regression checks have passed +- Release notes are ready + +### 5. Create the official tag + +Once the release is ready, create the official tag on `release/X.Y`: + +- The tag format must be `vX.Y.Z` + +Examples: + +- `v3.4.1` +- `v3.5.0` + +Requirements: + +- The tag must be created on the release branch for the current official release +- Do not use development tags as official release tags + +### 6. Publish the GitHub Release + +Create a GitHub Release based on the official tag for this release. + +### 7. Update dependency constraints and release notes + +If this release is the first release of a new minor line, or if it changes PaddleX dependency requirements, update the related release materials before or after the official tag is published. + +At minimum, this should include: + +- Checking whether the `paddlex` dependency constraints in `pyproject.toml` match the target release +- Checking whether installation docs, upgrade notes, and release notes are aligned with the release version + +Completion criteria: + +- The `paddlex` dependency constraints match the target release +- The version information in the documentation matches the released version + +### 8. Sync the release branch lineage back to main + +After the first official release of a new `release/X.Y` line is completed, sync the lineage of that `release/X.Y` branch back to `main`. + +This is a fixed step in the current workflow and should be done at least once for each new minor release line. + +Purpose: + +- Ensure `main` correctly reflects that the release line has produced an official version +- Keep subsequent development and release cadence aligned + +Requirements: + +- Perform this once after the first official release of each new `release/X.Y` +- For later patch releases on the same `release/X.Y`, it is usually not necessary to repeat it + +### 9. Move on to the next development cycle or patch release + +After the release: + +- `main` continues with ongoing development +- `release/X.Y` continues to maintain that release line + +If more patch releases are needed later on the same line: + +- Continue preparing patches on `release/X.Y` +- `cherry-pick` from `main` as needed +- Repeat the relevant steps in this SOP + +## How to Handle Different Bump Types + +### Patch Release + +Applicable scenarios: + +- Fixing production issues +- Small compatibility fixes +- Documentation, dependency, or stability patches + +How to handle it: + +- Continue preparing changes on the existing `release/X.Y` +- Create the next patch tag, for example `v3.4.2` + +### Minor Release + +Applicable scenarios: + +- Starting a new release line +- Releasing the next stable version, for example `3.5.0` + +How to handle it: + +- Create a new `release/X.Y` from `main` +- Prepare the release following the standard process +- Create the first official tag, for example `v3.5.0` +- Update `paddlex` dependency constraints if needed +- Sync the lineage of that release branch back to `main` after the release + +### Major Release + +Current conclusion: + +- It is not included in this SOP for now +- A separate process needs to be discussed and designed later + +Before a dedicated solution is defined, do not directly reuse the current minor/patch process for a major release. + +## Release Checklist + +Before each release, confirm the following: + +- The target version and corresponding release branch have been confirmed +- The content on the release branch is ready +- The release scope has been finalized +- Key tests and regression checks have passed +- Release notes are ready +- The official tag uses the `vX.Y.Z` format +- The GitHub Release has been created +- If this is the first official release on `release/X.Y`, its lineage has been synced back to `main` + +## Daily Maintenance Recommendations + +- New features should go to `main` first +- Official releases should always be performed through `release/X.Y` +- Patch releases should always be maintained on the corresponding `release/X.Y` +- For each new `release/X.Y`, sync its lineage once after the first official release + +If the current process changes, this document should be updated accordingly. diff --git a/RELEASING_cn.md b/RELEASING_cn.md new file mode 100644 index 0000000..8310f4b --- /dev/null +++ b/RELEASING_cn.md @@ -0,0 +1,194 @@ +# PaddleOCR Release SOP + +[English](./RELEASING.md) + +## 适用范围 + +本文档用于说明 PaddleOCR 的标准发版流程。 + +## 版本类型 + +当前流程支持以下两类发布: + +- `bump patch`:例如 `3.4.0 -> 3.4.1` +- `bump minor`:例如 `3.4.x -> 3.5.0` + +对于 `bump major`: + +- 当前流程暂不支持直接覆盖这类场景 +- 如需发布大版本,需要单独讨论并设计额外流程,例如引入额外分支或新的版本准备方式 + +## 发版原则 + +- 日常开发在 `main` 分支进行 +- 正式发布只在 `release/X.Y` 分支进行 +- 正式标签只使用 `vX.Y.Z` +- 不在 `main` 分支打正式发布标签 + +## 标准发版流程 + +### 1. 确认发布目标 + +先确认本次发布属于哪一条版本线,以及目标版本号。 + +示例: + +- 发布 `3.4.1`,对应分支为 `release/3.4` +- 发布 `3.5.0`,对应分支为 `release/3.5` + +### 2. 创建或切换到 release 分支 + +如果该版本线尚未建立,则从 `main` 创建对应的 `release/X.Y` 分支。 +如果该版本线已存在,则直接切换到该分支继续发版准备。 + +要求: + +- 一个 minor 版本线对应一个固定的 `release/X.Y` 分支 +- 该分支只承载该版本线的发布内容和补丁修复 + +### 3. 从 main 挑选发布内容 + +根据本次发布范围,从 `main` 将需要发布的提交按需 `cherry-pick` 到 `release/X.Y`,直到版本内容 ready。 + +要求: + +- 只挑选本次发布需要的内容 +- 避免把与本次发布无关的新功能带入 release 分支 +- 如果 release 分支上出现专门的发布修复,也应仅限于本次发布范围 + +### 4. 完成发布前检查 + +在 `release/X.Y` 上完成发布前检查。 + +至少应包括: + +- 当前分支正确 +- 工作区干净 +- 版本号符合预期 +- 关键功能验证通过 +- 必要的测试、构建、打包和回归通过 +- 发布说明已经准备好 + +### 5. 打正式标签 + +确认版本 ready 后,在 `release/X.Y` 分支上打正式标签: + +- 标签格式必须为 `vX.Y.Z` + +示例: + +- `v3.4.1` +- `v3.5.0` + +要求: + +- 标签必须打在本次正式发布对应的 release 分支上 +- 不使用开发态标签作为正式发布标签 + +### 6. 发布 GitHub Release + +在 GitHub 上基于本次正式标签创建 Release。 + +### 7. 更新依赖约束与发布说明 + +如果本次发布是新的 minor 版本首发,或本次发布涉及 PaddleX 依赖变化,则在正式标签发布前后同步完成相关更新。 + +至少应包括: + +- 检查 `pyproject.toml` 中 `paddlex` 相关依赖约束是否与本次发布版本匹配 +- 检查安装说明、升级说明和发布说明中的版本信息是否与本次发布一致 + +完成要求: + +- `paddlex` 相关依赖约束与本次发布目标一致 +- 文档中的版本信息与本次发布版本一致 + +### 8. 将 release 分支的 lineage 同步回 main + +当一条新的 `release/X.Y` 发布线完成首个正式版本发布后,需要将该 `release/X.Y` 的 lineage 同步回 `main`。 + +这一步是当前流程中的固定步骤,每条新的 minor 发布线至少执行一次。 + +目的: + +- 让 `main` 正确感知该发布线已经完成的正式版本 +- 保持后续开发与发布节奏一致 + +要求: + +- 每条新的 `release/X.Y` 在首个正式版本发布后执行一次 +- 同一条 `release/X.Y` 后续如果继续发布补丁版本,通常不要求重复执行 + +### 9. 进入下一轮开发或补丁发布 + +发布完成后: + +- `main` 继续进行后续开发 +- `release/X.Y` 继续维护该版本线 + +如果后续还要发布该版本线的补丁版本: + +- 继续在 `release/X.Y` 上准备补丁 +- 从 `main` 按需 `cherry-pick` +- 重复执行本 SOP 中的发布步骤 + +## 不同 bump 类型的处理方式 + +### Patch 发布 + +适用场景: + +- 修复线上问题 +- 小范围兼容性修复 +- 文档、依赖或稳定性补丁 + +处理方式: + +- 在现有 `release/X.Y` 分支上继续准备内容 +- 打下一个 patch 标签,例如 `v3.4.2` + +### Minor 发布 + +适用场景: + +- 开启一条新的发布线 +- 发布下一阶段稳定版本,例如 `3.5.0` + +处理方式: + +- 从 `main` 建立新的 `release/X.Y` +- 按标准流程准备版本内容 +- 打首个正式标签,例如 `v3.5.0` +- 如有需要,同步更新 `paddlex` 相关依赖约束 +- 发布后将该 release 分支的 lineage 同步回 `main` + +### Major 发布 + +当前结论: + +- 暂不纳入本 SOP +- 后续需要单独讨论并设计流程 + +在新的方案明确之前,不建议直接套用当前 minor/patch 的做法处理 major 发布。 + +## 发布检查清单 + +每次发版前,请确认以下事项: + +- 已确认目标版本号和对应 release 分支 +- release 分支中的内容已经 ready +- 发布范围已经收敛 +- 关键测试和回归已通过 +- 发布说明已准备完成 +- 正式标签格式为 `vX.Y.Z` +- GitHub Release 已创建 +- 如本次是该 `release/X.Y` 的首个正式版本,发布完成后已将 lineage 同步回 `main` + +## 日常维护建议 + +- 新功能优先进入 `main` +- 正式发布始终通过 `release/X.Y` 执行 +- 补丁版本始终在对应的 `release/X.Y` 上维护 +- 每条新的 `release/X.Y` 在首个正式版本发布后执行一次 lineage 同步 + +如当前流程发生调整,应同步更新本文档。 diff --git a/TEST_REPORT.md b/TEST_REPORT.md new file mode 100644 index 0000000..35ac392 --- /dev/null +++ b/TEST_REPORT.md @@ -0,0 +1,71 @@ +# PaddleOCR API SDK 集成测试报告 + +**测试分支**: feature/api-sdk (PR #18049) +**测试环境**: macOS Darwin 24.3.0, Python 3.9.6, requests 2.32.5 +**API Endpoint**: https://paddleocr.aistudio-app.com/api/v2/ocr/jobs + +--- + +## 测试结果总览 + +| # | 测试项                | 结果  | 耗时 | 说明                       | +| -----| ---------------------------------------| --------| ------| ---------------------------------------------------| +| 1 | OCR URL (PP-OCRv5)          | ✅ PASS | 3.6s | URL 输入,默认参数                | +| 2 | OCR URL + 自定义 Options       | ✅ PASS | 3.6s | 设置 use_doc_orientation_classify=True      | +| 3 | Doc Parsing URL (PP-StructureV3)   | ✅ PASS | 3.7s | 文档版面解析                   | +| 4 | Submit + Poll 分步调用        | ✅ PASS | 3.7s | 非阻塞 API:submit → get_result → wait_for_result | +| 5 | OCR 本地文件上传           | ✅ PASS | 4.3s | file_path 模式                  | +| 6 | 错误处理 (无效 token)         | ✅ PASS | 0.2s | 正确抛出 AuthError                | +| 7 | 输入校验               | ✅ PASS | 0.0s | 缺少输入 / 互斥参数均正确拦截           | +| 8 | Context Manager (with)        | ✅ PASS | 3.6s | with 语句正常工作                 | +| 9 | PaddleOCR-VL 模型           | ✅ PASS | 3.6s | VL 模型正常返回 markdown             | +| 10 | PaddleOCR-VL-1.5 模型         | ✅ PASS | 3.6s | VL-1.5 模型正常返回 markdown           | +| 11 | Doc Parsing 文件上传 (PP-StructureV3) | ✅ PASS | 4.3s | 本地文件上传 + 文档解析              | + +**总计: 11 passed, 0 failed** + +--- + +## 发现的 Bug(已修复验证) + +### 🔴 阻塞性 Bug: `fetch_jsonl` 请求 BOS 签名 URL 时携带了多余的 Authorization header + +**文件**: `paddleocr/_api_client/_http.py` (同步) + `async_client.py` (异步) + +**现象**: 任务提交和轮询均成功,但在最后一步下载 JSONL 结果文件时,SDK 使用带有 `Authorization: bearer ` 的 session 去请求百度 BOS 对象存储的预签名 URL。BOS 不认识这个 header,返回 400 Bad Request。 + +**根因**: `HTTPClient.fetch_jsonl()` 使用 `self._session.get(url)` 发起请求,而 session 在初始化时设置了 `Authorization` header。BOS 的预签名 URL 已经包含了自己的鉴权参数(`authorization=bce-auth-v1/...`),额外的 Authorization header 导致冲突。 + +**修复**: `fetch_jsonl` 应使用不带 auth header 的独立请求: +```python +# 修复前 +resp = self._session.get(url, timeout=self._timeout) + +# 修复后 +resp = requests.get(url, timeout=self._timeout) +``` + +**影响范围**: 所有实际 API 调用(OCR、Doc Parsing、所有模型)在修复前均无法获取结果。这是一个 **必须在合入前修复的阻塞性 bug**。 + +--- + +## 其他已知问题(非阻塞,可后续迭代) + +| 严重度 | 语言 | 问题 | +|--------|------|------| +| 中 | Python | AsyncAPIClient._poll_until_done 使用硬编码 DEFAULT_MAX_WAIT_TIME,忽略用户设置的 timeout | +| 中 | Go | submitURL/submitFile/getJobStatus 未使用 http.NewRequestWithContext,context 取消无法中断 HTTP 请求 | +| 中 | TypeScript | poller.ts 的 sleep 方法 abort listener 未设置 `{ once: true }`,长轮询会泄漏 listener | +| 中 | TypeScript | http.ts 的 fetchJsonl 未检查 resp.ok(同样的 BOS auth 问题可能也存在于 TS SDK) | +| 低 | 全部 | 轮询循环先 sleep 再 check,对已完成任务多等 3 秒 | +| 低 | Python | CLI argparse 的 store_true 传 False 而非 None,导致多余字段发送给 API | + +--- + +## 结论 + +**修复 `fetch_jsonl` 的 auth header 问题后,Python SDK 的核心功能全部正常工作。** 4 个模型(PP-OCRv5、PP-StructureV3、PaddleOCR-VL、PaddleOCR-VL-1.5)均可正常调用,URL 输入和文件上传两种模式均可用,错误处理和输入校验逻辑正确。 + +**建议**: +1. ⚠️ **合入前必须修复** `fetch_jsonl` 的 BOS auth header bug(Python 同步 + 异步,以及 Go/TypeScript SDK 中的同类问题) +2. 其他问题可以合入后迭代修复 diff --git a/api_sdk/README.md b/api_sdk/README.md new file mode 100644 index 0000000..729cdfe --- /dev/null +++ b/api_sdk/README.md @@ -0,0 +1,50 @@ +# PaddleOCR official API SDKs + +English | [简体中文](README_cn.md) + +This directory contains source-adjacent maintainer files for the PaddleOCR +official API SDKs. The SDKs call hosted PaddleOCR official API services; they do +not run local PaddleOCR inference or load local models. + +The official user documentation: + +- [Overview](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/overview.md) +- [Python SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/python.md) +- [TypeScript SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/typescript.md) +- [Go SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/go.md) +- [CLI](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/cli.md) + +## Maintainer Files + +| File | Purpose | +| --- | --- | +| [`typescript/README.md`](typescript/README.md) | Package-level README for the TypeScript SDK. | +| [`go/README.md`](go/README.md) | Package-level README for the Go SDK. | + +The Python SDK is part of the main `paddleocr` package. + +## Package Locations + +| Language | Source location | User docs | +| --- | --- | --- | +| Python | [`../paddleocr`](../paddleocr) | [Python SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/python.md) | +| TypeScript | [`typescript`](typescript) | [TypeScript SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/typescript.md) | +| Go | [`go`](go) | [Go SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/go.md) | + +## Validation + +Run these from the PaddleOCR repo root directory: + +```bash +# Python +python -m pytest tests/api_client/ + +# TypeScript +cd api_sdk/typescript +npm run lint +npm test + +# Go +cd ../go +go test ./... +``` diff --git a/api_sdk/README_cn.md b/api_sdk/README_cn.md new file mode 100644 index 0000000..b5a9d3e --- /dev/null +++ b/api_sdk/README_cn.md @@ -0,0 +1,48 @@ +# PaddleOCR 官方 API SDK + +[English](README.md) | 简体中文 + +本目录包含 PaddleOCR 官方 API SDK 的源码相邻维护文档。SDK 调用 PaddleOCR 官方 API 托管服务;它们不在本地执行 PaddleOCR 推理,也不加载本地模型。 + +正式用户文档: + +- [总览](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/overview.md) +- [Python SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/python.md) +- [TypeScript SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/typescript.md) +- [Go SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/go.md) +- [CLI](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/cli.md) + +## 维护者文件 + +| 文件 | 作用 | +| --- | --- | +| [`typescript/README_cn.md`](typescript/README_cn.md) | TypeScript SDK 的包级 README。 | +| [`go/README_cn.md`](go/README_cn.md) | Go SDK 的包级 README。 | + +Python SDK 是主 `paddleocr` 包的一部分。 + +## 包位置 + +| 语言 | 源码位置 | 用户文档 | +| --- | --- | --- | +| Python | [`../paddleocr`](../paddleocr) | [Python SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/python.md) | +| TypeScript | [`typescript`](typescript) | [TypeScript SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/typescript.md) | +| Go | [`go`](go) | [Go SDK](../docs/version3.x/inference_deployment/serving/paddleocr_official_api/go.md) | + +## 验证 + +在 PaddleOCR 仓库根目录执行: + +```bash +# Python +python -m pytest tests/api_client/ + +# TypeScript +cd api_sdk/typescript +npm run lint +npm test + +# Go +cd ../go +go test ./... +``` diff --git a/api_sdk/go/README.md b/api_sdk/go/README.md new file mode 100644 index 0000000..f964c1f --- /dev/null +++ b/api_sdk/go/README.md @@ -0,0 +1,72 @@ +# PaddleOCR Go SDK + +English | [简体中文](README_cn.md) + +Go client for the PaddleOCR official API. It submits OCR and document parsing +jobs to hosted PaddleOCR services; it does not run local PaddleOCR inference or +load local models. + +Official user docs: + +- [Go SDK](../../docs/version3.x/inference_deployment/serving/paddleocr_official_api/go.md) +- [Go SDK (English)](../../docs/version3.x/inference_deployment/serving/paddleocr_official_api/go.en.md) + +## Install + +```bash +go get github.com/PaddlePaddle/PaddleOCR/api_sdk/go +``` + +Versioned releases use submodule tags such as `api_sdk/go/v0.1.0`. + +## Minimal Usage + +Set `PADDLEOCR_ACCESS_TOKEN` or pass `WithToken` when constructing the client: + +```bash +export PADDLEOCR_ACCESS_TOKEN="your-access-token" +``` + +```go +client, err := paddleocr.NewClient() +if err != nil { + return err +} + +result, err := client.OCR(ctx, &paddleocr.OCRRequest{ + Model: paddleocr.PPOCRv5, + FileURL: "https://example.com/invoice.pdf", +}) +if err != nil { + return err +} +fmt.Println(result.JobID, len(result.Pages)) +``` + +Set `Model: paddleocr.PPOCRv6` (or `"PP-OCRv6"`) to use the PP-OCRv6 hosted OCR model. +Set `Model: paddleocr.PPOCRv5Latin` (or `"PP-OCRv5-latin"`) to use the PP-OCRv5 Latin-script hosted OCR model. + +Document parsing defaults to PaddleOCR-VL-1.6: + +```go +doc, err := client.ParseDocument(ctx, &paddleocr.DocParsingRequest{ + FilePath: "./report.pdf", + Options: &paddleocr.PaddleOCRVLOptions{ + UseChartRecognition: paddleocr.Bool(true), + }, +}) +if err != nil { + return err +} +fmt.Println(doc.JobID, len(doc.Pages)) +``` + +## Build And Test + +```bash +go test ./... +go vet ./... +go test -race ./... +``` + +`go test -race ./...` is recommended before public release. diff --git a/api_sdk/go/README_cn.md b/api_sdk/go/README_cn.md new file mode 100644 index 0000000..f718a12 --- /dev/null +++ b/api_sdk/go/README_cn.md @@ -0,0 +1,70 @@ +# PaddleOCR Go SDK + +[English](README.md) | 简体中文 + +面向 PaddleOCR 官方 API 的 Go 客户端。它会把 OCR 和文档解析任务提交到 PaddleOCR 官方托管服务;不会运行本地 PaddleOCR 推理,也不会加载本地模型。 + +正式用户文档: + +- [Go SDK](../../docs/version3.x/inference_deployment/serving/paddleocr_official_api/go.md) +- [Go SDK(英文)](../../docs/version3.x/inference_deployment/serving/paddleocr_official_api/go.en.md) + +## 安装 + +```bash +go get github.com/PaddlePaddle/PaddleOCR/api_sdk/go +``` + +版本化发布使用 `api_sdk/go/v0.1.0` 这类子目录 module tag。 + +## 最小示例 + +设置 `PADDLEOCR_ACCESS_TOKEN`,或在构造客户端时传入 `WithToken`: + +```bash +export PADDLEOCR_ACCESS_TOKEN="your-access-token" +``` + +```go +client, err := paddleocr.NewClient() +if err != nil { + return err +} + +result, err := client.OCR(ctx, &paddleocr.OCRRequest{ + Model: paddleocr.PPOCRv5, + FileURL: "https://example.com/invoice.pdf", +}) +if err != nil { + return err +} +fmt.Println(result.JobID, len(result.Pages)) +``` + +将 `Model` 设为 `paddleocr.PPOCRv6`(或 `"PP-OCRv6"`)可使用 PP-OCRv6 云端 OCR 模型。 +将 `Model` 设为 `paddleocr.PPOCRv5Latin`(或 `"PP-OCRv5-latin"`)可使用 PP-OCRv5 拉丁语系云端 OCR 模型。 + +文档解析默认使用 PaddleOCR-VL-1.6: + +```go +doc, err := client.ParseDocument(ctx, &paddleocr.DocParsingRequest{ + FilePath: "./report.pdf", + Options: &paddleocr.PaddleOCRVLOptions{ + UseChartRecognition: paddleocr.Bool(true), + }, +}) +if err != nil { + return err +} +fmt.Println(doc.JobID, len(doc.Pages)) +``` + +## 构建与测试 + +```bash +go test ./... +go vet ./... +go test -race ./... +``` + +公开发布前建议运行 `go test -race ./...`。 diff --git a/api_sdk/go/client.go b/api_sdk/go/client.go new file mode 100644 index 0000000..4df6c0f --- /dev/null +++ b/api_sdk/go/client.go @@ -0,0 +1,66 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paddleocr + +import ( + "net/http" + "os" + "strings" + "time" +) + +type Client struct { + token string + baseURL string + jobsURL string + requestTimeout time.Duration + pollTimeout time.Duration + clientPlatform string + httpClient *http.Client +} + +func NewClient(opts ...ClientOption) (*Client, error) { + c := &Client{ + requestTimeout: 5 * time.Minute, + pollTimeout: 10 * time.Minute, + } + for _, opt := range opts { + opt(c) + } + if c.token == "" { + c.token = os.Getenv("PADDLEOCR_ACCESS_TOKEN") + } + if c.token == "" { + return nil, &AuthError{PaddleOCRAPIError{Message: "Token is required. Set PADDLEOCR_ACCESS_TOKEN or use WithToken()."}} + } + if c.baseURL == "" { + c.baseURL = os.Getenv("PADDLEOCR_BASE_URL") + } + if c.baseURL == "" { + c.baseURL = DefaultBaseURL + } + c.baseURL = strings.TrimRight(c.baseURL, "/") + c.jobsURL = c.baseURL + apiPath + if c.httpClient == nil { + c.httpClient = &http.Client{Timeout: c.requestTimeout} + } + return c, nil +} + +func (c *Client) setClientPlatformHeader(req *http.Request) { + if c.clientPlatform != "" { + req.Header.Set("Client-Platform", c.clientPlatform) + } +} diff --git a/api_sdk/go/client_test.go b/api_sdk/go/client_test.go new file mode 100644 index 0000000..7434e3c --- /dev/null +++ b/api_sdk/go/client_test.go @@ -0,0 +1,235 @@ +package paddleocr + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" +) + +func TestClientPlatformHeader(t *testing.T) { + var got string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + got = r.Header.Get("Client-Platform") + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "code": 0, + "data": map[string]string{"jobId": "job-1"}, + }) + })) + defer server.Close() + + client, err := NewClient( + WithToken("token"), + WithBaseURL(server.URL), + WithClientPlatform("my-app"), + ) + if err != nil { + t.Fatalf("NewClient() error = %v", err) + } + + job, err := client.SubmitOCR(context.Background(), &OCRRequest{ + FileURL: "https://example.test/input.pdf", + }) + if err != nil { + t.Fatalf("SubmitOCR() error = %v", err) + } + if job.JobID != "job-1" { + t.Fatalf("JobID = %q, want job-1", job.JobID) + } + if got != "my-app" { + t.Fatalf("Client-Platform = %q, want my-app", got) + } +} + +func TestDocumentParsingOptionsIncludeCurrentAndFutureServiceParameters(t *testing.T) { + trueValue := true + falseValue := false + var got map[string]interface{} + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var body struct { + OptionalPayload map[string]interface{} `json:"optionalPayload"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Fatalf("Decode request body error = %v", err) + } + got = body.OptionalPayload + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "code": 0, + "data": map[string]string{"jobId": "job-doc"}, + }) + })) + defer server.Close() + + client, err := NewClient( + WithToken("token"), + WithBaseURL(server.URL), + ) + if err != nil { + t.Fatalf("NewClient() error = %v", err) + } + + _, err = client.SubmitDocumentParsing(context.Background(), &DocParsingRequest{ + Model: PaddleOCRVL16, + FileURL: "https://example.test/doc.pdf", + Options: &PaddleOCRVLOptions{ + UseOcrForImageBlock: &trueValue, + FormatBlockContent: &trueValue, + MarkdownIgnoreLabels: []string{"image"}, + VlmExtraArgs: map[string]interface{}{"temperature": 0.1}, + ReturnMarkdownImages: &falseValue, + OutputFormats: []string{"docx"}, + ExtraOptions: map[string]interface{}{"futureOption": "enabled"}, + }, + }) + if err != nil { + t.Fatalf("SubmitDocumentParsing() error = %v", err) + } + + if got["useOcrForImageBlock"] != true || got["formatBlockContent"] != true { + t.Fatalf("got boolean options %#v", got) + } + if got["returnMarkdownImages"] != false || got["futureOption"] != "enabled" { + t.Fatalf("got passthrough options %#v", got) + } +} + +func TestResultParsersPreserveRawFieldsAndDataInfo(t *testing.T) { + ocrLine := map[string]interface{}{ + "result": map[string]interface{}{ + "dataInfo": map[string]interface{}{"numPages": float64(1)}, + "ocrResults": []interface{}{ + map[string]interface{}{ + "prunedResult": map[string]interface{}{"text": "hello"}, + "ocrImage": "ocr.png", + "docPreprocessingImage": "pre.png", + "inputImage": "input.png", + }, + }, + }, + } + ocrResult, err := parseOCRResult("job-ocr", []map[string]interface{}{ocrLine}) + if err != nil { + t.Fatalf("parseOCRResult() error = %v", err) + } + if ocrResult.DataInfo["numPages"] != float64(1) { + t.Fatalf("OCR metadata not preserved: %#v", ocrResult) + } + if ocrResult.Pages[0].DocPreprocessingImageURL != "pre.png" || ocrResult.Pages[0].InputImageURL != "input.png" { + t.Fatalf("OCR page image URLs not preserved: %#v", ocrResult.Pages[0]) + } + if ocrResult.Pages[0].Raw["ocrImage"] != "ocr.png" { + t.Fatalf("OCR raw page not preserved: %#v", ocrResult.Pages[0].Raw) + } + + docPage := map[string]interface{}{ + "prunedResult": map[string]interface{}{"blocks": []interface{}{map[string]interface{}{"label": "text"}}}, + "markdown": map[string]interface{}{"text": "hello", "images": map[string]interface{}{"figure.png": "figure-url"}, "isStart": true}, + "outputImages": map[string]interface{}{"page.png": "page-url"}, + "inputImage": "input.png", + "exports": map[string]interface{}{"docx": "docx-url"}, + } + docLine := map[string]interface{}{ + "result": map[string]interface{}{ + "dataInfo": map[string]interface{}{"numPages": float64(1)}, + "layoutParsingResults": []interface{}{docPage}, + }, + } + docResult, err := parseDocParsingResult("job-doc", []map[string]interface{}{docLine}) + if err != nil { + t.Fatalf("parseDocParsingResult() error = %v", err) + } + if docResult.DataInfo["numPages"] != float64(1) { + t.Fatalf("document metadata not preserved: %#v", docResult) + } + if docResult.Pages[0].PrunedResult == nil || docResult.Pages[0].Raw["inputImage"] != "input.png" { + t.Fatalf("document page raw fields not preserved: %#v", docResult.Pages[0]) + } + if docResult.Pages[0].Exports["docx"] != "docx-url" || docResult.Pages[0].Markdown["isStart"] != true { + t.Fatalf("document structured fields not preserved: %#v", docResult.Pages[0]) + } +} + +func TestSubmitOCRAcceptsOfficialModelNameString(t *testing.T) { + var got string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var body struct { + Model string `json:"model"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Fatalf("Decode request body error = %v", err) + } + got = body.Model + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "code": 0, + "data": map[string]string{"jobId": "job-1"}, + }) + })) + defer server.Close() + + client, err := NewClient( + WithToken("token"), + WithBaseURL(server.URL), + ) + if err != nil { + t.Fatalf("NewClient() error = %v", err) + } + + job, err := client.SubmitOCR(context.Background(), &OCRRequest{ + Model: "PP-OCRv5", + FileURL: "https://example.test/input.pdf", + }) + if err != nil { + t.Fatalf("SubmitOCR() error = %v", err) + } + if job.Model != "PP-OCRv5" { + t.Fatalf("Job model = %q, want PP-OCRv5", job.Model) + } + if got != "PP-OCRv5" { + t.Fatalf("Request model = %q, want PP-OCRv5", got) + } +} + +func TestSubmitOCRAcceptsPPOCRv5LatinModelNameString(t *testing.T) { + var got string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var body struct { + Model string `json:"model"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Fatalf("Decode request body error = %v", err) + } + got = body.Model + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]interface{}{ + "code": 0, + "data": map[string]string{"jobId": "job-latin"}, + }) + })) + defer server.Close() + + client, err := NewClient( + WithToken("token"), + WithBaseURL(server.URL), + ) + if err != nil { + t.Fatalf("NewClient() error = %v", err) + } + + job, err := client.SubmitOCR(context.Background(), &OCRRequest{ + Model: PPOCRv5Latin, + FileURL: "https://example.test/latin.pdf", + }) + if err != nil { + t.Fatalf("SubmitOCR() error = %v", err) + } + if job.Model != PPOCRv5Latin { + t.Fatalf("Job model = %q, want %s", job.Model, PPOCRv5Latin) + } + if got != PPOCRv5Latin { + t.Fatalf("Request model = %q, want %s", got, PPOCRv5Latin) + } +} diff --git a/api_sdk/go/doc.go b/api_sdk/go/doc.go new file mode 100644 index 0000000..67dc185 --- /dev/null +++ b/api_sdk/go/doc.go @@ -0,0 +1,30 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package paddleocr provides a Go client for the PaddleOCR official API. +// +// Create a client with NewClient and authenticate with WithToken or the +// PADDLEOCR_ACCESS_TOKEN environment variable. Use OCR for supported OCR models +// and ParseDocument for document parsing models. +// SubmitOCR and SubmitDocumentParsing return an Operation for non-blocking +// status checks with Poll or typed waits with WaitOCR and WaitDocumentParsing. +// SaveResource downloads one result resource URL. SaveOCRResultResources and +// SaveDocumentParsingResultResources save resources from typed result objects +// into an existing directory. +// +// Request timeout and polling timeout are configured separately with +// WithRequestTimeout and WithPollTimeout. Errors are exposed as typed values, +// such as AuthError, InvalidRequestError, APIError, ResponseFormatError, and +// ResultParseError, and are suitable for errors.As. +package paddleocr diff --git a/api_sdk/go/errors.go b/api_sdk/go/errors.go new file mode 100644 index 0000000..dd4e484 --- /dev/null +++ b/api_sdk/go/errors.go @@ -0,0 +1,103 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paddleocr + +import "fmt" + +type PaddleOCRAPIError struct { + Message string + Cause error +} + +func (e *PaddleOCRAPIError) Error() string { + if e.Message == "" && e.Cause != nil { + return e.Cause.Error() + } + return e.Message +} + +func (e *PaddleOCRAPIError) Unwrap() error { + return e.Cause +} + +type AuthError struct { + PaddleOCRAPIError +} + +type InvalidRequestError struct { + PaddleOCRAPIError +} + +type APIError struct { + StatusCode int + PaddleOCRAPIError +} + +func (e *APIError) Error() string { + return fmt.Sprintf("HTTP %d: %s", e.StatusCode, e.Message) +} + +type RateLimitError struct { + APIError +} + +type ServiceUnavailableError struct { + APIError +} + +type JobFailedError struct { + JobID string + ErrorMsg string + PaddleOCRAPIError +} + +func (e *JobFailedError) Error() string { + return fmt.Sprintf("Job %s failed: %s", e.JobID, e.ErrorMsg) +} + +type RequestTimeoutError struct { + PaddleOCRAPIError +} + +type PollTimeoutError struct { + JobID string + Elapsed float64 + PaddleOCRAPIError +} + +func (e *PollTimeoutError) Error() string { + return fmt.Sprintf("Timed out after %.1fs waiting for job %s", e.Elapsed, e.JobID) +} + +type NetworkError struct { + PaddleOCRAPIError +} + +type FileNotFoundError struct { + Path string + PaddleOCRAPIError +} + +func (e *FileNotFoundError) Error() string { + return fmt.Sprintf("File not found: %s", e.Path) +} + +type ResponseFormatError struct { + PaddleOCRAPIError +} + +type ResultParseError struct { + PaddleOCRAPIError +} diff --git a/api_sdk/go/examples/doc_parsing_file/main.go b/api_sdk/go/examples/doc_parsing_file/main.go new file mode 100644 index 0000000..09081a6 --- /dev/null +++ b/api_sdk/go/examples/doc_parsing_file/main.go @@ -0,0 +1,61 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "fmt" + "log" + + paddleocr "github.com/PaddlePaddle/PaddleOCR/api_sdk/go" +) + +func main() { + client, err := paddleocr.NewClient() + if err != nil { + log.Fatal(err) + } + ctx := context.Background() + + // Convenience method (blocks until done) + result, err := client.ParseDocument(ctx, &paddleocr.DocParsingRequest{ + Model: paddleocr.PPStructureV3, + FilePath: "./sample.pdf", + Options: &paddleocr.PPStructureV3Options{UseChartRecognition: paddleocr.Bool(true)}, + }) + if err != nil { + log.Fatal(err) + } + for i, page := range result.Pages { + fmt.Printf("Page %d:\n%s\n", i+1, page.MarkdownText) + } + + // Manual control with typed job metadata and typed wait methods. + ocrJob, _ := client.SubmitOCR(ctx, &paddleocr.OCRRequest{FileURL: "https://example.com/f1.pdf"}) + docJob, _ := client.SubmitDocumentParsing(ctx, &paddleocr.DocParsingRequest{ + Model: paddleocr.PPStructureV3, FilePath: "./sample.pdf", + }) + + ocrResult, err := client.WaitOCRResult(ctx, ocrJob.JobID) + if err != nil { + log.Printf("OCR job error: %v", err) + } + docResult, err := client.WaitDocumentParsingResult(ctx, docJob.JobID) + if err != nil { + log.Printf("document parsing job error: %v", err) + } + fmt.Printf("OCR done: %v\n", ocrResult) + fmt.Printf("Document parsing done: %v\n", docResult) +} diff --git a/api_sdk/go/examples/ocr_url/main.go b/api_sdk/go/examples/ocr_url/main.go new file mode 100644 index 0000000..fe21c35 --- /dev/null +++ b/api_sdk/go/examples/ocr_url/main.go @@ -0,0 +1,43 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "fmt" + "log" + + paddleocr "github.com/PaddlePaddle/PaddleOCR/api_sdk/go" +) + +func main() { + client, err := paddleocr.NewClient() + if err != nil { + log.Fatal(err) + } + ctx := context.Background() + + result, err := client.OCR(ctx, &paddleocr.OCRRequest{ + FileURL: "https://example.com/invoice.pdf", + }) + if err != nil { + log.Fatal(err) + } + + for i, page := range result.Pages { + fmt.Printf("Page %d: %v\n", i+1, page.PrunedResult) + fmt.Printf(" Image URL: %s\n", page.OCRImageURL) + } +} diff --git a/api_sdk/go/go.mod b/api_sdk/go/go.mod new file mode 100644 index 0000000..355dd87 --- /dev/null +++ b/api_sdk/go/go.mod @@ -0,0 +1,3 @@ +module github.com/PaddlePaddle/PaddleOCR/api_sdk/go + +go 1.21 diff --git a/api_sdk/go/models.go b/api_sdk/go/models.go new file mode 100644 index 0000000..168ae67 --- /dev/null +++ b/api_sdk/go/models.go @@ -0,0 +1,159 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paddleocr + +const ( + PPOCRv5 = "PP-OCRv5" + PPOCRv5Latin = "PP-OCRv5-latin" + PPOCRv6 = "PP-OCRv6" + PPStructureV3 = "PP-StructureV3" + PaddleOCRVL = "PaddleOCR-VL" + PaddleOCRVL15 = "PaddleOCR-VL-1.5" + PaddleOCRVL16 = "PaddleOCR-VL-1.6" +) + +// IsOCRModel reports whether model is supported by OCR APIs. +func IsOCRModel(model string) bool { + return model == PPOCRv5 || model == PPOCRv5Latin || model == PPOCRv6 +} + +// IsDocumentParsingModel reports whether model is supported by document parsing APIs. +func IsDocumentParsingModel(model string) bool { + switch model { + case PPStructureV3, PaddleOCRVL, PaddleOCRVL15, PaddleOCRVL16: + return true + default: + return false + } +} + +// IsVLModel reports whether model is a PaddleOCR-VL family model. +func IsVLModel(model string) bool { + switch model { + case PaddleOCRVL, PaddleOCRVL15, PaddleOCRVL16: + return true + default: + return false + } +} + +type OCROptions struct { + UseDocOrientationClassify *bool `json:"useDocOrientationClassify,omitempty"` + UseDocUnwarping *bool `json:"useDocUnwarping,omitempty"` + UseTextlineOrientation *bool `json:"useTextlineOrientation,omitempty"` + TextDetLimitSideLen *int `json:"textDetLimitSideLen,omitempty"` + TextDetLimitType *string `json:"textDetLimitType,omitempty"` + TextDetThresh *float64 `json:"textDetThresh,omitempty"` + TextDetBoxThresh *float64 `json:"textDetBoxThresh,omitempty"` + TextDetUnclipRatio *float64 `json:"textDetUnclipRatio,omitempty"` + TextRecScoreThresh *float64 `json:"textRecScoreThresh,omitempty"` + Visualize *bool `json:"visualize,omitempty"` + ExtraOptions map[string]interface{} `json:"-"` +} + +type PPStructureV3Options struct { + UseDocOrientationClassify *bool `json:"useDocOrientationClassify,omitempty"` + UseDocUnwarping *bool `json:"useDocUnwarping,omitempty"` + UseTextlineOrientation *bool `json:"useTextlineOrientation,omitempty"` + UseSealRecognition *bool `json:"useSealRecognition,omitempty"` + UseTableRecognition *bool `json:"useTableRecognition,omitempty"` + UseFormulaRecognition *bool `json:"useFormulaRecognition,omitempty"` + UseChartRecognition *bool `json:"useChartRecognition,omitempty"` + UseRegionDetection *bool `json:"useRegionDetection,omitempty"` + LayoutThreshold interface{} `json:"layoutThreshold,omitempty"` + LayoutNms *bool `json:"layoutNms,omitempty"` + LayoutUnclipRatio interface{} `json:"layoutUnclipRatio,omitempty"` + LayoutMergeBboxesMode interface{} `json:"layoutMergeBboxesMode,omitempty"` + FormatBlockContent *bool `json:"formatBlockContent,omitempty"` + TextDetLimitSideLen *int `json:"textDetLimitSideLen,omitempty"` + TextDetLimitType *string `json:"textDetLimitType,omitempty"` + TextDetThresh *float64 `json:"textDetThresh,omitempty"` + TextDetBoxThresh *float64 `json:"textDetBoxThresh,omitempty"` + TextDetUnclipRatio *float64 `json:"textDetUnclipRatio,omitempty"` + TextRecScoreThresh *float64 `json:"textRecScoreThresh,omitempty"` + UseWiredTableCellsTransToHtml *bool `json:"useWiredTableCellsTransToHtml,omitempty"` + UseWirelessTableCellsTransToHtml *bool `json:"useWirelessTableCellsTransToHtml,omitempty"` + UseTableOrientationClassify *bool `json:"useTableOrientationClassify,omitempty"` + UseOcrResultsWithTableCells *bool `json:"useOcrResultsWithTableCells,omitempty"` + UseE2eWiredTableRecModel *bool `json:"useE2eWiredTableRecModel,omitempty"` + UseE2eWirelessTableRecModel *bool `json:"useE2eWirelessTableRecModel,omitempty"` + MarkdownIgnoreLabels []string `json:"markdownIgnoreLabels,omitempty"` + PrettifyMarkdown *bool `json:"prettifyMarkdown,omitempty"` + ShowFormulaNumber *bool `json:"showFormulaNumber,omitempty"` + ReturnMarkdownImages *bool `json:"returnMarkdownImages,omitempty"` + OutputFormats []string `json:"outputFormats,omitempty"` + Visualize *bool `json:"visualize,omitempty"` + ExtraOptions map[string]interface{} `json:"-"` +} + +type PaddleOCRVLOptions struct { + UseDocOrientationClassify *bool `json:"useDocOrientationClassify,omitempty"` + UseDocUnwarping *bool `json:"useDocUnwarping,omitempty"` + UseLayoutDetection *bool `json:"useLayoutDetection,omitempty"` + UseChartRecognition *bool `json:"useChartRecognition,omitempty"` + UseSealRecognition *bool `json:"useSealRecognition,omitempty"` + UseOcrForImageBlock *bool `json:"useOcrForImageBlock,omitempty"` + LayoutThreshold interface{} `json:"layoutThreshold,omitempty"` + LayoutNms *bool `json:"layoutNms,omitempty"` + LayoutUnclipRatio interface{} `json:"layoutUnclipRatio,omitempty"` + LayoutMergeBboxesMode interface{} `json:"layoutMergeBboxesMode,omitempty"` + LayoutShapeMode *string `json:"layoutShapeMode,omitempty"` + PromptLabel *string `json:"promptLabel,omitempty"` + FormatBlockContent *bool `json:"formatBlockContent,omitempty"` + RepetitionPenalty *float64 `json:"repetitionPenalty,omitempty"` + Temperature *float64 `json:"temperature,omitempty"` + TopP *float64 `json:"topP,omitempty"` + MinPixels *int `json:"minPixels,omitempty"` + MaxPixels *int `json:"maxPixels,omitempty"` + MaxNewTokens *int `json:"maxNewTokens,omitempty"` + VlmExtraArgs map[string]interface{} `json:"vlmExtraArgs,omitempty"` + MergeLayoutBlocks *bool `json:"mergeLayoutBlocks,omitempty"` + MarkdownIgnoreLabels []string `json:"markdownIgnoreLabels,omitempty"` + PrettifyMarkdown *bool `json:"prettifyMarkdown,omitempty"` + ShowFormulaNumber *bool `json:"showFormulaNumber,omitempty"` + RestructurePages *bool `json:"restructurePages,omitempty"` + MergeTables *bool `json:"mergeTables,omitempty"` + RelevelTitles *bool `json:"relevelTitles,omitempty"` + ReturnMarkdownImages *bool `json:"returnMarkdownImages,omitempty"` + OutputFormats []string `json:"outputFormats,omitempty"` + Visualize *bool `json:"visualize,omitempty"` + ExtraOptions map[string]interface{} `json:"-"` +} + +// DocParsingOptionsProvider marks document parsing option structs. +type DocParsingOptionsProvider interface { + isDocParsingOptions() +} + +func (*PPStructureV3Options) isDocParsingOptions() {} +func (*PaddleOCRVLOptions) isDocParsingOptions() {} + +type OCRRequest struct { + Model string + FileURL string + FilePath string + PageRanges string + BatchID string + Options *OCROptions +} + +type DocParsingRequest struct { + Model string + FileURL string + FilePath string + PageRanges string + BatchID string + Options DocParsingOptionsProvider +} diff --git a/api_sdk/go/ocr.go b/api_sdk/go/ocr.go new file mode 100644 index 0000000..b7fd845 --- /dev/null +++ b/api_sdk/go/ocr.go @@ -0,0 +1,317 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paddleocr + +import ( + "context" + "encoding/json" +) + +func (c *Client) OCR(ctx context.Context, req *OCRRequest) (*OCRResult, error) { + job, err := c.SubmitOCR(ctx, req) + if err != nil { + return nil, err + } + return c.WaitOCRResult(ctx, job.JobID) +} + +// ParseDocument performs document parsing. Blocks until result is ready. +func (c *Client) ParseDocument(ctx context.Context, req *DocParsingRequest) (*DocParsingResult, error) { + job, err := c.SubmitDocumentParsing(ctx, req) + if err != nil { + return nil, err + } + return c.WaitDocumentParsingResult(ctx, job.JobID) +} + +// SubmitOCR submits an OCR job and returns job metadata for tracking. +func (c *Client) SubmitOCR(ctx context.Context, req *OCRRequest) (*Job, error) { + if req == nil { + return nil, &InvalidRequestError{PaddleOCRAPIError{Message: "OCR request is nil"}} + } + model := req.Model + if model == "" { + model = PPOCRv6 + } + if !IsOCRModel(model) { + return nil, &InvalidRequestError{PaddleOCRAPIError{Message: "model is not an OCR model: " + model}} + } + jobID, err := c.submit(ctx, model, req.FileURL, req.FilePath, req.Options, req.PageRanges, req.BatchID) + if err != nil { + return nil, err + } + return &Job{JobID: jobID, Model: model, Task: "ocr", PageRanges: req.PageRanges, BatchID: req.BatchID}, nil +} + +// SubmitDocumentParsing submits a document parsing job and returns job metadata for tracking. +func (c *Client) SubmitDocumentParsing(ctx context.Context, req *DocParsingRequest) (*Job, error) { + if req == nil { + return nil, &InvalidRequestError{PaddleOCRAPIError{Message: "document parsing request is nil"}} + } + model := req.Model + if model == "" { + model = PaddleOCRVL16 + } + if !IsDocumentParsingModel(model) { + return nil, &InvalidRequestError{PaddleOCRAPIError{Message: "model is not a document parsing model: " + model}} + } + jobID, err := c.submit(ctx, model, req.FileURL, req.FilePath, req.Options, req.PageRanges, req.BatchID) + if err != nil { + return nil, err + } + return &Job{JobID: jobID, Model: model, Task: "document_parsing", PageRanges: req.PageRanges, BatchID: req.BatchID}, nil +} + +func (c *Client) WaitOCRResult(ctx context.Context, jobID string) (*OCRResult, error) { + jsonlData, err := c.pollUntilDone(ctx, jobID) + if err != nil { + return nil, err + } + return parseOCRResult(jobID, jsonlData) +} + +func (c *Client) WaitDocumentParsingResult(ctx context.Context, jobID string) (*DocParsingResult, error) { + jsonlData, err := c.pollUntilDone(ctx, jobID) + if err != nil { + return nil, err + } + return parseDocParsingResult(jobID, jsonlData) +} + +func (c *Client) GetStatus(ctx context.Context, jobID string) (*JobStatus, error) { + status, err := c.getJobStatus(ctx, jobID) + if err != nil { + return nil, err + } + return normalizeStatus(jobID, status) +} + +func (c *Client) GetBatchStatus(ctx context.Context, batchID string) (*BatchStatus, error) { + if batchID == "" { + return nil, &InvalidRequestError{PaddleOCRAPIError{Message: "batchID is required"}} + } + return c.getBatchStatus(ctx, batchID) +} + +func (c *Client) submit(ctx context.Context, model, fileURL, filePath string, options interface{}, pageRanges, batchID string) (string, error) { + if fileURL == "" && filePath == "" { + return "", &InvalidRequestError{PaddleOCRAPIError{Message: "Either FileURL or FilePath is required."}} + } + if fileURL != "" && filePath != "" { + return "", &InvalidRequestError{PaddleOCRAPIError{Message: "FileURL and FilePath are mutually exclusive."}} + } + + payload := defaultPayload(model, options) + + if fileURL != "" { + return c.submitURL(ctx, model, fileURL, payload, pageRanges, batchID) + } + return c.submitFile(ctx, model, filePath, payload, pageRanges, batchID) +} + +func defaultPayload(model string, options interface{}) interface{} { + switch typed := options.(type) { + case *OCROptions: + if typed != nil { + return payloadWithExtraOptions(typed) + } + case *PPStructureV3Options: + if typed != nil { + return payloadWithExtraOptions(typed) + } + case *PaddleOCRVLOptions: + if typed != nil { + return payloadWithExtraOptions(typed) + } + default: + if options != nil { + return payloadWithExtraOptions(options) + } + } + if IsOCRModel(model) { + return payloadWithExtraOptions(&OCROptions{}) + } + if IsVLModel(model) { + return payloadWithExtraOptions(&PaddleOCRVLOptions{}) + } + return payloadWithExtraOptions(&PPStructureV3Options{}) +} + +func payloadWithExtraOptions(options interface{}) interface{} { + payloadBytes, _ := json.Marshal(options) + payload := map[string]interface{}{} + _ = json.Unmarshal(payloadBytes, &payload) + + var extraOptions map[string]interface{} + switch typed := options.(type) { + case *OCROptions: + extraOptions = typed.ExtraOptions + case *PPStructureV3Options: + extraOptions = typed.ExtraOptions + case *PaddleOCRVLOptions: + extraOptions = typed.ExtraOptions + } + for key, value := range extraOptions { + payload[key] = value + } + return payload +} + +func parseOCRResult(jobID string, jsonlData []map[string]interface{}) (*OCRResult, error) { + result := &OCRResult{JobID: jobID, DataInfo: map[string]interface{}{}} + for _, lineObj := range jsonlData { + resultData, ok := lineObj["result"].(map[string]interface{}) + if !ok { + return nil, &ResultParseError{PaddleOCRAPIError{Message: "OCR result item is missing result"}} + } + if dataInfo, ok := resultData["dataInfo"].(map[string]interface{}); ok { + for key, value := range dataInfo { + result.DataInfo[key] = value + } + } + ocrResults, ok := resultData["ocrResults"].([]interface{}) + if !ok { + return nil, &ResultParseError{PaddleOCRAPIError{Message: "OCR result item is missing ocrResults"}} + } + for _, item := range ocrResults { + itemMap, ok := item.(map[string]interface{}) + if !ok { + return nil, &ResultParseError{PaddleOCRAPIError{Message: "OCR result page is malformed"}} + } + if _, ok := itemMap["prunedResult"]; !ok { + return nil, &ResultParseError{PaddleOCRAPIError{Message: "OCR result page is missing prunedResult"}} + } + page := OCRPage{ + PrunedResult: itemMap["prunedResult"], + OCRImageURL: getString(itemMap, "ocrImage"), + DocPreprocessingImageURL: getString(itemMap, "docPreprocessingImage"), + InputImageURL: getString(itemMap, "inputImage"), + Raw: itemMap, + } + result.Pages = append(result.Pages, page) + } + } + return result, nil +} + +func parseDocParsingResult(jobID string, jsonlData []map[string]interface{}) (*DocParsingResult, error) { + result := &DocParsingResult{JobID: jobID, DataInfo: map[string]interface{}{}} + for _, lineObj := range jsonlData { + resultData, ok := lineObj["result"].(map[string]interface{}) + if !ok { + return nil, &ResultParseError{PaddleOCRAPIError{Message: "document parsing result item is missing result"}} + } + if dataInfo, ok := resultData["dataInfo"].(map[string]interface{}); ok { + for key, value := range dataInfo { + result.DataInfo[key] = value + } + } + lpResults, ok := resultData["layoutParsingResults"].([]interface{}) + if !ok { + return nil, &ResultParseError{PaddleOCRAPIError{Message: "document parsing result item is missing layoutParsingResults"}} + } + for _, item := range lpResults { + itemMap, ok := item.(map[string]interface{}) + if !ok { + return nil, &ResultParseError{PaddleOCRAPIError{Message: "document parsing result page is malformed"}} + } + markdown, ok := itemMap["markdown"].(map[string]interface{}) + if !ok || getString(markdown, "text") == "" { + return nil, &ResultParseError{PaddleOCRAPIError{Message: "document parsing result page is missing markdown.text"}} + } + page := DocParsingPage{ + MarkdownText: getString(markdown, "text"), + MarkdownImages: getStringMap(markdown, "images"), + OutputImages: getStringMap(itemMap, "outputImages"), + PrunedResult: itemMap["prunedResult"], + InputImageURL: getString(itemMap, "inputImage"), + Exports: getMap(itemMap, "exports"), + Markdown: markdown, + Raw: itemMap, + } + result.Pages = append(result.Pages, page) + } + } + return result, nil +} + +func getString(m map[string]interface{}, key string) string { + if m == nil { + return "" + } + v, _ := m[key].(string) + return v +} + +func getMap(m map[string]interface{}, key string) map[string]interface{} { + value, ok := m[key].(map[string]interface{}) + if !ok { + return map[string]interface{}{} + } + return value +} + +func getStringMap(m map[string]interface{}, key string) map[string]string { + if m == nil { + return nil + } + raw, ok := m[key] + if !ok { + return nil + } + switch v := raw.(type) { + case map[string]interface{}: + result := make(map[string]string, len(v)) + for k, val := range v { + if s, ok := val.(string); ok { + result[k] = s + } + } + return result + default: + b, _ := json.Marshal(raw) + var result map[string]string + json.Unmarshal(b, &result) + return result + } +} + +func normalizeStatus(jobID string, status *jobStatusResponse) (*JobStatus, error) { + if status == nil || status.State == "" { + return nil, &ResponseFormatError{PaddleOCRAPIError{Message: "status response is missing state"}} + } + if status.State != "pending" && status.State != "running" && status.State != "done" && status.State != "failed" { + return nil, &ResponseFormatError{PaddleOCRAPIError{Message: "unknown job state: " + status.State}} + } + result := &JobStatus{ + JobID: jobID, + State: status.State, + ResultURL: status.ResultURL, + ErrorMsg: status.ErrorMsg, + } + if len(status.ExtractProgress) != 0 && string(status.ExtractProgress) != "null" { + var progress extractProgress + if err := json.Unmarshal(status.ExtractProgress, &progress); err != nil { + return nil, &ResponseFormatError{PaddleOCRAPIError{Message: "status progress is malformed", Cause: err}} + } + result.Progress = &Progress{ + TotalPages: progress.TotalPages, + ExtractedPages: progress.ExtractedPages, + StartTime: progress.StartTime, + EndTime: progress.EndTime, + } + } + return result, nil +} diff --git a/api_sdk/go/operation.go b/api_sdk/go/operation.go new file mode 100644 index 0000000..86a558f --- /dev/null +++ b/api_sdk/go/operation.go @@ -0,0 +1,69 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paddleocr + +import ( + "context" + "encoding/json" +) + +// Operation represents an in-progress job. Use Wait() to block until done, +// or Poll() to check status without blocking. +type Operation struct { + client *Client + JobID string + model string +} + +// Wait blocks until the job completes and returns the parsed result. +func (op *Operation) Wait(ctx context.Context) (interface{}, error) { + jsonlData, err := op.client.pollUntilDone(ctx, op.JobID) + if err != nil { + return nil, err + } + if IsOCRModel(op.model) { + return parseOCRResult(op.JobID, jsonlData) + } + return parseDocParsingResult(op.JobID, jsonlData) +} + +// Poll checks the current job status without waiting. +// Returns the status, whether the job is done, and any error. +func (op *Operation) Poll(ctx context.Context) (*JobStatus, bool, error) { + status, err := op.client.getJobStatus(ctx, op.JobID) + if err != nil { + return nil, false, err + } + + js := &JobStatus{ + JobID: op.JobID, + State: status.State, + ErrorMsg: status.ErrorMsg, + } + + if status.ExtractProgress != nil { + var ep extractProgress + if err := json.Unmarshal(status.ExtractProgress, &ep); err == nil { + js.Progress = &Progress{ + TotalPages: ep.TotalPages, + ExtractedPages: ep.ExtractedPages, + StartTime: ep.StartTime, + EndTime: ep.EndTime, + } + } + } + + return js, status.State == "done", nil +} diff --git a/api_sdk/go/options.go b/api_sdk/go/options.go new file mode 100644 index 0000000..6b958ea --- /dev/null +++ b/api_sdk/go/options.go @@ -0,0 +1,73 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paddleocr + +import ( + "net/http" + "time" +) + +const DefaultBaseURL = "https://paddleocr.aistudio-app.com" + +const apiPath = "/api/v2/ocr/jobs" + +type ClientOption func(*Client) + +func WithToken(token string) ClientOption { + return func(c *Client) { + c.token = token + } +} + +func WithBaseURL(url string) ClientOption { + return func(c *Client) { + c.baseURL = url + } +} + +func WithTimeout(d time.Duration) ClientOption { + return func(c *Client) { + c.requestTimeout = d + c.pollTimeout = d + } +} + +func WithRequestTimeout(d time.Duration) ClientOption { + return func(c *Client) { + c.requestTimeout = d + } +} + +func WithPollTimeout(d time.Duration) ClientOption { + return func(c *Client) { + c.pollTimeout = d + } +} + +func WithClientPlatform(clientPlatform string) ClientOption { + return func(c *Client) { + c.clientPlatform = clientPlatform + } +} + +func WithHTTPClient(hc *http.Client) ClientOption { + return func(c *Client) { + c.httpClient = hc + } +} + +func Bool(v bool) *bool { + return &v +} diff --git a/api_sdk/go/poller.go b/api_sdk/go/poller.go new file mode 100644 index 0000000..4a4ce85 --- /dev/null +++ b/api_sdk/go/poller.go @@ -0,0 +1,90 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paddleocr + +import ( + "context" + "fmt" + "time" +) + +const ( + initialInterval = 3 * time.Second + multiplier = 1.5 + maxInterval = 15 * time.Second +) + +func (c *Client) pollUntilDone(ctx context.Context, jobID string) ([]map[string]interface{}, error) { + interval := initialInterval + deadline := time.Now().Add(c.pollTimeout) + pollCtx, cancel := context.WithDeadline(ctx, deadline) + defer cancel() + + for time.Now().Before(deadline) { + remaining := time.Until(deadline) + if remaining <= 0 { + break + } + if interval > remaining { + interval = remaining + } + timer := time.NewTimer(interval) + select { + case <-pollCtx.Done(): + timer.Stop() + if ctx.Err() != nil { + return nil, ctx.Err() + } + return nil, &PollTimeoutError{ + JobID: jobID, + Elapsed: c.pollTimeout.Seconds(), + PaddleOCRAPIError: PaddleOCRAPIError{Message: fmt.Sprintf("Timed out after %.1fs", c.pollTimeout.Seconds())}, + } + case <-timer.C: + } + + status, err := c.getJobStatus(pollCtx, jobID) + if err != nil { + return nil, err + } + + switch status.State { + case "done": + jsonURL := status.ResultURL["jsonUrl"] + if jsonURL == "" { + return nil, &ResponseFormatError{PaddleOCRAPIError{Message: "done job response is missing resultUrl.jsonUrl"}} + } + return c.fetchJSONL(pollCtx, jsonURL) + case "failed": + return nil, &JobFailedError{ + JobID: jobID, + ErrorMsg: status.ErrorMsg, + PaddleOCRAPIError: PaddleOCRAPIError{Message: status.ErrorMsg}, + } + } + + next := time.Duration(float64(interval) * multiplier) + if next > maxInterval { + next = maxInterval + } + interval = next + } + + return nil, &PollTimeoutError{ + JobID: jobID, + Elapsed: c.pollTimeout.Seconds(), + PaddleOCRAPIError: PaddleOCRAPIError{Message: fmt.Sprintf("Timed out after %.1fs", c.pollTimeout.Seconds())}, + } +} diff --git a/api_sdk/go/resource.go b/api_sdk/go/resource.go new file mode 100644 index 0000000..bef6841 --- /dev/null +++ b/api_sdk/go/resource.go @@ -0,0 +1,267 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paddleocr + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "sort" + "strings" +) + +type SaveResourceOption func(*saveResourceOptions) + +type saveResourceOptions struct { + overwrite bool +} + +func WithOverwrite(overwrite bool) SaveResourceOption { + return func(o *saveResourceOptions) { + o.overwrite = overwrite + } +} + +// SaveResource downloads one result resource URL. +func (c *Client) SaveResource(ctx context.Context, resourceURL, dest string, opts ...SaveResourceOption) (string, error) { + if resourceURL == "" { + return "", &InvalidRequestError{PaddleOCRAPIError{Message: "resource URL is required"}} + } + if dest == "" { + return "", &InvalidRequestError{PaddleOCRAPIError{Message: "destination path is required"}} + } + options := saveResourceOptions{} + for _, opt := range opts { + opt(&options) + } + savedPath, err := resolveSavePath(resourceURL, dest) + if err != nil { + return "", err + } + parent := filepath.Dir(savedPath) + if st, err := os.Stat(parent); err != nil { + if errors.Is(err, os.ErrNotExist) { + return "", &FileNotFoundError{Path: parent, PaddleOCRAPIError: PaddleOCRAPIError{Message: "destination parent not found: " + parent, Cause: err}} + } + return "", err + } else if !st.IsDir() { + return "", &FileNotFoundError{Path: parent, PaddleOCRAPIError: PaddleOCRAPIError{Message: "destination parent is not a directory: " + parent}} + } + if !options.overwrite { + if _, err := os.Stat(savedPath); err == nil { + return "", &InvalidRequestError{PaddleOCRAPIError{Message: "destination already exists: " + savedPath}} + } else if err != nil && !errors.Is(err, os.ErrNotExist) { + return "", err + } + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, resourceURL, nil) + if err != nil { + return "", &NetworkError{PaddleOCRAPIError{Message: err.Error(), Cause: err}} + } + resp, err := c.httpClient.Do(req) + if err != nil { + return "", classifyHTTPError(err) + } + defer resp.Body.Close() + if err := raiseForResourceResponse(resp); err != nil { + return "", err + } + + tmp, err := os.CreateTemp(parent, "."+filepath.Base(savedPath)+".tmp-*") + if err != nil { + return "", err + } + tmpPath := tmp.Name() + cleanupTemp := true + defer func() { + if cleanupTemp { + _ = os.Remove(tmpPath) + } + }() + if _, err := io.Copy(tmp, resp.Body); err != nil { + _ = tmp.Close() + return "", err + } + if err := tmp.Close(); err != nil { + return "", err + } + if options.overwrite { + if err := os.Rename(tmpPath, savedPath); err != nil { + return "", err + } + } else { + if err := os.Link(tmpPath, savedPath); err != nil { + if errors.Is(err, os.ErrExist) { + return "", &InvalidRequestError{PaddleOCRAPIError{Message: "destination already exists: " + savedPath, Cause: err}} + } + return "", err + } + if err := os.Remove(tmpPath); err != nil { + return "", err + } + } + cleanupTemp = false + return savedPath, nil +} + +func (c *Client) SaveOCRResultResources(ctx context.Context, result *OCRResult, destDir string, opts ...SaveResourceOption) ([]string, error) { + if result == nil { + return nil, &InvalidRequestError{PaddleOCRAPIError{Message: "OCR result is required"}} + } + if err := requireExistingDirectory(destDir); err != nil { + return nil, err + } + saved := make([]string, 0, len(result.Pages)) + for i, page := range result.Pages { + if page.OCRImageURL == "" { + continue + } + name := fmt.Sprintf("ocr-page-%d%s", i+1, safeResourceExtension(page.OCRImageURL)) + if err := validateResultResourceFilename(name); err != nil { + return nil, err + } + path, err := c.SaveResource(ctx, page.OCRImageURL, filepath.Join(destDir, name), opts...) + if err != nil { + return nil, err + } + saved = append(saved, path) + } + return saved, nil +} + +func (c *Client) SaveDocumentParsingResultResources(ctx context.Context, result *DocParsingResult, destDir string, opts ...SaveResourceOption) ([]string, error) { + if result == nil { + return nil, &InvalidRequestError{PaddleOCRAPIError{Message: "document parsing result is required"}} + } + if err := requireExistingDirectory(destDir); err != nil { + return nil, err + } + var saved []string + for _, page := range result.Pages { + paths, err := c.saveNamedResourceMap(ctx, page.MarkdownImages, destDir, opts...) + if err != nil { + return nil, err + } + saved = append(saved, paths...) + paths, err = c.saveNamedResourceMap(ctx, page.OutputImages, destDir, opts...) + if err != nil { + return nil, err + } + saved = append(saved, paths...) + } + return saved, nil +} + +func (c *Client) saveNamedResourceMap(ctx context.Context, resources map[string]string, destDir string, opts ...SaveResourceOption) ([]string, error) { + keys := make([]string, 0, len(resources)) + for key := range resources { + keys = append(keys, key) + } + sort.Strings(keys) + saved := make([]string, 0, len(keys)) + for _, key := range keys { + resourceURL := resources[key] + if resourceURL == "" { + continue + } + name, err := resultResourceFilename(key) + if err != nil { + return nil, err + } + path, err := c.SaveResource(ctx, resourceURL, filepath.Join(destDir, name), opts...) + if err != nil { + return nil, err + } + saved = append(saved, path) + } + return saved, nil +} + +func resolveSavePath(resourceURL, dest string) (string, error) { + if st, err := os.Stat(dest); err == nil && st.IsDir() { + name := "resource" + u, parseErr := url.Parse(resourceURL) + if parseErr == nil { + if base := path.Base(u.Path); base != "." && base != "/" && base != "" { + name = base + } + } + return filepath.Join(dest, name), nil + } else if err != nil && !errors.Is(err, os.ErrNotExist) { + return "", err + } + return dest, nil +} + +func raiseForResourceResponse(resp *http.Response) error { + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + return nil + } + body, _ := io.ReadAll(resp.Body) + return &APIError{StatusCode: resp.StatusCode, PaddleOCRAPIError: PaddleOCRAPIError{Message: string(body)}} +} + +func requireExistingDirectory(destDir string) error { + if destDir == "" { + return &InvalidRequestError{PaddleOCRAPIError{Message: "destination directory is required"}} + } + st, err := os.Stat(destDir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return &FileNotFoundError{Path: destDir, PaddleOCRAPIError: PaddleOCRAPIError{Message: "destination directory not found: " + destDir, Cause: err}} + } + return err + } + if !st.IsDir() { + return &FileNotFoundError{Path: destDir, PaddleOCRAPIError: PaddleOCRAPIError{Message: "destination is not a directory: " + destDir}} + } + return nil +} + +func resultResourceFilename(key string) (string, error) { + if err := validateResultResourceFilename(key); err != nil { + return "", err + } + return key, nil +} + +func validateResultResourceFilename(name string) error { + if name == "" || name == "." || name == ".." || filepath.IsAbs(name) || path.IsAbs(name) || strings.ContainsAny(name, `/\`) { + return &InvalidRequestError{PaddleOCRAPIError{Message: "unsafe resource filename: " + name}} + } + return nil +} + +func safeResourceExtension(resourceURL string) string { + u, err := url.Parse(resourceURL) + if err != nil { + return "" + } + ext := path.Ext(path.Base(u.Path)) + if len(ext) <= 1 { + return "" + } + if err := validateResultResourceFilename("resource" + ext); err != nil { + return "" + } + return ext +} diff --git a/api_sdk/go/results.go b/api_sdk/go/results.go new file mode 100644 index 0000000..81a0d14 --- /dev/null +++ b/api_sdk/go/results.go @@ -0,0 +1,74 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paddleocr + +type OCRPage struct { + PrunedResult interface{} `json:"prunedResult"` + OCRImageURL string `json:"ocrImageUrl,omitempty"` + DocPreprocessingImageURL string `json:"docPreprocessingImageUrl,omitempty"` + InputImageURL string `json:"inputImageUrl,omitempty"` + Raw map[string]interface{} `json:"raw,omitempty"` +} + +type DocParsingPage struct { + MarkdownText string `json:"markdownText"` + MarkdownImages map[string]string `json:"markdownImages"` + OutputImages map[string]string `json:"outputImages"` + PrunedResult interface{} `json:"prunedResult,omitempty"` + InputImageURL string `json:"inputImageUrl,omitempty"` + Exports map[string]interface{} `json:"exports,omitempty"` + Markdown map[string]interface{} `json:"markdown,omitempty"` + Raw map[string]interface{} `json:"raw,omitempty"` +} + +type OCRResult struct { + JobID string `json:"jobId"` + Pages []OCRPage `json:"pages"` + DataInfo map[string]interface{} `json:"dataInfo,omitempty"` +} + +type DocParsingResult struct { + JobID string `json:"jobId"` + Pages []DocParsingPage `json:"pages"` + DataInfo map[string]interface{} `json:"dataInfo,omitempty"` +} + +type Progress struct { + TotalPages int `json:"totalPages"` + ExtractedPages int `json:"extractedPages"` + StartTime string `json:"startTime,omitempty"` + EndTime string `json:"endTime,omitempty"` +} + +type Job struct { + JobID string `json:"jobId"` + Model string `json:"model"` + Task string `json:"task"` + PageRanges string `json:"pageRanges,omitempty"` + BatchID string `json:"batchId,omitempty"` +} + +type JobStatus struct { + JobID string `json:"jobId"` + State string `json:"state"` + Progress *Progress `json:"progress,omitempty"` + ResultURL map[string]string `json:"resultUrl,omitempty"` + ErrorMsg string `json:"errorMsg,omitempty"` +} + +type BatchStatus struct { + BatchID string `json:"batchId"` + Jobs []*JobStatus `json:"jobs"` +} diff --git a/api_sdk/go/transport.go b/api_sdk/go/transport.go new file mode 100644 index 0000000..b2f01a3 --- /dev/null +++ b/api_sdk/go/transport.go @@ -0,0 +1,329 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package paddleocr + +import ( + "bytes" + "context" + "encoding/json" + "io" + "mime/multipart" + "net" + "net/http" + "os" + "path/filepath" + "strings" +) + +type apiResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data json.RawMessage `json:"data"` +} + +type submitResponse struct { + JobID string `json:"jobId"` +} + +type jobStatusResponse struct { + JobID string `json:"jobId"` + State string `json:"state"` + ExtractProgress json.RawMessage `json:"extractProgress"` + ResultURL map[string]string `json:"resultUrl"` + ErrorMsg string `json:"errorMsg"` +} + +type extractProgress struct { + TotalPages int `json:"totalPages"` + ExtractedPages int `json:"extractedPages"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` +} + +func (c *Client) submitURL(ctx context.Context, model, fileURL string, payload interface{}, pageRanges, batchID string) (string, error) { + body := map[string]interface{}{ + "fileUrl": fileURL, + "model": model, + "optionalPayload": payload, + } + if pageRanges != "" { + body["pageRanges"] = pageRanges + } + if batchID != "" { + body["batchId"] = batchID + } + jsonBody, err := json.Marshal(body) + if err != nil { + return "", &ResponseFormatError{PaddleOCRAPIError{Message: "failed to encode submit request", Cause: err}} + } + + req, err := http.NewRequestWithContext(ctx, "POST", c.jobsURL, bytes.NewReader(jsonBody)) + if err != nil { + return "", &NetworkError{PaddleOCRAPIError{Message: err.Error()}} + } + req.Header.Set("Authorization", "Bearer "+c.token) + req.Header.Set("Content-Type", "application/json") + c.setClientPlatformHeader(req) + + resp, err := c.httpClient.Do(req) + if err != nil { + return "", classifyHTTPError(err) + } + defer resp.Body.Close() + + if err := raiseForResponse(resp); err != nil { + return "", err + } + + apiResp, err := decodeAPIResponse(resp) + if err != nil { + return "", err + } + var sr submitResponse + if err := json.Unmarshal(apiResp.Data, &sr); err != nil { + return "", &ResponseFormatError{PaddleOCRAPIError{Message: "submit response is malformed", Cause: err}} + } + if sr.JobID == "" { + return "", &ResponseFormatError{PaddleOCRAPIError{Message: "submit response is missing jobId"}} + } + return sr.JobID, nil +} + +func (c *Client) submitFile(ctx context.Context, model, filePath string, payload interface{}, pageRanges, batchID string) (string, error) { + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return "", &FileNotFoundError{Path: filePath, PaddleOCRAPIError: PaddleOCRAPIError{Message: "File not found: " + filePath}} + } + + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + + _ = w.WriteField("model", model) + payloadJSON, err := json.Marshal(payload) + if err != nil { + return "", &ResponseFormatError{PaddleOCRAPIError{Message: "failed to encode optionalPayload", Cause: err}} + } + if err := w.WriteField("optionalPayload", string(payloadJSON)); err != nil { + return "", &ResponseFormatError{PaddleOCRAPIError{Message: "failed to prepare multipart body", Cause: err}} + } + if pageRanges != "" { + _ = w.WriteField("pageRanges", pageRanges) + } + if batchID != "" { + _ = w.WriteField("batchId", batchID) + } + + file, err := os.Open(filePath) + if err != nil { + return "", &FileNotFoundError{Path: filePath, PaddleOCRAPIError: PaddleOCRAPIError{Message: "Failed to open file: " + filePath, Cause: err}} + } + defer file.Close() + + fw, err := w.CreateFormFile("file", filepath.Base(filePath)) + if err != nil { + return "", &ResponseFormatError{PaddleOCRAPIError{Message: "failed to prepare multipart body", Cause: err}} + } + if _, err := io.Copy(fw, file); err != nil { + return "", &NetworkError{PaddleOCRAPIError{Message: "failed to read file content", Cause: err}} + } + if err := w.Close(); err != nil { + return "", &ResponseFormatError{PaddleOCRAPIError{Message: "failed to finalize multipart body", Cause: err}} + } + + req, err := http.NewRequestWithContext(ctx, "POST", c.jobsURL, &buf) + if err != nil { + return "", &NetworkError{PaddleOCRAPIError{Message: err.Error()}} + } + req.Header.Set("Authorization", "Bearer "+c.token) + req.Header.Set("Content-Type", w.FormDataContentType()) + c.setClientPlatformHeader(req) + + resp, err := c.httpClient.Do(req) + if err != nil { + return "", classifyHTTPError(err) + } + defer resp.Body.Close() + + if err := raiseForResponse(resp); err != nil { + return "", err + } + + apiResp, err := decodeAPIResponse(resp) + if err != nil { + return "", err + } + var sr submitResponse + if err := json.Unmarshal(apiResp.Data, &sr); err != nil { + return "", &ResponseFormatError{PaddleOCRAPIError{Message: "submit response is malformed", Cause: err}} + } + if sr.JobID == "" { + return "", &ResponseFormatError{PaddleOCRAPIError{Message: "submit response is missing jobId"}} + } + return sr.JobID, nil +} + +func (c *Client) getJobStatus(ctx context.Context, jobID string) (*jobStatusResponse, error) { + req, err := http.NewRequestWithContext(ctx, "GET", c.jobsURL+"/"+jobID, nil) + if err != nil { + return nil, &NetworkError{PaddleOCRAPIError{Message: err.Error()}} + } + req.Header.Set("Authorization", "Bearer "+c.token) + c.setClientPlatformHeader(req) + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, classifyHTTPError(err) + } + defer resp.Body.Close() + + if err := raiseForResponse(resp); err != nil { + return nil, err + } + + apiResp, err := decodeAPIResponse(resp) + if err != nil { + return nil, err + } + var status jobStatusResponse + if err := json.Unmarshal(apiResp.Data, &status); err != nil { + return nil, &ResponseFormatError{PaddleOCRAPIError{Message: "status response is malformed", Cause: err}} + } + if status.State == "" { + return nil, &ResponseFormatError{PaddleOCRAPIError{Message: "status response is missing state"}} + } + return &status, nil +} + +func (c *Client) getBatchStatus(ctx context.Context, batchID string) (*BatchStatus, error) { + req, err := http.NewRequestWithContext(ctx, "GET", c.jobsURL+"/batch/"+batchID, nil) + if err != nil { + return nil, &NetworkError{PaddleOCRAPIError{Message: err.Error(), Cause: err}} + } + req.Header.Set("Authorization", "Bearer "+c.token) + c.setClientPlatformHeader(req) + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, classifyHTTPError(err) + } + defer resp.Body.Close() + + if err := raiseForResponse(resp); err != nil { + return nil, err + } + apiResp, err := decodeAPIResponse(resp) + if err != nil { + return nil, err + } + var payload struct { + ExtractResult []jobStatusResponse `json:"extractResult"` + } + if err := json.Unmarshal(apiResp.Data, &payload); err != nil { + return nil, &ResponseFormatError{PaddleOCRAPIError{Message: "batch response is malformed", Cause: err}} + } + result := &BatchStatus{BatchID: batchID} + for _, item := range payload.ExtractResult { + jobID := item.JobID + if jobID == "" { + return nil, &ResponseFormatError{PaddleOCRAPIError{Message: "batch response item is missing jobId"}} + } + status, err := normalizeStatus(jobID, &item) + if err != nil { + return nil, err + } + result.Jobs = append(result.Jobs, status) + } + return result, nil +} + +func (c *Client) fetchJSONL(ctx context.Context, url string) ([]map[string]interface{}, error) { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, &NetworkError{PaddleOCRAPIError{Message: err.Error()}} + } + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, classifyHTTPError(err) + } + defer resp.Body.Close() + if err := raiseForResponse(resp); err != nil { + return nil, err + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + lines := strings.Split(strings.TrimSpace(string(body)), "\n") + var results []map[string]interface{} + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + var obj map[string]interface{} + if err := json.Unmarshal([]byte(line), &obj); err != nil { + return nil, &ResultParseError{PaddleOCRAPIError{Message: "failed to parse JSONL result payload", Cause: err}} + } + results = append(results, obj) + } + return results, nil +} + +func raiseForResponse(resp *http.Response) error { + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + return nil + } + body, _ := io.ReadAll(resp.Body) + msg := string(body) + + switch { + case resp.StatusCode == 401 || resp.StatusCode == 403: + return &AuthError{PaddleOCRAPIError{Message: "Authentication failed: " + msg}} + case resp.StatusCode == 400: + return &InvalidRequestError{PaddleOCRAPIError{Message: "Bad request: " + msg}} + case resp.StatusCode == 429: + return &RateLimitError{APIError{StatusCode: 429, PaddleOCRAPIError: PaddleOCRAPIError{Message: "Rate limit exceeded: " + msg}}} + case resp.StatusCode == 503 || resp.StatusCode == 504: + return &ServiceUnavailableError{APIError{StatusCode: resp.StatusCode, PaddleOCRAPIError: PaddleOCRAPIError{Message: "Service unavailable: " + msg}}} + default: + return &APIError{StatusCode: resp.StatusCode, PaddleOCRAPIError: PaddleOCRAPIError{Message: msg}} + } +} + +func decodeAPIResponse(resp *http.Response) (*apiResponse, error) { + var apiResp apiResponse + if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil { + return nil, &ResponseFormatError{PaddleOCRAPIError{Message: "expected a JSON response body", Cause: err}} + } + if apiResp.Code != 0 { + msg := apiResp.Msg + if msg == "" { + msg = "PaddleOCR official API request failed" + } + return nil, &APIError{StatusCode: resp.StatusCode, PaddleOCRAPIError: PaddleOCRAPIError{Message: msg}} + } + if len(apiResp.Data) == 0 || string(apiResp.Data) == "null" { + return nil, &ResponseFormatError{PaddleOCRAPIError{Message: "response body is missing data"}} + } + return &apiResp, nil +} + +func classifyHTTPError(err error) error { + if ne, ok := err.(net.Error); ok && ne.Timeout() { + return &RequestTimeoutError{PaddleOCRAPIError: PaddleOCRAPIError{Message: err.Error(), Cause: err}} + } + return &NetworkError{PaddleOCRAPIError{Message: err.Error(), Cause: err}} +} diff --git a/api_sdk/typescript/.gitignore b/api_sdk/typescript/.gitignore new file mode 100644 index 0000000..a45cc03 --- /dev/null +++ b/api_sdk/typescript/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +dist/ +.vite/ +coverage/ +.nyc_output/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* diff --git a/api_sdk/typescript/README.md b/api_sdk/typescript/README.md new file mode 100644 index 0000000..06216f6 --- /dev/null +++ b/api_sdk/typescript/README.md @@ -0,0 +1,72 @@ +# PaddleOCR TypeScript SDK + +English | [简体中文](README_cn.md) + +TypeScript client for the PaddleOCR official API. It submits OCR and document +parsing jobs to hosted PaddleOCR services; it does not perform local OCR +inference. + +Official user docs: + +- [TypeScript SDK](../../docs/version3.x/inference_deployment/serving/paddleocr_official_api/typescript.md) +- [TypeScript SDK (English)](../../docs/version3.x/inference_deployment/serving/paddleocr_official_api/typescript.en.md) + +## Install + +```bash +npm install @paddleocr/api-sdk +``` + +This package follows SemVer and is published as a public scoped npm package. + +For local development: + +```bash +npm install +npm run build +``` + +## Minimal Usage + +Set `PADDLEOCR_ACCESS_TOKEN` or pass `token` to the client: + +```bash +export PADDLEOCR_ACCESS_TOKEN="your-access-token" +``` + +```ts +import { Model, PaddleOCRClient } from "@paddleocr/api-sdk"; + +const client = new PaddleOCRClient(); +const result = await client.ocr({ + model: Model.PPOCRv5, + fileUrl: "https://example.com/invoice.pdf", +}); + +console.log(result.jobId, result.pages.length); +``` + +Specify `model: Model.PPOCRv6` (or `"PP-OCRv6"`) to use the PP-OCRv6 hosted OCR model. +Use `Model.PPOCRv5Latin` (or `"PP-OCRv5-latin"`) for the PP-OCRv5 Latin-script hosted OCR model. + +Document parsing defaults to PaddleOCR-VL-1.6: + +```ts +const doc = await client.parseDocument({ + filePath: "./report.pdf", + options: { + useChartRecognition: true, + }, +}); + +console.log(doc.jobId, doc.pages.length); +``` + +## Build And Test + +```bash +npm run lint +npm run build +npm test +npm audit --audit-level=moderate +``` diff --git a/api_sdk/typescript/README_cn.md b/api_sdk/typescript/README_cn.md new file mode 100644 index 0000000..ef156f9 --- /dev/null +++ b/api_sdk/typescript/README_cn.md @@ -0,0 +1,70 @@ +# PaddleOCR TypeScript SDK + +[English](README.md) | 简体中文 + +面向 PaddleOCR 官方 API 的 TypeScript 客户端。它会把 OCR 和文档解析任务提交到 PaddleOCR 官方托管服务;不会在本地执行 OCR 推理。 + +正式用户文档: + +- [TypeScript SDK](../../docs/version3.x/inference_deployment/serving/paddleocr_official_api/typescript.md) +- [TypeScript SDK(英文)](../../docs/version3.x/inference_deployment/serving/paddleocr_official_api/typescript.en.md) + +## 安装 + +```bash +npm install @paddleocr/api-sdk +``` + +该包遵循语义化版本,并作为公开 scoped npm 包发布。 + +本地开发: + +```bash +npm install +npm run build +``` + +## 最小示例 + +设置 `PADDLEOCR_ACCESS_TOKEN`,或在构造客户端时传入 `token`: + +```bash +export PADDLEOCR_ACCESS_TOKEN="your-access-token" +``` + +```ts +import { Model, PaddleOCRClient } from "@paddleocr/api-sdk"; + +const client = new PaddleOCRClient(); +const result = await client.ocr({ + model: Model.PPOCRv5, + fileUrl: "https://example.com/invoice.pdf", +}); + +console.log(result.jobId, result.pages.length); +``` + +通过 `model: Model.PPOCRv6`(或 `"PP-OCRv6"`)可指定 PP-OCRv6 云端 OCR 模型。 +使用 `Model.PPOCRv5Latin`(或 `"PP-OCRv5-latin"`)可指定 PP-OCRv5 拉丁语系云端 OCR 模型。 + +文档解析默认使用 PaddleOCR-VL-1.6: + +```ts +const doc = await client.parseDocument({ + filePath: "./report.pdf", + options: { + useChartRecognition: true, + }, +}); + +console.log(doc.jobId, doc.pages.length); +``` + +## 构建与测试 + +```bash +npm run lint +npm run build +npm test +npm audit --audit-level=moderate +``` diff --git a/api_sdk/typescript/examples/doc-parsing-file.ts b/api_sdk/typescript/examples/doc-parsing-file.ts new file mode 100644 index 0000000..7cd751b --- /dev/null +++ b/api_sdk/typescript/examples/doc-parsing-file.ts @@ -0,0 +1,47 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { PaddleOCRClient, Model } from "@paddleocr/api-sdk"; + +async function main() { + const client = new PaddleOCRClient(); + + // Convenience: doc parsing with local file + const result = await client.parseDocument({ + model: Model.PPStructureV3, + filePath: "./sample.pdf", + options: { useChartRecognition: true }, + }); + + for (const page of result.pages) { + console.log(page.markdownText); + } + + // Manual control: submit + concurrent wait + const job1 = await client.submitOcr({ fileUrl: "https://example.com/f1.pdf" }); + const job2 = await client.submitDocumentParsing({ + model: Model.PPStructureV3, + filePath: "./sample.pdf", + }); + + const [r1, r2] = await Promise.all([ + client.waitOcrResult(job1.jobId), + client.waitDocumentParsingResult(job2.jobId), + ]); + + console.log("Job1 done:", r1); + console.log("Job2 done:", r2); +} + +main().catch(console.error); diff --git a/api_sdk/typescript/examples/ocr-url.ts b/api_sdk/typescript/examples/ocr-url.ts new file mode 100644 index 0000000..430334f --- /dev/null +++ b/api_sdk/typescript/examples/ocr-url.ts @@ -0,0 +1,31 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { PaddleOCRClient, Model } from "@paddleocr/api-sdk"; + +async function main() { + const client = new PaddleOCRClient(); + + // Convenience: OCR with URL + const ocrResult = await client.ocr({ + fileUrl: "https://example.com/invoice.pdf", + }); + + for (const page of ocrResult.pages) { + console.log("Result:", page.prunedResult); + console.log("Image:", page.ocrImageUrl); + } +} + +main().catch(console.error); diff --git a/api_sdk/typescript/package-lock.json b/api_sdk/typescript/package-lock.json new file mode 100644 index 0000000..a3060ed --- /dev/null +++ b/api_sdk/typescript/package-lock.json @@ -0,0 +1,2640 @@ +{ + "name": "@paddleocr/api-sdk", + "version": "0.2.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@paddleocr/api-sdk", + "version": "0.2.2", + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "^25.9.1", + "tsup": "^8.0.0", + "typescript": "^5.3.0", + "vitest": "^4.1.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", + "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", + "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", + "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", + "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.7", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", + "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "@vitest/utils": "4.1.7", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", + "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", + "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/rolldown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.2", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", + "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.7", + "@vitest/mocker": "4.1.7", + "@vitest/pretty-format": "4.1.7", + "@vitest/runner": "4.1.7", + "@vitest/snapshot": "4.1.7", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.7", + "@vitest/browser-preview": "4.1.7", + "@vitest/browser-webdriverio": "4.1.7", + "@vitest/coverage-istanbul": "4.1.7", + "@vitest/coverage-v8": "4.1.7", + "@vitest/ui": "4.1.7", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest/node_modules/tinyexec": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.2.tgz", + "integrity": "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/api_sdk/typescript/package.json b/api_sdk/typescript/package.json new file mode 100644 index 0000000..9252c3e --- /dev/null +++ b/api_sdk/typescript/package.json @@ -0,0 +1,62 @@ +{ + "name": "@paddleocr/api-sdk", + "version": "0.2.3", + "description": "TypeScript SDK for the PaddleOCR official API", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "files": [ + "dist", + "README.md", + "README_cn.md" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/PaddlePaddle/PaddleOCR.git", + "directory": "api_sdk/typescript" + }, + "homepage": "https://github.com/PaddlePaddle/PaddleOCR/tree/main/api_sdk/typescript", + "bugs": { + "url": "https://github.com/PaddlePaddle/PaddleOCR/issues" + }, + "keywords": [ + "paddleocr", + "ocr", + "document-parsing", + "api-sdk", + "typescript", + "official-api" + ], + "scripts": { + "build": "tsup", + "test": "vitest run", + "lint": "tsc --noEmit", + "prepublishOnly": "npm run lint && npm run build && npm test" + }, + "engines": { + "node": ">=18" + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/node": "^25.9.1", + "tsup": "^8.0.0", + "typescript": "^5.3.0", + "vitest": "^4.1.7" + } +} diff --git a/api_sdk/typescript/src/client.ts b/api_sdk/typescript/src/client.ts new file mode 100644 index 0000000..96a287f --- /dev/null +++ b/api_sdk/typescript/src/client.ts @@ -0,0 +1,394 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { stat, writeFile } from "node:fs/promises"; +import { basename, dirname, extname, join } from "node:path"; +import { AuthError, FileNotFoundError, InvalidRequestError, ResultParseError } from "./errors.js"; +import { HttpClient } from "./internal/http.js"; +import { Poller } from "./internal/poller.js"; +import type { ClientOptions, DocParsingRequest, OCRRequest, SaveResourceOptions } from "./models.js"; +import { isDocumentParsingModel, isOCRModel, Model } from "./models.js"; +import type { BatchStatus, DocParsingResult, Job, JobStatus, OCRResult } from "./results.js"; + +const DEFAULT_BASE_URL = "https://paddleocr.aistudio-app.com"; + +interface ResourceSavePlan { + resourceUrl: string; + filename: string; +} + +export class PaddleOCRClient { + private http: HttpClient; + private poller: Poller; + + constructor(options: ClientOptions = {}) { + const token = options.token || process.env.PADDLEOCR_ACCESS_TOKEN || ""; + if (!token) { + throw new AuthError("Token is required. Set PADDLEOCR_ACCESS_TOKEN or pass token option."); + } + const baseUrl = options.baseUrl || process.env.PADDLEOCR_BASE_URL || DEFAULT_BASE_URL; + const requestTimeout = options.requestTimeout || options.timeout || 300000; + const pollTimeout = options.pollTimeout || options.timeout || 600000; + + this.http = new HttpClient( + token, + baseUrl, + requestTimeout, + options.fetch, + options.clientPlatform, + ); + this.poller = new Poller(this.http, pollTimeout); + } + + async ocr(req: OCRRequest, options?: { signal?: AbortSignal }): Promise { + const job = await this.submitOcr(req, options); + return this.waitOcrResult(job, options); + } + + async parseDocument(req: DocParsingRequest, options?: { signal?: AbortSignal }): Promise { + const job = await this.submitDocumentParsing(req, options); + return this.waitDocumentParsingResult(job, options); + } + + async submitOcr(req: OCRRequest, options?: { signal?: AbortSignal }): Promise { + const model = req.model ?? Model.PPOCRv6; + const jobId = await this.submit(model, "ocr", req, options?.signal); + return { jobId, model, task: "ocr", pageRanges: req.pageRanges, batchId: req.batchId }; + } + + async submitDocumentParsing(req: DocParsingRequest, options?: { signal?: AbortSignal }): Promise { + const model = req.model ?? Model.PaddleOCRVL16; + if (!isDocumentParsingModel(model)) { + throw new InvalidRequestError(`Model ${model} is not a document parsing model.`); + } + const jobId = await this.submit(model, "document_parsing", req, options?.signal); + return { jobId, model, task: "document_parsing", pageRanges: req.pageRanges, batchId: req.batchId }; + } + + async waitOcrResult(job: Job | string, options?: { signal?: AbortSignal }): Promise { + const resolved = this.resolveJob(job, "ocr"); + const jsonlData = await this.poller.pollUntilDone(resolved.jobId, options?.signal); + return this.parseOCRResult(resolved.jobId, jsonlData); + } + + async waitDocumentParsingResult(job: Job | string, options?: { signal?: AbortSignal }): Promise { + const resolved = this.resolveJob(job, "document_parsing"); + const jsonlData = await this.poller.pollUntilDone(resolved.jobId, options?.signal); + return this.parseDocParsingResult(resolved.jobId, jsonlData); + } + + async getStatus(jobId: string, options?: { signal?: AbortSignal }): Promise { + return this.poller.getStatus(jobId, options?.signal); + } + + async getBatchStatus(batchId: string, options?: { signal?: AbortSignal }): Promise { + return this.poller.getBatchStatus(batchId, options?.signal); + } + + async saveResource( + resourceUrl: string, + destination: string, + options: SaveResourceOptions = {}, + ): Promise { + return this.saveResourceUrl(resourceUrl, destination, options); + } + + async saveOcrResultResources( + result: OCRResult, + destination: string, + options: SaveResourceOptions = {}, + ): Promise { + return this.saveResultResources(result, destination, options); + } + + async saveDocumentParsingResultResources( + result: DocParsingResult, + destination: string, + options: SaveResourceOptions = {}, + ): Promise { + return this.saveResultResources(result, destination, options); + } + + private async saveResourceUrl(resourceUrl: string, destination: string, options: SaveResourceOptions): Promise { + if (!resourceUrl) { + throw new InvalidRequestError("resourceUrl is required."); + } + let url: URL; + try { + url = new URL(resourceUrl); + } catch (error) { + throw new InvalidRequestError(`Invalid resource URL: ${resourceUrl}`, { cause: error }); + } + const target = await this.resolveDestination(url, destination, options); + const content = await this.http.fetchResource(resourceUrl); + await writeFile(target, Buffer.from(content), { flag: options.overwrite ? "w" : "wx" }); + return target; + } + + private async saveResultResources( + result: OCRResult | DocParsingResult, + destination: string, + options: SaveResourceOptions, + ): Promise { + await this.requireExistingDirectory(destination); + const plans = this.collectResultResourcePlans(result); + const targets = plans.map((plan) => join(destination, plan.filename)); + for (const target of targets) { + await this.requireWritableTarget(target, options); + } + this.requireUniqueTargets(targets, options); + const savedPaths: string[] = []; + for (const [index, plan] of plans.entries()) { + await this.saveResourceUrl(plan.resourceUrl, targets[index], options); + savedPaths.push(targets[index]); + } + return savedPaths; + } + + private collectResultResourcePlans(result: OCRResult | DocParsingResult): ResourceSavePlan[] { + if (isDocParsingResult(result)) { + return result.pages.flatMap((page) => [ + ...this.collectMappedResourcePlans(page.markdownImages), + ...this.collectMappedResourcePlans(page.outputImages), + ]); + } + return result.pages.flatMap((page, index) => { + if (!page.ocrImageUrl) { + return []; + } + return [{ + resourceUrl: page.ocrImageUrl, + filename: `ocr-page-${index + 1}${resourceExtension(page.ocrImageUrl)}`, + }]; + }); + } + + private collectMappedResourcePlans(resources: Record): ResourceSavePlan[] { + return Object.keys(resources) + .sort() + .map((key) => ({ + resourceUrl: resources[key], + filename: safeMapKeyFilename(key), + })); + } + + private async requireExistingDirectory(destination: string): Promise { + let destinationStat; + try { + destinationStat = await stat(destination); + } catch { + throw new FileNotFoundError(destination); + } + if (!destinationStat.isDirectory()) { + throw new InvalidRequestError(`Destination must be an existing directory: ${destination}`); + } + } + + private async requireWritableTarget(target: string, options: SaveResourceOptions): Promise { + try { + await stat(target); + } catch { + return; + } + if (!options.overwrite) { + throw new InvalidRequestError(`Destination already exists: ${target}`); + } + } + + private requireUniqueTargets(targets: string[], options: SaveResourceOptions): void { + if (options.overwrite) { + return; + } + const seen = new Set(); + for (const target of targets) { + if (seen.has(target)) { + throw new InvalidRequestError(`Destination already exists: ${target}`); + } + seen.add(target); + } + } + + private async resolveDestination(url: URL, destination: string, options: SaveResourceOptions): Promise { + let destinationStat; + try { + destinationStat = await stat(destination); + } catch { + destinationStat = undefined; + } + const target = destinationStat?.isDirectory() ? join(destination, safeUrlBasename(url)) : destination; + await this.requireWritableTarget(target, options); + const parent = dirname(target); + try { + const parentStat = await stat(parent); + if (!parentStat.isDirectory()) { + throw new InvalidRequestError(`Destination parent must be a directory: ${parent}`); + } + } catch (error) { + if (error instanceof InvalidRequestError) { + throw error; + } + throw new FileNotFoundError(parent, { cause: error }); + } + return target; + } + + private async submit( + model: string, + task: Job["task"], + req: { fileUrl?: string; filePath?: string; pageRanges?: string; batchId?: string; options?: object }, + signal?: AbortSignal, + ): Promise { + if (!req.fileUrl && !req.filePath) { + throw new InvalidRequestError("Either fileUrl or filePath is required."); + } + if (req.fileUrl && req.filePath) { + throw new InvalidRequestError("fileUrl and filePath are mutually exclusive."); + } + + this.validateModelForTask(model, task); + const payload = req.options || {}; + + if (req.fileUrl) { + return this.http.submitUrl(model, req.fileUrl, payload, { + pageRanges: req.pageRanges, + batchId: req.batchId, + signal, + }); + } + return this.http.submitFile(model, req.filePath!, payload, { + pageRanges: req.pageRanges, + batchId: req.batchId, + signal, + }); + } + + private parseOCRResult(jobId: string, jsonlData: unknown[]): OCRResult { + const dataInfo: Record = {}; + const pages = jsonlData.flatMap((lineObj) => { + if (!isRecord(lineObj) || !isRecord(lineObj.result) || !Array.isArray(lineObj.result.ocrResults)) { + throw new ResultParseError("OCR result item is missing result.ocrResults."); + } + if (isRecord(lineObj.result.dataInfo)) { + Object.assign(dataInfo, lineObj.result.dataInfo); + } + return lineObj.result.ocrResults.map((item) => { + if (!isRecord(item) || !("prunedResult" in item)) { + throw new ResultParseError("OCR result page is missing prunedResult."); + } + return { + prunedResult: item.prunedResult, + ocrImageUrl: typeof item.ocrImage === "string" ? item.ocrImage : undefined, + docPreprocessingImageUrl: typeof item.docPreprocessingImage === "string" ? item.docPreprocessingImage : undefined, + inputImageUrl: typeof item.inputImage === "string" ? item.inputImage : undefined, + raw: item, + }; + }); + }); + return { jobId, pages, dataInfo }; + } + + private parseDocParsingResult(jobId: string, jsonlData: unknown[]): DocParsingResult { + const dataInfo: Record = {}; + const pages = jsonlData.flatMap((lineObj) => { + if (!isRecord(lineObj) || !isRecord(lineObj.result) || !Array.isArray(lineObj.result.layoutParsingResults)) { + throw new ResultParseError("Document parsing result item is missing result.layoutParsingResults."); + } + if (isRecord(lineObj.result.dataInfo)) { + Object.assign(dataInfo, lineObj.result.dataInfo); + } + return lineObj.result.layoutParsingResults.map((item) => { + if (!isRecord(item) || !isRecord(item.markdown) || typeof item.markdown.text !== "string") { + throw new ResultParseError("Document parsing result page is missing markdown.text."); + } + return { + markdownText: item.markdown.text, + markdownImages: isRecord(item.markdown.images) ? stringMap(item.markdown.images) : {}, + outputImages: isRecord(item.outputImages) ? stringMap(item.outputImages) : {}, + prunedResult: item.prunedResult, + inputImageUrl: typeof item.inputImage === "string" ? item.inputImage : undefined, + exports: isRecord(item.exports) ? item.exports : {}, + markdown: item.markdown, + raw: item, + }; + }); + }); + return { jobId, pages, dataInfo }; + } + + private resolveJob(job: Job | string, expectedTask: Job["task"]): Job { + if (typeof job === "string") { + return { + jobId: job, + model: expectedTask === "ocr" ? Model.PPOCRv6 : Model.PaddleOCRVL16, + task: expectedTask, + }; + } + if (job.task !== expectedTask) { + throw new InvalidRequestError(`Job ${job.jobId} is a ${job.task} job, not a ${expectedTask} job.`); + } + this.validateModelForTask(job.model, expectedTask); + return job; + } + + private validateModelForTask(model: string, task: Job["task"]): void { + if (task === "ocr" && !isOCRModel(model)) { + throw new InvalidRequestError(`Model ${model} is not an OCR model.`); + } + if (task === "document_parsing" && !isDocumentParsingModel(model)) { + throw new InvalidRequestError(`Model ${model} is not a document parsing model.`); + } + } +} + +function stringMap(value: Record): Record { + const result: Record = {}; + for (const [key, val] of Object.entries(value)) { + if (typeof val === "string") { + result[key] = val; + } + } + return result; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function isDocParsingResult(result: OCRResult | DocParsingResult): result is DocParsingResult { + return result.pages.some((page) => "markdownText" in page); +} + +function safeMapKeyFilename(key: string): string { + if (!key || key === "." || key === ".." || key.includes("/") || key.includes("\\") || key.startsWith(".")) { + throw new InvalidRequestError(`Unsafe resource filename: ${key}`); + } + return key; +} + +function safeUrlBasename(url: URL): string { + const name = basename(url.pathname) || "resource"; + if (name === "." || name === "..") { + return "resource"; + } + return name; +} + +function resourceExtension(resourceUrl: string): string { + try { + const url = new URL(resourceUrl); + return extname(url.pathname); + } catch { + return ""; + } +} diff --git a/api_sdk/typescript/src/errors.ts b/api_sdk/typescript/src/errors.ts new file mode 100644 index 0000000..76a6d44 --- /dev/null +++ b/api_sdk/typescript/src/errors.ts @@ -0,0 +1,119 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export class PaddleOCRAPIError extends Error { + constructor(message: string, options?: ErrorOptions) { + super(message); + this.name = "PaddleOCRAPIError"; + this.cause = options?.cause; + } +} + +export class AuthError extends PaddleOCRAPIError { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = "AuthError"; + } +} + +export class InvalidRequestError extends PaddleOCRAPIError { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = "InvalidRequestError"; + } +} + +export class APIError extends PaddleOCRAPIError { + statusCode: number; + constructor(statusCode: number, message: string, options?: ErrorOptions) { + super(`HTTP ${statusCode}: ${message}`, options); + this.name = "APIError"; + this.statusCode = statusCode; + } +} + +export class RateLimitError extends APIError { + constructor(message: string, options?: ErrorOptions) { + super(429, message, options); + this.name = "RateLimitError"; + } +} + +export class ServiceUnavailableError extends APIError { + constructor(statusCode: number, message: string, options?: ErrorOptions) { + super(statusCode, message, options); + this.name = "ServiceUnavailableError"; + } +} + +export class JobFailedError extends PaddleOCRAPIError { + jobId: string; + errorMsg: string; + constructor(jobId: string, errorMsg: string, options?: ErrorOptions) { + super(`Job ${jobId} failed: ${errorMsg}`, options); + this.name = "JobFailedError"; + this.jobId = jobId; + this.errorMsg = errorMsg; + } +} + +export class RequestTimeoutError extends PaddleOCRAPIError { + timeoutMs: number; + constructor(timeoutMs: number, options?: ErrorOptions) { + super(`Request timed out after ${timeoutMs}ms`, options); + this.name = "RequestTimeoutError"; + this.timeoutMs = timeoutMs; + } +} + +export class PollTimeoutError extends PaddleOCRAPIError { + jobId: string; + timeoutMs: number; + constructor(jobId: string, timeoutMs: number, options?: ErrorOptions) { + super(`Timed out after ${timeoutMs}ms waiting for job ${jobId}`, options); + this.name = "PollTimeoutError"; + this.jobId = jobId; + this.timeoutMs = timeoutMs; + } +} + +export class NetworkError extends PaddleOCRAPIError { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = "NetworkError"; + } +} + +export class FileNotFoundError extends PaddleOCRAPIError { + path: string; + constructor(path: string, options?: ErrorOptions) { + super(`File not found: ${path}`, options); + this.name = "FileNotFoundError"; + this.path = path; + } +} + +export class ResponseFormatError extends PaddleOCRAPIError { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = "ResponseFormatError"; + } +} + +export class ResultParseError extends PaddleOCRAPIError { + constructor(message: string, options?: ErrorOptions) { + super(message, options); + this.name = "ResultParseError"; + } +} diff --git a/api_sdk/typescript/src/index.ts b/api_sdk/typescript/src/index.ts new file mode 100644 index 0000000..4e57a82 --- /dev/null +++ b/api_sdk/typescript/src/index.ts @@ -0,0 +1,51 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export { PaddleOCRClient } from "./client.js"; +export { Model, isDocumentParsingModel, isOCRModel, isVLModel } from "./models.js"; +export type { + ClientOptions, + DocParsingOptions, + DocParsingRequest, + OCROptions, + OCRRequest, + PaddleOCRVLOptions, + PPStructureV3Options, + SaveResourceOptions, +} from "./models.js"; +export type { + BatchStatus, + DocParsingPage, + DocParsingResult, + Job, + JobStatus, + OCRPage, + OCRResult, + Progress, +} from "./results.js"; +export { + APIError, + AuthError, + FileNotFoundError, + InvalidRequestError, + JobFailedError, + NetworkError, + PaddleOCRAPIError, + PollTimeoutError, + RateLimitError, + RequestTimeoutError, + ResponseFormatError, + ResultParseError, + ServiceUnavailableError, +} from "./errors.js"; diff --git a/api_sdk/typescript/src/internal/abort.ts b/api_sdk/typescript/src/internal/abort.ts new file mode 100644 index 0000000..8839143 --- /dev/null +++ b/api_sdk/typescript/src/internal/abort.ts @@ -0,0 +1,25 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** Value to reject/throw when an operation was aborted via `AbortSignal` (aligns with `fetch` + `DOMException`). */ +export function userAbortReason(signal: AbortSignal): unknown { + return signal.reason !== undefined + ? signal.reason + : new DOMException("The user aborted a request.", "AbortError"); +} + +export function throwIfAborted(signal?: AbortSignal): void { + if (!signal?.aborted) return; + throw userAbortReason(signal); +} diff --git a/api_sdk/typescript/src/internal/http.ts b/api_sdk/typescript/src/internal/http.ts new file mode 100644 index 0000000..fe40700 --- /dev/null +++ b/api_sdk/typescript/src/internal/http.ts @@ -0,0 +1,255 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { + APIError, + AuthError, + InvalidRequestError, + NetworkError, + RateLimitError, + RequestTimeoutError, + ResponseFormatError, + ResultParseError, + ServiceUnavailableError, +} from "../errors.js"; +import { userAbortReason } from "./abort.js"; + +const DEFAULT_BASE_URL = "https://paddleocr.aistudio-app.com"; +const API_PATH = "/api/v2/ocr/jobs"; + +interface SubmitOptions { + pageRanges?: string; + batchId?: string; + signal?: AbortSignal; +} + +interface APIResponse { + code?: number; + msg?: string; + data: T; +} + +interface SubmitResponse { + jobId: string; +} + +export class HttpClient { + private baseUrl: string; + private jobsUrl: string; + private token: string; + private requestTimeout: number; + private fetchImpl: typeof fetch; + private clientPlatform?: string; + + constructor( + token: string, + baseUrl: string = DEFAULT_BASE_URL, + requestTimeout: number = 300000, + fetchImpl: typeof fetch = fetch, + clientPlatform?: string, + ) { + this.token = token; + this.baseUrl = baseUrl.replace(/\/+$/, ""); + this.jobsUrl = `${this.baseUrl}${API_PATH}`; + this.requestTimeout = requestTimeout; + this.fetchImpl = fetchImpl; + this.clientPlatform = clientPlatform; + } + + async submitUrl(model: string, fileUrl: string, optionalPayload: object, options: SubmitOptions = {}): Promise { + const body: Record = { fileUrl, model, optionalPayload }; + if (options.pageRanges !== undefined) { + body.pageRanges = options.pageRanges; + } + if (options.batchId !== undefined) { + body.batchId = options.batchId; + } + const data = await this.fetchJson(this.jobsUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }, options.signal); + return requireJobId(data); + } + + async submitFile(model: string, filePath: string, optionalPayload: object, options: SubmitOptions = {}): Promise { + const fs = await import("fs"); + const path = await import("path"); + + if (!fs.existsSync(filePath)) { + const { FileNotFoundError } = await import("../errors.js"); + throw new FileNotFoundError(filePath); + } + + const form = new FormData(); + form.append("model", model); + form.append("optionalPayload", JSON.stringify(optionalPayload)); + if (options.pageRanges !== undefined) { + form.append("pageRanges", options.pageRanges); + } + if (options.batchId !== undefined) { + form.append("batchId", options.batchId); + } + + const fileBuffer = fs.readFileSync(filePath); + const blob = new Blob([fileBuffer]); + form.append("file", blob, path.basename(filePath)); + + const data = await this.fetchJson(this.jobsUrl, { + method: "POST", + body: form, + }, options.signal); + return requireJobId(data); + } + + async getJobStatus(jobId: string, signal?: AbortSignal, timeoutMs?: number): Promise { + return this.fetchJson( + `${this.jobsUrl}/${encodeURIComponent(jobId)}`, + { method: "GET" }, + signal, + true, + timeoutMs, + ); + } + + async getBatchStatus(batchId: string, signal?: AbortSignal, timeoutMs?: number): Promise { + return this.fetchJson( + `${this.jobsUrl}/batch/${encodeURIComponent(batchId)}`, + { method: "GET" }, + signal, + true, + timeoutMs, + ); + } + + async fetchJsonl(url: string, signal?: AbortSignal, timeoutMs?: number): Promise { + const resp = await this.fetch(url, { method: "GET" }, signal, false, timeoutMs); + const text = await resp.text(); + try { + return text + .trim() + .split("\n") + .filter((line) => line.trim()) + .map((line) => JSON.parse(line) as unknown); + } catch (error) { + throw new ResultParseError("Failed to parse JSONL result payload.", { cause: error }); + } + } + + async fetchResource(url: string, signal?: AbortSignal, timeoutMs?: number): Promise { + const resp = await this.fetch(url, { method: "GET" }, signal, false, timeoutMs); + return resp.arrayBuffer(); + } + + private async fetchJson( + url: string, + init: RequestInit, + signal?: AbortSignal, + withAuth: boolean = true, + timeoutMs?: number, + ): Promise { + const resp = await this.fetch(url, init, signal, withAuth, timeoutMs); + let payload: APIResponse; + try { + payload = await resp.json() as APIResponse; + } catch (error) { + throw new ResponseFormatError("Expected a JSON response body.", { cause: error }); + } + if (payload.code !== undefined && payload.code !== 0) { + throw new APIError(resp.status, payload.msg || "PaddleOCR official API request failed."); + } + if (!payload || typeof payload !== "object" || !("data" in payload)) { + throw new ResponseFormatError("Response body is missing data."); + } + return payload.data; + } + + private async fetch( + url: string, + init: RequestInit, + signal?: AbortSignal, + withAuth: boolean = true, + timeoutMs?: number, + ): Promise { + const headers: Record = { + ...(init.headers as Record || {}), + }; + if (withAuth) { + headers.Authorization = `Bearer ${this.token}`; + if (this.clientPlatform) { + headers["Client-Platform"] = this.clientPlatform; + } + } + + let resp: Response; + const timeoutController = new AbortController(); + const effectiveTimeout = Math.max(0, Math.min(this.requestTimeout, timeoutMs ?? this.requestTimeout)); + const timeoutID = setTimeout(() => timeoutController.abort(), effectiveTimeout); + const abortController = new AbortController(); + const abort = () => abortController.abort(); + timeoutController.signal.addEventListener("abort", abort, { once: true }); + if (signal?.aborted) { + abort(); + } else { + signal?.addEventListener("abort", abort, { once: true }); + } + try { + resp = await this.fetchImpl(url, { + ...init, + headers, + signal: abortController.signal, + }); + } catch (e: unknown) { + if (signal?.aborted) { + throw userAbortReason(signal); + } + if (timeoutController.signal.aborted) { + throw new RequestTimeoutError(effectiveTimeout, { cause: e }); + } + const message = e instanceof Error ? e.message : String(e); + throw new NetworkError(`Connection failed: ${message}`); + } finally { + clearTimeout(timeoutID); + signal?.removeEventListener("abort", abort); + } + + if (resp.ok) return resp; + + let text = await resp.text(); + try { + const payload = JSON.parse(text) as { msg?: string; message?: string; errorMsg?: string }; + text = payload.msg || payload.message || payload.errorMsg || text; + } catch { + // Keep raw response text. + } + if (resp.status === 401 || resp.status === 403) { + throw new AuthError(`Authentication failed: ${text}`); + } else if (resp.status === 400) { + throw new InvalidRequestError(`Bad request: ${text}`); + } else if (resp.status === 429) { + throw new RateLimitError(`Rate limit exceeded: ${text}`); + } else if (resp.status === 503 || resp.status === 504) { + throw new ServiceUnavailableError(resp.status, `Service unavailable: ${text}`); + } else { + throw new APIError(resp.status, text); + } + } +} + +function requireJobId(data: SubmitResponse): string { + if (!data || typeof data.jobId !== "string" || data.jobId.length === 0) { + throw new ResponseFormatError("Submit response is missing jobId."); + } + return data.jobId; +} diff --git a/api_sdk/typescript/src/internal/poller.ts b/api_sdk/typescript/src/internal/poller.ts new file mode 100644 index 0000000..dc6f0b2 --- /dev/null +++ b/api_sdk/typescript/src/internal/poller.ts @@ -0,0 +1,162 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { JobFailedError, PollTimeoutError, RequestTimeoutError, ResponseFormatError } from "../errors.js"; +import type { BatchStatus, JobStatus, Progress } from "../results.js"; +import { throwIfAborted, userAbortReason } from "./abort.js"; +import { HttpClient } from "./http.js"; + +const INITIAL_INTERVAL = 3000; +const MULTIPLIER = 1.5; +const MAX_INTERVAL = 15000; +const MAX_WAIT_TIME = 600000; + +export class Poller { + private http: HttpClient; + private maxWaitTime: number; + + constructor(http: HttpClient, maxWaitTime: number = MAX_WAIT_TIME) { + this.http = http; + this.maxWaitTime = maxWaitTime; + } + + async pollUntilDone(jobId: string, signal?: AbortSignal): Promise { + let interval = INITIAL_INTERVAL; + const deadline = Date.now() + this.maxWaitTime; + + while (Date.now() < deadline) { + throwIfAborted(signal); + + const remaining = deadline - Date.now(); + const data = await this.withPollTimeout(jobId, remaining, () => this.http.getJobStatus(jobId, signal, remaining)); + const status = normalizeStatus(jobId, data); + + if (status.state === "done") { + const jsonUrl = resultJsonUrl(data); + const resultRemaining = deadline - Date.now(); + return await this.withPollTimeout(jobId, resultRemaining, () => this.http.fetchJsonl(jsonUrl, signal, resultRemaining)); + } + + if (status.state === "failed") { + throw new JobFailedError(jobId, status.errorMsg || "Unknown error"); + } + + await this.sleep(Math.min(interval, Math.max(0, deadline - Date.now())), signal); + interval = Math.min(interval * MULTIPLIER, MAX_INTERVAL); + } + + throw new PollTimeoutError(jobId, this.maxWaitTime); + } + + async getStatus(jobId: string, signal?: AbortSignal): Promise { + return normalizeStatus(jobId, await this.http.getJobStatus(jobId, signal)); + } + + async getBatchStatus(batchId: string, signal?: AbortSignal): Promise { + const data = await this.http.getBatchStatus(batchId, signal); + if (!isRecord(data) || !Array.isArray(data.extractResult)) { + throw new ResponseFormatError("Batch response is missing extractResult."); + } + return { + batchId, + jobs: data.extractResult.map((item) => { + if (!isRecord(item) || typeof item.jobId !== "string") { + throw new ResponseFormatError("Batch extractResult item is missing jobId."); + } + return normalizeStatus(item.jobId, item); + }), + }; + } + + private sleep(ms: number, signal?: AbortSignal): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(resolve, ms); + if (!signal) return; + signal.addEventListener( + "abort", + () => { + clearTimeout(timer); + reject(userAbortReason(signal)); + }, + { once: true }, + ); + }); + } + + private async withPollTimeout(jobId: string, remainingMs: number, operation: () => Promise): Promise { + if (remainingMs <= 0) { + throw new PollTimeoutError(jobId, this.maxWaitTime); + } + try { + return await operation(); + } catch (error) { + if (error instanceof RequestTimeoutError && error.timeoutMs === remainingMs) { + throw new PollTimeoutError(jobId, this.maxWaitTime, { cause: error }); + } + throw error; + } + } +} + +function normalizeStatus(jobId: string, data: unknown): JobStatus { + if (!isRecord(data) || typeof data.state !== "string") { + throw new ResponseFormatError("Status response is missing state."); + } + if (!["pending", "running", "done", "failed"].includes(data.state)) { + throw new ResponseFormatError(`Unknown job state: ${data.state}`); + } + return { + jobId, + state: data.state as JobStatus["state"], + progress: normalizeProgress(data.extractProgress), + resultUrl: isRecord(data.resultUrl) ? stringMap(data.resultUrl) : undefined, + errorMsg: typeof data.errorMsg === "string" ? data.errorMsg : undefined, + }; +} + +function normalizeProgress(progress: unknown): Progress | undefined { + if (progress === undefined || progress === null) { + return undefined; + } + if (!isRecord(progress)) { + throw new ResponseFormatError("Status progress must be an object."); + } + return { + totalPages: typeof progress.totalPages === "number" ? progress.totalPages : 0, + extractedPages: typeof progress.extractedPages === "number" ? progress.extractedPages : 0, + startTime: typeof progress.startTime === "string" ? progress.startTime : undefined, + endTime: typeof progress.endTime === "string" ? progress.endTime : undefined, + }; +} + +function resultJsonUrl(data: unknown): string { + if (!isRecord(data) || !isRecord(data.resultUrl) || typeof data.resultUrl.jsonUrl !== "string") { + throw new ResponseFormatError("Done job response is missing resultUrl.jsonUrl."); + } + return data.resultUrl.jsonUrl; +} + +function stringMap(value: Record): Record { + const result: Record = {}; + for (const [key, val] of Object.entries(value)) { + if (typeof val === "string") { + result[key] = val; + } + } + return result; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} diff --git a/api_sdk/typescript/src/models.ts b/api_sdk/typescript/src/models.ts new file mode 100644 index 0000000..6cf7d7d --- /dev/null +++ b/api_sdk/typescript/src/models.ts @@ -0,0 +1,168 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export enum Model { + PPOCRv5 = "PP-OCRv5", + PPOCRv5Latin = "PP-OCRv5-latin", + PPOCRv6 = "PP-OCRv6", + PPStructureV3 = "PP-StructureV3", + PaddleOCRVL = "PaddleOCR-VL", + PaddleOCRVL15 = "PaddleOCR-VL-1.5", + PaddleOCRVL16 = "PaddleOCR-VL-1.6", +} + +const OCR_MODELS = new Set([Model.PPOCRv5, Model.PPOCRv5Latin, Model.PPOCRv6]); +const DOCUMENT_PARSING_MODELS = new Set([ + Model.PPStructureV3, + Model.PaddleOCRVL, + Model.PaddleOCRVL15, + Model.PaddleOCRVL16, +]); +const VL_MODELS = new Set([ + Model.PaddleOCRVL, + Model.PaddleOCRVL15, + Model.PaddleOCRVL16, +]); + +export function isOCRModel( + model: string +): model is Model.PPOCRv5 | Model.PPOCRv5Latin | Model.PPOCRv6 { + return OCR_MODELS.has(model); +} + +export function isDocumentParsingModel(model: string): boolean { + return DOCUMENT_PARSING_MODELS.has(model); +} + +export function isVLModel(model: string): boolean { + return VL_MODELS.has(model); +} + +export interface OCROptions { + useDocOrientationClassify?: boolean; + useDocUnwarping?: boolean; + useTextlineOrientation?: boolean; + textDetLimitSideLen?: number; + textDetLimitType?: string; + textDetThresh?: number; + textDetBoxThresh?: number; + textDetUnclipRatio?: number; + textRecScoreThresh?: number; + visualize?: boolean; + [key: string]: unknown; +} + +export interface PPStructureV3Options { + useDocOrientationClassify?: boolean; + useDocUnwarping?: boolean; + useTextlineOrientation?: boolean; + useSealRecognition?: boolean; + useTableRecognition?: boolean; + useFormulaRecognition?: boolean; + useChartRecognition?: boolean; + useRegionDetection?: boolean; + layoutThreshold?: number | Record; + layoutNms?: boolean; + layoutUnclipRatio?: number | number[] | Record; + layoutMergeBboxesMode?: string | Record; + formatBlockContent?: boolean; + textDetLimitSideLen?: number; + textDetLimitType?: string; + textDetThresh?: number; + textDetBoxThresh?: number; + textDetUnclipRatio?: number; + textRecScoreThresh?: number; + useWiredTableCellsTransToHtml?: boolean; + useWirelessTableCellsTransToHtml?: boolean; + useTableOrientationClassify?: boolean; + useOcrResultsWithTableCells?: boolean; + useE2eWiredTableRecModel?: boolean; + useE2eWirelessTableRecModel?: boolean; + markdownIgnoreLabels?: string[]; + prettifyMarkdown?: boolean; + showFormulaNumber?: boolean; + returnMarkdownImages?: boolean; + outputFormats?: string[]; + visualize?: boolean; + [key: string]: unknown; +} + +export interface PaddleOCRVLOptions { + useDocOrientationClassify?: boolean; + useDocUnwarping?: boolean; + useLayoutDetection?: boolean; + useChartRecognition?: boolean; + useSealRecognition?: boolean; + useOcrForImageBlock?: boolean; + layoutThreshold?: number | Record; + layoutNms?: boolean; + layoutUnclipRatio?: number | number[] | Record; + layoutMergeBboxesMode?: string | Record; + layoutShapeMode?: "rect" | "quad" | "poly" | "auto"; + promptLabel?: "ocr" | "formula" | "table" | "chart" | "seal" | "spotting"; + formatBlockContent?: boolean; + repetitionPenalty?: number; + temperature?: number; + topP?: number; + minPixels?: number; + maxPixels?: number; + maxNewTokens?: number; + vlmExtraArgs?: Record; + mergeLayoutBlocks?: boolean; + markdownIgnoreLabels?: string[]; + prettifyMarkdown?: boolean; + showFormulaNumber?: boolean; + restructurePages?: boolean; + mergeTables?: boolean; + relevelTitles?: boolean; + returnMarkdownImages?: boolean; + outputFormats?: string[]; + visualize?: boolean; + [key: string]: unknown; +} + +export type DocParsingOptions = PPStructureV3Options | PaddleOCRVLOptions; + +export interface OCRRequest { + model?: Model | string; + fileUrl?: string; + filePath?: string; + pageRanges?: string; + batchId?: string; + options?: OCROptions; +} + +export interface DocParsingRequest { + model?: Model | string; + fileUrl?: string; + filePath?: string; + pageRanges?: string; + batchId?: string; + options?: DocParsingOptions; +} + +export interface ClientOptions { + token?: string; + baseUrl?: string; + timeout?: number; + requestTimeout?: number; + pollTimeout?: number; + clientPlatform?: string; + fetch?: typeof fetch; +} + +export interface SaveResourceOptions { + overwrite?: boolean; + filename?: string; +} diff --git a/api_sdk/typescript/src/results.ts b/api_sdk/typescript/src/results.ts new file mode 100644 index 0000000..60c01ce --- /dev/null +++ b/api_sdk/typescript/src/results.ts @@ -0,0 +1,72 @@ +// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +export interface OCRPage { + prunedResult: unknown; + ocrImageUrl?: string; + docPreprocessingImageUrl?: string; + inputImageUrl?: string; + raw?: unknown; +} + +export interface DocParsingPage { + markdownText: string; + markdownImages: Record; + outputImages: Record; + prunedResult?: unknown; + inputImageUrl?: string; + exports?: Record; + markdown?: Record; + raw?: unknown; +} + +export interface OCRResult { + jobId: string; + pages: OCRPage[]; + dataInfo?: Record; +} + +export interface DocParsingResult { + jobId: string; + pages: DocParsingPage[]; + dataInfo?: Record; +} + +export interface Progress { + totalPages: number; + extractedPages: number; + startTime?: string; + endTime?: string; +} + +export interface Job { + jobId: string; + model: string; + task: "ocr" | "document_parsing"; + pageRanges?: string; + batchId?: string; +} + +export interface JobStatus { + jobId: string; + state: "pending" | "running" | "done" | "failed"; + progress?: Progress; + resultUrl?: Record; + errorMsg?: string; +} + +export interface BatchStatus { + batchId: string; + jobs: JobStatus[]; +} diff --git a/api_sdk/typescript/tests/client.test.ts b/api_sdk/typescript/tests/client.test.ts new file mode 100644 index 0000000..1167c67 --- /dev/null +++ b/api_sdk/typescript/tests/client.test.ts @@ -0,0 +1,703 @@ +import { mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, test, vi } from "vitest"; +import { + APIError, + AuthError, + FileNotFoundError, + InvalidRequestError, + JobFailedError, + Model, + PaddleOCRClient, + PollTimeoutError, + RequestTimeoutError, + ResponseFormatError, + ResultParseError, +} from "../src/index.js"; +import type { DocParsingResult, Job, OCRResult } from "../src/index.js"; + +type FetchCall = { + url: string; + init: RequestInit; +}; + +function jsonResponse(body: unknown, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +function textResponse(body: string, status = 200): Response { + return new Response(body, { status }); +} + +function createClient(fetchImpl: typeof fetch, options: Partial[0]> = {}) { + return new PaddleOCRClient({ + token: "test-token", + baseUrl: "https://api.example.test", + requestTimeout: 100, + pollTimeout: 100, + fetch: fetchImpl, + ...options, + }); +} + +function captureFetch(responses: Array>): { fetch: typeof fetch; calls: FetchCall[] } { + const calls: FetchCall[] = []; + const fetchImpl = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { + calls.push({ url: String(input), init: init ?? {} }); + const next = responses.shift(); + if (next instanceof Error) { + throw next; + } + if (!next) { + throw new Error("No mocked response remaining"); + } + return next; + }) as unknown as typeof fetch; + return { fetch: fetchImpl, calls }; +} + +afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); +}); + +describe("PaddleOCRClient public contract", () => { + test("requires token at construction and reads PADDLEOCR_ACCESS_TOKEN fallback", () => { + const previous = process.env.PADDLEOCR_ACCESS_TOKEN; + delete process.env.PADDLEOCR_ACCESS_TOKEN; + expect(() => new PaddleOCRClient()).toThrow(AuthError); + + process.env.PADDLEOCR_ACCESS_TOKEN = "env-token"; + const { fetch } = captureFetch([jsonResponse({ data: { state: "running" } })]); + expect(() => new PaddleOCRClient({ baseUrl: "https://api.example.test", fetch })).not.toThrow(); + + if (previous === undefined) { + delete process.env.PADDLEOCR_ACCESS_TOKEN; + } else { + process.env.PADDLEOCR_ACCESS_TOKEN = previous; + } + }); + + test("exposes contract names and omits unpublished draft aliases", () => { + const { fetch } = captureFetch([]); + const client = createClient(fetch); + expect(client.getStatus).toBeTypeOf("function"); + expect(client.parseDocument).toBeTypeOf("function"); + expect(client.submitDocumentParsing).toBeTypeOf("function"); + expect(client.waitOcrResult).toBeTypeOf("function"); + expect(client.waitDocumentParsingResult).toBeTypeOf("function"); + expect(client.saveResource).toBeTypeOf("function"); + expect(client.saveOcrResultResources).toBeTypeOf("function"); + expect(client.saveDocumentParsingResultResources).toBeTypeOf("function"); + + expect("getResult" in client).toBe(false); + expect("waitForResult" in client).toBe(false); + expect("docParsing" in client).toBe(false); + expect("submitDocParsing" in client).toBe(false); + }); + + test("submitOcr sends contract body and returns job metadata", async () => { + const { fetch, calls } = captureFetch([jsonResponse({ data: { jobId: "job-1" } })]); + const client = createClient(fetch); + + const job = await client.submitOcr({ + fileUrl: "https://files.example.test/invoice.pdf", + pageRanges: "1-2", + batchId: "batch-1", + options: { visualize: true }, + }); + + expect(job).toEqual({ + jobId: "job-1", + model: Model.PPOCRv6, + task: "ocr", + pageRanges: "1-2", + batchId: "batch-1", + }); + expect(calls[0].url).toBe("https://api.example.test/api/v2/ocr/jobs"); + expect(calls[0].init.method).toBe("POST"); + expect(calls[0].init.headers).toMatchObject({ + Authorization: "Bearer test-token", + "Content-Type": "application/json", + }); + expect(JSON.parse(String(calls[0].init.body))).toEqual({ + fileUrl: "https://files.example.test/invoice.pdf", + model: Model.PPOCRv6, + optionalPayload: { visualize: true }, + pageRanges: "1-2", + batchId: "batch-1", + }); + }); + + test("clientPlatform is sent as Client-Platform header on API requests", async () => { + const { fetch, calls } = captureFetch([jsonResponse({ data: { jobId: "job-1" } })]); + const client = createClient(fetch, { clientPlatform: "my-app" }); + + await client.submitOcr({ + fileUrl: "https://files.example.test/invoice.pdf", + }); + + expect(calls[0].init.headers).toMatchObject({ + "Client-Platform": "my-app", + }); + }); + + test("submitOcr propagates explicit OCR model and defaults to PP-OCRv6", async () => { + const { fetch, calls } = captureFetch([ + jsonResponse({ data: { jobId: "job-explicit" } }), + jsonResponse({ data: { jobId: "job-default" } }), + ]); + const client = createClient(fetch); + + const explicit = await client.submitOcr({ + model: Model.PPOCRv5, + fileUrl: "https://files.example.test/explicit.pdf", + }); + const implicit = await client.submitOcr({ + fileUrl: "https://files.example.test/default.pdf", + }); + + expect(explicit.model).toBe(Model.PPOCRv5); + expect(implicit.model).toBe(Model.PPOCRv6); + expect(JSON.parse(String(calls[0].init.body)).model).toBe(Model.PPOCRv5); + expect(JSON.parse(String(calls[1].init.body)).model).toBe(Model.PPOCRv6); + }); + + test("submitOcr accepts PP-OCRv6 model name", async () => { + const { fetch, calls } = captureFetch([jsonResponse({ data: { jobId: "job-v6" } })]); + const client = createClient(fetch); + + const job = await client.submitOcr({ + model: Model.PPOCRv6, + fileUrl: "https://files.example.test/v6.pdf", + }); + + expect(job.model).toBe(Model.PPOCRv6); + expect(JSON.parse(String(calls[0].init.body)).model).toBe(Model.PPOCRv6); + }); + + test("submitOcr accepts PP-OCRv5-latin model name", async () => { + const { fetch, calls } = captureFetch([jsonResponse({ data: { jobId: "job-latin" } })]); + const client = createClient(fetch); + + const job = await client.submitOcr({ + model: Model.PPOCRv5Latin, + fileUrl: "https://files.example.test/latin.pdf", + }); + + expect(job.model).toBe(Model.PPOCRv5Latin); + expect(JSON.parse(String(calls[0].init.body)).model).toBe(Model.PPOCRv5Latin); + }); + + test("submitOcr and submitDocumentParsing accept official model name strings", async () => { + const { fetch, calls } = captureFetch([ + jsonResponse({ data: { jobId: "job-ocr" } }), + jsonResponse({ data: { jobId: "job-doc" } }), + ]); + const client = createClient(fetch); + + const ocrJob = await client.submitOcr({ + model: "PP-OCRv5", + fileUrl: "https://files.example.test/ocr.pdf", + }); + const docJob = await client.submitDocumentParsing({ + model: "PaddleOCR-VL-1.6", + fileUrl: "https://files.example.test/doc.pdf", + }); + + expect(ocrJob.model).toBe("PP-OCRv5"); + expect(docJob.model).toBe("PaddleOCR-VL-1.6"); + expect(JSON.parse(String(calls[0].init.body)).model).toBe("PP-OCRv5"); + expect(JSON.parse(String(calls[1].init.body)).model).toBe("PaddleOCR-VL-1.6"); + }); + + test("model helpers classify current OCR and document parsing models", async () => { + const mod = await import("../src/index.js"); + + expect(mod.isOCRModel(Model.PPOCRv5)).toBe(true); + expect(mod.isOCRModel(Model.PPOCRv5Latin)).toBe(true); + expect(mod.isOCRModel(Model.PPOCRv6)).toBe(true); + expect(mod.isOCRModel(Model.PPStructureV3)).toBe(false); + expect(mod.isOCRModel("future-unknown-model")).toBe(false); + expect(mod.isDocumentParsingModel(Model.PaddleOCRVL)).toBe(true); + expect(mod.isDocumentParsingModel(Model.PaddleOCRVL16)).toBe(true); + expect(mod.isDocumentParsingModel(Model.PPOCRv5)).toBe(false); + }); + + test("submitDocumentParsing passes current and future document parsing options", async () => { + const { fetch, calls } = captureFetch([jsonResponse({ data: { jobId: "job-doc" } })]); + const client = createClient(fetch); + + await client.submitDocumentParsing({ + model: Model.PaddleOCRVL16, + fileUrl: "https://files.example.test/doc.pdf", + options: { + useOcrForImageBlock: true, + formatBlockContent: true, + markdownIgnoreLabels: ["image"], + vlmExtraArgs: { temperature: 0.1 }, + returnMarkdownImages: false, + outputFormats: ["docx"], + futureOption: "enabled", + }, + }); + + expect(JSON.parse(String(calls[0].init.body)).optionalPayload).toEqual({ + useOcrForImageBlock: true, + formatBlockContent: true, + markdownIgnoreLabels: ["image"], + vlmExtraArgs: { temperature: 0.1 }, + returnMarkdownImages: false, + outputFormats: ["docx"], + futureOption: "enabled", + }); + }); + + test("submitDocumentParsing defaults to PaddleOCR-VL-1.6", async () => { + const { fetch, calls } = captureFetch([jsonResponse({ data: { jobId: "job-doc" } })]); + const client = createClient(fetch); + + const job = await client.submitDocumentParsing({ + fileUrl: "https://files.example.test/doc.pdf", + }); + + expect(job.model).toBe(Model.PaddleOCRVL16); + expect(JSON.parse(String(calls[0].init.body)).model).toBe(Model.PaddleOCRVL16); + }); + + test("submitOcr rejects non-OCR models before network calls", async () => { + const { fetch } = captureFetch([]); + const client = createClient(fetch); + + await expect( + client.submitOcr({ + model: Model.PPStructureV3, + fileUrl: "https://files.example.test/not-ocr.pdf", + }), + ).rejects.toThrow(InvalidRequestError); + expect(fetch).not.toHaveBeenCalled(); + }); + + test("validates submit requests before network calls", async () => { + const { fetch } = captureFetch([]); + const client = createClient(fetch); + + await expect(client.submitOcr({})).rejects.toThrow(InvalidRequestError); + await expect( + client.submitOcr({ fileUrl: "https://files.example.test/a.pdf", filePath: "./a.pdf" }), + ).rejects.toThrow(InvalidRequestError); + expect(fetch).not.toHaveBeenCalled(); + }); + + test("getStatus performs one non-blocking status request", async () => { + const { fetch, calls } = captureFetch([ + jsonResponse({ + data: { + state: "running", + extractProgress: { totalPages: 3, extractedPages: 1 }, + }, + }), + ]); + const client = createClient(fetch); + + await expect(client.getStatus("job-1")).resolves.toEqual({ + jobId: "job-1", + state: "running", + progress: { totalPages: 3, extractedPages: 1 }, + errorMsg: undefined, + }); + expect(calls).toHaveLength(1); + expect(calls[0].url).toBe("https://api.example.test/api/v2/ocr/jobs/job-1"); + }); + + test("normalizes multiple trailing baseUrl slashes", async () => { + const { fetch, calls } = captureFetch([jsonResponse({ data: { state: "running" } })]); + const client = createClient(fetch, { baseUrl: "https://api.example.test///" }); + + await client.getStatus("job-1"); + + expect(calls[0].url).toBe("https://api.example.test/api/v2/ocr/jobs/job-1"); + }); + + test("typed wait accepts bare jobId, fetches JSONL without Authorization, and parses OCR results", async () => { + const { fetch, calls } = captureFetch([ + jsonResponse({ data: { state: "done", resultUrl: { jsonUrl: "https://storage.example.test/job-1.jsonl" } } }), + textResponse( + JSON.stringify({ + result: { + dataInfo: { numPages: 1 }, + ocrResults: [ + { + prunedResult: { text: "hello" }, + ocrImage: "img.png", + docPreprocessingImage: "pre.png", + inputImage: "input.png", + }, + ], + }, + }), + ), + ]); + const client = createClient(fetch); + + await expect(client.waitOcrResult("job-1")).resolves.toMatchObject({ + jobId: "job-1", + dataInfo: { numPages: 1 }, + pages: [ + { + prunedResult: { text: "hello" }, + ocrImageUrl: "img.png", + docPreprocessingImageUrl: "pre.png", + inputImageUrl: "input.png", + raw: { + prunedResult: { text: "hello" }, + ocrImage: "img.png", + docPreprocessingImage: "pre.png", + inputImage: "input.png", + }, + }, + ], + }); + expect(calls[1].url).toBe("https://storage.example.test/job-1.jsonl"); + expect((calls[1].init.headers as Record).Authorization).toBeUndefined(); + }); + + test("typed waits reject mismatched Job task before polling", async () => { + const { fetch } = captureFetch([]); + const client = createClient(fetch); + const docJob: Job = { jobId: "job-1", model: Model.PPStructureV3, task: "document_parsing" }; + + await expect(client.waitOcrResult(docJob)).rejects.toThrow(InvalidRequestError); + expect(fetch).not.toHaveBeenCalled(); + }); + + test("typed waits reject mismatched Job model before polling", async () => { + const { fetch } = captureFetch([]); + const client = createClient(fetch); + const ocrTaskWithDocumentModel: Job = { jobId: "job-1", model: Model.PPStructureV3, task: "ocr" }; + const documentTaskWithOcrModel: Job = { jobId: "job-2", model: Model.PPOCRv5, task: "document_parsing" }; + + await expect(client.waitOcrResult(ocrTaskWithDocumentModel)).rejects.toThrow(InvalidRequestError); + await expect(client.waitDocumentParsingResult(documentTaskWithOcrModel)).rejects.toThrow(InvalidRequestError); + expect(fetch).not.toHaveBeenCalled(); + }); + + test("polling maps failed, timeout, unknown state, and missing result URL", async () => { + const failed = createClient(captureFetch([jsonResponse({ data: { state: "failed", errorMsg: "boom" } })]).fetch); + await expect(failed.waitOcrResult("job-1")).rejects.toThrow(JobFailedError); + + const unknown = createClient(captureFetch([jsonResponse({ data: { state: "paused" } })]).fetch); + await expect(unknown.waitOcrResult("job-1")).rejects.toThrow(ResponseFormatError); + + const missingResult = createClient(captureFetch([jsonResponse({ data: { state: "done" } })]).fetch); + await expect(missingResult.waitOcrResult("job-1")).rejects.toThrow(ResponseFormatError); + + vi.useFakeTimers(); + const timeoutFetch = vi.fn(async () => jsonResponse({ data: { state: "running" } })) as unknown as typeof fetch; + const timeout = createClient(timeoutFetch, { pollTimeout: 1 }); + const promise = expect(timeout.waitOcrResult("job-1")).rejects.toThrow(PollTimeoutError); + await vi.runAllTimersAsync(); + await promise; + }); + + test("malformed successful responses and malformed JSONL use dedicated errors", async () => { + const malformedSubmit = createClient(captureFetch([jsonResponse({ data: {} })]).fetch); + await expect(malformedSubmit.submitOcr({ fileUrl: "https://files.example.test/a.pdf" })).rejects.toThrow( + ResponseFormatError, + ); + + const malformedJsonl = createClient( + captureFetch([ + jsonResponse({ data: { state: "done", resultUrl: { jsonUrl: "https://storage.example.test/job.jsonl" } } }), + textResponse("{not-json}\n"), + ]).fetch, + ); + await expect(malformedJsonl.waitOcrResult("job-1")).rejects.toThrow(ResultParseError); + }); + + test("malformed fetched OCR result records use ResultParseError", async () => { + const client = createClient( + captureFetch([ + jsonResponse({ data: { state: "done", resultUrl: { jsonUrl: "https://storage.example.test/job.jsonl" } } }), + textResponse(JSON.stringify({ notResult: {} })), + ]).fetch, + ); + + await expect(client.waitOcrResult("job-1")).rejects.toThrow(ResultParseError); + }); + + test.each([ + ["missing ocrResults", { result: {} }], + ["missing prunedResult", { result: { ocrResults: [{ ocrImage: "img.png" }] } }], + ])("malformed OCR payload with %s uses ResultParseError", async (_name, payload) => { + const client = createClient( + captureFetch([ + jsonResponse({ data: { state: "done", resultUrl: { jsonUrl: "https://storage.example.test/job.jsonl" } } }), + textResponse(JSON.stringify(payload)), + ]).fetch, + ); + + await expect(client.waitOcrResult("job-1")).rejects.toThrow(ResultParseError); + }); + + test("waitDocumentParsingResult preserves raw page data and dataInfo", async () => { + const page = { + prunedResult: { blocks: [{ label: "text", content: "hello" }] }, + markdown: { text: "hello", images: { "figure.png": "https://example.test/figure.png" }, isStart: true, isEnd: true }, + outputImages: { "page.png": "https://example.test/page.png" }, + inputImage: "https://example.test/input.png", + exports: { docx: "https://example.test/result.docx" }, + }; + const { fetch } = captureFetch([ + jsonResponse({ data: { state: "done", resultUrl: { jsonUrl: "https://storage.example.test/job.jsonl" } } }), + textResponse(JSON.stringify({ result: { dataInfo: { numPages: 1 }, layoutParsingResults: [page] } })), + ]); + const client = createClient(fetch); + + await expect(client.waitDocumentParsingResult("job-1")).resolves.toMatchObject({ + jobId: "job-1", + dataInfo: { numPages: 1 }, + pages: [ + { + markdownText: "hello", + markdownImages: { "figure.png": "https://example.test/figure.png" }, + outputImages: { "page.png": "https://example.test/page.png" }, + prunedResult: { blocks: [{ label: "text", content: "hello" }] }, + inputImageUrl: "https://example.test/input.png", + exports: { docx: "https://example.test/result.docx" }, + markdown: page.markdown, + raw: page, + }, + ], + }); + }); + + test("malformed fetched document result records use ResultParseError", async () => { + const client = createClient( + captureFetch([ + jsonResponse({ data: { state: "done", resultUrl: { jsonUrl: "https://storage.example.test/job.jsonl" } } }), + textResponse(JSON.stringify({ notResult: {} })), + ]).fetch, + ); + + await expect(client.waitDocumentParsingResult("job-1")).rejects.toThrow(ResultParseError); + }); + + test.each([ + ["missing layoutParsingResults", { result: {} }], + ["missing markdown", { result: { layoutParsingResults: [{}] } }], + ["missing markdown.text", { result: { layoutParsingResults: [{ markdown: { images: {} } }] } }], + ])("malformed document payload with %s uses ResultParseError", async (_name, payload) => { + const client = createClient( + captureFetch([ + jsonResponse({ data: { state: "done", resultUrl: { jsonUrl: "https://storage.example.test/job.jsonl" } } }), + textResponse(JSON.stringify(payload)), + ]).fetch, + ); + + await expect(client.waitDocumentParsingResult("job-1")).rejects.toThrow(ResultParseError); + }); + + test("HTTP status and timeout failures map to contract errors", async () => { + await expect(createClient(captureFetch([textResponse("bad auth", 401)]).fetch).getStatus("job-1")).rejects.toThrow( + AuthError, + ); + await expect(createClient(captureFetch([textResponse("bad request", 400)]).fetch).getStatus("job-1")).rejects.toThrow( + InvalidRequestError, + ); + await expect(createClient(captureFetch([textResponse("server error", 500)]).fetch).getStatus("job-1")).rejects.toThrow( + APIError, + ); + + const timeoutFetch = vi.fn((_input: RequestInfo | URL, init?: RequestInit) => + new Promise((_resolve, reject) => { + init?.signal?.addEventListener("abort", () => reject(new DOMException("aborted", "AbortError"))); + }), + ) as unknown as typeof fetch; + await expect(createClient(timeoutFetch, { requestTimeout: 1 }).getStatus("job-1")).rejects.toThrow( + RequestTimeoutError, + ); + }); + + test("submitFile reports missing local files before network calls", async () => { + const { fetch } = captureFetch([]); + const client = createClient(fetch); + + await expect(client.submitOcr({ filePath: "/definitely/missing/file.pdf" })).rejects.toThrow(FileNotFoundError); + expect(fetch).not.toHaveBeenCalled(); + }); + + test("saveResource writes URL downloads without auth and requires overwrite opt-in", async () => { + const dir = await mkdtemp(join(tmpdir(), "paddleocr-api-sdk-")); + try { + const { fetch, calls } = captureFetch([textResponse("content"), textResponse("replacement")]); + const client = createClient(fetch); + + const saved = await client.saveResource("https://storage.example.test/a/b/c.png", dir); + expect(saved).toBe(join(dir, "c.png")); + await expect(readFile(join(dir, "c.png"), "utf8")).resolves.toBe("content"); + expect((calls[0].init.headers as Record).Authorization).toBeUndefined(); + + await expect(client.saveResource("https://storage.example.test/a/b/c.png", dir)).rejects.toThrow( + InvalidRequestError, + ); + + await client.saveResource("https://storage.example.test/a/b/c.png", dir, { overwrite: true }); + await expect(readFile(join(dir, "c.png"), "utf8")).resolves.toBe("replacement"); + await expect(stat(join(dir, "c.png"))).resolves.toBeTruthy(); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + test("saveOcrResultResources saves OCR result image URLs with stable page filenames", async () => { + const dir = await mkdtemp(join(tmpdir(), "paddleocr-api-sdk-")); + try { + const { fetch, calls } = captureFetch([textResponse("page 1"), textResponse("page 2")]); + const client = createClient(fetch); + const result: OCRResult = { + jobId: "job-ocr", + pages: [ + { prunedResult: { text: "one" }, ocrImageUrl: "https://storage.example.test/results/page-a.png?sig=opaque" }, + { prunedResult: { text: "two" }, ocrImageUrl: "https://storage.example.test/results/page-b.jpg" }, + ], + }; + + const saved = await client.saveOcrResultResources(result, dir); + + expect(saved).toEqual([join(dir, "ocr-page-1.png"), join(dir, "ocr-page-2.jpg")]); + await expect(readFile(join(dir, "ocr-page-1.png"), "utf8")).resolves.toBe("page 1"); + await expect(readFile(join(dir, "ocr-page-2.jpg"), "utf8")).resolves.toBe("page 2"); + expect(calls).toHaveLength(2); + expect(calls.map((call) => (call.init.headers as Record).Authorization)).toEqual([ + undefined, + undefined, + ]); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + test("saveDocumentParsingResultResources preserves safe document parsing map keys over opaque URL basenames", async () => { + const dir = await mkdtemp(join(tmpdir(), "paddleocr-api-sdk-")); + try { + const { fetch } = captureFetch([textResponse("markdown image"), textResponse("output image")]); + const client = createClient(fetch); + const result: DocParsingResult = { + jobId: "job-doc", + pages: [ + { + markdownText: "![figure](figure 1.png)", + markdownImages: { + "figure 1.png": "https://storage.example.test/download?id=markdown-image", + }, + outputImages: { + "rendered-page.jpg": "https://storage.example.test/blob?id=output-image", + }, + }, + ], + }; + + const saved = await client.saveDocumentParsingResultResources(result, dir); + + expect(saved).toEqual([join(dir, "figure 1.png"), join(dir, "rendered-page.jpg")]); + await expect(readFile(join(dir, "figure 1.png"), "utf8")).resolves.toBe("markdown image"); + await expect(readFile(join(dir, "rendered-page.jpg"), "utf8")).resolves.toBe("output image"); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + test("saveDocumentParsingResultResources sorts document parsing map keys for deterministic output", async () => { + const dir = await mkdtemp(join(tmpdir(), "paddleocr-api-sdk-")); + try { + const { fetch, calls } = captureFetch([textResponse("alpha"), textResponse("bravo"), textResponse("charlie")]); + const client = createClient(fetch); + const result: DocParsingResult = { + jobId: "job-doc", + pages: [ + { + markdownText: "", + markdownImages: { + "charlie.png": "https://storage.example.test/charlie", + "alpha.png": "https://storage.example.test/alpha", + "bravo.png": "https://storage.example.test/bravo", + }, + outputImages: {}, + }, + ], + }; + + const saved = await client.saveDocumentParsingResultResources(result, dir); + + expect(saved).toEqual([join(dir, "alpha.png"), join(dir, "bravo.png"), join(dir, "charlie.png")]); + expect(calls.map((call) => call.url)).toEqual([ + "https://storage.example.test/alpha", + "https://storage.example.test/bravo", + "https://storage.example.test/charlie", + ]); + await expect(readFile(join(dir, "alpha.png"), "utf8")).resolves.toBe("alpha"); + await expect(readFile(join(dir, "bravo.png"), "utf8")).resolves.toBe("bravo"); + await expect(readFile(join(dir, "charlie.png"), "utf8")).resolves.toBe("charlie"); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + test("saveOcrResultResources requires overwrite opt-in for bulk result files", async () => { + const dir = await mkdtemp(join(tmpdir(), "paddleocr-api-sdk-")); + try { + const result: OCRResult = { + jobId: "job-ocr", + pages: [{ prunedResult: {}, ocrImageUrl: "https://storage.example.test/page.png" }], + }; + + await writeFile(join(dir, "ocr-page-1.png"), "existing"); + const blocked = captureFetch([textResponse("unexpected")]); + await expect(createClient(blocked.fetch).saveOcrResultResources(result, dir)).rejects.toThrow(InvalidRequestError); + expect(blocked.calls).toHaveLength(0); + + const replacement = captureFetch([textResponse("replacement")]); + await createClient(replacement.fetch).saveOcrResultResources(result, dir, { overwrite: true }); + await expect(readFile(join(dir, "ocr-page-1.png"), "utf8")).resolves.toBe("replacement"); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + test.each([ + ["empty", ""], + ["absolute", "/absolute.png"], + ["parent traversal", "../escape.png"], + ["traversal segment", ".."], + ["forward separator", "nested/escape.png"], + ["backslash separator", "nested\\escape.png"], + ])("saveDocumentParsingResultResources rejects unsafe document parsing map key: %s", async (_name, unsafeKey) => { + const dir = await mkdtemp(join(tmpdir(), "paddleocr-api-sdk-")); + try { + const { fetch } = captureFetch([]); + const client = createClient(fetch); + const result: DocParsingResult = { + jobId: "job-doc", + pages: [ + { + markdownText: "", + markdownImages: { + [unsafeKey]: "https://storage.example.test/escape.png", + }, + outputImages: {}, + }, + ], + }; + + await expect(client.saveDocumentParsingResultResources(result, dir)).rejects.toThrow(InvalidRequestError); + expect(fetch).not.toHaveBeenCalled(); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/api_sdk/typescript/tsconfig.json b/api_sdk/typescript/tsconfig.json new file mode 100644 index 0000000..9eb3140 --- /dev/null +++ b/api_sdk/typescript/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM"], + "types": ["node"], + "outDir": "./dist", + "declaration": true, + "strict": true, + "esModuleInterop": true, + "baseUrl": ".", + "paths": { + "@paddleocr/api-sdk": ["src/index.ts"] + }, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests", "examples"] +} diff --git a/api_sdk/typescript/tsup.config.ts b/api_sdk/typescript/tsup.config.ts new file mode 100644 index 0000000..328eeed --- /dev/null +++ b/api_sdk/typescript/tsup.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm", "cjs"], + dts: true, + clean: true, + splitting: false, +}); diff --git a/applications/README.md b/applications/README.md new file mode 100644 index 0000000..a0a7595 --- /dev/null +++ b/applications/README.md @@ -0,0 +1 @@ +移步[docs](https://www.paddleocr.ai/v2.10.0/applications/overview.html) diff --git a/awesome_projects.md b/awesome_projects.md new file mode 100644 index 0000000..ac0c87c --- /dev/null +++ b/awesome_projects.md @@ -0,0 +1,30 @@ +## 😃 Awesome projects based on PaddleOCR +💗 PaddleOCR wouldn’t be where it is today without its incredible community! A massive 🙌 thank you 🙌 to all our longtime partners, new collaborators, and everyone who’s poured their passion into PaddleOCR — whether we’ve named you or not. Your support fuels our fire! 🔥 +| Project Name | Description | +| ------------ | ----------- | +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |Free, Open-source, Batch Offline OCR Software.| +| [LearnOpenCV](http://github.com/spmallick/learnopencv) | code for Computer Vision, Deep learning, and AI research articles.| +| [OmniParser](https://github.com/microsoft/OmniParser) |OmniParser: Screen Parsing tool for Pure Vision Based GUI Agent.| +| [QAnything](https://github.com/netease-youdao/QAnything) |Question and Answer based on Anything.| +| [PaddleHub](https://github.com/PaddlePaddle/PaddleHub) |400+ AI Models: Rich, high-quality AI models, including CV, NLP, Speech, Video and Cross-Modal.| +| [PaddleNLP](https://github.com/PaddlePaddle/PaddleNLP) |A Large Language Model (LLM) development suite based on the PaddlePaddle.| +| [Rerun](https://github.com/rerun-io/rerun) | Rerun is building the multimodal data stack to model, ingest, store, query and view robotics-style data | +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) | Recognize text on the screen, translate it and show the translation results in real time.| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) | PDF-Extract-Kit is a powerful open-source toolkit designed to efficiently extract high-quality content from complex and diverse PDF documents. | +| [manga-image-translator](https://github.com/zyddnys/manga-image-translator) | Translate texts in manga/images.| +| [March7thAssistant](https://github.com/moesnow/March7thAssistant) | Daily Tasks: Stamina recovery, daily training, claiming rewards, commissions, and farming. | +| [PaddlePaddle/models](https://github.com/PaddlePaddle/models) |PaddlePaddle's industrial-grade model zoo.| +| [katanaml/sparrow](https://github.com/katanaml/sparrow) | Sparrow is an innovative open-source solution for efficient data extraction and processing from various documents and images. | +| [RapidOCR](https://github.com/RapidAI/RapidOCR) | Awesome OCR multiple programming languages toolkits based on ONNXRuntime, OpenVINO, PaddlePaddle and PyTorch | +| [autoMate](https://github.com/yuruotong1/autoMate) | AI-Powered Local Automation Tool & Let Your Computer Work for You. | +| [Agent-S](https://github.com/simular-ai/Agent-S) | A Compositional Generalist-Specialist Framework for Computer Use Agents. | +| [pdf-craft](https://github.com/oomol-lab/pdf-craft) | PDF Craft can convert PDF files into various other formats. | +| [VV](https://github.com/Cicada000/VV) | Zhang Weiwei Quotations Search Project. | +| [docetl](https://github.com/ucbepic/docetl) | DocETL is a tool for creating and executing data processing pipelines, especially suited for complex document processing tasks. | +| [ZenlessZoneZero-Auto](https://github.com/sMythicalBird/ZenlessZoneZero-Auto) | Zenless Zone Zero Automation Framework. | +| [Yuxi-Know](https://github.com/xerrors/Yuxi-Know) | Knowledge graph question answering system based on LLMs. | +| [PaddleSharp](https://github.com/sdcb/PaddleSharp) |.NET/C# binding for Baidu paddle inference library and PaddleOCR | +| [python-office](https://github.com/CoderWanFeng/python-office) | Python tool for office works. | +| [OnnxOCR](https://github.com/jingsongliujing/OnnxOCR) |A lightweight OCR system based on PaddleOCR, decoupled from the PaddlePaddle deep learning training framework, with ultra-fast inference speed | +| [Frigate](https://github.com/blakeblackshear/frigate) | Real-time NVR system with AI-powered object detection and License Plate Recognition (LPR) using PaddleOCR. | +| ... |... | diff --git a/benchmark/PaddleOCR_DBNet/.gitattributes b/benchmark/PaddleOCR_DBNet/.gitattributes new file mode 100644 index 0000000..b4419d4 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/.gitattributes @@ -0,0 +1,2 @@ +*.html linguist-language=python +*.ipynb linguist-language=python diff --git a/benchmark/PaddleOCR_DBNet/.gitignore b/benchmark/PaddleOCR_DBNet/.gitignore new file mode 100644 index 0000000..f18fe10 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/.gitignore @@ -0,0 +1,16 @@ +.DS_Store +*.pth +*.pyc +*.pyo +*.log +*.tmp +*.pkl +__pycache__/ +.idea/ +output/ +test/*.jpg +datasets/ +index/ +train_log/ +log/ +profiling_log/ diff --git a/benchmark/PaddleOCR_DBNet/LICENSE.md b/benchmark/PaddleOCR_DBNet/LICENSE.md new file mode 100644 index 0000000..b09cd78 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/LICENSE.md @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/benchmark/PaddleOCR_DBNet/README.MD b/benchmark/PaddleOCR_DBNet/README.MD new file mode 100644 index 0000000..81e64bc --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/README.MD @@ -0,0 +1,132 @@ +# Real-time Scene Text Detection with Differentiable Binarization + +**note**: some code is inherited from [WenmuZhou/DBNet.pytorch](https://github.com/WenmuZhou/DBNet.pytorch) + +[中文解读](https://zhuanlan.zhihu.com/p/94677957) + +![network](imgs/paper/db.jpg) + +## update +2020-06-07: 添加灰度图训练,训练灰度图时需要在配置里移除`dataset.args.transforms.Normalize` + +## Install Using Conda +``` +conda env create -f environment.yml +git clone https://github.com/WenmuZhou/DBNet.paddle.git +cd DBNet.paddle/ +``` + +or +## Install Manually +```bash +conda create -n dbnet python=3.6 +conda activate dbnet + +conda install ipython pip + +# python dependencies +pip install -r requirement.txt + +# clone repo +git clone https://github.com/WenmuZhou/DBNet.paddle.git +cd DBNet.paddle/ + +``` + +## Requirements +* paddlepaddle 2.4+ + +## Download + +TBD + +## Data Preparation + +Training data: prepare a text `train.txt` in the following format, use '\t' as a separator +``` +./datasets/train/img/001.jpg ./datasets/train/gt/001.txt +``` + +Validation data: prepare a text `test.txt` in the following format, use '\t' as a separator +``` +./datasets/test/img/001.jpg ./datasets/test/gt/001.txt +``` +- Store images in the `img` folder +- Store groundtruth in the `gt` folder + +The groundtruth can be `.txt` files, with the following format: +``` +x1, y1, x2, y2, x3, y3, x4, y4, annotation +``` + + +## Train +1. config the `dataset['train']['dataset'['data_path']'`,`dataset['validate']['dataset'['data_path']`in [config/icdar2015_resnet18_fpn_DBhead_polyLR.yaml](cconfig/icdar2015_resnet18_fpn_DBhead_polyLR.yaml) +* . single gpu train +```bash +bash single_gpu_train.sh +``` +* . Multi-gpu training +```bash +bash multi_gpu_train.sh +``` +## Test + +[eval.py](tools/eval.py) is used to test model on test dataset + +1. config `model_path` in [eval.sh](eval.sh) +2. use following script to test +```bash +bash eval.sh +``` + +## Predict +[predict.py](tools/predict.py) Can be used to inference on all images in a folder +1. config `model_path`,`input_folder`,`output_folder` in [predict.sh](predict.sh) +2. use following script to predict +``` +bash predict.sh +``` +You can change the `model_path` in the `predict.sh` file to your model location. + +tips: if result is not good, you can change `thre` in [predict.sh](predict.sh) + +## Export Model + +[export_model.py](tools/export_model.py) Can be used to inference on all images in a folder + +use following script to export inference model +``` +python tools/export_model.py --config_file config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml -o trainer.resume_checkpoint=model_best.pth trainer.output_dir=output/infer +``` + +## Paddle Inference infer + +[infer.py](tools/infer.py) Can be used to inference on all images in a folder + +use following script to export inference model +``` +python tools/infer.py --model-dir=output/infer/ --img-path imgs/paper/db.jpg +``` + +

Performance

+ +### [ICDAR 2015](http://rrc.cvc.uab.es/?ch=4) +only train on ICDAR2015 dataset + +| Method | image size (short size) |learning rate | Precision (%) | Recall (%) | F-measure (%) | FPS | +|:--------------------------:|:-------:|:--------:|:--------:|:------------:|:---------------:|:-----:| +| ImageNet-resnet50-FPN-DBHead(torch) |736 |1e-3|90.19 | 78.14 | 83.88 | 27 | +| ImageNet-resnet50-FPN-DBHead(paddle) |736 |1e-3| 89.47 | 79.03 | 83.92 | 27 | +| ImageNet-resnet50-FPN-DBHead(paddle_amp) |736 |1e-3| 88.62 | 79.95 | 84.06 | 27 | + + +### examples +TBD + + +### reference +1. https://arxiv.org/pdf/1911.08947.pdf +2. https://github.com/WenmuZhou/DBNet.pytorch + +**If this repository helps you,please star it. Thanks.** diff --git a/benchmark/PaddleOCR_DBNet/base/__init__.py b/benchmark/PaddleOCR_DBNet/base/__init__.py new file mode 100644 index 0000000..5d7b417 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/base/__init__.py @@ -0,0 +1,2 @@ +from .base_trainer import BaseTrainer +from .base_dataset import BaseDataSet diff --git a/benchmark/PaddleOCR_DBNet/base/base_dataset.py b/benchmark/PaddleOCR_DBNet/base/base_dataset.py new file mode 100644 index 0000000..e9f6c4d --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/base/base_dataset.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/4 13:12 +# @Author : zhoujun +import copy +from paddle.io import Dataset +from data_loader.modules import * + + +class BaseDataSet(Dataset): + def __init__( + self, + data_path: str, + img_mode, + pre_processes, + filter_keys, + ignore_tags, + transform=None, + target_transform=None, + ): + assert img_mode in ["RGB", "BRG", "GRAY"] + self.ignore_tags = ignore_tags + self.data_list = self.load_data(data_path) + item_keys = ["img_path", "img_name", "text_polys", "texts", "ignore_tags"] + for item in item_keys: + assert ( + item in self.data_list[0] + ), "data_list from load_data must contains {}".format(item_keys) + self.img_mode = img_mode + self.filter_keys = filter_keys + self.transform = transform + self.target_transform = target_transform + self._init_pre_processes(pre_processes) + + def _init_pre_processes(self, pre_processes): + self.aug = [] + if pre_processes is not None: + for aug in pre_processes: + if "args" not in aug: + args = {} + else: + args = aug["args"] + if isinstance(args, dict): + cls = eval(aug["type"])(**args) + else: + cls = eval(aug["type"])(args) + self.aug.append(cls) + + def load_data(self, data_path: str) -> list: + """ + 把数据加载为一个list: + :params data_path: 存储数据的文件夹或者文件 + return a dict ,包含了,'img_path','img_name','text_polys','texts','ignore_tags' + """ + raise NotImplementedError + + def apply_pre_processes(self, data): + for aug in self.aug: + data = aug(data) + return data + + def __getitem__(self, index): + try: + data = copy.deepcopy(self.data_list[index]) + im = cv2.imread(data["img_path"], 1 if self.img_mode != "GRAY" else 0) + if self.img_mode == "RGB": + im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB) + data["img"] = im + data["shape"] = [im.shape[0], im.shape[1]] + data = self.apply_pre_processes(data) + + if self.transform: + data["img"] = self.transform(data["img"]) + data["text_polys"] = data["text_polys"].tolist() + if len(self.filter_keys): + data_dict = {} + for k, v in data.items(): + if k not in self.filter_keys: + data_dict[k] = v + return data_dict + else: + return data + except: + return self.__getitem__(np.random.randint(self.__len__())) + + def __len__(self): + return len(self.data_list) diff --git a/benchmark/PaddleOCR_DBNet/base/base_trainer.py b/benchmark/PaddleOCR_DBNet/base/base_trainer.py new file mode 100644 index 0000000..f0d7f74 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/base/base_trainer.py @@ -0,0 +1,269 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:50 +# @Author : zhoujun + +import os +import pathlib +import shutil +from pprint import pformat + +import anyconfig +import paddle +import numpy as np +import random +from paddle.jit import to_static +from paddle.static import InputSpec + +from utils import setup_logger + + +class BaseTrainer: + def __init__( + self, + config, + model, + criterion, + train_loader, + validate_loader, + metric_cls, + post_process=None, + ): + config["trainer"]["output_dir"] = os.path.join( + str(pathlib.Path(os.path.abspath(__name__)).parent), + config["trainer"]["output_dir"], + ) + config["name"] = config["name"] + "_" + model.name + self.save_dir = config["trainer"]["output_dir"] + self.checkpoint_dir = os.path.join(self.save_dir, "checkpoint") + + os.makedirs(self.checkpoint_dir, exist_ok=True) + + self.global_step = 0 + self.start_epoch = 0 + self.config = config + self.criterion = criterion + # logger and tensorboard + self.visualdl_enable = self.config["trainer"].get("visual_dl", False) + self.epochs = self.config["trainer"]["epochs"] + self.log_iter = self.config["trainer"]["log_iter"] + if paddle.distributed.get_rank() == 0: + anyconfig.dump(config, os.path.join(self.save_dir, "config.yaml")) + self.logger = setup_logger(os.path.join(self.save_dir, "train.log")) + self.logger_info(pformat(self.config)) + + self.model = self.apply_to_static(model) + + # device + if ( + paddle.device.cuda.device_count() > 0 + and paddle.device.is_compiled_with_cuda() + ): + self.with_cuda = True + random.seed(self.config["trainer"]["seed"]) + np.random.seed(self.config["trainer"]["seed"]) + paddle.seed(self.config["trainer"]["seed"]) + else: + self.with_cuda = False + self.logger_info("train with and paddle {}".format(paddle.__version__)) + # metrics + self.metrics = { + "recall": 0, + "precision": 0, + "hmean": 0, + "train_loss": float("inf"), + "best_model_epoch": 0, + } + + self.train_loader = train_loader + if validate_loader is not None: + assert post_process is not None and metric_cls is not None + self.validate_loader = validate_loader + self.post_process = post_process + self.metric_cls = metric_cls + self.train_loader_len = len(train_loader) + + if self.validate_loader is not None: + self.logger_info( + "train dataset has {} samples,{} in dataloader, validate dataset has {} samples,{} in dataloader".format( + len(self.train_loader.dataset), + self.train_loader_len, + len(self.validate_loader.dataset), + len(self.validate_loader), + ) + ) + else: + self.logger_info( + "train dataset has {} samples,{} in dataloader".format( + len(self.train_loader.dataset), self.train_loader_len + ) + ) + + self._initialize_scheduler() + + self._initialize_optimizer() + + # resume or finetune + if self.config["trainer"]["resume_checkpoint"] != "": + self._load_checkpoint( + self.config["trainer"]["resume_checkpoint"], resume=True + ) + elif self.config["trainer"]["finetune_checkpoint"] != "": + self._load_checkpoint( + self.config["trainer"]["finetune_checkpoint"], resume=False + ) + + if self.visualdl_enable and paddle.distributed.get_rank() == 0: + from visualdl import LogWriter + + self.writer = LogWriter(self.save_dir) + + # 混合精度训练 + self.amp = self.config.get("amp", None) + if self.amp == "None": + self.amp = None + if self.amp: + self.amp["scaler"] = paddle.amp.GradScaler( + init_loss_scaling=self.amp.get("scale_loss", 1024), + use_dynamic_loss_scaling=self.amp.get("use_dynamic_loss_scaling", True), + ) + self.model, self.optimizer = paddle.amp.decorate( + models=self.model, + optimizers=self.optimizer, + level=self.amp.get("amp_level", "O2"), + ) + + # 分布式训练 + if paddle.device.cuda.device_count() > 1: + self.model = paddle.DataParallel(self.model) + # make inverse Normalize + self.UN_Normalize = False + for t in self.config["dataset"]["train"]["dataset"]["args"]["transforms"]: + if t["type"] == "Normalize": + self.normalize_mean = t["args"]["mean"] + self.normalize_std = t["args"]["std"] + self.UN_Normalize = True + + def apply_to_static(self, model): + support_to_static = self.config["trainer"].get("to_static", False) + if support_to_static: + specs = None + print("static") + specs = [InputSpec([None, 3, -1, -1])] + model = to_static(model, input_spec=specs) + self.logger_info( + "Successfully to apply @to_static with specs: {}".format(specs) + ) + return model + + def train(self): + """ + Full training logic + """ + for epoch in range(self.start_epoch + 1, self.epochs + 1): + self.epoch_result = self._train_epoch(epoch) + self._on_epoch_finish() + if paddle.distributed.get_rank() == 0 and self.visualdl_enable: + self.writer.close() + self._on_train_finish() + + def _train_epoch(self, epoch): + """ + Training logic for an epoch + + :param epoch: Current epoch number + """ + raise NotImplementedError + + def _eval(self, epoch): + """ + eval logic for an epoch + + :param epoch: Current epoch number + """ + raise NotImplementedError + + def _on_epoch_finish(self): + raise NotImplementedError + + def _on_train_finish(self): + raise NotImplementedError + + def _save_checkpoint(self, epoch, file_name): + """ + Saving checkpoints + + :param epoch: current epoch number + :param log: logging information of the epoch + :param save_best: if True, rename the saved checkpoint to 'model_best.pth.tar' + """ + state_dict = self.model.state_dict() + state = { + "epoch": epoch, + "global_step": self.global_step, + "state_dict": state_dict, + "optimizer": self.optimizer.state_dict(), + "config": self.config, + "metrics": self.metrics, + } + filename = os.path.join(self.checkpoint_dir, file_name) + paddle.save(state, filename) + + def _load_checkpoint(self, checkpoint_path, resume): + """ + Resume from saved checkpoints + :param checkpoint_path: Checkpoint path to be resumed + """ + self.logger_info("Loading checkpoint: {} ...".format(checkpoint_path)) + checkpoint = paddle.load(checkpoint_path) + self.model.set_state_dict(checkpoint["state_dict"]) + if resume: + self.global_step = checkpoint["global_step"] + self.start_epoch = checkpoint["epoch"] + self.config["lr_scheduler"]["args"]["last_epoch"] = self.start_epoch + # self.scheduler.load_state_dict(checkpoint['scheduler']) + self.optimizer.set_state_dict(checkpoint["optimizer"]) + if "metrics" in checkpoint: + self.metrics = checkpoint["metrics"] + self.logger_info( + "resume from checkpoint {} (epoch {})".format( + checkpoint_path, self.start_epoch + ) + ) + else: + self.logger_info("finetune from checkpoint {}".format(checkpoint_path)) + + def _initialize(self, name, module, *args, **kwargs): + module_name = self.config[name]["type"] + module_args = self.config[name].get("args", {}) + assert all( + [k not in module_args for k in kwargs] + ), "Overwriting kwargs given in config file is not allowed" + module_args.update(kwargs) + return getattr(module, module_name)(*args, **module_args) + + def _initialize_scheduler(self): + self.lr_scheduler = self._initialize("lr_scheduler", paddle.optimizer.lr) + + def _initialize_optimizer(self): + self.optimizer = self._initialize( + "optimizer", + paddle.optimizer, + parameters=self.model.parameters(), + learning_rate=self.lr_scheduler, + ) + + def inverse_normalize(self, batch_img): + if self.UN_Normalize: + batch_img[:, 0, :, :] = ( + batch_img[:, 0, :, :] * self.normalize_std[0] + self.normalize_mean[0] + ) + batch_img[:, 1, :, :] = ( + batch_img[:, 1, :, :] * self.normalize_std[1] + self.normalize_mean[1] + ) + batch_img[:, 2, :, :] = ( + batch_img[:, 2, :, :] * self.normalize_std[2] + self.normalize_mean[2] + ) + + def logger_info(self, s): + if paddle.distributed.get_rank() == 0: + self.logger.info(s) diff --git a/benchmark/PaddleOCR_DBNet/config/SynthText.yaml b/benchmark/PaddleOCR_DBNet/config/SynthText.yaml new file mode 100644 index 0000000..8fd511c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/SynthText.yaml @@ -0,0 +1,40 @@ +name: DBNet +dataset: + train: + dataset: + type: SynthTextDataset # 数据集类型 + args: + data_path: ''# SynthTextDataset 根目录 + pre_processes: # 数据的预处理过程,包含augment和标签制作 + - type: IaaAugment # 使用imgaug进行变换 + args: + - {'type':Fliplr, 'args':{'p':0.5}} + - {'type': Affine, 'args':{'rotate':[-10,10]}} + - {'type':Resize,'args':{'size':[0.5,3]}} + - type: EastRandomCropData + args: + size: [640,640] + max_tries: 50 + keep_ratio: true + - type: MakeBorderMap + args: + shrink_ratio: 0.4 + - type: MakeShrinkMap + args: + shrink_ratio: 0.4 + min_text_size: 8 + transforms: # 对图片进行的变换方式 + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + filter_keys: ['img_path','img_name','text_polys','texts','ignore_tags','shape'] # 返回数据之前,从数据字典里删除的key + ignore_tags: ['*', '###'] + loader: + batch_size: 1 + shuffle: true + num_workers: 0 + collate_fn: '' diff --git a/benchmark/PaddleOCR_DBNet/config/SynthText_resnet18_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/SynthText_resnet18_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..c285d36 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/SynthText_resnet18_FPN_DBhead_polyLR.yaml @@ -0,0 +1,65 @@ +name: DBNet +base: ['config/SynthText.yaml'] +arch: + type: Model + backbone: + type: resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 10 + show_images_iter: 50 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: ./datasets/SynthText + img_mode: RGB + loader: + batch_size: 2 + shuffle: true + num_workers: 6 + collate_fn: '' diff --git a/benchmark/PaddleOCR_DBNet/config/icdar2015.yaml b/benchmark/PaddleOCR_DBNet/config/icdar2015.yaml new file mode 100644 index 0000000..4233d3e --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/icdar2015.yaml @@ -0,0 +1,69 @@ +name: DBNet +dataset: + train: + dataset: + type: ICDAR2015Dataset # 数据集类型 + args: + data_path: # 一个存放 img_path \t gt_path的文件 + - '' + pre_processes: # 数据的预处理过程,包含augment和标签制作 + - type: IaaAugment # 使用imgaug进行变换 + args: + - {'type':Fliplr, 'args':{'p':0.5}} + - {'type': Affine, 'args':{'rotate':[-10,10]}} + - {'type':Resize,'args':{'size':[0.5,3]}} + - type: EastRandomCropData + args: + size: [640,640] + max_tries: 50 + keep_ratio: true + - type: MakeBorderMap + args: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - type: MakeShrinkMap + args: + shrink_ratio: 0.4 + min_text_size: 8 + transforms: # 对图片进行的变换方式 + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + filter_keys: [img_path,img_name,text_polys,texts,ignore_tags,shape] # 返回数据之前,从数据字典里删除的key + ignore_tags: ['*', '###'] + loader: + batch_size: 1 + shuffle: true + num_workers: 0 + collate_fn: '' + validate: + dataset: + type: ICDAR2015Dataset + args: + data_path: + - '' + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + transforms: + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + filter_keys: [] + ignore_tags: ['*', '###'] + loader: + batch_size: 1 + shuffle: true + num_workers: 0 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/icdar2015_dcn_resnet18_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/icdar2015_dcn_resnet18_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..3e24428 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/icdar2015_dcn_resnet18_FPN_DBhead_polyLR.yaml @@ -0,0 +1,82 @@ +name: DBNet +base: ['config/icdar2015.yaml'] +arch: + type: Model + backbone: + type: deformable_resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 10 + show_images_iter: 50 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.txt + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.txt + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..62c392b --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR.yaml @@ -0,0 +1,82 @@ +name: DBNet +base: ['config/icdar2015.yaml'] +arch: + type: Model + backbone: + type: resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 10 + show_images_iter: 50 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.txt + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.txt + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR_finetune.yaml b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR_finetune.yaml new file mode 100644 index 0000000..9b018d5 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet18_FPN_DBhead_polyLR_finetune.yaml @@ -0,0 +1,83 @@ +name: DBNet +base: ['config/icdar2015.yaml'] +arch: + type: Model + backbone: + type: resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: StepLR + args: + step_size: 10 + gama: 0.8 +trainer: + seed: 2 + epochs: 500 + log_iter: 10 + show_images_iter: 50 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.txt + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.txt + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..2a870fd --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml @@ -0,0 +1,79 @@ +name: DBNet +base: ['config/icdar2015.yaml'] +arch: + type: Model + backbone: + type: resnet50 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam +lr_scheduler: + type: Polynomial + args: + learning_rate: 0.001 + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 10 + show_images_iter: 50 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output/fp16_o2 + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.txt + img_mode: RGB + loader: + batch_size: 16 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.txt + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/open_dataset.yaml b/benchmark/PaddleOCR_DBNet/config/open_dataset.yaml new file mode 100644 index 0000000..05ece6e --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/open_dataset.yaml @@ -0,0 +1,73 @@ +name: DBNet +dataset: + train: + dataset: + type: DetDataset # 数据集类型 + args: + data_path: # 一个存放 img_path \t gt_path的文件 + - '' + pre_processes: # 数据的预处理过程,包含augment和标签制作 + - type: IaaAugment # 使用imgaug进行变换 + args: + - {'type':Fliplr, 'args':{'p':0.5}} + - {'type': Affine, 'args':{'rotate':[-10,10]}} + - {'type':Resize,'args':{'size':[0.5,3]}} + - type: EastRandomCropData + args: + size: [640,640] + max_tries: 50 + keep_ratio: true + - type: MakeBorderMap + args: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - type: MakeShrinkMap + args: + shrink_ratio: 0.4 + min_text_size: 8 + transforms: # 对图片进行的变换方式 + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + load_char_annotation: false + expand_one_char: false + filter_keys: [img_path,img_name,text_polys,texts,ignore_tags,shape] # 返回数据之前,从数据字典里删除的key + ignore_tags: ['*', '###'] + loader: + batch_size: 1 + shuffle: true + num_workers: 0 + collate_fn: '' + validate: + dataset: + type: DetDataset + args: + data_path: + - '' + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + transforms: + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + load_char_annotation: false # 是否加载字符级标注 + expand_one_char: false # 是否对只有一个字符的框进行宽度扩充,扩充后w = w+h + filter_keys: [] + ignore_tags: ['*', '###'] + loader: + batch_size: 1 + shuffle: true + num_workers: 0 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/open_dataset_dcn_resnet50_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/open_dataset_dcn_resnet50_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..6c81738 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/open_dataset_dcn_resnet50_FPN_DBhead_polyLR.yaml @@ -0,0 +1,86 @@ +name: DBNet +base: ['config/open_dataset.yaml'] +arch: + type: Model + backbone: + type: deformable_resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 1 + show_images_iter: 1 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.json + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 2 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.json + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/open_dataset_resnest50_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/open_dataset_resnest50_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..d47ab06 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/open_dataset_resnest50_FPN_DBhead_polyLR.yaml @@ -0,0 +1,86 @@ +name: DBNet +base: ['config/open_dataset.yaml'] +arch: + type: Model + backbone: + type: resnest50 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 1 + show_images_iter: 1 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.json + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 2 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.json + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/config/open_dataset_resnet18_FPN_DBhead_polyLR.yaml b/benchmark/PaddleOCR_DBNet/config/open_dataset_resnet18_FPN_DBhead_polyLR.yaml new file mode 100644 index 0000000..ff16ddb --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/config/open_dataset_resnet18_FPN_DBhead_polyLR.yaml @@ -0,0 +1,93 @@ +name: DBNet +base: ['config/open_dataset.yaml'] +arch: + type: Model + backbone: + type: resnet18 + pretrained: true + neck: + type: FPN + inner_channels: 256 + head: + type: DBHead + out_channels: 2 + k: 50 +post_processing: + type: SegDetectorRepresenter + args: + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 # from paper +metric: + type: QuadMetric + args: + is_output_polygon: false +loss: + type: DBLoss + alpha: 1 + beta: 10 + ohem_ratio: 3 +optimizer: + type: Adam + args: + lr: 0.001 + weight_decay: 0 + amsgrad: true +lr_scheduler: + type: WarmupPolyLR + args: + warmup_epoch: 3 +trainer: + seed: 2 + epochs: 1200 + log_iter: 1 + show_images_iter: 1 + resume_checkpoint: '' + finetune_checkpoint: '' + output_dir: output + visual_dl: false +amp: + scale_loss: 1024 + amp_level: O2 + custom_white_list: [] + custom_black_list: ['exp', 'sigmoid', 'concat'] +dataset: + train: + dataset: + args: + data_path: + - ./datasets/train.json + transforms: # 对图片进行的变换方式 + - type: ToTensor + args: {} + - type: Normalize + args: + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 2 + shuffle: true + num_workers: 6 + collate_fn: '' + validate: + dataset: + args: + data_path: + - ./datasets/test.json + pre_processes: + - type: ResizeShortSize + args: + short_size: 736 + resize_text_polys: false + img_mode: RGB + load_char_annotation: false + expand_one_char: false + loader: + batch_size: 1 + shuffle: true + num_workers: 6 + collate_fn: ICDARCollectFN diff --git a/benchmark/PaddleOCR_DBNet/data_loader/__init__.py b/benchmark/PaddleOCR_DBNet/data_loader/__init__.py new file mode 100644 index 0000000..96c473f --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/__init__.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:52 +# @Author : zhoujun +import copy + +import PIL +import numpy as np +import paddle +from paddle.io import DataLoader, DistributedBatchSampler, BatchSampler + +from paddle.vision import transforms + + +def get_dataset(data_path, module_name, transform, dataset_args): + """ + 获取训练dataset + :param data_path: dataset文件列表,每个文件内以如下格式存储 ‘path/to/img\tlabel’ + :param module_name: 所使用的自定义dataset名称,目前只支持data_loaders.ImageDataset + :param transform: 该数据集使用的transforms + :param dataset_args: module_name的参数 + :return: 如果data_path列表不为空,返回对于的ConcatDataset对象,否则None + """ + from . import dataset + + s_dataset = getattr(dataset, module_name)( + transform=transform, data_path=data_path, **dataset_args + ) + return s_dataset + + +def get_transforms(transforms_config): + tr_list = [] + for item in transforms_config: + if "args" not in item: + args = {} + else: + args = item["args"] + cls = getattr(transforms, item["type"])(**args) + tr_list.append(cls) + tr_list = transforms.Compose(tr_list) + return tr_list + + +class ICDARCollectFN: + def __init__(self, *args, **kwargs): + pass + + def __call__(self, batch): + data_dict = {} + to_tensor_keys = [] + for sample in batch: + for k, v in sample.items(): + if k not in data_dict: + data_dict[k] = [] + if isinstance(v, (np.ndarray, paddle.Tensor, PIL.Image.Image)): + if k not in to_tensor_keys: + to_tensor_keys.append(k) + data_dict[k].append(v) + for k in to_tensor_keys: + data_dict[k] = paddle.stack(data_dict[k], 0) + return data_dict + + +def get_dataloader(module_config, distributed=False): + if module_config is None: + return None + config = copy.deepcopy(module_config) + dataset_args = config["dataset"]["args"] + if "transforms" in dataset_args: + img_transforms = get_transforms(dataset_args.pop("transforms")) + else: + img_transforms = None + # 创建数据集 + dataset_name = config["dataset"]["type"] + data_path = dataset_args.pop("data_path") + if data_path == None: + return None + + data_path = [x for x in data_path if x is not None] + if len(data_path) == 0: + return None + if ( + "collate_fn" not in config["loader"] + or config["loader"]["collate_fn"] is None + or len(config["loader"]["collate_fn"]) == 0 + ): + config["loader"]["collate_fn"] = None + else: + config["loader"]["collate_fn"] = eval(config["loader"]["collate_fn"])() + + _dataset = get_dataset( + data_path=data_path, + module_name=dataset_name, + transform=img_transforms, + dataset_args=dataset_args, + ) + sampler = None + if distributed: + # 3)使用DistributedSampler + batch_sampler = DistributedBatchSampler( + dataset=_dataset, + batch_size=config["loader"].pop("batch_size"), + shuffle=config["loader"].pop("shuffle"), + ) + else: + batch_sampler = BatchSampler( + dataset=_dataset, + batch_size=config["loader"].pop("batch_size"), + shuffle=config["loader"].pop("shuffle"), + ) + loader = DataLoader( + dataset=_dataset, batch_sampler=batch_sampler, **config["loader"] + ) + return loader diff --git a/benchmark/PaddleOCR_DBNet/data_loader/dataset.py b/benchmark/PaddleOCR_DBNet/data_loader/dataset.py new file mode 100644 index 0000000..5353ec8 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/dataset.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:54 +# @Author : zhoujun +import pathlib +import os +import cv2 +import numpy as np +import scipy.io as sio +from tqdm.auto import tqdm + +from base import BaseDataSet +from utils import order_points_clockwise, get_datalist, load, expand_polygon + + +class ICDAR2015Dataset(BaseDataSet): + def __init__( + self, + data_path: str, + img_mode, + pre_processes, + filter_keys, + ignore_tags, + transform=None, + **kwargs, + ): + super().__init__( + data_path, img_mode, pre_processes, filter_keys, ignore_tags, transform + ) + + def load_data(self, data_path: str) -> list: + data_list = get_datalist(data_path) + t_data_list = [] + for img_path, label_path in data_list: + data = self._get_annotation(label_path) + if len(data["text_polys"]) > 0: + item = {"img_path": img_path, "img_name": pathlib.Path(img_path).stem} + item.update(data) + t_data_list.append(item) + else: + print("there is no suit bbox in {}".format(label_path)) + return t_data_list + + def _get_annotation(self, label_path: str) -> dict: + boxes = [] + texts = [] + ignores = [] + with open(label_path, encoding="utf-8", mode="r") as f: + for line in f.readlines(): + params = line.strip().strip("\ufeff").strip("\xef\xbb\xbf").split(",") + try: + box = order_points_clockwise( + np.array(list(map(float, params[:8]))).reshape(-1, 2) + ) + if cv2.contourArea(box) > 0: + boxes.append(box) + label = params[8] + texts.append(label) + ignores.append(label in self.ignore_tags) + except: + print("load label failed on {}".format(label_path)) + data = { + "text_polys": np.array(boxes), + "texts": texts, + "ignore_tags": ignores, + } + return data + + +class DetDataset(BaseDataSet): + def __init__( + self, + data_path: str, + img_mode, + pre_processes, + filter_keys, + ignore_tags, + transform=None, + **kwargs, + ): + self.load_char_annotation = kwargs["load_char_annotation"] + self.expand_one_char = kwargs["expand_one_char"] + super().__init__( + data_path, img_mode, pre_processes, filter_keys, ignore_tags, transform + ) + + def load_data(self, data_path: str) -> list: + """ + 从json文件中读取出 文本行的坐标和gt,字符的坐标和gt + :param data_path: + :return: + """ + data_list = [] + for path in data_path: + content = load(path) + for gt in tqdm(content["data_list"], desc="read file {}".format(path)): + img_path = os.path.join(content["data_root"], gt["img_name"]) + polygons = [] + texts = [] + illegibility_list = [] + language_list = [] + for annotation in gt["annotations"]: + if len(annotation["polygon"]) == 0 or len(annotation["text"]) == 0: + continue + if len(annotation["text"]) > 1 and self.expand_one_char: + annotation["polygon"] = expand_polygon(annotation["polygon"]) + polygons.append(annotation["polygon"]) + texts.append(annotation["text"]) + illegibility_list.append(annotation["illegibility"]) + language_list.append(annotation["language"]) + if self.load_char_annotation: + for char_annotation in annotation["chars"]: + if ( + len(char_annotation["polygon"]) == 0 + or len(char_annotation["char"]) == 0 + ): + continue + polygons.append(char_annotation["polygon"]) + texts.append(char_annotation["char"]) + illegibility_list.append(char_annotation["illegibility"]) + language_list.append(char_annotation["language"]) + data_list.append( + { + "img_path": img_path, + "img_name": gt["img_name"], + "text_polys": np.array(polygons), + "texts": texts, + "ignore_tags": illegibility_list, + } + ) + return data_list + + +class SynthTextDataset(BaseDataSet): + def __init__( + self, + data_path: str, + img_mode, + pre_processes, + filter_keys, + transform=None, + **kwargs, + ): + self.transform = transform + self.dataRoot = pathlib.Path(data_path) + if not self.dataRoot.exists(): + raise FileNotFoundError("Dataset folder is not exist.") + + self.targetFilePath = self.dataRoot / "gt.mat" + if not self.targetFilePath.exists(): + raise FileExistsError("Target file is not exist.") + targets = {} + sio.loadmat( + self.targetFilePath, + targets, + squeeze_me=True, + struct_as_record=False, + variable_names=["imnames", "wordBB", "txt"], + ) + + self.imageNames = targets["imnames"] + self.wordBBoxes = targets["wordBB"] + self.transcripts = targets["txt"] + super().__init__(data_path, img_mode, pre_processes, filter_keys, transform) + + def load_data(self, data_path: str) -> list: + t_data_list = [] + for imageName, wordBBoxes, texts in zip( + self.imageNames, self.wordBBoxes, self.transcripts + ): + item = {} + wordBBoxes = ( + np.expand_dims(wordBBoxes, axis=2) + if (wordBBoxes.ndim == 2) + else wordBBoxes + ) + _, _, numOfWords = wordBBoxes.shape + text_polys = wordBBoxes.reshape( + [8, numOfWords], order="F" + ).T # num_words * 8 + text_polys = text_polys.reshape(numOfWords, 4, 2) # num_of_words * 4 * 2 + transcripts = [word for line in texts for word in line.split()] + if numOfWords != len(transcripts): + continue + item["img_path"] = str(self.dataRoot / imageName) + item["img_name"] = (self.dataRoot / imageName).stem + item["text_polys"] = text_polys + item["texts"] = transcripts + item["ignore_tags"] = [x in self.ignore_tags for x in transcripts] + t_data_list.append(item) + return t_data_list diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/__init__.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/__init__.py new file mode 100644 index 0000000..bc055da --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/4 10:53 +# @Author : zhoujun +from .iaa_augment import IaaAugment +from .augment import * +from .random_crop_data import EastRandomCropData, PSERandomCrop +from .make_border_map import MakeBorderMap +from .make_shrink_map import MakeShrinkMap diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/augment.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/augment.py new file mode 100644 index 0000000..3b08a02 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/augment.py @@ -0,0 +1,308 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:52 +# @Author : zhoujun + +import math +import numbers +import random + +import cv2 +import numpy as np +from skimage.util import random_noise + + +class RandomNoise: + def __init__(self, random_rate): + self.random_rate = random_rate + + def __call__(self, data: dict): + """ + 对图片加噪声 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + data["img"] = ( + random_noise(data["img"], mode="gaussian", clip=True) * 255 + ).astype(data["img"].dtype) + return data + + +class RandomScale: + def __init__(self, scales, random_rate): + """ + :param scales: 尺度 + :param random_rate: 随机系数 + :return: + """ + self.random_rate = random_rate + self.scales = scales + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + im = data["img"] + text_polys = data["text_polys"] + + tmp_text_polys = text_polys.copy() + rd_scale = float(np.random.choice(self.scales)) + im = cv2.resize(im, dsize=None, fx=rd_scale, fy=rd_scale) + tmp_text_polys *= rd_scale + + data["img"] = im + data["text_polys"] = tmp_text_polys + return data + + +class RandomRotateImgBox: + def __init__(self, degrees, random_rate, same_size=False): + """ + :param degrees: 角度,可以是一个数值或者list + :param random_rate: 随机系数 + :param same_size: 是否保持和原图一样大 + :return: + """ + if isinstance(degrees, numbers.Number): + if degrees < 0: + raise ValueError("If degrees is a single number, it must be positive.") + degrees = (-degrees, degrees) + elif ( + isinstance(degrees, list) + or isinstance(degrees, tuple) + or isinstance(degrees, np.ndarray) + ): + if len(degrees) != 2: + raise ValueError("If degrees is a sequence, it must be of len 2.") + degrees = degrees + else: + raise Exception("degrees must in Number or list or tuple or np.ndarray") + self.degrees = degrees + self.same_size = same_size + self.random_rate = random_rate + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + im = data["img"] + text_polys = data["text_polys"] + + # ---------------------- 旋转图像 ---------------------- + w = im.shape[1] + h = im.shape[0] + angle = np.random.uniform(self.degrees[0], self.degrees[1]) + + if self.same_size: + nw = w + nh = h + else: + # 角度变弧度 + rangle = np.deg2rad(angle) + # 计算旋转之后图像的w, h + nw = abs(np.sin(rangle) * h) + abs(np.cos(rangle) * w) + nh = abs(np.cos(rangle) * h) + abs(np.sin(rangle) * w) + # 构造仿射矩阵 + rot_mat = cv2.getRotationMatrix2D((nw * 0.5, nh * 0.5), angle, 1) + # 计算原图中心点到新图中心点的偏移量 + rot_move = np.dot(rot_mat, np.array([(nw - w) * 0.5, (nh - h) * 0.5, 0])) + # 更新仿射矩阵 + rot_mat[0, 2] += rot_move[0] + rot_mat[1, 2] += rot_move[1] + # 仿射变换 + rot_img = cv2.warpAffine( + im, + rot_mat, + (int(math.ceil(nw)), int(math.ceil(nh))), + flags=cv2.INTER_LANCZOS4, + ) + + # ---------------------- 矫正bbox坐标 ---------------------- + # rot_mat是最终的旋转矩阵 + # 获取原始bbox的四个中点,然后将这四个点转换到旋转后的坐标系下 + rot_text_polys = list() + for bbox in text_polys: + point1 = np.dot(rot_mat, np.array([bbox[0, 0], bbox[0, 1], 1])) + point2 = np.dot(rot_mat, np.array([bbox[1, 0], bbox[1, 1], 1])) + point3 = np.dot(rot_mat, np.array([bbox[2, 0], bbox[2, 1], 1])) + point4 = np.dot(rot_mat, np.array([bbox[3, 0], bbox[3, 1], 1])) + rot_text_polys.append([point1, point2, point3, point4]) + data["img"] = rot_img + data["text_polys"] = np.array(rot_text_polys) + return data + + +class RandomResize: + def __init__(self, size, random_rate, keep_ratio=False): + """ + :param input_size: resize尺寸,数字或者list的形式,如果为list形式,就是[w,h] + :param random_rate: 随机系数 + :param keep_ratio: 是否保持长宽比 + :return: + """ + if isinstance(size, numbers.Number): + if size < 0: + raise ValueError( + "If input_size is a single number, it must be positive." + ) + size = (size, size) + elif ( + isinstance(size, list) + or isinstance(size, tuple) + or isinstance(size, np.ndarray) + ): + if len(size) != 2: + raise ValueError("If input_size is a sequence, it must be of len 2.") + size = (size[0], size[1]) + else: + raise Exception("input_size must in Number or list or tuple or np.ndarray") + self.size = size + self.keep_ratio = keep_ratio + self.random_rate = random_rate + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + im = data["img"] + text_polys = data["text_polys"] + + if self.keep_ratio: + # 将图片短边pad到和长边一样 + h, w, c = im.shape + max_h = max(h, self.size[0]) + max_w = max(w, self.size[1]) + im_padded = np.zeros((max_h, max_w, c), dtype=np.uint8) + im_padded[:h, :w] = im.copy() + im = im_padded + text_polys = text_polys.astype(np.float32) + h, w, _ = im.shape + im = cv2.resize(im, self.size) + w_scale = self.size[0] / float(w) + h_scale = self.size[1] / float(h) + text_polys[:, :, 0] *= w_scale + text_polys[:, :, 1] *= h_scale + + data["img"] = im + data["text_polys"] = text_polys + return data + + +def resize_image(img, short_size): + height, width, _ = img.shape + if height < width: + new_height = short_size + new_width = new_height / height * width + else: + new_width = short_size + new_height = new_width / width * height + new_height = int(round(new_height / 32) * 32) + new_width = int(round(new_width / 32) * 32) + resized_img = cv2.resize(img, (new_width, new_height)) + return resized_img, (new_width / width, new_height / height) + + +class ResizeShortSize: + def __init__(self, short_size, resize_text_polys=True): + """ + :param size: resize尺寸,数字或者list的形式,如果为list形式,就是[w,h] + :return: + """ + self.short_size = short_size + self.resize_text_polys = resize_text_polys + + def __call__(self, data: dict) -> dict: + """ + 对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + im = data["img"] + text_polys = data["text_polys"] + + h, w, _ = im.shape + short_edge = min(h, w) + if short_edge < self.short_size: + # 保证短边 >= short_size + scale = self.short_size / short_edge + im = cv2.resize(im, dsize=None, fx=scale, fy=scale) + scale = (scale, scale) + # im, scale = resize_image(im, self.short_size) + if self.resize_text_polys: + # text_polys *= scale + text_polys[:, 0] *= scale[0] + text_polys[:, 1] *= scale[1] + + data["img"] = im + data["text_polys"] = text_polys + return data + + +class HorizontalFlip: + def __init__(self, random_rate): + """ + + :param random_rate: 随机系数 + """ + self.random_rate = random_rate + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + im = data["img"] + text_polys = data["text_polys"] + + flip_text_polys = text_polys.copy() + flip_im = cv2.flip(im, 1) + h, w, _ = flip_im.shape + flip_text_polys[:, :, 0] = w - flip_text_polys[:, :, 0] + + data["img"] = flip_im + data["text_polys"] = flip_text_polys + return data + + +class VerticalFlip: + def __init__(self, random_rate): + """ + + :param random_rate: 随机系数 + """ + self.random_rate = random_rate + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + if random.random() > self.random_rate: + return data + im = data["img"] + text_polys = data["text_polys"] + + flip_text_polys = text_polys.copy() + flip_im = cv2.flip(im, 0) + h, w, _ = flip_im.shape + flip_text_polys[:, :, 1] = h - flip_text_polys[:, :, 1] + data["img"] = flip_im + data["text_polys"] = flip_text_polys + return data diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/iaa_augment.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/iaa_augment.py new file mode 100644 index 0000000..1f0fa19 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/iaa_augment.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/4 18:06 +# @Author : zhoujun +import numpy as np +import imgaug +import imgaug.augmenters as iaa + + +class AugmenterBuilder(object): + def __init__(self): + pass + + def build(self, args, root=True): + if args is None or len(args) == 0: + return None + elif isinstance(args, list): + if root: + sequence = [self.build(value, root=False) for value in args] + return iaa.Sequential(sequence) + else: + return getattr(iaa, args[0])( + *[self.to_tuple_if_list(a) for a in args[1:]] + ) + elif isinstance(args, dict): + cls = getattr(iaa, args["type"]) + return cls(**{k: self.to_tuple_if_list(v) for k, v in args["args"].items()}) + else: + raise RuntimeError("unknown augmenter arg: " + str(args)) + + def to_tuple_if_list(self, obj): + if isinstance(obj, list): + return tuple(obj) + return obj + + +class IaaAugment: + def __init__(self, augmenter_args): + self.augmenter_args = augmenter_args + self.augmenter = AugmenterBuilder().build(self.augmenter_args) + + def __call__(self, data): + image = data["img"] + shape = image.shape + + if self.augmenter: + aug = self.augmenter.to_deterministic() + data["img"] = aug.augment_image(image) + data = self.may_augment_annotation(aug, data, shape) + return data + + def may_augment_annotation(self, aug, data, shape): + if aug is None: + return data + + line_polys = [] + for poly in data["text_polys"]: + new_poly = self.may_augment_poly(aug, shape, poly) + line_polys.append(new_poly) + data["text_polys"] = np.array(line_polys) + return data + + def may_augment_poly(self, aug, img_shape, poly): + keypoints = [imgaug.Keypoint(p[0], p[1]) for p in poly] + keypoints = aug.augment_keypoints( + [imgaug.KeypointsOnImage(keypoints, shape=img_shape)] + )[0].keypoints + poly = [(p.x, p.y) for p in keypoints] + return poly diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/make_border_map.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/make_border_map.py new file mode 100644 index 0000000..28b9ac9 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/make_border_map.py @@ -0,0 +1,159 @@ +import cv2 +import numpy as np + +np.seterr(divide="ignore", invalid="ignore") +import pyclipper +from shapely.geometry import Polygon + + +class MakeBorderMap: + def __init__(self, shrink_ratio=0.4, thresh_min=0.3, thresh_max=0.7): + self.shrink_ratio = shrink_ratio + self.thresh_min = thresh_min + self.thresh_max = thresh_max + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + im = data["img"] + text_polys = data["text_polys"] + ignore_tags = data["ignore_tags"] + + canvas = np.zeros(im.shape[:2], dtype=np.float32) + mask = np.zeros(im.shape[:2], dtype=np.float32) + + for i in range(len(text_polys)): + if ignore_tags[i]: + continue + self.draw_border_map(text_polys[i], canvas, mask=mask) + canvas = canvas * (self.thresh_max - self.thresh_min) + self.thresh_min + + data["threshold_map"] = canvas + data["threshold_mask"] = mask + return data + + def draw_border_map(self, polygon, canvas, mask): + polygon = np.array(polygon) + assert polygon.ndim == 2 + assert polygon.shape[1] == 2 + + polygon_shape = Polygon(polygon) + if polygon_shape.area <= 0: + return + distance = ( + polygon_shape.area + * (1 - np.power(self.shrink_ratio, 2)) + / polygon_shape.length + ) + subject = [tuple(l) for l in polygon] + padding = pyclipper.PyclipperOffset() + padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + + padded_polygon = np.array(padding.Execute(distance)[0]) + cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0) + + xmin = padded_polygon[:, 0].min() + xmax = padded_polygon[:, 0].max() + ymin = padded_polygon[:, 1].min() + ymax = padded_polygon[:, 1].max() + width = xmax - xmin + 1 + height = ymax - ymin + 1 + + polygon[:, 0] = polygon[:, 0] - xmin + polygon[:, 1] = polygon[:, 1] - ymin + + xs = np.broadcast_to( + np.linspace(0, width - 1, num=width).reshape(1, width), (height, width) + ) + ys = np.broadcast_to( + np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width) + ) + + distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32) + for i in range(polygon.shape[0]): + j = (i + 1) % polygon.shape[0] + absolute_distance = self.distance(xs, ys, polygon[i], polygon[j]) + distance_map[i] = np.clip(absolute_distance / distance, 0, 1) + distance_map = distance_map.min(axis=0) + + xmin_valid = min(max(0, xmin), canvas.shape[1] - 1) + xmax_valid = min(max(0, xmax), canvas.shape[1] - 1) + ymin_valid = min(max(0, ymin), canvas.shape[0] - 1) + ymax_valid = min(max(0, ymax), canvas.shape[0] - 1) + canvas[ymin_valid : ymax_valid + 1, xmin_valid : xmax_valid + 1] = np.fmax( + 1 + - distance_map[ + ymin_valid - ymin : ymax_valid - ymax + height, + xmin_valid - xmin : xmax_valid - xmax + width, + ], + canvas[ymin_valid : ymax_valid + 1, xmin_valid : xmax_valid + 1], + ) + + def distance(self, xs, ys, point_1, point_2): + """ + compute the distance from point to a line + ys: coordinates in the first axis + xs: coordinates in the second axis + point_1, point_2: (x, y), the end of the line + """ + height, width = xs.shape[:2] + square_distance_1 = np.square(xs - point_1[0]) + np.square(ys - point_1[1]) + square_distance_2 = np.square(xs - point_2[0]) + np.square(ys - point_2[1]) + square_distance = np.square(point_1[0] - point_2[0]) + np.square( + point_1[1] - point_2[1] + ) + + cosin = (square_distance - square_distance_1 - square_distance_2) / ( + 2 * np.sqrt(square_distance_1 * square_distance_2) + ) + square_sin = 1 - np.square(cosin) + square_sin = np.nan_to_num(square_sin) + + result = np.sqrt( + square_distance_1 * square_distance_2 * square_sin / square_distance + ) + result[cosin < 0] = np.sqrt(np.fmin(square_distance_1, square_distance_2))[ + cosin < 0 + ] + # self.extend_line(point_1, point_2, result) + return result + + def extend_line(self, point_1, point_2, result): + ex_point_1 = ( + int( + round(point_1[0] + (point_1[0] - point_2[0]) * (1 + self.shrink_ratio)) + ), + int( + round(point_1[1] + (point_1[1] - point_2[1]) * (1 + self.shrink_ratio)) + ), + ) + cv2.line( + result, + tuple(ex_point_1), + tuple(point_1), + 4096.0, + 1, + lineType=cv2.LINE_AA, + shift=0, + ) + ex_point_2 = ( + int( + round(point_2[0] + (point_2[0] - point_1[0]) * (1 + self.shrink_ratio)) + ), + int( + round(point_2[1] + (point_2[1] - point_1[1]) * (1 + self.shrink_ratio)) + ), + ) + cv2.line( + result, + tuple(ex_point_2), + tuple(point_2), + 4096.0, + 1, + lineType=cv2.LINE_AA, + shift=0, + ) + return ex_point_1, ex_point_2 diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/make_shrink_map.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/make_shrink_map.py new file mode 100644 index 0000000..000ffed --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/make_shrink_map.py @@ -0,0 +1,129 @@ +import numpy as np +import cv2 + + +def shrink_polygon_py(polygon, shrink_ratio): + """ + 对框进行缩放,返回去的比例为1/shrink_ratio 即可 + """ + cx = polygon[:, 0].mean() + cy = polygon[:, 1].mean() + polygon[:, 0] = cx + (polygon[:, 0] - cx) * shrink_ratio + polygon[:, 1] = cy + (polygon[:, 1] - cy) * shrink_ratio + return polygon + + +def shrink_polygon_pyclipper(polygon, shrink_ratio): + from shapely.geometry import Polygon + import pyclipper + + polygon_shape = Polygon(polygon) + distance = ( + polygon_shape.area * (1 - np.power(shrink_ratio, 2)) / polygon_shape.length + ) + subject = [tuple(l) for l in polygon] + padding = pyclipper.PyclipperOffset() + padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + shrunk = padding.Execute(-distance) + if shrunk == []: + shrunk = np.array(shrunk) + else: + shrunk = np.array(shrunk[0]).reshape(-1, 2) + return shrunk + + +class MakeShrinkMap: + r""" + Making binary mask from detection data with ICDAR format. + Typically following the process of class `MakeICDARData`. + """ + + def __init__(self, min_text_size=8, shrink_ratio=0.4, shrink_type="pyclipper"): + shrink_func_dict = { + "py": shrink_polygon_py, + "pyclipper": shrink_polygon_pyclipper, + } + self.shrink_func = shrink_func_dict[shrink_type] + self.min_text_size = min_text_size + self.shrink_ratio = shrink_ratio + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + image = data["img"] + text_polys = data["text_polys"] + ignore_tags = data["ignore_tags"] + + h, w = image.shape[:2] + text_polys, ignore_tags = self.validate_polygons(text_polys, ignore_tags, h, w) + gt = np.zeros((h, w), dtype=np.float32) + mask = np.ones((h, w), dtype=np.float32) + for i in range(len(text_polys)): + polygon = text_polys[i] + height = max(polygon[:, 1]) - min(polygon[:, 1]) + width = max(polygon[:, 0]) - min(polygon[:, 0]) + if ignore_tags[i] or min(height, width) < self.min_text_size: + cv2.fillPoly(mask, polygon.astype(np.int32)[np.newaxis, :, :], 0) + ignore_tags[i] = True + else: + shrunk = self.shrink_func(polygon, self.shrink_ratio) + if shrunk.size == 0: + cv2.fillPoly(mask, polygon.astype(np.int32)[np.newaxis, :, :], 0) + ignore_tags[i] = True + continue + cv2.fillPoly(gt, [shrunk.astype(np.int32)], 1) + + data["shrink_map"] = gt + data["shrink_mask"] = mask + return data + + def validate_polygons(self, polygons, ignore_tags, h, w): + """ + polygons (numpy.array, required): of shape (num_instances, num_points, 2) + """ + if len(polygons) == 0: + return polygons, ignore_tags + assert len(polygons) == len(ignore_tags) + for polygon in polygons: + polygon[:, 0] = np.clip(polygon[:, 0], 0, w - 1) + polygon[:, 1] = np.clip(polygon[:, 1], 0, h - 1) + + for i in range(len(polygons)): + area = self.polygon_area(polygons[i]) + if abs(area) < 1: + ignore_tags[i] = True + if area > 0: + polygons[i] = polygons[i][::-1, :] + return polygons, ignore_tags + + def polygon_area(self, polygon): + return cv2.contourArea(polygon) + # edge = 0 + # for i in range(polygon.shape[0]): + # next_index = (i + 1) % polygon.shape[0] + # edge += (polygon[next_index, 0] - polygon[i, 0]) * (polygon[next_index, 1] - polygon[i, 1]) + # + # return edge / 2. + + +if __name__ == "__main__": + from shapely.geometry import Polygon + import pyclipper + + polygon = np.array([[0, 0], [100, 10], [100, 100], [10, 90]]) + a = shrink_polygon_py(polygon, 0.4) + print(a) + print(shrink_polygon_py(a, 1 / 0.4)) + b = shrink_polygon_pyclipper(polygon, 0.4) + print(b) + poly = Polygon(b) + distance = poly.area * 1.5 / poly.length + offset = pyclipper.PyclipperOffset() + offset.AddPath(b, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + expanded = np.array(offset.Execute(distance)) + bounding_box = cv2.minAreaRect(expanded) + points = cv2.boxPoints(bounding_box) + print(points) diff --git a/benchmark/PaddleOCR_DBNet/data_loader/modules/random_crop_data.py b/benchmark/PaddleOCR_DBNet/data_loader/modules/random_crop_data.py new file mode 100644 index 0000000..8c6f656 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/data_loader/modules/random_crop_data.py @@ -0,0 +1,211 @@ +import random + +import cv2 +import numpy as np + + +# random crop algorithm similar to https://github.com/argman/EAST +class EastRandomCropData: + def __init__( + self, + size=(640, 640), + max_tries=50, + min_crop_side_ratio=0.1, + require_original_image=False, + keep_ratio=True, + ): + self.size = size + self.max_tries = max_tries + self.min_crop_side_ratio = min_crop_side_ratio + self.require_original_image = require_original_image + self.keep_ratio = keep_ratio + + def __call__(self, data: dict) -> dict: + """ + 从scales中随机选择一个尺度,对图片和文本框进行缩放 + :param data: {'img':,'text_polys':,'texts':,'ignore_tags':} + :return: + """ + im = data["img"] + text_polys = data["text_polys"] + ignore_tags = data["ignore_tags"] + texts = data["texts"] + all_care_polys = [text_polys[i] for i, tag in enumerate(ignore_tags) if not tag] + # 计算crop区域 + crop_x, crop_y, crop_w, crop_h = self.crop_area(im, all_care_polys) + # crop 图片 保持比例填充 + scale_w = self.size[0] / crop_w + scale_h = self.size[1] / crop_h + scale = min(scale_w, scale_h) + h = int(crop_h * scale) + w = int(crop_w * scale) + if self.keep_ratio: + if len(im.shape) == 3: + padimg = np.zeros((self.size[1], self.size[0], im.shape[2]), im.dtype) + else: + padimg = np.zeros((self.size[1], self.size[0]), im.dtype) + padimg[:h, :w] = cv2.resize( + im[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w], (w, h) + ) + img = padimg + else: + img = cv2.resize( + im[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w], tuple(self.size) + ) + # crop 文本框 + text_polys_crop = [] + ignore_tags_crop = [] + texts_crop = [] + for poly, text, tag in zip(text_polys, texts, ignore_tags): + poly = ((poly - (crop_x, crop_y)) * scale).tolist() + if not self.is_poly_outside_rect(poly, 0, 0, w, h): + text_polys_crop.append(poly) + ignore_tags_crop.append(tag) + texts_crop.append(text) + data["img"] = img + data["text_polys"] = np.float32(text_polys_crop) + data["ignore_tags"] = ignore_tags_crop + data["texts"] = texts_crop + return data + + def is_poly_in_rect(self, poly, x, y, w, h): + poly = np.array(poly) + if poly[:, 0].min() < x or poly[:, 0].max() > x + w: + return False + if poly[:, 1].min() < y or poly[:, 1].max() > y + h: + return False + return True + + def is_poly_outside_rect(self, poly, x, y, w, h): + poly = np.array(poly) + if poly[:, 0].max() < x or poly[:, 0].min() > x + w: + return True + if poly[:, 1].max() < y or poly[:, 1].min() > y + h: + return True + return False + + def split_regions(self, axis): + regions = [] + min_axis = 0 + for i in range(1, axis.shape[0]): + if axis[i] != axis[i - 1] + 1: + region = axis[min_axis:i] + min_axis = i + regions.append(region) + return regions + + def random_select(self, axis, max_size): + xx = np.random.choice(axis, size=2) + xmin = np.min(xx) + xmax = np.max(xx) + xmin = np.clip(xmin, 0, max_size - 1) + xmax = np.clip(xmax, 0, max_size - 1) + return xmin, xmax + + def region_wise_random_select(self, regions, max_size): + selected_index = list(np.random.choice(len(regions), 2)) + selected_values = [] + for index in selected_index: + axis = regions[index] + xx = int(np.random.choice(axis, size=1)) + selected_values.append(xx) + xmin = min(selected_values) + xmax = max(selected_values) + return xmin, xmax + + def crop_area(self, im, text_polys): + h, w = im.shape[:2] + h_array = np.zeros(h, dtype=np.int32) + w_array = np.zeros(w, dtype=np.int32) + for points in text_polys: + points = np.round(points, decimals=0).astype(np.int32) + minx = np.min(points[:, 0]) + maxx = np.max(points[:, 0]) + w_array[minx:maxx] = 1 + miny = np.min(points[:, 1]) + maxy = np.max(points[:, 1]) + h_array[miny:maxy] = 1 + # ensure the cropped area not across a text + h_axis = np.where(h_array == 0)[0] + w_axis = np.where(w_array == 0)[0] + + if len(h_axis) == 0 or len(w_axis) == 0: + return 0, 0, w, h + + h_regions = self.split_regions(h_axis) + w_regions = self.split_regions(w_axis) + + for i in range(self.max_tries): + if len(w_regions) > 1: + xmin, xmax = self.region_wise_random_select(w_regions, w) + else: + xmin, xmax = self.random_select(w_axis, w) + if len(h_regions) > 1: + ymin, ymax = self.region_wise_random_select(h_regions, h) + else: + ymin, ymax = self.random_select(h_axis, h) + + if ( + xmax - xmin < self.min_crop_side_ratio * w + or ymax - ymin < self.min_crop_side_ratio * h + ): + # area too small + continue + num_poly_in_rect = 0 + for poly in text_polys: + if not self.is_poly_outside_rect( + poly, xmin, ymin, xmax - xmin, ymax - ymin + ): + num_poly_in_rect += 1 + break + + if num_poly_in_rect > 0: + return xmin, ymin, xmax - xmin, ymax - ymin + + return 0, 0, w, h + + +class PSERandomCrop: + def __init__(self, size): + self.size = size + + def __call__(self, data): + imgs = data["imgs"] + + h, w = imgs[0].shape[0:2] + th, tw = self.size + if w == tw and h == th: + return imgs + + # label中存在文本实例,并且按照概率进行裁剪,使用threshold_label_map控制 + if np.max(imgs[2]) > 0 and random.random() > 3 / 8: + # 文本实例的左上角点 + tl = np.min(np.where(imgs[2] > 0), axis=1) - self.size + tl[tl < 0] = 0 + # 文本实例的右下角点 + br = np.max(np.where(imgs[2] > 0), axis=1) - self.size + br[br < 0] = 0 + # 保证选到右下角点时,有足够的距离进行crop + br[0] = min(br[0], h - th) + br[1] = min(br[1], w - tw) + + for _ in range(50000): + i = random.randint(tl[0], br[0]) + j = random.randint(tl[1], br[1]) + # 保证shrink_label_map有文本 + if imgs[1][i : i + th, j : j + tw].sum() <= 0: + continue + else: + break + else: + i = random.randint(0, h - th) + j = random.randint(0, w - tw) + + # return i, j, th, tw + for idx in range(len(imgs)): + if len(imgs[idx].shape) == 3: + imgs[idx] = imgs[idx][i : i + th, j : j + tw, :] + else: + imgs[idx] = imgs[idx][i : i + th, j : j + tw] + data["imgs"] = imgs + return data diff --git a/benchmark/PaddleOCR_DBNet/environment.yml b/benchmark/PaddleOCR_DBNet/environment.yml new file mode 100644 index 0000000..571dbf2 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/environment.yml @@ -0,0 +1,21 @@ +name: dbnet +channels: + - conda-forge + - defaults +dependencies: + - anyconfig==0.9.10 + - future==0.18.2 + - imgaug==0.4.0 + - matplotlib==3.1.2 + - numpy==1.17.4 + - opencv + - pyclipper + - PyYAML==5.2 + - scikit-image==0.16.2 + - Shapely==1.6.4 + - tensorboard=2 + - tqdm==4.40.1 + - ipython + - pip + - pip: + - polygon3 diff --git a/benchmark/PaddleOCR_DBNet/eval.sh b/benchmark/PaddleOCR_DBNet/eval.sh new file mode 100644 index 0000000..7520a73 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/eval.sh @@ -0,0 +1 @@ +CUDA_VISIBLE_DEVICES=0 python3 tools/eval.py --model_path '' diff --git a/benchmark/PaddleOCR_DBNet/generate_lists.sh b/benchmark/PaddleOCR_DBNet/generate_lists.sh new file mode 100644 index 0000000..84f408c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/generate_lists.sh @@ -0,0 +1,17 @@ +#Only use if your file names of the images and txts are identical +rm ./datasets/train_img.txt +rm ./datasets/train_gt.txt +rm ./datasets/test_img.txt +rm ./datasets/test_gt.txt +rm ./datasets/train.txt +rm ./datasets/test.txt +ls ./datasets/train/img/*.jpg > ./datasets/train_img.txt +ls ./datasets/train/gt/*.txt > ./datasets/train_gt.txt +ls ./datasets/test/img/*.jpg > ./datasets/test_img.txt +ls ./datasets/test/gt/*.txt > ./datasets/test_gt.txt +paste ./datasets/train_img.txt ./datasets/train_gt.txt > ./datasets/train.txt +paste ./datasets/test_img.txt ./datasets/test_gt.txt > ./datasets/test.txt +rm ./datasets/train_img.txt +rm ./datasets/train_gt.txt +rm ./datasets/test_img.txt +rm ./datasets/test_gt.txt diff --git a/benchmark/PaddleOCR_DBNet/imgs/paper/db.jpg b/benchmark/PaddleOCR_DBNet/imgs/paper/db.jpg new file mode 100644 index 0000000..aa6c7e9 Binary files /dev/null and b/benchmark/PaddleOCR_DBNet/imgs/paper/db.jpg differ diff --git a/benchmark/PaddleOCR_DBNet/models/__init__.py b/benchmark/PaddleOCR_DBNet/models/__init__.py new file mode 100644 index 0000000..a2669bf --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:55 +# @Author : zhoujun +import copy +from .model import Model +from .losses import build_loss + +__all__ = ["build_loss", "build_model"] +support_model = ["Model"] + + +def build_model(config): + """ + get architecture model class + """ + copy_config = copy.deepcopy(config) + arch_type = copy_config.pop("type") + assert ( + arch_type in support_model + ), f"{arch_type} is not developed yet!, only {support_model} are support now" + arch_model = eval(arch_type)(copy_config) + return arch_model diff --git a/benchmark/PaddleOCR_DBNet/models/backbone/__init__.py b/benchmark/PaddleOCR_DBNet/models/backbone/__init__.py new file mode 100644 index 0000000..5d0d8a2 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/backbone/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:54 +# @Author : zhoujun + +from .resnet import * + +__all__ = ["build_backbone"] + +support_backbone = [ + "resnet18", + "deformable_resnet18", + "deformable_resnet50", + "resnet50", + "resnet34", + "resnet101", + "resnet152", +] + + +def build_backbone(backbone_name, **kwargs): + assert ( + backbone_name in support_backbone + ), f"all support backbone is {support_backbone}" + backbone = eval(backbone_name)(**kwargs) + return backbone diff --git a/benchmark/PaddleOCR_DBNet/models/backbone/resnet.py b/benchmark/PaddleOCR_DBNet/models/backbone/resnet.py new file mode 100644 index 0000000..9aa886d --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/backbone/resnet.py @@ -0,0 +1,366 @@ +import math +import paddle +from paddle import nn + +BatchNorm2d = nn.BatchNorm2D + +__all__ = [ + "ResNet", + "resnet18", + "resnet34", + "resnet50", + "resnet101", + "deformable_resnet18", + "deformable_resnet50", + "resnet152", +] + +model_urls = { + "resnet18": "https://download.pytorch.org/models/resnet18-5c106cde.pth", + "resnet34": "https://download.pytorch.org/models/resnet34-333f7ec4.pth", + "resnet50": "https://download.pytorch.org/models/resnet50-19c8e357.pth", + "resnet101": "https://download.pytorch.org/models/resnet101-5d3b4d8f.pth", + "resnet152": "https://download.pytorch.org/models/resnet152-b121ed2d.pth", +} + + +def constant_init(module, constant, bias=0): + module.weight = paddle.create_parameter( + shape=module.weight.shape, + dtype="float32", + default_initializer=paddle.nn.initializer.Constant(constant), + ) + if hasattr(module, "bias"): + module.bias = paddle.create_parameter( + shape=module.bias.shape, + dtype="float32", + default_initializer=paddle.nn.initializer.Constant(bias), + ) + + +def conv3x3(in_planes, out_planes, stride=1): + """3x3 convolution with padding""" + return nn.Conv2D( + in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias_attr=False + ) + + +class BasicBlock(nn.Layer): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None, dcn=None): + super(BasicBlock, self).__init__() + self.with_dcn = dcn is not None + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = BatchNorm2d(planes, momentum=0.1) + self.relu = nn.ReLU() + self.with_modulated_dcn = False + if not self.with_dcn: + self.conv2 = nn.Conv2D( + planes, planes, kernel_size=3, padding=1, bias_attr=False + ) + else: + from paddle.vision.ops import DeformConv2D + + deformable_groups = dcn.get("deformable_groups", 1) + offset_channels = 18 + self.conv2_offset = nn.Conv2D( + planes, deformable_groups * offset_channels, kernel_size=3, padding=1 + ) + self.conv2 = DeformConv2D( + planes, planes, kernel_size=3, padding=1, bias_attr=False + ) + self.bn2 = BatchNorm2d(planes, momentum=0.1) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + # out = self.conv2(out) + if not self.with_dcn: + out = self.conv2(out) + else: + offset = self.conv2_offset(out) + out = self.conv2(out, offset) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Layer): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None, dcn=None): + super(Bottleneck, self).__init__() + self.with_dcn = dcn is not None + self.conv1 = nn.Conv2D(inplanes, planes, kernel_size=1, bias_attr=False) + self.bn1 = BatchNorm2d(planes, momentum=0.1) + self.with_modulated_dcn = False + if not self.with_dcn: + self.conv2 = nn.Conv2D( + planes, planes, kernel_size=3, stride=stride, padding=1, bias_attr=False + ) + else: + deformable_groups = dcn.get("deformable_groups", 1) + from paddle.vision.ops import DeformConv2D + + offset_channels = 18 + self.conv2_offset = nn.Conv2D( + planes, + deformable_groups * offset_channels, + stride=stride, + kernel_size=3, + padding=1, + ) + self.conv2 = DeformConv2D( + planes, planes, kernel_size=3, padding=1, stride=stride, bias_attr=False + ) + self.bn2 = BatchNorm2d(planes, momentum=0.1) + self.conv3 = nn.Conv2D(planes, planes * 4, kernel_size=1, bias_attr=False) + self.bn3 = BatchNorm2d(planes * 4, momentum=0.1) + self.relu = nn.ReLU() + self.downsample = downsample + self.stride = stride + self.dcn = dcn + self.with_dcn = dcn is not None + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + # out = self.conv2(out) + if not self.with_dcn: + out = self.conv2(out) + else: + offset = self.conv2_offset(out) + out = self.conv2(out, offset) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class ResNet(nn.Layer): + def __init__(self, block, layers, in_channels=3, dcn=None): + self.dcn = dcn + self.inplanes = 64 + super(ResNet, self).__init__() + self.out_channels = [] + self.conv1 = nn.Conv2D( + in_channels, 64, kernel_size=7, stride=2, padding=3, bias_attr=False + ) + self.bn1 = BatchNorm2d(64, momentum=0.1) + self.relu = nn.ReLU() + self.maxpool = nn.MaxPool2D(kernel_size=3, stride=2, padding=1) + self.layer1 = self._make_layer(block, 64, layers[0]) + self.layer2 = self._make_layer(block, 128, layers[1], stride=2, dcn=dcn) + self.layer3 = self._make_layer(block, 256, layers[2], stride=2, dcn=dcn) + self.layer4 = self._make_layer(block, 512, layers[3], stride=2, dcn=dcn) + + if self.dcn is not None: + for m in self.modules(): + if isinstance(m, Bottleneck) or isinstance(m, BasicBlock): + if hasattr(m, "conv2_offset"): + constant_init(m.conv2_offset, 0) + + def _make_layer(self, block, planes, blocks, stride=1, dcn=None): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2D( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias_attr=False, + ), + BatchNorm2d(planes * block.expansion, momentum=0.1), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample, dcn=dcn)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes, dcn=dcn)) + self.out_channels.append(planes * block.expansion) + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x2 = self.layer1(x) + x3 = self.layer2(x2) + x4 = self.layer3(x3) + x5 = self.layer4(x4) + + return x2, x3, x4, x5 + + +def load_torch_params(paddle_model, torch_patams): + paddle_params = paddle_model.state_dict() + + fc_names = ["classifier"] + for key, torch_value in torch_patams.items(): + if "num_batches_tracked" in key: + continue + key = ( + key.replace("running_var", "_variance") + .replace("running_mean", "_mean") + .replace("module.", "") + ) + torch_value = torch_value.detach().cpu().numpy() + if key in paddle_params: + flag = [i in key for i in fc_names] + if any(flag) and "weight" in key: # ignore bias + new_shape = [1, 0] + list(range(2, torch_value.ndim)) + print( + f"name: {key}, ori shape: {torch_value.shape}, new shape: {torch_value.transpose(new_shape).shape}" + ) + torch_value = torch_value.transpose(new_shape) + paddle_params[key] = torch_value + else: + print(f"{key} not in paddle") + paddle_model.set_state_dict(paddle_params) + + +def load_models(model, model_name): + import torch.utils.model_zoo as model_zoo + + torch_patams = model_zoo.load_url(model_urls[model_name]) + load_torch_params(model, torch_patams) + + +def resnet18(pretrained=True, **kwargs): + """Constructs a ResNet-18 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + print("load from imagenet") + load_models(model, "resnet18") + return model + + +def deformable_resnet18(pretrained=True, **kwargs): + """Constructs a ResNet-18 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(BasicBlock, [2, 2, 2, 2], dcn=dict(deformable_groups=1), **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + print("load from imagenet") + model.load_state_dict(model_zoo.load_url(model_urls["resnet18"]), strict=False) + return model + + +def resnet34(pretrained=True, **kwargs): + """Constructs a ResNet-34 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(BasicBlock, [3, 4, 6, 3], **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + model.load_state_dict(model_zoo.load_url(model_urls["resnet34"]), strict=False) + return model + + +def resnet50(pretrained=True, **kwargs): + """Constructs a ResNet-50 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + load_models(model, "resnet50") + return model + + +def deformable_resnet50(pretrained=True, **kwargs): + """Constructs a ResNet-50 model with deformable conv. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 6, 3], dcn=dict(deformable_groups=1), **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + model.load_state_dict(model_zoo.load_url(model_urls["resnet50"]), strict=False) + return model + + +def resnet101(pretrained=True, **kwargs): + """Constructs a ResNet-101 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + model.load_state_dict(model_zoo.load_url(model_urls["resnet101"]), strict=False) + return model + + +def resnet152(pretrained=True, **kwargs): + """Constructs a ResNet-152 model. + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 8, 36, 3], **kwargs) + if pretrained: + assert ( + kwargs.get("in_channels", 3) == 3 + ), "in_channels must be 3 when pretrained is True" + model.load_state_dict(model_zoo.load_url(model_urls["resnet152"]), strict=False) + return model + + +if __name__ == "__main__": + x = paddle.zeros([2, 3, 640, 640]) + net = resnet50(pretrained=True) + y = net(x) + for u in y: + print(u.shape) + + print(net.out_channels) diff --git a/benchmark/PaddleOCR_DBNet/models/basic.py b/benchmark/PaddleOCR_DBNet/models/basic.py new file mode 100644 index 0000000..49d6a6a --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/basic.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/6 11:19 +# @Author : zhoujun +from paddle import nn + + +class ConvBnRelu(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + bias=True, + padding_mode="zeros", + inplace=True, + ): + super().__init__() + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias_attr=bias, + padding_mode=padding_mode, + ) + self.bn = nn.BatchNorm2D(out_channels) + self.relu = nn.ReLU() + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + x = self.relu(x) + return x diff --git a/benchmark/PaddleOCR_DBNet/models/head/DBHead.py b/benchmark/PaddleOCR_DBNet/models/head/DBHead.py new file mode 100644 index 0000000..3a57914 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/head/DBHead.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/4 14:54 +# @Author : zhoujun +import paddle +from paddle import nn, ParamAttr + + +class DBHead(nn.Layer): + def __init__(self, in_channels, out_channels, k=50): + super().__init__() + self.k = k + self.binarize = nn.Sequential( + nn.Conv2D( + in_channels, + in_channels // 4, + 3, + padding=1, + weight_attr=ParamAttr(initializer=nn.initializer.KaimingNormal()), + ), + nn.BatchNorm2D( + in_channels // 4, + weight_attr=ParamAttr(initializer=nn.initializer.Constant(1)), + bias_attr=ParamAttr(initializer=nn.initializer.Constant(1e-4)), + ), + nn.ReLU(), + nn.Conv2DTranspose( + in_channels // 4, + in_channels // 4, + 2, + 2, + weight_attr=ParamAttr(initializer=nn.initializer.KaimingNormal()), + ), + nn.BatchNorm2D( + in_channels // 4, + weight_attr=ParamAttr(initializer=nn.initializer.Constant(1)), + bias_attr=ParamAttr(initializer=nn.initializer.Constant(1e-4)), + ), + nn.ReLU(), + nn.Conv2DTranspose( + in_channels // 4, 1, 2, 2, weight_attr=nn.initializer.KaimingNormal() + ), + nn.Sigmoid(), + ) + + self.thresh = self._init_thresh(in_channels) + + def forward(self, x): + shrink_maps = self.binarize(x) + threshold_maps = self.thresh(x) + if self.training: + binary_maps = self.step_function(shrink_maps, threshold_maps) + y = paddle.concat((shrink_maps, threshold_maps, binary_maps), axis=1) + else: + y = paddle.concat((shrink_maps, threshold_maps), axis=1) + return y + + def _init_thresh(self, inner_channels, serial=False, smooth=False, bias=False): + in_channels = inner_channels + if serial: + in_channels += 1 + self.thresh = nn.Sequential( + nn.Conv2D( + in_channels, + inner_channels // 4, + 3, + padding=1, + bias_attr=bias, + weight_attr=ParamAttr(initializer=nn.initializer.KaimingNormal()), + ), + nn.BatchNorm2D( + inner_channels // 4, + weight_attr=ParamAttr(initializer=nn.initializer.Constant(1)), + bias_attr=ParamAttr(initializer=nn.initializer.Constant(1e-4)), + ), + nn.ReLU(), + self._init_upsample( + inner_channels // 4, inner_channels // 4, smooth=smooth, bias=bias + ), + nn.BatchNorm2D( + inner_channels // 4, + weight_attr=ParamAttr(initializer=nn.initializer.Constant(1)), + bias_attr=ParamAttr(initializer=nn.initializer.Constant(1e-4)), + ), + nn.ReLU(), + self._init_upsample(inner_channels // 4, 1, smooth=smooth, bias=bias), + nn.Sigmoid(), + ) + return self.thresh + + def _init_upsample(self, in_channels, out_channels, smooth=False, bias=False): + if smooth: + inter_out_channels = out_channels + if out_channels == 1: + inter_out_channels = in_channels + module_list = [ + nn.Upsample(scale_factor=2, mode="nearest"), + nn.Conv2D( + in_channels, + inter_out_channels, + 3, + 1, + 1, + bias_attr=bias, + weight_attr=ParamAttr(initializer=nn.initializer.KaimingNormal()), + ), + ] + if out_channels == 1: + module_list.append( + nn.Conv2D( + in_channels, + out_channels, + kernel_size=1, + stride=1, + padding=1, + bias_attr=True, + weight_attr=ParamAttr( + initializer=nn.initializer.KaimingNormal() + ), + ) + ) + return nn.Sequential(module_list) + else: + return nn.Conv2DTranspose( + in_channels, + out_channels, + 2, + 2, + weight_attr=ParamAttr(initializer=nn.initializer.KaimingNormal()), + ) + + def step_function(self, x, y): + return paddle.reciprocal(1 + paddle.exp(-self.k * (x - y))) diff --git a/benchmark/PaddleOCR_DBNet/models/head/__init__.py b/benchmark/PaddleOCR_DBNet/models/head/__init__.py new file mode 100644 index 0000000..708ea9a --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/head/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# @Time : 2020/6/5 11:35 +# @Author : zhoujun +from .DBHead import DBHead + +__all__ = ["build_head"] +support_head = ["DBHead"] + + +def build_head(head_name, **kwargs): + assert head_name in support_head, f"all support head is {support_head}" + head = eval(head_name)(**kwargs) + return head diff --git a/benchmark/PaddleOCR_DBNet/models/losses/DB_loss.py b/benchmark/PaddleOCR_DBNet/models/losses/DB_loss.py new file mode 100644 index 0000000..bad05b6 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/losses/DB_loss.py @@ -0,0 +1,50 @@ +import paddle +from models.losses.basic_loss import BalanceCrossEntropyLoss, MaskL1Loss, DiceLoss + + +class DBLoss(paddle.nn.Layer): + def __init__(self, alpha=1.0, beta=10, ohem_ratio=3, reduction="mean", eps=1e-06): + """ + Implement PSE Loss. + :param alpha: binary_map loss 前面的系数 + :param beta: threshold_map loss 前面的系数 + :param ohem_ratio: OHEM的比例 + :param reduction: 'mean' or 'sum'对 batch里的loss 算均值或求和 + """ + super().__init__() + assert reduction in ["mean", "sum"], " reduction must in ['mean','sum']" + self.alpha = alpha + self.beta = beta + self.bce_loss = BalanceCrossEntropyLoss(negative_ratio=ohem_ratio) + self.dice_loss = DiceLoss(eps=eps) + self.l1_loss = MaskL1Loss(eps=eps) + self.ohem_ratio = ohem_ratio + self.reduction = reduction + + def forward(self, pred, batch): + shrink_maps = pred[:, 0, :, :] + threshold_maps = pred[:, 1, :, :] + binary_maps = pred[:, 2, :, :] + loss_shrink_maps = self.bce_loss( + shrink_maps, batch["shrink_map"], batch["shrink_mask"] + ) + loss_threshold_maps = self.l1_loss( + threshold_maps, batch["threshold_map"], batch["threshold_mask"] + ) + metrics = dict( + loss_shrink_maps=loss_shrink_maps, loss_threshold_maps=loss_threshold_maps + ) + if pred.shape[1] > 2: + loss_binary_maps = self.dice_loss( + binary_maps, batch["shrink_map"], batch["shrink_mask"] + ) + metrics["loss_binary_maps"] = loss_binary_maps + loss_all = ( + self.alpha * loss_shrink_maps + + self.beta * loss_threshold_maps + + loss_binary_maps + ) + metrics["loss"] = loss_all + else: + metrics["loss"] = loss_shrink_maps + return metrics diff --git a/benchmark/PaddleOCR_DBNet/models/losses/__init__.py b/benchmark/PaddleOCR_DBNet/models/losses/__init__.py new file mode 100644 index 0000000..e783f7c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/losses/__init__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# @Time : 2020/6/5 11:36 +# @Author : zhoujun +import copy +from .DB_loss import DBLoss + +__all__ = ["build_loss"] +support_loss = ["DBLoss"] + + +def build_loss(config): + copy_config = copy.deepcopy(config) + loss_type = copy_config.pop("type") + assert loss_type in support_loss, f"all support loss is {support_loss}" + criterion = eval(loss_type)(**copy_config) + return criterion diff --git a/benchmark/PaddleOCR_DBNet/models/losses/basic_loss.py b/benchmark/PaddleOCR_DBNet/models/losses/basic_loss.py new file mode 100644 index 0000000..5f6fb5e --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/losses/basic_loss.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/4 14:39 +# @Author : zhoujun +import paddle +import paddle.nn as nn + + +class BalanceCrossEntropyLoss(nn.Layer): + """ + Balanced cross entropy loss. + Shape: + - Input: :math:`(N, 1, H, W)` + - GT: :math:`(N, 1, H, W)`, same shape as the input + - Mask: :math:`(N, H, W)`, same spatial shape as the input + - Output: scalar. + + """ + + def __init__(self, negative_ratio=3.0, eps=1e-6): + super(BalanceCrossEntropyLoss, self).__init__() + self.negative_ratio = negative_ratio + self.eps = eps + + def forward( + self, + pred: paddle.Tensor, + gt: paddle.Tensor, + mask: paddle.Tensor, + return_origin=False, + ): + """ + Args: + pred: shape :math:`(N, 1, H, W)`, the prediction of network + gt: shape :math:`(N, 1, H, W)`, the target + mask: shape :math:`(N, H, W)`, the mask indicates positive regions + """ + positive = gt * mask + negative = (1 - gt) * mask + positive_count = int(positive.sum()) + negative_count = min( + int(negative.sum()), int(positive_count * self.negative_ratio) + ) + loss = nn.functional.binary_cross_entropy(pred, gt, reduction="none") + positive_loss = loss * positive + negative_loss = loss * negative + negative_loss, _ = negative_loss.reshape([-1]).topk(negative_count) + + balance_loss = (positive_loss.sum() + negative_loss.sum()) / ( + positive_count + negative_count + self.eps + ) + + if return_origin: + return balance_loss, loss + return balance_loss + + +class DiceLoss(nn.Layer): + """ + Loss function from https://arxiv.org/abs/1707.03237, + where iou computation is introduced heatmap manner to measure the + diversity between tow heatmaps. + """ + + def __init__(self, eps=1e-6): + super(DiceLoss, self).__init__() + self.eps = eps + + def forward(self, pred: paddle.Tensor, gt, mask, weights=None): + """ + pred: one or two heatmaps of shape (N, 1, H, W), + the losses of tow heatmaps are added together. + gt: (N, 1, H, W) + mask: (N, H, W) + """ + return self._compute(pred, gt, mask, weights) + + def _compute(self, pred, gt, mask, weights): + if len(pred.shape) == 4: + pred = pred[:, 0, :, :] + gt = gt[:, 0, :, :] + assert pred.shape == gt.shape + assert pred.shape == mask.shape + if weights is not None: + assert weights.shape == mask.shape + mask = weights * mask + intersection = (pred * gt * mask).sum() + + union = (pred * mask).sum() + (gt * mask).sum() + self.eps + loss = 1 - 2.0 * intersection / union + assert loss <= 1 + return loss + + +class MaskL1Loss(nn.Layer): + def __init__(self, eps=1e-6): + super(MaskL1Loss, self).__init__() + self.eps = eps + + def forward(self, pred: paddle.Tensor, gt, mask): + loss = (paddle.abs(pred - gt) * mask).sum() / (mask.sum() + self.eps) + return loss diff --git a/benchmark/PaddleOCR_DBNet/models/model.py b/benchmark/PaddleOCR_DBNet/models/model.py new file mode 100644 index 0000000..ed36f90 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/model.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:57 +# @Author : zhoujun +from addict import Dict +from paddle import nn +import paddle.nn.functional as F + +from models.backbone import build_backbone +from models.neck import build_neck +from models.head import build_head + + +class Model(nn.Layer): + def __init__(self, model_config: dict): + """ + PANnet + :param model_config: 模型配置 + """ + super().__init__() + model_config = Dict(model_config) + backbone_type = model_config.backbone.pop("type") + neck_type = model_config.neck.pop("type") + head_type = model_config.head.pop("type") + self.backbone = build_backbone(backbone_type, **model_config.backbone) + self.neck = build_neck( + neck_type, in_channels=self.backbone.out_channels, **model_config.neck + ) + self.head = build_head( + head_type, in_channels=self.neck.out_channels, **model_config.head + ) + self.name = f"{backbone_type}_{neck_type}_{head_type}" + + def forward(self, x): + _, _, H, W = x.shape + backbone_out = self.backbone(x) + neck_out = self.neck(backbone_out) + y = self.head(neck_out) + y = F.interpolate(y, size=(H, W), mode="bilinear", align_corners=True) + return y diff --git a/benchmark/PaddleOCR_DBNet/models/neck/FPN.py b/benchmark/PaddleOCR_DBNet/models/neck/FPN.py new file mode 100644 index 0000000..3c49adf --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/neck/FPN.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/9/13 10:29 +# @Author : zhoujun +import paddle +import paddle.nn.functional as F +from paddle import nn + +from models.basic import ConvBnRelu + + +class FPN(nn.Layer): + def __init__(self, in_channels, inner_channels=256, **kwargs): + """ + :param in_channels: 基础网络输出的维度 + :param kwargs: + """ + super().__init__() + inplace = True + self.conv_out = inner_channels + inner_channels = inner_channels // 4 + # reduce layers + self.reduce_conv_c2 = ConvBnRelu( + in_channels[0], inner_channels, kernel_size=1, inplace=inplace + ) + self.reduce_conv_c3 = ConvBnRelu( + in_channels[1], inner_channels, kernel_size=1, inplace=inplace + ) + self.reduce_conv_c4 = ConvBnRelu( + in_channels[2], inner_channels, kernel_size=1, inplace=inplace + ) + self.reduce_conv_c5 = ConvBnRelu( + in_channels[3], inner_channels, kernel_size=1, inplace=inplace + ) + # Smooth layers + self.smooth_p4 = ConvBnRelu( + inner_channels, inner_channels, kernel_size=3, padding=1, inplace=inplace + ) + self.smooth_p3 = ConvBnRelu( + inner_channels, inner_channels, kernel_size=3, padding=1, inplace=inplace + ) + self.smooth_p2 = ConvBnRelu( + inner_channels, inner_channels, kernel_size=3, padding=1, inplace=inplace + ) + + self.conv = nn.Sequential( + nn.Conv2D(self.conv_out, self.conv_out, kernel_size=3, padding=1, stride=1), + nn.BatchNorm2D(self.conv_out), + nn.ReLU(), + ) + self.out_channels = self.conv_out + + def forward(self, x): + c2, c3, c4, c5 = x + # Top-down + p5 = self.reduce_conv_c5(c5) + p4 = self._upsample_add(p5, self.reduce_conv_c4(c4)) + p4 = self.smooth_p4(p4) + p3 = self._upsample_add(p4, self.reduce_conv_c3(c3)) + p3 = self.smooth_p3(p3) + p2 = self._upsample_add(p3, self.reduce_conv_c2(c2)) + p2 = self.smooth_p2(p2) + + x = self._upsample_cat(p2, p3, p4, p5) + x = self.conv(x) + return x + + def _upsample_add(self, x, y): + return F.interpolate(x, size=y.shape[2:]) + y + + def _upsample_cat(self, p2, p3, p4, p5): + h, w = p2.shape[2:] + p3 = F.interpolate(p3, size=(h, w)) + p4 = F.interpolate(p4, size=(h, w)) + p5 = F.interpolate(p5, size=(h, w)) + return paddle.concat([p2, p3, p4, p5], axis=1) diff --git a/benchmark/PaddleOCR_DBNet/models/neck/__init__.py b/benchmark/PaddleOCR_DBNet/models/neck/__init__.py new file mode 100644 index 0000000..d63b8b5 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/models/neck/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# @Time : 2020/6/5 11:34 +# @Author : zhoujun +from .FPN import FPN + +__all__ = ["build_neck"] +support_neck = ["FPN"] + + +def build_neck(neck_name, **kwargs): + assert neck_name in support_neck, f"all support neck is {support_neck}" + neck = eval(neck_name)(**kwargs) + return neck diff --git a/benchmark/PaddleOCR_DBNet/multi_gpu_train.sh b/benchmark/PaddleOCR_DBNet/multi_gpu_train.sh new file mode 100644 index 0000000..4b9a915 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/multi_gpu_train.sh @@ -0,0 +1,2 @@ +# export NCCL_P2P_DISABLE=1 +CUDA_VISIBLE_DEVICES=0,1,2,3 python3 -m paddle.distributed.launch tools/train.py --config_file "config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml" diff --git a/benchmark/PaddleOCR_DBNet/post_processing/__init__.py b/benchmark/PaddleOCR_DBNet/post_processing/__init__.py new file mode 100644 index 0000000..7d3a448 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/post_processing/__init__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/5 15:17 +# @Author : zhoujun + +from .seg_detector_representer import SegDetectorRepresenter + + +def get_post_processing(config): + try: + cls = eval(config["type"])(**config["args"]) + return cls + except: + return None diff --git a/benchmark/PaddleOCR_DBNet/post_processing/seg_detector_representer.py b/benchmark/PaddleOCR_DBNet/post_processing/seg_detector_representer.py new file mode 100644 index 0000000..6a394fb --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/post_processing/seg_detector_representer.py @@ -0,0 +1,191 @@ +import cv2 +import numpy as np +import pyclipper +import paddle +from shapely.geometry import Polygon + + +class SegDetectorRepresenter: + def __init__( + self, thresh=0.3, box_thresh=0.7, max_candidates=1000, unclip_ratio=1.5 + ): + self.min_size = 3 + self.thresh = thresh + self.box_thresh = box_thresh + self.max_candidates = max_candidates + self.unclip_ratio = unclip_ratio + + def __call__(self, batch, pred, is_output_polygon=False): + """ + batch: (image, polygons, ignore_tags + batch: a dict produced by dataloaders. + image: tensor of shape (N, C, H, W). + polygons: tensor of shape (N, K, 4, 2), the polygons of objective regions. + ignore_tags: tensor of shape (N, K), indicates whether a region is ignorable or not. + shape: the original shape of images. + filename: the original filenames of images. + pred: + binary: text region segmentation map, with shape (N, H, W) + thresh: [if exists] thresh hold prediction with shape (N, H, W) + thresh_binary: [if exists] binarized with threshold, (N, H, W) + """ + if isinstance(pred, paddle.Tensor): + pred = pred.numpy() + pred = pred[:, 0, :, :] + segmentation = self.binarize(pred) + boxes_batch = [] + scores_batch = [] + for batch_index in range(pred.shape[0]): + height, width = batch["shape"][batch_index] + if is_output_polygon: + boxes, scores = self.polygons_from_bitmap( + pred[batch_index], segmentation[batch_index], width, height + ) + else: + boxes, scores = self.boxes_from_bitmap( + pred[batch_index], segmentation[batch_index], width, height + ) + boxes_batch.append(boxes) + scores_batch.append(scores) + return boxes_batch, scores_batch + + def binarize(self, pred): + return pred > self.thresh + + def polygons_from_bitmap(self, pred, _bitmap, dest_width, dest_height): + """ + _bitmap: single map with shape (H, W), + whose values are binarized as {0, 1} + """ + + assert len(_bitmap.shape) == 2 + bitmap = _bitmap # The first channel + height, width = bitmap.shape + boxes = [] + scores = [] + + contours, _ = cv2.findContours( + (bitmap * 255).astype(np.uint8), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE + ) + + for contour in contours[: self.max_candidates]: + epsilon = 0.005 * cv2.arcLength(contour, True) + approx = cv2.approxPolyDP(contour, epsilon, True) + points = approx.reshape((-1, 2)) + if points.shape[0] < 4: + continue + # _, sside = self.get_mini_boxes(contour) + # if sside < self.min_size: + # continue + score = self.box_score_fast(pred, contour.squeeze(1)) + if self.box_thresh > score: + continue + + if points.shape[0] > 2: + box = self.unclip(points, unclip_ratio=self.unclip_ratio) + if len(box) > 1: + continue + else: + continue + box = box.reshape(-1, 2) + _, sside = self.get_mini_boxes(box.reshape((-1, 1, 2))) + if sside < self.min_size + 2: + continue + + if not isinstance(dest_width, int): + dest_width = dest_width.item() + dest_height = dest_height.item() + + box[:, 0] = np.clip(np.round(box[:, 0] / width * dest_width), 0, dest_width) + box[:, 1] = np.clip( + np.round(box[:, 1] / height * dest_height), 0, dest_height + ) + boxes.append(box) + scores.append(score) + return boxes, scores + + def boxes_from_bitmap(self, pred, _bitmap, dest_width, dest_height): + """ + _bitmap: single map with shape (H, W), + whose values are binarized as {0, 1} + """ + + assert len(_bitmap.shape) == 2 + bitmap = _bitmap # The first channel + height, width = bitmap.shape + contours, _ = cv2.findContours( + (bitmap * 255).astype(np.uint8), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE + ) + num_contours = min(len(contours), self.max_candidates) + boxes = np.zeros((num_contours, 4, 2), dtype=np.int16) + scores = np.zeros((num_contours,), dtype=np.float32) + + for index in range(num_contours): + contour = contours[index].squeeze(1) + points, sside = self.get_mini_boxes(contour) + if sside < self.min_size: + continue + points = np.array(points) + score = self.box_score_fast(pred, contour) + if self.box_thresh > score: + continue + + box = self.unclip(points, unclip_ratio=self.unclip_ratio).reshape(-1, 1, 2) + box, sside = self.get_mini_boxes(box) + if sside < self.min_size + 2: + continue + box = np.array(box) + if not isinstance(dest_width, int): + dest_width = dest_width.item() + dest_height = dest_height.item() + + box[:, 0] = np.clip(np.round(box[:, 0] / width * dest_width), 0, dest_width) + box[:, 1] = np.clip( + np.round(box[:, 1] / height * dest_height), 0, dest_height + ) + boxes[index, :, :] = box.astype(np.int16) + scores[index] = score + return boxes, scores + + def unclip(self, box, unclip_ratio=1.5): + poly = Polygon(box) + distance = poly.area * unclip_ratio / poly.length + offset = pyclipper.PyclipperOffset() + offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + expanded = np.array(offset.Execute(distance)) + return expanded + + def get_mini_boxes(self, contour): + bounding_box = cv2.minAreaRect(contour) + points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0]) + + index_1, index_2, index_3, index_4 = 0, 1, 2, 3 + if points[1][1] > points[0][1]: + index_1 = 0 + index_4 = 1 + else: + index_1 = 1 + index_4 = 0 + if points[3][1] > points[2][1]: + index_2 = 2 + index_3 = 3 + else: + index_2 = 3 + index_3 = 2 + + box = [points[index_1], points[index_2], points[index_3], points[index_4]] + return box, min(bounding_box[1]) + + def box_score_fast(self, bitmap, _box): + h, w = bitmap.shape[:2] + box = _box.copy() + xmin = np.clip(np.floor(box[:, 0].min()).astype(np.int32), 0, w - 1) + xmax = np.clip(np.ceil(box[:, 0].max()).astype(np.int32), 0, w - 1) + ymin = np.clip(np.floor(box[:, 1].min()).astype(np.int32), 0, h - 1) + ymax = np.clip(np.ceil(box[:, 1].max()).astype(np.int32), 0, h - 1) + + mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8) + box[:, 0] = box[:, 0] - xmin + box[:, 1] = box[:, 1] - ymin + cv2.fillPoly(mask, box.reshape(1, -1, 2).astype(np.int32), 1) + return cv2.mean(bitmap[ymin : ymax + 1, xmin : xmax + 1], mask)[0] diff --git a/benchmark/PaddleOCR_DBNet/predict.sh b/benchmark/PaddleOCR_DBNet/predict.sh new file mode 100644 index 0000000..a4b9bfa --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/predict.sh @@ -0,0 +1 @@ +CUDA_VISIBLE_DEVICES=0 python tools/predict.py --model_path model_best.pth --input_folder ./input --output_folder ./output --thre 0.7 --polygon --show --save_result diff --git a/benchmark/PaddleOCR_DBNet/requirement.txt b/benchmark/PaddleOCR_DBNet/requirement.txt new file mode 100644 index 0000000..23240ac --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/requirement.txt @@ -0,0 +1,14 @@ +anyconfig +future +imgaug +matplotlib +numpy +opencv-python +Polygon3 +pyclipper +PyYAML +scikit-image +Shapely +tqdm +addict +visualdl diff --git a/benchmark/PaddleOCR_DBNet/single_gpu_train.sh b/benchmark/PaddleOCR_DBNet/single_gpu_train.sh new file mode 100644 index 0000000..6803201 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/single_gpu_train.sh @@ -0,0 +1 @@ +CUDA_VISIBLE_DEVICES=0 python3 tools/train.py --config_file "config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml" diff --git a/benchmark/PaddleOCR_DBNet/test/README.MD b/benchmark/PaddleOCR_DBNet/test/README.MD new file mode 100644 index 0000000..3e2bb1a --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test/README.MD @@ -0,0 +1,8 @@ +Place the images that you want to detect here. You better named them as such: +img_10.jpg +img_11.jpg +img_{img_id}.jpg + +For predicting single images, you can change the `img_path` in the `/tools/predict.py` to your image number. + +The result will be saved in the output_folder(default is test/output) you give in predict.sh diff --git a/benchmark/PaddleOCR_DBNet/test_tipc/benchmark_train.sh b/benchmark/PaddleOCR_DBNet/test_tipc/benchmark_train.sh new file mode 100644 index 0000000..d94dac2 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test_tipc/benchmark_train.sh @@ -0,0 +1,287 @@ +#!/bin/bash +source test_tipc/common_func.sh + +# run benchmark sh +# Usage: +# bash run_benchmark_train.sh config.txt params +# or +# bash run_benchmark_train.sh config.txt + +function func_parser_params(){ + strs=$1 + IFS="=" + array=(${strs}) + tmp=${array[1]} + echo ${tmp} +} + +function set_dynamic_epoch(){ + string=$1 + num=$2 + _str=${string:1:6} + IFS="C" + arr=(${_str}) + M=${arr[0]} + P=${arr[1]} + ep=`expr $num \* $M \* $P` + echo $ep +} + +function func_sed_params(){ + filename=$1 + line=$2 + param_value=$3 + params=`sed -n "${line}p" $filename` + IFS=":" + array=(${params}) + key=${array[0]} + value=${array[1]} + + new_params="${key}:${param_value}" + IFS=";" + cmd="sed -i '${line}s/.*/${new_params}/' '${filename}'" + eval $cmd +} + +function set_gpu_id(){ + string=$1 + _str=${string:1:6} + IFS="C" + arr=(${_str}) + M=${arr[0]} + P=${arr[1]} + gn=`expr $P - 1` + gpu_num=`expr $gn / $M` + seq=`seq -s "," 0 $gpu_num` + echo $seq +} + +function get_repo_name(){ + IFS=";" + cur_dir=$(pwd) + IFS="/" + arr=(${cur_dir}) + echo ${arr[-1]} +} + +FILENAME=$1 +# copy FILENAME as new +new_filename="./test_tipc/benchmark_train.txt" +cmd=`yes|cp $FILENAME $new_filename` +FILENAME=$new_filename +# MODE must be one of ['benchmark_train'] +MODE=$2 +PARAMS=$3 + +to_static="" +# parse "to_static" options and modify trainer into "to_static_trainer" +if [[ $PARAMS =~ "dynamicTostatic" ]] ;then + to_static="d2sT_" + sed -i 's/trainer:norm_train/trainer:to_static_train/g' $FILENAME + # clear PARAM contents + if [ $PARAMS = "to_static" ] ;then + PARAMS="" + fi +fi +# bash test_tipc/benchmark_train.sh test_tipc/configs/det_mv3_db_v2_0/train_benchmark.txt benchmark_train dynamic_bs8_fp32_DP_N1C8 +# bash test_tipc/benchmark_train.sh test_tipc/configs/det_mv3_db_v2_0/train_benchmark.txt benchmark_train dynamicTostatic_bs8_fp32_DP_N1C8 +# bash test_tipc/benchmark_train.sh test_tipc/configs/det_mv3_db_v2_0/train_benchmark.txt benchmark_train dynamic_bs8_null_DP_N1C1 +IFS=$'\n' +# parser params from train_benchmark.txt +dataline=`cat $FILENAME` +# parser params +IFS=$'\n' +lines=(${dataline}) +model_name=$(func_parser_value "${lines[1]}") +python_name=$(func_parser_value "${lines[2]}") + +# set env +python=${python_name} +export str_tmp=$(echo `pip list|grep paddlepaddle-gpu|awk -F ' ' '{print $2}'`) +export frame_version=${str_tmp%%.post*} +export frame_commit=$(echo `${python} -c "import paddle;print(paddle.version.commit)"`) + +# 获取benchmark_params所在的行数 +line_num=`grep -n -w "train_benchmark_params" $FILENAME | cut -d ":" -f 1` +# for train log parser +batch_size=$(func_parser_value "${lines[line_num]}") +line_num=`expr $line_num + 1` +fp_items=$(func_parser_value "${lines[line_num]}") +line_num=`expr $line_num + 1` +epoch=$(func_parser_value "${lines[line_num]}") + +line_num=`expr $line_num + 1` +profile_option_key=$(func_parser_key "${lines[line_num]}") +profile_option_params=$(func_parser_value "${lines[line_num]}") +profile_option="${profile_option_key}:${profile_option_params}" + +line_num=`expr $line_num + 1` +flags_value=$(func_parser_value "${lines[line_num]}") +# set flags +IFS=";" +flags_list=(${flags_value}) +for _flag in ${flags_list[*]}; do + cmd="export ${_flag}" + eval $cmd +done + +# set log_name +repo_name=$(get_repo_name ) +SAVE_LOG=${BENCHMARK_LOG_DIR:-$(pwd)} # */benchmark_log +mkdir -p "${SAVE_LOG}/benchmark_log/" +status_log="${SAVE_LOG}/benchmark_log/results.log" + +# The number of lines in which train params can be replaced. +line_python=3 +line_gpuid=4 +line_precision=6 +line_epoch=7 +line_batchsize=9 +line_profile=13 +line_eval_py=24 +line_export_py=30 + +func_sed_params "$FILENAME" "${line_eval_py}" "null" +func_sed_params "$FILENAME" "${line_export_py}" "null" +func_sed_params "$FILENAME" "${line_python}" "$python" + +# if params +if [ ! -n "$PARAMS" ] ;then + # PARAMS input is not a word. + IFS="|" + batch_size_list=(${batch_size}) + fp_items_list=(${fp_items}) + device_num_list=(N1C4) + run_mode="DP" +elif [[ ${PARAMS} = "dynamicTostatic" ]];then + IFS="|" + model_type=$PARAMS + batch_size_list=(${batch_size}) + fp_items_list=(${fp_items}) + device_num_list=(N1C4) + run_mode="DP" +else + # parser params from input: modeltype_bs${bs_item}_${fp_item}_${run_mode}_${device_num} + IFS="_" + params_list=(${PARAMS}) + model_type=${params_list[0]} + batch_size=${params_list[1]} + batch_size=`echo ${batch_size} | tr -cd "[0-9]" ` + precision=${params_list[2]} + run_mode=${params_list[3]} + device_num=${params_list[4]} + IFS=";" + + if [ ${precision} = "fp16" ];then + precision="amp" + fi + + epoch=$(set_dynamic_epoch $device_num $epoch) + fp_items_list=($precision) + batch_size_list=($batch_size) + device_num_list=($device_num) +fi + +IFS="|" +for batch_size in ${batch_size_list[*]}; do + for train_precision in ${fp_items_list[*]}; do + for device_num in ${device_num_list[*]}; do + # sed batchsize and precision + if [ ${train_precision} = "amp" ];then + precision="fp16" + else + precision="fp32" + fi + + func_sed_params "$FILENAME" "${line_precision}" "$train_precision" + func_sed_params "$FILENAME" "${line_batchsize}" "$MODE=$batch_size" + func_sed_params "$FILENAME" "${line_epoch}" "$MODE=$epoch" + gpu_id=$(set_gpu_id $device_num) + + if [ ${#gpu_id} -le 1 ];then + log_path="$SAVE_LOG/profiling_log" + mkdir -p $log_path + log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}profiling" + func_sed_params "$FILENAME" "${line_gpuid}" "0" # sed used gpu_id + # set profile_option params + tmp=`sed -i "${line_profile}s/.*/${profile_option}/" "${FILENAME}"` + + # run test_train_inference_python.sh + cmd="bash test_tipc/test_train_inference_python.sh ${FILENAME} benchmark_train > ${log_path}/${log_name} 2>&1 " + echo $cmd + eval $cmd + eval "cat ${log_path}/${log_name}" + + # without profile + log_path="$SAVE_LOG/train_log" + speed_log_path="$SAVE_LOG/index" + mkdir -p $log_path + mkdir -p $speed_log_path + log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}log" + speed_log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}speed" + func_sed_params "$FILENAME" "${line_profile}" "null" # sed profile_id as null + cmd="bash test_tipc/test_train_inference_python.sh ${FILENAME} benchmark_train > ${log_path}/${log_name} 2>&1 " + echo $cmd + job_bt=`date '+%Y%m%d%H%M%S'` + eval $cmd + job_et=`date '+%Y%m%d%H%M%S'` + export model_run_time=$((${job_et}-${job_bt})) + eval "cat ${log_path}/${log_name}" + + # parser log + _model_name="${model_name}_bs${batch_size}_${precision}_${run_mode}" + cmd="${python} ${BENCHMARK_ROOT}/scripts/analysis.py --filename ${log_path}/${log_name} \ + --speed_log_file '${speed_log_path}/${speed_log_name}' \ + --model_name ${_model_name} \ + --base_batch_size ${batch_size} \ + --run_mode ${run_mode} \ + --fp_item ${precision} \ + --keyword ips: \ + --skip_steps 2 \ + --device_num ${device_num} \ + --speed_unit samples/s \ + --convergence_key loss: " + echo $cmd + eval $cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${cmd}" "${status_log}" + else + IFS=";" + unset_env=`unset CUDA_VISIBLE_DEVICES` + log_path="$SAVE_LOG/train_log" + speed_log_path="$SAVE_LOG/index" + mkdir -p $log_path + mkdir -p $speed_log_path + log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}log" + speed_log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}speed" + func_sed_params "$FILENAME" "${line_gpuid}" "$gpu_id" # sed used gpu_id + func_sed_params "$FILENAME" "${line_profile}" "null" # sed --profile_option as null + cmd="bash test_tipc/test_train_inference_python.sh ${FILENAME} benchmark_train > ${log_path}/${log_name} 2>&1 " + echo $cmd + job_bt=`date '+%Y%m%d%H%M%S'` + eval $cmd + job_et=`date '+%Y%m%d%H%M%S'` + export model_run_time=$((${job_et}-${job_bt})) + eval "cat ${log_path}/${log_name}" + # parser log + _model_name="${model_name}_bs${batch_size}_${precision}_${run_mode}" + + cmd="${python} ${BENCHMARK_ROOT}/scripts/analysis.py --filename ${log_path}/${log_name} \ + --speed_log_file '${speed_log_path}/${speed_log_name}' \ + --model_name ${_model_name} \ + --base_batch_size ${batch_size} \ + --run_mode ${run_mode} \ + --fp_item ${precision} \ + --keyword ips: \ + --skip_steps 2 \ + --device_num ${device_num} \ + --speed_unit images/s \ + --convergence_key loss: " + echo $cmd + eval $cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${cmd}" "${status_log}" + fi + done + done +done diff --git a/benchmark/PaddleOCR_DBNet/test_tipc/common_func.sh b/benchmark/PaddleOCR_DBNet/test_tipc/common_func.sh new file mode 100644 index 0000000..9ec22f0 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test_tipc/common_func.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +function func_parser_key(){ + strs=$1 + IFS=":" + array=(${strs}) + tmp=${array[0]} + echo ${tmp} +} + +function func_parser_value(){ + strs=$1 + IFS=":" + array=(${strs}) + tmp=${array[1]} + echo ${tmp} +} + +function func_set_params(){ + key=$1 + value=$2 + if [ ${key}x = "null"x ];then + echo " " + elif [[ ${value} = "null" ]] || [[ ${value} = " " ]] || [ ${#value} -le 0 ];then + echo " " + else + echo "${key}=${value}" + fi +} + +function func_parser_params(){ + strs=$1 + MODE=$2 + IFS=":" + array=(${strs}) + key=${array[0]} + tmp=${array[1]} + IFS="|" + res="" + for _params in ${tmp[*]}; do + IFS="=" + array=(${_params}) + mode=${array[0]} + value=${array[1]} + if [[ ${mode} = ${MODE} ]]; then + IFS="|" + #echo $(func_set_params "${mode}" "${value}") + echo $value + break + fi + IFS="|" + done + echo ${res} +} + +function status_check(){ + last_status=$1 # the exit code + run_command=$2 + run_log=$3 + model_name=$4 + log_path=$5 + if [ $last_status -eq 0 ]; then + echo -e "\033[33m Run successfully with command - ${model_name} - ${run_command} - ${log_path} \033[0m" | tee -a ${run_log} + else + echo -e "\033[33m Run failed with command - ${model_name} - ${run_command} - ${log_path} \033[0m" | tee -a ${run_log} + fi +} diff --git a/benchmark/PaddleOCR_DBNet/test_tipc/configs/det_res50_db/train_infer_python.txt b/benchmark/PaddleOCR_DBNet/test_tipc/configs/det_res50_db/train_infer_python.txt new file mode 100644 index 0000000..7dc3da3 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test_tipc/configs/det_res50_db/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:det_res50_db +python:python +gpu_list:0|0,1 +trainer.use_gpu:True|True +amp:null +trainer.epochs:lite_train_lite_infer=1|whole_train_whole_infer=300 +trainer.output_dir:./output/ +dataset.train.loader.batch_size:lite_train_lite_infer=8|whole_train_lite_infer=8 +trainer.finetune_checkpoint:null +train_model_name:checkpoint/model_latest.pth +train_infer_img_dir:imgs/paper/db.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py --config_file config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml -o trainer.log_iter=1 trainer.enable_eval=False dataset.train.loader.shuffle=false arch.backbone.pretrained=False +quant_export:null +fpgm_export:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +trainer.output_dir:./output/ +trainer.resume_checkpoint: +norm_export:tools/export_model.py --config_file config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/det_r50_vd_db_v2.0_train/best_accuracy +infer_export:tools/export_model.py --config_file config/icdar2015_resnet50_FPN_DBhead_polyLR.yaml -o +infer_quant:False +inference:tools/infer.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--batch_size:1 +--use_tensorrt:False +--precision:fp32 +--model_dir: +--img_path:imgs/paper/db.jpg +--save_log_path:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================to_static_train_benchmark_params=========================== +to_static_train:trainer.to_static=true diff --git a/benchmark/PaddleOCR_DBNet/test_tipc/prepare.sh b/benchmark/PaddleOCR_DBNet/test_tipc/prepare.sh new file mode 100644 index 0000000..a961603 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test_tipc/prepare.sh @@ -0,0 +1,54 @@ +#!/bin/bash +source test_tipc/common_func.sh + +FILENAME=$1 + +# MODE be one of ['lite_train_lite_infer' 'lite_train_whole_infer' 'whole_train_whole_infer', +# 'whole_infer', 'klquant_whole_infer', +# 'cpp_infer', 'serving_infer'] + +MODE=$2 + +dataline=$(cat ${FILENAME}) + +# parser params +IFS=$'\n' +lines=(${dataline}) + +# The training params +model_name=$(func_parser_value "${lines[1]}") + +trainer_list=$(func_parser_value "${lines[14]}") + +if [ ${MODE} = "lite_train_lite_infer" ];then + python_name_list=$(func_parser_value "${lines[2]}") + array=(${python_name_list}) + python_name=${array[0]} + ${python_name} -m pip install -r requirement.txt + if [[ ${model_name} =~ "det_res50_db" ]];then + wget -nc https://paddle-wheel.bj.bcebos.com/benchmark/resnet50-19c8e357.pth -O /root/.cache/torch/hub/checkpoints/resnet50-19c8e357.pth + + # 下载数据集并解压 + rm -rf datasets + wget -nc https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/benchmark_train/datasets.tar + tar xf datasets.tar + fi +elif [ ${MODE} = "benchmark_train" ];then + python_name_list=$(func_parser_value "${lines[2]}") + array=(${python_name_list}) + python_name=${array[0]} + ${python_name} -m pip install -r requirement.txt + if [[ ${model_name} =~ "det_res50_db" ]];then + wget -nc https://paddle-wheel.bj.bcebos.com/benchmark/resnet50-19c8e357.pth -O /root/.cache/torch/hub/checkpoints/resnet50-19c8e357.pth + + # 下载数据集并解压 + rm -rf datasets + wget -nc https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/benchmark_train/datasets.tar + tar xf datasets.tar + # expand gt.txt 2 times + # cd ./train_data/icdar2015/text_localization + # for i in `seq 2`;do cp train_icdar2015_label.txt dup$i.txt;done + # cat dup* > train_icdar2015_label.txt && rm -rf dup* + # cd ../../../ + fi +fi diff --git a/benchmark/PaddleOCR_DBNet/test_tipc/test_train_inference_python.sh b/benchmark/PaddleOCR_DBNet/test_tipc/test_train_inference_python.sh new file mode 100644 index 0000000..33c81e1 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/test_tipc/test_train_inference_python.sh @@ -0,0 +1,343 @@ +#!/bin/bash +source test_tipc/common_func.sh + +FILENAME=$1 +# MODE be one of ['lite_train_lite_infer' 'lite_train_whole_infer' 'whole_train_whole_infer', 'whole_infer'] +MODE=$2 + +dataline=$(awk 'NR>=1{print}' $FILENAME) + +# parser params +IFS=$'\n' +lines=(${dataline}) + +# The training params +model_name=$(func_parser_value "${lines[1]}") +python=$(func_parser_value "${lines[2]}") +gpu_list=$(func_parser_value "${lines[3]}") +train_use_gpu_key=$(func_parser_key "${lines[4]}") +train_use_gpu_value=$(func_parser_value "${lines[4]}") +autocast_list=$(func_parser_value "${lines[5]}") +autocast_key=$(func_parser_key "${lines[5]}") +epoch_key=$(func_parser_key "${lines[6]}") +epoch_num=$(func_parser_params "${lines[6]}" "${MODE}") +save_model_key=$(func_parser_key "${lines[7]}") +train_batch_key=$(func_parser_key "${lines[8]}") +train_batch_value=$(func_parser_params "${lines[8]}" "${MODE}") +pretrain_model_key=$(func_parser_key "${lines[9]}") +pretrain_model_value=$(func_parser_value "${lines[9]}") +train_model_name=$(func_parser_value "${lines[10]}") +train_infer_img_dir=$(func_parser_value "${lines[11]}") +train_param_key1=$(func_parser_key "${lines[12]}") +train_param_value1=$(func_parser_value "${lines[12]}") + +trainer_list=$(func_parser_value "${lines[14]}") +trainer_norm=$(func_parser_key "${lines[15]}") +norm_trainer=$(func_parser_value "${lines[15]}") +pact_key=$(func_parser_key "${lines[16]}") +pact_trainer=$(func_parser_value "${lines[16]}") +fpgm_key=$(func_parser_key "${lines[17]}") +fpgm_trainer=$(func_parser_value "${lines[17]}") +distill_key=$(func_parser_key "${lines[18]}") +distill_trainer=$(func_parser_value "${lines[18]}") +trainer_key1=$(func_parser_key "${lines[19]}") +trainer_value1=$(func_parser_value "${lines[19]}") +trainer_key2=$(func_parser_key "${lines[20]}") +trainer_value2=$(func_parser_value "${lines[20]}") + +eval_py=$(func_parser_value "${lines[23]}") +eval_key1=$(func_parser_key "${lines[24]}") +eval_value1=$(func_parser_value "${lines[24]}") + +save_infer_key=$(func_parser_key "${lines[27]}") +export_weight=$(func_parser_key "${lines[28]}") +norm_export=$(func_parser_value "${lines[29]}") +pact_export=$(func_parser_value "${lines[30]}") +fpgm_export=$(func_parser_value "${lines[31]}") +distill_export=$(func_parser_value "${lines[32]}") +export_key1=$(func_parser_key "${lines[33]}") +export_value1=$(func_parser_value "${lines[33]}") +export_key2=$(func_parser_key "${lines[34]}") +export_value2=$(func_parser_value "${lines[34]}") +inference_dir=$(func_parser_value "${lines[35]}") + +# parser inference model +infer_model_dir_list=$(func_parser_value "${lines[36]}") +infer_export_list=$(func_parser_value "${lines[37]}") +infer_is_quant=$(func_parser_value "${lines[38]}") +# parser inference +inference_py=$(func_parser_value "${lines[39]}") +use_gpu_key=$(func_parser_key "${lines[40]}") +use_gpu_list=$(func_parser_value "${lines[40]}") +use_mkldnn_key=$(func_parser_key "${lines[41]}") +use_mkldnn_list=$(func_parser_value "${lines[41]}") +cpu_threads_key=$(func_parser_key "${lines[42]}") +cpu_threads_list=$(func_parser_value "${lines[42]}") +batch_size_key=$(func_parser_key "${lines[43]}") +batch_size_list=$(func_parser_value "${lines[43]}") +use_trt_key=$(func_parser_key "${lines[44]}") +use_trt_list=$(func_parser_value "${lines[44]}") +precision_key=$(func_parser_key "${lines[45]}") +precision_list=$(func_parser_value "${lines[45]}") +infer_model_key=$(func_parser_key "${lines[46]}") +image_dir_key=$(func_parser_key "${lines[47]}") +infer_img_dir=$(func_parser_value "${lines[47]}") +save_log_key=$(func_parser_key "${lines[48]}") +benchmark_key=$(func_parser_key "${lines[49]}") +benchmark_value=$(func_parser_value "${lines[49]}") +infer_key1=$(func_parser_key "${lines[50]}") +infer_value1=$(func_parser_value "${lines[50]}") + +LOG_PATH="./test_tipc/output/${model_name}/${MODE}" +mkdir -p ${LOG_PATH} +status_log="${LOG_PATH}/results_python.log" + +line_num=`grep -n -w "to_static_train_benchmark_params" $FILENAME | cut -d ":" -f 1` +to_static_key=$(func_parser_key "${lines[line_num]}") +to_static_trainer=$(func_parser_value "${lines[line_num]}") + +function func_inference(){ + IFS='|' + _python=$1 + _script=$2 + _model_dir=$3 + _log_path=$4 + _img_dir=$5 + _flag_quant=$6 + _gpu=$7 + # inference + for use_gpu in ${use_gpu_list[*]}; do + if [ ${use_gpu} = "False" ] || [ ${use_gpu} = "cpu" ]; then + for use_mkldnn in ${use_mkldnn_list[*]}; do + # if [ ${use_mkldnn} = "False" ] && [ ${_flag_quant} = "True" ]; then + # continue + # fi + for threads in ${cpu_threads_list[*]}; do + for batch_size in ${batch_size_list[*]}; do + for precision in ${precision_list[*]}; do + if [ ${use_mkldnn} = "False" ] && [ ${precision} = "fp16" ]; then + continue + fi # skip when enable fp16 but disable mkldnn + if [ ${_flag_quant} = "True" ] && [ ${precision} != "int8" ]; then + continue + fi # skip when quant model inference but precision is not int8 + set_precision=$(func_set_params "${precision_key}" "${precision}") + + _save_log_path="${_log_path}/python_infer_cpu_gpus_${_gpu}_usemkldnn_${use_mkldnn}_threads_${threads}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${benchmark_key}" "${benchmark_value}") + set_batchsize=$(func_set_params "${batch_size_key}" "${batch_size}") + set_mkldnn=$(func_set_params "${use_mkldnn_key}" "${use_mkldnn}") + set_cpu_threads=$(func_set_params "${cpu_threads_key}" "${threads}") + set_model_dir=$(func_set_params "${infer_model_key}" "${_model_dir}") + set_infer_params0=$(func_set_params "${save_log_key}" "${save_log_value}") + set_infer_params1=$(func_set_params "${infer_key1}" "${infer_value1}") + command="${_python} ${_script} ${use_gpu_key}=${use_gpu} ${set_mkldnn} ${set_cpu_threads} ${set_model_dir} ${set_batchsize} ${set_infer_params0} ${set_infer_data} ${set_benchmark} ${set_precision} ${set_infer_params1} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + done + done + done + done + elif [ ${use_gpu} = "True" ] || [ ${use_gpu} = "gpu" ]; then + for use_trt in ${use_trt_list[*]}; do + for precision in ${precision_list[*]}; do + if [[ ${_flag_quant} = "False" ]] && [[ ${precision} =~ "int8" ]]; then + continue + fi + if [[ ${precision} =~ "fp16" || ${precision} =~ "int8" ]] && [ ${use_trt} = "False" ]; then + continue + fi + if [[ ${use_trt} = "False" && ${precision} =~ "int8" ]] && [ ${_flag_quant} = "True" ]; then + continue + fi + for batch_size in ${batch_size_list[*]}; do + _save_log_path="${_log_path}/python_infer_gpu_gpus_${_gpu}_usetrt_${use_trt}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${benchmark_key}" "${benchmark_value}") + set_batchsize=$(func_set_params "${batch_size_key}" "${batch_size}") + set_tensorrt=$(func_set_params "${use_trt_key}" "${use_trt}") + set_precision=$(func_set_params "${precision_key}" "${precision}") + set_model_dir=$(func_set_params "${infer_model_key}" "${_model_dir}") + set_infer_params0=$(func_set_params "${save_log_key}" "${save_log_value}") + set_infer_params1=$(func_set_params "${infer_key1}" "${infer_value1}") + command="${_python} ${_script} ${use_gpu_key}=${use_gpu} ${set_tensorrt} ${set_precision} ${set_model_dir} ${set_batchsize} ${set_infer_data} ${set_benchmark} ${set_infer_params1} ${set_infer_params0} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + + done + done + done + else + echo "Does not support hardware other than CPU and GPU Currently!" + fi + done +} + +if [ ${MODE} = "whole_infer" ]; then + GPUID=$3 + if [ ${#GPUID} -le 0 ];then + env=" " + else + env="export CUDA_VISIBLE_DEVICES=${GPUID}" + fi + # set CUDA_VISIBLE_DEVICES + eval $env + export Count=0 + gpu=0 + IFS="|" + infer_run_exports=(${infer_export_list}) + infer_quant_flag=(${infer_is_quant}) + for infer_model in ${infer_model_dir_list[*]}; do + # run export + if [ ${infer_run_exports[Count]} != "null" ];then + save_infer_dir="${infer_model}" + set_export_weight=$(func_set_params "${export_weight}" "${infer_model}") + set_save_infer_key=$(func_set_params "${save_infer_key}" "${save_infer_dir}") + export_log_path="${LOG_PATH}_export_${Count}.log" + export_cmd="${python} ${infer_run_exports[Count]} ${set_export_weight} ${set_save_infer_key} > ${export_log_path} 2>&1 " + echo ${infer_run_exports[Count]} + echo $export_cmd + eval $export_cmd + status_export=$? + status_check $status_export "${export_cmd}" "${status_log}" "${model_name}" "${export_log_path}" + else + save_infer_dir=${infer_model} + fi + #run inference + is_quant=${infer_quant_flag[Count]} + func_inference "${python}" "${inference_py}" "${save_infer_dir}" "${LOG_PATH}" "${infer_img_dir}" ${is_quant} "${gpu}" + Count=$(($Count + 1)) + done +else + IFS="|" + export Count=0 + USE_GPU_KEY=(${train_use_gpu_value}) + for gpu in ${gpu_list[*]}; do + train_use_gpu=${USE_GPU_KEY[Count]} + Count=$(($Count + 1)) + ips="" + if [ ${gpu} = "-1" ];then + env="" + elif [ ${#gpu} -le 1 ];then + env="export CUDA_VISIBLE_DEVICES=${gpu}" + elif [ ${#gpu} -le 15 ];then + IFS="," + array=(${gpu}) + env="export CUDA_VISIBLE_DEVICES=${array[0]}" + IFS="|" + else + IFS=";" + array=(${gpu}) + ips=${array[0]} + gpu=${array[1]} + IFS="|" + env=" " + fi + for autocast in ${autocast_list[*]}; do + if [ ${autocast} = "amp" ]; then + set_amp_config="amp.scale_loss=1024.0 amp.use_dynamic_loss_scaling=True amp.amp_level=O2" + else + set_amp_config="amp=None" + fi + for trainer in ${trainer_list[*]}; do + flag_quant=False + if [ ${trainer} = ${pact_key} ]; then + run_train=${pact_trainer} + run_export=${pact_export} + flag_quant=True + elif [ ${trainer} = "${fpgm_key}" ]; then + run_train=${fpgm_trainer} + run_export=${fpgm_export} + elif [ ${trainer} = "${distill_key}" ]; then + run_train=${distill_trainer} + run_export=${distill_export} + elif [ ${trainer} = "${to_static_key}" ]; then + run_train="${norm_trainer} ${to_static_trainer}" + run_export=${norm_export} + elif [[ ${trainer} = ${trainer_key2} ]]; then + run_train=${trainer_value2} + run_export=${export_value2} + else + run_train=${norm_trainer} + run_export=${norm_export} + fi + + if [ ${run_train} = "null" ]; then + continue + fi + + set_epoch=$(func_set_params "${epoch_key}" "${epoch_num}") + set_pretrain=$(func_set_params "${pretrain_model_key}" "${pretrain_model_value}") + set_batchsize=$(func_set_params "${train_batch_key}" "${train_batch_value}") + set_train_params1=$(func_set_params "${train_param_key1}" "${train_param_value1}") + set_use_gpu=$(func_set_params "${train_use_gpu_key}" "${train_use_gpu}") + # if length of ips >= 15, then it is seen as multi-machine + # 15 is the min length of ips info for multi-machine: 0.0.0.0,0.0.0.0 + if [ ${#ips} -le 15 ];then + save_log="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}" + nodes=1 + else + IFS="," + ips_array=(${ips}) + IFS="|" + nodes=${#ips_array[@]} + save_log="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}_nodes_${nodes}" + fi + + + set_save_model=$(func_set_params "${save_model_key}" "${save_log}") + if [ ${#gpu} -le 2 ];then # train with cpu or single gpu + cmd="${python} ${run_train} ${set_use_gpu} ${set_save_model} ${set_epoch} ${set_pretrain} ${set_batchsize} ${set_amp_config} ${set_train_params1}" + elif [ ${#ips} -le 15 ];then # train with multi-gpu + cmd="${python} -m paddle.distributed.launch --gpus=${gpu} ${run_train} ${set_use_gpu} ${set_save_model} ${set_epoch} ${set_pretrain} ${set_batchsize} ${set_amp_config} ${set_train_params1}" + else # train with multi-machine + cmd="${python} -m paddle.distributed.launch --ips=${ips} --gpus=${gpu} ${run_train} ${set_use_gpu} ${set_save_model} ${set_pretrain} ${set_epoch} ${set_batchsize} ${set_amp_config} ${set_train_params1}" + fi + # run train + eval $cmd + eval "cat ${save_log}/train.log >> ${save_log}.log" + status_check $? "${cmd}" "${status_log}" "${model_name}" "${save_log}.log" + + set_eval_pretrain=$(func_set_params "${pretrain_model_key}" "${save_log}/${train_model_name}") + + # run eval + if [ ${eval_py} != "null" ]; then + eval ${env} + set_eval_params1=$(func_set_params "${eval_key1}" "${eval_value1}") + eval_log_path="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}_nodes_${nodes}_eval.log" + eval_cmd="${python} ${eval_py} ${set_eval_pretrain} ${set_use_gpu} ${set_eval_params1} > ${eval_log_path} 2>&1 " + eval $eval_cmd + status_check $? "${eval_cmd}" "${status_log}" "${model_name}" "${eval_log_path}" + fi + # run export model + if [ ${run_export} != "null" ]; then + # run export model + save_infer_path="${save_log}" + export_log_path="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}_nodes_${nodes}_export.log" + set_export_weight=$(func_set_params "${export_weight}" "${save_log}/${train_model_name}") + set_save_infer_key=$(func_set_params "${save_infer_key}" "${save_infer_path}") + export_cmd="${python} ${run_export} ${set_export_weight} ${set_save_infer_key} > ${export_log_path} 2>&1 " + eval $export_cmd + status_check $? "${export_cmd}" "${status_log}" "${model_name}" "${export_log_path}" + + #run inference + eval $env + save_infer_path="${save_log}" + if [[ ${inference_dir} != "null" ]] && [[ ${inference_dir} != '##' ]]; then + infer_model_dir="${save_infer_path}/${inference_dir}" + else + infer_model_dir=${save_infer_path} + fi + func_inference "${python}" "${inference_py}" "${infer_model_dir}" "${LOG_PATH}" "${train_infer_img_dir}" "${flag_quant}" "${gpu}" + + eval "unset CUDA_VISIBLE_DEVICES" + fi + done # done with: for trainer in ${trainer_list[*]}; do + done # done with: for autocast in ${autocast_list[*]}; do + done # done with: for gpu in ${gpu_list[*]}; do +fi # end if [ ${MODE} = "infer" ]; then diff --git a/benchmark/PaddleOCR_DBNet/tools/__init__.py b/benchmark/PaddleOCR_DBNet/tools/__init__.py new file mode 100644 index 0000000..aa5fe6c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/8 13:14 +# @Author : zhoujun diff --git a/benchmark/PaddleOCR_DBNet/tools/eval.py b/benchmark/PaddleOCR_DBNet/tools/eval.py new file mode 100644 index 0000000..21a5a4a --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/eval.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# @Time : 2018/6/11 15:54 +# @Author : zhoujun +import os +import sys +import pathlib + +__dir__ = pathlib.Path(os.path.abspath(__file__)) +sys.path.append(str(__dir__)) +sys.path.append(str(__dir__.parent.parent)) + +import argparse +import time +import paddle +from tqdm.auto import tqdm + + +class EVAL: + def __init__(self, model_path, gpu_id=0): + from models import build_model + from data_loader import get_dataloader + from post_processing import get_post_processing + from utils import get_metric + + self.gpu_id = gpu_id + if ( + self.gpu_id is not None + and isinstance(self.gpu_id, int) + and paddle.device.is_compiled_with_cuda() + ): + paddle.device.set_device("gpu:{}".format(self.gpu_id)) + else: + paddle.device.set_device("cpu") + checkpoint = paddle.load(model_path) + config = checkpoint["config"] + config["arch"]["backbone"]["pretrained"] = False + + self.validate_loader = get_dataloader( + config["dataset"]["validate"], config["distributed"] + ) + + self.model = build_model(config["arch"]) + self.model.set_state_dict(checkpoint["state_dict"]) + + self.post_process = get_post_processing(config["post_processing"]) + self.metric_cls = get_metric(config["metric"]) + + def eval(self): + self.model.eval() + raw_metrics = [] + total_frame = 0.0 + total_time = 0.0 + for i, batch in tqdm( + enumerate(self.validate_loader), + total=len(self.validate_loader), + desc="test model", + ): + with paddle.no_grad(): + start = time.time() + preds = self.model(batch["img"]) + boxes, scores = self.post_process( + batch, preds, is_output_polygon=self.metric_cls.is_output_polygon + ) + total_frame += batch["img"].shape[0] + total_time += time.time() - start + raw_metric = self.metric_cls.validate_measure(batch, (boxes, scores)) + raw_metrics.append(raw_metric) + metrics = self.metric_cls.gather_measure(raw_metrics) + print("FPS:{}".format(total_frame / total_time)) + return { + "recall": metrics["recall"].avg, + "precision": metrics["precision"].avg, + "fmeasure": metrics["fmeasure"].avg, + } + + +def init_args(): + parser = argparse.ArgumentParser(description="DBNet.paddle") + parser.add_argument( + "--model_path", + required=False, + default="output/DBNet_resnet18_FPN_DBHead/checkpoint/1.pth", + type=str, + ) + args = parser.parse_args() + return args + + +if __name__ == "__main__": + args = init_args() + eval = EVAL(args.model_path) + result = eval.eval() + print(result) diff --git a/benchmark/PaddleOCR_DBNet/tools/export_model.py b/benchmark/PaddleOCR_DBNet/tools/export_model.py new file mode 100644 index 0000000..71a33d0 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/export_model.py @@ -0,0 +1,57 @@ +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +import argparse + +import paddle +from paddle.jit import to_static + +from models import build_model +from utils import Config, ArgsParser + + +def init_args(): + parser = ArgsParser() + args = parser.parse_args() + return args + + +def load_checkpoint(model, checkpoint_path): + """ + load checkpoints + :param checkpoint_path: Checkpoint path to be loaded + """ + checkpoint = paddle.load(checkpoint_path) + model.set_state_dict(checkpoint["state_dict"]) + print("load checkpoint from {}".format(checkpoint_path)) + + +def main(config): + model = build_model(config["arch"]) + load_checkpoint(model, config["trainer"]["resume_checkpoint"]) + model.eval() + + save_path = config["trainer"]["output_dir"] + save_path = os.path.join(save_path, "inference") + infer_shape = [3, -1, -1] + model = to_static( + model, + input_spec=[ + paddle.static.InputSpec(shape=[None] + infer_shape, dtype="float32") + ], + ) + + paddle.jit.save(model, save_path) + print("inference model is saved to {}".format(save_path)) + + +if __name__ == "__main__": + args = init_args() + assert os.path.exists(args.config_file) + config = Config(args.config_file) + config.merge_dict(args.opt) + main(config.cfg) diff --git a/benchmark/PaddleOCR_DBNet/tools/infer.py b/benchmark/PaddleOCR_DBNet/tools/infer.py new file mode 100644 index 0000000..fb42536 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/infer.py @@ -0,0 +1,315 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import pathlib + +__dir__ = pathlib.Path(os.path.abspath(__file__)) +sys.path.append(str(__dir__)) +sys.path.append(str(__dir__.parent.parent)) + +import cv2 +import paddle +from paddle import inference +import numpy as np +from PIL import Image + +from paddle.vision import transforms +from tools.predict import resize_image +from post_processing import get_post_processing +from utils.util import draw_bbox, save_result + + +class InferenceEngine(object): + """InferenceEngine + + Inference engine class which contains preprocess, run, postprocess + """ + + def __init__(self, args): + """ + Args: + args: Parameters generated using argparser. + Returns: None + """ + super().__init__() + self.args = args + + # init inference engine + ( + self.predictor, + self.config, + self.input_tensor, + self.output_tensor, + ) = self.load_predictor( + os.path.join(args.model_dir, "inference.pdmodel"), + os.path.join(args.model_dir, "inference.pdiparams"), + ) + + # build transforms + self.transforms = transforms.Compose( + [ + transforms.ToTensor(), + transforms.Normalize( + mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] + ), + ] + ) + + # wamrup + if self.args.warmup > 0: + for idx in range(args.warmup): + print(idx) + x = np.random.rand( + 1, 3, self.args.crop_size, self.args.crop_size + ).astype("float32") + self.input_tensor.copy_from_cpu(x) + self.predictor.run() + self.output_tensor.copy_to_cpu() + + self.post_process = get_post_processing( + { + "type": "SegDetectorRepresenter", + "args": { + "thresh": 0.3, + "box_thresh": 0.7, + "max_candidates": 1000, + "unclip_ratio": 1.5, + }, + } + ) + + def load_predictor(self, model_file_path, params_file_path): + """load_predictor + initialize the inference engine + Args: + model_file_path: inference model path (*.pdmodel) + model_file_path: inference parameter path (*.pdiparams) + Return: + predictor: Predictor created using Paddle Inference. + config: Configuration of the predictor. + input_tensor: Input tensor of the predictor. + output_tensor: Output tensor of the predictor. + """ + args = self.args + config = inference.Config(model_file_path, params_file_path) + if args.use_gpu: + config.enable_use_gpu(1000, 0) + if args.use_tensorrt: + config.enable_tensorrt_engine( + workspace_size=1 << 30, + precision_mode=precision, + max_batch_size=args.max_batch_size, + min_subgraph_size=args.min_subgraph_size, # skip the minimum trt subgraph + use_calib_mode=False, + ) + + # collect shape + trt_shape_f = os.path.join(model_dir, "_trt_dynamic_shape.txt") + + if not os.path.exists(trt_shape_f): + config.collect_shape_range_info(trt_shape_f) + logger.info(f"collect dynamic shape info into : {trt_shape_f}") + try: + config.enable_tuned_tensorrt_dynamic_shape(trt_shape_f, True) + except Exception as E: + logger.info(E) + logger.info("Please keep your paddlepaddle-gpu >= 2.3.0!") + else: + config.disable_gpu() + # The thread num should not be greater than the number of cores in the CPU. + if args.enable_mkldnn: + # cache 10 different shapes for mkldnn to avoid memory leak + config.set_mkldnn_cache_capacity(10) + config.enable_mkldnn() + if args.precision == "fp16": + config.enable_mkldnn_bfloat16() + if hasattr(args, "cpu_threads"): + config.set_cpu_math_library_num_threads(args.cpu_threads) + else: + # default cpu threads as 10 + config.set_cpu_math_library_num_threads(10) + + # enable memory optim + config.enable_memory_optim() + config.disable_glog_info() + + config.switch_use_feed_fetch_ops(False) + config.switch_ir_optim(True) + + # create predictor + predictor = inference.create_predictor(config) + + # get input and output tensor property + input_names = predictor.get_input_names() + input_tensor = predictor.get_input_handle(input_names[0]) + + output_names = predictor.get_output_names() + output_tensor = predictor.get_output_handle(output_names[0]) + + return predictor, config, input_tensor, output_tensor + + def preprocess(self, img_path, short_size): + """preprocess + Preprocess to the input. + Args: + img_path: Image path. + Returns: Input data after preprocess. + """ + img = cv2.imread(img_path, 1) + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + h, w = img.shape[:2] + img = resize_image(img, short_size) + img = self.transforms(img) + img = np.expand_dims(img, axis=0) + shape_info = {"shape": [(h, w)]} + return img, shape_info + + def postprocess(self, x, shape_info, is_output_polygon): + """postprocess + Postprocess to the inference engine output. + Args: + x: Inference engine output. + Returns: Output data after argmax. + """ + box_list, score_list = self.post_process( + shape_info, x, is_output_polygon=is_output_polygon + ) + box_list, score_list = box_list[0], score_list[0] + if len(box_list) > 0: + if is_output_polygon: + idx = [x.sum() > 0 for x in box_list] + box_list = [box_list[i] for i, v in enumerate(idx) if v] + score_list = [score_list[i] for i, v in enumerate(idx) if v] + else: + idx = ( + box_list.reshape(box_list.shape[0], -1).sum(axis=1) > 0 + ) # 去掉全为0的框 + box_list, score_list = box_list[idx], score_list[idx] + else: + box_list, score_list = [], [] + return box_list, score_list + + def run(self, x): + """run + Inference process using inference engine. + Args: + x: Input data after preprocess. + Returns: Inference engine output + """ + self.input_tensor.copy_from_cpu(x) + self.predictor.run() + output = self.output_tensor.copy_to_cpu() + return output + + +def get_args(add_help=True): + """ + parse args + """ + import argparse + + def str2bool(v): + return v.lower() in ("true", "t", "1") + + parser = argparse.ArgumentParser( + description="PaddlePaddle Classification Training", add_help=add_help + ) + + parser.add_argument("--model_dir", default=None, help="inference model dir") + parser.add_argument("--batch_size", type=int, default=1) + parser.add_argument("--short_size", default=1024, type=int, help="short size") + parser.add_argument("--img_path", default="./images/demo.jpg") + + parser.add_argument("--benchmark", default=False, type=str2bool, help="benchmark") + parser.add_argument("--warmup", default=0, type=int, help="warmup iter") + parser.add_argument("--polygon", action="store_true", help="output polygon or box") + + parser.add_argument("--use_gpu", type=str2bool, default=True) + parser.add_argument("--use_tensorrt", type=str2bool, default=False) + parser.add_argument("--precision", type=str, default="fp32") + parser.add_argument("--gpu_mem", type=int, default=500) + parser.add_argument("--gpu_id", type=int, default=0) + parser.add_argument("--enable_mkldnn", type=str2bool, default=False) + parser.add_argument("--cpu_threads", type=int, default=10) + + args = parser.parse_args() + return args + + +def main(args): + """ + Main inference function. + Args: + args: Parameters generated using argparser. + Returns: + class_id: Class index of the input. + prob: : Probability of the input. + """ + inference_engine = InferenceEngine(args) + + # init benchmark + if args.benchmark: + import auto_log + + autolog = auto_log.AutoLogger( + model_name="db", + batch_size=args.batch_size, + inference_config=inference_engine.config, + gpu_ids="auto" if args.use_gpu else None, + ) + + # enable benchmark + if args.benchmark: + autolog.times.start() + + # preprocess + img, shape_info = inference_engine.preprocess(args.img_path, args.short_size) + + if args.benchmark: + autolog.times.stamp() + + output = inference_engine.run(img) + + if args.benchmark: + autolog.times.stamp() + + # postprocess + box_list, score_list = inference_engine.postprocess( + output, shape_info, args.polygon + ) + + if args.benchmark: + autolog.times.stamp() + autolog.times.end(stamp=True) + autolog.report() + + img = draw_bbox(cv2.imread(args.img_path)[:, :, ::-1], box_list) + # 保存结果到路径 + os.makedirs("output", exist_ok=True) + img_path = pathlib.Path(args.img_path) + output_path = os.path.join("output", img_path.stem + "_infer_result.jpg") + cv2.imwrite(output_path, img[:, :, ::-1]) + save_result( + output_path.replace("_infer_result.jpg", ".txt"), + box_list, + score_list, + args.polygon, + ) + + +if __name__ == "__main__": + args = get_args() + main(args) diff --git a/benchmark/PaddleOCR_DBNet/tools/predict.py b/benchmark/PaddleOCR_DBNet/tools/predict.py new file mode 100644 index 0000000..3d6a089 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/predict.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/24 12:06 +# @Author : zhoujun + +import os +import sys +import pathlib + +__dir__ = pathlib.Path(os.path.abspath(__file__)) +sys.path.append(str(__dir__)) +sys.path.append(str(__dir__.parent.parent)) + +import time +import cv2 +import paddle + +from data_loader import get_transforms +from models import build_model +from post_processing import get_post_processing + + +def resize_image(img, short_size): + height, width, _ = img.shape + if height < width: + new_height = short_size + new_width = new_height / height * width + else: + new_width = short_size + new_height = new_width / width * height + new_height = int(round(new_height / 32) * 32) + new_width = int(round(new_width / 32) * 32) + resized_img = cv2.resize(img, (new_width, new_height)) + return resized_img + + +class PaddleModel: + def __init__(self, model_path, post_p_thre=0.7, gpu_id=None): + """ + 初始化模型 + :param model_path: 模型地址(可以是模型的参数或者参数和计算图一起保存的文件) + :param gpu_id: 在哪一块gpu上运行 + """ + self.gpu_id = gpu_id + + if ( + self.gpu_id is not None + and isinstance(self.gpu_id, int) + and paddle.device.is_compiled_with_cuda() + ): + paddle.device.set_device("gpu:{}".format(self.gpu_id)) + else: + paddle.device.set_device("cpu") + checkpoint = paddle.load(model_path) + + config = checkpoint["config"] + config["arch"]["backbone"]["pretrained"] = False + self.model = build_model(config["arch"]) + self.post_process = get_post_processing(config["post_processing"]) + self.post_process.box_thresh = post_p_thre + self.img_mode = config["dataset"]["train"]["dataset"]["args"]["img_mode"] + self.model.set_state_dict(checkpoint["state_dict"]) + self.model.eval() + + self.transform = [] + for t in config["dataset"]["train"]["dataset"]["args"]["transforms"]: + if t["type"] in ["ToTensor", "Normalize"]: + self.transform.append(t) + self.transform = get_transforms(self.transform) + + def predict(self, img_path: str, is_output_polygon=False, short_size: int = 1024): + """ + 对传入的图像进行预测,支持图像地址,opencv 读取图片,偏慢 + :param img_path: 图像地址 + :param is_numpy: + :return: + """ + assert os.path.exists(img_path), "file is not exists" + img = cv2.imread(img_path, 1 if self.img_mode != "GRAY" else 0) + if self.img_mode == "RGB": + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + h, w = img.shape[:2] + img = resize_image(img, short_size) + # 将图片由(w,h)变为(1,img_channel,h,w) + tensor = self.transform(img) + tensor = tensor.unsqueeze_(0) + + batch = {"shape": [(h, w)]} + with paddle.no_grad(): + start = time.time() + preds = self.model(tensor) + box_list, score_list = self.post_process( + batch, preds, is_output_polygon=is_output_polygon + ) + box_list, score_list = box_list[0], score_list[0] + if len(box_list) > 0: + if is_output_polygon: + idx = [x.sum() > 0 for x in box_list] + box_list = [box_list[i] for i, v in enumerate(idx) if v] + score_list = [score_list[i] for i, v in enumerate(idx) if v] + else: + idx = ( + box_list.reshape(box_list.shape[0], -1).sum(axis=1) > 0 + ) # 去掉全为0的框 + box_list, score_list = box_list[idx], score_list[idx] + else: + box_list, score_list = [], [] + t = time.time() - start + return preds[0, 0, :, :].detach().cpu().numpy(), box_list, score_list, t + + +def save_depoly(net, input, save_path): + input_spec = [paddle.static.InputSpec(shape=[None, 3, None, None], dtype="float32")] + net = paddle.jit.to_static(net, input_spec=input_spec) + + # save static model for inference directly + paddle.jit.save(net, save_path) + + +def init_args(): + import argparse + + parser = argparse.ArgumentParser(description="DBNet.paddle") + parser.add_argument("--model_path", default=r"model_best.pth", type=str) + parser.add_argument( + "--input_folder", default="./test/input", type=str, help="img path for predict" + ) + parser.add_argument( + "--output_folder", default="./test/output", type=str, help="img path for output" + ) + parser.add_argument("--gpu", default=0, type=int, help="gpu for inference") + parser.add_argument( + "--thre", default=0.3, type=float, help="the thresh of post_processing" + ) + parser.add_argument("--polygon", action="store_true", help="output polygon or box") + parser.add_argument("--show", action="store_true", help="show result") + parser.add_argument( + "--save_result", action="store_true", help="save box and score to txt file" + ) + args = parser.parse_args() + return args + + +if __name__ == "__main__": + import pathlib + from tqdm import tqdm + import matplotlib.pyplot as plt + from utils.util import show_img, draw_bbox, save_result, get_image_file_list + + args = init_args() + print(args) + # 初始化网络 + model = PaddleModel(args.model_path, post_p_thre=args.thre, gpu_id=args.gpu) + img_folder = pathlib.Path(args.input_folder) + for img_path in tqdm(get_image_file_list(args.input_folder)): + preds, boxes_list, score_list, t = model.predict( + img_path, is_output_polygon=args.polygon + ) + img = draw_bbox(cv2.imread(img_path)[:, :, ::-1], boxes_list) + if args.show: + show_img(preds) + show_img(img, title=os.path.basename(img_path)) + plt.show() + # 保存结果到路径 + os.makedirs(args.output_folder, exist_ok=True) + img_path = pathlib.Path(img_path) + output_path = os.path.join(args.output_folder, img_path.stem + "_result.jpg") + pred_path = os.path.join(args.output_folder, img_path.stem + "_pred.jpg") + cv2.imwrite(output_path, img[:, :, ::-1]) + cv2.imwrite(pred_path, preds * 255) + save_result( + output_path.replace("_result.jpg", ".txt"), + boxes_list, + score_list, + args.polygon, + ) diff --git a/benchmark/PaddleOCR_DBNet/tools/train.py b/benchmark/PaddleOCR_DBNet/tools/train.py new file mode 100644 index 0000000..170eebd --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/tools/train.py @@ -0,0 +1,64 @@ +import os +import sys +import pathlib + +__dir__ = pathlib.Path(os.path.abspath(__file__)) +sys.path.append(str(__dir__)) +sys.path.append(str(__dir__.parent.parent)) + +import paddle +import paddle.distributed as dist +from utils import Config, ArgsParser + + +def init_args(): + parser = ArgsParser() + args = parser.parse_args() + return args + + +def main(config, profiler_options): + from models import build_model, build_loss + from data_loader import get_dataloader + from trainer import Trainer + from post_processing import get_post_processing + from utils import get_metric + + if paddle.device.cuda.device_count() > 1: + dist.init_parallel_env() + config["distributed"] = True + else: + config["distributed"] = False + train_loader = get_dataloader(config["dataset"]["train"], config["distributed"]) + assert train_loader is not None + if "validate" in config["dataset"]: + validate_loader = get_dataloader(config["dataset"]["validate"], False) + else: + validate_loader = None + criterion = build_loss(config["loss"]) + config["arch"]["backbone"]["in_channels"] = ( + 3 if config["dataset"]["train"]["dataset"]["args"]["img_mode"] != "GRAY" else 1 + ) + model = build_model(config["arch"]) + # set @to_static for benchmark, skip this by default. + post_p = get_post_processing(config["post_processing"]) + metric = get_metric(config["metric"]) + trainer = Trainer( + config=config, + model=model, + criterion=criterion, + train_loader=train_loader, + post_process=post_p, + metric_cls=metric, + validate_loader=validate_loader, + profiler_options=profiler_options, + ) + trainer.train() + + +if __name__ == "__main__": + args = init_args() + assert os.path.exists(args.config_file) + config = Config(args.config_file) + config.merge_dict(args.opt) + main(config.cfg, args.profiler_options) diff --git a/benchmark/PaddleOCR_DBNet/trainer/__init__.py b/benchmark/PaddleOCR_DBNet/trainer/__init__.py new file mode 100644 index 0000000..e5b2234 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/trainer/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:58 +# @Author : zhoujun +from .trainer import Trainer diff --git a/benchmark/PaddleOCR_DBNet/trainer/trainer.py b/benchmark/PaddleOCR_DBNet/trainer/trainer.py new file mode 100644 index 0000000..077d39c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/trainer/trainer.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:58 +# @Author : zhoujun +import time + +import paddle +from tqdm import tqdm + +from base import BaseTrainer +from utils import runningScore, cal_text_score, Polynomial, profiler + + +class Trainer(BaseTrainer): + def __init__( + self, + config, + model, + criterion, + train_loader, + validate_loader, + metric_cls, + post_process=None, + profiler_options=None, + ): + super(Trainer, self).__init__( + config, + model, + criterion, + train_loader, + validate_loader, + metric_cls, + post_process, + ) + self.profiler_options = profiler_options + self.enable_eval = config["trainer"].get("enable_eval", True) + + def _train_epoch(self, epoch): + self.model.train() + total_samples = 0 + train_reader_cost = 0.0 + train_batch_cost = 0.0 + reader_start = time.time() + epoch_start = time.time() + train_loss = 0.0 + running_metric_text = runningScore(2) + + for i, batch in enumerate(self.train_loader): + profiler.add_profiler_step(self.profiler_options) + if i >= self.train_loader_len: + break + self.global_step += 1 + lr = self.optimizer.get_lr() + + cur_batch_size = batch["img"].shape[0] + + train_reader_cost += time.time() - reader_start + if self.amp: + with paddle.amp.auto_cast( + enable="gpu" in paddle.device.get_device(), + custom_white_list=self.amp.get("custom_white_list", []), + custom_black_list=self.amp.get("custom_black_list", []), + level=self.amp.get("level", "O2"), + ): + preds = self.model(batch["img"]) + loss_dict = self.criterion(preds.astype(paddle.float32), batch) + scaled_loss = self.amp["scaler"].scale(loss_dict["loss"]) + scaled_loss.backward() + self.amp["scaler"].minimize(self.optimizer, scaled_loss) + else: + preds = self.model(batch["img"]) + loss_dict = self.criterion(preds, batch) + # backward + loss_dict["loss"].backward() + self.optimizer.step() + self.lr_scheduler.step() + self.optimizer.clear_grad() + + train_batch_time = time.time() - reader_start + train_batch_cost += train_batch_time + total_samples += cur_batch_size + + # acc iou + score_shrink_map = cal_text_score( + preds[:, 0, :, :], + batch["shrink_map"], + batch["shrink_mask"], + running_metric_text, + thred=self.config["post_processing"]["args"]["thresh"], + ) + + # loss 和 acc 记录到日志 + loss_str = "loss: {:.4f}, ".format(loss_dict["loss"].item()) + for idx, (key, value) in enumerate(loss_dict.items()): + loss_dict[key] = value.item() + if key == "loss": + continue + loss_str += "{}: {:.4f}".format(key, loss_dict[key]) + if idx < len(loss_dict) - 1: + loss_str += ", " + + train_loss += loss_dict["loss"] + acc = score_shrink_map["Mean Acc"] + iou_shrink_map = score_shrink_map["Mean IoU"] + + if self.global_step % self.log_iter == 0: + self.logger_info( + "[{}/{}], [{}/{}], global_step: {}, ips: {:.1f} samples/sec, avg_reader_cost: {:.5f} s, avg_batch_cost: {:.5f} s, avg_samples: {}, acc: {:.4f}, iou_shrink_map: {:.4f}, {}lr:{:.6}, time:{:.2f}".format( + epoch, + self.epochs, + i + 1, + self.train_loader_len, + self.global_step, + total_samples / train_batch_cost, + train_reader_cost / self.log_iter, + train_batch_cost / self.log_iter, + total_samples / self.log_iter, + acc, + iou_shrink_map, + loss_str, + lr, + train_batch_cost, + ) + ) + total_samples = 0 + train_reader_cost = 0.0 + train_batch_cost = 0.0 + + if self.visualdl_enable and paddle.distributed.get_rank() == 0: + # write tensorboard + for key, value in loss_dict.items(): + self.writer.add_scalar( + "TRAIN/LOSS/{}".format(key), value, self.global_step + ) + self.writer.add_scalar("TRAIN/ACC_IOU/acc", acc, self.global_step) + self.writer.add_scalar( + "TRAIN/ACC_IOU/iou_shrink_map", iou_shrink_map, self.global_step + ) + self.writer.add_scalar("TRAIN/lr", lr, self.global_step) + reader_start = time.time() + return { + "train_loss": train_loss / self.train_loader_len, + "lr": lr, + "time": time.time() - epoch_start, + "epoch": epoch, + } + + def _eval(self, epoch): + self.model.eval() + raw_metrics = [] + total_frame = 0.0 + total_time = 0.0 + for i, batch in tqdm( + enumerate(self.validate_loader), + total=len(self.validate_loader), + desc="test model", + ): + with paddle.no_grad(): + start = time.time() + if self.amp: + with paddle.amp.auto_cast( + enable="gpu" in paddle.device.get_device(), + custom_white_list=self.amp.get("custom_white_list", []), + custom_black_list=self.amp.get("custom_black_list", []), + level=self.amp.get("level", "O2"), + ): + preds = self.model(batch["img"]) + preds = preds.astype(paddle.float32) + else: + preds = self.model(batch["img"]) + boxes, scores = self.post_process( + batch, preds, is_output_polygon=self.metric_cls.is_output_polygon + ) + total_frame += batch["img"].shape[0] + total_time += time.time() - start + raw_metric = self.metric_cls.validate_measure(batch, (boxes, scores)) + raw_metrics.append(raw_metric) + metrics = self.metric_cls.gather_measure(raw_metrics) + self.logger_info("FPS:{}".format(total_frame / total_time)) + return metrics["recall"].avg, metrics["precision"].avg, metrics["fmeasure"].avg + + def _on_epoch_finish(self): + self.logger_info( + "[{}/{}], train_loss: {:.4f}, time: {:.4f}, lr: {}".format( + self.epoch_result["epoch"], + self.epochs, + self.epoch_result["train_loss"], + self.epoch_result["time"], + self.epoch_result["lr"], + ) + ) + net_save_path = "{}/model_latest.pth".format(self.checkpoint_dir) + net_save_path_best = "{}/model_best.pth".format(self.checkpoint_dir) + + if paddle.distributed.get_rank() == 0: + self._save_checkpoint(self.epoch_result["epoch"], net_save_path) + save_best = False + if ( + self.validate_loader is not None + and self.metric_cls is not None + and self.enable_eval + ): # 使用f1作为最优模型指标 + recall, precision, hmean = self._eval(self.epoch_result["epoch"]) + + if self.visualdl_enable: + self.writer.add_scalar("EVAL/recall", recall, self.global_step) + self.writer.add_scalar( + "EVAL/precision", precision, self.global_step + ) + self.writer.add_scalar("EVAL/hmean", hmean, self.global_step) + self.logger_info( + "test: recall: {:.6f}, precision: {:.6f}, hmean: {:.6f}".format( + recall, precision, hmean + ) + ) + + if hmean >= self.metrics["hmean"]: + save_best = True + self.metrics["train_loss"] = self.epoch_result["train_loss"] + self.metrics["hmean"] = hmean + self.metrics["precision"] = precision + self.metrics["recall"] = recall + self.metrics["best_model_epoch"] = self.epoch_result["epoch"] + else: + if self.epoch_result["train_loss"] <= self.metrics["train_loss"]: + save_best = True + self.metrics["train_loss"] = self.epoch_result["train_loss"] + self.metrics["best_model_epoch"] = self.epoch_result["epoch"] + best_str = "current best, " + for k, v in self.metrics.items(): + best_str += "{}: {:.6f}, ".format(k, v) + self.logger_info(best_str) + if save_best: + import shutil + + shutil.copy(net_save_path, net_save_path_best) + self.logger_info("Saving current best: {}".format(net_save_path_best)) + else: + self.logger_info("Saving checkpoint: {}".format(net_save_path)) + + def _on_train_finish(self): + if self.enable_eval: + for k, v in self.metrics.items(): + self.logger_info("{}:{}".format(k, v)) + self.logger_info("finish train") + + def _initialize_scheduler(self): + if self.config["lr_scheduler"]["type"] == "Polynomial": + self.config["lr_scheduler"]["args"]["epochs"] = self.config["trainer"][ + "epochs" + ] + self.config["lr_scheduler"]["args"]["step_each_epoch"] = len( + self.train_loader + ) + self.lr_scheduler = Polynomial(**self.config["lr_scheduler"]["args"])() + else: + self.lr_scheduler = self._initialize("lr_scheduler", paddle.optimizer.lr) diff --git a/benchmark/PaddleOCR_DBNet/utils/__init__.py b/benchmark/PaddleOCR_DBNet/utils/__init__.py new file mode 100644 index 0000000..c7bf6b9 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:58 +# @Author : zhoujun +from .util import * +from .metrics import * +from .schedulers import * +from .cal_recall.script import cal_recall_precision_f1 +from .ocr_metric import get_metric diff --git a/benchmark/PaddleOCR_DBNet/utils/cal_recall/__init__.py b/benchmark/PaddleOCR_DBNet/utils/cal_recall/__init__.py new file mode 100644 index 0000000..b5c75e4 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/cal_recall/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# @Time : 1/16/19 6:40 AM +# @Author : zhoujun +from .script import cal_recall_precision_f1 + +__all__ = ["cal_recall_precision_f1"] diff --git a/benchmark/PaddleOCR_DBNet/utils/cal_recall/rrc_evaluation_funcs.py b/benchmark/PaddleOCR_DBNet/utils/cal_recall/rrc_evaluation_funcs.py new file mode 100644 index 0000000..426549b --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/cal_recall/rrc_evaluation_funcs.py @@ -0,0 +1,494 @@ +#!/usr/bin/env python2 +# encoding: UTF-8 +import json +import sys + +sys.path.append("./") +import zipfile +import re +import sys +import os +import codecs +import traceback +import numpy as np +from utils import order_points_clockwise + + +def print_help(): + sys.stdout.write( + "Usage: python %s.py -g= -s= [-o= -p=]" + % sys.argv[0] + ) + sys.exit(2) + + +def load_zip_file_keys(file, fileNameRegExp=""): + """ + Returns an array with the entries of the ZIP file that match with the regular expression. + The key's are the names or the file or the capturing group defined in the fileNameRegExp + """ + try: + archive = zipfile.ZipFile(file, mode="r", allowZip64=True) + except: + raise Exception("Error loading the ZIP archive.") + + pairs = [] + + for name in archive.namelist(): + addFile = True + keyName = name + if fileNameRegExp != "": + m = re.match(fileNameRegExp, name) + if m == None: + addFile = False + else: + if len(m.groups()) > 0: + keyName = m.group(1) + + if addFile: + pairs.append(keyName) + + return pairs + + +def load_zip_file(file, fileNameRegExp="", allEntries=False): + """ + Returns an array with the contents (filtered by fileNameRegExp) of a ZIP file. + The key's are the names or the file or the capturing group defined in the fileNameRegExp + allEntries validates that all entries in the ZIP file pass the fileNameRegExp + """ + try: + archive = zipfile.ZipFile(file, mode="r", allowZip64=True) + except: + raise Exception("Error loading the ZIP archive") + + pairs = [] + for name in archive.namelist(): + addFile = True + keyName = name + if fileNameRegExp != "": + m = re.match(fileNameRegExp, name) + if m == None: + addFile = False + else: + if len(m.groups()) > 0: + keyName = m.group(1) + + if addFile: + pairs.append([keyName, archive.read(name)]) + else: + if allEntries: + raise Exception("ZIP entry not valid: %s" % name) + + return dict(pairs) + + +def load_folder_file(file, fileNameRegExp="", allEntries=False): + """ + Returns an array with the contents (filtered by fileNameRegExp) of a ZIP file. + The key's are the names or the file or the capturing group defined in the fileNameRegExp + allEntries validates that all entries in the ZIP file pass the fileNameRegExp + """ + pairs = [] + for name in os.listdir(file): + addFile = True + keyName = name + if fileNameRegExp != "": + m = re.match(fileNameRegExp, name) + if m == None: + addFile = False + else: + if len(m.groups()) > 0: + keyName = m.group(1) + + if addFile: + pairs.append([keyName, open(os.path.join(file, name)).read()]) + else: + if allEntries: + raise Exception("ZIP entry not valid: %s" % name) + + return dict(pairs) + + +def decode_utf8(raw): + """ + Returns a Unicode object on success, or None on failure + """ + try: + raw = codecs.decode(raw, "utf-8", "replace") + # extracts BOM if exists + raw = raw.encode("utf8") + if raw.startswith(codecs.BOM_UTF8): + raw = raw.replace(codecs.BOM_UTF8, "", 1) + return raw.decode("utf-8") + except: + return None + + +def validate_lines_in_file( + fileName, + file_contents, + CRLF=True, + LTRB=True, + withTranscription=False, + withConfidence=False, + imWidth=0, + imHeight=0, +): + """ + This function validates that all lines of the file calling the Line validation function for each line + """ + utf8File = decode_utf8(file_contents) + if utf8File is None: + raise Exception("The file %s is not UTF-8" % fileName) + + lines = utf8File.split("\r\n" if CRLF else "\n") + for line in lines: + line = line.replace("\r", "").replace("\n", "") + if line != "": + try: + validate_tl_line( + line, LTRB, withTranscription, withConfidence, imWidth, imHeight + ) + except Exception as e: + raise Exception( + ( + "Line in sample not valid. Sample: %s Line: %s Error: %s" + % (fileName, line, str(e)) + ).encode("utf-8", "replace") + ) + + +def validate_tl_line( + line, LTRB=True, withTranscription=True, withConfidence=True, imWidth=0, imHeight=0 +): + """ + Validate the format of the line. If the line is not valid an exception will be raised. + If maxWidth and maxHeight are specified, all points must be inside the image bounds. + Possible values are: + LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription] + LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription] + """ + get_tl_line_values(line, LTRB, withTranscription, withConfidence, imWidth, imHeight) + + +def get_tl_line_values( + line, + LTRB=True, + withTranscription=False, + withConfidence=False, + imWidth=0, + imHeight=0, +): + """ + Validate the format of the line. If the line is not valid an exception will be raised. + If maxWidth and maxHeight are specified, all points must be inside the image bounds. + Possible values are: + LTRB=True: xmin,ymin,xmax,ymax[,confidence][,transcription] + LTRB=False: x1,y1,x2,y2,x3,y3,x4,y4[,confidence][,transcription] + Returns values from a textline. Points , [Confidences], [Transcriptions] + """ + confidence = 0.0 + transcription = "" + points = [] + + numPoints = 4 + + if LTRB: + numPoints = 4 + + if withTranscription and withConfidence: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$", + line, + ) + if m == None: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$", + line, + ) + raise Exception( + "Format incorrect. Should be: xmin,ymin,xmax,ymax,confidence,transcription" + ) + elif withConfidence: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-1].?[0-9]*)\s*$", + line, + ) + if m == None: + raise Exception( + "Format incorrect. Should be: xmin,ymin,xmax,ymax,confidence" + ) + elif withTranscription: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,(.*)$", + line, + ) + if m == None: + raise Exception( + "Format incorrect. Should be: xmin,ymin,xmax,ymax,transcription" + ) + else: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,?\s*$", + line, + ) + if m == None: + raise Exception("Format incorrect. Should be: xmin,ymin,xmax,ymax") + + xmin = int(m.group(1)) + ymin = int(m.group(2)) + xmax = int(m.group(3)) + ymax = int(m.group(4)) + if xmax < xmin: + raise Exception("Xmax value (%s) not valid (Xmax < Xmin)." % (xmax)) + if ymax < ymin: + raise Exception("Ymax value (%s) not valid (Ymax < Ymin)." % (ymax)) + + points = [float(m.group(i)) for i in range(1, (numPoints + 1))] + + if imWidth > 0 and imHeight > 0: + validate_point_inside_bounds(xmin, ymin, imWidth, imHeight) + validate_point_inside_bounds(xmax, ymax, imWidth, imHeight) + + else: + numPoints = 8 + + if withTranscription and withConfidence: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-1].?[0-9]*)\s*,(.*)$", + line, + ) + if m == None: + raise Exception( + "Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,confidence,transcription" + ) + elif withConfidence: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*([0-1].?[0-9]*)\s*$", + line, + ) + if m == None: + raise Exception( + "Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,confidence" + ) + elif withTranscription: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,(.*)$", + line, + ) + if m == None: + raise Exception( + "Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4,transcription" + ) + else: + m = re.match( + r"^\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*,\s*(-?[0-9]+)\s*$", + line, + ) + if m == None: + raise Exception("Format incorrect. Should be: x1,y1,x2,y2,x3,y3,x4,y4") + + points = [float(m.group(i)) for i in range(1, (numPoints + 1))] + + points = order_points_clockwise(np.array(points).reshape(-1, 2)).reshape(-1) + validate_clockwise_points(points) + + if imWidth > 0 and imHeight > 0: + validate_point_inside_bounds(points[0], points[1], imWidth, imHeight) + validate_point_inside_bounds(points[2], points[3], imWidth, imHeight) + validate_point_inside_bounds(points[4], points[5], imWidth, imHeight) + validate_point_inside_bounds(points[6], points[7], imWidth, imHeight) + + if withConfidence: + try: + confidence = float(m.group(numPoints + 1)) + except ValueError: + raise Exception("Confidence value must be a float") + + if withTranscription: + posTranscription = numPoints + (2 if withConfidence else 1) + transcription = m.group(posTranscription) + m2 = re.match(r"^\s*\"(.*)\"\s*$", transcription) + if ( + m2 != None + ): # Transcription with double quotes, we extract the value and replace escaped characters + transcription = m2.group(1).replace("\\\\", "\\").replace('\\"', '"') + + return points, confidence, transcription + + +def validate_point_inside_bounds(x, y, imWidth, imHeight): + if x < 0 or x > imWidth: + raise Exception( + "X value (%s) not valid. Image dimensions: (%s,%s)" + % (xmin, imWidth, imHeight) + ) + if y < 0 or y > imHeight: + raise Exception( + "Y value (%s) not valid. Image dimensions: (%s,%s) Sample: %s Line:%s" + % (ymin, imWidth, imHeight) + ) + + +def validate_clockwise_points(points): + """ + Validates that the points that the 4 points that dlimite a polygon are in clockwise order. + """ + + if len(points) != 8: + raise Exception("Points list not valid." + str(len(points))) + + point = [ + [int(points[0]), int(points[1])], + [int(points[2]), int(points[3])], + [int(points[4]), int(points[5])], + [int(points[6]), int(points[7])], + ] + edge = [ + (point[1][0] - point[0][0]) * (point[1][1] + point[0][1]), + (point[2][0] - point[1][0]) * (point[2][1] + point[1][1]), + (point[3][0] - point[2][0]) * (point[3][1] + point[2][1]), + (point[0][0] - point[3][0]) * (point[0][1] + point[3][1]), + ] + + summatory = edge[0] + edge[1] + edge[2] + edge[3] + if summatory > 0: + raise Exception( + "Points are not clockwise. The coordinates of bounding quadrilaterals have to be given in clockwise order. Regarding the correct interpretation of 'clockwise' remember that the image coordinate system used is the standard one, with the image origin at the upper left, the X axis extending to the right and Y axis extending downwards." + ) + + +def get_tl_line_values_from_file_contents( + content, + CRLF=True, + LTRB=True, + withTranscription=False, + withConfidence=False, + imWidth=0, + imHeight=0, + sort_by_confidences=True, +): + """ + Returns all points, confindences and transcriptions of a file in lists. Valid line formats: + xmin,ymin,xmax,ymax,[confidence],[transcription] + x1,y1,x2,y2,x3,y3,x4,y4,[confidence],[transcription] + """ + pointsList = [] + transcriptionsList = [] + confidencesList = [] + + lines = content.split("\r\n" if CRLF else "\n") + for line in lines: + line = line.replace("\r", "").replace("\n", "") + if line != "": + points, confidence, transcription = get_tl_line_values( + line, LTRB, withTranscription, withConfidence, imWidth, imHeight + ) + pointsList.append(points) + transcriptionsList.append(transcription) + confidencesList.append(confidence) + + if withConfidence and len(confidencesList) > 0 and sort_by_confidences: + import numpy as np + + sorted_ind = np.argsort(-np.array(confidencesList)) + confidencesList = [confidencesList[i] for i in sorted_ind] + pointsList = [pointsList[i] for i in sorted_ind] + transcriptionsList = [transcriptionsList[i] for i in sorted_ind] + + return pointsList, confidencesList, transcriptionsList + + +def main_evaluation( + p, + default_evaluation_params_fn, + validate_data_fn, + evaluate_method_fn, + show_result=True, + per_sample=True, +): + """ + This process validates a method, evaluates it and if it succeed generates a ZIP file with a JSON entry for each sample. + Params: + p: Dictionary of parameters with the GT/submission locations. If None is passed, the parameters send by the system are used. + default_evaluation_params_fn: points to a function that returns a dictionary with the default parameters used for the evaluation + validate_data_fn: points to a method that validates the correct format of the submission + evaluate_method_fn: points to a function that evaluated the submission and return a Dictionary with the results + """ + evalParams = default_evaluation_params_fn() + if "p" in p.keys(): + evalParams.update( + p["p"] if isinstance(p["p"], dict) else json.loads(p["p"][1:-1]) + ) + + resDict = {"calculated": True, "Message": "", "method": "{}", "per_sample": "{}"} + try: + # validate_data_fn(p['g'], p['s'], evalParams) + evalData = evaluate_method_fn(p["g"], p["s"], evalParams) + resDict.update(evalData) + + except Exception as e: + traceback.print_exc() + resDict["Message"] = str(e) + resDict["calculated"] = False + + if "o" in p: + if not os.path.exists(p["o"]): + os.makedirs(p["o"]) + + resultsOutputname = p["o"] + "/results.zip" + outZip = zipfile.ZipFile(resultsOutputname, mode="w", allowZip64=True) + + del resDict["per_sample"] + if "output_items" in resDict.keys(): + del resDict["output_items"] + + outZip.writestr("method.json", json.dumps(resDict)) + + if not resDict["calculated"]: + if show_result: + sys.stderr.write("Error!\n" + resDict["Message"] + "\n\n") + if "o" in p: + outZip.close() + return resDict + + if "o" in p: + if per_sample == True: + for k, v in evalData["per_sample"].items(): + outZip.writestr(k + ".json", json.dumps(v)) + + if "output_items" in evalData.keys(): + for k, v in evalData["output_items"].items(): + outZip.writestr(k, v) + + outZip.close() + + if show_result: + sys.stdout.write("Calculated!") + sys.stdout.write(json.dumps(resDict["method"])) + + return resDict + + +def main_validation(default_evaluation_params_fn, validate_data_fn): + """ + This process validates a method + Params: + default_evaluation_params_fn: points to a function that returns a dictionary with the default parameters used for the evaluation + validate_data_fn: points to a method that validates the correct format of the submission + """ + try: + p = dict([s[1:].split("=") for s in sys.argv[1:]]) + evalParams = default_evaluation_params_fn() + if "p" in p.keys(): + evalParams.update( + p["p"] if isinstance(p["p"], dict) else json.loads(p["p"][1:-1]) + ) + + validate_data_fn(p["g"], p["s"], evalParams) + print("SUCCESS") + sys.exit(0) + except Exception as e: + print(str(e)) + sys.exit(101) diff --git a/benchmark/PaddleOCR_DBNet/utils/cal_recall/script.py b/benchmark/PaddleOCR_DBNet/utils/cal_recall/script.py new file mode 100644 index 0000000..5e1552d --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/cal_recall/script.py @@ -0,0 +1,402 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from collections import namedtuple +from . import rrc_evaluation_funcs +import Polygon as plg +import numpy as np + + +def default_evaluation_params(): + """ + default_evaluation_params: Default parameters to use for the validation and evaluation. + """ + return { + "IOU_CONSTRAINT": 0.5, + "AREA_PRECISION_CONSTRAINT": 0.5, + "GT_SAMPLE_NAME_2_ID": "gt_img_([0-9]+).txt", + "DET_SAMPLE_NAME_2_ID": "res_img_([0-9]+).txt", + "LTRB": False, # LTRB:2points(left,top,right,bottom) or 4 points(x1,y1,x2,y2,x3,y3,x4,y4) + "CRLF": False, # Lines are delimited by Windows CRLF format + "CONFIDENCES": False, # Detections must include confidence value. AP will be calculated + "PER_SAMPLE_RESULTS": True, # Generate per sample results and produce data for visualization + } + + +def validate_data(gtFilePath, submFilePath, evaluationParams): + """ + Method validate_data: validates that all files in the results folder are correct (have the correct name contents). + Validates also that there are no missing files in the folder. + If some error detected, the method raises the error + """ + gt = rrc_evaluation_funcs.load_folder_file( + gtFilePath, evaluationParams["GT_SAMPLE_NAME_2_ID"] + ) + + subm = rrc_evaluation_funcs.load_folder_file( + submFilePath, evaluationParams["DET_SAMPLE_NAME_2_ID"], True + ) + + # Validate format of GroundTruth + for k in gt: + rrc_evaluation_funcs.validate_lines_in_file( + k, gt[k], evaluationParams["CRLF"], evaluationParams["LTRB"], True + ) + + # Validate format of results + for k in subm: + if (k in gt) == False: + raise Exception("The sample %s not present in GT" % k) + + rrc_evaluation_funcs.validate_lines_in_file( + k, + subm[k], + evaluationParams["CRLF"], + evaluationParams["LTRB"], + False, + evaluationParams["CONFIDENCES"], + ) + + +def evaluate_method(gtFilePath, submFilePath, evaluationParams): + """ + Method evaluate_method: evaluate method and returns the results + Results. Dictionary with the following values: + - method (required) Global method metrics. Ex: { 'Precision':0.8,'Recall':0.9 } + - samples (optional) Per sample metrics. Ex: {'sample1' : { 'Precision':0.8,'Recall':0.9 } , 'sample2' : { 'Precision':0.8,'Recall':0.9 } + """ + + def polygon_from_points(points): + """ + Returns a Polygon object to use with the Polygon2 class from a list of 8 points: x1,y1,x2,y2,x3,y3,x4,y4 + """ + resBoxes = np.empty([1, 8], dtype="int32") + resBoxes[0, 0] = int(points[0]) + resBoxes[0, 4] = int(points[1]) + resBoxes[0, 1] = int(points[2]) + resBoxes[0, 5] = int(points[3]) + resBoxes[0, 2] = int(points[4]) + resBoxes[0, 6] = int(points[5]) + resBoxes[0, 3] = int(points[6]) + resBoxes[0, 7] = int(points[7]) + pointMat = resBoxes[0].reshape([2, 4]).T + return plg.Polygon(pointMat) + + def rectangle_to_polygon(rect): + resBoxes = np.empty([1, 8], dtype="int32") + resBoxes[0, 0] = int(rect.xmin) + resBoxes[0, 4] = int(rect.ymax) + resBoxes[0, 1] = int(rect.xmin) + resBoxes[0, 5] = int(rect.ymin) + resBoxes[0, 2] = int(rect.xmax) + resBoxes[0, 6] = int(rect.ymin) + resBoxes[0, 3] = int(rect.xmax) + resBoxes[0, 7] = int(rect.ymax) + + pointMat = resBoxes[0].reshape([2, 4]).T + + return plg.Polygon(pointMat) + + def rectangle_to_points(rect): + points = [ + int(rect.xmin), + int(rect.ymax), + int(rect.xmax), + int(rect.ymax), + int(rect.xmax), + int(rect.ymin), + int(rect.xmin), + int(rect.ymin), + ] + return points + + def get_union(pD, pG): + areaA = pD.area() + areaB = pG.area() + return areaA + areaB - get_intersection(pD, pG) + + def get_intersection_over_union(pD, pG): + try: + return get_intersection(pD, pG) / get_union(pD, pG) + except: + return 0 + + def get_intersection(pD, pG): + pInt = pD & pG + if len(pInt) == 0: + return 0 + return pInt.area() + + def compute_ap(confList, matchList, numGtCare): + correct = 0 + AP = 0 + if len(confList) > 0: + confList = np.array(confList) + matchList = np.array(matchList) + sorted_ind = np.argsort(-confList) + confList = confList[sorted_ind] + matchList = matchList[sorted_ind] + for n in range(len(confList)): + match = matchList[n] + if match: + correct += 1 + AP += float(correct) / (n + 1) + + if numGtCare > 0: + AP /= numGtCare + + return AP + + perSampleMetrics = {} + + matchedSum = 0 + + Rectangle = namedtuple("Rectangle", "xmin ymin xmax ymax") + + gt = rrc_evaluation_funcs.load_folder_file( + gtFilePath, evaluationParams["GT_SAMPLE_NAME_2_ID"] + ) + subm = rrc_evaluation_funcs.load_folder_file( + submFilePath, evaluationParams["DET_SAMPLE_NAME_2_ID"], True + ) + + numGlobalCareGt = 0 + numGlobalCareDet = 0 + + arrGlobalConfidences = [] + arrGlobalMatches = [] + + for resFile in gt: + gtFile = gt[resFile] # rrc_evaluation_funcs.decode_utf8(gt[resFile]) + recall = 0 + precision = 0 + hmean = 0 + + detMatched = 0 + + iouMat = np.empty([1, 1]) + + gtPols = [] + detPols = [] + + gtPolPoints = [] + detPolPoints = [] + + # Array of Ground Truth Polygons' keys marked as don't Care + gtDontCarePolsNum = [] + # Array of Detected Polygons' matched with a don't Care GT + detDontCarePolsNum = [] + + pairs = [] + detMatchedNums = [] + + arrSampleConfidences = [] + arrSampleMatch = [] + sampleAP = 0 + + evaluationLog = "" + + ( + pointsList, + _, + transcriptionsList, + ) = rrc_evaluation_funcs.get_tl_line_values_from_file_contents( + gtFile, evaluationParams["CRLF"], evaluationParams["LTRB"], True, False + ) + for n in range(len(pointsList)): + points = pointsList[n] + transcription = transcriptionsList[n] + dontCare = transcription == "###" + if evaluationParams["LTRB"]: + gtRect = Rectangle(*points) + gtPol = rectangle_to_polygon(gtRect) + else: + gtPol = polygon_from_points(points) + gtPols.append(gtPol) + gtPolPoints.append(points) + if dontCare: + gtDontCarePolsNum.append(len(gtPols) - 1) + + evaluationLog += ( + "GT polygons: " + + str(len(gtPols)) + + ( + " (" + str(len(gtDontCarePolsNum)) + " don't care)\n" + if len(gtDontCarePolsNum) > 0 + else "\n" + ) + ) + + if resFile in subm: + detFile = subm[resFile] # rrc_evaluation_funcs.decode_utf8(subm[resFile]) + + ( + pointsList, + confidencesList, + _, + ) = rrc_evaluation_funcs.get_tl_line_values_from_file_contents( + detFile, + evaluationParams["CRLF"], + evaluationParams["LTRB"], + False, + evaluationParams["CONFIDENCES"], + ) + for n in range(len(pointsList)): + points = pointsList[n] + + if evaluationParams["LTRB"]: + detRect = Rectangle(*points) + detPol = rectangle_to_polygon(detRect) + else: + detPol = polygon_from_points(points) + detPols.append(detPol) + detPolPoints.append(points) + if len(gtDontCarePolsNum) > 0: + for dontCarePol in gtDontCarePolsNum: + dontCarePol = gtPols[dontCarePol] + intersected_area = get_intersection(dontCarePol, detPol) + pdDimensions = detPol.area() + precision = ( + 0 if pdDimensions == 0 else intersected_area / pdDimensions + ) + if precision > evaluationParams["AREA_PRECISION_CONSTRAINT"]: + detDontCarePolsNum.append(len(detPols) - 1) + break + + evaluationLog += ( + "DET polygons: " + + str(len(detPols)) + + ( + " (" + str(len(detDontCarePolsNum)) + " don't care)\n" + if len(detDontCarePolsNum) > 0 + else "\n" + ) + ) + + if len(gtPols) > 0 and len(detPols) > 0: + # Calculate IoU and precision matrixs + outputShape = [len(gtPols), len(detPols)] + iouMat = np.empty(outputShape) + gtRectMat = np.zeros(len(gtPols), np.int8) + detRectMat = np.zeros(len(detPols), np.int8) + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + pG = gtPols[gtNum] + pD = detPols[detNum] + iouMat[gtNum, detNum] = get_intersection_over_union(pD, pG) + + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCarePolsNum + and detNum not in detDontCarePolsNum + ): + if ( + iouMat[gtNum, detNum] + > evaluationParams["IOU_CONSTRAINT"] + ): + gtRectMat[gtNum] = 1 + detRectMat[detNum] = 1 + detMatched += 1 + pairs.append({"gt": gtNum, "det": detNum}) + detMatchedNums.append(detNum) + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + "\n" + ) + + if evaluationParams["CONFIDENCES"]: + for detNum in range(len(detPols)): + if detNum not in detDontCarePolsNum: + # we exclude the don't care detections + match = detNum in detMatchedNums + + arrSampleConfidences.append(confidencesList[detNum]) + arrSampleMatch.append(match) + + arrGlobalConfidences.append(confidencesList[detNum]) + arrGlobalMatches.append(match) + + numGtCare = len(gtPols) - len(gtDontCarePolsNum) + numDetCare = len(detPols) - len(detDontCarePolsNum) + if numGtCare == 0: + recall = float(1) + precision = float(0) if numDetCare > 0 else float(1) + sampleAP = precision + else: + recall = float(detMatched) / numGtCare + precision = 0 if numDetCare == 0 else float(detMatched) / numDetCare + if ( + evaluationParams["CONFIDENCES"] + and evaluationParams["PER_SAMPLE_RESULTS"] + ): + sampleAP = compute_ap(arrSampleConfidences, arrSampleMatch, numGtCare) + + hmean = ( + 0 + if (precision + recall) == 0 + else 2.0 * precision * recall / (precision + recall) + ) + + matchedSum += detMatched + numGlobalCareGt += numGtCare + numGlobalCareDet += numDetCare + + if evaluationParams["PER_SAMPLE_RESULTS"]: + perSampleMetrics[resFile] = { + "precision": precision, + "recall": recall, + "hmean": hmean, + "pairs": pairs, + "AP": sampleAP, + "iouMat": [] if len(detPols) > 100 else iouMat.tolist(), + "gtPolPoints": gtPolPoints, + "detPolPoints": detPolPoints, + "gtDontCare": gtDontCarePolsNum, + "detDontCare": detDontCarePolsNum, + "evaluationParams": evaluationParams, + "evaluationLog": evaluationLog, + } + + # Compute MAP and MAR + AP = 0 + if evaluationParams["CONFIDENCES"]: + AP = compute_ap(arrGlobalConfidences, arrGlobalMatches, numGlobalCareGt) + + methodRecall = 0 if numGlobalCareGt == 0 else float(matchedSum) / numGlobalCareGt + methodPrecision = ( + 0 if numGlobalCareDet == 0 else float(matchedSum) / numGlobalCareDet + ) + methodHmean = ( + 0 + if methodRecall + methodPrecision == 0 + else 2 * methodRecall * methodPrecision / (methodRecall + methodPrecision) + ) + + methodMetrics = { + "precision": methodPrecision, + "recall": methodRecall, + "hmean": methodHmean, + "AP": AP, + } + + resDict = { + "calculated": True, + "Message": "", + "method": methodMetrics, + "per_sample": perSampleMetrics, + } + + return resDict + + +def cal_recall_precision_f1(gt_path, result_path, show_result=False): + p = {"g": gt_path, "s": result_path} + result = rrc_evaluation_funcs.main_evaluation( + p, default_evaluation_params, validate_data, evaluate_method, show_result + ) + return result["method"] diff --git a/benchmark/PaddleOCR_DBNet/utils/compute_mean_std.py b/benchmark/PaddleOCR_DBNet/utils/compute_mean_std.py new file mode 100644 index 0000000..c76015e --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/compute_mean_std.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/7 14:46 +# @Author : zhoujun + +import numpy as np +import cv2 +import os +import random +from tqdm import tqdm + +# calculate means and std +train_txt_path = "./train_val_list.txt" + +CNum = 10000 # 挑选多少图片进行计算 + +img_h, img_w = 640, 640 +imgs = np.zeros([img_w, img_h, 3, 1]) +means, stdevs = [], [] + +with open(train_txt_path, "r") as f: + lines = f.readlines() + random.shuffle(lines) # shuffle , 随机挑选图片 + + for i in tqdm(range(CNum)): + img_path = lines[i].split("\t")[0] + + img = cv2.imread(img_path) + img = cv2.resize(img, (img_h, img_w)) + img = img[:, :, :, np.newaxis] + + imgs = np.concatenate((imgs, img), axis=3) +# print(i) + +imgs = imgs.astype(np.float32) / 255.0 + +for i in tqdm(range(3)): + pixels = imgs[:, :, i, :].ravel() # 拉成一行 + means.append(np.mean(pixels)) + stdevs.append(np.std(pixels)) + +# cv2 读取的图像格式为BGR,PIL/Skimage读取到的都是RGB不用转 +means.reverse() # BGR --> RGB +stdevs.reverse() + +print("normMean = {}".format(means)) +print("normStd = {}".format(stdevs)) +print("transforms.Normalize(normMean = {}, normStd = {})".format(means, stdevs)) diff --git a/benchmark/PaddleOCR_DBNet/utils/make_trainfile.py b/benchmark/PaddleOCR_DBNet/utils/make_trainfile.py new file mode 100644 index 0000000..3232b01 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/make_trainfile.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/24 12:06 +# @Author : zhoujun +import os +import glob +import pathlib + +data_path = r"test" +# data_path/img 存放图片 +# data_path/gt 存放标签文件 + +f_w = open(os.path.join(data_path, "test.txt"), "w", encoding="utf8") +for img_path in glob.glob(data_path + "/img/*.jpg", recursive=True): + d = pathlib.Path(img_path) + label_path = os.path.join(data_path, "gt", ("gt_" + str(d.stem) + ".txt")) + if os.path.exists(img_path) and os.path.exists(label_path): + print(img_path, label_path) + else: + print("不存在", img_path, label_path) + f_w.write("{}\t{}\n".format(img_path, label_path)) +f_w.close() diff --git a/benchmark/PaddleOCR_DBNet/utils/metrics.py b/benchmark/PaddleOCR_DBNet/utils/metrics.py new file mode 100644 index 0000000..81aa9bb --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/metrics.py @@ -0,0 +1,60 @@ +# Adapted from score written by wkentaro +# https://github.com/wkentaro/pytorch-fcn/blob/master/torchfcn/utils.py + +import numpy as np + + +class runningScore(object): + def __init__(self, n_classes): + self.n_classes = n_classes + self.confusion_matrix = np.zeros((n_classes, n_classes)) + + def _fast_hist(self, label_true, label_pred, n_class): + mask = (label_true >= 0) & (label_true < n_class) + + if np.sum((label_pred[mask] < 0)) > 0: + print(label_pred[label_pred < 0]) + hist = np.bincount( + n_class * label_true[mask].astype(int) + label_pred[mask], + minlength=n_class**2, + ).reshape(n_class, n_class) + return hist + + def update(self, label_trues, label_preds): + # print label_trues.dtype, label_preds.dtype + for lt, lp in zip(label_trues, label_preds): + try: + self.confusion_matrix += self._fast_hist( + lt.flatten(), lp.flatten(), self.n_classes + ) + except: + pass + + def get_scores(self): + """Returns accuracy score evaluation result. + - overall accuracy + - mean accuracy + - mean IU + - fwavacc + """ + hist = self.confusion_matrix + acc = np.diag(hist).sum() / (hist.sum() + 0.0001) + acc_cls = np.diag(hist) / (hist.sum(axis=1) + 0.0001) + acc_cls = np.nanmean(acc_cls) + iu = np.diag(hist) / ( + hist.sum(axis=1) + hist.sum(axis=0) - np.diag(hist) + 0.0001 + ) + mean_iu = np.nanmean(iu) + freq = hist.sum(axis=1) / (hist.sum() + 0.0001) + fwavacc = (freq[freq > 0] * iu[freq > 0]).sum() + cls_iu = dict(zip(range(self.n_classes), iu)) + + return { + "Overall Acc": acc, + "Mean Acc": acc_cls, + "FreqW Acc": fwavacc, + "Mean IoU": mean_iu, + }, cls_iu + + def reset(self): + self.confusion_matrix = np.zeros((self.n_classes, self.n_classes)) diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/__init__.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/__init__.py new file mode 100644 index 0000000..005f39c --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/5 15:36 +# @Author : zhoujun +from .icdar2015 import QuadMetric + + +def get_metric(config): + try: + if "args" not in config: + args = {} + else: + args = config["args"] + if isinstance(args, dict): + cls = eval(config["type"])(**args) + else: + cls = eval(config["type"])(args) + return cls + except: + return None diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/__init__.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/__init__.py new file mode 100644 index 0000000..5fd0e45 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/12/5 15:36 +# @Author : zhoujun + +from .quad_metric import QuadMetric diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/__init__.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/deteval.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/deteval.py new file mode 100644 index 0000000..7b799ec --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/deteval.py @@ -0,0 +1,474 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import math +from collections import namedtuple +import numpy as np +from shapely.geometry import Polygon + + +class DetectionDetEvalEvaluator(object): + def __init__( + self, + area_recall_constraint=0.8, + area_precision_constraint=0.4, + ev_param_ind_center_diff_thr=1, + mtype_oo_o=1.0, + mtype_om_o=0.8, + mtype_om_m=1.0, + ): + self.area_recall_constraint = area_recall_constraint + self.area_precision_constraint = area_precision_constraint + self.ev_param_ind_center_diff_thr = ev_param_ind_center_diff_thr + self.mtype_oo_o = mtype_oo_o + self.mtype_om_o = mtype_om_o + self.mtype_om_m = mtype_om_m + + def evaluate_image(self, gt, pred): + def get_union(pD, pG): + return Polygon(pD).union(Polygon(pG)).area + + def get_intersection_over_union(pD, pG): + return get_intersection(pD, pG) / get_union(pD, pG) + + def get_intersection(pD, pG): + return Polygon(pD).intersection(Polygon(pG)).area + + def one_to_one_match(row, col): + cont = 0 + for j in range(len(recallMat[0])): + if ( + recallMat[row, j] >= self.area_recall_constraint + and precisionMat[row, j] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + cont = 0 + for i in range(len(recallMat)): + if ( + recallMat[i, col] >= self.area_recall_constraint + and precisionMat[i, col] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + + if ( + recallMat[row, col] >= self.area_recall_constraint + and precisionMat[row, col] >= self.area_precision_constraint + ): + return True + return False + + def num_overlaps_gt(gtNum): + cont = 0 + for detNum in range(len(detRects)): + if detNum not in detDontCareRectsNum: + if recallMat[gtNum, detNum] > 0: + cont = cont + 1 + return cont + + def num_overlaps_det(detNum): + cont = 0 + for gtNum in range(len(recallMat)): + if gtNum not in gtDontCareRectsNum: + if recallMat[gtNum, detNum] > 0: + cont = cont + 1 + return cont + + def is_single_overlap(row, col): + if num_overlaps_gt(row) == 1 and num_overlaps_det(col) == 1: + return True + else: + return False + + def one_to_many_match(gtNum): + many_sum = 0 + detRects = [] + for detNum in range(len(recallMat[0])): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and detNum not in detDontCareRectsNum + ): + if precisionMat[gtNum, detNum] >= self.area_precision_constraint: + many_sum += recallMat[gtNum, detNum] + detRects.append(detNum) + if round(many_sum, 4) >= self.area_recall_constraint: + return True, detRects + else: + return False, [] + + def many_to_one_match(detNum): + many_sum = 0 + gtRects = [] + for gtNum in range(len(recallMat)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + ): + if recallMat[gtNum, detNum] >= self.area_recall_constraint: + many_sum += precisionMat[gtNum, detNum] + gtRects.append(gtNum) + if round(many_sum, 4) >= self.area_precision_constraint: + return True, gtRects + else: + return False, [] + + def center_distance(r1, r2): + return ((np.mean(r1, axis=0) - np.mean(r2, axis=0)) ** 2).sum() ** 0.5 + + def diag(r): + r = np.array(r) + return ( + (r[:, 0].max() - r[:, 0].min()) ** 2 + + (r[:, 1].max() - r[:, 1].min()) ** 2 + ) ** 0.5 + + perSampleMetrics = {} + + recall = 0 + precision = 0 + hmean = 0 + recallAccum = 0.0 + precisionAccum = 0.0 + gtRects = [] + detRects = [] + gtPolPoints = [] + detPolPoints = [] + gtDontCareRectsNum = ( + [] + ) # Array of Ground Truth Rectangles' keys marked as don't Care + detDontCareRectsNum = ( + [] + ) # Array of Detected Rectangles' matched with a don't Care GT + pairs = [] + evaluationLog = "" + + recallMat = np.empty([1, 1]) + precisionMat = np.empty([1, 1]) + + for n in range(len(gt)): + points = gt[n]["points"] + # transcription = gt[n]['text'] + dontCare = gt[n]["ignore"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + gtRects.append(points) + gtPolPoints.append(points) + if dontCare: + gtDontCareRectsNum.append(len(gtRects) - 1) + + evaluationLog += ( + "GT rectangles: " + + str(len(gtRects)) + + ( + " (" + str(len(gtDontCareRectsNum)) + " don't care)\n" + if len(gtDontCareRectsNum) > 0 + else "\n" + ) + ) + + for n in range(len(pred)): + points = pred[n]["points"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + detRect = points + detRects.append(detRect) + detPolPoints.append(points) + if len(gtDontCareRectsNum) > 0: + for dontCareRectNum in gtDontCareRectsNum: + dontCareRect = gtRects[dontCareRectNum] + intersected_area = get_intersection(dontCareRect, detRect) + rdDimensions = Polygon(detRect).area + if rdDimensions == 0: + precision = 0 + else: + precision = intersected_area / rdDimensions + if precision > self.area_precision_constraint: + detDontCareRectsNum.append(len(detRects) - 1) + break + + evaluationLog += ( + "DET rectangles: " + + str(len(detRects)) + + ( + " (" + str(len(detDontCareRectsNum)) + " don't care)\n" + if len(detDontCareRectsNum) > 0 + else "\n" + ) + ) + + if len(gtRects) == 0: + recall = 1 + precision = 0 if len(detRects) > 0 else 1 + + if len(detRects) > 0: + # Calculate recall and precision matrixes + outputShape = [len(gtRects), len(detRects)] + recallMat = np.empty(outputShape) + precisionMat = np.empty(outputShape) + gtRectMat = np.zeros(len(gtRects), np.int8) + detRectMat = np.zeros(len(detRects), np.int8) + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + rG = gtRects[gtNum] + rD = detRects[detNum] + intersected_area = get_intersection(rG, rD) + rgDimensions = Polygon(rG).area + rdDimensions = Polygon(rD).area + recallMat[gtNum, detNum] = ( + 0 if rgDimensions == 0 else intersected_area / rgDimensions + ) + precisionMat[gtNum, detNum] = ( + 0 if rdDimensions == 0 else intersected_area / rdDimensions + ) + + # Find one-to-one matches + evaluationLog += "Find one-to-one matches\n" + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + and detNum not in detDontCareRectsNum + ): + match = one_to_one_match(gtNum, detNum) + if match is True: + # in deteval we have to make other validation before mark as one-to-one + if is_single_overlap(gtNum, detNum) is True: + rG = gtRects[gtNum] + rD = detRects[detNum] + normDist = center_distance(rG, rD) + normDist /= diag(rG) + diag(rD) + normDist *= 2.0 + if normDist < self.ev_param_ind_center_diff_thr: + gtRectMat[gtNum] = 1 + detRectMat[detNum] = 1 + recallAccum += self.mtype_oo_o + precisionAccum += self.mtype_oo_o + pairs.append( + {"gt": gtNum, "det": detNum, "type": "OO"} + ) + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + "\n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + " normDist: " + + str(normDist) + + " \n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + " not single overlap\n" + ) + # Find one-to-many matches + evaluationLog += "Find one-to-many matches\n" + for gtNum in range(len(gtRects)): + if gtNum not in gtDontCareRectsNum: + match, matchesDet = one_to_many_match(gtNum) + if match is True: + evaluationLog += "num_overlaps_gt=" + str( + num_overlaps_gt(gtNum) + ) + # in deteval we have to make other validation before mark as one-to-one + if num_overlaps_gt(gtNum) >= 2: + gtRectMat[gtNum] = 1 + recallAccum += ( + self.mtype_oo_o + if len(matchesDet) == 1 + else self.mtype_om_o + ) + precisionAccum += ( + self.mtype_oo_o + if len(matchesDet) == 1 + else self.mtype_om_o * len(matchesDet) + ) + pairs.append( + { + "gt": gtNum, + "det": matchesDet, + "type": "OO" if len(matchesDet) == 1 else "OM", + } + ) + for detNum in matchesDet: + detRectMat[detNum] = 1 + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(matchesDet) + + "\n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(gtNum) + + " with Det #" + + str(matchesDet) + + " not single overlap\n" + ) + + # Find many-to-one matches + evaluationLog += "Find many-to-one matches\n" + for detNum in range(len(detRects)): + if detNum not in detDontCareRectsNum: + match, matchesGt = many_to_one_match(detNum) + if match is True: + # in deteval we have to make other validation before mark as one-to-one + if num_overlaps_det(detNum) >= 2: + detRectMat[detNum] = 1 + recallAccum += ( + self.mtype_oo_o + if len(matchesGt) == 1 + else self.mtype_om_m * len(matchesGt) + ) + precisionAccum += ( + self.mtype_oo_o + if len(matchesGt) == 1 + else self.mtype_om_m + ) + pairs.append( + { + "gt": matchesGt, + "det": detNum, + "type": "OO" if len(matchesGt) == 1 else "MO", + } + ) + for gtNum in matchesGt: + gtRectMat[gtNum] = 1 + evaluationLog += ( + "Match GT #" + + str(matchesGt) + + " with Det #" + + str(detNum) + + "\n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(matchesGt) + + " with Det #" + + str(detNum) + + " not single overlap\n" + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + if numGtCare == 0: + recall = float(1) + precision = float(0) if len(detRects) > 0 else float(1) + else: + recall = float(recallAccum) / numGtCare + precision = ( + float(0) + if (len(detRects) - len(detDontCareRectsNum)) == 0 + else float(precisionAccum) + / (len(detRects) - len(detDontCareRectsNum)) + ) + hmean = ( + 0 + if (precision + recall) == 0 + else 2.0 * precision * recall / (precision + recall) + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + numDetCare = len(detRects) - len(detDontCareRectsNum) + + perSampleMetrics = { + "precision": precision, + "recall": recall, + "hmean": hmean, + "pairs": pairs, + "recallMat": [] if len(detRects) > 100 else recallMat.tolist(), + "precisionMat": [] if len(detRects) > 100 else precisionMat.tolist(), + "gtPolPoints": gtPolPoints, + "detPolPoints": detPolPoints, + "gtCare": numGtCare, + "detCare": numDetCare, + "gtDontCare": gtDontCareRectsNum, + "detDontCare": detDontCareRectsNum, + "recallAccum": recallAccum, + "precisionAccum": precisionAccum, + "evaluationLog": evaluationLog, + } + + return perSampleMetrics + + def combine_results(self, results): + numGt = 0 + numDet = 0 + methodRecallSum = 0 + methodPrecisionSum = 0 + + for result in results: + numGt += result["gtCare"] + numDet += result["detCare"] + methodRecallSum += result["recallAccum"] + methodPrecisionSum += result["precisionAccum"] + + methodRecall = 0 if numGt == 0 else methodRecallSum / numGt + methodPrecision = 0 if numDet == 0 else methodPrecisionSum / numDet + methodHmean = ( + 0 + if methodRecall + methodPrecision == 0 + else 2 * methodRecall * methodPrecision / (methodRecall + methodPrecision) + ) + + methodMetrics = { + "precision": methodPrecision, + "recall": methodRecall, + "hmean": methodHmean, + } + + return methodMetrics + + +if __name__ == "__main__": + evaluator = DetectionDetEvalEvaluator() + gts = [ + [ + { + "points": [(0, 0), (1, 0), (1, 1), (0, 1)], + "text": 1234, + "ignore": False, + }, + { + "points": [(2, 2), (3, 2), (3, 3), (2, 3)], + "text": 5678, + "ignore": True, + }, + ] + ] + preds = [ + [ + { + "points": [(0.1, 0.1), (1, 0), (1, 1), (0, 1)], + "text": 123, + "ignore": False, + } + ] + ] + results = [] + for gt, pred in zip(gts, preds): + results.append(evaluator.evaluate_image(gt, pred)) + metrics = evaluator.combine_results(results) + print(metrics) diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/icdar2013.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/icdar2013.py new file mode 100644 index 0000000..1fd67b5 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/icdar2013.py @@ -0,0 +1,417 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import math +from collections import namedtuple +import numpy as np +from shapely.geometry import Polygon + + +class DetectionICDAR2013Evaluator(object): + def __init__( + self, + area_recall_constraint=0.8, + area_precision_constraint=0.4, + ev_param_ind_center_diff_thr=1, + mtype_oo_o=1.0, + mtype_om_o=0.8, + mtype_om_m=1.0, + ): + self.area_recall_constraint = area_recall_constraint + self.area_precision_constraint = area_precision_constraint + self.ev_param_ind_center_diff_thr = ev_param_ind_center_diff_thr + self.mtype_oo_o = mtype_oo_o + self.mtype_om_o = mtype_om_o + self.mtype_om_m = mtype_om_m + + def evaluate_image(self, gt, pred): + def get_union(pD, pG): + return Polygon(pD).union(Polygon(pG)).area + + def get_intersection_over_union(pD, pG): + return get_intersection(pD, pG) / get_union(pD, pG) + + def get_intersection(pD, pG): + return Polygon(pD).intersection(Polygon(pG)).area + + def one_to_one_match(row, col): + cont = 0 + for j in range(len(recallMat[0])): + if ( + recallMat[row, j] >= self.area_recall_constraint + and precisionMat[row, j] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + cont = 0 + for i in range(len(recallMat)): + if ( + recallMat[i, col] >= self.area_recall_constraint + and precisionMat[i, col] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + + if ( + recallMat[row, col] >= self.area_recall_constraint + and precisionMat[row, col] >= self.area_precision_constraint + ): + return True + return False + + def one_to_many_match(gtNum): + many_sum = 0 + detRects = [] + for detNum in range(len(recallMat[0])): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and detNum not in detDontCareRectsNum + ): + if precisionMat[gtNum, detNum] >= self.area_precision_constraint: + many_sum += recallMat[gtNum, detNum] + detRects.append(detNum) + if round(many_sum, 4) >= self.area_recall_constraint: + return True, detRects + else: + return False, [] + + def many_to_one_match(detNum): + many_sum = 0 + gtRects = [] + for gtNum in range(len(recallMat)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + ): + if recallMat[gtNum, detNum] >= self.area_recall_constraint: + many_sum += precisionMat[gtNum, detNum] + gtRects.append(gtNum) + if round(many_sum, 4) >= self.area_precision_constraint: + return True, gtRects + else: + return False, [] + + def center_distance(r1, r2): + return ((np.mean(r1, axis=0) - np.mean(r2, axis=0)) ** 2).sum() ** 0.5 + + def diag(r): + r = np.array(r) + return ( + (r[:, 0].max() - r[:, 0].min()) ** 2 + + (r[:, 1].max() - r[:, 1].min()) ** 2 + ) ** 0.5 + + perSampleMetrics = {} + + recall = 0 + precision = 0 + hmean = 0 + recallAccum = 0.0 + precisionAccum = 0.0 + gtRects = [] + detRects = [] + gtPolPoints = [] + detPolPoints = [] + gtDontCareRectsNum = ( + [] + ) # Array of Ground Truth Rectangles' keys marked as don't Care + detDontCareRectsNum = ( + [] + ) # Array of Detected Rectangles' matched with a don't Care GT + pairs = [] + evaluationLog = "" + + recallMat = np.empty([1, 1]) + precisionMat = np.empty([1, 1]) + + for n in range(len(gt)): + points = gt[n]["points"] + # transcription = gt[n]['text'] + dontCare = gt[n]["ignore"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + gtRects.append(points) + gtPolPoints.append(points) + if dontCare: + gtDontCareRectsNum.append(len(gtRects) - 1) + + evaluationLog += ( + "GT rectangles: " + + str(len(gtRects)) + + ( + " (" + str(len(gtDontCareRectsNum)) + " don't care)\n" + if len(gtDontCareRectsNum) > 0 + else "\n" + ) + ) + + for n in range(len(pred)): + points = pred[n]["points"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + detRect = points + detRects.append(detRect) + detPolPoints.append(points) + if len(gtDontCareRectsNum) > 0: + for dontCareRectNum in gtDontCareRectsNum: + dontCareRect = gtRects[dontCareRectNum] + intersected_area = get_intersection(dontCareRect, detRect) + rdDimensions = Polygon(detRect).area + if rdDimensions == 0: + precision = 0 + else: + precision = intersected_area / rdDimensions + if precision > self.area_precision_constraint: + detDontCareRectsNum.append(len(detRects) - 1) + break + + evaluationLog += ( + "DET rectangles: " + + str(len(detRects)) + + ( + " (" + str(len(detDontCareRectsNum)) + " don't care)\n" + if len(detDontCareRectsNum) > 0 + else "\n" + ) + ) + + if len(gtRects) == 0: + recall = 1 + precision = 0 if len(detRects) > 0 else 1 + + if len(detRects) > 0: + # Calculate recall and precision matrixes + outputShape = [len(gtRects), len(detRects)] + recallMat = np.empty(outputShape) + precisionMat = np.empty(outputShape) + gtRectMat = np.zeros(len(gtRects), np.int8) + detRectMat = np.zeros(len(detRects), np.int8) + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + rG = gtRects[gtNum] + rD = detRects[detNum] + intersected_area = get_intersection(rG, rD) + rgDimensions = Polygon(rG).area + rdDimensions = Polygon(rD).area + recallMat[gtNum, detNum] = ( + 0 if rgDimensions == 0 else intersected_area / rgDimensions + ) + precisionMat[gtNum, detNum] = ( + 0 if rdDimensions == 0 else intersected_area / rdDimensions + ) + + # Find one-to-one matches + evaluationLog += "Find one-to-one matches\n" + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + and detNum not in detDontCareRectsNum + ): + match = one_to_one_match(gtNum, detNum) + if match is True: + # in deteval we have to make other validation before mark as one-to-one + rG = gtRects[gtNum] + rD = detRects[detNum] + normDist = center_distance(rG, rD) + normDist /= diag(rG) + diag(rD) + normDist *= 2.0 + if normDist < self.ev_param_ind_center_diff_thr: + gtRectMat[gtNum] = 1 + detRectMat[detNum] = 1 + recallAccum += self.mtype_oo_o + precisionAccum += self.mtype_oo_o + pairs.append({"gt": gtNum, "det": detNum, "type": "OO"}) + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + "\n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + " normDist: " + + str(normDist) + + " \n" + ) + # Find one-to-many matches + evaluationLog += "Find one-to-many matches\n" + for gtNum in range(len(gtRects)): + if gtNum not in gtDontCareRectsNum: + match, matchesDet = one_to_many_match(gtNum) + if match is True: + evaluationLog += "num_overlaps_gt=" + str( + num_overlaps_gt(gtNum) + ) + gtRectMat[gtNum] = 1 + recallAccum += ( + self.mtype_oo_o if len(matchesDet) == 1 else self.mtype_om_o + ) + precisionAccum += ( + self.mtype_oo_o + if len(matchesDet) == 1 + else self.mtype_om_o * len(matchesDet) + ) + pairs.append( + { + "gt": gtNum, + "det": matchesDet, + "type": "OO" if len(matchesDet) == 1 else "OM", + } + ) + for detNum in matchesDet: + detRectMat[detNum] = 1 + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(matchesDet) + + "\n" + ) + + # Find many-to-one matches + evaluationLog += "Find many-to-one matches\n" + for detNum in range(len(detRects)): + if detNum not in detDontCareRectsNum: + match, matchesGt = many_to_one_match(detNum) + if match is True: + detRectMat[detNum] = 1 + recallAccum += ( + self.mtype_oo_o + if len(matchesGt) == 1 + else self.mtype_om_m * len(matchesGt) + ) + precisionAccum += ( + self.mtype_oo_o if len(matchesGt) == 1 else self.mtype_om_m + ) + pairs.append( + { + "gt": matchesGt, + "det": detNum, + "type": "OO" if len(matchesGt) == 1 else "MO", + } + ) + for gtNum in matchesGt: + gtRectMat[gtNum] = 1 + evaluationLog += ( + "Match GT #" + + str(matchesGt) + + " with Det #" + + str(detNum) + + "\n" + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + if numGtCare == 0: + recall = float(1) + precision = float(0) if len(detRects) > 0 else float(1) + else: + recall = float(recallAccum) / numGtCare + precision = ( + float(0) + if (len(detRects) - len(detDontCareRectsNum)) == 0 + else float(precisionAccum) + / (len(detRects) - len(detDontCareRectsNum)) + ) + hmean = ( + 0 + if (precision + recall) == 0 + else 2.0 * precision * recall / (precision + recall) + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + numDetCare = len(detRects) - len(detDontCareRectsNum) + + perSampleMetrics = { + "precision": precision, + "recall": recall, + "hmean": hmean, + "pairs": pairs, + "recallMat": [] if len(detRects) > 100 else recallMat.tolist(), + "precisionMat": [] if len(detRects) > 100 else precisionMat.tolist(), + "gtPolPoints": gtPolPoints, + "detPolPoints": detPolPoints, + "gtCare": numGtCare, + "detCare": numDetCare, + "gtDontCare": gtDontCareRectsNum, + "detDontCare": detDontCareRectsNum, + "recallAccum": recallAccum, + "precisionAccum": precisionAccum, + "evaluationLog": evaluationLog, + } + + return perSampleMetrics + + def combine_results(self, results): + numGt = 0 + numDet = 0 + methodRecallSum = 0 + methodPrecisionSum = 0 + + for result in results: + numGt += result["gtCare"] + numDet += result["detCare"] + methodRecallSum += result["recallAccum"] + methodPrecisionSum += result["precisionAccum"] + + methodRecall = 0 if numGt == 0 else methodRecallSum / numGt + methodPrecision = 0 if numDet == 0 else methodPrecisionSum / numDet + methodHmean = ( + 0 + if methodRecall + methodPrecision == 0 + else 2 * methodRecall * methodPrecision / (methodRecall + methodPrecision) + ) + + methodMetrics = { + "precision": methodPrecision, + "recall": methodRecall, + "hmean": methodHmean, + } + + return methodMetrics + + +if __name__ == "__main__": + evaluator = DetectionICDAR2013Evaluator() + gts = [ + [ + { + "points": [(0, 0), (1, 0), (1, 1), (0, 1)], + "text": 1234, + "ignore": False, + }, + { + "points": [(2, 2), (3, 2), (3, 3), (2, 3)], + "text": 5678, + "ignore": True, + }, + ] + ] + preds = [ + [ + { + "points": [(0.1, 0.1), (1, 0), (1, 1), (0, 1)], + "text": 123, + "ignore": False, + } + ] + ] + results = [] + for gt, pred in zip(gts, preds): + results.append(evaluator.evaluate_image(gt, pred)) + metrics = evaluator.combine_results(results) + print(metrics) diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/iou.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/iou.py new file mode 100644 index 0000000..85fde78 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/iou.py @@ -0,0 +1,300 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from collections import namedtuple +import numpy as np +from shapely.geometry import Polygon +import cv2 + + +def iou_rotate(box_a, box_b, method="union"): + rect_a = cv2.minAreaRect(box_a) + rect_b = cv2.minAreaRect(box_b) + r1 = cv2.rotatedRectangleIntersection(rect_a, rect_b) + if r1[0] == 0: + return 0 + else: + inter_area = cv2.contourArea(r1[1]) + area_a = cv2.contourArea(box_a) + area_b = cv2.contourArea(box_b) + union_area = area_a + area_b - inter_area + if union_area == 0 or inter_area == 0: + return 0 + if method == "union": + iou = inter_area / union_area + elif method == "intersection": + iou = inter_area / min(area_a, area_b) + else: + raise NotImplementedError + return iou + + +class DetectionIoUEvaluator(object): + def __init__( + self, is_output_polygon=False, iou_constraint=0.5, area_precision_constraint=0.5 + ): + self.is_output_polygon = is_output_polygon + self.iou_constraint = iou_constraint + self.area_precision_constraint = area_precision_constraint + + def evaluate_image(self, gt, pred): + def get_union(pD, pG): + return Polygon(pD).union(Polygon(pG)).area + + def get_intersection_over_union(pD, pG): + return get_intersection(pD, pG) / get_union(pD, pG) + + def get_intersection(pD, pG): + return Polygon(pD).intersection(Polygon(pG)).area + + def compute_ap(confList, matchList, numGtCare): + correct = 0 + AP = 0 + if len(confList) > 0: + confList = np.array(confList) + matchList = np.array(matchList) + sorted_ind = np.argsort(-confList) + confList = confList[sorted_ind] + matchList = matchList[sorted_ind] + for n in range(len(confList)): + match = matchList[n] + if match: + correct += 1 + AP += float(correct) / (n + 1) + + if numGtCare > 0: + AP /= numGtCare + + return AP + + perSampleMetrics = {} + + matchedSum = 0 + + Rectangle = namedtuple("Rectangle", "xmin ymin xmax ymax") + + numGlobalCareGt = 0 + numGlobalCareDet = 0 + + arrGlobalConfidences = [] + arrGlobalMatches = [] + + recall = 0 + precision = 0 + hmean = 0 + + detMatched = 0 + + iouMat = np.empty([1, 1]) + + gtPols = [] + detPols = [] + + gtPolPoints = [] + detPolPoints = [] + + # Array of Ground Truth Polygons' keys marked as don't Care + gtDontCarePolsNum = [] + # Array of Detected Polygons' matched with a don't Care GT + detDontCarePolsNum = [] + + pairs = [] + detMatchedNums = [] + + arrSampleConfidences = [] + arrSampleMatch = [] + + evaluationLog = "" + + for n in range(len(gt)): + points = gt[n]["points"] + # transcription = gt[n]['text'] + dontCare = gt[n]["ignore"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + gtPol = points + gtPols.append(gtPol) + gtPolPoints.append(points) + if dontCare: + gtDontCarePolsNum.append(len(gtPols) - 1) + + evaluationLog += ( + "GT polygons: " + + str(len(gtPols)) + + ( + " (" + str(len(gtDontCarePolsNum)) + " don't care)\n" + if len(gtDontCarePolsNum) > 0 + else "\n" + ) + ) + + for n in range(len(pred)): + points = pred[n]["points"] + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + detPol = points + detPols.append(detPol) + detPolPoints.append(points) + if len(gtDontCarePolsNum) > 0: + for dontCarePol in gtDontCarePolsNum: + dontCarePol = gtPols[dontCarePol] + intersected_area = get_intersection(dontCarePol, detPol) + pdDimensions = Polygon(detPol).area + precision = ( + 0 if pdDimensions == 0 else intersected_area / pdDimensions + ) + if precision > self.area_precision_constraint: + detDontCarePolsNum.append(len(detPols) - 1) + break + + evaluationLog += ( + "DET polygons: " + + str(len(detPols)) + + ( + " (" + str(len(detDontCarePolsNum)) + " don't care)\n" + if len(detDontCarePolsNum) > 0 + else "\n" + ) + ) + + if len(gtPols) > 0 and len(detPols) > 0: + # Calculate IoU and precision matrixs + outputShape = [len(gtPols), len(detPols)] + iouMat = np.empty(outputShape) + gtRectMat = np.zeros(len(gtPols), np.int8) + detRectMat = np.zeros(len(detPols), np.int8) + if self.is_output_polygon: + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + pG = gtPols[gtNum] + pD = detPols[detNum] + iouMat[gtNum, detNum] = get_intersection_over_union(pD, pG) + else: + # gtPols = np.float32(gtPols) + # detPols = np.float32(detPols) + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + pG = np.float32(gtPols[gtNum]) + pD = np.float32(detPols[detNum]) + iouMat[gtNum, detNum] = iou_rotate(pD, pG) + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCarePolsNum + and detNum not in detDontCarePolsNum + ): + if iouMat[gtNum, detNum] > self.iou_constraint: + gtRectMat[gtNum] = 1 + detRectMat[detNum] = 1 + detMatched += 1 + pairs.append({"gt": gtNum, "det": detNum}) + detMatchedNums.append(detNum) + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + "\n" + ) + + numGtCare = len(gtPols) - len(gtDontCarePolsNum) + numDetCare = len(detPols) - len(detDontCarePolsNum) + if numGtCare == 0: + recall = float(1) + precision = float(0) if numDetCare > 0 else float(1) + else: + recall = float(detMatched) / numGtCare + precision = 0 if numDetCare == 0 else float(detMatched) / numDetCare + + hmean = ( + 0 + if (precision + recall) == 0 + else 2.0 * precision * recall / (precision + recall) + ) + + matchedSum += detMatched + numGlobalCareGt += numGtCare + numGlobalCareDet += numDetCare + + perSampleMetrics = { + "precision": precision, + "recall": recall, + "hmean": hmean, + "pairs": pairs, + "iouMat": [] if len(detPols) > 100 else iouMat.tolist(), + "gtPolPoints": gtPolPoints, + "detPolPoints": detPolPoints, + "gtCare": numGtCare, + "detCare": numDetCare, + "gtDontCare": gtDontCarePolsNum, + "detDontCare": detDontCarePolsNum, + "detMatched": detMatched, + "evaluationLog": evaluationLog, + } + + return perSampleMetrics + + def combine_results(self, results): + numGlobalCareGt = 0 + numGlobalCareDet = 0 + matchedSum = 0 + for result in results: + numGlobalCareGt += result["gtCare"] + numGlobalCareDet += result["detCare"] + matchedSum += result["detMatched"] + + methodRecall = ( + 0 if numGlobalCareGt == 0 else float(matchedSum) / numGlobalCareGt + ) + methodPrecision = ( + 0 if numGlobalCareDet == 0 else float(matchedSum) / numGlobalCareDet + ) + methodHmean = ( + 0 + if methodRecall + methodPrecision == 0 + else 2 * methodRecall * methodPrecision / (methodRecall + methodPrecision) + ) + + methodMetrics = { + "precision": methodPrecision, + "recall": methodRecall, + "hmean": methodHmean, + } + + return methodMetrics + + +if __name__ == "__main__": + evaluator = DetectionIoUEvaluator() + preds = [ + [ + { + "points": [(0.1, 0.1), (0.5, 0), (0.5, 1), (0, 1)], + "text": 1234, + "ignore": False, + }, + { + "points": [(0.5, 0.1), (1, 0), (1, 1), (0.5, 1)], + "text": 5678, + "ignore": False, + }, + ] + ] + gts = [ + [ + { + "points": [(0.1, 0.1), (1, 0), (1, 1), (0, 1)], + "text": 123, + "ignore": False, + } + ] + ] + results = [] + for gt, pred in zip(gts, preds): + results.append(evaluator.evaluate_image(gt, pred)) + metrics = evaluator.combine_results(results) + print(metrics) diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/mtwi2018.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/mtwi2018.py new file mode 100644 index 0000000..51eccdc --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/detection/mtwi2018.py @@ -0,0 +1,398 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import math +from collections import namedtuple +import numpy as np +from shapely.geometry import Polygon + + +class DetectionMTWI2018Evaluator(object): + def __init__( + self, + area_recall_constraint=0.7, + area_precision_constraint=0.7, + ev_param_ind_center_diff_thr=1, + ): + self.area_recall_constraint = area_recall_constraint + self.area_precision_constraint = area_precision_constraint + self.ev_param_ind_center_diff_thr = ev_param_ind_center_diff_thr + + def evaluate_image(self, gt, pred): + def get_union(pD, pG): + return Polygon(pD).union(Polygon(pG)).area + + def get_intersection_over_union(pD, pG): + return get_intersection(pD, pG) / get_union(pD, pG) + + def get_intersection(pD, pG): + return Polygon(pD).intersection(Polygon(pG)).area + + def one_to_one_match(row, col): + cont = 0 + for j in range(len(recallMat[0])): + if ( + recallMat[row, j] >= self.area_recall_constraint + and precisionMat[row, j] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + cont = 0 + for i in range(len(recallMat)): + if ( + recallMat[i, col] >= self.area_recall_constraint + and precisionMat[i, col] >= self.area_precision_constraint + ): + cont = cont + 1 + if cont != 1: + return False + + if ( + recallMat[row, col] >= self.area_recall_constraint + and precisionMat[row, col] >= self.area_precision_constraint + ): + return True + return False + + def one_to_many_match(gtNum): + many_sum = 0 + detRects = [] + for detNum in range(len(recallMat[0])): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and detNum not in detDontCareRectsNum + ): + if precisionMat[gtNum, detNum] >= self.area_precision_constraint: + many_sum += recallMat[gtNum, detNum] + detRects.append(detNum) + if round(many_sum, 4) >= self.area_recall_constraint: + return True, detRects + else: + return False, [] + + def many_to_one_match(detNum): + many_sum = 0 + gtRects = [] + for gtNum in range(len(recallMat)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + ): + if recallMat[gtNum, detNum] >= self.area_recall_constraint: + many_sum += precisionMat[gtNum, detNum] + gtRects.append(gtNum) + if round(many_sum, 4) >= self.area_precision_constraint: + return True, gtRects + else: + return False, [] + + def center_distance(r1, r2): + return ((np.mean(r1, axis=0) - np.mean(r2, axis=0)) ** 2).sum() ** 0.5 + + def diag(r): + r = np.array(r) + return ( + (r[:, 0].max() - r[:, 0].min()) ** 2 + + (r[:, 1].max() - r[:, 1].min()) ** 2 + ) ** 0.5 + + perSampleMetrics = {} + + recall = 0 + precision = 0 + hmean = 0 + recallAccum = 0.0 + precisionAccum = 0.0 + gtRects = [] + detRects = [] + gtPolPoints = [] + detPolPoints = [] + gtDontCareRectsNum = ( + [] + ) # Array of Ground Truth Rectangles' keys marked as don't Care + detDontCareRectsNum = ( + [] + ) # Array of Detected Rectangles' matched with a don't Care GT + pairs = [] + evaluationLog = "" + + recallMat = np.empty([1, 1]) + precisionMat = np.empty([1, 1]) + + for n in range(len(gt)): + points = gt[n]["points"] + # transcription = gt[n]['text'] + dontCare = gt[n]["ignore"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + gtRects.append(points) + gtPolPoints.append(points) + if dontCare: + gtDontCareRectsNum.append(len(gtRects) - 1) + + evaluationLog += ( + "GT rectangles: " + + str(len(gtRects)) + + ( + " (" + str(len(gtDontCareRectsNum)) + " don't care)\n" + if len(gtDontCareRectsNum) > 0 + else "\n" + ) + ) + + for n in range(len(pred)): + points = pred[n]["points"] + + if not Polygon(points).is_valid or not Polygon(points).is_simple: + continue + + detRect = points + detRects.append(detRect) + detPolPoints.append(points) + if len(gtDontCareRectsNum) > 0: + for dontCareRectNum in gtDontCareRectsNum: + dontCareRect = gtRects[dontCareRectNum] + intersected_area = get_intersection(dontCareRect, detRect) + rdDimensions = Polygon(detRect).area + if rdDimensions == 0: + precision = 0 + else: + precision = intersected_area / rdDimensions + if precision > 0.5: + detDontCareRectsNum.append(len(detRects) - 1) + break + + evaluationLog += ( + "DET rectangles: " + + str(len(detRects)) + + ( + " (" + str(len(detDontCareRectsNum)) + " don't care)\n" + if len(detDontCareRectsNum) > 0 + else "\n" + ) + ) + + if len(gtRects) == 0: + recall = 1 + precision = 0 if len(detRects) > 0 else 1 + + if len(detRects) > 0: + # Calculate recall and precision matrixs + outputShape = [len(gtRects), len(detRects)] + recallMat = np.empty(outputShape) + precisionMat = np.empty(outputShape) + gtRectMat = np.zeros(len(gtRects), np.int8) + detRectMat = np.zeros(len(detRects), np.int8) + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + rG = gtRects[gtNum] + rD = detRects[detNum] + intersected_area = get_intersection(rG, rD) + rgDimensions = Polygon(rG).area + rdDimensions = Polygon(rD).area + recallMat[gtNum, detNum] = ( + 0 if rgDimensions == 0 else intersected_area / rgDimensions + ) + precisionMat[gtNum, detNum] = ( + 0 if rdDimensions == 0 else intersected_area / rdDimensions + ) + + # Find one-to-one matches + evaluationLog += "Find one-to-one matches\n" + for gtNum in range(len(gtRects)): + for detNum in range(len(detRects)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCareRectsNum + and detNum not in detDontCareRectsNum + ): + match = one_to_one_match(gtNum, detNum) + if match is True: + # in deteval we have to make other validation before mark as one-to-one + rG = gtRects[gtNum] + rD = detRects[detNum] + normDist = center_distance(rG, rD) + normDist /= diag(rG) + diag(rD) + normDist *= 2.0 + if normDist < self.ev_param_ind_center_diff_thr: + gtRectMat[gtNum] = 1 + detRectMat[detNum] = 1 + recallAccum += 1.0 + precisionAccum += 1.0 + pairs.append({"gt": gtNum, "det": detNum, "type": "OO"}) + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + "\n" + ) + else: + evaluationLog += ( + "Match Discarded GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + " normDist: " + + str(normDist) + + " \n" + ) + # Find one-to-many matches + evaluationLog += "Find one-to-many matches\n" + for gtNum in range(len(gtRects)): + if gtNum not in gtDontCareRectsNum: + match, matchesDet = one_to_many_match(gtNum) + if match is True: + gtRectMat[gtNum] = 1 + recallAccum += 1.0 + precisionAccum += len(matchesDet) / ( + 1 + math.log(len(matchesDet)) + ) + pairs.append( + { + "gt": gtNum, + "det": matchesDet, + "type": "OO" if len(matchesDet) == 1 else "OM", + } + ) + for detNum in matchesDet: + detRectMat[detNum] = 1 + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(matchesDet) + + "\n" + ) + + # Find many-to-one matches + evaluationLog += "Find many-to-one matches\n" + for detNum in range(len(detRects)): + if detNum not in detDontCareRectsNum: + match, matchesGt = many_to_one_match(detNum) + if match is True: + detRectMat[detNum] = 1 + recallAccum += len(matchesGt) / (1 + math.log(len(matchesGt))) + precisionAccum += 1.0 + pairs.append( + { + "gt": matchesGt, + "det": detNum, + "type": "OO" if len(matchesGt) == 1 else "MO", + } + ) + for gtNum in matchesGt: + gtRectMat[gtNum] = 1 + evaluationLog += ( + "Match GT #" + + str(matchesGt) + + " with Det #" + + str(detNum) + + "\n" + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + if numGtCare == 0: + recall = float(1) + precision = float(0) if len(detRects) > 0 else float(1) + else: + recall = float(recallAccum) / numGtCare + precision = ( + float(0) + if (len(detRects) - len(detDontCareRectsNum)) == 0 + else float(precisionAccum) + / (len(detRects) - len(detDontCareRectsNum)) + ) + hmean = ( + 0 + if (precision + recall) == 0 + else 2.0 * precision * recall / (precision + recall) + ) + + numGtCare = len(gtRects) - len(gtDontCareRectsNum) + numDetCare = len(detRects) - len(detDontCareRectsNum) + + perSampleMetrics = { + "precision": precision, + "recall": recall, + "hmean": hmean, + "pairs": pairs, + "recallMat": [] if len(detRects) > 100 else recallMat.tolist(), + "precisionMat": [] if len(detRects) > 100 else precisionMat.tolist(), + "gtPolPoints": gtPolPoints, + "detPolPoints": detPolPoints, + "gtCare": numGtCare, + "detCare": numDetCare, + "gtDontCare": gtDontCareRectsNum, + "detDontCare": detDontCareRectsNum, + "recallAccum": recallAccum, + "precisionAccum": precisionAccum, + "evaluationLog": evaluationLog, + } + + return perSampleMetrics + + def combine_results(self, results): + numGt = 0 + numDet = 0 + methodRecallSum = 0 + methodPrecisionSum = 0 + + for result in results: + numGt += result["gtCare"] + numDet += result["detCare"] + methodRecallSum += result["recallAccum"] + methodPrecisionSum += result["precisionAccum"] + + methodRecall = 0 if numGt == 0 else methodRecallSum / numGt + methodPrecision = 0 if numDet == 0 else methodPrecisionSum / numDet + methodHmean = ( + 0 + if methodRecall + methodPrecision == 0 + else 2 * methodRecall * methodPrecision / (methodRecall + methodPrecision) + ) + + methodMetrics = { + "precision": methodPrecision, + "recall": methodRecall, + "hmean": methodHmean, + } + + return methodMetrics + + +if __name__ == "__main__": + evaluator = DetectionICDAR2013Evaluator() + gts = [ + [ + { + "points": [(0, 0), (1, 0), (1, 1), (0, 1)], + "text": 1234, + "ignore": False, + }, + { + "points": [(2, 2), (3, 2), (3, 3), (2, 3)], + "text": 5678, + "ignore": True, + }, + ] + ] + preds = [ + [ + { + "points": [(0.1, 0.1), (1, 0), (1, 1), (0, 1)], + "text": 123, + "ignore": False, + } + ] + ] + results = [] + for gt, pred in zip(gts, preds): + results.append(evaluator.evaluate_image(gt, pred)) + metrics = evaluator.combine_results(results) + print(metrics) diff --git a/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/quad_metric.py b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/quad_metric.py new file mode 100644 index 0000000..b4b6714 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/ocr_metric/icdar2015/quad_metric.py @@ -0,0 +1,100 @@ +import numpy as np + +from .detection.iou import DetectionIoUEvaluator + + +class AverageMeter(object): + """Computes and stores the average and current value""" + + def __init__(self): + self.reset() + + def reset(self): + self.val = 0 + self.avg = 0 + self.sum = 0 + self.count = 0 + + def update(self, val, n=1): + self.val = val + self.sum += val * n + self.count += n + self.avg = self.sum / self.count + return self + + +class QuadMetric: + def __init__(self, is_output_polygon=False): + self.is_output_polygon = is_output_polygon + self.evaluator = DetectionIoUEvaluator(is_output_polygon=is_output_polygon) + + def measure(self, batch, output, box_thresh=0.6): + """ + batch: (image, polygons, ignore_tags + batch: a dict produced by dataloaders. + image: tensor of shape (N, C, H, W). + polygons: tensor of shape (N, K, 4, 2), the polygons of objective regions. + ignore_tags: tensor of shape (N, K), indicates whether a region is ignorable or not. + shape: the original shape of images. + filename: the original filenames of images. + output: (polygons, ...) + """ + results = [] + gt_polyons_batch = batch["text_polys"] + ignore_tags_batch = batch["ignore_tags"] + pred_polygons_batch = np.array(output[0]) + pred_scores_batch = np.array(output[1]) + for polygons, pred_polygons, pred_scores, ignore_tags in zip( + gt_polyons_batch, pred_polygons_batch, pred_scores_batch, ignore_tags_batch + ): + gt = [ + dict(points=np.int64(polygons[i]), ignore=ignore_tags[i]) + for i in range(len(polygons)) + ] + if self.is_output_polygon: + pred = [ + dict(points=pred_polygons[i]) for i in range(len(pred_polygons)) + ] + else: + pred = [] + # print(pred_polygons.shape) + for i in range(pred_polygons.shape[0]): + if pred_scores[i] >= box_thresh: + # print(pred_polygons[i,:,:].tolist()) + pred.append( + dict(points=pred_polygons[i, :, :].astype(np.int32)) + ) + # pred = [dict(points=pred_polygons[i,:,:].tolist()) if pred_scores[i] >= box_thresh for i in range(pred_polygons.shape[0])] + results.append(self.evaluator.evaluate_image(gt, pred)) + return results + + def validate_measure(self, batch, output, box_thresh=0.6): + return self.measure(batch, output, box_thresh) + + def evaluate_measure(self, batch, output): + return ( + self.measure(batch, output), + np.linspace(0, batch["image"].shape[0]).tolist(), + ) + + def gather_measure(self, raw_metrics): + raw_metrics = [ + image_metrics + for batch_metrics in raw_metrics + for image_metrics in batch_metrics + ] + + result = self.evaluator.combine_results(raw_metrics) + + precision = AverageMeter() + recall = AverageMeter() + fmeasure = AverageMeter() + + precision.update(result["precision"], n=len(raw_metrics)) + recall.update(result["recall"], n=len(raw_metrics)) + fmeasure_score = ( + 2 * precision.val * recall.val / (precision.val + recall.val + 1e-8) + ) + fmeasure.update(fmeasure_score) + + return {"precision": precision, "recall": recall, "fmeasure": fmeasure} diff --git a/benchmark/PaddleOCR_DBNet/utils/profiler.py b/benchmark/PaddleOCR_DBNet/utils/profiler.py new file mode 100644 index 0000000..34fcea8 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/profiler.py @@ -0,0 +1,112 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import paddle + +# A global variable to record the number of calling times for profiler +# functions. It is used to specify the tracing range of training steps. +_profiler_step_id = 0 + +# A global variable to avoid parsing from string every time. +_profiler_options = None + + +class ProfilerOptions(object): + """ + Use a string to initialize a ProfilerOptions. + The string should be in the format: "key1=value1;key2=value;key3=value3". + For example: + "profile_path=model.profile" + "batch_range=[50, 60]; profile_path=model.profile" + "batch_range=[50, 60]; tracer_option=OpDetail; profile_path=model.profile" + ProfilerOptions supports following key-value pair: + batch_range - a integer list, e.g. [100, 110]. + state - a string, the optional values are 'CPU', 'GPU' or 'All'. + sorted_key - a string, the optional values are 'calls', 'total', + 'max', 'min' or 'ave. + tracer_option - a string, the optional values are 'Default', 'OpDetail', + 'AllOpDetail'. + profile_path - a string, the path to save the serialized profile data, + which can be used to generate a timeline. + exit_on_finished - a boolean. + """ + + def __init__(self, options_str): + assert isinstance(options_str, str) + + self._options = { + "batch_range": [10, 20], + "state": "All", + "sorted_key": "total", + "tracer_option": "Default", + "profile_path": "/tmp/profile", + "exit_on_finished": True, + } + self._parse_from_string(options_str) + + def _parse_from_string(self, options_str): + for kv in options_str.replace(" ", "").split(";"): + key, value = kv.split("=") + if key == "batch_range": + value_list = value.replace("[", "").replace("]", "").split(",") + value_list = list(map(int, value_list)) + if ( + len(value_list) >= 2 + and value_list[0] >= 0 + and value_list[1] > value_list[0] + ): + self._options[key] = value_list + elif key == "exit_on_finished": + self._options[key] = value.lower() in ("yes", "true", "t", "1") + elif key in ["state", "sorted_key", "tracer_option", "profile_path"]: + self._options[key] = value + + def __getitem__(self, name): + if self._options.get(name, None) is None: + raise ValueError("ProfilerOptions does not have an option named %s." % name) + return self._options[name] + + +def add_profiler_step(options_str=None): + """ + Enable the operator-level timing using PaddlePaddle's profiler. + The profiler uses a independent variable to count the profiler steps. + One call of this function is treated as a profiler step. + + Args: + profiler_options - a string to initialize the ProfilerOptions. + Default is None, and the profiler is disabled. + """ + if options_str is None: + return + + global _profiler_step_id + global _profiler_options + + if _profiler_options is None: + _profiler_options = ProfilerOptions(options_str) + + if _profiler_step_id == _profiler_options["batch_range"][0]: + paddle.utils.profiler.start_profiler( + _profiler_options["state"], _profiler_options["tracer_option"] + ) + elif _profiler_step_id == _profiler_options["batch_range"][1]: + paddle.utils.profiler.stop_profiler( + _profiler_options["sorted_key"], _profiler_options["profile_path"] + ) + if _profiler_options["exit_on_finished"]: + sys.exit(0) + + _profiler_step_id += 1 diff --git a/benchmark/PaddleOCR_DBNet/utils/schedulers.py b/benchmark/PaddleOCR_DBNet/utils/schedulers.py new file mode 100644 index 0000000..e038ddc --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/schedulers.py @@ -0,0 +1,72 @@ +from paddle.optimizer import lr +import logging + +__all__ = ["Polynomial"] + + +class Polynomial(object): + """ + Polynomial learning rate decay + Args: + learning_rate (float): The initial learning rate. It is a python float number. + epochs(int): The decay epoch size. It determines the decay cycle, when by_epoch is set to true, it will change to epochs=epochs*step_each_epoch. + step_each_epoch: all steps in each epoch. + end_lr(float, optional): The minimum final learning rate. Default: 0.0001. + power(float, optional): Power of polynomial. Default: 1.0. + warmup_epoch(int): The epoch numbers for LinearWarmup. Default: 0, , when by_epoch is set to true, it will change to warmup_epoch=warmup_epoch*step_each_epoch. + warmup_start_lr(float): Initial learning rate of warm up. Default: 0.0. + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + by_epoch: Whether the set parameter is based on epoch or iter, when set to true,, epochs and warmup_epoch will be automatically multiplied by step_each_epoch. Default: True + """ + + def __init__( + self, + learning_rate, + epochs, + step_each_epoch, + end_lr=0.0, + power=1.0, + warmup_epoch=0, + warmup_start_lr=0.0, + last_epoch=-1, + by_epoch=True, + **kwargs, + ): + super().__init__() + if warmup_epoch >= epochs: + msg = f'When using warm up, the value of "epochs" must be greater than value of "Optimizer.lr.warmup_epoch". The value of "Optimizer.lr.warmup_epoch" has been set to {epochs}.' + logging.warning(msg) + warmup_epoch = epochs + self.learning_rate = learning_rate + self.epochs = epochs + self.end_lr = end_lr + self.power = power + self.last_epoch = last_epoch + self.warmup_epoch = warmup_epoch + self.warmup_start_lr = warmup_start_lr + + if by_epoch: + self.epochs *= step_each_epoch + self.warmup_epoch = int(self.warmup_epoch * step_each_epoch) + + def __call__(self): + learning_rate = ( + lr.PolynomialDecay( + learning_rate=self.learning_rate, + decay_steps=self.epochs, + end_lr=self.end_lr, + power=self.power, + last_epoch=self.last_epoch, + ) + if self.epochs > 0 + else self.learning_rate + ) + if self.warmup_epoch > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=self.warmup_start_lr, + end_lr=self.learning_rate, + last_epoch=self.last_epoch, + ) + return learning_rate diff --git a/benchmark/PaddleOCR_DBNet/utils/util.py b/benchmark/PaddleOCR_DBNet/utils/util.py new file mode 100644 index 0000000..e46caa1 --- /dev/null +++ b/benchmark/PaddleOCR_DBNet/utils/util.py @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- +# @Time : 2019/8/23 21:59 +# @Author : zhoujun +import json +import pathlib +import time +import os +import glob +import cv2 +import yaml +from collections.abc import Mapping +import matplotlib.pyplot as plt +import numpy as np + +from argparse import ArgumentParser, RawDescriptionHelpFormatter + + +def _check_image_file(path): + img_end = {"jpg", "bmp", "png", "jpeg", "rgb", "tif", "tiff", "gif", "pdf"} + return any([path.lower().endswith(e) for e in img_end]) + + +def get_image_file_list(img_file): + imgs_lists = [] + if img_file is None or not os.path.exists(img_file): + raise Exception("not found any img file in {}".format(img_file)) + + img_end = {"jpg", "bmp", "png", "jpeg", "rgb", "tif", "tiff", "gif", "pdf"} + if os.path.isfile(img_file) and _check_image_file(img_file): + imgs_lists.append(img_file) + elif os.path.isdir(img_file): + for single_file in os.listdir(img_file): + file_path = os.path.join(img_file, single_file) + if os.path.isfile(file_path) and _check_image_file(file_path): + imgs_lists.append(file_path) + if len(imgs_lists) == 0: + raise Exception("not found any img file in {}".format(img_file)) + imgs_lists = sorted(imgs_lists) + return imgs_lists + + +def setup_logger(log_file_path: str = None): + import logging + + logging._warn_preinit_stderr = 0 + logger = logging.getLogger("DBNet.paddle") + formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s: %(message)s") + ch = logging.StreamHandler() + ch.setFormatter(formatter) + logger.addHandler(ch) + if log_file_path is not None: + file_handle = logging.FileHandler(log_file_path) + file_handle.setFormatter(formatter) + logger.addHandler(file_handle) + logger.setLevel(logging.DEBUG) + return logger + + +# --exeTime +def exe_time(func): + def newFunc(*args, **args2): + t0 = time.time() + back = func(*args, **args2) + print("{} cost {:.3f}s".format(func.__name__, time.time() - t0)) + return back + + return newFunc + + +def load(file_path: str): + file_path = pathlib.Path(file_path) + func_dict = {".txt": _load_txt, ".json": _load_json, ".list": _load_txt} + assert file_path.suffix in func_dict + return func_dict[file_path.suffix](file_path) + + +def _load_txt(file_path: str): + with open(file_path, "r", encoding="utf8") as f: + content = [ + x.strip().strip("\ufeff").strip("\xef\xbb\xbf") for x in f.readlines() + ] + return content + + +def _load_json(file_path: str): + with open(file_path, "r", encoding="utf8") as f: + content = json.load(f) + return content + + +def save(data, file_path): + file_path = pathlib.Path(file_path) + func_dict = {".txt": _save_txt, ".json": _save_json} + assert file_path.suffix in func_dict + return func_dict[file_path.suffix](data, file_path) + + +def _save_txt(data, file_path): + """ + 将一个list的数组写入txt文件里 + :param data: + :param file_path: + :return: + """ + if not isinstance(data, list): + data = [data] + with open(file_path, mode="w", encoding="utf8") as f: + f.write("\n".join(data)) + + +def _save_json(data, file_path): + with open(file_path, "w", encoding="utf-8") as json_file: + json.dump(data, json_file, ensure_ascii=False, indent=4) + + +def show_img(imgs: np.ndarray, title="img"): + color = len(imgs.shape) == 3 and imgs.shape[-1] == 3 + imgs = np.expand_dims(imgs, axis=0) + for i, img in enumerate(imgs): + plt.figure() + plt.title("{}_{}".format(title, i)) + plt.imshow(img, cmap=None if color else "gray") + plt.show() + + +def draw_bbox(img_path, result, color=(255, 0, 0), thickness=2): + if isinstance(img_path, str): + img_path = cv2.imread(img_path) + # img_path = cv2.cvtColor(img_path, cv2.COLOR_BGR2RGB) + img_path = img_path.copy() + for point in result: + point = point.astype(int) + cv2.polylines(img_path, [point], True, color, thickness) + return img_path + + +def cal_text_score(texts, gt_texts, training_masks, running_metric_text, thred=0.5): + training_masks = training_masks.numpy() + pred_text = texts.numpy() * training_masks + pred_text[pred_text <= thred] = 0 + pred_text[pred_text > thred] = 1 + pred_text = pred_text.astype(np.int32) + gt_text = gt_texts.numpy() * training_masks + gt_text = gt_text.astype(np.int32) + running_metric_text.update(gt_text, pred_text) + score_text, _ = running_metric_text.get_scores() + return score_text + + +def order_points_clockwise(pts): + rect = np.zeros((4, 2), dtype="float32") + s = pts.sum(axis=1) + rect[0] = pts[np.argmin(s)] + rect[2] = pts[np.argmax(s)] + diff = np.diff(pts, axis=1) + rect[1] = pts[np.argmin(diff)] + rect[3] = pts[np.argmax(diff)] + return rect + + +def order_points_clockwise_list(pts): + pts = pts.tolist() + pts.sort(key=lambda x: (x[1], x[0])) + pts[:2] = sorted(pts[:2], key=lambda x: x[0]) + pts[2:] = sorted(pts[2:], key=lambda x: -x[0]) + pts = np.array(pts) + return pts + + +def get_datalist(train_data_path): + """ + 获取训练和验证的数据list + :param train_data_path: 训练的dataset文件列表,每个文件内以如下格式存储 ‘path/to/img\tlabel’ + :return: + """ + train_data = [] + for p in train_data_path: + with open(p, "r", encoding="utf-8") as f: + for line in f.readlines(): + line = line.strip("\n").replace(".jpg ", ".jpg\t").split("\t") + if len(line) > 1: + img_path = pathlib.Path(line[0].strip(" ")) + label_path = pathlib.Path(line[1].strip(" ")) + if ( + img_path.exists() + and img_path.stat().st_size > 0 + and label_path.exists() + and label_path.stat().st_size > 0 + ): + train_data.append((str(img_path), str(label_path))) + return train_data + + +def save_result(result_path, box_list, score_list, is_output_polygon): + if is_output_polygon: + with open(result_path, "wt") as res: + for i, box in enumerate(box_list): + box = box.reshape(-1).tolist() + result = ",".join([str(int(x)) for x in box]) + score = score_list[i] + res.write(result + "," + str(score) + "\n") + else: + with open(result_path, "wt") as res: + for i, box in enumerate(box_list): + score = score_list[i] + box = box.reshape(-1).tolist() + result = ",".join([str(int(x)) for x in box]) + res.write(result + "," + str(score) + "\n") + + +def expand_polygon(polygon): + """ + 对只有一个字符的框进行扩充 + """ + (x, y), (w, h), angle = cv2.minAreaRect(np.float32(polygon)) + if angle < -45: + w, h = h, w + angle += 90 + new_w = w + h + box = ((x, y), (new_w, h), angle) + points = cv2.boxPoints(box) + return order_points_clockwise(points) + + +def _merge_dict(config, merge_dct): + """Recursive dict merge. Inspired by :meth:``dict.update()``, instead of + updating only top-level keys, dict_merge recurses down into dicts nested + to an arbitrary depth, updating keys. The ``merge_dct`` is merged into + ``dct``. + Args: + config: dict onto which the merge is executed + merge_dct: dct merged into config + Returns: dct + """ + for key, value in merge_dct.items(): + sub_keys = key.split(".") + key = sub_keys[0] + if key in config and len(sub_keys) > 1: + _merge_dict(config[key], {".".join(sub_keys[1:]): value}) + elif ( + key in config + and isinstance(config[key], dict) + and isinstance(value, Mapping) + ): + _merge_dict(config[key], value) + else: + config[key] = value + return config + + +def print_dict(cfg, print_func=print, delimiter=0): + """ + Recursively visualize a dict and + indenting acrrording by the relationship of keys. + """ + for k, v in sorted(cfg.items()): + if isinstance(v, dict): + print_func("{}{} : ".format(delimiter * " ", str(k))) + print_dict(v, print_func, delimiter + 4) + elif isinstance(v, list) and len(v) >= 1 and isinstance(v[0], dict): + print_func("{}{} : ".format(delimiter * " ", str(k))) + for value in v: + print_dict(value, print_func, delimiter + 4) + else: + print_func("{}{} : {}".format(delimiter * " ", k, v)) + + +class Config(object): + def __init__(self, config_path, BASE_KEY="base"): + self.BASE_KEY = BASE_KEY + self.cfg = self._load_config_with_base(config_path) + + def _load_config_with_base(self, file_path): + """ + Load config from file. + Args: + file_path (str): Path of the config file to be loaded. + Returns: global config + """ + _, ext = os.path.splitext(file_path) + assert ext in [".yml", ".yaml"], "only support yaml files for now" + + with open(file_path) as f: + file_cfg = yaml.load(f, Loader=yaml.SafeLoader) + + # NOTE: cfgs outside have higher priority than cfgs in _BASE_ + if self.BASE_KEY in file_cfg: + all_base_cfg = dict() + base_ymls = list(file_cfg[self.BASE_KEY]) + for base_yml in base_ymls: + with open(base_yml) as f: + base_cfg = self._load_config_with_base(base_yml) + all_base_cfg = _merge_dict(all_base_cfg, base_cfg) + + del file_cfg[self.BASE_KEY] + file_cfg = _merge_dict(all_base_cfg, file_cfg) + file_cfg["filename"] = os.path.splitext(os.path.split(file_path)[-1])[0] + return file_cfg + + def merge_dict(self, args): + self.cfg = _merge_dict(self.cfg, args) + + def print_cfg(self, print_func=print): + """ + Recursively visualize a dict and + indenting according by the relationship of keys. + """ + print_func("----------- Config -----------") + print_dict(self.cfg, print_func) + print_func("---------------------------------------------") + + def save(self, p): + with open(p, "w") as f: + yaml.dump(dict(self.cfg), f, default_flow_style=False, sort_keys=False) + + +class ArgsParser(ArgumentParser): + def __init__(self): + super(ArgsParser, self).__init__(formatter_class=RawDescriptionHelpFormatter) + self.add_argument("-c", "--config_file", help="configuration file to use") + self.add_argument("-o", "--opt", nargs="*", help="set configuration options") + self.add_argument( + "-p", + "--profiler_options", + type=str, + default=None, + help="The option of profiler, which should be in format " + '"key1=value1;key2=value2;key3=value3".', + ) + + def parse_args(self, argv=None): + args = super(ArgsParser, self).parse_args(argv) + assert ( + args.config_file is not None + ), "Please specify --config_file=configure_file_path." + args.opt = self._parse_opt(args.opt) + return args + + def _parse_opt(self, opts): + config = {} + if not opts: + return config + for s in opts: + s = s.strip() + k, v = s.split("=", 1) + if "." not in k: + config[k] = yaml.load(v, Loader=yaml.SafeLoader) + else: + keys = k.split(".") + if keys[0] not in config: + config[keys[0]] = {} + cur = config[keys[0]] + for idx, key in enumerate(keys[1:]): + if idx == len(keys) - 2: + cur[key] = yaml.load(v, Loader=yaml.SafeLoader) + else: + cur[key] = {} + cur = cur[key] + return config + + +if __name__ == "__main__": + img = np.zeros((1, 3, 640, 640)) + show_img(img[0][0]) + plt.show() diff --git a/benchmark/analysis.py b/benchmark/analysis.py new file mode 100644 index 0000000..bf6233f --- /dev/null +++ b/benchmark/analysis.py @@ -0,0 +1,354 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import print_function + +import argparse +import json +import os +import re +import traceback + + +def parse_args(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--filename", type=str, help="The name of log which need to analysis." + ) + parser.add_argument( + "--log_with_profiler", type=str, help="The path of train log with profiler" + ) + parser.add_argument( + "--profiler_path", type=str, help="The path of profiler timeline log." + ) + parser.add_argument("--keyword", type=str, help="Keyword to specify analysis data") + parser.add_argument( + "--separator", + type=str, + default=None, + help="Separator of different field in log", + ) + parser.add_argument( + "--position", type=int, default=None, help="The position of data field" + ) + parser.add_argument( + "--range", type=str, default="", help="The range of data field to intercept" + ) + parser.add_argument("--base_batch_size", type=int, help="base_batch size on gpu") + parser.add_argument( + "--skip_steps", type=int, default=0, help="The number of steps to be skipped" + ) + parser.add_argument( + "--model_mode", type=int, default=-1, help="Analysis mode, default value is -1" + ) + parser.add_argument("--ips_unit", type=str, default=None, help="IPS unit") + parser.add_argument( + "--model_name", + type=str, + default=0, + help="training model_name, transformer_base", + ) + parser.add_argument( + "--mission_name", type=str, default=0, help="training mission name" + ) + parser.add_argument( + "--direction_id", type=int, default=0, help="training direction_id" + ) + parser.add_argument( + "--run_mode", type=str, default="sp", help="multi process or single process" + ) + parser.add_argument( + "--index", + type=int, + default=1, + help="{1: speed, 2:mem, 3:profiler, 6:max_batch_size}", + ) + parser.add_argument("--gpu_num", type=int, default=1, help="nums of training gpus") + args = parser.parse_args() + args.separator = None if args.separator == "None" else args.separator + return args + + +def _is_number(num): + pattern = re.compile(r"^[-+]?[-0-9]\d*\.\d*|[-+]?\.?[0-9]\d*$") + result = pattern.match(num) + if result: + return True + else: + return False + + +class TimeAnalyzer(object): + def __init__( + self, filename, keyword=None, separator=None, position=None, range="-1" + ): + if filename is None: + raise Exception("Please specify the filename!") + + if keyword is None: + raise Exception("Please specify the keyword!") + + self.filename = filename + self.keyword = keyword + self.separator = separator + self.position = position + self.range = range + self.records = None + self._distil() + + def _distil(self): + self.records = [] + with open(self.filename, "r") as f_object: + lines = f_object.readlines() + for line in lines: + if self.keyword not in line: + continue + try: + result = None + + # Distil the string from a line. + line = line.strip() + line_words = ( + line.split(self.separator) if self.separator else line.split() + ) + if args.position: + result = line_words[self.position] + else: + # Distil the string following the keyword. + for i in range(len(line_words) - 1): + if line_words[i] == self.keyword: + result = line_words[i + 1] + break + + # Distil the result from the picked string. + if not self.range: + result = result[0:] + elif _is_number(self.range): + result = result[0 : int(self.range)] + else: + result = result[ + int(self.range.split(":")[0]) : int( + self.range.split(":")[1] + ) + ] + self.records.append(float(result)) + except Exception as exc: + print( + "line is: {}; separator={}; position={}".format( + line, self.separator, self.position + ) + ) + + print( + "Extract {} records: separator={}; position={}".format( + len(self.records), self.separator, self.position + ) + ) + + def _get_fps(self, mode, batch_size, gpu_num, avg_of_records, run_mode, unit=None): + if mode == -1 and run_mode == "sp": + assert unit, "Please set the unit when mode is -1." + fps = gpu_num * avg_of_records + elif mode == -1 and run_mode == "mp": + assert unit, "Please set the unit when mode is -1." + fps = gpu_num * avg_of_records # temporarily, not used now + print("------------this is mp") + elif mode == 0: + # s/step -> samples/s + fps = (batch_size * gpu_num) / avg_of_records + unit = "samples/s" + elif mode == 1: + # steps/s -> steps/s + fps = avg_of_records + unit = "steps/s" + elif mode == 2: + # s/step -> steps/s + fps = 1 / avg_of_records + unit = "steps/s" + elif mode == 3: + # steps/s -> samples/s + fps = batch_size * gpu_num * avg_of_records + unit = "samples/s" + elif mode == 4: + # s/epoch -> s/epoch + fps = avg_of_records + unit = "s/epoch" + else: + ValueError("Unsupported analysis mode.") + + return fps, unit + + def analysis( + self, batch_size, gpu_num=1, skip_steps=0, mode=-1, run_mode="sp", unit=None + ): + if batch_size <= 0: + print("base_batch_size should larger than 0.") + return 0, "" + + if ( + len(self.records) <= skip_steps + ): # to address the condition which item of log equals to skip_steps + print("no records") + return 0, "" + + sum_of_records = 0 + sum_of_records_skipped = 0 + skip_min = self.records[skip_steps] + skip_max = self.records[skip_steps] + + count = len(self.records) + for i in range(count): + sum_of_records += self.records[i] + if i >= skip_steps: + sum_of_records_skipped += self.records[i] + if self.records[i] < skip_min: + skip_min = self.records[i] + if self.records[i] > skip_max: + skip_max = self.records[i] + + avg_of_records = sum_of_records / float(count) + avg_of_records_skipped = sum_of_records_skipped / float(count - skip_steps) + + fps, fps_unit = self._get_fps( + mode, batch_size, gpu_num, avg_of_records, run_mode, unit + ) + fps_skipped, _ = self._get_fps( + mode, batch_size, gpu_num, avg_of_records_skipped, run_mode, unit + ) + if mode == -1: + print("average ips of %d steps, skip 0 step:" % count) + print("\tAvg: %.3f %s" % (avg_of_records, fps_unit)) + print("\tFPS: %.3f %s" % (fps, fps_unit)) + if skip_steps > 0: + print("average ips of %d steps, skip %d steps:" % (count, skip_steps)) + print("\tAvg: %.3f %s" % (avg_of_records_skipped, fps_unit)) + print("\tMin: %.3f %s" % (skip_min, fps_unit)) + print("\tMax: %.3f %s" % (skip_max, fps_unit)) + print("\tFPS: %.3f %s" % (fps_skipped, fps_unit)) + elif mode == 1 or mode == 3: + print("average latency of %d steps, skip 0 step:" % count) + print("\tAvg: %.3f steps/s" % avg_of_records) + print("\tFPS: %.3f %s" % (fps, fps_unit)) + if skip_steps > 0: + print( + "average latency of %d steps, skip %d steps:" % (count, skip_steps) + ) + print("\tAvg: %.3f steps/s" % avg_of_records_skipped) + print("\tMin: %.3f steps/s" % skip_min) + print("\tMax: %.3f steps/s" % skip_max) + print("\tFPS: %.3f %s" % (fps_skipped, fps_unit)) + elif mode == 0 or mode == 2: + print("average latency of %d steps, skip 0 step:" % count) + print("\tAvg: %.3f s/step" % avg_of_records) + print("\tFPS: %.3f %s" % (fps, fps_unit)) + if skip_steps > 0: + print( + "average latency of %d steps, skip %d steps:" % (count, skip_steps) + ) + print("\tAvg: %.3f s/step" % avg_of_records_skipped) + print("\tMin: %.3f s/step" % skip_min) + print("\tMax: %.3f s/step" % skip_max) + print("\tFPS: %.3f %s" % (fps_skipped, fps_unit)) + + return round(fps_skipped, 3), fps_unit + + +if __name__ == "__main__": + args = parse_args() + run_info = dict() + run_info["log_file"] = args.filename + run_info["model_name"] = args.model_name + run_info["mission_name"] = args.mission_name + run_info["direction_id"] = args.direction_id + run_info["run_mode"] = args.run_mode + run_info["index"] = args.index + run_info["gpu_num"] = args.gpu_num + run_info["FINAL_RESULT"] = 0 + run_info["JOB_FAIL_FLAG"] = 0 + + try: + if args.index == 1: + if args.gpu_num == 1: + run_info["log_with_profiler"] = args.log_with_profiler + run_info["profiler_path"] = args.profiler_path + analyzer = TimeAnalyzer( + args.filename, args.keyword, args.separator, args.position, args.range + ) + run_info["FINAL_RESULT"], run_info["UNIT"] = analyzer.analysis( + batch_size=args.base_batch_size, + gpu_num=args.gpu_num, + skip_steps=args.skip_steps, + mode=args.model_mode, + run_mode=args.run_mode, + unit=args.ips_unit, + ) + try: + if ( + int(os.getenv("job_fail_flag")) == 1 + or int(run_info["FINAL_RESULT"]) == 0 + ): + run_info["JOB_FAIL_FLAG"] = 1 + except: + pass + elif args.index == 3: + run_info["FINAL_RESULT"] = {} + records_fo_total = TimeAnalyzer( + args.filename, "Framework overhead", None, 3, "" + ).records + records_fo_ratio = TimeAnalyzer( + args.filename, "Framework overhead", None, 5 + ).records + records_ct_total = TimeAnalyzer( + args.filename, "Computation time", None, 3, "" + ).records + records_gm_total = TimeAnalyzer( + args.filename, "GpuMemcpy Calls", None, 4, "" + ).records + records_gm_ratio = TimeAnalyzer( + args.filename, "GpuMemcpy Calls", None, 6 + ).records + records_gmas_total = TimeAnalyzer( + args.filename, "GpuMemcpyAsync Calls", None, 4, "" + ).records + records_gms_total = TimeAnalyzer( + args.filename, "GpuMemcpySync Calls", None, 4, "" + ).records + run_info["FINAL_RESULT"]["Framework_Total"] = ( + records_fo_total[0] if records_fo_total else 0 + ) + run_info["FINAL_RESULT"]["Framework_Ratio"] = ( + records_fo_ratio[0] if records_fo_ratio else 0 + ) + run_info["FINAL_RESULT"]["ComputationTime_Total"] = ( + records_ct_total[0] if records_ct_total else 0 + ) + run_info["FINAL_RESULT"]["GpuMemcpy_Total"] = ( + records_gm_total[0] if records_gm_total else 0 + ) + run_info["FINAL_RESULT"]["GpuMemcpy_Ratio"] = ( + records_gm_ratio[0] if records_gm_ratio else 0 + ) + run_info["FINAL_RESULT"]["GpuMemcpyAsync_Total"] = ( + records_gmas_total[0] if records_gmas_total else 0 + ) + run_info["FINAL_RESULT"]["GpuMemcpySync_Total"] = ( + records_gms_total[0] if records_gms_total else 0 + ) + else: + print("Not support!") + except Exception: + traceback.print_exc() + print( + "{}".format(json.dumps(run_info)) + ) # it's required, for the log file path insert to the database diff --git a/benchmark/readme.md b/benchmark/readme.md new file mode 100644 index 0000000..d90d214 --- /dev/null +++ b/benchmark/readme.md @@ -0,0 +1,30 @@ + +# PaddleOCR DB/EAST/PSE 算法训练benchmark测试 + +PaddleOCR/benchmark目录下的文件用于获取并分析训练日志。 +训练采用icdar2015数据集,包括1000张训练图像和500张测试图像。模型配置采用resnet18_vd作为backbone,分别训练batch_size=8和batch_size=16的情况。 + +## 运行训练benchmark + +benchmark/run_det.sh 中包含了三个过程: +- 安装依赖 +- 下载数据 +- 执行训练 +- 日志分析获取IPS + +在执行训练部分,会执行单机单卡(默认0号卡)单机多卡训练,并分别执行batch_size=8和batch_size=16的情况。所以执行完后,每种模型会得到4个日志文件。 + +run_det.sh 执行方式如下: + +``` +# cd PaddleOCR/ +bash benchmark/run_det.sh +``` + +以DB为例,将得到四个日志文件,如下: +``` +det_res18_db_v2.0_sp_bs16_fp32_1 +det_res18_db_v2.0_sp_bs8_fp32_1 +det_res18_db_v2.0_mp_bs16_fp32_1 +det_res18_db_v2.0_mp_bs8_fp32_1 +``` diff --git a/benchmark/run_benchmark_det.sh b/benchmark/run_benchmark_det.sh new file mode 100644 index 0000000..125d874 --- /dev/null +++ b/benchmark/run_benchmark_det.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# 运行示例:CUDA_VISIBLE_DEVICES=0 bash run_benchmark.sh ${run_mode} ${bs_item} ${fp_item} 500 ${model_mode} +# 参数说明 +function _set_params(){ + run_mode=${1:-"sp"} # 单卡sp|多卡mp + batch_size=${2:-"64"} + fp_item=${3:-"fp32"} # fp32|fp16 + max_epoch=${4:-"10"} # 可选,如果需要修改代码提前中断 + model_item=${5:-"model_item"} + run_log_path=${TRAIN_LOG_DIR:-$(pwd)} # TRAIN_LOG_DIR 后续QA设置该参数 +# 日志解析所需参数 + base_batch_size=${batch_size} + mission_name="OCR" + direction_id="0" + ips_unit="images/sec" + skip_steps=2 # 解析日志,有些模型前几个step耗时长,需要跳过 (必填) + keyword="ips:" # 解析日志,筛选出数据所在行的关键字 (必填) + index="1" + model_name=${model_item}_bs${batch_size}_${fp_item} # model_item 用于yml文件名匹配,model_name 用于数据入库前端展示 +# 以下不用修改 + device=${CUDA_VISIBLE_DEVICES//,/ } + arr=(${device}) + num_gpu_devices=${#arr[*]} + log_file=${run_log_path}/${model_item}_${run_mode}_bs${batch_size}_${fp_item}_${num_gpu_devices} +} +function _train(){ + echo "Train on ${num_gpu_devices} GPUs" + echo "current CUDA_VISIBLE_DEVICES=$CUDA_VISIBLE_DEVICES, gpus=$num_gpu_devices, batch_size=$batch_size" + + train_cmd="-c configs/det/${model_item}.yml -o Train.loader.batch_size_per_card=${batch_size} Global.epoch_num=${max_epoch} Global.eval_batch_step=[0,20000] Global.print_batch_step=2" + case ${run_mode} in + sp) + train_cmd="python tools/train.py "${train_cmd}"" + ;; + mp) + rm -rf ./mylog + train_cmd="python -m paddle.distributed.launch --log_dir=./mylog --gpus=$CUDA_VISIBLE_DEVICES tools/train.py ${train_cmd}" + ;; + *) echo "choose run_mode(sp or mp)"; exit 1; + esac +# 以下不用修改 + echo ${train_cmd} + timeout 15m ${train_cmd} > ${log_file} 2>&1 + if [ $? -ne 0 ];then + echo -e "${model_name}, FAIL" + export job_fail_flag=1 + else + echo -e "${model_name}, SUCCESS" + export job_fail_flag=0 + fi + + if [ $run_mode = "mp" -a -d mylog ]; then + rm ${log_file} + cp mylog/workerlog.0 ${log_file} + fi +} + +source ${BENCHMARK_ROOT}/scripts/run_model.sh # 在该脚本中会对符合benchmark规范的log使用analysis.py 脚本进行性能数据解析;该脚本在连调时可从benchmark repo中下载https://github.com/PaddlePaddle/benchmark/blob/master/scripts/run_model.sh;如果不联调只想要产出训练log可以注掉本行,提交时需打开 +_set_params $@ +#_train # 如果只想产出训练log,不解析,可取消注释 +_run # 该函数在run_model.sh中,执行时会调用_train; 如果不联调只想要产出训练log可以注掉本行,提交时需打开 diff --git a/benchmark/run_det.sh b/benchmark/run_det.sh new file mode 100644 index 0000000..75b0f17 --- /dev/null +++ b/benchmark/run_det.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# 提供可稳定复现性能的脚本,默认在标准docker环境内py37执行: paddlepaddle/paddle:latest-gpu-cuda10.1-cudnn7 paddle=2.1.2 py=37 +# 执行目录: ./PaddleOCR +# 1 安装该模型需要的依赖 (如需开启优化策略请注明) +log_path=${LOG_PATH_INDEX_DIR:-$(pwd)} +python -m pip install -r requirements.txt +# 2 拷贝该模型需要数据、预训练模型 +wget -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/icdar2015.tar && cd train_data && tar xf icdar2015.tar && cd ../ +wget -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet50_vd_pretrained.pdparams +wget -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet18_vd_pretrained.pdparams +wget -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet50_vd_ssld_pretrained.pdparams +# 3 批量运行(如不方便批量,1,2需放到单个模型中) + +model_mode_list=(det_res18_db_v2.0 det_r50_vd_east det_r50_vd_pse) +fp_item_list=(fp32) +for model_mode in ${model_mode_list[@]}; do + for fp_item in ${fp_item_list[@]}; do + if [ ${model_mode} == "det_r50_vd_east" ]; then + bs_list=(16) + else + bs_list=(8 16) + fi + for bs_item in ${bs_list[@]}; do + echo "index is speed, 1gpus, begin, ${model_name}" + run_mode=sp + log_name=ocr_${model_mode}_bs${bs_item}_${fp_item}_${run_mode} + CUDA_VISIBLE_DEVICES=0 bash benchmark/run_benchmark_det.sh ${run_mode} ${bs_item} ${fp_item} 1 ${model_mode} | tee ${log_path}/${log_name}_speed_1gpus 2>&1 # (5min) + sleep 60 + echo "index is speed, 8gpus, run_mode is multi_process, begin, ${model_name}" + run_mode=mp + log_name=ocr_${model_mode}_bs${bs_item}_${fp_item}_${run_mode} + CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 bash benchmark/run_benchmark_det.sh ${run_mode} ${bs_item} ${fp_item} 2 ${model_mode} | tee ${log_path}/${log_name}_speed_8gpus8p 2>&1 + sleep 60 + done + done +done diff --git a/configs/cls/ch_PP-OCRv3/ch_PP-OCRv3_rotnet.yml b/configs/cls/ch_PP-OCRv3/ch_PP-OCRv3_rotnet.yml new file mode 100644 index 0000000..f7e327d --- /dev/null +++ b/configs/cls/ch_PP-OCRv3/ch_PP-OCRv3_rotnet.yml @@ -0,0 +1,98 @@ +Global: + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v3_rotnet + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + save_res_path: ./output/rec/predicts_chinese_lite_v2.0.txt +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: cls + algorithm: CLS + Transform: null + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + Neck: + Head: + name: ClsHead + class_dim: 4 + +Loss: + name: ClsLoss + main_indicator: acc + +PostProcess: + name: ClsPostProcess + +Metric: + name: ClsMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - BaseDataAugmentation: + - RandAugment: + - SSLRotateResize: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: ["image", "label"] + loader: + collate_fn: "SSLRotateCollate" + shuffle: true + batch_size_per_card: 32 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - SSLRotateResize: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: ["image", "label"] + loader: + collate_fn: "SSLRotateCollate" + shuffle: false + drop_last: false + batch_size_per_card: 64 + num_workers: 8 +profiler_options: null diff --git a/configs/cls/cls_mv3.yml b/configs/cls/cls_mv3.yml new file mode 100644 index 0000000..0c46ff5 --- /dev/null +++ b/configs/cls/cls_mv3.yml @@ -0,0 +1,94 @@ +Global: + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/cls/mv3/ + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 1000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + label_list: ['0','180'] + +Architecture: + model_type: cls + algorithm: CLS + Transform: + Backbone: + name: MobileNetV3 + scale: 0.35 + model_name: small + Neck: + Head: + name: ClsHead + class_dim: 2 + +Loss: + name: ClsLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: ClsPostProcess + +Metric: + name: ClsMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/cls + label_file_list: + - ./train_data/cls/train.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ClsLabelEncode: # Class handling label + - BaseDataAugmentation: + - RandAugment: + - ClsResizeImg: + image_shape: [3, 48, 192] + - KeepKeys: + keep_keys: ['image', 'label'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 512 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/cls + label_file_list: + - ./train_data/cls/test.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ClsLabelEncode: # Class handling label + - ClsResizeImg: + image_shape: [3, 48, 192] + - KeepKeys: + keep_keys: ['image', 'label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 512 + num_workers: 4 diff --git a/configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml b/configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml new file mode 100644 index 0000000..252d159 --- /dev/null +++ b/configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml @@ -0,0 +1,226 @@ +Global: + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_PP-OCR_v3_det/ + save_epoch_step: 100 + eval_batch_step: + - 0 + - 400 + cal_metric_during_train: false + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + distributed: true + d2s_train_image_shape: [3, -1, -1] + amp_dtype: bfloat16 + +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Student: + pretrained: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: true + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + Student2: + pretrained: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: true + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + Teacher: + freeze_params: true + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: ResNet_vd + in_channels: 3 + layers: 50 + Neck: + name: LKPAN + out_channels: 256 + Head: + name: DBHead + kernel_list: [7,2,2] + k: 50 + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDilaDBLoss: + weight: 1.0 + model_name_pairs: + - ["Student", "Teacher"] + - ["Student2", "Teacher"] + key: maps + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + - DistillationDMLLoss: + model_name_pairs: + - ["Student", "Student2"] + maps_name: "thrink_maps" + weight: 1.0 + model_name_pairs: ["Student", "Student2"] + key: maps + - DistillationDBLoss: + weight: 1.0 + model_name_list: ["Student", "Student2"] + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 + +PostProcess: + name: DistillationDBPostProcess + model_name: ["Student"] + key: head_out + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 960 + - 960 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/PP-OCRv3/PP-OCRv3_det_dml.yml b/configs/det/PP-OCRv3/PP-OCRv3_det_dml.yml new file mode 100644 index 0000000..0cb7a3c --- /dev/null +++ b/configs/det/PP-OCRv3/PP-OCRv3_det_dml.yml @@ -0,0 +1,174 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + d2s_train_image_shape: [3, -1, -1] + +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Student: + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: ResNet_vd + in_channels: 3 + layers: 50 + Neck: + name: LKPAN + out_channels: 256 + Head: + name: DBHead + kernel_list: [7,2,2] + k: 50 + Student2: + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: ResNet_vd + in_channels: 3 + layers: 50 + Neck: + name: LKPAN + out_channels: 256 + Head: + name: DBHead + kernel_list: [7,2,2] + k: 50 + + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDMLLoss: + model_name_pairs: + - ["Student", "Student2"] + maps_name: "thrink_maps" + weight: 1.0 + # act: None + model_name_pairs: ["Student", "Student2"] + key: maps + - DistillationDBLoss: + weight: 1.0 + model_name_list: ["Student", "Student2"] + # key: maps + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DistillationDBPostProcess + model_name: ["Student", "Student2"] + key: head_out + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - CopyPaste: + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/PP-OCRv3/PP-OCRv3_mobile_det.yml b/configs/det/PP-OCRv3/PP-OCRv3_mobile_det.yml new file mode 100644 index 0000000..b7195ff --- /dev/null +++ b/configs/det/PP-OCRv3/PP-OCRv3_mobile_det.yml @@ -0,0 +1,165 @@ +Global: + model_name: PP-OCRv3_mobile_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv3_mobile_det/ + save_epoch_step: 100 + eval_batch_step: + - 0 + - 400 + cal_metric_during_train: false + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/MobileNetV3_large_x0_5_pretrained.pdparams + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + distributed: true + d2s_train_image_shape: [3, -1, -1] + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 +Metric: + name: DetMetric + main_indicator: hmean +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 960 + - 960 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: null + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 diff --git a/configs/det/PP-OCRv3/PP-OCRv3_server_det.yml b/configs/det/PP-OCRv3/PP-OCRv3_server_det.yml new file mode 100644 index 0000000..d052ed2 --- /dev/null +++ b/configs/det/PP-OCRv3/PP-OCRv3_server_det.yml @@ -0,0 +1,160 @@ +Global: + model_name: PP-OCRv3_server_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv3_server_det/ + save_epoch_step: 100 + eval_batch_step: + - 0 + - 400 + cal_metric_during_train: false + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/ResNet50_vd_ssld_pretrained.pdparams + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + distributed: true + d2s_train_image_shape: [3, -1, -1] + amp_dtype: bfloat16 + +Architecture: + model_type: det + algorithm: DB + Backbone: + name: ResNet_vd + in_channels: 3 + layers: 50 + Neck: + name: LKPAN + out_channels: 256 + Head: + name: DBHead + kernel_list: [7,2,2] + k: 50 + + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 960 + - 960 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv4/PP-OCRv4_det_cml.yml b/configs/det/PP-OCRv4/PP-OCRv4_det_cml.yml new file mode 100644 index 0000000..7e7ddcb --- /dev/null +++ b/configs/det/PP-OCRv4/PP-OCRv4_det_cml.yml @@ -0,0 +1,240 @@ +Global: + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/ch_PP-OCRv4 + save_epoch_step: 50 + eval_batch_step: + - 0 + - 1000 + cal_metric_during_train: false + checkpoints: null + pretrained_model: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + d2s_train_image_shape: [3, 640, 640] + distributed: true +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Student: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.75 + pretrained: false + det: true + Neck: + name: RSEFPN + out_channels: 96 + shortcut: true + Head: + name: DBHead + k: 50 + Student2: + pretrained: null + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.75 + pretrained: true + det: true + Neck: + name: RSEFPN + out_channels: 96 + shortcut: true + Head: + name: DBHead + k: 50 + Teacher: + pretrained: https://paddleocr.bj.bcebos.com/PP-OCRv4/chinese/ch_PP-OCRv4_det_cml_teacher_pretrained/teacher.pdparams + freeze_params: true + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: ResNet_vd + in_channels: 3 + layers: 50 + Neck: + name: LKPAN + out_channels: 256 + Head: + name: DBHead + kernel_list: + - 7 + - 2 + - 2 + k: 50 +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDilaDBLoss: + weight: 1.0 + model_name_pairs: + - - Student + - Teacher + - - Student2 + - Teacher + key: maps + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + - DistillationDMLLoss: + model_name_pairs: + - Student + - Student2 + maps_name: thrink_maps + weight: 1.0 + key: maps + - DistillationDBLoss: + weight: 1.0 + model_name_list: + - Student + - Student2 + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 +PostProcess: + name: DistillationDBPostProcess + model_name: + - Student + key: head_out + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: Student +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: 500 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: 500 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 16 + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + limit_side_len: 960 + limit_type: max + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv4/PP-OCRv4_mobile_det.yml b/configs/det/PP-OCRv4/PP-OCRv4_mobile_det.yml new file mode 100644 index 0000000..f243d17 --- /dev/null +++ b/configs/det/PP-OCRv4/PP-OCRv4_mobile_det.yml @@ -0,0 +1,174 @@ +Global: + model_name: PP-OCRv4_mobile_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/PP-OCRv4_mobile_det + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1500 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/PPLCNetV3_x0_75_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + d2s_train_image_shape: [3, 640, 640] + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.75 + det: True + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + fix_nan: True + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv4/PP-OCRv4_mobile_seal_det.yml b/configs/det/PP-OCRv4/PP-OCRv4_mobile_seal_det.yml new file mode 100644 index 0000000..79a45cd --- /dev/null +++ b/configs/det/PP-OCRv4/PP-OCRv4_mobile_seal_det.yml @@ -0,0 +1,171 @@ +Global: + model_name: PP-OCRv4_mobile_seal_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: output + save_epoch_step: 1 + eval_batch_step: + - 0 + - 100 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/PPLCNetV3_x0_75_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + distributed: true + d2s_train_image_shape: [3, 640, 640] + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.75 + det: True + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 1e-6 + +PostProcess: + name: DBPostProcess + thresh: 0.2 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 0.5 + box_type: "poly" + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: TextDetDataset + data_dir: datasets/ICDAR2015 + label_file_list: + - datasets/ICDAR2015/train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.8 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: 500 + - MakeShrinkMap: + shrink_ratio: 0.8 + min_text_size: 8 + total_epoch: 500 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 3 + +Eval: + dataset: + name: TextDetDataset + data_dir: datasets/ICDAR2015 + label_file_list: + - datasets/ICDAR2015/val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + resize_long: 736 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 0 +profiler_options: null diff --git a/configs/det/PP-OCRv4/PP-OCRv4_server_det.yml b/configs/det/PP-OCRv4/PP-OCRv4_server_det.yml new file mode 100644 index 0000000..bac6311 --- /dev/null +++ b/configs/det/PP-OCRv4/PP-OCRv4_server_det.yml @@ -0,0 +1,175 @@ +Global: + model_name: PP-OCRv4_server_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/PP-OCRv4_server_det + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1500 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/PPHGNet_small_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + d2s_train_image_shape: [3, 640, 640] + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPHGNet_small + det: True + Neck: + name: LKPAN + out_channels: 256 + intracl: true + Head: + name: PFHeadLocal + k: 50 + mode: "large" + fix_nan: True + + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 1e-6 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv4/PP-OCRv4_server_seal_det.yml b/configs/det/PP-OCRv4/PP-OCRv4_server_seal_det.yml new file mode 100644 index 0000000..ebf1341 --- /dev/null +++ b/configs/det/PP-OCRv4/PP-OCRv4_server_seal_det.yml @@ -0,0 +1,171 @@ +Global: + model_name: PP-OCRv4_server_seal_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: output + save_epoch_step: 1 + eval_batch_step: + - 0 + - 100 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/PPHGNet_small_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + distributed: true + d2s_train_image_shape: [3, 640, 640] + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPHGNet_small + det: True + Neck: + name: LKPAN + out_channels: 256 + intracl: true + Head: + name: PFHeadLocal + k: 50 + mode: "large" + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 1e-6 + +PostProcess: + name: DBPostProcess + thresh: 0.2 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 0.5 + box_type: "poly" + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: TextDetDataset + data_dir: datasets/ICDAR2015 + label_file_list: + - datasets/ICDAR2015/train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.8 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: 500 + - MakeShrinkMap: + shrink_ratio: 0.8 + min_text_size: 8 + total_epoch: 500 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 4 + num_workers: 3 + +Eval: + dataset: + name: TextDetDataset + data_dir: datasets/ICDAR2015 + label_file_list: + - datasets/ICDAR2015/val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + resize_long: 736 + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 0 +profiler_options: null diff --git a/configs/det/PP-OCRv5/PP-OCRv5_mobile_det.yml b/configs/det/PP-OCRv5/PP-OCRv5_mobile_det.yml new file mode 100644 index 0000000..55d4da6 --- /dev/null +++ b/configs/det/PP-OCRv5/PP-OCRv5_mobile_det.yml @@ -0,0 +1,174 @@ +Global: + model_name: PP-OCRv5_mobile_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/PP-OCRv5_mobile_det + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1500 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddleocr.bj.bcebos.com/pretrained/PPLCNetV3_x0_75_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + d2s_train_image_shape: [3, 640, 640] + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.75 + det: True + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + fix_nan: True + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv5/PP-OCRv5_server_det.yml b/configs/det/PP-OCRv5/PP-OCRv5_server_det.yml new file mode 100644 index 0000000..7c88fc8 --- /dev/null +++ b/configs/det/PP-OCRv5/PP-OCRv5_server_det.yml @@ -0,0 +1,173 @@ +Global: + model_name: PP-OCRv5_server_det # To use static model for inference. + debug: false + use_gpu: true + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv5_server_det + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1500 + cal_metric_during_train: false + checkpoints: + pretrained_model: https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/PPHGNetV2_B4_ocr_det.pdparams + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPHGNetV2_B4 + det: True + Neck: + name: LKPAN + out_channels: 256 + intracl: true + Head: + name: PFHeadLocal + k: 50 + mode: "large" + + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 1e-6 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv6/PP-OCRv6_medium_det.yml b/configs/det/PP-OCRv6/PP-OCRv6_medium_det.yml new file mode 100644 index 0000000..19b2a4e --- /dev/null +++ b/configs/det/PP-OCRv6/PP-OCRv6_medium_det.yml @@ -0,0 +1,195 @@ +Global: + model_name: PP-OCRv6_medium_det # To use static model for inference. + debug: false + use_gpu: true + use_ema: true + ema_decay: 0.9996 + ema_decay_type: threshold + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv6_medium_det + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1500 + cal_metric_during_train: false + checkpoints: null + pretrained_model: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + d2s_train_image_shape: [3, 640, 640] + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV4 + det: true + model_size: medium + Neck: + name: RepLKPAN + out_channels: 256 + intracl: true + Head: + name: DBHead + k: 50 + fix_nan: true + aux_in_channels: 256 + +Loss: + name: DBLoss + main_loss_type: DiceFocalLoss + alpha: 5 + beta: 10 + focal_alpha: 0.25 + focal_gamma: 2.5 + aux_weight_p4: 0.2 + aux_weight_p3: 0.3 + aux_weight_p2: 0.4 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 1.0e-05 + +PostProcess: + name: DBPostProcess + thresh: 0.2 + box_thresh: 0.45 + max_candidates: 3000 + unclip_ratio: 1.4 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - ColorJitter: + brightness: 0.4 + contrast: 0.4 + saturation: 0.7 + hue: 0.1 + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + p: 0.5 + rotate: + - -45 + - 45 + fit_output: true + - type: Resize + args: + size: + - 0.1 + - 2 + - RandomPerspective: + prob: 0.3 + scale: 0.0 + shear: 20.0 + perspective: 0.001 + fit_output: true + fill_value: [0, 0, 0] + - RandomCrop: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: null + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv6/PP-OCRv6_small_det.yml b/configs/det/PP-OCRv6/PP-OCRv6_small_det.yml new file mode 100644 index 0000000..12b1327 --- /dev/null +++ b/configs/det/PP-OCRv6/PP-OCRv6_small_det.yml @@ -0,0 +1,184 @@ +Global: + model_name: PP-OCRv6_small_det # To use static model for inference. + debug: false + use_gpu: true + use_ema: true + ema_decay: 0.9997 + ema_decay_type: threshold + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv6_small_det + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1500 + cal_metric_during_train: false + checkpoints: null + pretrained_model: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + d2s_train_image_shape: [3, 640, 640] + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV4 + det: true + model_size: small + Neck: + name: RepLKFPN + out_channels: 96 + dilated_kernel_size: 7 + shortcut: true + Head: + name: DBHead + k: 50 + fix_nan: true + aux_in_channels: 96 + +Loss: + name: DBLoss + main_loss_type: DiceFocalLoss + alpha: 5 + beta: 10 + focal_alpha: 0.25 + focal_gamma: 2.5 + aux_weight_p4: 0.2 + aux_weight_p3: 0.3 + aux_weight_p2: 0.4 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 1.0e-05 + +PostProcess: + name: DBPostProcess + thresh: 0.2 + box_thresh: 0.45 + max_candidates: 3000 + unclip_ratio: 1.4 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + p: 0.5 + rotate: + - -45 + - 45 + fit_output: true + - type: Resize + args: + size: + - 0.1 + - 2 + - RandomCrop: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: null + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/PP-OCRv6/PP-OCRv6_tiny_det.yml b/configs/det/PP-OCRv6/PP-OCRv6_tiny_det.yml new file mode 100644 index 0000000..d6deb55 --- /dev/null +++ b/configs/det/PP-OCRv6/PP-OCRv6_tiny_det.yml @@ -0,0 +1,189 @@ +Global: + model_name: PP-OCRv6_tiny_det # To use static model for inference. + debug: false + use_gpu: true + use_ema: true + ema_decay: 0.9998 + ema_decay_type: threshold + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv6_tiny_det + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1500 + cal_metric_during_train: false + checkpoints: null + pretrained_model: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + d2s_train_image_shape: [3, 640, 640] + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: PPLCNetV4 + det: true + model_size: tiny + Neck: + name: RepLKFPN + out_channels: 64 + dilated_kernel_size: 5 + shortcut: true + Head: + name: DBHead + k: 50 + fix_nan: true + aux_in_channels: 64 + +Loss: + name: DBLoss + main_loss_type: DiceFocalLoss + alpha: 5 + beta: 10 + focal_alpha: 0.25 + focal_gamma: 2.5 + aux_weight_p4: 0.2 + aux_weight_p3: 0.3 + aux_weight_p2: 0.4 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 1.0e-06 + +PostProcess: + name: DBPostProcess + thresh: 0.2 + box_thresh: 0.4 + max_candidates: 3000 + unclip_ratio: 1.4 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - ColorJitter: + brightness: 0.4 + contrast: 0.4 + saturation: 0.7 + hue: 0.1 + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + p: 0.5 + rotate: + - -45 + - 45 + fit_output: true + - type: Resize + args: + size: + - 0.3 + - 2 + - RandomCrop: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: null + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml new file mode 100644 index 0000000..d35e08a --- /dev/null +++ b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml @@ -0,0 +1,206 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + use_amp: False + amp_level: O2 + amp_dtype: bfloat16 + +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Teacher: + pretrained: ./pretrain_models/ch_ppocr_server_v2.0_det_train/best_accuracy + freeze_params: true + return_all_feats: false + model_type: det + algorithm: DB + Transform: + Backbone: + name: ResNet_vd + layers: 18 + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + Student: + pretrained: + freeze_params: false + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + Student2: + pretrained: + freeze_params: false + return_all_feats: false + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDilaDBLoss: + weight: 1.0 + model_name_pairs: + - ["Student", "Teacher"] + - ["Student2", "Teacher"] + key: maps + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + - DistillationDMLLoss: + model_name_pairs: + - ["Student", "Student2"] + maps_name: "thrink_maps" + weight: 1.0 + # act: None + model_name_pairs: ["Student", "Student2"] + key: maps + - DistillationDBLoss: + weight: 1.0 + model_name_list: ["Student", "Student2"] + # key: maps + # name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DistillationDBPostProcess + model_name: ["Student", "Student2", "Teacher"] + # key: maps + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - CopyPaste: + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_distill.yml b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_distill.yml new file mode 100644 index 0000000..d24ee11 --- /dev/null +++ b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_distill.yml @@ -0,0 +1,175 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Student: + pretrained: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + freeze_params: false + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + Teacher: + pretrained: ./pretrain_models/ch_ppocr_server_v2.0_det_train/best_accuracy + freeze_params: true + return_all_feats: false + model_type: det + algorithm: DB + Transform: + Backbone: + name: ResNet_vd + layers: 18 + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDilaDBLoss: + weight: 1.0 + model_name_pairs: + - ["Student", "Teacher"] + key: maps + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + - DistillationDBLoss: + weight: 1.0 + model_name_list: ["Student"] + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DistillationDBPostProcess + model_name: ["Student"] + key: head_out + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - CopyPaste: + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_dml.yml b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_dml.yml new file mode 100644 index 0000000..8b160f6 --- /dev/null +++ b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_dml.yml @@ -0,0 +1,178 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + name: DistillationModel + algorithm: Distillation + model_type: det + Models: + Student: + pretrained: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + freeze_params: false + return_all_feats: false + model_type: det + algorithm: DB + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + Teacher: + pretrained: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + freeze_params: false + return_all_feats: false + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDMLLoss: + model_name_pairs: + - ["Student", "Teacher"] + maps_name: "thrink_maps" + weight: 1.0 + # act: None + model_name_pairs: ["Student", "Teacher"] + key: maps + - DistillationDBLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + # key: maps + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DistillationDBPostProcess + model_name: ["Student", "Teacher"] + key: head_out + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DistillationMetric + base_metric_name: DetMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - CopyPaste: + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml new file mode 100644 index 0000000..cca2a59 --- /dev/null +++ b/configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_student.yml @@ -0,0 +1,132 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 400] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/student.pdparams + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml b/configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml new file mode 100644 index 0000000..87718ca --- /dev/null +++ b/configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml @@ -0,0 +1,132 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + disable_se: True + Neck: + name: DBFPN + out_channels: 96 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/ch_ppocr_v2.0/ch_det_res18_db_v2.0.yml b/configs/det/ch_ppocr_v2.0/ch_det_res18_db_v2.0.yml new file mode 100644 index 0000000..e983c22 --- /dev/null +++ b/configs/det/ch_ppocr_v2.0/ch_det_res18_db_v2.0.yml @@ -0,0 +1,131 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_res18/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet18_vd_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: ResNet_vd + layers: 18 + disable_se: True + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_mv3_db.yml b/configs/det/det_mv3_db.yml new file mode 100644 index 0000000..8f5685e --- /dev/null +++ b/configs/det/det_mv3_db.yml @@ -0,0 +1,133 @@ +Global: + use_gpu: true + use_xpu: false + use_mlu: false + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [640, 640] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 16 + num_workers: 8 + use_shared_memory: True + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 8 + use_shared_memory: True diff --git a/configs/det/det_mv3_east.yml b/configs/det/det_mv3_east.yml new file mode 100644 index 0000000..461179e --- /dev/null +++ b/configs/det/det_mv3_east.yml @@ -0,0 +1,109 @@ +Global: + use_gpu: true + epoch_num: 10000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/east_mv3/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/det_east/predicts_east.txt + +Architecture: + model_type: det + algorithm: EAST + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: EASTFPN + model_name: small + Head: + name: EASTHead + model_name: small + +Loss: + name: EASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: EASTPostProcess + score_thresh: 0.8 + cover_thresh: 0.1 + nms_thresh: 0.2 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - EASTProcessTrain: + image_shape: [512, 512] + background_ratio: 0.125 + min_crop_side_ratio: 0.1 + min_text_size: 10 + - KeepKeys: + keep_keys: ['image', 'score_map', 'geo_map', 'training_mask'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 16 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 2400 + limit_type: max + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_mv3_pse.yml b/configs/det/det_mv3_pse.yml new file mode 100644 index 0000000..4b8c4be --- /dev/null +++ b/configs/det/det_mv3_pse.yml @@ -0,0 +1,135 @@ +Global: + use_gpu: true + epoch_num: 600 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_mv3_pse/ + save_epoch_step: 600 + # evaluation is run every 63 iterations + eval_batch_step: [ 0,63 ] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: #./output/det_r50_vd_pse_batch8_ColorJitter/best_accuracy + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_pse/predicts_pse.txt + +Architecture: + model_type: det + algorithm: PSE + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: FPN + out_channels: 96 + Head: + name: PSEHead + hidden_dim: 96 + out_channels: 7 + +Loss: + name: PSELoss + alpha: 0.7 + ohem_ratio: 3 + kernel_sample_mask: pred + reduction: none + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Step + learning_rate: 0.001 + step_size: 200 + gamma: 0.1 + regularizer: + name: 'L2' + factor: 0.0005 + +PostProcess: + name: PSEPostProcess + thresh: 0 + box_thresh: 0.85 + min_area: 16 + box_type: quad # 'quad' or 'poly' + scale: 1 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - ColorJitter: + brightness: 0.12549019607843137 + saturation: 0.5 + - IaaAugment: + augmenter_args: + - { 'type': Resize, 'args': { 'size': [ 0.5, 3 ] } } + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [ -10, 10 ] } } + - MakePseGt: + kernel_num: 7 + min_shrink_ratio: 0.4 + size: 640 + - RandomCropImgMask: + size: [ 640,640 ] + main_key: gt_text + crop_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 16 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 736 + limit_type: min + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'shape', 'polys', 'ignore_tags' ] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 8 diff --git a/configs/det/det_r18_vd_ct.yml b/configs/det/det_r18_vd_ct.yml new file mode 100644 index 0000000..42922df --- /dev/null +++ b/configs/det/det_r18_vd_ct.yml @@ -0,0 +1,107 @@ +Global: + use_gpu: true + epoch_num: 600 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_ct/ + save_epoch_step: 10 + # evaluation is run every 2000 iterations + eval_batch_step: [0,1000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet18_vd_pretrained.pdparams + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img623.jpg + save_res_path: ./output/det_ct/predicts_ct.txt + +Architecture: + model_type: det + algorithm: CT + Transform: + Backbone: + name: ResNet_vd + layers: 18 + Neck: + name: CTFPN + Head: + name: CT_Head + in_channels: 512 + hidden_dim: 128 + num_classes: 3 + +Loss: + name: CTLoss + +Optimizer: + name: Adam + lr: #PolynomialDecay + name: Linear + learning_rate: 0.001 + end_lr: 0. + epochs: 600 + step_each_epoch: 1254 + power: 0.9 + +PostProcess: + name: CTPostProcess + box_type: poly + +Metric: + name: CTMetric + main_indicator: f_score + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/total_text/train + label_file_list: + - ./train_data/total_text/train/train.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: RGB + channel_first: False + - CTLabelEncode: # Class handling label + - RandomScale: + - MakeShrink: + - GroupRandomHorizontalFlip: + - GroupRandomRotate: + - GroupRandomCropPadding: + - MakeCentripetalShift: + - ColorJitter: + brightness: 0.125 + saturation: 0.5 + - ToCHWImage: + - NormalizeImage: + - KeepKeys: + keep_keys: ['image', 'gt_kernel', 'training_mask', 'gt_instance', 'gt_kernel_instance', 'training_mask_distance', 'gt_distance'] # the order of the dataloader list + loader: + shuffle: True + drop_last: True + batch_size_per_card: 4 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/total_text/test + label_file_list: + - ./train_data/total_text/test/test.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: RGB + channel_first: False + - CTLabelEncode: # Class handling label + - ScaleAlignedShort: + - NormalizeImage: + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'texts'] # the order of the dataloader list + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 + num_workers: 2 diff --git a/configs/det/det_r50_db++_icdar15.yml b/configs/det/det_r50_db++_icdar15.yml new file mode 100644 index 0000000..2bb2cb8 --- /dev/null +++ b/configs/det/det_r50_db++_icdar15.yml @@ -0,0 +1,164 @@ +Global: + debug: false + use_gpu: true + epoch_num: 1000 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_r50_icdar15/ + save_epoch_step: 200 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: false + pretrained_model: ./pretrain_models/ResNet50_dcn_asf_synthtext_pretrained + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt +Architecture: + model_type: det + algorithm: DB++ + Transform: null + Backbone: + name: ResNet + layers: 50 + dcn_stage: [False, True, True, True] + Neck: + name: DBFPN + out_channels: 256 + use_asf: True + Head: + name: DBHead + k: 50 +Loss: + name: DBLoss + balance_loss: true + main_loss_type: BCELoss + alpha: 5 + beta: 10 + ohem_ratio: 3 +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: DecayLearningRate + learning_rate: 0.007 + epochs: 1000 + factor: 0.9 + end_lr: 0 + weight_decay: 0.0001 +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + det_box_type: 'quad' # 'quad' or 'poly' +Metric: + name: DetMetric + main_indicator: hmean +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: + - 1.0 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 10 + keep_ratio: true + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - NormalizeImage: + scale: 1./255. + mean: + - 0.48109378172549 + - 0.45752457890196 + - 0.40787054090196 + std: + - 1.0 + - 1.0 + - 1.0 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 4 + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + image_shape: + - 1152 + - 2048 + - NormalizeImage: + scale: 1./255. + mean: + - 0.48109378172549 + - 0.45752457890196 + - 0.40787054090196 + std: + - 1.0 + - 1.0 + - 1.0 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/det_r50_db++_td_tr.yml b/configs/det/det_r50_db++_td_tr.yml new file mode 100644 index 0000000..f3b02aa --- /dev/null +++ b/configs/det/det_r50_db++_td_tr.yml @@ -0,0 +1,167 @@ +Global: + debug: false + use_gpu: true + epoch_num: 1000 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_r50_td_tr/ + save_epoch_step: 200 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: false + pretrained_model: ./pretrain_models/ResNet50_dcn_asf_synthtext_pretrained + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt +Architecture: + model_type: det + algorithm: DB++ + Transform: null + Backbone: + name: ResNet + layers: 50 + dcn_stage: [False, True, True, True] + Neck: + name: DBFPN + out_channels: 256 + use_asf: True + Head: + name: DBHead + k: 50 +Loss: + name: DBLoss + balance_loss: true + main_loss_type: BCELoss + alpha: 5 + beta: 10 + ohem_ratio: 3 +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: DecayLearningRate + learning_rate: 0.007 + epochs: 1000 + factor: 0.9 + end_lr: 0 + weight_decay: 0.0001 +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.5 + max_candidates: 1000 + unclip_ratio: 1.5 + det_box_type: 'quad' # 'quad' or 'poly' +Metric: + name: DetMetric + main_indicator: hmean +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/TD_TR/TD500/train_gt_labels.txt + - ./train_data/TD_TR/TR400/gt_labels.txt + ratio_list: + - 1.0 + - 1.0 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 10 + keep_ratio: true + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - NormalizeImage: + scale: 1./255. + mean: + - 0.48109378172549 + - 0.45752457890196 + - 0.40787054090196 + std: + - 1.0 + - 1.0 + - 1.0 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 4 + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/TD_TR/TD500/test_gt_labels.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + image_shape: + - 736 + - 736 + keep_ratio: True + - NormalizeImage: + scale: 1./255. + mean: + - 0.48109378172549 + - 0.45752457890196 + - 0.40787054090196 + std: + - 1.0 + - 1.0 + - 1.0 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/det_r50_drrg_ctw.yml b/configs/det/det_r50_drrg_ctw.yml new file mode 100755 index 0000000..f56ac39 --- /dev/null +++ b/configs/det/det_r50_drrg_ctw.yml @@ -0,0 +1,133 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/det_r50_drrg_ctw/ + save_epoch_step: 100 + # evaluation is run every 1260 iterations + eval_batch_step: [37800, 1260] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained.pdparams + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_drrg/predicts_drrg.txt + + +Architecture: + model_type: det + algorithm: DRRG + Transform: + Backbone: + name: ResNet_vd + layers: 50 + Neck: + name: FPN_UNet + in_channels: [256, 512, 1024, 2048] + out_channels: 32 + Head: + name: DRRGHead + in_channels: 32 + text_region_thr: 0.3 + center_region_thr: 0.4 +Loss: + name: DRRGLoss + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: DecayLearningRate + learning_rate: 0.028 + epochs: 1200 + factor: 0.9 + end_lr: 0.0000001 + weight_decay: 0.0001 + +PostProcess: + name: DRRGPostprocess + link_thr: 0.8 + +Metric: + name: DetFCEMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ctw1500/imgs/ + label_file_list: + - ./train_data/ctw1500/imgs/training.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + ignore_orientation: True + - DetLabelEncode: # Class handling label + - ColorJitter: + brightness: 0.12549019607843137 + saturation: 0.5 + - RandomScaling: + - RandomCropFlip: + crop_ratio: 0.5 + - RandomCropPolyInstances: + crop_ratio: 0.8 + min_side_ratio: 0.3 + - RandomRotatePolyInstances: + rotate_ratio: 0.5 + max_angle: 60 + pad_with_fixed_color: False + - SquareResizePad: + target_size: 800 + pad_ratio: 0.6 + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - DRRGTargets: + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'gt_text_mask', 'gt_center_region_mask', 'gt_mask', + 'gt_top_height_map', 'gt_bot_height_map', 'gt_sin_map', + 'gt_cos_map', 'gt_comp_attribs'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ctw1500/imgs/ + label_file_list: + - ./train_data/ctw1500/imgs/test.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + ignore_orientation: True + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_type: 'min' + limit_side_len: 640 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - Pad: + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_r50_vd_db.yml b/configs/det/det_r50_vd_db.yml new file mode 100644 index 0000000..288dcc8 --- /dev/null +++ b/configs/det/det_r50_vd_db.yml @@ -0,0 +1,128 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_r50_vd/ + save_epoch_step: 1200 + # evaluation is run every 2000 iterations + eval_batch_step: [0,2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: ResNet_vd + layers: 50 + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.7 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [640, 640] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 16 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 8 diff --git a/configs/det/det_r50_vd_dcn_fce_ctw.yml b/configs/det/det_r50_vd_dcn_fce_ctw.yml new file mode 100755 index 0000000..5e851d1 --- /dev/null +++ b/configs/det/det_r50_vd_dcn_fce_ctw.yml @@ -0,0 +1,139 @@ +Global: + use_gpu: true + epoch_num: 1500 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/det_r50_dcn_fce_ctw/ + save_epoch_step: 100 + # evaluation is run every 835 iterations + eval_batch_step: [0, 835] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_fce/predicts_fce.txt + + +Architecture: + model_type: det + algorithm: FCE + Transform: + Backbone: + name: ResNet_vd + layers: 50 + dcn_stage: [False, True, True, True] + out_indices: [1,2,3] + Neck: + name: FCEFPN + out_channels: 256 + has_extra_convs: False + extra_stage: 0 + Head: + name: FCEHead + fourier_degree: 5 +Loss: + name: FCELoss + fourier_degree: 5 + num_sample: 50 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0001 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: FCEPostProcess + scales: [8, 16, 32] + alpha: 1.0 + beta: 1.0 + fourier_degree: 5 + box_type: 'poly' + +Metric: + name: DetFCEMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ctw1500/imgs/ + label_file_list: + - ./train_data/ctw1500/imgs/training.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + ignore_orientation: True + - DetLabelEncode: # Class handling label + - ColorJitter: + brightness: 0.142 + saturation: 0.5 + contrast: 0.5 + - RandomScaling: + - RandomCropFlip: + crop_ratio: 0.5 + - RandomCropPolyInstances: + crop_ratio: 0.8 + min_side_ratio: 0.3 + - RandomRotatePolyInstances: + rotate_ratio: 0.5 + max_angle: 30 + pad_with_fixed_color: False + - SquareResizePad: + target_size: 800 + pad_ratio: 0.6 + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - FCENetTargets: + fourier_degree: 5 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'p3_maps', 'p4_maps', 'p5_maps'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 6 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ctw1500/imgs/ + label_file_list: + - ./train_data/ctw1500/imgs/test.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + ignore_orientation: True + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_type: 'min' + limit_side_len: 736 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - Pad: + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_r50_vd_east.yml b/configs/det/det_r50_vd_east.yml new file mode 100644 index 0000000..5a488dd --- /dev/null +++ b/configs/det/det_r50_vd_east.yml @@ -0,0 +1,108 @@ +Global: + use_gpu: true + epoch_num: 10000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/east_r50_vd/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/det_east/predicts_east.txt + +Architecture: + model_type: det + algorithm: EAST + Transform: + Backbone: + name: ResNet_vd + layers: 50 + Neck: + name: EASTFPN + model_name: large + Head: + name: EASTHead + model_name: large + +Loss: + name: EASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: EASTPostProcess + score_thresh: 0.8 + cover_thresh: 0.1 + nms_thresh: 0.2 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - EASTProcessTrain: + image_shape: [512, 512] + background_ratio: 0.125 + min_crop_side_ratio: 0.1 + min_text_size: 10 + - KeepKeys: + keep_keys: ['image', 'score_map', 'geo_map', 'training_mask'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 2400 + limit_type: max + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_r50_vd_pse.yml b/configs/det/det_r50_vd_pse.yml new file mode 100644 index 0000000..408e16d --- /dev/null +++ b/configs/det/det_r50_vd_pse.yml @@ -0,0 +1,134 @@ +Global: + use_gpu: true + epoch_num: 600 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_r50_vd_pse/ + save_epoch_step: 600 + # evaluation is run every 125 iterations + eval_batch_step: [ 0,125 ] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: #./output/det_r50_vd_pse_batch8_ColorJitter/best_accuracy + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_pse/predicts_pse.txt + +Architecture: + model_type: det + algorithm: PSE + Transform: + Backbone: + name: ResNet_vd + layers: 50 + Neck: + name: FPN + out_channels: 256 + Head: + name: PSEHead + hidden_dim: 256 + out_channels: 7 + +Loss: + name: PSELoss + alpha: 0.7 + ohem_ratio: 3 + kernel_sample_mask: pred + reduction: none + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Step + learning_rate: 0.0001 + step_size: 200 + gamma: 0.1 + regularizer: + name: 'L2' + factor: 0.0005 + +PostProcess: + name: PSEPostProcess + thresh: 0 + box_thresh: 0.85 + min_area: 16 + box_type: quad # 'quad' or 'poly' + scale: 1 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - ColorJitter: + brightness: 0.12549019607843137 + saturation: 0.5 + - IaaAugment: + augmenter_args: + - { 'type': Resize, 'args': { 'size': [ 0.5, 3 ] } } + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [ -10, 10 ] } } + - MakePseGt: + kernel_num: 7 + min_shrink_ratio: 0.4 + size: 640 + - RandomCropImgMask: + size: [ 640,640 ] + main_key: gt_text + crop_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 736 + limit_type: min + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'shape', 'polys', 'ignore_tags' ] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 8 diff --git a/configs/det/det_r50_vd_sast_icdar15.yml b/configs/det/det_r50_vd_sast_icdar15.yml new file mode 100755 index 0000000..e1bf6fa --- /dev/null +++ b/configs/det/det_r50_vd_sast_icdar15.yml @@ -0,0 +1,109 @@ +Global: + use_gpu: true + epoch_num: 5000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/sast_r50_vd_ic15/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/sast_r50_vd_ic15/predicts_sast.txt + + +Architecture: + model_type: det + algorithm: SAST + Transform: + Backbone: + name: ResNet_SAST + layers: 50 + Neck: + name: SASTFPN + with_cab: True + Head: + name: SASTHead + +Loss: + name: SASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: SASTPostProcess + score_thresh: 0.5 + sample_pts_num: 2 + nms_thresh: 0.2 + expand_scale: 1.0 + shrink_ratio_of_width: 0.3 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: [./train_data/icdar2013/train_label_json.txt, ./train_data/icdar2015/train_label_json.txt, ./train_data/icdar17_mlt_latin/train_label_json.txt, ./train_data/coco_text_icdar_4pts/train_label_json.txt] + ratio_list: [0.1, 0.45, 0.3, 0.15] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - SASTProcessTrain: + image_shape: [512, 512] + min_crop_side_ratio: 0.3 + min_crop_size: 24 + min_text_size: 4 + max_text_size: 512 + - KeepKeys: + keep_keys: ['image', 'score_map', 'border_map', 'training_mask', 'tvo_map', 'tco_map'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + resize_long: 1536 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_r50_vd_sast_totaltext.yml b/configs/det/det_r50_vd_sast_totaltext.yml new file mode 100755 index 0000000..557ff8b --- /dev/null +++ b/configs/det/det_r50_vd_sast_totaltext.yml @@ -0,0 +1,108 @@ +Global: + use_gpu: true + epoch_num: 5000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/sast_r50_vd_tt/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/sast_r50_vd_tt/predicts_sast.txt + +Architecture: + model_type: det + algorithm: SAST + Transform: + Backbone: + name: ResNet_SAST + layers: 50 + Neck: + name: SASTFPN + with_cab: True + Head: + name: SASTHead + +Loss: + name: SASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: SASTPostProcess + score_thresh: 0.5 + sample_pts_num: 6 + nms_thresh: 0.2 + expand_scale: 1.2 + shrink_ratio_of_width: 0.2 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: [./train_data/art_latin_icdar_14pt/train_no_tt_test/train_label_json.txt, ./train_data/total_text_icdar_14pt/train_label_json.txt] + ratio_list: [0.5, 0.5] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - SASTProcessTrain: + image_shape: [512, 512] + min_crop_side_ratio: 0.3 + min_crop_size: 24 + min_text_size: 4 + max_text_size: 512 + - KeepKeys: + keep_keys: ['image', 'score_map', 'border_map', 'training_mask', 'tvo_map', 'tco_map'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/total_text_icdar_14pt/test_label_json.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + resize_long: 768 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/det/det_repsvtr_db.yml b/configs/det/det_repsvtr_db.yml new file mode 100644 index 0000000..8c4768e --- /dev/null +++ b/configs/det/det_repsvtr_db.yml @@ -0,0 +1,169 @@ +Global: + debug: false + use_gpu: true + epoch_num: &epoch_num 500 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/det_repsvtr_db + save_epoch_step: 10 + eval_batch_step: + - 0 + - 1000 + cal_metric_during_train: false + checkpoints: + pretrained_model: + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./checkpoints/det_db/predicts_db.txt + distributed: true + +Architecture: + model_type: det + algorithm: DB + Transform: null + Backbone: + name: RepSVTR_det + Neck: + name: RSEFPN + out_channels: 96 + shortcut: True + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 #(8*8c) + warmup_epoch: 2 + regularizer: + name: L2 + factor: 5.0e-05 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - CopyPaste: null + - IaaAugment: + augmenter_args: + - type: Fliplr + args: + p: 0.5 + - type: Affine + args: + rotate: + - -10 + - 10 + - type: Resize + args: + size: + - 0.5 + - 3 + - EastRandomCropData: + size: + - 640 + - 640 + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + total_epoch: *epoch_num + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + total_epoch: *epoch_num + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - threshold_map + - threshold_mask + - shrink_map + - shrink_mask + loader: + shuffle: true + drop_last: false + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - DetLabelEncode: null + - DetResizeForTest: + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - shape + - polys + - ignore_tags + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 2 +profiler_options: null diff --git a/configs/det/det_res18_db_v2.0.yml b/configs/det/det_res18_db_v2.0.yml new file mode 100644 index 0000000..e983c22 --- /dev/null +++ b/configs/det/det_res18_db_v2.0.yml @@ -0,0 +1,131 @@ +Global: + use_gpu: true + epoch_num: 1200 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/ch_db_res18/ + save_epoch_step: 1200 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [3000, 2000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet18_vd_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: ResNet_vd + layers: 18 + disable_se: True + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 + beta: 10 + ohem_ratio: 3 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [-10, 10] } } + - { 'type': Resize, 'args': { 'size': [0.5, 3] } } + - EastRandomCropData: + size: [960, 960] + max_tries: 50 + keep_ratio: true + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: +# image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/e2e/e2e_r50_vd_pg.yml b/configs/e2e/e2e_r50_vd_pg.yml new file mode 100644 index 0000000..4642f54 --- /dev/null +++ b/configs/e2e/e2e_r50_vd_pg.yml @@ -0,0 +1,121 @@ +Global: + use_gpu: True + epoch_num: 600 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/pgnet_r50_vd_totaltext/ + save_epoch_step: 10 + # evaluation is run every 0 iterationss after the 1000th iteration + eval_batch_step: [ 0, 1000 ] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + infer_visual_type: EN # two mode: EN is for english datasets, CN is for chinese datasets + valid_set: totaltext # two mode: totaltext valid curved words, partvgg valid non-curved words + save_res_path: ./output/pgnet_r50_vd_totaltext/predicts_pgnet.txt + character_dict_path: ppocr/utils/ic15_dict.txt + character_type: EN + max_text_length: 50 # the max length in seq + max_text_nums: 30 # the max seq nums in a pic + tcl_len: 64 + +Architecture: + model_type: e2e + algorithm: PGNet + Transform: + Backbone: + name: ResNet + layers: 50 + Neck: + name: PGFPN + Head: + name: PGHead + character_dict_path: ppocr/utils/ic15_dict.txt # the same as Global:character_dict_path + +Loss: + name: PGLoss + tcl_bs: 64 + max_text_length: 50 # the same as Global: max_text_length + max_text_nums: 30 # the same as Global:max_text_nums + pad_num: 36 # the length of dict for pad + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 50 + regularizer: + name: 'L2' + factor: 0.0001 + +PostProcess: + name: PGPostProcess + score_thresh: 0.5 + mode: fast # fast or slow two ways + point_gather_mode: align # same as PGProcessTrain: point_gather_mode + +Metric: + name: E2EMetric + mode: A # two ways for eval, A: label from txt, B: label from gt_mat + gt_mat_dir: ./train_data/total_text/gt # the dir of gt_mat + character_dict_path: ppocr/utils/ic15_dict.txt + main_indicator: f_score_e2e + +Train: + dataset: + name: PGDataSet + data_dir: ./train_data/total_text/train + label_file_list: [./train_data/total_text/train/train.txt] + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - E2ELabelEncodeTrain: + - PGProcessTrain: + batch_size: 14 # same as loader: batch_size_per_card + use_resize: True + use_random_crop: False + min_crop_size: 24 + min_text_size: 4 + max_text_size: 512 + point_gather_mode: align # two mode: align and none, align mode is better than none mode + - KeepKeys: + keep_keys: [ 'images', 'tcl_maps', 'tcl_label_maps', 'border_maps','direction_maps', 'training_masks', 'label_list', 'pos_list', 'pos_mask' ] # dataloader will return list in this order + loader: + shuffle: True + drop_last: True + batch_size_per_card: 14 + num_workers: 16 + +Eval: + dataset: + name: PGDataSet + data_dir: ./train_data/total_text/test + label_file_list: [./train_data/total_text/test/test.txt] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - E2ELabelEncodeTest: + - E2EResizeForTest: + max_side_len: 768 + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'shape', 'polys', 'texts', 'ignore_tags', 'img_id'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/configs/kie/layoutlm_series/re_layoutlmv2_xfund_zh.yml b/configs/kie/layoutlm_series/re_layoutlmv2_xfund_zh.yml new file mode 100644 index 0000000..3e3578d --- /dev/null +++ b/configs/kie/layoutlm_series/re_layoutlmv2_xfund_zh.yml @@ -0,0 +1,123 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/re_layoutlmv2_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_21.jpg + save_res_path: ./output/re_layoutlmv2_xfund_zh/res/ + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutLMv2" + Transform: + Backbone: + name: LayoutLMv2ForRe + pretrained: True + checkpoints: + +Loss: + name: LossFromOutput + key: loss + reduction: mean + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 10 + lr: + learning_rate: 0.00005 + warmup_epoch: 10 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQAReTokenLayoutLMPostProcess + +Metric: + name: VQAReTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: &class_path train_data/XFUND/class_list_xfun.txt + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids','image', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 8 + collate_fn: ListCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image','entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 8 + collate_fn: ListCollator diff --git a/configs/kie/layoutlm_series/re_layoutxlm_xfund_zh.yml b/configs/kie/layoutlm_series/re_layoutxlm_xfund_zh.yml new file mode 100644 index 0000000..99dc771 --- /dev/null +++ b/configs/kie/layoutlm_series/re_layoutxlm_xfund_zh.yml @@ -0,0 +1,123 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 130 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/re_layoutxlm_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_21.jpg + save_res_path: ./output/re_layoutxlm_xfund_zh/res/ + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForRe + pretrained: True + checkpoints: + +Loss: + name: LossFromOutput + key: loss + reduction: mean + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 10 + lr: + learning_rate: 0.00005 + warmup_epoch: 10 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQAReTokenLayoutLMPostProcess + +Metric: + name: VQAReTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: &class_path train_data/XFUND/class_list_xfun.txt + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox','attention_mask', 'token_type_ids', 'image', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 2 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 8 diff --git a/configs/kie/layoutlm_series/ser_layoutlm_xfund_zh.yml b/configs/kie/layoutlm_series/ser_layoutlm_xfund_zh.yml new file mode 100644 index 0000000..5424d7e --- /dev/null +++ b/configs/kie/layoutlm_series/ser_layoutlm_xfund_zh.yml @@ -0,0 +1,121 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/ser_layoutlm_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_42.jpg + save_res_path: ./output/re_layoutlm_xfund_zh/res + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutLM" + Transform: + Backbone: + name: LayoutLMForSer + pretrained: True + checkpoints: + num_classes: &num_classes 7 + +Loss: + name: VQASerTokenLayoutLMLoss + num_classes: *num_classes + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Linear + learning_rate: 0.00005 + epochs: *epoch_num + warmup_epoch: 2 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQASerTokenLayoutLMPostProcess + class_path: &class_path train_data/XFUND/class_list_xfun.txt + +Metric: + name: VQASerTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 16 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 4 diff --git a/configs/kie/layoutlm_series/ser_layoutlmv2_xfund_zh.yml b/configs/kie/layoutlm_series/ser_layoutlmv2_xfund_zh.yml new file mode 100644 index 0000000..c5e8335 --- /dev/null +++ b/configs/kie/layoutlm_series/ser_layoutlmv2_xfund_zh.yml @@ -0,0 +1,122 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/ser_layoutlmv2_xfund_zh/ + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_42.jpg + save_res_path: ./output/ser_layoutlmv2_xfund_zh/res/ + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutLMv2" + Transform: + Backbone: + name: LayoutLMv2ForSer + pretrained: True + checkpoints: + num_classes: &num_classes 7 + +Loss: + name: VQASerTokenLayoutLMLoss + num_classes: *num_classes + key: "backbone_out" + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Linear + learning_rate: 0.00005 + epochs: *epoch_num + warmup_epoch: 2 + regularizer: + + name: L2 + factor: 0.00000 + +PostProcess: + name: VQASerTokenLayoutLMPostProcess + class_path: &class_path train_data/XFUND/class_list_xfun.txt + +Metric: + name: VQASerTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 4 diff --git a/configs/kie/layoutlm_series/ser_layoutxlm_xfund_zh.yml b/configs/kie/layoutlm_series/ser_layoutxlm_xfund_zh.yml new file mode 100644 index 0000000..abcfec2 --- /dev/null +++ b/configs/kie/layoutlm_series/ser_layoutxlm_xfund_zh.yml @@ -0,0 +1,122 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/ser_layoutxlm_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_42.jpg + save_res_path: ./output/ser_layoutxlm_xfund_zh/res + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForSer + pretrained: True + checkpoints: + num_classes: &num_classes 7 + +Loss: + name: VQASerTokenLayoutLMLoss + num_classes: *num_classes + key: "backbone_out" + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Linear + learning_rate: 0.00005 + epochs: *epoch_num + warmup_epoch: 2 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQASerTokenLayoutLMPostProcess + class_path: &class_path train_data/XFUND/class_list_xfun.txt + +Metric: + name: VQASerTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 4 diff --git a/configs/kie/sdmgr/kie_unet_sdmgr.yml b/configs/kie/sdmgr/kie_unet_sdmgr.yml new file mode 100644 index 0000000..da2e4fd --- /dev/null +++ b/configs/kie/sdmgr/kie_unet_sdmgr.yml @@ -0,0 +1,111 @@ +Global: + use_gpu: True + epoch_num: 60 + log_smooth_window: 20 + print_batch_step: 50 + save_model_dir: ./output/kie_5/ + save_epoch_step: 50 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [ 0, 80 ] + # 1. If pretrained_model is saved in static mode, such as classification pretrained model + # from static branch, load_static_weights must be set as True. + # 2. If you want to finetune the pretrained models we provide in the docs, + # you should set load_static_weights as False. + load_static_weights: False + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + class_path: &class_path ./train_data/wildreceipt/class_list.txt + infer_img: ./train_data/wildreceipt/1.txt + save_res_path: ./output/sdmgr_kie/predicts_kie.txt + img_scale: [ 1024, 512 ] + +Architecture: + model_type: kie + algorithm: SDMGR + Transform: + Backbone: + name: Kie_backbone + Head: + name: SDMGRHead + +Loss: + name: SDMGRLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + learning_rate: 0.001 + decay_epochs: [ 60, 80, 100] + values: [ 0.001, 0.0001, 0.00001] + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0.00005 + +PostProcess: + name: None + +Metric: + name: KIEMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/wildreceipt/ + label_file_list: [ './train_data/wildreceipt/wildreceipt_train.txt' ] + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - KieLabelEncode: # Class handling label + character_dict_path: ./train_data/wildreceipt/dict.txt + class_path: *class_path + - KieResize: + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'relations', 'texts', 'points', 'labels', 'tag', 'shape'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/wildreceipt + label_file_list: + - ./train_data/wildreceipt/wildreceipt_test.txt + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - KieLabelEncode: # Class handling label + character_dict_path: ./train_data/wildreceipt/dict.txt + - KieResize: + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'relations', 'texts', 'points', 'labels', 'tag', 'ori_image', 'ori_boxes', 'shape'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 4 diff --git a/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml b/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml new file mode 100644 index 0000000..e65af0a --- /dev/null +++ b/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml @@ -0,0 +1,130 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 130 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/re_vi_layoutxlm_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_21.jpg + save_res_path: ./output/re/xfund_zh/with_gt + kie_rec_model_dir: + kie_det_model_dir: + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForRe + pretrained: True + mode: vi + checkpoints: + +Loss: + name: LossFromOutput + key: loss + reduction: mean + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 10 + lr: + learning_rate: 0.00005 + warmup_epoch: 10 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQAReTokenLayoutLMPostProcess + +Metric: + name: VQAReTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: &class_path train_data/XFUND/class_list_xfun.txt + use_textline_bbox_info: &use_textline_bbox_info True + order_method: &order_method "tb-yx" + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox','attention_mask', 'token_type_ids', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 2 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: *class_path + use_textline_bbox_info: *use_textline_bbox_info + order_method: *order_method + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 8 diff --git a/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh_udml.yml b/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh_udml.yml new file mode 100644 index 0000000..eda3a2b --- /dev/null +++ b/configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh_udml.yml @@ -0,0 +1,175 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 130 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/re_vi_layoutxlm_xfund_zh_udml + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_21.jpg + save_res_path: ./output/re/xfund_zh/with_gt + +Architecture: + model_type: &model_type "kie" + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForRe + pretrained: True + mode: vi + checkpoints: + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: *algorithm + Transform: + Backbone: + name: LayoutXLMForRe + pretrained: True + mode: vi + checkpoints: + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationLossFromOutput: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: loss + reduction: mean + - DistillationVQADistanceLoss: + weight: 0.5 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: hidden_states + index: 5 + name: "loss_5" + - DistillationVQADistanceLoss: + weight: 0.5 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: hidden_states + index: 8 + name: "loss_8" + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 10 + lr: + learning_rate: 0.00005 + warmup_epoch: 10 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: DistillationRePostProcess + model_name: ["Student", "Teacher"] + key: null + + +Metric: + name: DistillationMetric + base_metric_name: VQAReTokenMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: &class_path train_data/XFUND/class_list_xfun.txt + use_textline_bbox_info: &use_textline_bbox_info True + # [None, "tb-yx"] + order_method: &order_method "tb-yx" + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox','attention_mask', 'token_type_ids', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 2 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: True + algorithm: *algorithm + class_path: *class_path + use_textline_bbox_info: *use_textline_bbox_info + order_method: *order_method + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQAReTokenRelation: + - VQAReTokenChunk: + max_seq_len: *max_seq_len + - TensorizeEntitiesRelations: + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'entities', 'relations'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 8 diff --git a/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml b/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml new file mode 100644 index 0000000..50b04ba --- /dev/null +++ b/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml @@ -0,0 +1,138 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/ser_vi_layoutxlm_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_42.jpg + d2s_train_image_shape: [3, 224, 224] + # if you want to predict using the groundtruth ocr info, + # you can use the following config + # infer_img: train_data/XFUND/zh_val/val.json + # infer_mode: False + + save_res_path: ./output/ser/xfund_zh/res + kie_rec_model_dir: + kie_det_model_dir: + amp_custom_white_list: ['scale', 'concat', 'elementwise_add'] + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForSer + pretrained: True + checkpoints: + # one of base or vi + mode: vi + num_classes: &num_classes 7 + +Loss: + name: VQASerTokenLayoutLMLoss + num_classes: *num_classes + key: "backbone_out" + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Linear + learning_rate: 0.00005 + epochs: *epoch_num + warmup_epoch: 2 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQASerTokenLayoutLMPostProcess + class_path: &class_path train_data/XFUND/class_list_xfun.txt + +Metric: + name: VQASerTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + use_textline_bbox_info: &use_textline_bbox_info True + # one of [None, "tb-yx"] + order_method: &order_method "tb-yx" + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + use_textline_bbox_info: *use_textline_bbox_info + order_method: *order_method + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 4 diff --git a/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh_udml.yml b/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh_udml.yml new file mode 100644 index 0000000..5166533 --- /dev/null +++ b/configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh_udml.yml @@ -0,0 +1,182 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/ser_vi_layoutxlm_xfund_zh_udml + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 19 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_42.jpg + save_res_path: ./output/ser_layoutxlm_xfund_zh/res + + +Architecture: + model_type: &model_type "kie" + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForSer + pretrained: True + # one of base or vi + mode: vi + checkpoints: + num_classes: &num_classes 7 + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: *algorithm + Transform: + Backbone: + name: LayoutXLMForSer + pretrained: True + # one of base or vi + mode: vi + checkpoints: + num_classes: *num_classes + + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationVQASerTokenLayoutLMLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: backbone_out + num_classes: *num_classes + - DistillationSERDMLLoss: + weight: 1.0 + act: "softmax" + use_log: true + model_name_pairs: + - ["Student", "Teacher"] + key: backbone_out + - DistillationVQADistanceLoss: + weight: 0.5 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: hidden_states_5 + name: "loss_5" + - DistillationVQADistanceLoss: + weight: 0.5 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: hidden_states_8 + name: "loss_8" + + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Linear + learning_rate: 0.00005 + epochs: *epoch_num + warmup_epoch: 10 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: DistillationSerPostProcess + model_name: ["Student", "Teacher"] + key: backbone_out + class_path: &class_path train_data/XFUND/class_list_xfun.txt + +Metric: + name: DistillationMetric + base_metric_name: VQASerTokenMetric + main_indicator: hmean + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + # one of [None, "tb-yx"] + order_method: &order_method "tb-yx" + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + order_method: *order_method + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 4 diff --git a/configs/rec/LaTeX_OCR_rec.yaml b/configs/rec/LaTeX_OCR_rec.yaml new file mode 100644 index 0000000..514f4e6 --- /dev/null +++ b/configs/rec/LaTeX_OCR_rec.yaml @@ -0,0 +1,131 @@ +Global: + model_name: LaTeX_OCR_rec # To use static model for inference. + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/rec/latex_ocr/ + save_epoch_step: 5 + max_seq_len: 512 + # evaluation is run every 60000 iterations (22 epoch)(batch_size = 56) + eval_batch_step: [0, 60000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: ppocr/utils/dict/latex_ocr_tokenizer.json + save_res_path: ./output/rec/predicts_latexocr.txt + d2s_train_image_shape: [1,256,256] + find_unused_parameters: True + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Const + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: LaTeXOCR + in_channels: 1 + Transform: + Backbone: + name: HybridTransformer + img_size: [192, 672] + patch_size: 16 + num_classes: 0 + embed_dim: 256 + depth: 4 + num_heads: 8 + input_channel: 1 + is_predict: False + is_export: False + Head: + name: LaTeXOCRHead + pad_value: 0 + is_export: False + decoder_args: + attn_on_attn: True + cross_attend: True + ff_glu: True + rel_pos_bias: False + use_scalenorm: False + +Loss: + name: LaTeXOCRLoss + +PostProcess: + name: LaTeXOCRDecode + rec_char_dict_path: ppocr/utils/dict/latex_ocr_tokenizer.json + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: LaTeXOCRDataSet + data_dir: ./train_data/LaTeXOCR/train + data: ./train_data/LaTeXOCR/latexocr_train.pkl + min_dimensions: [32, 32] + max_dimensions: [672, 192] + batch_size_per_pair: 56 + keep_smaller_batches: False + transforms: + - DecodeImage: + channel_first: False + - MinMaxResize: + min_dimensions: [32, 32] + max_dimensions: [672, 192] + - LatexTrainTransform: + bitmap_prob: .04 + - NormalizeImage: + mean: [0.7931, 0.7931, 0.7931] + std: [0.1738, 0.1738, 0.1738] + order: 'hwc' + - LatexImageFormat: + - KeepKeys: + keep_keys: ['image'] + loader: + shuffle: True + batch_size_per_card: 1 + drop_last: False + num_workers: 0 + collate_fn: LaTeXOCRCollator + +Eval: + dataset: + name: LaTeXOCRDataSet + data_dir: ./train_data/LaTeXOCR/val + data: ./train_data/LaTeXOCR/latexocr_val.pkl + min_dimensions: [32, 32] + max_dimensions: [672, 192] + batch_size_per_pair: 10 + keep_smaller_batches: True + transforms: + - DecodeImage: + channel_first: False + - MinMaxResize: + min_dimensions: [32, 32] + max_dimensions: [672, 192] + - LatexTestTransform: + - NormalizeImage: + mean: [0.7931, 0.7931, 0.7931] + std: [0.1738, 0.1738, 0.1738] + order: 'hwc' + - LatexImageFormat: + - KeepKeys: + keep_keys: ['image'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 + num_workers: 0 + collate_fn: LaTeXOCRCollator diff --git a/configs/rec/PP-FormuaNet/PP-FormulaNet-L.yaml b/configs/rec/PP-FormuaNet/PP-FormulaNet-L.yaml new file mode 100644 index 0000000..6ecbb47 --- /dev/null +++ b/configs/rec/PP-FormuaNet/PP-FormulaNet-L.yaml @@ -0,0 +1,119 @@ +Global: + model_name: PP-FormulaNet-L # To use static model for inference. + use_gpu: True + epoch_num: 10 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/pp_formulanet_l/ + save_epoch_step: 2 + # evaluation is run every 417 iterations (1 epoch)(batch_size = 24) # max_seq_len: 1024 + eval_batch_step: [0, 417 ] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + max_new_tokens: &max_new_tokens 1024 + input_size: &input_size [768, 768] + save_res_path: ./output/rec/predicts_pp_formulanet_l.txt + allow_resize_largeImg: False + start_ema: True + d2s_train_image_shape: [1,768,768] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: PP-FormulaNet-L + in_channels: 3 + Transform: + Backbone: + name: Vary_VIT_B_Formula + image_size: 768 + encoder_embed_dim: 768 + encoder_depth: 12 + encoder_num_heads: 12 + encoder_global_attn_indexes: [2, 5, 8, 11] + Head: + name: PPFormulaNet_Head + max_new_tokens: *max_new_tokens + decoder_start_token_id: 0 + decoder_ffn_dim: 2048 + decoder_hidden_size: 512 + decoder_layers: 8 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 1024 + is_export: False + length_aware: False + use_parallel: False + parallel_step: 0 + +Loss: + name: PPFormulaNet_L_Loss + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/train.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTrainTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_new_tokens + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 6 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/val.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_new_tokens + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask', 'filename'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 10 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/PP-FormuaNet/PP-FormulaNet-S.yaml b/configs/rec/PP-FormuaNet/PP-FormulaNet-S.yaml new file mode 100644 index 0000000..3360c7d --- /dev/null +++ b/configs/rec/PP-FormuaNet/PP-FormulaNet-S.yaml @@ -0,0 +1,117 @@ +Global: + model_name: PP-FormulaNet-S # To use static model for inference. + use_gpu: True + epoch_num: 20 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/pp_formulanet_s/ + save_epoch_step: 2 + # evaluation is run every 179 iterations (1 epoch)(batch_size = 56) # max_seq_len: 1024 + eval_batch_step: [0, 179] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + max_new_tokens: &max_new_tokens 1024 + input_size: &input_size [384, 384] + save_res_path: ./output/rec/predicts_pp_formulanet_s.txt + allow_resize_largeImg: False + start_ema: True + d2s_train_image_shape: [1,384,384] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: PP-FormulaNet-S + in_channels: 3 + Transform: + Backbone: + name: PPHGNetV2_B4_Formula + class_num: 1024 + + Head: + name: PPFormulaNet_Head + max_new_tokens: *max_new_tokens + decoder_start_token_id: 0 + decoder_ffn_dim: 1536 + decoder_hidden_size: 384 + decoder_layers: 2 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 2048 + is_export: False + length_aware: True + use_parallel: True + parallel_step: 3 + +Loss: + name: PPFormulaNet_S_Loss + parallel_step: 3 + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/train.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTrainTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_new_tokens + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 14 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/val.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_new_tokens + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask', 'filename'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 30 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-L.yaml b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-L.yaml new file mode 100644 index 0000000..eb8d368 --- /dev/null +++ b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-L.yaml @@ -0,0 +1,122 @@ +Global: + model_name: PP-FormulaNet_plus-L # To use static model for inference. + use_gpu: True + epoch_num: 10 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/pp_formulanet_plus_l/ + save_epoch_step: 2 + # evaluation is run every 417 iterations (1 epoch)(batch_size = 24) # max_seq_len: 1024 + eval_batch_step: [0, 417 ] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + max_new_tokens: &max_new_tokens 2560 + input_size: &input_size [768, 768] + save_res_path: ./output/rec/predicts_pp_formulanet_plus_l.txt + allow_resize_largeImg: False + start_ema: True + d2s_train_image_shape: [1,768,768] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: PP-FormulaNet_plus-L + in_channels: 3 + Transform: + Backbone: + name: Vary_VIT_B_Formula + image_size: 768 + encoder_embed_dim: 768 + encoder_depth: 12 + encoder_num_heads: 12 + encoder_global_attn_indexes: [2, 5, 8, 11] + Head: + name: PPFormulaNet_Head + max_new_tokens: *max_new_tokens + decoder_start_token_id: 0 + decoder_ffn_dim: 2048 + decoder_hidden_size: 512 + decoder_layers: 8 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 1024 + is_export: False + length_aware: False + use_parallel: False + parallel_step: 0 + +Loss: + name: PPFormulaNet_L_Loss + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/train.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + random_padding: True + random_resize: True + random_crop: True + - UniMERNetTrainTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_new_tokens + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 3 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/val.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_new_tokens + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask', 'filename'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 10 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-M.yaml b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-M.yaml new file mode 100644 index 0000000..fbaf356 --- /dev/null +++ b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-M.yaml @@ -0,0 +1,119 @@ +Global: + model_name: PP-FormulaNet_plus-M # To use static model for inference. + use_gpu: True + epoch_num: 20 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/pp_formulanet_plus_m/ + save_epoch_step: 2 + # evaluation is run every 179 iterations (1 epoch)(batch_size = 56) # max_seq_len: 1024 + eval_batch_step: [0, 179] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + max_new_tokens: &max_new_tokens 2560 + input_size: &input_size [384, 384] + save_res_path: ./output/rec/predicts_pp_formulanet_plus_m.txt + allow_resize_largeImg: False + start_ema: True + d2s_train_image_shape: [1,384,384] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: PP-FormulaNet_plus-M + in_channels: 3 + Transform: + Backbone: + name: PPHGNetV2_B6_Formula + class_num: 1024 + + Head: + name: PPFormulaNet_Head + max_new_tokens: *max_new_tokens + decoder_start_token_id: 0 + decoder_ffn_dim: 2048 + decoder_hidden_size: 512 + decoder_layers: 6 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 2048 + is_export: False + length_aware: False + use_parallel: False + parallel_step: 0 + +Loss: + name: PPFormulaNet_L_Loss + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/train.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + random_padding: True + random_resize: True + random_crop: True + - UniMERNetTrainTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_new_tokens + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 14 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/val.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_new_tokens + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask', 'filename'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 30 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-S.yaml b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-S.yaml new file mode 100644 index 0000000..0b24316 --- /dev/null +++ b/configs/rec/PP-FormuaNet/PP-FormulaNet_plus-S.yaml @@ -0,0 +1,120 @@ +Global: + model_name: PP-FormulaNet_plus-S # To use static model for inference. + use_gpu: True + epoch_num: 20 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/pp_formulanet_plus_s/ + save_epoch_step: 2 + # evaluation is run every 179 iterations (1 epoch)(batch_size = 56) # max_seq_len: 1024 + eval_batch_step: [0, 179] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + max_new_tokens: &max_new_tokens 1024 + input_size: &input_size [384, 384] + save_res_path: ./output/rec/predicts_pp_formulanet_plus_s.txt + allow_resize_largeImg: False + start_ema: True + d2s_train_image_shape: [1,384,384] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: PP-FormulaNet_plus-S + in_channels: 3 + Transform: + Backbone: + name: PPHGNetV2_B4_Formula + class_num: 1024 + + Head: + name: PPFormulaNet_Head + max_new_tokens: *max_new_tokens + decoder_start_token_id: 0 + decoder_ffn_dim: 1536 + decoder_hidden_size: 384 + decoder_layers: 2 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 2048 + is_export: False + length_aware: True + use_parallel: True, + parallel_step: 3 + +Loss: + name: PPFormulaNet_S_Loss + parallel_step: 3 + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/train.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + random_padding: True + random_resize: True + random_crop: True + - UniMERNetTrainTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_new_tokens + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 14 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./ocr_rec_latexocr_dataset_example + label_file_list: ["./ocr_rec_latexocr_dataset_example/val.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - LatexImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_new_tokens + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask', 'filename'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 30 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..a239288 --- /dev/null +++ b/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec.yml @@ -0,0 +1,134 @@ +Global: + model_name: PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v3 + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + d2s_train_image_shape: [3,48,320] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec_distillation.yml b/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec_distillation.yml new file mode 100644 index 0000000..3b82ef8 --- /dev/null +++ b/configs/rec/PP-OCRv3/PP-OCRv3_mobile_rec_distillation.yml @@ -0,0 +1,209 @@ +Global: + debug: false + use_gpu: true + epoch_num: 800 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v3_distillation + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_distillation.txt + d2s_train_image_shape: [3, 48, -1] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs : [700] + values : [0.0005, 0.00005] + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: &model_type "rec" + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDMLLoss: + weight: 1.0 + act: "softmax" + use_log: true + model_name_pairs: + - ["Student", "Teacher"] + key: head_out + multi_head: True + dis_head: ctc + name: dml_ctc + - DistillationDMLLoss: + weight: 0.5 + act: "softmax" + use_log: true + model_name_pairs: + - ["Student", "Teacher"] + key: head_out + multi_head: True + dis_head: sar + name: dml_sar + - DistillationDistanceLoss: + weight: 1.0 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: backbone_out + - DistillationCTCLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: head_out + multi_head: True + - DistillationSARLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: head_out + multi_head: True + +PostProcess: + name: DistillationCTCLabelDecode + model_name: ["Student", "Teacher"] + key: head_out + multi_head: True + +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: "Student" + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/en_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/en_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..7c21860 --- /dev/null +++ b/configs/rec/PP-OCRv3/en_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,134 @@ +Global: + model_name: en_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_en_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/en_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_en.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/.gitkeep b/configs/rec/PP-OCRv3/multi_language/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/configs/rec/PP-OCRv3/multi_language/arabic_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/arabic_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..5c82700 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/arabic_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: arabic_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_arabic_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: ./doc/imgs_words/arabic/ar_2.jpg + character_dict_path: ppocr/utils/dict/arabic_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_arabic.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/chinese_cht_PP-OCRv3_mobile_rec.yaml b/configs/rec/PP-OCRv3/multi_language/chinese_cht_PP-OCRv3_mobile_rec.yaml new file mode 100644 index 0000000..2ba7be6 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/chinese_cht_PP-OCRv3_mobile_rec.yaml @@ -0,0 +1,133 @@ +Global: + model_name: chinese_cht_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_chinese_cht_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/chinese_cht_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_chinese_cht.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/cyrillic_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/cyrillic_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..0991717 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/cyrillic_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: cyrillic_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_cyrillic_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/cyrillic_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_cyrillic.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/devanagari_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/devanagari_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..8328757 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/devanagari_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: devanagari_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_devanagari_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/devanagari_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_devanagari.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/japan_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/japan_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..98b7faa --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/japan_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: japan_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_japan_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/japan_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_japan.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/ka_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/ka_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..47905e2 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/ka_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: ka_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_ka_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/ka_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_ka.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/korean_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/korean_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..08f3501 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/korean_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: korean_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_korean_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/korean_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_korean.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/latin_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/latin_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..5c2987a --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/latin_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: latin_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_latin_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/latin_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_latin.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/ta_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/ta_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..3551eb0 --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/ta_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: ta_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_ta_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/ta_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_ta.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv3/multi_language/te_PP-OCRv3_mobile_rec.yml b/configs/rec/PP-OCRv3/multi_language/te_PP-OCRv3_mobile_rec.yml new file mode 100644 index 0000000..fb7fe5e --- /dev/null +++ b/configs/rec/PP-OCRv3/multi_language/te_PP-OCRv3_mobile_rec.yml @@ -0,0 +1,133 @@ +Global: + model_name: te_PP-OCRv3_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/v3_te_mobile + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/te_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_te.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + last_pool_kernel_size: [2, 2] + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - SARLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml new file mode 100644 index 0000000..bd0369d --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml @@ -0,0 +1,140 @@ +Global: + model_name: PP-OCRv4_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4 + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_ampO2_ultra.yml b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_ampO2_ultra.yml new file mode 100644 index 0000000..475c551 --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_ampO2_ultra.yml @@ -0,0 +1,140 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4 + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + use_amp: True + amp_level: O2 + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 384 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 16 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 16 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_distillation.yml b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_distillation.yml new file mode 100644 index 0000000..aa2a36d --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_distillation.yml @@ -0,0 +1,231 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_dkd_400w_svtr_ctc_lcnet_blank_dkd0.1/ + save_epoch_step: 40 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: ./output/rec_dkd_400w_svtr_ctc_lcnet_blank_dkd0.1/latest + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 2 + regularizer: + name: L2 + factor: 3.0e-05 +Architecture: + model_type: rec + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: true + return_all_feats: true + model_type: rec + algorithm: SVTR + Transform: null + Backbone: + name: SVTRNet + img_size: + - 48 + - 320 + out_char_num: 40 + out_channels: 192 + patch_merging: Conv + embed_dim: + - 64 + - 128 + - 256 + depth: + - 3 + - 6 + - 3 + num_heads: + - 2 + - 4 + - 8 + mixer: + - Conv + - Conv + - Conv + - Conv + - Conv + - Conv + - Global + - Global + - Global + - Global + - Global + - Global + local_mixer: + - - 5 + - 5 + - - 5 + - 5 + - - 5 + - 5 + last_stage: false + prenorm: true + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: rec + algorithm: SVTR + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDKDLoss: + weight: 0.1 + model_name_pairs: + - - Student + - Teacher + key: head_out + multi_head: true + alpha: 1.0 + beta: 2.0 + dis_head: gtc + name: dkd + - DistillationCTCLoss: + weight: 1.0 + model_name_list: + - Student + key: head_out + multi_head: true + - DistillationNRTRLoss: + weight: 1.0 + smoothing: false + model_name_list: + - Student + key: head_out + multi_head: true + - DistillCTCLogits: + weight: 1.0 + reduction: mean + model_name_pairs: + - - Student + - Teacher + key: head_out +PostProcess: + name: DistillationCTCLabelDecode + model_name: + - Student + key: head_out + multi_head: true +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: Student + ignore_space: false +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/train_list.txt + ratio_list: + - 1.0 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 8 + use_shared_memory: true +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 +profiler_options: null diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_fp32_ultra.yml b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_fp32_ultra.yml new file mode 100644 index 0000000..8c26730 --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_fp32_ultra.yml @@ -0,0 +1,138 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4 + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 16 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 16 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_server_rec.yml b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec.yml new file mode 100644 index 0000000..6ec4960 --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec.yml @@ -0,0 +1,138 @@ +Global: + model_name: PP-OCRv4_server_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4_hgnet + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + d2s_train_image_shape: [3, 48, 320] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: PPHGNet_small + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_ampO2_ultra.yml b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_ampO2_ultra.yml new file mode 100644 index 0000000..4303521 --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_ampO2_ultra.yml @@ -0,0 +1,139 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4_hgnet + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + use_amp: True + amp_level: O2 + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: PPHGNet_small + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 256 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 16 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 16 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_doc.yml b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_doc.yml new file mode 100644 index 0000000..46bf0fb --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_doc.yml @@ -0,0 +1,138 @@ +Global: + model_name: PP-OCRv4_server_rec_doc # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4_hgnet + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/ppocrv4_doc_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + d2s_train_image_shape: [3, 48, 320] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: PPHGNet_small + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_fp32_ultra.yml b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_fp32_ultra.yml new file mode 100644 index 0000000..ee9ebca --- /dev/null +++ b/configs/rec/PP-OCRv4/PP-OCRv4_server_rec_fp32_ultra.yml @@ -0,0 +1,137 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4_hgnet + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: PPHGNet_small + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 256 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 16 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 16 diff --git a/configs/rec/PP-OCRv4/ch_PP-OCRv4_rec_svtr_large.yml b/configs/rec/PP-OCRv4/ch_PP-OCRv4_rec_svtr_large.yml new file mode 100644 index 0000000..525d1c0 --- /dev/null +++ b/configs/rec/PP-OCRv4/ch_PP-OCRv4_rec_svtr_large.yml @@ -0,0 +1,144 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr_large/ + save_epoch_step: 10 + # evaluation is run every 2000 iterations after the 0th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 40 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_svtr_large.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.0e-08 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed char_node_embed pos_node_embed char_pos_embed vis_pos_embed + one_dim_param_no_weight_decay: true + lr: + name: Cosine + learning_rate: 0.00025 # 8gpus 64bs + warmup_epoch: 5 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: SVTRNet + img_size: + - 48 + - 320 + out_char_num: 40 + out_channels: 512 + patch_merging: Conv + embed_dim: [192, 256, 512] + depth: [6, 6, 9] + num_heads: [6, 8, 16] + mixer: ['Conv','Conv','Conv','Conv','Conv','Conv','Conv','Conv','Conv','Global','Global','Global','Global','Global','Global','Global','Global','Global','Global','Global','Global'] + local_mixer: [[5, 5], [5, 5], [5, 5]] + last_stage: False + prenorm: True + Head: + name: MultiHead + use_pool: true + use_pos: true + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: true + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 64 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - SVTRRecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv4/en_PP-OCRv4_mobile_rec.yml b/configs/rec/PP-OCRv4/en_PP-OCRv4_mobile_rec.yml new file mode 100644 index 0000000..ddbb982 --- /dev/null +++ b/configs/rec/PP-OCRv4/en_PP-OCRv4_mobile_rec.yml @@ -0,0 +1,151 @@ +Global: + model_name: en_PP-OCRv4_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 50 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v4 + save_epoch_step: 10 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/en_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3.txt +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: + - 1 + - 3 + use_guide: true + Head: + fc_decay: 1.0e-05 + - NRTRHead: + nrtr_dim: 384 + max_text_length: 25 +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: null + - NRTRLoss: null +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc + ignore_space: false +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: + - 48 + - 320 + - 3 + max_text_length: 25 + - RecAug: null + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: + - - 320 + - 32 + - - 320 + - 48 + - - 320 + - 64 + first_bs: 96 + fix_bs: false + divided_factor: + - 8 + - 16 + is_training: true + loader: + shuffle: true + batch_size_per_card: 96 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: + - 3 + - 48 + - 320 + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 +profiler_options: null diff --git a/configs/rec/PP-OCRv5/PP-OCRv5_mobile_rec.yml b/configs/rec/PP-OCRv5/PP-OCRv5_mobile_rec.yml new file mode 100644 index 0000000..ad882db --- /dev/null +++ b/configs/rec/PP-OCRv5/PP-OCRv5_mobile_rec.yml @@ -0,0 +1,140 @@ +Global: + model_name: PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv5_mobile_rec + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ./ppocr/utils/dict/ppocrv5_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/PP-OCRv5_server_rec.yml b/configs/rec/PP-OCRv5/PP-OCRv5_server_rec.yml new file mode 100644 index 0000000..abbda00 --- /dev/null +++ b/configs/rec/PP-OCRv5/PP-OCRv5_server_rec.yml @@ -0,0 +1,136 @@ +Global: + model_name: PP-OCRv5_server_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv5_server_rec + save_epoch_step: 1 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + calc_epoch_interval: 1 + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ./ppocr/utils/dict/ppocrv5_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 1 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: PPHGNetV2_B4 + text_rec: True + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 16 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/arabic_PP-OCRv5_mobile_rec.yaml b/configs/rec/PP-OCRv5/multi_language/arabic_PP-OCRv5_mobile_rec.yaml new file mode 100644 index 0000000..c087ce5 --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/arabic_PP-OCRv5_mobile_rec.yaml @@ -0,0 +1,141 @@ +Global: + model_name: arabic_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/arabic_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_arabic_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_arabic_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/cyrillic_PP-OCRv5_mobile_rec.yaml b/configs/rec/PP-OCRv5/multi_language/cyrillic_PP-OCRv5_mobile_rec.yaml new file mode 100644 index 0000000..0d887ea --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/cyrillic_PP-OCRv5_mobile_rec.yaml @@ -0,0 +1,141 @@ +Global: + model_name: cyrillic_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/cyrillic_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_cyrillic_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_cyrillic_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/devanagari_PP-OCRv5_mobile_rec.yaml b/configs/rec/PP-OCRv5/multi_language/devanagari_PP-OCRv5_mobile_rec.yaml new file mode 100644 index 0000000..dd85e51 --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/devanagari_PP-OCRv5_mobile_rec.yaml @@ -0,0 +1,141 @@ +Global: + model_name: devanagari_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/devanagari_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_devanagari_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_devanagari_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/el_PP-OCRv5_mobile_rec.yaml b/configs/rec/PP-OCRv5/multi_language/el_PP-OCRv5_mobile_rec.yaml new file mode 100644 index 0000000..2c34e04 --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/el_PP-OCRv5_mobile_rec.yaml @@ -0,0 +1,141 @@ +Global: + model_name: el_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/el_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_el_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_el_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/en_PP-OCRv5_mobile_rec.yaml b/configs/rec/PP-OCRv5/multi_language/en_PP-OCRv5_mobile_rec.yaml new file mode 100644 index 0000000..fba7afa --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/en_PP-OCRv5_mobile_rec.yaml @@ -0,0 +1,141 @@ +Global: + model_name: en_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/en_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_en_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_en_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/eslav_PP-OCRv5_mobile_rec.yml b/configs/rec/PP-OCRv5/multi_language/eslav_PP-OCRv5_mobile_rec.yml new file mode 100644 index 0000000..777b312 --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/eslav_PP-OCRv5_mobile_rec.yml @@ -0,0 +1,141 @@ +Global: + model_name: eslav_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/eslav_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_eslav_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/korean_PP-OCRv5_mobile_rec.yml b/configs/rec/PP-OCRv5/multi_language/korean_PP-OCRv5_mobile_rec.yml new file mode 100644 index 0000000..01244c4 --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/korean_PP-OCRv5_mobile_rec.yml @@ -0,0 +1,141 @@ +Global: + model_name: korean_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/korean_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_korean_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/latin_PP-OCRv5_mobile_rec.yml b/configs/rec/PP-OCRv5/multi_language/latin_PP-OCRv5_mobile_rec.yml new file mode 100644 index 0000000..6e5d4b9 --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/latin_PP-OCRv5_mobile_rec.yml @@ -0,0 +1,143 @@ +Global: + model_name: latin_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/latin_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 500] + cal_metric_during_train: true + pretrained_model: PP-OCRv5_mobile_rec_pretrained.pdparams + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_latin_dict.txt + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: 25 + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: 25 + - RecAug: + - MultiLabelEncode: + max_text_length: 25 + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/eval_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + max_text_length: 1000 + gtc_encode: NRTRLabelEncode + - RecResizeImg: + eval_mode: True + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 1 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/ta_PP-OCRv5_mobile_rec.yaml b/configs/rec/PP-OCRv5/multi_language/ta_PP-OCRv5_mobile_rec.yaml new file mode 100644 index 0000000..17f17e7 --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/ta_PP-OCRv5_mobile_rec.yaml @@ -0,0 +1,141 @@ +Global: + model_name: ta_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ta_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_ta_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ta_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/te_PP-OCRv5_mobile_rec.yaml b/configs/rec/PP-OCRv5/multi_language/te_PP-OCRv5_mobile_rec.yaml new file mode 100644 index 0000000..75c0fc9 --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/te_PP-OCRv5_mobile_rec.yaml @@ -0,0 +1,141 @@ +Global: + model_name: te_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/te_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_te_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_te_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv5/multi_language/th_PP-OCRv5_mobile_rec.yaml b/configs/rec/PP-OCRv5/multi_language/th_PP-OCRv5_mobile_rec.yaml new file mode 100644 index 0000000..305b4ad --- /dev/null +++ b/configs/rec/PP-OCRv5/multi_language/th_PP-OCRv5_mobile_rec.yaml @@ -0,0 +1,141 @@ +Global: + model_name: th_PP-OCRv5_mobile_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 75 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/th_rec_ppocr_v5 + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: + character_dict_path: ./ppocr/utils/dict/ppocrv5_th_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_th_ppocrv5.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV3 + scale: 0.95 + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 120 + depth: 2 + hidden_dims: 120 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + ignore_space: False + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: true + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv6/PP-OCRv6_medium_rec.yml b/configs/rec/PP-OCRv6/PP-OCRv6_medium_rec.yml new file mode 100644 index 0000000..d8149ed --- /dev/null +++ b/configs/rec/PP-OCRv6/PP-OCRv6_medium_rec.yml @@ -0,0 +1,143 @@ +Global: + model_name: PP-OCRv6_medium_rec + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv6_medium_rec + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/ppocrv6_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv6_medium.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV4 + model_size: medium + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: lightsvtr + dims: 192 + depth: 2 + mlp_ratio: 4.0 + local_kernel: 7 + use_guide: false + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 512 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + ratio_list: + - 0.5 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 64 + fix_bs: false + divided_factor: [8, 16] + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv6/PP-OCRv6_small_rec.yml b/configs/rec/PP-OCRv6/PP-OCRv6_small_rec.yml new file mode 100644 index 0000000..cf2b417 --- /dev/null +++ b/configs/rec/PP-OCRv6/PP-OCRv6_small_rec.yml @@ -0,0 +1,140 @@ +Global: + model_name: PP-OCRv6_small_rec + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv6_small_rec + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/ppocrv6_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv6_small.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV4 + model_size: small + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: lightsvtr + dims: 120 + depth: 2 + mlp_ratio: 2.0 + local_kernel: 7 + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/PP-OCRv6/PP-OCRv6_tiny_rec.yml b/configs/rec/PP-OCRv6/PP-OCRv6_tiny_rec.yml new file mode 100644 index 0000000..c5486b8 --- /dev/null +++ b/configs/rec/PP-OCRv6/PP-OCRv6_tiny_rec.yml @@ -0,0 +1,138 @@ +Global: + model_name: PP-OCRv6_tiny_rec + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/PP-OCRv6_tiny_rec + save_epoch_step: 10 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/dict/ppocrv6_tiny_dict.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv6_tiny.txt + d2s_train_image_shape: [3, 48, 320] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: rec + algorithm: SVTR_LCNet + Transform: + Backbone: + name: PPLCNetV4 + model_size: tiny + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: reshape + Head: + mid_channels: 80 + use_guide: true + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + max_text_length: *max_text_length + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 128 + fix_bs: false + divided_factor: [8, 16] + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 8 diff --git a/configs/rec/SVTRv2/ch_RepSVTR_rec.yml b/configs/rec/SVTRv2/ch_RepSVTR_rec.yml new file mode 100644 index 0000000..5c31916 --- /dev/null +++ b/configs/rec/SVTRv2/ch_RepSVTR_rec.yml @@ -0,0 +1,136 @@ +Global: + model_name: ch_RepSVTR_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_RepSVTR_rec + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_repsvtr.txt + d2s_train_image_shape: [3, 48, 320] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1.e-8 + weight_decay: 0.025 + no_weight_decay_name: norm + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.001 # 8gpus 192bs + warmup_epoch: 5 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: RepSVTR + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + num_decoder_layers: 2 + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/SVTRv2/ch_RepSVTR_rec_gtc.yml b/configs/rec/SVTRv2/ch_RepSVTR_rec_gtc.yml new file mode 100644 index 0000000..28e3611 --- /dev/null +++ b/configs/rec/SVTRv2/ch_RepSVTR_rec_gtc.yml @@ -0,0 +1,134 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_RepSVTR_rec_gtc + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_repsvtr.txt + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1.e-8 + weight_decay: 0.025 + no_weight_decay_name: norm + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.001 # 8gpus 192bs + warmup_epoch: 5 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: RepSVTR + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + num_decoder_layers: 2 + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/SVTRv2/ch_SVTRv2_rec.yml b/configs/rec/SVTRv2/ch_SVTRv2_rec.yml new file mode 100644 index 0000000..7b3fe63 --- /dev/null +++ b/configs/rec/SVTRv2/ch_SVTRv2_rec.yml @@ -0,0 +1,144 @@ +Global: + model_name: ch_SVTRv2_rec # To use static model for inference. + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_SVTRv2_rec + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_svrtv2.txt + d2s_train_image_shape: [3, 48, 320] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.001 # 8gpus 192bs + warmup_epoch: 5 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: SVTRv2 + use_pos_embed: False + dims: [128, 256, 384] + depths: [6, 6, 6] + num_heads: [4, 8, 12] + mixer: [['Conv','Conv','Conv','Conv','Conv','Conv'],['Conv','Conv','Global','Global','Global','Global'],['Global','Global','Global','Global','Global','Global']] + local_k: [[5, 5], [5, 5], [-1, -1]] + sub_k: [[2, 1], [2, 1], [-1, -1]] + last_stage: False + use_pool: True + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + num_decoder_layers: 2 + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/SVTRv2/ch_SVTRv2_rec_distillation.yml b/configs/rec/SVTRv2/ch_SVTRv2_rec_distillation.yml new file mode 100644 index 0000000..4c80148 --- /dev/null +++ b/configs/rec/SVTRv2/ch_SVTRv2_rec_distillation.yml @@ -0,0 +1,208 @@ +Global: + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_SVTRv2_rec_distill_lr00002/ + save_epoch_step: 5 + eval_batch_step: + - 0 + - 1000 + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_svtrv2_ch_distill.txt +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed patch_embed downsample + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.0002 # 8gpus 192bs + warmup_epoch: 5 +Architecture: + model_type: rec + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: ./output/ch_SVTRv2_rec/best_accuracy + freeze_params: true + return_all_feats: true + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: SVTRv2 + use_pos_embed: False + dims: [128, 256, 384] + depths: [6, 6, 6] + num_heads: [4, 8, 12] + mixer: [['Conv','Conv','Conv','Conv','Conv','Conv'],['Conv','Conv','Global','Global','Global','Global'],['Global','Global','Global','Global','Global','Global']] + local_k: [[5, 5], [5, 5], [-1, -1]] + sub_k: [[2, 1], [2, 1], [-1, -1]] + last_stage: False + use_pool: True + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + num_decoder_layers: 2 + max_text_length: *max_text_length + Student: + pretrained: ./output/ch_RepSVTR_rec/best_accuracy + freeze_params: false + return_all_feats: true + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: RepSVTR + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + num_decoder_layers: 2 + max_text_length: *max_text_length +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDKDLoss: + weight: 0.1 + model_name_pairs: + - - Student + - Teacher + key: head_out + multi_head: true + alpha: 1.0 + beta: 2.0 + dis_head: gtc + name: dkd + - DistillationCTCLoss: + weight: 1.0 + model_name_list: + - Student + key: head_out + multi_head: true + - DistillationNRTRLoss: + weight: 1.0 + smoothing: false + model_name_list: + - Student + key: head_out + multi_head: true + - DistillCTCLogits: + weight: 1.0 + reduction: mean + model_name_pairs: + - - Student + - Teacher + key: head_out +PostProcess: + name: DistillationCTCLabelDecode + model_name: + - Student + key: head_out + multi_head: true +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: Student + + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc.yml b/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc.yml new file mode 100644 index 0000000..bf7587f --- /dev/null +++ b/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc.yml @@ -0,0 +1,145 @@ +Global: + debug: false + use_gpu: true + epoch_num: 200 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_SVTRv2_rec_gtc + save_epoch_step: 10 + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_svrtv2.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.001 # 8gpus 192bs + warmup_epoch: 5 + + +Architecture: + model_type: rec + algorithm: SVTR_HGNet + Transform: + Backbone: + name: SVTRv2 + use_pos_embed: False + dims: [128, 256, 384] + depths: [6, 6, 6] + num_heads: [4, 8, 12] + mixer: [['Conv','Conv','Conv','Conv','Conv','Conv'],['Conv','Conv','Global','Global','Global','Global'],['Global','Global','Global','Global','Global','Global']] + local_k: [[5, 5], [5, 5], [-1, -1]] + sub_k: [[2, 1], [2, 1], [-1, -1]] + last_stage: False + use_pool: True + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + max_text_length: *max_text_length + num_decoder_layers: 2 + +Loss: + name: MultiLoss + loss_config_list: + - CTCLoss: + - NRTRLoss: + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + + + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc_distill.yml b/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc_distill.yml new file mode 100644 index 0000000..c588c3a --- /dev/null +++ b/configs/rec/SVTRv2/ch_SVTRv2_rec_gtc_distill.yml @@ -0,0 +1,208 @@ +Global: + debug: false + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/ch_SVTRv2_rec_gtc_distill_lr00002/ + save_epoch_step: 5 + eval_batch_step: + - 0 + - 1000 + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_svtrv2_gtc_distill.txt +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed patch_embed downsample + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.0002 # 8gpus 192bs + warmup_epoch: 5 +Architecture: + model_type: rec + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: ./output/ch_SVTRv2_rec_gtc/best_accuracy + freeze_params: true + return_all_feats: true + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: SVTRv2 + use_pos_embed: False + dims: [128, 256, 384] + depths: [6, 6, 6] + num_heads: [4, 8, 12] + mixer: [['Conv','Conv','Conv','Conv','Conv','Conv'],['Conv','Conv','Global','Global','Global','Global'],['Global','Global','Global','Global','Global','Global']] + local_k: [[5, 5], [5, 5], [-1, -1]] + sub_k: [[2, 1], [2, 1], [-1, -1]] + last_stage: False + use_pool: True + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + num_decoder_layers: 2 + max_text_length: *max_text_length + Student: + pretrained: ./output/ch_RepSVTR_rec_gtc/best_accuracy + freeze_params: false + return_all_feats: true + model_type: rec + algorithm: SVTR_LCNet + Transform: null + Backbone: + name: repvit_svtr + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 256 + depth: 2 + hidden_dims: 256 + kernel_size: [1, 3] + use_guide: True + Head: + fc_decay: 0.00001 + - NRTRHead: + nrtr_dim: 384 + num_decoder_layers: 2 + max_text_length: *max_text_length +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDKDLoss: + weight: 0.1 + model_name_pairs: + - - Student + - Teacher + key: head_out + multi_head: true + alpha: 1.0 + beta: 2.0 + dis_head: gtc + name: dkd + - DistillationCTCLoss: + weight: 1.0 + model_name_list: + - Student + key: head_out + multi_head: true + - DistillationNRTRLoss: + weight: 1.0 + smoothing: false + model_name_list: + - Student + key: head_out + multi_head: true + - DistillCTCLogits: + weight: 1.0 + reduction: mean + model_name_pairs: + - - Student + - Teacher + key: head_out +PostProcess: + name: DistillationCTCLabelDecode + model_name: + - Student + key: head_out + multi_head: true +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: Student + + +Train: + dataset: + name: MultiScaleDataSet + ds_width: false + data_dir: ./train_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + sampler: + name: MultiScaleSampler + scales: [[320, 32], [320, 48], [320, 64]] + first_bs: &bs 192 + fix_bs: false + divided_factor: [8, 16] # w, h + is_training: True + loader: + shuffle: true + batch_size_per_card: *bs + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + gtc_encode: NRTRLabelEncode + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_gtc + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/configs/rec/UniMERNet.yaml b/configs/rec/UniMERNet.yaml new file mode 100644 index 0000000..b8befc8 --- /dev/null +++ b/configs/rec/UniMERNet.yaml @@ -0,0 +1,115 @@ +Global: + model_name: UniMERNet # To use static model for inference. + use_gpu: True + epoch_num: 40 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/rec/unimernet/ + save_epoch_step: 5 + # evaluation is run every 37880 iterations after the 0th iteration + eval_batch_step: [0, 37880] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/pme_demo/0000013.png + infer_mode: False + use_space_char: False + rec_char_dict_path: &rec_char_dict_path ppocr/utils/dict/unimernet_tokenizer + input_size: &input_size [192, 672] + max_seq_len: &max_seq_len 1024 + save_res_path: ./output/rec/predicts_unimernet.txt + allow_resize_largeImg: False + d2s_train_image_shape: [1,192,672] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.05 + lr: + name: LinearWarmupCosine + learning_rate: 1e-4 + start_lr: 1e-5 + min_lr: 1e-8 + warmup_steps: 5000 + +Architecture: + model_type: rec + algorithm: UniMERNet + in_channels: 3 + Transform: + Backbone: + name: DonutSwinModel + hidden_size : 1024 + num_layers: 4 + num_heads: [4, 8, 16, 32] + add_pooling_layer: True + use_mask_token: False + Head: + name: UniMERNetHead + max_new_tokens: 1536 + decoder_start_token_id: 0 + temperature: 0.2 + do_sample: False + top_p: 0.95 + encoder_hidden_size: 1024 + is_export: False + length_aware: True + +Loss: + name: UniMERNetLoss + +PostProcess: + name: UniMERNetDecode + rec_char_dict_path: *rec_char_dict_path + +Metric: + name: LaTeXOCRMetric + main_indicator: exp_rate + cal_bleu_score: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/UniMERNet/ + label_file_list: ["./train_data/UniMERNet/train_unimernet_1M.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTrainTransform: + - UniMERNetImageFormat: + - UniMERNetLabelEncode: + rec_char_dict_path: *rec_char_dict_path + max_seq_len: *max_seq_len + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 7 + num_workers: 0 + collate_fn: UniMERNetCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/UniMERNet/UniMER-Test/cpe + label_file_list: ["./train_data/UniMERNet/test_unimernet_cpe.txt"] + transforms: + - UniMERNetImgDecode: + input_size: *input_size + - UniMERNetTestTransform: + - UniMERNetImageFormat: + - UniMERNetLabelEncode: + max_seq_len: *max_seq_len + rec_char_dict_path: *rec_char_dict_path + - KeepKeys: + keep_keys: ['image', 'label', 'attention_mask'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 30 + num_workers: 0 + collate_fn: UniMERNetCollator diff --git a/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml new file mode 100644 index 0000000..793bb5c --- /dev/null +++ b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec.yml @@ -0,0 +1,110 @@ +Global: + debug: false + use_gpu: true + epoch_num: 800 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_mobile_pp-OCRv2 + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_mobile_pp-OCRv2.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs : [700] + values : [0.001, 0.0001] + warmup_epoch: 5 + regularizer: + name: L2 + factor: 2.0e-05 + + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 64 + Head: + name: CTCHead + mid_channels: 96 + fc_decay: 0.00002 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 8 diff --git a/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_distillation.yml b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_distillation.yml new file mode 100644 index 0000000..3855005 --- /dev/null +++ b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_distillation.yml @@ -0,0 +1,160 @@ +Global: + debug: false + use_gpu: true + epoch_num: 800 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_pp-OCRv2_distillation + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_pp-OCRv2_distillation.txt + amp_custom_black_list: ['matmul','matmul_v2','elementwise_add'] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs : [700] + values : [0.001, 0.0001] + warmup_epoch: 5 + regularizer: + name: L2 + factor: 2.0e-05 + +Architecture: + model_type: &model_type "rec" + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 64 + Head: + name: CTCHead + mid_channels: 96 + fc_decay: 0.00002 + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 64 + Head: + name: CTCHead + mid_channels: 96 + fc_decay: 0.00002 + + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationCTCLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: head_out + - DistillationDMLLoss: + weight: 1.0 + act: "softmax" + use_log: true + model_name_pairs: + - ["Student", "Teacher"] + key: head_out + - DistillationDistanceLoss: + weight: 1.0 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: backbone_out + +PostProcess: + name: DistillationCTCLabelDecode + model_name: ["Student", "Teacher"] + key: head_out + +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_sections: 1 + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 8 diff --git a/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_enhanced_ctc_loss.yml b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_enhanced_ctc_loss.yml new file mode 100644 index 0000000..ef0e893 --- /dev/null +++ b/configs/rec/ch_PP-OCRv2/ch_PP-OCRv2_rec_enhanced_ctc_loss.yml @@ -0,0 +1,124 @@ +Global: + debug: false + use_gpu: true + epoch_num: 800 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_mobile_pp-OCRv2_enhanced_ctc_loss + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_mobile_pp-OCRv2_enhanced_ctc_loss.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs : [700] + values : [0.001, 0.0001] + warmup_epoch: 5 + regularizer: + name: L2 + factor: 2.0e-05 + + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 64 + Head: + name: CTCHead + mid_channels: 96 + fc_decay: 0.00002 + return_feats: true + +Loss: + name: CombinedLoss + loss_config_list: + - CTCLoss: + use_focal_loss: false + weight: 1.0 + - CenterLoss: + weight: 0.05 + num_classes: 6625 + feat_dim: 96 + center_file_path: + # you can also try to add ace loss on your own dataset + # - ACELoss: + # weight: 0.1 + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/train_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + - label_ace + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 8 diff --git a/configs/rec/ch_ppocr_v2.0/rec_chinese_common_train_v2.0.yml b/configs/rec/ch_ppocr_v2.0/rec_chinese_common_train_v2.0.yml new file mode 100644 index 0000000..3e427b6 --- /dev/null +++ b/configs/rec/ch_ppocr_v2.0/rec_chinese_common_train_v2.0.yml @@ -0,0 +1,100 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_chinese_common_v2.0 + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: False + use_space_char: True + save_res_path: ./output/rec/predicts_chinese_common_v2.0.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: 'L2' + factor: 0.00004 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 + Head: + name: CTCHead + fc_decay: 0.00004 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/val_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/ch_ppocr_v2.0/rec_chinese_lite_train_v2.0.yml b/configs/rec/ch_ppocr_v2.0/rec_chinese_lite_train_v2.0.yml new file mode 100644 index 0000000..abd5cd9 --- /dev/null +++ b/configs/rec/ch_ppocr_v2.0/rec_chinese_lite_train_v2.0.yml @@ -0,0 +1,102 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_chinese_lite_v2.0 + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: False + use_space_char: True + save_res_path: ./output/rec/predicts_chinese_lite_v2.0.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + warmup_epoch: 5 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: ["./train_data/val_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/generate_multi_language_configs.py b/configs/rec/multi_language/generate_multi_language_configs.py new file mode 100644 index 0000000..1c34045 --- /dev/null +++ b/configs/rec/multi_language/generate_multi_language_configs.py @@ -0,0 +1,307 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import yaml +from argparse import ArgumentParser, RawDescriptionHelpFormatter +import os.path +import logging + +logger = logging.getLogger("ppocr") + +support_list = { + "it": "italian", + "xi": "spanish", + "pu": "portuguese", + "ru": "russian", + "ar": "arabic", + "ta": "tamil", + "ug": "uyghur", + "fa": "persian", + "ur": "urdu", + "rs": "serbian latin", + "oc": "occitan", + "rsc": "serbian cyrillic", + "bg": "bulgarian", + "uk": "ukranian", + "be": "belarusian", + "te": "telugu", + "ka": "kannada", + "chinese_cht": "chinese tradition", + "hi": "hindi", + "mr": "marathi", + "ne": "nepali", +} + +latin_lang = [ + "af", + "az", + "bs", + "cs", + "cy", + "da", + "de", + "es", + "et", + "fr", + "ga", + "hr", + "hu", + "id", + "is", + "it", + "ku", + "la", + "lt", + "lv", + "mi", + "ms", + "mt", + "nl", + "no", + "oc", + "pi", + "pl", + "pt", + "ro", + "rs_latin", + "sk", + "sl", + "sq", + "sv", + "sw", + "tl", + "tr", + "uz", + "vi", + "latin", +] +arabic_lang = ["ar", "fa", "ug", "ur"] +cyrillic_lang = [ + "ru", + "rs_cyrillic", + "be", + "bg", + "uk", + "mn", + "abq", + "ady", + "kbd", + "ava", + "dar", + "inh", + "che", + "lbe", + "lez", + "tab", + "cyrillic", +] +devanagari_lang = [ + "hi", + "mr", + "ne", + "bh", + "mai", + "ang", + "bho", + "mah", + "sck", + "new", + "gom", + "sa", + "bgc", + "devanagari", +] +multi_lang = latin_lang + arabic_lang + cyrillic_lang + devanagari_lang + +assert os.path.isfile( + "./rec_multi_language_lite_train.yml" +), "Loss basic configuration file rec_multi_language_lite_train.yml.\ +You can download it from \ +https://github.com/PaddlePaddle/PaddleOCR/tree/dygraph/configs/rec/multi_language/" + +global_config = yaml.load( + open("./rec_multi_language_lite_train.yml", "rb"), Loader=yaml.SafeLoader +) +project_path = os.path.abspath(os.path.join(os.getcwd(), "../../../")) + + +class ArgsParser(ArgumentParser): + def __init__(self): + super(ArgsParser, self).__init__(formatter_class=RawDescriptionHelpFormatter) + self.add_argument("-o", "--opt", nargs="+", help="set configuration options") + self.add_argument( + "-l", + "--language", + nargs="+", + help="set language type, support {}".format(support_list), + ) + self.add_argument( + "--train", + type=str, + help="you can use this command to change the train dataset default path", + ) + self.add_argument( + "--val", + type=str, + help="you can use this command to change the eval dataset default path", + ) + self.add_argument( + "--dict", + type=str, + help="you can use this command to change the dictionary default path", + ) + self.add_argument( + "--data_dir", + type=str, + help="you can use this command to change the dataset default root path", + ) + + def parse_args(self, argv=None): + args = super(ArgsParser, self).parse_args(argv) + args.opt = self._parse_opt(args.opt) + args.language = self._set_language(args.language) + return args + + def _parse_opt(self, opts): + config = {} + if not opts: + return config + for s in opts: + s = s.strip() + k, v = s.split("=") + config[k] = yaml.load(v, Loader=yaml.SafeLoader) + return config + + def _set_language(self, type): + lang = type[0] + assert type, "please use -l or --language to choose language type" + assert lang in support_list.keys() or lang in multi_lang, ( + "the sub_keys(-l or --language) can only be one of support list: \n{},\nbut get: {}, " + "please check your running command".format(multi_lang, type) + ) + if lang in latin_lang: + lang = "latin" + elif lang in arabic_lang: + lang = "arabic" + elif lang in cyrillic_lang: + lang = "cyrillic" + elif lang in devanagari_lang: + lang = "devanagari" + global_config["Global"]["character_dict_path"] = ( + "ppocr/utils/dict/{}_dict.txt".format(lang) + ) + global_config["Global"]["save_model_dir"] = "./output/rec_{}_lite".format(lang) + global_config["Train"]["dataset"]["label_file_list"] = [ + "train_data/{}_train.txt".format(lang) + ] + global_config["Eval"]["dataset"]["label_file_list"] = [ + "train_data/{}_val.txt".format(lang) + ] + global_config["Global"]["character_type"] = lang + assert os.path.isfile( + os.path.join(project_path, global_config["Global"]["character_dict_path"]) + ), "Loss default dictionary file {}_dict.txt.You can download it from \ +https://github.com/PaddlePaddle/PaddleOCR/tree/dygraph/ppocr/utils/dict/".format( + lang + ) + return lang + + +def merge_config(config): + """ + Merge config into global config. + Args: + config (dict): Config to be merged. + Returns: global config + """ + for key, value in config.items(): + if "." not in key: + if isinstance(value, dict) and key in global_config: + global_config[key].update(value) + else: + global_config[key] = value + else: + sub_keys = key.split(".") + assert ( + sub_keys[0] in global_config + ), "the sub_keys can only be one of global_config: {}, but get: {}, please check your running command".format( + global_config.keys(), sub_keys[0] + ) + cur = global_config[sub_keys[0]] + for idx, sub_key in enumerate(sub_keys[1:]): + if idx == len(sub_keys) - 2: + cur[sub_key] = value + else: + cur = cur[sub_key] + + +def loss_file(path): + assert os.path.exists( + path + ), "There is no such file:{},Please do not forget to put in the specified file".format( + path + ) + + +if __name__ == "__main__": + FLAGS = ArgsParser().parse_args() + merge_config(FLAGS.opt) + save_file_path = "rec_{}_lite_train.yml".format(FLAGS.language) + if os.path.isfile(save_file_path): + os.remove(save_file_path) + + if FLAGS.train: + global_config["Train"]["dataset"]["label_file_list"] = [FLAGS.train] + train_label_path = os.path.join(project_path, FLAGS.train) + loss_file(train_label_path) + if FLAGS.val: + global_config["Eval"]["dataset"]["label_file_list"] = [FLAGS.val] + eval_label_path = os.path.join(project_path, FLAGS.val) + loss_file(eval_label_path) + if FLAGS.dict: + global_config["Global"]["character_dict_path"] = FLAGS.dict + dict_path = os.path.join(project_path, FLAGS.dict) + loss_file(dict_path) + if FLAGS.data_dir: + global_config["Eval"]["dataset"]["data_dir"] = FLAGS.data_dir + global_config["Train"]["dataset"]["data_dir"] = FLAGS.data_dir + data_dir = os.path.join(project_path, FLAGS.data_dir) + loss_file(data_dir) + + with open(save_file_path, "w") as f: + yaml.dump(dict(global_config), f, default_flow_style=False, sort_keys=False) + logger.info("Project path is :{}".format(project_path)) + logger.info( + "Train list path set to :{}".format( + global_config["Train"]["dataset"]["label_file_list"][0] + ) + ) + logger.info( + "Eval list path set to :{}".format( + global_config["Eval"]["dataset"]["label_file_list"][0] + ) + ) + logger.info( + "Dataset root path set to :{}".format( + global_config["Eval"]["dataset"]["data_dir"] + ) + ) + logger.info( + "Dict path set to :{}".format( + global_config["Global"]["character_dict_path"] + ) + ) + logger.info( + "Config file set to :configs/rec/multi_language/{}".format(save_file_path) + ) diff --git a/configs/rec/multi_language/rec_arabic_lite_train.yml b/configs/rec/multi_language/rec_arabic_lite_train.yml new file mode 100644 index 0000000..a746260 --- /dev/null +++ b/configs/rec/multi_language/rec_arabic_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_arabic_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/arabic_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/arabic_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/arabic_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_cyrillic_lite_train.yml b/configs/rec/multi_language/rec_cyrillic_lite_train.yml new file mode 100644 index 0000000..98544f6 --- /dev/null +++ b/configs/rec/multi_language/rec_cyrillic_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_cyrillic_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/cyrillic_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/cyrillic_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/cyrillic_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_devanagari_lite_train.yml b/configs/rec/multi_language/rec_devanagari_lite_train.yml new file mode 100644 index 0000000..518b9f1 --- /dev/null +++ b/configs/rec/multi_language/rec_devanagari_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_devanagari_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/devanagari_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/devanagari_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/devanagari_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_en_number_lite_train.yml b/configs/rec/multi_language/rec_en_number_lite_train.yml new file mode 100644 index 0000000..ff1fb86 --- /dev/null +++ b/configs/rec/multi_language/rec_en_number_lite_train.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_en_number_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/en_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: True + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/eval_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_french_lite_train.yml b/configs/rec/multi_language/rec_french_lite_train.yml new file mode 100644 index 0000000..217369d --- /dev/null +++ b/configs/rec/multi_language/rec_french_lite_train.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_french_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict/french_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/eval_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_german_lite_train.yml b/configs/rec/multi_language/rec_german_lite_train.yml new file mode 100644 index 0000000..67520f5 --- /dev/null +++ b/configs/rec/multi_language/rec_german_lite_train.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_german_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict/german_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/eval_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_hebrew_lite_train.yml b/configs/rec/multi_language/rec_hebrew_lite_train.yml new file mode 100644 index 0000000..056073e --- /dev/null +++ b/configs/rec/multi_language/rec_hebrew_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_hebrew_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/hebrew_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/hebrew_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/hebrew_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_japan_lite_train.yml b/configs/rec/multi_language/rec_japan_lite_train.yml new file mode 100644 index 0000000..448aff1 --- /dev/null +++ b/configs/rec/multi_language/rec_japan_lite_train.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_japan_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict/japan_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/eval_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_korean_lite_train.yml b/configs/rec/multi_language/rec_korean_lite_train.yml new file mode 100644 index 0000000..8118119 --- /dev/null +++ b/configs/rec/multi_language/rec_korean_lite_train.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_korean_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict/korean_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: ["./train_data/eval_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_latin_lite_train.yml b/configs/rec/multi_language/rec_latin_lite_train.yml new file mode 100644 index 0000000..04fe6d1 --- /dev/null +++ b/configs/rec/multi_language/rec_latin_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_latin_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/latin_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/latin_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/latin_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_multi_language_lite_train.yml b/configs/rec/multi_language/rec_multi_language_lite_train.yml new file mode 100644 index 0000000..c42a3d1 --- /dev/null +++ b/configs/rec/multi_language/rec_multi_language_lite_train.yml @@ -0,0 +1,103 @@ +Global: + use_gpu: True + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_multi_language_lite + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + # if pretrained_model is saved in static mode, load_static_weights must set to True + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: + # Set the language of training, if set, select the default dictionary file + character_type: + max_text_length: 25 + infer_mode: False + use_space_char: True + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: ["./train_data/train_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: ["./train_data/val_list.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_samaritan_lite_train.yml b/configs/rec/multi_language/rec_samaritan_lite_train.yml new file mode 100644 index 0000000..82f9f2c --- /dev/null +++ b/configs/rec/multi_language/rec_samaritan_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_samaritan_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/samaritan_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/samaritan_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/samaritan_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/multi_language/rec_syriac_lite_train.yml b/configs/rec/multi_language/rec_syriac_lite_train.yml new file mode 100644 index 0000000..8d7d22f --- /dev/null +++ b/configs/rec/multi_language/rec_syriac_lite_train.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_syriac_lite + save_epoch_step: 3 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: null + character_dict_path: ppocr/utils/dict/syriac_dict.txt + max_text_length: 25 + infer_mode: false + use_space_char: true +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: L2 + factor: 1.0e-05 +Architecture: + model_type: rec + algorithm: CRNN + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: + - 1 + - 2 + - 2 + - 2 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 1.0e-05 +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/syriac_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: null + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ + label_file_list: + - train_data/syriac_val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - RecResizeImg: + image_shape: + - 3 + - 32 + - 320 + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/rec_d28_can.yml b/configs/rec/rec_d28_can.yml new file mode 100644 index 0000000..7c3b0fd --- /dev/null +++ b/configs/rec/rec_d28_can.yml @@ -0,0 +1,122 @@ +Global: + use_gpu: True + epoch_num: 240 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/can/ + save_epoch_step: 1 + # evaluation is run every 1105 iterations (1 epoch)(batch_size = 8) + eval_batch_step: [0, 1105] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/crohme_demo/hme_00.jpg + # for data or label process + character_dict_path: ppocr/utils/dict/latex_symbol_dict.txt + max_text_length: 36 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_can.txt + +Optimizer: + name: Momentum + momentum: 0.9 + clip_norm_global: 100.0 + lr: + name: TwoStepCosine + learning_rate: 0.01 + warmup_epoch: 1 + weight_decay: 0.0001 + +Architecture: + model_type: rec + algorithm: CAN + in_channels: 1 + Transform: + Backbone: + name: DenseNet + growthRate: 24 + reduction: 0.5 + bottleneck: True + use_dropout: True + input_channel: 1 + Head: + name: CANHead + in_channel: 684 + out_channel: 111 + max_text_length: 36 + ratio: 16 + attdecoder: + is_train: True + input_size: 256 + hidden_size: 256 + encoder_out_channel: 684 + dropout: True + dropout_ratio: 0.5 + word_num: 111 + counting_decoder_out_channel: 111 + attention: + attention_dim: 512 + word_conv_kernel: 1 + +Loss: + name: CANLoss + +PostProcess: + name: CANLabelDecode + +Metric: + name: CANMetric + main_indicator: exp_rate + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/CROHME/training/images/ + label_file_list: ["./train_data/CROHME/training/labels.txt"] + transforms: + - DecodeImage: + channel_first: False + - NormalizeImage: + mean: [0,0,0] + std: [1,1,1] + order: 'hwc' + - GrayImageChannelFormat: + inverse: True + - CANLabelEncode: + lower: False + - KeepKeys: + keep_keys: ['image', 'label'] + loader: + shuffle: True + batch_size_per_card: 8 + drop_last: False + num_workers: 4 + collate_fn: DyMaskCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/CROHME/evaluation/images/ + label_file_list: ["./train_data/CROHME/evaluation/labels.txt"] + transforms: + - DecodeImage: + channel_first: False + - NormalizeImage: + mean: [0,0,0] + std: [1,1,1] + order: 'hwc' + - GrayImageChannelFormat: + inverse: True + - CANLabelEncode: + lower: False + - KeepKeys: + keep_keys: ['image', 'label'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 + num_workers: 4 + collate_fn: DyMaskCollator diff --git a/configs/rec/rec_efficientb3_fpn_pren.yml b/configs/rec/rec_efficientb3_fpn_pren.yml new file mode 100644 index 0000000..0fac6a7 --- /dev/null +++ b/configs/rec/rec_efficientb3_fpn_pren.yml @@ -0,0 +1,92 @@ +Global: + use_gpu: True + epoch_num: 8 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/rec/pren_new + save_epoch_step: 3 + # evaluation is run every 2000 iterations after the 4000th iteration + eval_batch_step: [4000, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: + max_text_length: &max_text_length 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_pren.txt + +Optimizer: + name: Adadelta + lr: + name: Piecewise + decay_epochs: [2, 5, 7] + values: [0.5, 0.1, 0.01, 0.001] + +Architecture: + model_type: rec + algorithm: PREN + in_channels: 3 + Backbone: + name: EfficientNetb3_PREN + Neck: + name: PRENFPN + n_r: 5 + d_model: 384 + max_len: *max_text_length + dropout: 0.1 + Head: + name: PRENHead + +Loss: + name: PRENLoss + +PostProcess: + name: PRENLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - PRENLabelEncode: + - RecAug: + - PRENResizeImg: + image_shape: [64, 256] # h,w + - KeepKeys: + keep_keys: ['image', 'label'] + loader: + shuffle: True + batch_size_per_card: 128 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - PRENLabelEncode: + - PRENResizeImg: + image_shape: [64, 256] # h,w + - KeepKeys: + keep_keys: ['image', 'label'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 64 + num_workers: 8 diff --git a/configs/rec/rec_icdar15_train.yml b/configs/rec/rec_icdar15_train.yml new file mode 100644 index 0000000..893f738 --- /dev/null +++ b/configs/rec/rec_icdar15_train.yml @@ -0,0 +1,99 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/ic15/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: ./ + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/en_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_ic15.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 96 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + use_shared_memory: False + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/configs/rec/rec_mtb_nrtr.yml b/configs/rec/rec_mtb_nrtr.yml new file mode 100644 index 0000000..aa9a347 --- /dev/null +++ b/configs/rec/rec_mtb_nrtr.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 21 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/nrtr/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_nrtr.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.99 + clip_norm: 5.0 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0. + +Architecture: + model_type: rec + algorithm: NRTR + in_channels: 1 + Transform: + Backbone: + name: MTB + cnn_num: 2 + Head: + name: Transformer + d_model: 512 + num_encoder_layers: 6 + beam_size: -1 # When Beam size is greater than 0, it means to use beam search when evaluation. + + +Loss: + name: CELoss + smoothing: True + +PostProcess: + name: NRTRLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - NRTRLabelEncode: # Class handling label + - GrayRecResizeImg: + image_shape: [100, 32] # W H + resize_type: PIL # PIL or OpenCV + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 512 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - NRTRLabelEncode: # Class handling label + - GrayRecResizeImg: + image_shape: [100, 32] # W H + resize_type: PIL # PIL or OpenCV + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/configs/rec/rec_mv3_none_bilstm_ctc.yml b/configs/rec/rec_mv3_none_bilstm_ctc.yml new file mode 100644 index 0000000..9a95092 --- /dev/null +++ b/configs/rec/rec_mv3_none_bilstm_ctc.yml @@ -0,0 +1,95 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/mv3_none_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_none_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 96 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_mv3_none_none_ctc.yml b/configs/rec/rec_mv3_none_none_ctc.yml new file mode 100644 index 0000000..28f0252 --- /dev/null +++ b/configs/rec/rec_mv3_none_none_ctc.yml @@ -0,0 +1,94 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/mv3_none_none_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_none_none_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: Rosetta + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + fc_decay: 0.0004 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/rec_mv3_tps_bilstm_att.yml b/configs/rec/rec_mv3_tps_bilstm_att.yml new file mode 100644 index 0000000..6c347e7 --- /dev/null +++ b/configs/rec/rec_mv3_tps_bilstm_att.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/rec_mv3_tps_bilstm_att/ + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_tps_bilstm_att.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: RARE + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: small + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 96 + Head: + name: AttentionHead + hidden_size: 96 + + +Loss: + name: AttentionLoss + +PostProcess: + name: AttnLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 1 diff --git a/configs/rec/rec_mv3_tps_bilstm_ctc.yml b/configs/rec/rec_mv3_tps_bilstm_ctc.yml new file mode 100644 index 0000000..9d1ebbe --- /dev/null +++ b/configs/rec/rec_mv3_tps_bilstm_ctc.yml @@ -0,0 +1,99 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/mv3_tps_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_tps_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: STARNet + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: small + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 96 + Head: + name: CTCHead + fc_decay: 0.0004 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_r31_robustscanner.yml b/configs/rec/rec_r31_robustscanner.yml new file mode 100644 index 0000000..54b69d4 --- /dev/null +++ b/configs/rec/rec_r31_robustscanner.yml @@ -0,0 +1,109 @@ +Global: + use_gpu: true + epoch_num: 5 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/rec/rec_r31_robustscanner/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/dict90.txt + max_text_length: &max_text_length 40 + infer_mode: False + use_space_char: False + rm_symbol: True + save_res_path: ./output/rec/predicts_robustscanner.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs: [3, 4] + values: [0.001, 0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: RobustScanner + Transform: + Backbone: + name: ResNet31 + init_type: KaimingNormal + Head: + name: RobustScannerHead + enc_outchannles: 128 + hybrid_dec_rnn_layers: 2 + hybrid_dec_dropout: 0 + position_dec_rnn_layers: 2 + start_idx: 91 + mask: True + padding_idx: 92 + encode_value: False + max_text_length: *max_text_length + +Loss: + name: SARLoss + +PostProcess: + name: SARLabelDecode + +Metric: + name: RecMetric + is_filter: True + + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - RobustScannerRecResizeImg: + image_shape: [3, 48, 48, 160] # h:48 w:[48,160] + width_downsample_ratio: 0.25 + max_text_length: *max_text_length + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio', 'word_positons'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 64 + drop_last: True + num_workers: 8 + use_shared_memory: False + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - RobustScannerRecResizeImg: + image_shape: [3, 48, 48, 160] + max_text_length: *max_text_length + width_downsample_ratio: 0.25 + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio', 'word_positons'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 64 + num_workers: 4 + use_shared_memory: False + diff --git a/configs/rec/rec_r31_sar.yml b/configs/rec/rec_r31_sar.yml new file mode 100644 index 0000000..65e7877 --- /dev/null +++ b/configs/rec/rec_r31_sar.yml @@ -0,0 +1,98 @@ +Global: + use_gpu: true + epoch_num: 5 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./sar_rec + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict90.txt + max_text_length: 30 + infer_mode: False + use_space_char: False + rm_symbol: True + save_res_path: ./output/rec/predicts_sar.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs: [3, 4] + values: [0.001, 0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: SAR + Transform: + Backbone: + name: ResNet31 + Head: + name: SARHead + +Loss: + name: SARLoss + +PostProcess: + name: SARLabelDecode + +Metric: + name: RecMetric + + +Train: + dataset: + name: SimpleDataSet + label_file_list: ['./train_data/train_list.txt'] + data_dir: ./train_data/ + ratio_list: 1.0 + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - SARRecResizeImg: + image_shape: [3, 48, 48, 160] # h:48 w:[48,160] + width_downsample_ratio: 0.25 + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 64 + drop_last: True + num_workers: 8 + use_shared_memory: False + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - SARRecResizeImg: + image_shape: [3, 48, 48, 160] + width_downsample_ratio: 0.25 + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 64 + num_workers: 4 + use_shared_memory: False + diff --git a/configs/rec/rec_r32_gaspin_bilstm_att.yml b/configs/rec/rec_r32_gaspin_bilstm_att.yml new file mode 100644 index 0000000..91d3e10 --- /dev/null +++ b/configs/rec/rec_r32_gaspin_bilstm_att.yml @@ -0,0 +1,116 @@ +Global: + use_gpu: True + epoch_num: 6 + log_smooth_window: 50 + print_batch_step: 50 + save_model_dir: ./output/rec/rec_r32_gaspin_bilstm_att/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ./ppocr/utils/dict/spin_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r32_gaspin_bilstm_att.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs: [3, 4, 5] + values: [0.001, 0.0003, 0.00009, 0.000027] + clip_norm: 5 + +Architecture: + model_type: rec + algorithm: SPIN + in_channels: 1 + Transform: + name: GA_SPIN + offsets: True + default_type: 6 + loc_lr: 0.1 + stn: True + Backbone: + name: ResNet32 + out_channels: 512 + Neck: + name: SequenceEncoder + encoder_type: cascadernn + hidden_size: 256 + out_channels: [256, 512] + with_linear: True + Head: + name: SPINAttentionHead + hidden_size: 256 + + +Loss: + name: SPINAttentionLoss + ignore_index: 0 + +PostProcess: + name: SPINLabelDecode + use_space_char: False + + +Metric: + name: RecMetric + main_indicator: acc + is_filter: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SPINLabelEncode: # Class handling label + - SPINRecResizeImg: + image_shape: [100, 32] + interpolation : 2 + mean: [127.5] + std: [127.5] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 8 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SPINLabelEncode: # Class handling label + - SPINRecResizeImg: + image_shape: [100, 32] + interpolation : 2 + mean: [127.5] + std: [127.5] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 2 diff --git a/configs/rec/rec_r34_vd_none_bilstm_ctc.yml b/configs/rec/rec_r34_vd_none_bilstm_ctc.yml new file mode 100644 index 0000000..9fdb5e9 --- /dev/null +++ b/configs/rec/rec_r34_vd_none_bilstm_ctc.yml @@ -0,0 +1,94 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r34_vd_none_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r34_vd_none_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_r34_vd_none_none_ctc.yml b/configs/rec/rec_r34_vd_none_none_ctc.yml new file mode 100644 index 0000000..0af2b2f --- /dev/null +++ b/configs/rec/rec_r34_vd_none_none_ctc.yml @@ -0,0 +1,92 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r34_vd_none_none_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r34_vd_none_none_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: Rosetta + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + fc_decay: 0.0004 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_r34_vd_tps_bilstm_att.yml b/configs/rec/rec_r34_vd_tps_bilstm_att.yml new file mode 100644 index 0000000..8919aae --- /dev/null +++ b/configs/rec/rec_r34_vd_tps_bilstm_att.yml @@ -0,0 +1,100 @@ +Global: + use_gpu: True + epoch_num: 400 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/b3_rare_r34_none_gru/ + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_b3_rare_r34_none_gru.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0.00000 + +Architecture: + model_type: rec + algorithm: RARE + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: large + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 #96 + Head: + name: AttentionHead # AttentionHead + hidden_size: 256 # + l2_decay: 0.00001 + +Loss: + name: AttentionLoss + +PostProcess: + name: AttnLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/rec_r34_vd_tps_bilstm_ctc.yml b/configs/rec/rec_r34_vd_tps_bilstm_ctc.yml new file mode 100644 index 0000000..c21fe61 --- /dev/null +++ b/configs/rec/rec_r34_vd_tps_bilstm_ctc.yml @@ -0,0 +1,98 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r34_vd_tps_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r34_vd_tps_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: STARNet + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: large + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_r45_abinet.yml b/configs/rec/rec_r45_abinet.yml new file mode 100644 index 0000000..db2f28f --- /dev/null +++ b/configs/rec/rec_r45_abinet.yml @@ -0,0 +1,103 @@ +Global: + use_gpu: True + epoch_num: 10 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r45_abinet/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: ./pretrain_models/abinet_vl_pretrained + checkpoints: + save_inference_dir: ./output/rec/r45_abinet/infer + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: &max_text_length 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_abinet.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.99 + clip_norm: 20.0 + lr: + name: Piecewise + decay_epochs: [6] + values: [0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0. + +Architecture: + model_type: rec + algorithm: ABINet + in_channels: 3 + Transform: + Backbone: + name: ResNet45 + Head: + name: ABINetHead + use_lang: True + iter_size: 3 + max_length: *max_text_length + image_size: [ &h 32, &w 128 ] # [ h, w ] + + +Loss: + name: CELoss + ignore_index: &ignore_index 100 # Must be greater than the number of character classes + +PostProcess: + name: ABINetLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetRecAug: + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, *h, *w] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 96 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, *h, *w] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/configs/rec/rec_r45_visionlan.yml b/configs/rec/rec_r45_visionlan.yml new file mode 100644 index 0000000..2501765 --- /dev/null +++ b/configs/rec/rec_r45_visionlan.yml @@ -0,0 +1,106 @@ +Global: + use_gpu: true + epoch_num: 8 + log_smooth_window: 200 + print_batch_step: 200 + save_model_dir: ./output/rec/r45_visionlan + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: True + infer_img: doc/imgs_words/en/word_2.png + # for data or label process + character_dict_path: + max_text_length: &max_text_length 25 + training_step: &training_step LA + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_visionlan.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 20.0 + group_lr: true + training_step: *training_step + lr: + name: Piecewise + decay_epochs: [6] + values: [0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: VisionLAN + Transform: + Backbone: + name: ResNet45 + strides: [2, 2, 2, 1, 1] + Head: + name: VLHead + n_layers: 3 + n_position: 256 + n_dim: 512 + max_text_length: *max_text_length + training_step: *training_step + +Loss: + name: VLLoss + mode: *training_step + weight_res: 0.5 + weight_mas: 0.5 + +PostProcess: + name: VLLabelDecode + +Metric: + name: RecMetric + is_filter: true + + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetRecAug: + - VLLabelEncode: # Class handling label + - VLRecResizeImg: + image_shape: [3, 64, 256] + - KeepKeys: + keep_keys: ['image', 'label', 'label_res', 'label_sub', 'label_id', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 220 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VLLabelEncode: # Class handling label + - VLRecResizeImg: + image_shape: [3, 64, 256] + - KeepKeys: + keep_keys: ['image', 'label', 'label_res', 'label_sub', 'label_id', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 64 + num_workers: 4 + diff --git a/configs/rec/rec_r50_fpn_srn.yml b/configs/rec/rec_r50_fpn_srn.yml new file mode 100644 index 0000000..b685362 --- /dev/null +++ b/configs/rec/rec_r50_fpn_srn.yml @@ -0,0 +1,106 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/rec/srn_new + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 5000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: + max_text_length: 25 + num_heads: 8 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_srn.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 10.0 + lr: + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: SRN + in_channels: 1 + Transform: + Backbone: + name: ResNetFPN + Head: + name: SRNHead + max_text_length: 25 + num_heads: 8 + num_encoder_TUs: 2 + num_decoder_TUs: 4 + hidden_dims: 512 + +Loss: + name: SRNLoss + +PostProcess: + name: SRNLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SRNLabelEncode: # Class handling label + - SRNRecResizeImg: + image_shape: [1, 64, 256] + - KeepKeys: + keep_keys: ['image', + 'label', + 'length', + 'encoder_word_pos', + 'gsrm_word_pos', + 'gsrm_slf_attn_bias1', + 'gsrm_slf_attn_bias2'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 64 + drop_last: False + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SRNLabelEncode: # Class handling label + - SRNRecResizeImg: + image_shape: [1, 64, 256] + - KeepKeys: + keep_keys: ['image', + 'label', + 'length', + 'encoder_word_pos', + 'gsrm_word_pos', + 'gsrm_slf_attn_bias1', + 'gsrm_slf_attn_bias2'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 32 + num_workers: 4 diff --git a/configs/rec/rec_resnet_rfl_att.yml b/configs/rec/rec_resnet_rfl_att.yml new file mode 100644 index 0000000..b9fb741 --- /dev/null +++ b/configs/rec/rec_resnet_rfl_att.yml @@ -0,0 +1,112 @@ +Global: + use_gpu: True + epoch_num: 6 + log_smooth_window: 20 + print_batch_step: 50 + save_model_dir: ./output/rec/rec_resnet_rfl_att/ + save_epoch_step: 1 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 5000] + cal_metric_during_train: True + pretrained_model: ./pretrain_models/rec_resnet_rfl_visual/best_accuracy.pdparams + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/rec_resnet_rfl.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.0 + clip_norm_global: 5.0 + lr: + name: Piecewise + decay_epochs : [3, 4, 5] + values : [0.001, 0.0003, 0.00009, 0.000027] + +Architecture: + model_type: rec + algorithm: RFL + in_channels: 1 + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 1.0 + model_name: large + Backbone: + name: ResNetRFL + use_cnt: True + use_seq: True + Neck: + name: RFAdaptor + use_v2s: True + use_s2v: True + Head: + name: RFLHead + in_channels: 512 + hidden_size: 256 + batch_max_legnth: 25 + out_channels: 38 + use_cnt: True + use_seq: True + +Loss: + name: RFLLoss + # ignore_index: 0 + +PostProcess: + name: RFLLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RFLLabelEncode: # Class handling label + - RFLRecResizeImg: + image_shape: [1, 32, 100] + padding: false + interpolation: 2 + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'cnt_label'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 64 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/validation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RFLLabelEncode: # Class handling label + - RFLRecResizeImg: + image_shape: [1, 32, 100] + padding: false + interpolation: 2 + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'cnt_label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/rec_resnet_rfl_visual.yml b/configs/rec/rec_resnet_rfl_visual.yml new file mode 100644 index 0000000..5eaea08 --- /dev/null +++ b/configs/rec/rec_resnet_rfl_visual.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: True + epoch_num: 6 + log_smooth_window: 20 + print_batch_step: 50 + save_model_dir: ./output/rec/rec_resnet_rfl_visual/ + save_epoch_step: 1 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 5000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/rec_resnet_rfl_visual.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.0 + clip_norm_global: 5.0 + lr: + name: Piecewise + decay_epochs : [3, 4, 5] + values : [0.001, 0.0003, 0.00009, 0.000027] + +Architecture: + model_type: rec + algorithm: RFL + in_channels: 1 + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 1.0 + model_name: large + Backbone: + name: ResNetRFL + use_cnt: True + use_seq: False + Neck: + name: RFAdaptor + use_v2s: False + use_s2v: False + Head: + name: RFLHead + in_channels: 512 + hidden_size: 256 + batch_max_legnth: 25 + out_channels: 38 + use_cnt: True + use_seq: False +Loss: + name: RFLLoss + +PostProcess: + name: RFLLabelDecode + +Metric: + name: CNTMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RFLLabelEncode: # Class handling label + - RFLRecResizeImg: + image_shape: [1, 32, 100] + padding: false + interpolation: 2 + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'cnt_label'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 64 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RFLLabelEncode: # Class handling label + - RFLRecResizeImg: + image_shape: [1, 32, 100] + padding: false + interpolation: 2 + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'cnt_label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/configs/rec/rec_resnet_stn_bilstm_att.yml b/configs/rec/rec_resnet_stn_bilstm_att.yml new file mode 100644 index 0000000..20ed953 --- /dev/null +++ b/configs/rec/rec_resnet_stn_bilstm_att.yml @@ -0,0 +1,108 @@ +Global: + use_gpu: True + epoch_num: 6 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/seed + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 100 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_seed.txt + + +Optimizer: + name: Adadelta + weight_deacy: 0.0 + momentum: 0.9 + lr: + name: Piecewise + decay_epochs: [4, 5] + values: [1.0, 0.1, 0.01] + regularizer: + name: 'L2' + factor: 2.0e-05 + + +Architecture: + model_type: rec + algorithm: SEED + Transform: + name: STN_ON + tps_inputsize: [32, 64] + tps_outputsize: [32, 100] + num_control_points: 20 + tps_margins: [0.05,0.05] + stn_activation: none + Backbone: + name: ResNet_ASTER + Head: + name: AsterHead # AttentionHead + sDim: 512 + attDim: 512 + max_len_labels: 100 + +Loss: + name: AsterLoss + +PostProcess: + name: SEEDLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + is_filter: True + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - Fasttext: + path: "./cc.en.300.bin" + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SEEDLabelEncode: # Class handling label + - RecResizeImg: + character_dict_path: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'fast_label'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 6 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SEEDLabelEncode: # Class handling label + - RecResizeImg: + character_dict_path: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: True + batch_size_per_card: 256 + num_workers: 4 diff --git a/configs/rec/rec_satrn.yml b/configs/rec/rec_satrn.yml new file mode 100644 index 0000000..376c2cc --- /dev/null +++ b/configs/rec/rec_satrn.yml @@ -0,0 +1,116 @@ +Global: + use_gpu: true + epoch_num: 5 + log_smooth_window: 20 + print_batch_step: 50 + save_model_dir: ./output/rec/rec_satrn/ + save_epoch_step: 1 + # evaluation is run every 5000 iterations + eval_batch_step: [0, 5000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict90.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + rm_symbol: True + save_res_path: ./output/rec/predicts_satrn.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs: [3, 4] + values: [0.0003, 0.00003, 0.000003] + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: SATRN + Backbone: + name: ShallowCNN + in_channels: 3 + hidden_dim: 256 + Head: + name: SATRNHead + enc_cfg: + n_layers: 6 + n_head: 8 + d_k: 32 + d_v: 32 + d_model: 256 + n_position: 100 + d_inner: 1024 + dropout: 0.1 + dec_cfg: + n_layers: 6 + d_embedding: 256 + n_head: 8 + d_model: 256 + d_inner: 1024 + d_k: 32 + d_v: 32 + max_seq_len: 25 + start_idx: 91 + +Loss: + name: SATRNLoss + +PostProcess: + name: SATRNLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SATRNLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 32, 100] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 128 + drop_last: True + num_workers: 8 + use_shared_memory: False + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SATRNLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 32, 100] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio'] # dataloader will return list in this order + + loader: + shuffle: False + drop_last: False + batch_size_per_card: 128 + num_workers: 4 + use_shared_memory: False diff --git a/configs/rec/rec_svtrnet.yml b/configs/rec/rec_svtrnet.yml new file mode 100644 index 0000000..4657c5c --- /dev/null +++ b/configs/rec/rec_svtrnet.yml @@ -0,0 +1,118 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_svtr_tiny.txt + d2s_train_image_shape: [3, 64, 256] + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 + +Architecture: + model_type: rec + algorithm: SVTR + Transform: + name: STN_ON + tps_inputsize: [32, 64] + tps_outputsize: [32, 100] + num_control_points: 20 + tps_margins: [0.05,0.05] + stn_activation: none + Backbone: + name: SVTRNet + img_size: [32, 100] + out_char_num: 25 # W//4 or W//8 or W/12 + out_channels: 192 + patch_merging: 'Conv' + embed_dim: [64, 128, 256] + depth: [3, 6, 3] + num_heads: [2, 4, 8] + mixer: ['Local','Local','Local','Local','Local','Local','Global','Global','Global','Global','Global','Global'] + local_mixer: [[7, 11], [7, 11], [7, 11]] + last_stage: True + prenorm: False + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SVTRRecAug: + aug_type: 0 # or 1 + - CTCLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 512 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/configs/rec/rec_svtrnet_ch.yml b/configs/rec/rec_svtrnet_ch.yml new file mode 100644 index 0000000..bb82cfc --- /dev/null +++ b/configs/rec/rec_svtrnet_ch.yml @@ -0,0 +1,156 @@ +Global: + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr_ch_all/ + save_epoch_step: 10 + eval_batch_step: + - 0 + - 2000 + cal_metric_during_train: true + pretrained_model: null + checkpoints: null + save_inference_dir: null + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + save_res_path: ./output/rec/predicts_svtr_tiny_ch_all.txt + d2s_train_image_shape: [3, 32, 320] +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.0e-08 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed + one_dim_param_no_weight_decay: true + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 +Architecture: + model_type: rec + algorithm: SVTR + Transform: null + Backbone: + name: SVTRNet + img_size: + - 32 + - 320 + out_char_num: 40 # W//4 or W//8 or W/12 + out_channels: 96 + patch_merging: Conv + embed_dim: + - 64 + - 128 + - 256 + depth: + - 3 + - 6 + - 3 + num_heads: + - 2 + - 4 + - 8 + mixer: + - Local + - Local + - Local + - Local + - Local + - Local + - Global + - Global + - Global + - Global + - Global + - Global + local_mixer: + - - 7 + - 11 + - - 7 + - 11 + - - 7 + - 11 + last_stage: true + prenorm: false + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead +Loss: + name: CTCLoss +PostProcess: + name: CTCLabelDecode +Metric: + name: RecMetric + main_indicator: acc +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/train_list.txt + ext_op_transform_idx: 1 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: + - 32 + - 320 + - 3 + - RecAug: null + - CTCLabelEncode: null + - SVTRRecResizeImg: + image_shape: + - 3 + - 32 + - 320 + padding: true + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 256 + drop_last: true + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data + label_file_list: + - ./train_data/val_list.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: null + - SVTRRecResizeImg: + image_shape: + - 3 + - 32 + - 320 + padding: true + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 256 + num_workers: 2 +profiler_options: null diff --git a/configs/rec/rec_svtrnet_cppd_base_ch.yml b/configs/rec/rec_svtrnet_cppd_base_ch.yml new file mode 100644 index 0000000..e2ed04c --- /dev/null +++ b/configs/rec/rec_svtrnet_cppd_base_ch.yml @@ -0,0 +1,113 @@ +Global: + use_gpu: True + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr_cppd_base_ch/ + save_epoch_step: 10 + # evaluation is run every 2000 iterations after the 0th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_svtr_cppd_base_ch.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed char_node_embed pos_node_embed char_pos_embed vis_pos_embed + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.0005 # 4gpus 128bs + warmup_epoch: 5 + +Architecture: + model_type: rec + algorithm: CPPD + Transform: + Backbone: + name: SVTRNet + img_size: [32, 256] + patch_merging: 'Conv' + embed_dim: [128, 256, 384] + depth: [6, 6, 4] + num_heads: [4, 8, 12] + mixer: ['Conv','Conv','Conv','Conv','Conv','Conv', 'Conv','Conv', 'Global','Global','Global','Global','Global','Global','Global','Global','Global','Global'] + local_mixer: [[5, 5], [5, 5], [5, 5]] + last_stage: False + prenorm: True + Head: + name: CPPDHead + dim: 384 + vis_seq: 128 + ch: &ch True + +Loss: + name: CPPDLoss + ignore_index: &ignore_index 7000 # must be greater than the number of character classes + smoothing: True + sideloss_weight: 1.0 + +PostProcess: + name: CPPDLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CPPDLabelEncode: # Class handling label + ignore_index: *ignore_index + ch: *ch + - SVTRRecResizeImg: + image_shape: [3, 32, 256] + padding: True + - KeepKeys: + keep_keys: ['image', 'label', 'label_node', 'label_index', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CPPDLabelEncode: # Class handling label + ignore_index: *ignore_index + ch: *ch + - SVTRRecResizeImg: + image_shape: [3, 32, 256] + padding: True + - KeepKeys: + keep_keys: ['image', 'label', 'label_node','label_index','length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/configs/rec/rec_svtrnet_cppd_base_en.yml b/configs/rec/rec_svtrnet_cppd_base_en.yml new file mode 100644 index 0000000..99885fb --- /dev/null +++ b/configs/rec/rec_svtrnet_cppd_base_en.yml @@ -0,0 +1,112 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr_cppd_base/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_svtr_cppd_base.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 1.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed char_node_embed pos_node_embed char_pos_embed vis_pos_embed + one_dim_param_no_weight_decay: True + lr: + name: Cosine + learning_rate: 0.0005 # 4gpus 256bs + warmup_epoch: 2 + +Architecture: + model_type: rec + algorithm: CPPD + Transform: + Backbone: + name: SVTRNet + img_size: [32, 100] + patch_merging: 'Conv' + embed_dim: [128, 256, 384] + depth: [6, 6, 4] + num_heads: [4, 8, 12] + mixer: ['Conv','Conv','Conv','Conv','Conv','Conv', 'Conv','Conv', 'Global','Global','Global','Global','Global','Global','Global','Global','Global','Global'] + local_mixer: [[5, 5], [5, 5], [5, 5]] + last_stage: False + prenorm: True + Head: + name: CPPDHead + dim: 384 + vis_seq: 50 + num_layer: 3 + +Loss: + name: CPPDLoss + ignore_index: &ignore_index 100 # must be greater than the number of character classes + smoothing: True + sideloss_weight: 1.0 + +PostProcess: + name: CPPDLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CPPDLabelEncode: # Class handling label + ignore_index: *ignore_index + - SVTRRecResizeImg: + image_shape: [3, 32, 100] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'label_node', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CPPDLabelEncode: # Class handling label + ignore_index: *ignore_index + - SVTRRecResizeImg: + image_shape: [3, 32, 100] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'label_node','length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/configs/rec/rec_vit_parseq.yml b/configs/rec/rec_vit_parseq.yml new file mode 100644 index 0000000..8ba99e6 --- /dev/null +++ b/configs/rec/rec_vit_parseq.yml @@ -0,0 +1,116 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/rec/parseq + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 500] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/dict/parseq_dict.txt + character_type: en + max_text_length: 25 + num_heads: 8 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_parseq.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: OneCycle + max_lr: 0.0007 + +Architecture: + model_type: rec + algorithm: ParseQ + in_channels: 3 + Transform: + Backbone: + name: ViTParseQ + img_size: [32, 128] + patch_size: [4, 8] + embed_dim: 384 + depth: 12 + num_heads: 6 + mlp_ratio: 4 + in_channels: 3 + Head: + name: ParseQHead + # Architecture + max_text_length: 25 + embed_dim: 384 + dec_num_heads: 12 + dec_mlp_ratio: 4 + dec_depth: 1 + # Training + perm_num: 6 + perm_forward: true + perm_mirrored: true + dropout: 0.1 + # Decoding mode (test) + decode_ar: true + refine_iters: 1 + +Loss: + name: ParseQLoss + +PostProcess: + name: ParseQLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + is_filter: True + +Train: + dataset: + name: LMDBDataSet + data_dir: + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ParseQRecAug: + aug_type: 0 # or 1 + - ParseQLabelEncode: + - SVTRRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 192 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSet + data_dir: + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ParseQLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 384 + num_workers: 4 diff --git a/configs/rec/rec_vitstr_none_ce.yml b/configs/rec/rec_vitstr_none_ce.yml new file mode 100644 index 0000000..ebe304f --- /dev/null +++ b/configs/rec/rec_vitstr_none_ce.yml @@ -0,0 +1,102 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/vitstr_none_ce/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration# + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_vitstr.txt + + +Optimizer: + name: Adadelta + epsilon: 1.e-8 + rho: 0.95 + clip_norm: 5.0 + lr: + learning_rate: 1.0 + +Architecture: + model_type: rec + algorithm: ViTSTR + in_channels: 1 + Transform: + Backbone: + name: ViTSTR + scale: tiny + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CELoss + with_all: True + ignore_index: &ignore_index 0 # Must be zero or greater than the number of character classes + +PostProcess: + name: ViTSTRLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/training/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSet + data_dir: ./train_data/data_lmdb_release/evaluation/ + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/configs/sr/sr_telescope.yml b/configs/sr/sr_telescope.yml new file mode 100644 index 0000000..ed25725 --- /dev/null +++ b/configs/sr/sr_telescope.yml @@ -0,0 +1,83 @@ +Global: + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/sr/sr_telescope/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: ./output/sr/sr_telescope/infer + use_visualdl: False + infer_img: doc/imgs_words_en/word_52.png + # for data or label process + character_dict_path: + max_text_length: 100 + infer_mode: False + use_space_char: False + save_res_path: ./output/sr/predicts_telescope.txt + +Optimizer: + name: Adam + beta1: 0.5 + beta2: 0.999 + clip_norm: 0.25 + lr: + learning_rate: 0.0001 + +Architecture: + model_type: sr + algorithm: Telescope + Transform: + name: TBSRN + STN: True + infer_mode: False + +Loss: + name: TelescopeLoss + confuse_dict_path: ./ppocr/utils/dict/confuse.pkl + + +PostProcess: + name: None + +Metric: + name: SRMetric + main_indicator: all + +Train: + dataset: + name: LMDBDataSetSR + data_dir: ./train_data/TextZoom/train + transforms: + - SRResize: + imgH: 32 + imgW: 128 + down_sample_scale: 2 + - KeepKeys: + keep_keys: ['img_lr', 'img_hr', 'label'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 16 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSetSR + data_dir: ./train_data/TextZoom/test + transforms: + - SRResize: + imgH: 32 + imgW: 128 + down_sample_scale: 2 + - KeepKeys: + keep_keys: ['img_lr', 'img_hr', 'label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 16 + num_workers: 4 diff --git a/configs/sr/sr_tsrn_transformer_strock.yml b/configs/sr/sr_tsrn_transformer_strock.yml new file mode 100644 index 0000000..627bf24 --- /dev/null +++ b/configs/sr/sr_tsrn_transformer_strock.yml @@ -0,0 +1,84 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/sr/sr_tsrn_transformer_strock/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: sr_output + use_visualdl: False + infer_img: doc/imgs_words_en/word_52.png + # for data or label process + character_dict_path: ./train_data/srdata/english_decomposition.txt + max_text_length: 100 + infer_mode: False + use_space_char: False + save_res_path: ./output/sr/predicts_gestalt.txt + +Optimizer: + name: Adam + beta1: 0.5 + beta2: 0.999 + clip_norm: 0.25 + lr: + learning_rate: 0.0001 + +Architecture: + model_type: sr + algorithm: Gestalt + Transform: + name: TSRN + STN: True + infer_mode: False + +Loss: + name: StrokeFocusLoss + character_dict_path: ./train_data/srdata/english_decomposition.txt + +PostProcess: + name: None + +Metric: + name: SRMetric + main_indicator: all + +Train: + dataset: + name: LMDBDataSetSR + data_dir: ./train_data/srdata/train + transforms: + - SRResize: + imgH: 32 + imgW: 128 + down_sample_scale: 2 + - SRLabelEncode: # Class handling label + - KeepKeys: + keep_keys: ['img_lr', 'img_hr', 'length', 'input_tensor', 'label'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 16 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSetSR + data_dir: ./train_data/srdata/test + transforms: + - SRResize: + imgH: 32 + imgW: 128 + down_sample_scale: 2 + - SRLabelEncode: # Class handling label + - KeepKeys: + keep_keys: ['img_lr', 'img_hr','length', 'input_tensor', 'label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 16 + num_workers: 4 diff --git a/configs/table/SLANeXt_wired.yml b/configs/table/SLANeXt_wired.yml new file mode 100644 index 0000000..b6d5f6a --- /dev/null +++ b/configs/table/SLANeXt_wired.yml @@ -0,0 +1,181 @@ +Global: + model_name: SLANeXt_wired # To use static model for inference. + use_gpu: true + epoch_num: 400 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/SLANeXt_wired + save_epoch_step: 400 + eval_batch_step: + - 0 + - 331 + cal_metric_during_train: true + pretrained_model: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/SLANeXt_wired_pretrained.pdparams" + checkpoints: null + save_inference_dir: ./output/SLANeXt_wired/infer + use_visualdl: false + infer_img: ppstructure/docs/table/table.jpg + character_dict_path: ppocr/utils/dict/table_structure_dict_ch.txt + character_type: en + max_text_length: 500 + box_format: xyxyxyxy + infer_mode: false + use_sync_bn: true + save_res_path: output/infer + d2s_train_image_shape: [3, 512, 512] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + name: Cosine + learning_rate: 0.0001 + warmup_epoch: 1 + regularizer: + name: L2 + factor: 0.0 + +Architecture: + model_type: table + algorithm: SLANeXt + Backbone: + name: Vary_VIT_B + image_size: 512 + encoder_embed_dim: 768 + encoder_depth: 12 + encoder_num_heads: 12 + encoder_global_attn_indexes: [2, 5, 8, 11] + Head: + name: SLAHead + hidden_size: 512 + max_text_length: 500 + loc_reg_num: 8 + +Loss: + name: SLALoss + structure_weight: 1.0 + # SLANeXt does not train the cell location task by default, set the loc_weight if needed. + loc_weight: 0.0 + loc_loss: smooth_l1 + +PostProcess: + name: TableLabelDecode + merge_no_span_structure: true + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: false + loc_reg_num: 8 + box_format: xyxyxyxy + del_thead_tbody: true + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/train/ + label_file_list: + - train_data/table/train.txt + ratio_list: + - 1 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - TableLabelEncode: + learn_empty_box: false + merge_no_span_structure: true + replace_empty_cell_token: false + loc_reg_num: 8 + max_text_length: 500 + - TableBoxEncode: + in_box_format: xyxyxyxy + out_box_format: xyxyxyxy + - ResizeTableImage: + max_len: 512 + resize_bboxes: true + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - PaddingTableImage: + size: + - 512 + - 512 + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - structure + - bboxes + - bbox_masks + - length + - shape + loader: + shuffle: true + batch_size_per_card: 48 + drop_last: true + num_workers: 1 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/val/ + label_file_list: + - train_data/table/val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - TableLabelEncode: + learn_empty_box: false + merge_no_span_structure: true + replace_empty_cell_token: false + loc_reg_num: 8 + max_text_length: 500 + - TableBoxEncode: + in_box_format: xyxyxyxy + out_box_format: xyxyxyxy + - ResizeTableImage: + max_len: 512 + resize_bboxes: true + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - PaddingTableImage: + size: + - 512 + - 512 + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - structure + - bboxes + - bbox_masks + - length + - shape + loader: + shuffle: false + drop_last: false + batch_size_per_card: 48 + num_workers: 1 + +profiler_options: null diff --git a/configs/table/SLANeXt_wireless.yml b/configs/table/SLANeXt_wireless.yml new file mode 100644 index 0000000..9d18ba9 --- /dev/null +++ b/configs/table/SLANeXt_wireless.yml @@ -0,0 +1,181 @@ +Global: + model_name: SLANeXt_wireless # To use static model for inference. + use_gpu: true + epoch_num: 400 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/SLANeXt_wireless + save_epoch_step: 400 + eval_batch_step: + - 0 + - 331 + cal_metric_during_train: true + pretrained_model: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/SLANeXt_wireless_pretrained.pdparams" + checkpoints: null + save_inference_dir: ./output/SLANeXt_wireless/infer + use_visualdl: false + infer_img: ppstructure/docs/table/table.jpg + character_dict_path: ppocr/utils/dict/table_structure_dict_ch.txt + character_type: en + max_text_length: 500 + box_format: xyxyxyxy + infer_mode: false + use_sync_bn: true + save_res_path: output/infer + d2s_train_image_shape: [3, 512, 512] + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + name: Cosine + learning_rate: 0.0001 + warmup_epoch: 1 + regularizer: + name: L2 + factor: 0.0 + +Architecture: + model_type: table + algorithm: SLANeXt + Backbone: + name: Vary_VIT_B + image_size: 512 + encoder_embed_dim: 768 + encoder_depth: 12 + encoder_num_heads: 12 + encoder_global_attn_indexes: [2, 5, 8, 11] + Head: + name: SLAHead + hidden_size: 512 + max_text_length: 500 + loc_reg_num: 8 + +Loss: + name: SLALoss + structure_weight: 1.0 + # SLANeXt does not train the cell location task by default, set the loc_weight if needed. + loc_weight: 0.0 + loc_loss: smooth_l1 + +PostProcess: + name: TableLabelDecode + merge_no_span_structure: true + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: false + loc_reg_num: 8 + box_format: xyxyxyxy + del_thead_tbody: true + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/train/ + label_file_list: + - train_data/table/train.txt + ratio_list: + - 1 + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - TableLabelEncode: + learn_empty_box: false + merge_no_span_structure: true + replace_empty_cell_token: false + loc_reg_num: 8 + max_text_length: 500 + - TableBoxEncode: + in_box_format: xyxyxyxy + out_box_format: xyxyxyxy + - ResizeTableImage: + max_len: 512 + resize_bboxes: true + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - PaddingTableImage: + size: + - 512 + - 512 + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - structure + - bboxes + - bbox_masks + - length + - shape + loader: + shuffle: true + batch_size_per_card: 48 + drop_last: true + num_workers: 1 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/val/ + label_file_list: + - train_data/table/val.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - TableLabelEncode: + learn_empty_box: false + merge_no_span_structure: true + replace_empty_cell_token: false + loc_reg_num: 8 + max_text_length: 500 + - TableBoxEncode: + in_box_format: xyxyxyxy + out_box_format: xyxyxyxy + - ResizeTableImage: + max_len: 512 + resize_bboxes: true + - NormalizeImage: + scale: 1./255. + mean: + - 0.485 + - 0.456 + - 0.406 + std: + - 0.229 + - 0.224 + - 0.225 + order: hwc + - PaddingTableImage: + size: + - 512 + - 512 + - ToCHWImage: null + - KeepKeys: + keep_keys: + - image + - structure + - bboxes + - bbox_masks + - length + - shape + loader: + shuffle: false + drop_last: false + batch_size_per_card: 48 + num_workers: 1 + +profiler_options: null diff --git a/configs/table/SLANet.yml b/configs/table/SLANet.yml new file mode 100644 index 0000000..05da5a9 --- /dev/null +++ b/configs/table/SLANet.yml @@ -0,0 +1,146 @@ +Global: + model_name: SLANet # To use static model for inference. + use_gpu: true + epoch_num: 100 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/SLANet + save_epoch_step: 400 + # evaluation is run every 1000 iterations after the 0th iteration + eval_batch_step: [0, 1000] + cal_metric_during_train: True + pretrained_model: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/SLANet_pretrained.pdparams" + checkpoints: + save_inference_dir: ./output/SLANet/infer + use_visualdl: False + infer_img: ppstructure/docs/table/table.jpg + # for data or label process + character_dict_path: ppocr/utils/dict/table_structure_dict.txt + character_type: en + max_text_length: &max_text_length 500 + box_format: &box_format 'xyxy' # 'xywh', 'xyxy', 'xyxyxyxy' + infer_mode: False + use_sync_bn: True + save_res_path: 'output/infer' + d2s_train_image_shape: [3, -1, -1] + amp_custom_white_list: ['concat', 'elementwise_sub', 'set_value'] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + name: Piecewise + learning_rate: 0.001 + decay_epochs : [40, 50] + values : [0.001, 0.0001, 0.00005] + regularizer: + name: 'L2' + factor: 0.00000 + +Architecture: + model_type: table + algorithm: SLANet + Backbone: + name: PPLCNet + scale: 1.0 + pretrained: true + use_ssld: true + Neck: + name: CSPPAN + out_channels: 96 + Head: + name: SLAHead + hidden_size: 256 + max_text_length: *max_text_length + loc_reg_num: &loc_reg_num 4 + +Loss: + name: SLALoss + structure_weight: 1.0 + loc_weight: 2.0 + loc_loss: smooth_l1 + +PostProcess: + name: TableLabelDecode + merge_no_span_structure: &merge_no_span_structure True + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: False + loc_reg_num: *loc_reg_num + box_format: *box_format + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/train/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_train.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'structure', 'bboxes', 'bbox_masks', 'length', 'shape'] + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 1 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/val/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_val.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'structure', 'bboxes', 'bbox_masks', 'length', 'shape'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 48 + num_workers: 1 diff --git a/configs/table/SLANet_lcnetv2.yml b/configs/table/SLANet_lcnetv2.yml new file mode 100644 index 0000000..df11acf --- /dev/null +++ b/configs/table/SLANet_lcnetv2.yml @@ -0,0 +1,139 @@ +Global: + use_gpu: true + epoch_num: 50 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/SLANet_lcnetv2 + save_epoch_step: 20 + # evaluation is run every 1000 iterations after the 0th iteration + eval_batch_step: [0, 1000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: ./SLANet_lcnetv2_infer + use_visualdl: False + infer_img: ppstructure/docs/table/table.jpg + # for data or label process + character_dict_path: ppocr/utils/dict/table_structure_dict.txt + character_type: en + max_text_length: &max_text_length 500 + box_format: &box_format 'xyxy' # 'xywh', 'xyxy', 'xyxyxyxy' + infer_mode: False + use_sync_bn: True + save_res_path: 'output/infer' + d2s_train_image_shape: [3, -1, -1] + amp_custom_white_list: ['concat', 'elementwise_sub', 'set_value'] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00000 + +Architecture: + model_type: table + algorithm: SLANet + Backbone: + name: PPLCNetV2_base + Neck: + name: CSPPAN + out_channels: 96 + Head: + name: SLAHead + hidden_size: 256 + max_text_length: *max_text_length + loc_reg_num: &loc_reg_num 4 + +Loss: + name: SLALoss + structure_weight: 1.0 + loc_weight: 2.0 + loc_loss: smooth_l1 + +PostProcess: + name: TableLabelDecode + merge_no_span_structure: &merge_no_span_structure True + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: False + loc_reg_num: *loc_reg_num + box_format: *box_format + +Train: + dataset: + name: PubTabDataSet + data_dir: ../table_data/pubtabnet/train/ + label_file_list: [../table_data/pubtabnet/PubTabNet_2.0.0_train.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'structure', 'bboxes', 'bbox_masks', 'length', 'shape' ] + loader: + shuffle: True + batch_size_per_card: 24 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: PubTabDataSet + data_dir: ../table_data/pubtabnet/val/ + label_file_list: [../table_data/pubtabnet/PubTabNet_2.0.0_val.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'structure', 'bboxes', 'bbox_masks', 'shape' ] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 48 + num_workers: 4 diff --git a/configs/table/SLANet_plus.yml b/configs/table/SLANet_plus.yml new file mode 100644 index 0000000..e67ce66 --- /dev/null +++ b/configs/table/SLANet_plus.yml @@ -0,0 +1,143 @@ +Global: + model_name: SLANet_plus # To use static model for inference. + use_gpu: True + epoch_num: 400 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/SLANet_plus + save_epoch_step: 400 + # evaluation is run every 331 iterations after the 0th iteration + eval_batch_step: [0, 331] + cal_metric_during_train: True + pretrained_model: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/SLANet_plus_pretrained.pdparams" + checkpoints: + save_inference_dir: ./output/SLANet_plus/infer + use_visualdl: False + infer_img: ppstructure/docs/table/table.jpg + # for data or label process + character_dict_path: ppocr/utils/dict/table_structure_dict_ch.txt + character_type: en + max_text_length: &max_text_length 500 + box_format: &box_format xyxyxyxy # 'xywh', 'xyxy', 'xyxyxyxy' + infer_mode: False + use_sync_bn: True + save_res_path: output/infer + d2s_train_image_shape: [3, 488, 488] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00000 + +Architecture: + model_type: table + algorithm: SLANet + Backbone: + name: PPLCNet + scale: 1.0 + pretrained: True + use_ssld: True + Neck: + name: CSPPAN + out_channels: 96 + Head: + name: SLAHead + hidden_size: 256 + max_text_length: *max_text_length + loc_reg_num: &loc_reg_num 8 + +Loss: + name: SLALoss + structure_weight: 1.0 + loc_weight: 2.0 + loc_loss: smooth_l1 + +PostProcess: + name: TableLabelDecode + merge_no_span_structure: &merge_no_span_structure True + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: False + loc_reg_num: *loc_reg_num + box_format: *box_format + del_thead_tbody: True + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/train/ + label_file_list: [train_data/table/train.txt] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'structure', 'bboxes', 'bbox_masks', 'length', 'shape'] + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 1 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/val/ + label_file_list: [train_data/table/val.txt] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'structure', 'bboxes', 'bbox_masks', 'length', 'shape'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 48 + num_workers: 1 diff --git a/configs/table/table_master.yml b/configs/table/table_master.yml new file mode 100755 index 0000000..37cf44d --- /dev/null +++ b/configs/table/table_master.yml @@ -0,0 +1,145 @@ +Global: + use_gpu: true + epoch_num: 17 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/table_master/ + save_epoch_step: 17 + eval_batch_step: [0, 6259] + cal_metric_during_train: true + pretrained_model: null + checkpoints: + save_inference_dir: output/table_master/infer + use_visualdl: false + infer_img: ppstructure/docs/table/table.jpg + save_res_path: ./output/table_master + character_dict_path: ppocr/utils/dict/table_master_structure_dict.txt + infer_mode: false + max_text_length: &max_text_length 500 + box_format: &box_format 'xywh' # 'xywh', 'xyxy', 'xyxyxyxy' + d2s_train_image_shape: [3, 480, 480] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: MultiStepDecay + learning_rate: 0.001 + milestones: [12, 15] + gamma: 0.1 + warmup_epoch: 0.02 + regularizer: + name: L2 + factor: 0.0 + +Architecture: + model_type: table + algorithm: TableMaster + Backbone: + name: TableResNetExtra + gcb_config: + ratio: 0.0625 + headers: 1 + att_scale: False + fusion_type: channel_add + layers: [False, True, True, True] + layers: [1,2,5,3] + Head: + name: TableMasterHead + hidden_size: 512 + headers: 8 + dropout: 0 + d_ff: 2024 + max_text_length: *max_text_length + loc_reg_num: &loc_reg_num 4 + +Loss: + name: TableMasterLoss + ignore_index: 42 # set to len of dict + 3 + +PostProcess: + name: TableMasterLabelDecode + box_shape: pad + merge_no_span_structure: &merge_no_span_structure True + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: False + box_format: *box_format + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/train/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_train.jsonl] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - TableMasterLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: True + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - ResizeTableImage: + max_len: 480 + resize_bboxes: True + - PaddingTableImage: + size: [480, 480] + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - NormalizeImage: + scale: 1./255. + mean: [0.5, 0.5, 0.5] + std: [0.5, 0.5, 0.5] + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: [image, structure, bboxes, bbox_masks, shape] + loader: + shuffle: True + batch_size_per_card: 10 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/val/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_val.jsonl] + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - TableMasterLabelEncode: + learn_empty_box: False + merge_no_span_structure: *merge_no_span_structure + replace_empty_cell_token: True + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - ResizeTableImage: + max_len: 480 + resize_bboxes: True + - PaddingTableImage: + size: [480, 480] + - TableBoxEncode: + in_box_format: *box_format + out_box_format: *box_format + - NormalizeImage: + scale: 1./255. + mean: [0.5, 0.5, 0.5] + std: [0.5, 0.5, 0.5] + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: [image, structure, bboxes, bbox_masks, shape] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 10 + num_workers: 8 diff --git a/configs/table/table_mv3.yml b/configs/table/table_mv3.yml new file mode 100755 index 0000000..50c8439 --- /dev/null +++ b/configs/table/table_mv3.yml @@ -0,0 +1,129 @@ +Global: + use_gpu: true + epoch_num: 400 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/table_mv3/ + save_epoch_step: 400 + # evaluation is run every 400 iterations after the 0th iteration + eval_batch_step: [0, 400] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: ppstructure/docs/table/table.jpg + save_res_path: output/table_mv3 + # for data or label process + character_dict_path: ppocr/utils/dict/table_structure_dict.txt + character_type: en + max_text_length: &max_text_length 500 + box_format: &box_format 'xyxy' # 'xywh', 'xyxy', 'xyxyxyxy' + infer_mode: False + amp_custom_black_list: ['matmul_v2','elementwise_add'] + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 5.0 + lr: + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00000 + +Architecture: + model_type: table + algorithm: TableAttn + Backbone: + name: MobileNetV3 + scale: 1.0 + model_name: small + disable_se: true + Head: + name: TableAttentionHead + hidden_size: 256 + max_text_length: *max_text_length + loc_reg_num: &loc_reg_num 4 + +Loss: + name: TableAttentionLoss + structure_weight: 100.0 + loc_weight: 10000.0 + +PostProcess: + name: TableLabelDecode + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: false # cost many time, set False for training + +Train: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/train/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_train.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: False + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'structure', 'bboxes', 'bbox_masks', 'shape' ] + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 1 + +Eval: + dataset: + name: PubTabDataSet + data_dir: train_data/table/pubtabnet/val/ + label_file_list: [train_data/table/pubtabnet/PubTabNet_2.0.0_val.jsonl] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - TableLabelEncode: + learn_empty_box: False + merge_no_span_structure: False + replace_empty_cell_token: False + loc_reg_num: *loc_reg_num + max_text_length: *max_text_length + - TableBoxEncode: + - ResizeTableImage: + max_len: 488 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - PaddingTableImage: + size: [488, 488] + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'structure', 'bboxes', 'bbox_masks', 'shape' ] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 48 + num_workers: 1 diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..43ee65e --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,30 @@ +English | [简体中文](README_ch.md) + +# PP-OCR Deployment + +- [PP-OCR Deployment](#pp-ocr-deployment) + - [Paddle Deployment Introduction](#paddle-deployment-introduction) + - [PP-OCR Deployment](#pp-ocr-deployment-1) + + +## Paddle Deployment Introduction + +Paddle provides a variety of deployment schemes to meet the deployment requirements of different scenarios. Please choose according to the actual situation: + +
+ +
+ + + +## PP-OCR Deployment + +PP-OCR has supported multi deployment schemes. Click the link to get the specific tutorial. + +- [Python Inference](../doc/doc_en/inference_ppocr_en.md) +- [C++ Inference](./cpp_infer/readme.md) +- [Serving (Python/C++)](./pdserving/README.md) +- [Paddle-Lite (ARM CPU/OpenCL ARM GPU)](./lite/readme.md) +- [Paddle2ONNX](./paddle2onnx/readme.md) + +If you need the deployment tutorial of academic algorithm models other than PP-OCR, please directly enter the main page of corresponding algorithms, [entrance](../doc/doc_en/algorithm_overview_en.md)。 diff --git a/deploy/README_ch.md b/deploy/README_ch.md new file mode 100644 index 0000000..97c7bf9 --- /dev/null +++ b/deploy/README_ch.md @@ -0,0 +1 @@ +See [link](https://www.paddleocr.ai/main/version3.x/inference_deployment/local_inference/high_performance_inference.html) for details. diff --git a/deploy/android_demo/.gitignore b/deploy/android_demo/.gitignore new file mode 100644 index 0000000..d77f574 --- /dev/null +++ b/deploy/android_demo/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/* +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/deploy/android_demo/README.md b/deploy/android_demo/README.md new file mode 100644 index 0000000..3c59ca6 --- /dev/null +++ b/deploy/android_demo/README.md @@ -0,0 +1,118 @@ +- [Android Demo](#android-demo) + - [1. 简介](#1-简介) + - [2. 近期更新](#2-近期更新) + - [3. 快速使用](#3-快速使用) + - [3.1 环境准备](#31-环境准备) + - [3.2 导入项目](#32-导入项目) + - [3.3 运行demo](#33-运行demo) + - [3.4 运行模式](#34-运行模式) + - [3.5 设置](#35-设置) + - [4 更多支持](#4-更多支持) + +# Android Demo + +## 1. 简介 +此为PaddleOCR的Android Demo,目前支持文本检测,文本方向分类器和文本识别模型的使用。使用 [PaddleLite v2.10](https://github.com/PaddlePaddle/Paddle-Lite/tree/release/v2.10) 进行开发。 + +## 2. 近期更新 +* 2022.02.27 + * 预测库更新到PaddleLite v2.10 + * 支持6种运行模式: + * 检测+分类+识别 + * 检测+识别 + * 分类+识别 + * 检测 + * 识别 + * 分类 + +## 3. 快速使用 + +### 3.1 环境准备 +1. 在本地环境安装好 Android Studio 工具,详细安装方法请见[Android Studio 官网](https://developer.android.com/studio)。 +2. 准备一部 Android 手机,并开启 USB 调试模式。开启方法: `手机设置 -> 查找开发者选项 -> 打开开发者选项和 USB 调试模式` + +**注意**:如果您的 Android Studio 尚未配置 NDK ,请根据 Android Studio 用户指南中的[安装及配置 NDK 和 CMake ](https://developer.android.com/studio/projects/install-ndk)内容,预先配置好 NDK 。您可以选择最新的 NDK 版本,或者使用 Paddle Lite 预测库版本一样的 NDK + +### 3.2 导入项目 + +点击 File->New->Import Project..., 然后跟着Android Studio的引导导入 +导入完成后呈现如下界面 +![](https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/imgs/import_demo.jpg) + +### 3.3 运行demo +将手机连接上电脑后,点击Android Studio工具栏中的运行按钮即可运行demo。在此过程中,手机会弹出"允许从 USB 安装软件权限"的弹窗,点击允许即可。 + +软件安转到手机上后会在手机主屏最后一页看到如下app +
+ +
+ +点击app图标即可启动app,启动后app主页如下 + +
+ +
+ +app主页中有四个按钮,一个下拉列表和一个菜单按钮,他们的功能分别为 + +* 运行模型:按照已选择的模式,运行对应的模型组合 +* 拍照识别:唤起手机相机拍照并获取拍照的图像,拍照完成后需要点击运行模型进行识别 +* 选取图片:唤起手机相册拍照选择图像,选择完成后需要点击运行模型进行识别 +* 清空绘图:清空当前显示图像上绘制的文本框,以便进行下一次识别(每次识别使用的图像都是当前显示的图像) +* 下拉列表:进行运行模式的选择,目前包含6种运行模式,默认模式为**检测+分类+识别**详细说明见下一节。 +* 菜单按钮:点击后会进入菜单界面,进行模型和内置图像有关设置 + +点击运行模型后,会按照所选择的模式运行对应的模型,**检测+分类+识别**模式下运行的模型结果如下所示: + + + +模型运行完成后,模型和运行状态显示区`STATUS`字段显示了当前模型的运行状态,这里显示为`run model succeeded`表明模型运行成功。 + +模型的运行结果显示在运行结果显示区,显示格式为 +```text +序号:Det:(x1,y1)(x2,y2)(x3,y3)(x4,y4) Rec: 识别文本,识别置信度 Cls:分类类别,分类分时 +``` + +### 3.4 运行模式 + +PaddleOCR demo共提供了6种运行模式,如下图 +
+ +
+ +每种模式的运行结果如下表所示 + +| 检测+分类+识别 | 检测+识别 | 分类+识别 | +|------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| +| | | | + + +| 检测 | 识别 | 分类 | +|----------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------| +| | | | + +### 3.5 设置 + +设置界面如下 + +
+ +
+ +在设置界面可以进行如下几项设定: +1. 普通设置 + * Enable custom settings: 选中状态下才能更改设置 + * Model Path: 所运行的模型地址,使用默认值就好 + * Label Path: 识别模型的字典 + * Image Path: 进行识别的内置图像名 +2. 模型运行态设置,此项设置更改后返回主界面时,会自动重新加载模型 + * CPU Thread Num: 模型运行使用的CPU核心数量 + * CPU Power Mode: 模型运行模式,大小核设定 +3. 输入设置 + * det long size: DB模型预处理时图像的长边长度,超过此长度resize到该值,短边进行等比例缩放,小于此长度不进行处理。 +4. 输出设置 + * Score Threshold: DB模型后处理box的阈值,低于此阈值的box进行过滤,不显示。 + +## 4 更多支持 +1. 实时识别,更新预测库可参考 https://github.com/PaddlePaddle/Paddle-Lite-Demo/tree/develop/ocr/android/app/cxx/ppocr_demo +2. 更多Paddle-Lite相关问题可前往[Paddle-Lite](https://github.com/PaddlePaddle/Paddle-Lite) ,获得更多开发支持 diff --git a/deploy/android_demo/app/.gitignore b/deploy/android_demo/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/deploy/android_demo/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/deploy/android_demo/app/build.gradle b/deploy/android_demo/app/build.gradle new file mode 100644 index 0000000..00fae70 --- /dev/null +++ b/deploy/android_demo/app/build.gradle @@ -0,0 +1,93 @@ +import java.security.MessageDigest + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 29 + defaultConfig { + applicationId "com.baidu.paddle.lite.demo.ocr" + minSdkVersion 23 + targetSdkVersion 29 + versionCode 2 + versionName "2.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + cppFlags "-std=c++11 -frtti -fexceptions -Wno-format" + arguments '-DANDROID_PLATFORM=android-23', '-DANDROID_STL=c++_shared' ,"-DANDROID_ARM_NEON=TRUE" + } + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + externalNativeBuild { + cmake { + path "src/main/cpp/CMakeLists.txt" + version "3.10.2" + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} + +def archives = [ + [ + 'src' : 'https://paddleocr.bj.bcebos.com/libs/paddle_lite_libs_v2_10.tar.gz', + 'dest': 'PaddleLite' + ], + [ + 'src' : 'https://paddlelite-demo.bj.bcebos.com/libs/android/opencv-4.2.0-android-sdk.tar.gz', + 'dest': 'OpenCV' + ], + [ + 'src' : 'https://paddleocr.bj.bcebos.com/PP-OCRv2/lite/ch_PP-OCRv2.tar.gz', + 'dest' : 'src/main/assets/models' + ], + [ + 'src' : 'https://paddleocr.bj.bcebos.com/dygraph_v2.0/lite/ch_dict.tar.gz', + 'dest' : 'src/main/assets/labels' + ] +] + +task downloadAndExtractArchives(type: DefaultTask) { + doFirst { + println "Downloading and extracting archives including libs and models" + } + doLast { + // Prepare cache folder for archives + String cachePath = "cache" + if (!file("${cachePath}").exists()) { + mkdir "${cachePath}" + } + archives.eachWithIndex { archive, index -> + MessageDigest messageDigest = MessageDigest.getInstance('MD5') + messageDigest.update(archive.src.bytes) + String cacheName = new BigInteger(1, messageDigest.digest()).toString(32) + // Download the target archive if not exists + boolean copyFiles = !file("${archive.dest}").exists() + if (!file("${cachePath}/${cacheName}.tar.gz").exists()) { + ant.get(src: archive.src, dest: file("${cachePath}/${cacheName}.tar.gz")) + copyFiles = true; // force to copy files from the latest archive files + } + // Extract the target archive if its dest path does not exists + if (copyFiles) { + copy { + from tarTree("${cachePath}/${cacheName}.tar.gz") + into "${archive.dest}" + } + } + } + } +} +preBuild.dependsOn downloadAndExtractArchives diff --git a/deploy/android_demo/app/proguard-rules.pro b/deploy/android_demo/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/deploy/android_demo/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/deploy/android_demo/app/src/androidTest/java/com/baidu/paddle/lite/demo/ocr/ExampleInstrumentedTest.java b/deploy/android_demo/app/src/androidTest/java/com/baidu/paddle/lite/demo/ocr/ExampleInstrumentedTest.java new file mode 100644 index 0000000..77b179d --- /dev/null +++ b/deploy/android_demo/app/src/androidTest/java/com/baidu/paddle/lite/demo/ocr/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.baidu.paddle.lite.demo", appContext.getPackageName()); + } +} diff --git a/deploy/android_demo/app/src/main/AndroidManifest.xml b/deploy/android_demo/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fef3a39 --- /dev/null +++ b/deploy/android_demo/app/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deploy/android_demo/app/src/main/assets/images/det_0.jpg b/deploy/android_demo/app/src/main/assets/images/det_0.jpg new file mode 100644 index 0000000..8517e12 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/det_0.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/det_180.jpg b/deploy/android_demo/app/src/main/assets/images/det_180.jpg new file mode 100644 index 0000000..b1bb8a4 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/det_180.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/det_270.jpg b/deploy/android_demo/app/src/main/assets/images/det_270.jpg new file mode 100644 index 0000000..5687390 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/det_270.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/det_90.jpg b/deploy/android_demo/app/src/main/assets/images/det_90.jpg new file mode 100644 index 0000000..49e949a Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/det_90.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/rec_0.jpg b/deploy/android_demo/app/src/main/assets/images/rec_0.jpg new file mode 100644 index 0000000..2c34cd3 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/rec_0.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/rec_0_180.jpg b/deploy/android_demo/app/src/main/assets/images/rec_0_180.jpg new file mode 100644 index 0000000..02bc3b9 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/rec_0_180.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/rec_1.jpg b/deploy/android_demo/app/src/main/assets/images/rec_1.jpg new file mode 100644 index 0000000..22031ba Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/rec_1.jpg differ diff --git a/deploy/android_demo/app/src/main/assets/images/rec_1_180.jpg b/deploy/android_demo/app/src/main/assets/images/rec_1_180.jpg new file mode 100644 index 0000000..d745530 Binary files /dev/null and b/deploy/android_demo/app/src/main/assets/images/rec_1_180.jpg differ diff --git a/deploy/android_demo/app/src/main/cpp/CMakeLists.txt b/deploy/android_demo/app/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000..39b7102 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/CMakeLists.txt @@ -0,0 +1,117 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +# Sets the minimum version of CMake required to build the native library. + +cmake_minimum_required(VERSION 3.4.1) + +# Creates and names a library, sets it as either STATIC or SHARED, and provides +# the relative paths to its source code. You can define multiple libraries, and +# CMake builds them for you. Gradle automatically packages shared libraries with +# your APK. + +set(PaddleLite_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../PaddleLite") +include_directories(${PaddleLite_DIR}/cxx/include) + +set(OpenCV_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../OpenCV/sdk/native/jni") +message(STATUS "opencv dir: ${OpenCV_DIR}") +find_package(OpenCV REQUIRED) +message(STATUS "OpenCV libraries: ${OpenCV_LIBS}") +include_directories(${OpenCV_INCLUDE_DIRS}) +aux_source_directory(. SOURCES) +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -ffast-math -Ofast -Os" + ) +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -fvisibility=hidden -fvisibility-inlines-hidden -fdata-sections -ffunction-sections" + ) +set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections -Wl,-z,nocopyreloc") + +add_library( + # Sets the name of the library. + Native + # Sets the library as a shared library. + SHARED + # Provides a relative path to your source file(s). + ${SOURCES}) + +find_library( + # Sets the name of the path variable. + log-lib + # Specifies the name of the NDK library that you want CMake to locate. + log) + +add_library( + # Sets the name of the library. + paddle_light_api_shared + # Sets the library as a shared library. + SHARED + # Provides a relative path to your source file(s). + IMPORTED) + +set_target_properties( + # Specifies the target library. + paddle_light_api_shared + # Specifies the parameter you want to define. + PROPERTIES + IMPORTED_LOCATION + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libpaddle_light_api_shared.so + # Provides the path to the library you want to import. +) + + +# Specifies libraries CMake should link to your target library. You can link +# multiple libraries, such as libraries you define in this build script, +# prebuilt third-party libraries, or system libraries. + +target_link_libraries( + # Specifies the target library. + Native + paddle_light_api_shared + ${OpenCV_LIBS} + GLESv2 + EGL + jnigraphics + ${log-lib} +) + +add_custom_command( + TARGET Native + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libc++_shared.so + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libc++_shared.so) + +add_custom_command( + TARGET Native + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libpaddle_light_api_shared.so + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libpaddle_light_api_shared.so) + +add_custom_command( + TARGET Native + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libhiai.so + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libhiai.so) + +add_custom_command( + TARGET Native + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libhiai_ir.so + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libhiai_ir.so) + +add_custom_command( + TARGET Native + POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E copy + ${PaddleLite_DIR}/cxx/libs/${ANDROID_ABI}/libhiai_ir_build.so + ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libhiai_ir_build.so) diff --git a/deploy/android_demo/app/src/main/cpp/common.h b/deploy/android_demo/app/src/main/cpp/common.h new file mode 100644 index 0000000..fc47407 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/common.h @@ -0,0 +1,37 @@ +// +// Created by fu on 4/25/18. +// + +#pragma once +#import +#import + +#ifdef __ANDROID__ + +#include + +#define LOG_TAG "OCR_NDK" + +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#else +#include +#define LOGI(format, ...) \ + fprintf(stdout, "[" LOG_TAG "]" format "\n", ##__VA_ARGS__) +#define LOGW(format, ...) \ + fprintf(stdout, "[" LOG_TAG "]" format "\n", ##__VA_ARGS__) +#define LOGE(format, ...) \ + fprintf(stderr, "[" LOG_TAG "]Error: " format "\n", ##__VA_ARGS__) +#endif + +enum RETURN_CODE { RETURN_OK = 0 }; + +enum NET_TYPE { NET_OCR = 900100, NET_OCR_INTERNAL = 991008 }; + +template inline T product(const std::vector &vec) { + if (vec.empty()) { + return 0; + } + return std::accumulate(vec.begin(), vec.end(), 1, std::multiplies()); +} diff --git a/deploy/android_demo/app/src/main/cpp/native.cpp b/deploy/android_demo/app/src/main/cpp/native.cpp new file mode 100644 index 0000000..5674f64 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/native.cpp @@ -0,0 +1,120 @@ +// +// Created by fujiayi on 2020/7/5. +// + +#include "native.h" +#include "ocr_ppredictor.h" +#include +#include +#include + +static paddle::lite_api::PowerMode str_to_cpu_mode(const std::string &cpu_mode); + +extern "C" JNIEXPORT jlong JNICALL +Java_com_baidu_paddle_lite_demo_ocr_OCRPredictorNative_init( + JNIEnv *env, jobject thiz, jstring j_det_model_path, + jstring j_rec_model_path, jstring j_cls_model_path, jint j_use_opencl, + jint j_thread_num, jstring j_cpu_mode) { + std::string det_model_path = jstring_to_cpp_string(env, j_det_model_path); + std::string rec_model_path = jstring_to_cpp_string(env, j_rec_model_path); + std::string cls_model_path = jstring_to_cpp_string(env, j_cls_model_path); + int thread_num = j_thread_num; + std::string cpu_mode = jstring_to_cpp_string(env, j_cpu_mode); + ppredictor::OCR_Config conf; + conf.use_opencl = j_use_opencl; + conf.thread_num = thread_num; + conf.mode = str_to_cpu_mode(cpu_mode); + ppredictor::OCR_PPredictor *orc_predictor = + new ppredictor::OCR_PPredictor{conf}; + orc_predictor->init_from_file(det_model_path, rec_model_path, cls_model_path); + return reinterpret_cast(orc_predictor); +} + +/** + * "LITE_POWER_HIGH" convert to paddle::lite_api::LITE_POWER_HIGH + * @param cpu_mode + * @return + */ +static paddle::lite_api::PowerMode +str_to_cpu_mode(const std::string &cpu_mode) { + static std::map cpu_mode_map{ + {"LITE_POWER_HIGH", paddle::lite_api::LITE_POWER_HIGH}, + {"LITE_POWER_LOW", paddle::lite_api::LITE_POWER_HIGH}, + {"LITE_POWER_FULL", paddle::lite_api::LITE_POWER_FULL}, + {"LITE_POWER_NO_BIND", paddle::lite_api::LITE_POWER_NO_BIND}, + {"LITE_POWER_RAND_HIGH", paddle::lite_api::LITE_POWER_RAND_HIGH}, + {"LITE_POWER_RAND_LOW", paddle::lite_api::LITE_POWER_RAND_LOW}}; + std::string upper_key; + std::transform(cpu_mode.cbegin(), cpu_mode.cend(), upper_key.begin(), + ::toupper); + auto index = cpu_mode_map.find(upper_key.c_str()); + if (index == cpu_mode_map.end()) { + LOGE("cpu_mode not found %s", upper_key.c_str()); + return paddle::lite_api::LITE_POWER_HIGH; + } else { + return index->second; + } +} + +extern "C" JNIEXPORT jfloatArray JNICALL +Java_com_baidu_paddle_lite_demo_ocr_OCRPredictorNative_forward( + JNIEnv *env, jobject thiz, jlong java_pointer, jobject original_image, + jint j_max_size_len, jint j_run_det, jint j_run_cls, jint j_run_rec) { + LOGI("begin to run native forward"); + if (java_pointer == 0) { + LOGE("JAVA pointer is NULL"); + return cpp_array_to_jfloatarray(env, nullptr, 0); + } + + cv::Mat origin = bitmap_to_cv_mat(env, original_image); + if (origin.size == 0) { + LOGE("origin bitmap cannot convert to CV Mat"); + return cpp_array_to_jfloatarray(env, nullptr, 0); + } + + int max_size_len = j_max_size_len; + int run_det = j_run_det; + int run_cls = j_run_cls; + int run_rec = j_run_rec; + + ppredictor::OCR_PPredictor *ppredictor = + (ppredictor::OCR_PPredictor *)java_pointer; + std::vector dims_arr; + std::vector results = + ppredictor->infer_ocr(origin, max_size_len, run_det, run_cls, run_rec); + LOGI("infer_ocr finished with boxes %ld", results.size()); + + // 这里将std::vector 序列化成 + // float数组,传输到java层再反序列化 + std::vector float_arr; + for (const ppredictor::OCRPredictResult &r : results) { + float_arr.push_back(r.points.size()); + float_arr.push_back(r.word_index.size()); + float_arr.push_back(r.score); + // add det point + for (const std::vector &point : r.points) { + float_arr.push_back(point.at(0)); + float_arr.push_back(point.at(1)); + } + // add rec word idx + for (int index : r.word_index) { + float_arr.push_back(index); + } + // add cls result + float_arr.push_back(r.cls_label); + float_arr.push_back(r.cls_score); + } + return cpp_array_to_jfloatarray(env, float_arr.data(), float_arr.size()); +} + +extern "C" JNIEXPORT void JNICALL +Java_com_baidu_paddle_lite_demo_ocr_OCRPredictorNative_release( + JNIEnv *env, jobject thiz, jlong java_pointer) { + if (java_pointer == 0) { + LOGE("JAVA pointer is NULL"); + return; + } + ppredictor::OCR_PPredictor *ppredictor = + (ppredictor::OCR_PPredictor *)java_pointer; + delete ppredictor; +} diff --git a/deploy/android_demo/app/src/main/cpp/native.h b/deploy/android_demo/app/src/main/cpp/native.h new file mode 100644 index 0000000..9a87106 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/native.h @@ -0,0 +1,146 @@ +// +// Created by fujiayi on 2020/7/5. +// + +#pragma once + +#include "common.h" +#include +#include +#include +#include +#include + +inline std::string jstring_to_cpp_string(JNIEnv *env, jstring jstr) { + // In java, a unicode char will be encoded using 2 bytes (utf16). + // so jstring will contain characters utf16. std::string in c++ is + // essentially a string of bytes, not characters, so if we want to + // pass jstring from JNI to c++, we have convert utf16 to bytes. + if (!jstr) { + return ""; + } + const jclass stringClass = env->GetObjectClass(jstr); + const jmethodID getBytes = + env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B"); + const jbyteArray stringJbytes = (jbyteArray)env->CallObjectMethod( + jstr, getBytes, env->NewStringUTF("UTF-8")); + + size_t length = (size_t)env->GetArrayLength(stringJbytes); + jbyte *pBytes = env->GetByteArrayElements(stringJbytes, NULL); + + std::string ret = std::string(reinterpret_cast(pBytes), length); + env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT); + + env->DeleteLocalRef(stringJbytes); + env->DeleteLocalRef(stringClass); + return ret; +} + +inline jstring cpp_string_to_jstring(JNIEnv *env, std::string str) { + auto *data = str.c_str(); + jclass strClass = env->FindClass("java/lang/String"); + jmethodID strClassInitMethodID = + env->GetMethodID(strClass, "", "([BLjava/lang/String;)V"); + + jbyteArray bytes = env->NewByteArray(strlen(data)); + env->SetByteArrayRegion(bytes, 0, strlen(data), + reinterpret_cast(data)); + + jstring encoding = env->NewStringUTF("UTF-8"); + jstring res = (jstring)(env->NewObject(strClass, strClassInitMethodID, bytes, + encoding)); + + env->DeleteLocalRef(strClass); + env->DeleteLocalRef(encoding); + env->DeleteLocalRef(bytes); + + return res; +} + +inline jfloatArray cpp_array_to_jfloatarray(JNIEnv *env, const float *buf, + int64_t len) { + if (len == 0) { + return env->NewFloatArray(0); + } + jfloatArray result = env->NewFloatArray(len); + env->SetFloatArrayRegion(result, 0, len, buf); + return result; +} + +inline jintArray cpp_array_to_jintarray(JNIEnv *env, const int *buf, + int64_t len) { + jintArray result = env->NewIntArray(len); + env->SetIntArrayRegion(result, 0, len, buf); + return result; +} + +inline jbyteArray cpp_array_to_jbytearray(JNIEnv *env, const int8_t *buf, + int64_t len) { + jbyteArray result = env->NewByteArray(len); + env->SetByteArrayRegion(result, 0, len, buf); + return result; +} + +inline jlongArray int64_vector_to_jlongarray(JNIEnv *env, + const std::vector &vec) { + jlongArray result = env->NewLongArray(vec.size()); + jlong *buf = new jlong[vec.size()]; + for (size_t i = 0; i < vec.size(); ++i) { + buf[i] = (jlong)vec[i]; + } + env->SetLongArrayRegion(result, 0, vec.size(), buf); + delete[] buf; + return result; +} + +inline std::vector jlongarray_to_int64_vector(JNIEnv *env, + jlongArray data) { + int data_size = env->GetArrayLength(data); + jlong *data_ptr = env->GetLongArrayElements(data, nullptr); + std::vector data_vec(data_ptr, data_ptr + data_size); + env->ReleaseLongArrayElements(data, data_ptr, 0); + return data_vec; +} + +inline std::vector jfloatarray_to_float_vector(JNIEnv *env, + jfloatArray data) { + int data_size = env->GetArrayLength(data); + jfloat *data_ptr = env->GetFloatArrayElements(data, nullptr); + std::vector data_vec(data_ptr, data_ptr + data_size); + env->ReleaseFloatArrayElements(data, data_ptr, 0); + return data_vec; +} + +inline cv::Mat bitmap_to_cv_mat(JNIEnv *env, jobject bitmap) { + AndroidBitmapInfo info; + int result = AndroidBitmap_getInfo(env, bitmap, &info); + if (result != ANDROID_BITMAP_RESULT_SUCCESS) { + LOGE("AndroidBitmap_getInfo failed, result: %d", result); + return cv::Mat{}; + } + if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { + LOGE("Bitmap format is not RGBA_8888 !"); + return cv::Mat{}; + } + if (info.width == 0 || info.height == 0 || info.stride < info.width * 4u) { + LOGE("Invalid bitmap dimensions or stride"); + return cv::Mat{}; + } + unsigned char *srcData = NULL; + AndroidBitmap_lockPixels(env, bitmap, (void **)&srcData); + cv::Mat mat = cv::Mat::zeros(info.height, info.width, CV_8UC4); + if (!mat.data) { + AndroidBitmap_unlockPixels(env, bitmap); + return cv::Mat{}; + } + size_t copy_size = (size_t)info.height * info.width * 4; + memcpy(mat.data, srcData, copy_size); + AndroidBitmap_unlockPixels(env, bitmap); + cv::cvtColor(mat, mat, cv::COLOR_RGBA2BGR); + /** + if (!cv::imwrite("/sdcard/1/copy.jpg", mat)){ + LOGE("Write image failed " ); + } + */ + return mat; +} diff --git a/deploy/android_demo/app/src/main/cpp/ocr_clipper.cpp b/deploy/android_demo/app/src/main/cpp/ocr_clipper.cpp new file mode 100644 index 0000000..2d52d20 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_clipper.cpp @@ -0,0 +1,4378 @@ +/******************************************************************************* + * * + * Author : Angus Johnson * Version : 6.4.2 * Date : 27 February + *2017 * Website : + *http://www.angusj.com * Copyright : + *Angus Johnson 2010-2017 * + * * + * License: * Use, modification & distribution is subject to Boost Software + *License Ver 1. * http://www.boost.org/LICENSE_1_0.txt * + * * + * Attributions: * The code in this library is an extension of Bala Vatti's + *clipping algorithm: * "A generic solution to polygon clipping" * + * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * + * http://portal.acm.org/citation.cfm?id=129906 * + * * + * Computer graphics and geometric modeling: implementation and algorithms * By + *Max K. Agoston * + * Springer; 1 edition (January 4, 2005) * + * http://books.google.com/books?q=vatti+clipping+agoston * + * * + * See also: * "Polygon Offsetting by Computing Winding Numbers" * Paper no. + *DETC2005-85513 pp. 565-575 * ASME 2005 + *International Design Engineering Technical Conferences * and + *Computers and Information in Engineering Conference (IDETC/CIE2005) * + * September 24-28, 2005 , Long Beach, California, USA * + * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * + * * + *******************************************************************************/ + +/******************************************************************************* + * * + * This is a translation of the Delphi Clipper library and the naming style * + * used has retained a Delphi flavour. * + * * + *******************************************************************************/ + +#include "ocr_clipper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +static double const pi = 3.141592653589793238; +static double const two_pi = pi * 2; +static double const def_arc_tolerance = 0.25; + +enum Direction { dRightToLeft, dLeftToRight }; + +static int const Unassigned = -1; // edge not currently 'owning' a solution +static int const Skip = -2; // edge that would otherwise close a path + +#define HORIZONTAL (-1.0E+40) +#define TOLERANCE (1.0e-20) +#define NEAR_ZERO(val) (((val) > -TOLERANCE) && ((val) < TOLERANCE)) + +struct TEdge { + IntPoint Bot; + IntPoint Curr; // current (updated for every new scanbeam) + IntPoint Top; + double Dx; + PolyType PolyTyp; + EdgeSide Side; // side only refers to current side of solution poly + int WindDelta; // 1 or -1 depending on winding direction + int WindCnt; + int WindCnt2; // winding count of the opposite polytype + int OutIdx; + TEdge *Next; + TEdge *Prev; + TEdge *NextInLML; + TEdge *NextInAEL; + TEdge *PrevInAEL; + TEdge *NextInSEL; + TEdge *PrevInSEL; +}; + +struct IntersectNode { + TEdge *Edge1; + TEdge *Edge2; + IntPoint Pt; +}; + +struct LocalMinimum { + cInt Y; + TEdge *LeftBound; + TEdge *RightBound; +}; + +struct OutPt; + +// OutRec: contains a path in the clipping solution. Edges in the AEL will +// carry a pointer to an OutRec when they are part of the clipping solution. +struct OutRec { + int Idx; + bool IsHole; + bool IsOpen; + OutRec *FirstLeft; // see comments in clipper.pas + PolyNode *PolyNd; + OutPt *Pts; + OutPt *BottomPt; +}; + +struct OutPt { + int Idx; + IntPoint Pt; + OutPt *Next; + OutPt *Prev; +}; + +struct Join { + OutPt *OutPt1; + OutPt *OutPt2; + IntPoint OffPt; +}; + +struct LocMinSorter { + inline bool operator()(const LocalMinimum &locMin1, + const LocalMinimum &locMin2) { + return locMin2.Y < locMin1.Y; + } +}; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + +inline cInt Round(double val) { + if ((val < 0)) + return static_cast(val - 0.5); + else + return static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +inline cInt Abs(cInt val) { return val < 0 ? -val : val; } + +//------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() { + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Children.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode *PolyTree::GetFirst() const { + if (!Children.empty()) + return Children[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() const { + int result = (int)AllNodes.size(); + // with negative offsets, ignore the hidden outer polygon ... + if (result > 0 && Children[0] != AllNodes[0]) + result--; + return result; +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + +PolyNode::PolyNode() : Parent(0), Index(0), m_IsOpen(false) {} +//------------------------------------------------------------------------------ + +int PolyNode::ChildCount() const { return (int)Children.size(); } +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode &child) { + unsigned cnt = (unsigned)Children.size(); + Children.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode *PolyNode::GetNext() const { + if (!Children.empty()) + return Children[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode *PolyNode::GetNextSiblingUp() const { + if (!Parent) // protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Children.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Children[Index + 1]; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsHole() const { + bool result = true; + PolyNode *node = Parent; + while (node) { + result = !result; + node = node->Parent; + } + return result; +} +//------------------------------------------------------------------------------ + +bool PolyNode::IsOpen() const { return m_IsOpen; } +//------------------------------------------------------------------------------ + +#ifndef use_int32 + +//------------------------------------------------------------------------------ +// Int128 class (enables safe math on signed 64bit integers) +// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1 +// Int128 val2((long64)9223372036854775807); +// Int128 val3 = val1 * val2; +// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37) +//------------------------------------------------------------------------------ + +class Int128 { +public: + ulong64 lo; + long64 hi; + + Int128(long64 _lo = 0) { + lo = (ulong64)_lo; + if (_lo < 0) + hi = -1; + else + hi = 0; + } + + Int128(const Int128 &val) : lo(val.lo), hi(val.hi) {} + + Int128(const long64 &_hi, const ulong64 &_lo) : lo(_lo), hi(_hi) {} + + Int128 &operator=(const long64 &val) { + lo = (ulong64)val; + if (val < 0) + hi = -1; + else + hi = 0; + return *this; + } + + bool operator==(const Int128 &val) const { + return (hi == val.hi && lo == val.lo); + } + + bool operator!=(const Int128 &val) const { return !(*this == val); } + + bool operator>(const Int128 &val) const { + if (hi != val.hi) + return hi > val.hi; + else + return lo > val.lo; + } + + bool operator<(const Int128 &val) const { + if (hi != val.hi) + return hi < val.hi; + else + return lo < val.lo; + } + + bool operator>=(const Int128 &val) const { return !(*this < val); } + + bool operator<=(const Int128 &val) const { return !(*this > val); } + + Int128 &operator+=(const Int128 &rhs) { + hi += rhs.hi; + lo += rhs.lo; + if (lo < rhs.lo) + hi++; + return *this; + } + + Int128 operator+(const Int128 &rhs) const { + Int128 result(*this); + result += rhs; + return result; + } + + Int128 &operator-=(const Int128 &rhs) { + *this += -rhs; + return *this; + } + + Int128 operator-(const Int128 &rhs) const { + Int128 result(*this); + result -= rhs; + return result; + } + + Int128 operator-() const // unary negation + { + if (lo == 0) + return Int128(-hi, 0); + else + return Int128(~hi, ~lo + 1); + } + + operator double() const { + const double shift64 = 18446744073709551616.0; // 2^64 + if (hi < 0) { + if (lo == 0) + return (double)hi * shift64; + else + return -(double)(~lo + ~hi * shift64); + } else + return (double)(lo + hi * shift64); + } +}; +//------------------------------------------------------------------------------ + +Int128 Int128Mul(long64 lhs, long64 rhs) { + bool negate = (lhs < 0) != (rhs < 0); + + if (lhs < 0) + lhs = -lhs; + ulong64 int1Hi = ulong64(lhs) >> 32; + ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF); + + if (rhs < 0) + rhs = -rhs; + ulong64 int2Hi = ulong64(rhs) >> 32; + ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF); + + // nb: see comments in clipper.pas + ulong64 a = int1Hi * int2Hi; + ulong64 b = int1Lo * int2Lo; + ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi; + + Int128 tmp; + tmp.hi = long64(a + (c >> 32)); + tmp.lo = long64(c << 32); + tmp.lo += long64(b); + if (tmp.lo < b) + tmp.hi++; + if (negate) + tmp = -tmp; + return tmp; +}; +#endif + +//------------------------------------------------------------------------------ +// Miscellaneous global functions +//------------------------------------------------------------------------------ + +bool Orientation(const Path &poly) { return Area(poly) >= 0; } +//------------------------------------------------------------------------------ + +double Area(const Path &poly) { + int size = (int)poly.size(); + if (size < 3) + return 0; + + double a = 0; + for (int i = 0, j = size - 1; i < size; ++i) { + a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y); + j = i; + } + return -a * 0.5; +} +//------------------------------------------------------------------------------ + +double Area(const OutPt *op) { + const OutPt *startOp = op; + if (!op) + return 0; + double a = 0; + do { + a += (double)(op->Prev->Pt.X + op->Pt.X) * + (double)(op->Prev->Pt.Y - op->Pt.Y); + op = op->Next; + } while (op != startOp); + return a * 0.5; +} +//------------------------------------------------------------------------------ + +double Area(const OutRec &outRec) { return Area(outRec.Pts); } +//------------------------------------------------------------------------------ + +bool PointIsVertex(const IntPoint &Pt, OutPt *pp) { + OutPt *pp2 = pp; + do { + if (pp2->Pt == Pt) + return true; + pp2 = pp2->Next; + } while (pp2 != pp); + return false; +} +//------------------------------------------------------------------------------ + +// See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann & +// Agathos +// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf +int PointInPolygon(const IntPoint &pt, const Path &path) { + // returns 0 if false, +1 if true, -1 if pt ON polygon boundary + int result = 0; + size_t cnt = path.size(); + if (cnt < 3) + return 0; + IntPoint ip = path[0]; + for (size_t i = 1; i <= cnt; ++i) { + IntPoint ipNext = (i == cnt ? path[0] : path[i]); + if (ipNext.Y == pt.Y) { + if ((ipNext.X == pt.X) || + (ip.Y == pt.Y && ((ipNext.X > pt.X) == (ip.X < pt.X)))) + return -1; + } + if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) { + if (ip.X >= pt.X) { + if (ipNext.X > pt.X) + result = 1 - result; + else { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) + return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) + result = 1 - result; + } + } else { + if (ipNext.X > pt.X) { + double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) - + (double)(ipNext.X - pt.X) * (ip.Y - pt.Y); + if (!d) + return -1; + if ((d > 0) == (ipNext.Y > ip.Y)) + result = 1 - result; + } + } + } + ip = ipNext; + } + return result; +} +//------------------------------------------------------------------------------ + +int PointInPolygon(const IntPoint &pt, OutPt *op) { + // returns 0 if false, +1 if true, -1 if pt ON polygon boundary + int result = 0; + OutPt *startOp = op; + for (;;) { + if (op->Next->Pt.Y == pt.Y) { + if ((op->Next->Pt.X == pt.X) || + (op->Pt.Y == pt.Y && ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X)))) + return -1; + } + if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) { + if (op->Pt.X >= pt.X) { + if (op->Next->Pt.X > pt.X) + result = 1 - result; + else { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) + return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) + result = 1 - result; + } + } else { + if (op->Next->Pt.X > pt.X) { + double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) - + (double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y); + if (!d) + return -1; + if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y)) + result = 1 - result; + } + } + } + op = op->Next; + if (startOp == op) + break; + } + return result; +} +//------------------------------------------------------------------------------ + +bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) { + OutPt *op = OutPt1; + do { + // nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon + int res = PointInPolygon(op->Pt, OutPt2); + if (res >= 0) + return res > 0; + op = op->Next; + } while (op != OutPt1); + return true; +} +//---------------------------------------------------------------------- + +bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) { +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) == + Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y); + else +#endif + return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) == + (e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3, + bool UseFullInt64Range) { +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) == + Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y); + else +#endif + return (pt1.Y - pt2.Y) * (pt2.X - pt3.X) == + (pt1.X - pt2.X) * (pt2.Y - pt3.Y); +} +//------------------------------------------------------------------------------ + +bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3, + const IntPoint pt4, bool UseFullInt64Range) { +#ifndef use_int32 + if (UseFullInt64Range) + return Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) == + Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y); + else +#endif + return (pt1.Y - pt2.Y) * (pt3.X - pt4.X) == + (pt1.X - pt2.X) * (pt3.Y - pt4.Y); +} +//------------------------------------------------------------------------------ + +inline bool IsHorizontal(TEdge &e) { return e.Dx == HORIZONTAL; } +//------------------------------------------------------------------------------ + +inline double GetDx(const IntPoint pt1, const IntPoint pt2) { + return (pt1.Y == pt2.Y) ? HORIZONTAL + : (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y); +} +//--------------------------------------------------------------------------- + +inline void SetDx(TEdge &e) { + cInt dy = (e.Top.Y - e.Bot.Y); + if (dy == 0) + e.Dx = HORIZONTAL; + else + e.Dx = (double)(e.Top.X - e.Bot.X) / dy; +} +//--------------------------------------------------------------------------- + +inline void SwapSides(TEdge &Edge1, TEdge &Edge2) { + EdgeSide Side = Edge1.Side; + Edge1.Side = Edge2.Side; + Edge2.Side = Side; +} +//------------------------------------------------------------------------------ + +inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) { + int OutIdx = Edge1.OutIdx; + Edge1.OutIdx = Edge2.OutIdx; + Edge2.OutIdx = OutIdx; +} +//------------------------------------------------------------------------------ + +inline cInt TopX(TEdge &edge, const cInt currentY) { + return (currentY == edge.Top.Y) + ? edge.Top.X + : edge.Bot.X + Round(edge.Dx * (currentY - edge.Bot.Y)); +} +//------------------------------------------------------------------------------ + +void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) { +#ifdef use_xyz + ip.Z = 0; +#endif + + double b1, b2; + if (Edge1.Dx == Edge2.Dx) { + ip.Y = Edge1.Curr.Y; + ip.X = TopX(Edge1, ip.Y); + return; + } else if (Edge1.Dx == 0) { + ip.X = Edge1.Bot.X; + if (IsHorizontal(Edge2)) + ip.Y = Edge2.Bot.Y; + else { + b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx); + ip.Y = Round(ip.X / Edge2.Dx + b2); + } + } else if (Edge2.Dx == 0) { + ip.X = Edge2.Bot.X; + if (IsHorizontal(Edge1)) + ip.Y = Edge1.Bot.Y; + else { + b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx); + ip.Y = Round(ip.X / Edge1.Dx + b1); + } + } else { + b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx; + b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx; + double q = (b2 - b1) / (Edge1.Dx - Edge2.Dx); + ip.Y = Round(q); + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = Round(Edge1.Dx * q + b1); + else + ip.X = Round(Edge2.Dx * q + b2); + } + + if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) { + if (Edge1.Top.Y > Edge2.Top.Y) + ip.Y = Edge1.Top.Y; + else + ip.Y = Edge2.Top.Y; + if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx)) + ip.X = TopX(Edge1, ip.Y); + else + ip.X = TopX(Edge2, ip.Y); + } + // finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ... + if (ip.Y > Edge1.Curr.Y) { + ip.Y = Edge1.Curr.Y; + // use the more vertical edge to derive X ... + if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx)) + ip.X = TopX(Edge2, ip.Y); + else + ip.X = TopX(Edge1, ip.Y); + } +} +//------------------------------------------------------------------------------ + +void ReversePolyPtLinks(OutPt *pp) { + if (!pp) + return; + OutPt *pp1, *pp2; + pp1 = pp; + do { + pp2 = pp1->Next; + pp1->Next = pp1->Prev; + pp1->Prev = pp2; + pp1 = pp2; + } while (pp1 != pp); +} +//------------------------------------------------------------------------------ + +void DisposeOutPts(OutPt *&pp) { + if (pp == 0) + return; + pp->Prev->Next = 0; + while (pp) { + OutPt *tmpPp = pp; + pp = pp->Next; + delete tmpPp; + } +} +//------------------------------------------------------------------------------ + +inline void InitEdge(TEdge *e, TEdge *eNext, TEdge *ePrev, const IntPoint &Pt) { + std::memset(e, 0, sizeof(TEdge)); + e->Next = eNext; + e->Prev = ePrev; + e->Curr = Pt; + e->OutIdx = Unassigned; +} +//------------------------------------------------------------------------------ + +void InitEdge2(TEdge &e, PolyType Pt) { + if (e.Curr.Y >= e.Next->Curr.Y) { + e.Bot = e.Curr; + e.Top = e.Next->Curr; + } else { + e.Top = e.Curr; + e.Bot = e.Next->Curr; + } + SetDx(e); + e.PolyTyp = Pt; +} +//------------------------------------------------------------------------------ + +TEdge *RemoveEdge(TEdge *e) { + // removes e from double_linked_list (but without removing from memory) + e->Prev->Next = e->Next; + e->Next->Prev = e->Prev; + TEdge *result = e->Next; + e->Prev = 0; // flag as removed (see ClipperBase.Clear) + return result; +} +//------------------------------------------------------------------------------ + +inline void ReverseHorizontal(TEdge &e) { + // swap horizontal edges' Top and Bottom x's so they follow the natural + // progression of the bounds - ie so their xbots will align with the + // adjoining lower edge. [Helpful in the ProcessHorizontal() method.] + std::swap(e.Top.X, e.Bot.X); +#ifdef use_xyz + std::swap(e.Top.Z, e.Bot.Z); +#endif +} +//------------------------------------------------------------------------------ + +void SwapPoints(IntPoint &pt1, IntPoint &pt2) { + IntPoint tmp = pt1; + pt1 = pt2; + pt2 = tmp; +} +//------------------------------------------------------------------------------ + +bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a, + IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) { + // precondition: segments are Collinear. + if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) { + if (pt1a.X > pt1b.X) + SwapPoints(pt1a, pt1b); + if (pt2a.X > pt2b.X) + SwapPoints(pt2a, pt2b); + if (pt1a.X > pt2a.X) + pt1 = pt1a; + else + pt1 = pt2a; + if (pt1b.X < pt2b.X) + pt2 = pt1b; + else + pt2 = pt2b; + return pt1.X < pt2.X; + } else { + if (pt1a.Y < pt1b.Y) + SwapPoints(pt1a, pt1b); + if (pt2a.Y < pt2b.Y) + SwapPoints(pt2a, pt2b); + if (pt1a.Y < pt2a.Y) + pt1 = pt1a; + else + pt1 = pt2a; + if (pt1b.Y > pt2b.Y) + pt2 = pt1b; + else + pt2 = pt2b; + return pt1.Y > pt2.Y; + } +} +//------------------------------------------------------------------------------ + +bool FirstIsBottomPt(const OutPt *btmPt1, const OutPt *btmPt2) { + OutPt *p = btmPt1->Prev; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) + p = p->Prev; + double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + p = btmPt1->Next; + while ((p->Pt == btmPt1->Pt) && (p != btmPt1)) + p = p->Next; + double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt)); + + p = btmPt2->Prev; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) + p = p->Prev; + double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + p = btmPt2->Next; + while ((p->Pt == btmPt2->Pt) && (p != btmPt2)) + p = p->Next; + double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt)); + + if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) && + std::min(dx1p, dx1n) == std::min(dx2p, dx2n)) + return Area(btmPt1) > 0; // if otherwise identical use orientation + else + return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n); +} +//------------------------------------------------------------------------------ + +OutPt *GetBottomPt(OutPt *pp) { + OutPt *dups = 0; + OutPt *p = pp->Next; + while (p != pp) { + if (p->Pt.Y > pp->Pt.Y) { + pp = p; + dups = 0; + } else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) { + if (p->Pt.X < pp->Pt.X) { + dups = 0; + pp = p; + } else { + if (p->Next != pp && p->Prev != pp) + dups = p; + } + } + p = p->Next; + } + if (dups) { + // there appears to be at least 2 vertices at BottomPt so ... + while (dups != p) { + if (!FirstIsBottomPt(p, dups)) + pp = dups; + dups = dups->Next; + while (dups->Pt != pp->Pt) + dups = dups->Next; + } + } + return pp; +} +//------------------------------------------------------------------------------ + +bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, const IntPoint pt2, + const IntPoint pt3) { + if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) + return false; + else if (pt1.X != pt3.X) + return (pt2.X > pt1.X) == (pt2.X < pt3.X); + else + return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y); +} +//------------------------------------------------------------------------------ + +bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) { + if (seg1a > seg1b) + std::swap(seg1a, seg1b); + if (seg2a > seg2b) + std::swap(seg2a, seg2b); + return (seg1a < seg2b) && (seg2a < seg1b); +} + +//------------------------------------------------------------------------------ +// ClipperBase class methods ... +//------------------------------------------------------------------------------ + +ClipperBase::ClipperBase() // constructor +{ + m_CurrentLM = m_MinimaList.begin(); // begin() == end() here + m_UseFullRange = false; +} +//------------------------------------------------------------------------------ + +ClipperBase::~ClipperBase() // destructor +{ + Clear(); +} +//------------------------------------------------------------------------------ + +void RangeTest(const IntPoint &Pt, bool &useFullRange) { + if (useFullRange) { + if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange) + throw clipperException("Coordinate outside allowed range"); + } else if (Pt.X > loRange || Pt.Y > loRange || -Pt.X > loRange || + -Pt.Y > loRange) { + useFullRange = true; + RangeTest(Pt, useFullRange); + } +} +//------------------------------------------------------------------------------ + +TEdge *FindNextLocMin(TEdge *E) { + for (;;) { + while (E->Bot != E->Prev->Bot || E->Curr == E->Top) + E = E->Next; + if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev)) + break; + while (IsHorizontal(*E->Prev)) + E = E->Prev; + TEdge *E2 = E; + while (IsHorizontal(*E)) + E = E->Next; + if (E->Top.Y == E->Prev->Bot.Y) + continue; // ie just an intermediate horz. + if (E2->Prev->Bot.X < E->Bot.X) + E = E2; + break; + } + return E; +} +//------------------------------------------------------------------------------ + +TEdge *ClipperBase::ProcessBound(TEdge *E, bool NextIsForward) { + TEdge *Result = E; + TEdge *Horz = 0; + + if (E->OutIdx == Skip) { + // if edges still remain in the current bound beyond the skip edge then + // create another LocMin and call ProcessBound once more + if (NextIsForward) { + while (E->Top.Y == E->Next->Bot.Y) + E = E->Next; + // don't include top horizontals when parsing a bound a second time, + // they will be contained in the opposite bound ... + while (E != Result && IsHorizontal(*E)) + E = E->Prev; + } else { + while (E->Top.Y == E->Prev->Bot.Y) + E = E->Prev; + while (E != Result && IsHorizontal(*E)) + E = E->Next; + } + + if (E == Result) { + if (NextIsForward) + Result = E->Next; + else + Result = E->Prev; + } else { + // there are more edges in the bound beyond result starting with E + if (NextIsForward) + E = Result->Next; + else + E = Result->Prev; + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + locMin.LeftBound = 0; + locMin.RightBound = E; + E->WindDelta = 0; + Result = ProcessBound(E, NextIsForward); + m_MinimaList.push_back(locMin); + } + return Result; + } + + TEdge *EStart; + + if (IsHorizontal(*E)) { + // We need to be careful with open paths because this may not be a + // true local minima (ie E may be following a skip edge). + // Also, consecutive horz. edges may start heading left before going right. + if (NextIsForward) + EStart = E->Prev; + else + EStart = E->Next; + if (IsHorizontal(*EStart)) // ie an adjoining horizontal skip edge + { + if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X) + ReverseHorizontal(*E); + } else if (EStart->Bot.X != E->Bot.X) + ReverseHorizontal(*E); + } + + EStart = E; + if (NextIsForward) { + while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip) + Result = Result->Next; + if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) { + // nb: at the top of a bound, horizontals are added to the bound + // only when the preceding edge attaches to the horizontal's left vertex + // unless a Skip edge is encountered when that becomes the top divide + Horz = Result; + while (IsHorizontal(*Horz->Prev)) + Horz = Horz->Prev; + if (Horz->Prev->Top.X > Result->Next->Top.X) + Result = Horz->Prev; + } + while (E != Result) { + E->NextInLML = E->Next; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + E = E->Next; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + Result = Result->Next; // move to the edge just beyond current bound + } else { + while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip) + Result = Result->Prev; + if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) { + Horz = Result; + while (IsHorizontal(*Horz->Next)) + Horz = Horz->Next; + if (Horz->Next->Top.X == Result->Prev->Top.X || + Horz->Next->Top.X > Result->Prev->Top.X) + Result = Horz->Next; + } + + while (E != Result) { + E->NextInLML = E->Prev; + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + E = E->Prev; + } + if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X) + ReverseHorizontal(*E); + Result = Result->Prev; // move to the edge just beyond current bound + } + + return Result; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) { +#ifdef use_lines + if (!Closed && PolyTyp == ptClip) + throw clipperException("AddPath: Open paths must be subject."); +#else + if (!Closed) + throw clipperException("AddPath: Open paths have been disabled."); +#endif + + int highI = (int)pg.size() - 1; + if (Closed) + while (highI > 0 && (pg[highI] == pg[0])) + --highI; + while (highI > 0 && (pg[highI] == pg[highI - 1])) + --highI; + if ((Closed && highI < 2) || (!Closed && highI < 1)) + return false; + + // create a new edge array ... + TEdge *edges = new TEdge[highI + 1]; + + bool IsFlat = true; + // 1. Basic (first) edge initialization ... + try { + edges[1].Curr = pg[1]; + RangeTest(pg[0], m_UseFullRange); + RangeTest(pg[highI], m_UseFullRange); + InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]); + InitEdge(&edges[highI], &edges[0], &edges[highI - 1], pg[highI]); + for (int i = highI - 1; i >= 1; --i) { + RangeTest(pg[i], m_UseFullRange); + InitEdge(&edges[i], &edges[i + 1], &edges[i - 1], pg[i]); + } + } catch (...) { + delete[] edges; + throw; // range test fails + } + TEdge *eStart = &edges[0]; + + // 2. Remove duplicate vertices, and (when closed) collinear edges ... + TEdge *E = eStart, *eLoopStop = eStart; + for (;;) { + // nb: allows matching start and end points when not Closed ... + if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) { + if (E == E->Next) + break; + if (E == eStart) + eStart = E->Next; + E = RemoveEdge(E); + eLoopStop = E; + continue; + } + if (E->Prev == E->Next) + break; // only two vertices + else if (Closed && + SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr, + m_UseFullRange) && + (!m_PreserveCollinear || + !Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) { + // Collinear edges are allowed for open paths but in closed paths + // the default is to merge adjacent collinear edges into a single edge. + // However, if the PreserveCollinear property is enabled, only overlapping + // collinear edges (ie spikes) will be removed from closed paths. + if (E == eStart) + eStart = E->Next; + E = RemoveEdge(E); + E = E->Prev; + eLoopStop = E; + continue; + } + E = E->Next; + if ((E == eLoopStop) || (!Closed && E->Next == eStart)) + break; + } + + if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) { + delete[] edges; + return false; + } + + if (!Closed) { + m_HasOpenPaths = true; + eStart->Prev->OutIdx = Skip; + } + + // 3. Do second stage of edge initialization ... + E = eStart; + do { + InitEdge2(*E, PolyTyp); + E = E->Next; + if (IsFlat && E->Curr.Y != eStart->Curr.Y) + IsFlat = false; + } while (E != eStart); + + // 4. Finally, add edge bounds to LocalMinima list ... + + // Totally flat paths must be handled differently when adding them + // to LocalMinima list to avoid endless loops etc ... + if (IsFlat) { + if (Closed) { + delete[] edges; + return false; + } + E->Prev->OutIdx = Skip; + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + locMin.LeftBound = 0; + locMin.RightBound = E; + locMin.RightBound->Side = esRight; + locMin.RightBound->WindDelta = 0; + for (;;) { + if (E->Bot.X != E->Prev->Top.X) + ReverseHorizontal(*E); + if (E->Next->OutIdx == Skip) + break; + E->NextInLML = E->Next; + E = E->Next; + } + m_MinimaList.push_back(locMin); + m_edges.push_back(edges); + return true; + } + + m_edges.push_back(edges); + bool leftBoundIsForward; + TEdge *EMin = 0; + + // workaround to avoid an endless loop in the while loop below when + // open paths have matching start and end points ... + if (E->Prev->Bot == E->Prev->Top) + E = E->Next; + + for (;;) { + E = FindNextLocMin(E); + if (E == EMin) + break; + else if (!EMin) + EMin = E; + + // E and E.Prev now share a local minima (left aligned if horizontal). + // Compare their slopes to find which starts which bound ... + MinimaList::value_type locMin; + locMin.Y = E->Bot.Y; + if (E->Dx < E->Prev->Dx) { + locMin.LeftBound = E->Prev; + locMin.RightBound = E; + leftBoundIsForward = false; // Q.nextInLML = Q.prev + } else { + locMin.LeftBound = E; + locMin.RightBound = E->Prev; + leftBoundIsForward = true; // Q.nextInLML = Q.next + } + + if (!Closed) + locMin.LeftBound->WindDelta = 0; + else if (locMin.LeftBound->Next == locMin.RightBound) + locMin.LeftBound->WindDelta = -1; + else + locMin.LeftBound->WindDelta = 1; + locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta; + + E = ProcessBound(locMin.LeftBound, leftBoundIsForward); + if (E->OutIdx == Skip) + E = ProcessBound(E, leftBoundIsForward); + + TEdge *E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward); + if (E2->OutIdx == Skip) + E2 = ProcessBound(E2, !leftBoundIsForward); + + if (locMin.LeftBound->OutIdx == Skip) + locMin.LeftBound = 0; + else if (locMin.RightBound->OutIdx == Skip) + locMin.RightBound = 0; + m_MinimaList.push_back(locMin); + if (!leftBoundIsForward) + E = E2; + } + return true; +} +//------------------------------------------------------------------------------ + +bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) { + bool result = false; + for (Paths::size_type i = 0; i < ppg.size(); ++i) + if (AddPath(ppg[i], PolyTyp, Closed)) + result = true; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Clear() { + DisposeLocalMinimaList(); + for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) { + TEdge *edges = m_edges[i]; + delete[] edges; + } + m_edges.clear(); + m_UseFullRange = false; + m_HasOpenPaths = false; +} +//------------------------------------------------------------------------------ + +void ClipperBase::Reset() { + m_CurrentLM = m_MinimaList.begin(); + if (m_CurrentLM == m_MinimaList.end()) + return; // ie nothing to process + std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter()); + + m_Scanbeam = ScanbeamList(); // clears/resets priority_queue + // reset all edges ... + for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end(); + ++lm) { + InsertScanbeam(lm->Y); + TEdge *e = lm->LeftBound; + if (e) { + e->Curr = e->Bot; + e->Side = esLeft; + e->OutIdx = Unassigned; + } + + e = lm->RightBound; + if (e) { + e->Curr = e->Bot; + e->Side = esRight; + e->OutIdx = Unassigned; + } + } + m_ActiveEdges = 0; + m_CurrentLM = m_MinimaList.begin(); +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeLocalMinimaList() { + m_MinimaList.clear(); + m_CurrentLM = m_MinimaList.begin(); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) { + if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y) + return false; + locMin = &(*m_CurrentLM); + ++m_CurrentLM; + return true; +} +//------------------------------------------------------------------------------ + +IntRect ClipperBase::GetBounds() { + IntRect result; + MinimaList::iterator lm = m_MinimaList.begin(); + if (lm == m_MinimaList.end()) { + result.left = result.top = result.right = result.bottom = 0; + return result; + } + result.left = lm->LeftBound->Bot.X; + result.top = lm->LeftBound->Bot.Y; + result.right = lm->LeftBound->Bot.X; + result.bottom = lm->LeftBound->Bot.Y; + while (lm != m_MinimaList.end()) { + // todo - needs fixing for open paths + result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y); + TEdge *e = lm->LeftBound; + for (;;) { + TEdge *bottomE = e; + while (e->NextInLML) { + if (e->Bot.X < result.left) + result.left = e->Bot.X; + if (e->Bot.X > result.right) + result.right = e->Bot.X; + e = e->NextInLML; + } + result.left = std::min(result.left, e->Bot.X); + result.right = std::max(result.right, e->Bot.X); + result.left = std::min(result.left, e->Top.X); + result.right = std::max(result.right, e->Top.X); + result.top = std::min(result.top, e->Top.Y); + if (bottomE == lm->LeftBound) + e = lm->RightBound; + else + break; + } + ++lm; + } + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::InsertScanbeam(const cInt Y) { m_Scanbeam.push(Y); } +//------------------------------------------------------------------------------ + +bool ClipperBase::PopScanbeam(cInt &Y) { + if (m_Scanbeam.empty()) + return false; + Y = m_Scanbeam.top(); + m_Scanbeam.pop(); + while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) { + m_Scanbeam.pop(); + } // Pop duplicates. + return true; +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeAllOutRecs() { + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) + DisposeOutRec(i); + m_PolyOuts.clear(); +} +//------------------------------------------------------------------------------ + +void ClipperBase::DisposeOutRec(PolyOutList::size_type index) { + OutRec *outRec = m_PolyOuts[index]; + if (outRec->Pts) + DisposeOutPts(outRec->Pts); + delete outRec; + m_PolyOuts[index] = 0; +} +//------------------------------------------------------------------------------ + +void ClipperBase::DeleteFromAEL(TEdge *e) { + TEdge *AelPrev = e->PrevInAEL; + TEdge *AelNext = e->NextInAEL; + if (!AelPrev && !AelNext && (e != m_ActiveEdges)) + return; // already deleted + if (AelPrev) + AelPrev->NextInAEL = AelNext; + else + m_ActiveEdges = AelNext; + if (AelNext) + AelNext->PrevInAEL = AelPrev; + e->NextInAEL = 0; + e->PrevInAEL = 0; +} +//------------------------------------------------------------------------------ + +OutRec *ClipperBase::CreateOutRec() { + OutRec *result = new OutRec; + result->IsHole = false; + result->IsOpen = false; + result->FirstLeft = 0; + result->Pts = 0; + result->BottomPt = 0; + result->PolyNd = 0; + m_PolyOuts.push_back(result); + result->Idx = (int)m_PolyOuts.size() - 1; + return result; +} +//------------------------------------------------------------------------------ + +void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) { + // check that one or other edge hasn't already been removed from AEL ... + if (Edge1->NextInAEL == Edge1->PrevInAEL || + Edge2->NextInAEL == Edge2->PrevInAEL) + return; + + if (Edge1->NextInAEL == Edge2) { + TEdge *Next = Edge2->NextInAEL; + if (Next) + Next->PrevInAEL = Edge1; + TEdge *Prev = Edge1->PrevInAEL; + if (Prev) + Prev->NextInAEL = Edge2; + Edge2->PrevInAEL = Prev; + Edge2->NextInAEL = Edge1; + Edge1->PrevInAEL = Edge2; + Edge1->NextInAEL = Next; + } else if (Edge2->NextInAEL == Edge1) { + TEdge *Next = Edge1->NextInAEL; + if (Next) + Next->PrevInAEL = Edge2; + TEdge *Prev = Edge2->PrevInAEL; + if (Prev) + Prev->NextInAEL = Edge1; + Edge1->PrevInAEL = Prev; + Edge1->NextInAEL = Edge2; + Edge2->PrevInAEL = Edge1; + Edge2->NextInAEL = Next; + } else { + TEdge *Next = Edge1->NextInAEL; + TEdge *Prev = Edge1->PrevInAEL; + Edge1->NextInAEL = Edge2->NextInAEL; + if (Edge1->NextInAEL) + Edge1->NextInAEL->PrevInAEL = Edge1; + Edge1->PrevInAEL = Edge2->PrevInAEL; + if (Edge1->PrevInAEL) + Edge1->PrevInAEL->NextInAEL = Edge1; + Edge2->NextInAEL = Next; + if (Edge2->NextInAEL) + Edge2->NextInAEL->PrevInAEL = Edge2; + Edge2->PrevInAEL = Prev; + if (Edge2->PrevInAEL) + Edge2->PrevInAEL->NextInAEL = Edge2; + } + + if (!Edge1->PrevInAEL) + m_ActiveEdges = Edge1; + else if (!Edge2->PrevInAEL) + m_ActiveEdges = Edge2; +} +//------------------------------------------------------------------------------ + +void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) { + if (!e->NextInLML) + throw clipperException("UpdateEdgeIntoAEL: invalid call"); + + e->NextInLML->OutIdx = e->OutIdx; + TEdge *AelPrev = e->PrevInAEL; + TEdge *AelNext = e->NextInAEL; + if (AelPrev) + AelPrev->NextInAEL = e->NextInLML; + else + m_ActiveEdges = e->NextInLML; + if (AelNext) + AelNext->PrevInAEL = e->NextInLML; + e->NextInLML->Side = e->Side; + e->NextInLML->WindDelta = e->WindDelta; + e->NextInLML->WindCnt = e->WindCnt; + e->NextInLML->WindCnt2 = e->WindCnt2; + e = e->NextInLML; + e->Curr = e->Bot; + e->PrevInAEL = AelPrev; + e->NextInAEL = AelNext; + if (!IsHorizontal(*e)) + InsertScanbeam(e->Top.Y); +} +//------------------------------------------------------------------------------ + +bool ClipperBase::LocalMinimaPending() { + return (m_CurrentLM != m_MinimaList.end()); +} + +//------------------------------------------------------------------------------ +// TClipper methods ... +//------------------------------------------------------------------------------ + +Clipper::Clipper(int initOptions) + : ClipperBase() // constructor +{ + m_ExecuteLocked = false; + m_UseFullRange = false; + m_ReverseOutput = ((initOptions & ioReverseSolution) != 0); + m_StrictSimple = ((initOptions & ioStrictlySimple) != 0); + m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0); + m_HasOpenPaths = false; +#ifdef use_xyz + m_ZFill = 0; +#endif +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::ZFillFunction(ZFillCallback zFillFunc) { m_ZFill = zFillFunc; } +//------------------------------------------------------------------------------ +#endif + +bool Clipper::Execute(ClipType clipType, Paths &solution, + PolyFillType fillType) { + return Execute(clipType, solution, fillType, fillType); +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree &polytree, + PolyFillType fillType) { + return Execute(clipType, polytree, fillType, fillType); +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, Paths &solution, + PolyFillType subjFillType, PolyFillType clipFillType) { + if (m_ExecuteLocked) + return false; + if (m_HasOpenPaths) + throw clipperException( + "Error: PolyTree struct is needed for open path clipping."); + m_ExecuteLocked = true; + solution.resize(0); + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = false; + bool succeeded = ExecuteInternal(); + if (succeeded) + BuildResult(solution); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +bool Clipper::Execute(ClipType clipType, PolyTree &polytree, + PolyFillType subjFillType, PolyFillType clipFillType) { + if (m_ExecuteLocked) + return false; + m_ExecuteLocked = true; + m_SubjFillType = subjFillType; + m_ClipFillType = clipFillType; + m_ClipType = clipType; + m_UsingPolyTree = true; + bool succeeded = ExecuteInternal(); + if (succeeded) + BuildResult2(polytree); + DisposeAllOutRecs(); + m_ExecuteLocked = false; + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::FixHoleLinkage(OutRec &outrec) { + // skip OutRecs that (a) contain outermost polygons or + //(b) already have the correct owner/child linkage ... + if (!outrec.FirstLeft || + (outrec.IsHole != outrec.FirstLeft->IsHole && outrec.FirstLeft->Pts)) + return; + + OutRec *orfl = outrec.FirstLeft; + while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts)) + orfl = orfl->FirstLeft; + outrec.FirstLeft = orfl; +} +//------------------------------------------------------------------------------ + +bool Clipper::ExecuteInternal() { + bool succeeded = true; + try { + Reset(); + m_Maxima = MaximaList(); + m_SortedEdges = 0; + + succeeded = true; + cInt botY, topY; + if (!PopScanbeam(botY)) + return false; + InsertLocalMinimaIntoAEL(botY); + while (PopScanbeam(topY) || LocalMinimaPending()) { + ProcessHorizontals(); + ClearGhostJoins(); + if (!ProcessIntersections(topY)) { + succeeded = false; + break; + } + ProcessEdgesAtTopOfScanbeam(topY); + botY = topY; + InsertLocalMinimaIntoAEL(botY); + } + } catch (...) { + succeeded = false; + } + + if (succeeded) { + // fix orientations ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts || outRec->IsOpen) + continue; + if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0)) + ReversePolyPtLinks(outRec->Pts); + } + + if (!m_Joins.empty()) + JoinCommonEdges(); + + // unfortunately FixupOutPolygon() must be done after JoinCommonEdges() + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->Pts) + continue; + if (outRec->IsOpen) + FixupOutPolyline(*outRec); + else + FixupOutPolygon(*outRec); + } + + if (m_StrictSimple) + DoSimplePolygons(); + } + + ClearJoins(); + ClearGhostJoins(); + return succeeded; +} +//------------------------------------------------------------------------------ + +void Clipper::SetWindingCount(TEdge &edge) { + TEdge *e = edge.PrevInAEL; + // find the edge of the same polytype that immediately precedes 'edge' in AEL + while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0))) + e = e->PrevInAEL; + if (!e) { + if (edge.WindDelta == 0) { + PolyFillType pft = + (edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType); + edge.WindCnt = (pft == pftNegative ? -1 : 1); + } else + edge.WindCnt = edge.WindDelta; + edge.WindCnt2 = 0; + e = m_ActiveEdges; // ie get ready to calc WindCnt2 + } else if (edge.WindDelta == 0 && m_ClipType != ctUnion) { + edge.WindCnt = 1; + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; // ie get ready to calc WindCnt2 + } else if (IsEvenOddFillType(edge)) { + // EvenOdd filling ... + if (edge.WindDelta == 0) { + // are we inside a subj polygon ... + bool Inside = true; + TEdge *e2 = e->PrevInAEL; + while (e2) { + if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0) + Inside = !Inside; + e2 = e2->PrevInAEL; + } + edge.WindCnt = (Inside ? 0 : 1); + } else { + edge.WindCnt = edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; // ie get ready to calc WindCnt2 + } else { + // nonZero, Positive or Negative filling ... + if (e->WindCnt * e->WindDelta < 0) { + // prev edge is 'decreasing' WindCount (WC) toward zero + // so we're outside the previous polygon ... + if (Abs(e->WindCnt) > 1) { + // outside prev poly but still inside another. + // when reversing direction of prev poly use the same WC + if (e->WindDelta * edge.WindDelta < 0) + edge.WindCnt = e->WindCnt; + // otherwise continue to 'decrease' WC ... + else + edge.WindCnt = e->WindCnt + edge.WindDelta; + } else + // now outside all polys of same polytype so set own WC ... + edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta); + } else { + // prev edge is 'increasing' WindCount (WC) away from zero + // so we're inside the previous polygon ... + if (edge.WindDelta == 0) + edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1); + // if wind direction is reversing prev then use same WC + else if (e->WindDelta * edge.WindDelta < 0) + edge.WindCnt = e->WindCnt; + // otherwise add to WC ... + else + edge.WindCnt = e->WindCnt + edge.WindDelta; + } + edge.WindCnt2 = e->WindCnt2; + e = e->NextInAEL; // ie get ready to calc WindCnt2 + } + + // update WindCnt2 ... + if (IsEvenOddAltFillType(edge)) { + // EvenOdd filling ... + while (e != &edge) { + if (e->WindDelta != 0) + edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0); + e = e->NextInAEL; + } + } else { + // nonZero, Positive or Negative filling ... + while (e != &edge) { + edge.WindCnt2 += e->WindDelta; + e = e->NextInAEL; + } + } +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddFillType(const TEdge &edge) const { + if (edge.PolyTyp == ptSubject) + return m_SubjFillType == pftEvenOdd; + else + return m_ClipFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsEvenOddAltFillType(const TEdge &edge) const { + if (edge.PolyTyp == ptSubject) + return m_ClipFillType == pftEvenOdd; + else + return m_SubjFillType == pftEvenOdd; +} +//------------------------------------------------------------------------------ + +bool Clipper::IsContributing(const TEdge &edge) const { + PolyFillType pft, pft2; + if (edge.PolyTyp == ptSubject) { + pft = m_SubjFillType; + pft2 = m_ClipFillType; + } else { + pft = m_ClipFillType; + pft2 = m_SubjFillType; + } + + switch (pft) { + case pftEvenOdd: + // return false if a subj line has been flagged as inside a subj polygon + if (edge.WindDelta == 0 && edge.WindCnt != 1) + return false; + break; + case pftNonZero: + if (Abs(edge.WindCnt) != 1) + return false; + break; + case pftPositive: + if (edge.WindCnt != 1) + return false; + break; + default: // pftNegative + if (edge.WindCnt != -1) + return false; + } + + switch (m_ClipType) { + case ctIntersection: + switch (pft2) { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctUnion: + switch (pft2) { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + break; + case ctDifference: + if (edge.PolyTyp == ptSubject) + switch (pft2) { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + switch (pft2) { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 != 0); + case pftPositive: + return (edge.WindCnt2 > 0); + default: + return (edge.WindCnt2 < 0); + } + break; + case ctXor: + if (edge.WindDelta == 0) // XOr always contributing unless open + switch (pft2) { + case pftEvenOdd: + case pftNonZero: + return (edge.WindCnt2 == 0); + case pftPositive: + return (edge.WindCnt2 <= 0); + default: + return (edge.WindCnt2 >= 0); + } + else + return true; + break; + default: + return true; + } +} +//------------------------------------------------------------------------------ + +OutPt *Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { + OutPt *result; + TEdge *e, *prevE; + if (IsHorizontal(*e2) || (e1->Dx > e2->Dx)) { + result = AddOutPt(e1, Pt); + e2->OutIdx = e1->OutIdx; + e1->Side = esLeft; + e2->Side = esRight; + e = e1; + if (e->PrevInAEL == e2) + prevE = e2->PrevInAEL; + else + prevE = e->PrevInAEL; + } else { + result = AddOutPt(e2, Pt); + e1->OutIdx = e2->OutIdx; + e1->Side = esRight; + e2->Side = esLeft; + e = e2; + if (e->PrevInAEL == e1) + prevE = e1->PrevInAEL; + else + prevE = e->PrevInAEL; + } + + if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y) { + cInt xPrev = TopX(*prevE, Pt.Y); + cInt xE = TopX(*e, Pt.Y); + if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) && + SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y), + e->Top, m_UseFullRange)) { + OutPt *outPt = AddOutPt(prevE, Pt); + AddJoin(result, outPt, e->Top); + } + } + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) { + AddOutPt(e1, Pt); + if (e2->WindDelta == 0) + AddOutPt(e2, Pt); + if (e1->OutIdx == e2->OutIdx) { + e1->OutIdx = Unassigned; + e2->OutIdx = Unassigned; + } else if (e1->OutIdx < e2->OutIdx) + AppendPolygon(e1, e2); + else + AppendPolygon(e2, e1); +} +//------------------------------------------------------------------------------ + +void Clipper::AddEdgeToSEL(TEdge *edge) { + // SEL pointers in PEdge are reused to build a list of horizontal edges. + // However, we don't need to worry about order with horizontal edge + // processing. + if (!m_SortedEdges) { + m_SortedEdges = edge; + edge->PrevInSEL = 0; + edge->NextInSEL = 0; + } else { + edge->NextInSEL = m_SortedEdges; + edge->PrevInSEL = 0; + m_SortedEdges->PrevInSEL = edge; + m_SortedEdges = edge; + } +} +//------------------------------------------------------------------------------ + +bool Clipper::PopEdgeFromSEL(TEdge *&edge) { + if (!m_SortedEdges) + return false; + edge = m_SortedEdges; + DeleteFromSEL(m_SortedEdges); + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::CopyAELToSEL() { + TEdge *e = m_ActiveEdges; + m_SortedEdges = e; + while (e) { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) { + Join *j = new Join; + j->OutPt1 = op1; + j->OutPt2 = op2; + j->OffPt = OffPt; + m_Joins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearJoins() { + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) + delete m_Joins[i]; + m_Joins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::ClearGhostJoins() { + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++) + delete m_GhostJoins[i]; + m_GhostJoins.resize(0); +} +//------------------------------------------------------------------------------ + +void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) { + Join *j = new Join; + j->OutPt1 = op; + j->OutPt2 = 0; + j->OffPt = OffPt; + m_GhostJoins.push_back(j); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) { + const LocalMinimum *lm; + while (PopLocalMinima(botY, lm)) { + TEdge *lb = lm->LeftBound; + TEdge *rb = lm->RightBound; + + OutPt *Op1 = 0; + if (!lb) { + // nb: don't insert LB into either AEL or SEL + InsertEdgeIntoAEL(rb, 0); + SetWindingCount(*rb); + if (IsContributing(*rb)) + Op1 = AddOutPt(rb, rb->Bot); + } else if (!rb) { + InsertEdgeIntoAEL(lb, 0); + SetWindingCount(*lb); + if (IsContributing(*lb)) + Op1 = AddOutPt(lb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } else { + InsertEdgeIntoAEL(lb, 0); + InsertEdgeIntoAEL(rb, lb); + SetWindingCount(*lb); + rb->WindCnt = lb->WindCnt; + rb->WindCnt2 = lb->WindCnt2; + if (IsContributing(*lb)) + Op1 = AddLocalMinPoly(lb, rb, lb->Bot); + InsertScanbeam(lb->Top.Y); + } + + if (rb) { + if (IsHorizontal(*rb)) { + AddEdgeToSEL(rb); + if (rb->NextInLML) + InsertScanbeam(rb->NextInLML->Top.Y); + } else + InsertScanbeam(rb->Top.Y); + } + + if (!lb || !rb) + continue; + + // if any output polygons share an edge, they'll need joining later ... + if (Op1 && IsHorizontal(*rb) && m_GhostJoins.size() > 0 && + (rb->WindDelta != 0)) { + for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) { + Join *jr = m_GhostJoins[i]; + // if the horizontal Rb and a 'ghost' horizontal overlap, then convert + // the 'ghost' join to a real join ready for later ... + if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X, + rb->Top.X)) + AddJoin(jr->OutPt1, Op1, jr->OffPt); + } + } + + if (lb->OutIdx >= 0 && lb->PrevInAEL && + lb->PrevInAEL->Curr.X == lb->Bot.X && lb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top, + m_UseFullRange) && + (lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) { + OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot); + AddJoin(Op1, Op2, lb->Top); + } + + if (lb->NextInAEL != rb) { + + if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 && + SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr, + rb->Top, m_UseFullRange) && + (rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) { + OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot); + AddJoin(Op1, Op2, rb->Top); + } + + TEdge *e = lb->NextInAEL; + if (e) { + while (e != rb) { + // nb: For calculating winding counts etc, IntersectEdges() assumes + // that param1 will be to the Right of param2 ABOVE the intersection + // ... + IntersectEdges(rb, e, lb->Curr); // order important here + e = e->NextInAEL; + } + } + } + } +} +//------------------------------------------------------------------------------ + +void Clipper::DeleteFromSEL(TEdge *e) { + TEdge *SelPrev = e->PrevInSEL; + TEdge *SelNext = e->NextInSEL; + if (!SelPrev && !SelNext && (e != m_SortedEdges)) + return; // already deleted + if (SelPrev) + SelPrev->NextInSEL = SelNext; + else + m_SortedEdges = SelNext; + if (SelNext) + SelNext->PrevInSEL = SelPrev; + e->NextInSEL = 0; + e->PrevInSEL = 0; +} +//------------------------------------------------------------------------------ + +#ifdef use_xyz +void Clipper::SetZ(IntPoint &pt, TEdge &e1, TEdge &e2) { + if (pt.Z != 0 || !m_ZFill) + return; + else if (pt == e1.Bot) + pt.Z = e1.Bot.Z; + else if (pt == e1.Top) + pt.Z = e1.Top.Z; + else if (pt == e2.Bot) + pt.Z = e2.Bot.Z; + else if (pt == e2.Top) + pt.Z = e2.Top.Z; + else + (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt); +} +//------------------------------------------------------------------------------ +#endif + +void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) { + bool e1Contributing = (e1->OutIdx >= 0); + bool e2Contributing = (e2->OutIdx >= 0); + +#ifdef use_xyz + SetZ(Pt, *e1, *e2); +#endif + +#ifdef use_lines + // if either edge is on an OPEN path ... + if (e1->WindDelta == 0 || e2->WindDelta == 0) { + // ignore subject-subject open path intersections UNLESS they + // are both open paths, AND they are both 'contributing maximas' ... + if (e1->WindDelta == 0 && e2->WindDelta == 0) + return; + + // if intersecting a subj line with a subj poly ... + else if (e1->PolyTyp == e2->PolyTyp && e1->WindDelta != e2->WindDelta && + m_ClipType == ctUnion) { + if (e1->WindDelta == 0) { + if (e2Contributing) { + AddOutPt(e1, Pt); + if (e1Contributing) + e1->OutIdx = Unassigned; + } + } else { + if (e1Contributing) { + AddOutPt(e2, Pt); + if (e2Contributing) + e2->OutIdx = Unassigned; + } + } + } else if (e1->PolyTyp != e2->PolyTyp) { + // toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ... + if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 && + (m_ClipType != ctUnion || e2->WindCnt2 == 0)) { + AddOutPt(e1, Pt); + if (e1Contributing) + e1->OutIdx = Unassigned; + } else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) && + (m_ClipType != ctUnion || e1->WindCnt2 == 0)) { + AddOutPt(e2, Pt); + if (e2Contributing) + e2->OutIdx = Unassigned; + } + } + return; + } +#endif + + // update winding counts... + // assumes that e1 will be to the Right of e2 ABOVE the intersection + if (e1->PolyTyp == e2->PolyTyp) { + if (IsEvenOddFillType(*e1)) { + int oldE1WindCnt = e1->WindCnt; + e1->WindCnt = e2->WindCnt; + e2->WindCnt = oldE1WindCnt; + } else { + if (e1->WindCnt + e2->WindDelta == 0) + e1->WindCnt = -e1->WindCnt; + else + e1->WindCnt += e2->WindDelta; + if (e2->WindCnt - e1->WindDelta == 0) + e2->WindCnt = -e2->WindCnt; + else + e2->WindCnt -= e1->WindDelta; + } + } else { + if (!IsEvenOddFillType(*e2)) + e1->WindCnt2 += e2->WindDelta; + else + e1->WindCnt2 = (e1->WindCnt2 == 0) ? 1 : 0; + if (!IsEvenOddFillType(*e1)) + e2->WindCnt2 -= e1->WindDelta; + else + e2->WindCnt2 = (e2->WindCnt2 == 0) ? 1 : 0; + } + + PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2; + if (e1->PolyTyp == ptSubject) { + e1FillType = m_SubjFillType; + e1FillType2 = m_ClipFillType; + } else { + e1FillType = m_ClipFillType; + e1FillType2 = m_SubjFillType; + } + if (e2->PolyTyp == ptSubject) { + e2FillType = m_SubjFillType; + e2FillType2 = m_ClipFillType; + } else { + e2FillType = m_ClipFillType; + e2FillType2 = m_SubjFillType; + } + + cInt e1Wc, e2Wc; + switch (e1FillType) { + case pftPositive: + e1Wc = e1->WindCnt; + break; + case pftNegative: + e1Wc = -e1->WindCnt; + break; + default: + e1Wc = Abs(e1->WindCnt); + } + switch (e2FillType) { + case pftPositive: + e2Wc = e2->WindCnt; + break; + case pftNegative: + e2Wc = -e2->WindCnt; + break; + default: + e2Wc = Abs(e2->WindCnt); + } + + if (e1Contributing && e2Contributing) { + if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) || + (e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor)) { + AddLocalMaxPoly(e1, e2, Pt); + } else { + AddOutPt(e1, Pt); + AddOutPt(e2, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } else if (e1Contributing) { + if (e2Wc == 0 || e2Wc == 1) { + AddOutPt(e1, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } else if (e2Contributing) { + if (e1Wc == 0 || e1Wc == 1) { + AddOutPt(e2, Pt); + SwapSides(*e1, *e2); + SwapPolyIndexes(*e1, *e2); + } + } else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) { + // neither edge is currently contributing ... + + cInt e1Wc2, e2Wc2; + switch (e1FillType2) { + case pftPositive: + e1Wc2 = e1->WindCnt2; + break; + case pftNegative: + e1Wc2 = -e1->WindCnt2; + break; + default: + e1Wc2 = Abs(e1->WindCnt2); + } + switch (e2FillType2) { + case pftPositive: + e2Wc2 = e2->WindCnt2; + break; + case pftNegative: + e2Wc2 = -e2->WindCnt2; + break; + default: + e2Wc2 = Abs(e2->WindCnt2); + } + + if (e1->PolyTyp != e2->PolyTyp) { + AddLocalMinPoly(e1, e2, Pt); + } else if (e1Wc == 1 && e2Wc == 1) + switch (m_ClipType) { + case ctIntersection: + if (e1Wc2 > 0 && e2Wc2 > 0) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctUnion: + if (e1Wc2 <= 0 && e2Wc2 <= 0) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctDifference: + if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + AddLocalMinPoly(e1, e2, Pt); + break; + case ctXor: + AddLocalMinPoly(e1, e2, Pt); + } + else + SwapSides(*e1, *e2); + } +} +//------------------------------------------------------------------------------ + +void Clipper::SetHoleState(TEdge *e, OutRec *outrec) { + TEdge *e2 = e->PrevInAEL; + TEdge *eTmp = 0; + while (e2) { + if (e2->OutIdx >= 0 && e2->WindDelta != 0) { + if (!eTmp) + eTmp = e2; + else if (eTmp->OutIdx == e2->OutIdx) + eTmp = 0; + } + e2 = e2->PrevInAEL; + } + if (!eTmp) { + outrec->FirstLeft = 0; + outrec->IsHole = false; + } else { + outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx]; + outrec->IsHole = !outrec->FirstLeft->IsHole; + } +} +//------------------------------------------------------------------------------ + +OutRec *GetLowermostRec(OutRec *outRec1, OutRec *outRec2) { + // work out which polygon fragment has the correct hole state ... + if (!outRec1->BottomPt) + outRec1->BottomPt = GetBottomPt(outRec1->Pts); + if (!outRec2->BottomPt) + outRec2->BottomPt = GetBottomPt(outRec2->Pts); + OutPt *OutPt1 = outRec1->BottomPt; + OutPt *OutPt2 = outRec2->BottomPt; + if (OutPt1->Pt.Y > OutPt2->Pt.Y) + return outRec1; + else if (OutPt1->Pt.Y < OutPt2->Pt.Y) + return outRec2; + else if (OutPt1->Pt.X < OutPt2->Pt.X) + return outRec1; + else if (OutPt1->Pt.X > OutPt2->Pt.X) + return outRec2; + else if (OutPt1->Next == OutPt1) + return outRec2; + else if (OutPt2->Next == OutPt2) + return outRec1; + else if (FirstIsBottomPt(OutPt1, OutPt2)) + return outRec1; + else + return outRec2; +} +//------------------------------------------------------------------------------ + +bool OutRec1RightOfOutRec2(OutRec *outRec1, OutRec *outRec2) { + do { + outRec1 = outRec1->FirstLeft; + if (outRec1 == outRec2) + return true; + } while (outRec1); + return false; +} +//------------------------------------------------------------------------------ + +OutRec *Clipper::GetOutRec(int Idx) { + OutRec *outrec = m_PolyOuts[Idx]; + while (outrec != m_PolyOuts[outrec->Idx]) + outrec = m_PolyOuts[outrec->Idx]; + return outrec; +} +//------------------------------------------------------------------------------ + +void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) { + // get the start and ends of both output polygons ... + OutRec *outRec1 = m_PolyOuts[e1->OutIdx]; + OutRec *outRec2 = m_PolyOuts[e2->OutIdx]; + + OutRec *holeStateRec; + if (OutRec1RightOfOutRec2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + // get the start and ends of both output polygons and + // join e2 poly onto e1 poly and delete pointers to e2 ... + + OutPt *p1_lft = outRec1->Pts; + OutPt *p1_rt = p1_lft->Prev; + OutPt *p2_lft = outRec2->Pts; + OutPt *p2_rt = p2_lft->Prev; + + // join e2 poly onto e1 poly and delete pointers to e2 ... + if (e1->Side == esLeft) { + if (e2->Side == esLeft) { + // z y x a b c + ReversePolyPtLinks(p2_lft); + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + outRec1->Pts = p2_rt; + } else { + // x y z a b c + p2_rt->Next = p1_lft; + p1_lft->Prev = p2_rt; + p2_lft->Prev = p1_rt; + p1_rt->Next = p2_lft; + outRec1->Pts = p2_lft; + } + } else { + if (e2->Side == esRight) { + // a b c z y x + ReversePolyPtLinks(p2_lft); + p1_rt->Next = p2_rt; + p2_rt->Prev = p1_rt; + p2_lft->Next = p1_lft; + p1_lft->Prev = p2_lft; + } else { + // a b c x y z + p1_rt->Next = p2_lft; + p2_lft->Prev = p1_rt; + p1_lft->Prev = p2_rt; + p2_rt->Next = p1_lft; + } + } + + outRec1->BottomPt = 0; + if (holeStateRec == outRec2) { + if (outRec2->FirstLeft != outRec1) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec1->IsHole = outRec2->IsHole; + } + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->FirstLeft = outRec1; + + int OKIdx = e1->OutIdx; + int ObsoleteIdx = e2->OutIdx; + + e1->OutIdx = + Unassigned; // nb: safe because we only get here via AddLocalMaxPoly + e2->OutIdx = Unassigned; + + TEdge *e = m_ActiveEdges; + while (e) { + if (e->OutIdx == ObsoleteIdx) { + e->OutIdx = OKIdx; + e->Side = e1->Side; + break; + } + e = e->NextInAEL; + } + + outRec2->Idx = outRec1->Idx; +} +//------------------------------------------------------------------------------ + +OutPt *Clipper::AddOutPt(TEdge *e, const IntPoint &pt) { + if (e->OutIdx < 0) { + OutRec *outRec = CreateOutRec(); + outRec->IsOpen = (e->WindDelta == 0); + OutPt *newOp = new OutPt; + outRec->Pts = newOp; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = newOp; + newOp->Prev = newOp; + if (!outRec->IsOpen) + SetHoleState(e, outRec); + e->OutIdx = outRec->Idx; + return newOp; + } else { + OutRec *outRec = m_PolyOuts[e->OutIdx]; + // OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most' + OutPt *op = outRec->Pts; + + bool ToFront = (e->Side == esLeft); + if (ToFront && (pt == op->Pt)) + return op; + else if (!ToFront && (pt == op->Prev->Pt)) + return op->Prev; + + OutPt *newOp = new OutPt; + newOp->Idx = outRec->Idx; + newOp->Pt = pt; + newOp->Next = op; + newOp->Prev = op->Prev; + newOp->Prev->Next = newOp; + op->Prev = newOp; + if (ToFront) + outRec->Pts = newOp; + return newOp; + } +} +//------------------------------------------------------------------------------ + +OutPt *Clipper::GetLastOutPt(TEdge *e) { + OutRec *outRec = m_PolyOuts[e->OutIdx]; + if (e->Side == esLeft) + return outRec->Pts; + else + return outRec->Pts->Prev; +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessHorizontals() { + TEdge *horzEdge; + while (PopEdgeFromSEL(horzEdge)) + ProcessHorizontal(horzEdge); +} +//------------------------------------------------------------------------------ + +inline bool IsMinima(TEdge *e) { + return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e); +} +//------------------------------------------------------------------------------ + +inline bool IsMaxima(TEdge *e, const cInt Y) { + return e && e->Top.Y == Y && !e->NextInLML; +} +//------------------------------------------------------------------------------ + +inline bool IsIntermediate(TEdge *e, const cInt Y) { + return e->Top.Y == Y && e->NextInLML; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPair(TEdge *e) { + if ((e->Next->Top == e->Top) && !e->Next->NextInLML) + return e->Next; + else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML) + return e->Prev; + else + return 0; +} +//------------------------------------------------------------------------------ + +TEdge *GetMaximaPairEx(TEdge *e) { + // as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's + // horizontal) + TEdge *result = GetMaximaPair(e); + if (result && + (result->OutIdx == Skip || + (result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result)))) + return 0; + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) { + if (!(Edge1->NextInSEL) && !(Edge1->PrevInSEL)) + return; + if (!(Edge2->NextInSEL) && !(Edge2->PrevInSEL)) + return; + + if (Edge1->NextInSEL == Edge2) { + TEdge *Next = Edge2->NextInSEL; + if (Next) + Next->PrevInSEL = Edge1; + TEdge *Prev = Edge1->PrevInSEL; + if (Prev) + Prev->NextInSEL = Edge2; + Edge2->PrevInSEL = Prev; + Edge2->NextInSEL = Edge1; + Edge1->PrevInSEL = Edge2; + Edge1->NextInSEL = Next; + } else if (Edge2->NextInSEL == Edge1) { + TEdge *Next = Edge1->NextInSEL; + if (Next) + Next->PrevInSEL = Edge2; + TEdge *Prev = Edge2->PrevInSEL; + if (Prev) + Prev->NextInSEL = Edge1; + Edge1->PrevInSEL = Prev; + Edge1->NextInSEL = Edge2; + Edge2->PrevInSEL = Edge1; + Edge2->NextInSEL = Next; + } else { + TEdge *Next = Edge1->NextInSEL; + TEdge *Prev = Edge1->PrevInSEL; + Edge1->NextInSEL = Edge2->NextInSEL; + if (Edge1->NextInSEL) + Edge1->NextInSEL->PrevInSEL = Edge1; + Edge1->PrevInSEL = Edge2->PrevInSEL; + if (Edge1->PrevInSEL) + Edge1->PrevInSEL->NextInSEL = Edge1; + Edge2->NextInSEL = Next; + if (Edge2->NextInSEL) + Edge2->NextInSEL->PrevInSEL = Edge2; + Edge2->PrevInSEL = Prev; + if (Edge2->PrevInSEL) + Edge2->PrevInSEL->NextInSEL = Edge2; + } + + if (!Edge1->PrevInSEL) + m_SortedEdges = Edge1; + else if (!Edge2->PrevInSEL) + m_SortedEdges = Edge2; +} +//------------------------------------------------------------------------------ + +TEdge *GetNextInAEL(TEdge *e, Direction dir) { + return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL; +} +//------------------------------------------------------------------------------ + +void GetHorzDirection(TEdge &HorzEdge, Direction &Dir, cInt &Left, + cInt &Right) { + if (HorzEdge.Bot.X < HorzEdge.Top.X) { + Left = HorzEdge.Bot.X; + Right = HorzEdge.Top.X; + Dir = dLeftToRight; + } else { + Left = HorzEdge.Top.X; + Right = HorzEdge.Bot.X; + Dir = dRightToLeft; + } +} +//------------------------------------------------------------------------ + +/******************************************************************************* + * Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or * + * Bottom of a scanbeam) are processed as if layered. The order in which HEs * + * are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] * + * (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), * and + *with other non-horizontal edges [*]. Once these intersections are * + * processed, intermediate HEs then 'promote' the Edge above (NextInLML) into * + * the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. * + *******************************************************************************/ + +void Clipper::ProcessHorizontal(TEdge *horzEdge) { + Direction dir; + cInt horzLeft, horzRight; + bool IsOpen = (horzEdge->WindDelta == 0); + + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + TEdge *eLastHorz = horzEdge, *eMaxPair = 0; + while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML)) + eLastHorz = eLastHorz->NextInLML; + if (!eLastHorz->NextInLML) + eMaxPair = GetMaximaPair(eLastHorz); + + MaximaList::const_iterator maxIt; + MaximaList::const_reverse_iterator maxRit; + if (m_Maxima.size() > 0) { + // get the first maxima in range (X) ... + if (dir == dLeftToRight) { + maxIt = m_Maxima.begin(); + while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X) + maxIt++; + if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X) + maxIt = m_Maxima.end(); + } else { + maxRit = m_Maxima.rbegin(); + while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X) + maxRit++; + if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X) + maxRit = m_Maxima.rend(); + } + } + + OutPt *op1 = 0; + + for (;;) // loop through consec. horizontal edges + { + + bool IsLastHorz = (horzEdge == eLastHorz); + TEdge *e = GetNextInAEL(horzEdge, dir); + while (e) { + + // this code block inserts extra coords into horizontal edges (in output + // polygons) wherever maxima touch these horizontal edges. This helps + //'simplifying' polygons (ie if the Simplify property is set). + if (m_Maxima.size() > 0) { + if (dir == dLeftToRight) { + while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) { + if (horzEdge->OutIdx >= 0 && !IsOpen) + AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y)); + maxIt++; + } + } else { + while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) { + if (horzEdge->OutIdx >= 0 && !IsOpen) + AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y)); + maxRit++; + } + } + }; + + if ((dir == dLeftToRight && e->Curr.X > horzRight) || + (dir == dRightToLeft && e->Curr.X < horzLeft)) + break; + + // Also break if we've got to the end of an intermediate horizontal edge + // ... + // nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal. + if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML && + e->Dx < horzEdge->NextInLML->Dx) + break; + + if (horzEdge->OutIdx >= 0 && !IsOpen) // note: may be done multiple times + { +#ifdef use_xyz + if (dir == dLeftToRight) + SetZ(e->Curr, *horzEdge, *e); + else + SetZ(e->Curr, *e, *horzEdge); +#endif + op1 = AddOutPt(horzEdge, e->Curr); + TEdge *eNextHorz = m_SortedEdges; + while (eNextHorz) { + if (eNextHorz->OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X, + eNextHorz->Bot.X, eNextHorz->Top.X)) { + OutPt *op2 = GetLastOutPt(eNextHorz); + AddJoin(op2, op1, eNextHorz->Top); + } + eNextHorz = eNextHorz->NextInSEL; + } + AddGhostJoin(op1, horzEdge->Bot); + } + + // OK, so far we're still in range of the horizontal Edge but make sure + // we're at the last of consec. horizontals when matching with eMaxPair + if (e == eMaxPair && IsLastHorz) { + if (horzEdge->OutIdx >= 0) + AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top); + DeleteFromAEL(horzEdge); + DeleteFromAEL(eMaxPair); + return; + } + + if (dir == dLeftToRight) { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges(horzEdge, e, Pt); + } else { + IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y); + IntersectEdges(e, horzEdge, Pt); + } + TEdge *eNext = GetNextInAEL(e, dir); + SwapPositionsInAEL(horzEdge, e); + e = eNext; + } // end while(e) + + // Break out of loop if HorzEdge.NextInLML is not also horizontal ... + if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML)) + break; + + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->OutIdx >= 0) + AddOutPt(horzEdge, horzEdge->Bot); + GetHorzDirection(*horzEdge, dir, horzLeft, horzRight); + + } // end for (;;) + + if (horzEdge->OutIdx >= 0 && !op1) { + op1 = GetLastOutPt(horzEdge); + TEdge *eNextHorz = m_SortedEdges; + while (eNextHorz) { + if (eNextHorz->OutIdx >= 0 && + HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X, + eNextHorz->Bot.X, eNextHorz->Top.X)) { + OutPt *op2 = GetLastOutPt(eNextHorz); + AddJoin(op2, op1, eNextHorz->Top); + } + eNextHorz = eNextHorz->NextInSEL; + } + AddGhostJoin(op1, horzEdge->Top); + } + + if (horzEdge->NextInLML) { + if (horzEdge->OutIdx >= 0) { + op1 = AddOutPt(horzEdge, horzEdge->Top); + UpdateEdgeIntoAEL(horzEdge); + if (horzEdge->WindDelta == 0) + return; + // nb: HorzEdge is no longer horizontal here + TEdge *ePrev = horzEdge->PrevInAEL; + TEdge *eNext = horzEdge->NextInAEL; + if (ePrev && ePrev->Curr.X == horzEdge->Bot.X && + ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 && + (ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) { + OutPt *op2 = AddOutPt(ePrev, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } else if (eNext && eNext->Curr.X == horzEdge->Bot.X && + eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 && + eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) { + OutPt *op2 = AddOutPt(eNext, horzEdge->Bot); + AddJoin(op1, op2, horzEdge->Top); + } + } else + UpdateEdgeIntoAEL(horzEdge); + } else { + if (horzEdge->OutIdx >= 0) + AddOutPt(horzEdge, horzEdge->Top); + DeleteFromAEL(horzEdge); + } +} +//------------------------------------------------------------------------------ + +bool Clipper::ProcessIntersections(const cInt topY) { + if (!m_ActiveEdges) + return true; + try { + BuildIntersectList(topY); + size_t IlSize = m_IntersectList.size(); + if (IlSize == 0) + return true; + if (IlSize == 1 || FixupIntersectionOrder()) + ProcessIntersectList(); + else + return false; + } catch (...) { + m_SortedEdges = 0; + DisposeIntersectNodes(); + throw clipperException("ProcessIntersections error"); + } + m_SortedEdges = 0; + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DisposeIntersectNodes() { + for (size_t i = 0; i < m_IntersectList.size(); ++i) + delete m_IntersectList[i]; + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +void Clipper::BuildIntersectList(const cInt topY) { + if (!m_ActiveEdges) + return; + + // prepare for sorting ... + TEdge *e = m_ActiveEdges; + m_SortedEdges = e; + while (e) { + e->PrevInSEL = e->PrevInAEL; + e->NextInSEL = e->NextInAEL; + e->Curr.X = TopX(*e, topY); + e = e->NextInAEL; + } + + // bubblesort ... + bool isModified; + do { + isModified = false; + e = m_SortedEdges; + while (e->NextInSEL) { + TEdge *eNext = e->NextInSEL; + IntPoint Pt; + if (e->Curr.X > eNext->Curr.X) { + IntersectPoint(*e, *eNext, Pt); + if (Pt.Y < topY) + Pt = IntPoint(TopX(*e, topY), topY); + IntersectNode *newNode = new IntersectNode; + newNode->Edge1 = e; + newNode->Edge2 = eNext; + newNode->Pt = Pt; + m_IntersectList.push_back(newNode); + + SwapPositionsInSEL(e, eNext); + isModified = true; + } else + e = eNext; + } + if (e->PrevInSEL) + e->PrevInSEL->NextInSEL = 0; + else + break; + } while (isModified); + m_SortedEdges = 0; // important +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessIntersectList() { + for (size_t i = 0; i < m_IntersectList.size(); ++i) { + IntersectNode *iNode = m_IntersectList[i]; + { + IntersectEdges(iNode->Edge1, iNode->Edge2, iNode->Pt); + SwapPositionsInAEL(iNode->Edge1, iNode->Edge2); + } + delete iNode; + } + m_IntersectList.clear(); +} +//------------------------------------------------------------------------------ + +bool IntersectListSort(IntersectNode *node1, IntersectNode *node2) { + return node2->Pt.Y < node1->Pt.Y; +} +//------------------------------------------------------------------------------ + +inline bool EdgesAdjacent(const IntersectNode &inode) { + return (inode.Edge1->NextInSEL == inode.Edge2) || + (inode.Edge1->PrevInSEL == inode.Edge2); +} +//------------------------------------------------------------------------------ + +bool Clipper::FixupIntersectionOrder() { + // pre-condition: intersections are sorted Bottom-most first. + // Now it's crucial that intersections are made only between adjacent edges, + // so to ensure this the order of intersections may need adjusting ... + CopyAELToSEL(); + std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort); + size_t cnt = m_IntersectList.size(); + for (size_t i = 0; i < cnt; ++i) { + if (!EdgesAdjacent(*m_IntersectList[i])) { + size_t j = i + 1; + while (j < cnt && !EdgesAdjacent(*m_IntersectList[j])) + j++; + if (j == cnt) + return false; + std::swap(m_IntersectList[i], m_IntersectList[j]); + } + SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2); + } + return true; +} +//------------------------------------------------------------------------------ + +void Clipper::DoMaxima(TEdge *e) { + TEdge *eMaxPair = GetMaximaPairEx(e); + if (!eMaxPair) { + if (e->OutIdx >= 0) + AddOutPt(e, e->Top); + DeleteFromAEL(e); + return; + } + + TEdge *eNext = e->NextInAEL; + while (eNext && eNext != eMaxPair) { + IntersectEdges(e, eNext, e->Top); + SwapPositionsInAEL(e, eNext); + eNext = e->NextInAEL; + } + + if (e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) { + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } else if (e->OutIdx >= 0 && eMaxPair->OutIdx >= 0) { + if (e->OutIdx >= 0) + AddLocalMaxPoly(e, eMaxPair, e->Top); + DeleteFromAEL(e); + DeleteFromAEL(eMaxPair); + } +#ifdef use_lines + else if (e->WindDelta == 0) { + if (e->OutIdx >= 0) { + AddOutPt(e, e->Top); + e->OutIdx = Unassigned; + } + DeleteFromAEL(e); + + if (eMaxPair->OutIdx >= 0) { + AddOutPt(eMaxPair, e->Top); + eMaxPair->OutIdx = Unassigned; + } + DeleteFromAEL(eMaxPair); + } +#endif + else + throw clipperException("DoMaxima error"); +} +//------------------------------------------------------------------------------ + +void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) { + TEdge *e = m_ActiveEdges; + while (e) { + // 1. process maxima, treating them as if they're 'bent' horizontal edges, + // but exclude maxima with horizontal edges. nb: e can't be a horizontal. + bool IsMaximaEdge = IsMaxima(e, topY); + + if (IsMaximaEdge) { + TEdge *eMaxPair = GetMaximaPairEx(e); + IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair)); + } + + if (IsMaximaEdge) { + if (m_StrictSimple) + m_Maxima.push_back(e->Top.X); + TEdge *ePrev = e->PrevInAEL; + DoMaxima(e); + if (!ePrev) + e = m_ActiveEdges; + else + e = ePrev->NextInAEL; + } else { + // 2. promote horizontal edges, otherwise update Curr.X and Curr.Y ... + if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) { + UpdateEdgeIntoAEL(e); + if (e->OutIdx >= 0) + AddOutPt(e, e->Bot); + AddEdgeToSEL(e); + } else { + e->Curr.X = TopX(*e, topY); + e->Curr.Y = topY; +#ifdef use_xyz + e->Curr.Z = + topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0); +#endif + } + + // When StrictlySimple and 'e' is being touched by another edge, then + // make sure both edges have a vertex here ... + if (m_StrictSimple) { + TEdge *ePrev = e->PrevInAEL; + if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev && + (ePrev->OutIdx >= 0) && (ePrev->Curr.X == e->Curr.X) && + (ePrev->WindDelta != 0)) { + IntPoint pt = e->Curr; +#ifdef use_xyz + SetZ(pt, *ePrev, *e); +#endif + OutPt *op = AddOutPt(ePrev, pt); + OutPt *op2 = AddOutPt(e, pt); + AddJoin(op, op2, pt); // StrictlySimple (type-3) join + } + } + + e = e->NextInAEL; + } + } + + // 3. Process horizontals at the Top of the scanbeam ... + m_Maxima.sort(); + ProcessHorizontals(); + m_Maxima.clear(); + + // 4. Promote intermediate vertices ... + e = m_ActiveEdges; + while (e) { + if (IsIntermediate(e, topY)) { + OutPt *op = 0; + if (e->OutIdx >= 0) + op = AddOutPt(e, e->Top); + UpdateEdgeIntoAEL(e); + + // if output polygons share an edge, they'll need joining later ... + TEdge *ePrev = e->PrevInAEL; + TEdge *eNext = e->NextInAEL; + if (ePrev && ePrev->Curr.X == e->Bot.X && ePrev->Curr.Y == e->Bot.Y && + op && ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y && + SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top, + m_UseFullRange) && + (e->WindDelta != 0) && (ePrev->WindDelta != 0)) { + OutPt *op2 = AddOutPt(ePrev, e->Bot); + AddJoin(op, op2, e->Top); + } else if (eNext && eNext->Curr.X == e->Bot.X && + eNext->Curr.Y == e->Bot.Y && op && eNext->OutIdx >= 0 && + eNext->Curr.Y > eNext->Top.Y && + SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top, + m_UseFullRange) && + (e->WindDelta != 0) && (eNext->WindDelta != 0)) { + OutPt *op2 = AddOutPt(eNext, e->Bot); + AddJoin(op, op2, e->Top); + } + } + e = e->NextInAEL; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolyline(OutRec &outrec) { + OutPt *pp = outrec.Pts; + OutPt *lastPP = pp->Prev; + while (pp != lastPP) { + pp = pp->Next; + if (pp->Pt == pp->Prev->Pt) { + if (pp == lastPP) + lastPP = pp->Prev; + OutPt *tmpPP = pp->Prev; + tmpPP->Next = pp->Next; + pp->Next->Prev = tmpPP; + delete pp; + pp = tmpPP; + } + } + + if (pp == pp->Prev) { + DisposeOutPts(pp); + outrec.Pts = 0; + return; + } +} +//------------------------------------------------------------------------------ + +void Clipper::FixupOutPolygon(OutRec &outrec) { + // FixupOutPolygon() - removes duplicate points and simplifies consecutive + // parallel edges by removing the middle vertex. + OutPt *lastOK = 0; + outrec.BottomPt = 0; + OutPt *pp = outrec.Pts; + bool preserveCol = m_PreserveCollinear || m_StrictSimple; + + for (;;) { + if (pp->Prev == pp || pp->Prev == pp->Next) { + DisposeOutPts(pp); + outrec.Pts = 0; + return; + } + + // test for duplicate points and collinear edges ... + if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) || + (SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) && + (!preserveCol || + !Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) { + lastOK = 0; + OutPt *tmp = pp; + pp->Prev->Next = pp->Next; + pp->Next->Prev = pp->Prev; + pp = pp->Prev; + delete tmp; + } else if (pp == lastOK) + break; + else { + if (!lastOK) + lastOK = pp; + pp = pp->Next; + } + } + outrec.Pts = pp; +} +//------------------------------------------------------------------------------ + +int PointCount(OutPt *Pts) { + if (!Pts) + return 0; + int result = 0; + OutPt *p = Pts; + do { + result++; + p = p->Next; + } while (p != Pts); + return result; +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult(Paths &polys) { + polys.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + if (!m_PolyOuts[i]->Pts) + continue; + Path pg; + OutPt *p = m_PolyOuts[i]->Pts->Prev; + int cnt = PointCount(p); + if (cnt < 2) + continue; + pg.reserve(cnt); + for (int i = 0; i < cnt; ++i) { + pg.push_back(p->Pt); + p = p->Prev; + } + polys.push_back(pg); + } +} +//------------------------------------------------------------------------------ + +void Clipper::BuildResult2(PolyTree &polytree) { + polytree.Clear(); + polytree.AllNodes.reserve(m_PolyOuts.size()); + // add each output polygon/contour to polytree ... + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) { + OutRec *outRec = m_PolyOuts[i]; + int cnt = PointCount(outRec->Pts); + if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3)) + continue; + FixHoleLinkage(*outRec); + PolyNode *pn = new PolyNode(); + // nb: polytree takes ownership of all the PolyNodes + polytree.AllNodes.push_back(pn); + outRec->PolyNd = pn; + pn->Parent = 0; + pn->Index = 0; + pn->Contour.reserve(cnt); + OutPt *op = outRec->Pts->Prev; + for (int j = 0; j < cnt; j++) { + pn->Contour.push_back(op->Pt); + op = op->Prev; + } + } + + // fixup PolyNode links etc ... + polytree.Children.reserve(m_PolyOuts.size()); + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) { + OutRec *outRec = m_PolyOuts[i]; + if (!outRec->PolyNd) + continue; + if (outRec->IsOpen) { + outRec->PolyNd->m_IsOpen = true; + polytree.AddChild(*outRec->PolyNd); + } else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd) + outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd); + else + polytree.AddChild(*outRec->PolyNd); + } +} +//------------------------------------------------------------------------------ + +void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) { + // just swap the contents (because fIntersectNodes is a single-linked-list) + IntersectNode inode = int1; // gets a copy of Int1 + int1.Edge1 = int2.Edge1; + int1.Edge2 = int2.Edge2; + int1.Pt = int2.Pt; + int2.Edge1 = inode.Edge1; + int2.Edge2 = inode.Edge2; + int2.Pt = inode.Pt; +} +//------------------------------------------------------------------------------ + +inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) { + if (e2.Curr.X == e1.Curr.X) { + if (e2.Top.Y > e1.Top.Y) + return e2.Top.X < TopX(e1, e2.Top.Y); + else + return e1.Top.X > TopX(e2, e1.Top.Y); + } else + return e2.Curr.X < e1.Curr.X; +} +//------------------------------------------------------------------------------ + +bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2, + cInt &Left, cInt &Right) { + if (a1 < a2) { + if (b1 < b2) { + Left = std::max(a1, b1); + Right = std::min(a2, b2); + } else { + Left = std::max(a1, b2); + Right = std::min(a2, b1); + } + } else { + if (b1 < b2) { + Left = std::max(a2, b1); + Right = std::min(a1, b2); + } else { + Left = std::max(a2, b2); + Right = std::min(a1, b1); + } + } + return Left < Right; +} +//------------------------------------------------------------------------------ + +inline void UpdateOutPtIdxs(OutRec &outrec) { + OutPt *op = outrec.Pts; + do { + op->Idx = outrec.Idx; + op = op->Prev; + } while (op != outrec.Pts); +} +//------------------------------------------------------------------------------ + +void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge) { + if (!m_ActiveEdges) { + edge->PrevInAEL = 0; + edge->NextInAEL = 0; + m_ActiveEdges = edge; + } else if (!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) { + edge->PrevInAEL = 0; + edge->NextInAEL = m_ActiveEdges; + m_ActiveEdges->PrevInAEL = edge; + m_ActiveEdges = edge; + } else { + if (!startEdge) + startEdge = m_ActiveEdges; + while (startEdge->NextInAEL && + !E2InsertsBeforeE1(*startEdge->NextInAEL, *edge)) + startEdge = startEdge->NextInAEL; + edge->NextInAEL = startEdge->NextInAEL; + if (startEdge->NextInAEL) + startEdge->NextInAEL->PrevInAEL = edge; + edge->PrevInAEL = startEdge; + startEdge->NextInAEL = edge; + } +} +//---------------------------------------------------------------------- + +OutPt *DupOutPt(OutPt *outPt, bool InsertAfter) { + OutPt *result = new OutPt; + result->Pt = outPt->Pt; + result->Idx = outPt->Idx; + if (InsertAfter) { + result->Next = outPt->Next; + result->Prev = outPt; + outPt->Next->Prev = result; + outPt->Next = result; + } else { + result->Prev = outPt->Prev; + result->Next = outPt; + outPt->Prev->Next = result; + outPt->Prev = result; + } + return result; +} +//------------------------------------------------------------------------------ + +bool JoinHorz(OutPt *op1, OutPt *op1b, OutPt *op2, OutPt *op2b, + const IntPoint Pt, bool DiscardLeft) { + Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight); + Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight); + if (Dir1 == Dir2) + return false; + + // When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we + // want Op1b to be on the Right. (And likewise with Op2 and Op2b.) + // So, to facilitate this while inserting Op1b and Op2b ... + // when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b, + // otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.) + if (Dir1 == dLeftToRight) { + while (op1->Next->Pt.X <= Pt.X && op1->Next->Pt.X >= op1->Pt.X && + op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (DiscardLeft && (op1->Pt.X != Pt.X)) + op1 = op1->Next; + op1b = DupOutPt(op1, !DiscardLeft); + if (op1b->Pt != Pt) { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, !DiscardLeft); + } + } else { + while (op1->Next->Pt.X >= Pt.X && op1->Next->Pt.X <= op1->Pt.X && + op1->Next->Pt.Y == Pt.Y) + op1 = op1->Next; + if (!DiscardLeft && (op1->Pt.X != Pt.X)) + op1 = op1->Next; + op1b = DupOutPt(op1, DiscardLeft); + if (op1b->Pt != Pt) { + op1 = op1b; + op1->Pt = Pt; + op1b = DupOutPt(op1, DiscardLeft); + } + } + + if (Dir2 == dLeftToRight) { + while (op2->Next->Pt.X <= Pt.X && op2->Next->Pt.X >= op2->Pt.X && + op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (DiscardLeft && (op2->Pt.X != Pt.X)) + op2 = op2->Next; + op2b = DupOutPt(op2, !DiscardLeft); + if (op2b->Pt != Pt) { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, !DiscardLeft); + }; + } else { + while (op2->Next->Pt.X >= Pt.X && op2->Next->Pt.X <= op2->Pt.X && + op2->Next->Pt.Y == Pt.Y) + op2 = op2->Next; + if (!DiscardLeft && (op2->Pt.X != Pt.X)) + op2 = op2->Next; + op2b = DupOutPt(op2, DiscardLeft); + if (op2b->Pt != Pt) { + op2 = op2b; + op2->Pt = Pt; + op2b = DupOutPt(op2, DiscardLeft); + }; + }; + + if ((Dir1 == dLeftToRight) == DiscardLeft) { + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + } else { + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + } + return true; +} +//------------------------------------------------------------------------------ + +bool Clipper::JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2) { + OutPt *op1 = j->OutPt1, *op1b; + OutPt *op2 = j->OutPt2, *op2b; + + // There are 3 kinds of joins for output polygons ... + // 1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere + // along (horizontal) collinear edges (& Join.OffPt is on the same + // horizontal). + // 2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same + // location at the Bottom of the overlapping segment (& Join.OffPt is above). + // 3. StrictSimple joins where edges touch but are not collinear and where + // Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point. + bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y); + + if (isHorizontal && (j->OffPt == j->OutPt1->Pt) && + (j->OffPt == j->OutPt2->Pt)) { + // Strictly Simple join ... + if (outRec1 != outRec2) + return false; + op1b = j->OutPt1->Next; + while (op1b != op1 && (op1b->Pt == j->OffPt)) + op1b = op1b->Next; + bool reverse1 = (op1b->Pt.Y > j->OffPt.Y); + op2b = j->OutPt2->Next; + while (op2b != op2 && (op2b->Pt == j->OffPt)) + op2b = op2b->Next; + bool reverse2 = (op2b->Pt.Y > j->OffPt.Y); + if (reverse1 == reverse2) + return false; + if (reverse1) { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } else if (isHorizontal) { + // treat horizontal joins differently to non-horizontal joins since with + // them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt + // may be anywhere along the horizontal edge. + op1b = op1; + while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b && + op1->Prev != op2) + op1 = op1->Prev; + while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 && + op1b->Next != op2) + op1b = op1b->Next; + if (op1b->Next == op1 || op1b->Next == op2) + return false; // a flat 'polygon' + + op2b = op2; + while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b && + op2->Prev != op1b) + op2 = op2->Prev; + while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 && + op2b->Next != op1) + op2b = op2b->Next; + if (op2b->Next == op2 || op2b->Next == op1) + return false; // a flat 'polygon' + + cInt Left, Right; + // Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges + if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right)) + return false; + + // DiscardLeftSide: when overlapping edges are joined, a spike will created + // which needs to be cleaned up. However, we don't want Op1 or Op2 caught up + // on the discard Side as either may still be needed for other joins ... + IntPoint Pt; + bool DiscardLeftSide; + if (op1->Pt.X >= Left && op1->Pt.X <= Right) { + Pt = op1->Pt; + DiscardLeftSide = (op1->Pt.X > op1b->Pt.X); + } else if (op2->Pt.X >= Left && op2->Pt.X <= Right) { + Pt = op2->Pt; + DiscardLeftSide = (op2->Pt.X > op2b->Pt.X); + } else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) { + Pt = op1b->Pt; + DiscardLeftSide = op1b->Pt.X > op1->Pt.X; + } else { + Pt = op2b->Pt; + DiscardLeftSide = (op2b->Pt.X > op2->Pt.X); + } + j->OutPt1 = op1; + j->OutPt2 = op2; + return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide); + } else { + // nb: For non-horizontal joins ... + // 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y + // 2. Jr.OutPt1.Pt > Jr.OffPt.Y + + // make sure the polygons are correctly oriented ... + op1b = op1->Next; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) + op1b = op1b->Next; + bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse1) { + op1b = op1->Prev; + while ((op1b->Pt == op1->Pt) && (op1b != op1)) + op1b = op1b->Prev; + if ((op1b->Pt.Y > op1->Pt.Y) || + !SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange)) + return false; + }; + op2b = op2->Next; + while ((op2b->Pt == op2->Pt) && (op2b != op2)) + op2b = op2b->Next; + bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)); + if (Reverse2) { + op2b = op2->Prev; + while ((op2b->Pt == op2->Pt) && (op2b != op2)) + op2b = op2b->Prev; + if ((op2b->Pt.Y > op2->Pt.Y) || + !SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange)) + return false; + } + + if ((op1b == op1) || (op2b == op2) || (op1b == op2b) || + ((outRec1 == outRec2) && (Reverse1 == Reverse2))) + return false; + + if (Reverse1) { + op1b = DupOutPt(op1, false); + op2b = DupOutPt(op2, true); + op1->Prev = op2; + op2->Next = op1; + op1b->Next = op2b; + op2b->Prev = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } else { + op1b = DupOutPt(op1, true); + op2b = DupOutPt(op2, false); + op1->Next = op2; + op2->Prev = op1; + op1b->Prev = op2b; + op2b->Next = op1b; + j->OutPt1 = op1; + j->OutPt2 = op1b; + return true; + } + } +} +//---------------------------------------------------------------------- + +static OutRec *ParseFirstLeft(OutRec *FirstLeft) { + while (FirstLeft && !FirstLeft->Pts) + FirstLeft = FirstLeft->FirstLeft; + return FirstLeft; +} +//------------------------------------------------------------------------------ + +void Clipper::FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec) { + // tests if NewOutRec contains the polygon before reassigning FirstLeft + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + OutRec *outRec = m_PolyOuts[i]; + OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (outRec->Pts && firstLeft == OldOutRec) { + if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts)) + outRec->FirstLeft = NewOutRec; + } + } +} +//---------------------------------------------------------------------- + +void Clipper::FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec) { + // A polygon has split into two such that one is now the inner of the other. + // It's possible that these polygons now wrap around other polygons, so check + // every polygon that's also contained by OuterOutRec's FirstLeft container + //(including 0) to see if they've become inner to the new inner polygon ... + OutRec *orfl = OuterOutRec->FirstLeft; + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + OutRec *outRec = m_PolyOuts[i]; + + if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec) + continue; + OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (firstLeft != orfl && firstLeft != InnerOutRec && + firstLeft != OuterOutRec) + continue; + if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts)) + outRec->FirstLeft = InnerOutRec; + else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts)) + outRec->FirstLeft = OuterOutRec; + else if (outRec->FirstLeft == InnerOutRec || + outRec->FirstLeft == OuterOutRec) + outRec->FirstLeft = orfl; + } +} +//---------------------------------------------------------------------- +void Clipper::FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec) { + // reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon + for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) { + OutRec *outRec = m_PolyOuts[i]; + OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft); + if (outRec->Pts && firstLeft == OldOutRec) + outRec->FirstLeft = NewOutRec; + } +} +//---------------------------------------------------------------------- + +void Clipper::JoinCommonEdges() { + for (JoinList::size_type i = 0; i < m_Joins.size(); i++) { + Join *join = m_Joins[i]; + + OutRec *outRec1 = GetOutRec(join->OutPt1->Idx); + OutRec *outRec2 = GetOutRec(join->OutPt2->Idx); + + if (!outRec1->Pts || !outRec2->Pts) + continue; + if (outRec1->IsOpen || outRec2->IsOpen) + continue; + + // get the polygon fragment with the correct hole state (FirstLeft) + // before calling JoinPoints() ... + OutRec *holeStateRec; + if (outRec1 == outRec2) + holeStateRec = outRec1; + else if (OutRec1RightOfOutRec2(outRec1, outRec2)) + holeStateRec = outRec2; + else if (OutRec1RightOfOutRec2(outRec2, outRec1)) + holeStateRec = outRec1; + else + holeStateRec = GetLowermostRec(outRec1, outRec2); + + if (!JoinPoints(join, outRec1, outRec2)) + continue; + + if (outRec1 == outRec2) { + // instead of joining two polygons, we've just created a new one by + // splitting one polygon into two. + outRec1->Pts = join->OutPt1; + outRec1->BottomPt = 0; + outRec2 = CreateOutRec(); + outRec2->Pts = join->OutPt2; + + // update all OutRec2.Pts Idx's ... + UpdateOutPtIdxs(*outRec2); + + if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) { + // outRec1 contains outRec2 ... + outRec2->IsHole = !outRec1->IsHole; + outRec2->FirstLeft = outRec1; + + if (m_UsingPolyTree) + FixupFirstLefts2(outRec2, outRec1); + + if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0)) + ReversePolyPtLinks(outRec2->Pts); + + } else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) { + // outRec2 contains outRec1 ... + outRec2->IsHole = outRec1->IsHole; + outRec1->IsHole = !outRec2->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + outRec1->FirstLeft = outRec2; + + if (m_UsingPolyTree) + FixupFirstLefts2(outRec1, outRec2); + + if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0)) + ReversePolyPtLinks(outRec1->Pts); + } else { + // the 2 polygons are completely separate ... + outRec2->IsHole = outRec1->IsHole; + outRec2->FirstLeft = outRec1->FirstLeft; + + // fixup FirstLeft pointers that may need reassigning to OutRec2 + if (m_UsingPolyTree) + FixupFirstLefts1(outRec1, outRec2); + } + + } else { + // joined 2 polygons together ... + + outRec2->Pts = 0; + outRec2->BottomPt = 0; + outRec2->Idx = outRec1->Idx; + + outRec1->IsHole = holeStateRec->IsHole; + if (holeStateRec == outRec2) + outRec1->FirstLeft = outRec2->FirstLeft; + outRec2->FirstLeft = outRec1; + + if (m_UsingPolyTree) + FixupFirstLefts3(outRec2, outRec1); + } + } +} + +//------------------------------------------------------------------------------ +// ClipperOffset support functions ... +//------------------------------------------------------------------------------ + +DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) { + if (pt2.X == pt1.X && pt2.Y == pt1.Y) + return DoublePoint(0, 0); + + double Dx = (double)(pt2.X - pt1.X); + double dy = (double)(pt2.Y - pt1.Y); + double f = 1 * 1.0 / std::sqrt(Dx * Dx + dy * dy); + Dx *= f; + dy *= f; + return DoublePoint(dy, -Dx); +} + +//------------------------------------------------------------------------------ +// ClipperOffset class +//------------------------------------------------------------------------------ + +ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) { + this->MiterLimit = miterLimit; + this->ArcTolerance = arcTolerance; + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +ClipperOffset::~ClipperOffset() { Clear(); } +//------------------------------------------------------------------------------ + +void ClipperOffset::Clear() { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) + delete m_polyNodes.Children[i]; + m_polyNodes.Children.clear(); + m_lowest.X = -1; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPath(const Path &path, JoinType joinType, + EndType endType) { + int highI = (int)path.size() - 1; + if (highI < 0) + return; + PolyNode *newNode = new PolyNode(); + newNode->m_jointype = joinType; + newNode->m_endtype = endType; + + // strip duplicate points from path and also get index to the lowest point ... + if (endType == etClosedLine || endType == etClosedPolygon) + while (highI > 0 && path[0] == path[highI]) + highI--; + newNode->Contour.reserve(highI + 1); + newNode->Contour.push_back(path[0]); + int j = 0, k = 0; + for (int i = 1; i <= highI; i++) + if (newNode->Contour[j] != path[i]) { + j++; + newNode->Contour.push_back(path[i]); + if (path[i].Y > newNode->Contour[k].Y || + (path[i].Y == newNode->Contour[k].Y && + path[i].X < newNode->Contour[k].X)) + k = j; + } + if (endType == etClosedPolygon && j < 2) { + delete newNode; + return; + } + m_polyNodes.AddChild(*newNode); + + // if this path's lowest pt is lower than all the others then update m_lowest + if (endType != etClosedPolygon) + return; + if (m_lowest.X < 0) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + else { + IntPoint ip = + m_polyNodes.Children[(int)m_lowest.X]->Contour[(int)m_lowest.Y]; + if (newNode->Contour[k].Y > ip.Y || + (newNode->Contour[k].Y == ip.Y && newNode->Contour[k].X < ip.X)) + m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPaths(const Paths &paths, JoinType joinType, + EndType endType) { + for (Paths::size_type i = 0; i < paths.size(); ++i) + AddPath(paths[i], joinType, endType); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::FixOrientations() { + // fixup orientations of all closed paths if the orientation of the + // closed path with the lowermost vertex is wrong ... + if (m_lowest.X >= 0 && + !Orientation(m_polyNodes.Children[(int)m_lowest.X]->Contour)) { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) { + PolyNode &node = *m_polyNodes.Children[i]; + if (node.m_endtype == etClosedPolygon || + (node.m_endtype == etClosedLine && Orientation(node.Contour))) + ReversePath(node.Contour); + } + } else { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) { + PolyNode &node = *m_polyNodes.Children[i]; + if (node.m_endtype == etClosedLine && !Orientation(node.Contour)) + ReversePath(node.Contour); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(Paths &solution, double delta) { + solution.clear(); + FixOrientations(); + DoOffset(delta); + + // now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } else { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + if (solution.size() > 0) + solution.erase(solution.begin()); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::Execute(PolyTree &solution, double delta) { + solution.Clear(); + FixOrientations(); + DoOffset(delta); + + // now clean up 'corners' ... + Clipper clpr; + clpr.AddPaths(m_destPolys, ptSubject, true); + if (delta > 0) { + clpr.Execute(ctUnion, solution, pftPositive, pftPositive); + } else { + IntRect r = clpr.GetBounds(); + Path outer(4); + outer[0] = IntPoint(r.left - 10, r.bottom + 10); + outer[1] = IntPoint(r.right + 10, r.bottom + 10); + outer[2] = IntPoint(r.right + 10, r.top - 10); + outer[3] = IntPoint(r.left - 10, r.top - 10); + + clpr.AddPath(outer, ptSubject, true); + clpr.ReverseSolution(true); + clpr.Execute(ctUnion, solution, pftNegative, pftNegative); + // remove the outer PolyNode rectangle ... + if (solution.ChildCount() == 1 && solution.Children[0]->ChildCount() > 0) { + PolyNode *outerNode = solution.Children[0]; + solution.Children.reserve(outerNode->ChildCount()); + solution.Children[0] = outerNode->Children[0]; + solution.Children[0]->Parent = outerNode->Parent; + for (int i = 1; i < outerNode->ChildCount(); ++i) + solution.AddChild(*outerNode->Children[i]); + } else + solution.Clear(); + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoOffset(double delta) { + m_destPolys.clear(); + m_delta = delta; + + // if Zero offset, just copy any CLOSED polygons to m_p and return ... + if (NEAR_ZERO(delta)) { + m_destPolys.reserve(m_polyNodes.ChildCount()); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) { + PolyNode &node = *m_polyNodes.Children[i]; + if (node.m_endtype == etClosedPolygon) + m_destPolys.push_back(node.Contour); + } + return; + } + + // see offset_triginometry3.svg in the documentation folder ... + if (MiterLimit > 2) + m_miterLim = 2 / (MiterLimit * MiterLimit); + else + m_miterLim = 0.5; + + double y; + if (ArcTolerance <= 0.0) + y = def_arc_tolerance; + else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance) + y = std::fabs(delta) * def_arc_tolerance; + else + y = ArcTolerance; + // see offset_triginometry2.svg in the documentation folder ... + double steps = pi / std::acos(1 - y / std::fabs(delta)); + if (steps > std::fabs(delta) * pi) + steps = std::fabs(delta) * pi; // ie excessive precision check + m_sin = std::sin(two_pi / steps); + m_cos = std::cos(two_pi / steps); + m_StepsPerRad = steps / two_pi; + if (delta < 0.0) + m_sin = -m_sin; + + m_destPolys.reserve(m_polyNodes.ChildCount() * 2); + for (int i = 0; i < m_polyNodes.ChildCount(); i++) { + PolyNode &node = *m_polyNodes.Children[i]; + m_srcPoly = node.Contour; + + int len = (int)m_srcPoly.size(); + if (len == 0 || + (delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon))) + continue; + + m_destPoly.clear(); + if (len == 1) { + if (node.m_jointype == jtRound) { + double X = 1.0, Y = 0.0; + for (cInt j = 1; j <= steps; j++) { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + double X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + } else { + double X = -1.0, Y = -1.0; + for (int j = 0; j < 4; ++j) { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[0].X + X * delta), + Round(m_srcPoly[0].Y + Y * delta))); + if (X < 0) + X = 1; + else if (Y < 0) + Y = 1; + else + X = -1; + } + } + m_destPolys.push_back(m_destPoly); + continue; + } + // build m_normals ... + m_normals.clear(); + m_normals.reserve(len); + for (int j = 0; j < len - 1; ++j) + m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1])); + if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon) + m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0])); + else + m_normals.push_back(DoublePoint(m_normals[len - 2])); + + if (node.m_endtype == etClosedPolygon) { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } else if (node.m_endtype == etClosedLine) { + int k = len - 1; + for (int j = 0; j < len; ++j) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + m_destPoly.clear(); + // re-build m_normals ... + DoublePoint n = m_normals[len - 1]; + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-n.X, -n.Y); + k = 0; + for (int j = len - 1; j >= 0; j--) + OffsetPoint(j, k, node.m_jointype); + m_destPolys.push_back(m_destPoly); + } else { + int k = 0; + for (int j = 1; j < len - 1; ++j) + OffsetPoint(j, k, node.m_jointype); + + IntPoint pt1; + if (node.m_endtype == etOpenButt) { + int j = len - 1; + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * delta), + (cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * delta), + (cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta)); + m_destPoly.push_back(pt1); + } else { + int j = len - 1; + k = len - 2; + m_sinA = 0; + m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y); + if (node.m_endtype == etOpenSquare) + DoSquare(j, k); + else + DoRound(j, k); + } + + // re-build m_normals ... + for (int j = len - 1; j > 0; j--) + m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y); + m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y); + + k = len - 1; + for (int j = k - 1; j > 0; --j) + OffsetPoint(j, k, node.m_jointype); + + if (node.m_endtype == etOpenButt) { + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta), + (cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta)); + m_destPoly.push_back(pt1); + } else { + k = 1; + m_sinA = 0; + if (node.m_endtype == etOpenSquare) + DoSquare(0, 1); + else + DoRound(0, 1); + } + m_destPolys.push_back(m_destPoly); + } + } +} +//------------------------------------------------------------------------------ + +void ClipperOffset::OffsetPoint(int j, int &k, JoinType jointype) { + // cross product ... + m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y); + if (std::fabs(m_sinA * m_delta) < 1.0) { + // dot product ... + double cosA = + (m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y); + if (cosA > 0) // angle => 0 degrees + { + m_destPoly.push_back( + IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + return; + } + // else angle => 180 degrees + } else if (m_sinA > 1.0) + m_sinA = 1.0; + else if (m_sinA < -1.0) + m_sinA = -1.0; + + if (m_sinA * m_delta < 0) { + m_destPoly.push_back( + IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta))); + m_destPoly.push_back(m_srcPoly[j]); + m_destPoly.push_back( + IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); + } else + switch (jointype) { + case jtMiter: { + double r = 1 + (m_normals[j].X * m_normals[k].X + + m_normals[j].Y * m_normals[k].Y); + if (r >= m_miterLim) + DoMiter(j, k, r); + else + DoSquare(j, k); + break; + } + case jtSquare: + DoSquare(j, k); + break; + case jtRound: + DoRound(j, k); + break; + } + k = j; +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoSquare(int j, int k) { + double dx = std::tan(std::atan2(m_sinA, m_normals[k].X * m_normals[j].X + + m_normals[k].Y * m_normals[j].Y) / + 4); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)), + Round(m_srcPoly[j].Y + + m_delta * (m_normals[k].Y + m_normals[k].X * dx)))); + m_destPoly.push_back(IntPoint( + Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)), + Round(m_srcPoly[j].Y + + m_delta * (m_normals[j].Y - m_normals[j].X * dx)))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoMiter(int j, int k, double r) { + double q = m_delta / r; + m_destPoly.push_back( + IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q), + Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q))); +} +//------------------------------------------------------------------------------ + +void ClipperOffset::DoRound(int j, int k) { + double a = std::atan2(m_sinA, m_normals[k].X * m_normals[j].X + + m_normals[k].Y * m_normals[j].Y); + int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1); + + double X = m_normals[k].X, Y = m_normals[k].Y, X2; + for (int i = 0; i < steps; ++i) { + m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + X * m_delta), + Round(m_srcPoly[j].Y + Y * m_delta))); + X2 = X; + X = X * m_cos - m_sin * Y; + Y = X2 * m_sin + Y * m_cos; + } + m_destPoly.push_back( + IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta), + Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta))); +} + +//------------------------------------------------------------------------------ +// Miscellaneous public functions +//------------------------------------------------------------------------------ + +void Clipper::DoSimplePolygons() { + PolyOutList::size_type i = 0; + while (i < m_PolyOuts.size()) { + OutRec *outrec = m_PolyOuts[i++]; + OutPt *op = outrec->Pts; + if (!op || outrec->IsOpen) + continue; + do // for each Pt in Polygon until duplicate found do ... + { + OutPt *op2 = op->Next; + while (op2 != outrec->Pts) { + if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) { + // split the polygon into two ... + OutPt *op3 = op->Prev; + OutPt *op4 = op2->Prev; + op->Prev = op4; + op4->Next = op; + op2->Prev = op3; + op3->Next = op2; + + outrec->Pts = op; + OutRec *outrec2 = CreateOutRec(); + outrec2->Pts = op2; + UpdateOutPtIdxs(*outrec2); + if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) { + // OutRec2 is contained by OutRec1 ... + outrec2->IsHole = !outrec->IsHole; + outrec2->FirstLeft = outrec; + if (m_UsingPolyTree) + FixupFirstLefts2(outrec2, outrec); + } else if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) { + // OutRec1 is contained by OutRec2 ... + outrec2->IsHole = outrec->IsHole; + outrec->IsHole = !outrec2->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + outrec->FirstLeft = outrec2; + if (m_UsingPolyTree) + FixupFirstLefts2(outrec, outrec2); + } else { + // the 2 polygons are separate ... + outrec2->IsHole = outrec->IsHole; + outrec2->FirstLeft = outrec->FirstLeft; + if (m_UsingPolyTree) + FixupFirstLefts1(outrec, outrec2); + } + op2 = op; // ie get ready for the Next iteration + } + op2 = op2->Next; + } + op = op->Next; + } while (op != outrec->Pts); + } +} +//------------------------------------------------------------------------------ + +void ReversePath(Path &p) { std::reverse(p.begin(), p.end()); } +//------------------------------------------------------------------------------ + +void ReversePaths(Paths &p) { + for (Paths::size_type i = 0; i < p.size(); ++i) + ReversePath(p[i]); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, + PolyFillType fillType) { + Clipper c; + c.StrictlySimple(true); + c.AddPath(in_poly, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, + PolyFillType fillType) { + Clipper c; + c.StrictlySimple(true); + c.AddPaths(in_polys, ptSubject, true); + c.Execute(ctUnion, out_polys, fillType, fillType); +} +//------------------------------------------------------------------------------ + +void SimplifyPolygons(Paths &polys, PolyFillType fillType) { + SimplifyPolygons(polys, polys, fillType); +} +//------------------------------------------------------------------------------ + +inline double DistanceSqrd(const IntPoint &pt1, const IntPoint &pt2) { + double Dx = ((double)pt1.X - pt2.X); + double dy = ((double)pt1.Y - pt2.Y); + return (Dx * Dx + dy * dy); +} +//------------------------------------------------------------------------------ + +double DistanceFromLineSqrd(const IntPoint &pt, const IntPoint &ln1, + const IntPoint &ln2) { + // The equation of a line in general form (Ax + By + C = 0) + // given 2 points (x�,y�) & (x�,y�) is ... + //(y� - y�)x + (x� - x�)y + (y� - y�)x� - (x� - x�)y� = 0 + // A = (y� - y�); B = (x� - x�); C = (y� - y�)x� - (x� - x�)y� + // perpendicular distance of point (x�,y�) = (Ax� + By� + C)/Sqrt(A� + B�) + // see http://en.wikipedia.org/wiki/Perpendicular_distance + double A = double(ln1.Y - ln2.Y); + double B = double(ln2.X - ln1.X); + double C = A * ln1.X + B * ln1.Y; + C = A * pt.X + B * pt.Y - C; + return (C * C) / (A * A + B * B); +} +//--------------------------------------------------------------------------- + +bool SlopesNearCollinear(const IntPoint &pt1, const IntPoint &pt2, + const IntPoint &pt3, double distSqrd) { + // this function is more accurate when the point that's geometrically + // between the other 2 points is the one that's tested for distance. + // ie makes it more likely to pick up 'spikes' ... + if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) { + if ((pt1.X > pt2.X) == (pt1.X < pt3.X)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.X > pt1.X) == (pt2.X < pt3.X)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } else { + if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y)) + return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd; + else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y)) + return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd; + else + return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd; + } +} +//------------------------------------------------------------------------------ + +bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) { + double Dx = (double)pt1.X - pt2.X; + double dy = (double)pt1.Y - pt2.Y; + return ((Dx * Dx) + (dy * dy) <= distSqrd); +} +//------------------------------------------------------------------------------ + +OutPt *ExcludeOp(OutPt *op) { + OutPt *result = op->Prev; + result->Next = op->Next; + op->Next->Prev = result; + result->Idx = 0; + return result; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(const Path &in_poly, Path &out_poly, double distance) { + // distance = proximity in units/pixels below which vertices + // will be stripped. Default ~= sqrt(2). + + size_t size = in_poly.size(); + + if (size == 0) { + out_poly.clear(); + return; + } + + OutPt *outPts = new OutPt[size]; + for (size_t i = 0; i < size; ++i) { + outPts[i].Pt = in_poly[i]; + outPts[i].Next = &outPts[(i + 1) % size]; + outPts[i].Next->Prev = &outPts[i]; + outPts[i].Idx = 0; + } + + double distSqrd = distance * distance; + OutPt *op = &outPts[0]; + while (op->Idx == 0 && op->Next != op->Prev) { + if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) { + op = ExcludeOp(op); + size--; + } else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) { + ExcludeOp(op->Next); + op = ExcludeOp(op); + size -= 2; + } else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt, + distSqrd)) { + op = ExcludeOp(op); + size--; + } else { + op->Idx = 1; + op = op->Next; + } + } + + if (size < 3) + size = 0; + out_poly.resize(size); + for (size_t i = 0; i < size; ++i) { + out_poly[i] = op->Pt; + op = op->Next; + } + delete[] outPts; +} +//------------------------------------------------------------------------------ + +void CleanPolygon(Path &poly, double distance) { + CleanPolygon(poly, poly, distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(const Paths &in_polys, Paths &out_polys, double distance) { + out_polys.resize(in_polys.size()); + for (Paths::size_type i = 0; i < in_polys.size(); ++i) + CleanPolygon(in_polys[i], out_polys[i], distance); +} +//------------------------------------------------------------------------------ + +void CleanPolygons(Paths &polys, double distance) { + CleanPolygons(polys, polys, distance); +} +//------------------------------------------------------------------------------ + +void Minkowski(const Path &poly, const Path &path, Paths &solution, bool isSum, + bool isClosed) { + int delta = (isClosed ? 1 : 0); + size_t polyCnt = poly.size(); + size_t pathCnt = path.size(); + Paths pp; + pp.reserve(pathCnt); + if (isSum) + for (size_t i = 0; i < pathCnt; ++i) { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y)); + pp.push_back(p); + } + else + for (size_t i = 0; i < pathCnt; ++i) { + Path p; + p.reserve(polyCnt); + for (size_t j = 0; j < poly.size(); ++j) + p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y)); + pp.push_back(p); + } + + solution.clear(); + solution.reserve((pathCnt + delta) * (polyCnt + 1)); + for (size_t i = 0; i < pathCnt - 1 + delta; ++i) + for (size_t j = 0; j < polyCnt; ++j) { + Path quad; + quad.reserve(4); + quad.push_back(pp[i % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]); + quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]); + quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]); + if (!Orientation(quad)) + ReversePath(quad); + solution.push_back(quad); + } +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution, + bool pathIsClosed) { + Minkowski(pattern, path, solution, true, pathIsClosed); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void TranslatePath(const Path &input, Path &output, const IntPoint delta) { + // precondition: input != output + output.resize(input.size()); + for (size_t i = 0; i < input.size(); ++i) + output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y); +} +//------------------------------------------------------------------------------ + +void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution, + bool pathIsClosed) { + Clipper c; + for (size_t i = 0; i < paths.size(); ++i) { + Paths tmp; + Minkowski(pattern, paths[i], tmp, true, pathIsClosed); + c.AddPaths(tmp, ptSubject, true); + if (pathIsClosed) { + Path tmp2; + TranslatePath(paths[i], tmp2, pattern[0]); + c.AddPath(tmp2, ptClip, true); + } + } + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution) { + Minkowski(poly1, poly2, solution, false, true); + Clipper c; + c.AddPaths(solution, ptSubject, true); + c.Execute(ctUnion, solution, pftNonZero, pftNonZero); +} +//------------------------------------------------------------------------------ + +enum NodeType { ntAny, ntOpen, ntClosed }; + +void AddPolyNodeToPaths(const PolyNode &polynode, NodeType nodetype, + Paths &paths) { + bool match = true; + if (nodetype == ntClosed) + match = !polynode.IsOpen(); + else if (nodetype == ntOpen) + return; + + if (!polynode.Contour.empty() && match) + paths.push_back(polynode.Contour); + for (int i = 0; i < polynode.ChildCount(); ++i) + AddPolyNodeToPaths(*polynode.Children[i], nodetype, paths); +} +//------------------------------------------------------------------------------ + +void PolyTreeToPaths(const PolyTree &polytree, Paths &paths) { + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPaths(polytree, ntAny, paths); +} +//------------------------------------------------------------------------------ + +void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths) { + paths.resize(0); + paths.reserve(polytree.Total()); + AddPolyNodeToPaths(polytree, ntClosed, paths); +} +//------------------------------------------------------------------------------ + +void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths) { + paths.resize(0); + paths.reserve(polytree.Total()); + // Open paths are top level only, so ... + for (int i = 0; i < polytree.ChildCount(); ++i) + if (polytree.Children[i]->IsOpen()) + paths.push_back(polytree.Children[i]->Contour); +} +//------------------------------------------------------------------------------ + +std::ostream &operator<<(std::ostream &s, const IntPoint &p) { + s << "(" << p.X << "," << p.Y << ")"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream &operator<<(std::ostream &s, const Path &p) { + if (p.empty()) + return s; + Path::size_type last = p.size() - 1; + for (Path::size_type i = 0; i < last; i++) + s << "(" << p[i].X << "," << p[i].Y << "), "; + s << "(" << p[last].X << "," << p[last].Y << ")\n"; + return s; +} +//------------------------------------------------------------------------------ + +std::ostream &operator<<(std::ostream &s, const Paths &p) { + for (Paths::size_type i = 0; i < p.size(); i++) + s << p[i]; + s << "\n"; + return s; +} +//------------------------------------------------------------------------------ + +} // namespace ClipperLib diff --git a/deploy/android_demo/app/src/main/cpp/ocr_clipper.hpp b/deploy/android_demo/app/src/main/cpp/ocr_clipper.hpp new file mode 100644 index 0000000..0d1ac24 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_clipper.hpp @@ -0,0 +1,540 @@ +/******************************************************************************* + * * + * Author : Angus Johnson * Version : 6.4.2 * Date : 27 February + *2017 * Website : + *http://www.angusj.com * Copyright : + *Angus Johnson 2010-2017 * + * * + * License: * Use, modification & distribution is subject to Boost Software + *License Ver 1. * http://www.boost.org/LICENSE_1_0.txt * + * * + * Attributions: * The code in this library is an extension of Bala Vatti's + *clipping algorithm: * "A generic solution to polygon clipping" * + * Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * + * http://portal.acm.org/citation.cfm?id=129906 * + * * + * Computer graphics and geometric modeling: implementation and algorithms * By + *Max K. Agoston * + * Springer; 1 edition (January 4, 2005) * + * http://books.google.com/books?q=vatti+clipping+agoston * + * * + * See also: * "Polygon Offsetting by Computing Winding Numbers" * Paper no. + *DETC2005-85513 pp. 565-575 * ASME 2005 + *International Design Engineering Technical Conferences * and + *Computers and Information in Engineering Conference (IDETC/CIE2005) * + * September 24-28, 2005 , Long Beach, California, USA * + * http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * + * * + *******************************************************************************/ + +#ifndef clipper_hpp +#define clipper_hpp + +#define CLIPPER_VERSION "6.4.2" + +// use_int32: When enabled 32bit ints are used instead of 64bit ints. This +// improve performance but coordinate values are limited to the range +/- 46340 +//#define use_int32 + +// use_xyz: adds a Z member to IntPoint. Adds a minor cost to performance. +//#define use_xyz + +// use_lines: Enables line clipping. Adds a very minor cost to performance. +#define use_lines + +// use_deprecated: Enables temporary support for the obsolete functions +//#define use_deprecated + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; +enum PolyType { ptSubject, ptClip }; +// By far the most widely used winding rules for polygon filling are +// EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) +// Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) +// see http://glprogramming.com/red/chapter11.html +enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + +#ifdef use_int32 +typedef int cInt; +static cInt const loRange = 0x7FFF; +static cInt const hiRange = 0x7FFF; +#else +typedef signed long long cInt; +static cInt const loRange = 0x3FFFFFFF; +static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; +typedef signed long long long64; // used by Int128 class +typedef unsigned long long ulong64; + +#endif + +struct IntPoint { + cInt X; + cInt Y; +#ifdef use_xyz + cInt Z; + IntPoint(cInt x = 0, cInt y = 0, cInt z = 0) : X(x), Y(y), Z(z){}; +#else + + IntPoint(cInt x = 0, cInt y = 0) : X(x), Y(y){}; +#endif + + friend inline bool operator==(const IntPoint &a, const IntPoint &b) { + return a.X == b.X && a.Y == b.Y; + } + + friend inline bool operator!=(const IntPoint &a, const IntPoint &b) { + return a.X != b.X || a.Y != b.Y; + } +}; +//------------------------------------------------------------------------------ + +typedef std::vector Path; +typedef std::vector Paths; + +inline Path &operator<<(Path &poly, const IntPoint &p) { + poly.push_back(p); + return poly; +} + +inline Paths &operator<<(Paths &polys, const Path &p) { + polys.push_back(p); + return polys; +} + +std::ostream &operator<<(std::ostream &s, const IntPoint &p); + +std::ostream &operator<<(std::ostream &s, const Path &p); + +std::ostream &operator<<(std::ostream &s, const Paths &p); + +struct DoublePoint { + double X; + double Y; + + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} + + DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} +}; +//------------------------------------------------------------------------------ + +#ifdef use_xyz +typedef void (*ZFillCallback)(IntPoint &e1bot, IntPoint &e1top, IntPoint &e2bot, + IntPoint &e2top, IntPoint &pt); +#endif + +enum InitOptions { + ioReverseSolution = 1, + ioStrictlySimple = 2, + ioPreserveCollinear = 4 +}; +enum JoinType { jtSquare, jtRound, jtMiter }; +enum EndType { + etClosedPolygon, + etClosedLine, + etOpenButt, + etOpenSquare, + etOpenRound +}; + +class PolyNode; + +typedef std::vector PolyNodes; + +class PolyNode { +public: + PolyNode(); + + virtual ~PolyNode(){}; + Path Contour; + PolyNodes Children; + PolyNode *Parent; + + PolyNode *GetNext() const; + + bool IsHole() const; + + bool IsOpen() const; + + int ChildCount() const; + +private: + // PolyNode& operator =(PolyNode& other); + unsigned Index; // node index in Parent.Children + bool m_IsOpen; + JoinType m_jointype; + EndType m_endtype; + + PolyNode *GetNextSiblingUp() const; + + void AddChild(PolyNode &child); + + friend class Clipper; // to access Index + friend class ClipperOffset; +}; + +class PolyTree : public PolyNode { +public: + ~PolyTree() { Clear(); }; + + PolyNode *GetFirst() const; + + void Clear(); + + int Total() const; + +private: + // PolyTree& operator =(PolyTree& other); + PolyNodes AllNodes; + + friend class Clipper; // to access AllNodes +}; + +bool Orientation(const Path &poly); + +double Area(const Path &poly); + +int PointInPolygon(const IntPoint &pt, const Path &path); + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, + PolyFillType fillType = pftEvenOdd); + +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, + PolyFillType fillType = pftEvenOdd); + +void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); + +void CleanPolygon(const Path &in_poly, Path &out_poly, double distance = 1.415); + +void CleanPolygon(Path &poly, double distance = 1.415); + +void CleanPolygons(const Paths &in_polys, Paths &out_polys, + double distance = 1.415); + +void CleanPolygons(Paths &polys, double distance = 1.415); + +void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution, + bool pathIsClosed); + +void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution, + bool pathIsClosed); + +void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution); + +void PolyTreeToPaths(const PolyTree &polytree, Paths &paths); + +void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths); + +void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths); + +void ReversePath(Path &p); + +void ReversePaths(Paths &p); + +struct IntRect { + cInt left; + cInt top; + cInt right; + cInt bottom; +}; + +// enums that are used internally ... +enum EdgeSide { esLeft = 1, esRight = 2 }; + +// forward declarations (for stuff used internally) ... +struct TEdge; +struct IntersectNode; +struct LocalMinimum; +struct OutPt; +struct OutRec; +struct Join; + +typedef std::vector PolyOutList; +typedef std::vector EdgeList; +typedef std::vector JoinList; +typedef std::vector IntersectList; + +//------------------------------------------------------------------------------ + +// ClipperBase is the ancestor to the Clipper class. It should not be +// instantiated directly. This class simply abstracts the conversion of sets of +// polygon coordinates into edge objects that are stored in a LocalMinima list. +class ClipperBase { +public: + ClipperBase(); + + virtual ~ClipperBase(); + + virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); + + bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); + + virtual void Clear(); + + IntRect GetBounds(); + + bool PreserveCollinear() { return m_PreserveCollinear; }; + + void PreserveCollinear(bool value) { m_PreserveCollinear = value; }; + +protected: + void DisposeLocalMinimaList(); + + TEdge *AddBoundsToLML(TEdge *e, bool IsClosed); + + virtual void Reset(); + + TEdge *ProcessBound(TEdge *E, bool IsClockwise); + + void InsertScanbeam(const cInt Y); + + bool PopScanbeam(cInt &Y); + + bool LocalMinimaPending(); + + bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin); + + OutRec *CreateOutRec(); + + void DisposeAllOutRecs(); + + void DisposeOutRec(PolyOutList::size_type index); + + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + + void DeleteFromAEL(TEdge *e); + + void UpdateEdgeIntoAEL(TEdge *&e); + + typedef std::vector MinimaList; + MinimaList::iterator m_CurrentLM; + MinimaList m_MinimaList; + + bool m_UseFullRange; + EdgeList m_edges; + bool m_PreserveCollinear; + bool m_HasOpenPaths; + PolyOutList m_PolyOuts; + TEdge *m_ActiveEdges; + + typedef std::priority_queue ScanbeamList; + ScanbeamList m_Scanbeam; +}; +//------------------------------------------------------------------------------ + +class Clipper : public virtual ClipperBase { +public: + Clipper(int initOptions = 0); + + bool Execute(ClipType clipType, Paths &solution, + PolyFillType fillType = pftEvenOdd); + + bool Execute(ClipType clipType, Paths &solution, PolyFillType subjFillType, + PolyFillType clipFillType); + + bool Execute(ClipType clipType, PolyTree &polytree, + PolyFillType fillType = pftEvenOdd); + + bool Execute(ClipType clipType, PolyTree &polytree, PolyFillType subjFillType, + PolyFillType clipFillType); + + bool ReverseSolution() { return m_ReverseOutput; }; + + void ReverseSolution(bool value) { m_ReverseOutput = value; }; + + bool StrictlySimple() { return m_StrictSimple; }; + + void StrictlySimple(bool value) { m_StrictSimple = value; }; +// set the callback function for z value filling on intersections (otherwise Z +// is 0) +#ifdef use_xyz + void ZFillFunction(ZFillCallback zFillFunc); +#endif +protected: + virtual bool ExecuteInternal(); + +private: + JoinList m_Joins; + JoinList m_GhostJoins; + IntersectList m_IntersectList; + ClipType m_ClipType; + typedef std::list MaximaList; + MaximaList m_Maxima; + TEdge *m_SortedEdges; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + bool m_StrictSimple; +#ifdef use_xyz + ZFillCallback m_ZFill; // custom callback +#endif + + void SetWindingCount(TEdge &edge); + + bool IsEvenOddFillType(const TEdge &edge) const; + + bool IsEvenOddAltFillType(const TEdge &edge) const; + + void InsertLocalMinimaIntoAEL(const cInt botY); + + void InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge); + + void AddEdgeToSEL(TEdge *edge); + + bool PopEdgeFromSEL(TEdge *&edge); + + void CopyAELToSEL(); + + void DeleteFromSEL(TEdge *e); + + void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); + + bool IsContributing(const TEdge &edge) const; + + bool IsTopHorz(const cInt XPos); + + void DoMaxima(TEdge *e); + + void ProcessHorizontals(); + + void ProcessHorizontal(TEdge *horzEdge); + + void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + + OutPt *AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + + OutRec *GetOutRec(int idx); + + void AppendPolygon(TEdge *e1, TEdge *e2); + + void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); + + OutPt *AddOutPt(TEdge *e, const IntPoint &pt); + + OutPt *GetLastOutPt(TEdge *e); + + bool ProcessIntersections(const cInt topY); + + void BuildIntersectList(const cInt topY); + + void ProcessIntersectList(); + + void ProcessEdgesAtTopOfScanbeam(const cInt topY); + + void BuildResult(Paths &polys); + + void BuildResult2(PolyTree &polytree); + + void SetHoleState(TEdge *e, OutRec *outrec); + + void DisposeIntersectNodes(); + + bool FixupIntersectionOrder(); + + void FixupOutPolygon(OutRec &outrec); + + void FixupOutPolyline(OutRec &outrec); + + bool IsHole(TEdge *e); + + bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); + + void FixHoleLinkage(OutRec &outrec); + + void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); + + void ClearJoins(); + + void ClearGhostJoins(); + + void AddGhostJoin(OutPt *op, const IntPoint offPt); + + bool JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2); + + void JoinCommonEdges(); + + void DoSimplePolygons(); + + void FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec); + + void FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec); + + void FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec); + +#ifdef use_xyz + void SetZ(IntPoint &pt, TEdge &e1, TEdge &e2); +#endif +}; +//------------------------------------------------------------------------------ + +class ClipperOffset { +public: + ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); + + ~ClipperOffset(); + + void AddPath(const Path &path, JoinType joinType, EndType endType); + + void AddPaths(const Paths &paths, JoinType joinType, EndType endType); + + void Execute(Paths &solution, double delta); + + void Execute(PolyTree &solution, double delta); + + void Clear(); + + double MiterLimit; + double ArcTolerance; + +private: + Paths m_destPolys; + Path m_srcPoly; + Path m_destPoly; + std::vector m_normals; + double m_delta, m_sinA, m_sin, m_cos; + double m_miterLim, m_StepsPerRad; + IntPoint m_lowest; + PolyNode m_polyNodes; + + void FixOrientations(); + + void DoOffset(double delta); + + void OffsetPoint(int j, int &k, JoinType jointype); + + void DoSquare(int j, int k); + + void DoMiter(int j, int k, double r); + + void DoRound(int j, int k); +}; +//------------------------------------------------------------------------------ + +class clipperException : public std::exception { +public: + clipperException(const char *description) : m_descr(description) {} + + virtual ~clipperException() throw() {} + + virtual const char *what() const throw() { return m_descr.c_str(); } + +private: + std::string m_descr; +}; +//------------------------------------------------------------------------------ + +} // namespace ClipperLib + +#endif // clipper_hpp diff --git a/deploy/android_demo/app/src/main/cpp/ocr_cls_process.cpp b/deploy/android_demo/app/src/main/cpp/ocr_cls_process.cpp new file mode 100644 index 0000000..c3434d9 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_cls_process.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ocr_cls_process.h" +#include +#include +#include +#include +#include + +const std::vector CLS_IMAGE_SHAPE = {3, 48, 192}; + +cv::Mat cls_resize_img(const cv::Mat &img) { + int imgC = CLS_IMAGE_SHAPE[0]; + int imgW = CLS_IMAGE_SHAPE[2]; + int imgH = CLS_IMAGE_SHAPE[1]; + + float ratio = float(img.cols) / float(img.rows); + int resize_w = 0; + if (ceilf(imgH * ratio) > imgW) + resize_w = imgW; + else + resize_w = int(ceilf(imgH * ratio)); + + cv::Mat resize_img; + cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f, + cv::INTER_CUBIC); + + if (resize_w < imgW) { + cv::copyMakeBorder(resize_img, resize_img, 0, 0, 0, int(imgW - resize_w), + cv::BORDER_CONSTANT, {0, 0, 0}); + } + return resize_img; +} diff --git a/deploy/android_demo/app/src/main/cpp/ocr_cls_process.h b/deploy/android_demo/app/src/main/cpp/ocr_cls_process.h new file mode 100644 index 0000000..17480a9 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_cls_process.h @@ -0,0 +1,23 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "common.h" +#include +#include + +extern const std::vector CLS_IMAGE_SHAPE; + +cv::Mat cls_resize_img(const cv::Mat &img); diff --git a/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.cpp b/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.cpp new file mode 100644 index 0000000..7e61a33 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.cpp @@ -0,0 +1,141 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ocr_crnn_process.h" +#include +#include +#include +#include +#include + +const std::string CHARACTER_TYPE = "ch"; +const int MAX_DICT_LENGTH = 6624; +const std::vector REC_IMAGE_SHAPE = {3, 32, 320}; + +static cv::Mat crnn_resize_norm_img(cv::Mat img, float wh_ratio) { + int imgC = REC_IMAGE_SHAPE[0]; + int imgW = REC_IMAGE_SHAPE[2]; + int imgH = REC_IMAGE_SHAPE[1]; + + if (CHARACTER_TYPE == "ch") + imgW = int(32 * wh_ratio); + + float ratio = float(img.cols) / float(img.rows); + int resize_w = 0; + if (ceilf(imgH * ratio) > imgW) + resize_w = imgW; + else + resize_w = int(ceilf(imgH * ratio)); + cv::Mat resize_img; + cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f, + cv::INTER_CUBIC); + + resize_img.convertTo(resize_img, CV_32FC3, 1 / 255.f); + + for (int h = 0; h < resize_img.rows; h++) { + for (int w = 0; w < resize_img.cols; w++) { + resize_img.at(h, w)[0] = + (resize_img.at(h, w)[0] - 0.5) * 2; + resize_img.at(h, w)[1] = + (resize_img.at(h, w)[1] - 0.5) * 2; + resize_img.at(h, w)[2] = + (resize_img.at(h, w)[2] - 0.5) * 2; + } + } + + cv::Mat dist; + cv::copyMakeBorder(resize_img, dist, 0, 0, 0, int(imgW - resize_w), + cv::BORDER_CONSTANT, {0, 0, 0}); + + return dist; +} + +cv::Mat crnn_resize_img(const cv::Mat &img, float wh_ratio) { + int imgC = REC_IMAGE_SHAPE[0]; + int imgW = REC_IMAGE_SHAPE[2]; + int imgH = REC_IMAGE_SHAPE[1]; + + if (CHARACTER_TYPE == "ch") { + imgW = int(32 * wh_ratio); + } + + float ratio = float(img.cols) / float(img.rows); + int resize_w = 0; + if (ceilf(imgH * ratio) > imgW) + resize_w = imgW; + else + resize_w = int(ceilf(imgH * ratio)); + cv::Mat resize_img; + cv::resize(img, resize_img, cv::Size(resize_w, imgH)); + return resize_img; +} + +cv::Mat get_rotate_crop_image(const cv::Mat &srcimage, + const std::vector> &box) { + + std::vector> points = box; + + int x_collect[4] = {box[0][0], box[1][0], box[2][0], box[3][0]}; + int y_collect[4] = {box[0][1], box[1][1], box[2][1], box[3][1]}; + int left = int(*std::min_element(x_collect, x_collect + 4)); + int right = int(*std::max_element(x_collect, x_collect + 4)); + int top = int(*std::min_element(y_collect, y_collect + 4)); + int bottom = int(*std::max_element(y_collect, y_collect + 4)); + + cv::Mat img_crop; + srcimage(cv::Rect(left, top, right - left, bottom - top)).copyTo(img_crop); + + for (int i = 0; i < points.size(); i++) { + points[i][0] -= left; + points[i][1] -= top; + } + + int img_crop_width = int(sqrt(pow(points[0][0] - points[1][0], 2) + + pow(points[0][1] - points[1][1], 2))); + int img_crop_height = int(sqrt(pow(points[0][0] - points[3][0], 2) + + pow(points[0][1] - points[3][1], 2))); + + cv::Point2f pts_std[4]; + pts_std[0] = cv::Point2f(0., 0.); + pts_std[1] = cv::Point2f(img_crop_width, 0.); + pts_std[2] = cv::Point2f(img_crop_width, img_crop_height); + pts_std[3] = cv::Point2f(0.f, img_crop_height); + + cv::Point2f pointsf[4]; + pointsf[0] = cv::Point2f(points[0][0], points[0][1]); + pointsf[1] = cv::Point2f(points[1][0], points[1][1]); + pointsf[2] = cv::Point2f(points[2][0], points[2][1]); + pointsf[3] = cv::Point2f(points[3][0], points[3][1]); + + cv::Mat M = cv::getPerspectiveTransform(pointsf, pts_std); + + cv::Mat dst_img; + cv::warpPerspective(img_crop, dst_img, M, + cv::Size(img_crop_width, img_crop_height), + cv::BORDER_REPLICATE); + + if (float(dst_img.rows) >= float(dst_img.cols) * 1.5) { + /* + cv::Mat srcCopy = cv::Mat(dst_img.rows, dst_img.cols, dst_img.depth()); + cv::transpose(dst_img, srcCopy); + cv::flip(srcCopy, srcCopy, 0); + return srcCopy; + */ + cv::transpose(dst_img, dst_img); + cv::flip(dst_img, dst_img, 0); + return dst_img; + } else { + return dst_img; + } +} diff --git a/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.h b/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.h new file mode 100644 index 0000000..6ce8128 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_crnn_process.h @@ -0,0 +1,20 @@ +// +// Created by fujiayi on 2020/7/3. +// +#pragma once + +#include "common.h" +#include +#include + +extern const std::vector REC_IMAGE_SHAPE; + +cv::Mat get_rotate_crop_image(const cv::Mat &srcimage, + const std::vector> &box); + +cv::Mat crnn_resize_img(const cv::Mat &img, float wh_ratio); + +template +inline size_t argmax(ForwardIterator first, ForwardIterator last) { + return std::distance(first, std::max_element(first, last)); +} diff --git a/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.cpp b/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.cpp new file mode 100644 index 0000000..ec731dd --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.cpp @@ -0,0 +1,342 @@ +// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ocr_clipper.hpp" +#include "opencv2/core.hpp" +#include "opencv2/imgcodecs.hpp" +#include "opencv2/imgproc.hpp" +#include +#include +#include + +static void getcontourarea(float **box, float unclip_ratio, float &distance) { + int pts_num = 4; + float area = 0.0f; + float dist = 0.0f; + for (int i = 0; i < pts_num; i++) { + area += box[i][0] * box[(i + 1) % pts_num][1] - + box[i][1] * box[(i + 1) % pts_num][0]; + dist += sqrtf((box[i][0] - box[(i + 1) % pts_num][0]) * + (box[i][0] - box[(i + 1) % pts_num][0]) + + (box[i][1] - box[(i + 1) % pts_num][1]) * + (box[i][1] - box[(i + 1) % pts_num][1])); + } + area = fabs(float(area / 2.0)); + + distance = area * unclip_ratio / dist; +} + +static cv::RotatedRect unclip(float **box) { + float unclip_ratio = 2.0; + float distance = 1.0; + + getcontourarea(box, unclip_ratio, distance); + + ClipperLib::ClipperOffset offset; + ClipperLib::Path p; + p << ClipperLib::IntPoint(int(box[0][0]), int(box[0][1])) + << ClipperLib::IntPoint(int(box[1][0]), int(box[1][1])) + << ClipperLib::IntPoint(int(box[2][0]), int(box[2][1])) + << ClipperLib::IntPoint(int(box[3][0]), int(box[3][1])); + offset.AddPath(p, ClipperLib::jtRound, ClipperLib::etClosedPolygon); + + ClipperLib::Paths soln; + offset.Execute(soln, distance); + std::vector points; + + for (int j = 0; j < soln.size(); j++) { + for (int i = 0; i < soln[soln.size() - 1].size(); i++) { + points.emplace_back(soln[j][i].X, soln[j][i].Y); + } + } + cv::RotatedRect res = cv::minAreaRect(points); + + return res; +} + +static float **Mat2Vec(cv::Mat mat) { + auto **array = new float *[mat.rows]; + for (int i = 0; i < mat.rows; ++i) { + array[i] = new float[mat.cols]; + } + for (int i = 0; i < mat.rows; ++i) { + for (int j = 0; j < mat.cols; ++j) { + array[i][j] = mat.at(i, j); + } + } + + return array; +} + +static void quickSort(float **s, int l, int r) { + if (l < r) { + int i = l, j = r; + float x = s[l][0]; + float *xp = s[l]; + while (i < j) { + while (i < j && s[j][0] >= x) { + j--; + } + if (i < j) { + std::swap(s[i++], s[j]); + } + while (i < j && s[i][0] < x) { + i++; + } + if (i < j) { + std::swap(s[j--], s[i]); + } + } + s[i] = xp; + quickSort(s, l, i - 1); + quickSort(s, i + 1, r); + } +} + +static void quickSort_vector(std::vector> &box, int l, int r, + int axis) { + if (l < r) { + int i = l, j = r; + int x = box[l][axis]; + std::vector xp(box[l]); + while (i < j) { + while (i < j && box[j][axis] >= x) { + j--; + } + if (i < j) { + std::swap(box[i++], box[j]); + } + while (i < j && box[i][axis] < x) { + i++; + } + if (i < j) { + std::swap(box[j--], box[i]); + } + } + box[i] = xp; + quickSort_vector(box, l, i - 1, axis); + quickSort_vector(box, i + 1, r, axis); + } +} + +static std::vector> +order_points_clockwise(std::vector> pts) { + std::vector> box = pts; + quickSort_vector(box, 0, int(box.size() - 1), 0); + std::vector> leftmost = {box[0], box[1]}; + std::vector> rightmost = {box[2], box[3]}; + + if (leftmost[0][1] > leftmost[1][1]) { + std::swap(leftmost[0], leftmost[1]); + } + + if (rightmost[0][1] > rightmost[1][1]) { + std::swap(rightmost[0], rightmost[1]); + } + + std::vector> rect = {leftmost[0], rightmost[0], rightmost[1], + leftmost[1]}; + return rect; +} + +static float **get_mini_boxes(cv::RotatedRect box, float &ssid) { + ssid = box.size.width >= box.size.height ? box.size.height : box.size.width; + + cv::Mat points; + cv::boxPoints(box, points); + // sorted box points + auto array = Mat2Vec(points); + quickSort(array, 0, 3); + + float *idx1 = array[0], *idx2 = array[1], *idx3 = array[2], *idx4 = array[3]; + if (array[3][1] <= array[2][1]) { + idx2 = array[3]; + idx3 = array[2]; + } else { + idx2 = array[2]; + idx3 = array[3]; + } + if (array[1][1] <= array[0][1]) { + idx1 = array[1]; + idx4 = array[0]; + } else { + idx1 = array[0]; + idx4 = array[1]; + } + + array[0] = idx1; + array[1] = idx2; + array[2] = idx3; + array[3] = idx4; + + return array; +} + +template T clamp(T x, T min, T max) { + if (x > max) { + return max; + } + if (x < min) { + return min; + } + return x; +} + +static float clampf(float x, float min, float max) { + if (x > max) + return max; + if (x < min) + return min; + return x; +} + +float box_score_fast(float **box_array, cv::Mat pred) { + auto array = box_array; + int width = pred.cols; + int height = pred.rows; + + float box_x[4] = {array[0][0], array[1][0], array[2][0], array[3][0]}; + float box_y[4] = {array[0][1], array[1][1], array[2][1], array[3][1]}; + + int xmin = clamp(int(std::floorf(*(std::min_element(box_x, box_x + 4)))), 0, + width - 1); + int xmax = clamp(int(std::ceilf(*(std::max_element(box_x, box_x + 4)))), 0, + width - 1); + int ymin = clamp(int(std::floorf(*(std::min_element(box_y, box_y + 4)))), 0, + height - 1); + int ymax = clamp(int(std::ceilf(*(std::max_element(box_y, box_y + 4)))), 0, + height - 1); + + cv::Mat mask; + mask = cv::Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8UC1); + + cv::Point root_point[4]; + root_point[0] = cv::Point(int(array[0][0]) - xmin, int(array[0][1]) - ymin); + root_point[1] = cv::Point(int(array[1][0]) - xmin, int(array[1][1]) - ymin); + root_point[2] = cv::Point(int(array[2][0]) - xmin, int(array[2][1]) - ymin); + root_point[3] = cv::Point(int(array[3][0]) - xmin, int(array[3][1]) - ymin); + const cv::Point *ppt[1] = {root_point}; + int npt[] = {4}; + cv::fillPoly(mask, ppt, npt, 1, cv::Scalar(1)); + + cv::Mat croppedImg; + pred(cv::Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1)) + .copyTo(croppedImg); + + auto score = cv::mean(croppedImg, mask)[0]; + return score; +} + +std::vector>> +boxes_from_bitmap(const cv::Mat &pred, const cv::Mat &bitmap) { + const int min_size = 3; + const int max_candidates = 1000; + const float box_thresh = 0.5; + + int width = bitmap.cols; + int height = bitmap.rows; + + std::vector> contours; + std::vector hierarchy; + + cv::findContours(bitmap, contours, hierarchy, cv::RETR_LIST, + cv::CHAIN_APPROX_SIMPLE); + + int num_contours = + contours.size() >= max_candidates ? max_candidates : contours.size(); + + std::vector>> boxes; + + for (int _i = 0; _i < num_contours; _i++) { + float ssid; + cv::RotatedRect box = cv::minAreaRect(contours[_i]); + auto array = get_mini_boxes(box, ssid); + + auto box_for_unclip = array; + // end get_mini_box + + if (ssid < min_size) { + continue; + } + + float score; + score = box_score_fast(array, pred); + // end box_score_fast + if (score < box_thresh) { + continue; + } + + // start for unclip + cv::RotatedRect points = unclip(box_for_unclip); + // end for unclip + + cv::RotatedRect clipbox = points; + auto cliparray = get_mini_boxes(clipbox, ssid); + + if (ssid < min_size + 2) + continue; + + int dest_width = pred.cols; + int dest_height = pred.rows; + std::vector> intcliparray; + + for (int num_pt = 0; num_pt < 4; num_pt++) { + std::vector a{int(clampf(roundf(cliparray[num_pt][0] / float(width) * + float(dest_width)), + 0, float(dest_width))), + int(clampf(roundf(cliparray[num_pt][1] / + float(height) * float(dest_height)), + 0, float(dest_height)))}; + intcliparray.emplace_back(std::move(a)); + } + boxes.emplace_back(std::move(intcliparray)); + + } // end for + return boxes; +} + +int _max(int a, int b) { return a >= b ? a : b; } + +int _min(int a, int b) { return a >= b ? b : a; } + +std::vector>> +filter_tag_det_res(const std::vector>> &o_boxes, + float ratio_h, float ratio_w, const cv::Mat &srcimg) { + int oriimg_h = srcimg.rows; + int oriimg_w = srcimg.cols; + std::vector>> boxes{o_boxes}; + std::vector>> root_points; + for (int n = 0; n < boxes.size(); n++) { + boxes[n] = order_points_clockwise(boxes[n]); + for (int m = 0; m < boxes[0].size(); m++) { + boxes[n][m][0] /= ratio_w; + boxes[n][m][1] /= ratio_h; + + boxes[n][m][0] = int(_min(_max(boxes[n][m][0], 0), oriimg_w - 1)); + boxes[n][m][1] = int(_min(_max(boxes[n][m][1], 0), oriimg_h - 1)); + } + } + + for (int n = 0; n < boxes.size(); n++) { + int rect_width, rect_height; + rect_width = int(sqrt(pow(boxes[n][0][0] - boxes[n][1][0], 2) + + pow(boxes[n][0][1] - boxes[n][1][1], 2))); + rect_height = int(sqrt(pow(boxes[n][0][0] - boxes[n][3][0], 2) + + pow(boxes[n][0][1] - boxes[n][3][1], 2))); + if (rect_width <= 10 || rect_height <= 10) + continue; + root_points.push_back(boxes[n]); + } + return root_points; +} diff --git a/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.h b/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.h new file mode 100644 index 0000000..6d84762 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_db_post_process.h @@ -0,0 +1,13 @@ +// +// Created by fujiayi on 2020/7/2. +// +#pragma once +#include +#include + +std::vector>> +boxes_from_bitmap(const cv::Mat &pred, const cv::Mat &bitmap); + +std::vector>> +filter_tag_det_res(const std::vector>> &o_boxes, + float ratio_h, float ratio_w, const cv::Mat &srcimg); diff --git a/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.cpp b/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.cpp new file mode 100644 index 0000000..95b86a2 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.cpp @@ -0,0 +1,354 @@ +// +// Created by fujiayi on 2020/7/1. +// + +#include "ocr_ppredictor.h" +#include "common.h" +#include "ocr_cls_process.h" +#include "ocr_crnn_process.h" +#include "ocr_db_post_process.h" +#include "preprocess.h" + +namespace ppredictor { + +OCR_PPredictor::OCR_PPredictor(const OCR_Config &config) : _config(config) {} + +int OCR_PPredictor::init(const std::string &det_model_content, + const std::string &rec_model_content, + const std::string &cls_model_content) { + _det_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR, _config.mode}); + _det_predictor->init_nb(det_model_content); + + _rec_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR_INTERNAL, _config.mode}); + _rec_predictor->init_nb(rec_model_content); + + _cls_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR_INTERNAL, _config.mode}); + _cls_predictor->init_nb(cls_model_content); + return RETURN_OK; +} + +int OCR_PPredictor::init_from_file(const std::string &det_model_path, + const std::string &rec_model_path, + const std::string &cls_model_path) { + _det_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR, _config.mode}); + _det_predictor->init_from_file(det_model_path); + + _rec_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR_INTERNAL, _config.mode}); + _rec_predictor->init_from_file(rec_model_path); + + _cls_predictor = std::unique_ptr(new PPredictor{ + _config.use_opencl, _config.thread_num, NET_OCR_INTERNAL, _config.mode}); + _cls_predictor->init_from_file(cls_model_path); + return RETURN_OK; +} +/** + * for debug use, show result of First Step + * @param filter_boxes + * @param boxes + * @param srcimg + */ +static void +visual_img(const std::vector>> &filter_boxes, + const std::vector>> &boxes, + const cv::Mat &srcimg) { + // visualization + cv::Point rook_points[filter_boxes.size()][4]; + for (int n = 0; n < filter_boxes.size(); n++) { + for (int m = 0; m < filter_boxes[0].size(); m++) { + rook_points[n][m] = + cv::Point(int(filter_boxes[n][m][0]), int(filter_boxes[n][m][1])); + } + } + + cv::Mat img_vis; + srcimg.copyTo(img_vis); + for (int n = 0; n < boxes.size(); n++) { + const cv::Point *ppt[1] = {rook_points[n]}; + int npt[] = {4}; + cv::polylines(img_vis, ppt, npt, 1, 1, CV_RGB(0, 255, 0), 2, 8, 0); + } + // 调试用,自行替换需要修改的路径 + cv::imwrite("/sdcard/1/vis.png", img_vis); +} + +std::vector +OCR_PPredictor::infer_ocr(cv::Mat &origin, int max_size_len, int run_det, + int run_cls, int run_rec) { + LOGI("ocr cpp start *****************"); + LOGI("ocr cpp det: %d, cls: %d, rec: %d", run_det, run_cls, run_rec); + std::vector ocr_results; + if (run_det) { + infer_det(origin, max_size_len, ocr_results); + } + if (run_rec) { + if (ocr_results.size() == 0) { + OCRPredictResult res; + ocr_results.emplace_back(std::move(res)); + } + for (int i = 0; i < ocr_results.size(); i++) { + infer_rec(origin, run_cls, ocr_results[i]); + } + } else if (run_cls) { + ClsPredictResult cls_res = infer_cls(origin); + OCRPredictResult res; + res.cls_score = cls_res.cls_score; + res.cls_label = cls_res.cls_label; + ocr_results.push_back(res); + } + + LOGI("ocr cpp end *****************"); + return ocr_results; +} + +cv::Mat DetResizeImg(const cv::Mat img, int max_size_len, + std::vector &ratio_hw) { + int w = img.cols; + int h = img.rows; + + float ratio = 1.f; + int max_wh = w >= h ? w : h; + if (max_wh > max_size_len) { + if (h > w) { + ratio = static_cast(max_size_len) / static_cast(h); + } else { + ratio = static_cast(max_size_len) / static_cast(w); + } + } + + int resize_h = static_cast(float(h) * ratio); + int resize_w = static_cast(float(w) * ratio); + if (resize_h % 32 == 0) + resize_h = resize_h; + else if (resize_h / 32 < 1 + 1e-5) + resize_h = 32; + else + resize_h = (resize_h / 32 - 1) * 32; + + if (resize_w % 32 == 0) + resize_w = resize_w; + else if (resize_w / 32 < 1 + 1e-5) + resize_w = 32; + else + resize_w = (resize_w / 32 - 1) * 32; + + cv::Mat resize_img; + cv::resize(img, resize_img, cv::Size(resize_w, resize_h)); + + ratio_hw.push_back(static_cast(resize_h) / static_cast(h)); + ratio_hw.push_back(static_cast(resize_w) / static_cast(w)); + return resize_img; +} + +void OCR_PPredictor::infer_det(cv::Mat &origin, int max_size_len, + std::vector &ocr_results) { + std::vector mean = {0.485f, 0.456f, 0.406f}; + std::vector scale = {1 / 0.229f, 1 / 0.224f, 1 / 0.225f}; + + PredictorInput input = _det_predictor->get_first_input(); + + std::vector ratio_hw; + cv::Mat input_image = DetResizeImg(origin, max_size_len, ratio_hw); + input_image.convertTo(input_image, CV_32FC3, 1 / 255.0f); + const float *dimg = reinterpret_cast(input_image.data); + int input_size = input_image.rows * input_image.cols; + + input.set_dims({1, 3, input_image.rows, input_image.cols}); + + neon_mean_scale(dimg, input.get_mutable_float_data(), input_size, mean, + scale); + LOGI("ocr cpp det shape %d,%d", input_image.rows, input_image.cols); + std::vector results = _det_predictor->infer(); + PredictorOutput &res = results.at(0); + std::vector>> filtered_box = + calc_filtered_boxes(res.get_float_data(), res.get_size(), + input_image.rows, input_image.cols, origin); + LOGI("ocr cpp det Filter_box size %ld", filtered_box.size()); + + for (int i = 0; i < filtered_box.size(); i++) { + LOGI("ocr cpp box %d,%d,%d,%d,%d,%d,%d,%d", filtered_box[i][0][0], + filtered_box[i][0][1], filtered_box[i][1][0], filtered_box[i][1][1], + filtered_box[i][2][0], filtered_box[i][2][1], filtered_box[i][3][0], + filtered_box[i][3][1]); + OCRPredictResult res; + res.points = filtered_box[i]; + ocr_results.push_back(res); + } +} + +void OCR_PPredictor::infer_rec(const cv::Mat &origin_img, int run_cls, + OCRPredictResult &ocr_result) { + std::vector mean = {0.5f, 0.5f, 0.5f}; + std::vector scale = {1 / 0.5f, 1 / 0.5f, 1 / 0.5f}; + std::vector dims = {1, 3, 0, 0}; + + PredictorInput input = _rec_predictor->get_first_input(); + + const std::vector> &box = ocr_result.points; + cv::Mat crop_img; + if (box.size() > 0) { + crop_img = get_rotate_crop_image(origin_img, box); + } else { + crop_img = origin_img; + } + + if (run_cls) { + ClsPredictResult cls_res = infer_cls(crop_img); + crop_img = cls_res.img; + ocr_result.cls_score = cls_res.cls_score; + ocr_result.cls_label = cls_res.cls_label; + } + + float wh_ratio = float(crop_img.cols) / float(crop_img.rows); + cv::Mat input_image = crnn_resize_img(crop_img, wh_ratio); + input_image.convertTo(input_image, CV_32FC3, 1 / 255.0f); + const float *dimg = reinterpret_cast(input_image.data); + int input_size = input_image.rows * input_image.cols; + + dims[2] = input_image.rows; + dims[3] = input_image.cols; + input.set_dims(dims); + + neon_mean_scale(dimg, input.get_mutable_float_data(), input_size, mean, + scale); + + std::vector results = _rec_predictor->infer(); + const float *predict_batch = results.at(0).get_float_data(); + const std::vector predict_shape = results.at(0).get_shape(); + + // ctc decode + int argmax_idx; + int last_index = 0; + float score = 0.f; + int count = 0; + float max_value = 0.0f; + + for (int n = 0; n < predict_shape[1]; n++) { + argmax_idx = int(argmax(&predict_batch[n * predict_shape[2]], + &predict_batch[(n + 1) * predict_shape[2]])); + max_value = + float(*std::max_element(&predict_batch[n * predict_shape[2]], + &predict_batch[(n + 1) * predict_shape[2]])); + if (argmax_idx > 0 && (!(n > 0 && argmax_idx == last_index))) { + score += max_value; + count += 1; + ocr_result.word_index.push_back(argmax_idx); + } + last_index = argmax_idx; + } + score /= count; + ocr_result.score = score; + LOGI("ocr cpp rec word size %ld", count); +} + +ClsPredictResult OCR_PPredictor::infer_cls(const cv::Mat &img, float thresh) { + std::vector mean = {0.5f, 0.5f, 0.5f}; + std::vector scale = {1 / 0.5f, 1 / 0.5f, 1 / 0.5f}; + std::vector dims = {1, 3, 0, 0}; + + PredictorInput input = _cls_predictor->get_first_input(); + + cv::Mat input_image = cls_resize_img(img); + input_image.convertTo(input_image, CV_32FC3, 1 / 255.0f); + const float *dimg = reinterpret_cast(input_image.data); + int input_size = input_image.rows * input_image.cols; + + dims[2] = input_image.rows; + dims[3] = input_image.cols; + input.set_dims(dims); + + neon_mean_scale(dimg, input.get_mutable_float_data(), input_size, mean, + scale); + + std::vector results = _cls_predictor->infer(); + + const float *scores = results.at(0).get_float_data(); + float score = 0; + int label = 0; + for (int64_t i = 0; i < results.at(0).get_size(); i++) { + LOGI("ocr cpp cls output scores [%f]", scores[i]); + if (scores[i] > score) { + score = scores[i]; + label = i; + } + } + cv::Mat srcimg; + img.copyTo(srcimg); + if (label % 2 == 1 && score > thresh) { + cv::rotate(srcimg, srcimg, 1); + } + ClsPredictResult res; + res.cls_label = label; + res.cls_score = score; + res.img = srcimg; + LOGI("ocr cpp cls word cls %ld, %f", label, score); + return res; +} + +std::vector>> +OCR_PPredictor::calc_filtered_boxes(const float *pred, int pred_size, + int output_height, int output_width, + const cv::Mat &origin) { + const double threshold = 0.3; + const double maxvalue = 1; + + cv::Mat pred_map = cv::Mat::zeros(output_height, output_width, CV_32F); + memcpy(pred_map.data, pred, pred_size * sizeof(float)); + cv::Mat cbuf_map; + pred_map.convertTo(cbuf_map, CV_8UC1); + + cv::Mat bit_map; + cv::threshold(cbuf_map, bit_map, threshold, maxvalue, cv::THRESH_BINARY); + + std::vector>> boxes = + boxes_from_bitmap(pred_map, bit_map); + float ratio_h = output_height * 1.0f / origin.rows; + float ratio_w = output_width * 1.0f / origin.cols; + std::vector>> filter_boxes = + filter_tag_det_res(boxes, ratio_h, ratio_w, origin); + return filter_boxes; +} + +std::vector +OCR_PPredictor::postprocess_rec_word_index(const PredictorOutput &res) { + const int *rec_idx = res.get_int_data(); + const std::vector> rec_idx_lod = res.get_lod(); + + std::vector pred_idx; + for (int n = int(rec_idx_lod[0][0]); n < int(rec_idx_lod[0][1] * 2); n += 2) { + pred_idx.emplace_back(rec_idx[n]); + } + return pred_idx; +} + +float OCR_PPredictor::postprocess_rec_score(const PredictorOutput &res) { + const float *predict_batch = res.get_float_data(); + const std::vector predict_shape = res.get_shape(); + const std::vector> predict_lod = res.get_lod(); + int blank = predict_shape[1]; + float score = 0.f; + int count = 0; + for (int n = predict_lod[0][0]; n < predict_lod[0][1] - 1; n++) { + int argmax_idx = argmax(predict_batch + n * predict_shape[1], + predict_batch + (n + 1) * predict_shape[1]); + float max_value = predict_batch[n * predict_shape[1] + argmax_idx]; + if (blank - 1 - argmax_idx > 1e-5) { + score += max_value; + count += 1; + } + } + if (count == 0) { + LOGE("calc score count 0"); + } else { + score /= count; + } + LOGI("calc score: %f", score); + return score; +} + +NET_TYPE OCR_PPredictor::get_net_flag() const { return NET_OCR; } +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.h b/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.h new file mode 100644 index 0000000..1567684 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ocr_ppredictor.h @@ -0,0 +1,131 @@ +// +// Created by fujiayi on 2020/7/1. +// + +#pragma once + +#include "ppredictor.h" +#include +#include +#include + +namespace ppredictor { + +/** + * Config + */ +struct OCR_Config { + int use_opencl = 0; + int thread_num = 4; // Thread num + paddle::lite_api::PowerMode mode = + paddle::lite_api::LITE_POWER_HIGH; // PaddleLite Mode +}; + +/** + * Polygons Result + */ +struct OCRPredictResult { + std::vector word_index; + std::vector> points; + float score; + float cls_score; + int cls_label = -1; +}; + +struct ClsPredictResult { + float cls_score; + int cls_label = -1; + cv::Mat img; +}; +/** + * OCR there are 2 models + * 1. First model(det),select polygons to show where are the texts + * 2. crop from the origin images, use these polygons to infer + */ +class OCR_PPredictor : public PPredictor_Interface { +public: + OCR_PPredictor(const OCR_Config &config); + + virtual ~OCR_PPredictor() {} + + /** + * 初始化二个模型的Predictor + * @param det_model_content + * @param rec_model_content + * @return + */ + int init(const std::string &det_model_content, + const std::string &rec_model_content, + const std::string &cls_model_content); + int init_from_file(const std::string &det_model_path, + const std::string &rec_model_path, + const std::string &cls_model_path); + /** + * Return OCR result + * @param dims + * @param input_data + * @param input_len + * @param net_flag + * @param origin + * @return + */ + virtual std::vector infer_ocr(cv::Mat &origin, + int max_size_len, int run_det, + int run_cls, int run_rec); + + virtual NET_TYPE get_net_flag() const; + +private: + /** + * calculate polygons from the result image of first model + * @param pred + * @param output_height + * @param output_width + * @param origin + * @return + */ + std::vector>> + calc_filtered_boxes(const float *pred, int pred_size, int output_height, + int output_width, const cv::Mat &origin); + + void infer_det(cv::Mat &origin, int max_side_len, + std::vector &ocr_results); + /** + * infer for rec model + * + * @param boxes + * @param origin + * @return + */ + void infer_rec(const cv::Mat &origin, int run_cls, + OCRPredictResult &ocr_result); + + /** + * infer for cls model + * + * @param boxes + * @param origin + * @return + */ + ClsPredictResult infer_cls(const cv::Mat &origin, float thresh = 0.9); + + /** + * Postprocess or second model to extract text + * @param res + * @return + */ + std::vector postprocess_rec_word_index(const PredictorOutput &res); + + /** + * calculate confidence of second model text result + * @param res + * @return + */ + float postprocess_rec_score(const PredictorOutput &res); + + std::unique_ptr _det_predictor; + std::unique_ptr _rec_predictor; + std::unique_ptr _cls_predictor; + OCR_Config _config; +}; +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/ppredictor.cpp b/deploy/android_demo/app/src/main/cpp/ppredictor.cpp new file mode 100644 index 0000000..10b8d7e --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ppredictor.cpp @@ -0,0 +1,99 @@ +#include "ppredictor.h" +#include "common.h" + +namespace ppredictor { +PPredictor::PPredictor(int use_opencl, int thread_num, int net_flag, + paddle::lite_api::PowerMode mode) + : _use_opencl(use_opencl), _thread_num(thread_num), _net_flag(net_flag), + _mode(mode) {} + +int PPredictor::init_nb(const std::string &model_content) { + paddle::lite_api::MobileConfig config; + config.set_model_from_buffer(model_content); + return _init(config); +} + +int PPredictor::init_from_file(const std::string &model_content) { + paddle::lite_api::MobileConfig config; + config.set_model_from_file(model_content); + return _init(config); +} + +template int PPredictor::_init(ConfigT &config) { + bool is_opencl_backend_valid = + paddle::lite_api::IsOpenCLBackendValid(/*check_fp16_valid = false*/); + if (is_opencl_backend_valid) { + if (_use_opencl != 0) { + // Make sure you have write permission of the binary path. + // We strongly recommend each model has a unique binary name. + const std::string bin_path = "/data/local/tmp/"; + const std::string bin_name = "lite_opencl_kernel.bin"; + config.set_opencl_binary_path_name(bin_path, bin_name); + + // opencl tune option + // CL_TUNE_NONE: 0 + // CL_TUNE_RAPID: 1 + // CL_TUNE_NORMAL: 2 + // CL_TUNE_EXHAUSTIVE: 3 + const std::string tuned_path = "/data/local/tmp/"; + const std::string tuned_name = "lite_opencl_tuned.bin"; + config.set_opencl_tune(paddle::lite_api::CL_TUNE_NORMAL, tuned_path, + tuned_name); + + // opencl precision option + // CL_PRECISION_AUTO: 0, first fp16 if valid, default + // CL_PRECISION_FP32: 1, force fp32 + // CL_PRECISION_FP16: 2, force fp16 + config.set_opencl_precision(paddle::lite_api::CL_PRECISION_FP32); + LOGI("ocr cpp device: running on gpu."); + } + } else { + LOGI("ocr cpp device: running on cpu."); + // you can give backup cpu nb model instead + // config.set_model_from_file(cpu_nb_model_dir); + } + config.set_threads(_thread_num); + config.set_power_mode(_mode); + _predictor = paddle::lite_api::CreatePaddlePredictor(config); + LOGI("ocr cpp paddle instance created"); + return RETURN_OK; +} + +PredictorInput PPredictor::get_input(int index) { + PredictorInput input{_predictor->GetInput(index), index, _net_flag}; + _is_input_get = true; + return input; +} + +std::vector PPredictor::get_inputs(int num) { + std::vector results; + for (int i = 0; i < num; i++) { + results.emplace_back(get_input(i)); + } + return results; +} + +PredictorInput PPredictor::get_first_input() { return get_input(0); } + +std::vector PPredictor::infer() { + LOGI("ocr cpp infer Run start %d", _net_flag); + std::vector results; + if (!_is_input_get) { + return results; + } + _predictor->Run(); + LOGI("ocr cpp infer Run end"); + + for (int i = 0; i < _predictor->GetOutputNames().size(); i++) { + std::unique_ptr output_tensor = + _predictor->GetOutput(i); + LOGI("ocr cpp output tensor[%d] size %ld", i, + product(output_tensor->shape())); + PredictorOutput result{std::move(output_tensor), i, _net_flag}; + results.emplace_back(std::move(result)); + } + return results; +} + +NET_TYPE PPredictor::get_net_flag() const { return (NET_TYPE)_net_flag; } +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/ppredictor.h b/deploy/android_demo/app/src/main/cpp/ppredictor.h new file mode 100644 index 0000000..230a84d --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/ppredictor.h @@ -0,0 +1,64 @@ +#pragma once + +#include "paddle_api.h" +#include "predictor_input.h" +#include "predictor_output.h" + +namespace ppredictor { + +/** + * PaddleLite Preditor Common Interface + */ +class PPredictor_Interface { +public: + virtual ~PPredictor_Interface() {} + + virtual NET_TYPE get_net_flag() const = 0; +}; + +/** + * Common Predictor + */ +class PPredictor : public PPredictor_Interface { +public: + PPredictor( + int use_opencl, int thread_num, int net_flag = 0, + paddle::lite_api::PowerMode mode = paddle::lite_api::LITE_POWER_HIGH); + + virtual ~PPredictor() {} + + /** + * init paddlitelite opt model,nb format ,or use ini_paddle + * @param model_content + * @return 0 + */ + virtual int init_nb(const std::string &model_content); + + virtual int init_from_file(const std::string &model_content); + + std::vector infer(); + + std::shared_ptr get_predictor() { + return _predictor; + } + + virtual std::vector get_inputs(int num); + + virtual PredictorInput get_input(int index); + + virtual PredictorInput get_first_input(); + + virtual NET_TYPE get_net_flag() const; + +protected: + template int _init(ConfigT &config); + +private: + int _use_opencl; + int _thread_num; + paddle::lite_api::PowerMode _mode; + std::shared_ptr _predictor; + bool _is_input_get = false; + int _net_flag; +}; +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/predictor_input.cpp b/deploy/android_demo/app/src/main/cpp/predictor_input.cpp new file mode 100644 index 0000000..2d5666f --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/predictor_input.cpp @@ -0,0 +1,28 @@ +#include "predictor_input.h" + +namespace ppredictor { + +void PredictorInput::set_dims(std::vector dims) { + // yolov3 + if (_net_flag == 101 && _index == 1) { + _tensor->Resize({1, 2}); + _tensor->mutable_data()[0] = (int)dims.at(2); + _tensor->mutable_data()[1] = (int)dims.at(3); + } else { + _tensor->Resize(dims); + } + _is_dims_set = true; +} + +float *PredictorInput::get_mutable_float_data() { + if (!_is_dims_set) { + LOGE("PredictorInput::set_dims is not called"); + } + return _tensor->mutable_data(); +} + +void PredictorInput::set_data(const float *input_data, int input_float_len) { + float *input_raw_data = get_mutable_float_data(); + memcpy(input_raw_data, input_data, input_float_len * sizeof(float)); +} +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/predictor_input.h b/deploy/android_demo/app/src/main/cpp/predictor_input.h new file mode 100644 index 0000000..185f081 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/predictor_input.h @@ -0,0 +1,26 @@ +#pragma once + +#include "common.h" +#include +#include + +namespace ppredictor { +class PredictorInput { +public: + PredictorInput(std::unique_ptr &&tensor, int index, + int net_flag) + : _tensor(std::move(tensor)), _index(index), _net_flag(net_flag) {} + + void set_dims(std::vector dims); + + float *get_mutable_float_data(); + + void set_data(const float *input_data, int input_float_len); + +private: + std::unique_ptr _tensor; + bool _is_dims_set = false; + int _index; + int _net_flag; +}; +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/predictor_output.cpp b/deploy/android_demo/app/src/main/cpp/predictor_output.cpp new file mode 100644 index 0000000..2176ef5 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/predictor_output.cpp @@ -0,0 +1,26 @@ +#include "predictor_output.h" +namespace ppredictor { +const float *PredictorOutput::get_float_data() const { + return _tensor->data(); +} + +const int *PredictorOutput::get_int_data() const { + return _tensor->data(); +} + +const std::vector> PredictorOutput::get_lod() const { + return _tensor->lod(); +} + +int64_t PredictorOutput::get_size() const { + if (_net_flag == NET_OCR) { + return _tensor->shape().at(2) * _tensor->shape().at(3); + } else { + return product(_tensor->shape()); + } +} + +const std::vector PredictorOutput::get_shape() const { + return _tensor->shape(); +} +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/predictor_output.h b/deploy/android_demo/app/src/main/cpp/predictor_output.h new file mode 100644 index 0000000..ce00af7 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/predictor_output.h @@ -0,0 +1,31 @@ +#pragma once + +#include "common.h" +#include +#include + +namespace ppredictor { +class PredictorOutput { +public: + PredictorOutput() {} + PredictorOutput(std::unique_ptr &&tensor, + int index, int net_flag) + : _tensor(std::move(tensor)), _index(index), _net_flag(net_flag) {} + + const float *get_float_data() const; + const int *get_int_data() const; + int64_t get_size() const; + const std::vector> get_lod() const; + const std::vector get_shape() const; + + std::vector data; // return float, or use data_int + std::vector data_int; // several layers return int ,or use data + std::vector shape; // PaddleLite output shape + std::vector> lod; // PaddleLite output lod + +private: + std::unique_ptr _tensor; + int _index; + int _net_flag; +}; +} // namespace ppredictor diff --git a/deploy/android_demo/app/src/main/cpp/preprocess.cpp b/deploy/android_demo/app/src/main/cpp/preprocess.cpp new file mode 100644 index 0000000..f3b29ec --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/preprocess.cpp @@ -0,0 +1,82 @@ +#include "preprocess.h" +#include + +cv::Mat bitmap_to_cv_mat(JNIEnv *env, jobject bitmap) { + AndroidBitmapInfo info; + int result = AndroidBitmap_getInfo(env, bitmap, &info); + if (result != ANDROID_BITMAP_RESULT_SUCCESS) { + LOGE("AndroidBitmap_getInfo failed, result: %d", result); + return cv::Mat{}; + } + if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { + LOGE("Bitmap format is not RGBA_8888 !"); + return cv::Mat{}; + } + unsigned char *srcData = NULL; + AndroidBitmap_lockPixels(env, bitmap, (void **)&srcData); + cv::Mat mat = cv::Mat::zeros(info.height, info.width, CV_8UC4); + memcpy(mat.data, srcData, info.height * info.width * 4); + AndroidBitmap_unlockPixels(env, bitmap); + cv::cvtColor(mat, mat, cv::COLOR_RGBA2BGR); + /** + if (!cv::imwrite("/sdcard/1/copy.jpg", mat)){ + LOGE("Write image failed " ); + } + */ + + return mat; +} + +cv::Mat resize_img(const cv::Mat &img, int height, int width) { + if (img.rows == height && img.cols == width) { + return img; + } + cv::Mat new_img; + cv::resize(img, new_img, cv::Size(height, width)); + return new_img; +} + +// fill tensor with mean and scale and trans layout: nhwc -> nchw, neon speed up +void neon_mean_scale(const float *din, float *dout, int size, + const std::vector &mean, + const std::vector &scale) { + if (mean.size() != 3 || scale.size() != 3) { + LOGE("[ERROR] mean or scale size must equal to 3"); + return; + } + + float32x4_t vmean0 = vdupq_n_f32(mean[0]); + float32x4_t vmean1 = vdupq_n_f32(mean[1]); + float32x4_t vmean2 = vdupq_n_f32(mean[2]); + float32x4_t vscale0 = vdupq_n_f32(scale[0]); + float32x4_t vscale1 = vdupq_n_f32(scale[1]); + float32x4_t vscale2 = vdupq_n_f32(scale[2]); + + float *dout_c0 = dout; + float *dout_c1 = dout + size; + float *dout_c2 = dout + size * 2; + + int i = 0; + for (; i < size - 3; i += 4) { + float32x4x3_t vin3 = vld3q_f32(din); + float32x4_t vsub0 = vsubq_f32(vin3.val[0], vmean0); + float32x4_t vsub1 = vsubq_f32(vin3.val[1], vmean1); + float32x4_t vsub2 = vsubq_f32(vin3.val[2], vmean2); + float32x4_t vs0 = vmulq_f32(vsub0, vscale0); + float32x4_t vs1 = vmulq_f32(vsub1, vscale1); + float32x4_t vs2 = vmulq_f32(vsub2, vscale2); + vst1q_f32(dout_c0, vs0); + vst1q_f32(dout_c1, vs1); + vst1q_f32(dout_c2, vs2); + + din += 12; + dout_c0 += 4; + dout_c1 += 4; + dout_c2 += 4; + } + for (; i < size; i++) { + *(dout_c0++) = (*(din++) - mean[0]) * scale[0]; + *(dout_c1++) = (*(din++) - mean[1]) * scale[1]; + *(dout_c2++) = (*(din++) - mean[2]) * scale[2]; + } +} diff --git a/deploy/android_demo/app/src/main/cpp/preprocess.h b/deploy/android_demo/app/src/main/cpp/preprocess.h new file mode 100644 index 0000000..7909152 --- /dev/null +++ b/deploy/android_demo/app/src/main/cpp/preprocess.h @@ -0,0 +1,12 @@ +#pragma once + +#include "common.h" +#include +#include +cv::Mat bitmap_to_cv_mat(JNIEnv *env, jobject bitmap); + +cv::Mat resize_img(const cv::Mat &img, int height, int width); + +void neon_mean_scale(const float *din, float *dout, int size, + const std::vector &mean, + const std::vector &scale); diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java new file mode 100644 index 0000000..49af0af --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.baidu.paddle.lite.demo.ocr; + +import android.content.res.Configuration; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.LayoutRes; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.appcompat.widget.Toolbar; + +/** + * A {@link PreferenceActivity} which implements and proxies the necessary calls + * to be used with AppCompat. + *

+ * This technique can be used with an {@link android.app.Activity} class, not just + * {@link PreferenceActivity}. + */ +public abstract class AppCompatPreferenceActivity extends PreferenceActivity { + private AppCompatDelegate mDelegate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + super.onCreate(savedInstanceState); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + getDelegate().onPostCreate(savedInstanceState); + } + + public ActionBar getSupportActionBar() { + return getDelegate().getSupportActionBar(); + } + + public void setSupportActionBar(@Nullable Toolbar toolbar) { + getDelegate().setSupportActionBar(toolbar); + } + + @Override + public MenuInflater getMenuInflater() { + return getDelegate().getMenuInflater(); + } + + @Override + public void setContentView(@LayoutRes int layoutResID) { + getDelegate().setContentView(layoutResID); + } + + @Override + public void setContentView(View view) { + getDelegate().setContentView(view); + } + + @Override + public void setContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().setContentView(view, params); + } + + @Override + public void addContentView(View view, ViewGroup.LayoutParams params) { + getDelegate().addContentView(view, params); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + protected void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + protected void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + public void invalidateOptionsMenu() { + getDelegate().invalidateOptionsMenu(); + } + + private AppCompatDelegate getDelegate() { + if (mDelegate == null) { + mDelegate = AppCompatDelegate.create(this, null); + } + return mDelegate; + } +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java new file mode 100644 index 0000000..91a6d76 --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java @@ -0,0 +1,520 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.Manifest; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.media.ExifInterface; +import android.content.res.AssetManager; +import android.media.FaceDetector; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.preference.PreferenceManager; +import android.provider.MediaStore; +import android.text.method.ScrollingMovementMethod; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class MainActivity extends AppCompatActivity { + private static final String TAG = MainActivity.class.getSimpleName(); + public static final int OPEN_GALLERY_REQUEST_CODE = 0; + public static final int TAKE_PHOTO_REQUEST_CODE = 1; + + public static final int REQUEST_LOAD_MODEL = 0; + public static final int REQUEST_RUN_MODEL = 1; + public static final int RESPONSE_LOAD_MODEL_SUCCESSED = 0; + public static final int RESPONSE_LOAD_MODEL_FAILED = 1; + public static final int RESPONSE_RUN_MODEL_SUCCESSED = 2; + public static final int RESPONSE_RUN_MODEL_FAILED = 3; + + protected ProgressDialog pbLoadModel = null; + protected ProgressDialog pbRunModel = null; + + protected Handler receiver = null; // Receive messages from worker thread + protected Handler sender = null; // Send command to worker thread + protected HandlerThread worker = null; // Worker thread to load&run model + + // UI components of object detection + protected TextView tvInputSetting; + protected TextView tvStatus; + protected ImageView ivInputImage; + protected TextView tvOutputResult; + protected TextView tvInferenceTime; + protected CheckBox cbOpencl; + protected Spinner spRunMode; + + // Model settings of ocr + protected String modelPath = ""; + protected String labelPath = ""; + protected String imagePath = ""; + protected int cpuThreadNum = 1; + protected String cpuPowerMode = ""; + protected int detLongSize = 960; + protected float scoreThreshold = 0.1f; + private String currentPhotoPath; + private AssetManager assetManager = null; + + protected Predictor predictor = new Predictor(); + + private Bitmap cur_predict_image = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // Clear all setting items to avoid app crashing due to the incorrect settings + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.clear(); + editor.apply(); + + // Setup the UI components + tvInputSetting = findViewById(R.id.tv_input_setting); + cbOpencl = findViewById(R.id.cb_opencl); + tvStatus = findViewById(R.id.tv_model_img_status); + ivInputImage = findViewById(R.id.iv_input_image); + tvInferenceTime = findViewById(R.id.tv_inference_time); + tvOutputResult = findViewById(R.id.tv_output_result); + spRunMode = findViewById(R.id.sp_run_mode); + tvInputSetting.setMovementMethod(ScrollingMovementMethod.getInstance()); + tvOutputResult.setMovementMethod(ScrollingMovementMethod.getInstance()); + + // Prepare the worker thread for mode loading and inference + receiver = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case RESPONSE_LOAD_MODEL_SUCCESSED: + if (pbLoadModel != null && pbLoadModel.isShowing()) { + pbLoadModel.dismiss(); + } + onLoadModelSuccessed(); + break; + case RESPONSE_LOAD_MODEL_FAILED: + if (pbLoadModel != null && pbLoadModel.isShowing()) { + pbLoadModel.dismiss(); + } + Toast.makeText(MainActivity.this, "Load model failed!", Toast.LENGTH_SHORT).show(); + onLoadModelFailed(); + break; + case RESPONSE_RUN_MODEL_SUCCESSED: + if (pbRunModel != null && pbRunModel.isShowing()) { + pbRunModel.dismiss(); + } + onRunModelSuccessed(); + break; + case RESPONSE_RUN_MODEL_FAILED: + if (pbRunModel != null && pbRunModel.isShowing()) { + pbRunModel.dismiss(); + } + Toast.makeText(MainActivity.this, "Run model failed!", Toast.LENGTH_SHORT).show(); + onRunModelFailed(); + break; + default: + break; + } + } + }; + + worker = new HandlerThread("Predictor Worker"); + worker.start(); + sender = new Handler(worker.getLooper()) { + public void handleMessage(Message msg) { + switch (msg.what) { + case REQUEST_LOAD_MODEL: + // Load model and reload test image + if (onLoadModel()) { + receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_SUCCESSED); + } else { + receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_FAILED); + } + break; + case REQUEST_RUN_MODEL: + // Run model if model is loaded + if (onRunModel()) { + receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_SUCCESSED); + } else { + receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_FAILED); + } + break; + default: + break; + } + } + }; + } + + @Override + protected void onResume() { + super.onResume(); + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); + boolean settingsChanged = false; + boolean model_settingsChanged = false; + String model_path = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY), + getString(R.string.MODEL_PATH_DEFAULT)); + String label_path = sharedPreferences.getString(getString(R.string.LABEL_PATH_KEY), + getString(R.string.LABEL_PATH_DEFAULT)); + String image_path = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY), + getString(R.string.IMAGE_PATH_DEFAULT)); + model_settingsChanged |= !model_path.equalsIgnoreCase(modelPath); + settingsChanged |= !label_path.equalsIgnoreCase(labelPath); + settingsChanged |= !image_path.equalsIgnoreCase(imagePath); + int cpu_thread_num = Integer.parseInt(sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY), + getString(R.string.CPU_THREAD_NUM_DEFAULT))); + model_settingsChanged |= cpu_thread_num != cpuThreadNum; + String cpu_power_mode = + sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY), + getString(R.string.CPU_POWER_MODE_DEFAULT)); + model_settingsChanged |= !cpu_power_mode.equalsIgnoreCase(cpuPowerMode); + + int det_long_size = Integer.parseInt(sharedPreferences.getString(getString(R.string.DET_LONG_SIZE_KEY), + getString(R.string.DET_LONG_SIZE_DEFAULT))); + settingsChanged |= det_long_size != detLongSize; + float score_threshold = + Float.parseFloat(sharedPreferences.getString(getString(R.string.SCORE_THRESHOLD_KEY), + getString(R.string.SCORE_THRESHOLD_DEFAULT))); + settingsChanged |= scoreThreshold != score_threshold; + if (settingsChanged) { + labelPath = label_path; + imagePath = image_path; + detLongSize = det_long_size; + scoreThreshold = score_threshold; + set_img(); + } + if (model_settingsChanged) { + modelPath = model_path; + cpuThreadNum = cpu_thread_num; + cpuPowerMode = cpu_power_mode; + // Update UI + tvInputSetting.setText("Model: " + modelPath.substring(modelPath.lastIndexOf("/") + 1) + "\nOPENCL: " + cbOpencl.isChecked() + "\nCPU Thread Num: " + cpuThreadNum + "\nCPU Power Mode: " + cpuPowerMode); + tvInputSetting.scrollTo(0, 0); + // Reload model if configure has been changed + loadModel(); + } + } + + public void loadModel() { + pbLoadModel = ProgressDialog.show(this, "", "loading model...", false, false); + sender.sendEmptyMessage(REQUEST_LOAD_MODEL); + } + + public void runModel() { + pbRunModel = ProgressDialog.show(this, "", "running model...", false, false); + sender.sendEmptyMessage(REQUEST_RUN_MODEL); + } + + public boolean onLoadModel() { + if (predictor.isLoaded()) { + predictor.releaseModel(); + } + return predictor.init(MainActivity.this, modelPath, labelPath, cbOpencl.isChecked() ? 1 : 0, cpuThreadNum, + cpuPowerMode, + detLongSize, scoreThreshold); + } + + public boolean onRunModel() { + String run_mode = spRunMode.getSelectedItem().toString(); + int run_det = run_mode.contains("检测") ? 1 : 0; + int run_cls = run_mode.contains("分类") ? 1 : 0; + int run_rec = run_mode.contains("识别") ? 1 : 0; + return predictor.isLoaded() && predictor.runModel(run_det, run_cls, run_rec); + } + + public void onLoadModelSuccessed() { + // Load test image from path and run model + tvInputSetting.setText("Model: " + modelPath.substring(modelPath.lastIndexOf("/") + 1) + "\nOPENCL: " + cbOpencl.isChecked() + "\nCPU Thread Num: " + cpuThreadNum + "\nCPU Power Mode: " + cpuPowerMode); + tvInputSetting.scrollTo(0, 0); + tvStatus.setText("STATUS: load model succeeded"); + + } + + public void onLoadModelFailed() { + tvStatus.setText("STATUS: load model failed"); + } + + public void onRunModelSuccessed() { + tvStatus.setText("STATUS: run model succeeded"); + // Obtain results and update UI + tvInferenceTime.setText("Inference time: " + predictor.inferenceTime() + " ms"); + Bitmap outputImage = predictor.outputImage(); + if (outputImage != null) { + ivInputImage.setImageBitmap(outputImage); + } + tvOutputResult.setText(predictor.outputResult()); + tvOutputResult.scrollTo(0, 0); + } + + public void onRunModelFailed() { + tvStatus.setText("STATUS: run model failed"); + } + + public void set_img() { + // Load test image from path and run model + try { + assetManager = getAssets(); + InputStream in = assetManager.open(imagePath); + Bitmap bmp = BitmapFactory.decodeStream(in); + cur_predict_image = bmp; + ivInputImage.setImageBitmap(bmp); + } catch (IOException e) { + Toast.makeText(MainActivity.this, "Load image failed!", Toast.LENGTH_SHORT).show(); + e.printStackTrace(); + } + } + + public void onSettingsClicked() { + startActivity(new Intent(MainActivity.this, SettingsActivity.class)); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_action_options, menu); + return true; + } + + public boolean onPrepareOptionsMenu(Menu menu) { + boolean isLoaded = predictor.isLoaded(); + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + break; + case R.id.settings: + if (requestAllPermissions()) { + // Make sure we have SDCard r&w permissions to load model from SDCard + onSettingsClicked(); + } + break; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show(); + } + } + + private boolean requestAllPermissions() { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this, + Manifest.permission.CAMERA) + != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.CAMERA}, + 0); + return false; + } + return true; + } + + private void openGallery() { + Intent intent = new Intent(Intent.ACTION_PICK, null); + intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); + startActivityForResult(intent, OPEN_GALLERY_REQUEST_CODE); + } + + private void takePhoto() { + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + // Ensure that there's a camera activity to handle the intent + if (takePictureIntent.resolveActivity(getPackageManager()) != null) { + // Create the File where the photo should go + File photoFile = null; + try { + photoFile = createImageFile(); + } catch (IOException ex) { + Log.e("MainActitity", ex.getMessage(), ex); + Toast.makeText(MainActivity.this, + "Create Camera temp file failed: " + ex.getMessage(), Toast.LENGTH_SHORT).show(); + } + // Continue only if the File was successfully created + if (photoFile != null) { + Log.i(TAG, "FILEPATH " + getExternalFilesDir("Pictures").getAbsolutePath()); + Uri photoURI = FileProvider.getUriForFile(this, + "com.baidu.paddle.lite.demo.ocr.fileprovider", + photoFile); + currentPhotoPath = photoFile.getAbsolutePath(); + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); + startActivityForResult(takePictureIntent, TAKE_PHOTO_REQUEST_CODE); + Log.i(TAG, "startActivityForResult finished"); + } + } + + } + + private File createImageFile() throws IOException { + // Create an image file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + String imageFileName = "JPEG_" + timeStamp + "_"; + File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); + File image = File.createTempFile( + imageFileName, /* prefix */ + ".bmp", /* suffix */ + storageDir /* directory */ + ); + + return image; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) { + switch (requestCode) { + case OPEN_GALLERY_REQUEST_CODE: + if (data == null) { + break; + } + try { + ContentResolver resolver = getContentResolver(); + Uri uri = data.getData(); + Bitmap image = MediaStore.Images.Media.getBitmap(resolver, uri); + String[] proj = {MediaStore.Images.Media.DATA}; + Cursor cursor = managedQuery(uri, proj, null, null, null); + cursor.moveToFirst(); + if (image != null) { + cur_predict_image = image; + ivInputImage.setImageBitmap(image); + } + } catch (IOException e) { + Log.e(TAG, e.toString()); + } + break; + case TAKE_PHOTO_REQUEST_CODE: + if (currentPhotoPath != null) { + ExifInterface exif = null; + try { + exif = new ExifInterface(currentPhotoPath); + } catch (IOException e) { + e.printStackTrace(); + } + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_UNDEFINED); + Log.i(TAG, "rotation " + orientation); + Bitmap image = BitmapFactory.decodeFile(currentPhotoPath); + image = Utils.rotateBitmap(image, orientation); + if (image != null) { + cur_predict_image = image; + ivInputImage.setImageBitmap(image); + } + } else { + Log.e(TAG, "currentPhotoPath is null"); + } + break; + default: + break; + } + } + } + + public void btn_reset_img_click(View view) { + ivInputImage.setImageBitmap(cur_predict_image); + } + + public void cb_opencl_click(View view) { + tvStatus.setText("STATUS: load model ......"); + loadModel(); + } + + public void btn_run_model_click(View view) { + Bitmap image = ((BitmapDrawable) ivInputImage.getDrawable()).getBitmap(); + if (image == null) { + tvStatus.setText("STATUS: image is not exists"); + } else if (!predictor.isLoaded()) { + tvStatus.setText("STATUS: model is not loaded"); + } else { + tvStatus.setText("STATUS: run model ...... "); + predictor.setInputImage(image); + runModel(); + } + } + + public void btn_choice_img_click(View view) { + if (requestAllPermissions()) { + openGallery(); + } + } + + public void btn_take_photo_click(View view) { + if (requestAllPermissions()) { + takePhoto(); + } + } + + @Override + protected void onDestroy() { + if (predictor != null) { + predictor.releaseModel(); + } + worker.quit(); + super.onDestroy(); + } + + public int get_run_mode() { + String run_mode = spRunMode.getSelectedItem().toString(); + int mode; + switch (run_mode) { + case "检测+分类+识别": + mode = 1; + break; + case "检测+识别": + mode = 2; + break; + case "识别+分类": + mode = 3; + break; + case "检测": + mode = 4; + break; + case "识别": + mode = 5; + break; + case "分类": + mode = 6; + break; + default: + mode = 1; + } + return mode; + } +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java new file mode 100644 index 0000000..c0bfd84 --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java @@ -0,0 +1,105 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.graphics.Bitmap; +import android.util.Log; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +public class OCRPredictorNative { + + private static final AtomicBoolean isSOLoaded = new AtomicBoolean(); + + public static void loadLibrary() throws RuntimeException { + if (!isSOLoaded.get() && isSOLoaded.compareAndSet(false, true)) { + try { + System.loadLibrary("Native"); + } catch (Throwable e) { + RuntimeException exception = new RuntimeException( + "Load libNative.so failed, please check it exists in apk file.", e); + throw exception; + } + } + } + + private Config config; + + private long nativePointer = 0; + + public OCRPredictorNative(Config config) { + this.config = config; + loadLibrary(); + nativePointer = init(config.detModelFilename, config.recModelFilename, config.clsModelFilename, config.useOpencl, + config.cpuThreadNum, config.cpuPower); + Log.i("OCRPredictorNative", "load success " + nativePointer); + + } + + + public ArrayList runImage(Bitmap originalImage, int max_size_len, int run_det, int run_cls, int run_rec) { + Log.i("OCRPredictorNative", "begin to run image "); + float[] rawResults = forward(nativePointer, originalImage, max_size_len, run_det, run_cls, run_rec); + ArrayList results = postprocess(rawResults); + return results; + } + + public static class Config { + public int useOpencl; + public int cpuThreadNum; + public String cpuPower; + public String detModelFilename; + public String recModelFilename; + public String clsModelFilename; + + } + + public void destroy() { + if (nativePointer != 0) { + release(nativePointer); + nativePointer = 0; + } + } + + protected native long init(String detModelPath, String recModelPath, String clsModelPath, int useOpencl, int threadNum, String cpuMode); + + protected native float[] forward(long pointer, Bitmap originalImage,int max_size_len, int run_det, int run_cls, int run_rec); + + protected native void release(long pointer); + + private ArrayList postprocess(float[] raw) { + ArrayList results = new ArrayList(); + int begin = 0; + + while (begin < raw.length) { + int point_num = Math.round(raw[begin]); + int word_num = Math.round(raw[begin + 1]); + OcrResultModel res = parse(raw, begin + 2, point_num, word_num); + begin += 2 + 1 + point_num * 2 + word_num + 2; + results.add(res); + } + + return results; + } + + private OcrResultModel parse(float[] raw, int begin, int pointNum, int wordNum) { + int current = begin; + OcrResultModel res = new OcrResultModel(); + res.setConfidence(raw[current]); + current++; + for (int i = 0; i < pointNum; i++) { + res.addPoints(Math.round(raw[current + i * 2]), Math.round(raw[current + i * 2 + 1])); + } + current += (pointNum * 2); + for (int i = 0; i < wordNum; i++) { + int index = Math.round(raw[current + i]); + res.addWordIndex(index); + } + current += wordNum; + res.setClsIdx(raw[current]); + res.setClsConfidence(raw[current + 1]); + Log.i("OCRPredictorNative", "word finished " + wordNum); + return res; + } + + +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java new file mode 100644 index 0000000..1bccbc7 --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java @@ -0,0 +1,79 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.graphics.Point; + +import java.util.ArrayList; +import java.util.List; + +public class OcrResultModel { + private List points; + private List wordIndex; + private String label; + private float confidence; + private float cls_idx; + private String cls_label; + private float cls_confidence; + + public OcrResultModel() { + super(); + points = new ArrayList<>(); + wordIndex = new ArrayList<>(); + } + + public void addPoints(int x, int y) { + Point point = new Point(x, y); + points.add(point); + } + + public void addWordIndex(int index) { + wordIndex.add(index); + } + + public List getPoints() { + return points; + } + + public List getWordIndex() { + return wordIndex; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public float getConfidence() { + return confidence; + } + + public void setConfidence(float confidence) { + this.confidence = confidence; + } + + public float getClsIdx() { + return cls_idx; + } + + public void setClsIdx(float idx) { + this.cls_idx = idx; + } + + public String getClsLabel() { + return cls_label; + } + + public void setClsLabel(String label) { + this.cls_label = label; + } + + public float getClsConfidence() { + return cls_confidence; + } + + public void setClsConfidence(float confidence) { + this.cls_confidence = confidence; + } +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java new file mode 100644 index 0000000..7bd6245 --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java @@ -0,0 +1,278 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.util.Log; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Vector; + +import static android.graphics.Color.*; + +public class Predictor { + private static final String TAG = Predictor.class.getSimpleName(); + public boolean isLoaded = false; + public int warmupIterNum = 1; + public int inferIterNum = 1; + public int cpuThreadNum = 4; + public String cpuPowerMode = "LITE_POWER_HIGH"; + public String modelPath = ""; + public String modelName = ""; + protected OCRPredictorNative paddlePredictor = null; + protected float inferenceTime = 0; + // Only for object detection + protected Vector wordLabels = new Vector(); + protected int detLongSize = 960; + protected float scoreThreshold = 0.1f; + protected Bitmap inputImage = null; + protected Bitmap outputImage = null; + protected volatile String outputResult = ""; + protected float postprocessTime = 0; + + + public Predictor() { + } + + public boolean init(Context appCtx, String modelPath, String labelPath, int useOpencl, int cpuThreadNum, String cpuPowerMode) { + isLoaded = loadModel(appCtx, modelPath, useOpencl, cpuThreadNum, cpuPowerMode); + if (!isLoaded) { + return false; + } + isLoaded = loadLabel(appCtx, labelPath); + return isLoaded; + } + + + public boolean init(Context appCtx, String modelPath, String labelPath, int useOpencl, int cpuThreadNum, String cpuPowerMode, + int detLongSize, float scoreThreshold) { + boolean isLoaded = init(appCtx, modelPath, labelPath, useOpencl, cpuThreadNum, cpuPowerMode); + if (!isLoaded) { + return false; + } + this.detLongSize = detLongSize; + this.scoreThreshold = scoreThreshold; + return true; + } + + protected boolean loadModel(Context appCtx, String modelPath, int useOpencl, int cpuThreadNum, String cpuPowerMode) { + // Release model if exists + releaseModel(); + + // Load model + if (modelPath.isEmpty()) { + return false; + } + String realPath = modelPath; + if (!modelPath.substring(0, 1).equals("/")) { + // Read model files from custom path if the first character of mode path is '/' + // otherwise copy model to cache from assets + realPath = appCtx.getCacheDir() + "/" + modelPath; + Utils.copyDirectoryFromAssets(appCtx, modelPath, realPath); + } + if (realPath.isEmpty()) { + return false; + } + + OCRPredictorNative.Config config = new OCRPredictorNative.Config(); + config.useOpencl = useOpencl; + config.cpuThreadNum = cpuThreadNum; + config.cpuPower = cpuPowerMode; + config.detModelFilename = realPath + File.separator + "det_db.nb"; + config.recModelFilename = realPath + File.separator + "rec_crnn.nb"; + config.clsModelFilename = realPath + File.separator + "cls.nb"; + Log.i("Predictor", "model path" + config.detModelFilename + " ; " + config.recModelFilename + ";" + config.clsModelFilename); + paddlePredictor = new OCRPredictorNative(config); + + this.cpuThreadNum = cpuThreadNum; + this.cpuPowerMode = cpuPowerMode; + this.modelPath = realPath; + this.modelName = realPath.substring(realPath.lastIndexOf("/") + 1); + return true; + } + + public void releaseModel() { + if (paddlePredictor != null) { + paddlePredictor.destroy(); + paddlePredictor = null; + } + isLoaded = false; + cpuThreadNum = 1; + cpuPowerMode = "LITE_POWER_HIGH"; + modelPath = ""; + modelName = ""; + } + + protected boolean loadLabel(Context appCtx, String labelPath) { + wordLabels.clear(); + wordLabels.add("black"); + // Load word labels from file + try { + InputStream assetsInputStream = appCtx.getAssets().open(labelPath); + int available = assetsInputStream.available(); + byte[] lines = new byte[available]; + assetsInputStream.read(lines); + assetsInputStream.close(); + String words = new String(lines); + String[] contents = words.split("\n"); + for (String content : contents) { + wordLabels.add(content); + } + wordLabels.add(" "); + Log.i(TAG, "Word label size: " + wordLabels.size()); + } catch (Exception e) { + Log.e(TAG, e.getMessage()); + return false; + } + return true; + } + + + public boolean runModel(int run_det, int run_cls, int run_rec) { + if (inputImage == null || !isLoaded()) { + return false; + } + + // Warm up + for (int i = 0; i < warmupIterNum; i++) { + paddlePredictor.runImage(inputImage, detLongSize, run_det, run_cls, run_rec); + } + warmupIterNum = 0; // do not need warm + // Run inference + Date start = new Date(); + ArrayList results = paddlePredictor.runImage(inputImage, detLongSize, run_det, run_cls, run_rec); + Date end = new Date(); + inferenceTime = (end.getTime() - start.getTime()) / (float) inferIterNum; + + results = postprocess(results); + Log.i(TAG, "[stat] Inference Time: " + inferenceTime + " ;Box Size " + results.size()); + drawResults(results); + + return true; + } + + public boolean isLoaded() { + return paddlePredictor != null && isLoaded; + } + + public String modelPath() { + return modelPath; + } + + public String modelName() { + return modelName; + } + + public int cpuThreadNum() { + return cpuThreadNum; + } + + public String cpuPowerMode() { + return cpuPowerMode; + } + + public float inferenceTime() { + return inferenceTime; + } + + public Bitmap inputImage() { + return inputImage; + } + + public Bitmap outputImage() { + return outputImage; + } + + public String outputResult() { + return outputResult; + } + + public float postprocessTime() { + return postprocessTime; + } + + + public void setInputImage(Bitmap image) { + if (image == null) { + return; + } + this.inputImage = image.copy(Bitmap.Config.ARGB_8888, true); + } + + private ArrayList postprocess(ArrayList results) { + for (OcrResultModel r : results) { + StringBuffer word = new StringBuffer(); + for (int index : r.getWordIndex()) { + if (index >= 0 && index < wordLabels.size()) { + word.append(wordLabels.get(index)); + } else { + Log.e(TAG, "Word index is not in label list:" + index); + word.append("×"); + } + } + r.setLabel(word.toString()); + r.setClsLabel(r.getClsIdx() == 1 ? "180" : "0"); + } + return results; + } + + private void drawResults(ArrayList results) { + StringBuffer outputResultSb = new StringBuffer(""); + for (int i = 0; i < results.size(); i++) { + OcrResultModel result = results.get(i); + StringBuilder sb = new StringBuilder(""); + if(result.getPoints().size()>0){ + sb.append("Det: "); + for (Point p : result.getPoints()) { + sb.append("(").append(p.x).append(",").append(p.y).append(") "); + } + } + if(result.getLabel().length() > 0){ + sb.append("\n Rec: ").append(result.getLabel()); + sb.append(",").append(result.getConfidence()); + } + if(result.getClsIdx()!=-1){ + sb.append(" Cls: ").append(result.getClsLabel()); + sb.append(",").append(result.getClsConfidence()); + } + Log.i(TAG, sb.toString()); // show LOG in Logcat panel + outputResultSb.append(i + 1).append(": ").append(sb.toString()).append("\n"); + } + outputResult = outputResultSb.toString(); + outputImage = inputImage; + Canvas canvas = new Canvas(outputImage); + Paint paintFillAlpha = new Paint(); + paintFillAlpha.setStyle(Paint.Style.FILL); + paintFillAlpha.setColor(Color.parseColor("#3B85F5")); + paintFillAlpha.setAlpha(50); + + Paint paint = new Paint(); + paint.setColor(Color.parseColor("#3B85F5")); + paint.setStrokeWidth(5); + paint.setStyle(Paint.Style.STROKE); + + for (OcrResultModel result : results) { + Path path = new Path(); + List points = result.getPoints(); + if(points.size()==0){ + continue; + } + path.moveTo(points.get(0).x, points.get(0).y); + for (int i = points.size() - 1; i >= 0; i--) { + Point p = points.get(i); + path.lineTo(p.x, p.y); + } + canvas.drawPath(path, paint); + canvas.drawPath(path, paintFillAlpha); + } + } + +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java new file mode 100644 index 0000000..477cd5d --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java @@ -0,0 +1,172 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.ListPreference; + +import androidx.appcompat.app.ActionBar; + +import java.util.ArrayList; +import java.util.List; + + +public class SettingsActivity extends AppCompatPreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener { + ListPreference lpChoosePreInstalledModel = null; + CheckBoxPreference cbEnableCustomSettings = null; + EditTextPreference etModelPath = null; + EditTextPreference etLabelPath = null; + ListPreference etImagePath = null; + ListPreference lpCPUThreadNum = null; + ListPreference lpCPUPowerMode = null; + EditTextPreference etDetLongSize = null; + EditTextPreference etScoreThreshold = null; + + List preInstalledModelPaths = null; + List preInstalledLabelPaths = null; + List preInstalledImagePaths = null; + List preInstalledDetLongSizes = null; + List preInstalledCPUThreadNums = null; + List preInstalledCPUPowerModes = null; + List preInstalledInputColorFormats = null; + List preInstalledInputMeans = null; + List preInstalledInputStds = null; + List preInstalledScoreThresholds = null; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.settings); + ActionBar supportActionBar = getSupportActionBar(); + if (supportActionBar != null) { + supportActionBar.setDisplayHomeAsUpEnabled(true); + } + + // Initialized pre-installed models + preInstalledModelPaths = new ArrayList(); + preInstalledLabelPaths = new ArrayList(); + preInstalledImagePaths = new ArrayList(); + preInstalledDetLongSizes = new ArrayList(); + preInstalledCPUThreadNums = new ArrayList(); + preInstalledCPUPowerModes = new ArrayList(); + preInstalledInputColorFormats = new ArrayList(); + preInstalledInputMeans = new ArrayList(); + preInstalledInputStds = new ArrayList(); + preInstalledScoreThresholds = new ArrayList(); + // Add ssd_mobilenet_v1_pascalvoc_for_cpu + preInstalledModelPaths.add(getString(R.string.MODEL_PATH_DEFAULT)); + preInstalledLabelPaths.add(getString(R.string.LABEL_PATH_DEFAULT)); + preInstalledImagePaths.add(getString(R.string.IMAGE_PATH_DEFAULT)); + preInstalledCPUThreadNums.add(getString(R.string.CPU_THREAD_NUM_DEFAULT)); + preInstalledCPUPowerModes.add(getString(R.string.CPU_POWER_MODE_DEFAULT)); + preInstalledDetLongSizes.add(getString(R.string.DET_LONG_SIZE_DEFAULT)); + preInstalledScoreThresholds.add(getString(R.string.SCORE_THRESHOLD_DEFAULT)); + + // Setup UI components + lpChoosePreInstalledModel = + (ListPreference) findPreference(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY)); + String[] preInstalledModelNames = new String[preInstalledModelPaths.size()]; + for (int i = 0; i < preInstalledModelPaths.size(); i++) { + preInstalledModelNames[i] = + preInstalledModelPaths.get(i).substring(preInstalledModelPaths.get(i).lastIndexOf("/") + 1); + } + lpChoosePreInstalledModel.setEntries(preInstalledModelNames); + lpChoosePreInstalledModel.setEntryValues(preInstalledModelPaths.toArray(new String[preInstalledModelPaths.size()])); + cbEnableCustomSettings = + (CheckBoxPreference) findPreference(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY)); + etModelPath = (EditTextPreference) findPreference(getString(R.string.MODEL_PATH_KEY)); + etModelPath.setTitle("Model Path (SDCard: " + Utils.getSDCardDirectory() + ")"); + etLabelPath = (EditTextPreference) findPreference(getString(R.string.LABEL_PATH_KEY)); + etImagePath = (ListPreference) findPreference(getString(R.string.IMAGE_PATH_KEY)); + lpCPUThreadNum = + (ListPreference) findPreference(getString(R.string.CPU_THREAD_NUM_KEY)); + lpCPUPowerMode = + (ListPreference) findPreference(getString(R.string.CPU_POWER_MODE_KEY)); + etDetLongSize = (EditTextPreference) findPreference(getString(R.string.DET_LONG_SIZE_KEY)); + etScoreThreshold = (EditTextPreference) findPreference(getString(R.string.SCORE_THRESHOLD_KEY)); + } + + private void reloadPreferenceAndUpdateUI() { + SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); + boolean enableCustomSettings = + sharedPreferences.getBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false); + String modelPath = sharedPreferences.getString(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY), + getString(R.string.MODEL_PATH_DEFAULT)); + int modelIdx = lpChoosePreInstalledModel.findIndexOfValue(modelPath); + if (modelIdx >= 0 && modelIdx < preInstalledModelPaths.size()) { + if (!enableCustomSettings) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(getString(R.string.MODEL_PATH_KEY), preInstalledModelPaths.get(modelIdx)); + editor.putString(getString(R.string.LABEL_PATH_KEY), preInstalledLabelPaths.get(modelIdx)); + editor.putString(getString(R.string.IMAGE_PATH_KEY), preInstalledImagePaths.get(modelIdx)); + editor.putString(getString(R.string.CPU_THREAD_NUM_KEY), preInstalledCPUThreadNums.get(modelIdx)); + editor.putString(getString(R.string.CPU_POWER_MODE_KEY), preInstalledCPUPowerModes.get(modelIdx)); + editor.putString(getString(R.string.DET_LONG_SIZE_KEY), preInstalledDetLongSizes.get(modelIdx)); + editor.putString(getString(R.string.SCORE_THRESHOLD_KEY), + preInstalledScoreThresholds.get(modelIdx)); + editor.apply(); + } + lpChoosePreInstalledModel.setSummary(modelPath); + } + cbEnableCustomSettings.setChecked(enableCustomSettings); + etModelPath.setEnabled(enableCustomSettings); + etLabelPath.setEnabled(enableCustomSettings); + etImagePath.setEnabled(enableCustomSettings); + lpCPUThreadNum.setEnabled(enableCustomSettings); + lpCPUPowerMode.setEnabled(enableCustomSettings); + etDetLongSize.setEnabled(enableCustomSettings); + etScoreThreshold.setEnabled(enableCustomSettings); + modelPath = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY), + getString(R.string.MODEL_PATH_DEFAULT)); + String labelPath = sharedPreferences.getString(getString(R.string.LABEL_PATH_KEY), + getString(R.string.LABEL_PATH_DEFAULT)); + String imagePath = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY), + getString(R.string.IMAGE_PATH_DEFAULT)); + String cpuThreadNum = sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY), + getString(R.string.CPU_THREAD_NUM_DEFAULT)); + String cpuPowerMode = sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY), + getString(R.string.CPU_POWER_MODE_DEFAULT)); + String detLongSize = sharedPreferences.getString(getString(R.string.DET_LONG_SIZE_KEY), + getString(R.string.DET_LONG_SIZE_DEFAULT)); + String scoreThreshold = sharedPreferences.getString(getString(R.string.SCORE_THRESHOLD_KEY), + getString(R.string.SCORE_THRESHOLD_DEFAULT)); + etModelPath.setSummary(modelPath); + etModelPath.setText(modelPath); + etLabelPath.setSummary(labelPath); + etLabelPath.setText(labelPath); + etImagePath.setSummary(imagePath); + etImagePath.setValue(imagePath); + lpCPUThreadNum.setValue(cpuThreadNum); + lpCPUThreadNum.setSummary(cpuThreadNum); + lpCPUPowerMode.setValue(cpuPowerMode); + lpCPUPowerMode.setSummary(cpuPowerMode); + etDetLongSize.setSummary(detLongSize); + etDetLongSize.setText(detLongSize); + etScoreThreshold.setText(scoreThreshold); + etScoreThreshold.setSummary(scoreThreshold); + } + + @Override + protected void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + reloadPreferenceAndUpdateUI(); + } + + @Override + protected void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY))) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false); + editor.commit(); + } + reloadPreferenceAndUpdateUI(); + } +} diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java new file mode 100644 index 0000000..ef46805 --- /dev/null +++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java @@ -0,0 +1,159 @@ +package com.baidu.paddle.lite.demo.ocr; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.media.ExifInterface; +import android.os.Environment; + +import java.io.*; + +public class Utils { + private static final String TAG = Utils.class.getSimpleName(); + + public static void copyFileFromAssets(Context appCtx, String srcPath, String dstPath) { + if (srcPath.isEmpty() || dstPath.isEmpty()) { + return; + } + InputStream is = null; + OutputStream os = null; + try { + is = new BufferedInputStream(appCtx.getAssets().open(srcPath)); + os = new BufferedOutputStream(new FileOutputStream(new File(dstPath))); + byte[] buffer = new byte[1024]; + int length = 0; + while ((length = is.read(buffer)) != -1) { + os.write(buffer, 0, length); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + os.close(); + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static void copyDirectoryFromAssets(Context appCtx, String srcDir, String dstDir) { + if (srcDir.isEmpty() || dstDir.isEmpty()) { + return; + } + try { + if (!new File(dstDir).exists()) { + new File(dstDir).mkdirs(); + } + for (String fileName : appCtx.getAssets().list(srcDir)) { + String srcSubPath = srcDir + File.separator + fileName; + String dstSubPath = dstDir + File.separator + fileName; + if (new File(srcSubPath).isDirectory()) { + copyDirectoryFromAssets(appCtx, srcSubPath, dstSubPath); + } else { + copyFileFromAssets(appCtx, srcSubPath, dstSubPath); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static float[] parseFloatsFromString(String string, String delimiter) { + String[] pieces = string.trim().toLowerCase().split(delimiter); + float[] floats = new float[pieces.length]; + for (int i = 0; i < pieces.length; i++) { + floats[i] = Float.parseFloat(pieces[i].trim()); + } + return floats; + } + + public static long[] parseLongsFromString(String string, String delimiter) { + String[] pieces = string.trim().toLowerCase().split(delimiter); + long[] longs = new long[pieces.length]; + for (int i = 0; i < pieces.length; i++) { + longs[i] = Long.parseLong(pieces[i].trim()); + } + return longs; + } + + public static String getSDCardDirectory() { + return Environment.getExternalStorageDirectory().getAbsolutePath(); + } + + public static boolean isSupportedNPU() { + return false; + // String hardware = android.os.Build.HARDWARE; + // return hardware.equalsIgnoreCase("kirin810") || hardware.equalsIgnoreCase("kirin990"); + } + + public static Bitmap resizeWithStep(Bitmap bitmap, int maxLength, int step) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + int maxWH = Math.max(width, height); + float ratio = 1; + int newWidth = width; + int newHeight = height; + if (maxWH > maxLength) { + ratio = maxLength * 1.0f / maxWH; + newWidth = (int) Math.floor(ratio * width); + newHeight = (int) Math.floor(ratio * height); + } + + newWidth = newWidth - newWidth % step; + if (newWidth == 0) { + newWidth = step; + } + newHeight = newHeight - newHeight % step; + if (newHeight == 0) { + newHeight = step; + } + return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); + } + + public static Bitmap rotateBitmap(Bitmap bitmap, int orientation) { + + Matrix matrix = new Matrix(); + switch (orientation) { + case ExifInterface.ORIENTATION_NORMAL: + return bitmap; + case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: + matrix.setScale(-1, 1); + break; + case ExifInterface.ORIENTATION_ROTATE_180: + matrix.setRotate(180); + break; + case ExifInterface.ORIENTATION_FLIP_VERTICAL: + matrix.setRotate(180); + matrix.postScale(-1, 1); + break; + case ExifInterface.ORIENTATION_TRANSPOSE: + matrix.setRotate(90); + matrix.postScale(-1, 1); + break; + case ExifInterface.ORIENTATION_ROTATE_90: + matrix.setRotate(90); + break; + case ExifInterface.ORIENTATION_TRANSVERSE: + matrix.setRotate(-90); + matrix.postScale(-1, 1); + break; + case ExifInterface.ORIENTATION_ROTATE_270: + matrix.setRotate(-90); + break; + default: + return bitmap; + } + try { + Bitmap bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + bitmap.recycle(); + return bmRotated; + } + catch (OutOfMemoryError e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml b/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deploy/android_demo/app/src/main/res/layout/activity_main.xml b/deploy/android_demo/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..e90c99a --- /dev/null +++ b/deploy/android_demo/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + +

Initializing...
+ +
+
+
+

Visualization

+ +
+
+ +
+
+

Metrics

+
No runs yet
+
+
+

Recognition results

+
    +
    +
    +
    + + + + + diff --git a/paddleocr-js/apps/demo/package.json b/paddleocr-js/apps/demo/package.json new file mode 100644 index 0000000..ef75dd2 --- /dev/null +++ b/paddleocr-js/apps/demo/package.json @@ -0,0 +1,28 @@ +{ + "name": "demo", + "private": true, + "version": "0.1.0", + "type": "module", + "keywords": [ + "paddleocr", + "ocr-demo", + "browser-ocr", + "vite" + ], + "engines": { + "node": ">=20.11" + }, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@paddleocr/paddleocr-js": "*" + }, + "devDependencies": { + "typescript": "^5.9.3", + "vite": "^6.4.1" + } +} diff --git a/paddleocr-js/apps/demo/src/main.ts b/paddleocr-js/apps/demo/src/main.ts new file mode 100644 index 0000000..69b6247 --- /dev/null +++ b/paddleocr-js/apps/demo/src/main.ts @@ -0,0 +1,254 @@ +import { PaddleOCR } from "@paddleocr/paddleocr-js"; +import type { OcrResult, OcrResultItem } from "@paddleocr/paddleocr-js"; +import { OcrVisualizer } from "@paddleocr/paddleocr-js/viz"; + +type OcrEngine = Awaited>; + +const ORT_WASM_PATHS = "https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/"; +const DEFAULT_RUNTIME_PARAMS = Object.freeze({ + textDetThresh: 0.3, + textDetBoxThresh: 0.6, + textDetUnclipRatio: 1.5, + textRecScoreThresh: 0.1 +}); + +function getDemoThreadCount(): number { + return self.crossOriginIsolated + ? Math.min(4, Math.max(1, (navigator.hardwareConcurrency || 2) - 1)) + : 1; +} + +const ui = { + modelPreset: document.getElementById("modelPreset") as HTMLSelectElement, + runtimeBackend: document.getElementById("runtimeBackend") as HTMLSelectElement, + detThresh: document.getElementById("detThresh") as HTMLInputElement, + boxThresh: document.getElementById("boxThresh") as HTMLInputElement, + unclipRatio: document.getElementById("unclipRatio") as HTMLInputElement, + recScoreThresh: document.getElementById("recScoreThresh") as HTMLInputElement, + imageInput: document.getElementById("imageInput") as HTMLInputElement, + chooseImageBtn: document.getElementById("chooseImageBtn") as HTMLButtonElement, + reinitializeBtn: document.getElementById("reinitializeBtn") as HTMLButtonElement, + runBtn: document.getElementById("runBtn") as HTMLButtonElement, + status: document.getElementById("status") as HTMLElement, + metrics: document.getElementById("metrics") as HTMLPreElement, + results: document.getElementById("results") as HTMLOListElement, + vizImage: document.getElementById("vizImage") as HTMLImageElement +}; + +interface AppState { + imageFile: File | null; + previewBitmap: ImageBitmap | null; + lastResult: OcrResult | null; + ocr: OcrEngine | null; + ocrReady: boolean; + vizObjectUrl: string | null; +} + +const state: AppState = { + imageFile: null, + previewBitmap: null, + lastResult: null, + ocr: null, + ocrReady: false, + vizObjectUrl: null +}; + +function updateRunButtonState(): void { + ui.runBtn.disabled = !state.imageFile || !state.ocrReady; +} + +const visualizer = new OcrVisualizer({ + font: { + family: "PingFang SC", + source: + "https://paddle-model-ecology.bj.bcebos.com/paddlex/PaddleX3.0/fonts/PingFang-SC-Regular.ttf" + } +}); + +function setStatus(text: string, isError = false): void { + ui.status.textContent = text; + ui.status.style.color = isError ? "#b91c1c" : ""; +} + +function formatMs(value: number): string { + return `${value.toFixed(1)} ms`; +} + +function showVizImage(blob: Blob): void { + if (state.vizObjectUrl) { + URL.revokeObjectURL(state.vizObjectUrl); + } + state.vizObjectUrl = URL.createObjectURL(blob); + ui.vizImage.src = state.vizObjectUrl; + ui.vizImage.hidden = false; +} + +function showPreviewImage(bitmap: ImageBitmap): void { + // For pre-OCR preview, draw to an offscreen canvas and display as image + const canvas = document.createElement("canvas"); + canvas.width = bitmap.width; + canvas.height = bitmap.height; + const ctx = canvas.getContext("2d"); + if (!ctx) return; + ctx.drawImage(bitmap, 0, 0); + canvas.toBlob((blob) => { + if (blob) showVizImage(blob); + }); +} + +function renderResults(items: OcrResultItem[]): void { + ui.results.innerHTML = ""; + items.forEach((item) => { + const li = document.createElement("li"); + li.textContent = `${item.text} | score=${item.score.toFixed(3)}`; + ui.results.appendChild(li); + }); +} + +function getRuntimeOptions() { + return { + backend: ui.runtimeBackend.value as "auto" | "webgpu" | "wasm", + wasmPaths: ORT_WASM_PATHS, + numThreads: getDemoThreadCount(), + simd: true + }; +} + +async function initializeOcrEngine(): Promise { + state.ocrReady = false; + updateRunButtonState(); + + if (state.ocr) { + await state.ocr.dispose(); + } + + const preset = ui.modelPreset.value; + + state.ocr = await PaddleOCR.create({ + initialize: false, + worker: false, + textDetectionModelName: `${preset}_det`, + textRecognitionModelName: `${preset}_rec`, + ortOptions: getRuntimeOptions() + }); + + const summary = await state.ocr.initialize(); + state.ocrReady = true; + ui.metrics.textContent = [ + `model: ${preset}`, + `initialize: ${formatMs(summary.elapsedMs)}`, + `backend(requested): ${summary.backend}`, + `webgpu available: ${summary.webgpuAvailable ? "yes" : "no"}`, + `provider(det): ${summary.detProvider}`, + `provider(rec): ${summary.recProvider}`, + `assets: ${String(summary.assets.length)}` + ].join("\n"); + updateRunButtonState(); +} + +async function handleImageSelection(file: File | undefined): Promise { + if (!file) return; + state.imageFile = file; + state.previewBitmap?.close(); + state.previewBitmap = await createImageBitmap(file); + showPreviewImage(state.previewBitmap); + updateRunButtonState(); + setStatus(`Image selected: ${file.name}`); +} + +async function runOcr(): Promise { + if (!state.ocrReady || !state.ocr || !state.imageFile) { + setStatus("Wait for OCR engine initialization to finish, then choose an image.", true); + return; + } + + try { + setStatus("Running OCR..."); + const result: OcrResult = ( + await state.ocr.predict(state.imageFile, { + textDetThresh: Number(ui.detThresh.value), + textDetBoxThresh: Number(ui.boxThresh.value), + textDetUnclipRatio: Number(ui.unclipRatio.value), + textRecScoreThresh: Number(ui.recScoreThresh.value) + }) + )[0]; + + if (!state.previewBitmap) { + state.previewBitmap = await createImageBitmap(state.imageFile); + } + + // Render side-by-side visualization using viz module + const blob = await visualizer.toBlob(state.previewBitmap, result); + showVizImage(blob); + + renderResults(result.items); + state.lastResult = result; + ui.metrics.textContent = [ + ui.metrics.textContent, + "", + `det: ${formatMs(result.metrics.detMs)}`, + `rec: ${formatMs(result.metrics.recMs)}`, + `total: ${formatMs(result.metrics.totalMs)}`, + `detected boxes: ${String(result.metrics.detectedBoxes)}`, + `recognized lines: ${String(result.metrics.recognizedCount)}` + ].join("\n"); + setStatus(`OCR complete: ${String(result.metrics.recognizedCount)} text lines recognized.`); + } catch (err: unknown) { + console.error(err); + const message = err instanceof Error ? err.message : String(err); + setStatus(`OCR failed: ${message}`, true); + } +} + +ui.imageInput.addEventListener("change", (event: Event) => { + const target = event.target as HTMLInputElement; + void handleImageSelection(target.files?.[0]); +}); + +ui.chooseImageBtn.addEventListener("click", () => { + ui.imageInput.click(); +}); + +async function initialize(): Promise { + try { + ui.reinitializeBtn.disabled = true; + state.ocrReady = false; + updateRunButtonState(); + + setStatus("Initializing..."); + await initializeOcrEngine(); + + setStatus("Loading visualization font..."); + try { + await visualizer.loadFont(); + } catch (fontErr: unknown) { + console.warn("Font load failed, using system font:", fontErr); + setStatus("Ready (visualization will use system font)."); + updateRunButtonState(); + return; + } + + setStatus("Ready."); + updateRunButtonState(); + } catch (err: unknown) { + console.error(err); + const message = err instanceof Error ? err.message : String(err); + setStatus(`Initialization failed: ${message}`, true); + state.ocrReady = false; + updateRunButtonState(); + } finally { + ui.reinitializeBtn.disabled = false; + } +} + +ui.detThresh.value = String(DEFAULT_RUNTIME_PARAMS.textDetThresh); +ui.boxThresh.value = String(DEFAULT_RUNTIME_PARAMS.textDetBoxThresh); +ui.unclipRatio.value = String(DEFAULT_RUNTIME_PARAMS.textDetUnclipRatio); +ui.recScoreThresh.value = String(DEFAULT_RUNTIME_PARAMS.textRecScoreThresh); +ui.reinitializeBtn.addEventListener("click", () => void initialize()); +ui.modelPreset.addEventListener("change", () => void initialize()); +ui.runtimeBackend.addEventListener("change", () => void initialize()); + +ui.runBtn.addEventListener("click", () => void runOcr()); + +void initialize(); diff --git a/paddleocr-js/apps/demo/tsconfig.json b/paddleocr-js/apps/demo/tsconfig.json new file mode 100644 index 0000000..b7e5673 --- /dev/null +++ b/paddleocr-js/apps/demo/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "baseUrl": ".", + "paths": { + "@paddleocr/paddleocr-js": ["../../packages/core/src/index.ts"], + "@paddleocr/paddleocr-js/viz": ["../../packages/core/src/viz/index.ts"] + } + }, + "include": ["src", "../../packages/core/src/types/*.d.ts"] +} diff --git a/paddleocr-js/apps/demo/vite.config.js b/paddleocr-js/apps/demo/vite.config.js new file mode 100644 index 0000000..fecc3e2 --- /dev/null +++ b/paddleocr-js/apps/demo/vite.config.js @@ -0,0 +1,32 @@ +import { resolve } from "node:path"; +import { defineConfig } from "vite"; + +export default defineConfig(({ command }) => ({ + resolve: { + alias: + command === "serve" + ? { + "@paddleocr/paddleocr-js/viz": resolve( + __dirname, + "../../packages/core/src/viz/index.ts" + ), + "@paddleocr/paddleocr-js": resolve(__dirname, "../../packages/core/src/index.ts") + } + : {} + }, + worker: { + format: "es" + }, + server: { + headers: { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "credentialless" + } + }, + preview: { + headers: { + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "credentialless" + } + } +})); diff --git a/paddleocr-js/docs/architecture.md b/paddleocr-js/docs/architecture.md new file mode 100644 index 0000000..dc7560d --- /dev/null +++ b/paddleocr-js/docs/architecture.md @@ -0,0 +1,82 @@ +# Architecture + +English | [简体中文](architecture_cn.md) + +## Project structure + +The `paddleocr-js` folder has two main parts: + +- `packages/core`: the browser PaddleOCR SDK (published on npm as `@paddleocr/paddleocr-js`) +- `apps/demo`: a demo application for PP-OCR that consumes the SDK + +## SDK package layout (`packages/core`) + +``` +src/ +├── runtime/ — inference runtime setup +├── resources/ — model & asset management +├── models/ — model wiring +├── platform/ — browser/worker input adaptation +├── worker/ — worker transport layer +├── pipelines/ — pipeline implementations +├── viz/ — visualization (optional) +├── types/ — external type declarations +└── utils/ — shared utilities +``` + +The current high-level pipeline entry point is `PaddleOCR.create()`. It coordinates: + +1. runtime initialization +2. execution backend selection +3. model download +4. inference session creation +5. OCR pipeline execution + +## Worker execution model + +`PaddleOCR.create()` supports 2 execution modes: + +- main-thread mode: returns `PaddleOCR`, which runs OCR directly on the calling thread +- worker-backed mode: returns `WorkerBackedPaddleOCR`, which forwards OCR lifecycle calls to a dedicated worker + +The runtime flow for worker mode is: + +1. `PaddleOCR.create({ worker: true })` resolves OCR options and creates a `WorkerBackedPaddleOCR` +2. `WorkerBackedPaddleOCR` sends `init/predict/dispose` requests through `WorkerTransportClient` +3. the OCR pipeline layer owns the default worker factory and points it at `src/pipelines/ocr/worker-entry.ts` +4. `src/pipelines/ocr/worker-entry.ts` binds the generic worker bootstrap in `src/worker/entry.ts` to the OCR-specific worker handler +5. `OcrPipelineRunner` runs OpenCV.js, ONNX Runtime Web, model loading, detection, and recognition inside the worker +6. results and errors are serialized back to the main thread + +Input handling is split by environment: + +- main thread: browser inputs are normalized into transferable payloads +- worker: payloads are reconstructed into runtime inputs such as `cv.Mat` + +Worker mode uses the package worker path and explicitly disables ONNX Runtime Web wasm proxy internally. This avoids stacking two worker layers and keeps the package responsible for the concurrency model. + +ONNX Runtime Web requires WASM binaries at runtime. `ortOptions.wasmPaths` is a unified configuration that applies to both execution modes — setting it once controls where WASM is loaded in both main-thread and worker contexts: + +```ts +PaddleOCR.create({ + ortOptions: { wasmPaths: "/assets/" } +}); +``` + +When `wasmPaths` is set, both modes fetch WASM from the specified path. When it is not set, each mode falls back differently: + +- main-thread mode: ORT resolves WASM through the consumer's bundler (the bundler copies `.wasm` files from `node_modules/onnxruntime-web/dist/` into the build output and rewrites the URLs automatically) +- worker mode: the SDK falls back to a CDN URL pinned to the ORT version installed at SDK build time, and emits a console warning recommending the consumer set `ortOptions.wasmPaths` + +Setting `ortOptions.wasmPaths` explicitly is recommended for worker mode to ensure version consistency between the two modes. + +## Application responsibilities + +The SDK owns OCR runtime setup and inference orchestration. The host application still owns: + +- deployment headers required by the runtime environment +- static asset hosting and model URL configuration +- worker-capable bundler/runtime support when `worker: true` is used +- application UI, status messaging, and visualization + +Here, the `apps/` directory contains such host applications. diff --git a/paddleocr-js/docs/architecture_cn.md b/paddleocr-js/docs/architecture_cn.md new file mode 100644 index 0000000..f14bdd8 --- /dev/null +++ b/paddleocr-js/docs/architecture_cn.md @@ -0,0 +1,82 @@ +# 架构说明 + +[English](architecture.md) | 简体中文 + +## 项目结构 + +`paddleocr-js` 目录下主要有两部分: + +- `packages/core`:浏览器 PaddleOCR SDK,发布到 npm 时包名为 `@paddleocr/paddleocr-js` +- `apps/demo`:依赖该 SDK 的 PP-OCR 演示应用 + +## SDK 包布局(`packages/core`) + +``` +src/ +├── runtime/ — 推理运行时初始化 +├── resources/ — 模型与资源管理 +├── models/ — 模型接线 +├── platform/ — 浏览器 / Worker 输入适配 +├── worker/ — Worker 传输层 +├── pipelines/ — 产线实现 +├── viz/ — 可视化(可选) +├── types/ — 外部库类型声明 +└── utils/ — 共享工具 +``` + +当前高层产线入口为 `PaddleOCR.create()`,它负责协调: + +1. 运行时初始化 +2. 执行后端选择 +3. 模型下载 +4. 推理会话创建 +5. OCR 产线执行 + +## Worker 执行模型 + +`PaddleOCR.create()` 支持两种执行模式: + +- 主线程模式:返回 `PaddleOCR`,直接在调用线程上执行 OCR +- Worker 模式:返回 `WorkerBackedPaddleOCR`,将 OCR 生命周期调用转发到独立 Worker + +Worker 模式下的运行流程如下: + +1. `PaddleOCR.create({ worker: true })` 解析 OCR 选项并创建 `WorkerBackedPaddleOCR` +2. `WorkerBackedPaddleOCR` 通过 `WorkerTransportClient` 发送 `init` / `predict` / `dispose` 请求 +3. OCR 产线层持有默认 Worker 工厂,并将其指向 `src/pipelines/ocr/worker-entry.ts` +4. `src/pipelines/ocr/worker-entry.ts` 将 `src/worker/entry.ts` 中的通用 Worker 引导逻辑与 OCR 专用处理逻辑绑定 +5. `OcrPipelineRunner` 在 Worker 内运行 OpenCV.js、ONNX Runtime Web、模型加载、检测与识别 +6. 结果和错误会被序列化后传回主线程 + +输入处理按环境拆分: + +- 主线程:将浏览器输入标准化为可传输的负载 +- Worker:将负载还原为 `cv.Mat` 等运行时输入 + +Worker 模式使用包内 Worker 路径,并在内部显式关闭 ONNX Runtime Web 的 wasm proxy。这样可以避免双层 Worker 叠加,并让包本身负责并发模型。 + +ONNX Runtime Web 在运行时需要 WASM 二进制。`ortOptions.wasmPaths` 对两种执行模式统一生效,设置一次即可控制主线程和 Worker 两侧的 WASM 加载位置: + +```ts +PaddleOCR.create({ + ortOptions: { wasmPaths: "/assets/" } +}); +``` + +设置了 `wasmPaths` 时,两种模式都会从指定路径拉取 WASM。未设置时,两种模式的回退行为不同: + +- 主线程模式:ORT 通过使用方的打包工具解析 WASM(通常由打包工具把 `node_modules/onnxruntime-web/dist/` 下的 `.wasm` 文件拷贝到构建产物并自动改写 URL) +- Worker 模式:SDK 会回退到与构建时安装的 ORT 版本绑定的 CDN,并在控制台提示建议显式设置 `ortOptions.wasmPaths` + +因此,在 Worker 模式下建议显式设置 `ortOptions.wasmPaths`,以保证两种模式使用同一套 WASM 版本。 + +## 应用侧职责 + +SDK 负责 OCR 运行时初始化与推理编排;宿主应用仍需负责: + +- 运行环境所需的部署响应头 +- 静态资源托管与模型 URL 配置 +- `worker: true` 场景下支持产出并加载 Worker 的打包工具或运行时 +- 应用界面、状态提示与可视化 + +这里的 `apps/` 目录就承载了这类宿主应用。 diff --git a/paddleocr-js/docs/development.md b/paddleocr-js/docs/development.md new file mode 100644 index 0000000..aa96aab --- /dev/null +++ b/paddleocr-js/docs/development.md @@ -0,0 +1,66 @@ +# Development + +English | [简体中文](development_cn.md) + +## Install + +```bash +npm install +``` + +## Common commands + +Commands from the `paddleocr-js/` root: + +```bash +npm run build # build SDK then demo (explicit topological order) +npm run build:sdk # build only the SDK (packages/core) +npm run build:demo # build only the demo app (apps/demo) +npm run lint +npm run test +npm run typecheck # typecheck all workspaces (core + demo) +npm run check # format:check → lint → build:sdk → typecheck → test → build:demo +npm run clean # remove all dist/ directories +``` + +Demo app (Vite dev server): + +```bash +npm run dev:demo +``` + +Single-workspace examples: + +```bash +npm run build --workspace packages/core +npm run build --workspace apps/demo +``` + +## TypeScript + +Both the SDK (`packages/core`) and the demo app (`apps/demo`) are written in TypeScript with strict mode enabled. ESLint uses `typescript-eslint` with `strictTypeChecked` for source files under `packages/**/src/` and `apps/**/src/`. Test files under `packages/**/test/` use the lighter `recommendedTypeChecked` preset with relaxed rules (e.g. `no-unsafe-*` and `no-explicit-any` are disabled). + +`npm run typecheck` runs `tsc --noEmit` across all workspaces. The demo typechecks directly against the SDK's source using `paths` mapping in its `tsconfig.json`, so it does not strictly require `build:sdk` to run first for typechecking. + +## Build + +The SDK builds with Vite library mode (`npm run build` in `packages/core`). Output in `dist/`: + +- `index.mjs` — ESM entry +- `index.d.ts` — type declarations +- `viz.mjs` — ESM (viz subpath) +- `assets/worker-entry-*.js` — self-contained worker bundle (OpenCV.js + ORT JS runtime) + +A custom Vite plugin (`libraryWorkerPlugin`) post-processes the build output for npm compatibility: + +1. Rewrites absolute worker asset paths to relative, so the file resolves from the SDK module's location rather than the web origin. +2. Splits the `new Worker(new URL(STRING, import.meta.url))` pattern into a URL variable + Worker construction. This lets downstream bundlers' asset-URL plugins copy the worker file, while preventing their worker-detection plugins from trying to re-bundle it. +3. Strips base64-encoded WASM binaries that Vite inlines into the worker asset. In worker mode, ORT loads WASM at runtime via `ort.env.wasm.wasmPaths` (set by the consumer, or falling back to a CDN URL pinned to the installed ORT version). This significantly reduces the size of the worker file. + +The demo app uses a Vite alias during development (`npm run dev`) to build directly from core's TypeScript source, enabling instant HMR. During production builds (`npm run build`), it consumes the SDK's pre-built `dist/` via workspace linking — the downstream-compatible worker URL pattern allows Vite to correctly copy the worker asset into the demo's output. + +## Testing strategy + +- unit tests for config parsing and registry behavior +- lightweight jsdom checks for browser platform helpers +- no large real-model inference in CI by default diff --git a/paddleocr-js/docs/development_cn.md b/paddleocr-js/docs/development_cn.md new file mode 100644 index 0000000..be2da6e --- /dev/null +++ b/paddleocr-js/docs/development_cn.md @@ -0,0 +1,66 @@ +# 开发指南 + +[English](development.md) | 简体中文 + +## 安装依赖 + +```bash +npm install +``` + +## 常用命令 + +在 `paddleocr-js/` 根目录执行: + +```bash +npm run build # 先构建 SDK 再构建 demo(显式拓扑顺序) +npm run build:sdk # 仅构建 SDK(packages/core) +npm run build:demo # 仅构建 demo 应用(apps/demo) +npm run lint +npm run test +npm run typecheck # 对所有 workspace 执行类型检查(core + demo) +npm run check # format:check → lint → build:sdk → typecheck → test → build:demo +npm run clean # 删除所有 dist/ 目录 +``` + +Demo 应用(Vite 开发服务器): + +```bash +npm run dev:demo +``` + +单个 workspace 的示例: + +```bash +npm run build --workspace packages/core +npm run build --workspace apps/demo +``` + +## TypeScript + +SDK(`packages/core`)与 demo 应用(`apps/demo`)都使用 TypeScript 严格模式。`packages/**/src/` 与 `apps/**/src/` 下的源码使用 `typescript-eslint` 的 `strictTypeChecked` 规则集;`packages/**/test/` 下的测试使用较轻的 `recommendedTypeChecked`,并放宽了部分规则(例如 `no-unsafe-*` 与 `no-explicit-any`)。 + +`npm run typecheck` 会在所有 workspace 上执行 `tsc --noEmit`。demo 在 `tsconfig.json` 里通过 `paths` 直接引用 SDK 源码,因此类型检查并不严格依赖先执行 `build:sdk`。 + +## 构建 + +SDK 使用 Vite 的库模式构建(在 `packages/core` 中执行 `npm run build`)。`dist/` 下的构建产物包括: + +- `index.mjs` — ESM 入口 +- `index.d.ts` — 类型声明 +- `viz.mjs` — 可视化子路径的 ESM 入口 +- `assets/worker-entry-*.js` — 自包含的 Worker bundle(OpenCV.js + ORT JS 运行时) + +自定义 Vite 插件 `libraryWorkerPlugin` 会对构建产物做后处理,以兼容 npm 分发场景: + +1. 将 Worker 资源的绝对路径改写为相对路径,使文件相对于 SDK 模块路径解析,而不是站点根路径 +2. 将 `new Worker(new URL(STRING, import.meta.url))` 拆成 URL 变量与 Worker 构造,便于下游打包工具复制 Worker 文件,并避免被当作二次打包目标 +3. 移除 Vite 内联到 Worker 产物中的 base64 WASM 二进制;在 Worker 模式下,ORT 会在运行时通过 `ort.env.wasm.wasmPaths` 加载 WASM(由使用方配置,或回退到与安装 ORT 版本绑定的 CDN),这样可以显著减小 Worker 文件体积 + +开发阶段,demo 通过 Vite alias 直接引用 core 的 TypeScript 源码,从而获得更好的 HMR 体验;生产构建(`npm run build`)时,则通过 workspace 链接使用 SDK 预构建的 `dist/`,并依靠兼容下游打包器的 Worker URL 形式将 Worker 文件正确复制到 demo 产物中。 + +## 测试策略 + +- 配置解析与注册表行为的单元测试 +- 面向浏览器平台辅助函数的轻量级 jsdom 测试 +- CI 默认不运行大规模真实模型推理 diff --git a/paddleocr-js/docs/monorepo.md b/paddleocr-js/docs/monorepo.md new file mode 100644 index 0000000..035dee2 --- /dev/null +++ b/paddleocr-js/docs/monorepo.md @@ -0,0 +1,35 @@ +# Monorepo conventions + +English | [简体中文](monorepo_cn.md) + +## Command execution + +When you only want one workspace, use root-level workspace commands with explicit paths: + +```bash +npm run build --workspace packages/core +npm run dev --workspace apps/demo +``` + +(You can also use workspace package names where unambiguous, e.g. `npm run dev --workspace demo`.) + +## Workspace roles + +- `packages/*`: reusable packages; the SDK lives under `packages/core` but keeps the **npm package name** `@paddleocr/paddleocr-js` +- `apps/*`: private applications such as demos (`apps/demo`); not published to npm as products + +## Versioning and release + +- **Directory:** `packages/core` — SDK source and publish manifest for the public package +- **npm package name:** `@paddleocr/paddleocr-js` — what consumers `npm install` and import in code +- **Directory:** `apps/demo` — private demo, not an npm release target +- Changesets manages versioning; the demo package is ignored in `.changeset/config.json` +- `npm run release` builds the SDK and publishes via `changeset publish` +- `packages/core` has a `prepublishOnly` script that auto-builds before `npm publish` / `npm pack` + +## Linting and tests + +- `packages/**/src/**/*.ts` is linted with `strictTypeChecked` TypeScript rules and browser-oriented globals +- `packages/**/test/**/*.ts` is linted with the lighter `recommendedTypeChecked` preset (browser + Node globals, with relaxed rules such as `no-unsafe-*` and `no-explicit-any` disabled) +- `apps/**/src/**/*.ts` also uses `strictTypeChecked` TypeScript rules with browser-oriented globals +- `apps/**/*.js`, root config files (`*.config.{js,ts}`), and package config files (`packages/**/*.config.*`) are linted with basic ESLint rules and both Node and browser globals diff --git a/paddleocr-js/docs/monorepo_cn.md b/paddleocr-js/docs/monorepo_cn.md new file mode 100644 index 0000000..8c3ce11 --- /dev/null +++ b/paddleocr-js/docs/monorepo_cn.md @@ -0,0 +1,35 @@ +# Monorepo 约定 + +[English](monorepo.md) | 简体中文 + +## 命令与 workspace + +当您只想操作一个 workspace 时,请在仓库根目录使用带显式路径的 workspace 命令: + +```bash +npm run build --workspace packages/core +npm run dev --workspace apps/demo +``` + +(在包名没有歧义时,也可以直接使用 workspace 的包名,例如 `npm run dev --workspace demo`。) + +## Workspace 角色 + +- `packages/*`:可复用包;SDK 位于 `packages/core`,但其 **npm 包名** 仍为 `@paddleocr/paddleocr-js` +- `apps/*`:私有应用(如 `apps/demo`),不作为 npm 产品发布 + +## 版本与发布 + +- **目录:** `packages/core` — 对外 npm 包的源码与发布清单 +- **npm 包名:** `@paddleocr/paddleocr-js` — 用户 `npm install` 后在代码中实际导入的名称 +- **目录:** `apps/demo` — 私有 demo,不是 npm 发布目标 +- 版本由 Changesets 管理;demo 包在 `.changeset/config.json` 中被忽略 +- `npm run release` 会构建 SDK 并通过 `changeset publish` 发布 +- `packages/core` 的 `prepublishOnly` 会在 `npm publish` / `npm pack` 前自动执行构建 + +## Lint 与测试 + +- `packages/**/src/**/*.ts` 使用 `strictTypeChecked` TypeScript 规则,并启用面向浏览器的全局变量 +- `packages/**/test/**/*.ts` 使用较轻的 `recommendedTypeChecked` 规则集(浏览器 + Node 全局),并放宽部分规则,例如 `no-unsafe-*` 和 `no-explicit-any` +- `apps/**/src/**/*.ts` 同样使用 `strictTypeChecked` TypeScript 规则与浏览器全局变量 +- `apps/**/*.js`、仓库根目录配置文件(`*.config.{js,ts}`)以及包内配置文件(`packages/**/*.config.*`)使用基础 ESLint 规则,同时启用 Node 与浏览器全局 diff --git a/paddleocr-js/eslint.config.js b/paddleocr-js/eslint.config.js new file mode 100644 index 0000000..dc9a4a9 --- /dev/null +++ b/paddleocr-js/eslint.config.js @@ -0,0 +1,49 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import globals from "globals"; + +export default tseslint.config( + { + ignores: ["**/dist", "**/node_modules", "**/coverage", "**/.cache"] + }, + eslint.configs.recommended, + { + files: ["packages/**/src/**/*.ts", "apps/**/src/**/*.ts"], + extends: [...tseslint.configs.strictTypeChecked], + languageOptions: { + globals: { ...globals.browser }, + parserOptions: { + project: "./tsconfig.eslint.json", + tsconfigRootDir: import.meta.dirname + } + } + }, + { + files: ["packages/**/test/**/*.ts"], + extends: [...tseslint.configs.recommendedTypeChecked], + languageOptions: { + globals: { ...globals.browser }, + parserOptions: { + project: "./tsconfig.eslint.json", + tsconfigRootDir: import.meta.dirname + } + }, + rules: { + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/require-await": "off", + "@typescript-eslint/no-extraneous-class": "off", + "@typescript-eslint/unbound-method": "off" + } + }, + { + files: ["apps/**/*.js", "*.config.{js,ts}", "packages/**/*.config.*"], + languageOptions: { + globals: { ...globals.browser, ...globals.node } + } + } +); diff --git a/paddleocr-js/package-lock.json b/paddleocr-js/package-lock.json new file mode 100644 index 0000000..58c0c5d --- /dev/null +++ b/paddleocr-js/package-lock.json @@ -0,0 +1,5684 @@ +{ + "name": "paddleocr-js-workspace", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "paddleocr-js-workspace", + "version": "0.1.0", + "workspaces": [ + "packages/*", + "apps/*" + ], + "devDependencies": { + "@eslint/js": "^10.0.1", + "@vitest/coverage-v8": "^3.2.4", + "eslint": "^10.0.2", + "globals": "^17.4.0", + "husky": "^9.1.7", + "jsdom": "^26.1.0", + "lint-staged": "^16.3.2", + "prettier": "^3.8.1", + "typescript-eslint": "^8.57.2", + "vitest": "^3.2.4" + }, + "engines": { + "node": ">=20.11" + } + }, + "apps/demo": { + "version": "0.1.0", + "dependencies": { + "@paddleocr/paddleocr-js": "*" + }, + "devDependencies": { + "typescript": "^5.9.3", + "vite": "^6.4.1" + }, + "engines": { + "node": ">=20.11" + } + }, + "apps/demo/node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.3", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz", + "integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.3", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz", + "integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz", + "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz", + "integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz", + "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@paddleocr/paddleocr-js": { + "resolved": "packages/core", + "link": true + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@techstark/opencv-js": { + "version": "4.10.0-release.1", + "resolved": "https://registry.npmjs.org/@techstark/opencv-js/-/opencv-js-4.10.0-release.1.tgz", + "integrity": "sha512-S4XELidRiQeA0q1s9VQLo540wCxUo24r1O4C+LqZ6llX+sPCXvZCPv3Ice8dEIr0uavyZ8YZeKXSBdDgMXSXjw==", + "license": "Apache-2.0" + }, + "node_modules/@ts-morph/common": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.13.0.tgz", + "integrity": "sha512-fEJ6j7Cu8yiWjA4UmybOBH9Efgb/64ZTWuvCF4KysGu4xz8ettfyaqFt8WZ1btCxXsGZJjZ2/3svOF6rL+UFdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.11", + "minimatch": "^5.0.1", + "mkdirp": "^1.0.4", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ts-morph/common/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", + "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/type-utils": "8.57.2", + "@typescript-eslint/utils": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.57.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", + "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", + "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.57.2", + "@typescript-eslint/types": "^8.57.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", + "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", + "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", + "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", + "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", + "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.57.2", + "@typescript-eslint/tsconfig-utils": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", + "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", + "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", + "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipper-lib": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/clipper-lib/-/clipper-lib-6.4.2.tgz", + "integrity": "sha512-knglhjQX5ihNj/XCIs6zCHrTemdvHY3LPZP9XB2nq2/3igyYMFueFXtfp84baJvEE+f8pO1ZS4UVeEgmLnAprQ==", + "license": "BSL" + }, + "node_modules/code-block-writer": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-11.0.3.tgz", + "integrity": "sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/demo": { + "resolved": "apps/demo", + "link": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.3.tgz", + "integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.3", + "@eslint/config-helpers": "^0.5.2", + "@eslint/core": "^1.1.1", + "@eslint/plugin-kit": "^0.6.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.1.1", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatbuffers": { + "version": "25.9.23", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz", + "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==", + "license": "Apache-2.0" + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/guid-typescript": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", + "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lint-staged": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", + "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.3", + "listr2": "^9.0.5", + "picomatch": "^4.0.3", + "string-argv": "^0.3.2", + "tinyexec": "^1.0.4", + "yaml": "^2.8.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onnxruntime-common": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz", + "integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==", + "license": "MIT" + }, + "node_modules/onnxruntime-web": { + "version": "1.24.3", + "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.24.3.tgz", + "integrity": "sha512-41dDq7fxtTm0XzGE7N0d6m8FcOY8EWtUA65GkOixJPB/G7DGzBmiDAnVVXHznRw9bgUZpb+4/1lQK/PNxGpbrQ==", + "license": "MIT", + "dependencies": { + "flatbuffers": "^25.1.24", + "guid-typescript": "^1.0.9", + "long": "^5.2.3", + "onnxruntime-common": "1.24.3", + "platform": "^1.3.6", + "protobufjs": "^7.2.4" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/platform": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", + "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", + "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-morph": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-14.0.0.tgz", + "integrity": "sha512-tO8YQ1dP41fw8GVmeQAdNsD8roZi1JMqB7YwZrqU856DvmG5/710e41q2XauzTYrygH9XmMryaFeLo+kdCziyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.13.0", + "code-block-writer": "^11.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz", + "integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/core": { + "name": "@paddleocr/paddleocr-js", + "version": "0.3.3", + "license": "Apache-2.0", + "dependencies": { + "@techstark/opencv-js": "^4.10.0-release.1", + "clipper-lib": "^6.4.2", + "js-yaml": "^4.1.0", + "onnxruntime-web": "^1.22.0" + }, + "devDependencies": { + "@types/js-yaml": "^4.0.9", + "typescript": "^5.9.3", + "vite": "^6.4.1", + "vite-plugin-dts": "^1.0.5" + } + }, + "packages/core/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "packages/core/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "packages/core/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "packages/core/node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "packages/core/node_modules/vite-plugin-dts": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-1.0.5.tgz", + "integrity": "sha512-gTm9f+yUK88fNngpxdQIDpGk7oeWnD01fnKOmS2Z9sTRKTmDfMowXGE78g1mxfNMApnt99RNS0CNq9qc+RMCQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "debug": "^4.3.4", + "fast-glob": "^3.2.11", + "fs-extra": "^10.0.1", + "ts-morph": "^14.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "vite": ">=2.4.4" + } + } + } +} diff --git a/paddleocr-js/package.json b/paddleocr-js/package.json new file mode 100644 index 0000000..b35634b --- /dev/null +++ b/paddleocr-js/package.json @@ -0,0 +1,68 @@ +{ + "name": "paddleocr-js-workspace", + "version": "0.1.0", + "private": true, + "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/PaddlePaddle/PaddleOCR.git", + "directory": "paddleocr-js" + }, + "homepage": "https://github.com/PaddlePaddle/PaddleOCR/tree/main/paddleocr-js", + "bugs": { + "url": "https://github.com/PaddlePaddle/PaddleOCR/issues" + }, + "keywords": [ + "paddleocr", + "ocr", + "monorepo", + "browser-ocr", + "typescript" + ], + "engines": { + "node": ">=20.11" + }, + "workspaces": [ + "packages/*", + "apps/*" + ], + "scripts": { + "dev:demo": "npm run dev --workspace apps/demo --", + "build": "npm run build:sdk && npm run build:demo", + "build:demo": "npm run build --workspace apps/demo", + "build:sdk": "npm run build --workspace packages/core", + "preview:demo": "npm run preview --workspace apps/demo --", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "typecheck": "npm run typecheck --workspaces --if-present", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "test:watch": "vitest", + "format": "prettier . --write", + "format:check": "prettier . --check", + "clean": "rm -rf packages/*/dist apps/*/dist", + "check": "npm run format:check && npm run lint && npm run build:sdk && npm run typecheck && npm run test && npm run build:demo", + "release": "npm run build:sdk && npm publish --workspace packages/core" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@vitest/coverage-v8": "^3.2.4", + "eslint": "^10.0.2", + "globals": "^17.4.0", + "husky": "^9.1.7", + "jsdom": "^26.1.0", + "lint-staged": "^16.3.2", + "prettier": "^3.8.1", + "typescript-eslint": "^8.57.2", + "vitest": "^3.2.4" + }, + "lint-staged": { + "*.{js,ts}": [ + "eslint --fix", + "prettier --write" + ], + "*.{json,md,html,css,yaml,yml}": [ + "prettier --write" + ] + } +} diff --git a/paddleocr-js/packages/core/README.md b/paddleocr-js/packages/core/README.md new file mode 100644 index 0000000..c966b2b --- /dev/null +++ b/paddleocr-js/packages/core/README.md @@ -0,0 +1,254 @@ +# PaddleOCR.js SDK + +English | [简体中文](README_cn.md) + +`@paddleocr/paddleocr-js` is the browser SDK package for running PaddleOCR pipelines in the frontend. + +## Install + +```bash +npm install @paddleocr/paddleocr-js +``` + +## Quick Start + +```js +import { PaddleOCR } from "@paddleocr/paddleocr-js"; + +const ocr = await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5", + ortOptions: { + backend: "auto" + } +}); + +const [result] = await ocr.predict(fileOrBlob); +console.log(result.items); +``` + +`predict` resolves to an **array** of `OcrResult` (one per input image). A single `Blob` / `File` still produces a one-element array—use destructuring or `results[0]`. + +## Construction Options + +There are two main construction styles: + +### 1. Direct parameters + +With direct parameters, you can specify models and configure inference batch sizes, ORT options, and other runtime settings. + +**Model selection** — `lang` + `ocrVersion`: + +```js +await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5" +}); +``` + +`ocrVersion: "PP-OCRv6"` maps supported `lang` values to the built-in **PP-OCRv6_small** det/rec pair. For **PP-OCRv6_tiny**, pass explicit model names: + +```js +await PaddleOCR.create({ + textDetectionModelName: "PP-OCRv6_tiny_det", + textRecognitionModelName: "PP-OCRv6_tiny_rec" +}); +``` + +**Model selection** — explicit model names: + +```js +await PaddleOCR.create({ + textDetectionModelName: "PP-OCRv5_mobile_det", + textRecognitionModelName: "PP-OCRv5_mobile_rec" +}); +``` + +**Custom models** — provide model names and asset URLs for detection and recognition: + +```js +await PaddleOCR.create({ + textDetectionModelName: "my_det_model", + textDetectionModelAsset: { + url: "https://example.com/models/my_det_model.tar" + }, + textRecognitionModelName: "my_rec_model", + textRecognitionModelAsset: { + url: "https://example.com/models/my_rec_model.tar" + } +}); +``` + +**Custom model archive format and validation:** + +- The downloaded bytes must be an **uncompressed ustar `.tar`**. The SDK does **not** gunzip **`.tar.gz`**; gzip-compressed payloads will usually fail to parse. +- The tar must contain **`inference.onnx`** and **`inference.yml`** (optionally under a subdirectory; matched by basename). +- **`inference.yml`** must define **`model_name`**, and it must match `textDetectionModelName` / `textRecognitionModelName`. This is checked during initialization after load. + +Failures surface as **`Error`** during initialization (HTTP errors, missing tar entries, empty resources, missing/mismatched `model_name`, incomplete model config, ONNX session errors)—not silent fallbacks. + +**Batch sizes, ORT options, and other runtime settings:** + +```js +await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5", + textDetectionBatchSize: 2, + textRecognitionBatchSize: 8, + ortOptions: { + backend: "wasm", + wasmPaths: "/assets/" + } +}); +``` + +### 2. Pipeline config + +```js +import { PaddleOCR } from "@paddleocr/paddleocr-js"; + +const pipelineConfig = ` +pipeline_name: OCR +SubModules: + TextDetection: + model_name: PP-OCRv5_mobile_det + batch_size: 2 + TextRecognition: + model_name: PP-OCRv5_mobile_rec + batch_size: 6 +`; + +const ocr = await PaddleOCR.create({ pipelineConfig }); +``` + +`pipelineConfig` can be either YAML text or a parsed object. + +If direct parameters and `pipelineConfig` are both provided, direct parameters take precedence. + +## Prediction + +### Params + +`ocr.predict(image | images[], params?)` accepts both camelCase names and PaddleOCR-style snake_case names: + +- `textDetLimitSideLen` or `text_det_limit_side_len` +- `textDetLimitType` or `text_det_limit_type` +- `textDetMaxSideLimit` or `text_det_max_side_limit` +- `textDetThresh` or `text_det_thresh` +- `textDetBoxThresh` or `text_det_box_thresh` +- `textDetUnclipRatio` or `text_det_unclip_ratio` +- `textRecScoreThresh` or `text_rec_score_thresh` + +Supported `image` inputs include `Blob`, `ImageBitmap`, `ImageData`, `HTMLCanvasElement`, `HTMLImageElement`, and `cv.Mat`. Pass an array of these to run detection and recognition on multiple images in one call. + +In worker mode (see next section), `cv.Mat` is not transferable and is therefore not supported as a worker input. + +### Return value + +Resolves to `Promise`. Each `OcrResult` contains: + +- `image`: `{ width, height }` for that source +- `items`: recognized lines (`poly`, `text`, `score`) +- `metrics`: `detMs`, `recMs`, `totalMs`, `detectedBoxes`, `recognizedCount` — box and line counts are per image; `detMs`, `recMs`, and `totalMs` cover the **entire** `predict()` call (so they are identical on every element when you pass multiple images) +- `runtime`: requested backend and provider metadata + +## Worker Mode + +You can run the OCR pipeline inside a dedicated Worker while keeping the same high-level API: + +```js +import { PaddleOCR } from "@paddleocr/paddleocr-js"; + +const ocr = await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5", + worker: true, + ortOptions: { + backend: "wasm", + wasmPaths: "https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/", + numThreads: 2, + simd: true + } +}); +``` + +Worker behavior: + +- Worker mode uses the package worker path, not ONNX Runtime Web `env.wasm.proxy`. +- When `worker: true` is enabled, the package forces ORT wasm proxy off internally. +- Browser inputs are normalized on the main thread and transferred into the worker before inference runs. +- `cv.Mat` is only supported in the direct main-thread pipeline path. + +## Visualization + +The optional `@paddleocr/paddleocr-js/viz` subpath provides visualization utilities for rendering OCR results as images. + +```js +import { OcrVisualizer } from "@paddleocr/paddleocr-js/viz"; + +const viz = new OcrVisualizer({ + font: { family: "Noto Sans SC", source: "/fonts/NotoSansSC-Regular.ttf" } +}); + +const blob = await viz.toBlob(imageBitmap, result); + +// Trigger browser download +const url = URL.createObjectURL(blob); +const a = document.createElement("a"); +a.href = url; +a.download = "ocr_result.png"; +a.click(); +URL.revokeObjectURL(url); + +viz.dispose(); +``` + +A one-shot convenience function is also available: + +```js +import { renderOcrToBlob } from "@paddleocr/paddleocr-js/viz"; + +const blob = await renderOcrToBlob(imageBitmap, result, { + font: { family: "Noto Sans SC", source: "/fonts/NotoSansSC-Regular.ttf" } +}); +``` + +The viz module renders a side-by-side composite image: the original image with detection box overlays on the left, and recognized text on the right. Custom fonts can be loaded for CJK text rendering. Visualization requires a **single** `OcrResult` (for one image, take the first element of the array returned by `predict`, e.g. `const [result] = await ocr.predict(image)`). + +`deterministicColor(index)` is also exported from the viz subpath. It maps a numeric index to a stable RGB color and is used internally as the default color function for detection boxes and text labels. You can call it directly when building custom visualizations that need colors consistent with the built-in renderer. + +## API + +- `PaddleOCR.create(options)` +- `ocr.initialize()` +- `ocr.getInitializationSummary()` +- `ocr.predict(image | images[], params?)` → `Promise` +- `ocr.dispose()` +- `parseOcrPipelineConfigText(text)` +- `normalizeOcrPipelineConfig(config)` +- `OcrVisualizer` (from `@paddleocr/paddleocr-js/viz`) +- `renderOcrToBlob` (from `@paddleocr/paddleocr-js/viz`) +- `deterministicColor` (from `@paddleocr/paddleocr-js/viz`) + +## Package Layout + +``` +src/ +├── runtime/ — inference runtime setup +├── resources/ — model & asset management +├── models/ — model wiring +├── platform/ — browser/worker input adaptation +├── worker/ — worker transport layer +├── pipelines/ — pipeline implementations +├── viz/ — visualization (optional) +├── types/ — external type declarations +└── utils/ — shared utilities +``` + +## Runtime Responsibilities + +The SDK manages OpenCV.js and ONNX Runtime internally. The host application is still responsible for runtime environment concerns, including: + +- COOP/COEP headers when enabling threaded WASM or WebGPU +- ONNX Runtime Web environment options such as wasm asset hosting paths, thread counts, and SIMD flags +- a bundler/runtime setup that can emit and load module workers when `worker: true` is used diff --git a/paddleocr-js/packages/core/README_cn.md b/paddleocr-js/packages/core/README_cn.md new file mode 100644 index 0000000..25518eb --- /dev/null +++ b/paddleocr-js/packages/core/README_cn.md @@ -0,0 +1,254 @@ +# PaddleOCR.js SDK + +[English](README.md) | 简体中文 + +`@paddleocr/paddleocr-js` 是在前端运行 PaddleOCR 产线的浏览器 SDK 包。 + +## 安装 + +```bash +npm install @paddleocr/paddleocr-js +``` + +## 快速开始 + +```js +import { PaddleOCR } from "@paddleocr/paddleocr-js"; + +const ocr = await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5", + ortOptions: { + backend: "auto" + } +}); + +const [result] = await ocr.predict(fileOrBlob); +console.log(result.items); +``` + +`predict` 返回 **`OcrResult` 组成的数组**(每张输入图像对应一项)。传入单个 `Blob` / `File` 时也会得到长度为 1 的数组,请使用解构或 `results[0]` 取值。 + +## 构造方式 + +主要有两种构造方式: + +### 1. 直接参数 + +可通过直接参数指定模型,也可配置推理 batch size、ORT 选项等运行参数。 + +**模型选择 — `lang` + `ocrVersion`:** + +```js +await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5" +}); +``` + +`ocrVersion: "PP-OCRv6"` 会将受支持的 `lang` 映射到内置的 **PP-OCRv6_small** 检测/识别模型对。若需 **PP-OCRv6_tiny**,请显式指定模型名: + +```js +await PaddleOCR.create({ + textDetectionModelName: "PP-OCRv6_tiny_det", + textRecognitionModelName: "PP-OCRv6_tiny_rec" +}); +``` + +**模型选择 — 显式模型名:** + +```js +await PaddleOCR.create({ + textDetectionModelName: "PP-OCRv5_mobile_det", + textRecognitionModelName: "PP-OCRv5_mobile_rec" +}); +``` + +**自定义模型** — 为检测 / 识别分别指定模型名与资源地址: + +```js +await PaddleOCR.create({ + textDetectionModelName: "my_det_model", + textDetectionModelAsset: { + url: "https://example.com/models/my_det_model.tar" + }, + textRecognitionModelName: "my_rec_model", + textRecognitionModelAsset: { + url: "https://example.com/models/my_rec_model.tar" + } +}); +``` + +**自定义模型包格式与校验行为:** + +- 资源需为 **未压缩的标准 tar**(`.tar`)。SDK 按字节解析 tar,**不对 gzip 压缩包解压**;若 URL 指向 `.tar.gz` 等,通常会解析失败并报错。 +- tar 内必须包含 **`inference.onnx`** 与 **`inference.yml`**(可在子目录中,按文件名匹配)。 +- `inference.yml` 必须能解析出 **`model_name`**,且须与 `textDetectionModelName` / `textRecognitionModelName` **一致**;在 `initialize` 加载模型后会校验。 + +不满足时通常在初始化阶段以 **`Error`** 失败(下载失败、tar 中缺少条目、空资源、`model_name` 缺失或不匹配、模型配置不完整、ONNX 会话创建失败等),不会静默忽略。 + +**batch size、ORT 选项等运行参数:** + +```js +await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5", + textDetectionBatchSize: 2, + textRecognitionBatchSize: 8, + ortOptions: { + backend: "wasm", + wasmPaths: "/assets/" + } +}); +``` + +### 2. 产线配置 + +```js +import { PaddleOCR } from "@paddleocr/paddleocr-js"; + +const pipelineConfig = ` +pipeline_name: OCR +SubModules: + TextDetection: + model_name: PP-OCRv5_mobile_det + batch_size: 2 + TextRecognition: + model_name: PP-OCRv5_mobile_rec + batch_size: 6 +`; + +const ocr = await PaddleOCR.create({ pipelineConfig }); +``` + +`pipelineConfig` 可以是 YAML 文本,也可以是解析后的对象。 + +如果同时提供直接参数和 `pipelineConfig`,则以直接参数为准。 + +## 预测 + +### 参数 + +`ocr.predict(image | images[], params?)` 同时接受 camelCase 命名和 PaddleOCR 风格的 snake_case 命名: + +- `textDetLimitSideLen` 或 `text_det_limit_side_len` +- `textDetLimitType` 或 `text_det_limit_type` +- `textDetMaxSideLimit` 或 `text_det_max_side_limit` +- `textDetThresh` 或 `text_det_thresh` +- `textDetBoxThresh` 或 `text_det_box_thresh` +- `textDetUnclipRatio` 或 `text_det_unclip_ratio` +- `textRecScoreThresh` 或 `text_rec_score_thresh` + +支持的 `image` 输入包括 `Blob`、`ImageBitmap`、`ImageData`、`HTMLCanvasElement`、`HTMLImageElement` 和 `cv.Mat`。传入上述类型的数组可在一次调用中对多张图像做检测与识别。 + +在 worker 模式下(见下一节),`cv.Mat` 无法传输,因此不能作为 worker 输入。 + +### 返回值 + +返回 `Promise`。每个 `OcrResult` 包含: + +- `image`:该图源的尺寸 `{ width, height }` +- `items`:识别行(`poly`、`text`、`score`) +- `metrics`:`detMs`、`recMs`、`totalMs`、`detectedBoxes`、`recognizedCount` — 检测框与文本行数为**每张图**统计;`detMs`、`recMs`、`totalMs` 表示**整次** `predict()` 调用的耗时(传入多图时,数组中每一项上的这三个值相同) +- `runtime`:请求的后端与各阶段 Provider 等元数据 + +## Worker 模式 + +你可以在专用 Worker 中运行 OCR 产线,同时保持相同的高层 API: + +```js +import { PaddleOCR } from "@paddleocr/paddleocr-js"; + +const ocr = await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5", + worker: true, + ortOptions: { + backend: "wasm", + wasmPaths: "https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/", + numThreads: 2, + simd: true + } +}); +``` + +Worker 模式的行为: + +- Worker 模式使用包内的 worker 路径,而不是 ONNX Runtime Web 的 `env.wasm.proxy` +- 启用 `worker: true` 时,包内部会强制关闭 ORT 的 wasm proxy +- 浏览器输入会先在主线程标准化,再传入 worker 执行推理 +- `cv.Mat` 仅支持直接在主线程产线路径中使用 + +## 可视化 + +可选的 `@paddleocr/paddleocr-js/viz` 子路径提供了将 OCR 结果渲染为图像的可视化工具。 + +```js +import { OcrVisualizer } from "@paddleocr/paddleocr-js/viz"; + +const viz = new OcrVisualizer({ + font: { family: "Noto Sans SC", source: "/fonts/NotoSansSC-Regular.ttf" } +}); + +const blob = await viz.toBlob(imageBitmap, result); + +// 触发浏览器下载 +const url = URL.createObjectURL(blob); +const a = document.createElement("a"); +a.href = url; +a.download = "ocr_result.png"; +a.click(); +URL.revokeObjectURL(url); + +viz.dispose(); +``` + +也提供了一次性便捷函数: + +```js +import { renderOcrToBlob } from "@paddleocr/paddleocr-js/viz"; + +const blob = await renderOcrToBlob(imageBitmap, result, { + font: { family: "Noto Sans SC", source: "/fonts/NotoSansSC-Regular.ttf" } +}); +``` + +viz 模块会渲染一张左右对比的合成图像:左侧为带有检测框叠加的原始图像,右侧为识别出的文字。支持加载自定义字体以正确渲染中日韩等文字。可视化需传入**单个** `OcrResult`(单张图时取 `predict` 返回数组的首项,例如 `const [result] = await ocr.predict(image)`)。 + +`deterministicColor(index)` 同样从 viz 子路径导出。它根据数字索引生成稳定的 RGB 颜色,内部用作检测框和文字标签的默认配色函数。当你构建自定义可视化并需要与内置渲染器保持一致的配色时,可以直接调用该函数。 + +## API + +- `PaddleOCR.create(options)` +- `ocr.initialize()` +- `ocr.getInitializationSummary()` +- `ocr.predict(image | images[], params?)` → `Promise` +- `ocr.dispose()` +- `parseOcrPipelineConfigText(text)` +- `normalizeOcrPipelineConfig(config)` +- `OcrVisualizer`(来自 `@paddleocr/paddleocr-js/viz`) +- `renderOcrToBlob`(来自 `@paddleocr/paddleocr-js/viz`) +- `deterministicColor`(来自 `@paddleocr/paddleocr-js/viz`) + +## 包结构 + +``` +src/ +├── runtime/ — 推理运行时初始化 +├── resources/ — 模型与资源管理 +├── models/ — 模型接线 +├── platform/ — 浏览器/worker 输入适配 +├── worker/ — worker 传输层 +├── pipelines/ — 产线实现 +├── viz/ — 可视化(可选) +├── types/ — 外部库类型声明 +└── utils/ — 共享工具 +``` + +## 运行时职责边界 + +SDK 内部负责管理 OpenCV.js 和 ONNX Runtime。宿主应用仍需负责运行时环境相关事项,包括: + +- 启用多线程 WASM 或 WebGPU 时所需的 COOP/COEP 响应头 +- ONNX Runtime Web 的环境选项,例如 wasm 资源托管路径、线程数和 SIMD 开关 +- 当使用 `worker: true` 时,能够产出并加载 module worker 的构建工具或运行时配置 diff --git a/paddleocr-js/packages/core/package.json b/paddleocr-js/packages/core/package.json new file mode 100644 index 0000000..cbd416d --- /dev/null +++ b/paddleocr-js/packages/core/package.json @@ -0,0 +1,73 @@ +{ + "name": "@paddleocr/paddleocr-js", + "version": "0.4.2", + "description": "Browser-based OCR SDK powered by PaddleOCR, ONNX Runtime Web and OpenCV.js", + "license": "Apache-2.0", + "author": "PaddlePaddle Authors", + "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/PaddlePaddle/PaddleOCR.git", + "directory": "paddleocr-js/packages/core" + }, + "homepage": "https://github.com/PaddlePaddle/PaddleOCR/tree/main/paddleocr-js", + "bugs": { + "url": "https://github.com/PaddlePaddle/PaddleOCR/issues" + }, + "publishConfig": { + "access": "public" + }, + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.mjs" + } + }, + "./viz": { + "import": { + "types": "./dist/viz/index.d.ts", + "default": "./dist/viz.mjs" + } + } + }, + "files": [ + "dist", + "README.md", + "README_cn.md" + ], + "sideEffects": false, + "keywords": [ + "ocr", + "paddleocr", + "paddle", + "text-recognition", + "text-detection", + "onnx", + "onnxruntime", + "opencv", + "browser-ocr", + "wasm", + "webgpu", + "deep-learning", + "machine-learning", + "computer-vision" + ], + "scripts": { + "build": "vite build", + "typecheck": "tsc --noEmit", + "prepublishOnly": "npm run build" + }, + "dependencies": { + "@techstark/opencv-js": "^4.10.0-release.1", + "clipper-lib": "^6.4.2", + "js-yaml": "^4.1.0", + "onnxruntime-web": "^1.22.0" + }, + "devDependencies": { + "@types/js-yaml": "^4.0.9", + "typescript": "^5.9.3", + "vite": "^6.4.1", + "vite-plugin-dts": "^1.0.5" + } +} diff --git a/paddleocr-js/packages/core/src/index.ts b/paddleocr-js/packages/core/src/index.ts new file mode 100644 index 0000000..bfa8479 --- /dev/null +++ b/paddleocr-js/packages/core/src/index.ts @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export { + PaddleOCR, + normalizeOcrPipelineConfig, + parseOcrPipelineConfigText +} from "./pipelines/ocr/index"; + +export type { Point2D, NormalizeConfig, DetBox } from "./models/common"; + +export type { + DetModelConfig, + DetPostprocessConfig, + DetModel, + DetResult, + DetRuntimeOverrides, + LimitType +} from "./models/det"; + +export type { RecModelConfig, RecModel, RecResult, RecRuntimeOverrides } from "./models/rec"; + +export type { + OcrRuntimeParamsInput, + OcrModelConfig, + ResolvedOcrParams +} from "./pipelines/ocr/runtime-params"; + +export type { + OcrResult, + OcrResultItem, + OcrResultMetrics, + OcrResultRuntime, + InitializationSummary, + OcrPipelineRunnerOptions +} from "./pipelines/ocr/core"; + +export type { + NormalizedPipelineConfig, + PipelineModelSelection, + PipelineRuntimeDefaults +} from "./pipelines/ocr/config"; + +export type { + ResolvedBackend, + ResolvedOcrOptions, + NormalizedOrtOptions, + WorkerResolvedOptions +} from "./pipelines/ocr/shared"; + +export type { ModelAsset, ModelAssetsMap } from "./resources/model-asset"; + +export type { WebGpuState, OrtOptions } from "./runtime/ort"; + +export type { ImageSource, SourceMatResult } from "./platform/browser"; + +export type { PaddleOCRCreateOptions } from "./pipelines/ocr/index"; diff --git a/paddleocr-js/packages/core/src/models/common.ts b/paddleocr-js/packages/core/src/models/common.ts new file mode 100644 index 0000000..411bce6 --- /dev/null +++ b/paddleocr-js/packages/core/src/models/common.ts @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import yaml from "js-yaml"; +import ClipperLib from "clipper-lib"; +import type { OpenCv, Mat } from "@techstark/opencv-js"; + +import { clamp, distance2 } from "../utils/common"; + +export type Point2D = [number, number]; + +export interface NormalizeConfig { + mean: number[]; + std: number[]; + scale: number; +} + +export interface MiniBox { + box: Point2D[]; + side: number; +} + +export interface DetBox { + poly: Point2D[]; + score: number; +} + +type YamlValue = string | number | boolean | null | YamlValue[] | { [key: string]: YamlValue }; +type YamlObject = Record; + +function isPlainObject(value: unknown): value is YamlObject { + return Boolean(value) && typeof value === "object" && !Array.isArray(value); +} + +export function parseInferenceConfigText(text: string): YamlObject { + const parsed = yaml.load(text); + return isPlainObject(parsed) ? parsed : {}; +} + +export function parseScaleValue(rawScale: unknown, fallback: number): number { + if (typeof rawScale === "number") return rawScale; + if (typeof rawScale !== "string") return fallback; + const normalized = rawScale.replace(/\s/g, ""); + const direct = Number(normalized); + if (!Number.isNaN(direct)) return direct; + const divParts = normalized.split("/"); + if (divParts.length === 2) { + const numerator = Number(divParts[0].replace(/\.+$/, "")); + const denominator = Number(divParts[1].replace(/\.+$/, "")); + if (!Number.isNaN(numerator) && !Number.isNaN(denominator) && denominator !== 0) { + return numerator / denominator; + } + } + return fallback; +} + +export function getTransformOp( + transformOps: Array> | undefined, + opName: string +): Record | null { + for (const op of transformOps || []) { + if (Object.prototype.hasOwnProperty.call(op, opName)) { + return op[opName] as YamlObject; + } + } + return null; +} + +function findModelNameInYamlNode(value: YamlValue): string | null { + if (Array.isArray(value)) { + for (const item of value) { + const match = findModelNameInYamlNode(item); + if (match) return match; + } + return null; + } + + if (!isPlainObject(value)) { + return null; + } + + for (const [key, childValue] of Object.entries(value)) { + if (key === "model_name" && typeof childValue === "string" && childValue.trim()) { + return childValue; + } + const match = findModelNameInYamlNode(childValue); + if (match) return match; + } + + return null; +} + +export function extractInferenceModelName(configText: string): string | null { + const parsed = parseInferenceConfigText(configText); + const preferredCandidates = [ + (parsed.Global as YamlObject | undefined)?.model_name, + parsed.model_name + ]; + for (const candidate of preferredCandidates) { + if (typeof candidate === "string" && candidate.trim()) { + return candidate; + } + } + + return findModelNameInYamlNode(parsed); +} + +export function toBgrFloatCHWFromBgr( + bgr: Uint8Array, + width: number, + height: number, + normalizeConfig: NormalizeConfig +): Float32Array { + const data = new Float32Array(3 * width * height); + const hw = width * height; + const mean = normalizeConfig.mean; + const std = normalizeConfig.std; + const scale = normalizeConfig.scale; + for (let y = 0; y < height; y += 1) { + for (let x = 0; x < width; x += 1) { + const idx = y * width + x; + const p = idx * 3; + const b = bgr[p]; + const g = bgr[p + 1]; + const r = bgr[p + 2]; + data[idx] = (b * scale - mean[0]) / std[0]; + data[idx + hw] = (g * scale - mean[1]) / std[1]; + data[idx + 2 * hw] = (r * scale - mean[2]) / std[2]; + } + } + return data; +} + +function orderQuad(pts: Point2D[]): Point2D[] { + const points = pts.slice().sort((a, b) => a[0] - b[0]); + let indexA: number; + let indexB: number; + let indexC: number; + let indexD: number; + if (points[1][1] > points[0][1]) { + indexA = 0; + indexD = 1; + } else { + indexA = 1; + indexD = 0; + } + if (points[3][1] > points[2][1]) { + indexB = 2; + indexC = 3; + } else { + indexB = 3; + indexC = 2; + } + return [points[indexA], points[indexB], points[indexC], points[indexD]]; +} + +function polygonArea(poly: Point2D[]): number { + let area = 0; + for (let i = 0; i < poly.length; i += 1) { + const j = (i + 1) % poly.length; + area += poly[i][0] * poly[j][1] - poly[j][0] * poly[i][1]; + } + return Math.abs(area) * 0.5; +} + +function polygonPerimeter(poly: Point2D[]): number { + let peri = 0; + for (let i = 0; i < poly.length; i += 1) { + const j = (i + 1) % poly.length; + peri += distance2(poly[i], poly[j]); + } + return peri; +} + +interface ClipperPoint { + X: number; + Y: number; +} + +function chooseMaxAreaPath(paths: ClipperPoint[][]): ClipperPoint[] | null { + let best: ClipperPoint[] | null = null; + let bestArea = 0; + for (const path of paths) { + if (path.length < 4) continue; + const poly: Point2D[] = path.map((pt) => [pt.X, pt.Y]); + const area = polygonArea(poly); + if (area > bestArea) { + bestArea = area; + best = path; + } + } + return best; +} + +export function unclip(poly: Point2D[], unclipRatio: number): Point2D[] | null { + const area = polygonArea(poly); + const perimeter = polygonPerimeter(poly); + if (perimeter <= 0) return null; + const distance = (area * unclipRatio) / perimeter; + const path: ClipperPoint[] = poly.map((p) => ({ X: Math.trunc(p[0]), Y: Math.trunc(p[1]) })); + const offset = new ClipperLib.ClipperOffset(); + offset.AddPath(path, ClipperLib.JoinType.jtRound, ClipperLib.EndType.etClosedPolygon); + const expanded = new ClipperLib.Paths(); + offset.Execute(expanded, distance); + const best = chooseMaxAreaPath(expanded); + if (!best) return null; + return best.map((pt) => [pt.X, pt.Y]); +} + +export function getMiniBoxFromPoints(cv: OpenCv, points: Point2D[]): MiniBox { + const flat: number[] = []; + for (const p of points) flat.push(p[0], p[1]); + const contour = cv.matFromArray(points.length, 1, cv.CV_32FC2, flat); + const rect = cv.minAreaRect(contour); + const vertices = cv.RotatedRect.points(rect); + const box: Point2D[] = []; + for (let i = 0; i < 4; i += 1) box.push([vertices[i].x, vertices[i].y]); + contour.delete(); + const ordered = orderQuad(box); + const side = Math.min(distance2(ordered[0], ordered[1]), distance2(ordered[1], ordered[2])); + return { box: ordered, side }; +} + +export function boxScoreFast(cv: OpenCv, predMat: Mat, box: Point2D[]): number { + const h = predMat.rows; + const w = predMat.cols; + let minX = w - 1; + let maxX = 0; + let minY = h - 1; + let maxY = 0; + for (const p of box) { + minX = Math.min(minX, p[0]); + maxX = Math.max(maxX, p[0]); + minY = Math.min(minY, p[1]); + maxY = Math.max(maxY, p[1]); + } + minX = clamp(Math.floor(minX), 0, w - 1); + maxX = clamp(Math.ceil(maxX), 0, w - 1); + minY = clamp(Math.floor(minY), 0, h - 1); + maxY = clamp(Math.ceil(maxY), 0, h - 1); + const rw = Math.max(1, maxX - minX + 1); + const rh = Math.max(1, maxY - minY + 1); + const roi = predMat.roi(new cv.Rect(minX, minY, rw, rh)); + const mask = cv.Mat.zeros(rh, rw, cv.CV_8UC1); + const shifted = box.map((p) => [Math.trunc(p[0] - minX), Math.trunc(p[1] - minY)]); + const flat: number[] = []; + for (const p of shifted) flat.push(p[0], p[1]); + const pts = cv.matFromArray(shifted.length, 1, cv.CV_32SC2, flat); + const ptsVec = new cv.MatVector(); + ptsVec.push_back(pts); + cv.fillPoly(mask, ptsVec, new cv.Scalar(1)); + const mean = cv.mean(roi, mask)[0]; + roi.delete(); + mask.delete(); + pts.delete(); + ptsVec.delete(); + return mean; +} diff --git a/paddleocr-js/packages/core/src/models/det.ts b/paddleocr-js/packages/core/src/models/det.ts new file mode 100644 index 0000000..7acf6d9 --- /dev/null +++ b/paddleocr-js/packages/core/src/models/det.ts @@ -0,0 +1,551 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { OpenCv, Mat } from "@techstark/opencv-js"; +import type { InferenceSession, Tensor } from "onnxruntime-web"; + +import { assertModelResources } from "../resources/model-asset"; +import { createSession, getProviderCandidates, releaseSessions } from "../runtime/ort"; +import type { OrtModule, WebGpuState, SessionState } from "../runtime/ort"; +import { chunkArray, clamp, resolveRuntimeBatchSize, withTimeout } from "../utils/common"; +import { runInference } from "./infer"; +import { + boxScoreFast, + getMiniBoxFromPoints, + getTransformOp, + parseInferenceConfigText, + parseScaleValue, + toBgrFloatCHWFromBgr, + unclip +} from "./common"; +import type { Point2D, NormalizeConfig, DetBox } from "./common"; + +export type LimitType = "min" | "max"; + +export interface DetRuntimeOverrides { + batchSize?: number; + thresh?: number; + boxThresh?: number; + unclipRatio?: number; + limitSideLen?: number; + limitType?: LimitType; + maxSideLimit?: number; +} + +export interface DetPostprocessConfig { + thresh: number; + boxThresh: number; + maxCandidates: number; + unclipRatio: number; +} + +export interface DetModelConfig { + resizeLong: number; + limitType: LimitType; + maxSideLimit: number; + normalize: NormalizeConfig; + postprocess: DetPostprocessConfig; +} + +export interface DetModel { + readonly kind: "det"; + readonly config: DetModelConfig; + readonly provider: string; + predict(cv: OpenCv, mats: Mat[], overrides?: DetRuntimeOverrides): Promise; + dispose(): Promise; +} + +interface DetPreprocessResult { + tensor: Tensor; + srcW: number; + srcH: number; + dstW: number; + dstH: number; +} + +export interface DetResult { + boxes: DetBox[]; + srcW: number; + srcH: number; +} + +interface InternalDetParams { + limitSideLen: number; + limitType: LimitType; + maxSideLimit: number; + thresh: number; + boxThresh: number; + unclipRatio: number; +} + +interface InternalDetBatchItem { + prep: DetPreprocessResult; + boxes: DetBox[]; +} + +const DET_BOX_MIN_SIZE = 3; + +export const DEFAULT_DET_MODEL_PARSE_FALLBACKS: Readonly = Object.freeze({ + resizeLong: 960, + limitType: "max", + maxSideLimit: 4000, + normalize: { + mean: [0.485, 0.456, 0.406], + std: [0.229, 0.224, 0.225], + scale: 1 / 255 + }, + postprocess: { + thresh: 0.3, + boxThresh: 0.6, + maxCandidates: 1000, + unclipRatio: 2.0 + } +}); + +export const DEFAULT_DET_MODEL_CONFIG: Readonly = Object.freeze({ + ...DEFAULT_DET_MODEL_PARSE_FALLBACKS +}); + +function parseDetLimitType(raw: unknown): LimitType { + const v = typeof raw === "string" ? raw.trim().toLowerCase() : ""; + if (v === "min" || v === "max") { + return v; + } + return DEFAULT_DET_MODEL_PARSE_FALLBACKS.limitType; +} + +export function parseDetModelConfigText(text: string): DetModelConfig { + const parsed = parseInferenceConfigText(text); + const preProcess = parsed.PreProcess as Record | undefined; + const transformOps = preProcess?.transform_ops as Array> | undefined; + const resize = getTransformOp(transformOps, "DetResizeForTest"); + const normalize = getTransformOp(transformOps, "NormalizeImage"); + const postprocess = (parsed.PostProcess || {}) as Record; + + const maxSideRaw = resize?.max_side_limit; + const maxSideLimit = Number(maxSideRaw); + const maxSide = + Number.isFinite(maxSideLimit) && maxSideLimit > 0 + ? maxSideLimit + : DEFAULT_DET_MODEL_PARSE_FALLBACKS.maxSideLimit; + + return { + resizeLong: Number(resize?.resize_long ?? DEFAULT_DET_MODEL_PARSE_FALLBACKS.resizeLong), + limitType: parseDetLimitType(resize?.limit_type), + maxSideLimit: maxSide, + normalize: { + mean: + (normalize?.mean as number[] | undefined) ?? + DEFAULT_DET_MODEL_PARSE_FALLBACKS.normalize.mean, + std: + (normalize?.std as number[] | undefined) ?? DEFAULT_DET_MODEL_PARSE_FALLBACKS.normalize.std, + scale: parseScaleValue(normalize?.scale, DEFAULT_DET_MODEL_PARSE_FALLBACKS.normalize.scale) + }, + postprocess: { + thresh: Number(postprocess.thresh ?? DEFAULT_DET_MODEL_PARSE_FALLBACKS.postprocess.thresh), + boxThresh: Number( + postprocess.box_thresh ?? DEFAULT_DET_MODEL_PARSE_FALLBACKS.postprocess.boxThresh + ), + maxCandidates: Number( + postprocess.max_candidates ?? DEFAULT_DET_MODEL_PARSE_FALLBACKS.postprocess.maxCandidates + ), + unclipRatio: Number( + postprocess.unclip_ratio ?? DEFAULT_DET_MODEL_PARSE_FALLBACKS.postprocess.unclipRatio + ) + } + }; +} + +interface CreateDetModelArgs { + ort: OrtModule; + modelBytes: Uint8Array; + configText: string; + backend: string; + webgpuState: WebGpuState; + batchSize?: number; +} + +function resolveDetParams( + defaults: InternalDetParams, + overrides?: DetRuntimeOverrides +): InternalDetParams { + return { + limitSideLen: overrides?.limitSideLen ?? defaults.limitSideLen, + limitType: overrides?.limitType ?? defaults.limitType, + maxSideLimit: overrides?.maxSideLimit ?? defaults.maxSideLimit, + thresh: overrides?.thresh ?? defaults.thresh, + boxThresh: overrides?.boxThresh ?? defaults.boxThresh, + unclipRatio: overrides?.unclipRatio ?? defaults.unclipRatio + }; +} + +export async function createDetModel({ + ort, + modelBytes, + configText, + backend, + webgpuState, + batchSize: batchSizeArg +}: CreateDetModelArgs): Promise { + assertModelResources("Detection", { + model: modelBytes, + config: configText + }); + const config = parseDetModelConfigText(configText); + const defaultBatchSize = Math.max(1, batchSizeArg ?? 1); + const defaultParams: InternalDetParams = { + limitSideLen: config.resizeLong, + limitType: config.limitType, + maxSideLimit: config.maxSideLimit, + thresh: config.postprocess.thresh, + boxThresh: config.postprocess.boxThresh, + unclipRatio: config.postprocess.unclipRatio + }; + let sessionState: SessionState | null = await createDetModelSession( + ort, + modelBytes, + backend, + webgpuState + ); + + return { + kind: "det", + config, + get provider() { + return sessionState?.provider || ""; + }, + async predict(cv, mats, overrides) { + if (!sessionState?.session) { + throw new Error("Detection model session is not initialized."); + } + const params = resolveDetParams(defaultParams, overrides); + const batchSize = resolveRuntimeBatchSize(overrides?.batchSize, defaultBatchSize); + const results: DetResult[] = []; + const runCtx: DetRunContext = { + cv, + ort, + config, + session: sessionState.session + }; + for (const chunk of chunkArray(mats, batchSize)) { + const preps = preprocess({ cv, ort, config }, chunk, params); + const inputTensor = packDetBatchTensor(ort, preps); + const fullOutput = await runInference(sessionState.session, inputTensor); + const internals = postprocess(runCtx, fullOutput, preps, params); + for (const internal of internals) { + results.push({ + boxes: internal.boxes, + srcW: internal.prep.srcW, + srcH: internal.prep.srcH + }); + } + } + return results; + }, + async dispose() { + await releaseSessions(sessionState?.session); + sessionState = null; + } + }; +} + +export async function createDetModelSession( + ort: OrtModule, + modelBytes: Uint8Array, + backend: string, + webgpuState: WebGpuState +): Promise { + const providerCandidates = getProviderCandidates(backend, webgpuState); + return withTimeout(createSession(ort, modelBytes, providerCandidates), 60000, "Detection model"); +} + +interface DetContext { + cv: OpenCv; + ort: OrtModule; + config: DetModelConfig; +} + +interface DetRunContext extends DetContext { + session: InferenceSession; +} + +function preprocess( + context: DetContext, + mats: Mat[], + params: InternalDetParams +): DetPreprocessResult[] { + return mats.map((mat) => preprocessSample(context, mat, params)); +} + +function preprocessSample( + context: DetContext, + sourceMat: Mat, + params: InternalDetParams +): DetPreprocessResult { + const { cv, ort, config } = context; + const srcW = sourceMat.cols; + const srcH = sourceMat.rows; + const limitSideLen = Math.max(32, params.limitSideLen); + const limitType: LimitType = params.limitType; + const maxSideLimit = Math.max(32, params.maxSideLimit); + let scale = 1.0; + if (limitType === "max") { + const maxSide = Math.max(srcW, srcH); + if (maxSide > limitSideLen) { + scale = limitSideLen / Math.max(1, maxSide); + } + } else { + const minSide = Math.min(srcW, srcH); + if (minSide < limitSideLen) { + scale = limitSideLen / Math.max(1, minSide); + } + } + let dstW = Math.max(32, Math.round((srcW * scale) / 32) * 32); + let dstH = Math.max(32, Math.round((srcH * scale) / 32) * 32); + if (Math.max(dstW, dstH) > maxSideLimit) { + const limitScale = maxSideLimit / Math.max(dstW, dstH); + dstW = Math.max(32, Math.floor(dstW * limitScale)); + dstH = Math.max(32, Math.floor(dstH * limitScale)); + } + dstW = clamp(dstW, 32, maxSideLimit); + dstH = clamp(dstH, 32, maxSideLimit); + dstW = Math.max(32, Math.round(dstW / 32) * 32); + dstH = Math.max(32, Math.round(dstH / 32) * 32); + + const resized = new cv.Mat(); + const bgr = new cv.Mat(); + cv.resize(sourceMat, resized, new cv.Size(dstW, dstH), 0, 0, cv.INTER_LINEAR); + if (resized.channels() === 4) { + cv.cvtColor(resized, bgr, cv.COLOR_RGBA2BGR); + } else if (resized.channels() === 1) { + cv.cvtColor(resized, bgr, cv.COLOR_GRAY2BGR); + } else { + resized.copyTo(bgr); + } + const chw = toBgrFloatCHWFromBgr(bgr.data, dstW, dstH, config.normalize); + resized.delete(); + bgr.delete(); + + return { + tensor: new ort.Tensor("float32", chw, [1, 3, dstH, dstW]), + srcW, + srcH, + dstW, + dstH + }; +} + +function getDetMap(outputTensor: Tensor): { data: Float32Array; h: number; w: number } { + const dims = outputTensor.dims; + const data = outputTensor.data as Float32Array; + if (dims.length === 4) return { data, h: dims[2], w: dims[3] }; + if (dims.length === 3) return { data, h: dims[1], w: dims[2] }; + throw new Error(`Unexpected det output dims: [${dims.join(", ")}]`); +} + +function createBatchDetTensor( + ort: OrtModule, + preps: DetPreprocessResult[], + maxH: number, + maxW: number +): Tensor { + const batch = preps.length; + const plane = 3 * maxH * maxW; + const out = new Float32Array(batch * plane); + for (let i = 0; i < batch; i += 1) { + const prep = preps[i]; + const chw = prep.tensor.data as Float32Array; + const { dstH, dstW } = prep; + const base = i * plane; + for (let c = 0; c < 3; c += 1) { + const srcChannelBase = c * dstH * dstW; + const dstChannelBase = base + c * maxH * maxW; + for (let y = 0; y < dstH; y += 1) { + const srcRow = srcChannelBase + y * dstW; + const dstRow = dstChannelBase + y * maxW; + out.set(chw.subarray(srcRow, srcRow + dstW), dstRow); + } + } + } + return new ort.Tensor("float32", out, [batch, 3, maxH, maxW]); +} + +function packDetBatchTensor(ort: OrtModule, preps: DetPreprocessResult[]): Tensor { + const maxH = Math.max(...preps.map((p) => p.dstH)); + const maxW = Math.max(...preps.map((p) => p.dstW)); + return createBatchDetTensor(ort, preps, maxH, maxW); +} + +function batchDetOutputPlaneOffset(dims: readonly number[], batchIndex: number): number { + const tail = dims.slice(1).reduce((a, b) => a * b, 1); + return batchIndex * tail; +} + +function detFeatureCropDims( + dstH: number, + dstW: number, + maxH: number, + maxW: number, + ohFull: number, + owFull: number +): { cropOh: number; cropOw: number } { + const cropOh = Math.max(1, Math.min(ohFull, Math.round((ohFull * dstH) / maxH))); + const cropOw = Math.max(1, Math.min(owFull, Math.round((owFull * dstW) / maxW))); + return { cropOh, cropOw }; +} + +function sliceBatchedDetOutputPlane( + ort: OrtModule, + fullOutput: Tensor, + batchIndex: number, + cropOh: number, + cropOw: number, + ohFull: number, + owFull: number +): Tensor { + const data = fullOutput.data as Float32Array; + const dims = fullOutput.dims; + const base = batchDetOutputPlaneOffset(dims, batchIndex); + const out = new Float32Array(cropOh * cropOw); + for (let r = 0; r < cropOh; r += 1) { + const rowStart = base + r * owFull; + out.set(data.subarray(rowStart, rowStart + cropOw), r * cropOw); + } + return new ort.Tensor("float32", out, [1, 1, cropOh, cropOw]); +} + +function postprocess( + context: DetRunContext, + fullOutput: Tensor, + preps: DetPreprocessResult[], + params: InternalDetParams +): InternalDetBatchItem[] { + const { cv, ort, config } = context; + const od = fullOutput.dims; + if (od.length !== 3 && od.length !== 4) { + throw new Error(`Unexpected det output dims: [${od.join(", ")}]`); + } + const ohFull = od.length === 4 ? od[2] : od[1]; + const owFull = od.length === 4 ? od[3] : od[2]; + const nOut = od.length === 4 ? od[0] : preps.length === 1 ? 1 : od[0]; + if (nOut !== preps.length) { + throw new Error( + `Detection batch output N=${String(nOut)} does not match input batch ${String(preps.length)}` + ); + } + + const maxH = Math.max(...preps.map((p) => p.dstH)); + const maxW = Math.max(...preps.map((p) => p.dstW)); + + const items: InternalDetBatchItem[] = []; + for (let i = 0; i < preps.length; i += 1) { + const prep = preps[i]; + const { cropOh, cropOw } = detFeatureCropDims(prep.dstH, prep.dstW, maxH, maxW, ohFull, owFull); + const planeTensor = sliceBatchedDetOutputPlane( + ort, + fullOutput, + i, + cropOh, + cropOw, + ohFull, + owFull + ); + const boxes = decodeDetOutput( + { cv, config }, + planeTensor, + prep, + params.thresh, + params.boxThresh, + params.unclipRatio + ); + items.push({ prep, boxes }); + } + return items; +} + +function decodeDetOutput( + context: { cv: OpenCv; config: DetModelConfig }, + detOutput: Tensor, + meta: DetPreprocessResult, + detThresh: number, + boxThresh: number, + unclipRatio: number +): DetBox[] { + const { cv, config } = context; + const { data, h, w } = getDetMap(detOutput); + const pred = cv.matFromArray(h, w, cv.CV_32FC1, data); + const maskData = new Uint8Array(h * w); + for (let i = 0; i < data.length; i += 1) { + maskData[i] = data[i] > detThresh ? 255 : 0; + } + const bitmap = cv.matFromArray(h, w, cv.CV_8UC1, maskData); + const contours = new cv.MatVector(); + const hierarchy = new cv.Mat(); + cv.findContours(bitmap, contours, hierarchy, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE); + + const boxes: DetBox[] = []; + const candidateCount = Math.min(config.postprocess.maxCandidates, contours.size()); + for (let i = 0; i < candidateCount; i += 1) { + const contour = contours.get(i); + if (contour.rows < 4) { + contour.delete(); + continue; + } + const points: Point2D[] = []; + for (let row = 0; row < contour.rows; row += 1) { + points.push([contour.data32S[row * 2], contour.data32S[row * 2 + 1]]); + } + const mini = getMiniBoxFromPoints(cv, points); + if (mini.side < DET_BOX_MIN_SIZE) { + contour.delete(); + continue; + } + const score = boxScoreFast(cv, pred, mini.box); + if (score < boxThresh) { + contour.delete(); + continue; + } + const expanded = unclip(mini.box, unclipRatio); + if (!expanded || expanded.length < 4) { + contour.delete(); + continue; + } + const miniUnclip = getMiniBoxFromPoints(cv, expanded); + if (miniUnclip.side < DET_BOX_MIN_SIZE + 2) { + contour.delete(); + continue; + } + + const poly: Point2D[] = miniUnclip.box.map((point) => [ + clamp(Math.round((point[0] * meta.srcW) / Math.max(1, w)), 0, meta.srcW), + clamp(Math.round((point[1] * meta.srcH) / Math.max(1, h)), 0, meta.srcH) + ]); + boxes.push({ poly, score }); + contour.delete(); + } + + pred.delete(); + bitmap.delete(); + contours.delete(); + hierarchy.delete(); + + boxes.sort((a, b) => a.poly[0][1] - b.poly[0][1] || a.poly[0][0] - b.poly[0][0]); + for (let i = 0; i < boxes.length - 1; i += 1) { + for (let j = i; j >= 0; j -= 1) { + if ( + Math.abs(boxes[j + 1].poly[0][1] - boxes[j].poly[0][1]) < 10 && + boxes[j + 1].poly[0][0] < boxes[j].poly[0][0] + ) { + const tmp = boxes[j]; + boxes[j] = boxes[j + 1]; + boxes[j + 1] = tmp; + } else { + break; + } + } + } + + return boxes; +} diff --git a/paddleocr-js/packages/core/src/models/index.ts b/paddleocr-js/packages/core/src/models/index.ts new file mode 100644 index 0000000..86d0d58 --- /dev/null +++ b/paddleocr-js/packages/core/src/models/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export { + DEFAULT_DET_MODEL_PARSE_FALLBACKS, + DEFAULT_DET_MODEL_CONFIG, + createDetModel, + createDetModelSession, + parseDetModelConfigText +} from "./det"; +export { + DEFAULT_REC_MODEL_PARSE_FALLBACKS, + DEFAULT_REC_RUNTIME_LIMITS, + DEFAULT_REC_MODEL_CONFIG, + createRecModel, + createRecModelSession, + parseRecModelConfigText +} from "./rec"; +export type { RecRuntimeOverrides } from "./rec"; diff --git a/paddleocr-js/packages/core/src/models/infer.ts b/paddleocr-js/packages/core/src/models/infer.ts new file mode 100644 index 0000000..7b16b9f --- /dev/null +++ b/paddleocr-js/packages/core/src/models/infer.ts @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { InferenceSession, Tensor } from "onnxruntime-web"; + +export async function runInference( + session: InferenceSession, + inputTensor: Tensor +): Promise { + const inputName = session.inputNames[0]; + const outputMap = await session.run({ [inputName]: inputTensor }); + return outputMap[session.outputNames[0]]; +} diff --git a/paddleocr-js/packages/core/src/models/rec.ts b/paddleocr-js/packages/core/src/models/rec.ts new file mode 100644 index 0000000..b85c72b --- /dev/null +++ b/paddleocr-js/packages/core/src/models/rec.ts @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { OpenCv, Mat } from "@techstark/opencv-js"; +import type { Tensor } from "onnxruntime-web"; + +import { assertModelResources } from "../resources/model-asset"; +import { createSession, getProviderCandidates, releaseSessions } from "../runtime/ort"; +import type { OrtModule, WebGpuState, SessionState } from "../runtime/ort"; +import { chunkArray, clamp, resolveRuntimeBatchSize, withTimeout } from "../utils/common"; +import { getTransformOp, parseInferenceConfigText, toBgrFloatCHWFromBgr } from "./common"; +import { runInference } from "./infer"; +import type { NormalizeConfig } from "./common"; + +export interface RecModelConfig { + imageShape: number[]; + charDict: string[]; +} + +export interface RecResult { + text: string; + score: number; +} + +export interface RecRuntimeOverrides { + batchSize?: number; +} + +export interface RecModel { + readonly kind: "rec"; + readonly config: RecModelConfig; + readonly provider: string; + predict(cv: OpenCv, mats: Mat[], overrides?: RecRuntimeOverrides): Promise; + dispose(): Promise; +} + +interface RecSample { + inputIndex: number; + width: number; + chw: Float32Array; +} + +const DEFAULT_REC_ALPHANUMERIC_DICT = "0123456789abcdefghijklmnopqrstuvwxyz".split(""); + +const REC_NORMALIZE: Readonly = Object.freeze({ + mean: [0.5, 0.5, 0.5], + std: [0.5, 0.5, 0.5], + scale: 1 / 255 +}); + +export const DEFAULT_REC_MODEL_PARSE_FALLBACKS: Readonly< + Pick +> = Object.freeze({ + imageShape: [3, 48, 320], + charDict: [] +}); + +export const DEFAULT_REC_RUNTIME_LIMITS = Object.freeze({}); + +const MAX_REC_WIDTH = 3200; + +export const DEFAULT_REC_MODEL_CONFIG: Readonly = Object.freeze({ + ...DEFAULT_REC_MODEL_PARSE_FALLBACKS +}); + +export function parseRecModelConfigText(text: string): RecModelConfig { + const parsed = parseInferenceConfigText(text); + const preProcess = parsed.PreProcess as Record | undefined; + const transformOps = preProcess?.transform_ops as Array> | undefined; + const resize = getTransformOp(transformOps, "RecResizeImg"); + const postprocess = (parsed.PostProcess || {}) as Record; + const baseCharDict = postprocess.character_dict; + + const imageShape = resize?.image_shape as number[] | undefined; + if (!imageShape || !Array.isArray(imageShape) || imageShape.length < 3) { + throw new Error("RecResizeImg.image_shape is required in rec inference.yml"); + } + + const charDict = + Array.isArray(baseCharDict) && baseCharDict.length > 0 + ? [...(baseCharDict as string[]), " "] + : [...DEFAULT_REC_ALPHANUMERIC_DICT, " "]; + + return { + imageShape, + charDict + }; +} + +interface CreateRecModelArgs { + ort: OrtModule; + modelBytes: Uint8Array; + configText: string; + backend: string; + webgpuState: WebGpuState; + batchSize?: number; +} + +export async function createRecModel({ + ort, + modelBytes, + configText, + backend, + webgpuState, + batchSize: batchSizeArg +}: CreateRecModelArgs): Promise { + assertModelResources("Recognition", { + model: modelBytes, + config: configText + }); + const config = parseRecModelConfigText(configText); + const defaultBatchSize = Math.max(1, batchSizeArg ?? 1); + let sessionState: SessionState | null = await createRecModelSession( + ort, + modelBytes, + backend, + webgpuState + ); + + return { + kind: "rec", + config, + get provider() { + return sessionState?.provider || ""; + }, + async predict(cv, mats, overrides) { + if (!sessionState?.session) { + throw new Error("Recognition model session is not initialized."); + } + const batchSize = resolveRuntimeBatchSize(overrides?.batchSize, defaultBatchSize); + const ctx = { cv, config }; + const samples = preprocess(ctx, mats); + const charDict = config.charDict; + const ordered = samples.slice().sort((a, b) => a.width - b.width); + const decoded: Array<{ inputIndex: number; text: string; score: number }> = []; + const targetH = config.imageShape[1]; + + for (const batch of chunkArray(ordered, batchSize)) { + const inputTensor = packRecBatchTensor(ort, batch, targetH); + const output = await runInference(sessionState.session, inputTensor); + const batchResults = postprocess(output, charDict); + for (let index = 0; index < batchResults.length; index += 1) { + decoded.push({ + inputIndex: batch[index].inputIndex, + ...batchResults[index] + }); + } + } + + decoded.sort((a, b) => a.inputIndex - b.inputIndex); + return decoded.map(({ text, score }) => ({ text, score })); + }, + async dispose() { + await releaseSessions(sessionState?.session); + sessionState = null; + } + }; +} + +export async function createRecModelSession( + ort: OrtModule, + modelBytes: Uint8Array, + backend: string, + webgpuState: WebGpuState +): Promise { + const providerCandidates = getProviderCandidates(backend, webgpuState); + return withTimeout( + createSession(ort, modelBytes, providerCandidates), + 60000, + "Recognition model" + ); +} + +function preprocess(context: { cv: OpenCv; config: RecModelConfig }, mats: Mat[]): RecSample[] { + const samples: RecSample[] = []; + for (let i = 0; i < mats.length; i += 1) { + samples.push(preprocessSample(context, mats[i], i)); + } + return samples; +} + +function preprocessSample( + context: { cv: OpenCv; config: RecModelConfig }, + cropMat: Mat, + inputIndex: number +): RecSample { + const { cv, config } = context; + const [channels, targetH, baseW] = config.imageShape; + const srcW = cropMat.cols; + const srcH = cropMat.rows; + if (channels !== 3) { + throw new Error(`Unexpected recognition channels: ${String(channels)}`); + } + const ratio = srcW / Math.max(1, srcH); + const maxWhRatio = Math.max(baseW / Math.max(1, targetH), ratio); + const recW = clamp(Math.trunc(targetH * maxWhRatio), 1, MAX_REC_WIDTH); + const resizedW = Math.min(recW, Math.ceil(targetH * ratio)); + const resized = new cv.Mat(); + const bgr = new cv.Mat(); + cv.resize(cropMat, resized, new cv.Size(resizedW, targetH), 0, 0, cv.INTER_LINEAR); + if (resized.channels() === 4) { + cv.cvtColor(resized, bgr, cv.COLOR_RGBA2BGR); + } else if (resized.channels() === 1) { + cv.cvtColor(resized, bgr, cv.COLOR_GRAY2BGR); + } else { + resized.copyTo(bgr); + } + const resizedChw = toBgrFloatCHWFromBgr(bgr.data, resizedW, targetH, REC_NORMALIZE); + const chw = new Float32Array(3 * targetH * recW); + const dstPerChannel = targetH * recW; + const srcPerChannel = targetH * resizedW; + for (let channel = 0; channel < 3; channel += 1) { + for (let row = 0; row < targetH; row += 1) { + const srcStart = channel * srcPerChannel + row * resizedW; + const dstStart = channel * dstPerChannel + row * recW; + chw.set(resizedChw.subarray(srcStart, srcStart + resizedW), dstStart); + } + } + bgr.delete(); + resized.delete(); + return { inputIndex, width: recW, chw }; +} + +function createBatchTensor( + ort: OrtModule, + samples: RecSample[], + maxW: number, + targetH: number +): Tensor { + const batch = samples.length; + const out = new Float32Array(batch * 3 * targetH * maxW); + const dstPerChannel = targetH * maxW; + for (let index = 0; index < batch; index += 1) { + const sample = samples[index]; + const srcW = sample.width; + const srcPerChannel = targetH * srcW; + for (let channel = 0; channel < 3; channel += 1) { + const srcBase = channel * srcPerChannel; + const dstBase = index * (3 * dstPerChannel) + channel * dstPerChannel; + for (let row = 0; row < targetH; row += 1) { + const srcStart = srcBase + row * srcW; + const dstStart = dstBase + row * maxW; + out.set(sample.chw.subarray(srcStart, srcStart + srcW), dstStart); + } + } + } + return new ort.Tensor("float32", out, [batch, 3, targetH, maxW]); +} + +function packRecBatchTensor(ort: OrtModule, samples: RecSample[], targetH: number): Tensor { + const maxW = samples.reduce((acc, sample) => Math.max(acc, sample.width), 1); + return createBatchTensor(ort, samples, maxW, targetH); +} + +function decodeCTCSample( + data: Float32Array, + offset: number, + timeSteps: number, + classes: number, + charDict: string[] +): { text: string; score: number } { + let prevIdx = -1; + let text = ""; + const probs: number[] = []; + for (let step = 0; step < timeSteps; step += 1) { + let maxIdx = 0; + let maxVal = -Infinity; + const stepOffset = offset + step * classes; + for (let cls = 0; cls < classes; cls += 1) { + const value = data[stepOffset + cls]; + if (value > maxVal) { + maxVal = value; + maxIdx = cls; + } + } + if (maxIdx > 0 && maxIdx !== prevIdx) { + const dictIdx = maxIdx - 1; + if (dictIdx >= 0 && dictIdx < charDict.length) { + text += charDict[dictIdx]; + probs.push(maxVal); + } + } + prevIdx = maxIdx; + } + const score = probs.length ? probs.reduce((a, b) => a + b, 0) / probs.length : 0; + return { text, score }; +} + +function postprocess(output: Tensor, charDict: string[]): Array<{ text: string; score: number }> { + const dims = output.dims; + if (dims.length !== 3) { + throw new Error(`Unexpected rec output dims: [${dims.join(", ")}]`); + } + const sampleCount = dims[0]; + const timeSteps = dims[1]; + const classes = dims[2]; + const data = output.data as Float32Array; + const stride = timeSteps * classes; + const results: Array<{ text: string; score: number }> = []; + for (let index = 0; index < sampleCount; index += 1) { + results.push(decodeCTCSample(data, index * stride, timeSteps, classes, charDict)); + } + return results; +} diff --git a/paddleocr-js/packages/core/src/pipelines/index.ts b/paddleocr-js/packages/core/src/pipelines/index.ts new file mode 100644 index 0000000..617b83f --- /dev/null +++ b/paddleocr-js/packages/core/src/pipelines/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export { PaddleOCR, parseOcrPipelineConfigText, normalizeOcrPipelineConfig } from "./ocr/index"; diff --git a/paddleocr-js/packages/core/src/pipelines/ocr/config.ts b/paddleocr-js/packages/core/src/pipelines/ocr/config.ts new file mode 100644 index 0000000..c2c5a09 --- /dev/null +++ b/paddleocr-js/packages/core/src/pipelines/ocr/config.ts @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import yaml from "js-yaml"; + +import type { ModelAsset } from "../../resources/model-asset"; +import { normalizeModelAsset } from "../../resources/model-asset"; +import type { LimitType } from "./runtime-params"; + +const SUPPORTED_PIPELINE_NAME = "OCR"; + +export interface NormalizedPipelineConfig { + pipelineName: string; + raw: Record; + warnings: string[]; + unsupportedFeatures: string[]; + modelSelection: PipelineModelSelection; + assets: Partial>; + runtimeDefaults: PipelineRuntimeDefaults; + pipelineBatchSize: number; + textDetectionBatchSize: number; + textRecognitionBatchSize: number; +} + +export interface PipelineModelSelection { + textDetectionModelName: string | null; + textRecognitionModelName: string | null; +} + +export interface PipelineRuntimeDefaults { + text_det_limit_side_len?: number; + text_det_limit_type?: LimitType; + text_det_max_side_limit?: number; + text_det_thresh?: number; + text_det_box_thresh?: number; + text_det_unclip_ratio?: number; + text_rec_score_thresh?: number; +} + +type YamlObject = Record; + +function isPlainObject(value: unknown): value is YamlObject { + return Boolean(value) && typeof value === "object" && !Array.isArray(value); +} + +export function toFiniteNumber(value: unknown): number | undefined { + if (value === null || value === undefined || value === "") { + return undefined; + } + const normalized = Number(value); + return Number.isFinite(normalized) ? normalized : undefined; +} + +function batchSizeOrOne(value: unknown): number { + const n = toFiniteNumber(value); + return n !== undefined && n >= 1 ? n : 1; +} + +function applyGeneralPipelineRuntimeDefaults( + textType: string, + runtimeDefaults: PipelineRuntimeDefaults +): PipelineRuntimeDefaults { + if (textType !== "general") { + return runtimeDefaults; + } + return { + text_det_limit_side_len: runtimeDefaults.text_det_limit_side_len ?? 960, + text_det_limit_type: runtimeDefaults.text_det_limit_type ?? "max", + text_det_max_side_limit: runtimeDefaults.text_det_max_side_limit ?? 4000, + text_det_thresh: runtimeDefaults.text_det_thresh ?? 0.3, + text_det_box_thresh: runtimeDefaults.text_det_box_thresh ?? 0.6, + text_det_unclip_ratio: runtimeDefaults.text_det_unclip_ratio ?? 2.0, + text_rec_score_thresh: runtimeDefaults.text_rec_score_thresh ?? 0 + }; +} + +function parsePipelineConfigInput(input: unknown): YamlObject { + if (typeof input === "string") { + const parsed = yaml.load(input); + if (!isPlainObject(parsed)) { + throw new Error("OCR pipeline config text must decode to an object."); + } + return parsed; + } + if (!isPlainObject(input)) { + throw new Error("OCR pipeline config must be an object or YAML text."); + } + return input; +} + +function addFeatureWarning(warnings: string[], featureName: string, reason?: string): void { + warnings.push( + `${featureName} is not yet supported in PaddleOCR.js${reason ? `: ${reason}` : ""}.` + ); +} + +function getModuleModelName(moduleConfig: YamlObject | null): string | null { + return typeof moduleConfig?.model_name === "string" ? moduleConfig.model_name : null; +} + +function validateModuleAsset(modulePath: string, modelName: string | null): void { + if (!modelName) { + throw new Error( + `${modulePath}.model_name must be provided when ${modulePath}.model_dir is set.` + ); + } +} + +function getModuleAsset( + assetName: string, + modulePath: string, + moduleConfig: YamlObject | null +): ModelAsset | null { + if (moduleConfig?.model_dir == null) { + return null; + } + if (isPlainObject(moduleConfig.model_dir)) { + const asset = normalizeModelAsset(assetName, moduleConfig.model_dir); + validateModuleAsset(modulePath, getModuleModelName(moduleConfig)); + return asset; + } + throw new Error( + `${modulePath}.model_dir must be null or an asset descriptor object in browser usage.` + ); +} + +export function parseOcrPipelineConfigText(text: string): YamlObject { + return parsePipelineConfigInput(text); +} + +export function normalizeOcrPipelineConfig(input: unknown): NormalizedPipelineConfig { + const config = parsePipelineConfigInput(input); + const pipelineName = (config.pipeline_name as string | undefined) ?? SUPPORTED_PIPELINE_NAME; + + if (pipelineName !== SUPPORTED_PIPELINE_NAME) { + throw new Error( + `Unsupported pipeline_name "${pipelineName}". PaddleOCR.js currently supports only "${SUPPORTED_PIPELINE_NAME}".` + ); + } + + const warnings: string[] = []; + const subModules = isPlainObject(config.SubModules) ? config.SubModules : {}; + const textDetection = isPlainObject(subModules.TextDetection) ? subModules.TextDetection : null; + const textRecognition = isPlainObject(subModules.TextRecognition) + ? subModules.TextRecognition + : null; + + if (!textDetection || !textRecognition) { + throw new Error( + 'OCR pipeline config must define both "SubModules.TextDetection" and "SubModules.TextRecognition".' + ); + } + + const useDocPreprocessor = Boolean(config.use_doc_preprocessor); + const useTextlineOrientation = Boolean(config.use_textline_orientation); + const subPipelines = config.SubPipelines as YamlObject | undefined; + const docPreprocessor = isPlainObject(subPipelines?.DocPreprocessor) + ? subPipelines.DocPreprocessor + : null; + const textLineOrientation = isPlainObject(subModules.TextLineOrientation) + ? subModules.TextLineOrientation + : null; + + if (useDocPreprocessor || docPreprocessor) { + addFeatureWarning(warnings, "DocPreprocessor", "config will be ignored for now"); + } + if (useTextlineOrientation || textLineOrientation) { + addFeatureWarning(warnings, "TextLineOrientation", "config will be ignored for now"); + } + const textType = + typeof config.text_type === "string" && config.text_type.length > 0 + ? config.text_type + : "general"; + + if (config.text_type && config.text_type !== "general") { + warnings.push(`text_type ${JSON.stringify(config.text_type)} is not used by PaddleOCR.js yet.`); + } + + const detAsset = getModuleAsset("det", "SubModules.TextDetection", textDetection); + const recAsset = getModuleAsset("rec", "SubModules.TextRecognition", textRecognition); + + const pipelineBatchSize = batchSizeOrOne(config.batch_size); + const textDetectionBatchSize = batchSizeOrOne(textDetection.batch_size); + const textRecognitionBatchSizeFromModule = batchSizeOrOne(textRecognition.batch_size); + + return { + pipelineName, + raw: config, + warnings, + unsupportedFeatures: [ + ...(useDocPreprocessor || docPreprocessor ? ["DocPreprocessor"] : []), + ...(useTextlineOrientation || textLineOrientation ? ["TextLineOrientation"] : []) + ], + modelSelection: { + textDetectionModelName: getModuleModelName(textDetection), + textRecognitionModelName: getModuleModelName(textRecognition) + }, + assets: { + ...(detAsset ? { det: detAsset } : {}), + ...(recAsset ? { rec: recAsset } : {}) + }, + runtimeDefaults: applyGeneralPipelineRuntimeDefaults(textType, { + text_det_limit_side_len: toFiniteNumber(textDetection.limit_side_len), + text_det_limit_type: (textDetection.limit_type as LimitType | undefined) || undefined, + text_det_max_side_limit: toFiniteNumber(textDetection.max_side_limit), + text_det_thresh: toFiniteNumber(textDetection.thresh), + text_det_box_thresh: toFiniteNumber(textDetection.box_thresh), + text_det_unclip_ratio: toFiniteNumber(textDetection.unclip_ratio), + text_rec_score_thresh: toFiniteNumber(textRecognition.score_thresh) + }), + pipelineBatchSize, + textDetectionBatchSize, + textRecognitionBatchSize: textRecognitionBatchSizeFromModule + }; +} diff --git a/paddleocr-js/packages/core/src/pipelines/ocr/core.ts b/paddleocr-js/packages/core/src/pipelines/ocr/core.ts new file mode 100644 index 0000000..280f014 --- /dev/null +++ b/paddleocr-js/packages/core/src/pipelines/ocr/core.ts @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { OpenCv, Mat } from "@techstark/opencv-js"; +import type { ModelAsset, ModelLoadSummary } from "../../resources/model-asset"; +import { loadModelAsset } from "../../resources/index"; +import { createDetModel, createRecModel } from "../../models/index"; +import type { DetModel } from "../../models/det"; +import type { RecModel } from "../../models/rec"; +import type { Point2D } from "../../models/common"; +import { cropByPoly } from "./crop"; +import { initOpenCvRuntime } from "../../runtime/opencv"; +import { initOrtRuntime } from "../../runtime/ort"; +import type { OrtModule, WebGpuState, OrtOptions } from "../../runtime/ort"; +import { chunkArray, nowMs } from "../../utils/common"; +import type { OcrModelConfig, OcrRuntimeParamsInput } from "./runtime-params"; +import { getOcrRuntimeParams } from "./runtime-params"; +import type { NormalizedPipelineConfig } from "./config"; +import { cloneDefaultOcrConfig, validateLoadedModelName } from "./shared"; +import type { NormalizedOrtOptions } from "./shared"; +import type { SourceMatResult } from "../../platform/browser"; + +export interface OcrResultItem { + poly: Point2D[]; + text: string; + score: number; +} + +export interface OcrResultMetrics { + detMs: number; + recMs: number; + totalMs: number; + detectedBoxes: number; + recognizedCount: number; +} + +export interface OcrResultRuntime { + requestedBackend: string; + detProvider: string; + recProvider: string; + webgpuAvailable: boolean; +} + +export interface OcrResult { + image: { width: number; height: number }; + items: OcrResultItem[]; + metrics: OcrResultMetrics; + runtime: OcrResultRuntime; +} + +export interface InitializationSummary { + backend: string; + webgpuAvailable: boolean; + detProvider: string; + recProvider: string; + assets: ModelLoadSummary[]; + elapsedMs: number; + pipelineConfigWarnings: string[]; +} + +export type SourceToMatFn = ( + cv: OpenCv, + source: unknown +) => SourceMatResult | Promise; +type EnsureServedFromHttpFn = () => void; + +export interface OcrPipelineRunnerOptions { + pipelineConfig: NormalizedPipelineConfig; + ortOptions?: OrtOptions | NormalizedOrtOptions; + fetch?: typeof fetch; + ensureServedFromHttp?: EnsureServedFromHttpFn; + sourceToMat?: SourceToMatFn; +} + +function noopEnsureServedFromHttp(): void {} + +function getResolvedAssets(assets: Partial> | undefined): { + det: ModelAsset; + rec: ModelAsset; +} { + const det = assets?.det; + const rec = assets?.rec; + if (!det || typeof det !== "object" || !rec || typeof rec !== "object") { + throw new Error( + "PaddleOCRCore requires pre-resolved detection and recognition asset descriptors." + ); + } + return { det, rec }; +} + +export class OcrPipelineRunner { + protected options: OcrPipelineRunnerOptions; + protected modelConfig: OcrModelConfig; + protected runtimeDefaults: Partial; + protected cv: OpenCv | null; + protected ort: OrtModule | null; + protected detModel: DetModel | null; + protected recModel: RecModel | null; + protected webgpuState: WebGpuState; + protected pipelineConfig: NormalizedPipelineConfig; + protected lastInitializationSummary: InitializationSummary | null; + private ensureServedFromHttp: EnsureServedFromHttpFn; + private sourceToMat: SourceToMatFn | undefined; + + constructor(options: OcrPipelineRunnerOptions) { + this.options = options; + this.modelConfig = cloneDefaultOcrConfig(); + this.pipelineConfig = options.pipelineConfig; + this.runtimeDefaults = { ...options.pipelineConfig.runtimeDefaults }; + this.cv = null; + this.ort = null; + this.detModel = null; + this.recModel = null; + this.webgpuState = { available: false, reason: "" }; + this.lastInitializationSummary = null; + this.ensureServedFromHttp = options.ensureServedFromHttp || noopEnsureServedFromHttp; + this.sourceToMat = options.sourceToMat; + } + + async initialize(): Promise { + this.ensureServedFromHttp(); + const start = nowMs(); + const { cv } = await initOpenCvRuntime(); + this.cv = cv; + const { ort, webgpuState, backend } = await initOrtRuntime(this.options.ortOptions || {}); + this.ort = ort; + this.webgpuState = webgpuState; + + const assets = getResolvedAssets(this.pipelineConfig.assets); + const fetchImpl = this.options.fetch || fetch; + const loadedAssets = await Promise.all([ + loadModelAsset(assets.det, fetchImpl), + loadModelAsset(assets.rec, fetchImpl) + ]); + validateLoadedModelName( + "TextDetection", + this.pipelineConfig.modelSelection.textDetectionModelName, + loadedAssets[0].configText + ); + validateLoadedModelName( + "TextRecognition", + this.pipelineConfig.modelSelection.textRecognitionModelName, + loadedAssets[1].configText + ); + await this.disposeModelsOnly(); + const detBatchSize = this.pipelineConfig.textDetectionBatchSize; + const recBatchSize = this.pipelineConfig.textRecognitionBatchSize; + + const [detModel, recModel] = await Promise.all([ + createDetModel({ + ort: this.ort, + modelBytes: loadedAssets[0].modelBytes, + configText: loadedAssets[0].configText, + backend, + webgpuState, + batchSize: detBatchSize + }), + createRecModel({ + ort: this.ort, + modelBytes: loadedAssets[1].modelBytes, + configText: loadedAssets[1].configText, + backend, + webgpuState, + batchSize: recBatchSize + }) + ]); + this.detModel = detModel; + this.recModel = recModel; + this.modelConfig = { + det: this.detModel.config, + rec: this.recModel.config + }; + + const elapsed = nowMs() - start; + this.lastInitializationSummary = { + backend, + webgpuAvailable: webgpuState.available, + detProvider: this.detModel.provider, + recProvider: this.recModel.provider, + assets: loadedAssets.map((asset) => asset.download), + elapsedMs: elapsed, + pipelineConfigWarnings: this.pipelineConfig.warnings + }; + return this.lastInitializationSummary; + } + + getInitializationSummary(): InitializationSummary | null { + return this.lastInitializationSummary; + } + + getModelConfig(): OcrModelConfig { + return this.modelConfig; + } + + async predict(input: unknown, params: OcrRuntimeParamsInput = {}): Promise { + if (!this.sourceToMat) { + throw new Error("PaddleOCR source adapter is not configured."); + } + if (!this.detModel || !this.recModel || !this.cv || !this.ort) { + await this.initialize(); + } + + const cv = this.cv; + const detModel = this.detModel; + const recModel = this.recModel; + if (!cv || !detModel || !recModel) { + throw new Error("Initialization did not complete. Call initialize() first."); + } + + const sources = Array.isArray(input) ? input : [input]; + const sourceToMat = this.sourceToMat; + const pipelineBatchSize = Math.max(1, Math.floor(this.pipelineConfig.pipelineBatchSize) || 1); + const sourceBatches = chunkArray(sources, pipelineBatchSize); + + const totalStart = nowMs(); + const resolved = getOcrRuntimeParams(this.modelConfig, this.runtimeDefaults, params); + + let sumDetMs = 0; + let sumRecMs = 0; + const partials: Array<{ + image: { width: number; height: number }; + items: OcrResultItem[]; + detectedBoxes: number; + recognizedCount: number; + }> = []; + + for (const batchSources of sourceBatches) { + const sourceImages = await Promise.all( + batchSources.map((source) => Promise.resolve(sourceToMat(cv, source))) + ); + try { + const detStart = nowMs(); + const detResults = await detModel.predict( + cv, + sourceImages.map((s) => s.mat), + resolved.det + ); + sumDetMs += nowMs() - detStart; + + const recStart = nowMs(); + const perImageItems: OcrResultItem[][] = []; + + for (let imgIdx = 0; imgIdx < detResults.length; imgIdx += 1) { + const detBoxes = detResults[imgIdx]?.boxes ?? []; + const cropMats: Mat[] = []; + for (let boxIdx = 0; boxIdx < detBoxes.length; boxIdx += 1) { + cropMats.push(cropByPoly(cv, sourceImages[imgIdx].mat, detBoxes[boxIdx].poly)); + } + + try { + const recResults = cropMats.length ? await recModel.predict(cv, cropMats) : []; + const items: OcrResultItem[] = []; + for (let boxIdx = 0; boxIdx < recResults.length; boxIdx += 1) { + const rec = recResults[boxIdx]; + if (rec.text && rec.score >= resolved.pipeline.scoreThresh) { + items.push({ + poly: detBoxes[boxIdx].poly, + text: rec.text, + score: rec.score + }); + } + } + perImageItems.push(items); + } finally { + for (const mat of cropMats) { + mat.delete(); + } + } + } + + sumRecMs += nowMs() - recStart; + + for (let i = 0; i < sourceImages.length; i += 1) { + const sourceImage = sourceImages[i]; + const detBoxes = detResults[i]?.boxes ?? []; + const items = perImageItems[i] ?? []; + partials.push({ + image: { + width: sourceImage.width, + height: sourceImage.height + }, + items, + detectedBoxes: detBoxes.length, + recognizedCount: items.length + }); + } + } finally { + for (const sourceImage of sourceImages) { + sourceImage.dispose(); + } + } + } + + const totalElapsed = nowMs() - totalStart; + const requestedBackend = + (this.options.ortOptions as NormalizedOrtOptions | undefined)?.backend ?? "auto"; + + return partials.map( + (p): OcrResult => ({ + image: p.image, + items: p.items, + metrics: { + detMs: sumDetMs, + recMs: sumRecMs, + totalMs: totalElapsed, + detectedBoxes: p.detectedBoxes, + recognizedCount: p.recognizedCount + }, + runtime: { + requestedBackend, + detProvider: detModel.provider, + recProvider: recModel.provider, + webgpuAvailable: this.webgpuState.available + } + }) + ); + } + + async disposeModelsOnly(): Promise { + await Promise.all([this.detModel?.dispose(), this.recModel?.dispose()]); + this.detModel = null; + this.recModel = null; + } + + async dispose(): Promise { + await this.disposeModelsOnly(); + } +} + +export { OcrPipelineRunner as PaddleOCRCore }; diff --git a/paddleocr-js/packages/core/src/pipelines/ocr/crop.ts b/paddleocr-js/packages/core/src/pipelines/ocr/crop.ts new file mode 100644 index 0000000..97d8c0b --- /dev/null +++ b/paddleocr-js/packages/core/src/pipelines/ocr/crop.ts @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { OpenCv, Mat } from "@techstark/opencv-js"; + +import { getMiniBoxFromPoints } from "../../models/common"; +import type { Point2D } from "../../models/common"; + +export function cropByPoly(cv: OpenCv, srcMat: Mat, poly: Point2D[]): Mat { + const ordered = getMiniBoxFromPoints(cv, poly).box; + const widthTop = Math.hypot(ordered[1][0] - ordered[0][0], ordered[1][1] - ordered[0][1]); + const widthBottom = Math.hypot(ordered[2][0] - ordered[3][0], ordered[2][1] - ordered[3][1]); + const heightLeft = Math.hypot(ordered[3][0] - ordered[0][0], ordered[3][1] - ordered[0][1]); + const heightRight = Math.hypot(ordered[2][0] - ordered[1][0], ordered[2][1] - ordered[1][1]); + const cropW = Math.max(1, Math.floor(Math.max(widthTop, widthBottom))); + const cropH = Math.max(1, Math.floor(Math.max(heightLeft, heightRight))); + + const srcTri = cv.matFromArray(4, 1, cv.CV_32FC2, [ + ordered[0][0], + ordered[0][1], + ordered[1][0], + ordered[1][1], + ordered[2][0], + ordered[2][1], + ordered[3][0], + ordered[3][1] + ]); + const dstTri = cv.matFromArray(4, 1, cv.CV_32FC2, [0, 0, cropW, 0, cropW, cropH, 0, cropH]); + const transform = cv.getPerspectiveTransform(srcTri, dstTri); + const warped = new cv.Mat(); + cv.warpPerspective( + srcMat, + warped, + transform, + new cv.Size(cropW, cropH), + cv.INTER_CUBIC, + cv.BORDER_REPLICATE, + new cv.Scalar() + ); + srcTri.delete(); + dstTri.delete(); + transform.delete(); + + if (warped.rows / Math.max(1, warped.cols) >= 1.5) { + const rotated = new cv.Mat(); + cv.rotate(warped, rotated, cv.ROTATE_90_COUNTERCLOCKWISE); + warped.delete(); + return rotated; + } + return warped; +} diff --git a/paddleocr-js/packages/core/src/pipelines/ocr/default-config.ts b/paddleocr-js/packages/core/src/pipelines/ocr/default-config.ts new file mode 100644 index 0000000..130c1fb --- /dev/null +++ b/paddleocr-js/packages/core/src/pipelines/ocr/default-config.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export const DEFAULT_OCR_PIPELINE_CONFIG_TEXT = ` +pipeline_name: OCR + +text_type: general + +use_doc_preprocessor: False +use_textline_orientation: False + +SubPipelines: + DocPreprocessor: + pipeline_name: doc_preprocessor + use_doc_orientation_classify: False + use_doc_unwarping: False + SubModules: + DocOrientationClassify: + module_name: doc_text_orientation + model_name: PP-LCNet_x1_0_doc_ori + model_dir: null + DocUnwarping: + module_name: image_unwarping + model_name: UVDoc + model_dir: null + +SubModules: + TextDetection: + module_name: text_detection + model_name: PP-OCRv5_mobile_det + model_dir: null + limit_side_len: 64 + limit_type: min + max_side_limit: 4000 + thresh: 0.3 + box_thresh: 0.6 + unclip_ratio: 1.5 + TextLineOrientation: + module_name: textline_orientation + model_name: PP-LCNet_x1_0_textline_ori + model_dir: null + batch_size: 6 + TextRecognition: + module_name: text_recognition + model_name: PP-OCRv5_mobile_rec + model_dir: null + batch_size: 6 + score_thresh: 0.0 +`.trimStart(); diff --git a/paddleocr-js/packages/core/src/pipelines/ocr/index.ts b/paddleocr-js/packages/core/src/pipelines/ocr/index.ts new file mode 100644 index 0000000..10bbafa --- /dev/null +++ b/paddleocr-js/packages/core/src/pipelines/ocr/index.ts @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { normalizeOcrPipelineConfig, parseOcrPipelineConfigText } from "./config"; +import { ensureServedFromHttp, sourceToMat } from "../../platform/browser"; +import type { OcrPipelineRunnerOptions } from "./core"; +import { OcrPipelineRunner } from "./core"; +import { resolvePaddleOCROptions, resolveWorkerOptions } from "./shared"; +import { createWorkerBackedPaddleOCR } from "./worker-backed"; +import type { WorkerBackedPaddleOCR } from "./worker-backed"; +import type { OrtOptions } from "../../runtime/ort"; +import type { ModelAsset } from "../../resources/model-asset"; +import type { LimitType } from "./runtime-params"; + +export interface PaddleOCRCreateOptions { + worker?: boolean | { createWorker?: () => Worker }; + fetch?: typeof fetch; + initialize?: boolean; + ortOptions?: OrtOptions; + + pipelineConfig?: unknown; + unsupportedBehavior?: "warn" | "ignore" | "error"; + + lang?: string; + ocrVersion?: string; + ocr_version?: string; + + textDetectionModelName?: string; + text_detection_model_name?: string; + textRecognitionModelName?: string; + text_recognition_model_name?: string; + + textDetectionModelAsset?: ModelAsset; + textDetectionModelDir?: ModelAsset; + text_detection_model_dir?: ModelAsset; + textRecognitionModelAsset?: ModelAsset; + textRecognitionModelDir?: ModelAsset; + text_recognition_model_dir?: ModelAsset; + + textDetectionBatchSize?: number; + text_detection_batch_size?: number; + textRecognitionBatchSize?: number; + text_recognition_batch_size?: number; + batch_size?: number; + + textDetLimitSideLen?: number; + text_det_limit_side_len?: number; + textDetLimitType?: LimitType; + text_det_limit_type?: LimitType; + textDetMaxSideLimit?: number; + text_det_max_side_limit?: number; + textDetThresh?: number; + text_det_thresh?: number; + textDetBoxThresh?: number; + text_det_box_thresh?: number; + textDetUnclipRatio?: number; + text_det_unclip_ratio?: number; + textRecScoreThresh?: number; + text_rec_score_thresh?: number; + + [key: string]: unknown; +} + +export class PaddleOCR extends OcrPipelineRunner { + constructor(options: OcrPipelineRunnerOptions) { + super({ + ...options, + ensureServedFromHttp, + sourceToMat + }); + } + + static async create( + options: PaddleOCRCreateOptions = {} + ): Promise { + const workerOptions = resolveWorkerOptions(options.worker); + if (workerOptions.enabled && options.fetch) { + throw new Error("worker mode does not support a custom fetch implementation."); + } + + const resolvedOptions = resolvePaddleOCROptions(options); + const instance = workerOptions.enabled + ? createWorkerBackedPaddleOCR(resolvedOptions, { + createWorker: workerOptions.createWorker ?? undefined + }) + : new PaddleOCR({ + ...resolvedOptions, + fetch: options.fetch + }); + + if (options.initialize !== false) { + await instance.initialize(); + } + return instance; + } +} + +export { normalizeOcrPipelineConfig, parseOcrPipelineConfigText }; diff --git a/paddleocr-js/packages/core/src/pipelines/ocr/runtime-params.ts b/paddleocr-js/packages/core/src/pipelines/ocr/runtime-params.ts new file mode 100644 index 0000000..d038c92 --- /dev/null +++ b/paddleocr-js/packages/core/src/pipelines/ocr/runtime-params.ts @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { DetModelConfig, DetRuntimeOverrides, LimitType } from "../../models/det"; +import type { RecModelConfig } from "../../models/rec"; + +export type { LimitType }; + +export interface OcrModelConfig { + det: DetModelConfig; + rec: RecModelConfig; +} + +export interface ResolvedOcrParams { + det: DetRuntimeOverrides; + pipeline: { scoreThresh: number }; +} + +export interface OcrRuntimeParamsInput { + text_det_limit_side_len?: number; + textDetLimitSideLen?: number; + text_det_limit_type?: LimitType; + textDetLimitType?: LimitType; + text_det_max_side_limit?: number; + textDetMaxSideLimit?: number; + text_det_thresh?: number; + textDetThresh?: number; + text_det_box_thresh?: number; + textDetBoxThresh?: number; + text_det_unclip_ratio?: number; + textDetUnclipRatio?: number; + text_rec_score_thresh?: number; + textRecScoreThresh?: number; +} + +function firstDefined(...values: Array): T | undefined { + for (const value of values) { + if (value !== undefined && value !== null) { + return value; + } + } + return undefined; +} + +function toNumberOrUndefined(value: unknown): number | undefined { + if (value === undefined || value === null) return undefined; + const num = Number(value); + return Number.isFinite(num) ? num : undefined; +} + +export function getOcrRuntimeParams( + config: OcrModelConfig, + defaults: Partial = {}, + params: OcrRuntimeParamsInput = {} +): ResolvedOcrParams { + return { + det: { + limitSideLen: toNumberOrUndefined( + firstDefined( + params.text_det_limit_side_len, + params.textDetLimitSideLen, + defaults.text_det_limit_side_len, + defaults.textDetLimitSideLen, + config.det.resizeLong + ) + ), + limitType: firstDefined( + params.text_det_limit_type, + params.textDetLimitType, + defaults.text_det_limit_type, + defaults.textDetLimitType, + config.det.limitType + ), + maxSideLimit: toNumberOrUndefined( + firstDefined( + params.text_det_max_side_limit, + params.textDetMaxSideLimit, + defaults.text_det_max_side_limit, + defaults.textDetMaxSideLimit, + config.det.maxSideLimit + ) + ), + thresh: toNumberOrUndefined( + firstDefined( + params.text_det_thresh, + params.textDetThresh, + defaults.text_det_thresh, + defaults.textDetThresh, + config.det.postprocess.thresh + ) + ), + boxThresh: toNumberOrUndefined( + firstDefined( + params.text_det_box_thresh, + params.textDetBoxThresh, + defaults.text_det_box_thresh, + defaults.textDetBoxThresh, + config.det.postprocess.boxThresh + ) + ), + unclipRatio: toNumberOrUndefined( + firstDefined( + params.text_det_unclip_ratio, + params.textDetUnclipRatio, + defaults.text_det_unclip_ratio, + defaults.textDetUnclipRatio, + config.det.postprocess.unclipRatio + ) + ) + }, + pipeline: { + scoreThresh: Number( + firstDefined( + params.text_rec_score_thresh, + params.textRecScoreThresh, + defaults.text_rec_score_thresh, + defaults.textRecScoreThresh, + 0 + ) + ) + } + }; +} diff --git a/paddleocr-js/packages/core/src/pipelines/ocr/shared.ts b/paddleocr-js/packages/core/src/pipelines/ocr/shared.ts new file mode 100644 index 0000000..d83dd96 --- /dev/null +++ b/paddleocr-js/packages/core/src/pipelines/ocr/shared.ts @@ -0,0 +1,637 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { ModelAsset } from "../../resources/model-asset"; +import { DEFAULT_MODEL_ASSETS } from "../../resources/model-asset"; +import { DEFAULT_DET_MODEL_CONFIG } from "../../models/det"; +import { DEFAULT_REC_MODEL_CONFIG } from "../../models/rec"; +import { extractInferenceModelName } from "../../models/common"; +import { deepClone } from "../../utils/common"; +import type { + NormalizedPipelineConfig, + PipelineModelSelection, + PipelineRuntimeDefaults +} from "./config"; +import { normalizeOcrPipelineConfig, toFiniteNumber } from "./config"; +import type { LimitType } from "./runtime-params"; +import { DEFAULT_OCR_PIPELINE_CONFIG_TEXT } from "./default-config"; +import type { OcrModelConfig } from "./runtime-params"; +import type { OrtOptions } from "../../runtime/ort"; + +export interface ResolvedOcrOptions { + pipelineConfig: NormalizedPipelineConfig; + ortOptions: NormalizedOrtOptions; +} + +export type ResolvedBackend = "webgpu" | "wasm" | "auto"; + +export interface NormalizedOrtOptions { + backend: ResolvedBackend; + wasmPaths?: string; + numThreads?: number; + simd?: boolean; + proxy?: boolean; +} + +export interface WorkerResolvedOptions { + enabled: boolean; + createWorker: (() => Worker) | null; +} + +export const DEFAULT_OCR_CONFIG: OcrModelConfig = { + det: DEFAULT_DET_MODEL_CONFIG, + rec: DEFAULT_REC_MODEL_CONFIG +}; + +interface ModelRole { + assetKey: string; + modelRole: string; + selectionKey: keyof PipelineModelSelection; + nameAliases: string[]; + assetAliases: string[]; + nameLabel: string; + assetLabel: string; + assetRequirementError: string; +} + +const DEFAULT_NORMALIZED_PIPELINE_CONFIG = normalizeOcrPipelineConfig( + DEFAULT_OCR_PIPELINE_CONFIG_TEXT +); +const DEFAULT_MODEL_SELECTION: Readonly = Object.freeze({ + ...DEFAULT_NORMALIZED_PIPELINE_CONFIG.modelSelection +}); +const DEFAULT_LANG_VERSION_MODEL_SELECTION: Readonly = Object.freeze({ + ...DEFAULT_MODEL_SELECTION +}); +const PP_OCRV6_LANG_VERSION_MODEL_SELECTION: Readonly = Object.freeze({ + textDetectionModelName: "PP-OCRv6_small_det", + textRecognitionModelName: "PP-OCRv6_small_rec" +}); +const _LATIN_LANGS = new Set([ + "af", + "az", + "bs", + "cs", + "cy", + "da", + "de", + "es", + "et", + "fr", + "ga", + "hr", + "hu", + "id", + "is", + "it", + "ku", + "la", + "lt", + "lv", + "mi", + "ms", + "mt", + "nl", + "no", + "oc", + "pi", + "pl", + "pt", + "ro", + "rs_latin", + "sk", + "sl", + "sq", + "sv", + "sw", + "tl", + "tr", + "uz", + "vi", + "french", + "german", + "fi", + "eu", + "gl", + "lb", + "rm", + "ca", + "qu" +]); +const _PPOCRV6_UNSUPPORTED_LATIN_LANGS = new Set(["pi"]); +const _PPOCRV6_LANGS = new Set([ + "ch", + "chinese_cht", + "en", + "japan", + ...[..._LATIN_LANGS].filter((lang) => !_PPOCRV6_UNSUPPORTED_LATIN_LANGS.has(lang)) +]); + +function isPpOcrV6Lang(lang: string): boolean { + return _PPOCRV6_LANGS.has(lang); +} +const OCR_MODEL_ROLES: Readonly = Object.freeze([ + { + assetKey: "det", + modelRole: "TextDetection", + selectionKey: "textDetectionModelName", + nameAliases: ["text_detection_model_name", "textDetectionModelName"], + assetAliases: ["textDetectionModelAsset", "text_detection_model_dir", "textDetectionModelDir"], + nameLabel: "text detection model name", + assetLabel: "text detection model asset", + assetRequirementError: "text_detection_model_dir requires text_detection_model_name." + }, + { + assetKey: "rec", + modelRole: "TextRecognition", + selectionKey: "textRecognitionModelName", + nameAliases: ["text_recognition_model_name", "textRecognitionModelName"], + assetAliases: [ + "textRecognitionModelAsset", + "text_recognition_model_dir", + "textRecognitionModelDir" + ], + nameLabel: "text recognition model name", + assetLabel: "text recognition model asset", + assetRequirementError: "text_recognition_model_dir requires text_recognition_model_name." + } +]); + +const SUPPORTED_LANG_VERSION_MODELS = new Map>([ + ["ch::PP-OCRv5", DEFAULT_LANG_VERSION_MODEL_SELECTION], + ["chinese_cht::PP-OCRv5", DEFAULT_LANG_VERSION_MODEL_SELECTION], + ["en::PP-OCRv5", DEFAULT_LANG_VERSION_MODEL_SELECTION], + ["japan::PP-OCRv5", DEFAULT_LANG_VERSION_MODEL_SELECTION] +]); + +function readAliasedOption( + options: Record, + aliases: string[], + label: string +): unknown { + let resolved: unknown; + let hasResolvedValue = false; + + for (const alias of aliases) { + if (!(alias in options)) continue; + const value = options[alias]; + if (!hasResolvedValue) { + resolved = value; + hasResolvedValue = true; + continue; + } + if (value !== resolved) { + throw new Error(`Conflicting values provided for ${label}: ${aliases.join(", ")}.`); + } + } + + return hasResolvedValue ? resolved : undefined; +} + +function isLimitType(value: unknown): value is LimitType { + return value === "min" || value === "max"; +} + +function overlayPipelineRuntimeDefaults( + base: PipelineRuntimeDefaults, + explicit: Partial +): PipelineRuntimeDefaults { + const next: Record = { ...base }; + for (const key of Object.keys(explicit) as Array) { + const value = explicit[key]; + if (value === undefined) continue; + next[key as string] = value as unknown; + } + return next as PipelineRuntimeDefaults; +} + +function readExplicitPipelineRuntimeDefaults( + options: Record +): Partial { + const out: Partial = {}; + + const limitSide = readAliasedOption( + options, + ["text_det_limit_side_len", "textDetLimitSideLen"], + "text_det_limit_side_len" + ); + if (limitSide !== undefined) { + const n = toFiniteNumber(limitSide); + if (n !== undefined) out.text_det_limit_side_len = n; + } + + const limitType = readAliasedOption( + options, + ["text_det_limit_type", "textDetLimitType"], + "text_det_limit_type" + ); + if (limitType !== undefined && isLimitType(limitType)) { + out.text_det_limit_type = limitType; + } + + const maxSide = readAliasedOption( + options, + ["text_det_max_side_limit", "textDetMaxSideLimit"], + "text_det_max_side_limit" + ); + if (maxSide !== undefined) { + const n = toFiniteNumber(maxSide); + if (n !== undefined) out.text_det_max_side_limit = n; + } + + const detThresh = readAliasedOption( + options, + ["text_det_thresh", "textDetThresh"], + "text_det_thresh" + ); + if (detThresh !== undefined) { + const n = toFiniteNumber(detThresh); + if (n !== undefined) out.text_det_thresh = n; + } + + const boxThresh = readAliasedOption( + options, + ["text_det_box_thresh", "textDetBoxThresh"], + "text_det_box_thresh" + ); + if (boxThresh !== undefined) { + const n = toFiniteNumber(boxThresh); + if (n !== undefined) out.text_det_box_thresh = n; + } + + const unclip = readAliasedOption( + options, + ["text_det_unclip_ratio", "textDetUnclipRatio"], + "text_det_unclip_ratio" + ); + if (unclip !== undefined) { + const n = toFiniteNumber(unclip); + if (n !== undefined) out.text_det_unclip_ratio = n; + } + + const recScore = readAliasedOption( + options, + ["text_rec_score_thresh", "textRecScoreThresh"], + "text_rec_score_thresh" + ); + if (recScore !== undefined) { + const n = toFiniteNumber(recScore); + if (n !== undefined) out.text_rec_score_thresh = n; + } + + return out; +} + +function toBatchSizeOption(value: unknown): number | undefined { + const n = toFiniteNumber(value); + return n !== undefined && n >= 1 ? Math.floor(n) : undefined; +} + +function readExplicitBatchSizes(options: Record): { + det: number | undefined; + rec: number | undefined; + pipeline: number | undefined; +} { + return { + det: toBatchSizeOption( + readAliasedOption( + options, + ["textDetectionBatchSize", "text_detection_batch_size"], + "textDetectionBatchSize" + ) + ), + rec: toBatchSizeOption( + readAliasedOption( + options, + ["textRecognitionBatchSize", "text_recognition_batch_size"], + "textRecognitionBatchSize" + ) + ), + pipeline: toBatchSizeOption( + readAliasedOption( + options, + ["pipelineBatchSize", "pipeline_batch_size", "batch_size"], + "pipelineBatchSize" + ) + ) + }; +} + +function mergeNormalizedPipelineConfigWithExplicit( + normalized: NormalizedPipelineConfig, + options: Record +): NormalizedPipelineConfig { + const explicitRuntime = readExplicitPipelineRuntimeDefaults(options); + const explicitBatch = readExplicitBatchSizes(options); + const merged = deepClone(normalized); + merged.runtimeDefaults = overlayPipelineRuntimeDefaults(merged.runtimeDefaults, explicitRuntime); + if (explicitBatch.det !== undefined) { + merged.textDetectionBatchSize = explicitBatch.det; + } + if (explicitBatch.rec !== undefined) { + merged.textRecognitionBatchSize = explicitBatch.rec; + } + if (explicitBatch.pipeline !== undefined) { + merged.pipelineBatchSize = explicitBatch.pipeline; + } + return merged; +} + +function resolveWarningBehavior(value: unknown): "warn" | "ignore" | "error" { + if (value === "ignore" || value === "error") return value; + return "warn"; +} + +function emitPipelineWarnings(warnings: string[], behavior: "warn" | "ignore" | "error"): void { + if (!warnings.length || behavior === "ignore") return; + if (behavior === "error") { + throw new Error(warnings.join(" ")); + } + for (const warning of warnings) { + console.warn(`[PaddleOCR.js] ${warning}`); + } +} + +function resolveModelAssetByName(_modelRole: string, modelName: string): ModelAsset { + const asset = DEFAULT_MODEL_ASSETS[modelName]; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for missing Record key + if (!asset) { + throw new Error(`Unknown model asset "${modelName}".`); + } + return { url: asset.url }; +} + +function getSelectedModelName( + baseSelection: PipelineModelSelection | null, + configSelection: PipelineModelSelection | null, + explicitSelection: Record | null, + selectionKey: keyof PipelineModelSelection +): string | null { + return ( + explicitSelection?.[selectionKey] ?? + configSelection?.[selectionKey] ?? + baseSelection?.[selectionKey] ?? + null + ); +} + +function createResolvedModelSelection( + baseSelection: PipelineModelSelection | null, + configSelection: PipelineModelSelection | null, + explicitSelection: Record | null +): PipelineModelSelection { + return Object.fromEntries( + OCR_MODEL_ROLES.map((role) => [ + role.selectionKey, + getSelectedModelName(baseSelection, configSelection, explicitSelection, role.selectionKey) + ]) + ) as unknown as PipelineModelSelection; +} + +export function validateLoadedModelName( + modelRole: string, + expectedModelName: string | null | undefined, + configText: string +): void { + if (!expectedModelName) { + throw new Error(`${modelRole} model selection must define model_name.`); + } + const declaredModelName = extractInferenceModelName(configText); + if (!declaredModelName) { + throw new Error(`${modelRole} in inference.yml must define model_name.`); + } + if (declaredModelName !== expectedModelName) { + throw new Error( + `${modelRole} in inference.yml declares model_name "${declaredModelName}" but requested model_name is "${expectedModelName}".` + ); + } +} + +function resolveSelectedAsset( + assetRole: string, + modelRole: string, + selectionKey: keyof PipelineModelSelection, + baseSelection: PipelineModelSelection | null, + configSelection: PipelineModelSelection | null, + explicitSelection: Record | null, + configAssets: Partial> | null, + explicitAssets: Record | null +): ModelAsset | null { + const explicitAsset = explicitAssets?.[assetRole]; + if (explicitAsset) { + return explicitAsset; + } + const explicitModelName = explicitSelection?.[selectionKey]; + if (explicitModelName) { + return resolveModelAssetByName(modelRole, explicitModelName); + } + const configAsset = configAssets?.[assetRole]; + if (configAsset) { + return configAsset; + } + const configModelName = configSelection?.[selectionKey]; + if (configModelName) { + return resolveModelAssetByName(modelRole, configModelName); + } + const baseModelName = baseSelection?.[selectionKey]; + if (baseModelName) { + return resolveModelAssetByName(modelRole, baseModelName); + } + return null; +} + +function createOcrAssets( + baseSelection: PipelineModelSelection | null, + configSelection: PipelineModelSelection | null, + explicitSelection: Record | null, + configAssets: Partial> | null, + explicitAssets: Record | null +): Record { + const assets = Object.fromEntries( + OCR_MODEL_ROLES.map((role) => [ + role.assetKey, + resolveSelectedAsset( + role.assetKey, + role.modelRole, + role.selectionKey, + baseSelection, + configSelection, + explicitSelection, + configAssets, + explicitAssets + ) + ]) + ); + + if (Object.values(assets).some((asset) => !asset)) { + throw new Error("OCR model selection must define both detection and recognition models."); + } + + return assets as Record; +} + +function getExplicitModelSelection(options: Record): { + modelSelection: Record; + assets: Record; +} | null { + const modelSelection: Record = {}; + const assets: Record = {}; + let hasAnyOption = false; + + for (const role of OCR_MODEL_ROLES) { + const modelName = readAliasedOption(options, role.nameAliases, role.nameLabel) as + | string + | undefined; + const asset = readAliasedOption(options, role.assetAliases, role.assetLabel) as + | ModelAsset + | undefined; + + if (modelName !== undefined) { + modelSelection[role.selectionKey] = modelName; + hasAnyOption = true; + } + if (asset !== undefined) { + if (modelName === undefined) { + throw new Error(role.assetRequirementError); + } + assets[role.assetKey] = asset; + hasAnyOption = true; + } + } + + if (!hasAnyOption) { + return null; + } + + return { + modelSelection, + assets + }; +} + +function resolveBaseModelSelection( + options: Record, + includeDefaultBase = false +): Readonly | null { + const ocrVersion = readAliasedOption(options, ["ocrVersion", "ocr_version"], "ocrVersion") as + | string + | undefined; + if (!options.lang && !ocrVersion) { + return includeDefaultBase ? DEFAULT_MODEL_SELECTION : null; + } + + const lang = (options.lang as string) || "ch"; + const resolvedOcrVersion = ocrVersion || "PP-OCRv5"; + + if (resolvedOcrVersion === "PP-OCRv6") { + if (!isPpOcrV6Lang(lang)) { + throw new Error( + `Unsupported lang/ocrVersion combination: lang="${lang}", ocrVersion="${resolvedOcrVersion}".` + ); + } + return PP_OCRV6_LANG_VERSION_MODEL_SELECTION; + } + + const modelSelection = SUPPORTED_LANG_VERSION_MODELS.get(`${lang}::${resolvedOcrVersion}`); + + if (!modelSelection) { + throw new Error( + `Unsupported lang/ocrVersion combination: lang="${lang}", ocrVersion="${resolvedOcrVersion}".` + ); + } + return modelSelection; +} + +function resolveConstructionOptions( + options: Record = {} +): NormalizedPipelineConfig { + const pipelineInput = options.pipelineConfig; + const userPipelineConfig = + pipelineInput != null ? normalizeOcrPipelineConfig(pipelineInput) : null; + const warningBehavior = resolveWarningBehavior(options.unsupportedBehavior); + const warnings = userPipelineConfig?.warnings || []; + const baseSelection = resolveBaseModelSelection(options, !userPipelineConfig); + const configSelection = userPipelineConfig?.modelSelection || null; + const configAssets = userPipelineConfig?.assets || null; + const explicitOptions = getExplicitModelSelection(options); + const explicitSelection = explicitOptions?.modelSelection || null; + const explicitAssets = explicitOptions?.assets || null; + const resolvedModelSelection = createResolvedModelSelection( + baseSelection, + configSelection, + explicitSelection + ); + const assets = createOcrAssets( + baseSelection, + configSelection, + explicitSelection, + configAssets, + explicitAssets + ); + + const baseNormalized = userPipelineConfig ?? DEFAULT_NORMALIZED_PIPELINE_CONFIG; + if (userPipelineConfig) { + emitPipelineWarnings(warnings, warningBehavior); + } + const merged = mergeNormalizedPipelineConfigWithExplicit(baseNormalized, options); + merged.modelSelection = resolvedModelSelection; + merged.assets = { ...assets }; + return merged; +} + +function resolveBackend(raw: string | undefined): ResolvedBackend { + if (raw === "webgpu" || raw === "wasm") return raw; + return "auto"; +} + +export function normalizeOrtOptions(ortOptions: OrtOptions = {}): NormalizedOrtOptions { + const backend = resolveBackend(ortOptions.backend); + + return { + backend, + ...(ortOptions.wasmPaths !== undefined ? { wasmPaths: ortOptions.wasmPaths } : {}), + ...(ortOptions.numThreads !== undefined ? { numThreads: ortOptions.numThreads } : {}), + ...(ortOptions.simd !== undefined ? { simd: ortOptions.simd } : {}), + ...(ortOptions.proxy !== undefined ? { proxy: ortOptions.proxy } : {}) + }; +} + +export function resolveWorkerOptions(workerOption: unknown): WorkerResolvedOptions { + if (!workerOption) { + return { + enabled: false, + createWorker: null + }; + } + + if (workerOption === true) { + return { + enabled: true, + createWorker: null + }; + } + + if (typeof workerOption === "object") { + const opts = workerOption as Record; + return { + enabled: true, + createWorker: + typeof opts.createWorker === "function" ? (opts.createWorker as () => Worker) : null + }; + } + + throw new Error("worker must be a boolean or an options object."); +} + +export function resolvePaddleOCROptions(options: Record = {}): ResolvedOcrOptions { + return { + pipelineConfig: resolveConstructionOptions(options), + ortOptions: normalizeOrtOptions((options.ortOptions || {}) as OrtOptions) + }; +} + +export function cloneDefaultOcrConfig(): OcrModelConfig { + return deepClone(DEFAULT_OCR_CONFIG); +} diff --git a/paddleocr-js/packages/core/src/pipelines/ocr/worker-backed.ts b/paddleocr-js/packages/core/src/pipelines/ocr/worker-backed.ts new file mode 100644 index 0000000..4750695 --- /dev/null +++ b/paddleocr-js/packages/core/src/pipelines/ocr/worker-backed.ts @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { sourceToWorkerPayload } from "../../platform/browser"; +import { createWorkerTransportClient } from "../../worker/client"; +import type { WorkerTransportClient, WorkerOptions } from "../../worker/client"; +import type { OcrModelConfig, OcrRuntimeParamsInput } from "./runtime-params"; +import type { InitializationSummary, OcrResult, OcrPipelineRunnerOptions } from "./core"; +import { cloneDefaultOcrConfig } from "./shared"; + +declare const __ORT_WASM_CDN_PREFIX__: string | undefined; + +function createDefaultWorker(): Worker { + if (typeof Worker !== "function") { + throw new Error("worker mode requires Web Worker support in this environment."); + } + return new Worker(new URL("./worker-entry.ts", import.meta.url), { + type: "module" + }); +} + +export class WorkerBackedPaddleOCR { + private options: OcrPipelineRunnerOptions; + private lastInitializationSummary: InitializationSummary | null; + private modelConfig: OcrModelConfig; + private transportClient: WorkerTransportClient; + private initPromise: Promise | null; + private disposed: boolean; + + constructor(options: OcrPipelineRunnerOptions, transportClient: WorkerTransportClient) { + this.options = options; + this.lastInitializationSummary = null; + this.modelConfig = cloneDefaultOcrConfig(); + this.transportClient = transportClient; + this.initPromise = null; + this.disposed = false; + } + + ensureActive(): void { + if (this.disposed) { + throw new Error("PaddleOCR worker instance has been disposed."); + } + } + + async initialize(): Promise { + this.ensureActive(); + if (this.lastInitializationSummary) { + return this.lastInitializationSummary; + } + if (!this.initPromise) { + const ortOpts = (this.options.ortOptions || {}) as Record; + if (ortOpts["wasmPaths"] === undefined && typeof __ORT_WASM_CDN_PREFIX__ === "string") { + console.warn( + "[PaddleOCR.js] Worker mode: ortOptions.wasmPaths is not set — falling back to CDN (%s). " + + "For version consistency between main thread and worker, set ortOptions.wasmPaths " + + "to the path where your bundler outputs the onnxruntime-web WASM files " + + '(e.g. ortOptions: { wasmPaths: "/assets/" }).', + __ORT_WASM_CDN_PREFIX__ + ); + } + const wasmCdnFallback = + ortOpts["wasmPaths"] === undefined && typeof __ORT_WASM_CDN_PREFIX__ === "string" + ? { wasmPaths: __ORT_WASM_CDN_PREFIX__ } + : {}; + this.initPromise = this.transportClient + .request("init", { + options: { + ...this.options, + ortOptions: { + ...ortOpts, + ...wasmCdnFallback, + disableWasmProxy: true + } + } + }) + .then((rawPayload) => { + const payload = rawPayload as { + summary: InitializationSummary; + modelConfig: OcrModelConfig; + }; + this.lastInitializationSummary = payload.summary; + this.modelConfig = payload.modelConfig; + return this.lastInitializationSummary; + }) + .catch((error: unknown) => { + this.initPromise = null; + this.transportClient.dispose(); + throw error; + }); + } + return this.initPromise; + } + + getInitializationSummary(): InitializationSummary | null { + return this.lastInitializationSummary; + } + + getModelConfig(): OcrModelConfig { + return this.modelConfig; + } + + async predict(input: unknown, params: OcrRuntimeParamsInput = {}): Promise { + this.ensureActive(); + await this.initialize(); + const sources: unknown[] = Array.isArray(input) ? input : [input]; + const payloads: Array<{ payload: unknown; transferables: Transferable[] }> = await Promise.all( + sources.map((source) => + sourceToWorkerPayload(source as Parameters[0]) + ) + ); + const combinedPayloads = payloads.map((p) => p.payload); + const combinedTransferables = payloads.flatMap((p) => p.transferables); + return this.transportClient.request( + "predict", + { + sources: combinedPayloads, + params + }, + combinedTransferables + ) as Promise; + } + + async dispose(): Promise { + if (this.disposed) { + return; + } + this.disposed = true; + try { + await this.transportClient.request("dispose", {}); + } catch { + // Transport disposal is authoritative even if the worker cannot respond. + } + this.transportClient.dispose(); + } +} + +export function createWorkerBackedPaddleOCR( + options: OcrPipelineRunnerOptions, + workerOptions: WorkerOptions = {} +): WorkerBackedPaddleOCR { + const transportClient = createWorkerTransportClient({ + ...workerOptions, + createWorker: workerOptions.createWorker || createDefaultWorker + }); + return new WorkerBackedPaddleOCR(options, transportClient); +} diff --git a/paddleocr-js/packages/core/src/pipelines/ocr/worker-entry.ts b/paddleocr-js/packages/core/src/pipelines/ocr/worker-entry.ts new file mode 100644 index 0000000..f92c8a7 --- /dev/null +++ b/paddleocr-js/packages/core/src/pipelines/ocr/worker-entry.ts @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { attachWorkerMessageHandler } from "../../worker/entry"; +import { sourcePayloadToMat, ensureServedFromHttp } from "../../platform/worker"; +import type { OcrPipelineRunnerOptions } from "./core"; +import { OcrPipelineRunner } from "./core"; +import type { OcrRuntimeParamsInput } from "./runtime-params"; + +function createPaddleOCRWorkerMessageHandler() { + let ocr: OcrPipelineRunner | null = null; + + async function handleInit(payload: Record) { + await ocr?.dispose(); + ocr = new OcrPipelineRunner({ + ...(payload.options as OcrPipelineRunnerOptions), + ensureServedFromHttp, + sourceToMat: sourcePayloadToMat + }); + const summary = await ocr.initialize(); + return { + summary, + modelConfig: ocr.getModelConfig() + }; + } + + async function handlePredict(payload: Record) { + if (!ocr) { + throw new Error("OCR worker is not initialized."); + } + const sources = payload.sources; + return ocr.predict(sources, (payload.params || {}) as OcrRuntimeParamsInput); + } + + async function handleDispose() { + await ocr?.dispose(); + ocr = null; + return {}; + } + + return async function handleMessage(type: string, payload: Record) { + switch (type) { + case "init": + return handleInit(payload); + case "predict": + return handlePredict(payload); + case "dispose": + return handleDispose(); + default: + throw new Error(`Unsupported worker request type "${type}".`); + } + }; +} + +attachWorkerMessageHandler(createPaddleOCRWorkerMessageHandler()); diff --git a/paddleocr-js/packages/core/src/platform/browser.ts b/paddleocr-js/packages/core/src/platform/browser.ts new file mode 100644 index 0000000..6a37380 --- /dev/null +++ b/paddleocr-js/packages/core/src/platform/browser.ts @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { OpenCv, Mat } from "@techstark/opencv-js"; + +export type ImageSource = ImageBitmap | Blob | HTMLCanvasElement | ImageData | HTMLImageElement; + +export interface SourceMatResult { + width: number; + height: number; + mat: Mat; + dispose(): void; +} + +export interface WorkerPayload { + kind: "imageBitmap"; + imageBitmap: ImageBitmap; +} + +export interface WorkerPayloadResult { + payload: WorkerPayload; + transferables: Transferable[]; +} + +export function ensureServedFromHttp(): void { + if (globalThis.location.protocol === "file:") { + throw new Error("PaddleOCR.js requires an HTTP(S) origin so model assets can be fetched."); + } +} + +function hasDomConstructor(name: string): boolean { + return typeof (globalThis as Record)[name] !== "undefined"; +} + +export async function sourceToImageBitmap(source: ImageSource): Promise { + if (typeof ImageBitmap !== "undefined" && source instanceof ImageBitmap) return source; + if (source instanceof Blob) return createImageBitmap(source); + if (hasDomConstructor("HTMLCanvasElement") && source instanceof HTMLCanvasElement) { + return createImageBitmap(source); + } + if (source instanceof ImageData) { + const canvas = document.createElement("canvas"); + canvas.width = source.width; + canvas.height = source.height; + const ctx = canvas.getContext("2d"); + if (!ctx) throw new Error("Failed to create a 2D canvas context."); + ctx.putImageData(source, 0, 0); + return createImageBitmap(canvas); + } + if (hasDomConstructor("HTMLImageElement") && source instanceof HTMLImageElement) { + return createImageBitmap(source); + } + throw new Error("Unsupported image source. Use a Blob, ImageBitmap, ImageData, canvas, or img."); +} + +async function sourceToClonedImageBitmap(source: ImageSource): Promise { + if (typeof ImageBitmap !== "undefined" && source instanceof ImageBitmap) { + return createImageBitmap(source); + } + return sourceToImageBitmap(source); +} + +export function bitmapToSourceMat( + cv: OpenCv, + imageBitmap: ImageBitmap +): { canvas: HTMLCanvasElement; mat: Mat } { + const canvas = document.createElement("canvas"); + canvas.width = imageBitmap.width; + canvas.height = imageBitmap.height; + const ctx = canvas.getContext("2d", { willReadFrequently: true }); + if (!ctx) throw new Error("Failed to create a 2D canvas context."); + ctx.drawImage(imageBitmap, 0, 0); + return { + canvas, + mat: cv.imread(canvas) + }; +} + +export async function sourceToMat(cv: OpenCv, source: unknown): Promise { + if (typeof cv.Mat === "function" && source instanceof cv.Mat) { + const cloned = source.clone(); + return { + width: source.cols, + height: source.rows, + mat: cloned, + dispose() { + cloned.delete(); + } + }; + } + + const imageBitmap = await sourceToImageBitmap(source as ImageSource); + const sourceImage = bitmapToSourceMat(cv, imageBitmap); + return { + width: imageBitmap.width, + height: imageBitmap.height, + mat: sourceImage.mat, + dispose() { + sourceImage.mat.delete(); + imageBitmap.close(); + } + }; +} + +export async function sourceToWorkerPayload(source: ImageSource): Promise { + if (typeof ImageBitmap === "undefined" || typeof createImageBitmap !== "function") { + throw new Error("Worker mode requires ImageBitmap support in this browser."); + } + const imageBitmap = await sourceToClonedImageBitmap(source); + return { + payload: { + kind: "imageBitmap", + imageBitmap + }, + transferables: [imageBitmap] + }; +} diff --git a/paddleocr-js/packages/core/src/platform/worker.ts b/paddleocr-js/packages/core/src/platform/worker.ts new file mode 100644 index 0000000..6c87881 --- /dev/null +++ b/paddleocr-js/packages/core/src/platform/worker.ts @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { OpenCv, Mat } from "@techstark/opencv-js"; +import type { SourceMatResult } from "./browser"; +import { ensureServedFromHttp } from "./browser"; + +export interface WorkerSourcePayload { + kind: "imageBitmap"; + imageBitmap: ImageBitmap; +} + +function imageBitmapToImageData(imageBitmap: ImageBitmap): ImageData { + if (typeof OffscreenCanvas !== "function") { + throw new Error("Worker mode requires OffscreenCanvas support in this browser."); + } + const canvas = new OffscreenCanvas(imageBitmap.width, imageBitmap.height); + const ctx = canvas.getContext("2d", { willReadFrequently: true }); + if (!ctx) { + throw new Error("Failed to create a 2D canvas context in the OCR worker."); + } + ctx.drawImage(imageBitmap, 0, 0); + return ctx.getImageData(0, 0, imageBitmap.width, imageBitmap.height); +} + +function imageDataToMat(cv: OpenCv, imageData: ImageData): Mat { + return cv.matFromArray(imageData.height, imageData.width, cv.CV_8UC4, imageData.data); +} + +function isWorkerSourcePayload(source: unknown): source is WorkerSourcePayload { + if (typeof source !== "object" || source === null) return false; + const candidate = source as Record; + return ( + candidate["kind"] === "imageBitmap" && + typeof ImageBitmap !== "undefined" && + candidate["imageBitmap"] instanceof ImageBitmap + ); +} + +export function sourcePayloadToMat(cv: OpenCv, source: unknown): SourceMatResult { + if (typeof cv.Mat === "function" && source instanceof cv.Mat) { + const cloned = source.clone(); + return { + width: source.cols, + height: source.rows, + mat: cloned, + dispose() { + cloned.delete(); + } + }; + } + + if (isWorkerSourcePayload(source)) { + const imageData = imageBitmapToImageData(source.imageBitmap); + const mat = imageDataToMat(cv, imageData); + return { + width: imageData.width, + height: imageData.height, + mat, + dispose() { + mat.delete(); + source.imageBitmap.close(); + } + }; + } + + throw new Error("Unsupported worker image source payload."); +} + +export { ensureServedFromHttp }; diff --git a/paddleocr-js/packages/core/src/resources/index.ts b/paddleocr-js/packages/core/src/resources/index.ts new file mode 100644 index 0000000..f98d5d0 --- /dev/null +++ b/paddleocr-js/packages/core/src/resources/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export type { ModelAsset, ModelAssetsMap, ModelLoadResult, ModelLoadSummary } from "./model-asset"; +export { + DEFAULT_MODEL_ASSETS, + MODEL_ENTRY_PATHS, + assertModelResourceSlot, + assertModelResources, + getModelEntryPath, + loadModelAsset, + normalizeAssets, + normalizeModelAsset +} from "./model-asset"; +export { extractTarEntries, pickTarEntry } from "./tar"; diff --git a/paddleocr-js/packages/core/src/resources/model-asset.ts b/paddleocr-js/packages/core/src/resources/model-asset.ts new file mode 100644 index 0000000..765cfc1 --- /dev/null +++ b/paddleocr-js/packages/core/src/resources/model-asset.ts @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface ModelAsset { + url: string; +} + +export type ModelAssetsMap = Record; + +export const DEFAULT_MODEL_ASSETS: ModelAssetsMap = { + "PP-OCRv5_mobile_det": { + url: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv5_mobile_det_onnx_infer.tar" + }, + "PP-OCRv5_mobile_rec": { + url: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv5_mobile_rec_onnx_infer.tar" + }, + "PP-OCRv6_small_det": { + url: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv6_small_det_onnx_infer.tar" + }, + "PP-OCRv6_small_rec": { + url: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv6_small_rec_onnx_infer.tar" + }, + "PP-OCRv6_tiny_det": { + url: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv6_tiny_det_onnx_infer.tar" + }, + "PP-OCRv6_tiny_rec": { + url: "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv6_tiny_rec_onnx_infer.tar" + } +}; + +export const MODEL_ENTRY_PATHS: Readonly> = Object.freeze({ + model: "inference.onnx", + config: "inference.yml" +}); + +export interface ModelLoadResult { + modelBytes: Uint8Array; + configText: string; + download: ModelLoadSummary; +} + +export interface ModelLoadSummary { + url: string; + bytes: number; +} + +// --- Validation helpers --- + +function isNonEmptyString(value: unknown): value is string { + return typeof value === "string" && value.length > 0; +} + +function isObject(value: unknown): value is Record { + return Boolean(value && typeof value === "object" && !Array.isArray(value)); +} + +export function normalizeModelAsset(assetName: string, asset: unknown): ModelAsset { + if (isNonEmptyString(asset)) { + const resolvedAsset = DEFAULT_MODEL_ASSETS[asset]; + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for missing Record key + if (!resolvedAsset) { + throw new Error(`Asset "${assetName}" references unknown model asset "${asset}".`); + } + return { url: resolvedAsset.url }; + } + + if (!isObject(asset)) { + throw new Error(`Asset "${assetName}" must be an object.`); + } + if (!isNonEmptyString(asset.url)) { + throw new Error(`Asset "${assetName}" must define url.`); + } + + return { + url: asset.url + }; +} + +export function normalizeAssets( + assets: Record | undefined +): Record { + const assetEntries = Object.entries(assets || {}); + + if (assetEntries.length === 0) { + throw new Error("Assets must define at least one asset."); + } + + return Object.fromEntries( + assetEntries.map(([assetName, asset]) => [assetName, normalizeModelAsset(assetName, asset)]) + ); +} + +// --- Model loading --- + +export function getModelEntryPath(slot: string): string | null { + return MODEL_ENTRY_PATHS[slot] || null; +} + +export function assertModelResourceSlot(kind: string, slot: string, value: unknown): void { + if (slot === "model") { + if (!(value instanceof Uint8Array) || value.byteLength === 0) { + throw new Error(`${kind} model requires a non-empty ${MODEL_ENTRY_PATHS.model} resource.`); + } + return; + } + + if (slot === "config") { + if (typeof value !== "string" || value.trim().length === 0) { + throw new Error(`${kind} model requires a non-empty ${MODEL_ENTRY_PATHS.config} resource.`); + } + return; + } + + throw new Error(`Unsupported model resource slot "${slot}".`); +} + +export function assertModelResources(kind: string, resources: Record): void { + for (const [slot, value] of Object.entries(resources)) { + assertModelResourceSlot(kind, slot, value); + } +} + +// --- Model loading (fetch + tar extraction) --- + +import { extractTarEntries, pickTarEntry } from "./tar"; + +export async function loadModelAsset( + asset: ModelAsset, + fetchImpl: typeof fetch = fetch +): Promise { + const response = await fetchImpl(asset.url); + if (!response.ok) { + throw new Error(`Failed to download ${asset.url}: HTTP ${String(response.status)}`); + } + const buffer = await response.arrayBuffer(); + const entries = extractTarEntries(buffer); + const modelBytes = pickTarEntry(entries, MODEL_ENTRY_PATHS.model); + const configBytes = pickTarEntry(entries, MODEL_ENTRY_PATHS.config); + + return { + modelBytes, + configText: new TextDecoder().decode(configBytes), + download: { + url: asset.url, + bytes: buffer.byteLength + } + }; +} diff --git a/paddleocr-js/packages/core/src/resources/tar.ts b/paddleocr-js/packages/core/src/resources/tar.ts new file mode 100644 index 0000000..90946c8 --- /dev/null +++ b/paddleocr-js/packages/core/src/resources/tar.ts @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +function readString(bytes: Uint8Array, start: number, length: number): string { + let output = ""; + for (let index = start; index < start + length; index += 1) { + const value = bytes[index]; + if (value === 0) break; + output += String.fromCharCode(value); + } + return output.replace(/\0.*$/, "").trim(); +} + +function readOctal(bytes: Uint8Array, start: number, length: number): number { + const raw = readString(bytes, start, length).replace(/\0/g, "").trim(); + return raw ? Number.parseInt(raw, 8) : 0; +} + +function isEmptyBlock(bytes: Uint8Array, offset: number): boolean { + for (let index = offset; index < offset + 512; index += 1) { + if (bytes[index] !== 0) return false; + } + return true; +} + +function normalizeEntryName(name: string): string { + return name.replace(/^\.?\//, ""); +} + +function isMetadataEntry(name: string): boolean { + const segments = normalizeEntryName(name).split("/"); + const baseName = segments[segments.length - 1] || ""; + return ( + baseName.startsWith("._") || segments.includes("PaxHeader") || segments.includes("__MACOSX") + ); +} + +export function extractTarEntries(buffer: ArrayBuffer | Uint8Array): Map { + const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); + const entries = new Map(); + let offset = 0; + + while (offset + 512 <= bytes.length) { + if (isEmptyBlock(bytes, offset)) { + break; + } + + const name = normalizeEntryName(readString(bytes, offset, 100)); + const size = readOctal(bytes, offset + 124, 12); + const type = bytes[offset + 156]; + const dataStart = offset + 512; + const dataEnd = dataStart + size; + + if (type !== 53 && type !== 120 && name && !isMetadataEntry(name)) { + entries.set(name, bytes.slice(dataStart, dataEnd)); + } + + offset = dataStart + Math.ceil(size / 512) * 512; + } + + return entries; +} + +export function pickTarEntry(entries: Map, targetName: string): Uint8Array { + const normalizedTarget = normalizeEntryName(targetName); + const entry = entries.get(normalizedTarget); + if (entry) { + return entry; + } + + for (const [name, value] of entries) { + if (name.endsWith(`/${normalizedTarget}`) || name === normalizedTarget) { + return value; + } + } + + throw new Error(`Entry "${targetName}" was not found in the tar archive.`); +} diff --git a/paddleocr-js/packages/core/src/runtime/index.ts b/paddleocr-js/packages/core/src/runtime/index.ts new file mode 100644 index 0000000..6e1abe8 --- /dev/null +++ b/paddleocr-js/packages/core/src/runtime/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export { initOpenCvRuntime } from "./opencv"; diff --git a/paddleocr-js/packages/core/src/runtime/opencv.ts b/paddleocr-js/packages/core/src/runtime/opencv.ts new file mode 100644 index 0000000..afe39c9 --- /dev/null +++ b/paddleocr-js/packages/core/src/runtime/opencv.ts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { OpenCv } from "@techstark/opencv-js"; +import cvModule from "@techstark/opencv-js"; + +let cachedCvPromise: Promise<{ cv: OpenCv }> | null = null; + +async function getOpenCv(): Promise<{ cv: OpenCv }> { + let cv: OpenCv; + if (cvModule instanceof Promise) { + cv = await cvModule; + } else { + const mod = cvModule as { Mat?: unknown; onRuntimeInitialized?: () => void }; + if (mod.Mat) { + cv = cvModule as OpenCv; + } else { + await new Promise((resolve) => { + mod.onRuntimeInitialized = () => { + resolve(); + }; + }); + cv = cvModule as OpenCv; + } + } + return { cv }; +} + +export async function initOpenCvRuntime(): Promise<{ cv: OpenCv }> { + if (!cachedCvPromise) { + cachedCvPromise = getOpenCv().catch((error: unknown) => { + cachedCvPromise = null; + throw error; + }); + } + return cachedCvPromise; +} diff --git a/paddleocr-js/packages/core/src/runtime/ort.ts b/paddleocr-js/packages/core/src/runtime/ort.ts new file mode 100644 index 0000000..9412557 --- /dev/null +++ b/paddleocr-js/packages/core/src/runtime/ort.ts @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export type OrtModule = typeof import("onnxruntime-web"); + +export interface WebGpuState { + available: boolean; + reason: string; +} + +export interface OrtOptions { + backend?: "webgpu" | "wasm" | "auto" | (string & {}); + wasmPaths?: string; + numThreads?: number; + simd?: boolean; + proxy?: boolean; + disableWasmProxy?: boolean; +} + +export interface OrtRuntimeResult { + ort: OrtModule; + webgpuState: WebGpuState; + backend: string; +} + +export interface SessionState { + session: import("onnxruntime-web").InferenceSession; + provider: string; +} + +let ortModulePromise: Promise | null = null; + +async function loadOrtModule(): Promise { + if (ortModulePromise) { + return ortModulePromise; + } + ortModulePromise = import("onnxruntime-web"); + return ortModulePromise; +} + +interface GpuLike { + requestAdapter(): Promise; +} + +export async function detectWebGpuAvailability(): Promise { + const gpu = (globalThis.navigator as (Navigator & { gpu?: GpuLike }) | undefined)?.gpu; + if (!gpu?.requestAdapter) { + return { + available: false, + reason: "navigator.gpu is unavailable in this browser." + }; + } + try { + const adapter = await gpu.requestAdapter(); + if (!adapter) { + return { + available: false, + reason: "The browser did not return a WebGPU adapter." + }; + } + return { + available: true, + reason: "" + }; + } catch (err: unknown) { + return { + available: false, + reason: err instanceof Error ? err.message : "Failed to request a WebGPU adapter." + }; + } +} + +export function getProviderCandidates(backend: string, webgpuState: WebGpuState): string[][] { + if (backend === "webgpu") { + if (!webgpuState.available) { + throw new Error(`WebGPU is unavailable: ${webgpuState.reason}`); + } + return [["webgpu"]]; + } + if (backend === "wasm") { + return [["wasm"]]; + } + return webgpuState.available ? [["webgpu"], ["wasm"]] : [["wasm"]]; +} + +function applyOrtEnvironmentOptions(ort: OrtModule, ortOptions: OrtOptions): void { + const wasmOptions = ort.env.wasm; + + if (ortOptions.wasmPaths !== undefined) { + wasmOptions.wasmPaths = ortOptions.wasmPaths; + } + if (ortOptions.numThreads !== undefined) { + wasmOptions.numThreads = ortOptions.numThreads; + } + if (ortOptions.simd !== undefined) { + wasmOptions.simd = ortOptions.simd; + } + if (ortOptions.proxy !== undefined) { + wasmOptions.proxy = ortOptions.proxy; + } + if (ortOptions.disableWasmProxy) { + wasmOptions.proxy = false; + } +} + +export async function initOrtRuntime( + ortOptions: OrtOptions | string = {} +): Promise { + const backend = + typeof ortOptions === "string" + ? ortOptions + : ortOptions.backend === "webgpu" || ortOptions.backend === "wasm" + ? ortOptions.backend + : "auto"; + const webgpuState = await detectWebGpuAvailability(); + const ort = await loadOrtModule(); + if (typeof ortOptions !== "string") { + applyOrtEnvironmentOptions(ort, ortOptions); + } + return { + ort, + webgpuState, + backend + }; +} + +export async function createSession( + ort: OrtModule, + modelBytes: Uint8Array, + providerCandidates: string[][] +): Promise { + let lastErr: unknown = null; + for (const executionProviders of providerCandidates) { + try { + const session = await ort.InferenceSession.create(modelBytes, { + executionProviders, + graphOptimizationLevel: "all" + }); + return { session, provider: executionProviders[0] }; + } catch (err: unknown) { + lastErr = err; + } + } + throw lastErr instanceof Error ? lastErr : new Error("Failed to create ONNX session."); +} + +export async function releaseSessions( + ...sessions: Array +): Promise { + await Promise.all( + sessions.map(async (session) => { + if (!session?.release) return; + await session.release(); + }) + ); +} diff --git a/paddleocr-js/packages/core/src/types/clipper-lib.d.ts b/paddleocr-js/packages/core/src/types/clipper-lib.d.ts new file mode 100644 index 0000000..cbaddee --- /dev/null +++ b/paddleocr-js/packages/core/src/types/clipper-lib.d.ts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* eslint-disable @typescript-eslint/no-unused-vars */ +declare module "clipper-lib" { + interface IntPoint { + X: number; + Y: number; + } + + type Path = IntPoint[]; + type Paths = Path[]; + + class ClipperOffset { + AddPath(path: Path, joinType: number, endType: number): void; + Execute(result: Paths, delta: number): void; + } + + const JoinType: { + readonly jtRound: number; + }; + + const EndType: { + readonly etClosedPolygon: number; + }; + + const ClipperLib: { + ClipperOffset: typeof ClipperOffset; + Paths: { new (): Paths }; + JoinType: typeof JoinType; + EndType: typeof EndType; + }; + + export default ClipperLib; +} diff --git a/paddleocr-js/packages/core/src/types/index.ts b/paddleocr-js/packages/core/src/types/index.ts new file mode 100644 index 0000000..89e717d --- /dev/null +++ b/paddleocr-js/packages/core/src/types/index.ts @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export type { OpenCv, Mat, MatVector, Size, Rect, Scalar, RotatedRect } from "@techstark/opencv-js"; + +export type { Point2D, NormalizeConfig, DetBox, MiniBox } from "../models/common"; + +export type { + DetModelConfig, + DetPostprocessConfig, + DetModel, + DetResult, + DetRuntimeOverrides, + LimitType +} from "../models/det"; + +export type { RecModelConfig, RecModel, RecResult, RecRuntimeOverrides } from "../models/rec"; + +export type { + OcrRuntimeParamsInput, + OcrModelConfig, + ResolvedOcrParams +} from "../pipelines/ocr/runtime-params"; + +export type { + OcrResult, + OcrResultItem, + OcrResultMetrics, + OcrResultRuntime, + InitializationSummary, + OcrPipelineRunnerOptions, + SourceToMatFn +} from "../pipelines/ocr/core"; + +export type { + NormalizedPipelineConfig, + PipelineModelSelection, + PipelineRuntimeDefaults +} from "../pipelines/ocr/config"; + +export type { + ResolvedBackend, + ResolvedOcrOptions, + NormalizedOrtOptions, + WorkerResolvedOptions +} from "../pipelines/ocr/shared"; + +export type { PaddleOCRCreateOptions } from "../pipelines/ocr/index"; + +export type { ModelAsset, ModelAssetsMap } from "../resources/model-asset"; + +export type { + OrtModule, + WebGpuState, + OrtOptions, + OrtRuntimeResult, + SessionState +} from "../runtime/ort"; + +export type { + ImageSource, + SourceMatResult, + WorkerPayload, + WorkerPayloadResult +} from "../platform/browser"; + +export type { + TransportRequest, + TransportResponse, + TransportSuccessResponse, + TransportErrorResponse, + SerializedError +} from "../worker/protocol"; + +export type { WorkerOptions } from "../worker/client"; +export type { MessageHandler } from "../worker/entry"; diff --git a/paddleocr-js/packages/core/src/types/opencv.d.ts b/paddleocr-js/packages/core/src/types/opencv.d.ts new file mode 100644 index 0000000..944292b --- /dev/null +++ b/paddleocr-js/packages/core/src/types/opencv.d.ts @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +declare module "@techstark/opencv-js" { + export interface Mat { + rows: number; + cols: number; + data: Uint8Array; + data32S: Int32Array; + channels(): number; + copyTo(dst: Mat): void; + clone(): Mat; + delete(): void; + roi(rect: Rect): Mat; + } + + export interface MatVector { + push_back(mat: Mat): void; + size(): number; + get(index: number): Mat; + delete(): void; + } + + export interface Size { + width: number; + height: number; + } + + export interface Rect { + x: number; + y: number; + width: number; + height: number; + } + + export interface Scalar { + [index: number]: number; + } + + export interface RotatedRect { + center: { x: number; y: number }; + size: Size; + angle: number; + } + + export interface OpenCv { + Mat: { + new (): Mat; + zeros(rows: number, cols: number, type: number): Mat; + }; + MatVector: { new (): MatVector }; + Size: { new (width: number, height: number): Size }; + Rect: { new (x: number, y: number, width: number, height: number): Rect }; + Scalar: { new (v0?: number, v1?: number, v2?: number, v3?: number): Scalar }; + RotatedRect: { + points(rect: RotatedRect): Array<{ x: number; y: number }>; + }; + + matFromArray(rows: number, cols: number, type: number, data: ArrayLike): Mat; + minAreaRect(points: Mat): RotatedRect; + imread(canvas: HTMLCanvasElement): Mat; + resize(src: Mat, dst: Mat, dsize: Size, fx?: number, fy?: number, interpolation?: number): void; + cvtColor(src: Mat, dst: Mat, code: number): void; + findContours( + image: Mat, + contours: MatVector, + hierarchy: Mat, + mode: number, + method: number + ): void; + fillPoly(img: Mat, pts: MatVector, color: Scalar): void; + mean(src: Mat, mask?: Mat): number[]; + getPerspectiveTransform(src: Mat, dst: Mat): Mat; + warpPerspective( + src: Mat, + dst: Mat, + M: Mat, + dsize: Size, + flags?: number, + borderMode?: number, + borderValue?: Scalar + ): void; + rotate(src: Mat, dst: Mat, rotateCode: number): void; + + // Mat type constants + CV_32FC1: number; + CV_32FC2: number; + CV_32SC2: number; + CV_8UC1: number; + CV_8UC4: number; + + // Color conversion codes + COLOR_RGBA2BGR: number; + COLOR_GRAY2BGR: number; + + // Interpolation flags + INTER_LINEAR: number; + INTER_CUBIC: number; + + // Border types + BORDER_REPLICATE: number; + + // Contour retrieval modes + RETR_LIST: number; + + // Contour approximation methods + CHAIN_APPROX_SIMPLE: number; + + // Rotation codes + ROTATE_90_COUNTERCLOCKWISE: number; + } + + export type CvModule = OpenCv & { onRuntimeInitialized?: () => void }; + + const cv: CvModule | Promise; + export default cv; +} diff --git a/paddleocr-js/packages/core/src/utils/common.ts b/paddleocr-js/packages/core/src/utils/common.ts new file mode 100644 index 0000000..db2cb36 --- /dev/null +++ b/paddleocr-js/packages/core/src/utils/common.ts @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export function nowMs(): number { + return performance.now(); +} + +export function clamp(value: number, min: number, max: number): number { + return Math.max(min, Math.min(max, value)); +} + +export function distance2(p0: [number, number], p1: [number, number]): number { + const dx = p0[0] - p1[0]; + const dy = p0[1] - p1[1]; + return Math.sqrt(dx * dx + dy * dy); +} + +export function formatMs(value: number): string { + return `${value.toFixed(1)} ms`; +} + +export function withTimeout(promise: Promise, ms: number, label: string): Promise { + let settled = false; + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + if (settled) return; + settled = true; + reject(new Error(`${label} timed out after ${String(ms / 1000)}s`)); + }, ms); + + promise + .then((result) => { + if (settled) return; + settled = true; + clearTimeout(timer); + resolve(result); + }) + .catch((err: unknown) => { + if (settled) return; + settled = true; + clearTimeout(timer); + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- propagating upstream rejection + reject(err); + }); + }); +} + +export function resolveRuntimeBatchSize(override: unknown, defaultBatchSize: number): number { + const rawBatch = override ?? defaultBatchSize; + const coercedBatch = + typeof rawBatch === "number" + ? rawBatch + : typeof rawBatch === "string" + ? Number.parseInt(rawBatch, 10) + : Number.NaN; + return Math.max(1, Number.isFinite(coercedBatch) ? coercedBatch : 1); +} + +export function chunkArray(items: T[], size: number): T[][] { + const chunks: T[][] = []; + for (let i = 0; i < items.length; i += size) { + chunks.push(items.slice(i, i + size)); + } + return chunks; +} + +export function deepClone(value: T): T { + return structuredClone(value); +} diff --git a/paddleocr-js/packages/core/src/viz/canvas-factory.ts b/paddleocr-js/packages/core/src/viz/canvas-factory.ts new file mode 100644 index 0000000..0856198 --- /dev/null +++ b/paddleocr-js/packages/core/src/viz/canvas-factory.ts @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +type AnyCanvas = OffscreenCanvas | HTMLCanvasElement; + +export function createCanvas(width: number, height: number): AnyCanvas { + if (typeof OffscreenCanvas !== "undefined") { + return new OffscreenCanvas(width, height); + } + const canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + return canvas; +} + +export function getContext2D( + canvas: AnyCanvas +): CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D { + const ctx = canvas.getContext("2d"); + if (!ctx) { + throw new Error("Failed to create 2D rendering context."); + } + return ctx; +} + +export function canvasToBlob(canvas: AnyCanvas, type: string, quality: number): Promise { + if (canvas instanceof OffscreenCanvas) { + return canvas.convertToBlob({ type, quality }); + } + return new Promise((resolve, reject) => { + canvas.toBlob( + (blob) => { + if (blob) { + resolve(blob); + } else { + reject(new Error("canvas.toBlob() returned null.")); + } + }, + type, + quality + ); + }); +} diff --git a/paddleocr-js/packages/core/src/viz/color.ts b/paddleocr-js/packages/core/src/viz/color.ts new file mode 100644 index 0000000..0db7acb --- /dev/null +++ b/paddleocr-js/packages/core/src/viz/color.ts @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { RgbColor } from "./types"; + +export function deterministicColor(index: number): RgbColor { + let seed = (index + 1) * 1103515245 + 12345; + seed >>>= 0; + const r = (seed >> 16) & 0xff; + seed = (seed * 1103515245 + 12345) >>> 0; + const g = (seed >> 16) & 0xff; + seed = (seed * 1103515245 + 12345) >>> 0; + const b = (seed >> 16) & 0xff; + return [r, g, b]; +} diff --git a/paddleocr-js/packages/core/src/viz/font.ts b/paddleocr-js/packages/core/src/viz/font.ts new file mode 100644 index 0000000..58fcf3c --- /dev/null +++ b/paddleocr-js/packages/core/src/viz/font.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { FontConfig } from "./types"; + +export async function loadFontFace(config: FontConfig): Promise { + const source = typeof config.source === "string" ? `url(${config.source})` : config.source; + + const face = new FontFace(config.family, source, config.descriptors); + await face.load(); + document.fonts.add(face); + return face; +} + +export function removeFontFace(face: FontFace): void { + document.fonts.delete(face); +} diff --git a/paddleocr-js/packages/core/src/viz/index.ts b/paddleocr-js/packages/core/src/viz/index.ts new file mode 100644 index 0000000..bf15d8f --- /dev/null +++ b/paddleocr-js/packages/core/src/viz/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export { OcrVisualizer, renderOcrToBlob } from "./ocr/renderer"; +export { deterministicColor } from "./color"; + +export type { RgbColor, FontConfig } from "./types"; +export type { BoxStyleOptions, OcrVisualizerOptions } from "./ocr/types"; diff --git a/paddleocr-js/packages/core/src/viz/ocr/draw-boxes.ts b/paddleocr-js/packages/core/src/viz/ocr/draw-boxes.ts new file mode 100644 index 0000000..74196e0 --- /dev/null +++ b/paddleocr-js/packages/core/src/viz/ocr/draw-boxes.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { OcrResultItem } from "../../pipelines/ocr/core"; +import type { Point2D } from "../../models/common"; +import type { RgbColor } from "../types"; +import type { BoxStyleOptions } from "./types"; +import { deterministicColor } from "../color"; + +const DEFAULT_FILL_OPACITY = 0.5; + +type DrawableImage = ImageBitmap | HTMLImageElement; + +function drawPolygonPath( + ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, + poly: Point2D[] +): void { + ctx.beginPath(); + ctx.moveTo(poly[0][0], poly[0][1]); + for (let i = 1; i < poly.length; i += 1) { + ctx.lineTo(poly[i][0], poly[i][1]); + } + ctx.closePath(); +} + +export function drawBoxesPanel( + ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, + image: DrawableImage, + items: OcrResultItem[], + style: BoxStyleOptions +): void { + const fillOpacity = style.fillOpacity ?? DEFAULT_FILL_OPACITY; + const getColor = style.colorFn ?? deterministicColor; + + // Draw original image + ctx.drawImage(image, 0, 0); + + // Draw filled polygons with the specified opacity. + // Canvas source-over compositing with alpha gives: + // result = color * alpha + original * (1 - alpha) + for (let i = 0; i < items.length; i += 1) { + const [r, g, b]: RgbColor = getColor(i); + ctx.save(); + ctx.fillStyle = `rgba(${String(r)}, ${String(g)}, ${String(b)}, ${String(fillOpacity)})`; + drawPolygonPath(ctx, items[i].poly); + ctx.fill(); + ctx.restore(); + } +} diff --git a/paddleocr-js/packages/core/src/viz/ocr/draw-text.ts b/paddleocr-js/packages/core/src/viz/ocr/draw-text.ts new file mode 100644 index 0000000..9dca752 --- /dev/null +++ b/paddleocr-js/packages/core/src/viz/ocr/draw-text.ts @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { OcrResultItem } from "../../pipelines/ocr/core"; +import type { Point2D } from "../../models/common"; +import type { RgbColor } from "../types"; +import type { BoxStyleOptions } from "./types"; +import { deterministicColor } from "../color"; + +const DEFAULT_BG = "#ffffff"; +const OUTLINE_LINE_WIDTH = 1; +const TEXT_COLOR = "#000000"; +const ROTATION_THRESHOLD_DEG = 5; +const VERTICAL_LINE_SPACING = 2; + +function topEdgeAngle(poly: Point2D[]): number { + const dx = poly[1][0] - poly[0][0]; + const dy = poly[1][1] - poly[0][1]; + return Math.atan2(dy, dx); +} + +function polyBounds(poly: Point2D[]): { + minX: number; + minY: number; + maxX: number; + maxY: number; + width: number; + height: number; +} { + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + for (const [x, y] of poly) { + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + } + return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY }; +} + +function drawPolygonPath( + ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, + poly: Point2D[], + offsetX: number +): void { + ctx.beginPath(); + ctx.moveTo(poly[0][0] + offsetX, poly[0][1]); + for (let i = 1; i < poly.length; i += 1) { + ctx.lineTo(poly[i][0] + offsetX, poly[i][1]); + } + ctx.closePath(); +} + +function drawVerticalText( + ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, + text: string, + x: number, + startY: number, + fontSize: number, + fontFamily: string +): void { + ctx.font = `${String(fontSize)}px "${fontFamily}"`; + let y = startY; + for (const char of text) { + ctx.fillText(char, x, y); + y += fontSize + VERTICAL_LINE_SPACING; + } +} + +export function drawTextPanel( + ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, + offsetX: number, + height: number, + items: OcrResultItem[], + style: BoxStyleOptions, + fontFamily: string, + background?: string +): void { + const getColor = style.colorFn ?? deterministicColor; + const bg = background ?? DEFAULT_BG; + + // Fill background + ctx.save(); + ctx.fillStyle = bg; + ctx.fillRect(offsetX, 0, offsetX, height); + ctx.restore(); + + for (let i = 0; i < items.length; i += 1) { + const item = items[i]; + const [r, g, b]: RgbColor = getColor(i); + const bounds = polyBounds(item.poly); + const angle = topEdgeAngle(item.poly); + const absDeg = Math.abs(angle * (180 / Math.PI)); + const needsRotation = absDeg > ROTATION_THRESHOLD_DEG && absDeg < 180 - ROTATION_THRESHOLD_DEG; + + // Detect vertical text: height > 2 * width and height > 30px + const isVertical = bounds.height > 2 * bounds.width && bounds.height > 30; + + // Draw box outline + ctx.save(); + ctx.lineWidth = OUTLINE_LINE_WIDTH; + ctx.strokeStyle = `rgb(${String(r)}, ${String(g)}, ${String(b)})`; + drawPolygonPath(ctx, item.poly, offsetX); + ctx.stroke(); + ctx.restore(); + + // Draw text + ctx.save(); + ctx.fillStyle = TEXT_COLOR; + + if (isVertical) { + // Vertical text: render characters one-by-one, stacked vertically + ctx.textBaseline = "top"; + const chars = Array.from(item.text); + const charCount = Math.max(1, chars.length); + let fontSize = Math.max(8, Math.floor(bounds.width * 0.8)); + + // Scale down if total character height exceeds box height + const totalHeight = charCount * (fontSize + VERTICAL_LINE_SPACING); + if (totalHeight > bounds.height) { + fontSize = Math.max(8, Math.floor((bounds.height / charCount) * 0.8)); + } + + // Ensure each character fits within the box width + ctx.font = `${String(fontSize)}px "${fontFamily}"`; + const maxCharWidth = Math.max(...chars.map((c) => ctx.measureText(c).width)); + if (maxCharWidth > bounds.width) { + fontSize = Math.max(8, Math.floor(fontSize * (bounds.width / maxCharWidth))); + } + + const x = bounds.minX + offsetX + (bounds.width - fontSize) / 2; + const y = bounds.minY + 2; + drawVerticalText(ctx, item.text, x, y, fontSize, fontFamily); + } else { + // Horizontal text + ctx.textBaseline = "middle"; + let fontSize = Math.max(12, Math.floor(bounds.height * 0.8)); + ctx.font = `${String(fontSize)}px "${fontFamily}"`; + + // Shrink font if text is wider than the box + const measured = ctx.measureText(item.text); + if (measured.width > bounds.width && bounds.width > 0) { + fontSize = Math.max(8, Math.floor(fontSize * (bounds.width / measured.width))); + ctx.font = `${String(fontSize)}px "${fontFamily}"`; + } + + if (needsRotation) { + const cx = bounds.minX + bounds.width / 2 + offsetX; + const cy = bounds.minY + bounds.height / 2; + ctx.translate(cx, cy); + ctx.rotate(angle); + ctx.fillText(item.text, -bounds.width / 2, 0); + } else { + const x = bounds.minX + offsetX + 2; + const y = bounds.minY + bounds.height / 2; + ctx.fillText(item.text, x, y); + } + } + + ctx.restore(); + } +} diff --git a/paddleocr-js/packages/core/src/viz/ocr/renderer.ts b/paddleocr-js/packages/core/src/viz/ocr/renderer.ts new file mode 100644 index 0000000..02325ee --- /dev/null +++ b/paddleocr-js/packages/core/src/viz/ocr/renderer.ts @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { OcrResult } from "../../pipelines/ocr/core"; +import type { OcrVisualizerOptions } from "./types"; +import { loadFontFace, removeFontFace } from "../font"; +import { renderSideBySideToImageBitmap, renderSideBySideToBlob } from "./side-by-side"; +import type { SideBySideOptions } from "./side-by-side"; + +type DrawableImage = ImageBitmap | HTMLImageElement; + +const DEFAULT_FONT_FAMILY = "sans-serif"; +const DEFAULT_OUTPUT_FORMAT = "png"; +const DEFAULT_OUTPUT_QUALITY = 0.92; +const DEFAULT_TEXT_PANEL_BG = "#ffffff"; + +function resolveOptions( + base: OcrVisualizerOptions, + overrides?: Partial +): SideBySideOptions { + const merged = overrides ? { ...base, ...overrides } : base; + return { + boxStyle: merged.boxStyle ?? {}, + fontFamily: merged.font?.family ?? DEFAULT_FONT_FAMILY, + textPanelBackground: merged.textPanelBackground ?? DEFAULT_TEXT_PANEL_BG, + outputFormat: merged.outputFormat ?? DEFAULT_OUTPUT_FORMAT, + outputQuality: merged.outputQuality ?? DEFAULT_OUTPUT_QUALITY + }; +} + +export class OcrVisualizer { + private options: OcrVisualizerOptions; + private loadedFace: FontFace | null = null; + + constructor(options?: OcrVisualizerOptions) { + this.options = options ?? {}; + } + + async loadFont(): Promise { + if (!this.options.font) return; + if (this.loadedFace) return; + this.loadedFace = await loadFontFace(this.options.font); + } + + async renderSideBySide( + image: DrawableImage, + result: OcrResult, + overrides?: Partial + ): Promise { + await this.loadFont(); + const opts = resolveOptions(this.options, overrides); + return renderSideBySideToImageBitmap(image, result, opts); + } + + async toBlob( + image: DrawableImage, + result: OcrResult, + overrides?: Partial + ): Promise { + await this.loadFont(); + const opts = resolveOptions(this.options, overrides); + return renderSideBySideToBlob(image, result, opts); + } + + dispose(): void { + if (this.loadedFace) { + removeFontFace(this.loadedFace); + this.loadedFace = null; + } + } +} + +export async function renderOcrToBlob( + image: DrawableImage, + result: OcrResult, + options?: OcrVisualizerOptions +): Promise { + const viz = new OcrVisualizer(options); + try { + return await viz.toBlob(image, result); + } finally { + viz.dispose(); + } +} diff --git a/paddleocr-js/packages/core/src/viz/ocr/side-by-side.ts b/paddleocr-js/packages/core/src/viz/ocr/side-by-side.ts new file mode 100644 index 0000000..3e7b24d --- /dev/null +++ b/paddleocr-js/packages/core/src/viz/ocr/side-by-side.ts @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { OcrResult } from "../../pipelines/ocr/core"; +import type { BoxStyleOptions } from "./types"; +import { drawBoxesPanel } from "./draw-boxes"; +import { drawTextPanel } from "./draw-text"; +import { createCanvas, getContext2D, canvasToBlob } from "../canvas-factory"; + +type DrawableImage = ImageBitmap | HTMLImageElement; + +function imageWidth(image: DrawableImage): number { + return image instanceof HTMLImageElement ? image.naturalWidth : image.width; +} + +function imageHeight(image: DrawableImage): number { + return image instanceof HTMLImageElement ? image.naturalHeight : image.height; +} + +export interface SideBySideOptions { + boxStyle: BoxStyleOptions; + fontFamily: string; + textPanelBackground: string; + outputFormat: string; + outputQuality: number; +} + +export function renderSideBySideToCanvas( + image: DrawableImage, + result: OcrResult, + options: SideBySideOptions +): OffscreenCanvas | HTMLCanvasElement { + const w = imageWidth(image); + const h = imageHeight(image); + const canvas = createCanvas(w * 2, h); + const ctx = getContext2D(canvas); + + drawBoxesPanel(ctx, image, result.items, options.boxStyle); + drawTextPanel( + ctx, + w, + h, + result.items, + options.boxStyle, + options.fontFamily, + options.textPanelBackground + ); + + return canvas; +} + +export async function renderSideBySideToImageBitmap( + image: DrawableImage, + result: OcrResult, + options: SideBySideOptions +): Promise { + const canvas = renderSideBySideToCanvas(image, result, options); + return createImageBitmap(canvas as ImageBitmapSource); +} + +export async function renderSideBySideToBlob( + image: DrawableImage, + result: OcrResult, + options: SideBySideOptions +): Promise { + const canvas = renderSideBySideToCanvas(image, result, options); + return canvasToBlob(canvas, `image/${options.outputFormat}`, options.outputQuality); +} diff --git a/paddleocr-js/packages/core/src/viz/ocr/types.ts b/paddleocr-js/packages/core/src/viz/ocr/types.ts new file mode 100644 index 0000000..f1ea65e --- /dev/null +++ b/paddleocr-js/packages/core/src/viz/ocr/types.ts @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import type { RgbColor, FontConfig } from "../types"; + +export interface BoxStyleOptions { + /** Fill opacity 0-1. Default: 0.5. */ + fillOpacity?: number; + /** Custom color function. Default: deterministic LCG-based colors. */ + colorFn?: (index: number) => RgbColor; +} + +export interface OcrVisualizerOptions { + /** Custom font configuration. Falls back to system sans-serif if omitted. */ + font?: FontConfig; + /** Detection box style overrides. */ + boxStyle?: BoxStyleOptions; + /** Right panel background color. Default: "#ffffff". */ + textPanelBackground?: string; + /** Output image format. Default: "png". */ + outputFormat?: "png" | "jpeg" | "webp"; + /** JPEG/WebP quality 0-1. Default: 0.92. */ + outputQuality?: number; +} diff --git a/paddleocr-js/packages/core/src/viz/types.ts b/paddleocr-js/packages/core/src/viz/types.ts new file mode 100644 index 0000000..db45177 --- /dev/null +++ b/paddleocr-js/packages/core/src/viz/types.ts @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/** An RGB color as a 3-element tuple of 0-255 integers. */ +export type RgbColor = [number, number, number]; + +export interface FontConfig { + /** CSS font-family name. */ + family: string; + /** Font source: URL string or ArrayBuffer. */ + source: string | ArrayBuffer; + /** FontFace descriptors (weight, style, etc.). */ + descriptors?: FontFaceDescriptors; +} diff --git a/paddleocr-js/packages/core/src/worker/client.ts b/paddleocr-js/packages/core/src/worker/client.ts new file mode 100644 index 0000000..edf13cc --- /dev/null +++ b/paddleocr-js/packages/core/src/worker/client.ts @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { createTransportRequest, deserializeError, isTransportResponse } from "./protocol"; + +interface PendingRequest { + resolve: (value: unknown) => void; + reject: (reason: unknown) => void; +} + +export interface WorkerOptions { + createWorker?: () => Worker; +} + +export class WorkerTransportClient { + private workerOptions: WorkerOptions; + private worker: Worker | null; + private pending: Map; + private nextRequestId: number; + private disposed: boolean; + + constructor(workerOptions: WorkerOptions = {}) { + this.workerOptions = workerOptions; + this.worker = null; + this.pending = new Map(); + this.nextRequestId = 1; + this.disposed = false; + } + + ensureActive(): void { + if (this.disposed) { + throw new Error("Worker transport client has been disposed."); + } + } + + ensureWorker(): Worker { + this.ensureActive(); + if (this.worker) { + return this.worker; + } + + const workerFactory = this.workerOptions.createWorker; + if (typeof workerFactory !== "function") { + throw new Error("Worker transport client requires a createWorker() factory."); + } + const worker = workerFactory(); + worker.onmessage = (event: MessageEvent) => { + const message = event.data as unknown; + if (!isTransportResponse(message)) return; + const pending = this.pending.get(message.requestId); + if (!pending) return; + this.pending.delete(message.requestId); + if (message.status === "success") { + pending.resolve(message.payload); + } else { + pending.reject(deserializeError(message.error)); + } + }; + worker.onerror = (event: ErrorEvent) => { + const error = new Error(event.message || "OCR worker failed."); + for (const pending of this.pending.values()) { + pending.reject(error); + } + this.pending.clear(); + }; + this.worker = worker; + return worker; + } + + request(type: string, payload: unknown, transferables: Transferable[] = []): Promise { + const worker = this.ensureWorker(); + const requestId = this.nextRequestId; + this.nextRequestId += 1; + + return new Promise((resolve, reject) => { + this.pending.set(requestId, { resolve, reject }); + worker.postMessage(createTransportRequest(type, payload, requestId), transferables); + }); + } + + disposeWorker(): void { + if (!this.worker) { + return; + } + this.worker.terminate(); + this.worker = null; + } + + dispose(): void { + if (this.disposed) { + return; + } + this.disposed = true; + for (const pending of this.pending.values()) { + pending.reject(new Error("Worker transport client has been disposed.")); + } + this.pending.clear(); + this.disposeWorker(); + } +} + +export function createWorkerTransportClient(workerOptions: WorkerOptions): WorkerTransportClient { + return new WorkerTransportClient(workerOptions); +} diff --git a/paddleocr-js/packages/core/src/worker/entry.ts b/paddleocr-js/packages/core/src/worker/entry.ts new file mode 100644 index 0000000..096c173 --- /dev/null +++ b/paddleocr-js/packages/core/src/worker/entry.ts @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { createTransportError, createTransportSuccess, isTransportRequest } from "./protocol"; + +export type MessageHandler = (type: string, payload: Record) => Promise; + +interface WorkerLikeScope { + onmessage: ((event: MessageEvent) => void) | null; + postMessage(message: unknown): void; +} + +export function attachWorkerMessageHandler( + handleMessage: MessageHandler, + workerScope: WorkerLikeScope = self as unknown as WorkerLikeScope +): void { + workerScope.onmessage = (event: MessageEvent) => { + const message = event.data as unknown; + if (!isTransportRequest(message)) { + return; + } + + void (async () => { + try { + const payload = await handleMessage( + message.type, + (message.payload || {}) as Record + ); + workerScope.postMessage(createTransportSuccess(message.requestId, payload)); + } catch (error: unknown) { + workerScope.postMessage(createTransportError(message.requestId, error)); + } + })(); + }; +} diff --git a/paddleocr-js/packages/core/src/worker/protocol.ts b/paddleocr-js/packages/core/src/worker/protocol.ts new file mode 100644 index 0000000..fb9c7f8 --- /dev/null +++ b/paddleocr-js/packages/core/src/worker/protocol.ts @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +const REQUEST_KIND = "worker-transport-request"; +const RESPONSE_KIND = "worker-transport-response"; + +export interface SerializedError { + name: string; + message: string; + stack: string; +} + +export interface TransportRequest { + kind: typeof REQUEST_KIND; + type: string; + payload: unknown; + requestId: number; +} + +export interface TransportSuccessResponse { + kind: typeof RESPONSE_KIND; + status: "success"; + requestId: number; + payload: unknown; +} + +export interface TransportErrorResponse { + kind: typeof RESPONSE_KIND; + status: "error"; + requestId: number; + error: SerializedError; +} + +export type TransportResponse = TransportSuccessResponse | TransportErrorResponse; + +export function createTransportRequest( + type: string, + payload: unknown, + requestId: number +): TransportRequest { + return { + kind: REQUEST_KIND, + type, + payload, + requestId + }; +} + +export function createTransportSuccess( + requestId: number, + payload: unknown +): TransportSuccessResponse { + return { + kind: RESPONSE_KIND, + status: "success", + requestId, + payload + }; +} + +export function createTransportError(requestId: number, error: unknown): TransportErrorResponse { + return { + kind: RESPONSE_KIND, + status: "error", + requestId, + error: serializeError(error) + }; +} + +export function isTransportRequest(message: unknown): message is TransportRequest { + return ( + typeof message === "object" && + message !== null && + "kind" in message && + message.kind === REQUEST_KIND + ); +} + +export function isTransportResponse(message: unknown): message is TransportResponse { + return ( + typeof message === "object" && + message !== null && + "kind" in message && + message.kind === RESPONSE_KIND + ); +} + +export function serializeError(error: unknown): SerializedError { + const err = error as Partial | undefined; + return { + name: err?.name || "Error", + message: err?.message || "Unknown worker error.", + stack: err?.stack || "" + }; +} + +export function deserializeError(error: unknown): Error { + const normalized = (error || {}) as SerializedError; + const instance = new Error(normalized.message || "Unknown worker error."); + instance.name = normalized.name || "Error"; + if (normalized.stack) { + instance.stack = normalized.stack; + } + return instance; +} diff --git a/paddleocr-js/packages/core/test/browser-source.test.ts b/paddleocr-js/packages/core/test/browser-source.test.ts new file mode 100644 index 0000000..618ac16 --- /dev/null +++ b/paddleocr-js/packages/core/test/browser-source.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest"; + +import { sourceToMat } from "../src/platform/browser"; + +class FakeMat { + constructor(rows, cols) { + this.rows = rows; + this.cols = cols; + this.deleted = false; + } + + clone() { + return new FakeMat(this.rows, this.cols); + } + + delete() { + this.deleted = true; + } +} + +describe("browser source helpers", () => { + it("accepts cv.Mat inputs by cloning them", async () => { + const source = new FakeMat(32, 64); + const loaded = await sourceToMat({ Mat: FakeMat }, source); + + expect(loaded.width).toBe(64); + expect(loaded.height).toBe(32); + expect(loaded.mat).toBeInstanceOf(FakeMat); + expect(loaded.mat).not.toBe(source); + + loaded.dispose(); + expect(loaded.mat.deleted).toBe(true); + expect(source.deleted).toBe(false); + }); +}); diff --git a/paddleocr-js/packages/core/test/det-model.test.ts b/paddleocr-js/packages/core/test/det-model.test.ts new file mode 100644 index 0000000..727b788 --- /dev/null +++ b/paddleocr-js/packages/core/test/det-model.test.ts @@ -0,0 +1,608 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { createMockOrtTensorClass } from "./helpers/mock-ort-tensor"; + +const assertModelResources = vi.fn(); +const createSession = vi.fn(); +const getProviderCandidates = vi.fn(); +const releaseSessions = vi.fn(); +const clamp = vi.fn((value, min, max) => Math.max(min, Math.min(max, value))); +const withTimeout = vi.fn((promise) => promise); +const boxScoreFast = vi.fn(); +const getMiniBoxFromPoints = vi.fn(); +const getTransformOp = vi.fn(); +const parseInferenceConfigText = vi.fn(); +const parseScaleValue = vi.fn(); +const toBgrFloatCHWFromBgr = vi.fn(); +const unclip = vi.fn(); + +vi.mock("../src/resources/model-asset", () => ({ + assertModelResources +})); + +vi.mock("../src/runtime/ort", () => ({ + createSession, + getProviderCandidates, + releaseSessions +})); + +vi.mock("../src/utils/common", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + clamp, + withTimeout + }; +}); + +vi.mock("../src/models/common", () => ({ + boxScoreFast, + getMiniBoxFromPoints, + getTransformOp, + parseInferenceConfigText, + parseScaleValue, + toBgrFloatCHWFromBgr, + unclip +})); + +afterEach(() => { + vi.resetModules(); + vi.clearAllMocks(); +}); + +async function loadDetModule() { + return import("../src/models/det"); +} + +/** CV facade for `createDetModel().predict()` integration-style test (preprocess → infer → postprocess). */ +function createDetModelIntegrationCv() { + return { + Mat: class Mat { + constructor() { + this.data = new Uint8Array(1); + } + channels() { + return 3; + } + copyTo() {} + delete() {} + }, + Size: class Size { + constructor(width, height) { + this.width = width; + this.height = height; + } + }, + INTER_LINEAR: "linear", + CV_32FC1: "float1", + CV_8UC1: "mask1", + RETR_LIST: "list", + CHAIN_APPROX_SIMPLE: "chain", + resize: vi.fn((src, dst, size) => { + dst.data = new Uint8Array(size.width * size.height * 3); + dst.channels = () => 3; + dst.copyTo = vi.fn(); + dst.delete = vi.fn(); + }), + cvtColor: vi.fn(), + matFromArray: vi + .fn() + .mockImplementationOnce(() => ({ delete: vi.fn() })) + .mockImplementationOnce(() => ({ delete: vi.fn() })), + MatVector: class MatVector { + size() { + return 1; + } + get() { + return { + rows: 4, + data32S: [0, 0, 4, 0, 4, 2, 0, 2], + delete: vi.fn() + }; + } + delete() {} + }, + findContours: vi.fn() + }; +} + +describe("detection model", () => { + it("parses detection configs with explicit values and fallbacks", async () => { + parseInferenceConfigText.mockReturnValue({ + PreProcess: { + transform_ops: [{ id: "resize" }, { id: "normalize" }] + }, + PostProcess: { + thresh: "0.22", + box_thresh: "0.55", + max_candidates: "200", + unclip_ratio: "1.8" + } + }); + getTransformOp + .mockReturnValueOnce({ resize_long: 736 }) + .mockReturnValueOnce({ mean: [0.1], std: [0.9], scale: "1./2." }); + parseScaleValue.mockReturnValue(0.5); + + const { DEFAULT_DET_MODEL_PARSE_FALLBACKS, parseDetModelConfigText } = await loadDetModule(); + expect(parseDetModelConfigText("config")).toEqual({ + resizeLong: 736, + limitType: "max", + maxSideLimit: 4000, + normalize: { + mean: [0.1], + std: [0.9], + scale: 0.5 + }, + postprocess: { + thresh: 0.22, + boxThresh: 0.55, + maxCandidates: 200, + unclipRatio: 1.8 + } + }); + + parseInferenceConfigText.mockReturnValue({}); + getTransformOp.mockReturnValue(undefined); + parseScaleValue.mockReturnValue(1 / 255); + + expect(parseDetModelConfigText("fallback")).toEqual({ + resizeLong: DEFAULT_DET_MODEL_PARSE_FALLBACKS.resizeLong, + limitType: DEFAULT_DET_MODEL_PARSE_FALLBACKS.limitType, + maxSideLimit: DEFAULT_DET_MODEL_PARSE_FALLBACKS.maxSideLimit, + normalize: { + mean: DEFAULT_DET_MODEL_PARSE_FALLBACKS.normalize.mean, + std: DEFAULT_DET_MODEL_PARSE_FALLBACKS.normalize.std, + scale: 1 / 255 + }, + postprocess: { + thresh: DEFAULT_DET_MODEL_PARSE_FALLBACKS.postprocess.thresh, + boxThresh: DEFAULT_DET_MODEL_PARSE_FALLBACKS.postprocess.boxThresh, + maxCandidates: DEFAULT_DET_MODEL_PARSE_FALLBACKS.postprocess.maxCandidates, + unclipRatio: DEFAULT_DET_MODEL_PARSE_FALLBACKS.postprocess.unclipRatio + } + }); + }); + + it("runs detection models and crops rotated boxes", async () => { + const { cropByPoly } = await import("../src/pipelines/ocr/crop"); + parseInferenceConfigText.mockReturnValue({ + PreProcess: { transform_ops: [] }, + PostProcess: { max_candidates: "10" } + }); + getTransformOp.mockImplementation((_ops, id) => { + if (id === "DetResizeForTest") return { resize_long: 64 }; + return null; + }); + parseScaleValue.mockReturnValue(1 / 255); + clamp.mockImplementation((value, min, max) => Math.max(min, Math.min(max, value))); + getProviderCandidates.mockReturnValue([["wasm"]]); + + const tensorCalls = []; + const ort = { + Tensor: createMockOrtTensorClass(tensorCalls) + }; + const sessionRun = vi.fn().mockResolvedValue({ + output: { + dims: [1, 1, 4, 8], + data: new Float32Array(32).fill(0.9) + } + }); + const session = { + inputNames: ["input"], + outputNames: ["output"], + run: sessionRun + }; + createSession.mockResolvedValue({ + session, + provider: "wasm" + }); + toBgrFloatCHWFromBgr.mockReturnValue(new Float32Array(3 * 32 * 64).fill(1)); + + const makeCv = () => { + const pred = { delete: vi.fn() }; + const bitmap = { delete: vi.fn() }; + const contour = { + rows: 4, + data32S: [0, 0, 4, 0, 4, 2, 0, 2], + delete: vi.fn() + }; + const warped = { + rows: 20, + cols: 10, + delete: vi.fn() + }; + const rotated = { + rows: 10, + cols: 20, + delete: vi.fn() + }; + return { + warped, + rotated, + Mat: class Mat { + constructor() { + return warped; + } + }, + Size: class Size { + constructor(width, height) { + this.width = width; + this.height = height; + } + }, + Scalar: class Scalar {}, + INTER_LINEAR: "linear", + INTER_CUBIC: "cubic", + BORDER_REPLICATE: "replicate", + COLOR_RGBA2BGR: "rgba", + COLOR_GRAY2BGR: "gray", + ROTATE_90_COUNTERCLOCKWISE: "ccw", + CV_32FC1: "float1", + CV_8UC1: "mask1", + CV_32FC2: "float", + RETR_LIST: "list", + CHAIN_APPROX_SIMPLE: "chain", + resize: vi.fn((src, dst, size) => { + dst.data = new Uint8Array(size.width * size.height * 3); + dst.channels = () => 3; + dst.copyTo = vi.fn(); + dst.delete = vi.fn(); + }), + cvtColor: vi.fn(), + matFromArray: vi + .fn() + .mockImplementationOnce(() => pred) + .mockImplementationOnce(() => bitmap) + .mockImplementationOnce(() => ({ delete: vi.fn() })) + .mockImplementationOnce(() => ({ delete: vi.fn() })), + MatVector: class MatVector { + size() { + return 1; + } + get() { + return contour; + } + delete() {} + }, + findContours: vi.fn(), + getPerspectiveTransform: vi.fn(() => ({ delete: vi.fn() })), + warpPerspective: vi.fn(), + rotate: vi.fn() + }; + }; + const cv = makeCv(); + + const { createDetModel } = await loadDetModule(); + getMiniBoxFromPoints + .mockReturnValueOnce({ + side: 4, + box: [ + [0, 0], + [4, 0], + [4, 2], + [0, 2] + ] + }) + .mockReturnValueOnce({ + side: 6, + box: [ + [0, 0], + [5, 0], + [5, 3], + [0, 3] + ] + }); + boxScoreFast.mockReturnValue(0.9); + unclip.mockReturnValue([ + [0, 0], + [5, 0], + [5, 3], + [0, 3] + ]); + + const model = await createDetModel({ + ort, + modelBytes: new Uint8Array([1]), + configText: "det-crop", + backend: "auto", + webgpuState: { available: false, reason: "" } + }); + const [detResult] = await model.predict( + cv, + [ + { + cols: 64, + rows: 32, + channels: () => 3 + } + ], + { + thresh: 0.3, + boxThresh: 0.5, + unclipRatio: 1.5, + limitSideLen: 64, + limitType: "max", + maxSideLimit: 96 + } + ); + + expect(sessionRun).toHaveBeenCalledTimes(1); + expect(tensorCalls[0]).toEqual({ type: "float32", dims: [1, 3, 32, 64], size: 6144 }); + expect(detResult.boxes).toEqual([ + { + poly: [ + [0, 0], + [40, 0], + [40, 24], + [0, 24] + ], + score: 0.9 + } + ]); + + const cropWarped = { + rows: 20, + cols: 10, + delete: vi.fn() + }; + const cropRotated = { + rows: 10, + cols: 20, + delete: vi.fn() + }; + let cropMatCount = 0; + const cropCv = { + Size: cv.Size, + Scalar: cv.Scalar, + INTER_CUBIC: cv.INTER_CUBIC, + BORDER_REPLICATE: cv.BORDER_REPLICATE, + ROTATE_90_COUNTERCLOCKWISE: cv.ROTATE_90_COUNTERCLOCKWISE, + CV_32FC2: cv.CV_32FC2, + Mat: class Mat { + constructor() { + cropMatCount += 1; + return cropMatCount === 1 ? cropWarped : cropRotated; + } + }, + matFromArray: vi + .fn() + .mockImplementationOnce(() => ({ delete: vi.fn() })) + .mockImplementationOnce(() => ({ delete: vi.fn() })), + getPerspectiveTransform: vi.fn(() => ({ delete: vi.fn() })), + warpPerspective: vi.fn(), + rotate: vi.fn() + }; + + getMiniBoxFromPoints.mockReturnValue({ + box: [ + [0, 0], + [10, 0], + [10, 20], + [0, 20] + ] + }); + const rotatedCrop = cropByPoly(cropCv, { id: "src" }, [[0, 0]]); + expect(cropCv.rotate).toHaveBeenCalled(); + expect(rotatedCrop).toBe(cropRotated); + }); + + it("runs batched detection when batchSize > 1 (one session.run per chunk)", async () => { + parseInferenceConfigText.mockReturnValue({ + PreProcess: { transform_ops: [] }, + PostProcess: { max_candidates: "10" } + }); + getTransformOp.mockImplementation((_ops, id) => { + if (id === "DetResizeForTest") return { resize_long: 64 }; + return null; + }); + parseScaleValue.mockReturnValue(1 / 255); + clamp.mockImplementation((value, min, max) => Math.max(min, Math.min(max, value))); + getProviderCandidates.mockReturnValue([["wasm"]]); + + const tensorCalls = []; + const ort = { + Tensor: createMockOrtTensorClass(tensorCalls) + }; + const sessionRun = vi.fn().mockResolvedValue({ + output: { + dims: [2, 1, 4, 8], + data: new Float32Array(64).fill(0.1) + } + }); + const session = { + inputNames: ["input"], + outputNames: ["output"], + run: sessionRun + }; + createSession.mockResolvedValue({ + session, + provider: "wasm" + }); + toBgrFloatCHWFromBgr.mockReturnValue(new Float32Array(3 * 32 * 64).fill(1)); + + const pred = { delete: vi.fn() }; + const bitmap = { delete: vi.fn() }; + const warped = { + rows: 20, + cols: 10, + delete: vi.fn() + }; + const cv = { + warped, + Mat: class Mat { + constructor() { + return warped; + } + }, + Size: class Size { + constructor(width, height) { + this.width = width; + this.height = height; + } + }, + Scalar: class Scalar {}, + INTER_LINEAR: "linear", + INTER_CUBIC: "cubic", + BORDER_REPLICATE: "replicate", + COLOR_RGBA2BGR: "rgba", + COLOR_GRAY2BGR: "gray", + ROTATE_90_COUNTERCLOCKWISE: "ccw", + CV_32FC1: "float1", + CV_8UC1: "mask1", + CV_32FC2: "float", + RETR_LIST: "list", + CHAIN_APPROX_SIMPLE: "chain", + resize: vi.fn((src, dst, size) => { + dst.data = new Uint8Array(size.width * size.height * 3); + dst.channels = () => 3; + dst.copyTo = vi.fn(); + dst.delete = vi.fn(); + }), + cvtColor: vi.fn(), + matFromArray: vi + .fn() + .mockImplementationOnce(() => pred) + .mockImplementationOnce(() => bitmap) + .mockImplementationOnce(() => pred) + .mockImplementationOnce(() => bitmap), + MatVector: class MatVector { + size() { + return 0; + } + delete() {} + }, + findContours: vi.fn(), + getPerspectiveTransform: vi.fn(() => ({ delete: vi.fn() })), + warpPerspective: vi.fn(), + rotate: vi.fn() + }; + + const { createDetModel } = await loadDetModule(); + const model = await createDetModel({ + ort, + modelBytes: new Uint8Array([1]), + configText: "det-batch", + backend: "auto", + webgpuState: { available: false, reason: "" }, + batchSize: 2 + }); + + const mat = { cols: 64, rows: 32, channels: () => 3 }; + const results = await model.predict(cv, [mat, mat], { + thresh: 0.3, + boxThresh: 0.5, + unclipRatio: 1.5, + limitSideLen: 64, + limitType: "max", + maxSideLimit: 96 + }); + + expect(sessionRun).toHaveBeenCalledTimes(1); + const batchInput = tensorCalls.find((t) => t.dims[0] === 2); + expect(batchInput).toEqual({ type: "float32", dims: [2, 3, 32, 64], size: 12288 }); + expect(results).toHaveLength(2); + expect(results[0].srcW).toBe(64); + expect(results[1].srcW).toBe(64); + }); + + it("creates, uses, and disposes detection models through runtime wrappers", async () => { + parseInferenceConfigText.mockReturnValue({ + PreProcess: { + transform_ops: [] + }, + PostProcess: {} + }); + getTransformOp.mockReturnValue(undefined); + parseScaleValue.mockReturnValue(1 / 255); + getProviderCandidates.mockReturnValue([["wasm"]]); + createSession.mockResolvedValue({ + session: { + inputNames: ["input"], + outputNames: ["output"], + run: vi.fn().mockResolvedValue({ + output: { + dims: [1, 1, 4, 8], + data: new Float32Array(32).fill(0.9) + } + }) + }, + provider: "wasm" + }); + releaseSessions.mockResolvedValue(undefined); + toBgrFloatCHWFromBgr.mockReturnValue(new Float32Array(3 * 32 * 64).fill(1)); + getMiniBoxFromPoints + .mockReturnValueOnce({ + side: 4, + box: [ + [0, 0], + [4, 0], + [4, 2], + [0, 2] + ] + }) + .mockReturnValueOnce({ + side: 6, + box: [ + [0, 0], + [5, 0], + [5, 3], + [0, 3] + ] + }); + boxScoreFast.mockReturnValue(0.9); + unclip.mockReturnValue([ + [0, 0], + [5, 0], + [5, 3], + [0, 3] + ]); + + const { createDetModel, createDetModelSession } = await loadDetModule(); + const sessionState = await createDetModelSession({}, new Uint8Array([1]), "auto", { + available: false, + reason: "" + }); + expect(getProviderCandidates).toHaveBeenCalledWith("auto", { available: false, reason: "" }); + expect(withTimeout).toHaveBeenCalled(); + expect(sessionState.provider).toBe("wasm"); + + const model = await createDetModel({ + ort: { + Tensor: createMockOrtTensorClass() + }, + modelBytes: new Uint8Array([1]), + configText: "config", + backend: "auto", + webgpuState: { available: false, reason: "" } + }); + + expect(assertModelResources).toHaveBeenCalled(); + expect(model.kind).toBe("det"); + expect(model.provider).toBe("wasm"); + await expect( + model.predict( + createDetModelIntegrationCv(), + [ + { + cols: 64, + rows: 32, + channels: () => 3 + } + ], + { + thresh: 0.3, + boxThresh: 0.5, + unclipRatio: 1.5 + } + ) + ).resolves.toMatchObject([ + { + boxes: expect.any(Array), + srcW: 64, + srcH: 32 + } + ]); + await expect(model.dispose()).resolves.toBeUndefined(); + await expect(model.predict({}, [{}], {})).rejects.toThrow(/session is not initialized/i); + }); +}); diff --git a/paddleocr-js/packages/core/test/helpers/mock-ort-tensor.ts b/paddleocr-js/packages/core/test/helpers/mock-ort-tensor.ts new file mode 100644 index 0000000..1b83b8f --- /dev/null +++ b/paddleocr-js/packages/core/test/helpers/mock-ort-tensor.ts @@ -0,0 +1,11 @@ +/** Minimal ORT `Tensor` mock; optional `tensorCalls` records each `{ type, dims, size }`. */ +export function createMockOrtTensorClass(tensorCalls = null) { + return class Tensor { + constructor(type, data, dims) { + this.type = type; + this.data = data; + this.dims = dims; + tensorCalls?.push({ type, dims, size: data.length }); + } + }; +} diff --git a/paddleocr-js/packages/core/test/model-asset.test.ts b/paddleocr-js/packages/core/test/model-asset.test.ts new file mode 100644 index 0000000..dc9f8a9 --- /dev/null +++ b/paddleocr-js/packages/core/test/model-asset.test.ts @@ -0,0 +1,101 @@ +import { describe, expect, it } from "vitest"; + +import { + normalizeModelAsset, + normalizeAssets, + getModelEntryPath, + assertModelResourceSlot, + assertModelResources +} from "../src/resources/model-asset"; + +describe("model asset normalization", () => { + it("resolves built-in model asset references", () => { + const assets = normalizeAssets({ + det: "PP-OCRv5_mobile_det", + rec: "PP-OCRv5_mobile_rec" + }); + expect(assets.det.url).toMatch(/\.tar$/); + }); + + it("resolves PP-OCRv6 tiny and small model asset references", () => { + const tiny = normalizeAssets({ + det: "PP-OCRv6_tiny_det", + rec: "PP-OCRv6_tiny_rec" + }); + expect(tiny.det.url).toMatch(/PP-OCRv6_tiny_det_onnx_infer\.tar$/); + expect(tiny.rec.url).toMatch(/PP-OCRv6_tiny_rec_onnx_infer\.tar$/); + + const small = normalizeAssets({ + det: "PP-OCRv6_small_det", + rec: "PP-OCRv6_small_rec" + }); + expect(small.det.url).toMatch(/PP-OCRv6_small_det_onnx_infer\.tar$/); + expect(small.rec.url).toMatch(/PP-OCRv6_small_rec_onnx_infer\.tar$/); + }); + + it("normalizes a single model asset directly", () => { + const asset = normalizeModelAsset("det", { + url: "/det.tar" + }); + + expect(asset.url).toBe("/det.tar"); + }); + + it("rejects invalid assets", () => { + expect(() => + normalizeAssets({ + encoder: {} + }) + ).toThrow(/must define url/i); + }); + + it("rejects non-object asset descriptors", () => { + expect(() => normalizeModelAsset("det", null)).toThrow(/must be an object/i); + }); + + it("rejects unknown model asset references", () => { + expect(() => + normalizeAssets({ + det: "missing_model" + }) + ).toThrow(/unknown model asset/i); + }); +}); + +describe("model resource validation", () => { + it("provides standard entry names", () => { + expect(getModelEntryPath("model")).toBe("inference.onnx"); + expect(getModelEntryPath("config")).toBe("inference.yml"); + expect(getModelEntryPath("other")).toBe(null); + }); + + it("rejects missing model binary resources", () => { + expect(() => assertModelResourceSlot("Detection", "model", new Uint8Array())).toThrow( + /inference\.onnx/i + ); + }); + + it("rejects missing model config resources", () => { + expect(() => assertModelResourceSlot("Recognition", "config", "")).toThrow(/inference\.yml/i); + }); + + it("supports validating multiple model resources together", () => { + expect(() => + assertModelResources("Detection", { + model: new Uint8Array([1]), + config: "Global:\n model_name: det" + }) + ).not.toThrow(); + }); + + it("rejects unsupported model resource slots", () => { + expect(() => assertModelResourceSlot("Detection", "labels", "abc")).toThrow( + /Unsupported model resource slot/i + ); + expect(() => + assertModelResources("Detection", { + labels: "abc" + }) + ).toThrow(/Unsupported model resource slot/i); + }); +}); diff --git a/paddleocr-js/packages/core/test/model-common.test.ts b/paddleocr-js/packages/core/test/model-common.test.ts new file mode 100644 index 0000000..b442696 --- /dev/null +++ b/paddleocr-js/packages/core/test/model-common.test.ts @@ -0,0 +1,174 @@ +import { describe, expect, it } from "vitest"; + +import { + boxScoreFast, + extractInferenceModelName, + getMiniBoxFromPoints, + getTransformOp, + parseInferenceConfigText, + parseScaleValue, + toBgrFloatCHWFromBgr, + unclip +} from "../src/models/common"; + +describe("model common helpers", () => { + it("parses inference config text into plain objects", () => { + expect(parseInferenceConfigText("Global:\n model_name: det")).toEqual({ + Global: { + model_name: "det" + } + }); + expect(parseInferenceConfigText("- item")).toEqual({}); + }); + + it("parses numeric and fractional scale values with fallback behavior", () => { + expect(parseScaleValue(0.5, 1)).toBe(0.5); + expect(parseScaleValue("1./255.", 1)).toBeCloseTo(1 / 255); + expect(parseScaleValue("2/0", 3)).toBe(3); + expect(parseScaleValue({}, 4)).toBe(4); + }); + + it("finds transform ops and nested model names", () => { + expect( + getTransformOp([{ NormalizeImage: { mean: [0.5] } }, { Other: {} }], "NormalizeImage") + ).toEqual({ mean: [0.5] }); + expect(getTransformOp([{ Other: {} }], "NormalizeImage")).toBeNull(); + + expect(extractInferenceModelName("Global:\n model_name: det")).toBe("det"); + expect( + extractInferenceModelName( + "Outer:\n nested:\n - item: 1\n - model_name: rec\narray:\n - another: thing" + ) + ).toBe("rec"); + }); + + it("converts BGR image data into CHW float tensors", () => { + const bgr = new Uint8Array([10, 20, 30, 40, 50, 60]); + const result = toBgrFloatCHWFromBgr(bgr, 2, 1, { + mean: [0, 0, 0], + std: [1, 1, 1], + scale: 1 + }); + + expect(Array.from(result)).toEqual([10, 40, 20, 50, 30, 60]); + }); + + it("returns null when unclipping a degenerate polygon and expands valid polygons", () => { + expect( + unclip( + [ + [0, 0], + [0, 0], + [0, 0] + ], + 2 + ) + ).toBeNull(); + + const expanded = unclip( + [ + [0, 0], + [10, 0], + [10, 10], + [0, 10] + ], + 2 + ); + + expect(expanded).not.toBeNull(); + expect(expanded.length).toBeGreaterThanOrEqual(4); + }); + + it("computes mini boxes from points with a cv facade", () => { + const contour = { + deleteCalled: false, + delete() { + this.deleteCalled = true; + } + }; + const cv = { + CV_32FC2: "float", + matFromArray: () => contour, + minAreaRect: () => ({ id: "rect" }), + RotatedRect: { + points: () => [ + { x: 0, y: 0 }, + { x: 10, y: 0 }, + { x: 10, y: 5 }, + { x: 0, y: 5 } + ] + } + }; + + const result = getMiniBoxFromPoints(cv, [ + [0, 0], + [10, 0], + [10, 5], + [0, 5] + ]); + + expect(result.box).toEqual([ + [0, 0], + [10, 0], + [10, 5], + [0, 5] + ]); + expect(result.side).toBe(5); + expect(contour.deleteCalled).toBe(true); + }); + + it("computes masked box scores with a cv facade", () => { + const roi = { + delete: () => {} + }; + const mask = { + delete: () => {} + }; + const pts = { + delete: () => {} + }; + const predMat = { + rows: 20, + cols: 30, + roi: () => roi + }; + const cv = { + Rect: class Rect { + constructor(x, y, width, height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + }, + Mat: { + zeros: function zeros() { + return mask; + } + }, + CV_8UC1: "mask", + CV_32SC2: "int", + MatVector: class MatVector { + push_back() {} + delete() {} + }, + Scalar: class Scalar { + constructor(value) { + this.value = value; + } + }, + matFromArray: () => pts, + fillPoly: () => {}, + mean: () => [0.75] + }; + + expect( + boxScoreFast(cv, predMat, [ + [1.2, 2.1], + [8.5, 2.4], + [8.1, 9.2], + [1.4, 9.8] + ]) + ).toBe(0.75); + }); +}); diff --git a/paddleocr-js/packages/core/test/model-config.test.ts b/paddleocr-js/packages/core/test/model-config.test.ts new file mode 100644 index 0000000..1827255 --- /dev/null +++ b/paddleocr-js/packages/core/test/model-config.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from "vitest"; + +import { parseDetModelConfigText, parseRecModelConfigText } from "../src/models/index"; + +const detConfig = ` +PreProcess: + transform_ops: + - DetResizeForTest: + resize_long: 736 + limit_type: max + max_side_limit: 4500 + - NormalizeImage: + mean: [0.1, 0.2, 0.3] + std: [0.9, 0.8, 0.7] + scale: 1./255. +PostProcess: + thresh: 0.22 + box_thresh: 0.55 + max_candidates: 200 + unclip_ratio: 1.8 +`; + +const recConfig = ` +PreProcess: + transform_ops: + - RecResizeImg: + image_shape: [3, 48, 320] + - NormalizeImage: + mean: [0.5, 0.5, 0.5] + std: [0.5, 0.5, 0.5] + scale: 1./255. +PostProcess: + character_dict: + - a + - b + - c +`; + +describe("model config parsers", () => { + it("parses detection model config text", () => { + const config = parseDetModelConfigText(detConfig); + + expect(config.resizeLong).toBe(736); + expect(config.limitType).toBe("max"); + expect(config.maxSideLimit).toBe(4500); + expect(config.postprocess.thresh).toBe(0.22); + expect(config.postprocess.boxThresh).toBe(0.55); + }); + + it("parses recognition model config text", () => { + const config = parseRecModelConfigText(recConfig); + + expect(config.imageShape).toEqual([3, 48, 320]); + expect(config.charDict.slice(0, 3)).toEqual(["a", "b", "c"]); + expect(config.charDict.at(-1)).toBe(" "); + }); +}); diff --git a/paddleocr-js/packages/core/test/ocr-api.test.ts b/paddleocr-js/packages/core/test/ocr-api.test.ts new file mode 100644 index 0000000..4d933e1 --- /dev/null +++ b/paddleocr-js/packages/core/test/ocr-api.test.ts @@ -0,0 +1,564 @@ +import { describe, expect, it, vi } from "vitest"; + +vi.mock("@techstark/opencv-js", () => ({ + default: { + Mat() {} + } +})); + +import { PaddleOCR, normalizeOcrPipelineConfig, parseOcrPipelineConfigText } from "../src/index"; +import { extractInferenceModelName } from "../src/models/common"; +import { DEFAULT_OCR_PIPELINE_CONFIG_TEXT } from "../src/pipelines/ocr/default-config"; +import { normalizeOrtOptions } from "../src/pipelines/ocr/shared"; +import { getOcrRuntimeParams } from "../src/pipelines/ocr/runtime-params"; + +const CREATE_WITHOUT_INIT = Object.freeze({ + initialize: false +}); + +const IGNORE_UNSUPPORTED = Object.freeze({ + initialize: false, + unsupportedBehavior: "ignore" +}); + +const pipelineConfigText = ` +pipeline_name: OCR +use_doc_preprocessor: true +use_textline_orientation: true +SubModules: + TextDetection: + model_name: PP-OCRv5_mobile_det + limit_side_len: 64 + limit_type: min + max_side_limit: 4000 + thresh: 0.3 + box_thresh: 0.6 + unclip_ratio: 1.5 + TextLineOrientation: + model_name: PP-LCNet_x1_0_textline_ori + TextRecognition: + model_name: PP-OCRv5_mobile_rec + score_thresh: 0.0 +`; + +const customDetAsset = { + url: "https://example.com/custom-det.tar" +}; + +const customRecAsset = { + url: "https://example.com/custom-rec.tar" +}; + +class MockWorker { + constructor(responder) { + this.responder = responder; + this.messages = []; + this.terminated = false; + this.onmessage = null; + this.onerror = null; + } + + postMessage(message) { + this.messages.push(message); + queueMicrotask(() => { + if (this.terminated) return; + const response = this.responder(message); + if (!response) return; + this.onmessage?.({ data: response }); + }); + } + + terminate() { + this.terminated = true; + } +} + +function expectDefaultModelAssets(ocr) { + expect(ocr.options.pipelineConfig.assets.det?.url).toMatch(/PP-OCRv5_mobile_det/); + expect(ocr.options.pipelineConfig.assets.rec?.url).toMatch(/PP-OCRv5_mobile_rec/); +} + +describe("PaddleOCR high-level API", () => { + it("parses and normalizes OCR pipeline configs", () => { + const parsed = parseOcrPipelineConfigText(pipelineConfigText); + const normalized = normalizeOcrPipelineConfig(parsed); + + expect(normalized.pipelineName).toBe("OCR"); + expect(normalized.modelSelection.textDetectionModelName).toBe("PP-OCRv5_mobile_det"); + expect(normalized.modelSelection.textRecognitionModelName).toBe("PP-OCRv5_mobile_rec"); + expect(normalized.modelSelection).not.toHaveProperty("detAsset"); + expect(normalized.modelSelection).not.toHaveProperty("recAsset"); + expect(normalized.runtimeDefaults.text_det_limit_side_len).toBe(64); + expect(normalized.runtimeDefaults.text_det_limit_type).toBe("min"); + expect(normalized.warnings).toHaveLength(2); + }); + + it("keeps pipeline-declared custom assets separate from model selection", () => { + const normalized = normalizeOcrPipelineConfig({ + pipeline_name: "OCR", + SubModules: { + TextDetection: { + model_name: "custom_det", + model_dir: customDetAsset + }, + TextRecognition: { + model_name: "custom_rec", + model_dir: customRecAsset + } + } + }); + + expect(normalized.modelSelection.textDetectionModelName).toBe("custom_det"); + expect(normalized.modelSelection.textRecognitionModelName).toBe("custom_rec"); + expect(normalized.assets.det.url).toBe("https://example.com/custom-det.tar"); + expect(normalized.assets.rec.url).toBe("https://example.com/custom-rec.tar"); + }); + + it("creates an OCR instance from lang and ocrVersion", async () => { + const ocr = await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5", + ...CREATE_WITHOUT_INIT + }); + + expect(ocr).toBeInstanceOf(PaddleOCR); + expectDefaultModelAssets(ocr); + }); + + it("keeps the same create API when worker mode is enabled", async () => { + const defaultOrt = normalizeOrtOptions(); + const ocr = await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5", + worker: true, + ...CREATE_WITHOUT_INIT + }); + + expect(typeof ocr.initialize).toBe("function"); + expect(typeof ocr.predict).toBe("function"); + expect(typeof ocr.dispose).toBe("function"); + expect(ocr.options.pipelineConfig.assets.det?.url).toMatch(/PP-OCRv5_mobile_det/); + expect(ocr.options.ortOptions.backend).toBe(defaultOrt.backend); + }); + + it("uses the package OCR.yaml defaults when no pipeline config is passed", async () => { + const defaultPipeline = normalizeOcrPipelineConfig(DEFAULT_OCR_PIPELINE_CONFIG_TEXT); + const ocr = await PaddleOCR.create(CREATE_WITHOUT_INIT); + + expect(ocr.options.pipelineConfig.assets.det?.url).toMatch( + new RegExp(defaultPipeline.modelSelection.textDetectionModelName) + ); + expect(ocr.options.pipelineConfig.assets.rec?.url).toMatch( + new RegExp(defaultPipeline.modelSelection.textRecognitionModelName) + ); + expect(ocr.pipelineConfig.runtimeDefaults).toMatchObject(defaultPipeline.runtimeDefaults); + }); + + it("maps English PP-OCRv5 selection to the mobile model set", async () => { + const ocr = await PaddleOCR.create({ + lang: "en", + ocrVersion: "PP-OCRv5", + ...CREATE_WITHOUT_INIT + }); + + expectDefaultModelAssets(ocr); + }); + + it("maps PP-OCRv6 lang selection to the small model set", async () => { + const ocr = await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv6", + ...CREATE_WITHOUT_INIT + }); + + expect(ocr.options.pipelineConfig.assets.det?.url).toMatch(/PP-OCRv6_small_det/); + expect(ocr.options.pipelineConfig.assets.rec?.url).toMatch(/PP-OCRv6_small_rec/); + expect(ocr.options.pipelineConfig.modelSelection.textDetectionModelName).toBe( + "PP-OCRv6_small_det" + ); + expect(ocr.options.pipelineConfig.modelSelection.textRecognitionModelName).toBe( + "PP-OCRv6_small_rec" + ); + }); + + it("maps PP-OCRv6 Latin languages to the small model set", async () => { + const ocr = await PaddleOCR.create({ + lang: "fr", + ocrVersion: "PP-OCRv6", + ...CREATE_WITHOUT_INIT + }); + + expect(ocr.options.pipelineConfig.assets.det?.url).toMatch(/PP-OCRv6_small_det/); + expect(ocr.options.pipelineConfig.assets.rec?.url).toMatch(/PP-OCRv6_small_rec/); + }); + + it("rejects PP-OCRv6 with languages outside the v6 coverage", async () => { + for (const lang of ["ru", "pi"]) { + await expect( + PaddleOCR.create({ + lang, + ocrVersion: "PP-OCRv6", + ...CREATE_WITHOUT_INIT + }) + ).rejects.toThrow(/Unsupported lang\/ocrVersion combination/); + } + }); + + it("maps PP-OCRv6 az and ku lang selection to the small model set", async () => { + for (const lang of ["az", "ku"]) { + const ocr = await PaddleOCR.create({ + lang, + ocrVersion: "PP-OCRv6", + ...CREATE_WITHOUT_INIT + }); + + expect(ocr.options.pipelineConfig.assets.det?.url).toMatch(/PP-OCRv6_small_det/); + expect(ocr.options.pipelineConfig.assets.rec?.url).toMatch(/PP-OCRv6_small_rec/); + } + }); + + it("resolves PP-OCRv6 tiny models via explicit model names", async () => { + const ocr = await PaddleOCR.create({ + text_detection_model_name: "PP-OCRv6_tiny_det", + text_recognition_model_name: "PP-OCRv6_tiny_rec", + ...CREATE_WITHOUT_INIT + }); + + expect(ocr.options.pipelineConfig.assets.det?.url).toMatch(/PP-OCRv6_tiny_det_onnx_infer\.tar$/); + expect(ocr.options.pipelineConfig.assets.rec?.url).toMatch(/PP-OCRv6_tiny_rec_onnx_infer\.tar$/); + }); + + it("allows overriding model selection via model_name options", async () => { + const ocr = await PaddleOCR.create({ + text_detection_model_name: "PP-OCRv5_mobile_det", + text_recognition_model_name: "PP-OCRv5_mobile_rec", + ...CREATE_WITHOUT_INIT + }); + + expectDefaultModelAssets(ocr); + }); + + it("creates an OCR instance from pipeline config model names", async () => { + const ocr = await PaddleOCR.create({ + pipelineConfig: pipelineConfigText, + ...IGNORE_UNSUPPORTED + }); + + expectDefaultModelAssets(ocr); + expect(ocr.pipelineConfig.runtimeDefaults.text_det_limit_type).toBe("min"); + expect(ocr.pipelineConfig.runtimeDefaults.text_rec_score_thresh).toBe(0); + }); + + it("lets explicit model assets override pipeline config model names", async () => { + const ocr = await PaddleOCR.create({ + pipelineConfig: pipelineConfigText, + text_detection_model_name: "custom_det", + textDetectionModelAsset: customDetAsset, + text_recognition_model_name: "custom_rec", + textRecognitionModelAsset: customRecAsset, + ...IGNORE_UNSUPPORTED + }); + + expect(ocr.options.pipelineConfig.assets.det?.url).toBe("https://example.com/custom-det.tar"); + expect(ocr.options.pipelineConfig.assets.rec?.url).toBe("https://example.com/custom-rec.tar"); + }); + + it("allows overriding only one side with a custom model asset", async () => { + const ocr = await PaddleOCR.create({ + pipelineConfig: pipelineConfigText, + text_detection_model_name: "custom_det", + textDetectionModelAsset: customDetAsset, + ...IGNORE_UNSUPPORTED + }); + + expect(ocr.options.pipelineConfig.assets.det?.url).toBe("https://example.com/custom-det.tar"); + expect(ocr.options.pipelineConfig.assets.rec?.url).toMatch(/PP-OCRv5_mobile_rec/); + }); + + it("lets explicit model names override pipeline config model assets", async () => { + const ocr = await PaddleOCR.create({ + pipelineConfig: { + pipeline_name: "OCR", + SubModules: { + TextDetection: { + model_name: "custom_det", + model_dir: customDetAsset + }, + TextRecognition: { + model_name: "custom_rec", + model_dir: customRecAsset + } + } + }, + text_detection_model_name: "PP-OCRv5_mobile_det", + text_recognition_model_name: "PP-OCRv5_mobile_rec", + ...CREATE_WITHOUT_INIT + }); + + expectDefaultModelAssets(ocr); + }); + + it("rejects unsupported lang/ocrVersion combinations", async () => { + await expect( + PaddleOCR.create({ + lang: "kl", + ocrVersion: "PP-OCRv5", + ...CREATE_WITHOUT_INIT + }) + ).rejects.toThrow(/Unsupported lang\/ocrVersion combination/i); + }); + + it("warns about unsupported pipeline features by default", async () => { + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + await PaddleOCR.create({ pipelineConfig: pipelineConfigText, ...CREATE_WITHOUT_INIT }); + + expect(warn).toHaveBeenCalled(); + warn.mockRestore(); + }); + + it("lets direct pipeline runtime parameters override pipelineConfig defaults", async () => { + const ocr = await PaddleOCR.create({ + pipelineConfig: pipelineConfigText, + text_det_limit_side_len: 200, + text_rec_score_thresh: 0.42, + ...IGNORE_UNSUPPORTED + }); + + expect(ocr.pipelineConfig.runtimeDefaults.text_det_limit_side_len).toBe(200); + expect(ocr.pipelineConfig.runtimeDefaults.text_rec_score_thresh).toBe(0.42); + }); + + it("lets direct batch sizes override pipelineConfig", async () => { + const ocr = await PaddleOCR.create({ + pipelineConfig: pipelineConfigText, + textRecognitionBatchSize: 2, + text_detection_batch_size: 3, + ...IGNORE_UNSUPPORTED + }); + + expect(ocr.pipelineConfig.textRecognitionBatchSize).toBe(2); + expect(ocr.pipelineConfig.textDetectionBatchSize).toBe(3); + }); + + it("applies textRecognitionBatchSize without user pipelineConfig", async () => { + const ocr = await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5", + text_recognition_batch_size: 4, + ...CREATE_WITHOUT_INIT + }); + + expect(ocr.pipelineConfig.textRecognitionBatchSize).toBe(4); + }); + + it("can turn unsupported pipeline warnings into errors", async () => { + await expect( + PaddleOCR.create({ + pipelineConfig: pipelineConfigText, + ...CREATE_WITHOUT_INIT, + unsupportedBehavior: "error" + }) + ).rejects.toThrow(/not yet supported/i); + }); + + it("parses the generated default OCR pipeline config text", () => { + const parsed = parseOcrPipelineConfigText(DEFAULT_OCR_PIPELINE_CONFIG_TEXT); + const normalized = normalizeOcrPipelineConfig(DEFAULT_OCR_PIPELINE_CONFIG_TEXT); + + expect(normalized).toMatchObject(normalizeOcrPipelineConfig(parsed)); + }); + + it("rejects custom model_dir without matching model_name", async () => { + await expect( + PaddleOCR.create({ + textDetectionModelAsset: customDetAsset, + ...CREATE_WITHOUT_INIT + }) + ).rejects.toThrow(/text_detection_model_dir requires text_detection_model_name/i); + }); + + it("rejects conflicting model asset aliases", async () => { + await expect( + PaddleOCR.create({ + text_detection_model_name: "custom_det", + textDetectionModelAsset: customDetAsset, + text_detection_model_dir: customRecAsset, + ...CREATE_WITHOUT_INIT + }) + ).rejects.toThrow(/Conflicting values provided for text detection model asset/i); + }); + + it("ignores unsupported asset metadata before inference.yml is loaded", async () => { + const ocr = await PaddleOCR.create({ + text_detection_model_name: "custom_det", + textDetectionModelAsset: { + ...customDetAsset, + model_name: "other_det" + }, + text_recognition_model_name: "custom_rec", + textRecognitionModelAsset: customRecAsset, + ...CREATE_WITHOUT_INIT + }); + + expect(ocr.options.pipelineConfig.assets.det?.url).toBe("https://example.com/custom-det.tar"); + }); + + it("initializes worker mode through the same API surface", async () => { + const worker = new MockWorker((message) => { + if (message.type === "init") { + return { + kind: "worker-transport-response", + status: "success", + requestId: message.requestId, + payload: { + summary: { + backend: "wasm", + webgpuAvailable: false, + detProvider: "wasm", + recProvider: "wasm", + assets: [], + elapsedMs: 12, + pipelineConfigWarnings: [] + }, + modelConfig: { + det: { resizeLong: 960 }, + rec: { imageShape: [3, 48, 320] } + } + } + }; + } + if (message.type === "dispose") { + return { + kind: "worker-transport-response", + status: "success", + requestId: message.requestId, + payload: {} + }; + } + return null; + }); + const ocr = await PaddleOCR.create({ + worker: { + createWorker: () => worker + }, + ortOptions: { + backend: "wasm", + proxy: true + }, + ...CREATE_WITHOUT_INIT + }); + + const summary = await ocr.initialize(); + + expect(summary.backend).toBe("wasm"); + expect(worker.messages[0].type).toBe("init"); + expect(worker.messages[0].payload.options.ortOptions.backend).toBe("wasm"); + expect(worker.messages[0].payload.options.ortOptions.disableWasmProxy).toBe(true); + expect(ocr.getModelConfig().det.resizeLong).toBe(960); + + await ocr.dispose(); + expect(worker.messages[1].type).toBe("dispose"); + expect(worker.terminated).toBe(true); + }); + + it("surfaces worker initialization failures", async () => { + const worker = new MockWorker((message) => { + if (message.type === "init") { + return { + kind: "worker-transport-response", + status: "error", + requestId: message.requestId, + error: { + name: "Error", + message: "worker init failed" + } + }; + } + return null; + }); + const ocr = await PaddleOCR.create({ + worker: { + createWorker: () => worker + }, + ...CREATE_WITHOUT_INIT + }); + + await expect(ocr.initialize()).rejects.toThrow(/worker init failed/i); + expect(worker.terminated).toBe(true); + }); +}); + +describe("OCR runtime parameter normalization", () => { + it("accepts camelCase aliases while preserving PaddleOCR names", () => { + const params = getOcrRuntimeParams( + { + det: { + resizeLong: 960, + limitType: "max", + maxSideLimit: 4000, + normalize: { + mean: [0.485, 0.456, 0.406], + std: [0.229, 0.224, 0.225], + scale: 1 / 255 + }, + postprocess: { + thresh: 0.3, + boxThresh: 0.6, + unclipRatio: 1.5 + } + }, + rec: { + charDict: [], + imageShape: [3, 48, 320] + } + }, + { + text_det_limit_type: "min" + }, + { + textDetThresh: 0.4, + textDetBoxThresh: 0.7, + textDetUnclipRatio: 2, + textRecScoreThresh: 0.2 + } + ); + + expect(params.det.limitType).toBe("min"); + expect(params.det.thresh).toBe(0.4); + expect(params.det.boxThresh).toBe(0.7); + expect(params.det.unclipRatio).toBe(2); + expect(params.pipeline.scoreThresh).toBe(0.2); + }); +}); + +describe("inference.yml model_name extraction", () => { + it("prefers Global.model_name when present", () => { + expect( + extractInferenceModelName(` +Global: + model_name: custom_det +PostProcess: + name: DBPostProcess +`) + ).toBe("custom_det"); + }); + + it("falls back to nested model_name fields", () => { + expect( + extractInferenceModelName(` +Deploy: + metadata: + model_name: custom_rec +`) + ).toBe("custom_rec"); + }); + + it("returns null when inference.yml has no model_name", () => { + expect( + extractInferenceModelName(` +PostProcess: + name: DBPostProcess +`) + ).toBeNull(); + }); +}); diff --git a/paddleocr-js/packages/core/test/ocr-config-branches.test.ts b/paddleocr-js/packages/core/test/ocr-config-branches.test.ts new file mode 100644 index 0000000..937d7e1 --- /dev/null +++ b/paddleocr-js/packages/core/test/ocr-config-branches.test.ts @@ -0,0 +1,98 @@ +import { describe, expect, it } from "vitest"; + +import { + normalizeOcrPipelineConfig, + parseOcrPipelineConfigText +} from "../src/pipelines/ocr/config"; + +describe("OCR pipeline config branches", () => { + it("rejects non-object YAML payloads and invalid input types", () => { + expect(() => parseOcrPipelineConfigText("- item")).toThrow(/must decode to an object/i); + expect(() => normalizeOcrPipelineConfig(123)).toThrow(/must be an object or YAML text/i); + }); + + it("rejects unsupported pipeline names", () => { + expect(() => + normalizeOcrPipelineConfig({ + pipeline_name: "DET_ONLY", + SubModules: { + TextDetection: { model_name: "PP-OCRv5_mobile_det" }, + TextRecognition: { model_name: "PP-OCRv5_mobile_rec" } + } + }) + ).toThrow(/Unsupported pipeline_name/i); + }); + + it("accepts default pipeline names and suppresses general text_type warnings", () => { + const normalized = normalizeOcrPipelineConfig({ + SubModules: { + TextDetection: { model_name: "PP-OCRv5_mobile_det" }, + TextRecognition: { model_name: "PP-OCRv5_mobile_rec" } + }, + text_type: "general" + }); + + expect(normalized.pipelineName).toBe("OCR"); + expect(normalized.warnings).toEqual([]); + expect(normalized.unsupportedFeatures).toEqual([]); + }); + + it("supports object model_dir assets and warns on unsupported text_type values", () => { + const normalized = normalizeOcrPipelineConfig({ + SubModules: { + TextDetection: { + model_name: "custom_det", + model_dir: { + url: "/det.tar" + }, + limit_side_len: "", + max_side_limit: null + }, + TextRecognition: { + model_name: "custom_rec", + score_thresh: "0.7" + } + }, + text_type: "seal" + }); + + expect(normalized.assets.det).toMatchObject({ + url: "/det.tar" + }); + expect(normalized.runtimeDefaults.text_det_limit_side_len).toBeUndefined(); + expect(normalized.runtimeDefaults.text_det_max_side_limit).toBeUndefined(); + expect(normalized.runtimeDefaults.text_rec_score_thresh).toBe(0.7); + expect(normalized.warnings).toContain('text_type "seal" is not used by PaddleOCR.js yet.'); + }); + + it("rejects invalid model_dir values and missing model names", () => { + expect(() => + normalizeOcrPipelineConfig({ + SubModules: { + TextDetection: { + model_dir: { + url: "/det.tar" + } + }, + TextRecognition: { + model_name: "PP-OCRv5_mobile_rec" + } + } + }) + ).toThrow(/model_name must be provided/i); + + expect(() => + normalizeOcrPipelineConfig({ + SubModules: { + TextDetection: { + model_name: "PP-OCRv5_mobile_det", + model_dir: "/det" + }, + TextRecognition: { + model_name: "PP-OCRv5_mobile_rec" + } + } + }) + ).toThrow(/model_dir must be null or an asset descriptor object/i); + }); +}); diff --git a/paddleocr-js/packages/core/test/ocr-core.test.ts b/paddleocr-js/packages/core/test/ocr-core.test.ts new file mode 100644 index 0000000..e44356f --- /dev/null +++ b/paddleocr-js/packages/core/test/ocr-core.test.ts @@ -0,0 +1,518 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +const loadModelAsset = vi.fn(); +const createDetModel = vi.fn(); +const createRecModel = vi.fn(); +const cropByPoly = vi.fn(); +const initOpenCvRuntime = vi.fn(); +const initOrtRuntime = vi.fn(); +const nowMs = vi.fn(); +const getOcrRuntimeParams = vi.fn(); +const cloneDefaultOcrConfig = vi.fn(); +const validateLoadedModelName = vi.fn(); + +vi.mock("../src/resources/index", () => ({ + loadModelAsset +})); + +vi.mock("../src/models/index", () => ({ + createDetModel, + createRecModel +})); + +vi.mock("../src/pipelines/ocr/crop", () => ({ + cropByPoly +})); + +vi.mock("../src/runtime/opencv", () => ({ + initOpenCvRuntime +})); + +vi.mock("../src/runtime/ort", () => ({ + initOrtRuntime +})); + +vi.mock("../src/utils/common", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + nowMs + }; +}); + +vi.mock("../src/pipelines/ocr/runtime-params", () => ({ + getOcrRuntimeParams +})); + +vi.mock("../src/pipelines/ocr/shared", () => ({ + cloneDefaultOcrConfig, + validateLoadedModelName +})); + +afterEach(() => { + vi.resetModules(); + vi.clearAllMocks(); +}); + +const AUTO_ORT_OPTIONS = Object.freeze({ + backend: "auto" +}); + +function createResolvedAssets() { + return { + det: { url: "/det.tar" }, + rec: { url: "/rec.tar" } + }; +} + +function minimalPipelineConfig(overrides: Record = {}) { + return { + pipelineName: "OCR", + raw: {}, + warnings: [] as string[], + unsupportedFeatures: [] as string[], + modelSelection: { + textDetectionModelName: "det-name", + textRecognitionModelName: "rec-name" + }, + assets: createResolvedAssets(), + runtimeDefaults: {} as Record, + pipelineBatchSize: 1, + textDetectionBatchSize: 1, + textRecognitionBatchSize: 1, + ...overrides + }; +} + +function mockEmptyDefaultOcrConfig() { + cloneDefaultOcrConfig.mockReturnValue({ det: {}, rec: {} }); +} + +async function loadCoreModule() { + return import("../src/pipelines/ocr/core"); +} + +describe("OCR pipeline core", () => { + it("initializes OpenCV and ORT, loads assets, and creates models", async () => { + const cv = { name: "cv" }; + const ort = { name: "ort" }; + const detModel = { config: { det: true }, provider: "wasm", dispose: vi.fn() }; + const recModel = { config: { rec: true }, provider: "webgpu", dispose: vi.fn() }; + + cloneDefaultOcrConfig.mockReturnValue({ + det: { marker: "default-det-config" }, + rec: { marker: "default-rec-config" } + }); + nowMs.mockReturnValueOnce(100).mockReturnValueOnce(145); + initOpenCvRuntime.mockResolvedValue({ cv }); + initOrtRuntime.mockResolvedValue({ + ort, + webgpuState: { available: true, reason: "" }, + backend: "auto" + }); + loadModelAsset + .mockResolvedValueOnce({ + modelBytes: new Uint8Array([1]), + configText: "det-config", + download: { url: "/det.tar", bytes: 100 } + }) + .mockResolvedValueOnce({ + modelBytes: new Uint8Array([2]), + configText: "rec-config", + download: { url: "/rec.tar", bytes: 200 } + }); + createDetModel.mockResolvedValue(detModel); + createRecModel.mockResolvedValue(recModel); + + const { OcrPipelineRunner } = await loadCoreModule(); + const ensureServedFromHttp = vi.fn(); + const runner = new OcrPipelineRunner({ + pipelineConfig: minimalPipelineConfig({ + warnings: ["warning"] + }), + ortOptions: AUTO_ORT_OPTIONS, + ensureServedFromHttp + }); + + const summary = await runner.initialize(); + + expect(ensureServedFromHttp).toHaveBeenCalledTimes(1); + expect(initOpenCvRuntime).toHaveBeenCalledTimes(1); + expect(initOrtRuntime).toHaveBeenCalledWith(AUTO_ORT_OPTIONS); + expect(loadModelAsset).toHaveBeenCalledTimes(2); + expect(validateLoadedModelName).toHaveBeenNthCalledWith( + 1, + "TextDetection", + "det-name", + "det-config" + ); + expect(validateLoadedModelName).toHaveBeenNthCalledWith( + 2, + "TextRecognition", + "rec-name", + "rec-config" + ); + expect(createDetModel).toHaveBeenCalledWith({ + ort, + modelBytes: new Uint8Array([1]), + configText: "det-config", + backend: AUTO_ORT_OPTIONS.backend, + webgpuState: { available: true, reason: "" }, + batchSize: 1 + }); + expect(createRecModel).toHaveBeenCalledWith({ + ort, + modelBytes: new Uint8Array([2]), + configText: "rec-config", + backend: AUTO_ORT_OPTIONS.backend, + webgpuState: { available: true, reason: "" }, + batchSize: 1 + }); + expect(summary).toEqual({ + backend: AUTO_ORT_OPTIONS.backend, + webgpuAvailable: true, + detProvider: "wasm", + recProvider: "webgpu", + assets: [ + { url: "/det.tar", bytes: 100 }, + { url: "/rec.tar", bytes: 200 } + ], + elapsedMs: 45, + pipelineConfigWarnings: ["warning"] + }); + expect(runner.getInitializationSummary()).toEqual(summary); + expect(runner.getModelConfig()).toEqual({ + det: { det: true }, + rec: { rec: true } + }); + }); + + it("rejects initialization when assets are not pre-resolved", async () => { + mockEmptyDefaultOcrConfig(); + initOpenCvRuntime.mockResolvedValue({ cv: {} }); + initOrtRuntime.mockResolvedValue({ + ort: {}, + webgpuState: { available: false, reason: "" }, + backend: "wasm" + }); + + const { OcrPipelineRunner } = await loadCoreModule(); + const runner = new OcrPipelineRunner({ + pipelineConfig: minimalPipelineConfig({ + assets: { + det: null, + rec: { id: "rec" } + } + }) + }); + + await expect(runner.initialize()).rejects.toThrow( + /requires pre-resolved detection and recognition asset/i + ); + }); + + it("predicts OCR results and filters by score threshold", async () => { + const cv = { name: "cv" }; + const sourceMat = { delete: vi.fn() }; + const sourceImage = { + width: 640, + height: 480, + mat: sourceMat, + dispose: vi.fn() + }; + const cropA = { delete: vi.fn() }; + const cropB = { delete: vi.fn() }; + const detModel = { + provider: "wasm", + predict: vi + .fn() + .mockResolvedValue([ + { boxes: [{ poly: [[1, 1]] }, { poly: [[2, 2]] }], srcW: 640, srcH: 480 } + ]), + dispose: vi.fn() + }; + const recModel = { + provider: "wasm", + predict: vi.fn().mockResolvedValue([ + { text: "high", score: 0.95 }, + { text: "low", score: 0.4 } + ]), + dispose: vi.fn() + }; + + mockEmptyDefaultOcrConfig(); + getOcrRuntimeParams.mockReturnValue({ + det: {}, + pipeline: { scoreThresh: 0.5 } + }); + cropByPoly.mockReturnValueOnce(cropA).mockReturnValueOnce(cropB); + nowMs + .mockReturnValueOnce(10) + .mockReturnValueOnce(20) + .mockReturnValueOnce(30) + .mockReturnValueOnce(40) + .mockReturnValueOnce(60) + .mockReturnValueOnce(70); + + const { OcrPipelineRunner } = await loadCoreModule(); + const runner = new OcrPipelineRunner({ + pipelineConfig: minimalPipelineConfig({ + runtimeDefaults: { text_det_limit_side_len: 64 } + }), + ortOptions: AUTO_ORT_OPTIONS, + sourceToMat: vi.fn().mockResolvedValue(sourceImage) + }); + runner.cv = cv; + runner.ort = { name: "ort" }; + runner.detModel = detModel; + runner.recModel = recModel; + runner.webgpuState = { available: false, reason: "" }; + runner.modelConfig = { det: { conf: true }, rec: { conf: true } }; + + const result = await runner.predict({ kind: "blob" }, { text_rec_score_thresh: 0.8 }); + + expect(getOcrRuntimeParams).toHaveBeenCalledWith( + { det: { conf: true }, rec: { conf: true } }, + { text_det_limit_side_len: 64 }, + { text_rec_score_thresh: 0.8 } + ); + expect(detModel.predict).toHaveBeenCalledWith(cv, [sourceMat], {}); + expect(cropByPoly).toHaveBeenNthCalledWith(1, cv, sourceMat, [[1, 1]]); + expect(cropByPoly).toHaveBeenNthCalledWith(2, cv, sourceMat, [[2, 2]]); + expect(recModel.predict).toHaveBeenCalledWith(cv, [cropA, cropB]); + expect(cropA.delete).toHaveBeenCalledTimes(1); + expect(cropB.delete).toHaveBeenCalledTimes(1); + expect(sourceImage.dispose).toHaveBeenCalledTimes(1); + expect(result).toEqual([ + { + image: { width: 640, height: 480 }, + items: [{ poly: [[1, 1]], text: "high", score: 0.95 }], + metrics: { + detMs: 10, + recMs: 20, + totalMs: 60, + detectedBoxes: 2, + recognizedCount: 1 + }, + runtime: { + requestedBackend: AUTO_ORT_OPTIONS.backend, + detProvider: "wasm", + recProvider: "wasm", + webgpuAvailable: false + } + } + ]); + }); + + it("returns one OCR result per source when predict receives an array of inputs", async () => { + const cv = { name: "cv" }; + const mat1 = { delete: vi.fn() }; + const mat2 = { delete: vi.fn() }; + const dispose1 = vi.fn(); + const dispose2 = vi.fn(); + const sourceImage1 = { width: 100, height: 100, mat: mat1, dispose: dispose1 }; + const sourceImage2 = { width: 200, height: 200, mat: mat2, dispose: dispose2 }; + const crop1 = { delete: vi.fn() }; + const crop2 = { delete: vi.fn() }; + const detModel = { + provider: "wasm", + predict: vi + .fn() + .mockResolvedValueOnce([{ boxes: [{ poly: [[1, 1]] }], srcW: 100, srcH: 100 }]) + .mockResolvedValueOnce([{ boxes: [{ poly: [[2, 2]] }], srcW: 200, srcH: 200 }]), + dispose: vi.fn() + }; + const recModel = { + provider: "wasm", + predict: vi + .fn() + .mockResolvedValueOnce([{ text: "a", score: 1 }]) + .mockResolvedValueOnce([{ text: "b", score: 1 }]), + dispose: vi.fn() + }; + + mockEmptyDefaultOcrConfig(); + getOcrRuntimeParams.mockReturnValue({ + det: {}, + pipeline: { scoreThresh: 0 } + }); + cropByPoly.mockReturnValueOnce(crop1).mockReturnValueOnce(crop2); + nowMs.mockReturnValue(0); + + const { OcrPipelineRunner } = await loadCoreModule(); + const runner = new OcrPipelineRunner({ + pipelineConfig: minimalPipelineConfig(), + ortOptions: AUTO_ORT_OPTIONS, + sourceToMat: vi.fn().mockResolvedValueOnce(sourceImage1).mockResolvedValueOnce(sourceImage2) + }); + runner.cv = cv; + runner.ort = { name: "ort" }; + runner.detModel = detModel; + runner.recModel = recModel; + runner.webgpuState = { available: false, reason: "" }; + runner.modelConfig = { det: { conf: true }, rec: { conf: true } }; + + const results = await runner.predict([{ kind: "a" }, { kind: "b" }], {}); + + expect(detModel.predict).toHaveBeenCalledTimes(2); + expect(detModel.predict).toHaveBeenNthCalledWith(1, cv, [mat1], {}); + expect(detModel.predict).toHaveBeenNthCalledWith(2, cv, [mat2], {}); + expect(recModel.predict).toHaveBeenNthCalledWith(1, cv, [crop1]); + expect(recModel.predict).toHaveBeenNthCalledWith(2, cv, [crop2]); + expect(results).toHaveLength(2); + expect(results[0].image).toEqual({ width: 100, height: 100 }); + expect(results[0].items[0].text).toBe("a"); + expect(results[1].image).toEqual({ width: 200, height: 200 }); + expect(results[1].items[0].text).toBe("b"); + expect(dispose1).toHaveBeenCalledTimes(1); + expect(dispose2).toHaveBeenCalledTimes(1); + }); + + it("passes multiple sources to det in one pipeline batch when pipelineBatchSize > 1", async () => { + const cv = { name: "cv" }; + const mat1 = { delete: vi.fn() }; + const mat2 = { delete: vi.fn() }; + const dispose1 = vi.fn(); + const dispose2 = vi.fn(); + const sourceImage1 = { width: 100, height: 100, mat: mat1, dispose: dispose1 }; + const sourceImage2 = { width: 200, height: 200, mat: mat2, dispose: dispose2 }; + const crop1 = { delete: vi.fn() }; + const crop2 = { delete: vi.fn() }; + const detModel = { + provider: "wasm", + predict: vi.fn().mockResolvedValue([ + { boxes: [{ poly: [[1, 1]] }], srcW: 100, srcH: 100 }, + { boxes: [{ poly: [[2, 2]] }], srcW: 200, srcH: 200 } + ]), + dispose: vi.fn() + }; + const recModel = { + provider: "wasm", + predict: vi + .fn() + .mockResolvedValueOnce([{ text: "a", score: 1 }]) + .mockResolvedValueOnce([{ text: "b", score: 1 }]), + dispose: vi.fn() + }; + + mockEmptyDefaultOcrConfig(); + getOcrRuntimeParams.mockReturnValue({ + det: {}, + pipeline: { scoreThresh: 0 } + }); + cropByPoly.mockReturnValueOnce(crop1).mockReturnValueOnce(crop2); + nowMs.mockReturnValue(0); + + const { OcrPipelineRunner } = await loadCoreModule(); + const runner = new OcrPipelineRunner({ + pipelineConfig: minimalPipelineConfig({ pipelineBatchSize: 2 }), + ortOptions: AUTO_ORT_OPTIONS, + sourceToMat: vi.fn().mockResolvedValueOnce(sourceImage1).mockResolvedValueOnce(sourceImage2) + }); + runner.cv = cv; + runner.ort = { name: "ort" }; + runner.detModel = detModel; + runner.recModel = recModel; + runner.webgpuState = { available: false, reason: "" }; + runner.modelConfig = { det: { conf: true }, rec: { conf: true } }; + + const results = await runner.predict([{ kind: "a" }, { kind: "b" }], {}); + + expect(detModel.predict).toHaveBeenCalledTimes(1); + expect(detModel.predict).toHaveBeenCalledWith(cv, [mat1, mat2], {}); + expect(recModel.predict).toHaveBeenNthCalledWith(1, cv, [crop1]); + expect(recModel.predict).toHaveBeenNthCalledWith(2, cv, [crop2]); + expect(results).toHaveLength(2); + expect(dispose1).toHaveBeenCalledTimes(1); + expect(dispose2).toHaveBeenCalledTimes(1); + }); + + it("auto-initializes on predict and rejects when source adapter is missing", async () => { + const detModel = { + provider: "wasm", + predict: vi.fn().mockResolvedValue([{ boxes: [], srcW: 1, srcH: 1 }]), + dispose: vi.fn() + }; + const recModel = { + provider: "wasm", + predict: vi.fn().mockResolvedValue([]), + dispose: vi.fn() + }; + + mockEmptyDefaultOcrConfig(); + nowMs.mockReturnValue(0); + initOpenCvRuntime.mockResolvedValue({ cv: {} }); + initOrtRuntime.mockResolvedValue({ + ort: {}, + webgpuState: { available: false, reason: "" }, + backend: "wasm" + }); + loadModelAsset + .mockResolvedValueOnce({ + modelBytes: new Uint8Array([1]), + configText: "det-config", + download: { url: "/det.tar", bytes: 100 } + }) + .mockResolvedValueOnce({ + modelBytes: new Uint8Array([2]), + configText: "rec-config", + download: { url: "/rec.tar", bytes: 200 } + }); + createDetModel.mockResolvedValue(detModel); + createRecModel.mockResolvedValue(recModel); + getOcrRuntimeParams.mockReturnValue({ + det: {}, + pipeline: { scoreThresh: 0 } + }); + + const { OcrPipelineRunner } = await loadCoreModule(); + const noSourceRunner = new OcrPipelineRunner({ + pipelineConfig: minimalPipelineConfig() + }); + await expect(noSourceRunner.predict({}, {})).rejects.toThrow( + /source adapter is not configured/i + ); + + const sourceImage = { + width: 1, + height: 1, + mat: {}, + dispose: vi.fn() + }; + const runner = new OcrPipelineRunner({ + pipelineConfig: minimalPipelineConfig(), + sourceToMat: vi.fn().mockResolvedValue(sourceImage) + }); + + const result = await runner.predict({}, {}); + + expect(initOpenCvRuntime).toHaveBeenCalled(); + expect(result[0].items).toEqual([]); + expect(sourceImage.dispose).toHaveBeenCalledTimes(1); + }); + + it("disposes models and clears references", async () => { + mockEmptyDefaultOcrConfig(); + const detDispose = vi.fn().mockResolvedValue(undefined); + const recDispose = vi.fn().mockResolvedValue(undefined); + + const { OcrPipelineRunner } = await loadCoreModule(); + const runner = new OcrPipelineRunner({ + pipelineConfig: minimalPipelineConfig() + }); + runner.detModel = { dispose: detDispose }; + runner.recModel = { dispose: recDispose }; + + await runner.disposeModelsOnly(); + expect(detDispose).toHaveBeenCalledTimes(1); + expect(recDispose).toHaveBeenCalledTimes(1); + expect(runner.detModel).toBeNull(); + expect(runner.recModel).toBeNull(); + + runner.detModel = { dispose: detDispose }; + runner.recModel = { dispose: recDispose }; + await runner.dispose(); + expect(runner.detModel).toBeNull(); + expect(runner.recModel).toBeNull(); + }); +}); diff --git a/paddleocr-js/packages/core/test/ocr-shared.test.ts b/paddleocr-js/packages/core/test/ocr-shared.test.ts new file mode 100644 index 0000000..bcd87b1 --- /dev/null +++ b/paddleocr-js/packages/core/test/ocr-shared.test.ts @@ -0,0 +1,130 @@ +import { describe, expect, it } from "vitest"; + +import { + cloneDefaultOcrConfig, + normalizeOrtOptions, + resolvePaddleOCROptions, + resolveWorkerOptions, + validateLoadedModelName +} from "../src/pipelines/ocr/shared"; + +describe("OCR shared option resolution", () => { + it("normalizes ORT options and reuses the same backend fallback", () => { + const defaultOrt = normalizeOrtOptions(); + + expect(defaultOrt).toMatchObject({ + backend: expect.any(String) + }); + expect(normalizeOrtOptions({ backend: "invalid", proxy: true })).toEqual({ + backend: defaultOrt.backend, + proxy: true + }); + expect( + normalizeOrtOptions({ + backend: "wasm", + wasmPaths: "/wasm/", + numThreads: 2, + simd: true, + proxy: false + }) + ).toEqual({ + backend: "wasm", + wasmPaths: "/wasm/", + numThreads: 2, + simd: true, + proxy: false + }); + }); + + it("resolves worker options from booleans and custom factories", () => { + const createWorker = () => ({}); + + expect(resolveWorkerOptions(false)).toEqual({ + enabled: false, + createWorker: null + }); + expect(resolveWorkerOptions(true)).toEqual({ + enabled: true, + createWorker: null + }); + expect(resolveWorkerOptions({ createWorker })).toEqual({ + enabled: true, + createWorker + }); + expect(resolveWorkerOptions({})).toEqual({ + enabled: true, + createWorker: null + }); + }); + + it("rejects unsupported worker option types", () => { + expect(() => resolveWorkerOptions("yes")).toThrow( + /worker must be a boolean or an options object/i + ); + }); + + it("returns ortOptions, assets, and model selection for explicit model names", () => { + const options = resolvePaddleOCROptions({ + text_detection_model_name: "PP-OCRv5_mobile_det", + text_recognition_model_name: "PP-OCRv5_mobile_rec", + ortOptions: { + backend: "webgpu", + proxy: true + } + }); + + expect(options.ortOptions).toEqual({ + backend: "webgpu", + proxy: true + }); + expect(options.pipelineConfig.assets.det?.url).toMatch(/PP-OCRv5_mobile_det.*\.tar$/); + expect(options.pipelineConfig.assets.rec?.url).toMatch(/PP-OCRv5_mobile_rec.*\.tar$/); + expect(options.pipelineConfig.modelSelection).toEqual({ + textDetectionModelName: "PP-OCRv5_mobile_det", + textRecognitionModelName: "PP-OCRv5_mobile_rec" + }); + }); + + it("rejects incomplete pipeline model selection", () => { + expect(() => + resolvePaddleOCROptions({ + pipelineConfig: { + pipeline_name: "OCR", + SubModules: { + TextDetection: { + model_name: "PP-OCRv5_mobile_det" + } + } + } + }) + ).toThrow(/must define both "SubModules.TextDetection" and "SubModules.TextRecognition"/i); + }); + + it("clones the default OCR config deeply", () => { + const cloned = cloneDefaultOcrConfig(); + cloned.det.postprocess.thresh = 0.99; + + expect(cloned.det.postprocess.thresh).toBe(0.99); + + const freshClone = cloneDefaultOcrConfig(); + expect(freshClone.det.postprocess.thresh).not.toBe(0.99); + }); + + it("validates loaded model names against inference.yml (roles match pipeline initialize)", () => { + expect(() => + validateLoadedModelName( + "TextDetection", + "PP-OCRv5_mobile_det", + "Global:\n model_name: PP-OCRv5_mobile_det" + ) + ).not.toThrow(); + + expect(() => + validateLoadedModelName( + "TextDetection", + "PP-OCRv5_mobile_det", + "Global:\n model_name: other" + ) + ).toThrow(/requested model_name is "PP-OCRv5_mobile_det"/i); + }); +}); diff --git a/paddleocr-js/packages/core/test/ocr-worker-entry.test.ts b/paddleocr-js/packages/core/test/ocr-worker-entry.test.ts new file mode 100644 index 0000000..b6c972f --- /dev/null +++ b/paddleocr-js/packages/core/test/ocr-worker-entry.test.ts @@ -0,0 +1,163 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +let capturedHandler = null; +const attachWorkerMessageHandler = vi.fn((handler) => { + capturedHandler = handler; +}); + +const sourcePayloadToMat = vi.fn(); +const ensureServedFromHttp = vi.fn(); +const initialize = vi.fn(); +const getModelConfig = vi.fn(); +const predict = vi.fn(); +const dispose = vi.fn(); + +const OcrPipelineRunner = vi.fn(function MockOcrPipelineRunner(options) { + this.options = options; + this.initialize = initialize; + this.getModelConfig = getModelConfig; + this.predict = predict; + this.dispose = dispose; +}); + +vi.mock("../src/worker/entry", () => ({ + attachWorkerMessageHandler +})); + +vi.mock("../src/platform/worker", () => ({ + sourcePayloadToMat, + ensureServedFromHttp +})); + +vi.mock("../src/pipelines/ocr/core", () => ({ + OcrPipelineRunner +})); + +afterEach(() => { + capturedHandler = null; + vi.resetModules(); + vi.clearAllMocks(); +}); + +async function loadWorkerEntry() { + await import("../src/pipelines/ocr/worker-entry"); + expect(typeof capturedHandler).toBe("function"); +} + +const WASM_INIT_SUMMARY = Object.freeze({ backend: "wasm" }); +const EMPTY_MODEL_CONFIG = Object.freeze({ det: {}, rec: {} }); + +const WORKER_TEST_PIPELINE = Object.freeze({ + pipelineName: "OCR", + raw: {}, + warnings: [] as string[], + unsupportedFeatures: [] as string[], + modelSelection: { + textDetectionModelName: "d", + textRecognitionModelName: "r" + }, + assets: { + det: { url: "/d" }, + rec: { url: "/r" } + }, + runtimeDefaults: {}, + pipelineBatchSize: 1, + textDetectionBatchSize: 1, + textRecognitionBatchSize: 1 +}); + +function setupResolvedInitAndModelConfig() { + initialize.mockResolvedValue(WASM_INIT_SUMMARY); + getModelConfig.mockReturnValue(EMPTY_MODEL_CONFIG); +} + +describe("OCR worker entry bootstrap", () => { + it("registers a worker message handler on module load", async () => { + await loadWorkerEntry(); + + expect(attachWorkerMessageHandler).toHaveBeenCalledTimes(1); + expect(typeof capturedHandler).toBe("function"); + }); + + it("initializes a runner and returns summary + model config", async () => { + setupResolvedInitAndModelConfig(); + + await loadWorkerEntry(); + const result = await capturedHandler("init", { + options: { + pipelineConfig: WORKER_TEST_PIPELINE, + ortOptions: { backend: "wasm" } + } + }); + + expect(OcrPipelineRunner).toHaveBeenCalledWith({ + pipelineConfig: WORKER_TEST_PIPELINE, + ortOptions: { backend: "wasm" }, + ensureServedFromHttp, + sourceToMat: sourcePayloadToMat + }); + expect(result).toEqual({ + summary: WASM_INIT_SUMMARY, + modelConfig: EMPTY_MODEL_CONFIG + }); + }); + + it("disposes an existing runner before re-initializing", async () => { + setupResolvedInitAndModelConfig(); + + await loadWorkerEntry(); + await capturedHandler("init", { options: { pipelineConfig: WORKER_TEST_PIPELINE, id: 1 } }); + await capturedHandler("init", { options: { pipelineConfig: WORKER_TEST_PIPELINE, id: 2 } }); + + expect(dispose).toHaveBeenCalledTimes(1); + expect(OcrPipelineRunner).toHaveBeenCalledTimes(2); + }); + + it("routes predict and dispose requests through the active runner", async () => { + setupResolvedInitAndModelConfig(); + const predictPayload = [ + { + image: { width: 1, height: 1 }, + items: [], + metrics: { + detMs: 0, + recMs: 0, + totalMs: 0, + detectedBoxes: 0, + recognizedCount: 0 + }, + runtime: { + requestedBackend: "auto", + detProvider: "wasm", + recProvider: "wasm", + webgpuAvailable: false + } + } + ]; + predict.mockResolvedValue(predictPayload); + dispose.mockResolvedValue(undefined); + + await loadWorkerEntry(); + await capturedHandler("init", { options: { pipelineConfig: WORKER_TEST_PIPELINE } }); + + await expect( + capturedHandler("predict", { + sources: [{ kind: "imageBitmap" }], + params: { limit: 1 } + }) + ).resolves.toEqual(predictPayload); + expect(predict).toHaveBeenCalledWith([{ kind: "imageBitmap" }], { limit: 1 }); + + await expect(capturedHandler("dispose", {})).resolves.toEqual({}); + expect(dispose).toHaveBeenCalledTimes(1); + }); + + it("rejects predict before initialization and unknown request types", async () => { + await loadWorkerEntry(); + + await expect(capturedHandler("predict", { sources: [{}], params: {} })).rejects.toThrow( + /not initialized/i + ); + await expect(capturedHandler("other", {})).rejects.toThrow(/Unsupported worker request type/i); + }); +}); diff --git a/paddleocr-js/packages/core/test/pipelines-index.test.ts b/paddleocr-js/packages/core/test/pipelines-index.test.ts new file mode 100644 index 0000000..08ede2a --- /dev/null +++ b/paddleocr-js/packages/core/test/pipelines-index.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it, vi } from "vitest"; + +vi.mock("@techstark/opencv-js", () => ({ + default: { + Mat() {} + } +})); + +import * as publicPipelines from "../src/pipelines/index"; +import * as ocrPipeline from "../src/pipelines/ocr/index"; + +describe("pipeline entrypoints", () => { + it("re-exports OCR pipeline APIs from the pipelines index", () => { + expect(publicPipelines.PaddleOCR).toBe(ocrPipeline.PaddleOCR); + expect(publicPipelines.parseOcrPipelineConfigText).toBe(ocrPipeline.parseOcrPipelineConfigText); + expect(publicPipelines.normalizeOcrPipelineConfig).toBe(ocrPipeline.normalizeOcrPipelineConfig); + }); + + it("exposes only the barrel surface re-exported from pipelines/ocr (no drift)", () => { + expect(Object.keys(publicPipelines).sort()).toEqual( + ["normalizeOcrPipelineConfig", "parseOcrPipelineConfigText", "PaddleOCR"].sort() + ); + }); +}); diff --git a/paddleocr-js/packages/core/test/platform-browser.test.ts b/paddleocr-js/packages/core/test/platform-browser.test.ts new file mode 100644 index 0000000..b583221 --- /dev/null +++ b/paddleocr-js/packages/core/test/platform-browser.test.ts @@ -0,0 +1,172 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { + ensureServedFromHttp, + sourceToImageBitmap, + sourceToMat, + sourceToWorkerPayload +} from "../src/platform/browser"; + +class FakeImageBitmap { + constructor(width = 10, height = 20) { + this.width = width; + this.height = height; + this.close = vi.fn(); + } +} + +class FakeImageData { + constructor(data, width, height) { + this.data = data; + this.width = width; + this.height = height; + } +} + +class FakeCanvasElement {} +class FakeImageElement {} + +function stubCanvasDocument() { + const drawImage = vi.fn(); + const putImageData = vi.fn(); + const canvas = { + width: 0, + height: 0, + getContext: vi.fn(() => ({ + drawImage, + putImageData + })) + }; + + vi.stubGlobal("document", { + createElement: vi.fn(() => canvas) + }); + + return { + canvas, + drawImage, + putImageData + }; +} + +afterEach(() => { + vi.unstubAllGlobals(); + vi.restoreAllMocks(); +}); + +describe("platform/browser", () => { + it("rejects file:// origins", () => { + vi.stubGlobal("location", { protocol: "file:" }); + + expect(() => ensureServedFromHttp()).toThrow(/requires an HTTP\(S\) origin/i); + }); + + it("allows http origins", () => { + vi.stubGlobal("location", { protocol: "https:" }); + + expect(() => ensureServedFromHttp()).not.toThrow(); + }); + + it("reuses existing ImageBitmap sources", async () => { + vi.stubGlobal("ImageBitmap", FakeImageBitmap); + const bitmap = new FakeImageBitmap(); + + await expect(sourceToImageBitmap(bitmap)).resolves.toBe(bitmap); + }); + + it("converts Blob and canvas sources through createImageBitmap", async () => { + const bitmap = new FakeImageBitmap(); + const createImageBitmap = vi.fn().mockResolvedValue(bitmap); + + vi.stubGlobal("ImageBitmap", FakeImageBitmap); + vi.stubGlobal("HTMLCanvasElement", FakeCanvasElement); + vi.stubGlobal("createImageBitmap", createImageBitmap); + + const blob = new Blob(["hello"], { type: "text/plain" }); + await expect(sourceToImageBitmap(blob)).resolves.toBe(bitmap); + + const canvas = new FakeCanvasElement(); + await expect(sourceToImageBitmap(canvas)).resolves.toBe(bitmap); + expect(createImageBitmap).toHaveBeenCalledTimes(2); + }); + + it("converts ImageData and img sources through createImageBitmap", async () => { + const bitmap = new FakeImageBitmap(); + const createImageBitmap = vi.fn().mockResolvedValue(bitmap); + const { canvas, putImageData } = stubCanvasDocument(); + + vi.stubGlobal("ImageBitmap", FakeImageBitmap); + vi.stubGlobal("ImageData", FakeImageData); + vi.stubGlobal("HTMLImageElement", FakeImageElement); + vi.stubGlobal("createImageBitmap", createImageBitmap); + + const imageData = new FakeImageData(new Uint8ClampedArray(16), 2, 2); + await expect(sourceToImageBitmap(imageData)).resolves.toBe(bitmap); + expect(canvas.width).toBe(2); + expect(canvas.height).toBe(2); + expect(putImageData).toHaveBeenCalledWith(imageData, 0, 0); + + const image = new FakeImageElement(); + await expect(sourceToImageBitmap(image)).resolves.toBe(bitmap); + }); + + it("rejects unsupported image sources", async () => { + vi.stubGlobal("ImageData", FakeImageData); + + await expect(sourceToImageBitmap({})).rejects.toThrow(/Unsupported image source/i); + }); + + it("converts bitmap-backed sources into cv.Mat wrappers", async () => { + const bitmap = new FakeImageBitmap(64, 32); + const { drawImage } = stubCanvasDocument(); + const mat = { + deleted: false, + delete() { + this.deleted = true; + } + }; + + vi.stubGlobal("ImageBitmap", FakeImageBitmap); + + const loaded = await sourceToMat( + { + imread: vi.fn(() => mat) + }, + bitmap + ); + + expect(loaded.width).toBe(64); + expect(loaded.height).toBe(32); + expect(drawImage).toHaveBeenCalledWith(bitmap, 0, 0); + + loaded.dispose(); + expect(mat.deleted).toBe(true); + expect(bitmap.close).toHaveBeenCalledTimes(1); + }); + + it("creates transferable worker payloads from ImageBitmap sources", async () => { + const sourceBitmap = new FakeImageBitmap(10, 20); + const clonedBitmap = new FakeImageBitmap(10, 20); + const createImageBitmap = vi.fn().mockResolvedValue(clonedBitmap); + + vi.stubGlobal("ImageBitmap", FakeImageBitmap); + vi.stubGlobal("createImageBitmap", createImageBitmap); + + const result = await sourceToWorkerPayload(sourceBitmap); + + expect(createImageBitmap).toHaveBeenCalledWith(sourceBitmap); + expect(result).toEqual({ + payload: { + kind: "imageBitmap", + imageBitmap: clonedBitmap + }, + transferables: [clonedBitmap] + }); + }); + + it("rejects worker payload creation when ImageBitmap support is missing", async () => { + await expect(sourceToWorkerPayload(new Blob(["x"]))).rejects.toThrow( + /Worker mode requires ImageBitmap support/i + ); + }); +}); diff --git a/paddleocr-js/packages/core/test/platform-worker.test.ts b/paddleocr-js/packages/core/test/platform-worker.test.ts new file mode 100644 index 0000000..97082ab --- /dev/null +++ b/paddleocr-js/packages/core/test/platform-worker.test.ts @@ -0,0 +1,143 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { ensureServedFromHttp, sourcePayloadToMat } from "../src/platform/worker"; + +class FakeMat { + constructor(rows, cols) { + this.rows = rows; + this.cols = cols; + this.deleted = false; + } + + clone() { + return new FakeMat(this.rows, this.cols); + } + + delete() { + this.deleted = true; + } +} + +class FakeImageBitmap { + constructor(width = 8, height = 6) { + this.width = width; + this.height = height; + this.close = vi.fn(); + } +} + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe("platform/worker", () => { + it("re-exports the HTTP origin guard", () => { + vi.stubGlobal("location", { protocol: "file:" }); + + expect(() => ensureServedFromHttp()).toThrow(/requires an HTTP\(S\) origin/i); + }); + + it("accepts cv.Mat payloads by cloning them", () => { + const source = new FakeMat(32, 64); + const loaded = sourcePayloadToMat({ Mat: FakeMat } as any, source); + + expect(loaded.width).toBe(64); + expect(loaded.height).toBe(32); + expect(loaded.mat).toBeInstanceOf(FakeMat); + expect(loaded.mat).not.toBe(source); + + loaded.dispose(); + expect(loaded.mat.deleted).toBe(true); + expect(source.deleted).toBe(false); + }); + + it("converts imageBitmap payloads into cv mats", () => { + const imageBitmap = new FakeImageBitmap(20, 10); + const getImageData = vi.fn(() => ({ + width: 20, + height: 10, + data: new Uint8ClampedArray(20 * 10 * 4) + })); + const drawImage = vi.fn(); + const mat = { + deleted: false, + delete() { + this.deleted = true; + } + }; + + vi.stubGlobal("ImageBitmap", FakeImageBitmap); + vi.stubGlobal( + "OffscreenCanvas", + class { + constructor(width, height) { + this.width = width; + this.height = height; + } + + getContext() { + return { + drawImage, + getImageData + }; + } + } + ); + + const loaded = sourcePayloadToMat( + { + Mat: FakeMat, + CV_8UC4: "rgba", + matFromArray: vi.fn(() => mat) + } as any, + { + kind: "imageBitmap", + imageBitmap + } + ); + + expect(drawImage).toHaveBeenCalledWith(imageBitmap, 0, 0); + expect(loaded.width).toBe(20); + expect(loaded.height).toBe(10); + + loaded.dispose(); + expect(mat.deleted).toBe(true); + expect(imageBitmap.close).toHaveBeenCalledTimes(1); + }); + + it("rejects imageBitmap payloads when OffscreenCanvas is unavailable", () => { + vi.stubGlobal("ImageBitmap", FakeImageBitmap); + + expect(() => + sourcePayloadToMat({ Mat: FakeMat } as any, { + kind: "imageBitmap", + imageBitmap: new FakeImageBitmap() + }) + ).toThrow(/requires OffscreenCanvas support/i); + }); + + it("rejects imageBitmap payloads when the canvas context cannot be created", () => { + vi.stubGlobal("ImageBitmap", FakeImageBitmap); + vi.stubGlobal( + "OffscreenCanvas", + class { + getContext() { + return null; + } + } + ); + + expect(() => + sourcePayloadToMat({ Mat: FakeMat } as any, { + kind: "imageBitmap", + imageBitmap: new FakeImageBitmap() + }) + ).toThrow(/Failed to create a 2D canvas context/i); + }); + + it("rejects unsupported worker payloads", () => { + expect(() => sourcePayloadToMat({ Mat: FakeMat } as any, { kind: "other" })).toThrow( + /Unsupported worker image source payload/i + ); + }); +}); diff --git a/paddleocr-js/packages/core/test/public-api.test.ts b/paddleocr-js/packages/core/test/public-api.test.ts new file mode 100644 index 0000000..b4b3d7d --- /dev/null +++ b/paddleocr-js/packages/core/test/public-api.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it, vi } from "vitest"; + +vi.mock("@techstark/opencv-js", () => ({ + default: { + Mat() {} + } +})); + +import { PaddleOCR, normalizeOcrPipelineConfig, parseOcrPipelineConfigText } from "../src/index"; + +describe("public pipeline exports", () => { + it("exports PaddleOCR", () => { + expect(typeof PaddleOCR).toBe("function"); + }); + + it("exports OCR pipeline config helpers", () => { + expect(typeof parseOcrPipelineConfigText).toBe("function"); + expect(typeof normalizeOcrPipelineConfig).toBe("function"); + }); + + it("supports worker mode through PaddleOCR.create()", async () => { + const ocr = await PaddleOCR.create({ + worker: true, + initialize: false + }); + + expect(typeof ocr.initialize).toBe("function"); + expect(typeof ocr.predict).toBe("function"); + expect(typeof ocr.dispose).toBe("function"); + }); + + it("resolves ortOptions on the instance (create-time ORT settings)", async () => { + const ocr = await PaddleOCR.create({ + lang: "ch", + ocrVersion: "PP-OCRv5", + initialize: false, + ortOptions: { backend: "wasm", wasmPaths: "/dist/" } + }); + + expect(ocr.options.ortOptions.backend).toBe("wasm"); + expect(ocr.options.ortOptions.wasmPaths).toBe("/dist/"); + }); +}); diff --git a/paddleocr-js/packages/core/test/rec-model.test.ts b/paddleocr-js/packages/core/test/rec-model.test.ts new file mode 100644 index 0000000..83ad3ef --- /dev/null +++ b/paddleocr-js/packages/core/test/rec-model.test.ts @@ -0,0 +1,381 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { createMockOrtTensorClass } from "./helpers/mock-ort-tensor"; + +const assertModelResources = vi.fn(); +const createSession = vi.fn(); +const getProviderCandidates = vi.fn(); +const releaseSessions = vi.fn(); +const clamp = vi.fn((value, min, max) => Math.max(min, Math.min(max, value))); +const withTimeout = vi.fn((promise) => promise); +const chunkArray = vi.fn((items, size) => { + const chunks = []; + for (let i = 0; i < items.length; i += size) { + chunks.push(items.slice(i, i + size)); + } + return chunks; +}); +const getTransformOp = vi.fn(); +const parseInferenceConfigText = vi.fn(); +const toBgrFloatCHWFromBgr = vi.fn(); + +vi.mock("../src/resources/model-asset", () => ({ + assertModelResources +})); + +vi.mock("../src/runtime/ort", () => ({ + createSession, + getProviderCandidates, + releaseSessions +})); + +vi.mock("../src/utils/common", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + clamp, + withTimeout, + chunkArray + }; +}); + +vi.mock("../src/models/common", () => ({ + getTransformOp, + parseInferenceConfigText, + toBgrFloatCHWFromBgr +})); + +afterEach(() => { + vi.resetModules(); + vi.clearAllMocks(); +}); + +async function loadRecModule() { + return import("../src/models/rec"); +} + +function createMat(channels, cols = 20, rows = 10) { + return { + cols, + rows, + data: new Uint8Array(cols * rows * 3).fill(1), + channels: () => channels, + copyTo: vi.fn(), + delete: vi.fn() + }; +} + +/** Minimal OpenCV-like `cv` used by `createRecModel().predict()` → internal `preprocessSample`. */ +function createRecPredictCvStub() { + return { + Mat: class Mat { + constructor() { + this.deleted = false; + this.data = new Uint8Array(8); + this._channels = 3; + } + channels() { + return this._channels; + } + copyTo(target) { + target.data = this.data; + target._channels = 3; + } + delete() { + this.deleted = true; + } + }, + Size: class Size { + constructor(width, height) { + this.width = width; + this.height = height; + } + }, + INTER_LINEAR: "linear", + COLOR_RGBA2BGR: "rgba", + COLOR_GRAY2BGR: "gray", + resize: vi.fn((src, dst, size) => { + dst.data = new Uint8Array(size.width * size.height * 3); + dst._channels = src.channels(); + }), + cvtColor: vi.fn((src, dst) => { + dst.data = src.data; + dst._channels = 3; + }) + }; +} + +describe("recognition model", () => { + it("parses recognition configs and validates character dictionaries", async () => { + parseInferenceConfigText.mockReturnValue({ + PreProcess: { + transform_ops: [{ id: "resize" }, { id: "normalize" }] + }, + PostProcess: { + character_dict: ["a", "b"] + } + }); + getTransformOp.mockReturnValueOnce({ image_shape: [3, 32, 160] }); + + const { DEFAULT_REC_MODEL_PARSE_FALLBACKS, parseRecModelConfigText } = await loadRecModule(); + expect(parseRecModelConfigText("config")).toEqual({ + imageShape: [3, 32, 160], + charDict: ["a", "b", " "] + }); + + parseInferenceConfigText.mockReturnValue({ + PreProcess: {}, + PostProcess: {} + }); + getTransformOp.mockReturnValue(undefined); + + expect(() => parseRecModelConfigText("invalid")).toThrow( + /RecResizeImg\.image_shape is required/i + ); + }); + + it("runs recognition batches through predict and decodes CTC output", async () => { + parseInferenceConfigText.mockReturnValue({ + PreProcess: { transform_ops: [] }, + PostProcess: { character_dict: ["A", "B", "C"] } + }); + getTransformOp.mockImplementation((_ops, id) => { + if (id === "RecResizeImg") return { image_shape: [3, 4, 8] }; + return null; + }); + clamp.mockImplementation((value, min, max) => Math.max(min, Math.min(max, value))); + + const tensorCalls = []; + const ort = { + Tensor: createMockOrtTensorClass(tensorCalls) + }; + const ctcRow = new Float32Array([0.1, 0.9, 0.2, 0.1, 0.2, 0.1, 0.8, 0.1, 0.8, 0.1, 0.1, 0.0]); + const sessionRun = vi + .fn() + .mockResolvedValueOnce({ + output: { + dims: [2, 3, 4], + data: new Float32Array([...ctcRow, ...ctcRow]) + } + }) + .mockResolvedValueOnce({ + output: { + dims: [1, 3, 4], + data: ctcRow + } + }); + const session = { + inputNames: ["input"], + outputNames: ["output"], + run: sessionRun + }; + getProviderCandidates.mockReturnValue([["wasm"]]); + createSession.mockResolvedValue({ + session, + provider: "wasm" + }); + + const { createRecModel } = await loadRecModule(); + const model = await createRecModel({ + ort, + modelBytes: new Uint8Array([1]), + configText: "rec-batch", + backend: "auto", + webgpuState: { available: false, reason: "" }, + batchSize: 2 + }); + + const cvFixture = createRecPredictCvStub(); + toBgrFloatCHWFromBgr.mockImplementation((data, width, height) => { + const out = new Float32Array(3 * width * height); + for (let i = 0; i < out.length; i += 1) out[i] = i + 1; + return out; + }); + const mat = createMat(3, 8, 4); + const results = await model.predict(cvFixture, [mat, mat, mat]); + + expect(sessionRun).toHaveBeenCalledTimes(2); + expect(tensorCalls).toEqual([ + { type: "float32", dims: [2, 3, 4, 8], size: 192 }, + { type: "float32", dims: [1, 3, 4, 8], size: 96 } + ]); + expect(results).toHaveLength(3); + expect(results[0]).toMatchObject({ text: "AB" }); + expect(results[0].score).toBeCloseTo(0.85, 5); + expect(results[1]).toMatchObject({ text: "AB" }); + expect(results[1].score).toBeCloseTo(0.85, 5); + expect(results[2]).toMatchObject({ text: "AB" }); + expect(results[2].score).toBeCloseTo(0.85, 5); + }); + + it("allows per-predict batch size override on the recognition model", async () => { + parseInferenceConfigText.mockReturnValue({ + PreProcess: { transform_ops: [] }, + PostProcess: { character_dict: ["A", "B", "C"] } + }); + getTransformOp.mockImplementation((_ops, id) => { + if (id === "RecResizeImg") return { image_shape: [3, 4, 8] }; + return null; + }); + clamp.mockImplementation((value, min, max) => Math.max(min, Math.min(max, value))); + + const ctcRow = new Float32Array([0.1, 0.9, 0.2, 0.1, 0.2, 0.1, 0.8, 0.1, 0.8, 0.1, 0.1, 0.0]); + const sessionRun = vi.fn().mockResolvedValue({ + output: { + dims: [1, 3, 4], + data: ctcRow + } + }); + const session = { + inputNames: ["input"], + outputNames: ["output"], + run: sessionRun + }; + getProviderCandidates.mockReturnValue([["wasm"]]); + createSession.mockResolvedValue({ + session, + provider: "wasm" + }); + + const { createRecModel } = await loadRecModule(); + const model = await createRecModel({ + ort: { Tensor: createMockOrtTensorClass() }, + modelBytes: new Uint8Array([1]), + configText: "rec-override-batch", + backend: "auto", + webgpuState: { available: false, reason: "" }, + batchSize: 6 + }); + + const cvFixture = createRecPredictCvStub(); + toBgrFloatCHWFromBgr.mockImplementation((data, width, height) => { + const out = new Float32Array(3 * width * height); + for (let i = 0; i < out.length; i += 1) out[i] = i + 1; + return out; + }); + const mat = createMat(3, 8, 4); + await model.predict(cvFixture, [mat, mat, mat], { batchSize: 1 }); + + expect(sessionRun).toHaveBeenCalledTimes(3); + }); + + it("rejects unexpected recognition output dimensions", async () => { + parseInferenceConfigText.mockReturnValue({ + PreProcess: { transform_ops: [] }, + PostProcess: { character_dict: ["A"] } + }); + getTransformOp.mockImplementation((_ops, id) => { + if (id === "RecResizeImg") return { image_shape: [3, 4, 8] }; + return null; + }); + getProviderCandidates.mockReturnValue([["wasm"]]); + createSession.mockResolvedValue({ + session: { + inputNames: ["input"], + outputNames: ["output"], + run: vi.fn().mockResolvedValue({ + output: { + dims: [1, 4], + data: new Float32Array([1, 2, 3, 4]) + } + }) + }, + provider: "wasm" + }); + + const { createRecModel } = await loadRecModule(); + const model = await createRecModel({ + ort: { + Tensor: createMockOrtTensorClass() + }, + modelBytes: new Uint8Array([1]), + configText: "rec-bad-out", + backend: "auto", + webgpuState: { available: false, reason: "" } + }); + const cvFixture = createRecPredictCvStub(); + toBgrFloatCHWFromBgr.mockImplementation((data, width, height) => { + const out = new Float32Array(3 * width * height); + out.fill(1); + return out; + }); + + await expect(model.predict(cvFixture, [createMat(3, 8, 4)])).rejects.toThrow( + /Unexpected rec output dims/i + ); + }); + + it("creates, uses, and disposes recognition models through runtime wrappers", async () => { + parseInferenceConfigText.mockReturnValue({ + PreProcess: { + transform_ops: [] + }, + PostProcess: { + character_dict: ["A"] + } + }); + getTransformOp.mockImplementation((_ops, id) => { + if (id === "RecResizeImg") return { image_shape: [3, 4, 8] }; + return undefined; + }); + getProviderCandidates.mockReturnValue([["wasm"]]); + createSession.mockResolvedValue({ + session: { + inputNames: ["input"], + outputNames: ["output"], + run: vi.fn().mockResolvedValue({ + output: { + dims: [1, 2, 2], + data: new Float32Array([0.1, 0.9, 0.9, 0.1]) + } + }) + }, + provider: "wasm" + }); + const released = []; + releaseSessions.mockImplementation(async (session) => { + released.push(session); + }); + + const { createRecModel, createRecModelSession } = await loadRecModule(); + const sessionState = await createRecModelSession({}, new Uint8Array([1]), "auto", { + available: false, + reason: "" + }); + expect(getProviderCandidates).toHaveBeenCalledWith("auto", { available: false, reason: "" }); + expect(withTimeout).toHaveBeenCalled(); + expect(sessionState.provider).toBe("wasm"); + + const model = await createRecModel({ + ort: { + Tensor: createMockOrtTensorClass() + }, + modelBytes: new Uint8Array([1]), + configText: "config", + backend: "auto", + webgpuState: { available: false, reason: "" } + }); + + expect(assertModelResources).toHaveBeenCalled(); + expect(model.kind).toBe("rec"); + expect(model.provider).toBe("wasm"); + expect(model.config.charDict).toEqual(["A", " "]); + + const cvFixture = createRecPredictCvStub(); + toBgrFloatCHWFromBgr.mockImplementation((data, width, height) => { + const out = new Float32Array(3 * width * height); + for (let i = 0; i < out.length; i += 1) out[i] = 1; + return out; + }); + await expect(model.predict(cvFixture, [createMat(3, 8, 4)])).resolves.toSatisfy((results) => { + expect(results).toHaveLength(1); + expect(results[0]).toMatchObject({ text: "A" }); + expect(results[0].score).toBeCloseTo(0.9, 5); + return true; + }); + await expect(model.dispose()).resolves.toBeUndefined(); + expect(released.at(-1)).toBeTruthy(); + await expect(model.predict(cvFixture, [createMat(3, 8, 4)])).rejects.toThrow( + /session is not initialized/i + ); + }); +}); diff --git a/paddleocr-js/packages/core/test/resolve.test.ts b/paddleocr-js/packages/core/test/resolve.test.ts new file mode 100644 index 0000000..f03dc3d --- /dev/null +++ b/paddleocr-js/packages/core/test/resolve.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it, vi } from "vitest"; + +import { loadModelAsset } from "../src/resources/model-asset"; +import { createTar } from "./tar-fixture"; + +describe("model asset loading", () => { + it("resolves model tar assets", async () => { + const asset = { + url: "/det.tar" + }; + + const fetchImpl = vi.fn(async (url) => { + if (url.endsWith("det.tar")) { + return new Response( + createTar([ + { name: "nested/inference.onnx", content: new Uint8Array([57, 56, 55]) }, + { name: "nested/inference.yml", content: "name: det" } + ]) + ); + } + return new Response( + createTar([ + { name: "nested/inference.onnx", content: new Uint8Array([49, 50, 51]) }, + { name: "nested/inference.yml", content: "name: det" } + ]) + ); + }); + + const resolved = await loadModelAsset(asset, fetchImpl); + + expect(Array.from(resolved.modelBytes)).toEqual([57, 56, 55]); + expect(resolved.configText).toBe("name: det"); + expect(resolved.download.url).toBe("/det.tar"); + }); + + it("uses standard inference entry names", async () => { + const asset = { + url: "/det-default.tar" + }; + + const fetchImpl = vi.fn( + async () => + new Response( + createTar([ + { name: "nested/inference.onnx", content: new Uint8Array([1, 2, 3]) }, + { name: "nested/inference.yml", content: "name: config" } + ]) + ) + ); + + const resolved = await loadModelAsset(asset, fetchImpl); + + expect(Array.from(resolved.modelBytes)).toEqual([1, 2, 3]); + expect(resolved.configText).toBe("name: config"); + }); +}); diff --git a/paddleocr-js/packages/core/test/runtime-opencv.test.ts b/paddleocr-js/packages/core/test/runtime-opencv.test.ts new file mode 100644 index 0000000..deac34e --- /dev/null +++ b/paddleocr-js/packages/core/test/runtime-opencv.test.ts @@ -0,0 +1,61 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +async function loadOpenCvModule(mockedDefault) { + vi.resetModules(); + vi.doMock("@techstark/opencv-js", () => ({ + __esModule: true, + default: mockedDefault + })); + return import("../src/runtime/opencv"); +} + +afterEach(() => { + vi.resetModules(); + vi.doUnmock("@techstark/opencv-js"); +}); + +describe("runtime/opencv", () => { + it("returns OpenCV immediately when the module already exposes Mat", async () => { + const cv = { Mat() {} }; + const { initOpenCvRuntime } = await loadOpenCvModule(cv); + + await expect(initOpenCvRuntime()).resolves.toEqual({ cv }); + }); + + it("awaits promised OpenCV modules", async () => { + const cv = { Mat() {} }; + const { initOpenCvRuntime } = await loadOpenCvModule(Promise.resolve(cv)); + + await expect(initOpenCvRuntime()).resolves.toEqual({ cv }); + }); + + it("awaits callback-based OpenCV initialization", async () => { + const cv = {}; + Object.defineProperty(cv, "onRuntimeInitialized", { + set(callback) { + callback(); + } + }); + const { initOpenCvRuntime } = await loadOpenCvModule(cv); + + await expect(initOpenCvRuntime()).resolves.toEqual({ cv }); + }); + + it("caches successful initialization results", async () => { + const cv = { Mat() {} }; + const { initOpenCvRuntime } = await loadOpenCvModule(cv); + + const first = await initOpenCvRuntime(); + const second = await initOpenCvRuntime(); + + expect(first).toBe(second); + }); + + it("re-exports initOpenCvRuntime from the runtime index", async () => { + const cv = { Mat() {} }; + const runtimeModule = await loadOpenCvModule(cv); + const runtimeIndex = await import("../src/runtime/index"); + + expect(runtimeIndex.initOpenCvRuntime).toBe(runtimeModule.initOpenCvRuntime); + }); +}); diff --git a/paddleocr-js/packages/core/test/runtime-ort.test.ts b/paddleocr-js/packages/core/test/runtime-ort.test.ts new file mode 100644 index 0000000..ac4b56c --- /dev/null +++ b/paddleocr-js/packages/core/test/runtime-ort.test.ts @@ -0,0 +1,174 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +async function loadOrtModule(mockedOrt = { env: { wasm: {} } }) { + vi.resetModules(); + vi.doMock("onnxruntime-web", () => ({ + __esModule: true, + ...mockedOrt + })); + return import("../src/runtime/ort"); +} + +afterEach(() => { + vi.resetModules(); + vi.doUnmock("onnxruntime-web"); + vi.unstubAllGlobals(); +}); + +describe("runtime/ort", () => { + it("reports when WebGPU is unavailable", async () => { + vi.stubGlobal("navigator", {}); + const { detectWebGpuAvailability } = await loadOrtModule(); + + await expect(detectWebGpuAvailability()).resolves.toEqual({ + available: false, + reason: "navigator.gpu is unavailable in this browser." + }); + }); + + it("reports WebGPU available when the browser returns an adapter", async () => { + vi.stubGlobal("navigator", { + gpu: { + requestAdapter: vi.fn().mockResolvedValue({}) + } + }); + const { detectWebGpuAvailability } = await loadOrtModule(); + await expect(detectWebGpuAvailability()).resolves.toEqual({ + available: true, + reason: "" + }); + }); + + it("reports WebGPU unavailable when the browser returns no adapter", async () => { + vi.stubGlobal("navigator", { + gpu: { + requestAdapter: vi.fn().mockResolvedValue(null) + } + }); + const { detectWebGpuAvailability } = await loadOrtModule(); + await expect(detectWebGpuAvailability()).resolves.toEqual({ + available: false, + reason: "The browser did not return a WebGPU adapter." + }); + }); + + it("reports WebGPU unavailable when adapter lookup throws", async () => { + vi.stubGlobal("navigator", { + gpu: { + requestAdapter: vi.fn().mockRejectedValue(new Error("GPU blocked")) + } + }); + const { detectWebGpuAvailability } = await loadOrtModule(); + await expect(detectWebGpuAvailability()).resolves.toEqual({ + available: false, + reason: "GPU blocked" + }); + }); + + it("selects provider candidates from backend preference and WebGPU availability", async () => { + const { getProviderCandidates } = await loadOrtModule(); + + expect(getProviderCandidates("webgpu", { available: true, reason: "" })).toEqual([["webgpu"]]); + expect(getProviderCandidates("wasm", { available: false, reason: "n/a" })).toEqual([["wasm"]]); + expect(getProviderCandidates("auto", { available: true, reason: "" })).toEqual([ + ["webgpu"], + ["wasm"] + ]); + expect(getProviderCandidates("auto", { available: false, reason: "n/a" })).toEqual([["wasm"]]); + expect(() => getProviderCandidates("webgpu", { available: false, reason: "missing" })).toThrow( + /WebGPU is unavailable: missing/ + ); + }); + + it("initializes ORT runtime and applies wasm environment options", async () => { + vi.stubGlobal("navigator", {}); + const mockedOrt = { + env: { + wasm: {} + } + }; + const { initOrtRuntime } = await loadOrtModule(mockedOrt); + + const runtime = await initOrtRuntime({ + wasmPaths: "/wasm/", + numThreads: 2, + simd: true, + proxy: true, + disableWasmProxy: true + }); + + expect(runtime.backend).toBe("auto"); + expect(runtime.webgpuState.available).toBe(false); + expect(runtime.ort.env.wasm).toEqual({ + wasmPaths: "/wasm/", + numThreads: 2, + simd: true, + proxy: false + }); + expect(mockedOrt.env.wasm).toEqual({ + wasmPaths: "/wasm/", + numThreads: 2, + simd: true, + proxy: false + }); + }); + + it("creates sessions by trying providers in order", async () => { + const create = vi + .fn() + .mockRejectedValueOnce(new Error("webgpu failed")) + .mockResolvedValueOnce({ id: "session" }); + const mockedOrt = { + InferenceSession: { create } + }; + const { createSession } = await loadOrtModule({ + env: { wasm: {} }, + InferenceSession: { create } + }); + const modelBytes = new Uint8Array([1, 2, 3]); + + const result = await createSession(mockedOrt, modelBytes, [["webgpu"], ["wasm"]]); + + expect(create).toHaveBeenNthCalledWith(1, modelBytes, { + executionProviders: ["webgpu"], + graphOptimizationLevel: "all" + }); + expect(create).toHaveBeenNthCalledWith(2, modelBytes, { + executionProviders: ["wasm"], + graphOptimizationLevel: "all" + }); + expect(result).toEqual({ + session: { id: "session" }, + provider: "wasm" + }); + }); + + it("throws the last session creation error when all providers fail", async () => { + const create = vi + .fn() + .mockRejectedValueOnce(new Error("webgpu failed")) + .mockRejectedValueOnce(new Error("wasm failed")); + const mockedOrt = { + InferenceSession: { create } + }; + const { createSession } = await loadOrtModule({ + env: { wasm: {} }, + InferenceSession: { create } + }); + + await expect( + createSession(mockedOrt, new Uint8Array([1]), [["webgpu"], ["wasm"]]) + ).rejects.toThrow("wasm failed"); + }); + + it("releases every session that exposes a release method", async () => { + const releaseA = vi.fn().mockResolvedValue(undefined); + const releaseB = vi.fn().mockResolvedValue(undefined); + const { releaseSessions } = await loadOrtModule(); + + await releaseSessions({ release: releaseA }, null, { release: releaseB }, {}); + + expect(releaseA).toHaveBeenCalledTimes(1); + expect(releaseB).toHaveBeenCalledTimes(1); + }); +}); diff --git a/paddleocr-js/packages/core/test/runtime-params.test.ts b/paddleocr-js/packages/core/test/runtime-params.test.ts new file mode 100644 index 0000000..6bdf99e --- /dev/null +++ b/paddleocr-js/packages/core/test/runtime-params.test.ts @@ -0,0 +1,103 @@ +import { describe, expect, it } from "vitest"; + +import { DEFAULT_DET_MODEL_CONFIG, DEFAULT_REC_MODEL_CONFIG } from "../src/models/index"; +import type { OcrModelConfig } from "../src/pipelines/ocr/runtime-params"; +import { getOcrRuntimeParams } from "../src/pipelines/ocr/runtime-params"; + +/** Fixture: full `OcrModelConfig` shapes (same as pipeline `getModelConfig()` after init). */ +const SAMPLE_MODEL_CONFIG: OcrModelConfig = { + det: { + ...DEFAULT_DET_MODEL_CONFIG, + resizeLong: 960, + maxSideLimit: 3200 + }, + rec: { + ...DEFAULT_REC_MODEL_CONFIG + } +}; + +describe("OCR runtime params", () => { + it("falls back from params to defaults and config values", () => { + expect( + getOcrRuntimeParams( + SAMPLE_MODEL_CONFIG, + { + text_det_limit_side_len: 736, + text_det_limit_type: "min", + text_det_max_side_limit: 4096, + text_det_thresh: 0.25, + text_det_box_thresh: 0.55, + text_det_unclip_ratio: 1.8, + text_rec_score_thresh: 0.4 + }, + {} + ) + ).toEqual({ + det: { + limitSideLen: 736, + limitType: "min", + maxSideLimit: 4096, + thresh: 0.25, + boxThresh: 0.55, + unclipRatio: 1.8 + }, + pipeline: { scoreThresh: 0.4 } + }); + }); + + it("prefers camelCase params; limit type falls back to model config when not specified", () => { + expect( + getOcrRuntimeParams( + SAMPLE_MODEL_CONFIG, + {}, + { + textDetLimitSideLen: 512, + textDetMaxSideLimit: 2048, + textDetThresh: 0.22, + textDetBoxThresh: 0.44, + textDetUnclipRatio: 2.2, + textRecScoreThresh: 0.9 + } + ) + ).toEqual({ + det: { + limitSideLen: 512, + limitType: DEFAULT_DET_MODEL_CONFIG.limitType, + maxSideLimit: 2048, + thresh: 0.22, + boxThresh: 0.44, + unclipRatio: 2.2 + }, + pipeline: { scoreThresh: 0.9 } + }); + }); + + it("treats nulls as missing values when resolving fallbacks", () => { + expect( + getOcrRuntimeParams( + SAMPLE_MODEL_CONFIG, + { + text_det_limit_type: null + }, + { + text_det_limit_side_len: null, + text_det_max_side_limit: null, + text_det_thresh: null, + text_det_box_thresh: null, + text_det_unclip_ratio: null, + text_rec_score_thresh: null + } + ) + ).toEqual({ + det: { + limitSideLen: SAMPLE_MODEL_CONFIG.det.resizeLong, + limitType: SAMPLE_MODEL_CONFIG.det.limitType, + maxSideLimit: SAMPLE_MODEL_CONFIG.det.maxSideLimit, + thresh: SAMPLE_MODEL_CONFIG.det.postprocess.thresh, + boxThresh: SAMPLE_MODEL_CONFIG.det.postprocess.boxThresh, + unclipRatio: SAMPLE_MODEL_CONFIG.det.postprocess.unclipRatio + }, + pipeline: { scoreThresh: 0 } + }); + }); +}); diff --git a/paddleocr-js/packages/core/test/tar-fixture.ts b/paddleocr-js/packages/core/test/tar-fixture.ts new file mode 100644 index 0000000..07d00e4 --- /dev/null +++ b/paddleocr-js/packages/core/test/tar-fixture.ts @@ -0,0 +1,47 @@ +function writeString(target, offset, length, value) { + const encoded = new TextEncoder().encode(value); + target.set(encoded.slice(0, length), offset); +} + +function writeOctal(target, offset, length, value) { + const octal = value.toString(8).padStart(length - 1, "0"); + writeString(target, offset, length, `${octal}\0`); +} + +export function createTar(entries) { + const chunks = []; + for (const entry of entries) { + const data = + typeof entry.content === "string" ? new TextEncoder().encode(entry.content) : entry.content; + const header = new Uint8Array(512); + writeString(header, 0, 100, entry.name); + writeString(header, 100, 8, "0000777"); + writeString(header, 108, 8, "0000000"); + writeString(header, 116, 8, "0000000"); + writeOctal(header, 124, 12, data.length); + writeString(header, 136, 12, "00000000000"); + header[156] = "0".charCodeAt(0); + writeString(header, 257, 6, "ustar"); + writeString(header, 263, 2, "00"); + for (let index = 148; index < 156; index += 1) { + header[index] = 32; + } + const checksum = header.reduce((sum, value) => sum + value, 0); + writeString(header, 148, 8, `${checksum.toString(8).padStart(6, "0")}\0 `); + chunks.push(header, data); + const remainder = data.length % 512; + if (remainder !== 0) { + chunks.push(new Uint8Array(512 - remainder)); + } + } + chunks.push(new Uint8Array(1024)); + + const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0); + const out = new Uint8Array(total); + let offset = 0; + for (const chunk of chunks) { + out.set(chunk, offset); + offset += chunk.length; + } + return out.buffer; +} diff --git a/paddleocr-js/packages/core/test/tar.test.ts b/paddleocr-js/packages/core/test/tar.test.ts new file mode 100644 index 0000000..e58414f --- /dev/null +++ b/paddleocr-js/packages/core/test/tar.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; + +import { extractTarEntries, pickTarEntry } from "../src/resources/tar"; +import { createTar } from "./tar-fixture"; + +describe("tar helpers", () => { + it("extracts nested inference files from a tar archive", () => { + const tarBuffer = createTar([ + { name: "det/inference.onnx", content: "onnx" }, + { name: "det/inference.yml", content: "yaml" } + ]); + + const entries = extractTarEntries(tarBuffer); + + expect(new TextDecoder().decode(pickTarEntry(entries, "inference.onnx"))).toBe("onnx"); + expect(new TextDecoder().decode(pickTarEntry(entries, "inference.yml"))).toBe("yaml"); + }); + + it("ignores AppleDouble and PaxHeader metadata entries", () => { + const tarBuffer = createTar([ + { name: "._det", content: "metadata" }, + { name: "PaxHeader/det", content: "metadata" }, + { name: "det/._inference.onnx", content: "metadata" }, + { name: "det/PaxHeader/inference.onnx", content: "metadata" }, + { name: "det/inference.onnx", content: "onnx" }, + { name: "det/._inference.yml", content: "metadata" }, + { name: "det/PaxHeader/inference.yml", content: "metadata" }, + { name: "det/inference.yml", content: "yaml" } + ]); + + const entries = extractTarEntries(tarBuffer); + + expect([...entries.keys()]).toEqual(["det/inference.onnx", "det/inference.yml"]); + expect(new TextDecoder().decode(pickTarEntry(entries, "inference.onnx"))).toBe("onnx"); + expect(new TextDecoder().decode(pickTarEntry(entries, "inference.yml"))).toBe("yaml"); + }); +}); diff --git a/paddleocr-js/packages/core/test/utils-common.test.ts b/paddleocr-js/packages/core/test/utils-common.test.ts new file mode 100644 index 0000000..ad44782 --- /dev/null +++ b/paddleocr-js/packages/core/test/utils-common.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it, vi } from "vitest"; + +import { + clamp, + deepClone, + distance2, + formatMs, + nowMs, + resolveRuntimeBatchSize, + withTimeout +} from "../src/utils/common"; + +describe("utils/common", () => { + it("reads the current high-resolution timestamp", () => { + const spy = vi.spyOn(performance, "now").mockReturnValue(123.45); + + expect(nowMs()).toBe(123.45); + + spy.mockRestore(); + }); + + it("clamps values into the requested range", () => { + expect(clamp(-1, 0, 10)).toBe(0); + expect(clamp(5, 0, 10)).toBe(5); + expect(clamp(15, 0, 10)).toBe(10); + }); + + it("computes Euclidean distance between two points", () => { + expect(distance2([0, 0], [3, 4])).toBe(5); + }); + + it("formats millisecond durations with one decimal place", () => { + expect(formatMs(12.34)).toBe("12.3 ms"); + }); + + it("resolveRuntimeBatchSize uses default when override is undefined", () => { + expect(resolveRuntimeBatchSize(undefined, 4)).toBe(4); + }); + + it("resolveRuntimeBatchSize coerces numeric strings and enforces a minimum of 1", () => { + expect(resolveRuntimeBatchSize("3", 1)).toBe(3); + expect(resolveRuntimeBatchSize(0, 2)).toBe(1); + expect(resolveRuntimeBatchSize(-1, 2)).toBe(1); + expect(resolveRuntimeBatchSize(Number.NaN, 2)).toBe(1); + }); + + it("resolves before the timeout when the promise settles in time", async () => { + await expect(withTimeout(Promise.resolve("ok"), 50, "load")).resolves.toBe("ok"); + }); + + it("rejects with the original error before the timeout elapses", async () => { + const error = new Error("boom"); + + await expect(withTimeout(Promise.reject(error), 50, "load")).rejects.toBe(error); + }); + + it("rejects when the timeout elapses first", async () => { + await expect(withTimeout(new Promise(() => {}), 1, "load")).rejects.toThrow( + "load timed out after 0.001s" + ); + }); + + it("deep clones structured data", () => { + const original = { + nested: { + values: [1, 2, 3] + } + }; + + const cloned = deepClone(original); + cloned.nested.values.push(4); + + expect(cloned).toEqual({ + nested: { + values: [1, 2, 3, 4] + } + }); + expect(original).toEqual({ + nested: { + values: [1, 2, 3] + } + }); + }); +}); diff --git a/paddleocr-js/packages/core/test/viz-canvas-factory.test.ts b/paddleocr-js/packages/core/test/viz-canvas-factory.test.ts new file mode 100644 index 0000000..bfc49d4 --- /dev/null +++ b/paddleocr-js/packages/core/test/viz-canvas-factory.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it, vi, afterEach } from "vitest"; +import { createCanvas } from "../src/viz/canvas-factory"; + +describe("viz/canvas-factory", () => { + const origOffscreen = globalThis.OffscreenCanvas; + + afterEach(() => { + if (origOffscreen) { + globalThis.OffscreenCanvas = origOffscreen; + } else { + delete (globalThis as Record).OffscreenCanvas; + } + }); + + it("returns an OffscreenCanvas when available", () => { + const mockCanvas = { width: 0, height: 0, getContext: vi.fn() }; + (globalThis as Record).OffscreenCanvas = vi.fn((w: number, h: number) => { + mockCanvas.width = w; + mockCanvas.height = h; + return mockCanvas; + }); + + const result = createCanvas(100, 200); + expect(result.width).toBe(100); + expect(result.height).toBe(200); + }); + + it("falls back to document.createElement when OffscreenCanvas is unavailable", () => { + delete (globalThis as Record).OffscreenCanvas; + + const mockCtx = {}; + const mockCanvas = { + width: 0, + height: 0, + getContext: vi.fn(() => mockCtx) + }; + vi.spyOn(document, "createElement").mockReturnValue(mockCanvas as unknown as HTMLElement); + + const result = createCanvas(300, 400); + expect(result.width).toBe(300); + expect(result.height).toBe(400); + expect(document.createElement).toHaveBeenCalledWith("canvas"); + }); +}); diff --git a/paddleocr-js/packages/core/test/viz-color.test.ts b/paddleocr-js/packages/core/test/viz-color.test.ts new file mode 100644 index 0000000..8d7cb13 --- /dev/null +++ b/paddleocr-js/packages/core/test/viz-color.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; +import { deterministicColor } from "../src/viz/color"; + +describe("viz/color", () => { + it("returns an RGB tuple of three integers 0-255", () => { + const [r, g, b] = deterministicColor(0); + expect(Number.isInteger(r)).toBe(true); + expect(Number.isInteger(g)).toBe(true); + expect(Number.isInteger(b)).toBe(true); + expect(r).toBeGreaterThanOrEqual(0); + expect(r).toBeLessThanOrEqual(255); + expect(g).toBeGreaterThanOrEqual(0); + expect(g).toBeLessThanOrEqual(255); + expect(b).toBeGreaterThanOrEqual(0); + expect(b).toBeLessThanOrEqual(255); + }); + + it("produces the same color for the same index", () => { + expect(deterministicColor(5)).toEqual(deterministicColor(5)); + expect(deterministicColor(42)).toEqual(deterministicColor(42)); + }); + + it("produces different colors for different indices", () => { + const c0 = deterministicColor(0); + const c1 = deterministicColor(1); + const c2 = deterministicColor(2); + const allSame = + JSON.stringify(c0) === JSON.stringify(c1) && JSON.stringify(c1) === JSON.stringify(c2); + expect(allSame).toBe(false); + }); + + it("matches the exact values from the demo app's LCG", () => { + const c0 = deterministicColor(0); + const c1 = deterministicColor(1); + // Verify determinism by snapshot + expect(c0).toMatchInlineSnapshot(` + [ + 198, + 126, + 223, + ] + `); + expect(c1).toMatchInlineSnapshot(` + [ + 140, + 33, + 85, + ] + `); + }); +}); diff --git a/paddleocr-js/packages/core/test/viz-draw-boxes.test.ts b/paddleocr-js/packages/core/test/viz-draw-boxes.test.ts new file mode 100644 index 0000000..316e990 --- /dev/null +++ b/paddleocr-js/packages/core/test/viz-draw-boxes.test.ts @@ -0,0 +1,108 @@ +import { describe, expect, it, vi } from "vitest"; +import { drawBoxesPanel } from "../src/viz/ocr/draw-boxes"; +import type { OcrResultItem } from "../src/pipelines/ocr/core"; +import type { Point2D } from "../src/models/common"; + +function createMockCtx() { + const calls: string[] = []; + return { + calls, + save: vi.fn(() => calls.push("save")), + restore: vi.fn(() => calls.push("restore")), + drawImage: vi.fn(() => calls.push("drawImage")), + beginPath: vi.fn(() => calls.push("beginPath")), + moveTo: vi.fn(() => calls.push("moveTo")), + lineTo: vi.fn(() => calls.push("lineTo")), + closePath: vi.fn(() => calls.push("closePath")), + fill: vi.fn(() => calls.push("fill")), + stroke: vi.fn(() => calls.push("stroke")), + lineWidth: 0, + strokeStyle: "", + fillStyle: "" + } as unknown as CanvasRenderingContext2D & { calls: string[] }; +} + +function makeItem(poly: Point2D[], text: string): OcrResultItem { + return { originalIndex: 0, poly, text, score: 0.95 }; +} + +describe("viz/draw-boxes", () => { + it("draws the source image at (0,0)", () => { + const ctx = createMockCtx(); + const image = { width: 100, height: 50 } as ImageBitmap; + + drawBoxesPanel(ctx, image, [], {}); + + expect(ctx.drawImage).toHaveBeenCalledWith(image, 0, 0); + }); + + it("draws a polygon fill and stroke for each item", () => { + const ctx = createMockCtx(); + const image = { width: 100, height: 50 } as ImageBitmap; + const items: OcrResultItem[] = [ + makeItem( + [ + [10, 10], + [90, 10], + [90, 40], + [10, 40] + ], + "hello" + ) + ]; + + drawBoxesPanel(ctx, image, items, {}); + + expect(ctx.beginPath).toHaveBeenCalled(); + expect(ctx.moveTo).toHaveBeenCalledWith(10, 10); + expect(ctx.lineTo).toHaveBeenCalledWith(90, 10); + expect(ctx.lineTo).toHaveBeenCalledWith(90, 40); + expect(ctx.lineTo).toHaveBeenCalledWith(10, 40); + expect(ctx.closePath).toHaveBeenCalled(); + expect(ctx.fill).toHaveBeenCalled(); + }); + + it("wraps each item draw in save/restore", () => { + const ctx = createMockCtx(); + const image = { width: 100, height: 50 } as ImageBitmap; + const items: OcrResultItem[] = [ + makeItem( + [ + [0, 0], + [10, 0], + [10, 10], + [0, 10] + ], + "a" + ) + ]; + + drawBoxesPanel(ctx, image, items, {}); + + const saveIdx = ctx.calls.indexOf("save"); + const restoreIdx = ctx.calls.lastIndexOf("restore"); + expect(saveIdx).toBeGreaterThan(-1); + expect(restoreIdx).toBeGreaterThan(saveIdx); + }); + + it("uses custom colorFn when provided", () => { + const ctx = createMockCtx(); + const image = { width: 100, height: 50 } as ImageBitmap; + const items: OcrResultItem[] = [ + makeItem( + [ + [0, 0], + [10, 0], + [10, 10], + [0, 10] + ], + "a" + ) + ]; + const colorFn = vi.fn().mockReturnValue([255, 0, 0]); + + drawBoxesPanel(ctx, image, items, { colorFn }); + + expect(colorFn).toHaveBeenCalledWith(0); + }); +}); diff --git a/paddleocr-js/packages/core/test/viz-draw-text.test.ts b/paddleocr-js/packages/core/test/viz-draw-text.test.ts new file mode 100644 index 0000000..91f2f22 --- /dev/null +++ b/paddleocr-js/packages/core/test/viz-draw-text.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, it, vi } from "vitest"; +import { drawTextPanel } from "../src/viz/ocr/draw-text"; +import type { OcrResultItem } from "../src/pipelines/ocr/core"; +import type { Point2D } from "../src/models/common"; + +function createMockCtx() { + return { + save: vi.fn(), + restore: vi.fn(), + fillRect: vi.fn(), + beginPath: vi.fn(), + moveTo: vi.fn(), + lineTo: vi.fn(), + closePath: vi.fn(), + stroke: vi.fn(), + fill: vi.fn(), + measureText: vi.fn().mockReturnValue({ width: 50 }), + fillText: vi.fn(), + translate: vi.fn(), + rotate: vi.fn(), + lineWidth: 0, + strokeStyle: "", + fillStyle: "", + font: "", + textBaseline: "" as CanvasTextBaseline + } as unknown as CanvasRenderingContext2D; +} + +function makeItem(poly: Point2D[], text: string): OcrResultItem { + return { originalIndex: 0, poly, text, score: 0.95 }; +} + +describe("viz/draw-text", () => { + it("fills the panel background", () => { + const ctx = createMockCtx(); + drawTextPanel(ctx, 200, 100, [], {}, "sans-serif"); + + expect(ctx.fillRect).toHaveBeenCalledWith(200, 0, 200, 100); + }); + + it("draws text for each item", () => { + const ctx = createMockCtx(); + const items: OcrResultItem[] = [ + makeItem( + [ + [10, 10], + [90, 10], + [90, 40], + [10, 40] + ], + "hello" + ) + ]; + + drawTextPanel(ctx, 200, 100, items, {}, "sans-serif"); + + expect(ctx.fillText).toHaveBeenCalled(); + // Verify the text content is "hello" + const textCall = (ctx.fillText as ReturnType).mock.calls[0]; + expect(textCall[0]).toBe("hello"); + }); + + it("draws box outlines on the right panel for each item", () => { + const ctx = createMockCtx(); + const items: OcrResultItem[] = [ + makeItem( + [ + [10, 10], + [90, 10], + [90, 40], + [10, 40] + ], + "hello" + ) + ]; + + drawTextPanel(ctx, 200, 100, items, {}, "sans-serif"); + + expect(ctx.beginPath).toHaveBeenCalled(); + expect(ctx.stroke).toHaveBeenCalled(); + }); + + it("uses custom textPanelBackground", () => { + const ctx = createMockCtx(); + drawTextPanel(ctx, 200, 100, [], {}, "sans-serif", "#f0f0f0"); + + // fillRect call for the background should happen + expect(ctx.fillRect).toHaveBeenCalledWith(200, 0, 200, 100); + }); +}); diff --git a/paddleocr-js/packages/core/test/viz-font.test.ts b/paddleocr-js/packages/core/test/viz-font.test.ts new file mode 100644 index 0000000..003215e --- /dev/null +++ b/paddleocr-js/packages/core/test/viz-font.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; +import { loadFontFace, removeFontFace } from "../src/viz/font"; +import type { FontConfig } from "../src/viz/types"; + +describe("viz/font", () => { + let mockFontFace: { load: ReturnType; family: string }; + + beforeEach(() => { + mockFontFace = { + load: vi.fn().mockResolvedValue(undefined), + family: "" + }; + (globalThis as Record).FontFace = vi.fn((family: string) => { + mockFontFace.family = family; + return mockFontFace; + }); + if (!document.fonts) { + Object.defineProperty(document, "fonts", { + value: { add: vi.fn(), delete: vi.fn() }, + configurable: true + }); + } else { + vi.spyOn(document.fonts, "add").mockImplementation(() => {}); + vi.spyOn(document.fonts, "delete").mockImplementation(() => true); + } + }); + + it("loads a font from a URL string and adds to document.fonts", async () => { + const config: FontConfig = { + family: "TestFont", + source: "https://example.com/font.woff2" + }; + + const face = await loadFontFace(config); + expect(globalThis.FontFace).toHaveBeenCalledWith( + "TestFont", + "url(https://example.com/font.woff2)", + undefined + ); + expect(mockFontFace.load).toHaveBeenCalled(); + expect(document.fonts.add).toHaveBeenCalledWith(face); + }); + + it("loads a font from an ArrayBuffer source", async () => { + const buffer = new ArrayBuffer(8); + const config: FontConfig = { + family: "BufFont", + source: buffer + }; + + await loadFontFace(config); + expect(globalThis.FontFace).toHaveBeenCalledWith("BufFont", buffer, undefined); + }); + + it("passes descriptors to FontFace constructor", async () => { + const config: FontConfig = { + family: "DescFont", + source: "https://example.com/font.woff2", + descriptors: { weight: "bold" } + }; + + await loadFontFace(config); + expect(globalThis.FontFace).toHaveBeenCalledWith( + "DescFont", + "url(https://example.com/font.woff2)", + { weight: "bold" } + ); + }); + + it("removes a font face from document.fonts", () => { + removeFontFace(mockFontFace as unknown as FontFace); + expect(document.fonts.delete).toHaveBeenCalledWith(mockFontFace); + }); +}); diff --git a/paddleocr-js/packages/core/test/viz-public-api.test.ts b/paddleocr-js/packages/core/test/viz-public-api.test.ts new file mode 100644 index 0000000..31bde91 --- /dev/null +++ b/paddleocr-js/packages/core/test/viz-public-api.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it, vi } from "vitest"; + +// Mock browser APIs needed by the module +(globalThis as Record).FontFace = vi.fn(() => ({ + load: vi.fn().mockResolvedValue(undefined) +})); +if (!document.fonts) { + Object.defineProperty(document, "fonts", { + value: { add: vi.fn(), delete: vi.fn() }, + configurable: true + }); +} + +import { OcrVisualizer, renderOcrToBlob, deterministicColor } from "../src/viz/index"; + +describe("viz public API", () => { + it("exports OcrVisualizer as a class", () => { + expect(typeof OcrVisualizer).toBe("function"); + }); + + it("exports renderOcrToBlob as a function", () => { + expect(typeof renderOcrToBlob).toBe("function"); + }); + + it("exports deterministicColor as a function", () => { + expect(typeof deterministicColor).toBe("function"); + }); +}); diff --git a/paddleocr-js/packages/core/test/viz-renderer.test.ts b/paddleocr-js/packages/core/test/viz-renderer.test.ts new file mode 100644 index 0000000..64310c1 --- /dev/null +++ b/paddleocr-js/packages/core/test/viz-renderer.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it, vi } from "vitest"; + +// Mock FontFace and document.fonts before importing +const mockFontFace = { + load: vi.fn().mockResolvedValue(undefined), + family: "" +}; +(globalThis as Record).FontFace = vi.fn((family: string) => { + mockFontFace.family = family; + return mockFontFace; +}); +if (!document.fonts) { + Object.defineProperty(document, "fonts", { + value: { add: vi.fn(), delete: vi.fn() }, + configurable: true + }); +} else { + vi.spyOn(document.fonts, "add").mockImplementation(() => {}); + vi.spyOn(document.fonts, "delete").mockImplementation(() => true); +} + +// Mock createImageBitmap +(globalThis as Record).createImageBitmap = vi + .fn() + .mockResolvedValue({ width: 200, height: 100, close: vi.fn() }); + +import { OcrVisualizer, renderOcrToBlob } from "../src/viz/ocr/renderer"; + +describe("OcrVisualizer", () => { + it("can be constructed with no options", () => { + const viz = new OcrVisualizer(); + expect(viz).toBeDefined(); + viz.dispose(); + }); + + it("can be constructed with font config", () => { + const viz = new OcrVisualizer({ + font: { family: "TestFont", source: "https://example.com/f.woff2" } + }); + expect(viz).toBeDefined(); + viz.dispose(); + }); + + it("dispose() is safe to call multiple times", () => { + const viz = new OcrVisualizer(); + viz.dispose(); + viz.dispose(); // should not throw + }); +}); + +describe("renderOcrToBlob", () => { + it("is exported as a function", () => { + expect(typeof renderOcrToBlob).toBe("function"); + }); +}); diff --git a/paddleocr-js/packages/core/test/viz-types.test.ts b/paddleocr-js/packages/core/test/viz-types.test.ts new file mode 100644 index 0000000..3244b09 --- /dev/null +++ b/paddleocr-js/packages/core/test/viz-types.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; +import type { FontConfig, RgbColor } from "../src/viz/types"; +import type { BoxStyleOptions, OcrVisualizerOptions } from "../src/viz/ocr/types"; + +describe("viz/types", () => { + it("allows constructing a minimal OcrVisualizerOptions", () => { + const opts: OcrVisualizerOptions = {}; + expect(opts).toEqual({}); + }); + + it("allows constructing a full OcrVisualizerOptions", () => { + const font: FontConfig = { + family: "Test", + source: "https://example.com/font.woff2" + }; + const boxStyle: BoxStyleOptions = { + lineWidth: 3, + fillOpacity: 0.5, + colorFn: (i: number): RgbColor => [i, i, i] + }; + const opts: OcrVisualizerOptions = { + font, + boxStyle, + textPanelBackground: "#f0f0f0", + outputFormat: "jpeg", + outputQuality: 0.8 + }; + expect(opts.font?.family).toBe("Test"); + expect(opts.boxStyle?.lineWidth).toBe(3); + expect(opts.outputFormat).toBe("jpeg"); + }); +}); diff --git a/paddleocr-js/packages/core/test/worker-backed.test.ts b/paddleocr-js/packages/core/test/worker-backed.test.ts new file mode 100644 index 0000000..98d6b25 --- /dev/null +++ b/paddleocr-js/packages/core/test/worker-backed.test.ts @@ -0,0 +1,233 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +const sourceToWorkerPayload = vi.fn(); + +vi.mock("../src/platform/browser", () => ({ + sourceToWorkerPayload +})); + +afterEach(() => { + vi.clearAllMocks(); +}); + +function basePipelineConfig() { + return { + pipelineName: "OCR", + raw: {}, + warnings: [] as string[], + unsupportedFeatures: [] as string[], + modelSelection: { + textDetectionModelName: "PP-OCRv5_mobile_det", + textRecognitionModelName: "PP-OCRv5_mobile_rec" + }, + assets: { + det: { id: "det" }, + rec: { id: "rec" } + }, + runtimeDefaults: {} as Record, + pipelineBatchSize: 1, + textDetectionBatchSize: 1, + textRecognitionBatchSize: 1 + }; +} + +function createWorkerBackedOptions(overrides: Record = {}) { + const { pipelineConfig: pipelineOverrides, ...rest } = overrides; + return { + pipelineConfig: { + ...basePipelineConfig(), + ...(pipelineOverrides as object) + }, + ortOptions: {}, + ...rest + }; +} + +describe("worker-backed OCR adapter", () => { + it("initializes once and forces wasm proxy off in worker mode", async () => { + const { WorkerBackedPaddleOCR } = await import("../src/pipelines/ocr/worker-backed"); + const transportClient = { + request: vi.fn().mockResolvedValue({ + summary: { backend: "wasm" }, + modelConfig: { det: { name: "det" }, rec: { name: "rec" } } + }), + dispose: vi.fn() + }; + + const ocr = new WorkerBackedPaddleOCR( + createWorkerBackedOptions({ + pipelineConfig: { runtimeDefaults: { text_det_limit_side_len: 64 } }, + ortOptions: { proxy: true } + }), + transportClient + ); + + const first = await ocr.initialize(); + const second = await ocr.initialize(); + + expect(first).toEqual({ backend: "wasm" }); + expect(second).toEqual({ backend: "wasm" }); + expect(transportClient.request).toHaveBeenCalledTimes(1); + expect(transportClient.request).toHaveBeenCalledWith("init", { + options: expect.objectContaining({ + ortOptions: { + proxy: true, + disableWasmProxy: true + } + }) + }); + expect(ocr.getInitializationSummary()).toEqual({ backend: "wasm" }); + expect(ocr.getModelConfig()).toEqual({ det: { name: "det" }, rec: { name: "rec" } }); + }); + + it("predicts through the worker transport using transferable payloads", async () => { + const { WorkerBackedPaddleOCR } = await import("../src/pipelines/ocr/worker-backed"); + const transferables = [{ id: "bitmap" }]; + const mockOcrResult = { + image: { width: 1, height: 1 }, + items: [{ poly: [[0, 0]], text: "hello", score: 1 }], + metrics: { + detMs: 1, + recMs: 1, + totalMs: 2, + detectedBoxes: 1, + recognizedCount: 1 + }, + runtime: { + requestedBackend: "auto", + detProvider: "wasm", + recProvider: "wasm", + webgpuAvailable: false + } + }; + const transportClient = { + request: vi + .fn() + .mockResolvedValueOnce({ + summary: { backend: "wasm" }, + modelConfig: { det: {}, rec: {} } + }) + .mockResolvedValueOnce([mockOcrResult]), + dispose: vi.fn() + }; + sourceToWorkerPayload.mockResolvedValue({ + payload: { + kind: "imageBitmap", + imageBitmap: transferables[0] + }, + transferables + }); + + const ocr = new WorkerBackedPaddleOCR(createWorkerBackedOptions(), transportClient); + + const result = await ocr.predict({ kind: "source" }, { text_rec_score_thresh: 0.5 }); + + expect(sourceToWorkerPayload).toHaveBeenCalledWith({ kind: "source" }); + expect(transportClient.request).toHaveBeenNthCalledWith( + 2, + "predict", + { + sources: [ + { + kind: "imageBitmap", + imageBitmap: transferables[0] + } + ], + params: { text_rec_score_thresh: 0.5 } + }, + transferables + ); + expect(result).toEqual([mockOcrResult]); + }); + + it("predicts multiple sources when input is an array", async () => { + const { WorkerBackedPaddleOCR } = await import("../src/pipelines/ocr/worker-backed"); + const mockOcrResult = { + image: { width: 10, height: 10 }, + items: [], + metrics: { + detMs: 0, + recMs: 0, + totalMs: 0, + detectedBoxes: 0, + recognizedCount: 0 + }, + runtime: { + requestedBackend: "auto", + detProvider: "wasm", + recProvider: "wasm", + webgpuAvailable: false + } + }; + const transportClient = { + request: vi + .fn() + .mockResolvedValueOnce({ + summary: { backend: "wasm" }, + modelConfig: { det: {}, rec: {} } + }) + .mockResolvedValueOnce([ + mockOcrResult, + { ...mockOcrResult, image: { width: 20, height: 20 } } + ]), + dispose: vi.fn() + }; + sourceToWorkerPayload.mockResolvedValue({ + payload: { kind: "imageBitmap", imageBitmap: {} }, + transferables: [] + }); + + const ocr = new WorkerBackedPaddleOCR(createWorkerBackedOptions(), transportClient); + + await ocr.predict([{ a: 1 }, { b: 2 }], {}); + + expect(sourceToWorkerPayload).toHaveBeenCalledTimes(2); + expect(transportClient.request).toHaveBeenNthCalledWith( + 2, + "predict", + { + sources: [ + { kind: "imageBitmap", imageBitmap: {} }, + { kind: "imageBitmap", imageBitmap: {} } + ], + params: {} + }, + [] + ); + }); + + it("disposes the transport after initialization failures and allows retrying", async () => { + const { WorkerBackedPaddleOCR } = await import("../src/pipelines/ocr/worker-backed"); + const transportClient = { + request: vi + .fn() + .mockRejectedValueOnce(new Error("init failed")) + .mockResolvedValueOnce({ + summary: { backend: "wasm" }, + modelConfig: { det: {}, rec: {} } + }), + dispose: vi.fn() + }; + + const ocr = new WorkerBackedPaddleOCR(createWorkerBackedOptions(), transportClient); + + await expect(ocr.initialize()).rejects.toThrow("init failed"); + expect(transportClient.dispose).toHaveBeenCalledTimes(1); + await expect(ocr.initialize()).resolves.toEqual({ backend: "wasm" }); + }); + + it("swallows dispose request failures and rejects use after disposal", async () => { + const { WorkerBackedPaddleOCR } = await import("../src/pipelines/ocr/worker-backed"); + const transportClient = { + request: vi.fn().mockRejectedValue(new Error("worker already gone")), + dispose: vi.fn() + }; + + const ocr = new WorkerBackedPaddleOCR(createWorkerBackedOptions(), transportClient); + + await expect(ocr.dispose()).resolves.toBeUndefined(); + expect(transportClient.request).toHaveBeenCalledWith("dispose", {}); + expect(transportClient.dispose).toHaveBeenCalledTimes(1); + await expect(ocr.predict({}, {})).rejects.toThrow(/worker instance has been disposed/i); + }); +}); diff --git a/paddleocr-js/packages/core/test/worker-client.test.ts b/paddleocr-js/packages/core/test/worker-client.test.ts new file mode 100644 index 0000000..577aa09 --- /dev/null +++ b/paddleocr-js/packages/core/test/worker-client.test.ts @@ -0,0 +1,132 @@ +import { describe, expect, it } from "vitest"; + +import { WorkerTransportClient, createWorkerTransportClient } from "../src/worker/client"; +import { createTransportSuccess } from "../src/worker/protocol"; + +class MockWorker { + constructor() { + this.messages = []; + this.terminated = false; + this.onmessage = null; + this.onerror = null; + } + + postMessage(message, transferables) { + this.messages.push({ message, transferables }); + } + + terminate() { + this.terminated = true; + } +} + +function createClientWithWorker() { + const worker = new MockWorker(); + const client = new WorkerTransportClient({ + createWorker: () => worker + }); + return { worker, client }; +} + +describe("worker transport client", () => { + it("creates a worker lazily and resolves request responses", async () => { + const { worker, client } = createClientWithWorker(); + + const responsePromise = client.request("predict", { value: 1 }, ["xfer"]); + expect(worker.messages).toHaveLength(1); + expect(worker.messages[0]).toMatchObject({ + message: { + kind: "worker-transport-request", + type: "predict", + payload: { value: 1 }, + requestId: 1 + }, + transferables: ["xfer"] + }); + + worker.onmessage({ + data: createTransportSuccess(1, { ok: true }) + }); + + await expect(responsePromise).resolves.toEqual({ ok: true }); + }); + + it("rejects when the worker reports an error response", async () => { + const { worker, client } = createClientWithWorker(); + + const responsePromise = client.request("predict", {}); + worker.onmessage({ + data: { + kind: "worker-transport-response", + status: "error", + requestId: 1, + error: { + name: "TypeError", + message: "bad payload", + stack: "stack" + } + } + }); + + await expect(responsePromise).rejects.toMatchObject({ + name: "TypeError", + message: "bad payload", + stack: "stack" + }); + }); + + it("ignores messages that are not transport responses", async () => { + const { worker, client } = createClientWithWorker(); + + const responsePromise = client.request("predict", {}); + worker.onmessage({ + data: { kind: "other" } + }); + worker.onmessage({ + data: createTransportSuccess(1, { ok: true }) + }); + + await expect(responsePromise).resolves.toEqual({ ok: true }); + }); + + it("rejects all pending requests when the worker errors", async () => { + const { worker, client } = createClientWithWorker(); + + const first = client.request("init", {}); + const second = client.request("predict", {}); + worker.onerror({ message: "worker crashed" }); + + await expect(first).rejects.toThrow("worker crashed"); + await expect(second).rejects.toThrow("worker crashed"); + }); + + it("rejects requests after disposal and terminates the worker", async () => { + const worker = new MockWorker(); + const client = createWorkerTransportClient({ + createWorker: () => worker + }); + + const pending = client.request("predict", {}); + client.dispose(); + + await expect(pending).rejects.toThrow("Worker transport client has been disposed."); + expect(worker.terminated).toBe(true); + expect(() => client.dispose()).not.toThrow(); + expect(() => client.ensureActive()).toThrow(/has been disposed/i); + }); + + it("requires a createWorker factory when the worker is first needed", () => { + const client = new WorkerTransportClient(); + + expect(() => client.ensureWorker()).toThrow(/requires a createWorker\(\) factory/i); + }); + + it("allows disposing before a worker has ever been created", () => { + const client = new WorkerTransportClient({ + createWorker: () => new MockWorker() + }); + + expect(() => client.disposeWorker()).not.toThrow(); + expect(() => client.dispose()).not.toThrow(); + }); +}); diff --git a/paddleocr-js/packages/core/test/worker-entry.test.ts b/paddleocr-js/packages/core/test/worker-entry.test.ts new file mode 100644 index 0000000..0bb6b94 --- /dev/null +++ b/paddleocr-js/packages/core/test/worker-entry.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it, vi } from "vitest"; + +import { attachWorkerMessageHandler } from "../src/worker/entry"; + +function createWorkerScope() { + return { + postMessage: vi.fn() + }; +} + +function transportRequest(overrides) { + return { + kind: "worker-transport-request", + type: "predict", + payload: {}, + requestId: 4, + ...overrides + }; +} + +describe("worker entry message handler", () => { + it("ignores messages that do not use the transport protocol", async () => { + const scope = createWorkerScope(); + + attachWorkerMessageHandler(async () => ({}), scope); + await scope.onmessage({ data: { kind: "other" } }); + + expect(scope.postMessage).not.toHaveBeenCalled(); + }); + + it("posts success responses for handled messages", async () => { + const scope = createWorkerScope(); + + attachWorkerMessageHandler(async (type, payload) => ({ type, payload }), scope); + await scope.onmessage({ + data: transportRequest({ + payload: { value: 1 }, + requestId: 3 + }) + }); + + expect(scope.postMessage).toHaveBeenCalledWith({ + kind: "worker-transport-response", + status: "success", + requestId: 3, + payload: { + type: "predict", + payload: { value: 1 } + } + }); + }); + + it("posts error responses when the handler throws", async () => { + const scope = createWorkerScope(); + + attachWorkerMessageHandler(async () => { + throw new Error("boom"); + }, scope); + + await scope.onmessage({ + data: transportRequest() + }); + + expect(scope.postMessage).toHaveBeenCalledWith( + expect.objectContaining({ + kind: "worker-transport-response", + status: "error", + requestId: 4, + error: expect.objectContaining({ + name: "Error", + message: "boom" + }) + }) + ); + }); +}); diff --git a/paddleocr-js/packages/core/test/worker-protocol.test.ts b/paddleocr-js/packages/core/test/worker-protocol.test.ts new file mode 100644 index 0000000..406c91f --- /dev/null +++ b/paddleocr-js/packages/core/test/worker-protocol.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, it } from "vitest"; + +import { + createTransportError, + createTransportRequest, + createTransportSuccess, + deserializeError, + isTransportRequest, + isTransportResponse, + serializeError +} from "../src/worker/protocol"; + +describe("worker transport protocol", () => { + it("creates transport requests with the expected wire format", () => { + const request = createTransportRequest("predict", { value: 1 }, 7); + + expect(request).toEqual({ + kind: "worker-transport-request", + type: "predict", + payload: { value: 1 }, + requestId: 7 + }); + expect(isTransportRequest(request)).toBe(true); + expect(isTransportResponse(request)).toBe(false); + }); + + it("creates success responses", () => { + const response = createTransportSuccess(7, { ok: true }); + + expect(response).toEqual({ + kind: "worker-transport-response", + status: "success", + requestId: 7, + payload: { ok: true } + }); + expect(isTransportResponse(response)).toBe(true); + expect(isTransportRequest(response)).toBe(false); + }); + + it("creates error responses by serializing the error", () => { + const response = createTransportError(9, new TypeError("bad payload")); + + expect(response).toMatchObject({ + kind: "worker-transport-response", + status: "error", + requestId: 9, + error: { + name: "TypeError", + message: "bad payload" + } + }); + }); + + it("serializes unknown errors with defaults", () => { + expect(serializeError(null)).toEqual({ + name: "Error", + message: "Unknown worker error.", + stack: "" + }); + }); + + it("deserializes error payloads back into Error instances", () => { + const error = deserializeError({ + name: "RangeError", + message: "out of range", + stack: "stack-trace" + }); + + expect(error).toBeInstanceOf(Error); + expect(error.name).toBe("RangeError"); + expect(error.message).toBe("out of range"); + expect(error.stack).toBe("stack-trace"); + }); +}); diff --git a/paddleocr-js/packages/core/tsconfig.json b/paddleocr-js/packages/core/tsconfig.json new file mode 100644 index 0000000..fa3db3e --- /dev/null +++ b/paddleocr-js/packages/core/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "lib": ["ES2022", "DOM", "DOM.Iterable", "WebWorker"], + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "test"] +} diff --git a/paddleocr-js/packages/core/tsconfig.test.json b/paddleocr-js/packages/core/tsconfig.test.json new file mode 100644 index 0000000..bba93f7 --- /dev/null +++ b/paddleocr-js/packages/core/tsconfig.test.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "declaration": false, + "declarationMap": false + }, + "include": ["src", "test"], + "exclude": ["node_modules", "dist"] +} diff --git a/paddleocr-js/packages/core/vite.config.ts b/paddleocr-js/packages/core/vite.config.ts new file mode 100644 index 0000000..451c6bd --- /dev/null +++ b/paddleocr-js/packages/core/vite.config.ts @@ -0,0 +1,105 @@ +import { resolve, dirname, join } from "node:path"; +import { readFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { defineConfig } from "vite"; +import dts from "vite-plugin-dts"; + +const require = createRequire(import.meta.url); +const ortPackageDir = dirname(require.resolve("onnxruntime-web")); +const ortVersion = JSON.parse( + readFileSync(join(ortPackageDir, "..", "package.json"), "utf-8") +).version; + +// Post-process library output for npm compatibility. +// +// 1. Rewrite Vite's worker URL references so downstream bundlers handle them +// correctly (relative paths + split new Worker(new URL()) pattern). +// +// 2. Strip ORT's base64-encoded WASM data URIs from the worker asset. +// Vite converts ORT's `new URL('./ort-wasm-*.wasm', import.meta.url)` +// references into `data:application/wasm;base64,...` data URIs, inflating +// the worker by ~50 MB. The SDK provides a CDN-based fallback for +// `ort.env.wasm.wasmPaths`, so the embedded binaries are never loaded. +// Only `data:application/wasm` URIs are stripped — OpenCV.js embeds its +// WASM via Emscripten as `data:application/octet-stream` and must be kept +// since there is no equivalent fallback for it. +// +// The stripping must run here (main build generateBundle), not in +// worker.rollupOptions.plugins, because Vite injects the data URIs as a +// post-processing step after the worker's own Rollup pipeline finishes. +function libraryWorkerPlugin() { + const ortWasmDataUriPattern = /data:application\/wasm;base64,[A-Za-z0-9+/=]+/g; + + return { + name: "paddleocr-library-worker", + apply: "build", + generateBundle(_, bundle) { + for (const item of Object.values(bundle)) { + if (item.type === "asset" && typeof item.source === "string") { + item.source = item.source.replace(ortWasmDataUriPattern, "data:application/wasm;base64,"); + continue; + } + if (item.type !== "chunk") continue; + if (!item.code.includes("/assets/")) continue; + + item.code = item.code.replace( + /\/\*\s*@vite-ignore\s*\*\/\s*"\/(assets\/[^"]+)"/g, + '"./$1"' + ); + + item.code = item.code.replace( + /new\s+Worker\(\s*new\s+URL\(\s*"(\.\/(assets\/[^"]+))"\s*,\s*(import\.meta\.url)\s*\)\s*,\s*(\{[^}]*\})\s*\)/g, + (_, fullPath, _assetPath, urlBase, workerOpts) => { + return `(() => { const _w = new URL("${fullPath}", ${urlBase}); return new Worker(_w, ${workerOpts}); })()`; + } + ); + } + } + }; +} + +export default defineConfig({ + plugins: [ + dts({ + rollupTypes: false + }), + libraryWorkerPlugin() + ], + define: { + __ORT_WASM_CDN_PREFIX__: JSON.stringify( + `https://cdn.jsdelivr.net/npm/onnxruntime-web@${ortVersion}/dist/` + ) + }, + worker: { + format: "es", + rollupOptions: { + output: { + inlineDynamicImports: true + } + } + }, + build: { + lib: { + entry: { + index: resolve(__dirname, "src/index.ts"), + viz: resolve(__dirname, "src/viz/index.ts") + }, + name: "paddleocr", + formats: ["es"], + fileName: (_format, entryName) => `${entryName}.mjs` + }, + rollupOptions: { + external: ["onnxruntime-web", "@techstark/opencv-js", "clipper-lib", "js-yaml"], + output: { + globals: { + "onnxruntime-web": "ort", + "@techstark/opencv-js": "cv", + "clipper-lib": "ClipperLib", + "js-yaml": "jsyaml" + } + } + }, + sourcemap: true, + minify: false + } +}); diff --git a/paddleocr-js/tsconfig.eslint.json b/paddleocr-js/tsconfig.eslint.json new file mode 100644 index 0000000..969664a --- /dev/null +++ b/paddleocr-js/tsconfig.eslint.json @@ -0,0 +1,14 @@ +{ + "extends": "./packages/core/tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "noEmit": true, + "baseUrl": ".", + "paths": { + "@paddleocr/paddleocr-js": ["./packages/core/src/index.ts"], + "@paddleocr/paddleocr-js/viz": ["./packages/core/src/viz/index.ts"] + } + }, + "include": ["packages/**/src/**/*.ts", "packages/**/test/**/*.ts", "apps/**/src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/paddleocr-js/tsconfig.json b/paddleocr-js/tsconfig.json new file mode 100644 index 0000000..dfab9f5 --- /dev/null +++ b/paddleocr-js/tsconfig.json @@ -0,0 +1,4 @@ +{ + "files": [], + "references": [{ "path": "packages/core" }, { "path": "apps/demo" }] +} diff --git a/paddleocr-js/vitest.config.js b/paddleocr-js/vitest.config.js new file mode 100644 index 0000000..ac2909e --- /dev/null +++ b/paddleocr-js/vitest.config.js @@ -0,0 +1,11 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "jsdom", + coverage: { + provider: "v8", + include: ["packages/core/src/**/*.ts"] + } + } +}); diff --git a/paddleocr/__init__.py b/paddleocr/__init__.py new file mode 100644 index 0000000..a09deb1 --- /dev/null +++ b/paddleocr/__init__.py @@ -0,0 +1,131 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddlex.inference.utils.benchmark import benchmark + +from ._models import ( + ChartParsing, + DocImgOrientationClassification, + DocVLM, + FormulaRecognition, + LayoutDetection, + SealTextDetection, + TableCellsDetection, + TableClassification, + TableStructureRecognition, + TextDetection, + TextImageUnwarping, + TextLineOrientationClassification, + TextRecognition, +) +from ._pipelines import ( + DocPreprocessor, + DocUnderstanding, + FormulaRecognitionPipeline, + PaddleOCR, + PaddleOCRVL, + PPChatOCRv4Doc, + PPDocTranslation, + PPStructureV3, + SealRecognition, + TableRecognitionPipelineV2, +) +from ._api_client.async_client import AsyncPaddleOCRClient +from ._api_client.client import PaddleOCRClient +from ._api_client.errors import ( + APIError, + AuthError, + InvalidRequestError, + JobFailedError, + NetworkError, + PaddleOCRAPIError, + PollTimeoutError, + RateLimitError, + RequestTimeoutError, + ResponseFormatError, + ResultParseError, + ServiceUnavailableError, +) +from ._api_client.models import ( + Model, + OCROptions, + PaddleOCRVLOptions, + PPStructureV3Options, +) +from ._utils.logging import logger +from ._version import version as __version__ + + +def doc2md_convert(source, **kwargs): + """Convert an office document to Markdown. See paddleocr._doc2md.convert.""" + from ._doc2md import convert + + return convert(source, **kwargs) + + +def doc2md_supported_formats(): + """Return supported file extensions. See paddleocr._doc2md.supported_formats.""" + from ._doc2md import supported_formats + + return supported_formats() + + +__all__ = [ + "benchmark", + "PaddleOCRClient", + "AsyncPaddleOCRClient", + "Model", + "OCROptions", + "PPStructureV3Options", + "PaddleOCRVLOptions", + "ChartParsing", + "DocImgOrientationClassification", + "DocVLM", + "FormulaRecognition", + "SealTextDetection", + "LayoutDetection", + "TableCellsDetection", + "TableClassification", + "TableStructureRecognition", + "TextDetection", + "TextImageUnwarping", + "TextLineOrientationClassification", + "TextRecognition", + "DocPreprocessor", + "DocUnderstanding", + "FormulaRecognitionPipeline", + "PaddleOCR", + "PaddleOCRVL", + "PPChatOCRv4Doc", + "PPDocTranslation", + "PPStructureV3", + "SealRecognition", + "TableRecognitionPipelineV2", + "doc2md_convert", + "doc2md_supported_formats", + "PaddleOCRAPIError", + "AuthError", + "InvalidRequestError", + "APIError", + "JobFailedError", + "RateLimitError", + "RequestTimeoutError", + "PollTimeoutError", + "ResponseFormatError", + "ResultParseError", + "ServiceUnavailableError", + "NetworkError", + "logger", + "__version__", +] diff --git a/paddleocr/__main__.py b/paddleocr/__main__.py new file mode 100644 index 0000000..abc3c1b --- /dev/null +++ b/paddleocr/__main__.py @@ -0,0 +1,39 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys + +from ._cli import main + + +def console_entry() -> int: + # See https://docs.python.org/3/library/signal.html#note-on-sigpipe + try: + # Flush output here to force SIGPIPE to be triggered while inside this + # try block. + main() + sys.stdout.flush() + sys.stderr.flush() + except BrokenPipeError: + # Python flushes standard streams on exit; + # redirect remaining output to devnull to avoid another BrokenPipeError + # at shutdown. + devnull = os.open(os.devnull, os.O_WRONLY) + os.dup2(devnull, sys.stdout.fileno()) + sys.exit(1) + + +if __name__ == "__main__": + console_entry() diff --git a/paddleocr/_abstract.py b/paddleocr/_abstract.py new file mode 100644 index 0000000..773e4fb --- /dev/null +++ b/paddleocr/_abstract.py @@ -0,0 +1,25 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc + + +class CLISubcommandExecutor(metaclass=abc.ABCMeta): + @abc.abstractmethod + def add_subparser(self, subparsers): + raise NotImplementedError + + @abc.abstractmethod + def execute_with_args(self, args): + raise NotImplementedError diff --git a/paddleocr/_api_client/__init__.py b/paddleocr/_api_client/__init__.py new file mode 100644 index 0000000..290f972 --- /dev/null +++ b/paddleocr/_api_client/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/paddleocr/_api_client/_async_http.py b/paddleocr/_api_client/_async_http.py new file mode 100644 index 0000000..7d02bed --- /dev/null +++ b/paddleocr/_api_client/_async_http.py @@ -0,0 +1,188 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +from typing import Any, Dict, Optional + +import aiohttp + +from ._core import ( + extract_api_message_from_payload, + extract_job_id, + raise_for_status, + unwrap_api_response, +) +from .errors import ( + NetworkError, + RequestTimeoutError, + ResponseFormatError, + ResultParseError, +) +from ._http import API_PATH, DEFAULT_BASE_URL + + +class AsyncHTTPClient: + def __init__( + self, + token: str, + base_url: str, + timeout: float, + client_platform: Optional[str] = None, + ): + self._token = token + self._base_url = base_url.rstrip("/") + self._jobs_url = f"{self._base_url}{API_PATH}" + self._timeout = timeout + self._client_platform = client_platform + self._session = None + + @property + def timeout(self) -> float: + return self._timeout + + async def __aenter__(self): + await self._ensure_session() + return self + + async def __aexit__(self, *args): + await self.close() + + async def _ensure_session(self): + if self._session is None: + self._session = aiohttp.ClientSession( + headers=self._api_headers(), + timeout=aiohttp.ClientTimeout(total=self._timeout), + ) + + async def close(self): + if self._session: + await self._session.close() + self._session = None + + def _api_headers(self) -> dict: + headers = {"Authorization": f"Bearer {self._token}"} + if self._client_platform: + headers["Client-Platform"] = self._client_platform + return headers + + async def submit_url( + self, + model: str, + file_url: str, + optional_payload: dict, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + ) -> str: + body = { + "fileUrl": file_url, + "model": model, + "optionalPayload": optional_payload, + } + if page_ranges is not None: + body["pageRanges"] = page_ranges + if batch_id is not None: + body["batchId"] = batch_id + + await self._ensure_session() + async with self._session.post( + self._jobs_url, + json=body, + headers={"Content-Type": "application/json"}, + ) as resp: + await self._raise_for_response(resp) + data = await self._response_data(resp) + return extract_job_id(data) + + async def submit_file( + self, + model: str, + file_path: str, + optional_payload: dict, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + ) -> str: + if not os.path.exists(file_path): + raise FileNotFoundError(file_path) + + form = aiohttp.FormData() + form.add_field("model", model) + form.add_field("optionalPayload", json.dumps(optional_payload)) + if page_ranges is not None: + form.add_field("pageRanges", page_ranges) + if batch_id is not None: + form.add_field("batchId", batch_id) + + with open(file_path, "rb") as f: + file_data = f.read() + form.add_field( + "file", + file_data, + filename=os.path.basename(file_path), + ) + + await self._ensure_session() + async with self._session.post(self._jobs_url, data=form) as resp: + await self._raise_for_response(resp) + data = await self._response_data(resp) + return extract_job_id(data) + + async def get_job_status(self, job_id: str) -> Dict[str, Any]: + await self._ensure_session() + async with self._session.get(f"{self._jobs_url}/{job_id}") as resp: + await self._raise_for_response(resp) + return await self._response_data(resp) + + async def get_batch_status(self, batch_id: str) -> Dict[str, Any]: + await self._ensure_session() + async with self._session.get(f"{self._jobs_url}/batch/{batch_id}") as resp: + await self._raise_for_response(resp) + return await self._response_data(resp) + + async def fetch_jsonl(self, url: str) -> list: + timeout = aiohttp.ClientTimeout(total=self._timeout) + async with aiohttp.ClientSession(timeout=timeout) as bare_session: + async with bare_session.get(url) as resp: + await self._raise_for_response(resp) + text = await resp.text() + try: + lines = text.strip().split("\n") + return [json.loads(line) for line in lines if line.strip()] + except json.JSONDecodeError as e: + raise ResultParseError( + f"Malformed JSONL result payload: {e}" + ) from e + + async def _raise_for_response(self, resp) -> None: + if 200 <= resp.status < 300: + return + try: + body = await resp.json() + msg = ( + extract_api_message_from_payload(body) + if isinstance(body, dict) + else None + ) + if not msg: + msg = await resp.text() + except Exception: + msg = await resp.text() + raise_for_status(resp.status, msg) + + async def _response_data(self, resp) -> Dict[str, Any]: + try: + payload = await resp.json() + except Exception as e: + raise ResponseFormatError(f"Response body is not valid JSON: {e}") from e + return unwrap_api_response(payload, resp.status) diff --git a/paddleocr/_api_client/_async_poller.py b/paddleocr/_api_client/_async_poller.py new file mode 100644 index 0000000..9195d04 --- /dev/null +++ b/paddleocr/_api_client/_async_poller.py @@ -0,0 +1,86 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio +from typing import Any + +from ._core import ( + job_status_from_data, + parse_batch_status, + validate_result_json_url, + validate_state, +) +from .errors import ( + JobFailedError, + PollTimeoutError, +) +from .results import BatchStatus, JobStatus + +DEFAULT_INITIAL_INTERVAL = 3.0 +DEFAULT_MULTIPLIER = 1.5 +DEFAULT_MAX_INTERVAL = 15.0 +DEFAULT_MAX_WAIT_TIME = 600.0 + + +class AsyncPoller: + def __init__( + self, + http_client, + initial_interval: float = DEFAULT_INITIAL_INTERVAL, + multiplier: float = DEFAULT_MULTIPLIER, + max_interval: float = DEFAULT_MAX_INTERVAL, + max_wait_time: float = DEFAULT_MAX_WAIT_TIME, + ): + self._http = http_client + self._initial_interval = initial_interval + self._multiplier = multiplier + self._max_interval = max_interval + self._max_wait_time = max_wait_time + + async def poll_until_done(self, job_id: str) -> Any: + interval = self._initial_interval + loop = asyncio.get_running_loop() + start = loop.time() + deadline = start + self._max_wait_time + + while True: + now = loop.time() + if now >= deadline: + raise PollTimeoutError(job_id, now - start) + + data = await self._http.get_job_status(job_id) + state = validate_state(data) + + if state == "done": + json_url = validate_result_json_url(data) + jsonl_data = await self._http.fetch_jsonl(json_url) + return jsonl_data, data + + if state == "failed": + error_msg = data.get("errorMsg", "Unknown error") + raise JobFailedError(job_id, error_msg) + + remaining = deadline - loop.time() + if remaining <= 0: + raise PollTimeoutError(job_id, loop.time() - start) + await asyncio.sleep(min(interval, remaining)) + interval = min(interval * self._multiplier, self._max_interval) + + async def get_status(self, job_id: str) -> JobStatus: + data = await self._http.get_job_status(job_id) + return job_status_from_data(job_id, data) + + async def get_batch_status(self, batch_id: str) -> BatchStatus: + data = await self._http.get_batch_status(batch_id) + return parse_batch_status(batch_id, data) diff --git a/paddleocr/_api_client/_core.py b/paddleocr/_api_client/_core.py new file mode 100644 index 0000000..77f0308 --- /dev/null +++ b/paddleocr/_api_client/_core.py @@ -0,0 +1,211 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional, Union + +from .errors import ( + APIError, + AuthError, + InvalidRequestError, + RateLimitError, + ResponseFormatError, + ServiceUnavailableError, +) +from .models import ( + DocParsingOptions, + Model, + OCROptions, + PaddleOCRVLOptions, + PPStructureV3Options, + is_document_parsing_model, + is_ocr_model, + is_vl_model, +) +from .results import BatchStatus, Job, JobStatus, Progress + + +def validate_input_source(file_url: Optional[str], file_path: Optional[str]) -> None: + if not file_url and not file_path: + raise InvalidRequestError("Either file_url or file_path is required.") + if file_url and file_path: + raise InvalidRequestError("file_url and file_path are mutually exclusive.") + + +def default_payload(model: Model) -> dict: + if is_ocr_model(model): + return OCROptions().to_payload() + return resolve_document_options(model, None).to_payload() + + +def resolve_ocr_model(model: Union[Model, str]) -> Model: + resolved = resolve_model(model) + if not is_ocr_model(resolved): + raise InvalidRequestError(f"Unsupported OCR model: {model}") + return resolved + + +def resolve_document_model(model: Union[Model, str]) -> Model: + resolved = resolve_model(model) + if not is_document_parsing_model(resolved): + raise InvalidRequestError(f"Unsupported document parsing model: {model}") + return resolved + + +def resolve_model(model: Union[Model, str]) -> Model: + if isinstance(model, Model): + return model + try: + return Model(model) + except ValueError as e: + raise InvalidRequestError(f"Unsupported model: {model}") from e + + +def resolve_document_options( + model: Model, options: Optional[DocParsingOptions] +) -> DocParsingOptions: + if options is not None: + if model == Model.PP_STRUCTURE_V3 and not isinstance( + options, PPStructureV3Options + ): + raise InvalidRequestError("PP-StructureV3 requires PPStructureV3Options.") + if is_vl_model(model) and not isinstance(options, PaddleOCRVLOptions): + raise InvalidRequestError("PaddleOCR-VL models require PaddleOCRVLOptions.") + return options + if model == Model.PP_STRUCTURE_V3: + return PPStructureV3Options() + return PaddleOCRVLOptions() + + +def job_id_for_task(job: Union[Job, str], task: str) -> str: + if isinstance(job, str): + return job + if job.task != task: + raise InvalidRequestError( + f"Job task mismatch: expected {task}, got {job.task}." + ) + if task == "ocr" and not is_ocr_model(job.model): + raise InvalidRequestError(f"Job model is not an OCR model: {job.model}.") + if task == "document_parsing" and not is_document_parsing_model(job.model): + raise InvalidRequestError( + f"Job model is not a document parsing model: {job.model}." + ) + return job.job_id + + +def extract_api_message_from_payload(payload: dict) -> Optional[str]: + for key in ("msg", "errorMsg", "message"): + value = payload.get(key) + if value: + return str(value) + data = payload.get("data") + if isinstance(data, dict): + value = data.get("errorMsg") + if value: + return str(value) + return None + + +def validate_state(data: dict) -> str: + state = data.get("state") + if state not in {"pending", "running", "done", "failed"}: + raise ResponseFormatError(f"Unknown or missing job state: {state}") + return state + + +def job_status_from_data(job_id: str, data: dict) -> JobStatus: + state = validate_state(data) + progress = None + ep = data.get("extractProgress") + if ep: + if not isinstance(ep, dict): + raise ResponseFormatError("'extractProgress' must be an object.") + progress = Progress( + total_pages=ep.get("totalPages", 0), + extracted_pages=ep.get("extractedPages", 0), + start_time=ep.get("startTime"), + end_time=ep.get("endTime"), + ) + return JobStatus( + job_id=job_id, + state=state, + progress=progress, + result=data.get("resultUrl"), + error_msg=data.get("errorMsg"), + ) + + +def raise_for_status(status_code: int, msg: str) -> None: + if 200 <= status_code < 300: + return + if status_code in (401, 403): + raise AuthError(f"Authentication failed: {msg}") + if status_code == 400: + raise InvalidRequestError(f"Bad request: {msg}") + if status_code == 429: + raise RateLimitError(f"Rate limit exceeded: {msg}") + if status_code in (503, 504): + raise ServiceUnavailableError(status_code, f"Service unavailable: {msg}") + raise APIError(status_code, msg) + + +def unwrap_api_response(payload: dict, status_code: int) -> dict: + if not isinstance(payload, dict): + raise ResponseFormatError("Response body must be a JSON object.") + code = payload.get("code", 0) + if code not in (0, None): + raise APIError(status_code, extract_api_message_from_payload(payload) or "") + data = payload.get("data") + if not isinstance(data, dict): + raise ResponseFormatError("Response JSON must contain object field 'data'.") + return data + + +def extract_job_id(data: dict) -> str: + job_id = data.get("jobId") + if not isinstance(job_id, str) or not job_id: + raise ResponseFormatError( + "Response data must contain non-empty string 'jobId'." + ) + return job_id + + +def validate_result_json_url(data: dict) -> str: + result_url = data.get("resultUrl") + if not isinstance(result_url, dict): + raise ResponseFormatError("Done job response must contain object 'resultUrl'.") + json_url = result_url.get("jsonUrl") + if not isinstance(json_url, str) or not json_url: + raise ResponseFormatError( + "Done job response resultUrl must contain non-empty string 'jsonUrl'." + ) + return json_url + + +def parse_batch_status(batch_id: str, data: dict) -> BatchStatus: + result = data.get("extractResult") + if not isinstance(result, list): + raise ResponseFormatError( + "Batch response data must contain list 'extractResult'." + ) + jobs = [] + for item in result: + if not isinstance(item, dict): + raise ResponseFormatError("Batch extractResult items must be objects.") + job_id = item.get("jobId") + if not isinstance(job_id, str) or not job_id: + raise ResponseFormatError( + "Batch extractResult items must contain non-empty string 'jobId'." + ) + jobs.append(job_status_from_data(job_id, item)) + return BatchStatus(batch_id=batch_id, jobs=jobs) diff --git a/paddleocr/_api_client/_http.py b/paddleocr/_api_client/_http.py new file mode 100644 index 0000000..26a1e32 --- /dev/null +++ b/paddleocr/_api_client/_http.py @@ -0,0 +1,206 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +from typing import Any, Dict, Optional + +import requests + +from ._core import ( + extract_api_message_from_payload, + extract_job_id, + raise_for_status, + unwrap_api_response, +) +from .errors import ( + NetworkError, + RequestTimeoutError, + ResponseFormatError, + ResultParseError, +) + +DEFAULT_BASE_URL = "https://paddleocr.aistudio-app.com" +API_PATH = "/api/v2/ocr/jobs" + + +def _raise_for_response(response: requests.Response) -> None: + if 200 <= response.status_code < 300: + return + raise_for_status(response.status_code, _extract_api_message(response)) + + +def _extract_api_message(response: requests.Response) -> str: + try: + payload = response.json() + except ValueError: + return response.text + if isinstance(payload, dict): + msg = extract_api_message_from_payload(payload) + if msg: + return msg + return response.text + + +def _response_json(response: requests.Response) -> Dict[str, Any]: + try: + payload = response.json() + except ValueError as e: + raise ResponseFormatError(f"Response body is not valid JSON: {e}") from e + if not isinstance(payload, dict): + raise ResponseFormatError("Response body must be a JSON object.") + return payload + + +def _response_data(response: requests.Response) -> Dict[str, Any]: + payload = _response_json(response) + return unwrap_api_response(payload, response.status_code) + + +def _job_id_from_response(response: requests.Response) -> str: + return extract_job_id(_response_data(response)) + + +class HTTPClient: + def __init__( + self, + token: str, + base_url: str, + timeout: float, + client_platform: Optional[str] = None, + ): + self._token = token + self._base_url = base_url.rstrip("/") + self._jobs_url = f"{self._base_url}{API_PATH}" + self._timeout = timeout + self._session = requests.Session() + self._session.headers["Authorization"] = f"Bearer {token}" + if client_platform: + self._session.headers["Client-Platform"] = client_platform + + @property + def timeout(self) -> float: + return self._timeout + + def submit_url( + self, + model: str, + file_url: str, + optional_payload: dict, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + ) -> str: + body = { + "fileUrl": file_url, + "model": model, + "optionalPayload": optional_payload, + } + if page_ranges is not None: + body["pageRanges"] = page_ranges + if batch_id is not None: + body["batchId"] = batch_id + try: + resp = self._session.post( + self._jobs_url, + json=body, + timeout=self._timeout, + ) + except requests.Timeout as e: + raise RequestTimeoutError(f"Request timed out: {e}") from e + except requests.ConnectionError as e: + raise NetworkError(f"Connection failed: {e}") from e + _raise_for_response(resp) + return _job_id_from_response(resp) + + def submit_file( + self, + model: str, + file_path: str, + optional_payload: dict, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + ) -> str: + if not os.path.exists(file_path): + raise FileNotFoundError(file_path) + data = { + "model": model, + "optionalPayload": json.dumps(optional_payload), + } + if page_ranges is not None: + data["pageRanges"] = page_ranges + if batch_id is not None: + data["batchId"] = batch_id + try: + with open(file_path, "rb") as f: + resp = self._session.post( + self._jobs_url, + data=data, + files={"file": f}, + timeout=self._timeout, + ) + except requests.Timeout as e: + raise RequestTimeoutError(f"Request timed out: {e}") from e + except requests.ConnectionError as e: + raise NetworkError(f"Connection failed: {e}") from e + _raise_for_response(resp) + return _job_id_from_response(resp) + + def get_job_status(self, job_id: str) -> Dict[str, Any]: + try: + resp = self._session.get( + f"{self._jobs_url}/{job_id}", + timeout=self._timeout, + ) + except requests.Timeout as e: + raise RequestTimeoutError(f"Request timed out: {e}") from e + except requests.ConnectionError as e: + raise NetworkError(f"Connection failed: {e}") from e + _raise_for_response(resp) + return _response_data(resp) + + def get_batch_status(self, batch_id: str) -> Dict[str, Any]: + try: + resp = self._session.get( + f"{self._jobs_url}/batch/{batch_id}", + timeout=self._timeout, + ) + except requests.Timeout as e: + raise RequestTimeoutError(f"Request timed out: {e}") from e + except requests.ConnectionError as e: + raise NetworkError(f"Connection failed: {e}") from e + _raise_for_response(resp) + return _response_data(resp) + + def fetch_jsonl(self, url: str) -> list: + # Result URLs are often pre-signed object storage links. + try: + resp = requests.get(url, timeout=self._timeout) + except requests.Timeout as e: + raise RequestTimeoutError(f"Request timed out: {e}") from e + except requests.ConnectionError as e: + raise NetworkError(f"Connection failed: {e}") from e + try: + resp.raise_for_status() + lines = resp.text.strip().split("\n") + results = [] + for line in lines: + line = line.strip() + if line: + results.append(json.loads(line)) + return results + except json.JSONDecodeError as e: + raise ResultParseError(f"Malformed JSONL result payload: {e}") from e + + def close(self): + self._session.close() diff --git a/paddleocr/_api_client/_naming.py b/paddleocr/_api_client/_naming.py new file mode 100644 index 0000000..63cc03b --- /dev/null +++ b/paddleocr/_api_client/_naming.py @@ -0,0 +1,17 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddleocr._utils.naming import snake_keys_to_camel, snake_to_camel + +__all__ = ["snake_keys_to_camel", "snake_to_camel"] diff --git a/paddleocr/_api_client/_poller.py b/paddleocr/_api_client/_poller.py new file mode 100644 index 0000000..b85b5ac --- /dev/null +++ b/paddleocr/_api_client/_poller.py @@ -0,0 +1,152 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time +from typing import Any + +from ._core import ( + job_status_from_data, + parse_batch_status, + validate_result_json_url, + validate_state, +) +from .errors import ( + JobFailedError, + PollTimeoutError, + ResultParseError, +) +from .results import ( + BatchStatus, + DocParsingPage, + DocParsingResult, + JobStatus, + OCRPage, + OCRResult, +) + +DEFAULT_INITIAL_INTERVAL = 3.0 +DEFAULT_MULTIPLIER = 1.5 +DEFAULT_MAX_INTERVAL = 15.0 +DEFAULT_MAX_WAIT_TIME = 600.0 + + +class Poller: + def __init__( + self, + http_client, + initial_interval: float = DEFAULT_INITIAL_INTERVAL, + multiplier: float = DEFAULT_MULTIPLIER, + max_interval: float = DEFAULT_MAX_INTERVAL, + max_wait_time: float = DEFAULT_MAX_WAIT_TIME, + ): + self._http = http_client + self._initial_interval = initial_interval + self._multiplier = multiplier + self._max_interval = max_interval + self._max_wait_time = max_wait_time + + def poll_until_done(self, job_id: str) -> Any: + interval = self._initial_interval + start = time.monotonic() + deadline = start + self._max_wait_time + + while True: + now = time.monotonic() + if now >= deadline: + raise PollTimeoutError(job_id, now - start) + + data = self._http.get_job_status(job_id) + state = validate_state(data) + + if state == "done": + json_url = validate_result_json_url(data) + jsonl_data = self._http.fetch_jsonl(json_url) + return jsonl_data, data + + if state == "failed": + error_msg = data.get("errorMsg", "Unknown error") + raise JobFailedError(job_id, error_msg) + + remaining = deadline - time.monotonic() + if remaining <= 0: + raise PollTimeoutError(job_id, time.monotonic() - start) + + time.sleep(min(interval, remaining)) + interval = min(interval * self._multiplier, self._max_interval) + + def get_status(self, job_id: str) -> JobStatus: + data = self._http.get_job_status(job_id) + return job_status_from_data(job_id, data) + + def get_batch_status(self, batch_id: str) -> BatchStatus: + data = self._http.get_batch_status(batch_id) + return parse_batch_status(batch_id, data) + + +def parse_ocr_result(job_id: str, jsonl_data: list) -> OCRResult: + try: + pages = [] + data_info = {} + for line_obj in jsonl_data: + result = line_obj["result"] + if isinstance(result.get("dataInfo"), dict): + data_info.update(result["dataInfo"]) + for item in result["ocrResults"]: + pages.append( + OCRPage( + pruned_result=item["prunedResult"], + ocr_image_url=item.get("ocrImage"), + doc_preprocessing_image_url=item.get("docPreprocessingImage"), + input_image_url=item.get("inputImage"), + raw=item, + ) + ) + return OCRResult( + job_id=job_id, + pages=pages, + data_info=data_info, + ) + except (KeyError, TypeError) as e: + raise ResultParseError(f"Malformed OCR result payload: {e}") from e + + +def parse_doc_parsing_result(job_id: str, jsonl_data: list) -> DocParsingResult: + try: + pages = [] + data_info = {} + for line_obj in jsonl_data: + result = line_obj["result"] + if isinstance(result.get("dataInfo"), dict): + data_info.update(result["dataInfo"]) + for item in result["layoutParsingResults"]: + markdown = item["markdown"] + pages.append( + DocParsingPage( + markdown_text=markdown["text"], + markdown_images=markdown.get("images", {}), + output_images=item.get("outputImages", {}), + pruned_result=item.get("prunedResult"), + input_image_url=item.get("inputImage"), + exports=item.get("exports", {}), + markdown=markdown, + raw=item, + ) + ) + return DocParsingResult( + job_id=job_id, + pages=pages, + data_info=data_info, + ) + except (KeyError, TypeError) as e: + raise ResultParseError(f"Malformed document parsing result payload: {e}") from e diff --git a/paddleocr/_api_client/_resources.py b/paddleocr/_api_client/_resources.py new file mode 100644 index 0000000..0f4caf0 --- /dev/null +++ b/paddleocr/_api_client/_resources.py @@ -0,0 +1,212 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import tempfile +from pathlib import Path +from typing import Dict, Iterable, List, Optional, Tuple +from urllib.parse import unquote, urlparse + +import requests + +from .errors import InvalidRequestError, NetworkError, RequestTimeoutError +from .results import DocParsingResult, OCRResult + + +def save_resource( + resource_url: str, + destination: str, + *, + overwrite: bool = False, + filename: Optional[str] = None, + timeout: float = 300.0, +) -> str: + if not resource_url: + raise InvalidRequestError("resource_url is required.") + if not destination: + raise InvalidRequestError("destination is required.") + + parsed_url = urlparse(resource_url) + if parsed_url.scheme not in ("http", "https") or not parsed_url.netloc: + raise InvalidRequestError(f"Invalid resource URL: {resource_url}") + + target = _resolve_destination(parsed_url.path, destination, filename) + _require_writable_target(target, overwrite) + + try: + response = requests.get(resource_url, timeout=timeout) + except requests.Timeout as e: + raise RequestTimeoutError(f"Request timed out: {e}") from e + except requests.ConnectionError as e: + raise NetworkError(f"Connection failed: {e}") from e + + try: + response.raise_for_status() + except requests.RequestException as e: + raise NetworkError(f"Failed to download resource: {e}") from e + + _atomic_write(target, response.content, overwrite) + return str(target) + + +def save_ocr_result_resources( + result: OCRResult, + destination: str, + *, + overwrite: bool = False, + timeout: float = 300.0, +) -> List[str]: + if result is None: + raise InvalidRequestError("OCR result is required.") + dest_dir = _require_existing_directory(destination) + saved_paths = [] + for index, page in enumerate(result.pages): + if not page.ocr_image_url: + continue + filename = f"ocr-page-{index + 1}{_safe_resource_extension(page.ocr_image_url)}" + saved_paths.append( + save_resource( + page.ocr_image_url, + str(dest_dir / filename), + overwrite=overwrite, + timeout=timeout, + ) + ) + return saved_paths + + +def save_document_parsing_result_resources( + result: DocParsingResult, + destination: str, + *, + overwrite: bool = False, + timeout: float = 300.0, +) -> List[str]: + if result is None: + raise InvalidRequestError("document parsing result is required.") + dest_dir = _require_existing_directory(destination) + saved_paths = [] + for page in result.pages: + for filename, resource_url in _iter_named_resources(page.markdown_images): + saved_paths.append( + save_resource( + resource_url, + str(dest_dir / filename), + overwrite=overwrite, + timeout=timeout, + ) + ) + for filename, resource_url in _iter_named_resources(page.output_images): + saved_paths.append( + save_resource( + resource_url, + str(dest_dir / filename), + overwrite=overwrite, + timeout=timeout, + ) + ) + return saved_paths + + +def _resolve_destination( + url_path: str, destination: str, filename: Optional[str] +) -> Path: + destination_path = Path(destination) + if filename is not None: + _validate_result_resource_filename(filename) + target = destination_path / filename + elif destination_path.exists() and destination_path.is_dir(): + target = destination_path / _safe_url_basename(url_path) + else: + target = destination_path + + parent = target.parent + if not parent.exists(): + raise FileNotFoundError(str(parent)) + if not parent.is_dir(): + raise InvalidRequestError(f"Destination parent must be a directory: {parent}") + return target + + +def _require_existing_directory(destination: str) -> Path: + dest_dir = Path(destination) + if not dest_dir.exists(): + raise FileNotFoundError(destination) + if not dest_dir.is_dir(): + raise InvalidRequestError( + f"Destination must be an existing directory: {destination}" + ) + return dest_dir + + +def _require_writable_target(target: Path, overwrite: bool) -> None: + if target.exists() and not overwrite: + raise InvalidRequestError(f"Destination already exists: {target}") + + +def _atomic_write(target: Path, content: bytes, overwrite: bool) -> None: + fd, temp_path = tempfile.mkstemp( + prefix=f".{target.name}.tmp-", + dir=str(target.parent), + ) + try: + with os.fdopen(fd, "wb") as temp_file: + temp_file.write(content) + if overwrite: + os.replace(temp_path, target) + else: + os.link(temp_path, target) + os.remove(temp_path) + except FileExistsError as e: + raise InvalidRequestError(f"Destination already exists: {target}") from e + finally: + if os.path.exists(temp_path): + os.remove(temp_path) + + +def _iter_named_resources(resources: Dict[str, str]) -> Iterable[Tuple[str, str]]: + for key in sorted(resources): + resource_url = resources[key] + if not resource_url: + continue + _validate_result_resource_filename(key) + yield key, resource_url + + +def _safe_url_basename(url_path: str) -> str: + name = Path(unquote(url_path)).name + if not name or name in (".", ".."): + return "resource" + _validate_result_resource_filename(name) + return name + + +def _safe_resource_extension(resource_url: str) -> str: + parsed = urlparse(resource_url) + suffix = Path(unquote(parsed.path)).suffix + if not suffix: + return "" + try: + _validate_result_resource_filename(f"resource{suffix}") + except InvalidRequestError: + return "" + return suffix + + +def _validate_result_resource_filename(name: str) -> None: + if not name: + raise InvalidRequestError("Resource filename must not be empty.") + path = Path(name) + if path.name != name or "/" in name or "\\" in name or name in (".", ".."): + raise InvalidRequestError(f"Unsafe resource filename: {name}") diff --git a/paddleocr/_api_client/async_client.py b/paddleocr/_api_client/async_client.py new file mode 100644 index 0000000..74812c7 --- /dev/null +++ b/paddleocr/_api_client/async_client.py @@ -0,0 +1,253 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import asyncio +import os +from typing import Optional, Union + +from ._core import ( + default_payload, + job_id_for_task, + resolve_document_model, + resolve_document_options, + resolve_ocr_model, + validate_input_source, +) +from ._async_http import AsyncHTTPClient +from ._http import DEFAULT_BASE_URL +from ._async_poller import AsyncPoller +from ._poller import parse_doc_parsing_result, parse_ocr_result +from ._resources import ( + save_document_parsing_result_resources, + save_ocr_result_resources, + save_resource, +) +from .errors import AuthError +from .models import ( + DocParsingOptions, + Model, + OCROptions, +) +from .results import BatchStatus, DocParsingResult, Job, JobStatus, OCRResult + + +class AsyncPaddleOCRClient: + """Async client for PaddleOCR API. + + Supports asyncio.gather for concurrent job submission and polling. + """ + + def __init__( + self, + token: Optional[str] = None, + base_url: Optional[str] = None, + request_timeout: float = 300.0, + poll_timeout: float = 600.0, + timeout: Optional[float] = None, + client_platform: Optional[str] = None, + ): + self._token = token or os.environ.get("PADDLEOCR_ACCESS_TOKEN", "") + if not self._token: + raise AuthError( + "Token is required. Set PADDLEOCR_ACCESS_TOKEN or pass token=." + ) + resolved_base_url = ( + base_url or os.environ.get("PADDLEOCR_BASE_URL") or DEFAULT_BASE_URL + ) + if timeout is not None: + request_timeout = timeout + poll_timeout = timeout + self._http = AsyncHTTPClient( + self._token, + resolved_base_url, + request_timeout, + client_platform=client_platform, + ) + self._poller = AsyncPoller(self._http, max_wait_time=poll_timeout) + + async def __aenter__(self): + await self._http.__aenter__() + return self + + async def __aexit__(self, *args): + await self._http.close() + + async def close(self): + await self._http.close() + + async def ocr( + self, + file_url: Optional[str] = None, + file_path: Optional[str] = None, + options: Optional[OCROptions] = None, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + model: Union[Model, str] = Model.PP_OCRV6, + ) -> OCRResult: + model = resolve_ocr_model(model) + job_id = await self._submit( + model, + file_url, + file_path, + options, + page_ranges, + batch_id, + ) + jsonl_data, _ = await self._poller.poll_until_done(job_id) + return parse_ocr_result(job_id, jsonl_data) + + async def parse_document( + self, + model: Union[Model, str] = Model.PADDLE_OCR_VL_16, + file_url: Optional[str] = None, + file_path: Optional[str] = None, + options: Optional[DocParsingOptions] = None, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + ) -> DocParsingResult: + model = resolve_document_model(model) + options = resolve_document_options(model, options) + job_id = await self._submit( + model, file_url, file_path, options, page_ranges, batch_id + ) + jsonl_data, _ = await self._poller.poll_until_done(job_id) + return parse_doc_parsing_result(job_id, jsonl_data) + + async def submit_ocr( + self, + file_url: Optional[str] = None, + file_path: Optional[str] = None, + options: Optional[OCROptions] = None, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + model: Union[Model, str] = Model.PP_OCRV6, + ) -> Job: + model = resolve_ocr_model(model) + job_id = await self._submit( + model, + file_url, + file_path, + options, + page_ranges, + batch_id, + ) + return Job(job_id=job_id, model=model.value, task="ocr") + + async def submit_document_parsing( + self, + model: Union[Model, str] = Model.PADDLE_OCR_VL_16, + file_url: Optional[str] = None, + file_path: Optional[str] = None, + options: Optional[DocParsingOptions] = None, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + ) -> Job: + model = resolve_document_model(model) + options = resolve_document_options(model, options) + job_id = await self._submit( + model, file_url, file_path, options, page_ranges, batch_id + ) + return Job(job_id=job_id, model=model.value, task="document_parsing") + + async def wait_ocr_result(self, job: Union[Job, str]) -> OCRResult: + job_id = job_id_for_task(job, "ocr") + jsonl_data, _ = await self._poller.poll_until_done(job_id) + return parse_ocr_result(job_id, jsonl_data) + + async def wait_document_parsing_result( + self, job: Union[Job, str] + ) -> DocParsingResult: + job_id = job_id_for_task(job, "document_parsing") + jsonl_data, _ = await self._poller.poll_until_done(job_id) + return parse_doc_parsing_result(job_id, jsonl_data) + + async def get_status(self, job_id: str) -> JobStatus: + return await self._poller.get_status(job_id) + + async def get_batch_status(self, batch_id: str) -> BatchStatus: + return await self._poller.get_batch_status(batch_id) + + async def save_resource( + self, + resource_url: str, + destination: str, + *, + overwrite: bool = False, + filename: Optional[str] = None, + ) -> str: + return await asyncio.to_thread( + save_resource, + resource_url, + destination, + overwrite=overwrite, + filename=filename, + timeout=self._http.timeout, + ) + + async def save_ocr_result_resources( + self, + result: OCRResult, + destination: str, + *, + overwrite: bool = False, + ) -> list: + return await asyncio.to_thread( + save_ocr_result_resources, + result, + destination, + overwrite=overwrite, + timeout=self._http.timeout, + ) + + async def save_document_parsing_result_resources( + self, + result: DocParsingResult, + destination: str, + *, + overwrite: bool = False, + ) -> list: + return await asyncio.to_thread( + save_document_parsing_result_resources, + result, + destination, + overwrite=overwrite, + timeout=self._http.timeout, + ) + + async def _submit( + self, + model: Model, + file_url: Optional[str], + file_path: Optional[str], + options, + page_ranges: Optional[str], + batch_id: Optional[str], + ) -> str: + validate_input_source(file_url, file_path) + payload = options.to_payload() if options else default_payload(model) + if file_url: + return await self._http.submit_url( + model.value, + file_url, + payload, + page_ranges=page_ranges, + batch_id=batch_id, + ) + return await self._http.submit_file( + model.value, + file_path, + payload, + page_ranges=page_ranges, + batch_id=batch_id, + ) diff --git a/paddleocr/_api_client/cli.py b/paddleocr/_api_client/cli.py new file mode 100644 index 0000000..5e76a86 --- /dev/null +++ b/paddleocr/_api_client/cli.py @@ -0,0 +1,357 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import sys + +from .._utils.cli import str2bool +from .client import PaddleOCRClient +from .models import ( + Model, + OCROptions, + PaddleOCRVLOptions, + PPStructureV3Options, + is_ocr_model, + is_vl_model, +) + + +def register_api_command(subparsers): + """Register the 'api' subcommand into paddleocr CLI.""" + subparser = subparsers.add_parser( + "api", + help="Call PaddleOCR cloud API for OCR or document parsing", + ) + subparser.add_argument( + "--model_type", + type=str, + required=True, + choices=["ocr", "doc_parsing"], + help="Task type: ocr or doc_parsing", + ) + subparser.add_argument( + "--model", + type=str, + default=None, + choices=[m.value for m in Model], + help="Model name.", + ) + subparser.add_argument( + "--file_url", + type=str, + default=None, + help="URL of the file to process", + ) + subparser.add_argument( + "--file_path", + type=str, + default=None, + help="Local file path to process", + ) + subparser.add_argument( + "--base_url", + type=str, + default=None, + help="Base URL of the PaddleOCR API service", + ) + subparser.add_argument( + "--token", + type=str, + default=None, + help="Access token (or set PADDLEOCR_ACCESS_TOKEN env variable)", + ) + subparser.add_argument( + "--client_platform", + type=str, + default=None, + help="Value for the Client-Platform request header", + ) + subparser.add_argument( + "--output", + type=str, + default=None, + help="Output JSON file path (prints to stdout if omitted)", + ) + subparser.add_argument( + "--request_timeout", + type=float, + default=300.0, + help="Timeout in seconds for one HTTP request", + ) + subparser.add_argument( + "--poll_timeout", + type=float, + default=600.0, + help="Total timeout in seconds while waiting for the remote job", + ) + subparser.add_argument( + "--save_resources", + type=str, + default=None, + help="Directory for saving resources referenced by the result", + ) + subparser.add_argument( + "--overwrite_resources", + action="store_true", + help="Overwrite existing files when saving resources", + ) + subparser.add_argument( + "--page_ranges", + type=str, + default=None, + help='Page ranges to parse, for example "2,4-6"', + ) + subparser.add_argument( + "--batch_id", + type=str, + default=None, + help="Optional batch identifier for querying related jobs", + ) + # --- Preprocessing --- + subparser.add_argument( + "--use_doc_orientation_classify", + type=str2bool, + default=None, + help="Enable document orientation classification (True/False)", + ) + subparser.add_argument( + "--use_doc_unwarping", + type=str2bool, + default=None, + help="Enable document unwarping (True/False)", + ) + # --- Text detection --- + subparser.add_argument( + "--use_textline_orientation", + type=str2bool, + default=None, + help="Enable textline orientation detection for OCR (True/False)", + ) + subparser.add_argument( + "--text_det_limit_side_len", + type=int, + default=None, + help="Image side length limit for text detection", + ) + subparser.add_argument( + "--text_det_limit_type", + type=str, + default=None, + choices=["min", "max"], + help="Side length limit type: min or max", + ) + # --- Text recognition --- + subparser.add_argument( + "--text_rec_score_thresh", + type=float, + default=None, + help="Score threshold for text recognition results", + ) + # --- Layout and feature toggles (doc_parsing only) --- + subparser.add_argument( + "--use_layout_detection", + type=str2bool, + default=None, + help="Enable layout detection for document parsing (True/False)", + ) + subparser.add_argument( + "--use_seal_recognition", + type=str2bool, + default=None, + help="Enable seal recognition for document parsing (True/False)", + ) + subparser.add_argument( + "--use_table_recognition", + type=str2bool, + default=None, + help="Enable table recognition for PP-StructureV3 (True/False)", + ) + subparser.add_argument( + "--use_formula_recognition", + type=str2bool, + default=None, + help="Enable formula recognition for PP-StructureV3 (True/False)", + ) + subparser.add_argument( + "--use_chart_recognition", + type=str2bool, + default=None, + help="Enable chart recognition for document parsing (True/False)", + ) + # --- Output --- + subparser.add_argument( + "--visualize", + type=str2bool, + default=None, + help="Enable result visualization images (True/False)", + ) + subparser.add_argument( + "--prettify_markdown", + type=str2bool, + default=None, + help="Enable markdown prettification for document parsing (True/False)", + ) + subparser.set_defaults(executor=_execute_api) + + +def _execute_api(args): + kwargs = {} + if args.token: + kwargs["token"] = args.token + if args.base_url: + kwargs["base_url"] = args.base_url + kwargs["request_timeout"] = args.request_timeout + kwargs["poll_timeout"] = args.poll_timeout + if args.client_platform: + kwargs["client_platform"] = args.client_platform + + try: + client = PaddleOCRClient(**kwargs) + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + + try: + model = _resolve_model(args.model) if args.model else None + + if args.model_type == "ocr": + if model is not None and not is_ocr_model(model): + print( + f"Error: OCR task does not support {model.value}.", + file=sys.stderr, + ) + sys.exit(2) + options = OCROptions( + use_doc_orientation_classify=args.use_doc_orientation_classify, + use_doc_unwarping=args.use_doc_unwarping, + use_textline_orientation=args.use_textline_orientation, + text_det_limit_side_len=args.text_det_limit_side_len, + text_det_limit_type=args.text_det_limit_type, + text_rec_score_thresh=args.text_rec_score_thresh, + visualize=args.visualize, + ) + result = client.ocr( + file_url=args.file_url, + file_path=args.file_path, + options=options, + page_ranges=args.page_ranges, + batch_id=args.batch_id, + model=model or Model.PP_OCRV6, + ) + output = _ocr_result_to_dict(result) + save_resources = client.save_ocr_result_resources + else: + if model is None: + model = Model.PADDLE_OCR_VL_16 + if is_vl_model(model): + options = PaddleOCRVLOptions( + use_doc_orientation_classify=args.use_doc_orientation_classify, + use_doc_unwarping=args.use_doc_unwarping, + use_chart_recognition=args.use_chart_recognition, + use_seal_recognition=args.use_seal_recognition, + use_layout_detection=args.use_layout_detection, + prettify_markdown=args.prettify_markdown, + visualize=args.visualize, + ) + else: + options = PPStructureV3Options( + use_doc_orientation_classify=args.use_doc_orientation_classify, + use_doc_unwarping=args.use_doc_unwarping, + use_textline_orientation=args.use_textline_orientation, + use_chart_recognition=args.use_chart_recognition, + use_seal_recognition=args.use_seal_recognition, + use_table_recognition=args.use_table_recognition, + use_formula_recognition=args.use_formula_recognition, + use_layout_detection=args.use_layout_detection, + text_det_limit_side_len=args.text_det_limit_side_len, + text_det_limit_type=args.text_det_limit_type, + text_rec_score_thresh=args.text_rec_score_thresh, + prettify_markdown=args.prettify_markdown, + visualize=args.visualize, + ) + result = client.parse_document( + model=model, + file_url=args.file_url, + file_path=args.file_path, + options=options, + page_ranges=args.page_ranges, + batch_id=args.batch_id, + ) + output = _doc_parsing_result_to_dict(result) + save_resources = client.save_document_parsing_result_resources + + json_str = json.dumps(output, ensure_ascii=False, indent=2) + + if args.save_resources: + saved_paths = save_resources( + result, + args.save_resources, + overwrite=args.overwrite_resources, + ) + print( + f"Resources saved to: {args.save_resources} ({len(saved_paths)} files)", + file=sys.stderr, + ) + + if args.output: + with open(args.output, "w", encoding="utf-8") as f: + f.write(json_str) + print(f"Result saved to: {args.output}") + else: + print(json_str) + + except Exception as e: + print(f"Error: {e}", file=sys.stderr) + sys.exit(1) + finally: + client.close() + + +def _resolve_model(model_str: str) -> Model: + try: + return Model(model_str) + except ValueError: + print( + f"Error: Unknown model '{model_str}'. " + f"Choose from: {', '.join(m.value for m in Model)}", + file=sys.stderr, + ) + sys.exit(1) + + +def _ocr_result_to_dict(result) -> dict: + return { + "jobId": result.job_id, + "pages": [ + { + "prunedResult": page.pruned_result, + "ocrImageUrl": page.ocr_image_url, + } + for page in result.pages + ], + } + + +def _doc_parsing_result_to_dict(result) -> dict: + return { + "jobId": result.job_id, + "pages": [ + { + "markdownText": page.markdown_text, + "markdownImages": page.markdown_images, + "outputImages": page.output_images, + } + for page in result.pages + ], + } diff --git a/paddleocr/_api_client/client.py b/paddleocr/_api_client/client.py new file mode 100644 index 0000000..b3ed57f --- /dev/null +++ b/paddleocr/_api_client/client.py @@ -0,0 +1,240 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from typing import Optional, Union + +from ._core import ( + default_payload, + job_id_for_task, + resolve_document_model, + resolve_document_options, + resolve_ocr_model, + validate_input_source, +) +from ._http import DEFAULT_BASE_URL, HTTPClient +from ._poller import Poller, parse_doc_parsing_result, parse_ocr_result +from ._resources import ( + save_document_parsing_result_resources, + save_ocr_result_resources, + save_resource, +) +from .errors import AuthError +from .models import ( + DocParsingOptions, + Model, + OCROptions, +) +from .results import BatchStatus, DocParsingResult, Job, JobStatus, OCRResult + + +class PaddleOCRClient: + """Synchronous blocking client for PaddleOCR official API. + + Wraps the async job API internally: submit → poll → fetch result. + """ + + def __init__( + self, + token: Optional[str] = None, + base_url: Optional[str] = None, + request_timeout: float = 300.0, + poll_timeout: float = 600.0, + client_platform: Optional[str] = None, + ): + self._token = token or os.environ.get("PADDLEOCR_ACCESS_TOKEN", "") + if not self._token: + raise AuthError( + "Token is required. Set PADDLEOCR_ACCESS_TOKEN or pass token=." + ) + resolved_base_url = ( + base_url or os.environ.get("PADDLEOCR_BASE_URL") or DEFAULT_BASE_URL + ) + self._http = HTTPClient( + self._token, + resolved_base_url, + request_timeout, + client_platform=client_platform, + ) + self._poller = Poller(self._http, max_wait_time=poll_timeout) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def close(self): + self._http.close() + + def ocr( + self, + file_url: Optional[str] = None, + file_path: Optional[str] = None, + options: Optional[OCROptions] = None, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + model: Union[Model, str] = Model.PP_OCRV6, + ) -> OCRResult: + model = resolve_ocr_model(model) + job_id = self._submit( + model, + file_url, + file_path, + options, + page_ranges, + batch_id, + ) + jsonl_data, _ = self._poller.poll_until_done(job_id) + return parse_ocr_result(job_id, jsonl_data) + + def parse_document( + self, + model: Union[Model, str] = Model.PADDLE_OCR_VL_16, + file_url: Optional[str] = None, + file_path: Optional[str] = None, + options: Optional[DocParsingOptions] = None, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + ) -> DocParsingResult: + model = resolve_document_model(model) + options = resolve_document_options(model, options) + job_id = self._submit( + model, file_url, file_path, options, page_ranges, batch_id + ) + jsonl_data, _ = self._poller.poll_until_done(job_id) + return parse_doc_parsing_result(job_id, jsonl_data) + + def submit_ocr( + self, + file_url: Optional[str] = None, + file_path: Optional[str] = None, + options: Optional[OCROptions] = None, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + model: Union[Model, str] = Model.PP_OCRV6, + ) -> Job: + model = resolve_ocr_model(model) + job_id = self._submit( + model, + file_url, + file_path, + options, + page_ranges, + batch_id, + ) + return Job(job_id=job_id, model=model.value, task="ocr") + + def submit_document_parsing( + self, + model: Union[Model, str] = Model.PADDLE_OCR_VL_16, + file_url: Optional[str] = None, + file_path: Optional[str] = None, + options: Optional[DocParsingOptions] = None, + page_ranges: Optional[str] = None, + batch_id: Optional[str] = None, + ) -> Job: + model = resolve_document_model(model) + options = resolve_document_options(model, options) + job_id = self._submit( + model, file_url, file_path, options, page_ranges, batch_id + ) + return Job(job_id=job_id, model=model.value, task="document_parsing") + + def wait_ocr_result(self, job: Union[Job, str]) -> OCRResult: + job_id = job_id_for_task(job, "ocr") + jsonl_data, _ = self._poller.poll_until_done(job_id) + return parse_ocr_result(job_id, jsonl_data) + + def wait_document_parsing_result(self, job: Union[Job, str]) -> DocParsingResult: + job_id = job_id_for_task(job, "document_parsing") + jsonl_data, _ = self._poller.poll_until_done(job_id) + return parse_doc_parsing_result(job_id, jsonl_data) + + def get_status(self, job_id: str) -> JobStatus: + return self._poller.get_status(job_id) + + def get_batch_status(self, batch_id: str) -> BatchStatus: + return self._poller.get_batch_status(batch_id) + + def save_resource( + self, + resource_url: str, + destination: str, + *, + overwrite: bool = False, + filename: Optional[str] = None, + ) -> str: + return save_resource( + resource_url, + destination, + overwrite=overwrite, + filename=filename, + timeout=self._http.timeout, + ) + + def save_ocr_result_resources( + self, + result: OCRResult, + destination: str, + *, + overwrite: bool = False, + ) -> list: + return save_ocr_result_resources( + result, + destination, + overwrite=overwrite, + timeout=self._http.timeout, + ) + + def save_document_parsing_result_resources( + self, + result: DocParsingResult, + destination: str, + *, + overwrite: bool = False, + ) -> list: + return save_document_parsing_result_resources( + result, + destination, + overwrite=overwrite, + timeout=self._http.timeout, + ) + + def _submit( + self, + model: Model, + file_url: Optional[str], + file_path: Optional[str], + options, + page_ranges: Optional[str], + batch_id: Optional[str], + ) -> str: + validate_input_source(file_url, file_path) + payload = options.to_payload() if options else default_payload(model) + if file_url: + return self._http.submit_url( + model.value, + file_url, + payload, + page_ranges=page_ranges, + batch_id=batch_id, + ) + return self._http.submit_file( + model.value, + file_path, + payload, + page_ranges=page_ranges, + batch_id=batch_id, + ) diff --git a/paddleocr/_api_client/errors.py b/paddleocr/_api_client/errors.py new file mode 100644 index 0000000..c679304 --- /dev/null +++ b/paddleocr/_api_client/errors.py @@ -0,0 +1,85 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class PaddleOCRAPIError(Exception): + """Base exception for PaddleOCR API SDK.""" + + def __init__(self, message: str): + self.message = message + super().__init__(message) + + +class AuthError(PaddleOCRAPIError): + """Token missing, invalid, or expired (HTTP 401/403).""" + + +class InvalidRequestError(PaddleOCRAPIError): + """Invalid parameters (HTTP 400).""" + + +class APIError(PaddleOCRAPIError): + """Non-2xx response from the API server.""" + + def __init__(self, status_code: int, message: str): + self.status_code = status_code + super().__init__(f"HTTP {status_code}: {message}") + + +class RateLimitError(APIError): + """Daily quota exceeded (HTTP 429).""" + + def __init__(self, message: str): + super().__init__(429, message) + + +class ServiceUnavailableError(APIError): + """Server overloaded or gateway timeout (HTTP 503/504).""" + + def __init__(self, status_code: int, message: str): + super().__init__(status_code, message) + + +class JobFailedError(PaddleOCRAPIError): + """Job execution failed on the server side.""" + + def __init__(self, job_id: str, error_msg: str): + self.job_id = job_id + self.error_msg = error_msg + super().__init__(f"Job {job_id} failed: {error_msg}") + + +class RequestTimeoutError(PaddleOCRAPIError): + """A single HTTP request exceeded the configured timeout.""" + + +class PollTimeoutError(PaddleOCRAPIError): + """Polling timed out waiting for job completion.""" + + def __init__(self, job_id: str, elapsed: float): + self.job_id = job_id + self.elapsed = elapsed + super().__init__(f"Timed out after {elapsed:.1f}s waiting for job {job_id}") + + +class ResponseFormatError(PaddleOCRAPIError): + """A successful API response did not match the documented schema.""" + + +class ResultParseError(PaddleOCRAPIError): + """A result JSONL payload could not be parsed as the expected result type.""" + + +class NetworkError(PaddleOCRAPIError): + """Network connection failure.""" diff --git a/paddleocr/_api_client/models.py b/paddleocr/_api_client/models.py new file mode 100644 index 0000000..d3a2f3f --- /dev/null +++ b/paddleocr/_api_client/models.py @@ -0,0 +1,202 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from dataclasses import dataclass, fields +from enum import Enum +from typing import Optional, Union + +from ._naming import snake_to_camel +from .errors import InvalidRequestError + + +class Model(str, Enum): + PP_OCRV5 = "PP-OCRv5" + PP_OCRV5_LATIN = "PP-OCRv5-latin" + PP_OCRV6 = "PP-OCRv6" + PP_STRUCTURE_V3 = "PP-StructureV3" + PADDLE_OCR_VL = "PaddleOCR-VL" + PADDLE_OCR_VL_15 = "PaddleOCR-VL-1.5" + PADDLE_OCR_VL_16 = "PaddleOCR-VL-1.6" + + +_OCR_MODELS = frozenset({Model.PP_OCRV5, Model.PP_OCRV5_LATIN, Model.PP_OCRV6}) +_DOCUMENT_PARSING_MODELS = frozenset( + { + Model.PP_STRUCTURE_V3, + Model.PADDLE_OCR_VL, + Model.PADDLE_OCR_VL_15, + Model.PADDLE_OCR_VL_16, + } +) +_VL_MODELS = frozenset( + { + Model.PADDLE_OCR_VL, + Model.PADDLE_OCR_VL_15, + Model.PADDLE_OCR_VL_16, + } +) + + +def _coerce_model(model: Union[Model, str]) -> Optional[Model]: + if isinstance(model, Model): + return model + try: + return Model(model) + except ValueError: + return None + + +def is_ocr_model(model: Union[Model, str]) -> bool: + return _coerce_model(model) in _OCR_MODELS + + +def is_document_parsing_model(model: Union[Model, str]) -> bool: + return _coerce_model(model) in _DOCUMENT_PARSING_MODELS + + +def is_vl_model(model: Union[Model, str]) -> bool: + return _coerce_model(model) in _VL_MODELS + + +@dataclass +class OCROptions: + use_doc_orientation_classify: Optional[bool] = None + use_doc_unwarping: Optional[bool] = None + use_textline_orientation: Optional[bool] = None + text_det_limit_side_len: Optional[int] = None + text_det_limit_type: Optional[str] = None + text_det_thresh: Optional[float] = None + text_det_box_thresh: Optional[float] = None + text_det_unclip_ratio: Optional[float] = None + text_rec_score_thresh: Optional[float] = None + visualize: Optional[bool] = None + extra_options: Optional[dict] = None + + def to_payload(self) -> dict: + return _build_payload(self) + + +@dataclass +class PPStructureV3Options: + use_doc_orientation_classify: Optional[bool] = None + use_doc_unwarping: Optional[bool] = None + use_textline_orientation: Optional[bool] = None + use_seal_recognition: Optional[bool] = None + use_table_recognition: Optional[bool] = None + use_formula_recognition: Optional[bool] = None + use_chart_recognition: Optional[bool] = None + use_region_detection: Optional[bool] = None + layout_threshold: Optional[Union[float, dict]] = None + layout_nms: Optional[bool] = None + layout_unclip_ratio: Optional[Union[float, list, dict]] = None + layout_merge_bboxes_mode: Optional[Union[str, dict]] = None + format_block_content: Optional[bool] = None + text_det_limit_side_len: Optional[int] = None + text_det_limit_type: Optional[str] = None + text_det_thresh: Optional[float] = None + text_det_box_thresh: Optional[float] = None + text_det_unclip_ratio: Optional[float] = None + text_rec_score_thresh: Optional[float] = None + use_wired_table_cells_trans_to_html: Optional[bool] = None + use_wireless_table_cells_trans_to_html: Optional[bool] = None + use_table_orientation_classify: Optional[bool] = None + use_ocr_results_with_table_cells: Optional[bool] = None + use_e2e_wired_table_rec_model: Optional[bool] = None + use_e2e_wireless_table_rec_model: Optional[bool] = None + markdown_ignore_labels: Optional[list] = None + prettify_markdown: Optional[bool] = None + show_formula_number: Optional[bool] = None + return_markdown_images: Optional[bool] = None + output_formats: Optional[list] = None + visualize: Optional[bool] = None + extra_options: Optional[dict] = None + + def to_payload(self) -> dict: + return _build_payload(self) + + +@dataclass +class PaddleOCRVLOptions: + use_doc_orientation_classify: Optional[bool] = None + use_doc_unwarping: Optional[bool] = None + use_layout_detection: Optional[bool] = None + use_chart_recognition: Optional[bool] = None + use_seal_recognition: Optional[bool] = None + use_ocr_for_image_block: Optional[bool] = None + layout_threshold: Optional[Union[float, dict]] = None + layout_nms: Optional[bool] = None + layout_unclip_ratio: Optional[Union[float, list, dict]] = None + layout_merge_bboxes_mode: Optional[Union[str, dict]] = None + layout_shape_mode: Optional[str] = None + prompt_label: Optional[str] = None + format_block_content: Optional[bool] = None + repetition_penalty: Optional[float] = None + temperature: Optional[float] = None + top_p: Optional[float] = None + min_pixels: Optional[int] = None + max_pixels: Optional[int] = None + max_new_tokens: Optional[int] = None + vlm_extra_args: Optional[dict] = None + merge_layout_blocks: Optional[bool] = None + markdown_ignore_labels: Optional[list] = None + prettify_markdown: Optional[bool] = None + show_formula_number: Optional[bool] = None + restructure_pages: Optional[bool] = None + merge_tables: Optional[bool] = None + relevel_titles: Optional[bool] = None + return_markdown_images: Optional[bool] = None + output_formats: Optional[list] = None + visualize: Optional[bool] = None + extra_options: Optional[dict] = None + + def to_payload(self) -> dict: + _validate_vl_options(self) + return _build_payload(self) + + +DocParsingOptions = Union[PPStructureV3Options, PaddleOCRVLOptions] + + +def _build_payload(options) -> dict: + payload = {} + for field in fields(options): + value = getattr(options, field.name) + if value is None: + continue + if field.name == "extra_options": + payload.update(value) + else: + payload[snake_to_camel(field.name)] = value + return payload + + +def _validate_vl_options(options: PaddleOCRVLOptions) -> None: + if options.top_p is not None and not (0 < options.top_p <= 1): + raise InvalidRequestError( + "top_p must be greater than 0 and less than or equal to 1." + ) + if options.temperature is not None and options.temperature < 0: + raise InvalidRequestError("temperature must be greater than or equal to 0.") + if options.repetition_penalty is not None and options.repetition_penalty <= 0: + raise InvalidRequestError("repetition_penalty must be greater than 0.") + if options.min_pixels is not None and options.min_pixels <= 0: + raise InvalidRequestError("min_pixels must be greater than 0.") + if options.max_pixels is not None and options.max_pixels <= 0: + raise InvalidRequestError("max_pixels must be greater than 0.") + if ( + options.min_pixels is not None + and options.max_pixels is not None + and options.min_pixels > options.max_pixels + ): + raise InvalidRequestError("min_pixels cannot be greater than max_pixels.") diff --git a/paddleocr/_api_client/results.py b/paddleocr/_api_client/results.py new file mode 100644 index 0000000..dc69b23 --- /dev/null +++ b/paddleocr/_api_client/results.py @@ -0,0 +1,81 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from dataclasses import dataclass, field +from typing import Any, Dict, List, Literal, Optional + + +@dataclass +class OCRPage: + pruned_result: Any + ocr_image_url: Optional[str] = None + doc_preprocessing_image_url: Optional[str] = None + input_image_url: Optional[str] = None + raw: Dict[str, Any] = field(default_factory=dict) + + +@dataclass +class DocParsingPage: + markdown_text: str + markdown_images: Dict[str, str] = field(default_factory=dict) + output_images: Dict[str, str] = field(default_factory=dict) + pruned_result: Any = None + input_image_url: Optional[str] = None + exports: Dict[str, Any] = field(default_factory=dict) + markdown: Dict[str, Any] = field(default_factory=dict) + raw: Dict[str, Any] = field(default_factory=dict) + + +@dataclass +class OCRResult: + job_id: str + pages: List[OCRPage] = field(default_factory=list) + data_info: Dict[str, Any] = field(default_factory=dict) + + +@dataclass +class DocParsingResult: + job_id: str + pages: List[DocParsingPage] = field(default_factory=list) + data_info: Dict[str, Any] = field(default_factory=dict) + + +@dataclass +class Progress: + total_pages: int = 0 + extracted_pages: int = 0 + start_time: Optional[str] = None + end_time: Optional[str] = None + + +@dataclass +class Job: + job_id: str + model: str + task: Literal["ocr", "document_parsing"] + + +@dataclass +class JobStatus: + job_id: str + state: str + progress: Optional[Progress] = None + result: Any = None + error_msg: Optional[str] = None + + +@dataclass +class BatchStatus: + batch_id: str + jobs: List[JobStatus] = field(default_factory=list) diff --git a/paddleocr/_cli.py b/paddleocr/_cli.py new file mode 100644 index 0000000..157df1b --- /dev/null +++ b/paddleocr/_cli.py @@ -0,0 +1,309 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import logging +import subprocess +import sys +import time +import warnings +from threading import Thread + +import requests + +from ._models import ( + ChartParsing, + DocImgOrientationClassification, + DocVLM, + FormulaRecognition, + LayoutDetection, + SealTextDetection, + TableCellsDetection, + TableClassification, + TableStructureRecognition, + TextDetection, + TextImageUnwarping, + TextLineOrientationClassification, + TextRecognition, +) +from ._pipelines import ( + DocPreprocessor, + DocUnderstanding, + FormulaRecognitionPipeline, + PaddleOCR, + PaddleOCRVL, + PPChatOCRv4Doc, + PPDocTranslation, + PPStructureV3, + SealRecognition, + TableRecognitionPipelineV2, +) +from ._version import version +from ._utils.deprecation import CLIDeprecationWarning +from ._utils.logging import logger + + +def _register_pipelines(subparsers): + for cls in [ + DocPreprocessor, + DocUnderstanding, + FormulaRecognitionPipeline, + PaddleOCR, + PaddleOCRVL, + PPChatOCRv4Doc, + PPDocTranslation, + PPStructureV3, + SealRecognition, + TableRecognitionPipelineV2, + ]: + subcommand_executor = cls.get_cli_subcommand_executor() + subparser = subcommand_executor.add_subparser(subparsers) + subparser.set_defaults(executor=subcommand_executor.execute_with_args) + + +def _register_models(subparsers): + for cls in [ + ChartParsing, + DocImgOrientationClassification, + DocVLM, + FormulaRecognition, + LayoutDetection, + SealTextDetection, + TableCellsDetection, + TableClassification, + TableStructureRecognition, + TextDetection, + TextImageUnwarping, + TextLineOrientationClassification, + TextRecognition, + ]: + subcommand_executor = cls.get_cli_subcommand_executor() + subparser = subcommand_executor.add_subparser(subparsers) + subparser.set_defaults(executor=subcommand_executor.execute_with_args) + + +def _register_install_hpi_deps_command(subparsers): + def _install_hpi_deps(args): + hpip = f"hpi-{args.variant}" + try: + subprocess.check_call(["paddlex", "--install", hpip]) + subprocess.check_call(["paddlex", "--install", "paddle2onnx"]) + except subprocess.CalledProcessError: + sys.exit("Failed to install dependencies") + + subparser = subparsers.add_parser("install_hpi_deps") + subparser.add_argument("variant", type=str, choices=["cpu", "gpu", "npu"]) + subparser.set_defaults(executor=_install_hpi_deps) + + +def _register_install_genai_server_deps_command(subparsers): + def _install_genai_server_deps(args): + try: + subprocess.check_call( + ["paddlex", "--install", f"genai-{args.variant}-server"] + ) + except subprocess.CalledProcessError: + sys.exit("Failed to install dependencies") + + subparser = subparsers.add_parser("install_genai_server_deps") + subparser.add_argument( + "variant", type=str, choices=["vllm", "sglang", "fastdeploy"] + ) + subparser.set_defaults(executor=_install_genai_server_deps) + + +def _register_genai_server_command(subparsers): + # TODO: Register the subparser whether the plugin is installed or not + try: + from paddlex.inference.genai.server import get_arg_parser, run_genai_server + except RuntimeError: + return + + def _show_prompt_when_server_is_running(host, port, backend): + if host == "0.0.0.0": + host = "localhost" + while True: + try: + resp = requests.get(f"http://{host}:{port}/health", timeout=1) + if resp.status_code == 200: + break + except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): + pass + time.sleep(1) + prompt = f"""The PaddleOCR GenAI server has been started. You can either: + 1. Set the server URL in the module or pipeline configuration and call the PaddleOCR CLI or Python API. For example: + paddleocr doc_parser --input demo.png --vl_rec_backend {backend}-server --vl_rec_server_url http://{host}:{port}/v1 + 2. Make HTTP requests directly, or using the OpenAI client library.""" + logger.info(prompt) + + def _run_genai_server(args): + Thread( + target=_show_prompt_when_server_is_running, + args=(args.host, args.port, args.backend), + daemon=True, + ).start() + try: + run_genai_server(args) + except subprocess.CalledProcessError: + sys.exit("Failed to run the server") + + paddlex_parser = get_arg_parser() + subparser = subparsers.add_parser( + "genai_server", parents=[paddlex_parser], conflict_handler="resolve" + ) + subparser.set_defaults(executor=_run_genai_server) + + +def _register_doc2md_command(subparsers): + """Register the doc2md subcommand.""" + + def _execute_doc2md(args): + if args.formats: + from ._doc2md import supported_formats + + fmts = supported_formats() + print("Supported formats: " + ", ".join(f".{f}" for f in fmts)) + return + + if not args.input: + logger.error("--input is required when --formats is not set") + sys.exit(2) + + from ._doc2md import convert + from pathlib import Path + + output = args.output + quiet = args.quiet + + # Build converter kwargs from CLI args + converter_kwargs = {} + if args.no_drawings: + converter_kwargs["extract_drawings"] = False + if args.no_headers_footers: + converter_kwargs["extract_headers_footers"] = False + if args.sheet_name is not None: + converter_kwargs["sheet_name"] = args.sheet_name + if args.max_rows is not None: + converter_kwargs["max_rows"] = args.max_rows + + t1 = time.time() + try: + result = convert(args.input, output=output, **converter_kwargs) + except Exception as e: + logger.error(f"Conversion failed: {e}") + sys.exit(1) + + elapsed = (time.time() - t1) * 1000 + if not quiet: + logger.info(f"Conversion done in {elapsed:.0f} ms") + + if output: + if not quiet: + logger.info(f"Saved to: {output}") + if result.images: + logger.info(f"Images saved to: {Path(output).parent / 'images'}/") + else: + print(result.markdown) + + subparser = subparsers.add_parser( + "doc2md", + help="Convert office documents (docx/xlsx/pptx) to Markdown", + ) + subparser.add_argument( + "-i", + "--input", + type=str, + default=None, + help="Input file path (.docx/.xlsx/.pptx)", + ) + subparser.add_argument( + "-o", + "--output", + type=str, + default=None, + help="Output Markdown file path (prints to stdout if omitted)", + ) + subparser.add_argument( + "-q", + "--quiet", + action="store_true", + help="Suppress informational output", + ) + subparser.add_argument( + "--formats", + action="store_true", + help="List supported formats and exit", + ) + # docx options + subparser.add_argument( + "--no-drawings", + action="store_true", + help="[docx/xlsx] Skip text box / drawing layer content extraction", + ) + subparser.add_argument( + "--no-headers-footers", + action="store_true", + help="[docx] Skip header and footer content extraction", + ) + # xlsx options + subparser.add_argument( + "--sheet-name", + type=str, + default=None, + help="[xlsx] Convert only the specified sheet (by name)", + ) + subparser.add_argument( + "--max-rows", + type=int, + default=None, + help="[xlsx] Maximum number of rows to convert per sheet", + ) + subparser.set_defaults(executor=_execute_doc2md) + + +def _register_api_command(subparsers): + from ._api_client.cli import register_api_command + + register_api_command(subparsers) + + +def _get_parser(): + parser = argparse.ArgumentParser(prog="paddleocr") + parser.add_argument( + "-v", "--version", action="version", version=f"%(prog)s {version}" + ) + subparsers = parser.add_subparsers(dest="subcommand", metavar="COMMAND") + _register_pipelines(subparsers) + _register_models(subparsers) + _register_install_hpi_deps_command(subparsers) + _register_install_genai_server_deps_command(subparsers) + _register_genai_server_command(subparsers) + _register_doc2md_command(subparsers) + _register_api_command(subparsers) + return parser + + +def _execute(args): + args.executor(args) + + +def main(): + logger.setLevel(logging.INFO) + warnings.filterwarnings("default", category=CLIDeprecationWarning) + parser = _get_parser() + args = parser.parse_args() + if args.subcommand is None: + parser.print_usage(sys.stderr) + sys.exit(2) + _execute(args) diff --git a/paddleocr/_common_args.py b/paddleocr/_common_args.py new file mode 100644 index 0000000..2a99bb6 --- /dev/null +++ b/paddleocr/_common_args.py @@ -0,0 +1,188 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddlex.utils.device import get_default_device, parse_device + +from ._constants import ( + DEFAULT_CPU_THREADS, + DEFAULT_DEVICE, + DEFAULT_ENABLE_MKLDNN, + DEFAULT_MKLDNN_CACHE_CAPACITY, + DEFAULT_PRECISION, + DEFAULT_USE_TENSORRT, + SUPPORTED_PRECISION_LIST, + DEFAULT_USE_CINN, +) +from ._utils.cli import str2bool + +SUPPORTED_INFERENCE_ENGINE_LIST = [ + "paddle", + "paddle_static", + "paddle_dynamic", + "transformers", + "onnxruntime", +] + + +def parse_common_args(kwargs, *, default_enable_hpi): + default_vals = { + "device": DEFAULT_DEVICE, + "engine": None, + "engine_config": None, + "enable_hpi": default_enable_hpi, + "use_tensorrt": DEFAULT_USE_TENSORRT, + "precision": DEFAULT_PRECISION, + "enable_mkldnn": DEFAULT_ENABLE_MKLDNN, + "mkldnn_cache_capacity": DEFAULT_MKLDNN_CACHE_CAPACITY, + "cpu_threads": DEFAULT_CPU_THREADS, + "enable_cinn": DEFAULT_USE_CINN, + } + + unknown_names = kwargs.keys() - default_vals.keys() + for name in unknown_names: + raise ValueError(f"Unknown argument: {name}") + + kwargs = {**default_vals, **kwargs} + + if ( + kwargs["engine"] is not None + and kwargs["engine"] not in SUPPORTED_INFERENCE_ENGINE_LIST + ): + raise ValueError( + f"Invalid engine: {kwargs['engine']}. Supported values are: {SUPPORTED_INFERENCE_ENGINE_LIST}." + ) + + if kwargs["precision"] not in SUPPORTED_PRECISION_LIST: + raise ValueError( + f"Invalid precision: {kwargs['precision']}. Supported values are: {SUPPORTED_PRECISION_LIST}." + ) + + kwargs["use_pptrt"] = kwargs.pop("use_tensorrt") + kwargs["pptrt_precision"] = kwargs.pop("precision") + + return kwargs + + +def _build_paddle_static_engine_config(common_args, device_type): + cfg = {} + if device_type == "gpu": + if common_args["use_pptrt"]: + if common_args["pptrt_precision"] == "fp32": + cfg["run_mode"] = "trt_fp32" + else: + assert common_args["pptrt_precision"] == "fp16", common_args[ + "pptrt_precision" + ] + cfg["run_mode"] = "trt_fp16" + else: + cfg["run_mode"] = "paddle" + elif device_type == "cpu": + if common_args["enable_mkldnn"]: + cfg["mkldnn_cache_capacity"] = common_args["mkldnn_cache_capacity"] + else: + cfg["run_mode"] = "paddle" + cfg["cpu_threads"] = common_args["cpu_threads"] + else: + cfg["run_mode"] = "paddle" + cfg["enable_cinn"] = common_args["enable_cinn"] + return cfg + + +def prepare_common_init_args(model_name, common_args): + device = common_args["device"] + if device is None: + device = get_default_device() + device_type, _ = parse_device(device) + + init_kwargs = {} + init_kwargs["device"] = device + init_kwargs["engine"] = common_args["engine"] + init_kwargs["use_hpip"] = common_args["enable_hpi"] + + user_engine_config = common_args["engine_config"] + engine = common_args["engine"] + built = _build_paddle_static_engine_config(common_args, device_type) + + if user_engine_config is not None: + init_kwargs["engine_config"] = user_engine_config + elif engine == "paddle_static": + init_kwargs["engine_config"] = built + elif engine in (None, "paddle"): + init_kwargs["engine_config"] = {"paddle_static": built} + else: + init_kwargs["engine_config"] = None + + return init_kwargs + + +def add_common_cli_opts(parser, *, default_enable_hpi, allow_multiple_devices): + if allow_multiple_devices: + help_ = "Device(s) to use for inference, e.g., `cpu`, `gpu`, `npu`, `gpu:0`, `gpu:0,1`. If multiple devices are specified, inference will be performed in parallel. Note that parallel inference is not always supported. By default, GPU 0 will be used if available; otherwise, the CPU will be used." + else: + help_ = "Device to use for inference, e.g., `cpu`, `gpu`, `npu`, `gpu:0`. By default, GPU 0 will be used if available; otherwise, the CPU will be used." + parser.add_argument( + "--device", + type=str, + default=DEFAULT_DEVICE, + help=help_, + ) + parser.add_argument( + "--engine", + type=str, + choices=SUPPORTED_INFERENCE_ENGINE_LIST, + help="Inference engine to use. For CLI, engine-specific configuration should be set in the PaddleX YAML config file.", + ) + parser.add_argument( + "--enable_hpi", + type=str2bool, + default=default_enable_hpi, + help="Enable the high performance inference.", + ) + parser.add_argument( + "--use_tensorrt", + type=str2bool, + default=DEFAULT_USE_TENSORRT, + help="Whether to use the Paddle Inference TensorRT subgraph engine. If the model does not support TensorRT acceleration, even if this flag is set, acceleration will not be used.", + ) + parser.add_argument( + "--precision", + type=str, + default=DEFAULT_PRECISION, + choices=SUPPORTED_PRECISION_LIST, + help="Precision for TensorRT when using the Paddle Inference TensorRT subgraph engine.", + ) + parser.add_argument( + "--enable_mkldnn", + type=str2bool, + default=DEFAULT_ENABLE_MKLDNN, + help="Enable MKL-DNN acceleration for inference. If MKL-DNN is unavailable or the model does not support it, acceleration will not be used even if this flag is set.", + ) + parser.add_argument( + "--mkldnn_cache_capacity", + type=int, + default=DEFAULT_MKLDNN_CACHE_CAPACITY, + help="MKL-DNN cache capacity.", + ) + parser.add_argument( + "--cpu_threads", + type=int, + default=DEFAULT_CPU_THREADS, + help="Number of threads to use for inference on CPUs.", + ) + parser.add_argument( + "--enable_cinn", + type=str2bool, + default=DEFAULT_USE_CINN, + help="Whether to use the CINN compiler.", + ) diff --git a/paddleocr/_constants.py b/paddleocr/_constants.py new file mode 100644 index 0000000..d96ba78 --- /dev/null +++ b/paddleocr/_constants.py @@ -0,0 +1,22 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +DEFAULT_DEVICE = None +DEFAULT_USE_TENSORRT = False +DEFAULT_PRECISION = "fp32" +DEFAULT_ENABLE_MKLDNN = True +DEFAULT_MKLDNN_CACHE_CAPACITY = 10 +DEFAULT_CPU_THREADS = 10 +SUPPORTED_PRECISION_LIST = ["fp32", "fp16"] +DEFAULT_USE_CINN = False diff --git a/paddleocr/_doc2md/__init__.py b/paddleocr/_doc2md/__init__.py new file mode 100644 index 0000000..d055fad --- /dev/null +++ b/paddleocr/_doc2md/__init__.py @@ -0,0 +1,26 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""paddleocr._doc2md - Convert office documents to Markdown.""" + +from .core import convert, supported_formats +from .base import ConvertResult, BaseConverter +from .registry import default_registry + +__all__ = [ + "convert", + "supported_formats", + "ConvertResult", + "BaseConverter", + "default_registry", +] diff --git a/paddleocr/_doc2md/base.py b/paddleocr/_doc2md/base.py new file mode 100644 index 0000000..755a59c --- /dev/null +++ b/paddleocr/_doc2md/base.py @@ -0,0 +1,39 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from pathlib import Path +from typing import Optional + + +@dataclass +class ConvertResult: + """Conversion result.""" + + markdown: str + title: Optional[str] = None + metadata: dict = field(default_factory=dict) + images: dict = field(default_factory=dict) # {relative_path: image_bytes} + + +class BaseConverter(ABC): + """Abstract base class for all format converters.""" + + supported_extensions: list[str] = [] + supported_mimetypes: list[str] = [] + + @abstractmethod + def convert_file(self, file_path: Path, **kwargs) -> ConvertResult: + """Convert a file to Markdown.""" + ... diff --git a/paddleocr/_doc2md/converters/__init__.py b/paddleocr/_doc2md/converters/__init__.py new file mode 100644 index 0000000..d48c02e --- /dev/null +++ b/paddleocr/_doc2md/converters/__init__.py @@ -0,0 +1,29 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Built-in converters - importing this package triggers registration of all converters.""" + +import importlib + +_converter_modules = [ + "docx", + "pptx", + "xlsx", +] + +for _mod in _converter_modules: + try: + importlib.import_module(f".{_mod}", package=__name__) + except ImportError: + # Silently skip missing optional dependencies; users will get a clear error on actual use + pass diff --git a/paddleocr/_doc2md/converters/docx.py b/paddleocr/_doc2md/converters/docx.py new file mode 100644 index 0000000..0661223 --- /dev/null +++ b/paddleocr/_doc2md/converters/docx.py @@ -0,0 +1,1630 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import re +from collections import Counter +from pathlib import Path + +from ..base import BaseConverter, ConvertResult +from ..math import convert_omath as _convert_omath +from ..math import _M +from ..registry import default_registry + +# Regex patterns for Chinese numbered headings +_RE_H2 = re.compile(r"^[一二三四五六七八九十百千]+[、..]") +_RE_H3 = re.compile(r"^([一二三四五六七八九十百千]+)") + +# Regex for field-code hyperlink instruction +_RE_FIELD_HYPERLINK = re.compile(r'HYPERLINK\s+"([^"]+)"') + +# Regex for page-number-only footer/header text (e.g. "第 页", "共 页", "- 3 -", "Page of") +_RE_PAGE_ONLY = re.compile(r"^[\s第页共of\d\-/|·]*$", re.IGNORECASE) + +# Word XML namespace +_W_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" +_W = "{" + _W_NS + "}" + +# Markup Compatibility namespace (mc:AlternateContent) +_MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006" +_MC = "{" + _MC_NS + "}" + +# WordprocessingShape namespace (wps:txbx) +_WPS_NS = "http://schemas.microsoft.com/office/word/2010/wordprocessingShape" +_WPS = "{" + _WPS_NS + "}" + +# Chart namespace +_C_NS = "http://schemas.openxmlformats.org/drawingml/2006/chart" +_C = "{" + _C_NS + "}" + +# WordprocessingDrawing namespace (wp:inline, wp:anchor) +_WPD = "{http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing}" +# DrawingML main namespace +_A = "{http://schemas.openxmlformats.org/drawingml/2006/main}" +# OPC relationships namespace +_R = "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}" + + +def _escape_md_url(url: str) -> str: + """Escape parentheses in URL for Markdown link syntax.""" + return url.replace("(", "%28").replace(")", "%29") + + +def _effective_bold(run, para) -> bool: + """Resolve effective bold: run-level > character style > paragraph style.""" + b = run.bold + if b is not None: + return b + try: + if run.style and run.style.font.bold is not None: + return run.style.font.bold + except Exception: + pass + try: + if para.style and para.style.font.bold is not None: + return para.style.font.bold + except Exception: + pass + return False + + +def _effective_italic(run, para) -> bool: + """Resolve effective italic: run-level > character style > paragraph style.""" + i = run.italic + if i is not None: + return i + try: + if run.style and run.style.font.italic is not None: + return run.style.font.italic + except Exception: + pass + try: + if para.style and para.style.font.italic is not None: + return para.style.font.italic + except Exception: + pass + return False + + +def _effective_underline(run, para) -> bool: + """Resolve effective underline: run-level > character style > paragraph style.""" + u = run.underline + if u is not None: + return bool(u) + try: + if run.style and run.style.font.underline is not None: + return bool(run.style.font.underline) + except Exception: + pass + try: + if para.style and para.style.font.underline is not None: + return bool(para.style.font.underline) + except Exception: + pass + return False + + +def _effective_superscript(run) -> bool: + """Return True if run has superscript set (w:vertAlign w:val='superscript').""" + try: + return bool(run.font.superscript) + except Exception: + return False + + +def _effective_subscript(run) -> bool: + """Return True if run has subscript set (w:vertAlign w:val='subscript').""" + try: + return bool(run.font.subscript) + except Exception: + return False + + +def _paragraph_has_math(para) -> bool: + """Check if paragraph XML contains OMML math elements.""" + return para._element.find(f".//{_M}oMath") is not None + + +def _iter_math_paragraph_parts(para) -> list: + """Parse a paragraph with mixed text/math content into a list of parts. + + Returns a list where each element is either: + - ("text", items_list) — a group of text runs to be formatted + - ("display_math", latex_str) — a display math block ($$...$$) + - ("inline_math", latex_str) — an inline math expression ($...$) + """ + from docx.text.run import Run + from docx.text.hyperlink import Hyperlink + + text_items = [] + parts = [] + + def flush_text(): + if text_items: + parts.append(("text", list(text_items))) + text_items.clear() + + for child in para._element: + tag = child.tag + local = tag.split("}")[-1] if "}" in tag else tag + + if tag == f"{_M}oMathPara": + flush_text() + for omath in child.findall(f"{_M}oMath"): + latex = _convert_omath(omath) + if latex: + parts.append(("display_math", latex)) + elif tag == f"{_M}oMath": + flush_text() + latex = _convert_omath(child) + if latex: + parts.append(("inline_math", latex)) + elif local == "r": + try: + run = Run(child, para) + if run.text: + text_items.append( + ( + _effective_bold(run, para), + _effective_italic(run, para), + _effective_underline(run, para), + bool(run.font.strike), + _effective_superscript(run), + _effective_subscript(run), + run.text, + "", + ) + ) + except Exception: + pass + elif local == "hyperlink": + try: + hl = Hyperlink(child, para) + try: + url = hl.url or "" + except (KeyError, AttributeError): + url = "" + for run in hl.runs: + if run.text: + text_items.append( + ( + _effective_bold(run, para), + _effective_italic(run, para), + False, + bool(run.font.strike), + _effective_superscript(run), + _effective_subscript(run), + run.text, + url, + ) + ) + except Exception: + pass + + flush_text() + return parts + + +def _paragraph_math_to_markdown(para) -> str: + """Convert a paragraph containing OMML math to Markdown.""" + result = [] + for kind, data in _iter_math_paragraph_parts(para): + if kind == "text": + md = _runs_to_markdown(data) + if md: + result.append(md) + elif kind == "display_math": + result.append(f"$$\n{data}\n$$") + elif kind == "inline_math": + result.append(f"${data}$") + return "".join(result) + + +def _paragraph_math_to_html(para) -> str: + """Convert a paragraph containing OMML math to HTML inline text.""" + result = [] + for kind, data in _iter_math_paragraph_parts(para): + if kind == "text": + html = _runs_to_html(data) + if html: + result.append(html) + elif kind == "display_math": + result.append(f"$$\n{data}\n$$") + elif kind == "inline_math": + result.append(f"${data}$") + return "".join(result) + + +def _get_body_font_size(doc) -> float: + """Return the most common font size in the document (used as body size). Defaults to 16.0.""" + sizes: Counter = Counter() + for p in doc.paragraphs: + if not p.text.strip(): + continue + for run in p.runs: + if run.font.size: + sizes[run.font.size.pt] += 1 + break # only check the first run with an explicit size per paragraph + return sizes.most_common(1)[0][0] if sizes else 16.0 + + +def _detect_heading_level(para, body_font_size: float) -> int: + """Return heading level (0 = not a heading, 1-6 = heading level).""" + # Prefer Word built-in Heading styles + style_name = para.style.name if para.style else "" + if style_name.startswith("Heading"): + try: + return int(style_name.split()[-1]) + except ValueError: + return 1 + if style_name == "Title": + return 1 + if style_name == "Subtitle": + return 2 + + text = para.text.strip() + if not text: + return 0 + + # Use font size of the first run that has an explicit size + font_size = None + for run in para.runs: + if run.font.size: + font_size = run.font.size.pt + break + + # Significantly larger than body font -> treat as heading (threshold: 1.5x, short paragraphs only) + if font_size and font_size > body_font_size * 1.5: + from docx.enum.text import WD_ALIGN_PARAGRAPH + + if para.alignment == WD_ALIGN_PARAGRAPH.CENTER: + return 1 + if len(text) <= 60: + return 2 + + # Chinese numbered heading patterns + if _RE_H2.match(text): + return 2 + if _RE_H3.match(text): + return 3 + + return 0 + + +class _FieldState: + """Mutable state for tracking w:fldChar field codes across paragraphs.""" + + __slots__ = ("active", "phase", "nest_depth", "url") + active: bool + phase: object # None | "instr" | "result" + nest_depth: int + url: object # None | str + + def __init__(self): + self.active = False + self.phase = None # None | "instr" | "result" + self.nest_depth = 0 + self.url = None + + +def _update_field_state_for_paragraph(para_element, field_state): + """Update field_state by scanning a paragraph element's runs. + + Used for early-exit paragraphs (TOC / math / empty / code) to keep + cross-paragraph field tracking accurate without building item lists. + """ + for child in para_element: + tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag + if tag != "r": + continue + fld_char = child.find(_W + "fldChar") + if fld_char is not None: + fld_type = fld_char.get(_W + "fldCharType") + if fld_type == "begin": + if field_state.phase == "result": + field_state.nest_depth += 1 + else: + field_state.active = True + field_state.phase = "instr" + field_state.url = None + elif fld_type == "separate": + if field_state.nest_depth == 0: + field_state.phase = "result" + elif fld_type == "end": + if field_state.nest_depth > 0: + field_state.nest_depth -= 1 + else: + field_state.active = False + field_state.phase = None + field_state.url = None + continue + instr_elem = child.find(_W + "instrText") + if instr_elem is not None and field_state.phase == "instr": + if instr_elem.text: + m = _RE_FIELD_HYPERLINK.search(instr_elem.text) + if m: + field_state.url = m.group(1) + + +def _iter_paragraph_items(para, field_state=None) -> list: + """Extract (bold, italic, underline, strikethrough, superscript, subscript, text, url) tuples from a paragraph in document order. + + Handles python-docx Hyperlink objects and w:fldChar field-code hyperlinks. + Silently degrades to plain text on error. + Note: underline is forced to False inside Hyperlink/field-hyperlink runs to avoid Word's default hyperlink underline style. + + field_state: optional _FieldState instance for cross-paragraph field tracking. + If None, a fresh _FieldState is created (single-paragraph mode). + """ + if field_state is None: + field_state = _FieldState() + + def _split_breaks(items): + """Expand items containing \\n (from soft line breaks) into per-line items with
    separators.""" + expanded = [] + for ( + bold, + italic, + underline, + strikethrough, + superscript, + subscript, + text, + url, + ) in items: + if "\n" not in text: + expanded.append( + ( + bold, + italic, + underline, + strikethrough, + superscript, + subscript, + text, + url, + ) + ) + continue + segments = text.split("\n") + for j, seg in enumerate(segments): + if seg: + expanded.append( + ( + bold, + italic, + underline, + strikethrough, + superscript, + subscript, + seg, + url, + ) + ) + if j < len(segments) - 1: + expanded.append( + (False, False, False, False, False, False, "
    \n", "") + ) + return expanded + + try: + from docx.text.hyperlink import Hyperlink + except ImportError: + return _split_breaks( + [ + ( + _effective_bold(r, para), + _effective_italic(r, para), + _effective_underline(r, para), + bool(r.font.strike), + _effective_superscript(r), + _effective_subscript(r), + r.text, + "", + ) + for r in para.runs + if r.text + ] + ) + + items = [] + try: + content_iter = para.iter_inner_content() + except Exception: + return _split_breaks( + [ + ( + _effective_bold(r, para), + _effective_italic(r, para), + _effective_underline(r, para), + bool(r.font.strike), + _effective_superscript(r), + _effective_subscript(r), + r.text, + "", + ) + for r in para.runs + if r.text + ] + ) + + for element in content_iter: + try: + if isinstance(element, Hyperlink): + try: + url = element.url or "" + except (KeyError, AttributeError): + url = "" + for run in element.runs: + if not run.text: + continue + # Force underline=False: Word's Hyperlink style adds underline by default + items.append( + ( + _effective_bold(run, para), + _effective_italic(run, para), + False, + bool(run.font.strike), + _effective_superscript(run), + _effective_subscript(run), + run.text, + url, + ) + ) + # Fallback: hyperlink with no runs but has text + if not element.runs and element.text: + items.append( + (False, False, False, False, False, False, element.text, url) + ) + else: + # Plain Run — check for fldChar control elements first + fld_char = element._element.find(_W + "fldChar") + if fld_char is not None: + fld_type = fld_char.get(_W + "fldCharType") + if fld_type == "begin": + if field_state.phase == "result": + field_state.nest_depth += 1 + else: + field_state.active = True + field_state.phase = "instr" + field_state.url = None + elif fld_type == "separate": + if field_state.nest_depth == 0: + field_state.phase = "result" + elif fld_type == "end": + if field_state.nest_depth > 0: + field_state.nest_depth -= 1 + else: + field_state.active = False + field_state.phase = None + field_state.url = None + continue + + instr_elem = element._element.find(_W + "instrText") + if instr_elem is not None: + if field_state.active and field_state.phase == "instr": + # Extract HYPERLINK url from instrText + if instr_elem.text: + m = _RE_FIELD_HYPERLINK.search(instr_elem.text) + if m: + field_state.url = m.group(1) + continue # Never emit instrText run as content + + # Plain Run + if not element.text: + continue + + # If we are in the result phase of a field-code hyperlink, apply URL + # and suppress underline (same as w:hyperlink element handling above). + if ( + field_state.phase == "result" + and field_state.nest_depth == 0 + and field_state.url + ): + items.append( + ( + _effective_bold(element, para), + _effective_italic(element, para), + False, # suppress underline for field hyperlinks + bool(element.font.strike), + _effective_superscript(element), + _effective_subscript(element), + element.text, + field_state.url, + ) + ) + else: + items.append( + ( + _effective_bold(element, para), + _effective_italic(element, para), + _effective_underline(element, para), + bool(element.font.strike), + _effective_superscript(element), + _effective_subscript(element), + element.text, + "", + ) + ) + except Exception: + continue + + return _split_breaks(items) + + +def _merge_runs(items) -> list: + """Merge adjacent items with identical (bold, italic, underline, strikethrough, superscript, subscript, url). + + Returns [(bold, italic, underline, strikethrough, superscript, subscript, text, url)]. + """ + merged: list[tuple[bool, bool, bool, bool, bool, bool, str, str]] = [] + for ( + bold, + italic, + underline, + strikethrough, + superscript, + subscript, + text, + url, + ) in items: + if not text: + continue + if ( + merged + and merged[-1][0] == bold + and merged[-1][1] == italic + and merged[-1][2] == underline + and merged[-1][3] == strikethrough + and merged[-1][4] == superscript + and merged[-1][5] == subscript + and merged[-1][7] == url + ): + merged[-1] = ( + bold, + italic, + underline, + strikethrough, + superscript, + subscript, + merged[-1][6] + text, + url, + ) + else: + merged.append( + ( + bold, + italic, + underline, + strikethrough, + superscript, + subscript, + text, + url, + ) + ) + return merged + + +def _runs_to_markdown(items) -> str: + """Convert paragraph items to Markdown inline text, merging adjacent items with identical formatting.""" + parts = [] + for ( + bold, + italic, + underline, + strikethrough, + superscript, + subscript, + text, + url, + ) in _merge_runs(items): + if bold or italic or underline or strikethrough or superscript or subscript: + # CommonMark: marker characters must not be surrounded by spaces + leading = len(text) - len(text.lstrip()) + trailing = len(text) - len(text.rstrip()) + prefix = text[:leading] if leading else "" + suffix = text[len(text) - trailing :] if trailing else "" + inner = text.strip() + if inner: + # Apply strikethrough first (innermost) + if strikethrough: + inner = f"~~{inner}~~" + # Apply bold/italic + if bold and italic: + inner = f"***{inner}***" + elif bold: + inner = f"**{inner}**" + elif italic: + inner = f"*{inner}*" + # Apply underline + if underline: + inner = f"{inner}" + # Apply superscript/subscript (outermost) + if superscript: + inner = f"{inner}" + elif subscript: + inner = f"{inner}" + text = prefix + inner + suffix + elif underline and text: + # Pure whitespace + underline = fill-in line (e.g. "作者姓名:___") + # Replace spaces with NBSP so Markdown renderers preserve width + text = "" + "\u00a0" * len(text) + "" + if url: + text = f"[{text}]({_escape_md_url(url)})" + parts.append(text) + # Prevent bold/italic/strikethrough markers from merging with adjacent alphanumeric text (CommonMark requirement) + result = [] + for i, part in enumerate(parts): + if i > 0 and result: + prev = result[-1] + # Previous part ends with closing marker and current part starts with alphanumeric + if prev.endswith(("**", "*", "~~")) and part and part[0].isalnum(): + result.append("\u200b") + result.append(part) + return "".join(result) + + +def _runs_to_html(items) -> str: + """Convert paragraph items to HTML inline text.""" + parts = [] + for ( + bold, + italic, + underline, + strikethrough, + superscript, + subscript, + text, + url, + ) in _merge_runs(items): + if bold: + text = f"{text}" + if italic: + text = f"{text}" + if underline: + text = f"{text}" + if strikethrough: + text = f"{text}" + if superscript: + text = f"{text}" + if subscript: + text = f"{text}" + if url: + text = f'{text}' + parts.append(text) + return "".join(parts) + + +# Regex for TOC style names (e.g. "toc 1", "TOC 2", "TOC3") +_RE_TOC_STYLE = re.compile(r"(?i)^toc\s*(\d+)$") + +# Flat TOC styles with no level concept (e.g. figure/table of contents) +_TOC_FLAT_STYLES = {"table of figures"} + +# Regex for PAGEREF anchor in field instructions +_RE_PAGEREF = re.compile(r"PAGEREF\s+(_Toc\w+)") + + +def _is_toc_paragraph(para): + """Return TOC level (1-9) if paragraph uses a TOC style, else None.""" + style_name = (para.style.name if para.style else "").strip() + m = _RE_TOC_STYLE.match(style_name) + if m: + return int(m.group(1)) + if style_name.lower() in _TOC_FLAT_STYLES: + return 1 + return None + + +def _extract_toc_text(para) -> str: + """Extract display text from a TOC paragraph, stripping trailing page numbers.""" + text = para.text + # Remove trailing page number: split on last tab, discard if it's a pure number + if "\t" in text: + before_tab, _, after_tab = text.rpartition("\t") + if after_tab.strip().isdigit(): + text = before_tab + return text.strip() + + +def _extract_toc_anchor(para_element): + """Extract anchor name from a TOC paragraph element. + + Priority: + 1. w:hyperlink[@w:anchor] attribute + 2. PAGEREF _TocXXXX in w:instrText + """ + # Check for w:hyperlink with anchor attribute + for hl in para_element.findall(f".//{_W}hyperlink"): + anchor = hl.get(f"{_W}anchor") + if anchor and anchor.startswith("_Toc"): + return anchor + + # Fall back to PAGEREF field instruction + for instr in para_element.findall(f".//{_W}instrText"): + if instr.text: + m = _RE_PAGEREF.search(instr.text) + if m: + return m.group(1) + return None + + +def _toc_entries_to_markdown(entries: list) -> str: + """Convert list of (text, anchor, level) TOC entries to Markdown list.""" + lines = [] + for text, anchor, level in entries: + indent = " " * (level - 1) + if anchor: + lines.append(f"{indent}- [{text}](#{anchor})") + else: + lines.append(f"{indent}- {text}") + return "\n".join(lines) + + +def _extract_heading_toc_bookmarks(para_element): + """Return all _Toc bookmark names found in a paragraph element. + + A heading may carry multiple _Toc bookmarks from repeated TOC updates + (each update inserts a new bookmark without removing old ones). Returning + all of them ensures that TOC entries from any generation can link here. + """ + names = [] + for bm in para_element.findall(f".//{_W}bookmarkStart"): + name = bm.get(f"{_W}name", "") + if name.startswith("_Toc"): + names.append(name) + return names + + +def _flatten_body(body): + """Yield body children, expanding sdt elements into their sdtContent children.""" + for child in body: + tag = child.tag.split("}")[-1] if "}" in child.tag else child.tag + if tag == "sdt": + sdt_content = child.find(f"{_W}sdtContent") + if sdt_content is not None: + yield from _flatten_body(sdt_content) + else: + yield child + + +_CODE_FONTS = { + "Courier New", + "Courier", + "Consolas", + "Monaco", + "Menlo", + "Source Code Pro", + "Fira Code", + "DejaVu Sans Mono", + "monospace", +} + + +def _is_code_paragraph(para) -> bool: + """Return True if all text-bearing runs in the paragraph use a monospace font.""" + runs_with_text = [r for r in para.runs if r.text.strip()] + if not runs_with_text: + return False + runs_with_font = [r for r in runs_with_text if r.font.name] + # At least one run must have an explicit font, and all such runs must be monospace + if not runs_with_font: + return False + return all(r.font.name in _CODE_FONTS for r in runs_with_font) + + +def _get_content_width(doc) -> int: + """Return the content area width of the document in EMU.""" + section = doc.sections[0] + return section.page_width - section.left_margin - section.right_margin + + +def _table_to_html( + table, doc, image_counter: list, images: dict, content_width: int = 0 +) -> str: + """Convert a python-docx Table to an HTML table, handling merged cells and inline images.""" + grid = [[cell for cell in row.cells] for row in table.rows] + nrows = len(grid) + if nrows == 0: + return "" + ncols = len(grid[0]) + + visited: set[tuple[int, int]] = set() + html_parts = [""] + + for i in range(nrows): + html_parts.append("") + for j in range(ncols): + if (i, j) in visited: + continue + tc = grid[i][j]._tc + # Compute colspan + colspan = 1 + while j + colspan < ncols and grid[i][j + colspan]._tc is tc: + visited.add((i, j + colspan)) + colspan += 1 + # Compute rowspan + rowspan = 1 + while i + rowspan < nrows and grid[i + rowspan][j]._tc is tc: + for k in range(colspan): + visited.add((i + rowspan, j + k)) + rowspan += 1 + + cell = grid[i][j] + content_parts = [] + for para in cell.paragraphs: + img_list = _extract_images_from_paragraph(para, doc, image_counter) + for filename, img_bytes, cx_emu in img_list: + rel_path = f"images/{filename}" + images[rel_path] = img_bytes + if cx_emu and content_width: + pct = min(round(cx_emu / content_width * 100), 100) + content_parts.append( + f'' + ) + else: + content_parts.append(f'') + para_html = ( + _paragraph_math_to_html(para).strip() + if _paragraph_has_math(para) + else _runs_to_html(_iter_paragraph_items(para)).strip() + or para.text.strip() + ) + if para_html: + content_parts.append(para_html) + # Extract text box content from table cell paragraph + try: + tb_groups = _extract_textbox_paragraphs(para._element) + for tb_paras in tb_groups: + for tb_p in tb_paras: + tb_t_elems = tb_p.findall(f".//{_W}t") + tb_text = "".join( + t.text for t in tb_t_elems if t.text + ).strip() + if tb_text: + content_parts.append(f"[{tb_text}]") + except Exception: + pass + cell_html = "
    ".join(content_parts) if content_parts else "" + + is_header = False + trPr = table.rows[i]._tr.find(f"{_W}trPr") + if trPr is not None: + tbl_header = trPr.find(f"{_W}tblHeader") + if tbl_header is not None: + val = tbl_header.get(f"{_W}val") + is_header = val is None or val.lower() not in ("false", "0", "off") + if not is_header and i == 0 and nrows > 1: + is_header = ( + True # fallback: treat first row as header in multi-row tables + ) + tag = "th" if is_header else "td" + attrs = "" + if colspan > 1: + attrs += f' colspan="{colspan}"' + if rowspan > 1: + attrs += f' rowspan="{rowspan}"' + html_parts.append(f"<{tag}{attrs}>{cell_html}") + html_parts.append("") + + html_parts.append("
    ") + return "\n".join(html_parts) + + +_MIME_TO_EXT = { + "image/jpeg": "jpeg", + "image/jpg": "jpg", + "image/png": "png", + "image/gif": "gif", + "image/bmp": "bmp", + "image/tiff": "tiff", + "image/webp": "webp", + "image/svg+xml": "svg", + "image/x-emf": "emf", + "image/x-wmf": "wmf", +} + + +def _extract_images_from_paragraph(para, doc, image_counter: list) -> list: + """Extract images from a paragraph. Returns [(filename, bytes, cx_emu)] where cx_emu is 0 if unknown.""" + results = [] + containers = para._element.findall(f".//{_WPD}inline") + para._element.findall( + f".//{_WPD}anchor" + ) + for container in containers: + extent = container.find(f"{_WPD}extent") + cx_emu = int(extent.get("cx", 0)) if extent is not None else 0 + + blip = container.find(f".//{_A}blip") + if blip is None: + continue + r_embed = blip.get(f"{_R}embed") + if not r_embed: + continue + try: + rel = doc.part.rels[r_embed] + img_bytes = rel.target_part.blob + content_type = rel.target_part.content_type + ext = _MIME_TO_EXT.get(content_type, "png") + image_counter[0] += 1 + filename = f"image{image_counter[0]}.{ext}" + results.append((filename, img_bytes, cx_emu)) + except (KeyError, AttributeError): + pass + return results + + +def _extract_chart_tables(para, doc) -> list: + """Extract chart data as HTML tables from drawings in a paragraph.""" + try: + from datetime import datetime, timedelta + + results = [] + containers = para._element.findall(f".//{_WPD}inline") + para._element.findall( + f".//{_WPD}anchor" + ) + + for container in containers: + chart_ref = container.find(f".//{_C}chart") + if chart_ref is None: + continue + r_id = chart_ref.get(f"{_R}id") + if not r_id: + continue + try: + rel = doc.part.rels[r_id] + chart_part = rel.target_part + except (KeyError, AttributeError): + continue + + try: + import lxml.etree as etree + + chart_root = etree.fromstring(chart_part.blob) + except Exception: + continue + + # Extract chart title + title_text = "" + title_el = chart_root.find(f".//{_C}title") + if title_el is not None: + a_t_els = title_el.findall(f".//{_A}t") + title_text = "".join((el.text or "") for el in a_t_els).strip() + + # Extract axis titles + cat_ax_title = "" + for ax_tag in (f"{_C}catAx", f"{_C}dateAx"): + ax_el = chart_root.find(f".//{ax_tag}") + if ax_el is not None: + t_el = ax_el.find(f"{_C}title") + if t_el is not None: + texts = t_el.findall(f".//{_A}t") + cat_ax_title = "".join(el.text or "" for el in texts).strip() + break + + val_ax_title = "" + val_ax_el = chart_root.find(f".//{_C}valAx") + if val_ax_el is not None: + t_el = val_ax_el.find(f"{_C}title") + if t_el is not None: + texts = t_el.findall(f".//{_A}t") + val_ax_title = "".join(el.text or "" for el in texts).strip() + + # Extract series data + series_list = chart_root.findall(f".//{_C}ser") + if not series_list: + continue + + # Collect categories from first series + categories = [] + cat_is_date = False + date_format_code = "" + first_ser = series_list[0] + cat_el = first_ser.find(f"{_C}cat") + if cat_el is not None: + str_cache = cat_el.find(f".//{_C}strCache") + num_cache = cat_el.find(f".//{_C}numCache") + if str_cache is not None: + for pt in str_cache.findall(f"{_C}pt"): + v = pt.find(f"{_C}v") + categories.append(v.text if v is not None else "") + elif num_cache is not None: + fmt_el = num_cache.find(f"{_C}formatCode") + if fmt_el is not None: + fc = fmt_el.text or "" + date_format_code = fc + cat_is_date = any(k in fc.lower() for k in ("y", "m", "d")) + for pt in num_cache.findall(f"{_C}pt"): + v = pt.find(f"{_C}v") + if v is not None and v.text: + try: + serial = float(v.text) + if cat_is_date: + dt = datetime(1899, 12, 30) + timedelta(days=serial) + categories.append(dt.strftime("%Y-%m-%d")) + else: + categories.append(v.text) + except (ValueError, OverflowError): + categories.append(v.text or "") + else: + categories.append("") + + # Collect series names and values + series_names = [] + series_values = [] + for ser in series_list: + # Series name: val axis title > > "" + name = val_ax_title + if not name: + tx_el = ser.find(f"{_C}tx") + if tx_el is not None: + str_ref = tx_el.find(f".//{_C}strCache") + if str_ref is not None: + pt = str_ref.find(f"{_C}pt") + if pt is not None: + v = pt.find(f"{_C}v") + name = v.text if v is not None else "" + else: + v = tx_el.find(f"{_C}v") + if v is not None: + name = v.text or "" + series_names.append(name) + + # Values + vals = [] + val_el = ser.find(f"{_C}val") + if val_el is not None: + num_cache = val_el.find(f".//{_C}numCache") + if num_cache is not None: + pts = { + int(pt.get("idx", 0)): pt + for pt in num_cache.findall(f"{_C}pt") + } + max_idx = max(pts.keys()) if pts else -1 + for idx in range(max_idx + 1): + pt = pts.get(idx) + if pt is not None: + v = pt.find(f"{_C}v") + vals.append(v.text if v is not None else "") + else: + vals.append("") + series_values.append(vals) + + if not series_names: + continue + + # Build HTML table + n_rows = max( + len(categories), + max((len(v) for v in series_values), default=0), + ) + html_parts = [""] + if title_text: + html_parts.append(f"") + # Header row — omit entirely if no header info + has_header = cat_ax_title or any(name for name in series_names) + if has_header: + html_parts.append("") + html_parts.append(f"") + for name in series_names: + html_parts.append(f"") + html_parts.append("") + # Data rows + html_parts.append("") + for i in range(n_rows): + cat = categories[i] if i < len(categories) else "" + html_parts.append(f"") + for vals in series_values: + val = vals[i] if i < len(vals) else "" + html_parts.append(f"") + html_parts.append("") + html_parts.append("
    {title_text}
    {cat_ax_title}{name}
    {cat}{val}
    ") + results.append("".join(html_parts)) + + return results + except Exception: + return [] + + +def _extract_textbox_paragraphs(element) -> list: + """Extract paragraphs from text boxes embedded in a body element. + + Text boxes appear as mc:AlternateContent > mc:Choice > wps:txbx > w:txbxContent > w:p. + Only mc:Choice is used to avoid duplicating VML fallback content. + + Returns list of lists of w:p elements, one list per non-empty text box. + """ + try: + result = [] + for alt_content in element.iter(f"{_MC}AlternateContent"): + choice = alt_content.find(f"{_MC}Choice") + if choice is None: + continue + for txbx in choice.iter(f"{_WPS}txbx"): + txbx_content = txbx.find(f"{_W}txbxContent") + if txbx_content is None: + continue + paras = txbx_content.findall(f"{_W}p") + if not paras: + continue + # Skip boxes where all paragraphs are empty + has_text = any( + p.find(f".//{_W}t") is not None + and any(t.text for t in p.findall(f".//{_W}t") if t.text) + for p in paras + ) + if has_text: + result.append(paras) + return result + except Exception: + return [] + + +def _textbox_paragraphs_to_markdown( + textbox_groups, doc, body_font_size, image_counter, images, content_width +) -> list: + """Convert text box paragraph groups to blockquote Markdown lines. + + Returns a list of strings (lines) to append to the main output. + """ + try: + from docx.text.paragraph import Paragraph + except ImportError: + return [] + + output_lines = [] + for group_idx, para_elements in enumerate(textbox_groups): + group_lines = [] + for p_elem in para_elements: + try: + para = Paragraph(p_elem, doc) + + # Handle images in text box + img_list = _extract_images_from_paragraph(para, doc, image_counter) + for filename, img_bytes, cx_emu in img_list: + rel_path = f"images/{filename}" + images[rel_path] = img_bytes + if cx_emu and content_width: + pct = min(round(cx_emu / content_width * 100), 100) + group_lines.append( + f'> ' + ) + else: + group_lines.append(f'> ') + + # Handle math formulas + if _paragraph_has_math(para): + math_md = _paragraph_math_to_markdown(para) + if math_md: + group_lines.append(f"> {math_md}") + continue + + # Plain inline text (no heading/list/code detection for text boxes) + inline = _runs_to_markdown(_iter_paragraph_items(para)) + if not inline: + inline = para.text.strip() + if inline: + group_lines.append(f"> {inline}") + except Exception: + continue + + if group_lines: + if output_lines: + output_lines.append("") + output_lines.extend(group_lines) + + return output_lines + + +def _build_numbering_map(doc) -> dict: + """Parse numbering.xml and return {numId: {ilvl: numFmt}} mapping.""" + numbering_map = {} + try: + numbering_part = doc.part.numbering_part + except Exception: + return numbering_map + numbering_elem = numbering_part._element + + # Build abstractNumId -> {ilvl: numFmt} + abstract = {} + for abs_num in numbering_elem.findall(f"{_W}abstractNum"): + abs_id = abs_num.get(f"{_W}abstractNumId") + levels = {} + for lvl in abs_num.findall(f"{_W}lvl"): + ilvl = int(lvl.get(f"{_W}ilvl", "0")) + fmt_elem = lvl.find(f"{_W}numFmt") + fmt = ( + fmt_elem.get(f"{_W}val", "bullet") if fmt_elem is not None else "bullet" + ) + levels[ilvl] = fmt + abstract[abs_id] = levels + + # Map numId -> abstractNumId + for num in numbering_elem.findall(f"{_W}num"): + num_id = num.get(f"{_W}numId") + abs_ref = num.find(f"{_W}abstractNumId") + if abs_ref is not None: + abs_id = abs_ref.get(f"{_W}val") + if abs_id in abstract: + numbering_map[num_id] = abstract[abs_id] + return numbering_map + + +def _get_list_info(para, numbering_map) -> tuple | None: + """Return (list_type, ilvl, num_id) or None. list_type: 'bullet' | 'ordered'""" + pPr = para._element.find(f"{_W}pPr") + if pPr is None: + return None + numPr = pPr.find(f"{_W}numPr") + if numPr is None: + return None + numId_elem = numPr.find(f"{_W}numId") + ilvl_elem = numPr.find(f"{_W}ilvl") + if numId_elem is None: + return None + num_id = numId_elem.get(f"{_W}val") + ilvl = int(ilvl_elem.get(f"{_W}val", "0")) if ilvl_elem is not None else 0 + if num_id not in numbering_map: + return None + fmt = numbering_map[num_id].get(ilvl, "bullet") + list_type = ( + "ordered" + if fmt in ("decimal", "lowerLetter", "upperLetter", "lowerRoman", "upperRoman") + else "bullet" + ) + return (list_type, ilvl, num_id) + + +def _convert_body(doc, *, extract_drawings=True) -> tuple: + """Traverse body elements in order and produce Markdown. Returns (markdown_str, images_dict).""" + try: + from docx.table import Table + from docx.text.paragraph import Paragraph + except ImportError: + raise RuntimeError( + "DOCX conversion requires python-docx: pip install paddleocr[doc2md]" + ) + + body_font_size = _get_body_font_size(doc) + content_width = _get_content_width(doc) + numbering_map = _build_numbering_map(doc) + lines: list[str] = [] + images: dict = {} + image_counter = [0] # wrapped in list so inner functions can mutate it + code_buf: list[str] = [] # buffer for consecutive code paragraphs + toc_buf: list[tuple] = [] # buffer for consecutive TOC paragraphs + ol_counters: dict[str, int] = {} # key = "{numId}-{ilvl}", value = current index + prev_was_list = False + + def flush_code_buf(): + """Flush the code buffer as a fenced code block.""" + if code_buf: + lines.append("```") + lines.extend(code_buf) + lines.append("```") + lines.append("") + code_buf.clear() + + def flush_toc_buf(): + """Flush the TOC buffer as a Markdown list.""" + if toc_buf: + if lines and lines[-1] != "": + lines.append("") + lines.append(_toc_entries_to_markdown(toc_buf)) + lines.append("") + toc_buf.clear() + + field_state = _FieldState() + for child in _flatten_body(doc.element.body): + tag = child.tag.split("}")[-1] + + if tag == "p": + para = Paragraph(child, doc) + + # TOC paragraph detection — before image extraction and other processing + toc_level = _is_toc_paragraph(para) + if toc_level is not None: + toc_text = _extract_toc_text(para) + if toc_text: + toc_anchor = _extract_toc_anchor(child) + toc_buf.append((toc_text, toc_anchor, toc_level)) + _update_field_state_for_paragraph(child, field_state) + continue + + # Non-TOC paragraph: flush any buffered TOC entries + flush_toc_buf() + + # Extract text box content for this paragraph element + pending_textbox_lines = [] + if extract_drawings: + tb_groups = _extract_textbox_paragraphs(child) + if tb_groups: + pending_textbox_lines = _textbox_paragraphs_to_markdown( + tb_groups, + doc, + body_font_size, + image_counter, + images, + content_width, + ) + + def _flush_textbox(): + if pending_textbox_lines: + if lines and lines[-1] != "": + lines.append("") + lines.extend(pending_textbox_lines) + lines.append("") + + # Extract images first + img_list = _extract_images_from_paragraph(para, doc, image_counter) + for filename, img_bytes, cx_emu in img_list: + flush_code_buf() + rel_path = f"images/{filename}" + images[rel_path] = img_bytes + if cx_emu and content_width: + pct = min(round(cx_emu / content_width * 100), 100) + lines.append(f'') + else: + lines.append(f'') + lines.append("") + + # Extract chart data tables + chart_tables = _extract_chart_tables(para, doc) + for chart_html in chart_tables: + flush_code_buf() + lines.append(chart_html) + lines.append("") + + text = para.text.strip() + + # Math formula detection — must check before skipping empty-text paragraphs + # (pure formula paragraphs have para.text == "") + if _paragraph_has_math(para): + flush_code_buf() + if prev_was_list: + lines.append("") + prev_was_list = False + math_md = _paragraph_math_to_markdown(para) + if math_md: + lines.append(math_md) + lines.append("") + _flush_textbox() + _update_field_state_for_paragraph(child, field_state) + continue + + if not text: + # Emit any _Toc bookmark anchors even for empty paragraphs. + # Headings whose visible text comes from list numbering (not w:t + # runs) have para.text == "" but still carry _Toc bookmarks that + # TOC entries link to. + for toc_bm in _extract_heading_toc_bookmarks(child): + lines.append(f'') + _flush_textbox() + if not img_list and not pending_textbox_lines: + if code_buf: + code_buf.append("") # preserve blank lines inside code blocks + elif lines and lines[-1] != "": + lines.append("") + _update_field_state_for_paragraph(child, field_state) + continue + + # Code paragraph: buffer it without heading/inline formatting + if _is_code_paragraph(para): + code_buf.append(para.text) + _flush_textbox() + _update_field_state_for_paragraph(child, field_state) + continue + + # Non-code paragraph: flush any buffered code first + flush_code_buf() + + level = _detect_heading_level(para, body_font_size) + inline = _runs_to_markdown(_iter_paragraph_items(para, field_state)) or text + + if level > 0: + # Strip outer **...** wrapping that headings may have inherited + clean = inline.strip() + if clean.startswith("**") and clean.endswith("**"): + clean = clean[2:-2] + # Heading lines cannot span multiple source lines; revert
    \n →
    + clean = clean.replace("
    \n", "
    ") + if prev_was_list: + lines.append("") + prev_was_list = False + # Add _Toc bookmark anchors if present (makes TOC links jumpable). + # A heading may have multiple _Toc bookmarks from repeated TOC updates. + for toc_bm in _extract_heading_toc_bookmarks(child): + lines.append(f'') + lines.append(f"{'#' * level} {clean}") + lines.append("") + else: + list_info = _get_list_info(para, numbering_map) + if list_info: + list_type, ilvl, num_id = list_info + indent = " " * ilvl + if list_type == "ordered": + counter_key = f"{num_id}-{ilvl}" + ol_counters[counter_key] = ol_counters.get(counter_key, 0) + 1 + prefix = f"{indent}{ol_counters[counter_key]}. " + else: + prefix = f"{indent}- " + if not prev_was_list and lines and lines[-1] != "": + lines.append("") + prev_was_list = True + # List item continuation lines need indentation; revert
    \n →
    + lines.append(prefix + inline.replace("
    \n", "
    ")) + else: + if prev_was_list: + lines.append("") + prev_was_list = False + # Reset ordered list counters when a list is interrupted + ol_counters.clear() + # Add _Toc bookmark anchors for non-heading paragraphs (e.g. Caption) + for toc_bm in _extract_heading_toc_bookmarks(child): + lines.append(f'') + lines.append(inline) + lines.append("") + + _flush_textbox() + + elif tag == "tbl": + flush_code_buf() + flush_toc_buf() + if prev_was_list: + lines.append("") + prev_was_list = False + ol_counters.clear() + table = Table(child, doc) + if lines and lines[-1] != "": + lines.append("") + lines.append( + _table_to_html(table, doc, image_counter, images, content_width) + ) + lines.append("") + + # Strip trailing blank lines + flush_code_buf() + flush_toc_buf() + while lines and lines[-1] == "": + lines.pop() + + return "\n".join(lines), images + + +def _extract_headers_footers(doc) -> tuple: + """Extract header and footer text from all document sections. + + Returns (header_lines: list[str], footer_lines: list[str]). + Deduplicates content and filters out page-number-only text. + """ + + def _collect_from_parts(parts, seen: set) -> list: + results = [] + for part in parts: + try: + if part.is_linked_to_previous: + continue + texts = [] + for para in part.paragraphs: + try: + items = list(_iter_paragraph_items(para)) + inline = _runs_to_markdown(items) + if not inline: + inline = para.text.strip() + if inline: + texts.append(inline) + except Exception: + pass + text = " ".join(texts) + # Filter: skip empty text, pure digits (page numbers), + # page-number patterns (e.g. "第 页", "第6页", "共 页"), and duplicates + if ( + text + and not text.strip().isdigit() + and not _RE_PAGE_ONLY.match(text.strip()) + and text not in seen + ): + seen.add(text) + results.append(text) + except Exception: + pass + return results + + header_lines = [] + footer_lines = [] + seen_headers: set = set() + seen_footers: set = set() + + # Check if the document uses different odd/even page headers/footers + try: + odd_even = doc.settings.odd_and_even_pages_header_footer + except Exception: + odd_even = False + + for section in doc.sections: + try: + # Collect headers + hdrs = [section.header] + if odd_even: + try: + hdrs.append(section.even_page_header) + except Exception: + pass + try: + if section.different_first_page_header_footer: + hdrs.append(section.first_page_header) + except Exception: + pass + header_lines.extend(_collect_from_parts(hdrs, seen_headers)) + + # Collect footers + ftrs = [section.footer] + if odd_even: + try: + ftrs.append(section.even_page_footer) + except Exception: + pass + try: + if section.different_first_page_header_footer: + ftrs.append(section.first_page_footer) + except Exception: + pass + footer_lines.extend(_collect_from_parts(ftrs, seen_footers)) + except Exception: + pass + + return header_lines, footer_lines + + +@default_registry.register +class DocxConverter(BaseConverter): + supported_extensions = ["docx"] + supported_mimetypes = [ + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + ] + + def convert_file(self, file_path: Path, **kwargs) -> ConvertResult: + try: + from docx import Document + except ImportError: + raise RuntimeError( + "DOCX conversion requires python-docx: pip install paddleocr[doc2md]" + ) + + extract_drawings = kwargs.pop("extract_drawings", True) + extract_headers_footers = kwargs.pop("extract_headers_footers", True) + doc = Document(str(file_path)) + md_text, images = _convert_body(doc, extract_drawings=extract_drawings) + + if extract_headers_footers: + header_lines, footer_lines = _extract_headers_footers(doc) + parts = [] + if header_lines: + parts.append("\n\n".join(header_lines)) + parts.append("") + parts.append(md_text) + if footer_lines: + parts.append("") + parts.append("---") + parts.append("\n\n".join(footer_lines)) + md_text = "\n".join(parts) + + return ConvertResult( + markdown=md_text, + images=images, + metadata={ + "format": "DOCX", + "paragraph_count": len(doc.paragraphs), + "table_count": len(doc.tables), + }, + ) diff --git a/paddleocr/_doc2md/converters/pptx.py b/paddleocr/_doc2md/converters/pptx.py new file mode 100644 index 0000000..910bd9e --- /dev/null +++ b/paddleocr/_doc2md/converters/pptx.py @@ -0,0 +1,563 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from pathlib import Path + +from ..base import BaseConverter, ConvertResult +from ..math import ( + extract_math_from_paragraph as _extract_math_from_paragraph, + paragraph_has_math as _paragraph_has_math, +) +from ..registry import default_registry + +# pptx XML namespace for DrawingML run properties +_A_NS = "http://schemas.openxmlformats.org/drawingml/2006/main" +_A = "{" + _A_NS + "}" +_A_STRIKE = f"{_A}strike" +_A_RPR = f"{_A}rPr" +_A_P = f"{_A}p" + +# Markup Compatibility namespace +_MC_NS = "http://schemas.openxmlformats.org/markup-compatibility/2006" +_MC = "{" + _MC_NS + "}" +_MC_CHOICE = f"{_MC}Choice" +_MC_ALT = f"{_MC}AlternateContent" + +# Chart namespace +_C_NS = "http://schemas.openxmlformats.org/drawingml/2006/chart" +_C = "{" + _C_NS + "}" + +# Chart type name lookup (used in _chart_to_html) +_CHART_TYPE_NAMES = { + 1: "Area Chart", # AREA + 2: "Area Chart", # AREA_STACKED + 4: "Line Chart", # LINE + 5: "Pie Chart", # PIE + 15: "Bubble Chart", # BUBBLE + 51: "Column Chart", # COLUMN_CLUSTERED + 52: "Column Chart", # COLUMN_STACKED + 53: "Column Chart", # COLUMN_STACKED_100 + 57: "Bar Chart", # BAR_CLUSTERED + 58: "Bar Chart", # BAR_STACKED + 65: "Line Chart", # LINE_MARKERS + -4120: "Doughnut Chart", # DOUGHNUT + -4151: "Radar Chart", # RADAR + -4169: "Scatter Chart", # XY_SCATTER +} + + +def _pptx_run_strike(run) -> bool: + """Return True if the run has strikethrough (sngStrike or dblStrike) set in XML.""" + try: + rPr = run._r.find(_A_RPR) + if rPr is not None: + val = rPr.get(_A_STRIKE) + return val in ("sngStrike", "dblStrike") + except Exception: + pass + return False + + +def _pptx_run_script(run) -> str: + """Return 'super', 'sub', or '' based on DrawingML baseline attribute. + + baseline > 0 -> superscript, baseline < 0 -> subscript. + baseline attribute is on with no namespace prefix. + """ + try: + rPr = run._r.find(_A_RPR) + if rPr is not None: + val = rPr.get("baseline") + if val is not None: + n = int(val) + if n > 0: + return "super" + if n < 0: + return "sub" + except Exception: + pass + return "" + + +def _escape_md_url(url: str) -> str: + """Escape parentheses in URL for Markdown link syntax.""" + return url.replace("(", "%28").replace(")", "%29") + + +def _classify_part(part: str) -> str: + """Classify a slide content part for grouping (heading/html/list/blockquote/other).""" + s = part.lstrip() + if s.startswith("##"): + return "heading" + if s.startswith("<"): + return "html" + if s.startswith("- "): + return "list" + if s.startswith(">"): + return "blockquote" + return "other" + + +def _format_run_segment( + seg: str, + bold: bool, + italic: bool, + underline: bool, + strikethrough: bool, + script: str, + url: str, +) -> str: + """Apply Markdown/HTML inline formatting to a text segment.""" + t = seg + if bold or italic or underline or strikethrough or script: + leading = len(t) - len(t.lstrip()) + trailing = len(t) - len(t.rstrip()) + prefix = t[:leading] if leading else "" + suffix = t[len(t) - trailing :] if trailing else "" + inner = t.strip() + if inner: + if strikethrough: + inner = f"~~{inner}~~" + if bold and italic: + inner = f"***{inner}***" + elif bold: + inner = f"**{inner}**" + elif italic: + inner = f"*{inner}*" + if underline: + inner = f"{inner}" + if script == "super": + inner = f"{inner}" + elif script == "sub": + inner = f"{inner}" + t = prefix + inner + suffix + elif underline and t: + # Pure whitespace + underline = fill-in line + # Replace spaces with NBSP so Markdown renderers preserve width + t = "" + "\u00a0" * len(t) + "" + if url: + escaped_url = _escape_md_url(url) + t = f"[{t}]({escaped_url})" + return t + + +@default_registry.register +class PptxConverter(BaseConverter): + supported_extensions = ["pptx"] + supported_mimetypes = [ + "application/vnd.openxmlformats-officedocument.presentationml.presentation" + ] + + def convert_file(self, file_path: Path, **kwargs) -> ConvertResult: + try: + from pptx import Presentation + from pptx.shapes.picture import Picture + except ImportError: + raise RuntimeError( + "PPTX conversion requires python-pptx: pip install paddleocr[doc2md]" + ) + try: + from pptx.enum.shapes import MSO_SHAPE_TYPE + except ImportError: + MSO_SHAPE_TYPE = None # type: ignore[assignment] + + prs = Presentation(str(file_path)) + slides_md = [] + images: dict = {} + image_counter = [0] + slide_width = prs.slide_width + self._Picture = Picture + self._MSO_SHAPE_TYPE = MSO_SHAPE_TYPE + + for slide in prs.slides: + slide_parts = [] + + # Process all shapes + for shape in slide.shapes: + self._process_shape( + shape, slide_parts, images, image_counter, slide_width, slide.part + ) + + # Handle math formulas inside mc:AlternateContent elements + # (python-pptx doesn't expose these as Shape objects) + for alt_content in slide._element.iter(_MC_ALT): + # Only look at mc:Choice (the preferred rendering path) + choice = alt_content.find(_MC_CHOICE) + if choice is None: + continue + for para_elem in choice.iter(_A_P): + if _paragraph_has_math(para_elem): + math_items = _extract_math_from_paragraph(para_elem) + for latex in math_items: + slide_parts.append(f"$$\n{latex}\n$$") + + # Speaker notes + if slide.has_notes_slide: + notes_text = slide.notes_slide.notes_text_frame.text.strip() + if notes_text: + slide_parts.append(f"\n> **Notes**: {notes_text}") + + # Group parts by content type and separate groups with blank lines + # to prevent HTML blocks from consuming adjacent list items + groups: list[list[str]] = [] + for part in slide_parts: + kind = _classify_part(part) + if groups and _classify_part(groups[-1][0]) == kind: + groups[-1].append(part) + else: + groups.append([part]) + + slides_md.append("\n\n".join("\n".join(g) for g in groups)) + + md_text = "\n\n---\n\n".join(slides_md) + + return ConvertResult( + markdown=md_text, + images=images, + metadata={ + "format": "PPTX", + "slide_count": len(prs.slides), + }, + ) + + def _process_shape( + self, shape, slide_parts, images, image_counter, slide_width, slide_part + ): + """Recursively process a shape: Picture, GroupShape, Chart, Table, or TextFrame.""" + Picture = self._Picture + MSO_SHAPE_TYPE = self._MSO_SHAPE_TYPE + + # 1. Picture + if isinstance(shape, Picture): + try: + img = shape.image + image_counter[0] += 1 + filename = f"image{image_counter[0]}.{img.ext}" + rel_path = f"images/{filename}" + images[rel_path] = img.blob + if shape.width and slide_width: + pct = min(round(shape.width / slide_width * 100), 100) + slide_parts.append(f'') + else: + slide_parts.append(f'') + except (ValueError, AttributeError): + pass + return + + # 2. GroupShape - recurse into child shapes + if MSO_SHAPE_TYPE and shape.shape_type == MSO_SHAPE_TYPE.GROUP: + try: + for child in shape.shapes: + self._process_shape( + child, + slide_parts, + images, + image_counter, + slide_width, + slide_part, + ) + except AttributeError: + pass + return + + # 3. Chart + if shape.has_chart: + slide_parts.append(self._chart_to_html(shape.chart)) + return + + # 4. Table + if shape.has_table: + slide_parts.append( + self._table_to_html(shape.table, slide_part, image_counter, images) + ) + return + + # 5. TextFrame + if shape.has_text_frame: + for paragraph in shape.text_frame.paragraphs: + # Check for math elements first + para_xml = paragraph._p + if _paragraph_has_math(para_xml): + math_items = _extract_math_from_paragraph(para_xml) + for latex in math_items: + slide_parts.append(f"$$\n{latex}\n$$") + continue + + parts = [] + for run in paragraph.runs: + t = run.text + if not t: + continue + try: + url = run.hyperlink.address + except Exception: + url = None + + bold = bool(run.font.bold) + italic = bool(run.font.italic) + underline = bool(run.font.underline) and not url + strikethrough = _pptx_run_strike(run) + script = _pptx_run_script(run) + + if "\n" in t: + segments = t.split("\n") + for j, seg in enumerate(segments): + if seg: + parts.append( + _format_run_segment( + seg, + bold, + italic, + underline, + strikethrough, + script, + url, + ) + ) + if j < len(segments) - 1: + parts.append("
    \n") + else: + parts.append( + _format_run_segment( + t, bold, italic, underline, strikethrough, script, url + ) + ) + text = "".join(parts).strip() + if not text: + continue + level = paragraph.level + indent = " " * level + # List item continuation lines need indentation; revert
    \n →
    + text = text.replace("
    \n", "
    ") + slide_parts.append(f"{indent}- {text}") + + def _chart_to_html(self, chart) -> str: + """Extract chart data as an HTML table.""" + try: + chart_type_val = chart.chart_type.value if chart.chart_type else 0 + chart_type_name = _CHART_TYPE_NAMES.get(chart_type_val, "Chart") + except Exception: + chart_type_name = "Chart" + + try: + title_text = "" + try: + title_text = chart.chart_title.text_frame.text.strip() + except Exception: + pass + + # Extract axis info from OOXML + chart_root = chart._element + + cat_ax_title = "" + for ax_tag in (f"{_C}catAx", f"{_C}dateAx"): + ax_el = chart_root.find(f".//{ax_tag}") + if ax_el is not None: + t_el = ax_el.find(f"{_C}title") + if t_el is not None: + texts = t_el.findall(f".//{_A}t") + cat_ax_title = "".join(el.text or "" for el in texts).strip() + break + + has_date_ax = chart_root.find(f".//{_C}dateAx") is not None + + val_ax_title = "" + val_ax_el = chart_root.find(f".//{_C}valAx") + if val_ax_el is not None: + t_el = val_ax_el.find(f"{_C}title") + if t_el is not None: + texts = t_el.findall(f".//{_A}t") + val_ax_title = "".join(el.text or "" for el in texts).strip() + + plot = chart.plots[0] + categories = list(plot.categories) if plot.categories else [] + series_list = list(plot.series) + + if not series_list: + return f"[{chart_type_name}]" + + # Convert Excel date serials to YYYY-MM-DD for date axes + if has_date_ax and categories: + from datetime import datetime, timedelta + + converted = [] + for c in categories: + try: + dt = datetime(1899, 12, 30) + timedelta(days=float(c)) + converted.append(dt.strftime("%Y-%m-%d")) + except (ValueError, TypeError): + converted.append(str(c) if c is not None else "") + categories = converted + + # Collect series names and values + series_names = [] + series_values = [] + for idx, series in enumerate(series_list): + try: + name = ( + (series.tx.text if series.tx else "") + or val_ax_title + or f"Series{idx+1}" + ) + except Exception: + name = val_ax_title or f"Series{idx+1}" + series_names.append(name) + try: + vals = [ + str(round(v, 4)) if v is not None else "" for v in series.values + ] + except Exception: + vals = [] + series_values.append(vals) + + # Build HTML table + html_parts = [""] + if title_text: + html_parts.append(f"") + + has_header = cat_ax_title or any(name for name in series_names) + if has_header: + html_parts.append("") + html_parts.append(f"") + for name in series_names: + html_parts.append(f"") + html_parts.append("") + + html_parts.append("") + if categories: + for i, cat in enumerate(categories): + html_parts.append(f"") + for vals in series_values: + v = vals[i] if i < len(vals) else "" + html_parts.append(f"") + html_parts.append("") + else: + max_len = max((len(v) for v in series_values), default=0) + for i in range(max_len): + html_parts.append(f"") + for vals in series_values: + v = vals[i] if i < len(vals) else "" + html_parts.append(f"") + html_parts.append("") + html_parts.append("
    {title_text}
    {cat_ax_title}{name}
    {cat}{v}
    Item{i+1}{v}
    ") + + return "\n".join(html_parts) + except Exception: + return f"[{chart_type_name}]" + + @staticmethod + def _table_to_html( + table, slide_part, image_counter_list: list, images: dict + ) -> str: + """Convert a PPTX table to an HTML table, handling merged cells and cell background images.""" + _BLIP_NS = "{http://schemas.openxmlformats.org/drawingml/2006/main}" + _REL_NS = ( + "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}" + ) + + visited: set[tuple[int, int]] = set() + html_parts = [""] + + for i, row in enumerate(table.rows): + html_parts.append("") + for j, cell in enumerate(row.cells): + if (i, j) in visited: + continue + tag = "th" if i == 0 else "td" + attrs = "" + if cell.is_merge_origin: + rs = cell.span_height + cs = cell.span_width + if cs > 1: + attrs += f' colspan="{cs}"' + if rs > 1: + attrs += f' rowspan="{rs}"' + for di in range(rs): + for dj in range(cs): + if (di, dj) != (0, 0): + visited.add((i + di, j + dj)) + + content_parts = [] + + # Extract cell background blip images + blips = cell._tc.findall(f".//{_BLIP_NS}blip") + for blip in blips: + r_embed = blip.get(f"{_REL_NS}embed") + if r_embed: + try: + image_part = slide_part.related_parts[r_embed] + image_counter_list[0] += 1 + ext = image_part.content_type.split("/")[-1] + filename = f"image{image_counter_list[0]}.{ext}" + rel_path = f"images/{filename}" + images[rel_path] = image_part.blob + content_parts.append( + f'' + ) + except (KeyError, AttributeError): + pass + + cell_text_parts = [] + for para in cell.text_frame.paragraphs: + # Check for math elements first + para_xml = para._p + if _paragraph_has_math(para_xml): + math_items = _extract_math_from_paragraph(para_xml) + for latex in math_items: + cell_text_parts.append(f"${latex}$") + continue + + run_parts = [] + for run in para.runs: + t = run.text or "" + if not t: + continue + try: + url = run.hyperlink.address + except Exception: + url = None + + bold = bool(run.font.bold) + italic = bool(run.font.italic) + underline = bool(run.font.underline) and not url + strikethrough = bool(run.font.strike) + script = _pptx_run_script(run) + + if bold: + t = f"{t}" + if italic: + t = f"{t}" + if underline: + t = f"{t}" + if strikethrough: + t = f"{t}" + if script == "super": + t = f"{t}" + elif script == "sub": + t = f"{t}" + + if url: + run_parts.append(f'{t}') + else: + run_parts.append(t) + cell_text_parts.append("".join(run_parts)) + text = "
    ".join(p for p in cell_text_parts if p.strip()) + if text: + content_parts.append(text) + cell_html = "
    ".join(content_parts) if content_parts else "" + + html_parts.append(f"<{tag}{attrs}>{cell_html}") + html_parts.append("") + + html_parts.append("
    ") + return "\n".join(html_parts) diff --git a/paddleocr/_doc2md/converters/xlsx.py b/paddleocr/_doc2md/converters/xlsx.py new file mode 100644 index 0000000..9b917bc --- /dev/null +++ b/paddleocr/_doc2md/converters/xlsx.py @@ -0,0 +1,339 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from io import BytesIO +from pathlib import Path +from typing import Optional + +from ..base import BaseConverter, ConvertResult +from ..math import ( + extract_math_from_paragraph as _extract_math_from_paragraph, + paragraph_has_math as _paragraph_has_math, +) +from ..registry import default_registry + +# DrawingML main namespace +_A = "{http://schemas.openxmlformats.org/drawingml/2006/main}" +# Markup Compatibility namespace +_MC = "{http://schemas.openxmlformats.org/markup-compatibility/2006}" +# OPC relationship type for drawings +_REL_DRAWING = ( + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing" +) +_REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships" + + +def _extract_drawing_math(zf, sheet_index: int) -> list: + """Extract LaTeX formulas from drawing layer of an xlsx sheet. + + Args: + zf: An already-opened zipfile.ZipFile object. + sheet_index: Zero-based sheet index. + """ + from lxml import etree + + results = [] + rels_path = f"xl/worksheets/_rels/sheet{sheet_index + 1}.xml.rels" + + # Read rels (file may not exist if sheet has no drawing) + try: + rels_data = zf.read(rels_path) + except KeyError: + return results + + # Find drawing relationship targets + rels_root = etree.fromstring(rels_data) + drawing_targets = [] + for rel in rels_root.findall(f"{{{_REL_NS}}}Relationship"): + if rel.get("Type") == _REL_DRAWING: + target = rel.get("Target", "") + # "../drawings/drawingX.xml" → "xl/drawings/drawingX.xml" + if target.startswith("../"): + target = "xl/" + target[3:] + elif not target.startswith("xl/"): + target = "xl/worksheets/" + target + drawing_targets.append(target) + + for drawing_path in drawing_targets: + try: + drawing_data = zf.read(drawing_path) + drawing_root = etree.fromstring(drawing_data) + except Exception: + continue # silently skip corrupted or missing drawing + + # Iterate over a:p paragraphs under mc:AlternateContent/mc:Choice + for alt in drawing_root.iter(f"{_MC}AlternateContent"): + choice = alt.find(f"{_MC}Choice") + if choice is None: + continue + for para in choice.iter(f"{_A}p"): + if _paragraph_has_math(para): + results.extend(_extract_math_from_paragraph(para)) + + return results + + +def _get_sheet_width_emu(ws, openpyxl_mod) -> int: + """Return the total column width of the worksheet in EMU. 1 char width ~ 7px, 1px = 9525 EMU.""" + CHAR_TO_EMU = 7 * 9525 + total = 0 + for col_idx in range(1, (ws.max_column or 1) + 1): + col_letter = openpyxl_mod.utils.get_column_letter(col_idx) + col_dim = ws.column_dimensions.get(col_letter) + if col_dim and col_dim.width is not None: + total += col_dim.width * CHAR_TO_EMU + else: + total += 8.43 * CHAR_TO_EMU # default column width + return int(total) + + +def _get_image_cx(anchor) -> Optional[int]: + """Return image display width in EMU for OneCellAnchor; return None for TwoCellAnchor.""" + try: + from openpyxl.drawing.spreadsheet_drawing import OneCellAnchor + + if isinstance(anchor, OneCellAnchor): + return anchor.ext.cx + except (AttributeError, ImportError): + pass + return None + + +def _find_data_bounds(ws, image_map, max_rows=None): + """Return (min_row, max_row, min_col, max_col) 1-based, or None if the sheet is empty.""" + min_r = min_c = float("inf") + max_r = max_c = 0 + + # Non-empty cells + for cell in ws._cells.values(): + if cell.value is not None: + r, c = cell.row, cell.column + if max_rows is not None and r > max_rows: + continue + min_r = min(min_r, r) + max_r = max(max_r, r) + min_c = min(min_c, c) + max_c = max(max_c, c) + + # Merged cell ranges + for mr in ws.merged_cells.ranges: + r1, r2 = mr.min_row, mr.max_row + if max_rows is not None: + r2 = min(r2, max_rows) + if r1 > r2: + continue + min_r = min(min_r, r1) + max_r = max(max_r, r2) + min_c = min(min_c, mr.min_col) + max_c = max(max_c, mr.max_col) + + # Image anchors (0-based -> 1-based) + for img_r0, img_c0 in image_map: + r, c = img_r0 + 1, img_c0 + 1 + if max_rows is not None and r > max_rows: + continue + min_r = min(min_r, r) + max_r = max(max_r, r) + min_c = min(min_c, c) + max_c = max(max_c, c) + + if max_r == 0: + return None + return (int(min_r), int(max_r), int(min_c), int(max_c)) + + +@default_registry.register +class XlsxConverter(BaseConverter): + supported_extensions = ["xlsx"] + supported_mimetypes = [ + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ] + + def convert_file(self, file_path: Path, **kwargs) -> ConvertResult: + try: + import openpyxl + from openpyxl.drawing.spreadsheet_drawing import ( + OneCellAnchor, + TwoCellAnchor, + ) + except ImportError: + raise RuntimeError( + "XLSX conversion requires openpyxl: pip install paddleocr[doc2md]" + ) + + sheet_name: Optional[str] = kwargs.get("sheet_name", None) + max_rows: Optional[int] = kwargs.get("max_rows", None) + extract_drawings: bool = kwargs.get("extract_drawings", True) + + import zipfile + + # read_only=False is required to access merged_cells + wb = openpyxl.load_workbook(str(file_path), read_only=False, data_only=True) + sheets_md = [] + images: dict = {} + image_counter = 0 + + target_sheets = [sheet_name] if sheet_name else wb.sheetnames + + with zipfile.ZipFile(str(file_path), "r") as _zf: + for sname in target_sheets: + if sname not in wb.sheetnames: + continue + ws = wb[sname] + + # Total sheet width in EMU, used for image percentage calculation + sheet_width_emu = _get_sheet_width_emu(ws, openpyxl) + + # Floating image map: (0-based row, 0-based col) -> [Image, ...] + image_map: dict = {} + for img in getattr(ws, "_images", []): + anchor = img.anchor + if isinstance(anchor, (OneCellAnchor, TwoCellAnchor)): + r, c = anchor._from.row, anchor._from.col + image_map.setdefault((r, c), []).append(img) + elif isinstance(anchor, str): + try: + from openpyxl.utils import coordinate_to_tuple + + r, c = coordinate_to_tuple(anchor) + image_map.setdefault((r - 1, c - 1), []).append(img) + except Exception: + pass + + # Merged cell map: (row, col) -> MergedCellRange + merge_map = {} + for mr in ws.merged_cells.ranges: + for r in range(mr.min_row, mr.max_row + 1): + for c in range(mr.min_col, mr.max_col + 1): + merge_map[(r, c)] = mr + + # Trim surrounding empty rows/columns + bounds = _find_data_bounds(ws, image_map, max_rows) + if bounds is None: + continue + data_min_row, data_max_row, data_min_col, data_max_col = bounds + + html_parts = [""] + for row_idx in range(data_min_row, data_max_row + 1): + html_parts.append("") + for col_idx in range(data_min_col, data_max_col + 1): + cell = ws.cell(row_idx, col_idx) + mr = merge_map.get((row_idx, col_idx)) + # Skip non-origin cells in a merged range + if mr and (row_idx, col_idx) != (mr.min_row, mr.min_col): + continue + tag = "th" if row_idx == data_min_row else "td" + attrs = "" + if mr: + cs = ( + min(mr.max_col, data_max_col) + - max(mr.min_col, data_min_col) + + 1 + ) + rs = ( + min(mr.max_row, data_max_row) + - max(mr.min_row, data_min_row) + + 1 + ) + if cs > 1: + attrs += f' colspan="{cs}"' + if rs > 1: + attrs += f' rowspan="{rs}"' + + # Cell text + value = cell.value + text = str(value) if value is not None else "" + # Cell-level font formatting (bold/italic/underline/strikethrough) + if text: + try: + font = cell.font + if font.bold: + text = f"{text}" + if font.italic: + text = f"{text}" + if font.underline: + text = f"{text}" + if font.strike: + text = f"{text}" + vert_align = font.vertAlign + if vert_align == "superscript": + text = f"{text}" + elif vert_align == "subscript": + text = f"{text}" + except Exception: + pass + # Hyperlink wrapping + if text: + try: + hl = cell.hyperlink + if hl and hl.target: + text = f'{text}' + except Exception: + pass + + # Floating images + cell_images = image_map.get((row_idx - 1, col_idx - 1), []) + img_html = "" + for img_obj in cell_images: + image_counter += 1 + ext = (img_obj.format or "png").lower() + filename = f"image{image_counter}.{ext}" + rel_path = f"images/{filename}" + try: + ref = img_obj.ref + if isinstance(ref, BytesIO): + ref.seek(0) + images[rel_path] = ref.read() + else: + images[rel_path] = img_obj._data() + cx_emu = _get_image_cx(img_obj.anchor) + if cx_emu and sheet_width_emu: + pct = min( + round(cx_emu / sheet_width_emu * 100), 100 + ) + img_html += ( + f'' + ) + else: + img_html += f'' + except Exception: + pass + + cell_content = img_html + text if img_html else text + html_parts.append(f"<{tag}{attrs}>{cell_content}") + html_parts.append("") + html_parts.append("
    ") + + table_html = "\n".join(html_parts) + + sheet_parts = [f"## {sname}\n\n{table_html}"] + if extract_drawings: + sheet_idx = list(wb.sheetnames).index(sname) + for latex in _extract_drawing_math(_zf, sheet_idx): + sheet_parts.append(f"\n$$\n{latex}\n$$") + sheets_md.append("\n".join(sheet_parts)) + + sheet_names = list(wb.sheetnames) + wb.close() + + md_text = "\n\n".join(sheets_md) + + return ConvertResult( + markdown=md_text, + images=images, + metadata={ + "format": "XLSX", + "sheet_count": len(sheet_names), + "sheet_names": sheet_names, + }, + ) diff --git a/paddleocr/_doc2md/core.py b/paddleocr/_doc2md/core.py new file mode 100644 index 0000000..f0ff2b0 --- /dev/null +++ b/paddleocr/_doc2md/core.py @@ -0,0 +1,76 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from pathlib import Path +from typing import Union, Optional + +from .base import ConvertResult +from .registry import default_registry + +# Trigger registration of all built-in converters +from . import converters # noqa: F401 + + +def convert( + source: Union[str, Path], + *, + output: Optional[Union[str, Path]] = None, + **kwargs, +) -> ConvertResult: + """ + Convert an office document to Markdown. + + Args: + source: Path to the source file. + output: Optional output file path. If provided, Markdown is written there. + **kwargs: Extra arguments forwarded to the specific converter. + + Returns: + ConvertResult object. + + Examples: + >>> from paddleocr import doc2md_convert + >>> result = doc2md_convert("report.docx") + >>> print(result.markdown) + """ + file_path = Path(source) + + if not file_path.exists(): + raise FileNotFoundError(f"File not found: {file_path}") + + converter = default_registry.get_converter(file_path) + + try: + result = converter.convert_file(file_path, **kwargs) + except Exception as e: + if isinstance(e, (FileNotFoundError, ValueError, RuntimeError)): + raise + raise RuntimeError(f"Failed to convert {file_path.name}: {e}") from e + + if output: + output_path = Path(output) + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(result.markdown, encoding="utf-8") + if result.images: + images_dir = output_path.parent / "images" + images_dir.mkdir(exist_ok=True) + for rel_path, img_bytes in result.images.items(): + img_file = output_path.parent / rel_path + img_file.write_bytes(img_bytes) + + return result + + +def supported_formats() -> list[str]: + """Return a list of supported file extensions.""" + return default_registry.supported_extensions() diff --git a/paddleocr/_doc2md/math/__init__.py b/paddleocr/_doc2md/math/__init__.py new file mode 100644 index 0000000..4fd73ec --- /dev/null +++ b/paddleocr/_doc2md/math/__init__.py @@ -0,0 +1,84 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from .omml import OMML_NS, oMath2Latex + +# XML namespace constants shared by converters for DrawingML math extraction +_M = OMML_NS # already includes braces: "{http://...}" +_A14 = "{http://schemas.microsoft.com/office/drawing/2010/main}" + + +def convert_omath(omath_element) -> str: + """Convert an m:oMath lxml element to LaTeX string. Returns empty string on failure.""" + try: + return str(oMath2Latex(omath_element)).strip() + except Exception: + return "" + + +def paragraph_has_math(para_element) -> bool: + """Check if an XML element contains OMML math (a14:m or m:oMath).""" + return ( + para_element.find(f".//{_A14}m") is not None + or para_element.find(f".//{_M}oMath") is not None + ) + + +def extract_math_from_paragraph(para_element) -> list: + """Extract LaTeX strings from math elements in a DrawingML paragraph XML element. + + Handles three nesting patterns: + 1. a14:m → m:oMath (or m:oMathPara → m:oMath) + 2. Direct m:oMathPara → m:oMath (not wrapped in a14:m) + 3. Direct m:oMath (not inside a14:m or m:oMathPara) + """ + results = [] + # a14:m wraps m:oMathPara or m:oMath + for a14m in para_element.findall(f".//{_A14}m"): + found_omath = False + for omath in a14m.findall(f".//{_M}oMath"): + latex = convert_omath(omath) + if latex: + results.append(latex) + found_omath = True + # No oMath inside this a14:m? Try the a14:m element itself + if not found_omath: + latex = convert_omath(a14m) + if latex: + results.append(latex) + # Direct m:oMathPara / m:oMath not wrapped in a14:m + for omath_para in para_element.findall(f".//{_M}oMathPara"): + parent = omath_para.getparent() + if parent is not None and parent.tag == f"{_A14}m": + continue # already handled above (oMathPara is inside a14:m) + for omath in omath_para.findall(f"{_M}oMath"): + latex = convert_omath(omath) + if latex: + results.append(latex) + for omath in para_element.findall(f".//{_M}oMath"): + parent = omath.getparent() + if parent is not None and parent.tag in (f"{_A14}m", f"{_M}oMathPara"): + continue # already handled + latex = convert_omath(omath) + if latex: + results.append(latex) + return results + + +__all__ = [ + "oMath2Latex", + "OMML_NS", + "convert_omath", + "paragraph_has_math", + "extract_math_from_paragraph", +] diff --git a/paddleocr/_doc2md/math/latex_dict.py b/paddleocr/_doc2md/math/latex_dict.py new file mode 100644 index 0000000..cd65132 --- /dev/null +++ b/paddleocr/_doc2md/math/latex_dict.py @@ -0,0 +1,329 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +LaTeX symbol dictionaries for OMML conversion. + +Adapted from https://github.com/xiilei/dwml/blob/master/dwml/latex_dict.py +via MinerU 3.0.0 (MIT License) +""" + +CHARS = ("{", "}", "_", "^", "#", "&", "$", "%") + +BLANK = "" +BACKSLASH = "\\" +ALN = "&" + +CHR = { + # Unicode : Latex Math Symbols + # Top accents + "\u0300": "\\grave{{{0}}}", + "\u0301": "\\acute{{{0}}}", + "\u0302": "\\hat{{{0}}}", + "\u0303": "\\tilde{{{0}}}", + "\u0304": "\\bar{{{0}}}", + "\u0305": "\\overbar{{{0}}}", + "\u0306": "\\breve{{{0}}}", + "\u0307": "\\dot{{{0}}}", + "\u0308": "\\ddot{{{0}}}", + "\u0309": "\\ovhook{{{0}}}", + "\u030a": "\\ocirc{{{0}}}", + "\u030c": "\\check{{{0}}}", + "\u0310": "\\candra{{{0}}}", + "\u0312": "\\oturnedcomma{{{0}}}", + "\u0315": "\\ocommatopright{{{0}}}", + "\u031a": "\\droang{{{0}}}", + "\u0338": "\\not{{{0}}}", + "\u20d0": "\\leftharpoonaccent{{{0}}}", + "\u20d1": "\\rightharpoonaccent{{{0}}}", + "\u20d2": "\\vertoverlay{{{0}}}", + "\u20d6": "\\overleftarrow{{{0}}}", + "\u20d7": "\\vec{{{0}}}", + "\u20db": "\\dddot{{{0}}}", + "\u20dc": "\\ddddot{{{0}}}", + "\u20e1": "\\overleftrightarrow{{{0}}}", + "\u20e7": "\\annuity{{{0}}}", + "\u20e9": "\\widebridgeabove{{{0}}}", + "\u20f0": "\\asteraccent{{{0}}}", + # Bottom accents + "\u0330": "\\wideutilde{{{0}}}", + "\u0331": "\\underbar{{{0}}}", + "\u20e8": "\\threeunderdot{{{0}}}", + "\u20ec": "\\underrightharpoondown{{{0}}}", + "\u20ed": "\\underleftharpoondown{{{0}}}", + "\u20ee": "\\underleftarrow{{{0}}}", + "\u20ef": "\\underrightarrow{{{0}}}", + # Over | group + "\u23b4": "\\overbracket{{{0}}}", + "\u23dc": "\\overparen{{{0}}}", + "\u23de": "\\overbrace{{{0}}}", + # Under| group + "\u23b5": "\\underbracket{{{0}}}", + "\u23dd": "\\underparen{{{0}}}", + "\u23df": "\\underbrace{{{0}}}", +} + +CHR_BO = { + # Big operators, + "\u2140": "\\Bbbsum", + "\u220f": "\\prod", + "\u2210": "\\coprod", + "\u2211": "\\sum", + "\u222b": "\\int", + "\u222c": "\\iint", + "\u222d": "\\iiint", + "\u222e": "\\oint", + "\u222f": "\\oiint", + "\u2230": "\\oiiint", + "\u22c0": "\\bigwedge", + "\u22c1": "\\bigvee", + "\u22c2": "\\bigcap", + "\u22c3": "\\bigcup", + "\u2a00": "\\bigodot", + "\u2a01": "\\bigoplus", + "\u2a02": "\\bigotimes", +} + +T = { + # Whitespace characters + " ": " ", # NON-BREAKING SPACE (U+00A0) — pylatexenc maps this to "~" (text-mode), + # which escape_latex would mangle to "\~" (invalid in math mode); + # use a plain space instead. + # Greek letters + "\U0001d6fc": "\\alpha ", + "\U0001d6fd": "\\beta ", + "\U0001d6fe": "\\gamma ", + "\U0001d6ff": "\\delta ", + "\U0001d700": "\\epsilon ", + "\U0001d701": "\\zeta ", + "\U0001d702": "\\eta ", + "\U0001d703": "\\theta ", + "\U0001d704": "\\iota ", + "\U0001d705": "\\kappa ", + "\U0001d706": "\\lambda ", + "\U0001d707": "\\mu ", + "\U0001d708": "\\nu ", + "\U0001d709": "\\xi ", + "\U0001d70a": "\\omicron ", + "\U0001d70b": "\\pi ", + "\U0001d70c": "\\rho ", + "\U0001d70d": "\\varsigma ", + "\U0001d70e": "\\sigma ", + "\U0001d70f": "\\tau ", + "\U0001d710": "\\upsilon ", + "\U0001d711": "\\phi ", + "\U0001d712": "\\chi ", + "\U0001d713": "\\psi ", + "\U0001d714": "\\omega ", + "\U0001d715": "\\partial ", + "\U0001d716": "\\varepsilon ", + "\U0001d717": "\\vartheta ", + "\U0001d718": "\\varkappa ", + "\U0001d719": "\\varphi ", + "\U0001d71a": "\\varrho ", + "\U0001d71b": "\\varpi ", + # Relation symbols + "\u2190": "\\leftarrow ", + "\u2191": "\\uparrow ", + "\u2192": "\\rightarrow ", + "\u2193": "\\downarrow ", + "\u2194": "\\leftrightarrow ", + "\u2195": "\\updownarrow ", + "\u2196": "\\nwarrow ", + "\u2197": "\\nearrow ", + "\u2198": "\\searrow ", + "\u2199": "\\swarrow ", + "\u2026": "\\ldots ", # HORIZONTAL ELLIPSIS (…) + "\u22ee": "\\vdots ", + "\u22ef": "\\cdots ", + "\u22f0": "\\adots ", + "\u22f1": "\\ddots ", + "\u2260": "\\ne ", + "\u2264": "\\leq ", + "\u2265": "\\geq ", + "\u2266": "\\leqq ", + "\u2267": "\\geqq ", + "\u2268": "\\lneqq ", + "\u2269": "\\gneqq ", + "\u226a": "\\ll ", + "\u226b": "\\gg ", + "\u2208": "\\in ", + "\u2209": "\\notin ", + "\u220b": "\\ni ", + "\u220c": "\\nni ", + # Ordinary symbols + "\u221e": "\\infty ", + # Binary relations + "\u00b1": "\\pm ", + "\u2213": "\\mp ", + # Characters whose pylatexenc text-mode mappings are invalid in math environments + "\u00f0": "\\eth ", + "\u0131": "\\imath ", + "\u2127": "\\mho ", + "\u212e": "e", + "\u00c5": "\\mathring{A} ", + # Multiplication/division operators + "\u00b7": "\\cdot ", + "\u22c5": "\\cdot ", + "\u2219": "\\bullet ", + "\u00d7": "\\times ", + "\u00f7": "\\div ", + "\u2212": "-", + # Degree / prime + "\u00b0": "\\circ ", + "\u2032": "'", + "\u2033": "''", + # Superscript digits + "\u00b2": "2", + "\u00b3": "3", + "\u00b9": "1", + # Big operators as plain text characters + "\u222f": "\\oiint ", + "\u2230": "\\oiiint ", + "\u2231": "\u2231", + "\u2232": "\u2232", + "\u2233": "\u2233", + "\u2a00": "\\bigodot ", + "\u2a01": "\\bigoplus ", + "\u2a02": "\\bigotimes ", + "\u2a03": "\u2a03", + "\u2a04": "\u2a04", + # Wave arrows + "\u219c": "\u219c", + "\u219d": "\u219d", + # Italic, Latin, uppercase + "\U0001d434": "A", + "\U0001d435": "B", + "\U0001d436": "C", + "\U0001d437": "D", + "\U0001d438": "E", + "\U0001d439": "F", + "\U0001d43a": "G", + "\U0001d43b": "H", + "\U0001d43c": "I", + "\U0001d43d": "J", + "\U0001d43e": "K", + "\U0001d43f": "L", + "\U0001d440": "M", + "\U0001d441": "N", + "\U0001d442": "O", + "\U0001d443": "P", + "\U0001d444": "Q", + "\U0001d445": "R", + "\U0001d446": "S", + "\U0001d447": "T", + "\U0001d448": "U", + "\U0001d449": "V", + "\U0001d44a": "W", + "\U0001d44b": "X", + "\U0001d44c": "Y", + "\U0001d44d": "Z", + # Italic, Latin, lowercase + "\U0001d44e": "a", + "\U0001d44f": "b", + "\U0001d450": "c", + "\U0001d451": "d", + "\U0001d452": "e", + "\U0001d453": "f", + "\U0001d454": "g", + "\U0001d456": "i", + "\U0001d457": "j", + "\U0001d458": "k", + "\U0001d459": "l", + "\U0001d45a": "m", + "\U0001d45b": "n", + "\U0001d45c": "o", + "\U0001d45d": "p", + "\U0001d45e": "q", + "\U0001d45f": "r", + "\U0001d460": "s", + "\U0001d461": "t", + "\U0001d462": "u", + "\U0001d463": "v", + "\U0001d464": "w", + "\U0001d465": "x", + "\U0001d466": "y", + "\U0001d467": "z", +} + +FUNC = { + "sin": "\\sin({fe})", + "cos": "\\cos({fe})", + "tan": "\\tan({fe})", + "arcsin": "\\arcsin({fe})", + "arccos": "\\arccos({fe})", + "arctan": "\\arctan({fe})", + "arccot": "\\arccot({fe})", + "sinh": "\\sinh({fe})", + "cosh": "\\cosh({fe})", + "tanh": "\\tanh({fe})", + "coth": "\\coth({fe})", + "sec": "\\sec({fe})", + "csc": "\\csc({fe})", + "mod": "\\mod {fe}", + "max": "\\max({fe})", + "min": "\\min({fe})", +} + +FUNC_PLACE = "{fe}" + +BRK = "\\\\" + +CHR_DEFAULT = { + "ACC_VAL": "\\hat{{{0}}}", +} + +POS = { + "top": "\\overline{{{0}}}", # not sure + "bot": "\\underline{{{0}}}", +} + +POS_DEFAULT = { + "BAR_VAL": "\\overline{{{0}}}", +} + +SUB = "_{{{0}}}" + +SUP = "^{{{0}}}" + +F = { + "bar": "\\frac{{{num}}}{{{den}}}", + "skw": r"^{{{num}}}/_{{{den}}}", + "noBar": "\\genfrac{{}}{{}}{{0pt}}{{}}{{{num}}}{{{den}}}", + "lin": "{{{num}}}/{{{den}}}", +} +F_DEFAULT = "\\frac{{{num}}}{{{den}}}" + +D = "\\left{left}{text}\\right{right}" + +D_DEFAULT = { + "left": "(", + "right": ")", + "null": ".", +} + +RAD = "\\sqrt[{deg}]{{{text}}}" +RAD_DEFAULT = "\\sqrt{{{text}}}" +ARR = "\\begin{{array}}{{c}}{text}\\end{{array}}" + +LIM_FUNC = { + "lim": "\\lim_{{{lim}}}", + "max": "\\max_{{{lim}}}", + "min": "\\min_{{{lim}}}", +} + +LIM_TO = ("\\rightarrow", "\\to") + +LIM_UPP = "\\overset{{{lim}}}{{{text}}}" + +M = "\\begin{{matrix}}{text}\\end{{matrix}}" diff --git a/paddleocr/_doc2md/math/omml.py b/paddleocr/_doc2md/math/omml.py new file mode 100644 index 0000000..8fadc1c --- /dev/null +++ b/paddleocr/_doc2md/math/omml.py @@ -0,0 +1,482 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Office Math Markup Language (OMML) to LaTeX converter. + +Adapted from https://github.com/xiilei/dwml/blob/master/dwml/omml.py +via MinerU 3.0.0 (MIT License) +""" + +import logging +import re + +import lxml.etree as ET + +from .latex_dict import ( + ALN, + ARR, + BACKSLASH, + BLANK, + BRK, + CHARS, + CHR, + CHR_BO, + CHR_DEFAULT, + D_DEFAULT, + F_DEFAULT, + FUNC, + FUNC_PLACE, + LIM_FUNC, + LIM_TO, + LIM_UPP, + POS, + POS_DEFAULT, + RAD, + RAD_DEFAULT, + SUB, + SUP, + D, + F, + M, + T, +) + +logger = logging.getLogger(__name__) + +OMML_NS = "{http://schemas.openxmlformats.org/officeDocument/2006/math}" + +# Mapping from OMML values to LaTeX math font commands. +SCR_TO_LATEX = { + "script": "\\mathscr{{{0}}}", + "fraktur": "\\mathfrak{{{0}}}", + "double-struck": "\\mathbb{{{0}}}", + "sans-serif": "\\mathsf{{{0}}}", + "monospace": "\\mathtt{{{0}}}", +} + + +def escape_latex(strs): + last = None + new_chr = [] + strs = strs.replace(r"\\", "\\") + for c in strs: + if (c in CHARS) and (last != BACKSLASH): + new_chr.append(BACKSLASH + c) + else: + new_chr.append(c) + last = c + return BLANK.join(new_chr) + + +def get_val(key, default=None, store=CHR): + if key is not None: + return key if not store else store.get(key, key) + else: + return default + + +class Tag2Method: + def call_method(self, elm, stag=None): + getmethod = self.tag2meth.get + if stag is None: + stag = elm.tag.replace(OMML_NS, "") + method = getmethod(stag) + if method: + return method(self, elm) + else: + return None + + def process_children_list(self, elm, include=None): + """ + process children of the elm,return iterable + """ + for _e in list(elm): + if OMML_NS not in _e.tag: + continue + stag = _e.tag.replace(OMML_NS, "") + if include and (stag not in include): + continue + t = self.call_method(_e, stag=stag) + if t is None: + t = self.process_unknow(_e, stag) + if t is None: + continue + yield (stag, t, _e) + + def process_children_dict(self, elm, include=None): + """ + process children of the elm,return dict + """ + latex_chars = dict() + for stag, t, e in self.process_children_list(elm, include): + latex_chars[stag] = t + return latex_chars + + def process_children(self, elm, include=None): + """ + process children of the elm,return string + """ + return BLANK.join( + ( + t if not isinstance(t, Tag2Method) else str(t) + for stag, t, e in self.process_children_list(elm, include) + ) + ) + + def process_unknow(self, elm, stag): + return None + + +class Pr(Tag2Method): + text = "" + + __val_tags = ("chr", "pos", "begChr", "endChr", "type") + + __innerdict = None # can't use the __dict__ + + """ common properties of element""" + + def __init__(self, elm): + self.__innerdict = {} + self.text = self.process_children(elm) + + def __str__(self): + return self.text + + def __getattr__(self, name): + return self.__innerdict.get(name, None) + + def do_brk(self, elm): + self.__innerdict["brk"] = BRK + return BRK + + def do_common(self, elm): + stag = elm.tag.replace(OMML_NS, "") + if stag in self.__val_tags: + t = elm.get(f"{OMML_NS}val") + self.__innerdict[stag] = t + return None + + tag2meth = { + "brk": do_brk, + "chr": do_common, + "pos": do_common, + "begChr": do_common, + "endChr": do_common, + "type": do_common, + } + + +class oMath2Latex(Tag2Method): + """ + Convert oMath element of omml to latex + """ + + _t_dict = T + + __direct_tags = ("box", "sSub", "sSup", "sSubSup", "num", "den", "deg", "e") + + _encoder = None # cached UnicodeToLatexEncoder instance (class-level) + + def __init__(self, element): + if oMath2Latex._encoder is None: + # Delayed import: pylatexenc is an optional dependency + try: + from pylatexenc.latexencode import UnicodeToLatexEncoder + except ImportError: + raise RuntimeError( + "pylatexenc is required for math formula conversion. " + "Install it with: pip install pylatexenc" + ) + oMath2Latex._encoder = UnicodeToLatexEncoder( + replacement_latex_protection="braces-all", + unknown_char_policy="keep", + unknown_char_warning=False, + ) + self.u = oMath2Latex._encoder + self._latex = self.process_children(element) + + def __str__(self): + return self.latex.replace(" ", " ") + + def process_unknow(self, elm, stag): + if stag in self.__direct_tags: + return self.process_children(elm) + elif stag[-2:] == "Pr": + return Pr(elm) + else: + return None + + @property + def latex(self): + return self._latex + + def do_acc(self, elm): + """ + the accent function + """ + c_dict = self.process_children_dict(elm) + latex_s = get_val( + c_dict["accPr"].chr, default=CHR_DEFAULT.get("ACC_VAL"), store=CHR + ) + return latex_s.format(c_dict["e"]) + + def do_bar(self, elm): + """ + the bar function + """ + c_dict = self.process_children_dict(elm) + pr = c_dict["barPr"] + latex_s = get_val(pr.pos, default=POS_DEFAULT.get("BAR_VAL"), store=POS) + return pr.text + latex_s.format(c_dict["e"]) + + def do_d(self, elm): + """ + the delimiter object + """ + c_dict = self.process_children_dict(elm) + pr = c_dict["dPr"] + null = D_DEFAULT.get("null") + + s_val = get_val(pr.begChr, default=D_DEFAULT.get("left"), store=T) + e_val = get_val(pr.endChr, default=D_DEFAULT.get("right"), store=T) + delim = pr.text + D.format( + left=null if not s_val else escape_latex(s_val), + text=c_dict["e"], + right=null if not e_val else escape_latex(e_val), + ) + return delim + + def do_sub(self, elm): + text = self.process_children(elm) + return SUB.format(text) + + def do_sup(self, elm): + text = self.process_children(elm) + return SUP.format(text) + + def do_f(self, elm): + """ + the fraction object + """ + c_dict = self.process_children_dict(elm) + pr = c_dict.get("fPr") + if pr is None: + logger.debug("Missing fPr element in fraction, using default formatting") + latex_s = F_DEFAULT + return latex_s.format( + num=c_dict.get("num"), + den=c_dict.get("den"), + ) + latex_s = get_val(pr.type, default=F_DEFAULT, store=F) + return pr.text + latex_s.format(num=c_dict.get("num"), den=c_dict.get("den")) + + def do_func(self, elm): + """ + the Function-Apply object (Examples:sin cos) + """ + c_dict = self.process_children_dict(elm) + func_name = c_dict.get("fName") + return func_name.replace(FUNC_PLACE, c_dict.get("e")) + + def do_fname(self, elm): + """ + the func name + """ + latex_chars = [] + for stag, t, e in self.process_children_list(elm): + if stag == "r": + if FUNC.get(t): + latex_chars.append(FUNC[t]) + else: + logger.warning( + "Function not supported, will default to text: %s", t + ) + if isinstance(t, str): + latex_chars.append(t) + elif isinstance(t, str): + latex_chars.append(t) + t = BLANK.join(latex_chars) + return t if FUNC_PLACE in t else t + FUNC_PLACE # do_func will replace this + + def do_groupchr(self, elm): + """ + the Group-Character object + """ + c_dict = self.process_children_dict(elm) + pr = c_dict["groupChrPr"] + latex_s = get_val(pr.chr) + return pr.text + latex_s.format(c_dict["e"]) + + def do_rad(self, elm): + """ + the radical object + """ + c_dict = self.process_children_dict(elm) + text = c_dict.get("e") + deg_text = c_dict.get("deg") + if deg_text: + return RAD.format(deg=deg_text, text=text) + else: + return RAD_DEFAULT.format(text=text) + + def do_eqarr(self, elm): + """ + the Array object. + """ + rows = [t for stag, t, e in self.process_children_list(elm, include=("e",))] + + if len(rows) == 1: + row = rows[0] + tag_match = re.search(r"\\#\s*\(([^)]*)\)\s*$", row) + if tag_match: + formula = row[: tag_match.start()].rstrip() + tag_content = tag_match.group(1) + return f"{formula}\\tag{{{tag_content}}}" + return row + + return ARR.format(text=BRK.join(rows)) + + def do_limlow(self, elm): + """ + the Lower-Limit object + """ + t_dict = self.process_children_dict(elm, include=("e", "lim")) + latex_s = LIM_FUNC.get(t_dict["e"]) + if not latex_s: + raise RuntimeError("Not support lim {}".format(t_dict["e"])) + else: + return latex_s.format(lim=t_dict.get("lim")) + + def do_limupp(self, elm): + """ + the Upper-Limit object + """ + t_dict = self.process_children_dict(elm, include=("e", "lim")) + return LIM_UPP.format(lim=t_dict.get("lim"), text=t_dict.get("e")) + + def do_lim(self, elm): + """ + the lower limit of the limLow object and the upper limit of the limUpp function + """ + return self.process_children(elm).replace(LIM_TO[0], LIM_TO[1]) + + def do_m(self, elm): + """ + the Matrix object + """ + rows = [] + for stag, t, e in self.process_children_list(elm): + if stag == "mPr": + pass + elif stag == "mr": + rows.append(t) + return M.format(text=BRK.join(rows)) + + def do_mr(self, elm): + """ + a single row of the matrix m + """ + return ALN.join( + [t for stag, t, e in self.process_children_list(elm, include=("e",))] + ) + + def do_nary(self, elm): + """ + the n-ary object + """ + res = [] + bo = "" + for stag, t, e in self.process_children_list(elm): + if stag == "naryPr": + bo = get_val(t.chr, default="\\int", store=CHR_BO) + else: + res.append(t) + return bo + BLANK.join(res) + + def process_unicode(self, s): + t_result = self._t_dict.get(s) + if t_result is not None: + return t_result + + out_latex_str = self.u.unicode_to_latex(s) + + if ( + s.startswith("{") is False + and out_latex_str.startswith("{") + and s.endswith("}") is False + and out_latex_str.endswith("}") + ): + out_latex_str = f" {out_latex_str[1:-1]} " + + if "ensuremath" in out_latex_str: + out_latex_str = out_latex_str.replace("\\ensuremath{", " ") + out_latex_str = out_latex_str.replace("}", " ") + + return out_latex_str + + def do_r(self, elm): + """ + Get text from 'r' element,And try convert them to latex symbols + """ + _str = [] + _base_str = [] + found_text = elm.findtext(f"./{OMML_NS}t") + if found_text: + for s in found_text: + out_latex_str = self.process_unicode(s) + _str.append(out_latex_str) + _base_str.append(s) + + proc_str = escape_latex(BLANK.join(_str)) + base_proc_str = BLANK.join(_base_str) + + if "{" not in base_proc_str and "\\{" in proc_str: + proc_str = proc_str.replace("\\{", "{") + + if "}" not in base_proc_str and "\\}" in proc_str: + proc_str = proc_str.replace("\\}", "}") + + # Handle math font style + rPr = elm.find(f"{OMML_NS}rPr") + if rPr is not None: + scr_elem = rPr.find(f"{OMML_NS}scr") + if scr_elem is not None: + scr_val = scr_elem.get(f"{OMML_NS}val") + latex_template = SCR_TO_LATEX.get(scr_val) + if latex_template and proc_str.strip(): + proc_str = latex_template.format(proc_str.strip()) + + return proc_str + + tag2meth = { + "acc": do_acc, + "r": do_r, + "bar": do_bar, + "sub": do_sub, + "sup": do_sup, + "f": do_f, + "func": do_func, + "fName": do_fname, + "groupChr": do_groupchr, + "d": do_d, + "rad": do_rad, + "eqArr": do_eqarr, + "limLow": do_limlow, + "limUpp": do_limupp, + "lim": do_lim, + "m": do_m, + "mr": do_mr, + "nary": do_nary, + } diff --git a/paddleocr/_doc2md/registry.py b/paddleocr/_doc2md/registry.py new file mode 100644 index 0000000..829d725 --- /dev/null +++ b/paddleocr/_doc2md/registry.py @@ -0,0 +1,54 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import mimetypes +from pathlib import Path +from typing import Type + +from .base import BaseConverter + + +class ConverterRegistry: + """Registry mapping file extensions and MIME types to converter classes.""" + + def __init__(self): + self._ext_map: dict[str, Type[BaseConverter]] = {} + self._mime_map: dict[str, Type[BaseConverter]] = {} + + def register(self, converter_cls: Type[BaseConverter]) -> Type[BaseConverter]: + """Register a converter class; can be used as a decorator.""" + for ext in converter_cls.supported_extensions: + self._ext_map[ext.lower().lstrip(".")] = converter_cls + for mime in converter_cls.supported_mimetypes: + self._mime_map[mime] = converter_cls + return converter_cls + + def get_converter(self, file_path: Path) -> BaseConverter: + """Return an appropriate converter instance for the given file path.""" + ext = file_path.suffix.lower().lstrip(".") + if ext in self._ext_map: + return self._ext_map[ext]() + + mime_type, _ = mimetypes.guess_type(str(file_path)) + if mime_type and mime_type in self._mime_map: + return self._mime_map[mime_type]() + + supported = ", ".join(f".{e}" for e in sorted(self._ext_map.keys())) + raise ValueError(f"Unsupported format: .{ext}\nSupported formats: {supported}") + + def supported_extensions(self) -> list[str]: + return sorted(self._ext_map.keys()) + + +# Global singleton registry +default_registry = ConverterRegistry() diff --git a/paddleocr/_env.py b/paddleocr/_env.py new file mode 100644 index 0000000..9f90e17 --- /dev/null +++ b/paddleocr/_env.py @@ -0,0 +1,19 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +DISABLE_AUTO_LOGGING_CONFIG = ( + os.getenv("PADDLEOCR_DISABLE_AUTO_LOGGING_CONFIG", "0") == "1" +) diff --git a/paddleocr/_models/__init__.py b/paddleocr/_models/__init__.py new file mode 100644 index 0000000..3e969ff --- /dev/null +++ b/paddleocr/_models/__init__.py @@ -0,0 +1,43 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .chart_parsing import ChartParsing +from .doc_img_orientation_classification import DocImgOrientationClassification +from .doc_vlm import DocVLM +from .formula_recognition import FormulaRecognition +from .layout_detection import LayoutDetection +from .seal_text_detection import SealTextDetection +from .table_cells_detection import TableCellsDetection +from .table_classification import TableClassification +from .table_structure_recognition import TableStructureRecognition +from .text_detection import TextDetection +from .text_image_unwarping import TextImageUnwarping +from .textline_orientation_classification import TextLineOrientationClassification +from .text_recognition import TextRecognition + +__all__ = [ + "ChartParsing", + "DocImgOrientationClassification", + "DocVLM", + "FormulaRecognition", + "LayoutDetection", + "SealTextDetection", + "TableCellsDetection", + "TableClassification", + "TableStructureRecognition", + "TextDetection", + "TextImageUnwarping", + "TextLineOrientationClassification", + "TextRecognition", +] diff --git a/paddleocr/_models/_doc_vlm.py b/paddleocr/_models/_doc_vlm.py new file mode 100644 index 0000000..18134fd --- /dev/null +++ b/paddleocr/_models/_doc_vlm.py @@ -0,0 +1,49 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc + +from .._utils.cli import ( + get_subcommand_args, + perform_simple_inference, +) +from .base import PaddleXPredictorWrapper, PredictorCLISubcommandExecutor +from paddlex.utils.pipeline_arguments import custom_type + + +class BaseDocVLM(PaddleXPredictorWrapper): + def __init__( + self, + *args, + **kwargs, + ): + self._extra_init_args = {} + super().__init__(*args, **kwargs) + + def _get_extra_paddlex_predictor_init_args(self): + return self._extra_init_args + + +class BaseDocVLMSubcommandExecutor(PredictorCLISubcommandExecutor): + input_validator = staticmethod(custom_type(dict)) + + @property + @abc.abstractmethod + def wrapper_cls(self): + raise NotImplementedError + + def execute_with_args(self, args): + params = get_subcommand_args(args) + params["input"] = self.input_validator(params["input"]) + perform_simple_inference(self.wrapper_cls, params) diff --git a/paddleocr/_models/_image_classification.py b/paddleocr/_models/_image_classification.py new file mode 100644 index 0000000..2bad088 --- /dev/null +++ b/paddleocr/_models/_image_classification.py @@ -0,0 +1,58 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, +) +from .base import PaddleXPredictorWrapper, PredictorCLISubcommandExecutor + + +class ImageClassification(PaddleXPredictorWrapper): + def __init__( + self, + *, + topk=None, + **kwargs, + ): + self._extra_init_args = { + "topk": topk, + } + super().__init__(**kwargs) + + def _get_extra_paddlex_predictor_init_args(self): + return self._extra_init_args + + +class ImageClassificationSubcommandExecutor(PredictorCLISubcommandExecutor): + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + subparser.add_argument( + "--topk", + type=int, + help="Top-k value for prediction results.", + ) + + @property + @abc.abstractmethod + def wrapper_cls(self): + raise NotImplementedError + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference(self.wrapper_cls, params) diff --git a/paddleocr/_models/_object_detection.py b/paddleocr/_models/_object_detection.py new file mode 100644 index 0000000..f7615d9 --- /dev/null +++ b/paddleocr/_models/_object_detection.py @@ -0,0 +1,87 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, + str2bool, +) +from .base import PaddleXPredictorWrapper, PredictorCLISubcommandExecutor + + +class ObjectDetection(PaddleXPredictorWrapper): + def __init__( + self, + *, + img_size=None, + threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + **kwargs, + ): + self._extra_init_args = { + "img_size": img_size, + "threshold": threshold, + "layout_nms": layout_nms, + "layout_unclip_ratio": layout_unclip_ratio, + "layout_merge_bboxes_mode": layout_merge_bboxes_mode, + } + super().__init__(**kwargs) + + def _get_extra_paddlex_predictor_init_args(self): + return self._extra_init_args + + +class ObjectDetectionSubcommandExecutor(PredictorCLISubcommandExecutor): + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + subparser.add_argument( + "--img_size", + type=int, + help="Input image size (w, h).", + ) + subparser.add_argument( + "--threshold", + type=float, + help="Threshold for filtering out low-confidence predictions.", + ) + subparser.add_argument( + "--layout_nms", + type=str2bool, + help="Whether to use layout-aware NMS.", + ) + subparser.add_argument( + "--layout_unclip_ratio", + type=float, + help="Ratio of unclipping the bounding box.", + ) + subparser.add_argument( + "--layout_merge_bboxes_mode", + type=str, + help="Mode for merging bounding boxes.", + ) + + @property + @abc.abstractmethod + def wrapper_cls(self): + raise NotImplementedError + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference(self.wrapper_cls, params) diff --git a/paddleocr/_models/_text_detection.py b/paddleocr/_models/_text_detection.py new file mode 100644 index 0000000..22122d3 --- /dev/null +++ b/paddleocr/_models/_text_detection.py @@ -0,0 +1,75 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class TextDetectionMixin: + def __init__( + self, + *, + limit_side_len=None, + limit_type=None, + thresh=None, + box_thresh=None, + unclip_ratio=None, + input_shape=None, + **kwargs, + ): + self._extra_init_args = { + "limit_side_len": limit_side_len, + "limit_type": limit_type, + "thresh": thresh, + "box_thresh": box_thresh, + "unclip_ratio": unclip_ratio, + "input_shape": input_shape, + } + super().__init__(**kwargs) + + def _get_extra_paddlex_predictor_init_args(self): + return self._extra_init_args + + +class TextDetectionSubcommandExecutorMixin: + def _add_text_detection_args(self, subparser): + subparser.add_argument( + "--limit_side_len", + type=int, + help="This sets a limit on the side length of the input image for the model.", + ) + subparser.add_argument( + "--limit_type", + type=str, + help="This determines how the side length limit is applied to the input image before feeding it into the model.", + ) + subparser.add_argument( + "--thresh", + type=float, + help="Detection pixel threshold for the model. Pixels with scores greater than this threshold in the output probability map are considered text pixels.", + ) + subparser.add_argument( + "--box_thresh", + type=float, + help="Detection box threshold for the model. A detection result is considered a text region if the average score of all pixels within the border of the result is greater than this threshold.", + ) + subparser.add_argument( + "--unclip_ratio", + type=float, + help="Expansion coefficient, which expands the text region using this method. The larger the value, the larger the expansion area.", + ) + subparser.add_argument( + "--input_shape", + nargs=3, + type=int, + metavar=("C", "H", "W"), + help="Input shape of the model.", + ) diff --git a/paddleocr/_models/base.py b/paddleocr/_models/base.py new file mode 100644 index 0000000..2dd88bc --- /dev/null +++ b/paddleocr/_models/base.py @@ -0,0 +1,107 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc + +from paddlex import create_predictor +from paddlex.utils.deps import DependencyError + +from .._abstract import CLISubcommandExecutor +from .._common_args import ( + add_common_cli_opts, + parse_common_args, + prepare_common_init_args, +) + +_DEFAULT_ENABLE_HPI = False + + +class PaddleXPredictorWrapper(metaclass=abc.ABCMeta): + def __init__( + self, + *, + model_name=None, + model_dir=None, + **common_args, + ): + super().__init__() + self._model_name = ( + model_name if model_name is not None else self.default_model_name + ) + self._model_dir = model_dir + self._common_args = parse_common_args( + common_args, default_enable_hpi=_DEFAULT_ENABLE_HPI + ) + self.paddlex_predictor = self._create_paddlex_predictor() + + @property + @abc.abstractmethod + def default_model_name(self): + raise NotImplementedError + + def predict_iter(self, *args, **kwargs): + return self.paddlex_predictor.predict(*args, **kwargs) + + def predict(self, *args, **kwargs): + result = list(self.predict_iter(*args, **kwargs)) + return result + + def close(self): + self.paddlex_predictor.close() + + @classmethod + @abc.abstractmethod + def get_cli_subcommand_executor(cls): + raise NotImplementedError + + def _get_extra_paddlex_predictor_init_args(self): + return {} + + def _create_paddlex_predictor(self): + kwargs = prepare_common_init_args(self._model_name, self._common_args) + kwargs = {**self._get_extra_paddlex_predictor_init_args(), **kwargs} + # Should we check model names? + try: + return create_predictor( + model_name=self._model_name, model_dir=self._model_dir, **kwargs + ) + except DependencyError as e: + raise RuntimeError( + "A dependency error occurred during predictor creation. Please refer to the installation documentation to ensure all required dependencies are installed." + ) from e + + +class PredictorCLISubcommandExecutor(CLISubcommandExecutor): + @property + @abc.abstractmethod + def subparser_name(self): + raise NotImplementedError + + def add_subparser(self, subparsers): + subparser = subparsers.add_parser(name=self.subparser_name) + self._update_subparser(subparser) + subparser.add_argument("--model_name", type=str, help="Name of the model.") + subparser.add_argument( + "--model_dir", type=str, help="Directory where the model is stored." + ) + add_common_cli_opts( + subparser, + default_enable_hpi=_DEFAULT_ENABLE_HPI, + allow_multiple_devices=False, + ) + return subparser + + @abc.abstractmethod + def _update_subparser(self, subparser): + raise NotImplementedError diff --git a/paddleocr/_models/chart_parsing.py b/paddleocr/_models/chart_parsing.py new file mode 100644 index 0000000..0078bff --- /dev/null +++ b/paddleocr/_models/chart_parsing.py @@ -0,0 +1,45 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import add_simple_inference_args +from ._doc_vlm import ( + BaseDocVLM, + BaseDocVLMSubcommandExecutor, +) + + +class ChartParsing(BaseDocVLM): + @property + def default_model_name(self): + return "PP-Chart2Table" + + @classmethod + def get_cli_subcommand_executor(cls): + return ChartParsingSubcommandExecutor() + + +class ChartParsingSubcommandExecutor(BaseDocVLMSubcommandExecutor): + @property + def subparser_name(self): + return "chart_parsing" + + @property + def wrapper_cls(self): + return ChartParsing + + def _update_subparser(self, subparser): + add_simple_inference_args( + subparser, + input_help='Input dict, e.g. `{"image": "https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/chart_parsing_02.png"}`.', + ) diff --git a/paddleocr/_models/doc_img_orientation_classification.py b/paddleocr/_models/doc_img_orientation_classification.py new file mode 100644 index 0000000..7339dfe --- /dev/null +++ b/paddleocr/_models/doc_img_orientation_classification.py @@ -0,0 +1,40 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ._image_classification import ( + ImageClassification, + ImageClassificationSubcommandExecutor, +) + + +class DocImgOrientationClassification(ImageClassification): + @property + def default_model_name(self): + return "PP-LCNet_x1_0_doc_ori" + + @classmethod + def get_cli_subcommand_executor(cls): + return DocImgOrientationClassificationSubcommandExecutor() + + +class DocImgOrientationClassificationSubcommandExecutor( + ImageClassificationSubcommandExecutor +): + @property + def subparser_name(self): + return "doc_img_orientation_classification" + + @property + def wrapper_cls(self): + return DocImgOrientationClassification diff --git a/paddleocr/_models/doc_vlm.py b/paddleocr/_models/doc_vlm.py new file mode 100644 index 0000000..b1452a3 --- /dev/null +++ b/paddleocr/_models/doc_vlm.py @@ -0,0 +1,45 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import add_simple_inference_args +from ._doc_vlm import ( + BaseDocVLM, + BaseDocVLMSubcommandExecutor, +) + + +class DocVLM(BaseDocVLM): + @property + def default_model_name(self): + return "PP-DocBee2-3B" + + @classmethod + def get_cli_subcommand_executor(cls): + return DocVLMSubcommandExecutor() + + +class DocVLMSubcommandExecutor(BaseDocVLMSubcommandExecutor): + @property + def subparser_name(self): + return "doc_vlm" + + @property + def wrapper_cls(self): + return DocVLM + + def _update_subparser(self, subparser): + add_simple_inference_args( + subparser, + input_help='Input dict, e.g. `{"image": "https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/medal_table.png", "query": "Recognize this table"}`.', + ) diff --git a/paddleocr/_models/formula_recognition.py b/paddleocr/_models/formula_recognition.py new file mode 100644 index 0000000..99d2ba7 --- /dev/null +++ b/paddleocr/_models/formula_recognition.py @@ -0,0 +1,54 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, +) +from .base import PaddleXPredictorWrapper, PredictorCLISubcommandExecutor + + +class FormulaRecognition(PaddleXPredictorWrapper): + def __init__( + self, + *args, + **kwargs, + ): + self._extra_init_args = {} + super().__init__(*args, **kwargs) + + @property + def default_model_name(self): + return "PP-FormulaNet_plus-M" + + @classmethod + def get_cli_subcommand_executor(cls): + return FormulaRecognitionSubcommandExecutor() + + def _get_extra_paddlex_predictor_init_args(self): + return self._extra_init_args + + +class FormulaRecognitionSubcommandExecutor(PredictorCLISubcommandExecutor): + @property + def subparser_name(self): + return "formula_recognition" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference(FormulaRecognition, params) diff --git a/paddleocr/_models/layout_detection.py b/paddleocr/_models/layout_detection.py new file mode 100644 index 0000000..14427fb --- /dev/null +++ b/paddleocr/_models/layout_detection.py @@ -0,0 +1,38 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ._object_detection import ( + ObjectDetection, + ObjectDetectionSubcommandExecutor, +) + + +class LayoutDetection(ObjectDetection): + @property + def default_model_name(self): + return "PP-DocLayout_plus-L" + + @classmethod + def get_cli_subcommand_executor(cls): + return LayoutDetectionSubcommandExecutor() + + +class LayoutDetectionSubcommandExecutor(ObjectDetectionSubcommandExecutor): + @property + def subparser_name(self): + return "layout_detection" + + @property + def wrapper_cls(self): + return LayoutDetection diff --git a/paddleocr/_models/seal_text_detection.py b/paddleocr/_models/seal_text_detection.py new file mode 100644 index 0000000..adfff70 --- /dev/null +++ b/paddleocr/_models/seal_text_detection.py @@ -0,0 +1,47 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, +) +from .base import PaddleXPredictorWrapper, PredictorCLISubcommandExecutor +from ._text_detection import TextDetectionMixin, TextDetectionSubcommandExecutorMixin + + +class SealTextDetection(TextDetectionMixin, PaddleXPredictorWrapper): + @property + def default_model_name(self): + return "PP-OCRv4_mobile_seal_det" + + @classmethod + def get_cli_subcommand_executor(cls): + return SealTextDetectionSubcommandExecutor() + + +class SealTextDetectionSubcommandExecutor( + TextDetectionSubcommandExecutorMixin, PredictorCLISubcommandExecutor +): + @property + def subparser_name(self): + return "seal_text_detection" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + self._add_text_detection_args(subparser) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference(SealTextDetection, params) diff --git a/paddleocr/_models/table_cells_detection.py b/paddleocr/_models/table_cells_detection.py new file mode 100644 index 0000000..afca6ed --- /dev/null +++ b/paddleocr/_models/table_cells_detection.py @@ -0,0 +1,38 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ._object_detection import ( + ObjectDetection, + ObjectDetectionSubcommandExecutor, +) + + +class TableCellsDetection(ObjectDetection): + @property + def default_model_name(self): + return "RT-DETR-L_wired_table_cell_det" + + @classmethod + def get_cli_subcommand_executor(cls): + return TableCellsDetectionSubcommandExecutor() + + +class TableCellsDetectionSubcommandExecutor(ObjectDetectionSubcommandExecutor): + @property + def subparser_name(self): + return "table_cells_detection" + + @property + def wrapper_cls(self): + return TableCellsDetection diff --git a/paddleocr/_models/table_classification.py b/paddleocr/_models/table_classification.py new file mode 100644 index 0000000..028e8d8 --- /dev/null +++ b/paddleocr/_models/table_classification.py @@ -0,0 +1,38 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ._image_classification import ( + ImageClassification, + ImageClassificationSubcommandExecutor, +) + + +class TableClassification(ImageClassification): + @property + def default_model_name(self): + return "PP-LCNet_x1_0_table_cls" + + @classmethod + def get_cli_subcommand_executor(cls): + return TableClassificationSubcommandExecutor() + + +class TableClassificationSubcommandExecutor(ImageClassificationSubcommandExecutor): + @property + def subparser_name(self): + return "table_classification" + + @property + def wrapper_cls(self): + return TableClassification diff --git a/paddleocr/_models/table_structure_recognition.py b/paddleocr/_models/table_structure_recognition.py new file mode 100644 index 0000000..270bd7b --- /dev/null +++ b/paddleocr/_models/table_structure_recognition.py @@ -0,0 +1,54 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, +) +from .base import PaddleXPredictorWrapper, PredictorCLISubcommandExecutor + + +class TableStructureRecognition(PaddleXPredictorWrapper): + def __init__( + self, + *args, + **kwargs, + ): + self._extra_init_args = {} + super().__init__(*args, **kwargs) + + @property + def default_model_name(self): + return "SLANet" + + @classmethod + def get_cli_subcommand_executor(cls): + return TableStructureRecognitionSubcommandExecutor() + + def _get_extra_paddlex_predictor_init_args(self): + return self._extra_init_args + + +class TableStructureRecognitionSubcommandExecutor(PredictorCLISubcommandExecutor): + @property + def subparser_name(self): + return "table_structure_recognition" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference(TableStructureRecognition, params) diff --git a/paddleocr/_models/text_detection.py b/paddleocr/_models/text_detection.py new file mode 100644 index 0000000..9f86316 --- /dev/null +++ b/paddleocr/_models/text_detection.py @@ -0,0 +1,47 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, +) +from .base import PaddleXPredictorWrapper, PredictorCLISubcommandExecutor +from ._text_detection import TextDetectionMixin, TextDetectionSubcommandExecutorMixin + + +class TextDetection(TextDetectionMixin, PaddleXPredictorWrapper): + @property + def default_model_name(self): + return "PP-OCRv6_medium_det" + + @classmethod + def get_cli_subcommand_executor(cls): + return TextDetectionSubcommandExecutor() + + +class TextDetectionSubcommandExecutor( + TextDetectionSubcommandExecutorMixin, PredictorCLISubcommandExecutor +): + @property + def subparser_name(self): + return "text_detection" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + self._add_text_detection_args(subparser) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference(TextDetection, params) diff --git a/paddleocr/_models/text_image_unwarping.py b/paddleocr/_models/text_image_unwarping.py new file mode 100644 index 0000000..6dbbe56 --- /dev/null +++ b/paddleocr/_models/text_image_unwarping.py @@ -0,0 +1,54 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, +) +from .base import PaddleXPredictorWrapper, PredictorCLISubcommandExecutor + + +class TextImageUnwarping(PaddleXPredictorWrapper): + def __init__( + self, + *args, + **kwargs, + ): + self._extra_init_args = {} + super().__init__(*args, **kwargs) + + @property + def default_model_name(self): + return "UVDoc" + + @classmethod + def get_cli_subcommand_executor(cls): + return TextImageUnwarpingSubcommandExecutor() + + def _get_extra_paddlex_predictor_init_args(self): + return self._extra_init_args + + +class TextImageUnwarpingSubcommandExecutor(PredictorCLISubcommandExecutor): + @property + def subparser_name(self): + return "text_image_unwarping" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference(TextImageUnwarping, params) diff --git a/paddleocr/_models/text_recognition.py b/paddleocr/_models/text_recognition.py new file mode 100644 index 0000000..e53002a --- /dev/null +++ b/paddleocr/_models/text_recognition.py @@ -0,0 +1,64 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, +) +from .base import PaddleXPredictorWrapper, PredictorCLISubcommandExecutor + + +class TextRecognition(PaddleXPredictorWrapper): + def __init__( + self, + *, + input_shape=None, + **kwargs, + ): + self._extra_init_args = { + "input_shape": input_shape, + } + super().__init__(**kwargs) + + @property + def default_model_name(self): + return "PP-OCRv6_medium_rec" + + @classmethod + def get_cli_subcommand_executor(cls): + return TextRecognitionSubcommandExecutor() + + def _get_extra_paddlex_predictor_init_args(self): + return self._extra_init_args + + +class TextRecognitionSubcommandExecutor(PredictorCLISubcommandExecutor): + @property + def subparser_name(self): + return "text_recognition" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + subparser.add_argument( + "--input_shape", + nargs=3, + type=int, + metavar=("C", "H", "W"), + help="Input shape of the model.", + ) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference(TextRecognition, params) diff --git a/paddleocr/_models/textline_orientation_classification.py b/paddleocr/_models/textline_orientation_classification.py new file mode 100644 index 0000000..908c0dd --- /dev/null +++ b/paddleocr/_models/textline_orientation_classification.py @@ -0,0 +1,40 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ._image_classification import ( + ImageClassification, + ImageClassificationSubcommandExecutor, +) + + +class TextLineOrientationClassification(ImageClassification): + @property + def default_model_name(self): + return "PP-LCNet_x0_25_textline_ori" + + @classmethod + def get_cli_subcommand_executor(cls): + return TextLineOrientationClassificationSubcommandExecutor() + + +class TextLineOrientationClassificationSubcommandExecutor( + ImageClassificationSubcommandExecutor +): + @property + def subparser_name(self): + return "textline_orientation_classification" + + @property + def wrapper_cls(self): + return TextLineOrientationClassification diff --git a/paddleocr/_pipelines/__init__.py b/paddleocr/_pipelines/__init__.py new file mode 100644 index 0000000..7315e65 --- /dev/null +++ b/paddleocr/_pipelines/__init__.py @@ -0,0 +1,37 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .doc_preprocessor import DocPreprocessor +from .doc_understanding import DocUnderstanding +from .formula_recognition import FormulaRecognitionPipeline +from .ocr import PaddleOCR +from .paddleocr_vl import PaddleOCRVL +from .pp_chatocrv4_doc import PPChatOCRv4Doc +from .pp_doctranslation import PPDocTranslation +from .pp_structurev3 import PPStructureV3 +from .seal_recognition import SealRecognition +from .table_recognition_v2 import TableRecognitionPipelineV2 + +__all__ = [ + "DocPreprocessor", + "DocUnderstanding", + "FormulaRecognitionPipeline", + "PaddleOCR", + "PaddleOCRVL", + "PPChatOCRv4Doc", + "PPDocTranslation", + "PPStructureV3", + "SealRecognition", + "TableRecognitionPipelineV2", +] diff --git a/paddleocr/_pipelines/_patch_layout_parsing.py b/paddleocr/_pipelines/_patch_layout_parsing.py new file mode 100644 index 0000000..6b73400 --- /dev/null +++ b/paddleocr/_pipelines/_patch_layout_parsing.py @@ -0,0 +1,133 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Patches for paddlex layout parsing utilities to fix: +- Integer overflow in calculate_overlap_ratio when bounding box coordinates + are large (e.g. after doc_unwarping), causing RuntimeWarning and incorrect + overlap calculations. +- ValueError in calculate_minimum_enclosing_bbox when the bounding box list + is empty, which can happen when overflow causes all overlap matches to fail. + +See: https://github.com/PaddlePaddle/PaddleOCR/issues/17503 +""" + +import logging + +import numpy as np + +logger = logging.getLogger(__name__) + +_patched = False + + +def _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="union"): + """ + Calculate the overlap ratio between two bounding boxes. + + This version casts coordinates to float64 before computing areas to + prevent integer overflow when bounding box coordinates are large + (e.g. after document unwarping). + """ + bbox1 = np.array(bbox1, dtype=np.float64) + bbox2 = np.array(bbox2, dtype=np.float64) + + x_min_inter = np.maximum(bbox1[0], bbox2[0]) + y_min_inter = np.maximum(bbox1[1], bbox2[1]) + x_max_inter = np.minimum(bbox1[2], bbox2[2]) + y_max_inter = np.minimum(bbox1[3], bbox2[3]) + + inter_width = np.maximum(0, x_max_inter - x_min_inter) + inter_height = np.maximum(0, y_max_inter - y_min_inter) + + inter_area = inter_width * inter_height + + bbox1_area = abs((bbox1[2] - bbox1[0]) * (bbox1[3] - bbox1[1])) + bbox2_area = abs((bbox2[2] - bbox2[0]) * (bbox2[3] - bbox2[1])) + + if mode == "union": + ref_area = bbox1_area + bbox2_area - inter_area + elif mode == "small": + ref_area = np.minimum(bbox1_area, bbox2_area) + elif mode == "large": + ref_area = np.maximum(bbox1_area, bbox2_area) + else: + raise ValueError( + f"Invalid mode {mode}, must be one of ['union', 'small', 'large']." + ) + + if ref_area == 0: + return 0.0 + + return inter_area / ref_area + + +def _fixed_calculate_minimum_enclosing_bbox(bboxes): + """ + Calculate the minimum enclosing bounding box for a list of bounding boxes. + + This version returns a zero-area bounding box at the origin instead of + raising ValueError when the list is empty, allowing the caller to + continue without crashing. + """ + if not bboxes: + logger.debug( + "calculate_minimum_enclosing_bbox received an empty list; " + "returning a degenerate bounding box" + ) + return np.array([0, 0, 0, 0]) + + bboxes_array = np.array(bboxes) + + min_x = np.min(bboxes_array[:, 0]) + min_y = np.min(bboxes_array[:, 1]) + max_x = np.max(bboxes_array[:, 2]) + max_y = np.max(bboxes_array[:, 3]) + + return np.array([min_x, min_y, max_x, max_y]) + + +def apply_patches(): + """ + Apply patches to paddlex layout parsing utilities to fix integer overflow + and empty bounding box errors. + + This function is idempotent and safe to call multiple times. + """ + global _patched + if _patched: + return + + try: + import paddlex.inference.pipelines.layout_parsing.utils as lp_utils + import paddlex.inference.pipelines.layout_parsing.pipeline_v2 as lp_pipeline + except ImportError: + logger.debug("paddlex layout parsing modules not available; skipping patches") + return + + # Patch the utils module + lp_utils.calculate_overlap_ratio = _fixed_calculate_overlap_ratio + lp_utils.calculate_minimum_enclosing_bbox = _fixed_calculate_minimum_enclosing_bbox + + # Also patch the references imported directly into pipeline_v2, since + # Python binds names at import time + if hasattr(lp_pipeline, "calculate_overlap_ratio"): + lp_pipeline.calculate_overlap_ratio = _fixed_calculate_overlap_ratio + if hasattr(lp_pipeline, "calculate_minimum_enclosing_bbox"): + lp_pipeline.calculate_minimum_enclosing_bbox = ( + _fixed_calculate_minimum_enclosing_bbox + ) + + _patched = True + logger.debug("Applied layout parsing patches for issue #17503") diff --git a/paddleocr/_pipelines/base.py b/paddleocr/_pipelines/base.py new file mode 100644 index 0000000..f400cab --- /dev/null +++ b/paddleocr/_pipelines/base.py @@ -0,0 +1,135 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import abc + +import yaml +from paddlex import create_pipeline +from paddlex.inference import load_pipeline_config +from paddlex.utils.config import AttrDict +from paddlex.utils.deps import DependencyError + +from .._abstract import CLISubcommandExecutor +from .._common_args import ( + add_common_cli_opts, + parse_common_args, + prepare_common_init_args, +) + +_DEFAULT_ENABLE_HPI = None + + +def _merge_dicts(d1, d2): + res = d1.copy() + for k, v in d2.items(): + if k in res and isinstance(res[k], dict) and isinstance(v, dict): + res[k] = _merge_dicts(res[k], v) + else: + res[k] = v + return res + + +def _to_builtin(obj): + if isinstance(obj, AttrDict): + return {k: _to_builtin(v) for k, v in obj.items()} + elif isinstance(obj, dict): + return {k: _to_builtin(v) for k, v in obj.items()} + elif isinstance(obj, list): + return [_to_builtin(item) for item in obj] + else: + return obj + + +class PaddleXPipelineWrapper(metaclass=abc.ABCMeta): + def __init__( + self, + *, + paddlex_config=None, + **common_args, + ): + super().__init__() + self._paddlex_config = paddlex_config + self._common_args = parse_common_args( + common_args, default_enable_hpi=_DEFAULT_ENABLE_HPI + ) + self._merged_paddlex_config = self._get_merged_paddlex_config() + self.paddlex_pipeline = self._create_paddlex_pipeline() + + @property + @abc.abstractmethod + def _paddlex_pipeline_name(self): + raise NotImplementedError + + def export_paddlex_config_to_yaml(self, yaml_path): + with open(yaml_path, "w", encoding="utf-8") as f: + config = _to_builtin(self._merged_paddlex_config) + yaml.safe_dump(config, f) + + def close(self): + self.paddlex_pipeline.close() + + @classmethod + @abc.abstractmethod + def get_cli_subcommand_executor(cls): + raise NotImplementedError + + def _get_paddlex_config_overrides(self): + return {} + + def _get_merged_paddlex_config(self): + if self._paddlex_config is None: + config = load_pipeline_config(self._paddlex_pipeline_name) + elif isinstance(self._paddlex_config, str): + config = load_pipeline_config(self._paddlex_config) + else: + config = self._paddlex_config + + overrides = self._get_paddlex_config_overrides() + + return _merge_dicts(config, overrides) + + def _create_paddlex_pipeline(self): + kwargs = prepare_common_init_args(None, self._common_args) + try: + return create_pipeline(config=self._merged_paddlex_config, **kwargs) + except DependencyError as e: + raise RuntimeError( + "A dependency error occurred during pipeline creation. Please refer to the installation documentation to ensure all required dependencies are installed." + ) from e + + +class PipelineCLISubcommandExecutor(CLISubcommandExecutor): + @property + @abc.abstractmethod + def subparser_name(self): + raise NotImplementedError + + def add_subparser(self, subparsers): + subparser = subparsers.add_parser(name=self.subparser_name) + self._update_subparser(subparser) + add_common_cli_opts( + subparser, + default_enable_hpi=_DEFAULT_ENABLE_HPI, + allow_multiple_devices=True, + ) + subparser.add_argument( + "--paddlex_config", + type=str, + help="Path to PaddleX pipeline configuration file.", + ) + return subparser + + @abc.abstractmethod + def _update_subparser(self, subparser): + raise NotImplementedError diff --git a/paddleocr/_pipelines/doc_preprocessor.py b/paddleocr/_pipelines/doc_preprocessor.py new file mode 100644 index 0000000..b8c34df --- /dev/null +++ b/paddleocr/_pipelines/doc_preprocessor.py @@ -0,0 +1,147 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, + str2bool, +) +from .base import PaddleXPipelineWrapper, PipelineCLISubcommandExecutor +from .utils import create_config_from_structure + + +class DocPreprocessor(PaddleXPipelineWrapper): + def __init__( + self, + doc_orientation_classify_model_name=None, + doc_orientation_classify_model_dir=None, + doc_unwarping_model_name=None, + doc_unwarping_model_dir=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + **kwargs, + ): + + self._params = { + "doc_orientation_classify_model_name": doc_orientation_classify_model_name, + "doc_orientation_classify_model_dir": doc_orientation_classify_model_dir, + "doc_unwarping_model_name": doc_unwarping_model_name, + "doc_unwarping_model_dir": doc_unwarping_model_dir, + "use_doc_orientation_classify": use_doc_orientation_classify, + "use_doc_unwarping": use_doc_unwarping, + } + super().__init__(**kwargs) + + @property + def _paddlex_pipeline_name(self): + return "doc_preprocessor" + + def predict_iter( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + ): + return self.paddlex_pipeline.predict( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + ) + + def predict( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + ): + return list( + self.predict_iter( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + ) + ) + + @classmethod + def get_cli_subcommand_executor(cls): + return DocPreprocessorCLISubcommandExecutor() + + def _get_paddlex_config_overrides(self): + STRUCTURE = { + "SubModules.DocOrientationClassify.model_name": self._params[ + "doc_orientation_classify_model_name" + ], + "SubModules.DocOrientationClassify.model_dir": self._params[ + "doc_orientation_classify_model_dir" + ], + "SubModules.DocUnwarping.model_name": self._params[ + "doc_unwarping_model_name" + ], + "SubModules.DocUnwarping.model_dir": self._params[ + "doc_unwarping_model_dir" + ], + "use_doc_orientation_classify": self._params[ + "use_doc_orientation_classify" + ], + "use_doc_unwarping": self._params["use_doc_unwarping"], + } + return create_config_from_structure(STRUCTURE) + + +class DocPreprocessorCLISubcommandExecutor(PipelineCLISubcommandExecutor): + @property + def subparser_name(self): + return "doc_preprocessor" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + subparser.add_argument( + "--doc_orientation_classify_model_name", + type=str, + help="Name of the document image orientation classification model.", + ) + subparser.add_argument( + "--doc_orientation_classify_model_dir", + type=str, + help="Path to the document image orientation classification model directory.", + ) + subparser.add_argument( + "--doc_unwarping_model_name", + type=str, + help="Name of the document image unwarping model.", + ) + subparser.add_argument( + "--doc_unwarping_model_dir", + type=str, + help="Path to the document image unwarping model directory.", + ) + subparser.add_argument( + "--use_doc_orientation_classify", + type=str2bool, + help="Whether to use document image orientation classification.", + ) + subparser.add_argument( + "--use_doc_unwarping", + type=str2bool, + help="Whether to use text image unwarping.", + ) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + + perform_simple_inference(DocPreprocessor, params) diff --git a/paddleocr/_pipelines/doc_understanding.py b/paddleocr/_pipelines/doc_understanding.py new file mode 100644 index 0000000..0cde72f --- /dev/null +++ b/paddleocr/_pipelines/doc_understanding.py @@ -0,0 +1,107 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddlex.utils.pipeline_arguments import custom_type + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, +) +from .base import PaddleXPipelineWrapper, PipelineCLISubcommandExecutor +from .utils import create_config_from_structure + + +class DocUnderstanding(PaddleXPipelineWrapper): + def __init__( + self, + doc_understanding_model_name=None, + doc_understanding_model_dir=None, + doc_understanding_batch_size=None, + **kwargs, + ): + + self._params = { + "doc_understanding_model_name": doc_understanding_model_name, + "doc_understanding_model_dir": doc_understanding_model_dir, + "doc_understanding_batch_size": doc_understanding_batch_size, + } + super().__init__(**kwargs) + + @property + def _paddlex_pipeline_name(self): + return "doc_understanding" + + def predict_iter(self, input, **kwargs): + return self.paddlex_pipeline.predict(input, **kwargs) + + def predict( + self, + input, + **kwargs, + ): + return list(self.predict_iter(input, **kwargs)) + + @classmethod + def get_cli_subcommand_executor(cls): + return DocUnderstandingCLISubcommandExecutor() + + def _get_paddlex_config_overrides(self): + STRUCTURE = { + "SubModules.DocUnderstanding.model_name": self._params[ + "doc_understanding_model_name" + ], + "SubModules.DocUnderstanding.model_dir": self._params[ + "doc_understanding_model_dir" + ], + "SubModules.DocUnderstanding.batch_size": self._params[ + "doc_understanding_batch_size" + ], + } + return create_config_from_structure(STRUCTURE) + + +class DocUnderstandingCLISubcommandExecutor(PipelineCLISubcommandExecutor): + input_validator = staticmethod(custom_type(dict)) + + @property + def subparser_name(self): + return "doc_understanding" + + def _update_subparser(self, subparser): + add_simple_inference_args( + subparser, + input_help='Input dict, e.g. `{"image": "https://paddle-model-ecology.bj.bcebos.com/paddlex/imgs/demo_image/medal_table.png", "query": "Recognize this table"}`.', + ) + + subparser.add_argument( + "--doc_understanding_model_name", + type=str, + help="Name of the document understanding model.", + ) + subparser.add_argument( + "--doc_understanding_model_dir", + type=str, + help="Path to the document understanding model directory.", + ) + subparser.add_argument( + "--doc_understanding_batch_size", + type=str, + help="Batch size for the document understanding model.", + ) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + params["input"] = self.input_validator(params["input"]) + perform_simple_inference(DocUnderstanding, params) diff --git a/paddleocr/_pipelines/formula_recognition.py b/paddleocr/_pipelines/formula_recognition.py new file mode 100644 index 0000000..7588f81 --- /dev/null +++ b/paddleocr/_pipelines/formula_recognition.py @@ -0,0 +1,285 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, + str2bool, +) +from .base import PaddleXPipelineWrapper, PipelineCLISubcommandExecutor +from .utils import create_config_from_structure + + +class FormulaRecognitionPipeline(PaddleXPipelineWrapper): + def __init__( + self, + doc_orientation_classify_model_name=None, + doc_orientation_classify_model_dir=None, + doc_orientation_classify_batch_size=None, + doc_unwarping_model_name=None, + doc_unwarping_model_dir=None, + doc_unwarping_batch_size=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + layout_detection_model_name=None, + layout_detection_model_dir=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + layout_detection_batch_size=None, + use_layout_detection=None, + formula_recognition_model_name=None, + formula_recognition_model_dir=None, + formula_recognition_batch_size=None, + **kwargs, + ): + params = locals().copy() + params.pop("self") + params.pop("kwargs") + self._params = params + + super().__init__(**kwargs) + + @property + def _paddlex_pipeline_name(self): + return "formula_recognition" + + def predict_iter( + self, + input, + *, + use_layout_detection=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + layout_det_res=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + **kwargs, + ): + return self.paddlex_pipeline.predict( + input, + use_layout_detection=use_layout_detection, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + layout_det_res=layout_det_res, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + **kwargs, + ) + + def predict( + self, + input, + *, + use_layout_detection=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + layout_det_res=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + **kwargs, + ): + return list( + self.predict_iter( + input, + use_layout_detection=use_layout_detection, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + layout_det_res=layout_det_res, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + **kwargs, + ) + ) + + @classmethod + def get_cli_subcommand_executor(cls): + return FormulaRecognitionPipelineCLISubcommandExecutor() + + def _get_paddlex_config_overrides(self): + STRUCTURE = { + "use_layout_detection": self._params["use_layout_detection"], + "SubModules.LayoutDetection.model_name": self._params[ + "layout_detection_model_name" + ], + "SubModules.LayoutDetection.model_dir": self._params[ + "layout_detection_model_dir" + ], + "SubModules.LayoutDetection.threshold": self._params["layout_threshold"], + "SubModules.LayoutDetection.layout_nms": self._params["layout_nms"], + "SubModules.LayoutDetection.layout_unclip_ratio": self._params[ + "layout_unclip_ratio" + ], + "SubModules.LayoutDetection.layout_merge_bboxes_mode": self._params[ + "layout_merge_bboxes_mode" + ], + "SubModules.LayoutDetection.batch_size": self._params[ + "layout_detection_batch_size" + ], + "SubModules.FormulaRecognition.model_name": self._params[ + "formula_recognition_model_name" + ], + "SubModules.FormulaRecognition.model_dir": self._params[ + "formula_recognition_model_dir" + ], + "SubModules.FormulaRecognition.batch_size": self._params[ + "formula_recognition_batch_size" + ], + "SubPipelines.DocPreprocessor.use_doc_orientation_classify": self._params[ + "use_doc_orientation_classify" + ], + "SubPipelines.DocPreprocessor.use_doc_unwarping": self._params[ + "use_doc_unwarping" + ], + "use_doc_preprocessor": self._params["use_doc_orientation_classify"] + or self._params["use_doc_unwarping"], + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_name": self._params[ + "doc_orientation_classify_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_dir": self._params[ + "doc_orientation_classify_model_dir" + ], + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.batch_size": self._params[ + "doc_orientation_classify_batch_size" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_name": self._params[ + "doc_unwarping_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_dir": self._params[ + "doc_unwarping_model_dir" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.batch_size": self._params[ + "doc_unwarping_batch_size" + ], + } + return create_config_from_structure(STRUCTURE) + + +class FormulaRecognitionPipelineCLISubcommandExecutor(PipelineCLISubcommandExecutor): + @property + def subparser_name(self): + return "formula_recognition_pipeline" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + subparser.add_argument( + "--doc_orientation_classify_model_name", + type=str, + help="Name of the document image orientation classification model.", + ) + subparser.add_argument( + "--doc_orientation_classify_model_dir", + type=str, + help="Directory of the document image orientation classification model.", + ) + subparser.add_argument( + "--doc_orientation_classify_batch_size", + type=int, + help="Batch size for document image orientation classification.", + ) + subparser.add_argument( + "--doc_unwarping_model_name", + type=str, + help="Name of the document unwarping model.", + ) + subparser.add_argument( + "--doc_unwarping_model_dir", + type=str, + help="Directory of the document unwarping model.", + ) + subparser.add_argument( + "--doc_unwarping_batch_size", + type=int, + help="Batch size for document unwarping.", + ) + subparser.add_argument( + "--use_doc_orientation_classify", + type=str2bool, + help="Use document image orientation classification.", + ) + subparser.add_argument( + "--use_doc_unwarping", + type=str2bool, + help="Use document unwarping.", + ) + subparser.add_argument( + "--layout_detection_model_name", + type=str, + help="Name of the layout detection model.", + ) + subparser.add_argument( + "--layout_detection_model_dir", + type=str, + help="Directory of the layout detection model.", + ) + subparser.add_argument( + "--layout_threshold", + type=float, + help="Threshold for layout detection.", + ) + subparser.add_argument( + "--layout_nms", + type=str2bool, + help="Non-maximum suppression for layout detection.", + ) + subparser.add_argument( + "--layout_unclip_ratio", + type=float, + help="Unclip ratio for layout detection.", + ) + subparser.add_argument( + "--layout_merge_bboxes_mode", + type=str, + help="Mode for merging bounding boxes in layout detection.", + ) + subparser.add_argument( + "--layout_detection_batch_size", + type=int, + help="Batch size for layout detection.", + ) + subparser.add_argument( + "--use_layout_detection", + type=str2bool, + help="Use layout detection.", + ) + subparser.add_argument( + "--formula_recognition_model_name", + type=str, + help="Name of the formula recognition model.", + ) + subparser.add_argument( + "--formula_recognition_model_dir", + type=str, + help="Directory of the formula recognition model.", + ) + subparser.add_argument( + "--formula_recognition_batch_size", + type=int, + help="Batch size for formula recognition.", + ) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference(FormulaRecognitionPipeline, params) diff --git a/paddleocr/_pipelines/ocr.py b/paddleocr/_pipelines/ocr.py new file mode 100644 index 0000000..a708a0e --- /dev/null +++ b/paddleocr/_pipelines/ocr.py @@ -0,0 +1,604 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO: Should we use a third-party CLI library to auto-generate command-line +# arguments from the pipeline class, to reduce boilerplate and improve +# maintainability? + +import sys +import warnings + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, + str2bool, +) +from .._utils.deprecation import ( + DeprecatedOptionAction, + deprecated, + warn_deprecated_param, +) +from .._utils.langs import ( + ARABIC_LANGS, + CYRILLIC_LANGS, + DEVANAGARI_LANGS, + ESLAV_LANGS, + LATIN_LANGS, +) +from .._utils.logging import logger +from .base import PaddleXPipelineWrapper, PipelineCLISubcommandExecutor +from .utils import create_config_from_structure + +_DEPRECATED_PARAM_NAME_MAPPING = { + "det_model_dir": "text_detection_model_dir", + "det_limit_side_len": "text_det_limit_side_len", + "det_limit_type": "text_det_limit_type", + "det_db_thresh": "text_det_thresh", + "det_db_box_thresh": "text_det_box_thresh", + "det_db_unclip_ratio": "text_det_unclip_ratio", + "rec_model_dir": "text_recognition_model_dir", + "rec_batch_num": "text_recognition_batch_size", + "use_angle_cls": "use_textline_orientation", + "cls_model_dir": "textline_orientation_model_dir", + "cls_batch_num": "textline_orientation_batch_size", +} + +_SUPPORTED_OCR_VERSIONS = ["PP-OCRv3", "PP-OCRv4", "PP-OCRv5", "PP-OCRv6"] +_PPOCRV6_UNSUPPORTED_LATIN_LANGS = frozenset({"pi"}) +_PPOCRV6_LANGS = frozenset({"ch", "chinese_cht", "en", "japan"}) | ( + LATIN_LANGS - _PPOCRV6_UNSUPPORTED_LATIN_LANGS +) + + +# Be comptable with PaddleOCR 2.x interfaces +class PaddleOCR(PaddleXPipelineWrapper): + def __init__( + self, + doc_orientation_classify_model_name=None, + doc_orientation_classify_model_dir=None, + doc_unwarping_model_name=None, + doc_unwarping_model_dir=None, + text_detection_model_name=None, + text_detection_model_dir=None, + textline_orientation_model_name=None, + textline_orientation_model_dir=None, + textline_orientation_batch_size=None, + text_recognition_model_name=None, + text_recognition_model_dir=None, + text_recognition_batch_size=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_det_input_shape=None, + text_rec_score_thresh=None, + return_word_box=None, + text_rec_input_shape=None, + lang=None, + ocr_version=None, + **kwargs, + ): + if ocr_version is not None and ocr_version not in _SUPPORTED_OCR_VERSIONS: + raise ValueError( + f"Invalid OCR version: {ocr_version}. Supported values are {_SUPPORTED_OCR_VERSIONS}." + ) + + if all( + map( + lambda p: p is None, + ( + text_detection_model_name, + text_detection_model_dir, + text_recognition_model_name, + text_recognition_model_dir, + ), + ) + ): + det_model_name, rec_model_name = self._get_ocr_model_names( + lang, ocr_version + ) + if det_model_name is None or rec_model_name is None: + raise ValueError( + f"No models are available for lang={repr(lang)} and ocr_version={repr(ocr_version)}." + ) + text_detection_model_name = det_model_name + text_recognition_model_name = rec_model_name + else: + if lang is not None or ocr_version is not None: + warnings.warn( + "`lang` and `ocr_version` will be ignored when model names or model directories are not `None`.", + stacklevel=2, + ) + + params = { + "doc_orientation_classify_model_name": doc_orientation_classify_model_name, + "doc_orientation_classify_model_dir": doc_orientation_classify_model_dir, + "doc_unwarping_model_name": doc_unwarping_model_name, + "doc_unwarping_model_dir": doc_unwarping_model_dir, + "text_detection_model_name": text_detection_model_name, + "text_detection_model_dir": text_detection_model_dir, + "textline_orientation_model_name": textline_orientation_model_name, + "textline_orientation_model_dir": textline_orientation_model_dir, + "textline_orientation_batch_size": textline_orientation_batch_size, + "text_recognition_model_name": text_recognition_model_name, + "text_recognition_model_dir": text_recognition_model_dir, + "text_recognition_batch_size": text_recognition_batch_size, + "use_doc_orientation_classify": use_doc_orientation_classify, + "use_doc_unwarping": use_doc_unwarping, + "use_textline_orientation": use_textline_orientation, + "text_det_limit_side_len": text_det_limit_side_len, + "text_det_limit_type": text_det_limit_type, + "text_det_thresh": text_det_thresh, + "text_det_box_thresh": text_det_box_thresh, + "text_det_unclip_ratio": text_det_unclip_ratio, + "text_det_input_shape": text_det_input_shape, + "text_rec_score_thresh": text_rec_score_thresh, + "return_word_box": return_word_box, + "text_rec_input_shape": text_rec_input_shape, + } + base_params = {} + for name, val in kwargs.items(): + if name in _DEPRECATED_PARAM_NAME_MAPPING: + new_name = _DEPRECATED_PARAM_NAME_MAPPING[name] + warn_deprecated_param(name, new_name) + assert ( + new_name in params + ), f"{repr(new_name)} is not a valid parameter name." + if params[new_name] is not None: + raise ValueError( + f"`{name}` and `{new_name}` are mutually exclusive." + ) + params[new_name] = val + else: + base_params[name] = val + + self._params = params + + super().__init__(**base_params) + + @property + def _paddlex_pipeline_name(self): + return "OCR" + + def predict_iter( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_rec_score_thresh=None, + return_word_box=None, + ): + return self.paddlex_pipeline.predict( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_textline_orientation=use_textline_orientation, + text_det_limit_side_len=text_det_limit_side_len, + text_det_limit_type=text_det_limit_type, + text_det_thresh=text_det_thresh, + text_det_box_thresh=text_det_box_thresh, + text_det_unclip_ratio=text_det_unclip_ratio, + text_rec_score_thresh=text_rec_score_thresh, + return_word_box=return_word_box, + ) + + def predict( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_rec_score_thresh=None, + return_word_box=None, + ): + return list( + self.predict_iter( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_textline_orientation=use_textline_orientation, + text_det_limit_side_len=text_det_limit_side_len, + text_det_limit_type=text_det_limit_type, + text_det_thresh=text_det_thresh, + text_det_box_thresh=text_det_box_thresh, + text_det_unclip_ratio=text_det_unclip_ratio, + text_rec_score_thresh=text_rec_score_thresh, + return_word_box=return_word_box, + ) + ) + + @deprecated("Please use `predict` instead.") + def ocr(self, img, **kwargs): + return self.predict(img, **kwargs) + + @classmethod + def get_cli_subcommand_executor(cls): + return PaddleOCRCLISubcommandExecutor() + + def _get_paddlex_config_overrides(self): + STRUCTURE = { + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_name": self._params[ + "doc_orientation_classify_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_dir": self._params[ + "doc_orientation_classify_model_dir" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_name": self._params[ + "doc_unwarping_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_dir": self._params[ + "doc_unwarping_model_dir" + ], + "SubModules.TextDetection.model_name": self._params[ + "text_detection_model_name" + ], + "SubModules.TextDetection.model_dir": self._params[ + "text_detection_model_dir" + ], + "SubModules.TextLineOrientation.model_name": self._params[ + "textline_orientation_model_name" + ], + "SubModules.TextLineOrientation.model_dir": self._params[ + "textline_orientation_model_dir" + ], + "SubModules.TextLineOrientation.batch_size": self._params[ + "textline_orientation_batch_size" + ], + "SubModules.TextRecognition.model_name": self._params[ + "text_recognition_model_name" + ], + "SubModules.TextRecognition.model_dir": self._params[ + "text_recognition_model_dir" + ], + "SubModules.TextRecognition.batch_size": self._params[ + "text_recognition_batch_size" + ], + "SubPipelines.DocPreprocessor.use_doc_orientation_classify": self._params[ + "use_doc_orientation_classify" + ], + "SubPipelines.DocPreprocessor.use_doc_unwarping": self._params[ + "use_doc_unwarping" + ], + "use_doc_preprocessor": self._params["use_doc_orientation_classify"] + or self._params["use_doc_unwarping"], + "use_textline_orientation": self._params["use_textline_orientation"], + "SubModules.TextDetection.limit_side_len": self._params[ + "text_det_limit_side_len" + ], + "SubModules.TextDetection.limit_type": self._params["text_det_limit_type"], + "SubModules.TextDetection.thresh": self._params["text_det_thresh"], + "SubModules.TextDetection.box_thresh": self._params["text_det_box_thresh"], + "SubModules.TextDetection.unclip_ratio": self._params[ + "text_det_unclip_ratio" + ], + "SubModules.TextDetection.input_shape": self._params[ + "text_det_input_shape" + ], + "SubModules.TextRecognition.score_thresh": self._params[ + "text_rec_score_thresh" + ], + "SubModules.TextRecognition.return_word_box": self._params[ + "return_word_box" + ], + "SubModules.TextRecognition.input_shape": self._params[ + "text_rec_input_shape" + ], + } + return create_config_from_structure(STRUCTURE) + + def _get_ocr_model_names(self, lang, ppocr_version): + SPECIFIC_LANGS = [ + "ch", + "en", + "korean", + "japan", + "chinese_cht", + "te", + "ka", + "ta", + ] + + if lang is None and ppocr_version is None: + return "PP-OCRv6_medium_det", "PP-OCRv6_medium_rec" + + if lang is None: + lang = "ch" + + if ppocr_version is None: + if lang in _PPOCRV6_LANGS: + ppocr_version = "PP-OCRv6" + elif lang in { + "korean", + "th", + "el", + "te", + "ta", + } | ESLAV_LANGS | ARABIC_LANGS | CYRILLIC_LANGS | DEVANAGARI_LANGS | ( + LATIN_LANGS - _PPOCRV6_LANGS + ): + ppocr_version = "PP-OCRv5" + elif lang == "ka": + ppocr_version = "PP-OCRv3" + else: + # Unknown language specified + return None, None + + if ppocr_version == "PP-OCRv6": + if lang in _PPOCRV6_LANGS: + return "PP-OCRv6_medium_det", "PP-OCRv6_medium_rec" + return None, None + + elif ppocr_version == "PP-OCRv5": + rec_lang, rec_model_name = None, None + if lang in ("ch", "chinese_cht", "japan"): + rec_model_name = "PP-OCRv5_server_rec" + elif lang == "en": + rec_model_name = "en_PP-OCRv5_mobile_rec" + elif lang in LATIN_LANGS: + rec_lang = "latin" + elif lang in ESLAV_LANGS: + rec_lang = "eslav" + elif lang in ARABIC_LANGS: + rec_lang = "arabic" + elif lang in CYRILLIC_LANGS: + rec_lang = "cyrillic" + elif lang in DEVANAGARI_LANGS: + rec_lang = "devanagari" + elif lang == "korean": + rec_lang = "korean" + elif lang == "th": + rec_lang = "th" + elif lang == "el": + rec_lang = "el" + elif lang == "te": + rec_lang = "te" + elif lang == "ta": + rec_lang = "ta" + + if rec_lang is not None: + rec_model_name = f"{rec_lang}_PP-OCRv5_mobile_rec" + if rec_model_name is None: + return None, None + return "PP-OCRv5_server_det", rec_model_name + + elif ppocr_version == "PP-OCRv4": + if lang == "ch": + return "PP-OCRv4_mobile_det", "PP-OCRv4_mobile_rec" + elif lang == "en": + return "PP-OCRv4_mobile_det", "en_PP-OCRv4_mobile_rec" + else: + return None, None + else: + # PP-OCRv3 + rec_lang = None + if lang in LATIN_LANGS: + rec_lang = "latin" + elif lang in ARABIC_LANGS: + rec_lang = "arabic" + elif lang in CYRILLIC_LANGS: + rec_lang = "cyrillic" + elif lang in DEVANAGARI_LANGS: + rec_lang = "devanagari" + else: + if lang in SPECIFIC_LANGS: + rec_lang = lang + + rec_model_name = None + if rec_lang == "ch": + rec_model_name = "PP-OCRv3_mobile_rec" + elif rec_lang is not None: + rec_model_name = f"{rec_lang}_PP-OCRv3_mobile_rec" + if rec_model_name is None: + return None, None + return "PP-OCRv3_mobile_det", rec_model_name + + +class PaddleOCRCLISubcommandExecutor(PipelineCLISubcommandExecutor): + @property + def subparser_name(self): + return "ocr" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + subparser.add_argument( + "--doc_orientation_classify_model_name", + type=str, + help="Name of the document image orientation classification model.", + ) + subparser.add_argument( + "--doc_orientation_classify_model_dir", + type=str, + help="Path to the document image orientation classification model directory.", + ) + subparser.add_argument( + "--doc_unwarping_model_name", + type=str, + help="Name of the text image unwarping model.", + ) + subparser.add_argument( + "--doc_unwarping_model_dir", + type=str, + help="Path to the image unwarping model directory.", + ) + subparser.add_argument( + "--text_detection_model_name", + type=str, + help="Name of the text detection model.", + ) + subparser.add_argument( + "--text_detection_model_dir", + type=str, + help="Path to the text detection model directory.", + ) + subparser.add_argument( + "--textline_orientation_model_name", + type=str, + help="Name of the text line orientation classification model.", + ) + subparser.add_argument( + "--textline_orientation_model_dir", + type=str, + help="Path to the text line orientation classification model directory.", + ) + subparser.add_argument( + "--textline_orientation_batch_size", + type=int, + help="Batch size for the text line orientation classification model.", + ) + subparser.add_argument( + "--text_recognition_model_name", + type=str, + help="Name of the text recognition model.", + ) + subparser.add_argument( + "--text_recognition_model_dir", + type=str, + help="Path to the text recognition model directory.", + ) + subparser.add_argument( + "--text_recognition_batch_size", + type=int, + help="Batch size for the text recognition model.", + ) + subparser.add_argument( + "--use_doc_orientation_classify", + type=str2bool, + help="Whether to use document image orientation classification.", + ) + subparser.add_argument( + "--use_doc_unwarping", + type=str2bool, + help="Whether to use text image unwarping.", + ) + subparser.add_argument( + "--use_textline_orientation", + type=str2bool, + help="Whether to use text line orientation classification.", + ) + subparser.add_argument( + "--text_det_limit_side_len", + type=int, + help="This sets a limit on the side length of the input image for the text detection model.", + ) + subparser.add_argument( + "--text_det_limit_type", + type=str, + help="This determines how the side length limit is applied to the input image before feeding it into the text deteciton model.", + ) + subparser.add_argument( + "--text_det_thresh", + type=float, + help="Detection pixel threshold for the text detection model. Pixels with scores greater than this threshold in the output probability map are considered text pixels.", + ) + subparser.add_argument( + "--text_det_box_thresh", + type=float, + help="Detection box threshold for the text detection model. A detection result is considered a text region if the average score of all pixels within the border of the result is greater than this threshold.", + ) + subparser.add_argument( + "--text_det_unclip_ratio", + type=float, + help="Text detection expansion coefficient, which expands the text region using this method. The larger the value, the larger the expansion area.", + ) + subparser.add_argument( + "--text_det_input_shape", + nargs=3, + type=int, + metavar=("C", "H", "W"), + help="Input shape of the text detection model.", + ) + subparser.add_argument( + "--text_rec_score_thresh", + type=float, + help="Text recognition threshold. Text results with scores greater than this threshold are retained.", + ) + subparser.add_argument( + "--return_word_box", + type=str2bool, + help="Whether to return the coordinates of the recognition result.", + ) + subparser.add_argument( + "--text_rec_input_shape", + nargs=3, + type=int, + metavar=("C", "H", "W"), + help="Input shape of the text recognition model.", + ) + subparser.add_argument( + "--lang", type=str, help="Language in the input image for OCR processing." + ) + subparser.add_argument( + "--ocr_version", + type=str, + choices=_SUPPORTED_OCR_VERSIONS, + help="PP-OCR version to use.", + ) + + deprecated_arg_types = { + "det_model_dir": str, + "det_limit_side_len": int, + "det_limit_type": str, + "det_db_thresh": float, + "det_db_box_thresh": float, + "det_db_unclip_ratio": float, + "rec_model_dir": str, + "rec_batch_num": int, + "use_angle_cls": str2bool, + "cls_model_dir": str, + "cls_batch_num": int, + } + + for name, new_name in _DEPRECATED_PARAM_NAME_MAPPING.items(): + assert name in deprecated_arg_types, name + subparser.add_argument( + "--" + name, + action=DeprecatedOptionAction, + type=deprecated_arg_types[name], + help=f"[Deprecated] Please use `--{new_name}` instead.", + ) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + for name, new_name in _DEPRECATED_PARAM_NAME_MAPPING.items(): + assert name in params + val = params[name] + new_val = params[new_name] + if val is not None and new_val is not None: + logger.error( + "`--%s` and `--%s` are mutually exclusive.", name, new_name + ) + sys.exit(2) + if val is None: + params.pop(name) + + perform_simple_inference(PaddleOCR, params) diff --git a/paddleocr/_pipelines/paddleocr_vl.py b/paddleocr/_pipelines/paddleocr_vl.py new file mode 100644 index 0000000..8edad89 --- /dev/null +++ b/paddleocr/_pipelines/paddleocr_vl.py @@ -0,0 +1,514 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, + str2bool, +) +from .base import PaddleXPipelineWrapper, PipelineCLISubcommandExecutor +from .utils import create_config_from_structure + + +_AVAILABLE_PIPELINE_VERSIONS = ["v1", "v1.5", "v1.6"] +_DEFAULT_PIPELINE_VERSION = "v1.6" +_SUPPORTED_VL_BACKENDS = [ + "native", + "vllm-server", + "sglang-server", + "fastdeploy-server", + "mlx-vlm-server", + "llama-cpp-server", +] + + +class PaddleOCRVL(PaddleXPipelineWrapper): + def __init__( + self, + pipeline_version=_DEFAULT_PIPELINE_VERSION, + layout_detection_model_name=None, + layout_detection_model_dir=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + vl_rec_model_name=None, + vl_rec_model_dir=None, + vl_rec_backend=None, + vl_rec_server_url=None, + vl_rec_max_concurrency=None, + vl_rec_api_model_name=None, + vl_rec_api_key=None, + doc_orientation_classify_model_name=None, + doc_orientation_classify_model_dir=None, + doc_unwarping_model_name=None, + doc_unwarping_model_dir=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_layout_detection=None, + use_chart_recognition=None, + use_seal_recognition=None, + use_ocr_for_image_block=None, + format_block_content=None, + merge_layout_blocks=None, + markdown_ignore_labels=None, + use_queues=None, + **kwargs, + ): + if pipeline_version not in _AVAILABLE_PIPELINE_VERSIONS: + raise ValueError( + f"Invalid pipeline version: {pipeline_version}. Supported versions are {_AVAILABLE_PIPELINE_VERSIONS}." + ) + + if vl_rec_backend is not None and vl_rec_backend not in _SUPPORTED_VL_BACKENDS: + raise ValueError( + f"Invalid backend for the VL recognition module: {vl_rec_backend}. Supported values are {_SUPPORTED_VL_BACKENDS}." + ) + + params = locals().copy() + params.pop("self") + params.pop("pipeline_version") + params.pop("kwargs") + self._params = params + self.pipeline_version = pipeline_version + + super().__init__(**kwargs) + + @property + def _paddlex_pipeline_name(self): + if self.pipeline_version == "v1": + return "PaddleOCR-VL" + elif self.pipeline_version == "v1.5": + return "PaddleOCR-VL-1.5" + elif self.pipeline_version == "v1.6": + return "PaddleOCR-VL-1.6" + else: + raise AssertionError(f"Unknown pipeline version: {self.pipeline_version}") + + def predict_iter( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_layout_detection=None, + use_chart_recognition=None, + use_seal_recognition=None, + use_ocr_for_image_block=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + layout_shape_mode="auto", + use_queues=None, + prompt_label=None, + format_block_content=None, + repetition_penalty=None, + temperature=None, + top_p=None, + min_pixels=None, + max_pixels=None, + max_new_tokens=None, + merge_layout_blocks=None, + markdown_ignore_labels=None, + vlm_extra_args=None, + **kwargs, + ): + return self.paddlex_pipeline.predict( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_layout_detection=use_layout_detection, + use_chart_recognition=use_chart_recognition, + use_seal_recognition=use_seal_recognition, + use_ocr_for_image_block=use_ocr_for_image_block, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + layout_shape_mode=layout_shape_mode, + use_queues=use_queues, + prompt_label=prompt_label, + format_block_content=format_block_content, + repetition_penalty=repetition_penalty, + temperature=temperature, + top_p=top_p, + min_pixels=min_pixels, + max_pixels=max_pixels, + max_new_tokens=max_new_tokens, + merge_layout_blocks=merge_layout_blocks, + markdown_ignore_labels=markdown_ignore_labels, + vlm_extra_args=vlm_extra_args, + **kwargs, + ) + + def predict( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_layout_detection=None, + use_chart_recognition=None, + use_seal_recognition=None, + use_ocr_for_image_block=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + layout_shape_mode="auto", + use_queues=None, + prompt_label=None, + format_block_content=None, + repetition_penalty=None, + temperature=None, + top_p=None, + min_pixels=None, + max_pixels=None, + max_new_tokens=None, + merge_layout_blocks=None, + markdown_ignore_labels=None, + vlm_extra_args=None, + **kwargs, + ): + return list( + self.predict_iter( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_layout_detection=use_layout_detection, + use_chart_recognition=use_chart_recognition, + use_seal_recognition=use_seal_recognition, + use_ocr_for_image_block=use_ocr_for_image_block, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + layout_shape_mode=layout_shape_mode, + use_queues=use_queues, + prompt_label=prompt_label, + format_block_content=format_block_content, + repetition_penalty=repetition_penalty, + temperature=temperature, + top_p=top_p, + min_pixels=min_pixels, + max_pixels=max_pixels, + max_new_tokens=max_new_tokens, + merge_layout_blocks=merge_layout_blocks, + markdown_ignore_labels=markdown_ignore_labels, + vlm_extra_args=vlm_extra_args, + **kwargs, + ) + ) + + def concatenate_markdown_pages(self, markdown_list): + return self.paddlex_pipeline.concatenate_markdown_pages(markdown_list) + + def restructure_pages( + self, res_list, merge_tables=True, relevel_titles=True, concatenate_pages=False + ): + return list( + self.paddlex_pipeline.restructure_pages( + res_list, + merge_tables=merge_tables, + relevel_titles=relevel_titles, + concatenate_pages=concatenate_pages, + ) + ) + + @classmethod + def get_cli_subcommand_executor(cls): + return PaddleOCRVLCLISubcommandExecutor() + + def _get_paddlex_config_overrides(self): + STRUCTURE = { + "SubPipelines.DocPreprocessor.use_doc_orientation_classify": self._params[ + "use_doc_orientation_classify" + ], + "SubPipelines.DocPreprocessor.use_doc_unwarping": self._params[ + "use_doc_unwarping" + ], + "use_doc_preprocessor": self._params["use_doc_orientation_classify"] + or self._params["use_doc_unwarping"], + "use_layout_detection": self._params["use_layout_detection"], + "use_chart_recognition": self._params["use_chart_recognition"], + "format_block_content": self._params["format_block_content"], + "merge_layout_blocks": self._params["merge_layout_blocks"], + "markdown_ignore_labels": self._params["markdown_ignore_labels"], + "use_queues": self._params["use_queues"], + "SubModules.LayoutDetection.model_name": self._params[ + "layout_detection_model_name" + ], + "SubModules.LayoutDetection.model_dir": self._params[ + "layout_detection_model_dir" + ], + "SubModules.LayoutDetection.threshold": self._params["layout_threshold"], + "SubModules.LayoutDetection.layout_nms": self._params["layout_nms"], + "SubModules.LayoutDetection.layout_unclip_ratio": self._params[ + "layout_unclip_ratio" + ], + "SubModules.LayoutDetection.layout_merge_bboxes_mode": self._params[ + "layout_merge_bboxes_mode" + ], + "SubModules.VLRecognition.model_name": self._params["vl_rec_model_name"], + "SubModules.VLRecognition.model_dir": self._params["vl_rec_model_dir"], + "SubModules.VLRecognition.genai_config.backend": self._params[ + "vl_rec_backend" + ], + "SubModules.VLRecognition.genai_config.server_url": self._params[ + "vl_rec_server_url" + ], + "SubModules.VLRecognition.genai_config.max_concurrency": self._params[ + "vl_rec_max_concurrency" + ], + "SubModules.VLRecognition.genai_config.client_kwargs.model_name": self._params[ + "vl_rec_api_model_name" + ], + "SubModules.VLRecognition.genai_config.client_kwargs.api_key": self._params[ + "vl_rec_api_key" + ], + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_name": self._params[ + "doc_orientation_classify_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_dir": self._params[ + "doc_orientation_classify_model_dir" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_name": self._params[ + "doc_unwarping_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_dir": self._params[ + "doc_unwarping_model_dir" + ], + "use_seal_recognition": self._params["use_seal_recognition"], + "use_ocr_for_image_block": self._params["use_ocr_for_image_block"], + } + return create_config_from_structure(STRUCTURE) + + +class PaddleOCRVLCLISubcommandExecutor(PipelineCLISubcommandExecutor): + @property + def subparser_name(self): + return "doc_parser" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + subparser.add_argument( + "--pipeline_version", + type=str, + default=_DEFAULT_PIPELINE_VERSION, + choices=_AVAILABLE_PIPELINE_VERSIONS, + ) + + subparser.add_argument( + "--layout_detection_model_name", + type=str, + help="Name of the layout analysis model.", + ) + subparser.add_argument( + "--layout_detection_model_dir", + type=str, + help="Path to the layout analysis model directory.", + ) + subparser.add_argument( + "--layout_threshold", + type=float, + help="Score threshold for the layout analysis model.", + ) + subparser.add_argument( + "--layout_nms", + type=str2bool, + help="Whether to use NMS in layout analysis.", + ) + subparser.add_argument( + "--layout_unclip_ratio", + type=float, + help="Expansion coefficient for layout analysis.", + ) + subparser.add_argument( + "--layout_merge_bboxes_mode", + type=str, + help="Overlapping box filtering method.", + ) + + subparser.add_argument( + "--vl_rec_model_name", + type=str, + help="Name of the VL recognition model.", + ) + subparser.add_argument( + "--vl_rec_model_dir", + type=str, + help="Path to the VL recognition model directory.", + ) + subparser.add_argument( + "--vl_rec_backend", + type=str, + help="Backend used by the VL recognition module.", + choices=_SUPPORTED_VL_BACKENDS, + ) + subparser.add_argument( + "--vl_rec_server_url", + type=str, + help="Server URL used by the VL recognition module.", + ) + subparser.add_argument( + "--vl_rec_max_concurrency", + type=int, + help="Maximum concurrency for making VLM requests.", + ) + subparser.add_argument( + "--vl_rec_api_model_name", + type=str, + help="Model name for the VLM server.", + ) + subparser.add_argument( + "--vl_rec_api_key", + type=str, + help="API key for the VLM server.", + ) + + subparser.add_argument( + "--doc_orientation_classify_model_name", + type=str, + help="Name of the document image orientation classification model.", + ) + subparser.add_argument( + "--doc_orientation_classify_model_dir", + type=str, + help="Path to the document image orientation classification model directory.", + ) + subparser.add_argument( + "--doc_unwarping_model_name", + type=str, + help="Name of the text image unwarping model.", + ) + subparser.add_argument( + "--doc_unwarping_model_dir", + type=str, + help="Path to the image unwarping model directory.", + ) + + subparser.add_argument( + "--use_doc_orientation_classify", + type=str2bool, + help="Whether to use document image orientation classification.", + ) + subparser.add_argument( + "--use_doc_unwarping", + type=str2bool, + help="Whether to use text image unwarping.", + ) + subparser.add_argument( + "--use_layout_detection", + type=str2bool, + help="Whether to use layout analysis.", + ) + subparser.add_argument( + "--use_chart_recognition", + type=str2bool, + help="Whether to use chart recognition.", + ) + subparser.add_argument( + "--use_seal_recognition", + type=str2bool, + help="Whether to use seal recognition.", + ) + subparser.add_argument( + "--use_ocr_for_image_block", + type=str2bool, + help="Whether to use OCR for image blocks.", + ) + subparser.add_argument( + "--format_block_content", + type=str2bool, + help="Whether to format block content to Markdown.", + ) + subparser.add_argument( + "--merge_layout_blocks", + type=str2bool, + help="Whether to merge layout blocks.", + ) + subparser.add_argument( + "--markdown_ignore_labels", + type=str, + nargs="+", + help="List of layout labels to ignore in Markdown output.", + ) + + subparser.add_argument( + "--use_queues", + type=str2bool, + help="Whether to use queues for asynchronous processing.", + ) + + subparser.add_argument( + "--layout_shape_mode", + type=str, + default="auto", + help="Mode for layout shape.", + ) + subparser.add_argument( + "--prompt_label", + type=str, + help="Prompt label for the VLM.", + ) + subparser.add_argument( + "--repetition_penalty", + type=float, + help="Repetition penalty used in sampling for the VLM.", + ) + subparser.add_argument( + "--temperature", + type=float, + help="Temperature parameter used in sampling for the VLM.", + ) + subparser.add_argument( + "--top_p", + type=float, + help="Top-p parameter used in sampling for the VLM.", + ) + subparser.add_argument( + "--min_pixels", + type=int, + help="Minimum pixels for image preprocessing for the VLM.", + ) + subparser.add_argument( + "--max_pixels", + type=int, + help="Maximum pixels for image preprocessing for the VLM.", + ) + subparser.add_argument( + "--max_new_tokens", + type=int, + help="Maximum number of tokens generated by the VLM.", + ) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference( + PaddleOCRVL, + params, + predict_param_names={ + "layout_shape_mode", + "prompt_label", + "repetition_penalty", + "temperature", + "top_p", + "min_pixels", + "max_pixels", + "max_new_tokens", + }, + ) diff --git a/paddleocr/_pipelines/pp_chatocrv4_doc.py b/paddleocr/_pipelines/pp_chatocrv4_doc.py new file mode 100644 index 0000000..c9f0f92 --- /dev/null +++ b/paddleocr/_pipelines/pp_chatocrv4_doc.py @@ -0,0 +1,745 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + get_subcommand_args, + str2bool, +) +from .base import PaddleXPipelineWrapper, PipelineCLISubcommandExecutor +from .utils import create_config_from_structure + + +class PPChatOCRv4Doc(PaddleXPipelineWrapper): + def __init__( + self, + layout_detection_model_name=None, + layout_detection_model_dir=None, + doc_orientation_classify_model_name=None, + doc_orientation_classify_model_dir=None, + doc_unwarping_model_name=None, + doc_unwarping_model_dir=None, + text_detection_model_name=None, + text_detection_model_dir=None, + textline_orientation_model_name=None, + textline_orientation_model_dir=None, + textline_orientation_batch_size=None, + text_recognition_model_name=None, + text_recognition_model_dir=None, + text_recognition_batch_size=None, + table_structure_recognition_model_name=None, + table_structure_recognition_model_dir=None, + seal_text_detection_model_name=None, + seal_text_detection_model_dir=None, + seal_text_recognition_model_name=None, + seal_text_recognition_model_dir=None, + seal_text_recognition_batch_size=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + use_seal_recognition=None, + use_table_recognition=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_rec_score_thresh=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_rec_score_thresh=None, + retriever_config=None, + mllm_chat_bot_config=None, + chat_bot_config=None, + **kwargs, + ): + params = locals().copy() + params.pop("self") + params.pop("kwargs") + self._params = params + + super().__init__(**kwargs) + + @property + def _paddlex_pipeline_name(self): + return "PP-ChatOCRv4-doc" + + def save_vector(self, vector_info, save_path, retriever_config=None): + return self.paddlex_pipeline.save_vector( + vector_info=vector_info, + save_path=save_path, + retriever_config=retriever_config, + ) + + def load_vector(self, data_path, retriever_config=None): + return self.paddlex_pipeline.load_vector( + data_path=data_path, retriever_config=retriever_config + ) + + def load_visual_info_list(self, data_path): + return self.paddlex_pipeline.load_visual_info_list(data_path=data_path) + + def save_visual_info_list(self, visual_info, save_path): + return self.paddlex_pipeline.save_visual_info_list( + visual_info=visual_info, save_path=save_path + ) + + def visual_predict_iter( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + use_seal_recognition=None, + use_table_recognition=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_rec_score_thresh=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_rec_score_thresh=None, + **kwargs, + ): + return self.paddlex_pipeline.visual_predict( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_textline_orientation=use_textline_orientation, + use_seal_recognition=use_seal_recognition, + use_table_recognition=use_table_recognition, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + text_det_limit_side_len=text_det_limit_side_len, + text_det_limit_type=text_det_limit_type, + text_det_thresh=text_det_thresh, + text_det_box_thresh=text_det_box_thresh, + text_det_unclip_ratio=text_det_unclip_ratio, + text_rec_score_thresh=text_rec_score_thresh, + seal_det_limit_side_len=seal_det_limit_side_len, + seal_det_limit_type=seal_det_limit_type, + seal_det_thresh=seal_det_thresh, + seal_det_box_thresh=seal_det_box_thresh, + seal_det_unclip_ratio=seal_det_unclip_ratio, + seal_rec_score_thresh=seal_rec_score_thresh, + **kwargs, + ) + + def visual_predict( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + use_seal_recognition=None, + use_table_recognition=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_rec_score_thresh=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_rec_score_thresh=None, + **kwargs, + ): + return list( + self.visual_predict_iter( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_textline_orientation=use_textline_orientation, + use_seal_recognition=use_seal_recognition, + use_table_recognition=use_table_recognition, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + text_det_limit_side_len=text_det_limit_side_len, + text_det_limit_type=text_det_limit_type, + text_det_thresh=text_det_thresh, + text_det_box_thresh=text_det_box_thresh, + text_det_unclip_ratio=text_det_unclip_ratio, + text_rec_score_thresh=text_rec_score_thresh, + seal_det_limit_side_len=seal_det_limit_side_len, + seal_det_limit_type=seal_det_limit_type, + seal_det_thresh=seal_det_thresh, + seal_det_box_thresh=seal_det_box_thresh, + seal_det_unclip_ratio=seal_det_unclip_ratio, + seal_rec_score_thresh=seal_rec_score_thresh, + **kwargs, + ) + ) + + def build_vector( + self, + visual_info, + *, + min_characters=3500, + block_size=300, + flag_save_bytes_vector=False, + retriever_config=None, + ): + return self.paddlex_pipeline.build_vector( + visual_info, + min_characters=min_characters, + block_size=block_size, + flag_save_bytes_vector=flag_save_bytes_vector, + retriever_config=retriever_config, + ) + + def mllm_pred(self, input, key_list, *, mllm_chat_bot_config=None): + return self.paddlex_pipeline.mllm_pred( + input, + key_list, + mllm_chat_bot_config=mllm_chat_bot_config, + ) + + def chat( + self, + key_list, + visual_info, + *, + use_vector_retrieval=True, + vector_info=None, + min_characters=3500, + text_task_description=None, + text_output_format=None, + text_rules_str=None, + text_few_shot_demo_text_content=None, + text_few_shot_demo_key_value_list=None, + table_task_description=None, + table_output_format=None, + table_rules_str=None, + table_few_shot_demo_text_content=None, + table_few_shot_demo_key_value_list=None, + mllm_predict_info=None, + mllm_integration_strategy="integration", + chat_bot_config=None, + retriever_config=None, + ): + return self.paddlex_pipeline.chat( + key_list, + visual_info, + use_vector_retrieval=use_vector_retrieval, + vector_info=vector_info, + min_characters=min_characters, + text_task_description=text_task_description, + text_output_format=text_output_format, + text_rules_str=text_rules_str, + text_few_shot_demo_text_content=text_few_shot_demo_text_content, + text_few_shot_demo_key_value_list=text_few_shot_demo_key_value_list, + table_task_description=table_task_description, + table_output_format=table_output_format, + table_rules_str=table_rules_str, + table_few_shot_demo_text_content=table_few_shot_demo_text_content, + table_few_shot_demo_key_value_list=table_few_shot_demo_key_value_list, + mllm_predict_info=mllm_predict_info, + mllm_integration_strategy=mllm_integration_strategy, + chat_bot_config=chat_bot_config, + retriever_config=retriever_config, + ) + + @classmethod + def get_cli_subcommand_executor(cls): + return PPChatOCRv4DocCLISubcommandExecutor() + + def _get_paddlex_config_overrides(self): + STRUCTURE = { + "SubPipelines.LayoutParser.SubModules.LayoutDetection.model_name": self._params[ + "layout_detection_model_name" + ], + "SubPipelines.LayoutParser.SubModules.LayoutDetection.model_dir": self._params[ + "layout_detection_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_name": self._params[ + "doc_orientation_classify_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_dir": self._params[ + "doc_orientation_classify_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_name": self._params[ + "doc_unwarping_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_dir": self._params[ + "doc_unwarping_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.model_name": self._params[ + "text_detection_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.model_dir": self._params[ + "text_detection_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.model_name": self._params[ + "textline_orientation_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.model_dir": self._params[ + "textline_orientation_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.batch_size": self._params[ + "textline_orientation_batch_size" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextRecognition.model_name": self._params[ + "text_recognition_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextRecognition.model_dir": self._params[ + "text_recognition_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextRecognition.batch_size": self._params[ + "text_recognition_batch_size" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.TableStructureRecognition.model_name": self._params[ + "table_structure_recognition_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.TableStructureRecognition.model_dir": self._params[ + "table_structure_recognition_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.model_name": self._params[ + "seal_text_detection_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.model_dir": self._params[ + "seal_text_detection_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextRecognition.model_name": self._params[ + "seal_text_recognition_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextRecognition.model_dir": self._params[ + "seal_text_recognition_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextRecognition.batch_size": self._params[ + "seal_text_recognition_batch_size" + ], + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.use_doc_orientation_classify": self._params[ + "use_doc_orientation_classify" + ], + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.use_doc_unwarping": self._params[ + "use_doc_unwarping" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.use_textline_orientation": self._params[ + "use_textline_orientation" + ], + "SubPipelines.LayoutParser.use_doc_preprocessor": self._params[ + "use_doc_orientation_classify" + ] + or self._params["use_doc_unwarping"], + "SubPipelines.LayoutParser.use_seal_recognition": self._params[ + "use_seal_recognition" + ], + "SubPipelines.LayoutParser.use_table_recognition": self._params[ + "use_table_recognition" + ], + "SubPipelines.LayoutParser.SubModules.LayoutDetection.threshold": self._params[ + "layout_threshold" + ], + "SubPipelines.LayoutParser.SubModules.LayoutDetection.nms": self._params[ + "layout_nms" + ], + "SubPipelines.LayoutParser.SubModules.LayoutDetection.unclip_ratio": self._params[ + "layout_unclip_ratio" + ], + "SubPipelines.LayoutParser.SubModules.LayoutDetection.merge_bboxes_mode": self._params[ + "layout_merge_bboxes_mode" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.limit_side_len": self._params[ + "text_det_limit_side_len" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.limit_type": self._params[ + "text_det_limit_type" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.thresh": self._params[ + "text_det_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.box_thresh": self._params[ + "text_det_box_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.unclip_ratio": self._params[ + "text_det_unclip_ratio" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextRecognition.score_thresh": self._params[ + "text_rec_score_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.limit_side_len": self._params[ + "text_det_limit_side_len" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.limit_type": self._params[ + "seal_det_limit_type" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.thresh": self._params[ + "seal_det_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.box_thresh": self._params[ + "seal_det_box_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.unclip_ratio": self._params[ + "seal_det_unclip_ratio" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextRecognition.score_thresh": self._params[ + "seal_rec_score_thresh" + ], + "SubModules.LLM_Retriever": self._params["retriever_config"], + "SubModules.MLLM_Chat": self._params["mllm_chat_bot_config"], + "SubModules.LLM_Chat": self._params["chat_bot_config"], + } + return create_config_from_structure(STRUCTURE) + + +class PPChatOCRv4DocCLISubcommandExecutor(PipelineCLISubcommandExecutor): + @property + def subparser_name(self): + return "pp_chatocrv4_doc" + + def _update_subparser(self, subparser): + subparser.add_argument( + "-i", + "--input", + type=str, + required=True, + help="Input path or URL.", + ) + subparser.add_argument( + "-k", + "--keys", + type=str, + nargs="+", + required=True, + metavar="KEY", + help="Keys use for information extraction.", + ) + subparser.add_argument( + "--save_path", + type=str, + help="Path to the output directory.", + ) + + subparser.add_argument( + "--invoke_mllm", + type=str2bool, + default=False, + help="Whether to invoke the multimodal large language model.", + ) + + subparser.add_argument( + "--layout_detection_model_name", + type=str, + help="Name of the layout detection model.", + ) + subparser.add_argument( + "--layout_detection_model_dir", + type=str, + help="Path to the layout detection model directory.", + ) + subparser.add_argument( + "--doc_orientation_classify_model_name", + type=str, + help="Name of the document image orientation classification model.", + ) + subparser.add_argument( + "--doc_orientation_classify_model_dir", + type=str, + help="Path to the document image orientation classification model directory.", + ) + subparser.add_argument( + "--doc_unwarping_model_name", + type=str, + help="Name of the text image unwarping model.", + ) + subparser.add_argument( + "--doc_unwarping_model_dir", + type=str, + help="Path to the image unwarping model directory.", + ) + subparser.add_argument( + "--text_detection_model_name", + type=str, + help="Name of the text detection model.", + ) + subparser.add_argument( + "--text_detection_model_dir", + type=str, + help="Path to the text detection model directory.", + ) + subparser.add_argument( + "--textline_orientation_model_name", + type=str, + help="Name of the text line orientation classification model.", + ) + subparser.add_argument( + "--textline_orientation_model_dir", + type=str, + help="Path to the text line orientation classification model directory.", + ) + subparser.add_argument( + "--textline_orientation_batch_size", + type=int, + help="Batch size for the text line orientation classification model.", + ) + subparser.add_argument( + "--text_recognition_model_name", + type=str, + help="Name of the text recognition model.", + ) + subparser.add_argument( + "--text_recognition_model_dir", + type=str, + help="Path to the text recognition model directory.", + ) + subparser.add_argument( + "--text_recognition_batch_size", + type=int, + help="Batch size for the text recognition model.", + ) + subparser.add_argument( + "--table_structure_recognition_model_name", + type=str, + help="Name of the table structure recognition model.", + ) + subparser.add_argument( + "--table_structure_recognition_model_dir", + type=str, + help="Path to the table structure recognition model directory.", + ) + subparser.add_argument( + "--seal_text_detection_model_name", + type=str, + help="Name of the seal text detection model.", + ) + subparser.add_argument( + "--seal_text_detection_model_dir", + type=str, + help="Path to the seal text detection model directory.", + ) + subparser.add_argument( + "--seal_text_recognition_model_name", + type=str, + help="Name of the seal text recognition model.", + ) + subparser.add_argument( + "--seal_text_recognition_model_dir", + type=str, + help="Path to the seal text recognition model directory.", + ) + subparser.add_argument( + "--seal_text_recognition_batch_size", + type=int, + help="Batch size for the seal text recognition model.", + ) + subparser.add_argument( + "--use_doc_orientation_classify", + type=str2bool, + help="Whether to use document image orientation classification.", + ) + subparser.add_argument( + "--use_doc_unwarping", + type=str2bool, + help="Whether to use text image unwarping.", + ) + subparser.add_argument( + "--use_textline_orientation", + type=str2bool, + help="Whether to use text line orientation classification.", + ) + subparser.add_argument( + "--use_seal_recognition", + type=str2bool, + help="Whether to use seal recognition.", + ) + subparser.add_argument( + "--use_table_recognition", + type=str2bool, + help="Whether to use table recognition.", + ) + # TODO: Support dict and list types + subparser.add_argument( + "--layout_threshold", + type=float, + help="Score threshold for the layout detection model.", + ) + subparser.add_argument( + "--layout_nms", + type=str2bool, + help="Whether to use NMS in layout detection.", + ) + subparser.add_argument( + "--layout_unclip_ratio", + type=float, + help="Expansion coefficient for layout detection.", + ) + subparser.add_argument( + "--layout_merge_bboxes_mode", + type=str, + help="Overlapping box filtering method.", + ) + subparser.add_argument( + "--text_det_limit_side_len", + type=int, + help="This sets a limit on the side length of the input image for the text detection model.", + ) + subparser.add_argument( + "--text_det_limit_type", + type=str, + help="This determines how the side length limit is applied to the input image before feeding it into the text deteciton model.", + ) + subparser.add_argument( + "--text_det_thresh", + type=float, + help="Detection pixel threshold for the text detection model. Pixels with scores greater than this threshold in the output probability map are considered text pixels.", + ) + subparser.add_argument( + "--text_det_box_thresh", + type=float, + help="Detection box threshold for the text detection model. A detection result is considered a text region if the average score of all pixels within the border of the result is greater than this threshold.", + ) + subparser.add_argument( + "--text_det_unclip_ratio", + type=float, + help="Text detection expansion coefficient, which expands the text region using this method. The larger the value, the larger the expansion area.", + ) + subparser.add_argument( + "--text_rec_score_thresh", + type=float, + help="Text recognition threshold used in general OCR. Text results with scores greater than this threshold are retained.", + ) + subparser.add_argument( + "--seal_det_limit_side_len", + type=int, + help="This sets a limit on the side length of the input image for the seal text detection model.", + ) + subparser.add_argument( + "--seal_det_limit_type", + type=str, + help="This determines how the side length limit is applied to the input image before feeding it into the seal text deteciton model.", + ) + subparser.add_argument( + "--seal_det_thresh", + type=float, + help="Detection pixel threshold for the seal text detection model. Pixels with scores greater than this threshold in the output probability map are considered text pixels.", + ) + subparser.add_argument( + "--seal_det_box_thresh", + type=float, + help="Detection box threshold for the seal text detection model. A detection result is considered a text region if the average score of all pixels within the border of the result is greater than this threshold.", + ) + subparser.add_argument( + "--seal_det_unclip_ratio", + type=float, + help="Seal text detection expansion coefficient, which expands the text region using this method. The larger the value, the larger the expansion area.", + ) + subparser.add_argument( + "--seal_rec_score_thresh", + type=float, + help="Seal text recognition threshold. Text results with scores greater than this threshold are retained.", + ) + + # FIXME: Passing API key through CLI is not secure; consider using + # environment variables. + subparser.add_argument( + "--qianfan_api_key", + type=str, + help="Configuration for the embedding model.", + ) + subparser.add_argument( + "--pp_docbee_base_url", + type=str, + help="Configuration for the multimodal large language model.", + ) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + input = params.pop("input") + keys = params.pop("keys") + save_path = params.pop("save_path") + invoke_mllm = params.pop("invoke_mllm") + qianfan_api_key = params.pop("qianfan_api_key") + if qianfan_api_key is not None: + params["retriever_config"] = { + "module_name": "retriever", + "model_name": "embedding-v1", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "qianfan", + "api_key": qianfan_api_key, + } + params["chat_bot_config"] = { + "module_name": "chat_bot", + "model_name": "ernie-3.5-8k", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "openai", + "api_key": qianfan_api_key, + } + pp_docbee_base_url = params.pop("pp_docbee_base_url") + if pp_docbee_base_url is not None: + params["mllm_chat_bot_config"] = { + "module_name": "chat_bot", + "model_name": "PP-DocBee", + # PaddleX requires endpoints such as ".../chat/completions", + # which, as the parameter name suggests, are not base URLs. + "base_url": pp_docbee_base_url, + "api_type": "openai", + "api_key": "fake_key", + } + + chatocr = PPChatOCRv4Doc(**params) + + result_visual = chatocr.visual_predict_iter(input) + + visual_info_list = [] + for res in result_visual: + visual_info_list.append(res["visual_info"]) + if save_path: + res["layout_parsing_result"].save_all(save_path) + + vector_info = chatocr.build_vector(visual_info_list) + + if invoke_mllm: + result_mllm = chatocr.mllm_pred(input, keys) + mllm_predict_info = result_mllm["mllm_res"] + else: + mllm_predict_info = None + + result_chat = chatocr.chat( + keys, + visual_info_list, + vector_info=vector_info, + mllm_predict_info=mllm_predict_info, + ) + + # Print the result to stdout + for k, v in result_chat["chat_res"].items(): + print(f"{k} {v}") diff --git a/paddleocr/_pipelines/pp_doctranslation.py b/paddleocr/_pipelines/pp_doctranslation.py new file mode 100644 index 0000000..22b907c --- /dev/null +++ b/paddleocr/_pipelines/pp_doctranslation.py @@ -0,0 +1,945 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + get_subcommand_args, + str2bool, +) +from .._utils.logging import logger +from .base import PaddleXPipelineWrapper, PipelineCLISubcommandExecutor +from .utils import create_config_from_structure + + +class PPDocTranslation(PaddleXPipelineWrapper): + def __init__( + self, + layout_detection_model_name=None, + layout_detection_model_dir=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + chart_recognition_model_name=None, + chart_recognition_model_dir=None, + chart_recognition_batch_size=None, + region_detection_model_name=None, + region_detection_model_dir=None, + doc_orientation_classify_model_name=None, + doc_orientation_classify_model_dir=None, + doc_unwarping_model_name=None, + doc_unwarping_model_dir=None, + text_detection_model_name=None, + text_detection_model_dir=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + textline_orientation_model_name=None, + textline_orientation_model_dir=None, + textline_orientation_batch_size=None, + text_recognition_model_name=None, + text_recognition_model_dir=None, + text_recognition_batch_size=None, + text_rec_score_thresh=None, + table_classification_model_name=None, + table_classification_model_dir=None, + wired_table_structure_recognition_model_name=None, + wired_table_structure_recognition_model_dir=None, + wireless_table_structure_recognition_model_name=None, + wireless_table_structure_recognition_model_dir=None, + wired_table_cells_detection_model_name=None, + wired_table_cells_detection_model_dir=None, + wireless_table_cells_detection_model_name=None, + wireless_table_cells_detection_model_dir=None, + table_orientation_classify_model_name=None, + table_orientation_classify_model_dir=None, + seal_text_detection_model_name=None, + seal_text_detection_model_dir=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_text_recognition_model_name=None, + seal_text_recognition_model_dir=None, + seal_text_recognition_batch_size=None, + seal_rec_score_thresh=None, + formula_recognition_model_name=None, + formula_recognition_model_dir=None, + formula_recognition_batch_size=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + use_seal_recognition=None, + use_table_recognition=None, + use_formula_recognition=None, + use_chart_recognition=None, + use_region_detection=None, + chat_bot_config=None, + **kwargs, + ): + params = locals().copy() + params.pop("self") + params.pop("kwargs") + self._params = params + + super().__init__(**kwargs) + + @property + def _paddlex_pipeline_name(self): + return "PP-DocTranslation" + + def visual_predict_iter( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + use_seal_recognition=None, + use_table_recognition=None, + use_formula_recognition=None, + use_chart_recognition=None, + use_region_detection=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_rec_score_thresh=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_rec_score_thresh=None, + use_wired_table_cells_trans_to_html=False, + use_wireless_table_cells_trans_to_html=False, + use_table_orientation_classify=True, + use_ocr_results_with_table_cells=True, + use_e2e_wired_table_rec_model=False, + use_e2e_wireless_table_rec_model=True, + **kwargs, + ): + return self.paddlex_pipeline.visual_predict( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_textline_orientation=use_textline_orientation, + use_seal_recognition=use_seal_recognition, + use_table_recognition=use_table_recognition, + use_formula_recognition=use_formula_recognition, + use_chart_recognition=use_chart_recognition, + use_region_detection=use_region_detection, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + text_det_limit_side_len=text_det_limit_side_len, + text_det_limit_type=text_det_limit_type, + text_det_thresh=text_det_thresh, + text_det_box_thresh=text_det_box_thresh, + text_det_unclip_ratio=text_det_unclip_ratio, + text_rec_score_thresh=text_rec_score_thresh, + seal_det_limit_side_len=seal_det_limit_side_len, + seal_det_limit_type=seal_det_limit_type, + seal_det_thresh=seal_det_thresh, + seal_det_box_thresh=seal_det_box_thresh, + seal_det_unclip_ratio=seal_det_unclip_ratio, + seal_rec_score_thresh=seal_rec_score_thresh, + use_wired_table_cells_trans_to_html=use_wired_table_cells_trans_to_html, + use_wireless_table_cells_trans_to_html=use_wireless_table_cells_trans_to_html, + use_table_orientation_classify=use_table_orientation_classify, + use_ocr_results_with_table_cells=use_ocr_results_with_table_cells, + use_e2e_wired_table_rec_model=use_e2e_wired_table_rec_model, + use_e2e_wireless_table_rec_model=use_e2e_wireless_table_rec_model, + **kwargs, + ) + + def visual_predict( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + use_seal_recognition=None, + use_table_recognition=None, + use_formula_recognition=None, + use_chart_recognition=None, + use_region_detection=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_rec_score_thresh=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_rec_score_thresh=None, + use_wired_table_cells_trans_to_html=False, + use_wireless_table_cells_trans_to_html=False, + use_table_orientation_classify=True, + use_ocr_results_with_table_cells=True, + use_e2e_wired_table_rec_model=False, + use_e2e_wireless_table_rec_model=True, + **kwargs, + ): + return list( + self.visual_predict_iter( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_textline_orientation=use_textline_orientation, + use_seal_recognition=use_seal_recognition, + use_table_recognition=use_table_recognition, + use_formula_recognition=use_formula_recognition, + use_chart_recognition=use_chart_recognition, + use_region_detection=use_region_detection, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + text_det_limit_side_len=text_det_limit_side_len, + text_det_limit_type=text_det_limit_type, + text_det_thresh=text_det_thresh, + text_det_box_thresh=text_det_box_thresh, + text_det_unclip_ratio=text_det_unclip_ratio, + text_rec_score_thresh=text_rec_score_thresh, + seal_det_limit_side_len=seal_det_limit_side_len, + seal_det_limit_type=seal_det_limit_type, + seal_det_thresh=seal_det_thresh, + seal_det_box_thresh=seal_det_box_thresh, + seal_det_unclip_ratio=seal_det_unclip_ratio, + seal_rec_score_thresh=seal_rec_score_thresh, + use_wired_table_cells_trans_to_html=use_wired_table_cells_trans_to_html, + use_wireless_table_cells_trans_to_html=use_wireless_table_cells_trans_to_html, + use_table_orientation_classify=use_table_orientation_classify, + use_ocr_results_with_table_cells=use_ocr_results_with_table_cells, + use_e2e_wired_table_rec_model=use_e2e_wired_table_rec_model, + use_e2e_wireless_table_rec_model=use_e2e_wireless_table_rec_model, + **kwargs, + ) + ) + + def translate_iter( + self, + ori_md_info_list, + *, + target_language="zh", + chunk_size=5000, + task_description=None, + output_format=None, + rules_str=None, + few_shot_demo_text_content=None, + few_shot_demo_key_value_list=None, + glossary=None, + llm_request_interval=0.0, + chat_bot_config=None, + **kwargs, + ): + return self.paddlex_pipeline.translate( + ori_md_info_list, + target_language=target_language, + chunk_size=chunk_size, + task_description=task_description, + output_format=output_format, + rules_str=rules_str, + few_shot_demo_text_content=few_shot_demo_text_content, + few_shot_demo_key_value_list=few_shot_demo_key_value_list, + glossary=glossary, + llm_request_interval=llm_request_interval, + chat_bot_config=chat_bot_config, + **kwargs, + ) + + def translate( + self, + ori_md_info_list, + *, + target_language="zh", + chunk_size=5000, + task_description=None, + output_format=None, + rules_str=None, + few_shot_demo_text_content=None, + few_shot_demo_key_value_list=None, + glossary=None, + llm_request_interval=0.0, + chat_bot_config=None, + **kwargs, + ): + return list( + self.translate_iter( + ori_md_info_list, + target_language=target_language, + chunk_size=chunk_size, + task_description=task_description, + output_format=output_format, + rules_str=rules_str, + few_shot_demo_text_content=few_shot_demo_text_content, + few_shot_demo_key_value_list=few_shot_demo_key_value_list, + glossary=glossary, + llm_request_interval=llm_request_interval, + chat_bot_config=chat_bot_config, + **kwargs, + ) + ) + + def load_from_markdown(self, input): + return self.paddlex_pipeline.load_from_markdown(input) + + def concatenate_markdown_pages(self, markdown_list): + return self.paddlex_pipeline.concatenate_markdown_pages(markdown_list) + + @classmethod + def get_cli_subcommand_executor(cls): + return PPDocTranslationCLISubcommandExecutor() + + def _get_paddlex_config_overrides(self): + # HACK: We should consider reducing duplication. + STRUCTURE = { + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.use_doc_orientation_classify": self._params[ + "use_doc_orientation_classify" + ], + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.use_doc_unwarping": self._params[ + "use_doc_unwarping" + ], + "SubPipelines.LayoutParser.use_doc_preprocessor": self._params[ + "use_doc_orientation_classify" + ] + or self._params["use_doc_unwarping"], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.use_textline_orientation": self._params[ + "use_textline_orientation" + ], + "SubPipelines.LayoutParser.use_seal_recognition": self._params[ + "use_seal_recognition" + ], + "SubPipelines.LayoutParser.use_table_recognition": self._params[ + "use_table_recognition" + ], + "SubPipelines.LayoutParser.use_formula_recognition": self._params[ + "use_formula_recognition" + ], + "SubPipelines.LayoutParser.use_chart_recognition": self._params[ + "use_chart_recognition" + ], + "SubPipelines.LayoutParser.use_region_detection": self._params[ + "use_region_detection" + ], + "SubPipelines.LayoutParser.SubModules.LayoutDetection.model_name": self._params[ + "layout_detection_model_name" + ], + "SubPipelines.LayoutParser.SubModules.LayoutDetection.model_dir": self._params[ + "layout_detection_model_dir" + ], + "SubPipelines.LayoutParser.SubModules.LayoutDetection.threshold": self._params[ + "layout_threshold" + ], + "SubPipelines.LayoutParser.SubModules.LayoutDetection.layout_nms": self._params[ + "layout_nms" + ], + "SubPipelines.LayoutParser.SubModules.LayoutDetection.layout_unclip_ratio": self._params[ + "layout_unclip_ratio" + ], + "SubPipelines.LayoutParser.SubModules.LayoutDetection.layout_merge_bboxes_mode": self._params[ + "layout_merge_bboxes_mode" + ], + "SubPipelines.LayoutParser.SubModules.ChartRecognition.model_name": self._params[ + "chart_recognition_model_name" + ], + "SubPipelines.LayoutParser.SubModules.ChartRecognition.model_dir": self._params[ + "chart_recognition_model_dir" + ], + "SubPipelines.LayoutParser.SubModules.ChartRecognition.batch_size": self._params[ + "chart_recognition_batch_size" + ], + "SubPipelines.LayoutParser.SubModules.RegionDetection.model_name": self._params[ + "region_detection_model_name" + ], + "SubPipelines.LayoutParser.SubModules.RegionDetection.model_dir": self._params[ + "region_detection_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_name": self._params[ + "doc_orientation_classify_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_dir": self._params[ + "doc_orientation_classify_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_name": self._params[ + "doc_unwarping_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_dir": self._params[ + "doc_unwarping_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.model_name": self._params[ + "text_detection_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.model_dir": self._params[ + "text_detection_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.limit_side_len": self._params[ + "text_det_limit_side_len" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.limit_type": self._params[ + "text_det_limit_type" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.thresh": self._params[ + "text_det_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.box_thresh": self._params[ + "text_det_box_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextDetection.unclip_ratio": self._params[ + "text_det_unclip_ratio" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.model_name": self._params[ + "textline_orientation_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.model_dir": self._params[ + "textline_orientation_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.batch_size": self._params[ + "textline_orientation_batch_size" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextRecognition.model_name": self._params[ + "text_recognition_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextRecognition.model_dir": self._params[ + "text_recognition_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextRecognition.batch_size": self._params[ + "text_recognition_batch_size" + ], + "SubPipelines.LayoutParser.SubPipelines.GeneralOCR.SubModules.TextRecognition.score_thresh": self._params[ + "text_rec_score_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.TableClassification.model_name": self._params[ + "table_classification_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.TableClassification.model_dir": self._params[ + "table_classification_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.WiredTableStructureRecognition.model_name": self._params[ + "wired_table_structure_recognition_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.WiredTableStructureRecognition.model_dir": self._params[ + "wired_table_structure_recognition_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.WirelessTableStructureRecognition.model_name": self._params[ + "wireless_table_structure_recognition_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.WirelessTableStructureRecognition.model_dir": self._params[ + "wireless_table_structure_recognition_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.WiredTableCellsDetection.model_name": self._params[ + "wired_table_cells_detection_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.WiredTableCellsDetection.model_dir": self._params[ + "wired_table_cells_detection_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.WirelessTableCellsDetection.model_name": self._params[ + "wireless_table_cells_detection_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.WirelessTableCellsDetection.model_dir": self._params[ + "wireless_table_cells_detection_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.TableOrientationClassify.model_name": self._params[ + "table_orientation_classify_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubModules.TableOrientationClassify.model_dir": self._params[ + "table_orientation_classify_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.model_name": self._params[ + "text_detection_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.model_dir": self._params[ + "text_detection_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.limit_side_len": self._params[ + "text_det_limit_side_len" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.limit_type": self._params[ + "text_det_limit_type" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.thresh": self._params[ + "text_det_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.box_thresh": self._params[ + "text_det_box_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.unclip_ratio": self._params[ + "text_det_unclip_ratio" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.model_name": self._params[ + "textline_orientation_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.model_dir": self._params[ + "textline_orientation_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.batch_size": self._params[ + "textline_orientation_batch_size" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextRecognition.model_name": self._params[ + "text_recognition_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextRecognition.model_dir": self._params[ + "text_recognition_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextRecognition.batch_size": self._params[ + "text_recognition_batch_size" + ], + "SubPipelines.LayoutParser.SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextRecognition.score_thresh": self._params[ + "text_rec_score_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.model_name": self._params[ + "seal_text_detection_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.model_dir": self._params[ + "seal_text_detection_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.limit_side_len": self._params[ + "text_det_limit_side_len" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.limit_type": self._params[ + "seal_det_limit_type" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.thresh": self._params[ + "seal_det_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.box_thresh": self._params[ + "seal_det_box_thresh" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.unclip_ratio": self._params[ + "seal_det_unclip_ratio" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextRecognition.model_name": self._params[ + "seal_text_recognition_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextRecognition.model_dir": self._params[ + "seal_text_recognition_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextRecognition.batch_size": self._params[ + "seal_text_recognition_batch_size" + ], + "SubPipelines.LayoutParser.SubPipelines.FormulaRecognition.SubModules.FormulaRecognition.model_name": self._params[ + "formula_recognition_model_name" + ], + "SubPipelines.LayoutParser.SubPipelines.FormulaRecognition.SubModules.FormulaRecognition.model_dir": self._params[ + "formula_recognition_model_dir" + ], + "SubPipelines.LayoutParser.SubPipelines.FormulaRecognition.SubModules.FormulaRecognition.batch_size": self._params[ + "formula_recognition_batch_size" + ], + "SubModules.LLM_Chat": self._params["chat_bot_config"], + } + return create_config_from_structure(STRUCTURE) + + +class PPDocTranslationCLISubcommandExecutor(PipelineCLISubcommandExecutor): + @property + def subparser_name(self): + return "pp_doctranslation" + + def _update_subparser(self, subparser): + subparser.add_argument( + "-i", + "--input", + type=str, + required=True, + help="Input path or URL.", + ) + subparser.add_argument( + "--save_path", + type=str, + help="Path to the output directory.", + ) + + subparser.add_argument( + "--target_language", + type=str, + default="zh", + help="Target language.", + ) + + subparser.add_argument( + "--layout_detection_model_name", + type=str, + help="Name of the layout detection model.", + ) + subparser.add_argument( + "--layout_detection_model_dir", + type=str, + help="Path to the layout detection model directory.", + ) + subparser.add_argument( + "--layout_threshold", + type=float, + help="Score threshold for the layout detection model.", + ) + subparser.add_argument( + "--layout_nms", + type=str2bool, + help="Whether to use NMS in layout detection.", + ) + subparser.add_argument( + "--layout_unclip_ratio", + type=float, + help="Expansion coefficient for layout detection.", + ) + subparser.add_argument( + "--layout_merge_bboxes_mode", + type=str, + help="Overlapping box filtering method.", + ) + + subparser.add_argument( + "--chart_recognition_model_name", + type=str, + help="Name of the chart recognition model.", + ) + subparser.add_argument( + "--chart_recognition_model_dir", + type=str, + help="Path to the chart recognition model directory.", + ) + subparser.add_argument( + "--chart_recognition_batch_size", + type=int, + help="Batch size for the chart recognition model.", + ) + + subparser.add_argument( + "--region_detection_model_name", + type=str, + help="Name of the region detection model.", + ) + subparser.add_argument( + "--region_detection_model_dir", + type=str, + help="Path to the region detection model directory.", + ) + + subparser.add_argument( + "--doc_orientation_classify_model_name", + type=str, + help="Name of the document image orientation classification model.", + ) + subparser.add_argument( + "--doc_orientation_classify_model_dir", + type=str, + help="Path to the document image orientation classification model directory.", + ) + subparser.add_argument( + "--doc_unwarping_model_name", + type=str, + help="Name of the text image unwarping model.", + ) + subparser.add_argument( + "--doc_unwarping_model_dir", + type=str, + help="Path to the image unwarping model directory.", + ) + + subparser.add_argument( + "--text_detection_model_name", + type=str, + help="Name of the text detection model.", + ) + subparser.add_argument( + "--text_detection_model_dir", + type=str, + help="Path to the text detection model directory.", + ) + subparser.add_argument( + "--text_det_limit_side_len", + type=int, + help="This sets a limit on the side length of the input image for the text detection model.", + ) + subparser.add_argument( + "--text_det_limit_type", + type=str, + help="This determines how the side length limit is applied to the input image before feeding it into the text deteciton model.", + ) + subparser.add_argument( + "--text_det_thresh", + type=float, + help="Detection pixel threshold for the text detection model. Pixels with scores greater than this threshold in the output probability map are considered text pixels.", + ) + subparser.add_argument( + "--text_det_box_thresh", + type=float, + help="Detection box threshold for the text detection model. A detection result is considered a text region if the average score of all pixels within the border of the result is greater than this threshold.", + ) + subparser.add_argument( + "--text_det_unclip_ratio", + type=float, + help="Text detection expansion coefficient, which expands the text region using this method. The larger the value, the larger the expansion area.", + ) + subparser.add_argument( + "--textline_orientation_model_name", + type=str, + help="Name of the text line orientation classification model.", + ) + subparser.add_argument( + "--textline_orientation_model_dir", + type=str, + help="Path to the text line orientation classification directory.", + ) + subparser.add_argument( + "--textline_orientation_batch_size", + type=int, + help="Batch size for the text line orientation classification model.", + ) + subparser.add_argument( + "--text_recognition_model_name", + type=str, + help="Name of the text recognition model.", + ) + subparser.add_argument( + "--text_recognition_model_dir", + type=str, + help="Path to the text recognition model directory.", + ) + subparser.add_argument( + "--text_recognition_batch_size", + type=int, + help="Batch size for the text recognition model.", + ) + subparser.add_argument( + "--text_rec_score_thresh", + type=float, + help="Text recognition threshold used in general OCR. Text results with scores greater than this threshold are retained.", + ) + + subparser.add_argument( + "--table_classification_model_name", + type=str, + help="Name of the table classification model.", + ) + subparser.add_argument( + "--table_classification_model_dir", + type=str, + help="Path to the table classification model directory.", + ) + subparser.add_argument( + "--wired_table_structure_recognition_model_name", + type=str, + help="Name of the wired table structure recognition model.", + ) + subparser.add_argument( + "--wired_table_structure_recognition_model_dir", + type=str, + help="Path to the wired table structure recognition model directory.", + ) + subparser.add_argument( + "--wireless_table_structure_recognition_model_name", + type=str, + help="Name of the wireless table structure recognition model.", + ) + subparser.add_argument( + "--wireless_table_structure_recognition_model_dir", + type=str, + help="Path to the wired table structure recognition model directory.", + ) + subparser.add_argument( + "--wired_table_cells_detection_model_name", + type=str, + help="Name of the wired table cells detection model.", + ) + subparser.add_argument( + "--wired_table_cells_detection_model_dir", + type=str, + help="Path to the wired table cells detection model directory.", + ) + subparser.add_argument( + "--wireless_table_cells_detection_model_name", + type=str, + help="Name of the wireless table cells detection model.", + ) + subparser.add_argument( + "--wireless_table_cells_detection_model_dir", + type=str, + help="Path to the wireless table cells detection model directory.", + ) + + subparser.add_argument( + "--seal_text_detection_model_name", + type=str, + help="Name of the seal text detection model.", + ) + subparser.add_argument( + "--seal_text_detection_model_dir", + type=str, + help="Path to the seal text detection model directory.", + ) + subparser.add_argument( + "--seal_det_limit_side_len", + type=int, + help="This sets a limit on the side length of the input image for the seal text detection model.", + ) + subparser.add_argument( + "--seal_det_limit_type", + type=str, + help="This determines how the side length limit is applied to the input image before feeding it into the seal text deteciton model.", + ) + subparser.add_argument( + "--seal_det_thresh", + type=float, + help="Detection pixel threshold for the seal text detection model. Pixels with scores greater than this threshold in the output probability map are considered text pixels.", + ) + subparser.add_argument( + "--seal_det_box_thresh", + type=float, + help="Detection box threshold for the seal text detection model. A detection result is considered a text region if the average score of all pixels within the border of the result is greater than this threshold.", + ) + subparser.add_argument( + "--seal_det_unclip_ratio", + type=float, + help="Seal text detection expansion coefficient, which expands the text region using this method. The larger the value, the larger the expansion area.", + ) + subparser.add_argument( + "--seal_text_recognition_model_name", + type=str, + help="Name of the seal text recognition model.", + ) + subparser.add_argument( + "--seal_text_recognition_model_dir", + type=str, + help="Path to the seal text recognition model directory.", + ) + subparser.add_argument( + "--seal_text_recognition_batch_size", + type=int, + help="Batch size for the seal text recognition model.", + ) + subparser.add_argument( + "--seal_rec_score_thresh", + type=float, + help="Seal text recognition threshold. Text results with scores greater than this threshold are retained.", + ) + + subparser.add_argument( + "--formula_recognition_model_name", + type=str, + help="Name of the formula recognition model.", + ) + subparser.add_argument( + "--formula_recognition_model_dir", + type=str, + help="Path to the formula recognition model directory.", + ) + subparser.add_argument( + "--formula_recognition_batch_size", + type=int, + help="Batch size for the formula recognition model.", + ) + + subparser.add_argument( + "--use_doc_orientation_classify", + type=str2bool, + help="Whether to use document image orientation classification.", + ) + subparser.add_argument( + "--use_doc_unwarping", + type=str2bool, + help="Whether to use text image unwarping.", + ) + subparser.add_argument( + "--use_textline_orientation", + type=str2bool, + help="Whether to use text line orientation classification.", + ) + subparser.add_argument( + "--use_seal_recognition", + type=str2bool, + help="Whether to use seal recognition.", + ) + subparser.add_argument( + "--use_table_recognition", + type=str2bool, + help="Whether to use table recognition.", + ) + subparser.add_argument( + "--use_formula_recognition", + type=str2bool, + help="Whether to use formula recognition.", + ) + subparser.add_argument( + "--use_chart_recognition", + type=str2bool, + help="Whether to use chart recognition.", + ) + subparser.add_argument( + "--use_region_detection", + type=str2bool, + help="Whether to use region detection.", + ) + + # FIXME: Passing API key through CLI is not secure; consider using + # environment variables. + subparser.add_argument( + "--qianfan_api_key", + type=str, + help="Configuration for the embedding model.", + ) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + input = params.pop("input") + target_language = params.pop("target_language") + save_path = params.pop("save_path") + qianfan_api_key = params.pop("qianfan_api_key") + if qianfan_api_key is not None: + params["chat_bot_config"] = { + "module_name": "chat_bot", + "model_name": "ernie-3.5-8k", + "base_url": "https://qianfan.baidubce.com/v2", + "api_type": "openai", + "api_key": qianfan_api_key, + } + + chatocr = PPDocTranslation(**params) + + logger.info("Start analyzing images") + result_visual = chatocr.visual_predict_iter(input) + + ori_md_info_list = [] + for res in result_visual: + ori_md_info_list.append(res["layout_parsing_result"].markdown) + if save_path: + res["layout_parsing_result"].save_all(save_path) + + logger.info("Start translation") + result_translate = chatocr.translate_iter( + ori_md_info_list, + target_language=target_language, + ) + + for res in result_translate: + res.print() + if save_path: + res.save_to_markdown(save_path) diff --git a/paddleocr/_pipelines/pp_structurev3.py b/paddleocr/_pipelines/pp_structurev3.py new file mode 100644 index 0000000..eefe7d1 --- /dev/null +++ b/paddleocr/_pipelines/pp_structurev3.py @@ -0,0 +1,1032 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import warnings +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, + str2bool, +) +from .base import PaddleXPipelineWrapper, PipelineCLISubcommandExecutor +from .utils import create_config_from_structure +from ._patch_layout_parsing import apply_patches as _apply_layout_parsing_patches + +_apply_layout_parsing_patches() + +_SUPPORTED_OCR_VERSIONS = ["PP-OCRv3", "PP-OCRv4", "PP-OCRv5"] + + +class PPStructureV3(PaddleXPipelineWrapper): + def __init__( + self, + layout_detection_model_name=None, + layout_detection_model_dir=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + chart_recognition_model_name=None, + chart_recognition_model_dir=None, + chart_recognition_batch_size=None, + region_detection_model_name=None, + region_detection_model_dir=None, + doc_orientation_classify_model_name=None, + doc_orientation_classify_model_dir=None, + doc_unwarping_model_name=None, + doc_unwarping_model_dir=None, + text_detection_model_name=None, + text_detection_model_dir=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + textline_orientation_model_name=None, + textline_orientation_model_dir=None, + textline_orientation_batch_size=None, + text_recognition_model_name=None, + text_recognition_model_dir=None, + text_recognition_batch_size=None, + text_rec_score_thresh=None, + table_classification_model_name=None, + table_classification_model_dir=None, + wired_table_structure_recognition_model_name=None, + wired_table_structure_recognition_model_dir=None, + wireless_table_structure_recognition_model_name=None, + wireless_table_structure_recognition_model_dir=None, + wired_table_cells_detection_model_name=None, + wired_table_cells_detection_model_dir=None, + wireless_table_cells_detection_model_name=None, + wireless_table_cells_detection_model_dir=None, + table_orientation_classify_model_name=None, + table_orientation_classify_model_dir=None, + seal_text_detection_model_name=None, + seal_text_detection_model_dir=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_text_recognition_model_name=None, + seal_text_recognition_model_dir=None, + seal_text_recognition_batch_size=None, + seal_rec_score_thresh=None, + formula_recognition_model_name=None, + formula_recognition_model_dir=None, + formula_recognition_batch_size=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + use_seal_recognition=None, + use_table_recognition=None, + use_formula_recognition=None, + use_chart_recognition=None, + use_region_detection=None, + format_block_content=None, + markdown_ignore_labels=None, + lang=None, + ocr_version=None, + **kwargs, + ): + if ocr_version is not None and ocr_version not in _SUPPORTED_OCR_VERSIONS: + raise ValueError( + f"Invalid OCR version: {ocr_version}. Supported values are {_SUPPORTED_OCR_VERSIONS}." + ) + + if all( + map( + lambda p: p is None, + ( + text_detection_model_name, + text_detection_model_dir, + text_recognition_model_name, + text_recognition_model_dir, + ), + ) + ): + if lang is not None or ocr_version is not None: + det_model_name, rec_model_name = self._get_ocr_model_names( + lang, ocr_version + ) + if det_model_name is None or rec_model_name is None: + raise ValueError( + f"No models are available for the language {repr(lang)} and OCR version {repr(ocr_version)}." + ) + text_detection_model_name = det_model_name + text_recognition_model_name = rec_model_name + else: + if lang is not None or ocr_version is not None: + warnings.warn( + "`lang` and `ocr_version` will be ignored when model names or model directories are not `None`.", + stacklevel=2, + ) + params = locals().copy() + params["text_detection_model_name"] = text_detection_model_name + params["text_recognition_model_name"] = text_recognition_model_name + params.pop("self") + params.pop("kwargs") + self._params = params + + super().__init__(**kwargs) + + @property + def _paddlex_pipeline_name(self): + return "PP-StructureV3" + + def predict_iter( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + use_seal_recognition=None, + use_table_recognition=None, + use_formula_recognition=None, + use_chart_recognition=None, + use_region_detection=None, + format_block_content=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_rec_score_thresh=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_rec_score_thresh=None, + use_wired_table_cells_trans_to_html=False, + use_wireless_table_cells_trans_to_html=False, + use_table_orientation_classify=True, + use_ocr_results_with_table_cells=True, + use_e2e_wired_table_rec_model=False, + use_e2e_wireless_table_rec_model=True, + markdown_ignore_labels=None, + **kwargs, + ): + return self.paddlex_pipeline.predict( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_textline_orientation=use_textline_orientation, + use_seal_recognition=use_seal_recognition, + use_table_recognition=use_table_recognition, + use_formula_recognition=use_formula_recognition, + use_chart_recognition=use_chart_recognition, + use_region_detection=use_region_detection, + format_block_content=format_block_content, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + text_det_limit_side_len=text_det_limit_side_len, + text_det_limit_type=text_det_limit_type, + text_det_thresh=text_det_thresh, + text_det_box_thresh=text_det_box_thresh, + text_det_unclip_ratio=text_det_unclip_ratio, + text_rec_score_thresh=text_rec_score_thresh, + seal_det_limit_side_len=seal_det_limit_side_len, + seal_det_limit_type=seal_det_limit_type, + seal_det_thresh=seal_det_thresh, + seal_det_box_thresh=seal_det_box_thresh, + seal_det_unclip_ratio=seal_det_unclip_ratio, + seal_rec_score_thresh=seal_rec_score_thresh, + use_wired_table_cells_trans_to_html=use_wired_table_cells_trans_to_html, + use_wireless_table_cells_trans_to_html=use_wireless_table_cells_trans_to_html, + use_table_orientation_classify=use_table_orientation_classify, + use_ocr_results_with_table_cells=use_ocr_results_with_table_cells, + use_e2e_wired_table_rec_model=use_e2e_wired_table_rec_model, + use_e2e_wireless_table_rec_model=use_e2e_wireless_table_rec_model, + markdown_ignore_labels=markdown_ignore_labels, + **kwargs, + ) + + def predict( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + use_seal_recognition=None, + use_table_recognition=None, + use_formula_recognition=None, + use_chart_recognition=None, + use_region_detection=None, + format_block_content=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_rec_score_thresh=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_rec_score_thresh=None, + use_wired_table_cells_trans_to_html=False, + use_wireless_table_cells_trans_to_html=False, + use_table_orientation_classify=True, + use_ocr_results_with_table_cells=True, + use_e2e_wired_table_rec_model=False, + use_e2e_wireless_table_rec_model=True, + markdown_ignore_labels=None, + **kwargs, + ): + return list( + self.predict_iter( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_textline_orientation=use_textline_orientation, + use_seal_recognition=use_seal_recognition, + use_table_recognition=use_table_recognition, + use_formula_recognition=use_formula_recognition, + use_chart_recognition=use_chart_recognition, + use_region_detection=use_region_detection, + format_block_content=format_block_content, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + text_det_limit_side_len=text_det_limit_side_len, + text_det_limit_type=text_det_limit_type, + text_det_thresh=text_det_thresh, + text_det_box_thresh=text_det_box_thresh, + text_det_unclip_ratio=text_det_unclip_ratio, + text_rec_score_thresh=text_rec_score_thresh, + seal_det_limit_side_len=seal_det_limit_side_len, + seal_det_limit_type=seal_det_limit_type, + seal_det_thresh=seal_det_thresh, + seal_det_box_thresh=seal_det_box_thresh, + seal_det_unclip_ratio=seal_det_unclip_ratio, + seal_rec_score_thresh=seal_rec_score_thresh, + use_wired_table_cells_trans_to_html=use_wired_table_cells_trans_to_html, + use_wireless_table_cells_trans_to_html=use_wireless_table_cells_trans_to_html, + use_table_orientation_classify=use_table_orientation_classify, + use_ocr_results_with_table_cells=use_ocr_results_with_table_cells, + use_e2e_wired_table_rec_model=use_e2e_wired_table_rec_model, + use_e2e_wireless_table_rec_model=use_e2e_wireless_table_rec_model, + markdown_ignore_labels=markdown_ignore_labels, + **kwargs, + ) + ) + + def concatenate_markdown_pages(self, markdown_list): + return self.paddlex_pipeline.concatenate_markdown_pages(markdown_list) + + @classmethod + def get_cli_subcommand_executor(cls): + return PPStructureV3CLISubcommandExecutor() + + def _get_paddlex_config_overrides(self): + STRUCTURE = { + "SubPipelines.DocPreprocessor.use_doc_orientation_classify": self._params[ + "use_doc_orientation_classify" + ], + "SubPipelines.DocPreprocessor.use_doc_unwarping": self._params[ + "use_doc_unwarping" + ], + "use_doc_preprocessor": self._params["use_doc_orientation_classify"] + or self._params["use_doc_unwarping"], + "SubPipelines.GeneralOCR.use_textline_orientation": self._params[ + "use_textline_orientation" + ], + "use_seal_recognition": self._params["use_seal_recognition"], + "use_table_recognition": self._params["use_table_recognition"], + "use_formula_recognition": self._params["use_formula_recognition"], + "use_chart_recognition": self._params["use_chart_recognition"], + "use_region_detection": self._params["use_region_detection"], + "format_block_content": self._params["format_block_content"], + "markdown_ignore_labels": self._params["markdown_ignore_labels"], + "SubModules.LayoutDetection.model_name": self._params[ + "layout_detection_model_name" + ], + "SubModules.LayoutDetection.model_dir": self._params[ + "layout_detection_model_dir" + ], + "SubModules.LayoutDetection.threshold": self._params["layout_threshold"], + "SubModules.LayoutDetection.layout_nms": self._params["layout_nms"], + "SubModules.LayoutDetection.layout_unclip_ratio": self._params[ + "layout_unclip_ratio" + ], + "SubModules.LayoutDetection.layout_merge_bboxes_mode": self._params[ + "layout_merge_bboxes_mode" + ], + "SubModules.ChartRecognition.model_name": self._params[ + "chart_recognition_model_name" + ], + "SubModules.ChartRecognition.model_dir": self._params[ + "chart_recognition_model_dir" + ], + "SubModules.ChartRecognition.batch_size": self._params[ + "chart_recognition_batch_size" + ], + "SubModules.RegionDetection.model_name": self._params[ + "region_detection_model_name" + ], + "SubModules.RegionDetection.model_dir": self._params[ + "region_detection_model_dir" + ], + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_name": self._params[ + "doc_orientation_classify_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_dir": self._params[ + "doc_orientation_classify_model_dir" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_name": self._params[ + "doc_unwarping_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_dir": self._params[ + "doc_unwarping_model_dir" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.model_name": self._params[ + "text_detection_model_name" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.model_dir": self._params[ + "text_detection_model_dir" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.limit_side_len": self._params[ + "text_det_limit_side_len" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.limit_type": self._params[ + "text_det_limit_type" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.thresh": self._params[ + "text_det_thresh" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.box_thresh": self._params[ + "text_det_box_thresh" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.unclip_ratio": self._params[ + "text_det_unclip_ratio" + ], + "SubPipelines.GeneralOCR.SubModules.TextLineOrientation.model_name": self._params[ + "textline_orientation_model_name" + ], + "SubPipelines.GeneralOCR.SubModules.TextLineOrientation.model_dir": self._params[ + "textline_orientation_model_dir" + ], + "SubPipelines.GeneralOCR.SubModules.TextLineOrientation.batch_size": self._params[ + "textline_orientation_batch_size" + ], + "SubPipelines.GeneralOCR.SubModules.TextRecognition.model_name": self._params[ + "text_recognition_model_name" + ], + "SubPipelines.GeneralOCR.SubModules.TextRecognition.model_dir": self._params[ + "text_recognition_model_dir" + ], + "SubPipelines.GeneralOCR.SubModules.TextRecognition.batch_size": self._params[ + "text_recognition_batch_size" + ], + "SubPipelines.GeneralOCR.SubModules.TextRecognition.score_thresh": self._params[ + "text_rec_score_thresh" + ], + "SubPipelines.TableRecognition.SubModules.TableClassification.model_name": self._params[ + "table_classification_model_name" + ], + "SubPipelines.TableRecognition.SubModules.TableClassification.model_dir": self._params[ + "table_classification_model_dir" + ], + "SubPipelines.TableRecognition.SubModules.WiredTableStructureRecognition.model_name": self._params[ + "wired_table_structure_recognition_model_name" + ], + "SubPipelines.TableRecognition.SubModules.WiredTableStructureRecognition.model_dir": self._params[ + "wired_table_structure_recognition_model_dir" + ], + "SubPipelines.TableRecognition.SubModules.WirelessTableStructureRecognition.model_name": self._params[ + "wireless_table_structure_recognition_model_name" + ], + "SubPipelines.TableRecognition.SubModules.WirelessTableStructureRecognition.model_dir": self._params[ + "wireless_table_structure_recognition_model_dir" + ], + "SubPipelines.TableRecognition.SubModules.WiredTableCellsDetection.model_name": self._params[ + "wired_table_cells_detection_model_name" + ], + "SubPipelines.TableRecognition.SubModules.WiredTableCellsDetection.model_dir": self._params[ + "wired_table_cells_detection_model_dir" + ], + "SubPipelines.TableRecognition.SubModules.WirelessTableCellsDetection.model_name": self._params[ + "wireless_table_cells_detection_model_name" + ], + "SubPipelines.TableRecognition.SubModules.WirelessTableCellsDetection.model_dir": self._params[ + "wireless_table_cells_detection_model_dir" + ], + "SubPipelines.TableRecognition.SubModules.TableOrientationClassify.model_name": self._params[ + "table_orientation_classify_model_name" + ], + "SubPipelines.TableRecognition.SubModules.TableOrientationClassify.model_dir": self._params[ + "table_orientation_classify_model_dir" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.model_name": self._params[ + "text_detection_model_name" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.model_dir": self._params[ + "text_detection_model_dir" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.limit_side_len": self._params[ + "text_det_limit_side_len" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.limit_type": self._params[ + "text_det_limit_type" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.thresh": self._params[ + "text_det_thresh" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.box_thresh": self._params[ + "text_det_box_thresh" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextDetection.unclip_ratio": self._params[ + "text_det_unclip_ratio" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.model_name": self._params[ + "textline_orientation_model_name" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.model_dir": self._params[ + "textline_orientation_model_dir" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextLineOrientation.batch_size": self._params[ + "textline_orientation_batch_size" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextRecognition.model_name": self._params[ + "text_recognition_model_name" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextRecognition.model_dir": self._params[ + "text_recognition_model_dir" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextRecognition.batch_size": self._params[ + "text_recognition_batch_size" + ], + "SubPipelines.TableRecognition.SubPipelines.GeneralOCR.SubModules.TextRecognition.score_thresh": self._params[ + "text_rec_score_thresh" + ], + "SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.model_name": self._params[ + "seal_text_detection_model_name" + ], + "SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.model_dir": self._params[ + "seal_text_detection_model_dir" + ], + "SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.limit_side_len": self._params[ + "text_det_limit_side_len" + ], + "SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.limit_type": self._params[ + "seal_det_limit_type" + ], + "SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.thresh": self._params[ + "seal_det_thresh" + ], + "SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.box_thresh": self._params[ + "seal_det_box_thresh" + ], + "SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextDetection.unclip_ratio": self._params[ + "seal_det_unclip_ratio" + ], + "SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextRecognition.model_name": self._params[ + "seal_text_recognition_model_name" + ], + "SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextRecognition.model_dir": self._params[ + "seal_text_recognition_model_dir" + ], + "SubPipelines.SealRecognition.SubPipelines.SealOCR.SubModules.TextRecognition.batch_size": self._params[ + "seal_text_recognition_batch_size" + ], + "SubPipelines.FormulaRecognition.SubModules.FormulaRecognition.model_name": self._params[ + "formula_recognition_model_name" + ], + "SubPipelines.FormulaRecognition.SubModules.FormulaRecognition.model_dir": self._params[ + "formula_recognition_model_dir" + ], + "SubPipelines.FormulaRecognition.SubModules.FormulaRecognition.batch_size": self._params[ + "formula_recognition_batch_size" + ], + } + return create_config_from_structure(STRUCTURE) + + def _get_ocr_model_names(self, lang, ppocr_version): + LATIN_LANGS = [ + "af", + "az", + "bs", + "cs", + "cy", + "da", + "de", + "es", + "et", + "fr", + "ga", + "hr", + "hu", + "id", + "is", + "it", + "ku", + "la", + "lt", + "lv", + "mi", + "ms", + "mt", + "nl", + "no", + "oc", + "pi", + "pl", + "pt", + "ro", + "rs_latin", + "sk", + "sl", + "sq", + "sv", + "sw", + "tl", + "tr", + "uz", + "vi", + "french", + "german", + ] + ARABIC_LANGS = ["ar", "fa", "ug", "ur"] + ESLAV_LANGS = ["ru", "be", "uk"] + CYRILLIC_LANGS = [ + "ru", + "rs_cyrillic", + "be", + "bg", + "uk", + "mn", + "abq", + "ady", + "kbd", + "ava", + "dar", + "inh", + "che", + "lbe", + "lez", + "tab", + ] + DEVANAGARI_LANGS = [ + "hi", + "mr", + "ne", + "bh", + "mai", + "ang", + "bho", + "mah", + "sck", + "new", + "gom", + "sa", + "bgc", + ] + SPECIFIC_LANGS = [ + "ch", + "en", + "korean", + "japan", + "chinese_cht", + "te", + "ka", + "ta", + ] + + if lang is None: + lang = "ch" + + if ppocr_version is None: + if ( + lang + in ["ch", "chinese_cht", "en", "japan", "korean", "th", "el"] + + LATIN_LANGS + + ESLAV_LANGS + ): + ppocr_version = "PP-OCRv5" + elif lang in ( + LATIN_LANGS + + ARABIC_LANGS + + CYRILLIC_LANGS + + DEVANAGARI_LANGS + + SPECIFIC_LANGS + ): + ppocr_version = "PP-OCRv3" + else: + # Unknown language specified + return None, None + + if ppocr_version == "PP-OCRv5": + rec_lang, rec_model_name = None, None + if lang in ("ch", "chinese_cht", "en", "japan"): + rec_model_name = "PP-OCRv5_server_rec" + elif lang in LATIN_LANGS: + rec_lang = "latin" + elif lang in ESLAV_LANGS: + rec_lang = "eslav" + elif lang == "korean": + rec_lang = "korean" + elif lang == "th": + rec_lang = "th" + elif lang == "el": + rec_lang = "el" + + if rec_lang is not None: + rec_model_name = f"{rec_lang}_PP-OCRv5_mobile_rec" + return "PP-OCRv5_server_det", rec_model_name + + elif ppocr_version == "PP-OCRv4": + if lang == "ch": + return "PP-OCRv4_mobile_det", "PP-OCRv4_mobile_rec" + elif lang == "en": + return "PP-OCRv4_mobile_det", "en_PP-OCRv4_mobile_rec" + else: + return None, None + else: + # PP-OCRv3 + rec_lang = None + if lang in LATIN_LANGS: + rec_lang = "latin" + elif lang in ARABIC_LANGS: + rec_lang = "arabic" + elif lang in CYRILLIC_LANGS: + rec_lang = "cyrillic" + elif lang in DEVANAGARI_LANGS: + rec_lang = "devanagari" + else: + if lang in SPECIFIC_LANGS: + rec_lang = lang + + rec_model_name = None + if rec_lang == "ch": + rec_model_name = "PP-OCRv3_mobile_rec" + elif rec_lang is not None: + rec_model_name = f"{rec_lang}_PP-OCRv3_mobile_rec" + return "PP-OCRv3_mobile_det", rec_model_name + + +class PPStructureV3CLISubcommandExecutor(PipelineCLISubcommandExecutor): + @property + def subparser_name(self): + return "pp_structurev3" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + subparser.add_argument( + "--layout_detection_model_name", + type=str, + help="Name of the layout detection model.", + ) + subparser.add_argument( + "--layout_detection_model_dir", + type=str, + help="Path to the layout detection model directory.", + ) + subparser.add_argument( + "--layout_threshold", + type=float, + help="Score threshold for the layout detection model.", + ) + subparser.add_argument( + "--layout_nms", + type=str2bool, + help="Whether to use NMS in layout detection.", + ) + subparser.add_argument( + "--layout_unclip_ratio", + type=float, + help="Expansion coefficient for layout detection.", + ) + subparser.add_argument( + "--layout_merge_bboxes_mode", + type=str, + help="Overlapping box filtering method.", + ) + + subparser.add_argument( + "--chart_recognition_model_name", + type=str, + help="Name of the chart recognition model.", + ) + subparser.add_argument( + "--chart_recognition_model_dir", + type=str, + help="Path to the chart recognition model directory.", + ) + subparser.add_argument( + "--chart_recognition_batch_size", + type=int, + help="Batch size for the chart recognition model.", + ) + + subparser.add_argument( + "--region_detection_model_name", + type=str, + help="Name of the region detection model.", + ) + subparser.add_argument( + "--region_detection_model_dir", + type=str, + help="Path to the region detection model directory.", + ) + + subparser.add_argument( + "--doc_orientation_classify_model_name", + type=str, + help="Name of the document image orientation classification model.", + ) + subparser.add_argument( + "--doc_orientation_classify_model_dir", + type=str, + help="Path to the document image orientation classification model directory.", + ) + subparser.add_argument( + "--doc_unwarping_model_name", + type=str, + help="Name of the text image unwarping model.", + ) + subparser.add_argument( + "--doc_unwarping_model_dir", + type=str, + help="Path to the image unwarping model directory.", + ) + + subparser.add_argument( + "--text_detection_model_name", + type=str, + help="Name of the text detection model.", + ) + subparser.add_argument( + "--text_detection_model_dir", + type=str, + help="Path to the text detection model directory.", + ) + subparser.add_argument( + "--text_det_limit_side_len", + type=int, + help="This sets a limit on the side length of the input image for the text detection model.", + ) + subparser.add_argument( + "--text_det_limit_type", + type=str, + help="This determines how the side length limit is applied to the input image before feeding it into the text deteciton model.", + ) + subparser.add_argument( + "--text_det_thresh", + type=float, + help="Detection pixel threshold for the text detection model. Pixels with scores greater than this threshold in the output probability map are considered text pixels.", + ) + subparser.add_argument( + "--text_det_box_thresh", + type=float, + help="Detection box threshold for the text detection model. A detection result is considered a text region if the average score of all pixels within the border of the result is greater than this threshold.", + ) + subparser.add_argument( + "--text_det_unclip_ratio", + type=float, + help="Text detection expansion coefficient, which expands the text region using this method. The larger the value, the larger the expansion area.", + ) + subparser.add_argument( + "--textline_orientation_model_name", + type=str, + help="Name of the text line orientation classification model.", + ) + subparser.add_argument( + "--textline_orientation_model_dir", + type=str, + help="Path to the text line orientation classification directory.", + ) + subparser.add_argument( + "--textline_orientation_batch_size", + type=int, + help="Batch size for the text line orientation classification model.", + ) + subparser.add_argument( + "--text_recognition_model_name", + type=str, + help="Name of the text recognition model.", + ) + subparser.add_argument( + "--text_recognition_model_dir", + type=str, + help="Path to the text recognition model directory.", + ) + subparser.add_argument( + "--text_recognition_batch_size", + type=int, + help="Batch size for the text recognition model.", + ) + subparser.add_argument( + "--text_rec_score_thresh", + type=float, + help="Text recognition threshold used in general OCR. Text results with scores greater than this threshold are retained.", + ) + + subparser.add_argument( + "--table_classification_model_name", + type=str, + help="Name of the table classification model.", + ) + subparser.add_argument( + "--table_classification_model_dir", + type=str, + help="Path to the table classification model directory.", + ) + subparser.add_argument( + "--wired_table_structure_recognition_model_name", + type=str, + help="Name of the wired table structure recognition model.", + ) + subparser.add_argument( + "--wired_table_structure_recognition_model_dir", + type=str, + help="Path to the wired table structure recognition model directory.", + ) + subparser.add_argument( + "--wireless_table_structure_recognition_model_name", + type=str, + help="Name of the wireless table structure recognition model.", + ) + subparser.add_argument( + "--wireless_table_structure_recognition_model_dir", + type=str, + help="Path to the wired table structure recognition model directory.", + ) + subparser.add_argument( + "--wired_table_cells_detection_model_name", + type=str, + help="Name of the wired table cells detection model.", + ) + subparser.add_argument( + "--wired_table_cells_detection_model_dir", + type=str, + help="Path to the wired table cells detection model directory.", + ) + subparser.add_argument( + "--wireless_table_cells_detection_model_name", + type=str, + help="Name of the wireless table cells detection model.", + ) + subparser.add_argument( + "--wireless_table_cells_detection_model_dir", + type=str, + help="Path to the wireless table cells detection model directory.", + ) + + subparser.add_argument( + "--seal_text_detection_model_name", + type=str, + help="Name of the seal text detection model.", + ) + subparser.add_argument( + "--seal_text_detection_model_dir", + type=str, + help="Path to the seal text detection model directory.", + ) + subparser.add_argument( + "--seal_det_limit_side_len", + type=int, + help="This sets a limit on the side length of the input image for the seal text detection model.", + ) + subparser.add_argument( + "--seal_det_limit_type", + type=str, + help="This determines how the side length limit is applied to the input image before feeding it into the seal text deteciton model.", + ) + subparser.add_argument( + "--seal_det_thresh", + type=float, + help="Detection pixel threshold for the seal text detection model. Pixels with scores greater than this threshold in the output probability map are considered text pixels.", + ) + subparser.add_argument( + "--seal_det_box_thresh", + type=float, + help="Detection box threshold for the seal text detection model. A detection result is considered a text region if the average score of all pixels within the border of the result is greater than this threshold.", + ) + subparser.add_argument( + "--seal_det_unclip_ratio", + type=float, + help="Seal text detection expansion coefficient, which expands the text region using this method. The larger the value, the larger the expansion area.", + ) + subparser.add_argument( + "--seal_text_recognition_model_name", + type=str, + help="Name of the seal text recognition model.", + ) + subparser.add_argument( + "--seal_text_recognition_model_dir", + type=str, + help="Path to the seal text recognition model directory.", + ) + subparser.add_argument( + "--seal_text_recognition_batch_size", + type=int, + help="Batch size for the seal text recognition model.", + ) + subparser.add_argument( + "--seal_rec_score_thresh", + type=float, + help="Seal text recognition threshold. Text results with scores greater than this threshold are retained.", + ) + + subparser.add_argument( + "--formula_recognition_model_name", + type=str, + help="Name of the formula recognition model.", + ) + subparser.add_argument( + "--formula_recognition_model_dir", + type=str, + help="Path to the formula recognition model directory.", + ) + subparser.add_argument( + "--formula_recognition_batch_size", + type=int, + help="Batch size for the formula recognition model.", + ) + + subparser.add_argument( + "--use_doc_orientation_classify", + type=str2bool, + help="Whether to use document image orientation classification.", + ) + subparser.add_argument( + "--use_doc_unwarping", + type=str2bool, + help="Whether to use text image unwarping.", + ) + subparser.add_argument( + "--use_textline_orientation", + type=str2bool, + help="Whether to use text line orientation classification.", + ) + subparser.add_argument( + "--use_seal_recognition", + type=str2bool, + help="Whether to use seal recognition.", + ) + subparser.add_argument( + "--use_table_recognition", + type=str2bool, + help="Whether to use table recognition.", + ) + subparser.add_argument( + "--use_formula_recognition", + type=str2bool, + help="Whether to use formula recognition.", + ) + subparser.add_argument( + "--use_chart_recognition", + type=str2bool, + help="Whether to use chart recognition.", + ) + subparser.add_argument( + "--use_region_detection", + type=str2bool, + help="Whether to use region detection.", + ) + + subparser.add_argument( + "--format_block_content", + type=str2bool, + help="Whether to format block content to Markdown.", + ) + subparser.add_argument( + "--markdown_ignore_labels", + type=str, + nargs="+", + help="List of layout labels to ignore in Markdown output.", + ) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference( + PPStructureV3, + params, + ) diff --git a/paddleocr/_pipelines/seal_recognition.py b/paddleocr/_pipelines/seal_recognition.py new file mode 100644 index 0000000..b018531 --- /dev/null +++ b/paddleocr/_pipelines/seal_recognition.py @@ -0,0 +1,377 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, + str2bool, +) +from .base import PaddleXPipelineWrapper, PipelineCLISubcommandExecutor +from .utils import create_config_from_structure + + +class SealRecognition(PaddleXPipelineWrapper): + def __init__( + self, + doc_orientation_classify_model_name=None, + doc_orientation_classify_model_dir=None, + doc_unwarping_model_name=None, + doc_unwarping_model_dir=None, + layout_detection_model_name=None, + layout_detection_model_dir=None, + seal_text_detection_model_name=None, + seal_text_detection_model_dir=None, + text_recognition_model_name=None, + text_recognition_model_dir=None, + text_recognition_batch_size=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_layout_detection=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_rec_score_thresh=None, + **kwargs, + ): + + self._params = { + "doc_orientation_classify_model_name": doc_orientation_classify_model_name, + "doc_orientation_classify_model_dir": doc_orientation_classify_model_dir, + "doc_unwarping_model_name": doc_unwarping_model_name, + "doc_unwarping_model_dir": doc_unwarping_model_dir, + "layout_detection_model_name": layout_detection_model_name, + "layout_detection_model_dir": layout_detection_model_dir, + "seal_text_detection_model_name": seal_text_detection_model_name, + "seal_text_detection_model_dir": seal_text_detection_model_dir, + "text_recognition_model_name": text_recognition_model_name, + "text_recognition_model_dir": text_recognition_model_dir, + "text_recognition_batch_size": text_recognition_batch_size, + "use_doc_orientation_classify": use_doc_orientation_classify, + "use_doc_unwarping": use_doc_unwarping, + "use_layout_detection": use_layout_detection, + "layout_threshold": layout_threshold, + "layout_nms": layout_nms, + "layout_unclip_ratio": layout_unclip_ratio, + "layout_merge_bboxes_mode": layout_merge_bboxes_mode, + "seal_det_limit_side_len": seal_det_limit_side_len, + "seal_det_limit_type": seal_det_limit_type, + "seal_det_thresh": seal_det_thresh, + "seal_det_box_thresh": seal_det_box_thresh, + "seal_det_unclip_ratio": seal_det_unclip_ratio, + "seal_rec_score_thresh": seal_rec_score_thresh, + } + super().__init__(**kwargs) + + @property + def _paddlex_pipeline_name(self): + return "seal_recognition" + + def predict_iter( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_layout_detection=None, + layout_det_res=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_rec_score_thresh=None, + **kwargs, + ): + return self.paddlex_pipeline.predict( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_layout_detection=use_layout_detection, + layout_det_res=layout_det_res, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + seal_det_limit_side_len=seal_det_limit_side_len, + seal_det_limit_type=seal_det_limit_type, + seal_det_thresh=seal_det_thresh, + seal_det_box_thresh=seal_det_box_thresh, + seal_det_unclip_ratio=seal_det_unclip_ratio, + seal_rec_score_thresh=seal_rec_score_thresh, + **kwargs, + ) + + def predict( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_layout_detection=None, + layout_det_res=None, + layout_threshold=None, + layout_nms=None, + layout_unclip_ratio=None, + layout_merge_bboxes_mode=None, + seal_det_limit_side_len=None, + seal_det_limit_type=None, + seal_det_thresh=None, + seal_det_box_thresh=None, + seal_det_unclip_ratio=None, + seal_rec_score_thresh=None, + **kwargs, + ): + return list( + self.predict_iter( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_layout_detection=use_layout_detection, + layout_det_res=layout_det_res, + layout_threshold=layout_threshold, + layout_nms=layout_nms, + layout_unclip_ratio=layout_unclip_ratio, + layout_merge_bboxes_mode=layout_merge_bboxes_mode, + seal_det_limit_side_len=seal_det_limit_side_len, + seal_det_limit_type=seal_det_limit_type, + seal_det_thresh=seal_det_thresh, + seal_det_box_thresh=seal_det_box_thresh, + seal_det_unclip_ratio=seal_det_unclip_ratio, + seal_rec_score_thresh=seal_rec_score_thresh, + **kwargs, + ) + ) + + @classmethod + def get_cli_subcommand_executor(cls): + return SealRecognitionCLISubcommandExecutor() + + def _get_paddlex_config_overrides(self): + STRUCTURE = { + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_name": self._params[ + "doc_orientation_classify_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_dir": self._params[ + "doc_orientation_classify_model_dir" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_name": self._params[ + "doc_unwarping_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_dir": self._params[ + "doc_unwarping_model_dir" + ], + "SubModules.LayoutDetection.model_name": self._params[ + "layout_detection_model_name" + ], + "SubModules.LayoutDetection.model_dir": self._params[ + "layout_detection_model_dir" + ], + "SubModules.LayoutDetection.threshold": self._params["layout_threshold"], + "SubModules.LayoutDetection.layout_nms": self._params["layout_nms"], + "SubModules.LayoutDetection.layout_unclip_ratio": self._params[ + "layout_unclip_ratio" + ], + "SubModules.LayoutDetection.layout_merge_bboxes_mode": self._params[ + "layout_merge_bboxes_mode" + ], + "SubPipelines.DocPreprocessor.use_doc_orientation_classify": self._params[ + "use_doc_orientation_classify" + ], + "SubPipelines.DocPreprocessor.use_doc_unwarping": self._params[ + "use_doc_unwarping" + ], + "use_doc_preprocessor": self._params["use_doc_orientation_classify"] + or self._params["use_doc_unwarping"], + "SubPipelines.SealOCR.SubModules.TextDetection.model_name": self._params[ + "seal_text_detection_model_name" + ], + "SubPipelines.SealOCR.SubModules.TextDetection.model_dir": self._params[ + "seal_text_detection_model_dir" + ], + "SubPipelines.SealOCR.SubModules.TextDetection.limit_side_len": self._params[ + "seal_det_limit_side_len" + ], + "SubPipelines.SealOCR.SubModules.TextDetection.limit_type": self._params[ + "seal_det_limit_type" + ], + "SubPipelines.SealOCR.SubModules.TextDetection.thresh": self._params[ + "seal_det_thresh" + ], + "SubPipelines.SealOCR.SubModules.TextDetection.box_thresh": self._params[ + "seal_det_box_thresh" + ], + "SubPipelines.SealOCR.SubModules.TextDetection.unclip_ratio": self._params[ + "seal_det_unclip_ratio" + ], + "SubPipelines.SealOCR.SubModules.TextRecognition.model_name": self._params[ + "text_recognition_model_name" + ], + "SubPipelines.SealOCR.SubModules.TextRecognition.model_dir": self._params[ + "text_recognition_model_dir" + ], + "SubPipelines.SealOCR.SubModules.TextRecognition.batch_size": self._params[ + "text_recognition_batch_size" + ], + "SubPipelines.SealOCR.SubModules.TextRecognition.score_thresh": self._params[ + "seal_rec_score_thresh" + ], + "use_layout_detection": self._params["use_layout_detection"], + } + return create_config_from_structure(STRUCTURE) + + +class SealRecognitionCLISubcommandExecutor(PipelineCLISubcommandExecutor): + @property + def subparser_name(self): + return "seal_recognition" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + subparser.add_argument( + "--doc_orientation_classify_model_name", + type=str, + help="Name of the document image orientation classification model.", + ) + subparser.add_argument( + "--doc_orientation_classify_model_dir", + type=str, + help="Path to the document image orientation classification model directory.", + ) + subparser.add_argument( + "--doc_unwarping_model_name", + type=str, + help="Name of the document image unwarping model.", + ) + subparser.add_argument( + "--doc_unwarping_model_dir", + type=str, + help="Path to the document image unwarping model directory.", + ) + subparser.add_argument( + "--layout_detection_model_name", + type=str, + help="Name of the layout detection model.", + ) + subparser.add_argument( + "--layout_detection_model_dir", + type=str, + help="Path to the layout detection model directory.", + ) + subparser.add_argument( + "--seal_text_detection_model_name", + type=str, + help="Name of the seal text detection model.", + ) + subparser.add_argument( + "--seal_text_detection_model_dir", + type=str, + help="Path to the seal text detection model directory.", + ) + subparser.add_argument( + "--text_recognition_model_name", + type=str, + help="Name of the text recognition model.", + ) + subparser.add_argument( + "--text_recognition_model_dir", + type=str, + help="Path to the text recognition model directory.", + ) + subparser.add_argument( + "--text_recognition_batch_size", + type=int, + help="Batch size for the text recognition model.", + ) + subparser.add_argument( + "--use_doc_orientation_classify", + type=str2bool, + help="Whether to use document image orientation classification.", + ) + subparser.add_argument( + "--use_doc_unwarping", + type=str2bool, + help="Whether to use document image unwarping.", + ) + subparser.add_argument( + "--use_layout_detection", + type=str2bool, + help="Whether to use layout detection.", + ) + subparser.add_argument( + "--layout_threshold", + type=float, + help="Threshold for layout detection model.", + ) + subparser.add_argument( + "--layout_nms", + type=str2bool, + help="Non-Maximum Suppression threshold for layout detection.", + ) + subparser.add_argument( + "--layout_unclip_ratio", + type=float, + help="Layout detection expansion coefficient.", + ) + subparser.add_argument( + "--layout_merge_bboxes_mode", + type=str, + help="Mode for merging bounding boxes in layout detection.", + ) + subparser.add_argument( + "--seal_det_limit_side_len", + type=int, + help="This sets a limit on the side length of the input image for the seal text detection model.", + ) + subparser.add_argument( + "--seal_det_limit_type", + type=str, + help="This determines how the side length limit is applied to the input image before feeding it into the seal text detection model.", + ) + subparser.add_argument( + "--seal_det_thresh", + type=float, + help="Detection pixel threshold for the seal text detection model. Pixels with scores greater than this threshold in the output probability map are considered text pixels.", + ) + subparser.add_argument( + "--seal_det_box_thresh", + type=float, + help="Detection box threshold for the seal text detection model. A detection result is considered a text region if the average score of all pixels within the border of the result is greater than this threshold.", + ) + subparser.add_argument( + "--seal_det_unclip_ratio", + type=float, + help="Seal text detection expansion coefficient, which expands the text region using this method. The larger the value, the larger the expansion area.", + ) + subparser.add_argument( + "--seal_rec_score_thresh", + type=float, + help="Text recognition threshold. Text results with scores greater than this threshold are retained.", + ) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + + perform_simple_inference(SealRecognition, params) diff --git a/paddleocr/_pipelines/table_recognition_v2.py b/paddleocr/_pipelines/table_recognition_v2.py new file mode 100644 index 0000000..5cf4868 --- /dev/null +++ b/paddleocr/_pipelines/table_recognition_v2.py @@ -0,0 +1,438 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .._utils.cli import ( + add_simple_inference_args, + get_subcommand_args, + perform_simple_inference, + str2bool, +) +from .base import PaddleXPipelineWrapper, PipelineCLISubcommandExecutor +from .utils import create_config_from_structure + + +class TableRecognitionPipelineV2(PaddleXPipelineWrapper): + def __init__( + self, + layout_detection_model_name=None, + layout_detection_model_dir=None, + table_classification_model_name=None, + table_classification_model_dir=None, + wired_table_structure_recognition_model_name=None, + wired_table_structure_recognition_model_dir=None, + wireless_table_structure_recognition_model_name=None, + wireless_table_structure_recognition_model_dir=None, + wired_table_cells_detection_model_name=None, + wired_table_cells_detection_model_dir=None, + wireless_table_cells_detection_model_name=None, + wireless_table_cells_detection_model_dir=None, + doc_orientation_classify_model_name=None, + doc_orientation_classify_model_dir=None, + doc_unwarping_model_name=None, + doc_unwarping_model_dir=None, + text_detection_model_name=None, + text_detection_model_dir=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_recognition_model_name=None, + text_recognition_model_dir=None, + text_recognition_batch_size=None, + text_rec_score_thresh=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_layout_detection=None, + use_ocr_model=None, + **kwargs, + ): + params = locals().copy() + params.pop("self") + params.pop("kwargs") + self._params = params + + super().__init__(**kwargs) + + @property + def _paddlex_pipeline_name(self): + return "table_recognition_v2" + + def predict_iter( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_layout_detection=None, + use_ocr_model=None, + overall_ocr_res=None, + layout_det_res=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_rec_score_thresh=None, + use_e2e_wired_table_rec_model=False, + use_e2e_wireless_table_rec_model=False, + use_wired_table_cells_trans_to_html=False, + use_wireless_table_cells_trans_to_html=False, + use_table_orientation_classify=True, + use_ocr_results_with_table_cells=True, + **kwargs, + ): + return self.paddlex_pipeline.predict( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_layout_detection=use_layout_detection, + use_ocr_model=use_ocr_model, + overall_ocr_res=overall_ocr_res, + layout_det_res=layout_det_res, + text_det_limit_side_len=text_det_limit_side_len, + text_det_limit_type=text_det_limit_type, + text_det_thresh=text_det_thresh, + text_det_box_thresh=text_det_box_thresh, + text_det_unclip_ratio=text_det_unclip_ratio, + text_rec_score_thresh=text_rec_score_thresh, + use_e2e_wired_table_rec_model=use_e2e_wired_table_rec_model, + use_e2e_wireless_table_rec_model=use_e2e_wireless_table_rec_model, + use_wired_table_cells_trans_to_html=use_wired_table_cells_trans_to_html, + use_wireless_table_cells_trans_to_html=use_wireless_table_cells_trans_to_html, + use_table_orientation_classify=use_table_orientation_classify, + use_ocr_results_with_table_cells=use_ocr_results_with_table_cells, + **kwargs, + ) + + def predict( + self, + input, + *, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_layout_detection=None, + use_ocr_model=None, + overall_ocr_res=None, + layout_det_res=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_det_thresh=None, + text_det_box_thresh=None, + text_det_unclip_ratio=None, + text_rec_score_thresh=None, + use_e2e_wired_table_rec_model=False, + use_e2e_wireless_table_rec_model=False, + use_wired_table_cells_trans_to_html=False, + use_wireless_table_cells_trans_to_html=False, + use_table_orientation_classify=True, + use_ocr_results_with_table_cells=True, + **kwargs, + ): + return list( + self.predict_iter( + input, + use_doc_orientation_classify=use_doc_orientation_classify, + use_doc_unwarping=use_doc_unwarping, + use_layout_detection=use_layout_detection, + use_ocr_model=use_ocr_model, + overall_ocr_res=overall_ocr_res, + layout_det_res=layout_det_res, + text_det_limit_side_len=text_det_limit_side_len, + text_det_limit_type=text_det_limit_type, + text_det_thresh=text_det_thresh, + text_det_box_thresh=text_det_box_thresh, + text_det_unclip_ratio=text_det_unclip_ratio, + text_rec_score_thresh=text_rec_score_thresh, + use_e2e_wired_table_rec_model=use_e2e_wired_table_rec_model, + use_e2e_wireless_table_rec_model=use_e2e_wireless_table_rec_model, + use_wired_table_cells_trans_to_html=use_wired_table_cells_trans_to_html, + use_wireless_table_cells_trans_to_html=use_wireless_table_cells_trans_to_html, + use_table_orientation_classify=use_table_orientation_classify, + use_ocr_results_with_table_cells=use_ocr_results_with_table_cells, + **kwargs, + ) + ) + + @classmethod + def get_cli_subcommand_executor(cls): + return TableRecognitionPipelineV2CLISubcommandExecutor() + + def _get_paddlex_config_overrides(self): + STRUCTURE = { + "SubPipelines.DocPreprocessor.use_doc_orientation_classify": self._params[ + "use_doc_orientation_classify" + ], + "SubPipelines.DocPreprocessor.use_doc_unwarping": self._params[ + "use_doc_unwarping" + ], + "use_doc_preprocessor": self._params["use_doc_orientation_classify"] + or self._params["use_doc_unwarping"], + "use_layout_detection": self._params["use_layout_detection"], + "use_ocr_model": self._params["use_ocr_model"], + "SubModules.LayoutDetection.model_name": self._params[ + "layout_detection_model_name" + ], + "SubModules.LayoutDetection.model_dir": self._params[ + "layout_detection_model_dir" + ], + "SubModules.TableClassification.model_name": self._params[ + "table_classification_model_name" + ], + "SubModules.TableClassification.model_dir": self._params[ + "table_classification_model_dir" + ], + "SubModules.WiredTableStructureRecognition.model_name": self._params[ + "wired_table_structure_recognition_model_name" + ], + "SubModules.WiredTableStructureRecognition.model_dir": self._params[ + "wired_table_structure_recognition_model_dir" + ], + "SubModules.WirelessTableStructureRecognition.model_name": self._params[ + "wireless_table_structure_recognition_model_name" + ], + "SubModules.WirelessTableStructureRecognition.model_dir": self._params[ + "wireless_table_structure_recognition_model_dir" + ], + "SubModules.WiredTableCellsDetection.model_name": self._params[ + "wired_table_cells_detection_model_name" + ], + "SubModules.WiredTableCellsDetection.model_dir": self._params[ + "wired_table_cells_detection_model_dir" + ], + "SubModules.WirelessTableCellsDetection.model_name": self._params[ + "wireless_table_cells_detection_model_name" + ], + "SubModules.WirelessTableCellsDetection.model_dir": self._params[ + "wireless_table_cells_detection_model_dir" + ], + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_name": self._params[ + "doc_orientation_classify_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocOrientationClassify.model_dir": self._params[ + "doc_orientation_classify_model_dir" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_name": self._params[ + "doc_unwarping_model_name" + ], + "SubPipelines.DocPreprocessor.SubModules.DocUnwarping.model_dir": self._params[ + "doc_unwarping_model_dir" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.model_name": self._params[ + "text_detection_model_name" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.model_dir": self._params[ + "text_detection_model_dir" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.limit_side_len": self._params[ + "text_det_limit_side_len" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.limit_type": self._params[ + "text_det_limit_type" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.thresh": self._params[ + "text_det_thresh" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.box_thresh": self._params[ + "text_det_box_thresh" + ], + "SubPipelines.GeneralOCR.SubModules.TextDetection.unclip_ratio": self._params[ + "text_det_unclip_ratio" + ], + "SubPipelines.GeneralOCR.SubModules.TextRecognition.model_name": self._params[ + "text_recognition_model_name" + ], + "SubPipelines.GeneralOCR.SubModules.TextRecognition.model_dir": self._params[ + "text_recognition_model_dir" + ], + "SubPipelines.GeneralOCR.SubModules.TextRecognition.batch_size": self._params[ + "text_recognition_batch_size" + ], + "SubPipelines.GeneralOCR.SubModules.TextRecognition.score_thresh": self._params[ + "text_rec_score_thresh" + ], + } + return create_config_from_structure(STRUCTURE) + + +class TableRecognitionPipelineV2CLISubcommandExecutor(PipelineCLISubcommandExecutor): + @property + def subparser_name(self): + return "table_recognition_v2" + + def _update_subparser(self, subparser): + add_simple_inference_args(subparser) + + subparser.add_argument( + "--layout_detection_model_name", + type=str, + help="Name of the layout detection model.", + ) + subparser.add_argument( + "--layout_detection_model_dir", + type=str, + help="Path to the layout detection model directory.", + ) + subparser.add_argument( + "--table_classification_model_name", + type=str, + help="Name of the table classification model.", + ) + subparser.add_argument( + "--table_classification_model_dir", + type=str, + help="Path to the table classification model directory.", + ) + subparser.add_argument( + "--wired_table_structure_recognition_model_name", + type=str, + help="Name of the wired table structure recognition model.", + ) + subparser.add_argument( + "--wired_table_structure_recognition_model_dir", + type=str, + help="Path to the wired table structure recognition model directory.", + ) + subparser.add_argument( + "--wireless_table_structure_recognition_model_name", + type=str, + help="Name of the wireless table structure recognition model.", + ) + subparser.add_argument( + "--wireless_table_structure_recognition_model_dir", + type=str, + help="Path to the wired table structure recognition model directory.", + ) + subparser.add_argument( + "--wired_table_cells_detection_model_name", + type=str, + help="Name of the wired table cells detection model.", + ) + subparser.add_argument( + "--wired_table_cells_detection_model_dir", + type=str, + help="Path to the wired table cells detection model directory.", + ) + subparser.add_argument( + "--wireless_table_cells_detection_model_name", + type=str, + help="Name of the wireless table cells detection model.", + ) + subparser.add_argument( + "--wireless_table_cells_detection_model_dir", + type=str, + help="Path to the wireless table cells detection model directory.", + ) + + subparser.add_argument( + "--doc_orientation_classify_model_name", + type=str, + help="Name of the document image orientation classification model.", + ) + subparser.add_argument( + "--doc_orientation_classify_model_dir", + type=str, + help="Path to the document image orientation classification model directory.", + ) + subparser.add_argument( + "--doc_unwarping_model_name", + type=str, + help="Name of the text image unwarping model.", + ) + subparser.add_argument( + "--doc_unwarping_model_dir", + type=str, + help="Path to the image unwarping model directory.", + ) + + subparser.add_argument( + "--text_detection_model_name", + type=str, + help="Name of the text detection model.", + ) + subparser.add_argument( + "--text_detection_model_dir", + type=str, + help="Path to the text detection model directory.", + ) + subparser.add_argument( + "--text_det_limit_side_len", + type=int, + help="This sets a limit on the side length of the input image for the text detection model.", + ) + subparser.add_argument( + "--text_det_limit_type", + type=str, + help="This determines how the side length limit is applied to the input image before feeding it into the text deteciton model.", + ) + subparser.add_argument( + "--text_det_thresh", + type=float, + help="Detection pixel threshold for the text detection model. Pixels with scores greater than this threshold in the output probability map are considered text pixels.", + ) + subparser.add_argument( + "--text_det_box_thresh", + type=float, + help="Detection box threshold for the text detection model. A detection result is considered a text region if the average score of all pixels within the border of the result is greater than this threshold.", + ) + subparser.add_argument( + "--text_det_unclip_ratio", + type=float, + help="Text detection expansion coefficient, which expands the text region using this method. The larger the value, the larger the expansion area.", + ) + subparser.add_argument( + "--text_recognition_model_name", + type=str, + help="Name of the text recognition model.", + ) + subparser.add_argument( + "--text_recognition_model_dir", + type=str, + help="Path to the text recognition model directory.", + ) + subparser.add_argument( + "--text_recognition_batch_size", + type=int, + help="Batch size for the text recognition model.", + ) + subparser.add_argument( + "--text_rec_score_thresh", + type=float, + help="Text recognition threshold used in general OCR. Text results with scores greater than this threshold are retained.", + ) + + subparser.add_argument( + "--use_doc_orientation_classify", + type=str2bool, + help="Whether to use document image orientation classification.", + ) + subparser.add_argument( + "--use_doc_unwarping", + type=str2bool, + help="Whether to use text image unwarping.", + ) + subparser.add_argument( + "--use_layout_detection", + type=str2bool, + help="Whether to use layout detection.", + ) + subparser.add_argument( + "--use_ocr_model", + type=str2bool, + help="Whether to use OCR models.", + ) + + def execute_with_args(self, args): + params = get_subcommand_args(args) + perform_simple_inference(TableRecognitionPipelineV2, params) diff --git a/paddleocr/_pipelines/utils.py b/paddleocr/_pipelines/utils.py new file mode 100644 index 0000000..9a7b4ba --- /dev/null +++ b/paddleocr/_pipelines/utils.py @@ -0,0 +1,30 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def create_config_from_structure(structure, *, unset=None, config=None): + if config is None: + config = {} + for k, v in structure.items(): + if v is unset: + continue + idx = k.find(".") + if idx == -1: + config[k] = v + else: + sk = k[:idx] + if sk not in config: + config[sk] = {} + create_config_from_structure({k[idx + 1 :]: v}, config=config[sk]) + return config diff --git a/paddleocr/_utils/__init__.py b/paddleocr/_utils/__init__.py new file mode 100644 index 0000000..a9cc79c --- /dev/null +++ b/paddleocr/_utils/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/paddleocr/_utils/cli.py b/paddleocr/_utils/cli.py new file mode 100644 index 0000000..6d21875 --- /dev/null +++ b/paddleocr/_utils/cli.py @@ -0,0 +1,75 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import time + +from .logging import logger + + +def str2bool(v, /): + return v.lower() in ("true", "yes", "t", "y", "1") + + +def get_subcommand_args(args): + args = vars(args).copy() + args.pop("subcommand") + args.pop("executor") + return args + + +def add_simple_inference_args(subparser, *, input_help=None): + if input_help is None: + input_help = "Input path or URL." + subparser.add_argument( + "-i", + "--input", + type=str, + required=True, + help=input_help, + ) + subparser.add_argument( + "--save_path", + type=str, + help="Path to the output directory.", + ) + + +def perform_simple_inference(wrapper_cls, params, predict_param_names=None): + params = params.copy() + + input_ = params.pop("input") + save_path = params.pop("save_path") + + if predict_param_names is not None: + predict_params = {} + for name in predict_param_names: + predict_params[name] = params.pop(name) + else: + predict_params = {} + init_params = params + + wrapper = wrapper_cls(**init_params) + + try: + result = wrapper.predict_iter(input_, **predict_params) + + t1 = time.time() + for i, res in enumerate(result): + logger.info(f"Processed item {i} in {(time.time()-t1) * 1000} ms") + t1 = time.time() + res.print() + if save_path: + res.save_all(save_path) + finally: + wrapper.close() diff --git a/paddleocr/_utils/deprecation.py b/paddleocr/_utils/deprecation.py new file mode 100644 index 0000000..b30419d --- /dev/null +++ b/paddleocr/_utils/deprecation.py @@ -0,0 +1,42 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import sys +import warnings + +from typing_extensions import deprecated as deprecated + + +class CLIDeprecationWarning(DeprecationWarning): + pass + + +class DeprecatedOptionAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + assert option_string + warnings.warn( + f"The option `{option_string}` has been deprecated and will be removed in the future. Please refer to the documentation for more details.", + CLIDeprecationWarning, + ) + setattr(namespace, self.dest, values) + + +def warn_deprecated_param(name, new_name=None): + msg = ( + f"The parameter `{name}` has been deprecated and will be removed in the future." + ) + if new_name is not None: + msg += f" Please use `{new_name}` instead." + warnings.warn(msg, DeprecationWarning, stacklevel=3) diff --git a/paddleocr/_utils/langs.py b/paddleocr/_utils/langs.py new file mode 100644 index 0000000..77e7d00 --- /dev/null +++ b/paddleocr/_utils/langs.py @@ -0,0 +1,126 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Language code groupings shared across OCR pipelines.""" + +LATIN_LANGS = frozenset( + { + "af", + "az", + "bs", + "cs", + "cy", + "da", + "de", + "es", + "et", + "fr", + "ga", + "hr", + "hu", + "id", + "is", + "it", + "ku", + "la", + "lt", + "lv", + "mi", + "ms", + "mt", + "nl", + "no", + "oc", + "pi", + "pl", + "pt", + "ro", + "rs_latin", + "sk", + "sl", + "sq", + "sv", + "sw", + "tl", + "tr", + "uz", + "vi", + "french", + "german", + "fi", + "eu", + "gl", + "lb", + "rm", + "ca", + "qu", + } +) + +ARABIC_LANGS = frozenset({"ar", "fa", "ug", "ur", "ps", "ku", "sd", "bal"}) +ESLAV_LANGS = frozenset({"ru", "be", "uk"}) +CYRILLIC_LANGS = frozenset( + { + "ru", + "rs_cyrillic", + "be", + "bg", + "uk", + "mn", + "abq", + "ady", + "kbd", + "ava", + "dar", + "inh", + "che", + "lbe", + "lez", + "tab", + "kk", + "ky", + "tg", + "mk", + "tt", + "cv", + "ba", + "mhr", + "mo", + "udm", + "kv", + "os", + "bua", + "xal", + "tyv", + "sah", + "kaa", + } +) +DEVANAGARI_LANGS = frozenset( + { + "hi", + "mr", + "ne", + "bh", + "mai", + "ang", + "bho", + "mah", + "sck", + "new", + "gom", + "sa", + "bgc", + } +) diff --git a/paddleocr/_utils/logging.py b/paddleocr/_utils/logging.py new file mode 100644 index 0000000..0ef8939 --- /dev/null +++ b/paddleocr/_utils/logging.py @@ -0,0 +1,39 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +from .._env import DISABLE_AUTO_LOGGING_CONFIG + +LOGGER_NAME = "paddleocr" + +logger = logging.getLogger(LOGGER_NAME) + + +def _set_up_logger(): + if DISABLE_AUTO_LOGGING_CONFIG: + return + + # Basically compatible with PaddleOCR 2.x, except for logging to stderr + formatter = logging.Formatter( + "[%(asctime)s] %(name)s %(levelname)s: %(message)s", datefmt="%Y/%m/%d %H:%M:%S" + ) + stream_handler = logging.StreamHandler() + stream_handler.setFormatter(formatter) + logger.addHandler(stream_handler) + logger.setLevel(logging.ERROR) + logger.propagate = False + + +_set_up_logger() diff --git a/paddleocr/_utils/naming.py b/paddleocr/_utils/naming.py new file mode 100644 index 0000000..94817f6 --- /dev/null +++ b/paddleocr/_utils/naming.py @@ -0,0 +1,30 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, Dict + + +def snake_to_camel(name: str) -> str: + if not name: + return name + components = name.split("_") + if len(components) == 1: + return name + return components[0] + "".join( + component.capitalize() for component in components[1:] + ) + + +def snake_keys_to_camel(params: Dict[str, Any]) -> Dict[str, Any]: + return {snake_to_camel(key): value for key, value in params.items()} diff --git a/paddleocr/_version.py b/paddleocr/_version.py new file mode 100644 index 0000000..91e943c --- /dev/null +++ b/paddleocr/_version.py @@ -0,0 +1,20 @@ +# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib.metadata + +try: + version = importlib.metadata.version(__package__) +except importlib.metadata.PackageNotFoundError: + version = "0.0.0" diff --git a/ppocr/__init__.py b/ppocr/__init__.py new file mode 100755 index 0000000..d0c32e2 --- /dev/null +++ b/ppocr/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/ppocr/data/__init__.py b/ppocr/data/__init__.py new file mode 100644 index 0000000..f288989 --- /dev/null +++ b/ppocr/data/__init__.py @@ -0,0 +1,154 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import os +import sys +import numpy as np +import skimage +import paddle +import signal +import random + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(os.path.abspath(os.path.join(__dir__, "../.."))) + +import copy +from paddle.io import Dataset, DataLoader, BatchSampler, DistributedBatchSampler +import paddle.distributed as dist + +from ppocr.data.imaug import transform, create_operators +from ppocr.data.simple_dataset import SimpleDataSet, MultiScaleDataSet +from ppocr.data.lmdb_dataset import LMDBDataSet, LMDBDataSetSR, LMDBDataSetTableMaster +from ppocr.data.pgnet_dataset import PGDataSet +from ppocr.data.pubtab_dataset import PubTabDataSet +from ppocr.data.multi_scale_sampler import MultiScaleSampler +from ppocr.data.latexocr_dataset import LaTeXOCRDataSet + +# for PaddleX dataset_type +TextDetDataset = SimpleDataSet +TextRecDataset = SimpleDataSet +MSTextRecDataset = MultiScaleDataSet +PubTabTableRecDataset = PubTabDataSet +KieDataset = SimpleDataSet +LaTeXOCRDataSet = LaTeXOCRDataSet + +__all__ = ["build_dataloader", "transform", "create_operators", "set_signal_handlers"] + + +def term_mp(sig_num, frame): + """kill all child processes""" + pid = os.getpid() + pgid = os.getpgid(os.getpid()) + print("main proc {} exit, kill process group " "{}".format(pid, pgid)) + os.killpg(pgid, signal.SIGKILL) + + +def set_signal_handlers(): + pid = os.getpid() + try: + pgid = os.getpgid(pid) + except AttributeError: + # In case `os.getpgid` is not available, no signal handler will be set, + # because we cannot do safe cleanup. + pass + else: + # XXX: `term_mp` kills all processes in the process group, which in + # some cases includes the parent process of current process and may + # cause unexpected results. To solve this problem, we set signal + # handlers only when current process is the group leader. In the + # future, it would be better to consider killing only descendants of + # the current process. + if pid == pgid: + # support exit using ctrl+c + signal.signal(signal.SIGINT, term_mp) + signal.signal(signal.SIGTERM, term_mp) + + +def build_dataloader(config, mode, device, logger, seed=None): + config = copy.deepcopy(config) + + support_dict = [ + "SimpleDataSet", + "LMDBDataSet", + "PGDataSet", + "PubTabDataSet", + "LMDBDataSetSR", + "LMDBDataSetTableMaster", + "MultiScaleDataSet", + "TextDetDataset", + "TextRecDataset", + "MSTextRecDataset", + "PubTabTableRecDataset", + "KieDataset", + "LaTeXOCRDataSet", + ] + module_name = config[mode]["dataset"]["name"] + assert module_name in support_dict, Exception( + "DataSet only support {}".format(support_dict) + ) + assert mode in ["Train", "Eval", "Test"], "Mode should be Train, Eval or Test." + + dataset = eval(module_name)(config, mode, logger, seed) + loader_config = config[mode]["loader"] + batch_size = loader_config["batch_size_per_card"] + drop_last = loader_config["drop_last"] + shuffle = loader_config["shuffle"] + num_workers = loader_config["num_workers"] + if "use_shared_memory" in loader_config.keys(): + use_shared_memory = loader_config["use_shared_memory"] + else: + use_shared_memory = True + + if mode == "Train": + # Distribute data to multiple cards + if "sampler" in config[mode]: + config_sampler = config[mode]["sampler"] + sampler_name = config_sampler.pop("name") + batch_sampler = eval(sampler_name)(dataset, **config_sampler) + else: + batch_sampler = DistributedBatchSampler( + dataset=dataset, + batch_size=batch_size, + shuffle=shuffle, + drop_last=drop_last, + ) + else: + # Distribute data to single card + batch_sampler = BatchSampler( + dataset=dataset, batch_size=batch_size, shuffle=shuffle, drop_last=drop_last + ) + + if "collate_fn" in loader_config: + from . import collate_fn + + collate_fn = getattr(collate_fn, loader_config["collate_fn"])() + else: + collate_fn = None + data_loader = DataLoader( + dataset=dataset, + batch_sampler=batch_sampler, + places=device, + num_workers=num_workers, + return_list=True, + use_shared_memory=use_shared_memory, + collate_fn=collate_fn, + persistent_workers=True if num_workers > 0 and mode == "Train" else False, + ) + + return data_loader diff --git a/ppocr/data/collate_fn.py b/ppocr/data/collate_fn.py new file mode 100644 index 0000000..309f60a --- /dev/null +++ b/ppocr/data/collate_fn.py @@ -0,0 +1,173 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle +import numbers +import numpy as np +from collections import defaultdict + + +class DictCollator(object): + """ + data batch + """ + + def __call__(self, batch): + # todo:support batch operators + data_dict = defaultdict(list) + to_tensor_keys = [] + for sample in batch: + for k, v in sample.items(): + if isinstance(v, (np.ndarray, paddle.Tensor, numbers.Number)): + if k not in to_tensor_keys: + to_tensor_keys.append(k) + data_dict[k].append(v) + for k in to_tensor_keys: + data_dict[k] = paddle.to_tensor(data_dict[k]) + return data_dict + + +class ListCollator(object): + """ + data batch + """ + + def __call__(self, batch): + # todo:support batch operators + data_dict = defaultdict(list) + to_tensor_idxs = [] + for sample in batch: + for idx, v in enumerate(sample): + if isinstance(v, (np.ndarray, paddle.Tensor, numbers.Number)): + if idx not in to_tensor_idxs: + to_tensor_idxs.append(idx) + data_dict[idx].append(v) + for idx in to_tensor_idxs: + data_dict[idx] = paddle.to_tensor(data_dict[idx]) + return list(data_dict.values()) + + +class SSLRotateCollate(object): + """ + bach: [ + [(4*3xH*W), (4,)] + [(4*3xH*W), (4,)] + ... + ] + """ + + def __call__(self, batch): + output = [np.concatenate(d, axis=0) for d in zip(*batch)] + return output + + +class DyMaskCollator(object): + """ + batch: [ + image [batch_size, channel, maxHinbatch, maxWinbatch] + image_mask [batch_size, channel, maxHinbatch, maxWinbatch] + label [batch_size, maxLabelLen] + label_mask [batch_size, maxLabelLen] + ... + ] + """ + + def __call__(self, batch): + max_width, max_height, max_length = 0, 0, 0 + bs, channel = len(batch), batch[0][0].shape[0] + proper_items = [] + for item in batch: + if ( + item[0].shape[1] * max_width > 1600 * 320 + or item[0].shape[2] * max_height > 1600 * 320 + ): + continue + max_height = ( + item[0].shape[1] if item[0].shape[1] > max_height else max_height + ) + max_width = item[0].shape[2] if item[0].shape[2] > max_width else max_width + max_length = len(item[1]) if len(item[1]) > max_length else max_length + proper_items.append(item) + + images, image_masks = np.zeros( + (len(proper_items), channel, max_height, max_width), dtype="float32" + ), np.zeros((len(proper_items), 1, max_height, max_width), dtype="float32") + labels, label_masks = np.zeros( + (len(proper_items), max_length), dtype="int64" + ), np.zeros((len(proper_items), max_length), dtype="int64") + + for i in range(len(proper_items)): + _, h, w = proper_items[i][0].shape + images[i][:, :h, :w] = proper_items[i][0] + image_masks[i][:, :h, :w] = 1 + l = len(proper_items[i][1]) + labels[i][:l] = proper_items[i][1] + label_masks[i][:l] = 1 + + return images, image_masks, labels, label_masks + + +class LaTeXOCRCollator(object): + """ + batch: [ + image [batch_size, channel, maxHinbatch, maxWinbatch] + label [batch_size, maxLabelLen] + label_mask [batch_size, maxLabelLen] + ... + ] + """ + + def __call__(self, batch): + images, labels, attention_mask = batch[0] + return images, labels, attention_mask + + +class UniMERNetCollator(object): + """ + batch: [ + image [batch_size, channel, maxHinbatch, maxWinbatch] + image_mask [batch_size, channel, maxHinbatch, maxWinbatch] + label [batch_size, maxLabelLen] + label_mask [batch_size, maxLabelLen] + ... + ] + """ + + def __call__(self, batch): + + max_width, max_height, max_length = 0, 0, 0 + bs, channel = len(batch), batch[0][0].shape[0] + proper_items = [] + for item in batch: + max_height = ( + item[0].shape[1] if item[0].shape[1] > max_height else max_height + ) + max_width = item[0].shape[2] if item[0].shape[2] > max_width else max_width + max_length = len(item[1]) if len(item[1]) > max_length else max_length + proper_items.append(item) + + images = np.ones( + (len(proper_items), channel, max_height, max_width), dtype="float32" + ) + + labels, label_masks = np.ones( + (len(proper_items), max_length), dtype="int64" + ), np.zeros((len(proper_items), max_length), dtype="int64") + for i in range(len(proper_items)): + _, h, w = proper_items[i][0].shape + images[i][:, :h, :w] = proper_items[i][0] + l = len(proper_items[i][1]) + labels[i][:l] = proper_items[i][1] + label_masks[i][:l] = proper_items[i][2] + return images, labels, label_masks diff --git a/ppocr/data/imaug/ColorJitter.py b/ppocr/data/imaug/ColorJitter.py new file mode 100644 index 0000000..46c1955 --- /dev/null +++ b/ppocr/data/imaug/ColorJitter.py @@ -0,0 +1,27 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from paddle.vision.transforms import ColorJitter as pp_ColorJitter + +__all__ = ["ColorJitter"] + + +class ColorJitter(object): + def __init__(self, brightness=0, contrast=0, saturation=0, hue=0, **kwargs): + self.aug = pp_ColorJitter(brightness, contrast, saturation, hue) + + def __call__(self, data): + image = data["image"] + image = self.aug(image) + data["image"] = image + return data diff --git a/ppocr/data/imaug/__init__.py b/ppocr/data/imaug/__init__.py new file mode 100644 index 0000000..06ac51a --- /dev/null +++ b/ppocr/data/imaug/__init__.py @@ -0,0 +1,96 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from .iaa_augment import IaaAugment +from .make_border_map import MakeBorderMap +from .make_shrink_map import MakeShrinkMap +from .random_crop_data import EastRandomCropData, RandomCrop, RandomCropImgMask +from .make_pse_gt import MakePseGt + + +from .rec_img_aug import ( + BaseDataAugmentation, + RecAug, + RecConAug, + RecResizeImg, + ClsResizeImg, + SRNRecResizeImg, + GrayRecResizeImg, + SARRecResizeImg, + PRENResizeImg, + ABINetRecResizeImg, + SVTRRecResizeImg, + ABINetRecAug, + VLRecResizeImg, + SPINRecResizeImg, + RobustScannerRecResizeImg, + RFLRecResizeImg, + SVTRRecAug, + ParseQRecAug, +) +from .ssl_img_aug import SSLRotateResize +from .randaugment import RandAugment +from .copy_paste import CopyPaste +from .ColorJitter import ColorJitter +from .operators import * +from .label_ops import * + +from .east_process import * +from .sast_process import * +from .pg_process import * +from .table_ops import * + +from .vqa import * + +from .fce_aug import * +from .fce_targets import FCENetTargets +from .ct_process import * +from .drrg_targets import DRRGTargets +from .latex_ocr_aug import * +from .unimernet_aug import * + + +def transform(data, ops=None): + """transform""" + if ops is None: + ops = [] + for op in ops: + data = op(data) + if data is None: + return None + return data + + +def create_operators(op_param_list, global_config=None): + """ + create operators based on the config + + Args: + params(list): a dict list, used to create some operators + """ + assert isinstance(op_param_list, list), "operator config should be a list" + ops = [] + for operator in op_param_list: + assert isinstance(operator, dict) and len(operator) == 1, "yaml format error" + op_name = list(operator)[0] + param = {} if operator[op_name] is None else operator[op_name] + if global_config is not None: + param.update(global_config) + op = eval(op_name)(**param) + ops.append(op) + return ops diff --git a/ppocr/data/imaug/abinet_aug.py b/ppocr/data/imaug/abinet_aug.py new file mode 100644 index 0000000..3df255e --- /dev/null +++ b/ppocr/data/imaug/abinet_aug.py @@ -0,0 +1,512 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/FangShancheng/ABINet/blob/main/transforms.py +""" +import math +import numbers +import random + +import cv2 +import numpy as np +from paddle.vision.transforms import Compose, ColorJitter + + +def sample_asym(magnitude, size=None): + return np.random.beta(1, 4, size) * magnitude + + +def sample_sym(magnitude, size=None): + return (np.random.beta(4, 4, size=size) - 0.5) * 2 * magnitude + + +def sample_uniform(low, high, size=None): + return np.random.uniform(low, high, size=size) + + +def get_interpolation(type="random"): + if type == "random": + choice = [cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA] + interpolation = choice[random.randint(0, len(choice) - 1)] + elif type == "nearest": + interpolation = cv2.INTER_NEAREST + elif type == "linear": + interpolation = cv2.INTER_LINEAR + elif type == "cubic": + interpolation = cv2.INTER_CUBIC + elif type == "area": + interpolation = cv2.INTER_AREA + else: + raise TypeError( + "Interpolation types only nearest, linear, cubic, area are supported!" + ) + return interpolation + + +class CVRandomRotation(object): + def __init__(self, degrees=15): + assert isinstance(degrees, numbers.Number), "degree should be a single number." + assert degrees >= 0, "degree must be positive." + self.degrees = degrees + + @staticmethod + def get_params(degrees): + return sample_sym(degrees) + + def __call__(self, img): + angle = self.get_params(self.degrees) + src_h, src_w = img.shape[:2] + M = cv2.getRotationMatrix2D( + center=(src_w / 2, src_h / 2), angle=angle, scale=1.0 + ) + abs_cos, abs_sin = abs(M[0, 0]), abs(M[0, 1]) + dst_w = int(src_h * abs_sin + src_w * abs_cos) + dst_h = int(src_h * abs_cos + src_w * abs_sin) + M[0, 2] += (dst_w - src_w) / 2 + M[1, 2] += (dst_h - src_h) / 2 + + flags = get_interpolation() + return cv2.warpAffine( + img, M, (dst_w, dst_h), flags=flags, borderMode=cv2.BORDER_REPLICATE + ) + + +class CVRandomAffine(object): + def __init__(self, degrees, translate=None, scale=None, shear=None): + assert isinstance(degrees, numbers.Number), "degree should be a single number." + assert degrees >= 0, "degree must be positive." + self.degrees = degrees + + if translate is not None: + assert ( + isinstance(translate, (tuple, list)) and len(translate) == 2 + ), "translate should be a list or tuple and it must be of length 2." + for t in translate: + if not (0.0 <= t <= 1.0): + raise ValueError("translation values should be between 0 and 1") + self.translate = translate + + if scale is not None: + assert ( + isinstance(scale, (tuple, list)) and len(scale) == 2 + ), "scale should be a list or tuple and it must be of length 2." + for s in scale: + if s <= 0: + raise ValueError("scale values should be positive") + self.scale = scale + + if shear is not None: + if isinstance(shear, numbers.Number): + if shear < 0: + raise ValueError( + "If shear is a single number, it must be positive." + ) + self.shear = [shear] + else: + assert isinstance(shear, (tuple, list)) and ( + len(shear) == 2 + ), "shear should be a list or tuple and it must be of length 2." + self.shear = shear + else: + self.shear = shear + + def _get_inverse_affine_matrix(self, center, angle, translate, scale, shear): + # https://github.com/pytorch/vision/blob/v0.4.0/torchvision/transforms/functional.py#L717 + from numpy import sin, cos, tan + + if isinstance(shear, numbers.Number): + shear = [shear, 0] + + if not isinstance(shear, (tuple, list)) and len(shear) == 2: + raise ValueError( + "Shear should be a single value or a tuple/list containing " + + "two values. Got {}".format(shear) + ) + + rot = math.radians(angle) + sx, sy = [math.radians(s) for s in shear] + + cx, cy = center + tx, ty = translate + + # RSS without scaling + a = cos(rot - sy) / cos(sy) + b = -cos(rot - sy) * tan(sx) / cos(sy) - sin(rot) + c = sin(rot - sy) / cos(sy) + d = -sin(rot - sy) * tan(sx) / cos(sy) + cos(rot) + + # Inverted rotation matrix with scale and shear + # det([[a, b], [c, d]]) == 1, since det(rotation) = 1 and det(shear) = 1 + M = [d, -b, 0, -c, a, 0] + M = [x / scale for x in M] + + # Apply inverse of translation and of center translation: RSS^-1 * C^-1 * T^-1 + M[2] += M[0] * (-cx - tx) + M[1] * (-cy - ty) + M[5] += M[3] * (-cx - tx) + M[4] * (-cy - ty) + + # Apply center translation: C * RSS^-1 * C^-1 * T^-1 + M[2] += cx + M[5] += cy + return M + + @staticmethod + def get_params(degrees, translate, scale_ranges, shears, height): + angle = sample_sym(degrees) + if translate is not None: + max_dx = translate[0] * height + max_dy = translate[1] * height + translations = (np.round(sample_sym(max_dx)), np.round(sample_sym(max_dy))) + else: + translations = (0, 0) + + if scale_ranges is not None: + scale = sample_uniform(scale_ranges[0], scale_ranges[1]) + else: + scale = 1.0 + + if shears is not None: + if len(shears) == 1: + shear = [sample_sym(shears[0]), 0.0] + elif len(shears) == 2: + shear = [sample_sym(shears[0]), sample_sym(shears[1])] + else: + shear = 0.0 + + return angle, translations, scale, shear + + def __call__(self, img): + src_h, src_w = img.shape[:2] + angle, translate, scale, shear = self.get_params( + self.degrees, self.translate, self.scale, self.shear, src_h + ) + + M = self._get_inverse_affine_matrix( + (src_w / 2, src_h / 2), angle, (0, 0), scale, shear + ) + M = np.array(M).reshape(2, 3) + + startpoints = [(0, 0), (src_w - 1, 0), (src_w - 1, src_h - 1), (0, src_h - 1)] + project = lambda x, y, a, b, c: int(a * x + b * y + c) + endpoints = [ + (project(x, y, *M[0]), project(x, y, *M[1])) for x, y in startpoints + ] + + rect = cv2.minAreaRect(np.array(endpoints)) + bbox = cv2.boxPoints(rect).astype(dtype=np.int32) + max_x, max_y = bbox[:, 0].max(), bbox[:, 1].max() + min_x, min_y = bbox[:, 0].min(), bbox[:, 1].min() + + dst_w = int(max_x - min_x) + dst_h = int(max_y - min_y) + M[0, 2] += (dst_w - src_w) / 2 + M[1, 2] += (dst_h - src_h) / 2 + + # add translate + dst_w += int(abs(translate[0])) + dst_h += int(abs(translate[1])) + if translate[0] < 0: + M[0, 2] += abs(translate[0]) + if translate[1] < 0: + M[1, 2] += abs(translate[1]) + + flags = get_interpolation() + return cv2.warpAffine( + img, M, (dst_w, dst_h), flags=flags, borderMode=cv2.BORDER_REPLICATE + ) + + +class CVRandomPerspective(object): + def __init__(self, distortion=0.5): + self.distortion = distortion + + def get_params(self, width, height, distortion): + offset_h = sample_asym(distortion * height / 2, size=4).astype(dtype=np.int32) + offset_w = sample_asym(distortion * width / 2, size=4).astype(dtype=np.int32) + topleft = (offset_w[0], offset_h[0]) + topright = (width - 1 - offset_w[1], offset_h[1]) + botright = (width - 1 - offset_w[2], height - 1 - offset_h[2]) + botleft = (offset_w[3], height - 1 - offset_h[3]) + + startpoints = [(0, 0), (width - 1, 0), (width - 1, height - 1), (0, height - 1)] + endpoints = [topleft, topright, botright, botleft] + return np.array(startpoints, dtype=np.float32), np.array( + endpoints, dtype=np.float32 + ) + + def __call__(self, img): + height, width = img.shape[:2] + startpoints, endpoints = self.get_params(width, height, self.distortion) + M = cv2.getPerspectiveTransform(startpoints, endpoints) + + # TODO: more robust way to crop image + rect = cv2.minAreaRect(endpoints) + bbox = cv2.boxPoints(rect).astype(dtype=np.int32) + max_x, max_y = bbox[:, 0].max(), bbox[:, 1].max() + min_x, min_y = bbox[:, 0].min(), bbox[:, 1].min() + min_x, min_y = max(min_x, 0), max(min_y, 0) + + flags = get_interpolation() + img = cv2.warpPerspective( + img, M, (max_x, max_y), flags=flags, borderMode=cv2.BORDER_REPLICATE + ) + img = img[min_y:, min_x:] + return img + + +class CVRescale(object): + def __init__(self, factor=4, base_size=(128, 512)): + """Define image scales using gaussian pyramid and rescale image to target scale. + + Args: + factor: the decayed factor from base size, factor=4 keeps target scale by default. + base_size: base size the build the bottom layer of pyramid + """ + if isinstance(factor, numbers.Number): + self.factor = round(sample_uniform(0, factor)) + elif isinstance(factor, (tuple, list)) and len(factor) == 2: + self.factor = round(sample_uniform(factor[0], factor[1])) + else: + raise Exception("factor must be number or list with length 2") + # assert factor is valid + self.base_h, self.base_w = base_size[:2] + + def __call__(self, img): + if self.factor == 0: + return img + src_h, src_w = img.shape[:2] + cur_w, cur_h = self.base_w, self.base_h + scale_img = cv2.resize(img, (cur_w, cur_h), interpolation=get_interpolation()) + for _ in range(self.factor): + scale_img = cv2.pyrDown(scale_img) + scale_img = cv2.resize( + scale_img, (src_w, src_h), interpolation=get_interpolation() + ) + return scale_img + + +class CVGaussianNoise(object): + def __init__(self, mean=0, var=20): + self.mean = mean + if isinstance(var, numbers.Number): + self.var = max(int(sample_asym(var)), 1) + elif isinstance(var, (tuple, list)) and len(var) == 2: + self.var = int(sample_uniform(var[0], var[1])) + else: + raise Exception("degree must be number or list with length 2") + + def __call__(self, img): + noise = np.random.normal(self.mean, self.var**0.5, img.shape) + img = np.clip(img + noise, 0, 255).astype(np.uint8) + return img + + +class CVPossionNoise(object): + def __init__(self, lam=20): + self.lam = lam + if isinstance(lam, numbers.Number): + self.lam = max(int(sample_asym(lam)), 1) + elif isinstance(lam, (tuple, list)) and len(lam) == 2: + self.lam = int(sample_uniform(lam[0], lam[1])) + else: + raise Exception("lam must be number or list with length 2") + + def __call__(self, img): + noise = np.random.poisson(lam=self.lam, size=img.shape) + img = np.clip(img + noise, 0, 255).astype(np.uint8) + return img + + +class CVGaussionBlur(object): + def __init__(self, radius): + self.radius = radius + if isinstance(radius, numbers.Number): + self.radius = max(int(sample_asym(radius)), 1) + elif isinstance(radius, (tuple, list)) and len(radius) == 2: + self.radius = int(sample_uniform(radius[0], radius[1])) + else: + raise Exception("radius must be number or list with length 2") + + def __call__(self, img): + fil = cv2.getGaussianKernel(ksize=self.radius, sigma=1, ktype=cv2.CV_32F) + img = cv2.sepFilter2D(img, -1, fil, fil) + return img + + +class CVMotionBlur(object): + def __init__(self, degrees=12, angle=90): + if isinstance(degrees, numbers.Number): + self.degree = max(int(sample_asym(degrees)), 1) + elif isinstance(degrees, (tuple, list)) and len(degrees) == 2: + self.degree = int(sample_uniform(degrees[0], degrees[1])) + else: + raise Exception("degree must be number or list with length 2") + self.angle = sample_uniform(-angle, angle) + + def __call__(self, img): + M = cv2.getRotationMatrix2D((self.degree // 2, self.degree // 2), self.angle, 1) + motion_blur_kernel = np.zeros((self.degree, self.degree)) + motion_blur_kernel[self.degree // 2, :] = 1 + motion_blur_kernel = cv2.warpAffine( + motion_blur_kernel, M, (self.degree, self.degree) + ) + motion_blur_kernel = motion_blur_kernel / self.degree + img = cv2.filter2D(img, -1, motion_blur_kernel) + img = np.clip(img, 0, 255).astype(np.uint8) + return img + + +class CVGeometry(object): + def __init__( + self, + degrees=15, + translate=(0.3, 0.3), + scale=(0.5, 2.0), + shear=(45, 15), + distortion=0.5, + p=0.5, + ): + self.p = p + type_p = random.random() + if type_p < 0.33: + self.transforms = CVRandomRotation(degrees=degrees) + elif type_p < 0.66: + self.transforms = CVRandomAffine( + degrees=degrees, translate=translate, scale=scale, shear=shear + ) + else: + self.transforms = CVRandomPerspective(distortion=distortion) + + def __call__(self, img): + if random.random() < self.p: + return self.transforms(img) + else: + return img + + +class CVDeterioration(object): + def __init__(self, var, degrees, factor, p=0.5): + self.p = p + transforms = [] + if var is not None: + transforms.append(CVGaussianNoise(var=var)) + if degrees is not None: + transforms.append(CVMotionBlur(degrees=degrees)) + if factor is not None: + transforms.append(CVRescale(factor=factor)) + + random.shuffle(transforms) + transforms = Compose(transforms) + self.transforms = transforms + + def __call__(self, img): + if random.random() < self.p: + return self.transforms(img) + else: + return img + + +class CVColorJitter(object): + def __init__(self, brightness=0.5, contrast=0.5, saturation=0.5, hue=0.1, p=0.5): + self.p = p + self.transforms = ColorJitter( + brightness=brightness, contrast=contrast, saturation=saturation, hue=hue + ) + + def __call__(self, img): + if random.random() < self.p: + return self.transforms(img) + else: + return img + + +class SVTRDeterioration(object): + def __init__(self, var, degrees, factor, p=0.5): + self.p = p + transforms = [] + if var is not None: + transforms.append(CVGaussianNoise(var=var)) + if degrees is not None: + transforms.append(CVMotionBlur(degrees=degrees)) + if factor is not None: + transforms.append(CVRescale(factor=factor)) + self.transforms = transforms + + def __call__(self, img): + if random.random() < self.p: + random.shuffle(self.transforms) + transforms = Compose(self.transforms) + return transforms(img) + else: + return img + + +class ParseQDeterioration(object): + def __init__(self, var, degrees, lam, radius, factor, p=0.5): + self.p = p + transforms = [] + if var is not None: + transforms.append(CVGaussianNoise(var=var)) + if degrees is not None: + transforms.append(CVMotionBlur(degrees=degrees)) + if lam is not None: + transforms.append(CVPossionNoise(lam=lam)) + if radius is not None: + transforms.append(CVGaussionBlur(radius=radius)) + if factor is not None: + transforms.append(CVRescale(factor=factor)) + self.transforms = transforms + + def __call__(self, img): + if random.random() < self.p: + random.shuffle(self.transforms) + transforms = Compose(self.transforms) + return transforms(img) + else: + return img + + +class SVTRGeometry(object): + def __init__( + self, + aug_type=0, + degrees=15, + translate=(0.3, 0.3), + scale=(0.5, 2.0), + shear=(45, 15), + distortion=0.5, + p=0.5, + ): + self.aug_type = aug_type + self.p = p + self.transforms = [] + self.transforms.append(CVRandomRotation(degrees=degrees)) + self.transforms.append( + CVRandomAffine( + degrees=degrees, translate=translate, scale=scale, shear=shear + ) + ) + self.transforms.append(CVRandomPerspective(distortion=distortion)) + + def __call__(self, img): + if random.random() < self.p: + if self.aug_type: + random.shuffle(self.transforms) + transforms = Compose(self.transforms[: random.randint(1, 3)]) + img = transforms(img) + else: + img = self.transforms[random.randint(0, 2)](img) + return img + else: + return img diff --git a/ppocr/data/imaug/copy_paste.py b/ppocr/data/imaug/copy_paste.py new file mode 100644 index 0000000..4d627d0 --- /dev/null +++ b/ppocr/data/imaug/copy_paste.py @@ -0,0 +1,179 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import copy +import cv2 +import random +import numpy as np +from PIL import Image +from shapely.geometry import Polygon + +from ppocr.data.imaug.iaa_augment import IaaAugment +from ppocr.data.imaug.random_crop_data import is_poly_outside_rect +from tools.infer.utility import get_rotate_crop_image + + +class CopyPaste(object): + def __init__(self, objects_paste_ratio=0.2, limit_paste=True, **kwargs): + self.ext_data_num = 1 + self.objects_paste_ratio = objects_paste_ratio + self.limit_paste = limit_paste + augmenter_args = [{"type": "Resize", "args": {"size": [0.5, 3]}}] + self.aug = IaaAugment(augmenter_args) + + def __call__(self, data): + point_num = data["polys"].shape[1] + src_img = data["image"] + src_polys = data["polys"].tolist() + src_texts = data["texts"] + src_ignores = data["ignore_tags"].tolist() + ext_data = data["ext_data"][0] + ext_image = ext_data["image"] + ext_polys = ext_data["polys"] + ext_texts = ext_data["texts"] + ext_ignores = ext_data["ignore_tags"] + + indexes = [i for i in range(len(ext_ignores)) if not ext_ignores[i]] + select_num = max(1, min(int(self.objects_paste_ratio * len(ext_polys)), 30)) + + random.shuffle(indexes) + select_idxs = indexes[:select_num] + select_polys = ext_polys[select_idxs] + select_ignores = ext_ignores[select_idxs] + + src_img = cv2.cvtColor(src_img, cv2.COLOR_BGR2RGB) + ext_image = cv2.cvtColor(ext_image, cv2.COLOR_BGR2RGB) + src_img = Image.fromarray(src_img).convert("RGBA") + for idx, poly, tag in zip(select_idxs, select_polys, select_ignores): + box_img = get_rotate_crop_image(ext_image, poly) + + src_img, box = self.paste_img(src_img, box_img, src_polys) + if box is not None: + box = box.tolist() + for _ in range(len(box), point_num): + box.append(box[-1]) + src_polys.append(box) + src_texts.append(ext_texts[idx]) + src_ignores.append(tag) + src_img = cv2.cvtColor(np.array(src_img), cv2.COLOR_RGB2BGR) + h, w = src_img.shape[:2] + src_polys = np.array(src_polys) + src_polys[:, :, 0] = np.clip(src_polys[:, :, 0], 0, w) + src_polys[:, :, 1] = np.clip(src_polys[:, :, 1], 0, h) + data["image"] = src_img + data["polys"] = src_polys + data["texts"] = src_texts + data["ignore_tags"] = np.array(src_ignores) + return data + + def paste_img(self, src_img, box_img, src_polys): + box_img_pil = Image.fromarray(box_img).convert("RGBA") + src_w, src_h = src_img.size + box_w, box_h = box_img_pil.size + + angle = np.random.randint(0, 360) + box = np.array([[[0, 0], [box_w, 0], [box_w, box_h], [0, box_h]]]) + box = rotate_bbox(box_img, box, angle)[0] + box_img_pil = box_img_pil.rotate(angle, expand=1) + box_w, box_h = box_img_pil.width, box_img_pil.height + if src_w - box_w < 0 or src_h - box_h < 0: + return src_img, None + + paste_x, paste_y = self.select_coord( + src_polys, box, src_w - box_w, src_h - box_h + ) + if paste_x is None: + return src_img, None + box[:, 0] += paste_x + box[:, 1] += paste_y + r, g, b, A = box_img_pil.split() + src_img.paste(box_img_pil, (paste_x, paste_y), mask=A) + + return src_img, box + + def select_coord(self, src_polys, box, endx, endy): + if self.limit_paste: + xmin, ymin, xmax, ymax = ( + box[:, 0].min(), + box[:, 1].min(), + box[:, 0].max(), + box[:, 1].max(), + ) + for _ in range(50): + paste_x = random.randint(0, endx) + paste_y = random.randint(0, endy) + xmin1 = xmin + paste_x + xmax1 = xmax + paste_x + ymin1 = ymin + paste_y + ymax1 = ymax + paste_y + + num_poly_in_rect = 0 + for poly in src_polys: + if not is_poly_outside_rect( + poly, xmin1, ymin1, xmax1 - xmin1, ymax1 - ymin1 + ): + num_poly_in_rect += 1 + break + if num_poly_in_rect == 0: + return paste_x, paste_y + return None, None + else: + paste_x = random.randint(0, endx) + paste_y = random.randint(0, endy) + return paste_x, paste_y + + +def get_union(pD, pG): + return Polygon(pD).union(Polygon(pG)).area + + +def get_intersection_over_union(pD, pG): + return get_intersection(pD, pG) / get_union(pD, pG) + + +def get_intersection(pD, pG): + return Polygon(pD).intersection(Polygon(pG)).area + + +def rotate_bbox(img, text_polys, angle, scale=1): + """ + from https://github.com/WenmuZhou/DBNet.pytorch/blob/master/data_loader/modules/augment.py + Args: + img: np.ndarray + text_polys: np.ndarray N*4*2 + angle: int + scale: int + + Returns: + + """ + w = img.shape[1] + h = img.shape[0] + + rangle = np.deg2rad(angle) + nw = abs(np.sin(rangle) * h) + abs(np.cos(rangle) * w) + nh = abs(np.cos(rangle) * h) + abs(np.sin(rangle) * w) + rot_mat = cv2.getRotationMatrix2D((nw * 0.5, nh * 0.5), angle, scale) + rot_move = np.dot(rot_mat, np.array([(nw - w) * 0.5, (nh - h) * 0.5, 0])) + rot_mat[0, 2] += rot_move[0] + rot_mat[1, 2] += rot_move[1] + + # ---------------------- rotate box ---------------------- + rot_text_polys = list() + for bbox in text_polys: + point1 = np.dot(rot_mat, np.array([bbox[0, 0], bbox[0, 1], 1])) + point2 = np.dot(rot_mat, np.array([bbox[1, 0], bbox[1, 1], 1])) + point3 = np.dot(rot_mat, np.array([bbox[2, 0], bbox[2, 1], 1])) + point4 = np.dot(rot_mat, np.array([bbox[3, 0], bbox[3, 1], 1])) + rot_text_polys.append([point1, point2, point3, point4]) + return np.array(rot_text_polys, dtype=np.float32) diff --git a/ppocr/data/imaug/ct_process.py b/ppocr/data/imaug/ct_process.py new file mode 100644 index 0000000..26e111d --- /dev/null +++ b/ppocr/data/imaug/ct_process.py @@ -0,0 +1,376 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import cv2 +import paddle +import random +import pyclipper +import numpy as np +from PIL import Image + +import paddle.vision.transforms as transforms + +from ppocr.utils.utility import check_install + + +class RandomScale: + def __init__(self, short_size=640, **kwargs): + self.short_size = short_size + + def scale_aligned(self, img, scale): + oh, ow = img.shape[0:2] + h = int(oh * scale + 0.5) + w = int(ow * scale + 0.5) + if h % 32 != 0: + h = h + (32 - h % 32) + if w % 32 != 0: + w = w + (32 - w % 32) + img = cv2.resize(img, dsize=(w, h)) + factor_h = h / oh + factor_w = w / ow + return img, factor_h, factor_w + + def __call__(self, data): + img = data["image"] + + h, w = img.shape[0:2] + random_scale = np.array([0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3]) + scale = (np.random.choice(random_scale) * self.short_size) / min(h, w) + img, factor_h, factor_w = self.scale_aligned(img, scale) + + data["scale_factor"] = (factor_w, factor_h) + data["image"] = img + return data + + +class MakeShrink: + def __init__(self, kernel_scale=0.7, **kwargs): + self.kernel_scale = kernel_scale + + def dist(self, a, b): + return np.linalg.norm((a - b), ord=2, axis=0) + + def perimeter(self, bbox): + peri = 0.0 + for i in range(bbox.shape[0]): + peri += self.dist(bbox[i], bbox[(i + 1) % bbox.shape[0]]) + return peri + + def shrink(self, bboxes, rate, max_shr=20): + check_install("Polygon", "Polygon3") + import Polygon as plg + + rate = rate * rate + shrinked_bboxes = [] + for bbox in bboxes: + area = plg.Polygon(bbox).area() + peri = self.perimeter(bbox) + + try: + pco = pyclipper.PyclipperOffset() + pco.AddPath(bbox, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + offset = min(int(area * (1 - rate) / (peri + 0.001) + 0.5), max_shr) + + shrinked_bbox = pco.Execute(-offset) + if len(shrinked_bbox) == 0: + shrinked_bboxes.append(bbox) + continue + + shrinked_bbox = np.array(shrinked_bbox[0]) + if shrinked_bbox.shape[0] <= 2: + shrinked_bboxes.append(bbox) + continue + + shrinked_bboxes.append(shrinked_bbox) + except Exception as e: + shrinked_bboxes.append(bbox) + + return shrinked_bboxes + + def __call__(self, data): + img = data["image"] + bboxes = data["polys"] + words = data["texts"] + scale_factor = data["scale_factor"] + + gt_instance = np.zeros(img.shape[0:2], dtype="uint8") # h,w + training_mask = np.ones(img.shape[0:2], dtype="uint8") + training_mask_distance = np.ones(img.shape[0:2], dtype="uint8") + + for i in range(len(bboxes)): + bboxes[i] = np.reshape( + bboxes[i] + * ([scale_factor[0], scale_factor[1]] * (bboxes[i].shape[0] // 2)), + (bboxes[i].shape[0] // 2, 2), + ).astype("int32") + + for i in range(len(bboxes)): + # different value for different bbox + cv2.drawContours(gt_instance, [bboxes[i]], -1, i + 1, -1) + + # set training mask to 0 + cv2.drawContours(training_mask, [bboxes[i]], -1, 0, -1) + + # for not accurate annotation, use training_mask_distance + if words[i] == "###" or words[i] == "???": + cv2.drawContours(training_mask_distance, [bboxes[i]], -1, 0, -1) + + # make shrink + gt_kernel_instance = np.zeros(img.shape[0:2], dtype="uint8") + kernel_bboxes = self.shrink(bboxes, self.kernel_scale) + for i in range(len(bboxes)): + cv2.drawContours(gt_kernel_instance, [kernel_bboxes[i]], -1, i + 1, -1) + + # for training mask, kernel and background= 1, box region=0 + if words[i] != "###" and words[i] != "???": + cv2.drawContours(training_mask, [kernel_bboxes[i]], -1, 1, -1) + + gt_kernel = gt_kernel_instance.copy() + # for gt_kernel, kernel = 1 + gt_kernel[gt_kernel > 0] = 1 + + # shrink 2 times + tmp1 = gt_kernel_instance.copy() + erode_kernel = np.ones((3, 3), np.uint8) + tmp1 = cv2.erode(tmp1, erode_kernel, iterations=1) + tmp2 = tmp1.copy() + tmp2 = cv2.erode(tmp2, erode_kernel, iterations=1) + + # compute text region + gt_kernel_inner = tmp1 - tmp2 + + # gt_instance: text instance, bg=0, diff word use diff value + # training_mask: text instance mask, word=0,kernel and bg=1 + # gt_kernel_instance: text kernel instance, bg=0, diff word use diff value + # gt_kernel: text_kernel, bg=0,diff word use same value + # gt_kernel_inner: text kernel reference + # training_mask_distance: word without anno = 0, else 1 + + data["image"] = [ + img, + gt_instance, + training_mask, + gt_kernel_instance, + gt_kernel, + gt_kernel_inner, + training_mask_distance, + ] + return data + + +class GroupRandomHorizontalFlip: + def __init__(self, p=0.5, **kwargs): + self.p = p + + def __call__(self, data): + imgs = data["image"] + + if random.random() < self.p: + for i in range(len(imgs)): + imgs[i] = np.flip(imgs[i], axis=1).copy() + data["image"] = imgs + return data + + +class GroupRandomRotate: + def __init__(self, **kwargs): + pass + + def __call__(self, data): + imgs = data["image"] + + max_angle = 10 + angle = random.random() * 2 * max_angle - max_angle + for i in range(len(imgs)): + img = imgs[i] + w, h = img.shape[:2] + rotation_matrix = cv2.getRotationMatrix2D((h / 2, w / 2), angle, 1) + img_rotation = cv2.warpAffine( + img, rotation_matrix, (h, w), flags=cv2.INTER_NEAREST + ) + imgs[i] = img_rotation + + data["image"] = imgs + return data + + +class GroupRandomCropPadding: + def __init__(self, target_size=(640, 640), **kwargs): + self.target_size = target_size + + def __call__(self, data): + imgs = data["image"] + + h, w = imgs[0].shape[0:2] + t_w, t_h = self.target_size + p_w, p_h = self.target_size + if w == t_w and h == t_h: + return data + + t_h = t_h if t_h < h else h + t_w = t_w if t_w < w else w + + if random.random() > 3.0 / 8.0 and np.max(imgs[1]) > 0: + # make sure to crop the text region + tl = np.min(np.where(imgs[1] > 0), axis=1) - (t_h, t_w) + tl[tl < 0] = 0 + br = np.max(np.where(imgs[1] > 0), axis=1) - (t_h, t_w) + br[br < 0] = 0 + br[0] = min(br[0], h - t_h) + br[1] = min(br[1], w - t_w) + + i = random.randint(tl[0], br[0]) if tl[0] < br[0] else 0 + j = random.randint(tl[1], br[1]) if tl[1] < br[1] else 0 + else: + i = random.randint(0, h - t_h) if h - t_h > 0 else 0 + j = random.randint(0, w - t_w) if w - t_w > 0 else 0 + + n_imgs = [] + for idx in range(len(imgs)): + if len(imgs[idx].shape) == 3: + s3_length = int(imgs[idx].shape[-1]) + img = imgs[idx][i : i + t_h, j : j + t_w, :] + img_p = cv2.copyMakeBorder( + img, + 0, + p_h - t_h, + 0, + p_w - t_w, + borderType=cv2.BORDER_CONSTANT, + value=tuple(0 for i in range(s3_length)), + ) + else: + img = imgs[idx][i : i + t_h, j : j + t_w] + img_p = cv2.copyMakeBorder( + img, + 0, + p_h - t_h, + 0, + p_w - t_w, + borderType=cv2.BORDER_CONSTANT, + value=(0,), + ) + n_imgs.append(img_p) + + data["image"] = n_imgs + return data + + +class MakeCentripetalShift: + def __init__(self, **kwargs): + pass + + def jaccard(self, As, Bs): + A = As.shape[0] # small + B = Bs.shape[0] # large + + dis = np.sqrt( + np.sum( + ( + As[:, np.newaxis, :].repeat(B, axis=1) + - Bs[np.newaxis, :, :].repeat(A, axis=0) + ) + ** 2, + axis=-1, + ) + ) + + ind = np.argmin(dis, axis=-1) + + return ind + + def __call__(self, data): + imgs = data["image"] + + ( + img, + gt_instance, + training_mask, + gt_kernel_instance, + gt_kernel, + gt_kernel_inner, + training_mask_distance, + ) = (imgs[0], imgs[1], imgs[2], imgs[3], imgs[4], imgs[5], imgs[6]) + + max_instance = np.max(gt_instance) # num bbox + + # make centripetal shift + gt_distance = np.zeros((2, *img.shape[0:2]), dtype=np.float32) + for i in range(1, max_instance + 1): + # kernel_reference + ind = gt_kernel_inner == i + + if np.sum(ind) == 0: + training_mask[gt_instance == i] = 0 + training_mask_distance[gt_instance == i] = 0 + continue + + kpoints = ( + np.array(np.where(ind)).transpose((1, 0))[:, ::-1].astype("float32") + ) + + ind = (gt_instance == i) * (gt_kernel_instance == 0) + if np.sum(ind) == 0: + continue + pixels = np.where(ind) + + points = np.array(pixels).transpose((1, 0))[:, ::-1].astype("float32") + + bbox_ind = self.jaccard(points, kpoints) + + offset_gt = kpoints[bbox_ind] - points + + gt_distance[:, pixels[0], pixels[1]] = offset_gt.T * 0.1 + + img = Image.fromarray(img) + img = img.convert("RGB") + + data["image"] = img + data["gt_kernel"] = gt_kernel.astype("int64") + data["training_mask"] = training_mask.astype("int64") + data["gt_instance"] = gt_instance.astype("int64") + data["gt_kernel_instance"] = gt_kernel_instance.astype("int64") + data["training_mask_distance"] = training_mask_distance.astype("int64") + data["gt_distance"] = gt_distance.astype("float32") + + return data + + +class ScaleAlignedShort: + def __init__(self, short_size=640, **kwargs): + self.short_size = short_size + + def __call__(self, data): + img = data["image"] + + org_img_shape = img.shape + + h, w = img.shape[0:2] + scale = self.short_size * 1.0 / min(h, w) + h = int(h * scale + 0.5) + w = int(w * scale + 0.5) + if h % 32 != 0: + h = h + (32 - h % 32) + if w % 32 != 0: + w = w + (32 - w % 32) + img = cv2.resize(img, dsize=(w, h)) + + new_img_shape = img.shape + img_shape = np.array(org_img_shape + new_img_shape) + + data["shape"] = img_shape + data["image"] = img + + return data diff --git a/ppocr/data/imaug/drrg_targets.py b/ppocr/data/imaug/drrg_targets.py new file mode 100644 index 0000000..8eeef26 --- /dev/null +++ b/ppocr/data/imaug/drrg_targets.py @@ -0,0 +1,770 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/datasets/pipelines/textdet_targets/drrg_targets.py +""" + +import cv2 +import numpy as np +from ppocr.utils.utility import check_install +from numpy.linalg import norm + + +class DRRGTargets(object): + def __init__( + self, + orientation_thr=2.0, + resample_step=8.0, + num_min_comps=9, + num_max_comps=600, + min_width=8.0, + max_width=24.0, + center_region_shrink_ratio=0.3, + comp_shrink_ratio=1.0, + comp_w_h_ratio=0.3, + text_comp_nms_thr=0.25, + min_rand_half_height=8.0, + max_rand_half_height=24.0, + jitter_level=0.2, + **kwargs, + ): + super().__init__() + self.orientation_thr = orientation_thr + self.resample_step = resample_step + self.num_max_comps = num_max_comps + self.num_min_comps = num_min_comps + self.min_width = min_width + self.max_width = max_width + self.center_region_shrink_ratio = center_region_shrink_ratio + self.comp_shrink_ratio = comp_shrink_ratio + self.comp_w_h_ratio = comp_w_h_ratio + self.text_comp_nms_thr = text_comp_nms_thr + self.min_rand_half_height = min_rand_half_height + self.max_rand_half_height = max_rand_half_height + self.jitter_level = jitter_level + self.eps = 1e-8 + + def vector_angle(self, vec1, vec2): + if vec1.ndim > 1: + unit_vec1 = vec1 / (norm(vec1, axis=-1) + self.eps).reshape((-1, 1)) + else: + unit_vec1 = vec1 / (norm(vec1, axis=-1) + self.eps) + if vec2.ndim > 1: + unit_vec2 = vec2 / (norm(vec2, axis=-1) + self.eps).reshape((-1, 1)) + else: + unit_vec2 = vec2 / (norm(vec2, axis=-1) + self.eps) + return np.arccos(np.clip(np.sum(unit_vec1 * unit_vec2, axis=-1), -1.0, 1.0)) + + def vector_slope(self, vec): + assert len(vec) == 2 + return abs(vec[1] / (vec[0] + self.eps)) + + def vector_sin(self, vec): + assert len(vec) == 2 + return vec[1] / (norm(vec) + self.eps) + + def vector_cos(self, vec): + assert len(vec) == 2 + return vec[0] / (norm(vec) + self.eps) + + def find_head_tail(self, points, orientation_thr): + assert points.ndim == 2 + assert points.shape[0] >= 4 + assert points.shape[1] == 2 + assert isinstance(orientation_thr, float) + + if len(points) > 4: + pad_points = np.vstack([points, points[0]]) + edge_vec = pad_points[1:] - pad_points[:-1] + + theta_sum = [] + adjacent_vec_theta = [] + for i, edge_vec1 in enumerate(edge_vec): + adjacent_ind = [x % len(edge_vec) for x in [i - 1, i + 1]] + adjacent_edge_vec = edge_vec[adjacent_ind] + temp_theta_sum = np.sum(self.vector_angle(edge_vec1, adjacent_edge_vec)) + temp_adjacent_theta = self.vector_angle( + adjacent_edge_vec[0], adjacent_edge_vec[1] + ) + theta_sum.append(temp_theta_sum) + adjacent_vec_theta.append(temp_adjacent_theta) + theta_sum_score = np.array(theta_sum) / np.pi + adjacent_theta_score = np.array(adjacent_vec_theta) / np.pi + poly_center = np.mean(points, axis=0) + edge_dist = np.maximum( + norm(pad_points[1:] - poly_center, axis=-1), + norm(pad_points[:-1] - poly_center, axis=-1), + ) + dist_score = edge_dist / (np.max(edge_dist) + self.eps) + position_score = np.zeros(len(edge_vec)) + score = 0.5 * theta_sum_score + 0.15 * adjacent_theta_score + score += 0.35 * dist_score + if len(points) % 2 == 0: + position_score[(len(score) // 2 - 1)] += 1 + position_score[-1] += 1 + score += 0.1 * position_score + pad_score = np.concatenate([score, score]) + score_matrix = np.zeros((len(score), len(score) - 3)) + x = np.arange(len(score) - 3) / float(len(score) - 4) + gaussian = ( + 1.0 + / (np.sqrt(2.0 * np.pi) * 0.5) + * np.exp(-np.power((x - 0.5) / 0.5, 2.0) / 2) + ) + gaussian = gaussian / np.max(gaussian) + for i in range(len(score)): + score_matrix[i, :] = ( + score[i] + + pad_score[(i + 2) : (i + len(score) - 1)] * gaussian * 0.3 + ) + + head_start, tail_increment = np.unravel_index( + score_matrix.argmax(), score_matrix.shape + ) + tail_start = (head_start + tail_increment + 2) % len(points) + head_end = (head_start + 1) % len(points) + tail_end = (tail_start + 1) % len(points) + + if head_end > tail_end: + head_start, tail_start = tail_start, head_start + head_end, tail_end = tail_end, head_end + head_inds = [head_start, head_end] + tail_inds = [tail_start, tail_end] + else: + if self.vector_slope(points[1] - points[0]) + self.vector_slope( + points[3] - points[2] + ) < self.vector_slope(points[2] - points[1]) + self.vector_slope( + points[0] - points[3] + ): + horizontal_edge_inds = [[0, 1], [2, 3]] + vertical_edge_inds = [[3, 0], [1, 2]] + else: + horizontal_edge_inds = [[3, 0], [1, 2]] + vertical_edge_inds = [[0, 1], [2, 3]] + + vertical_len_sum = norm( + points[vertical_edge_inds[0][0]] - points[vertical_edge_inds[0][1]] + ) + norm( + points[vertical_edge_inds[1][0]] - points[vertical_edge_inds[1][1]] + ) + horizontal_len_sum = norm( + points[horizontal_edge_inds[0][0]] - points[horizontal_edge_inds[0][1]] + ) + norm( + points[horizontal_edge_inds[1][0]] - points[horizontal_edge_inds[1][1]] + ) + + if vertical_len_sum > horizontal_len_sum * orientation_thr: + head_inds = horizontal_edge_inds[0] + tail_inds = horizontal_edge_inds[1] + else: + head_inds = vertical_edge_inds[0] + tail_inds = vertical_edge_inds[1] + + return head_inds, tail_inds + + def reorder_poly_edge(self, points): + assert points.ndim == 2 + assert points.shape[0] >= 4 + assert points.shape[1] == 2 + + head_inds, tail_inds = self.find_head_tail(points, self.orientation_thr) + head_edge, tail_edge = points[head_inds], points[tail_inds] + + pad_points = np.vstack([points, points]) + if tail_inds[1] < 1: + tail_inds[1] = len(points) + sideline1 = pad_points[head_inds[1] : tail_inds[1]] + sideline2 = pad_points[tail_inds[1] : (head_inds[1] + len(points))] + sideline_mean_shift = np.mean(sideline1, axis=0) - np.mean(sideline2, axis=0) + + if sideline_mean_shift[1] > 0: + top_sideline, bot_sideline = sideline2, sideline1 + else: + top_sideline, bot_sideline = sideline1, sideline2 + + return head_edge, tail_edge, top_sideline, bot_sideline + + def cal_curve_length(self, line): + assert line.ndim == 2 + assert len(line) >= 2 + + edges_length = np.sqrt( + (line[1:, 0] - line[:-1, 0]) ** 2 + (line[1:, 1] - line[:-1, 1]) ** 2 + ) + total_length = np.sum(edges_length) + return edges_length, total_length + + def resample_line(self, line, n): + assert line.ndim == 2 + assert line.shape[0] >= 2 + assert line.shape[1] == 2 + assert isinstance(n, int) + assert n > 2 + + edges_length, total_length = self.cal_curve_length(line) + t_org = np.insert(np.cumsum(edges_length), 0, 0) + unit_t = total_length / (n - 1) + t_equidistant = np.arange(1, n - 1, dtype=np.float32) * unit_t + edge_ind = 0 + points = [line[0]] + for t in t_equidistant: + while edge_ind < len(edges_length) - 1 and t > t_org[edge_ind + 1]: + edge_ind += 1 + t_l, t_r = t_org[edge_ind], t_org[edge_ind + 1] + weight = np.array([t_r - t, t - t_l], dtype=np.float32) / ( + t_r - t_l + self.eps + ) + p_coords = np.dot(weight, line[[edge_ind, edge_ind + 1]]) + points.append(p_coords) + points.append(line[-1]) + resampled_line = np.vstack(points) + + return resampled_line + + def resample_sidelines(self, sideline1, sideline2, resample_step): + assert sideline1.ndim == sideline2.ndim == 2 + assert sideline1.shape[1] == sideline2.shape[1] == 2 + assert sideline1.shape[0] >= 2 + assert sideline2.shape[0] >= 2 + assert isinstance(resample_step, float) + + _, length1 = self.cal_curve_length(sideline1) + _, length2 = self.cal_curve_length(sideline2) + + avg_length = (length1 + length2) / 2 + resample_point_num = max(int(float(avg_length) / resample_step) + 1, 3) + + resampled_line1 = self.resample_line(sideline1, resample_point_num) + resampled_line2 = self.resample_line(sideline2, resample_point_num) + + return resampled_line1, resampled_line2 + + def dist_point2line(self, point, line): + assert isinstance(line, tuple) + point1, point2 = line + d = abs(np.cross(point2 - point1, point - point1)) / ( + norm(point2 - point1) + 1e-8 + ) + return d + + def draw_center_region_maps( + self, + top_line, + bot_line, + center_line, + center_region_mask, + top_height_map, + bot_height_map, + sin_map, + cos_map, + region_shrink_ratio, + ): + assert top_line.shape == bot_line.shape == center_line.shape + assert ( + center_region_mask.shape + == top_height_map.shape + == bot_height_map.shape + == sin_map.shape + == cos_map.shape + ) + assert isinstance(region_shrink_ratio, float) + + h, w = center_region_mask.shape + for i in range(0, len(center_line) - 1): + top_mid_point = (top_line[i] + top_line[i + 1]) / 2 + bot_mid_point = (bot_line[i] + bot_line[i + 1]) / 2 + + sin_theta = self.vector_sin(top_mid_point - bot_mid_point) + cos_theta = self.vector_cos(top_mid_point - bot_mid_point) + + tl = center_line[i] + (top_line[i] - center_line[i]) * region_shrink_ratio + tr = ( + center_line[i + 1] + + (top_line[i + 1] - center_line[i + 1]) * region_shrink_ratio + ) + br = ( + center_line[i + 1] + + (bot_line[i + 1] - center_line[i + 1]) * region_shrink_ratio + ) + bl = center_line[i] + (bot_line[i] - center_line[i]) * region_shrink_ratio + current_center_box = np.vstack([tl, tr, br, bl]).astype(np.int32) + + cv2.fillPoly(center_region_mask, [current_center_box], color=1) + cv2.fillPoly(sin_map, [current_center_box], color=sin_theta) + cv2.fillPoly(cos_map, [current_center_box], color=cos_theta) + + current_center_box[:, 0] = np.clip(current_center_box[:, 0], 0, w - 1) + current_center_box[:, 1] = np.clip(current_center_box[:, 1], 0, h - 1) + min_coord = np.min(current_center_box, axis=0).astype(np.int32) + max_coord = np.max(current_center_box, axis=0).astype(np.int32) + current_center_box = current_center_box - min_coord + box_sz = max_coord - min_coord + 1 + + center_box_mask = np.zeros((box_sz[1], box_sz[0]), dtype=np.uint8) + cv2.fillPoly(center_box_mask, [current_center_box], color=1) + + inds = np.argwhere(center_box_mask > 0) + inds = inds + (min_coord[1], min_coord[0]) + inds_xy = np.fliplr(inds) + top_height_map[(inds[:, 0], inds[:, 1])] = self.dist_point2line( + inds_xy, (top_line[i], top_line[i + 1]) + ) + bot_height_map[(inds[:, 0], inds[:, 1])] = self.dist_point2line( + inds_xy, (bot_line[i], bot_line[i + 1]) + ) + + def generate_center_mask_attrib_maps(self, img_size, text_polys): + assert isinstance(img_size, tuple) + + h, w = img_size + + center_lines = [] + center_region_mask = np.zeros((h, w), np.uint8) + top_height_map = np.zeros((h, w), dtype=np.float32) + bot_height_map = np.zeros((h, w), dtype=np.float32) + sin_map = np.zeros((h, w), dtype=np.float32) + cos_map = np.zeros((h, w), dtype=np.float32) + + for poly in text_polys: + polygon_points = poly + _, _, top_line, bot_line = self.reorder_poly_edge(polygon_points) + resampled_top_line, resampled_bot_line = self.resample_sidelines( + top_line, bot_line, self.resample_step + ) + resampled_bot_line = resampled_bot_line[::-1] + center_line = (resampled_top_line + resampled_bot_line) / 2 + + if self.vector_slope(center_line[-1] - center_line[0]) > 2: + if (center_line[-1] - center_line[0])[1] < 0: + center_line = center_line[::-1] + resampled_top_line = resampled_top_line[::-1] + resampled_bot_line = resampled_bot_line[::-1] + else: + if (center_line[-1] - center_line[0])[0] < 0: + center_line = center_line[::-1] + resampled_top_line = resampled_top_line[::-1] + resampled_bot_line = resampled_bot_line[::-1] + + line_head_shrink_len = ( + np.clip( + (norm(top_line[0] - bot_line[0]) * self.comp_w_h_ratio), + self.min_width, + self.max_width, + ) + / 2 + ) + line_tail_shrink_len = ( + np.clip( + (norm(top_line[-1] - bot_line[-1]) * self.comp_w_h_ratio), + self.min_width, + self.max_width, + ) + / 2 + ) + num_head_shrink = int(line_head_shrink_len // self.resample_step) + num_tail_shrink = int(line_tail_shrink_len // self.resample_step) + if len(center_line) > num_head_shrink + num_tail_shrink + 2: + center_line = center_line[ + num_head_shrink : len(center_line) - num_tail_shrink + ] + resampled_top_line = resampled_top_line[ + num_head_shrink : len(resampled_top_line) - num_tail_shrink + ] + resampled_bot_line = resampled_bot_line[ + num_head_shrink : len(resampled_bot_line) - num_tail_shrink + ] + center_lines.append(center_line.astype(np.int32)) + + self.draw_center_region_maps( + resampled_top_line, + resampled_bot_line, + center_line, + center_region_mask, + top_height_map, + bot_height_map, + sin_map, + cos_map, + self.center_region_shrink_ratio, + ) + + return ( + center_lines, + center_region_mask, + top_height_map, + bot_height_map, + sin_map, + cos_map, + ) + + def generate_rand_comp_attribs(self, num_rand_comps, center_sample_mask): + assert isinstance(num_rand_comps, int) + assert num_rand_comps > 0 + assert center_sample_mask.ndim == 2 + + h, w = center_sample_mask.shape + + max_rand_half_height = self.max_rand_half_height + min_rand_half_height = self.min_rand_half_height + max_rand_height = max_rand_half_height * 2 + max_rand_width = np.clip( + max_rand_height * self.comp_w_h_ratio, self.min_width, self.max_width + ) + margin = ( + int(np.sqrt((max_rand_height / 2) ** 2 + (max_rand_width / 2) ** 2)) + 1 + ) + + if 2 * margin + 1 > min(h, w): + assert min(h, w) > (np.sqrt(2) * (self.min_width + 1)) + max_rand_half_height = max(min(h, w) / 4, self.min_width / 2 + 1) + min_rand_half_height = max(max_rand_half_height / 4, self.min_width / 2) + + max_rand_height = max_rand_half_height * 2 + max_rand_width = np.clip( + max_rand_height * self.comp_w_h_ratio, self.min_width, self.max_width + ) + margin = ( + int(np.sqrt((max_rand_height / 2) ** 2 + (max_rand_width / 2) ** 2)) + 1 + ) + + inner_center_sample_mask = np.zeros_like(center_sample_mask) + inner_center_sample_mask[margin : h - margin, margin : w - margin] = ( + center_sample_mask[margin : h - margin, margin : w - margin] + ) + kernel_size = int(np.clip(max_rand_half_height, 7, 21)) + inner_center_sample_mask = cv2.erode( + inner_center_sample_mask, np.ones((kernel_size, kernel_size), np.uint8) + ) + + center_candidates = np.argwhere(inner_center_sample_mask > 0) + num_center_candidates = len(center_candidates) + sample_inds = np.random.choice(num_center_candidates, num_rand_comps) + rand_centers = center_candidates[sample_inds] + + rand_top_height = np.random.randint( + min_rand_half_height, max_rand_half_height, size=(len(rand_centers), 1) + ) + rand_bot_height = np.random.randint( + min_rand_half_height, max_rand_half_height, size=(len(rand_centers), 1) + ) + + rand_cos = 2 * np.random.random(size=(len(rand_centers), 1)) - 1 + rand_sin = 2 * np.random.random(size=(len(rand_centers), 1)) - 1 + scale = np.sqrt(1.0 / (rand_cos**2 + rand_sin**2 + 1e-8)) + rand_cos = rand_cos * scale + rand_sin = rand_sin * scale + + height = rand_top_height + rand_bot_height + width = np.clip(height * self.comp_w_h_ratio, self.min_width, self.max_width) + + rand_comp_attribs = np.hstack( + [ + rand_centers[:, ::-1], + height, + width, + rand_cos, + rand_sin, + np.zeros_like(rand_sin), + ] + ).astype(np.float32) + + return rand_comp_attribs + + def jitter_comp_attribs(self, comp_attribs, jitter_level): + """Jitter text components attributes. + + Args: + comp_attribs (ndarray): The text component attributes. + jitter_level (float): The jitter level of text components + attributes. + + Returns: + jittered_comp_attribs (ndarray): The jittered text component + attributes (x, y, h, w, cos, sin, comp_label). + """ + + assert comp_attribs.shape[1] == 7 + assert comp_attribs.shape[0] > 0 + assert isinstance(jitter_level, float) + + x = comp_attribs[:, 0].reshape((-1, 1)) + y = comp_attribs[:, 1].reshape((-1, 1)) + h = comp_attribs[:, 2].reshape((-1, 1)) + w = comp_attribs[:, 3].reshape((-1, 1)) + cos = comp_attribs[:, 4].reshape((-1, 1)) + sin = comp_attribs[:, 5].reshape((-1, 1)) + comp_labels = comp_attribs[:, 6].reshape((-1, 1)) + + x += ( + (np.random.random(size=(len(comp_attribs), 1)) - 0.5) + * (h * np.abs(cos) + w * np.abs(sin)) + * jitter_level + ) + y += ( + (np.random.random(size=(len(comp_attribs), 1)) - 0.5) + * (h * np.abs(sin) + w * np.abs(cos)) + * jitter_level + ) + + h += (np.random.random(size=(len(comp_attribs), 1)) - 0.5) * h * jitter_level + w += (np.random.random(size=(len(comp_attribs), 1)) - 0.5) * w * jitter_level + + cos += (np.random.random(size=(len(comp_attribs), 1)) - 0.5) * 2 * jitter_level + sin += (np.random.random(size=(len(comp_attribs), 1)) - 0.5) * 2 * jitter_level + + scale = np.sqrt(1.0 / (cos**2 + sin**2 + 1e-8)) + cos = cos * scale + sin = sin * scale + + jittered_comp_attribs = np.hstack([x, y, h, w, cos, sin, comp_labels]) + + return jittered_comp_attribs + + def generate_comp_attribs( + self, + center_lines, + text_mask, + center_region_mask, + top_height_map, + bot_height_map, + sin_map, + cos_map, + ): + """Generate text component attributes. + + Args: + center_lines (list[ndarray]): The list of text center lines . + text_mask (ndarray): The text region mask. + center_region_mask (ndarray): The text center region mask. + top_height_map (ndarray): The map on which the distance from points + to top side lines will be drawn for each pixel in text center + regions. + bot_height_map (ndarray): The map on which the distance from points + to bottom side lines will be drawn for each pixel in text + center regions. + sin_map (ndarray): The sin(theta) map where theta is the angle + between vector (top point - bottom point) and vector (1, 0). + cos_map (ndarray): The cos(theta) map where theta is the angle + between vector (top point - bottom point) and vector (1, 0). + + Returns: + pad_comp_attribs (ndarray): The padded text component attributes + of a fixed size. + """ + + assert isinstance(center_lines, list) + assert ( + text_mask.shape + == center_region_mask.shape + == top_height_map.shape + == bot_height_map.shape + == sin_map.shape + == cos_map.shape + ) + + center_lines_mask = np.zeros_like(center_region_mask) + cv2.polylines(center_lines_mask, center_lines, 0, 1, 1) + center_lines_mask = center_lines_mask * center_region_mask + comp_centers = np.argwhere(center_lines_mask > 0) + + y = comp_centers[:, 0] + x = comp_centers[:, 1] + + top_height = top_height_map[y, x].reshape((-1, 1)) * self.comp_shrink_ratio + bot_height = bot_height_map[y, x].reshape((-1, 1)) * self.comp_shrink_ratio + sin = sin_map[y, x].reshape((-1, 1)) + cos = cos_map[y, x].reshape((-1, 1)) + + top_mid_points = comp_centers + np.hstack([top_height * sin, top_height * cos]) + bot_mid_points = comp_centers - np.hstack([bot_height * sin, bot_height * cos]) + + width = (top_height + bot_height) * self.comp_w_h_ratio + width = np.clip(width, self.min_width, self.max_width) + r = width / 2 + + tl = top_mid_points[:, ::-1] - np.hstack([-r * sin, r * cos]) + tr = top_mid_points[:, ::-1] + np.hstack([-r * sin, r * cos]) + br = bot_mid_points[:, ::-1] + np.hstack([-r * sin, r * cos]) + bl = bot_mid_points[:, ::-1] - np.hstack([-r * sin, r * cos]) + text_comps = np.hstack([tl, tr, br, bl]).astype(np.float32) + + score = np.ones((text_comps.shape[0], 1), dtype=np.float32) + text_comps = np.hstack([text_comps, score]) + check_install("lanms", "lanms-neo") + from lanms import merge_quadrangle_n9 as la_nms + + text_comps = la_nms(text_comps, self.text_comp_nms_thr) + + if text_comps.shape[0] >= 1: + img_h, img_w = center_region_mask.shape + text_comps[:, 0:8:2] = np.clip(text_comps[:, 0:8:2], 0, img_w - 1) + text_comps[:, 1:8:2] = np.clip(text_comps[:, 1:8:2], 0, img_h - 1) + + comp_centers = np.mean( + text_comps[:, 0:8].reshape((-1, 4, 2)), axis=1 + ).astype(np.int32) + x = comp_centers[:, 0] + y = comp_centers[:, 1] + + height = (top_height_map[y, x] + bot_height_map[y, x]).reshape((-1, 1)) + width = np.clip( + height * self.comp_w_h_ratio, self.min_width, self.max_width + ) + + cos = cos_map[y, x].reshape((-1, 1)) + sin = sin_map[y, x].reshape((-1, 1)) + + _, comp_label_mask = cv2.connectedComponents( + center_region_mask, connectivity=8 + ) + comp_labels = comp_label_mask[y, x].reshape((-1, 1)).astype(np.float32) + + x = x.reshape((-1, 1)).astype(np.float32) + y = y.reshape((-1, 1)).astype(np.float32) + comp_attribs = np.hstack([x, y, height, width, cos, sin, comp_labels]) + comp_attribs = self.jitter_comp_attribs(comp_attribs, self.jitter_level) + + if comp_attribs.shape[0] < self.num_min_comps: + num_rand_comps = self.num_min_comps - comp_attribs.shape[0] + rand_comp_attribs = self.generate_rand_comp_attribs( + num_rand_comps, 1 - text_mask + ) + comp_attribs = np.vstack([comp_attribs, rand_comp_attribs]) + else: + comp_attribs = self.generate_rand_comp_attribs( + self.num_min_comps, 1 - text_mask + ) + + num_comps = ( + np.ones((comp_attribs.shape[0], 1), dtype=np.float32) + * comp_attribs.shape[0] + ) + comp_attribs = np.hstack([num_comps, comp_attribs]) + + if comp_attribs.shape[0] > self.num_max_comps: + comp_attribs = comp_attribs[: self.num_max_comps, :] + comp_attribs[:, 0] = self.num_max_comps + + pad_comp_attribs = np.zeros( + (self.num_max_comps, comp_attribs.shape[1]), dtype=np.float32 + ) + pad_comp_attribs[: comp_attribs.shape[0], :] = comp_attribs + + return pad_comp_attribs + + def generate_text_region_mask(self, img_size, text_polys): + """Generate text center region mask and geometry attribute maps. + + Args: + img_size (tuple): The image size (height, width). + text_polys (list[list[ndarray]]): The list of text polygons. + + Returns: + text_region_mask (ndarray): The text region mask. + """ + + assert isinstance(img_size, tuple) + + h, w = img_size + text_region_mask = np.zeros((h, w), dtype=np.uint8) + + for poly in text_polys: + polygon = np.array(poly, dtype=np.int32).reshape((1, -1, 2)) + cv2.fillPoly(text_region_mask, polygon, 1) + + return text_region_mask + + def generate_effective_mask(self, mask_size: tuple, polygons_ignore): + """Generate effective mask by setting the ineffective regions to 0 and + effective regions to 1. + + Args: + mask_size (tuple): The mask size. + polygons_ignore (list[[ndarray]]: The list of ignored text + polygons. + + Returns: + mask (ndarray): The effective mask of (height, width). + """ + mask = np.ones(mask_size, dtype=np.uint8) + + for poly in polygons_ignore: + instance = poly.astype(np.int32).reshape(1, -1, 2) + cv2.fillPoly(mask, instance, 0) + + return mask + + def generate_targets(self, data): + """Generate the gt targets for DRRG. + + Args: + data (dict): The input result dictionary. + + Returns: + data (dict): The output result dictionary. + """ + + assert isinstance(data, dict) + + image = data["image"] + polygons = data["polys"] + ignore_tags = data["ignore_tags"] + h, w, _ = image.shape + + polygon_masks = [] + polygon_masks_ignore = [] + for tag, polygon in zip(ignore_tags, polygons): + if tag is True: + polygon_masks_ignore.append(polygon) + else: + polygon_masks.append(polygon) + + gt_text_mask = self.generate_text_region_mask((h, w), polygon_masks) + gt_mask = self.generate_effective_mask((h, w), polygon_masks_ignore) + ( + center_lines, + gt_center_region_mask, + gt_top_height_map, + gt_bot_height_map, + gt_sin_map, + gt_cos_map, + ) = self.generate_center_mask_attrib_maps((h, w), polygon_masks) + + gt_comp_attribs = self.generate_comp_attribs( + center_lines, + gt_text_mask, + gt_center_region_mask, + gt_top_height_map, + gt_bot_height_map, + gt_sin_map, + gt_cos_map, + ) + + mapping = { + "gt_text_mask": gt_text_mask, + "gt_center_region_mask": gt_center_region_mask, + "gt_mask": gt_mask, + "gt_top_height_map": gt_top_height_map, + "gt_bot_height_map": gt_bot_height_map, + "gt_sin_map": gt_sin_map, + "gt_cos_map": gt_cos_map, + } + + data.update(mapping) + data["gt_comp_attribs"] = gt_comp_attribs + return data + + def __call__(self, data): + data = self.generate_targets(data) + return data diff --git a/ppocr/data/imaug/east_process.py b/ppocr/data/imaug/east_process.py new file mode 100644 index 0000000..7e5bfa0 --- /dev/null +++ b/ppocr/data/imaug/east_process.py @@ -0,0 +1,446 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is referred from: +https://github.com/songdejia/EAST/blob/master/data_utils.py +""" +import math +import cv2 +import numpy as np +import json +import sys +import os + +__all__ = ["EASTProcessTrain"] + + +class EASTProcessTrain(object): + def __init__( + self, + image_shape=[512, 512], + background_ratio=0.125, + min_crop_side_ratio=0.1, + min_text_size=10, + **kwargs, + ): + self.input_size = image_shape[1] + self.random_scale = np.array([0.5, 1, 2.0, 3.0]) + self.background_ratio = background_ratio + self.min_crop_side_ratio = min_crop_side_ratio + self.min_text_size = min_text_size + + def preprocess(self, im): + input_size = self.input_size + im_shape = im.shape + im_size_min = np.min(im_shape[0:2]) + im_size_max = np.max(im_shape[0:2]) + im_scale = float(input_size) / float(im_size_max) + im = cv2.resize(im, None, None, fx=im_scale, fy=im_scale) + img_mean = [0.485, 0.456, 0.406] + img_std = [0.229, 0.224, 0.225] + # im = im[:, :, ::-1].astype(np.float32) + im = im / 255 + im -= img_mean + im /= img_std + new_h, new_w, _ = im.shape + im_padded = np.zeros((input_size, input_size, 3), dtype=np.float32) + im_padded[:new_h, :new_w, :] = im + im_padded = im_padded.transpose((2, 0, 1)) + im_padded = im_padded[np.newaxis, :] + return im_padded, im_scale + + def rotate_im_poly(self, im, text_polys): + """ + rotate image with 90 / 180 / 270 degre + """ + im_w, im_h = im.shape[1], im.shape[0] + dst_im = im.copy() + dst_polys = [] + rand_degree_ratio = np.random.rand() + rand_degree_cnt = 1 + if 0.333 < rand_degree_ratio < 0.666: + rand_degree_cnt = 2 + elif rand_degree_ratio > 0.666: + rand_degree_cnt = 3 + for i in range(rand_degree_cnt): + dst_im = np.rot90(dst_im) + rot_degree = -90 * rand_degree_cnt + rot_angle = rot_degree * math.pi / 180.0 + n_poly = text_polys.shape[0] + cx, cy = 0.5 * im_w, 0.5 * im_h + ncx, ncy = 0.5 * dst_im.shape[1], 0.5 * dst_im.shape[0] + for i in range(n_poly): + wordBB = text_polys[i] + poly = [] + for j in range(4): + sx, sy = wordBB[j][0], wordBB[j][1] + dx = ( + math.cos(rot_angle) * (sx - cx) + - math.sin(rot_angle) * (sy - cy) + + ncx + ) + dy = ( + math.sin(rot_angle) * (sx - cx) + + math.cos(rot_angle) * (sy - cy) + + ncy + ) + poly.append([dx, dy]) + dst_polys.append(poly) + dst_polys = np.array(dst_polys, dtype=np.float32) + return dst_im, dst_polys + + def polygon_area(self, poly): + """ + compute area of a polygon + :param poly: + :return: + """ + edge = [ + (poly[1][0] - poly[0][0]) * (poly[1][1] + poly[0][1]), + (poly[2][0] - poly[1][0]) * (poly[2][1] + poly[1][1]), + (poly[3][0] - poly[2][0]) * (poly[3][1] + poly[2][1]), + (poly[0][0] - poly[3][0]) * (poly[0][1] + poly[3][1]), + ] + return np.sum(edge) / 2.0 + + def check_and_validate_polys(self, polys, tags, img_height, img_width): + """ + check so that the text poly is in the same direction, + and also filter some invalid polygons + :param polys: + :param tags: + :return: + """ + h, w = img_height, img_width + if polys.shape[0] == 0: + return polys + polys[:, :, 0] = np.clip(polys[:, :, 0], 0, w - 1) + polys[:, :, 1] = np.clip(polys[:, :, 1], 0, h - 1) + + validated_polys = [] + validated_tags = [] + for poly, tag in zip(polys, tags): + p_area = self.polygon_area(poly) + # invalid poly + if abs(p_area) < 1: + continue + if p_area > 0: + #'poly in wrong direction' + if not tag: + tag = True # reversed cases should be ignore + poly = poly[(0, 3, 2, 1), :] + validated_polys.append(poly) + validated_tags.append(tag) + return np.array(validated_polys), np.array(validated_tags) + + def draw_img_polys(self, img, polys): + if len(img.shape) == 4: + img = np.squeeze(img, axis=0) + if img.shape[0] == 3: + img = img.transpose((1, 2, 0)) + img[:, :, 2] += 123.68 + img[:, :, 1] += 116.78 + img[:, :, 0] += 103.94 + cv2.imwrite("tmp.jpg", img) + img = cv2.imread("tmp.jpg") + for box in polys: + box = box.astype(np.int32).reshape((-1, 1, 2)) + cv2.polylines(img, [box], True, color=(255, 255, 0), thickness=2) + import random + + ino = random.randint(0, 100) + cv2.imwrite("tmp_%d.jpg" % ino, img) + return + + def shrink_poly(self, poly, r): + """ + fit a poly inside the origin poly, maybe bugs here... + used for generate the score map + :param poly: the text poly + :param r: r in the paper + :return: the shrunk poly + """ + # shrink ratio + R = 0.3 + # find the longer pair + dist0 = np.linalg.norm(poly[0] - poly[1]) + dist1 = np.linalg.norm(poly[2] - poly[3]) + dist2 = np.linalg.norm(poly[0] - poly[3]) + dist3 = np.linalg.norm(poly[1] - poly[2]) + if dist0 + dist1 > dist2 + dist3: + # first move (p0, p1), (p2, p3), then (p0, p3), (p1, p2) + ## p0, p1 + theta = np.arctan2((poly[1][1] - poly[0][1]), (poly[1][0] - poly[0][0])) + poly[0][0] += R * r[0] * np.cos(theta) + poly[0][1] += R * r[0] * np.sin(theta) + poly[1][0] -= R * r[1] * np.cos(theta) + poly[1][1] -= R * r[1] * np.sin(theta) + ## p2, p3 + theta = np.arctan2((poly[2][1] - poly[3][1]), (poly[2][0] - poly[3][0])) + poly[3][0] += R * r[3] * np.cos(theta) + poly[3][1] += R * r[3] * np.sin(theta) + poly[2][0] -= R * r[2] * np.cos(theta) + poly[2][1] -= R * r[2] * np.sin(theta) + ## p0, p3 + theta = np.arctan2((poly[3][0] - poly[0][0]), (poly[3][1] - poly[0][1])) + poly[0][0] += R * r[0] * np.sin(theta) + poly[0][1] += R * r[0] * np.cos(theta) + poly[3][0] -= R * r[3] * np.sin(theta) + poly[3][1] -= R * r[3] * np.cos(theta) + ## p1, p2 + theta = np.arctan2((poly[2][0] - poly[1][0]), (poly[2][1] - poly[1][1])) + poly[1][0] += R * r[1] * np.sin(theta) + poly[1][1] += R * r[1] * np.cos(theta) + poly[2][0] -= R * r[2] * np.sin(theta) + poly[2][1] -= R * r[2] * np.cos(theta) + else: + ## p0, p3 + # print poly + theta = np.arctan2((poly[3][0] - poly[0][0]), (poly[3][1] - poly[0][1])) + poly[0][0] += R * r[0] * np.sin(theta) + poly[0][1] += R * r[0] * np.cos(theta) + poly[3][0] -= R * r[3] * np.sin(theta) + poly[3][1] -= R * r[3] * np.cos(theta) + ## p1, p2 + theta = np.arctan2((poly[2][0] - poly[1][0]), (poly[2][1] - poly[1][1])) + poly[1][0] += R * r[1] * np.sin(theta) + poly[1][1] += R * r[1] * np.cos(theta) + poly[2][0] -= R * r[2] * np.sin(theta) + poly[2][1] -= R * r[2] * np.cos(theta) + ## p0, p1 + theta = np.arctan2((poly[1][1] - poly[0][1]), (poly[1][0] - poly[0][0])) + poly[0][0] += R * r[0] * np.cos(theta) + poly[0][1] += R * r[0] * np.sin(theta) + poly[1][0] -= R * r[1] * np.cos(theta) + poly[1][1] -= R * r[1] * np.sin(theta) + ## p2, p3 + theta = np.arctan2((poly[2][1] - poly[3][1]), (poly[2][0] - poly[3][0])) + poly[3][0] += R * r[3] * np.cos(theta) + poly[3][1] += R * r[3] * np.sin(theta) + poly[2][0] -= R * r[2] * np.cos(theta) + poly[2][1] -= R * r[2] * np.sin(theta) + return poly + + def generate_quad(self, im_size, polys, tags): + """ + Generate quadrangle. + """ + h, w = im_size + poly_mask = np.zeros((h, w), dtype=np.uint8) + score_map = np.zeros((h, w), dtype=np.uint8) + # (x1, y1, ..., x4, y4, short_edge_norm) + geo_map = np.zeros((h, w, 9), dtype=np.float32) + # mask used during training, to ignore some hard areas + training_mask = np.ones((h, w), dtype=np.uint8) + for poly_idx, poly_tag in enumerate(zip(polys, tags)): + poly = poly_tag[0] + tag = poly_tag[1] + + r = [None, None, None, None] + for i in range(4): + dist1 = np.linalg.norm(poly[i] - poly[(i + 1) % 4]) + dist2 = np.linalg.norm(poly[i] - poly[(i - 1) % 4]) + r[i] = min(dist1, dist2) + # score map + shrinked_poly = self.shrink_poly(poly.copy(), r).astype(np.int32)[ + np.newaxis, :, : + ] + cv2.fillPoly(score_map, shrinked_poly, 1) + cv2.fillPoly(poly_mask, shrinked_poly, poly_idx + 1) + # if the poly is too small, then ignore it during training + poly_h = min( + np.linalg.norm(poly[0] - poly[3]), np.linalg.norm(poly[1] - poly[2]) + ) + poly_w = min( + np.linalg.norm(poly[0] - poly[1]), np.linalg.norm(poly[2] - poly[3]) + ) + if min(poly_h, poly_w) < self.min_text_size: + cv2.fillPoly(training_mask, poly.astype(np.int32)[np.newaxis, :, :], 0) + + if tag: + cv2.fillPoly(training_mask, poly.astype(np.int32)[np.newaxis, :, :], 0) + + xy_in_poly = np.argwhere(poly_mask == (poly_idx + 1)) + # geo map. + y_in_poly = xy_in_poly[:, 0] + x_in_poly = xy_in_poly[:, 1] + poly[:, 0] = np.minimum(np.maximum(poly[:, 0], 0), w) + poly[:, 1] = np.minimum(np.maximum(poly[:, 1], 0), h) + for pno in range(4): + geo_channel_beg = pno * 2 + geo_map[y_in_poly, x_in_poly, geo_channel_beg] = ( + x_in_poly - poly[pno, 0] + ) + geo_map[y_in_poly, x_in_poly, geo_channel_beg + 1] = ( + y_in_poly - poly[pno, 1] + ) + geo_map[y_in_poly, x_in_poly, 8] = 1.0 / max(min(poly_h, poly_w), 1.0) + return score_map, geo_map, training_mask + + def crop_area(self, im, polys, tags, crop_background=False, max_tries=50): + """ + make random crop from the input image + :param im: + :param polys: + :param tags: + :param crop_background: + :param max_tries: + :return: + """ + h, w, _ = im.shape + pad_h = h // 10 + pad_w = w // 10 + h_array = np.zeros((h + pad_h * 2), dtype=np.int32) + w_array = np.zeros((w + pad_w * 2), dtype=np.int32) + for poly in polys: + poly = np.round(poly, decimals=0).astype(np.int32) + minx = np.min(poly[:, 0]) + maxx = np.max(poly[:, 0]) + w_array[minx + pad_w : maxx + pad_w] = 1 + miny = np.min(poly[:, 1]) + maxy = np.max(poly[:, 1]) + h_array[miny + pad_h : maxy + pad_h] = 1 + # ensure the cropped area not across a text + h_axis = np.where(h_array == 0)[0] + w_axis = np.where(w_array == 0)[0] + if len(h_axis) == 0 or len(w_axis) == 0: + return im, polys, tags + + for i in range(max_tries): + xx = np.random.choice(w_axis, size=2) + xmin = np.min(xx) - pad_w + xmax = np.max(xx) - pad_w + xmin = np.clip(xmin, 0, w - 1) + xmax = np.clip(xmax, 0, w - 1) + yy = np.random.choice(h_axis, size=2) + ymin = np.min(yy) - pad_h + ymax = np.max(yy) - pad_h + ymin = np.clip(ymin, 0, h - 1) + ymax = np.clip(ymax, 0, h - 1) + if ( + xmax - xmin < self.min_crop_side_ratio * w + or ymax - ymin < self.min_crop_side_ratio * h + ): + # area too small + continue + if polys.shape[0] != 0: + poly_axis_in_area = ( + (polys[:, :, 0] >= xmin) + & (polys[:, :, 0] <= xmax) + & (polys[:, :, 1] >= ymin) + & (polys[:, :, 1] <= ymax) + ) + selected_polys = np.where(np.sum(poly_axis_in_area, axis=1) == 4)[0] + else: + selected_polys = [] + + if len(selected_polys) == 0: + # no text in this area + if crop_background: + im = im[ymin : ymax + 1, xmin : xmax + 1, :] + polys = [] + tags = [] + return im, polys, tags + else: + continue + + im = im[ymin : ymax + 1, xmin : xmax + 1, :] + polys = polys[selected_polys] + tags = tags[selected_polys] + polys[:, :, 0] -= xmin + polys[:, :, 1] -= ymin + return im, polys, tags + return im, polys, tags + + def crop_background_infor(self, im, text_polys, text_tags): + im, text_polys, text_tags = self.crop_area( + im, text_polys, text_tags, crop_background=True + ) + + if len(text_polys) > 0: + return None + # pad and resize image + input_size = self.input_size + im, ratio = self.preprocess(im) + score_map = np.zeros((input_size, input_size), dtype=np.float32) + geo_map = np.zeros((input_size, input_size, 9), dtype=np.float32) + training_mask = np.ones((input_size, input_size), dtype=np.float32) + return im, score_map, geo_map, training_mask + + def crop_foreground_infor(self, im, text_polys, text_tags): + im, text_polys, text_tags = self.crop_area( + im, text_polys, text_tags, crop_background=False + ) + + if text_polys.shape[0] == 0: + return None + # continue for all ignore case + if np.sum((text_tags * 1.0)) >= text_tags.size: + return None + # pad and resize image + input_size = self.input_size + im, ratio = self.preprocess(im) + text_polys[:, :, 0] *= ratio + text_polys[:, :, 1] *= ratio + _, _, new_h, new_w = im.shape + # print(im.shape) + # self.draw_img_polys(im, text_polys) + score_map, geo_map, training_mask = self.generate_quad( + (new_h, new_w), text_polys, text_tags + ) + return im, score_map, geo_map, training_mask + + def __call__(self, data): + im = data["image"] + text_polys = data["polys"] + text_tags = data["ignore_tags"] + if im is None: + return None + if text_polys.shape[0] == 0: + return None + + # add rotate cases + if np.random.rand() < 0.5: + im, text_polys = self.rotate_im_poly(im, text_polys) + h, w, _ = im.shape + text_polys, text_tags = self.check_and_validate_polys( + text_polys, text_tags, h, w + ) + if text_polys.shape[0] == 0: + return None + + # random scale this image + rd_scale = np.random.choice(self.random_scale) + im = cv2.resize(im, dsize=None, fx=rd_scale, fy=rd_scale) + text_polys *= rd_scale + if np.random.rand() < self.background_ratio: + outs = self.crop_background_infor(im, text_polys, text_tags) + else: + outs = self.crop_foreground_infor(im, text_polys, text_tags) + + if outs is None: + return None + im, score_map, geo_map, training_mask = outs + score_map = score_map[np.newaxis, ::4, ::4].astype(np.float32) + geo_map = np.swapaxes(geo_map, 1, 2) + geo_map = np.swapaxes(geo_map, 1, 0) + geo_map = geo_map[:, ::4, ::4].astype(np.float32) + training_mask = training_mask[np.newaxis, ::4, ::4] + training_mask = training_mask.astype(np.float32) + + data["image"] = im[0] + data["score_map"] = score_map + data["geo_map"] = geo_map + data["training_mask"] = training_mask + return data diff --git a/ppocr/data/imaug/fce_aug.py b/ppocr/data/imaug/fce_aug.py new file mode 100644 index 0000000..2697344 --- /dev/null +++ b/ppocr/data/imaug/fce_aug.py @@ -0,0 +1,575 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/datasets/pipelines/transforms.py +""" +import numpy as np +from PIL import Image, ImageDraw +import cv2 +from shapely.geometry import Polygon +import math +from ppocr.utils.poly_nms import poly_intersection + + +class RandomScaling: + def __init__(self, size=800, scale=(3.0 / 4, 5.0 / 2), **kwargs): + """Random scale the image while keeping aspect. + + Args: + size (int) : Base size before scaling. + scale (tuple(float)) : The range of scaling. + """ + assert isinstance(size, int) + assert isinstance(scale, float) or isinstance(scale, tuple) + self.size = size + self.scale = scale if isinstance(scale, tuple) else (1 - scale, 1 + scale) + + def __call__(self, data): + image = data["image"] + text_polys = data["polys"] + h, w, _ = image.shape + + aspect_ratio = np.random.uniform(min(self.scale), max(self.scale)) + scales = self.size * 1.0 / max(h, w) * aspect_ratio + scales = np.array([scales, scales]) + out_size = (int(h * scales[1]), int(w * scales[0])) + image = cv2.resize(image, out_size[::-1]) + + data["image"] = image + text_polys[:, :, 0::2] = text_polys[:, :, 0::2] * scales[1] + text_polys[:, :, 1::2] = text_polys[:, :, 1::2] * scales[0] + data["polys"] = text_polys + + return data + + +class RandomCropFlip: + def __init__( + self, pad_ratio=0.1, crop_ratio=0.5, iter_num=1, min_area_ratio=0.2, **kwargs + ): + """Random crop and flip a patch of the image. + + Args: + crop_ratio (float): The ratio of cropping. + iter_num (int): Number of operations. + min_area_ratio (float): Minimal area ratio between cropped patch + and original image. + """ + assert isinstance(crop_ratio, float) + assert isinstance(iter_num, int) + assert isinstance(min_area_ratio, float) + + self.pad_ratio = pad_ratio + self.epsilon = 1e-2 + self.crop_ratio = crop_ratio + self.iter_num = iter_num + self.min_area_ratio = min_area_ratio + + def __call__(self, results): + for i in range(self.iter_num): + results = self.random_crop_flip(results) + + return results + + def random_crop_flip(self, results): + image = results["image"] + polygons = results["polys"] + ignore_tags = results["ignore_tags"] + if len(polygons) == 0: + return results + + if np.random.random() >= self.crop_ratio: + return results + + h, w, _ = image.shape + area = h * w + pad_h = int(h * self.pad_ratio) + pad_w = int(w * self.pad_ratio) + h_axis, w_axis = self.generate_crop_target(image, polygons, pad_h, pad_w) + if len(h_axis) == 0 or len(w_axis) == 0: + return results + + attempt = 0 + while attempt < 50: + attempt += 1 + polys_keep = [] + polys_new = [] + ignore_tags_keep = [] + ignore_tags_new = [] + xx = np.random.choice(w_axis, size=2) + xmin = np.min(xx) - pad_w + xmax = np.max(xx) - pad_w + xmin = np.clip(xmin, 0, w - 1) + xmax = np.clip(xmax, 0, w - 1) + yy = np.random.choice(h_axis, size=2) + ymin = np.min(yy) - pad_h + ymax = np.max(yy) - pad_h + ymin = np.clip(ymin, 0, h - 1) + ymax = np.clip(ymax, 0, h - 1) + if (xmax - xmin) * (ymax - ymin) < area * self.min_area_ratio: + # area too small + continue + + pts = np.stack( + [[xmin, xmax, xmax, xmin], [ymin, ymin, ymax, ymax]] + ).T.astype(np.int32) + pp = Polygon(pts) + fail_flag = False + for polygon, ignore_tag in zip(polygons, ignore_tags): + ppi = Polygon(polygon.reshape(-1, 2)) + ppiou, _ = poly_intersection(ppi, pp, buffer=0) + if ( + np.abs(ppiou - float(ppi.area)) > self.epsilon + and np.abs(ppiou) > self.epsilon + ): + fail_flag = True + break + elif np.abs(ppiou - float(ppi.area)) < self.epsilon: + polys_new.append(polygon) + ignore_tags_new.append(ignore_tag) + else: + polys_keep.append(polygon) + ignore_tags_keep.append(ignore_tag) + + if fail_flag: + continue + else: + break + + cropped = image[ymin:ymax, xmin:xmax, :] + select_type = np.random.randint(3) + if select_type == 0: + img = np.ascontiguousarray(cropped[:, ::-1]) + elif select_type == 1: + img = np.ascontiguousarray(cropped[::-1, :]) + else: + img = np.ascontiguousarray(cropped[::-1, ::-1]) + image[ymin:ymax, xmin:xmax, :] = img + results["img"] = image + + if len(polys_new) != 0: + height, width, _ = cropped.shape + if select_type == 0: + for idx, polygon in enumerate(polys_new): + poly = polygon.reshape(-1, 2) + poly[:, 0] = width - poly[:, 0] + 2 * xmin + polys_new[idx] = poly + elif select_type == 1: + for idx, polygon in enumerate(polys_new): + poly = polygon.reshape(-1, 2) + poly[:, 1] = height - poly[:, 1] + 2 * ymin + polys_new[idx] = poly + else: + for idx, polygon in enumerate(polys_new): + poly = polygon.reshape(-1, 2) + poly[:, 0] = width - poly[:, 0] + 2 * xmin + poly[:, 1] = height - poly[:, 1] + 2 * ymin + polys_new[idx] = poly + polygons = polys_keep + polys_new + ignore_tags = ignore_tags_keep + ignore_tags_new + results["polys"] = np.array(polygons) + results["ignore_tags"] = ignore_tags + + return results + + def generate_crop_target(self, image, all_polys, pad_h, pad_w): + """Generate crop target and make sure not to crop the polygon + instances. + + Args: + image (ndarray): The image waited to be crop. + all_polys (list[list[ndarray]]): All polygons including ground + truth polygons and ground truth ignored polygons. + pad_h (int): Padding length of height. + pad_w (int): Padding length of width. + Returns: + h_axis (ndarray): Vertical cropping range. + w_axis (ndarray): Horizontal cropping range. + """ + h, w, _ = image.shape + h_array = np.zeros((h + pad_h * 2), dtype=np.int32) + w_array = np.zeros((w + pad_w * 2), dtype=np.int32) + + text_polys = [] + for polygon in all_polys: + rect = cv2.minAreaRect(polygon.astype(np.int32).reshape(-1, 2)) + box = cv2.boxPoints(rect) + box = np.int64(box) + text_polys.append([box[0], box[1], box[2], box[3]]) + + polys = np.array(text_polys, dtype=np.int32) + for poly in polys: + poly = np.round(poly, decimals=0).astype(np.int32) + minx = np.min(poly[:, 0]) + maxx = np.max(poly[:, 0]) + w_array[minx + pad_w : maxx + pad_w] = 1 + miny = np.min(poly[:, 1]) + maxy = np.max(poly[:, 1]) + h_array[miny + pad_h : maxy + pad_h] = 1 + + h_axis = np.where(h_array == 0)[0] + w_axis = np.where(w_array == 0)[0] + return h_axis, w_axis + + +class RandomCropPolyInstances: + """Randomly crop images and make sure to contain at least one intact + instance.""" + + def __init__(self, crop_ratio=5.0 / 8.0, min_side_ratio=0.4, **kwargs): + super().__init__() + self.crop_ratio = crop_ratio + self.min_side_ratio = min_side_ratio + + def sample_valid_start_end(self, valid_array, min_len, max_start, min_end): + assert isinstance(min_len, int) + assert len(valid_array) > min_len + + start_array = valid_array.copy() + max_start = min(len(start_array) - min_len, max_start) + start_array[max_start:] = 0 + start_array[0] = 1 + diff_array = np.hstack([0, start_array]) - np.hstack([start_array, 0]) + region_starts = np.where(diff_array < 0)[0] + region_ends = np.where(diff_array > 0)[0] + region_ind = np.random.randint(0, len(region_starts)) + start = np.random.randint(region_starts[region_ind], region_ends[region_ind]) + + end_array = valid_array.copy() + min_end = max(start + min_len, min_end) + end_array[:min_end] = 0 + end_array[-1] = 1 + diff_array = np.hstack([0, end_array]) - np.hstack([end_array, 0]) + region_starts = np.where(diff_array < 0)[0] + region_ends = np.where(diff_array > 0)[0] + region_ind = np.random.randint(0, len(region_starts)) + end = np.random.randint(region_starts[region_ind], region_ends[region_ind]) + return start, end + + def sample_crop_box(self, img_size, results): + """Generate crop box and make sure not to crop the polygon instances. + + Args: + img_size (tuple(int)): The image size (h, w). + results (dict): The results dict. + """ + + assert isinstance(img_size, tuple) + h, w = img_size[:2] + + key_masks = results["polys"] + + x_valid_array = np.ones(w, dtype=np.int32) + y_valid_array = np.ones(h, dtype=np.int32) + + selected_mask = key_masks[np.random.randint(0, len(key_masks))] + selected_mask = selected_mask.reshape((-1, 2)).astype(np.int32) + max_x_start = max(np.min(selected_mask[:, 0]) - 2, 0) + min_x_end = min(np.max(selected_mask[:, 0]) + 3, w - 1) + max_y_start = max(np.min(selected_mask[:, 1]) - 2, 0) + min_y_end = min(np.max(selected_mask[:, 1]) + 3, h - 1) + + for mask in key_masks: + mask = mask.reshape((-1, 2)).astype(np.int32) + clip_x = np.clip(mask[:, 0], 0, w - 1) + clip_y = np.clip(mask[:, 1], 0, h - 1) + min_x, max_x = np.min(clip_x), np.max(clip_x) + min_y, max_y = np.min(clip_y), np.max(clip_y) + + x_valid_array[min_x - 2 : max_x + 3] = 0 + y_valid_array[min_y - 2 : max_y + 3] = 0 + + min_w = int(w * self.min_side_ratio) + min_h = int(h * self.min_side_ratio) + + x1, x2 = self.sample_valid_start_end( + x_valid_array, min_w, max_x_start, min_x_end + ) + y1, y2 = self.sample_valid_start_end( + y_valid_array, min_h, max_y_start, min_y_end + ) + + return np.array([x1, y1, x2, y2]) + + def crop_img(self, img, bbox): + assert img.ndim == 3 + h, w, _ = img.shape + assert 0 <= bbox[1] < bbox[3] <= h + assert 0 <= bbox[0] < bbox[2] <= w + return img[bbox[1] : bbox[3], bbox[0] : bbox[2]] + + def __call__(self, results): + image = results["image"] + polygons = results["polys"] + ignore_tags = results["ignore_tags"] + if len(polygons) < 1: + return results + + if np.random.random_sample() < self.crop_ratio: + crop_box = self.sample_crop_box(image.shape, results) + img = self.crop_img(image, crop_box) + results["image"] = img + # crop and filter masks + x1, y1, x2, y2 = crop_box + w = max(x2 - x1, 1) + h = max(y2 - y1, 1) + polygons[:, :, 0::2] = polygons[:, :, 0::2] - x1 + polygons[:, :, 1::2] = polygons[:, :, 1::2] - y1 + + valid_masks_list = [] + valid_tags_list = [] + for ind, polygon in enumerate(polygons): + if ( + (polygon[:, ::2] > -4).all() + and (polygon[:, ::2] < w + 4).all() + and (polygon[:, 1::2] > -4).all() + and (polygon[:, 1::2] < h + 4).all() + ): + polygon[:, ::2] = np.clip(polygon[:, ::2], 0, w) + polygon[:, 1::2] = np.clip(polygon[:, 1::2], 0, h) + valid_masks_list.append(polygon) + valid_tags_list.append(ignore_tags[ind]) + + results["polys"] = np.array(valid_masks_list) + results["ignore_tags"] = valid_tags_list + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + return repr_str + + +class RandomRotatePolyInstances: + def __init__( + self, + rotate_ratio=0.5, + max_angle=10, + pad_with_fixed_color=False, + pad_value=(0, 0, 0), + **kwargs, + ): + """Randomly rotate images and polygon masks. + + Args: + rotate_ratio (float): The ratio of samples to operate rotation. + max_angle (int): The maximum rotation angle. + pad_with_fixed_color (bool): The flag for whether to pad rotated + image with fixed value. If set to False, the rotated image will + be padded onto cropped image. + pad_value (tuple(int)): The color value for padding rotated image. + """ + self.rotate_ratio = rotate_ratio + self.max_angle = max_angle + self.pad_with_fixed_color = pad_with_fixed_color + self.pad_value = pad_value + + def rotate(self, center, points, theta, center_shift=(0, 0)): + # rotate points. + (center_x, center_y) = center + center_y = -center_y + x, y = points[:, ::2], points[:, 1::2] + y = -y + + theta = theta / 180 * math.pi + cos = math.cos(theta) + sin = math.sin(theta) + + x = x - center_x + y = y - center_y + + _x = center_x + x * cos - y * sin + center_shift[0] + _y = -(center_y + x * sin + y * cos) + center_shift[1] + + points[:, ::2], points[:, 1::2] = _x, _y + return points + + def cal_canvas_size(self, ori_size, degree): + assert isinstance(ori_size, tuple) + angle = degree * math.pi / 180.0 + h, w = ori_size[:2] + + cos = math.cos(angle) + sin = math.sin(angle) + canvas_h = int(w * math.fabs(sin) + h * math.fabs(cos)) + canvas_w = int(w * math.fabs(cos) + h * math.fabs(sin)) + + canvas_size = (canvas_h, canvas_w) + return canvas_size + + def sample_angle(self, max_angle): + angle = np.random.random_sample() * 2 * max_angle - max_angle + return angle + + def rotate_img(self, img, angle, canvas_size): + h, w = img.shape[:2] + rotation_matrix = cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1) + rotation_matrix[0, 2] += int((canvas_size[1] - w) / 2) + rotation_matrix[1, 2] += int((canvas_size[0] - h) / 2) + + if self.pad_with_fixed_color: + target_img = cv2.warpAffine( + img, + rotation_matrix, + (canvas_size[1], canvas_size[0]), + flags=cv2.INTER_NEAREST, + borderValue=self.pad_value, + ) + else: + mask = np.zeros_like(img) + (h_ind, w_ind) = ( + np.random.randint(0, h * 7 // 8), + np.random.randint(0, w * 7 // 8), + ) + img_cut = img[h_ind : (h_ind + h // 9), w_ind : (w_ind + w // 9)] + img_cut = cv2.resize(img_cut, (canvas_size[1], canvas_size[0])) + + mask = cv2.warpAffine( + mask, + rotation_matrix, + (canvas_size[1], canvas_size[0]), + borderValue=[1, 1, 1], + ) + target_img = cv2.warpAffine( + img, + rotation_matrix, + (canvas_size[1], canvas_size[0]), + borderValue=[0, 0, 0], + ) + target_img = target_img + img_cut * mask + + return target_img + + def __call__(self, results): + if np.random.random_sample() < self.rotate_ratio: + image = results["image"] + polygons = results["polys"] + h, w = image.shape[:2] + + angle = self.sample_angle(self.max_angle) + canvas_size = self.cal_canvas_size((h, w), angle) + center_shift = ( + int((canvas_size[1] - w) / 2), + int((canvas_size[0] - h) / 2), + ) + image = self.rotate_img(image, angle, canvas_size) + results["image"] = image + # rotate polygons + rotated_masks = [] + for mask in polygons: + rotated_mask = self.rotate((w / 2, h / 2), mask, angle, center_shift) + rotated_masks.append(rotated_mask) + results["polys"] = np.array(rotated_masks) + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + return repr_str + + +class SquareResizePad: + def __init__( + self, + target_size, + pad_ratio=0.6, + pad_with_fixed_color=False, + pad_value=(0, 0, 0), + **kwargs, + ): + """Resize or pad images to be square shape. + + Args: + target_size (int): The target size of square shaped image. + pad_with_fixed_color (bool): The flag for whether to pad rotated + image with fixed value. If set to False, the rescales image will + be padded onto cropped image. + pad_value (tuple(int)): The color value for padding rotated image. + """ + assert isinstance(target_size, int) + assert isinstance(pad_ratio, float) + assert isinstance(pad_with_fixed_color, bool) + assert isinstance(pad_value, tuple) + + self.target_size = target_size + self.pad_ratio = pad_ratio + self.pad_with_fixed_color = pad_with_fixed_color + self.pad_value = pad_value + + def resize_img(self, img, keep_ratio=True): + h, w, _ = img.shape + if keep_ratio: + t_h = self.target_size if h >= w else int(h * self.target_size / w) + t_w = self.target_size if h <= w else int(w * self.target_size / h) + else: + t_h = t_w = self.target_size + img = cv2.resize(img, (t_w, t_h)) + return img, (t_h, t_w) + + def square_pad(self, img): + h, w = img.shape[:2] + if h == w: + return img, (0, 0) + pad_size = max(h, w) + if self.pad_with_fixed_color: + expand_img = np.ones((pad_size, pad_size, 3), dtype=np.uint8) + expand_img[:] = self.pad_value + else: + (h_ind, w_ind) = ( + np.random.randint(0, h * 7 // 8), + np.random.randint(0, w * 7 // 8), + ) + img_cut = img[h_ind : (h_ind + h // 9), w_ind : (w_ind + w // 9)] + expand_img = cv2.resize(img_cut, (pad_size, pad_size)) + if h > w: + y0, x0 = 0, (h - w) // 2 + else: + y0, x0 = (w - h) // 2, 0 + expand_img[y0 : y0 + h, x0 : x0 + w] = img + offset = (x0, y0) + + return expand_img, offset + + def square_pad_mask(self, points, offset): + x0, y0 = offset + pad_points = points.copy() + pad_points[::2] = pad_points[::2] + x0 + pad_points[1::2] = pad_points[1::2] + y0 + return pad_points + + def __call__(self, results): + image = results["image"] + polygons = results["polys"] + h, w = image.shape[:2] + + if np.random.random_sample() < self.pad_ratio: + image, out_size = self.resize_img(image, keep_ratio=True) + image, offset = self.square_pad(image) + else: + image, out_size = self.resize_img(image, keep_ratio=False) + offset = (0, 0) + results["image"] = image + try: + polygons[:, :, 0::2] = polygons[:, :, 0::2] * out_size[1] / w + offset[0] + polygons[:, :, 1::2] = polygons[:, :, 1::2] * out_size[0] / h + offset[1] + except: + pass + results["polys"] = polygons + + return results + + def __repr__(self): + repr_str = self.__class__.__name__ + return repr_str diff --git a/ppocr/data/imaug/fce_targets.py b/ppocr/data/imaug/fce_targets.py new file mode 100644 index 0000000..9955980 --- /dev/null +++ b/ppocr/data/imaug/fce_targets.py @@ -0,0 +1,697 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/datasets/pipelines/textdet_targets/fcenet_targets.py +""" + +import cv2 +import numpy as np +from numpy.fft import fft +from numpy.linalg import norm +import sys + + +def vector_slope(vec): + assert len(vec) == 2 + return abs(vec[1] / (vec[0] + 1e-8)) + + +class FCENetTargets: + """Generate the ground truth targets of FCENet: Fourier Contour Embedding + for Arbitrary-Shaped Text Detection. + + [https://arxiv.org/abs/2104.10442] + + Args: + fourier_degree (int): The maximum Fourier transform degree k. + resample_step (float): The step size for resampling the text center + line (TCL). It's better not to exceed half of the minimum width. + center_region_shrink_ratio (float): The shrink ratio of text center + region. + level_size_divisors (tuple(int)): The downsample ratio on each level. + level_proportion_range (tuple(tuple(int))): The range of text sizes + assigned to each level. + """ + + def __init__( + self, + fourier_degree=5, + resample_step=4.0, + center_region_shrink_ratio=0.3, + level_size_divisors=(8, 16, 32), + level_proportion_range=((0, 0.25), (0.2, 0.65), (0.55, 1.0)), + orientation_thr=2.0, + **kwargs, + ): + super().__init__() + assert isinstance(level_size_divisors, tuple) + assert isinstance(level_proportion_range, tuple) + assert len(level_size_divisors) == len(level_proportion_range) + self.fourier_degree = fourier_degree + self.resample_step = resample_step + self.center_region_shrink_ratio = center_region_shrink_ratio + self.level_size_divisors = level_size_divisors + self.level_proportion_range = level_proportion_range + + self.orientation_thr = orientation_thr + + def vector_angle(self, vec1, vec2): + if vec1.ndim > 1: + unit_vec1 = vec1 / (norm(vec1, axis=-1) + 1e-8).reshape((-1, 1)) + else: + unit_vec1 = vec1 / (norm(vec1, axis=-1) + 1e-8) + if vec2.ndim > 1: + unit_vec2 = vec2 / (norm(vec2, axis=-1) + 1e-8).reshape((-1, 1)) + else: + unit_vec2 = vec2 / (norm(vec2, axis=-1) + 1e-8) + return np.arccos(np.clip(np.sum(unit_vec1 * unit_vec2, axis=-1), -1.0, 1.0)) + + def resample_line(self, line, n): + """Resample n points on a line. + + Args: + line (ndarray): The points composing a line. + n (int): The resampled points number. + + Returns: + resampled_line (ndarray): The points composing the resampled line. + """ + + assert line.ndim == 2 + assert line.shape[0] >= 2 + assert line.shape[1] == 2 + assert isinstance(n, int) + assert n > 0 + + length_list = [norm(line[i + 1] - line[i]) for i in range(len(line) - 1)] + total_length = sum(length_list) + length_cumsum = np.cumsum([0.0] + length_list) + delta_length = total_length / (float(n) + 1e-8) + + current_edge_ind = 0 + resampled_line = [line[0]] + + for i in range(1, n): + current_line_len = i * delta_length + + while ( + current_edge_ind + 1 < len(length_cumsum) + and current_line_len >= length_cumsum[current_edge_ind + 1] + ): + current_edge_ind += 1 + + current_edge_end_shift = current_line_len - length_cumsum[current_edge_ind] + + if current_edge_ind >= len(length_list): + break + end_shift_ratio = current_edge_end_shift / length_list[current_edge_ind] + current_point = ( + line[current_edge_ind] + + (line[current_edge_ind + 1] - line[current_edge_ind]) + * end_shift_ratio + ) + resampled_line.append(current_point) + resampled_line.append(line[-1]) + resampled_line = np.array(resampled_line) + + return resampled_line + + def reorder_poly_edge(self, points): + """Get the respective points composing head edge, tail edge, top + sideline and bottom sideline. + + Args: + points (ndarray): The points composing a text polygon. + + Returns: + head_edge (ndarray): The two points composing the head edge of text + polygon. + tail_edge (ndarray): The two points composing the tail edge of text + polygon. + top_sideline (ndarray): The points composing top curved sideline of + text polygon. + bot_sideline (ndarray): The points composing bottom curved sideline + of text polygon. + """ + + assert points.ndim == 2 + assert points.shape[0] >= 4 + assert points.shape[1] == 2 + + head_inds, tail_inds = self.find_head_tail(points, self.orientation_thr) + head_edge, tail_edge = points[head_inds], points[tail_inds] + + pad_points = np.vstack([points, points]) + if tail_inds[1] < 1: + tail_inds[1] = len(points) + sideline1 = pad_points[head_inds[1] : tail_inds[1]] + sideline2 = pad_points[tail_inds[1] : (head_inds[1] + len(points))] + sideline_mean_shift = np.mean(sideline1, axis=0) - np.mean(sideline2, axis=0) + + if sideline_mean_shift[1] > 0: + top_sideline, bot_sideline = sideline2, sideline1 + else: + top_sideline, bot_sideline = sideline1, sideline2 + + return head_edge, tail_edge, top_sideline, bot_sideline + + def find_head_tail(self, points, orientation_thr): + """Find the head edge and tail edge of a text polygon. + + Args: + points (ndarray): The points composing a text polygon. + orientation_thr (float): The threshold for distinguishing between + head edge and tail edge among the horizontal and vertical edges + of a quadrangle. + + Returns: + head_inds (list): The indexes of two points composing head edge. + tail_inds (list): The indexes of two points composing tail edge. + """ + + assert points.ndim == 2 + assert points.shape[0] >= 4 + assert points.shape[1] == 2 + assert isinstance(orientation_thr, float) + + if len(points) > 4: + pad_points = np.vstack([points, points[0]]) + edge_vec = pad_points[1:] - pad_points[:-1] + + theta_sum = [] + adjacent_vec_theta = [] + for i, edge_vec1 in enumerate(edge_vec): + adjacent_ind = [x % len(edge_vec) for x in [i - 1, i + 1]] + adjacent_edge_vec = edge_vec[adjacent_ind] + temp_theta_sum = np.sum(self.vector_angle(edge_vec1, adjacent_edge_vec)) + temp_adjacent_theta = self.vector_angle( + adjacent_edge_vec[0], adjacent_edge_vec[1] + ) + theta_sum.append(temp_theta_sum) + adjacent_vec_theta.append(temp_adjacent_theta) + theta_sum_score = np.array(theta_sum) / np.pi + adjacent_theta_score = np.array(adjacent_vec_theta) / np.pi + poly_center = np.mean(points, axis=0) + edge_dist = np.maximum( + norm(pad_points[1:] - poly_center, axis=-1), + norm(pad_points[:-1] - poly_center, axis=-1), + ) + dist_score = edge_dist / np.max(edge_dist) + position_score = np.zeros(len(edge_vec)) + score = 0.5 * theta_sum_score + 0.15 * adjacent_theta_score + score += 0.35 * dist_score + if len(points) % 2 == 0: + position_score[(len(score) // 2 - 1)] += 1 + position_score[-1] += 1 + score += 0.1 * position_score + pad_score = np.concatenate([score, score]) + score_matrix = np.zeros((len(score), len(score) - 3)) + x = np.arange(len(score) - 3) / float(len(score) - 4) + gaussian = ( + 1.0 + / (np.sqrt(2.0 * np.pi) * 0.5) + * np.exp(-np.power((x - 0.5) / 0.5, 2.0) / 2) + ) + gaussian = gaussian / np.max(gaussian) + for i in range(len(score)): + score_matrix[i, :] = ( + score[i] + + pad_score[(i + 2) : (i + len(score) - 1)] * gaussian * 0.3 + ) + + head_start, tail_increment = np.unravel_index( + score_matrix.argmax(), score_matrix.shape + ) + tail_start = (head_start + tail_increment + 2) % len(points) + head_end = (head_start + 1) % len(points) + tail_end = (tail_start + 1) % len(points) + + if head_end > tail_end: + head_start, tail_start = tail_start, head_start + head_end, tail_end = tail_end, head_end + head_inds = [head_start, head_end] + tail_inds = [tail_start, tail_end] + else: + if vector_slope(points[1] - points[0]) + vector_slope( + points[3] - points[2] + ) < vector_slope(points[2] - points[1]) + vector_slope( + points[0] - points[3] + ): + horizontal_edge_inds = [[0, 1], [2, 3]] + vertical_edge_inds = [[3, 0], [1, 2]] + else: + horizontal_edge_inds = [[3, 0], [1, 2]] + vertical_edge_inds = [[0, 1], [2, 3]] + + vertical_len_sum = norm( + points[vertical_edge_inds[0][0]] - points[vertical_edge_inds[0][1]] + ) + norm( + points[vertical_edge_inds[1][0]] - points[vertical_edge_inds[1][1]] + ) + horizontal_len_sum = norm( + points[horizontal_edge_inds[0][0]] - points[horizontal_edge_inds[0][1]] + ) + norm( + points[horizontal_edge_inds[1][0]] - points[horizontal_edge_inds[1][1]] + ) + + if vertical_len_sum > horizontal_len_sum * orientation_thr: + head_inds = horizontal_edge_inds[0] + tail_inds = horizontal_edge_inds[1] + else: + head_inds = vertical_edge_inds[0] + tail_inds = vertical_edge_inds[1] + + return head_inds, tail_inds + + def resample_sidelines(self, sideline1, sideline2, resample_step): + """Resample two sidelines to be of the same points number according to + step size. + + Args: + sideline1 (ndarray): The points composing a sideline of a text + polygon. + sideline2 (ndarray): The points composing another sideline of a + text polygon. + resample_step (float): The resampled step size. + + Returns: + resampled_line1 (ndarray): The resampled line 1. + resampled_line2 (ndarray): The resampled line 2. + """ + + assert sideline1.ndim == sideline2.ndim == 2 + assert sideline1.shape[1] == sideline2.shape[1] == 2 + assert sideline1.shape[0] >= 2 + assert sideline2.shape[0] >= 2 + assert isinstance(resample_step, float) + + length1 = sum( + [norm(sideline1[i + 1] - sideline1[i]) for i in range(len(sideline1) - 1)] + ) + length2 = sum( + [norm(sideline2[i + 1] - sideline2[i]) for i in range(len(sideline2) - 1)] + ) + + total_length = (length1 + length2) / 2 + resample_point_num = max(int(float(total_length) / resample_step), 1) + + resampled_line1 = self.resample_line(sideline1, resample_point_num) + resampled_line2 = self.resample_line(sideline2, resample_point_num) + + return resampled_line1, resampled_line2 + + def generate_center_region_mask(self, img_size, text_polys): + """Generate text center region mask. + + Args: + img_size (tuple): The image size of (height, width). + text_polys (list[list[ndarray]]): The list of text polygons. + + Returns: + center_region_mask (ndarray): The text center region mask. + """ + + assert isinstance(img_size, tuple) + # assert check_argument.is_2dlist(text_polys) + + h, w = img_size + + center_region_mask = np.zeros((h, w), np.uint8) + + center_region_boxes = [] + for poly in text_polys: + # assert len(poly) == 1 + polygon_points = poly.reshape(-1, 2) + _, _, top_line, bot_line = self.reorder_poly_edge(polygon_points) + resampled_top_line, resampled_bot_line = self.resample_sidelines( + top_line, bot_line, self.resample_step + ) + resampled_bot_line = resampled_bot_line[::-1] + if len(resampled_top_line) != len(resampled_bot_line): + continue + center_line = (resampled_top_line + resampled_bot_line) / 2 + + line_head_shrink_len = ( + norm(resampled_top_line[0] - resampled_bot_line[0]) / 4.0 + ) + line_tail_shrink_len = ( + norm(resampled_top_line[-1] - resampled_bot_line[-1]) / 4.0 + ) + head_shrink_num = int(line_head_shrink_len // self.resample_step) + tail_shrink_num = int(line_tail_shrink_len // self.resample_step) + if len(center_line) > head_shrink_num + tail_shrink_num + 2: + center_line = center_line[ + head_shrink_num : len(center_line) - tail_shrink_num + ] + resampled_top_line = resampled_top_line[ + head_shrink_num : len(resampled_top_line) - tail_shrink_num + ] + resampled_bot_line = resampled_bot_line[ + head_shrink_num : len(resampled_bot_line) - tail_shrink_num + ] + + for i in range(0, len(center_line) - 1): + tl = ( + center_line[i] + + (resampled_top_line[i] - center_line[i]) + * self.center_region_shrink_ratio + ) + tr = ( + center_line[i + 1] + + (resampled_top_line[i + 1] - center_line[i + 1]) + * self.center_region_shrink_ratio + ) + br = ( + center_line[i + 1] + + (resampled_bot_line[i + 1] - center_line[i + 1]) + * self.center_region_shrink_ratio + ) + bl = ( + center_line[i] + + (resampled_bot_line[i] - center_line[i]) + * self.center_region_shrink_ratio + ) + current_center_box = np.vstack([tl, tr, br, bl]).astype(np.int32) + center_region_boxes.append(current_center_box) + + cv2.fillPoly(center_region_mask, center_region_boxes, 1) + return center_region_mask + + def resample_polygon(self, polygon, n=400): + """Resample one polygon with n points on its boundary. + + Args: + polygon (list[float]): The input polygon. + n (int): The number of resampled points. + Returns: + resampled_polygon (list[float]): The resampled polygon. + """ + length = [] + + for i in range(len(polygon)): + p1 = polygon[i] + if i == len(polygon) - 1: + p2 = polygon[0] + else: + p2 = polygon[i + 1] + length.append(((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5) + + total_length = sum(length) + n_on_each_line = (np.array(length) / (total_length + 1e-8)) * n + n_on_each_line = n_on_each_line.astype(np.int32) + new_polygon = [] + + for i in range(len(polygon)): + num = n_on_each_line[i] + p1 = polygon[i] + if i == len(polygon) - 1: + p2 = polygon[0] + else: + p2 = polygon[i + 1] + + if num == 0: + continue + + dxdy = (p2 - p1) / num + for j in range(num): + point = p1 + dxdy * j + new_polygon.append(point) + + return np.array(new_polygon) + + def normalize_polygon(self, polygon): + """Normalize one polygon so that its start point is at right most. + + Args: + polygon (list[float]): The origin polygon. + Returns: + new_polygon (lost[float]): The polygon with start point at right. + """ + temp_polygon = polygon - polygon.mean(axis=0) + x = np.abs(temp_polygon[:, 0]) + y = temp_polygon[:, 1] + index_x = np.argsort(x) + index_y = np.argmin(y[index_x[:8]]) + index = index_x[index_y] + new_polygon = np.concatenate([polygon[index:], polygon[:index]]) + return new_polygon + + def poly2fourier(self, polygon, fourier_degree): + """Perform Fourier transformation to generate Fourier coefficients ck + from polygon. + + Args: + polygon (ndarray): An input polygon. + fourier_degree (int): The maximum Fourier degree K. + Returns: + c (ndarray(complex)): Fourier coefficients. + """ + points = polygon[:, 0] + polygon[:, 1] * 1j + c_fft = fft(points) / len(points) + c = np.hstack((c_fft[-fourier_degree:], c_fft[: fourier_degree + 1])) + return c + + def clockwise(self, c, fourier_degree): + """Make sure the polygon reconstructed from Fourier coefficients c in + the clockwise direction. + + Args: + polygon (list[float]): The origin polygon. + Returns: + new_polygon (lost[float]): The polygon in clockwise point order. + """ + if np.abs(c[fourier_degree + 1]) > np.abs(c[fourier_degree - 1]): + return c + elif np.abs(c[fourier_degree + 1]) < np.abs(c[fourier_degree - 1]): + return c[::-1] + else: + if np.abs(c[fourier_degree + 2]) > np.abs(c[fourier_degree - 2]): + return c + else: + return c[::-1] + + def cal_fourier_signature(self, polygon, fourier_degree): + """Calculate Fourier signature from input polygon. + + Args: + polygon (ndarray): The input polygon. + fourier_degree (int): The maximum Fourier degree K. + Returns: + fourier_signature (ndarray): An array shaped (2k+1, 2) containing + real part and image part of 2k+1 Fourier coefficients. + """ + resampled_polygon = self.resample_polygon(polygon) + resampled_polygon = self.normalize_polygon(resampled_polygon) + + fourier_coeff = self.poly2fourier(resampled_polygon, fourier_degree) + fourier_coeff = self.clockwise(fourier_coeff, fourier_degree) + + real_part = np.real(fourier_coeff).reshape((-1, 1)) + image_part = np.imag(fourier_coeff).reshape((-1, 1)) + fourier_signature = np.hstack([real_part, image_part]) + + return fourier_signature + + def generate_fourier_maps(self, img_size, text_polys): + """Generate Fourier coefficient maps. + + Args: + img_size (tuple): The image size of (height, width). + text_polys (list[list[ndarray]]): The list of text polygons. + + Returns: + fourier_real_map (ndarray): The Fourier coefficient real part maps. + fourier_image_map (ndarray): The Fourier coefficient image part + maps. + """ + + assert isinstance(img_size, tuple) + + h, w = img_size + k = self.fourier_degree + real_map = np.zeros((k * 2 + 1, h, w), dtype=np.float32) + imag_map = np.zeros((k * 2 + 1, h, w), dtype=np.float32) + + for poly in text_polys: + mask = np.zeros((h, w), dtype=np.uint8) + polygon = np.array(poly).reshape((1, -1, 2)) + cv2.fillPoly(mask, polygon.astype(np.int32), 1) + fourier_coeff = self.cal_fourier_signature(polygon[0], k) + for i in range(-k, k + 1): + if i != 0: + real_map[i + k, :, :] = ( + mask * fourier_coeff[i + k, 0] + + (1 - mask) * real_map[i + k, :, :] + ) + imag_map[i + k, :, :] = ( + mask * fourier_coeff[i + k, 1] + + (1 - mask) * imag_map[i + k, :, :] + ) + else: + yx = np.argwhere(mask > 0.5) + k_ind = np.ones((len(yx)), dtype=np.int64) * k + y, x = yx[:, 0], yx[:, 1] + real_map[k_ind, y, x] = fourier_coeff[k, 0] - x + imag_map[k_ind, y, x] = fourier_coeff[k, 1] - y + + return real_map, imag_map + + def generate_text_region_mask(self, img_size, text_polys): + """Generate text center region mask and geometry attribute maps. + + Args: + img_size (tuple): The image size (height, width). + text_polys (list[list[ndarray]]): The list of text polygons. + + Returns: + text_region_mask (ndarray): The text region mask. + """ + + assert isinstance(img_size, tuple) + + h, w = img_size + text_region_mask = np.zeros((h, w), dtype=np.uint8) + + for poly in text_polys: + polygon = np.array(poly, dtype=np.int32).reshape((1, -1, 2)) + cv2.fillPoly(text_region_mask, polygon, 1) + + return text_region_mask + + def generate_effective_mask(self, mask_size: tuple, polygons_ignore): + """Generate effective mask by setting the ineffective regions to 0 and + effective regions to 1. + + Args: + mask_size (tuple): The mask size. + polygons_ignore (list[[ndarray]]: The list of ignored text + polygons. + + Returns: + mask (ndarray): The effective mask of (height, width). + """ + + mask = np.ones(mask_size, dtype=np.uint8) + + for poly in polygons_ignore: + instance = poly.reshape(-1, 2).astype(np.int32).reshape(1, -1, 2) + cv2.fillPoly(mask, instance, 0) + + return mask + + def generate_level_targets(self, img_size, text_polys, ignore_polys): + """Generate ground truth target on each level. + + Args: + img_size (list[int]): Shape of input image. + text_polys (list[list[ndarray]]): A list of ground truth polygons. + ignore_polys (list[list[ndarray]]): A list of ignored polygons. + Returns: + level_maps (list(ndarray)): A list of ground target on each level. + """ + h, w = img_size + lv_size_divs = self.level_size_divisors + lv_proportion_range = self.level_proportion_range + lv_text_polys = [[] for i in range(len(lv_size_divs))] + lv_ignore_polys = [[] for i in range(len(lv_size_divs))] + level_maps = [] + for poly in text_polys: + polygon = np.array(poly, dtype=np.int32).reshape((1, -1, 2)) + _, _, box_w, box_h = cv2.boundingRect(polygon) + proportion = max(box_h, box_w) / (h + 1e-8) + + for ind, proportion_range in enumerate(lv_proportion_range): + if proportion_range[0] < proportion < proportion_range[1]: + lv_text_polys[ind].append(poly / lv_size_divs[ind]) + + for ignore_poly in ignore_polys: + polygon = np.array(ignore_poly, dtype=np.int32).reshape((1, -1, 2)) + _, _, box_w, box_h = cv2.boundingRect(polygon) + proportion = max(box_h, box_w) / (h + 1e-8) + + for ind, proportion_range in enumerate(lv_proportion_range): + if proportion_range[0] < proportion < proportion_range[1]: + lv_ignore_polys[ind].append(ignore_poly / lv_size_divs[ind]) + + for ind, size_divisor in enumerate(lv_size_divs): + current_level_maps = [] + level_img_size = (h // size_divisor, w // size_divisor) + + text_region = self.generate_text_region_mask( + level_img_size, lv_text_polys[ind] + )[None] + current_level_maps.append(text_region) + + center_region = self.generate_center_region_mask( + level_img_size, lv_text_polys[ind] + )[None] + current_level_maps.append(center_region) + + effective_mask = self.generate_effective_mask( + level_img_size, lv_ignore_polys[ind] + )[None] + current_level_maps.append(effective_mask) + + fourier_real_map, fourier_image_maps = self.generate_fourier_maps( + level_img_size, lv_text_polys[ind] + ) + current_level_maps.append(fourier_real_map) + current_level_maps.append(fourier_image_maps) + + level_maps.append(np.concatenate(current_level_maps)) + + return level_maps + + def generate_targets(self, results): + """Generate the ground truth targets for FCENet. + + Args: + results (dict): The input result dictionary. + + Returns: + results (dict): The output result dictionary. + """ + + assert isinstance(results, dict) + image = results["image"] + polygons = results["polys"] + ignore_tags = results["ignore_tags"] + h, w, _ = image.shape + + polygon_masks = [] + polygon_masks_ignore = [] + for tag, polygon in zip(ignore_tags, polygons): + if tag is True: + polygon_masks_ignore.append(polygon) + else: + polygon_masks.append(polygon) + + level_maps = self.generate_level_targets( + (h, w), polygon_masks, polygon_masks_ignore + ) + + mapping = { + "p3_maps": level_maps[0], + "p4_maps": level_maps[1], + "p5_maps": level_maps[2], + } + for key, value in mapping.items(): + results[key] = value + + return results + + def __call__(self, results): + results = self.generate_targets(results) + return results diff --git a/ppocr/data/imaug/frost_img/frost1.jpg b/ppocr/data/imaug/frost_img/frost1.jpg new file mode 100644 index 0000000..8705320 Binary files /dev/null and b/ppocr/data/imaug/frost_img/frost1.jpg differ diff --git a/ppocr/data/imaug/frost_img/frost2.png b/ppocr/data/imaug/frost_img/frost2.png new file mode 100644 index 0000000..48f7a86 Binary files /dev/null and b/ppocr/data/imaug/frost_img/frost2.png differ diff --git a/ppocr/data/imaug/frost_img/frost3.png b/ppocr/data/imaug/frost_img/frost3.png new file mode 100644 index 0000000..d47f9d2 Binary files /dev/null and b/ppocr/data/imaug/frost_img/frost3.png differ diff --git a/ppocr/data/imaug/frost_img/frost4.jpg b/ppocr/data/imaug/frost_img/frost4.jpg new file mode 100644 index 0000000..f8b0c41 Binary files /dev/null and b/ppocr/data/imaug/frost_img/frost4.jpg differ diff --git a/ppocr/data/imaug/frost_img/frost5.jpg b/ppocr/data/imaug/frost_img/frost5.jpg new file mode 100644 index 0000000..95dc905 Binary files /dev/null and b/ppocr/data/imaug/frost_img/frost5.jpg differ diff --git a/ppocr/data/imaug/frost_img/frost6.jpg b/ppocr/data/imaug/frost_img/frost6.jpg new file mode 100644 index 0000000..14e5d58 Binary files /dev/null and b/ppocr/data/imaug/frost_img/frost6.jpg differ diff --git a/ppocr/data/imaug/iaa_augment.py b/ppocr/data/imaug/iaa_augment.py new file mode 100644 index 0000000..4b2c67f --- /dev/null +++ b/ppocr/data/imaug/iaa_augment.py @@ -0,0 +1,220 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/WenmuZhou/DBNet.pytorch/blob/master/data_loader/modules/iaa_augment.py +""" +import os + +# Prevent automatic updates in Albumentations for stability in augmentation behavior +os.environ["NO_ALBUMENTATIONS_UPDATE"] = "1" + +import numpy as np +import albumentations as A +from albumentations.core.transforms_interface import DualTransform +from albumentations.augmentations.geometric import functional as fgeometric +from packaging import version + +ALBU_VERSION = version.parse(A.__version__) +IS_ALBU_NEW_VERSION = ALBU_VERSION >= version.parse("1.4.15") + + +# Custom resize transformation mimicking Imgaug's behavior with scaling +class ImgaugLikeResize(DualTransform): + def __init__(self, scale_range=(0.5, 3.0), interpolation=1, p=1.0): + super(ImgaugLikeResize, self).__init__(p) + self.scale_range = scale_range + self.interpolation = interpolation + + # Resize the image based on a randomly chosen scale within the scale range + def apply(self, img, scale=1.0, **params): + height, width = img.shape[:2] + new_height = int(height * scale) + new_width = int(width * scale) + + if IS_ALBU_NEW_VERSION: + return fgeometric.resize( + img, (new_height, new_width), interpolation=self.interpolation + ) + return fgeometric.resize( + img, new_height, new_width, interpolation=self.interpolation + ) + + # Apply the same scaling transformation to keypoints (e.g., polygon points) + def apply_to_keypoints(self, keypoints, scale=1.0, **params): + return np.array( + [(x * scale, y * scale) + tuple(rest) for x, y, *rest in keypoints] + ) + + # Get random scale parameter within the specified range + def get_params(self): + scale = np.random.uniform(self.scale_range[0], self.scale_range[1]) + return {"scale": scale} + + +# Builder class to translate custom augmenter arguments into Albumentations-compatible format +class AugmenterBuilder(object): + def __init__(self): + # Map common Imgaug transformations to equivalent Albumentations transforms + self.imgaug_to_albu = { + "Fliplr": "HorizontalFlip", + "Flipud": "VerticalFlip", + "Affine": "Affine", + # Additional mappings can be added here if needed + } + + # Recursive method to construct augmentation pipeline based on provided arguments + def build(self, args, root=True): + if args is None or len(args) == 0: + return None + elif isinstance(args, list): + # Build the full augmentation sequence if it's a root-level call + if root: + sequence = [self.build(value, root=False) for value in args] + return A.Compose( + sequence, + keypoint_params=A.KeypointParams( + format="xy", remove_invisible=False + ), + ) + else: + # Build individual augmenters for nested arguments + augmenter_type = args[0] + augmenter_args = args[1] if len(args) > 1 else {} + augmenter_args_mapped = self.map_arguments( + augmenter_type, augmenter_args + ) + augmenter_type_mapped = self.imgaug_to_albu.get( + augmenter_type, augmenter_type + ) + if augmenter_type_mapped == "Resize": + return ImgaugLikeResize(**augmenter_args_mapped) + else: + cls = getattr(A, augmenter_type_mapped) + return cls( + **{ + k: self.to_tuple_if_list(v) + for k, v in augmenter_args_mapped.items() + } + ) + elif isinstance(args, dict): + # Process individual transformation specified as dictionary + augmenter_type = args["type"] + augmenter_args = args.get("args", {}) + augmenter_args_mapped = self.map_arguments(augmenter_type, augmenter_args) + augmenter_type_mapped = self.imgaug_to_albu.get( + augmenter_type, augmenter_type + ) + if augmenter_type_mapped == "Resize": + return ImgaugLikeResize(**augmenter_args_mapped) + else: + cls = getattr(A, augmenter_type_mapped) + return cls( + **{ + k: self.to_tuple_if_list(v) + for k, v in augmenter_args_mapped.items() + } + ) + else: + raise RuntimeError("Unknown augmenter arg: " + str(args)) + + # Map arguments to expected format for each augmenter type + def map_arguments(self, augmenter_type, augmenter_args): + augmenter_args = augmenter_args.copy() # Avoid modifying the original arguments + if augmenter_type == "Resize": + # Ensure size is a valid 2-element list or tuple + size = augmenter_args.get("size") + if size: + if not isinstance(size, (list, tuple)) or len(size) != 2: + raise ValueError( + f"'size' must be a list or tuple of two numbers, but got {size}" + ) + min_scale, max_scale = size + return { + "scale_range": (min_scale, max_scale), + "interpolation": 1, # Linear interpolation + "p": 1.0, + } + else: + return {"scale_range": (1.0, 1.0), "interpolation": 1, "p": 1.0} + elif augmenter_type == "Affine": + # Map rotation to a tuple and ensure p=1.0 to apply transformation + rotate = augmenter_args.get("rotate", 0) + if isinstance(rotate, list): + rotate = tuple(rotate) + elif isinstance(rotate, (int, float)): + rotate = (float(rotate), float(rotate)) + augmenter_args["rotate"] = rotate + augmenter_args["p"] = augmenter_args.get("p", 1.0) + if augmenter_args.get("fit_output") is True: + if "border_mode" not in augmenter_args: + import cv2 + + augmenter_args["border_mode"] = cv2.BORDER_CONSTANT + if "fill" not in augmenter_args: + augmenter_args["fill"] = 0 + return augmenter_args + else: + # For other augmenters, ensure 'p' probability is specified + p = augmenter_args.get("p", 1.0) + augmenter_args["p"] = p + return augmenter_args + + # Convert lists to tuples for Albumentations compatibility + def to_tuple_if_list(self, obj): + if isinstance(obj, list): + return tuple(obj) + return obj + + +# Wrapper class for image and polygon transformations using Imgaug-style augmentation +class IaaAugment: + def __init__(self, augmenter_args=None, **kwargs): + if augmenter_args is None: + # Default augmenters if none are specified + augmenter_args = [ + {"type": "Fliplr", "args": {"p": 0.5}}, + {"type": "Affine", "args": {"rotate": [-10, 10]}}, + {"type": "Resize", "args": {"size": [0.5, 3]}}, + ] + self.augmenter = AugmenterBuilder().build(augmenter_args) + + # Apply the augmentations to image and polygon data + def __call__(self, data): + image = data["image"] + + if self.augmenter: + # Flatten polygons to individual keypoints for transformation + keypoints = [] + keypoints_lengths = [] + for poly in data["polys"]: + keypoints.extend([tuple(point) for point in poly]) + keypoints_lengths.append(len(poly)) + + # Apply the augmentation pipeline to image and keypoints + transformed = self.augmenter(image=image, keypoints=keypoints) + data["image"] = transformed["image"] + + # Extract transformed keypoints and reconstruct polygon structures + transformed_keypoints = transformed["keypoints"] + + # Reassemble polygons from transformed keypoints + new_polys = [] + idx = 0 + for length in keypoints_lengths: + new_poly = transformed_keypoints[idx : idx + length] + new_polys.append(np.array([kp[:2] for kp in new_poly])) + idx += length + data["polys"] = np.array(new_polys) + return data diff --git a/ppocr/data/imaug/label_ops.py b/ppocr/data/imaug/label_ops.py new file mode 100644 index 0000000..7988902 --- /dev/null +++ b/ppocr/data/imaug/label_ops.py @@ -0,0 +1,2205 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import os +from enum import Enum +import copy +import numpy as np +import string +from shapely.geometry import LineString, Point, Polygon +import json +import copy +import random +from random import sample +from collections import defaultdict + +from ppocr.utils.logging import get_logger +from ppocr.data.imaug.vqa.augment import order_by_tbyx + + +class ClsLabelEncode(object): + def __init__(self, label_list, **kwargs): + self.label_list = label_list + + def __call__(self, data): + label = data["label"] + if label not in self.label_list: + return None + label = self.label_list.index(label) + data["label"] = label + return data + + +class DetLabelEncode(object): + def __init__(self, **kwargs): + pass + + def __call__(self, data): + label = data["label"] + label = json.loads(label) + nBox = len(label) + boxes, txts, txt_tags = [], [], [] + for bno in range(0, nBox): + box = label[bno]["points"] + txt = label[bno]["transcription"] + boxes.append(box) + txts.append(txt) + if txt in ["*", "###"]: + txt_tags.append(True) + else: + txt_tags.append(False) + if len(boxes) == 0: + return None + boxes = self.expand_points_num(boxes) + boxes = np.array(boxes, dtype=np.float32) + txt_tags = np.array(txt_tags, dtype=np.bool_) + + # Mark polygons with NaN coordinates as ignore to avoid downstream errors + nan_mask = np.isnan(boxes).any(axis=(1, 2)) + txt_tags[nan_mask] = True + + data["polys"] = boxes + data["texts"] = txts + data["ignore_tags"] = txt_tags + return data + + def order_points_clockwise(self, pts): + rect = np.zeros((4, 2), dtype="float32") + s = pts.sum(axis=1) + rect[0] = pts[np.argmin(s)] + rect[2] = pts[np.argmax(s)] + tmp = np.delete(pts, (np.argmin(s), np.argmax(s)), axis=0) + diff = np.diff(np.array(tmp), axis=1) + rect[1] = tmp[np.argmin(diff)] + rect[3] = tmp[np.argmax(diff)] + return rect + + def expand_points_num(self, boxes): + max_points_num = 0 + for box in boxes: + if len(box) > max_points_num: + max_points_num = len(box) + ex_boxes = [] + for box in boxes: + ex_box = box + [box[-1]] * (max_points_num - len(box)) + ex_boxes.append(ex_box) + return ex_boxes + + +class BaseRecLabelEncode(object): + """Convert between text-label and text-index""" + + def __init__( + self, + max_text_length, + character_dict_path=None, + use_space_char=False, + lower=False, + ): + self.max_text_len = max_text_length + self.beg_str = "sos" + self.end_str = "eos" + self.lower = lower + + if character_dict_path is None: + logger = get_logger() + logger.warning( + "The character_dict_path is None, model can only recognize number and lower letters" + ) + self.character_str = "0123456789abcdefghijklmnopqrstuvwxyz" + dict_character = list(self.character_str) + self.lower = True + else: + self.character_str = [] + with open(character_dict_path, "rb") as fin: + lines = fin.readlines() + for line in lines: + line = line.decode("utf-8").strip("\n").strip("\r\n") + self.character_str.append(line) + if use_space_char: + self.character_str.append(" ") + dict_character = list(self.character_str) + dict_character = self.add_special_char(dict_character) + self.dict = {} + for i, char in enumerate(dict_character): + self.dict[char] = i + self.character = dict_character + + def add_special_char(self, dict_character): + return dict_character + + def encode(self, text): + """convert text-label into text-index. + input: + text: text labels of each image. [batch_size] + + output: + text: concatenated text index for CTCLoss. + [sum(text_lengths)] = [text_index_0 + text_index_1 + ... + text_index_(n - 1)] + length: length of each text. [batch_size] + """ + if len(text) == 0 or len(text) > self.max_text_len: + return None + if self.lower: + text = text.lower() + text_list = [] + for char in text: + if char not in self.dict: + # logger = get_logger() + # logger.warning('{} is not in dict'.format(char)) + continue + text_list.append(self.dict[char]) + if len(text_list) == 0: + return None + return text_list + + +class CTCLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, max_text_length, character_dict_path=None, use_space_char=False, **kwargs + ): + super(CTCLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + if text is None: + return None + data["length"] = np.array(len(text)) + text = text + [0] * (self.max_text_len - len(text)) + data["label"] = np.array(text) + + label = [0] * len(self.character) + for x in text: + label[x] += 1 + data["label_ace"] = np.array(label) + return data + + def add_special_char(self, dict_character): + dict_character = ["blank"] + dict_character + return dict_character + + +class E2ELabelEncodeTest(BaseRecLabelEncode): + def __init__( + self, max_text_length, character_dict_path=None, use_space_char=False, **kwargs + ): + super(E2ELabelEncodeTest, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + + def __call__(self, data): + import json + + padnum = len(self.dict) + label = data["label"] + label = json.loads(label) + nBox = len(label) + boxes, txts, txt_tags = [], [], [] + for bno in range(0, nBox): + box = label[bno]["points"] + txt = label[bno]["transcription"] + boxes.append(box) + txts.append(txt) + if txt in ["*", "###"]: + txt_tags.append(True) + else: + txt_tags.append(False) + boxes = np.array(boxes, dtype=np.float32) + txt_tags = np.array(txt_tags, dtype=np.bool_) + data["polys"] = boxes + data["ignore_tags"] = txt_tags + temp_texts = [] + for text in txts: + text = text.lower() + text = self.encode(text) + if text is None: + return None + text = text + [padnum] * (self.max_text_len - len(text)) # use 36 to pad + temp_texts.append(text) + data["texts"] = np.array(temp_texts) + return data + + +class E2ELabelEncodeTrain(object): + def __init__(self, **kwargs): + pass + + def __call__(self, data): + import json + + label = data["label"] + label = json.loads(label) + nBox = len(label) + boxes, txts, txt_tags = [], [], [] + for bno in range(0, nBox): + box = label[bno]["points"] + txt = label[bno]["transcription"] + boxes.append(box) + txts.append(txt) + if txt in ["*", "###"]: + txt_tags.append(True) + else: + txt_tags.append(False) + boxes = np.array(boxes, dtype=np.float32) + txt_tags = np.array(txt_tags, dtype=np.bool_) + + data["polys"] = boxes + data["texts"] = txts + data["ignore_tags"] = txt_tags + return data + + +class KieLabelEncode(object): + def __init__( + self, character_dict_path, class_path, norm=10, directed=False, **kwargs + ): + super(KieLabelEncode, self).__init__() + self.dict = dict({"": 0}) + self.label2classid_map = dict() + with open(character_dict_path, "r", encoding="utf-8") as fr: + idx = 1 + for line in fr: + char = line.strip() + self.dict[char] = idx + idx += 1 + with open(class_path, "r") as fin: + lines = fin.readlines() + for idx, line in enumerate(lines): + line = line.strip("\n") + self.label2classid_map[line] = idx + self.norm = norm + self.directed = directed + + def compute_relation(self, boxes): + """Compute relation between every two boxes.""" + x1s, y1s = boxes[:, 0:1], boxes[:, 1:2] + x2s, y2s = boxes[:, 4:5], boxes[:, 5:6] + ws, hs = x2s - x1s + 1, np.maximum(y2s - y1s + 1, 1) + dxs = (x1s[:, 0][None] - x1s) / self.norm + dys = (y1s[:, 0][None] - y1s) / self.norm + xhhs, xwhs = hs[:, 0][None] / hs, ws[:, 0][None] / hs + whs = ws / hs + np.zeros_like(xhhs) + relations = np.stack([dxs, dys, whs, xhhs, xwhs], -1) + bboxes = np.concatenate([x1s, y1s, x2s, y2s], -1).astype(np.float32) + return relations, bboxes + + def pad_text_indices(self, text_inds): + """Pad text index to same length.""" + max_len = 300 + recoder_len = max([len(text_ind) for text_ind in text_inds]) + padded_text_inds = -np.ones((len(text_inds), max_len), np.int32) + for idx, text_ind in enumerate(text_inds): + padded_text_inds[idx, : len(text_ind)] = np.array(text_ind) + return padded_text_inds, recoder_len + + def list_to_numpy(self, ann_infos): + """Convert bboxes, relations, texts and labels to ndarray.""" + boxes, text_inds = ann_infos["points"], ann_infos["text_inds"] + boxes = np.array(boxes, np.int32) + relations, bboxes = self.compute_relation(boxes) + + labels = ann_infos.get("labels", None) + if labels is not None: + labels = np.array(labels, np.int32) + edges = ann_infos.get("edges", None) + if edges is not None: + labels = labels[:, None] + edges = np.array(edges) + edges = (edges[:, None] == edges[None, :]).astype(np.int32) + if self.directed: + edges = (edges & labels == 1).astype(np.int32) + np.fill_diagonal(edges, -1) + labels = np.concatenate([labels, edges], -1) + padded_text_inds, recoder_len = self.pad_text_indices(text_inds) + max_num = 300 + temp_bboxes = np.zeros([max_num, 4]) + h, _ = bboxes.shape + temp_bboxes[:h, :] = bboxes + + temp_relations = np.zeros([max_num, max_num, 5]) + temp_relations[:h, :h, :] = relations + + temp_padded_text_inds = np.zeros([max_num, max_num]) + temp_padded_text_inds[:h, :] = padded_text_inds + + temp_labels = np.zeros([max_num, max_num]) + temp_labels[:h, : h + 1] = labels + + tag = np.array([h, recoder_len]) + return dict( + image=ann_infos["image"], + points=temp_bboxes, + relations=temp_relations, + texts=temp_padded_text_inds, + labels=temp_labels, + tag=tag, + ) + + def convert_canonical(self, points_x, points_y): + assert len(points_x) == 4 + assert len(points_y) == 4 + + points = [Point(points_x[i], points_y[i]) for i in range(4)] + + polygon = Polygon([(p.x, p.y) for p in points]) + min_x, min_y, _, _ = polygon.bounds + points_to_lefttop = [ + LineString([points[i], Point(min_x, min_y)]) for i in range(4) + ] + distances = np.array([line.length for line in points_to_lefttop]) + sort_dist_idx = np.argsort(distances) + lefttop_idx = sort_dist_idx[0] + + if lefttop_idx == 0: + point_orders = [0, 1, 2, 3] + elif lefttop_idx == 1: + point_orders = [1, 2, 3, 0] + elif lefttop_idx == 2: + point_orders = [2, 3, 0, 1] + else: + point_orders = [3, 0, 1, 2] + + sorted_points_x = [points_x[i] for i in point_orders] + sorted_points_y = [points_y[j] for j in point_orders] + + return sorted_points_x, sorted_points_y + + def sort_vertex(self, points_x, points_y): + assert len(points_x) == 4 + assert len(points_y) == 4 + + x = np.array(points_x) + y = np.array(points_y) + center_x = np.sum(x) * 0.25 + center_y = np.sum(y) * 0.25 + + x_arr = np.array(x - center_x) + y_arr = np.array(y - center_y) + + angle = np.arctan2(y_arr, x_arr) * 180.0 / np.pi + sort_idx = np.argsort(angle) + + sorted_points_x, sorted_points_y = [], [] + for i in range(4): + sorted_points_x.append(points_x[sort_idx[i]]) + sorted_points_y.append(points_y[sort_idx[i]]) + + return self.convert_canonical(sorted_points_x, sorted_points_y) + + def __call__(self, data): + import json + + label = data["label"] + annotations = json.loads(label) + boxes, texts, text_inds, labels, edges = [], [], [], [], [] + for ann in annotations: + box = ann["points"] + x_list = [box[i][0] for i in range(4)] + y_list = [box[i][1] for i in range(4)] + sorted_x_list, sorted_y_list = self.sort_vertex(x_list, y_list) + sorted_box = [] + for x, y in zip(sorted_x_list, sorted_y_list): + sorted_box.append(x) + sorted_box.append(y) + boxes.append(sorted_box) + text = ann["transcription"] + texts.append(ann["transcription"]) + text_ind = [self.dict[c] for c in text if c in self.dict] + text_inds.append(text_ind) + if "label" in ann.keys(): + labels.append(self.label2classid_map[ann["label"]]) + elif "key_cls" in ann.keys(): + labels.append(ann["key_cls"]) + else: + raise ValueError( + "Cannot found 'key_cls' in ann.keys(), please check your training annotation." + ) + edges.append(ann.get("edge", 0)) + ann_infos = dict( + image=data["image"], + points=boxes, + texts=texts, + text_inds=text_inds, + edges=edges, + labels=labels, + ) + + return self.list_to_numpy(ann_infos) + + +class AttnLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, max_text_length, character_dict_path=None, use_space_char=False, **kwargs + ): + super(AttnLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + + def add_special_char(self, dict_character): + self.beg_str = "sos" + self.end_str = "eos" + dict_character = [self.beg_str] + dict_character + [self.end_str] + return dict_character + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len: + return None + data["length"] = np.array(len(text)) + text = ( + [0] + + text + + [len(self.character) - 1] + + [0] * (self.max_text_len - len(text) - 2) + ) + data["label"] = np.array(text) + return data + + def get_ignored_tokens(self): + beg_idx = self.get_beg_end_flag_idx("beg") + end_idx = self.get_beg_end_flag_idx("end") + return [beg_idx, end_idx] + + def get_beg_end_flag_idx(self, beg_or_end): + if beg_or_end == "beg": + idx = np.array(self.dict[self.beg_str]) + elif beg_or_end == "end": + idx = np.array(self.dict[self.end_str]) + else: + assert False, "Unsupported type %s in get_beg_end_flag_idx" % beg_or_end + return idx + + +class RFLLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, max_text_length, character_dict_path=None, use_space_char=False, **kwargs + ): + super(RFLLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + + def add_special_char(self, dict_character): + self.beg_str = "sos" + self.end_str = "eos" + dict_character = [self.beg_str] + dict_character + [self.end_str] + return dict_character + + def encode_cnt(self, text): + cnt_label = [0.0] * len(self.character) + for char_ in text: + cnt_label[char_] += 1 + return np.array(cnt_label) + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len: + return None + cnt_label = self.encode_cnt(text) + data["length"] = np.array(len(text)) + text = ( + [0] + + text + + [len(self.character) - 1] + + [0] * (self.max_text_len - len(text) - 2) + ) + if len(text) != self.max_text_len: + return None + data["label"] = np.array(text) + data["cnt_label"] = cnt_label + return data + + def get_ignored_tokens(self): + beg_idx = self.get_beg_end_flag_idx("beg") + end_idx = self.get_beg_end_flag_idx("end") + return [beg_idx, end_idx] + + def get_beg_end_flag_idx(self, beg_or_end): + if beg_or_end == "beg": + idx = np.array(self.dict[self.beg_str]) + elif beg_or_end == "end": + idx = np.array(self.dict[self.end_str]) + else: + assert False, "Unsupported type %s in get_beg_end_flag_idx" % beg_or_end + return idx + + +class SEEDLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, max_text_length, character_dict_path=None, use_space_char=False, **kwargs + ): + super(SEEDLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + + def add_special_char(self, dict_character): + self.padding = "padding" + self.end_str = "eos" + self.unknown = "unknown" + dict_character = dict_character + [self.end_str, self.padding, self.unknown] + return dict_character + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len: + return None + data["length"] = np.array(len(text)) + 1 # conclude eos + text = ( + text + + [len(self.character) - 3] + + [len(self.character) - 2] * (self.max_text_len - len(text) - 1) + ) + data["label"] = np.array(text) + return data + + +class SRNLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, + max_text_length=25, + character_dict_path=None, + use_space_char=False, + **kwargs, + ): + super(SRNLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + + def add_special_char(self, dict_character): + dict_character = dict_character + [self.beg_str, self.end_str] + return dict_character + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + char_num = len(self.character) + if text is None: + return None + if len(text) > self.max_text_len: + return None + data["length"] = np.array(len(text)) + text = text + [char_num - 1] * (self.max_text_len - len(text)) + data["label"] = np.array(text) + return data + + def get_ignored_tokens(self): + beg_idx = self.get_beg_end_flag_idx("beg") + end_idx = self.get_beg_end_flag_idx("end") + return [beg_idx, end_idx] + + def get_beg_end_flag_idx(self, beg_or_end): + if beg_or_end == "beg": + idx = np.array(self.dict[self.beg_str]) + elif beg_or_end == "end": + idx = np.array(self.dict[self.end_str]) + else: + assert False, "Unsupported type %s in get_beg_end_flag_idx" % beg_or_end + return idx + + +class TableLabelEncode(AttnLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, + max_text_length, + character_dict_path, + replace_empty_cell_token=False, + merge_no_span_structure=False, + learn_empty_box=False, + loc_reg_num=4, + **kwargs, + ): + self.max_text_len = max_text_length + self.lower = False + self.learn_empty_box = learn_empty_box + self.merge_no_span_structure = merge_no_span_structure + self.replace_empty_cell_token = replace_empty_cell_token + + dict_character = [] + with open(character_dict_path, "rb") as fin: + lines = fin.readlines() + for line in lines: + line = line.decode("utf-8").strip("\n").strip("\r\n") + dict_character.append(line) + + if self.merge_no_span_structure: + if "" not in dict_character: + dict_character.append("") + if "" in dict_character: + dict_character.remove("") + + dict_character = self.add_special_char(dict_character) + self.dict = {} + for i, char in enumerate(dict_character): + self.dict[char] = i + self.idx2char = {v: k for k, v in self.dict.items()} + + self.character = dict_character + self.loc_reg_num = loc_reg_num + self.pad_idx = self.dict[self.beg_str] + self.start_idx = self.dict[self.beg_str] + self.end_idx = self.dict[self.end_str] + + self.td_token = ["", "", ""] + self.empty_bbox_token_dict = { + "[]": "", + "[' ']": "", + "['', ' ', '']": "", + "['\\u2028', '\\u2028']": "", + "['', ' ', '']": "", + "['', '']": "", + "['', ' ', '']": "", + "['', '', '', '']": "", + "['', '', ' ', '', '']": "", + "['', '']": "", + "['', ' ', '\\u2028', ' ', '\\u2028', ' ', '']": "", + } + + @property + def _max_text_len(self): + return self.max_text_len + 2 + + def __call__(self, data): + cells = data["cells"] + structure = data["structure"] + if self.merge_no_span_structure: + structure = self._merge_no_span_structure(structure) + if self.replace_empty_cell_token: + structure = self._replace_empty_cell_token(structure, cells) + # remove empty token and add " " to span token + new_structure = [] + for token in structure: + if token != "": + if "span" in token and token[0] != " ": + token = " " + token + new_structure.append(token) + # encode structure + structure = self.encode(new_structure) + if structure is None: + return None + data["length"] = len(structure) + structure = [self.start_idx] + structure + [self.end_idx] # add sos abd eos + structure = structure + [self.pad_idx] * ( + self._max_text_len - len(structure) + ) # pad + structure = np.array(structure) + data["structure"] = structure + + if len(structure) > self._max_text_len: + return None + + # encode box + bboxes = np.zeros((self._max_text_len, self.loc_reg_num), dtype=np.float32) + bbox_masks = np.zeros((self._max_text_len, 1), dtype=np.float32) + + bbox_idx = 0 + + for i, token in enumerate(structure): + if self.idx2char[token] in self.td_token: + if "bbox" in cells[bbox_idx] and len(cells[bbox_idx]["tokens"]) > 0: + bbox = cells[bbox_idx]["bbox"].copy() + bbox = np.array(bbox, dtype=np.float32).reshape(-1) + bboxes[i] = bbox + bbox_masks[i] = 1.0 + if self.learn_empty_box: + bbox_masks[i] = 1.0 + bbox_idx += 1 + data["bboxes"] = bboxes + data["bbox_masks"] = bbox_masks + return data + + def _merge_no_span_structure(self, structure): + """ + This code is refer from: + https://github.com/JiaquanYe/TableMASTER-mmocr/blob/master/table_recognition/data_preprocess.py + """ + new_structure = [] + i = 0 + while i < len(structure): + token = structure[i] + if token == "": + token = "" + i += 1 + new_structure.append(token) + i += 1 + return new_structure + + def _replace_empty_cell_token(self, token_list, cells): + """ + This fun code is refer from: + https://github.com/JiaquanYe/TableMASTER-mmocr/blob/master/table_recognition/data_preprocess.py + """ + + bbox_idx = 0 + add_empty_bbox_token_list = [] + for token in token_list: + if token in ["", ""]: + if "bbox" not in cells[bbox_idx].keys(): + content = str(cells[bbox_idx]["tokens"]) + token = self.empty_bbox_token_dict[content] + add_empty_bbox_token_list.append(token) + bbox_idx += 1 + else: + add_empty_bbox_token_list.append(token) + return add_empty_bbox_token_list + + +class TableMasterLabelEncode(TableLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, + max_text_length, + character_dict_path, + replace_empty_cell_token=False, + merge_no_span_structure=False, + learn_empty_box=False, + loc_reg_num=4, + **kwargs, + ): + super(TableMasterLabelEncode, self).__init__( + max_text_length, + character_dict_path, + replace_empty_cell_token, + merge_no_span_structure, + learn_empty_box, + loc_reg_num, + **kwargs, + ) + self.pad_idx = self.dict[self.pad_str] + self.unknown_idx = self.dict[self.unknown_str] + + @property + def _max_text_len(self): + return self.max_text_len + + def add_special_char(self, dict_character): + self.beg_str = "" + self.end_str = "" + self.unknown_str = "" + self.pad_str = "" + dict_character = dict_character + dict_character = dict_character + [ + self.unknown_str, + self.beg_str, + self.end_str, + self.pad_str, + ] + return dict_character + + +class TableBoxEncode(object): + def __init__(self, in_box_format="xyxy", out_box_format="xyxy", **kwargs): + assert out_box_format in ["xywh", "xyxy", "xyxyxyxy"] + self.in_box_format = in_box_format + self.out_box_format = out_box_format + + def __call__(self, data): + img_height, img_width = data["image"].shape[:2] + bboxes = data["bboxes"] + if self.in_box_format != self.out_box_format: + if self.out_box_format == "xywh": + if self.in_box_format == "xyxyxyxy": + bboxes = self.xyxyxyxy2xywh(bboxes) + elif self.in_box_format == "xyxy": + bboxes = self.xyxy2xywh(bboxes) + + bboxes[:, 0::2] /= img_width + bboxes[:, 1::2] /= img_height + data["bboxes"] = bboxes + return data + + def xyxyxyxy2xywh(self, boxes): + new_bboxes = np.zeros([len(boxes), 4]) + new_bboxes[:, 0] = boxes[:, 0::2].min() # x1 + new_bboxes[:, 1] = boxes[:, 1::2].min() # y1 + new_bboxes[:, 2] = boxes[:, 0::2].max() - new_bboxes[:, 0] # w + new_bboxes[:, 3] = boxes[:, 1::2].max() - new_bboxes[:, 1] # h + return new_bboxes + + def xyxy2xywh(self, bboxes): + new_bboxes = np.empty_like(bboxes) + new_bboxes[:, 0] = (bboxes[:, 0] + bboxes[:, 2]) / 2 # x center + new_bboxes[:, 1] = (bboxes[:, 1] + bboxes[:, 3]) / 2 # y center + new_bboxes[:, 2] = bboxes[:, 2] - bboxes[:, 0] # width + new_bboxes[:, 3] = bboxes[:, 3] - bboxes[:, 1] # height + return new_bboxes + + +class SARLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, max_text_length, character_dict_path=None, use_space_char=False, **kwargs + ): + super(SARLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + + def add_special_char(self, dict_character): + beg_end_str = "" + unknown_str = "" + padding_str = "" + dict_character = dict_character + [unknown_str] + self.unknown_idx = len(dict_character) - 1 + dict_character = dict_character + [beg_end_str] + self.start_idx = len(dict_character) - 1 + self.end_idx = len(dict_character) - 1 + dict_character = dict_character + [padding_str] + self.padding_idx = len(dict_character) - 1 + + return dict_character + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len - 1: + return None + data["length"] = np.array(len(text)) + target = [self.start_idx] + text + [self.end_idx] + padded_text = [self.padding_idx for _ in range(self.max_text_len)] + + padded_text[: len(target)] = target + data["label"] = np.array(padded_text) + return data + + def get_ignored_tokens(self): + return [self.padding_idx] + + +class SATRNLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, + max_text_length, + character_dict_path=None, + use_space_char=False, + lower=False, + **kwargs, + ): + super(SATRNLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + self.lower = lower + + def add_special_char(self, dict_character): + beg_end_str = "" + unknown_str = "" + padding_str = "" + dict_character = dict_character + [unknown_str] + self.unknown_idx = len(dict_character) - 1 + dict_character = dict_character + [beg_end_str] + self.start_idx = len(dict_character) - 1 + self.end_idx = len(dict_character) - 1 + dict_character = dict_character + [padding_str] + self.padding_idx = len(dict_character) - 1 + + return dict_character + + def encode(self, text): + if self.lower: + text = text.lower() + text_list = [] + for char in text: + text_list.append(self.dict.get(char, self.unknown_idx)) + if len(text_list) == 0: + return None + return text_list + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + if text is None: + return None + data["length"] = np.array(len(text)) + target = [self.start_idx] + text + [self.end_idx] + padded_text = [self.padding_idx for _ in range(self.max_text_len)] + if len(target) > self.max_text_len: + padded_text = target[: self.max_text_len] + else: + padded_text[: len(target)] = target + data["label"] = np.array(padded_text) + return data + + def get_ignored_tokens(self): + return [self.padding_idx] + + +class PRENLabelEncode(BaseRecLabelEncode): + def __init__( + self, max_text_length, character_dict_path, use_space_char=False, **kwargs + ): + super(PRENLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + + def add_special_char(self, dict_character): + padding_str = "" # 0 + end_str = "" # 1 + unknown_str = "" # 2 + + dict_character = [padding_str, end_str, unknown_str] + dict_character + self.padding_idx = 0 + self.end_idx = 1 + self.unknown_idx = 2 + + return dict_character + + def encode(self, text): + if len(text) == 0 or len(text) >= self.max_text_len: + return None + if self.lower: + text = text.lower() + text_list = [] + for char in text: + if char not in self.dict: + text_list.append(self.unknown_idx) + else: + text_list.append(self.dict[char]) + text_list.append(self.end_idx) + if len(text_list) < self.max_text_len: + text_list += [self.padding_idx] * (self.max_text_len - len(text_list)) + return text_list + + def __call__(self, data): + text = data["label"] + encoded_text = self.encode(text) + if encoded_text is None: + return None + data["label"] = np.array(encoded_text) + return data + + +class VQATokenLabelEncode(object): + """ + Label encode for NLP VQA methods + """ + + def __init__( + self, + class_path, + contains_re=False, + add_special_ids=False, + algorithm="LayoutXLM", + use_textline_bbox_info=True, + order_method=None, + infer_mode=False, + ocr_engine=None, + **kwargs, + ): + super(VQATokenLabelEncode, self).__init__() + from paddlenlp.transformers import ( + LayoutXLMTokenizer, + LayoutLMTokenizer, + LayoutLMv2Tokenizer, + ) + from ppocr.utils.utility import load_vqa_bio_label_maps + + tokenizer_dict = { + "LayoutXLM": { + "class": LayoutXLMTokenizer, + "pretrained_model": "layoutxlm-base-uncased", + }, + "LayoutLM": { + "class": LayoutLMTokenizer, + "pretrained_model": "layoutlm-base-uncased", + }, + "LayoutLMv2": { + "class": LayoutLMv2Tokenizer, + "pretrained_model": "layoutlmv2-base-uncased", + }, + } + self.contains_re = contains_re + tokenizer_config = tokenizer_dict[algorithm] + self.tokenizer = tokenizer_config["class"].from_pretrained( + tokenizer_config["pretrained_model"] + ) + self.label2id_map, id2label_map = load_vqa_bio_label_maps(class_path) + self.add_special_ids = add_special_ids + self.infer_mode = infer_mode + self.ocr_engine = ocr_engine + self.use_textline_bbox_info = use_textline_bbox_info + self.order_method = order_method + assert self.order_method in [None, "tb-yx"] + + def split_bbox(self, bbox, text, tokenizer): + words = text.split() + token_bboxes = [] + curr_word_idx = 0 + x1, y1, x2, y2 = bbox + unit_w = (x2 - x1) / len(text) + for idx, word in enumerate(words): + curr_w = len(word) * unit_w + word_bbox = [x1, y1, x1 + curr_w, y2] + token_bboxes.extend([word_bbox] * len(tokenizer.tokenize(word))) + x1 += (len(word) + 1) * unit_w + return token_bboxes + + def filter_empty_contents(self, ocr_info): + """ + find out the empty texts and remove the links + """ + new_ocr_info = [] + empty_index = [] + for idx, info in enumerate(ocr_info): + if len(info["transcription"]) > 0: + new_ocr_info.append(copy.deepcopy(info)) + else: + empty_index.append(info["id"]) + + for idx, info in enumerate(new_ocr_info): + new_link = [] + for link in info["linking"]: + if link[0] in empty_index or link[1] in empty_index: + continue + new_link.append(link) + new_ocr_info[idx]["linking"] = new_link + return new_ocr_info + + def __call__(self, data): + # load bbox and label info + ocr_info = self._load_ocr_info(data) + + for idx in range(len(ocr_info)): + if "bbox" not in ocr_info[idx]: + ocr_info[idx]["bbox"] = self.trans_poly_to_bbox(ocr_info[idx]["points"]) + + if self.order_method == "tb-yx": + ocr_info = order_by_tbyx(ocr_info) + + # for re + train_re = self.contains_re and not self.infer_mode + if train_re: + ocr_info = self.filter_empty_contents(ocr_info) + + height, width, _ = data["image"].shape + + words_list = [] + bbox_list = [] + input_ids_list = [] + token_type_ids_list = [] + segment_offset_id = [] + gt_label_list = [] + + entities = [] + + if train_re: + relations = [] + id2label = {} + entity_id_to_index_map = {} + empty_entity = set() + + data["ocr_info"] = copy.deepcopy(ocr_info) + + for info in ocr_info: + text = info["transcription"] + if len(text) <= 0: + continue + if train_re: + # for re + if len(text) == 0: + empty_entity.add(info["id"]) + continue + id2label[info["id"]] = info["label"] + relations.extend([tuple(sorted(l)) for l in info["linking"]]) + # smooth_box + info["bbox"] = self.trans_poly_to_bbox(info["points"]) + + encode_res = self.tokenizer.encode( + text, + pad_to_max_seq_len=False, + return_attention_mask=True, + return_token_type_ids=True, + ) + + if not self.add_special_ids: + # TODO: use tok.all_special_ids to remove + encode_res["input_ids"] = encode_res["input_ids"][1:-1] + encode_res["token_type_ids"] = encode_res["token_type_ids"][1:-1] + encode_res["attention_mask"] = encode_res["attention_mask"][1:-1] + + if self.use_textline_bbox_info: + bbox = [info["bbox"]] * len(encode_res["input_ids"]) + else: + bbox = self.split_bbox( + info["bbox"], info["transcription"], self.tokenizer + ) + if len(bbox) <= 0: + continue + bbox = self._smooth_box(bbox, height, width) + if self.add_special_ids: + bbox.insert(0, [0, 0, 0, 0]) + bbox.append([0, 0, 0, 0]) + + # parse label + if not self.infer_mode: + label = info["label"] + gt_label = self._parse_label(label, encode_res) + + # construct entities for re + if train_re: + if gt_label[0] != self.label2id_map["O"]: + entity_id_to_index_map[info["id"]] = len(entities) + label = label.upper() + entities.append( + { + "start": len(input_ids_list), + "end": len(input_ids_list) + len(encode_res["input_ids"]), + "label": label.upper(), + } + ) + else: + entities.append( + { + "start": len(input_ids_list), + "end": len(input_ids_list) + len(encode_res["input_ids"]), + "label": "O", + } + ) + input_ids_list.extend(encode_res["input_ids"]) + token_type_ids_list.extend(encode_res["token_type_ids"]) + bbox_list.extend(bbox) + words_list.append(text) + segment_offset_id.append(len(input_ids_list)) + if not self.infer_mode: + gt_label_list.extend(gt_label) + + data["input_ids"] = input_ids_list + data["token_type_ids"] = token_type_ids_list + data["bbox"] = bbox_list + data["attention_mask"] = [1] * len(input_ids_list) + data["labels"] = gt_label_list + data["segment_offset_id"] = segment_offset_id + data["tokenizer_params"] = dict( + padding_side=self.tokenizer.padding_side, + pad_token_type_id=self.tokenizer.pad_token_type_id, + pad_token_id=self.tokenizer.pad_token_id, + ) + data["entities"] = entities + + if train_re: + data["relations"] = relations + data["id2label"] = id2label + data["empty_entity"] = empty_entity + data["entity_id_to_index_map"] = entity_id_to_index_map + return data + + def trans_poly_to_bbox(self, poly): + x1 = int(np.min([p[0] for p in poly])) + x2 = int(np.max([p[0] for p in poly])) + y1 = int(np.min([p[1] for p in poly])) + y2 = int(np.max([p[1] for p in poly])) + return [x1, y1, x2, y2] + + def _load_ocr_info(self, data): + if self.infer_mode: + ocr_result = self.ocr_engine.ocr(data["image"], cls=False)[0] + ocr_info = [] + for res in ocr_result: + ocr_info.append( + { + "transcription": res[1][0], + "bbox": self.trans_poly_to_bbox(res[0]), + "points": res[0], + } + ) + return ocr_info + else: + info = data["label"] + # read text info + info_dict = json.loads(info) + return info_dict + + def _smooth_box(self, bboxes, height, width): + bboxes = np.array(bboxes) + bboxes[:, 0] = bboxes[:, 0] * 1000 / width + bboxes[:, 2] = bboxes[:, 2] * 1000 / width + bboxes[:, 1] = bboxes[:, 1] * 1000 / height + bboxes[:, 3] = bboxes[:, 3] * 1000 / height + bboxes = bboxes.astype("int64").tolist() + return bboxes + + def _parse_label(self, label, encode_res): + gt_label = [] + if label.lower() in ["other", "others", "ignore"]: + gt_label.extend([0] * len(encode_res["input_ids"])) + else: + gt_label.append(self.label2id_map[("b-" + label).upper()]) + gt_label.extend( + [self.label2id_map[("i-" + label).upper()]] + * (len(encode_res["input_ids"]) - 1) + ) + return gt_label + + +class MultiLabelEncode(BaseRecLabelEncode): + def __init__( + self, + max_text_length, + character_dict_path=None, + use_space_char=False, + gtc_encode=None, + **kwargs, + ): + super(MultiLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + + self.ctc_encode = CTCLabelEncode( + max_text_length, character_dict_path, use_space_char, **kwargs + ) + self.gtc_encode_type = gtc_encode + if gtc_encode is None: + self.gtc_encode = SARLabelEncode( + max_text_length, character_dict_path, use_space_char, **kwargs + ) + else: + self.gtc_encode = eval(gtc_encode)( + max_text_length, character_dict_path, use_space_char, **kwargs + ) + + def __call__(self, data): + data_ctc = copy.deepcopy(data) + data_gtc = copy.deepcopy(data) + data_out = dict() + data_out["img_path"] = data.get("img_path", None) + data_out["image"] = data["image"] + ctc = self.ctc_encode.__call__(data_ctc) + gtc = self.gtc_encode.__call__(data_gtc) + if ctc is None or gtc is None: + return None + data_out["label_ctc"] = ctc["label"] + if self.gtc_encode_type is not None: + data_out["label_gtc"] = gtc["label"] + else: + data_out["label_sar"] = gtc["label"] + data_out["length"] = ctc["length"] + return data_out + + +class NRTRLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, max_text_length, character_dict_path=None, use_space_char=False, **kwargs + ): + super(NRTRLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len - 1: + return None + data["length"] = np.array(len(text)) + text.insert(0, 2) + text.append(3) + text = text + [0] * (self.max_text_len - len(text)) + data["label"] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = ["blank", "", "", ""] + dict_character + return dict_character + + +class ParseQLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + BOS = "[B]" + EOS = "[E]" + PAD = "[P]" + + def __init__( + self, max_text_length, character_dict_path=None, use_space_char=False, **kwargs + ): + super(ParseQLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len - 2: + return None + data["length"] = np.array(len(text)) + text = [self.dict[self.BOS]] + text + [self.dict[self.EOS]] + text = text + [self.dict[self.PAD]] * (self.max_text_len - len(text)) + data["label"] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = [self.EOS] + dict_character + [self.BOS, self.PAD] + return dict_character + + +class ViTSTRLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, + max_text_length, + character_dict_path=None, + use_space_char=False, + ignore_index=0, + **kwargs, + ): + super(ViTSTRLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + self.ignore_index = ignore_index + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len: + return None + data["length"] = np.array(len(text)) + text.insert(0, self.ignore_index) + text.append(1) + text = text + [self.ignore_index] * (self.max_text_len + 2 - len(text)) + data["label"] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = ["", ""] + dict_character + return dict_character + + +class ABINetLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, + max_text_length, + character_dict_path=None, + use_space_char=False, + ignore_index=100, + **kwargs, + ): + super(ABINetLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + self.ignore_index = ignore_index + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len: + return None + data["length"] = np.array(len(text)) + text.append(0) + text = text + [self.ignore_index] * (self.max_text_len + 1 - len(text)) + data["label"] = np.array(text) + return data + + def add_special_char(self, dict_character): + dict_character = [""] + dict_character + return dict_character + + +class SRLabelEncode(BaseRecLabelEncode): + def __init__( + self, max_text_length, character_dict_path=None, use_space_char=False, **kwargs + ): + super(SRLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + self.dic = {} + with open(character_dict_path, "r") as fin: + for line in fin.readlines(): + line = line.strip() + character, sequence = line.split() + self.dic[character] = sequence + english_stroke_alphabet = "0123456789" + self.english_stroke_dict = {} + for index in range(len(english_stroke_alphabet)): + self.english_stroke_dict[english_stroke_alphabet[index]] = index + + def encode(self, label): + stroke_sequence = "" + for character in label: + if character not in self.dic: + continue + else: + stroke_sequence += self.dic[character] + stroke_sequence += "0" + label = stroke_sequence + + length = len(label) + + input_tensor = np.zeros(self.max_text_len).astype("int64") + for j in range(length - 1): + input_tensor[j + 1] = self.english_stroke_dict[label[j]] + + return length, input_tensor + + def __call__(self, data): + text = data["label"] + length, input_tensor = self.encode(text) + + data["length"] = length + data["input_tensor"] = input_tensor + if text is None: + return None + return data + + +class SPINLabelEncode(AttnLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, + max_text_length, + character_dict_path=None, + use_space_char=False, + lower=True, + **kwargs, + ): + super(SPINLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + self.lower = lower + + def add_special_char(self, dict_character): + self.beg_str = "sos" + self.end_str = "eos" + dict_character = [self.beg_str] + [self.end_str] + dict_character + return dict_character + + def __call__(self, data): + text = data["label"] + text = self.encode(text) + if text is None: + return None + if len(text) > self.max_text_len: + return None + data["length"] = np.array(len(text)) + target = [0] + text + [1] + padded_text = [0 for _ in range(self.max_text_len + 2)] + + padded_text[: len(target)] = target + data["label"] = np.array(padded_text) + return data + + +class VLLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, max_text_length, character_dict_path=None, use_space_char=False, **kwargs + ): + super(VLLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + self.dict = {} + for i, char in enumerate(self.character): + self.dict[char] = i + + def __call__(self, data): + text = data["label"] # original string + # generate occluded text + len_str = len(text) + if len_str <= 0: + return None + change_num = 1 + order = list(range(len_str)) + change_id = sample(order, change_num)[0] + label_sub = text[change_id] + if change_id == (len_str - 1): + label_res = text[:change_id] + elif change_id == 0: + label_res = text[1:] + else: + label_res = text[:change_id] + text[change_id + 1 :] + + data["label_res"] = label_res # remaining string + data["label_sub"] = label_sub # occluded character + data["label_id"] = change_id # character index + # encode label + text = self.encode(text) + if text is None: + return None + text = [i + 1 for i in text] + data["length"] = np.array(len(text)) + text = text + [0] * (self.max_text_len - len(text)) + data["label"] = np.array(text) + label_res = self.encode(label_res) + label_sub = self.encode(label_sub) + if label_res is None: + label_res = [] + else: + label_res = [i + 1 for i in label_res] + if label_sub is None: + label_sub = [] + else: + label_sub = [i + 1 for i in label_sub] + data["length_res"] = np.array(len(label_res)) + data["length_sub"] = np.array(len(label_sub)) + label_res = label_res + [0] * (self.max_text_len - len(label_res)) + label_sub = label_sub + [0] * (self.max_text_len - len(label_sub)) + data["label_res"] = np.array(label_res) + data["label_sub"] = np.array(label_sub) + return data + + +class CTLabelEncode(object): + def __init__(self, **kwargs): + pass + + def __call__(self, data): + label = data["label"] + + label = json.loads(label) + nBox = len(label) + boxes, txts = [], [] + for bno in range(0, nBox): + box = label[bno]["points"] + box = np.array(box) + + boxes.append(box) + txt = label[bno]["transcription"] + txts.append(txt) + + if len(boxes) == 0: + return None + + data["polys"] = boxes + data["texts"] = txts + return data + + +class CANLabelEncode(BaseRecLabelEncode): + def __init__( + self, + character_dict_path, + max_text_length=100, + use_space_char=False, + lower=True, + **kwargs, + ): + super(CANLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char, lower + ) + + def encode(self, text_seq): + text_seq_encoded = [] + for text in text_seq: + if text not in self.character: + continue + text_seq_encoded.append(self.dict.get(text)) + if len(text_seq_encoded) == 0: + return None + return text_seq_encoded + + def __call__(self, data): + label = data["label"] + if isinstance(label, str): + label = label.strip().split() + label.append(self.end_str) + data["label"] = self.encode(label) + return data + + +class CPPDLabelEncode(BaseRecLabelEncode): + """Convert between text-label and text-index""" + + def __init__( + self, + max_text_length, + character_dict_path=None, + use_space_char=False, + ch=False, + ignore_index=100, + **kwargs, + ): + super(CPPDLabelEncode, self).__init__( + max_text_length, character_dict_path, use_space_char + ) + self.ch = ch + self.ignore_index = ignore_index + + def __call__(self, data): + text = data["label"] + if self.ch: + text, text_node_index, text_node_num = self.encodech(text) + if text is None: + return None + if len(text) > self.max_text_len: + return None + data["length"] = np.array(len(text)) + + text_pos_node = [1] * (len(text) + 1) + [0] * ( + self.max_text_len - len(text) + ) + + text.append(0) # eos + text = text + [self.ignore_index] * (self.max_text_len + 1 - len(text)) + + data["label"] = np.array(text) + data["label_node"] = np.array(text_node_num + text_pos_node) + data["label_index"] = np.array(text_node_index) + return data + else: + text, text_char_node, ch_order = self.encode(text) + if text is None: + return None + if len(text) >= self.max_text_len: + return None + data["length"] = np.array(len(text)) + + text_pos_node = [1] * (len(text) + 1) + [0] * ( + self.max_text_len - len(text) + ) + + text.append(0) # eos + + text = text + [self.ignore_index] * (self.max_text_len + 1 - len(text)) + data["label"] = np.array(text) + data["label_node"] = np.array(text_char_node + text_pos_node) + data["label_order"] = np.array(ch_order) + + return data + + def add_special_char(self, dict_character): + dict_character = [""] + dict_character + self.num_character = len(dict_character) + return dict_character + + def encode(self, text): + """ """ + if len(text) == 0 or len(text) > self.max_text_len: + return None, None, None + if self.lower: + text = text.lower() + text_node = [0 for _ in range(self.num_character)] + text_node[0] = 1 + text_list = [] + ch_order = [] + order = 1 + for char in text: + if char not in self.dict: + continue + text_list.append(self.dict[char]) + text_node[self.dict[char]] += 1 + ch_order.append([self.dict[char], text_node[self.dict[char]], order]) + order += 1 + + no_ch_order = [] + for char in self.character: + if char not in text: + no_ch_order.append([self.dict[char], 1, 0]) + random.shuffle(no_ch_order) + ch_order = ch_order + no_ch_order + ch_order = ch_order[: self.max_text_len + 1] + + if len(text_list) == 0: + return None, None, None + return text_list, text_node, ch_order.sort() + + def encodech(self, text): + """ """ + if len(text) == 0 or len(text) > self.max_text_len: + return None, None, None + if self.lower: + text = text.lower() + text_node_dict = {} + text_node_dict.update({0: 1}) + character_index = [_ for _ in range(self.num_character)] + text_list = [] + for char in text: + if char not in self.dict: + continue + i_c = self.dict[char] + text_list.append(i_c) + if i_c in text_node_dict.keys(): + text_node_dict[i_c] += 1 + else: + text_node_dict.update({i_c: 1}) + for ic in list(text_node_dict.keys()): + character_index.remove(ic) + none_char_index = sample(character_index, 37 - len(list(text_node_dict.keys()))) + for ic in none_char_index: + text_node_dict[ic] = 0 + + text_node_index = sorted(text_node_dict) + text_node_num = [text_node_dict[k] for k in text_node_index] + if len(text_list) == 0: + return None, None, None + return text_list, text_node_index, text_node_num + + +class LatexOCRLabelEncode(object): + def __init__( + self, + rec_char_dict_path, + **kwargs, + ): + # Set the TOKENIZERS_PARALLELISM environment variable to 'false' to suppress + # the warning: "The current process just got forked, Disabling parallelism to avoid deadlocks.. + # To disable this warning, please explicitly set TOKENIZERS_PARALLELISM=(true | false)" from tokenizers + os.environ["TOKENIZERS_PARALLELISM"] = "false" + from tokenizers import Tokenizer as TokenizerFast + + self.tokenizer = TokenizerFast.from_file(rec_char_dict_path) + self.model_input_names = ["input_ids", "token_type_ids", "attention_mask"] + self.pad_token_id = 0 + self.bos_token_id = 1 + self.eos_token_id = 2 + + def _convert_encoding( + self, + encoding, + return_token_type_ids=None, + return_attention_mask=None, + return_overflowing_tokens=False, + return_special_tokens_mask=False, + return_offsets_mapping=False, + return_length=False, + verbose=True, + ): + + if return_token_type_ids is None: + return_token_type_ids = "token_type_ids" in self.model_input_names + if return_attention_mask is None: + return_attention_mask = "attention_mask" in self.model_input_names + + if return_overflowing_tokens and encoding.overflowing is not None: + encodings = [encoding] + encoding.overflowing + else: + encodings = [encoding] + + encoding_dict = defaultdict(list) + for e in encodings: + encoding_dict["input_ids"].append(e.ids) + + if return_token_type_ids: + encoding_dict["token_type_ids"].append(e.type_ids) + if return_attention_mask: + encoding_dict["attention_mask"].append(e.attention_mask) + if return_special_tokens_mask: + encoding_dict["special_tokens_mask"].append(e.special_tokens_mask) + if return_offsets_mapping: + encoding_dict["offset_mapping"].append(e.offsets) + if return_length: + encoding_dict["length"].append(len(e.ids)) + + return encoding_dict, encodings + + def encode( + self, + text, + text_pair=None, + return_token_type_ids=False, + add_special_tokens=True, + is_split_into_words=False, + ): + batched_input = text + encodings = self.tokenizer.encode_batch( + batched_input, + add_special_tokens=add_special_tokens, + is_pretokenized=is_split_into_words, + ) + tokens_and_encodings = [ + self._convert_encoding( + encoding=encoding, + return_token_type_ids=False, + return_attention_mask=None, + return_overflowing_tokens=False, + return_special_tokens_mask=False, + return_offsets_mapping=False, + return_length=False, + verbose=True, + ) + for encoding in encodings + ] + sanitized_tokens = {} + for key in tokens_and_encodings[0][0].keys(): + stack = [e for item, _ in tokens_and_encodings for e in item[key]] + sanitized_tokens[key] = stack + return sanitized_tokens + + def __call__(self, eqs): + topk = self.encode(eqs) + for k, p in zip(topk, [[self.bos_token_id, self.eos_token_id], [1, 1]]): + process_seq = [[p[0]] + x + [p[1]] for x in topk[k]] + max_length = 0 + for seq in process_seq: + max_length = max(max_length, len(seq)) + labels = np.zeros((len(process_seq), max_length), dtype="int64") + for idx, seq in enumerate(process_seq): + l = len(seq) + labels[idx][:l] = seq + topk[k] = labels + return ( + np.array(topk["input_ids"]).astype(np.int64), + np.array(topk["attention_mask"]).astype(np.int64), + max_length, + ) + + +class ExplicitEnum(str, Enum): + """ + Enum with more explicit error message for missing values. + """ + + @classmethod + def _missing_(cls, value): + raise ValueError( + f"{value} is not a valid {cls.__name__}, please select one of {list(cls._value2member_map_.keys())}" + ) + + +class TruncationStrategy(ExplicitEnum): + """ + Possible values for the `truncation` argument in [`PreTrainedTokenizerBase.__call__`]. Useful for tab-completion in + an IDE. + """ + + ONLY_FIRST = "only_first" + ONLY_SECOND = "only_second" + LONGEST_FIRST = "longest_first" + DO_NOT_TRUNCATE = "do_not_truncate" + + +class PaddingStrategy(ExplicitEnum): + """ + Possible values for the `padding` argument in [`PreTrainedTokenizerBase.__call__`]. Useful for tab-completion in an + IDE. + """ + + LONGEST = "longest" + MAX_LENGTH = "max_length" + DO_NOT_PAD = "do_not_pad" + + +class UniMERNetLabelEncode(object): + + SPECIAL_TOKENS_ATTRIBUTES = [ + "bos_token", + "eos_token", + "unk_token", + "sep_token", + "pad_token", + "cls_token", + "mask_token", + "additional_special_tokens", + ] + + def __init__( + self, + rec_char_dict_path, + max_seq_len, + **kwargs, + ): + # Set the TOKENIZERS_PARALLELISM environment variable to 'false' to suppress + # the warning: "The current process just got forked, Disabling parallelism to avoid deadlocks.. + # To disable this warning, please explicitly set TOKENIZERS_PARALLELISM=(true | false)" from tokenizers + os.environ["TOKENIZERS_PARALLELISM"] = "false" + from tokenizers import Tokenizer as TokenizerFast + from tokenizers import AddedToken + + self._unk_token = "" + self._bos_token = "" + self._eos_token = "" + self._pad_token = "" + self._sep_token = None + self._cls_token = None + self._mask_token = None + self._additional_special_tokens = [] + self.model_input_names = ["input_ids", "token_type_ids", "attention_mask"] + self.max_seq_len = max_seq_len + self.pad_token_id = 1 + self.bos_token_id = 0 + self.eos_token_id = 2 + self.padding_side = "right" + self.pad_token = "" + self.pad_token_type_id = 0 + self.pad_to_multiple_of = None + fast_tokenizer_file = os.path.join(rec_char_dict_path, "tokenizer.json") + tokenizer_config_file = os.path.join( + rec_char_dict_path, "tokenizer_config.json" + ) + self.tokenizer = TokenizerFast.from_file(fast_tokenizer_file) + added_tokens_decoder = {} + added_tokens_map = {} + + if tokenizer_config_file is not None: + with open( + tokenizer_config_file, encoding="utf-8" + ) as tokenizer_config_handle: + init_kwargs = json.load(tokenizer_config_handle) + if "added_tokens_decoder" in init_kwargs: + for idx, token in init_kwargs["added_tokens_decoder"].items(): + if isinstance(token, dict): + token = AddedToken(**token) + if isinstance(token, AddedToken): + added_tokens_decoder[int(idx)] = token + added_tokens_map[str(token)] = token + else: + raise ValueError( + f"Found a {token.__class__} in the saved `added_tokens_decoder`, should be a dictionary or an AddedToken instance" + ) + init_kwargs["added_tokens_decoder"] = added_tokens_decoder + added_tokens_decoder = init_kwargs.pop("added_tokens_decoder", {}) + tokens_to_add = [ + token + for index, token in sorted( + added_tokens_decoder.items(), key=lambda x: x[0] + ) + if token not in added_tokens_decoder + ] + added_tokens_encoder = self.added_tokens_encoder(added_tokens_decoder) + encoder = list(added_tokens_encoder.keys()) + [ + str(token) for token in tokens_to_add + ] + tokens_to_add += [ + token + for token in self.all_special_tokens_extended + if token not in encoder and token not in tokens_to_add + ] + if len(tokens_to_add) > 0: + is_last_special = None + tokens = [] + special_tokens = self.all_special_tokens + for token in tokens_to_add: + is_special = ( + (token.special or str(token) in special_tokens) + if isinstance(token, AddedToken) + else str(token) in special_tokens + ) + if is_last_special is None or is_last_special == is_special: + tokens.append(token) + else: + self._add_tokens(tokens, special_tokens=is_last_special) + tokens = [token] + is_last_special = is_special + if tokens: + self._add_tokens(tokens, special_tokens=is_last_special) + + def _add_tokens(self, new_tokens, special_tokens=False) -> int: + if special_tokens: + return self.tokenizer.add_special_tokens(new_tokens) + + return self.tokenizer.add_tokens(new_tokens) + + def added_tokens_encoder(self, added_tokens_decoder): + return { + k.content: v + for v, k in sorted(added_tokens_decoder.items(), key=lambda item: item[0]) + } + + @property + def all_special_tokens(self): + all_toks = [str(s) for s in self.all_special_tokens_extended] + return all_toks + + @property + def all_special_tokens_extended(self): + all_tokens = [] + seen = set() + for value in self.special_tokens_map_extended.values(): + if isinstance(value, (list, tuple)): + tokens_to_add = [token for token in value if str(token) not in seen] + else: + tokens_to_add = [value] if str(value) not in seen else [] + seen.update(map(str, tokens_to_add)) + all_tokens.extend(tokens_to_add) + return all_tokens + + @property + def special_tokens_map_extended(self): + set_attr = {} + for attr in self.SPECIAL_TOKENS_ATTRIBUTES: + attr_value = getattr(self, "_" + attr) + if attr_value: + set_attr[attr] = attr_value + return set_attr + + def set_truncation_and_padding( + self, + padding_strategy, + truncation_strategy, + max_length, + stride, + pad_to_multiple_of, + ): + _truncation = self.tokenizer.truncation + _padding = self.tokenizer.padding + # Set truncation and padding on the backend tokenizer + if truncation_strategy == TruncationStrategy.DO_NOT_TRUNCATE: + if _truncation is not None: + self._tokenizer.no_truncation() + else: + target = { + "max_length": max_length, + "stride": stride, + "strategy": truncation_strategy.value, + "direction": "right", + } + + if _truncation is None: + current = None + else: + current = {k: _truncation.get(k, None) for k in target} + + if current != target: + self.tokenizer.enable_truncation(**target) + if padding_strategy == PaddingStrategy.DO_NOT_PAD: + if _padding is not None: + self.tokenizer.no_padding() + else: + length = ( + max_length if padding_strategy == PaddingStrategy.MAX_LENGTH else None + ) + target = { + "length": length, + "direction": self.padding_side, + "pad_id": self.pad_token_id, + "pad_token": self.pad_token, + "pad_type_id": self.pad_token_type_id, + "pad_to_multiple_of": pad_to_multiple_of, + } + if _padding != target: + self.tokenizer.enable_padding(**target) + + def _convert_encoding( + self, + encoding, + return_token_type_ids=None, + return_attention_mask=None, + return_overflowing_tokens=False, + return_special_tokens_mask=False, + return_offsets_mapping=False, + return_length=False, + verbose=True, + ): + + if return_token_type_ids is None: + return_token_type_ids = "token_type_ids" in self.model_input_names + if return_attention_mask is None: + return_attention_mask = "attention_mask" in self.model_input_names + + if return_overflowing_tokens and encoding.overflowing is not None: + encodings = [encoding] + encoding.overflowing + else: + encodings = [encoding] + + encoding_dict = defaultdict(list) + for e in encodings: + encoding_dict["input_ids"].append(e.ids) + if return_token_type_ids: + encoding_dict["token_type_ids"].append(e.type_ids) + if return_attention_mask: + encoding_dict["attention_mask"].append(e.attention_mask) + if return_special_tokens_mask: + encoding_dict["special_tokens_mask"].append(e.special_tokens_mask) + if return_offsets_mapping: + encoding_dict["offset_mapping"].append(e.offsets) + if return_length: + encoding_dict["length"].append(len(e.ids)) + + return encoding_dict, encodings + + def encode( + self, + text, + text_pair=None, + return_token_type_ids=False, + add_special_tokens=True, + is_split_into_words=False, + ): + batched_input = text + self.set_truncation_and_padding( + padding_strategy=PaddingStrategy.LONGEST, + truncation_strategy=TruncationStrategy.LONGEST_FIRST, + max_length=self.max_seq_len, + stride=0, + pad_to_multiple_of=None, + ) + encodings = self.tokenizer.encode_batch( + batched_input, + add_special_tokens=add_special_tokens, + is_pretokenized=is_split_into_words, + ) + + tokens_and_encodings = [ + self._convert_encoding( + encoding=encoding, + return_token_type_ids=False, + return_attention_mask=None, + return_overflowing_tokens=False, + return_special_tokens_mask=False, + return_offsets_mapping=False, + return_length=False, + verbose=True, + ) + for encoding in encodings + ] + sanitized_tokens = {} + for key in tokens_and_encodings[0][0].keys(): + stack = [e for item, _ in tokens_and_encodings for e in item[key]] + sanitized_tokens[key] = stack + return sanitized_tokens + + def __call__(self, data): + eqs = data["label"] + topk = self.encode([eqs]) + for k, p in zip(topk, [[self.bos_token_id, self.eos_token_id], [1, 1]]): + process_seq = [x for x in topk[k]] + max_length = 0 + for seq in process_seq: + max_length = max(max_length, len(seq)) + data["label"] = np.array(topk["input_ids"]).astype(np.int64)[0] + data["attention_mask"] = np.array(topk["attention_mask"]).astype(np.int64)[0] + return data diff --git a/ppocr/data/imaug/latex_ocr_aug.py b/ppocr/data/imaug/latex_ocr_aug.py new file mode 100644 index 0000000..6417dab --- /dev/null +++ b/ppocr/data/imaug/latex_ocr_aug.py @@ -0,0 +1,183 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is refer from: +https://github.com/lukas-blecher/LaTeX-OCR/blob/main/pix2tex/dataset/transforms.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import os + +os.environ["NO_ALBUMENTATIONS_UPDATE"] = "1" + +import math +import cv2 +import numpy as np +import albumentations as A +from PIL import Image + + +class LatexTrainTransform: + def __init__(self, bitmap_prob=0.04, **kwargs): + # your init code + self.bitmap_prob = bitmap_prob + self.train_transform = A.Compose( + [ + A.Compose( + [ + A.ShiftScaleRotate( + shift_limit=0, + scale_limit=(-0.15, 0), + rotate_limit=1, + border_mode=0, + interpolation=3, + value=[255, 255, 255], + p=1, + ), + A.GridDistortion( + distort_limit=0.1, + border_mode=0, + interpolation=3, + value=[255, 255, 255], + p=0.5, + ), + ], + p=0.15, + ), + A.RGBShift(r_shift_limit=15, g_shift_limit=15, b_shift_limit=15, p=0.3), + A.GaussNoise(10, p=0.2), + A.RandomBrightnessContrast(0.05, (-0.2, 0), True, p=0.2), + A.ImageCompression(95, p=0.3), + A.ToGray(always_apply=True), + ] + ) + + def __call__(self, data): + img = data["image"] + if np.random.random() < self.bitmap_prob: + img[img != 255] = 0 + img = self.train_transform(image=img)["image"] + data["image"] = img + return data + + +class LatexTestTransform: + def __init__(self, **kwargs): + # your init code + self.test_transform = A.Compose( + [ + A.ToGray(always_apply=True), + ] + ) + + def __call__(self, data): + img = data["image"] + img = self.test_transform(image=img)["image"] + data["image"] = img + return data + + +class MinMaxResize: + def __init__(self, min_dimensions=[32, 32], max_dimensions=[672, 192], **kwargs): + # your init code + self.min_dimensions = min_dimensions + self.max_dimensions = max_dimensions + # pass + + def pad_(self, img, divable=32): + threshold = 128 + data = np.array(img.convert("LA")) + if data[..., -1].var() == 0: + data = (data[..., 0]).astype(np.uint8) + else: + data = (255 - data[..., -1]).astype(np.uint8) + data = (data - data.min()) / (data.max() - data.min()) * 255 + if data.mean() > threshold: + # To invert the text to white + gray = 255 * (data < threshold).astype(np.uint8) + else: + gray = 255 * (data > threshold).astype(np.uint8) + data = 255 - data + + coords = cv2.findNonZero(gray) # Find all non-zero points (text) + a, b, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box + rect = data[b : b + h, a : a + w] + im = Image.fromarray(rect).convert("L") + dims = [] + for x in [w, h]: + div, mod = divmod(x, divable) + dims.append(divable * (div + (1 if mod > 0 else 0))) + padded = Image.new("L", dims, 255) + padded.paste(im, (0, 0, im.size[0], im.size[1])) + return padded + + def minmax_size_(self, img, max_dimensions, min_dimensions): + if max_dimensions is not None: + ratios = [a / b for a, b in zip(img.size, max_dimensions)] + if any([r > 1 for r in ratios]): + size = np.array(img.size) // max(ratios) + img = img.resize(tuple(size.astype(int)), Image.BILINEAR) + if min_dimensions is not None: + # hypothesis: there is a dim in img smaller than min_dimensions, and return a proper dim >= min_dimensions + padded_size = [ + max(img_dim, min_dim) + for img_dim, min_dim in zip(img.size, min_dimensions) + ] + if padded_size != list(img.size): # assert hypothesis + padded_im = Image.new("L", padded_size, 255) + padded_im.paste(img, img.getbbox()) + img = padded_im + return img + + def __call__(self, data): + img = data["image"] + h, w = img.shape[:2] + if ( + self.min_dimensions[0] <= w <= self.max_dimensions[0] + and self.min_dimensions[1] <= h <= self.max_dimensions[1] + ): + return data + else: + im = Image.fromarray(np.uint8(img)) + im = self.minmax_size_( + self.pad_(im), self.max_dimensions, self.min_dimensions + ) + im = np.array(im) + im = np.dstack((im, im, im)) + data["image"] = im + return data + + +class LatexImageFormat: + def __init__(self, **kwargs): + # your init code + pass + + def __call__(self, data): + img = data["image"] + im_h, im_w = img.shape[:2] + divide_h = math.ceil(im_h / 16) * 16 + divide_w = math.ceil(im_w / 16) * 16 + img = img[:, :, 0] + img = np.pad( + img, ((0, divide_h - im_h), (0, divide_w - im_w)), constant_values=(1, 1) + ) + img_expanded = img[:, :, np.newaxis].transpose(2, 0, 1) + data["image"] = img_expanded + return data diff --git a/ppocr/data/imaug/make_border_map.py b/ppocr/data/imaug/make_border_map.py new file mode 100644 index 0000000..537c8b4 --- /dev/null +++ b/ppocr/data/imaug/make_border_map.py @@ -0,0 +1,191 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/WenmuZhou/DBNet.pytorch/blob/master/data_loader/modules/make_border_map.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy as np +import cv2 + +np.seterr(divide="ignore", invalid="ignore") +import pyclipper +from shapely.geometry import Polygon +import sys +import warnings + +warnings.simplefilter("ignore") + +__all__ = ["MakeBorderMap"] + + +class MakeBorderMap(object): + def __init__(self, shrink_ratio=0.4, thresh_min=0.3, thresh_max=0.7, **kwargs): + self.shrink_ratio = shrink_ratio + self.thresh_min = thresh_min + self.thresh_max = thresh_max + if "total_epoch" in kwargs: + self._base_shrink_ratio = shrink_ratio + self._total_epoch = kwargs["total_epoch"] + + def __call__(self, data): + img = data["image"] + text_polys = data["polys"] + ignore_tags = data["ignore_tags"] + + shrink_ratio = self.shrink_ratio + if "epoch" in data and hasattr(self, "_base_shrink_ratio"): + shrink_ratio = self._base_shrink_ratio + 0.2 * data["epoch"] / float( + self._total_epoch + ) + + canvas = np.zeros(img.shape[:2], dtype=np.float32) + mask = np.zeros(img.shape[:2], dtype=np.float32) + + for i in range(len(text_polys)): + if ignore_tags[i]: + continue + self.draw_border_map( + text_polys[i], canvas, mask=mask, shrink_ratio=shrink_ratio + ) + canvas = canvas * (self.thresh_max - self.thresh_min) + self.thresh_min + + data["threshold_map"] = canvas + data["threshold_mask"] = mask + return data + + def draw_border_map(self, polygon, canvas, mask, shrink_ratio=None): + if shrink_ratio is None: + shrink_ratio = self.shrink_ratio + polygon = np.array(polygon) + assert polygon.ndim == 2 + assert polygon.shape[1] == 2 + + if np.isnan(polygon).any(): + return + polygon_shape = Polygon(polygon) + if polygon_shape.area <= 0: + return + distance = ( + polygon_shape.area * (1 - np.power(shrink_ratio, 2)) / polygon_shape.length + ) + subject = [tuple(l) for l in polygon] + padding = pyclipper.PyclipperOffset() + padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + + padded_polygon = padding.Execute(distance) + if not padded_polygon: + return + padded_polygon = np.array(padded_polygon[0]) + cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0) + + xmin = padded_polygon[:, 0].min() + xmax = padded_polygon[:, 0].max() + ymin = padded_polygon[:, 1].min() + ymax = padded_polygon[:, 1].max() + width = xmax - xmin + 1 + height = ymax - ymin + 1 + + polygon[:, 0] = polygon[:, 0] - xmin + polygon[:, 1] = polygon[:, 1] - ymin + + xs = np.broadcast_to( + np.linspace(0, width - 1, num=width).reshape(1, width), (height, width) + ) + ys = np.broadcast_to( + np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width) + ) + + distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32) + for i in range(polygon.shape[0]): + j = (i + 1) % polygon.shape[0] + absolute_distance = self._distance(xs, ys, polygon[i], polygon[j]) + distance_map[i] = np.clip(absolute_distance / distance, 0, 1) + distance_map = distance_map.min(axis=0) + + xmin_valid = min(max(0, xmin), canvas.shape[1] - 1) + xmax_valid = min(max(0, xmax), canvas.shape[1] - 1) + ymin_valid = min(max(0, ymin), canvas.shape[0] - 1) + ymax_valid = min(max(0, ymax), canvas.shape[0] - 1) + canvas[ymin_valid : ymax_valid + 1, xmin_valid : xmax_valid + 1] = np.fmax( + 1 + - distance_map[ + ymin_valid - ymin : ymax_valid - ymax + height, + xmin_valid - xmin : xmax_valid - xmax + width, + ], + canvas[ymin_valid : ymax_valid + 1, xmin_valid : xmax_valid + 1], + ) + + def _distance(self, xs, ys, point_1, point_2): + """ + compute the distance from point to a line + ys: coordinates in the first axis + xs: coordinates in the second axis + point_1, point_2: (x, y), the end of the line + """ + height, width = xs.shape[:2] + square_distance_1 = np.square(xs - point_1[0]) + np.square(ys - point_1[1]) + square_distance_2 = np.square(xs - point_2[0]) + np.square(ys - point_2[1]) + square_distance = np.square(point_1[0] - point_2[0]) + np.square( + point_1[1] - point_2[1] + ) + + cosin = (square_distance - square_distance_1 - square_distance_2) / ( + 2 * np.sqrt(square_distance_1 * square_distance_2) + ) + square_sin = 1 - np.square(cosin) + square_sin = np.nan_to_num(square_sin) + result = np.sqrt( + square_distance_1 * square_distance_2 * square_sin / square_distance + ) + + result[cosin < 0] = np.sqrt(np.fmin(square_distance_1, square_distance_2))[ + cosin < 0 + ] + # self.extend_line(point_1, point_2, result) + return result + + def extend_line(self, point_1, point_2, result, shrink_ratio): + ex_point_1 = ( + int(round(point_1[0] + (point_1[0] - point_2[0]) * (1 + shrink_ratio))), + int(round(point_1[1] + (point_1[1] - point_2[1]) * (1 + shrink_ratio))), + ) + cv2.line( + result, + tuple(ex_point_1), + tuple(point_1), + 4096.0, + 1, + lineType=cv2.LINE_AA, + shift=0, + ) + ex_point_2 = ( + int(round(point_2[0] + (point_2[0] - point_1[0]) * (1 + shrink_ratio))), + int(round(point_2[1] + (point_2[1] - point_1[1]) * (1 + shrink_ratio))), + ) + cv2.line( + result, + tuple(ex_point_2), + tuple(point_2), + 4096.0, + 1, + lineType=cv2.LINE_AA, + shift=0, + ) + return ex_point_1, ex_point_2 diff --git a/ppocr/data/imaug/make_pse_gt.py b/ppocr/data/imaug/make_pse_gt.py new file mode 100644 index 0000000..7be1bb8 --- /dev/null +++ b/ppocr/data/imaug/make_pse_gt.py @@ -0,0 +1,104 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import cv2 +import numpy as np +import pyclipper +from shapely.geometry import Polygon + +__all__ = ["MakePseGt"] + + +class MakePseGt(object): + def __init__(self, kernel_num=7, size=640, min_shrink_ratio=0.4, **kwargs): + self.kernel_num = kernel_num + self.min_shrink_ratio = min_shrink_ratio + self.size = size + + def __call__(self, data): + image = data["image"] + text_polys = data["polys"] + ignore_tags = data["ignore_tags"] + + h, w, _ = image.shape + short_edge = min(h, w) + if short_edge < self.size: + # keep short_size >= self.size + scale = self.size / short_edge + image = cv2.resize(image, dsize=None, fx=scale, fy=scale) + text_polys *= scale + + gt_kernels = [] + for i in range(1, self.kernel_num + 1): + # s1->sn, from big to small + rate = 1.0 - (1.0 - self.min_shrink_ratio) / (self.kernel_num - 1) * i + text_kernel, ignore_tags = self.generate_kernel( + image.shape[0:2], rate, text_polys, ignore_tags + ) + gt_kernels.append(text_kernel) + + training_mask = np.ones(image.shape[0:2], dtype="uint8") + for i in range(text_polys.shape[0]): + if ignore_tags[i]: + cv2.fillPoly( + training_mask, text_polys[i].astype(np.int32)[np.newaxis, :, :], 0 + ) + + gt_kernels = np.array(gt_kernels) + gt_kernels[gt_kernels > 0] = 1 + + data["image"] = image + data["polys"] = text_polys + data["gt_kernels"] = gt_kernels[0:] + data["gt_text"] = gt_kernels[0] + data["mask"] = training_mask.astype("float32") + return data + + def generate_kernel(self, img_size, shrink_ratio, text_polys, ignore_tags=None): + """ + Refer to part of the code: + https://github.com/open-mmlab/mmocr/blob/main/mmocr/datasets/pipelines/textdet_targets/base_textdet_targets.py + """ + + h, w = img_size + text_kernel = np.zeros((h, w), dtype=np.float32) + for i, poly in enumerate(text_polys): + polygon = Polygon(poly) + distance = ( + polygon.area + * (1 - shrink_ratio * shrink_ratio) + / (polygon.length + 1e-6) + ) + subject = [tuple(l) for l in poly] + pco = pyclipper.PyclipperOffset() + pco.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + shrunk = np.array(pco.Execute(-distance)) + + if len(shrunk) == 0 or shrunk.size == 0: + if ignore_tags is not None: + ignore_tags[i] = True + continue + try: + shrunk = np.array(shrunk[0]).reshape(-1, 2) + except: + if ignore_tags is not None: + ignore_tags[i] = True + continue + cv2.fillPoly(text_kernel, [shrunk.astype(np.int32)], i + 1) + return text_kernel, ignore_tags diff --git a/ppocr/data/imaug/make_shrink_map.py b/ppocr/data/imaug/make_shrink_map.py new file mode 100644 index 0000000..c7bcc87 --- /dev/null +++ b/ppocr/data/imaug/make_shrink_map.py @@ -0,0 +1,132 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/WenmuZhou/DBNet.pytorch/blob/master/data_loader/modules/make_shrink_map.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy as np +import cv2 +from shapely.geometry import Polygon +import pyclipper + +__all__ = ["MakeShrinkMap"] + + +class MakeShrinkMap(object): + r""" + Making binary mask from detection data with ICDAR format. + Typically following the process of class `MakeICDARData`. + """ + + def __init__(self, min_text_size=8, shrink_ratio=0.4, **kwargs): + self.min_text_size = min_text_size + self.shrink_ratio = shrink_ratio + if "total_epoch" in kwargs: + self._base_shrink_ratio = shrink_ratio + self._total_epoch = kwargs["total_epoch"] + + def __call__(self, data): + image = data["image"] + text_polys = data["polys"] + ignore_tags = data["ignore_tags"] + + h, w = image.shape[:2] + text_polys, ignore_tags = self.validate_polygons(text_polys, ignore_tags, h, w) + gt = np.zeros((h, w), dtype=np.float32) + mask = np.ones((h, w), dtype=np.float32) + for i in range(len(text_polys)): + polygon = text_polys[i] + height = max(polygon[:, 1]) - min(polygon[:, 1]) + width = max(polygon[:, 0]) - min(polygon[:, 0]) + if ignore_tags[i] or min(height, width) < self.min_text_size: + cv2.fillPoly(mask, polygon.astype(np.int32)[np.newaxis, :, :], 0) + ignore_tags[i] = True + else: + if np.isnan(polygon).any(): + ignore_tags[i] = True + continue + polygon_shape = Polygon(polygon) + subject = [tuple(l) for l in polygon] + padding = pyclipper.PyclipperOffset() + padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + shrunk = [] + + # Increase the shrink ratio every time we get multiple polygon returned back + shrink_ratio = self.shrink_ratio + if "epoch" in data and hasattr(self, "_base_shrink_ratio"): + shrink_ratio = self._base_shrink_ratio + 0.2 * data[ + "epoch" + ] / float(self._total_epoch) + possible_ratios = np.arange(shrink_ratio, 1, shrink_ratio) + np.append(possible_ratios, 1) + # print(possible_ratios) + for ratio in possible_ratios: + # print(f"Change shrink ratio to {ratio}") + distance = ( + polygon_shape.area + * (1 - np.power(ratio, 2)) + / polygon_shape.length + ) + shrunk = padding.Execute(-distance) + if len(shrunk) == 1: + break + + if shrunk == []: + cv2.fillPoly(mask, polygon.astype(np.int32)[np.newaxis, :, :], 0) + ignore_tags[i] = True + continue + + for each_shrink in shrunk: + shrink = np.array(each_shrink).reshape(-1, 2) + cv2.fillPoly(gt, [shrink.astype(np.int32)], 1) + + data["shrink_map"] = gt + data["shrink_mask"] = mask + return data + + def validate_polygons(self, polygons, ignore_tags, h, w): + """ + polygons (numpy.array, required): of shape (num_instances, num_points, 2) + """ + if len(polygons) == 0: + return polygons, ignore_tags + assert len(polygons) == len(ignore_tags) + for polygon in polygons: + polygon[:, 0] = np.clip(polygon[:, 0], 0, w - 1) + polygon[:, 1] = np.clip(polygon[:, 1], 0, h - 1) + + for i in range(len(polygons)): + area = self.polygon_area(polygons[i]) + if abs(area) < 1: + ignore_tags[i] = True + if area > 0: + polygons[i] = polygons[i][::-1, :] + return polygons, ignore_tags + + def polygon_area(self, polygon): + """ + compute polygon area + """ + area = 0 + q = polygon[-1] + for p in polygon: + area += p[0] * q[1] - p[1] * q[0] + q = p + return area / 2.0 diff --git a/ppocr/data/imaug/operators.py b/ppocr/data/imaug/operators.py new file mode 100644 index 0000000..79b7a86 --- /dev/null +++ b/ppocr/data/imaug/operators.py @@ -0,0 +1,692 @@ +""" +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import sys +import cv2 +import numpy as np +import math +import random +from PIL import Image +from paddle import get_device + + +class DecodeImage(object): + """decode image""" + + def __init__( + self, img_mode="RGB", channel_first=False, ignore_orientation=False, **kwargs + ): + self.img_mode = img_mode + self.channel_first = channel_first + self.ignore_orientation = ignore_orientation + + def __call__(self, data): + img = data["image"] + assert type(img) is bytes and len(img) > 0, "invalid input 'img' in DecodeImage" + img = np.frombuffer(img, dtype="uint8") + if self.img_mode == "GRAY": + # For GRAY mode, decode directly to a single-channel grayscale image. + decode_flag = cv2.IMREAD_GRAYSCALE + else: + # For RGB mode, decode to a 3-channel color image. + decode_flag = cv2.IMREAD_COLOR + + if self.ignore_orientation: + decode_flag |= cv2.IMREAD_IGNORE_ORIENTATION + + img = cv2.imdecode(img, decode_flag) + + if img is None: + return None + if self.img_mode == "GRAY": + img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) + elif self.img_mode == "RGB": + assert img.shape[2] == 3, "invalid shape of image[%s]" % (img.shape) + img = img[:, :, ::-1] + + if self.channel_first: + img = img.transpose((2, 0, 1)) + + data["image"] = img + return data + + +class NormalizeImage(object): + """normalize image such as subtract mean, divide std""" + + def __init__(self, scale=None, mean=None, std=None, order="chw", **kwargs): + if isinstance(scale, str): + scale = eval(scale) + self.scale = np.float32(scale if scale is not None else 1.0 / 255.0) + mean = mean if mean is not None else [0.485, 0.456, 0.406] + std = std if std is not None else [0.229, 0.224, 0.225] + + shape = (3, 1, 1) if order == "chw" else (1, 1, 3) + self.mean = np.array(mean).reshape(shape).astype("float32") + self.std = np.array(std).reshape(shape).astype("float32") + + def __call__(self, data): + img = data["image"] + from PIL import Image + + if isinstance(img, Image.Image): + img = np.array(img) + assert isinstance(img, np.ndarray), "invalid input 'img' in NormalizeImage" + data["image"] = (img.astype("float32") * self.scale - self.mean) / self.std + return data + + +class ToCHWImage(object): + """convert hwc image to chw image""" + + def __init__(self, **kwargs): + pass + + def __call__(self, data): + img = data["image"] + from PIL import Image + + if isinstance(img, Image.Image): + img = np.array(img) + data["image"] = img.transpose((2, 0, 1)) + return data + + +class Fasttext(object): + def __init__(self, path="None", **kwargs): + import fasttext + + self.fast_model = fasttext.load_model(path) + + def __call__(self, data): + label = data["label"] + fast_label = self.fast_model[label] + data["fast_label"] = fast_label + return data + + +class KeepKeys(object): + def __init__(self, keep_keys, **kwargs): + self.keep_keys = keep_keys + + def __call__(self, data): + data_list = [] + for key in self.keep_keys: + data_list.append(data[key]) + return data_list + + +class Pad(object): + def __init__(self, size=None, size_div=32, **kwargs): + if size is not None and not isinstance(size, (int, list, tuple)): + raise TypeError( + "Type of target_size is invalid. Now is {}".format(type(size)) + ) + if isinstance(size, int): + size = [size, size] + self.size = size + self.size_div = size_div + + def __call__(self, data): + img = data["image"] + img_h, img_w = img.shape[0], img.shape[1] + if self.size: + resize_h2, resize_w2 = self.size + assert ( + img_h < resize_h2 and img_w < resize_w2 + ), "(h, w) of target size should be greater than (img_h, img_w)" + else: + resize_h2 = max( + int(math.ceil(img.shape[0] / self.size_div) * self.size_div), + self.size_div, + ) + resize_w2 = max( + int(math.ceil(img.shape[1] / self.size_div) * self.size_div), + self.size_div, + ) + img = cv2.copyMakeBorder( + img, + 0, + resize_h2 - img_h, + 0, + resize_w2 - img_w, + cv2.BORDER_CONSTANT, + value=0, + ) + data["image"] = img + return data + + +class Resize(object): + def __init__(self, size=(640, 640), **kwargs): + self.size = size + + def resize_image(self, img): + resize_h, resize_w = self.size + ori_h, ori_w = img.shape[:2] # (h, w, c) + ratio_h = float(resize_h) / ori_h + ratio_w = float(resize_w) / ori_w + img = cv2.resize(img, (int(resize_w), int(resize_h))) + return img, [ratio_h, ratio_w] + + def __call__(self, data): + img = data["image"] + if "polys" in data: + text_polys = data["polys"] + + img_resize, [ratio_h, ratio_w] = self.resize_image(img) + if "polys" in data: + new_boxes = [] + for box in text_polys: + new_box = [] + for cord in box: + new_box.append([cord[0] * ratio_w, cord[1] * ratio_h]) + new_boxes.append(new_box) + data["polys"] = np.array(new_boxes, dtype=np.float32) + data["image"] = img_resize + return data + + +class DetResizeForTest(object): + def __init__(self, **kwargs): + super(DetResizeForTest, self).__init__() + self.resize_type = 0 + self.keep_ratio = False + self.max_side_limit = kwargs.get("max_side_limit", 4000) + if "image_shape" in kwargs: + self.image_shape = kwargs["image_shape"] + self.resize_type = 1 + if "keep_ratio" in kwargs: + self.keep_ratio = kwargs["keep_ratio"] + elif "limit_side_len" in kwargs: + self.limit_side_len = kwargs["limit_side_len"] + self.limit_type = kwargs.get("limit_type", "min") + elif "resize_long" in kwargs: + self.resize_type = 2 + self.resize_long = kwargs.get("resize_long", 960) + else: + self.limit_side_len = 736 + self.limit_type = "min" + + def __call__(self, data): + img = data["image"] + src_h, src_w, _ = img.shape + if sum([src_h, src_w]) < 64: + img = self.image_padding(img) + + if self.resize_type == 0: + # img, shape = self.resize_image_type0(img) + img, [ratio_h, ratio_w] = self.resize_image_type0(img) + elif self.resize_type == 2: + img, [ratio_h, ratio_w] = self.resize_image_type2(img) + else: + # img, shape = self.resize_image_type1(img) + img, [ratio_h, ratio_w] = self.resize_image_type1(img) + data["image"] = img + data["shape"] = np.array([src_h, src_w, ratio_h, ratio_w]) + if "iluvatar_gpu" in get_device(): + data["shape"] = np.array([src_h, src_w, ratio_h, ratio_w]).astype( + np.float32 + ) + return data + + def image_padding(self, im, value=0): + h, w, c = im.shape + im_pad = np.zeros((max(32, h), max(32, w), c), np.uint8) + value + im_pad[:h, :w, :] = im + return im_pad + + def resize_image_type1(self, img): + resize_h, resize_w = self.image_shape + ori_h, ori_w = img.shape[:2] # (h, w, c) + if self.keep_ratio is True: + resize_w = ori_w * resize_h / ori_h + N = math.ceil(resize_w / 32) + resize_w = N * 32 + ratio_h = float(resize_h) / ori_h + ratio_w = float(resize_w) / ori_w + img = cv2.resize(img, (int(resize_w), int(resize_h))) + # return img, np.array([ori_h, ori_w]) + return img, [ratio_h, ratio_w] + + def resize_image_type0(self, img): + """ + resize image to a size multiple of 32 which is required by the network + args: + img(array): array with shape [h, w, c] + return(tuple): + img, (ratio_h, ratio_w) + """ + limit_side_len = self.limit_side_len + h, w, c = img.shape + + # limit the max side + if self.limit_type == "max": + if max(h, w) > limit_side_len: + if h > w: + ratio = float(limit_side_len) / h + else: + ratio = float(limit_side_len) / w + else: + ratio = 1.0 + elif self.limit_type == "min": + if min(h, w) < limit_side_len: + if h < w: + ratio = float(limit_side_len) / h + else: + ratio = float(limit_side_len) / w + else: + ratio = 1.0 + elif self.limit_type == "resize_long": + ratio = float(limit_side_len) / max(h, w) + else: + raise Exception("not support limit type, image ") + resize_h = int(h * ratio) + resize_w = int(w * ratio) + if max(resize_h, resize_w) > self.max_side_limit: + print( + f"Resized image size ({resize_h}x{resize_w}) exceeds max_side_limit of {self.max_side_limit}. " + f"Resizing to fit within limit." + ) + ratio = float(self.max_side_limit) / max(resize_h, resize_w) + resize_h, resize_w = int(resize_h * ratio), int(resize_w * ratio) + + resize_h = max(int(round(resize_h / 32) * 32), 32) + resize_w = max(int(round(resize_w / 32) * 32), 32) + + try: + if int(resize_w) <= 0 or int(resize_h) <= 0: + return None, (None, None) + img = cv2.resize(img, (int(resize_w), int(resize_h))) + except: + print(img.shape, resize_w, resize_h) + sys.exit(0) + ratio_h = resize_h / float(h) + ratio_w = resize_w / float(w) + return img, [ratio_h, ratio_w] + + def resize_image_type2(self, img): + h, w, _ = img.shape + + resize_w = w + resize_h = h + + if resize_h > resize_w: + ratio = float(self.resize_long) / resize_h + else: + ratio = float(self.resize_long) / resize_w + + resize_h = int(resize_h * ratio) + resize_w = int(resize_w * ratio) + + max_stride = 128 + resize_h = (resize_h + max_stride - 1) // max_stride * max_stride + resize_w = (resize_w + max_stride - 1) // max_stride * max_stride + img = cv2.resize(img, (int(resize_w), int(resize_h))) + ratio_h = resize_h / float(h) + ratio_w = resize_w / float(w) + + return img, [ratio_h, ratio_w] + + +class E2EResizeForTest(object): + def __init__(self, **kwargs): + super(E2EResizeForTest, self).__init__() + self.max_side_len = kwargs["max_side_len"] + self.valid_set = kwargs["valid_set"] + + def __call__(self, data): + img = data["image"] + src_h, src_w, _ = img.shape + if self.valid_set == "totaltext": + im_resized, [ratio_h, ratio_w] = self.resize_image_for_totaltext( + img, max_side_len=self.max_side_len + ) + else: + im_resized, (ratio_h, ratio_w) = self.resize_image( + img, max_side_len=self.max_side_len + ) + data["image"] = im_resized + data["shape"] = np.array([src_h, src_w, ratio_h, ratio_w]) + return data + + def resize_image_for_totaltext(self, im, max_side_len=512): + h, w, _ = im.shape + resize_w = w + resize_h = h + ratio = 1.25 + if h * ratio > max_side_len: + ratio = float(max_side_len) / resize_h + resize_h = int(resize_h * ratio) + resize_w = int(resize_w * ratio) + + max_stride = 128 + resize_h = (resize_h + max_stride - 1) // max_stride * max_stride + resize_w = (resize_w + max_stride - 1) // max_stride * max_stride + im = cv2.resize(im, (int(resize_w), int(resize_h))) + ratio_h = resize_h / float(h) + ratio_w = resize_w / float(w) + return im, (ratio_h, ratio_w) + + def resize_image(self, im, max_side_len=512): + """ + resize image to a size multiple of max_stride which is required by the network + :param im: the resized image + :param max_side_len: limit of max image size to avoid out of memory in gpu + :return: the resized image and the resize ratio + """ + h, w, _ = im.shape + + resize_w = w + resize_h = h + + # Fix the longer side + if resize_h > resize_w: + ratio = float(max_side_len) / resize_h + else: + ratio = float(max_side_len) / resize_w + + resize_h = int(resize_h * ratio) + resize_w = int(resize_w * ratio) + + max_stride = 128 + resize_h = (resize_h + max_stride - 1) // max_stride * max_stride + resize_w = (resize_w + max_stride - 1) // max_stride * max_stride + im = cv2.resize(im, (int(resize_w), int(resize_h))) + ratio_h = resize_h / float(h) + ratio_w = resize_w / float(w) + + return im, (ratio_h, ratio_w) + + +class KieResize(object): + def __init__(self, **kwargs): + super(KieResize, self).__init__() + self.max_side, self.min_side = kwargs["img_scale"][0], kwargs["img_scale"][1] + + def __call__(self, data): + img = data["image"] + points = data["points"] + src_h, src_w, _ = img.shape + ( + im_resized, + scale_factor, + [ratio_h, ratio_w], + [new_h, new_w], + ) = self.resize_image(img) + resize_points = self.resize_boxes(img, points, scale_factor) + data["ori_image"] = img + data["ori_boxes"] = points + data["points"] = resize_points + data["image"] = im_resized + data["shape"] = np.array([new_h, new_w]) + return data + + def resize_image(self, img): + norm_img = np.zeros([1024, 1024, 3], dtype="float32") + scale = [512, 1024] + h, w = img.shape[:2] + max_long_edge = max(scale) + max_short_edge = min(scale) + scale_factor = min(max_long_edge / max(h, w), max_short_edge / min(h, w)) + resize_w, resize_h = int(w * float(scale_factor) + 0.5), int( + h * float(scale_factor) + 0.5 + ) + max_stride = 32 + resize_h = (resize_h + max_stride - 1) // max_stride * max_stride + resize_w = (resize_w + max_stride - 1) // max_stride * max_stride + im = cv2.resize(img, (resize_w, resize_h)) + new_h, new_w = im.shape[:2] + w_scale = new_w / w + h_scale = new_h / h + scale_factor = np.array([w_scale, h_scale, w_scale, h_scale], dtype=np.float32) + norm_img[:new_h, :new_w, :] = im + return norm_img, scale_factor, [h_scale, w_scale], [new_h, new_w] + + def resize_boxes(self, im, points, scale_factor): + points = points * scale_factor + img_shape = im.shape[:2] + points[:, 0::2] = np.clip(points[:, 0::2], 0, img_shape[1]) + points[:, 1::2] = np.clip(points[:, 1::2], 0, img_shape[0]) + return points + + +class SRResize(object): + def __init__( + self, + imgH=32, + imgW=128, + down_sample_scale=4, + keep_ratio=False, + min_ratio=1, + mask=False, + infer_mode=False, + **kwargs, + ): + self.imgH = imgH + self.imgW = imgW + self.keep_ratio = keep_ratio + self.min_ratio = min_ratio + self.down_sample_scale = down_sample_scale + self.mask = mask + self.infer_mode = infer_mode + + def __call__(self, data): + imgH = self.imgH + imgW = self.imgW + images_lr = data["image_lr"] + transform2 = ResizeNormalize( + (imgW // self.down_sample_scale, imgH // self.down_sample_scale) + ) + images_lr = transform2(images_lr) + data["img_lr"] = images_lr + if self.infer_mode: + return data + + images_HR = data["image_hr"] + label_strs = data["label"] + transform = ResizeNormalize((imgW, imgH)) + images_HR = transform(images_HR) + data["img_hr"] = images_HR + return data + + +class ResizeNormalize(object): + def __init__(self, size, interpolation=Image.BICUBIC): + self.size = size + self.interpolation = interpolation + + def __call__(self, img): + img = img.resize(self.size, self.interpolation) + img_numpy = np.array(img).astype("float32") + img_numpy = img_numpy.transpose((2, 0, 1)) / 255 + return img_numpy + + +class GrayImageChannelFormat(object): + """ + format gray scale image's channel: (3,h,w) -> (1,h,w) + Args: + inverse: inverse gray image + """ + + def __init__(self, inverse=False, **kwargs): + self.inverse = inverse + + def __call__(self, data): + img = data["image"] + img_single_channel = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + img_expanded = np.expand_dims(img_single_channel, 0) + + if self.inverse: + data["image"] = np.abs(img_expanded - 1) + else: + data["image"] = img_expanded + + data["src_image"] = img + return data + + +class RandomPerspective(object): + """Random perspective transform for OCR detection training. + + Operates on data["image"] (H,W,C) and data["polys"] (N, P, 2). + Should be placed after IaaAugment and before EastRandomCropData. + """ + + def __init__( + self, + prob=0.3, + degrees=0.0, + scale=0.2, + shear=5.0, + perspective=0.0002, + fit_output=True, + fill_value=(123.675, 116.28, 103.53), + min_area_ratio=0.1, + **kwargs, + ): + self.prob = prob + self.degrees = degrees + self.scale = scale + self.shear = shear + self.perspective = perspective + self.fit_output = fit_output + self.min_area_ratio = min_area_ratio + if isinstance(fill_value, (int, float)): + fill_value = (fill_value,) * 3 + self.fill_value = tuple(fill_value) + + def __call__(self, data): + if random.random() > self.prob: + return data + + im = data["image"] + h, w = im.shape[:2] + + # Build perspective matrix + M_core = self._get_core_matrix(h, w) + + # Compute output bounds + corners = np.array([[0, 0], [w, 0], [w, h], [0, h]], dtype=np.float32).reshape( + -1, 1, 2 + ) + warped_corners = cv2.perspectiveTransform(corners, M_core) + x_min, y_min = warped_corners.min(axis=0).ravel() + x_max, y_max = warped_corners.max(axis=0).ravel() + + if self.fit_output: + new_w = int(np.ceil(x_max) - np.floor(x_min)) + new_h = int(np.ceil(y_max) - np.floor(y_min)) + T_fit = np.eye(3, dtype=np.float32) + T_fit[0, 2] = -np.floor(x_min) + T_fit[1, 2] = -np.floor(y_min) + M = T_fit @ M_core + target_size = (new_w, new_h) + else: + T_orig = np.eye(3, dtype=np.float32) + T_orig[0, 2] = w / 2.0 + T_orig[1, 2] = h / 2.0 + M = T_orig @ M_core + target_size = (w, h) + + # Warp image + transformed_im = cv2.warpPerspective( + im, + M, + target_size, + flags=cv2.INTER_LINEAR, + borderMode=cv2.BORDER_CONSTANT, + borderValue=self.fill_value, + ) + data["image"] = transformed_im + + # Transform polys + polys = data["polys"] + ignore_tags = data["ignore_tags"] + texts = data["texts"] + + if len(polys) > 0: + # polys: (N, P, 2) — flatten to (N*P, 1, 2) for perspectiveTransform + n = len(polys) + points_per_poly = ( + polys[0].shape[0] if hasattr(polys[0], "shape") else len(polys[0]) + ) + all_points = np.array(polys, dtype=np.float32).reshape(-1, 1, 2) + + warped_pts = cv2.perspectiveTransform(all_points, M) + warped_pts = warped_pts.reshape(n, points_per_poly, 2) + + # Compute original areas for filtering + orig_areas = np.array( + [ + cv2.contourArea(p.astype(np.float32)) + for p in np.array(polys, dtype=np.float32) + ] + ) + new_areas = np.array( + [cv2.contourArea(p.astype(np.float32)) for p in warped_pts] + ) + + tw, th = target_size + # Filter: area ratio + center within bounds + centers_x = warped_pts[:, :, 0].mean(axis=1) + centers_y = warped_pts[:, :, 1].mean(axis=1) + valid = ( + (new_areas > orig_areas * self.min_area_ratio) + & (centers_x > 0) + & (centers_x < tw) + & (centers_y > 0) + & (centers_y < th) + ) + + valid_ids = np.where(valid)[0] + data["polys"] = warped_pts[valid_ids] + data["ignore_tags"] = [ignore_tags[i] for i in valid_ids] + data["texts"] = [texts[i] for i in valid_ids] + + return data + + def _get_core_matrix(self, h, w): + C = np.eye(3, dtype=np.float32) + C[0, 2] = -w / 2 + C[1, 2] = -h / 2 + + # Normalize perspective coefficients using 640px as reference size + # to ensure consistent distortion ratio across different image sizes + ref_size = 640.0 + max_dim = max(h, w) + p_normalized = self.perspective * (ref_size / max_dim) + + P = np.eye(3, dtype=np.float32) + P[2, 0] = random.uniform(-p_normalized, p_normalized) + P[2, 1] = random.uniform(-p_normalized, p_normalized) + + s = random.uniform(1 - self.scale, 1 + self.scale) + a = random.uniform(-self.degrees, self.degrees) + R = np.eye(3, dtype=np.float32) + R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s) + + S = np.eye(3, dtype=np.float32) + S[0, 1] = math.tan(random.uniform(-self.shear, self.shear) * math.pi / 180) + S[1, 0] = math.tan(random.uniform(-self.shear, self.shear) * math.pi / 180) + + return S @ R @ P @ C diff --git a/ppocr/data/imaug/pg_process.py b/ppocr/data/imaug/pg_process.py new file mode 100644 index 0000000..b86590d --- /dev/null +++ b/ppocr/data/imaug/pg_process.py @@ -0,0 +1,1116 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import cv2 +import numpy as np +from skimage.morphology._skeletonize import thin +from ppocr.utils.e2e_utils.extract_textpoint_fast import ( + sort_and_expand_with_direction_v2, +) + +__all__ = ["PGProcessTrain"] + + +class PGProcessTrain(object): + def __init__( + self, + character_dict_path, + max_text_length, + max_text_nums, + tcl_len, + batch_size=14, + use_resize=True, + use_random_crop=False, + min_crop_size=24, + min_text_size=4, + max_text_size=512, + point_gather_mode=None, + **kwargs, + ): + self.tcl_len = tcl_len + self.max_text_length = max_text_length + self.max_text_nums = max_text_nums + self.batch_size = batch_size + if use_random_crop is True: + self.min_crop_size = min_crop_size + self.use_random_crop = use_random_crop + self.min_text_size = min_text_size + self.max_text_size = max_text_size + self.use_resize = use_resize + self.point_gather_mode = point_gather_mode + self.Lexicon_Table = self.get_dict(character_dict_path) + self.pad_num = len(self.Lexicon_Table) + self.img_id = 0 + + def get_dict(self, character_dict_path): + character_str = "" + with open(character_dict_path, "rb") as fin: + lines = fin.readlines() + for line in lines: + line = line.decode("utf-8").strip("\n").strip("\r\n") + character_str += line + dict_character = list(character_str) + return dict_character + + def quad_area(self, poly): + """ + compute area of a polygon + :param poly: + :return: + """ + edge = [ + (poly[1][0] - poly[0][0]) * (poly[1][1] + poly[0][1]), + (poly[2][0] - poly[1][0]) * (poly[2][1] + poly[1][1]), + (poly[3][0] - poly[2][0]) * (poly[3][1] + poly[2][1]), + (poly[0][0] - poly[3][0]) * (poly[0][1] + poly[3][1]), + ] + return np.sum(edge) / 2.0 + + def gen_quad_from_poly(self, poly): + """ + Generate min area quad from poly. + """ + point_num = poly.shape[0] + min_area_quad = np.zeros((4, 2), dtype=np.float32) + rect = cv2.minAreaRect( + poly.astype(np.int32) + ) # (center (x,y), (width, height), angle of rotation) + box = np.array(cv2.boxPoints(rect)) + + first_point_idx = 0 + min_dist = 1e4 + for i in range(4): + dist = ( + np.linalg.norm(box[(i + 0) % 4] - poly[0]) + + np.linalg.norm(box[(i + 1) % 4] - poly[point_num // 2 - 1]) + + np.linalg.norm(box[(i + 2) % 4] - poly[point_num // 2]) + + np.linalg.norm(box[(i + 3) % 4] - poly[-1]) + ) + if dist < min_dist: + min_dist = dist + first_point_idx = i + for i in range(4): + min_area_quad[i] = box[(first_point_idx + i) % 4] + + return min_area_quad + + def check_and_validate_polys(self, polys, tags, im_size): + """ + check so that the text poly is in the same direction, + and also filter some invalid polygons + :param polys: + :param tags: + :return: + """ + (h, w) = im_size + if polys.shape[0] == 0: + return polys, np.array([]), np.array([]) + polys[:, :, 0] = np.clip(polys[:, :, 0], 0, w - 1) + polys[:, :, 1] = np.clip(polys[:, :, 1], 0, h - 1) + + validated_polys = [] + validated_tags = [] + hv_tags = [] + for poly, tag in zip(polys, tags): + quad = self.gen_quad_from_poly(poly) + p_area = self.quad_area(quad) + if abs(p_area) < 1: + print("invalid poly") + continue + if p_area > 0: + if tag == False: + print("poly in wrong direction") + tag = True # reversed cases should be ignore + poly = poly[(0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1), :] + quad = quad[(0, 3, 2, 1), :] + + len_w = np.linalg.norm(quad[0] - quad[1]) + np.linalg.norm( + quad[3] - quad[2] + ) + len_h = np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm( + quad[1] - quad[2] + ) + hv_tag = 1 + + if len_w * 2.0 < len_h: + hv_tag = 0 + + validated_polys.append(poly) + validated_tags.append(tag) + hv_tags.append(hv_tag) + return np.array(validated_polys), np.array(validated_tags), np.array(hv_tags) + + def crop_area( + self, im, polys, tags, hv_tags, txts, crop_background=False, max_tries=25 + ): + """ + make random crop from the input image + :param im: + :param polys: [b,4,2] + :param tags: + :param crop_background: + :param max_tries: 50 -> 25 + :return: + """ + h, w, _ = im.shape + pad_h = h // 10 + pad_w = w // 10 + h_array = np.zeros((h + pad_h * 2), dtype=np.int32) + w_array = np.zeros((w + pad_w * 2), dtype=np.int32) + for poly in polys: + poly = np.round(poly, decimals=0).astype(np.int32) + minx = np.min(poly[:, 0]) + maxx = np.max(poly[:, 0]) + w_array[minx + pad_w : maxx + pad_w] = 1 + miny = np.min(poly[:, 1]) + maxy = np.max(poly[:, 1]) + h_array[miny + pad_h : maxy + pad_h] = 1 + # ensure the cropped area not across a text + h_axis = np.where(h_array == 0)[0] + w_axis = np.where(w_array == 0)[0] + if len(h_axis) == 0 or len(w_axis) == 0: + return im, polys, tags, hv_tags, txts + for i in range(max_tries): + xx = np.random.choice(w_axis, size=2) + xmin = np.min(xx) - pad_w + xmax = np.max(xx) - pad_w + xmin = np.clip(xmin, 0, w - 1) + xmax = np.clip(xmax, 0, w - 1) + yy = np.random.choice(h_axis, size=2) + ymin = np.min(yy) - pad_h + ymax = np.max(yy) - pad_h + ymin = np.clip(ymin, 0, h - 1) + ymax = np.clip(ymax, 0, h - 1) + if xmax - xmin < self.min_crop_size or ymax - ymin < self.min_crop_size: + continue + if polys.shape[0] != 0: + poly_axis_in_area = ( + (polys[:, :, 0] >= xmin) + & (polys[:, :, 0] <= xmax) + & (polys[:, :, 1] >= ymin) + & (polys[:, :, 1] <= ymax) + ) + selected_polys = np.where(np.sum(poly_axis_in_area, axis=1) == 4)[0] + else: + selected_polys = [] + if len(selected_polys) == 0: + # no text in this area + if crop_background: + txts_tmp = [] + for selected_poly in selected_polys: + txts_tmp.append(txts[selected_poly]) + txts = txts_tmp + return ( + im[ymin : ymax + 1, xmin : xmax + 1, :], + polys[selected_polys], + tags[selected_polys], + hv_tags[selected_polys], + txts, + ) + else: + continue + im = im[ymin : ymax + 1, xmin : xmax + 1, :] + polys = polys[selected_polys] + tags = tags[selected_polys] + hv_tags = hv_tags[selected_polys] + txts_tmp = [] + for selected_poly in selected_polys: + txts_tmp.append(txts[selected_poly]) + txts = txts_tmp + polys[:, :, 0] -= xmin + polys[:, :, 1] -= ymin + return im, polys, tags, hv_tags, txts + + return im, polys, tags, hv_tags, txts + + def fit_and_gather_tcl_points_v2( + self, + min_area_quad, + poly, + max_h, + max_w, + fixed_point_num=64, + img_id=0, + reference_height=3, + ): + """ + Find the center point of poly as key_points, then fit and gather. + """ + key_point_xys = [] + point_num = poly.shape[0] + for idx in range(point_num // 2): + center_point = (poly[idx] + poly[point_num - 1 - idx]) / 2.0 + key_point_xys.append(center_point) + + tmp_image = np.zeros( + shape=( + max_h, + max_w, + ), + dtype="float32", + ) + cv2.polylines(tmp_image, [np.array(key_point_xys).astype("int32")], False, 1.0) + ys, xs = np.where(tmp_image > 0) + xy_text = np.array(list(zip(xs, ys)), dtype="float32") + + left_center_pt = ((min_area_quad[0] + min_area_quad[3]) / 2.0).reshape(1, 2) + right_center_pt = ((min_area_quad[1] + min_area_quad[2]) / 2.0).reshape(1, 2) + proj_unit_vec = (right_center_pt - left_center_pt) / ( + np.linalg.norm(right_center_pt - left_center_pt) + 1e-6 + ) + proj_unit_vec_tile = np.tile(proj_unit_vec, (xy_text.shape[0], 1)) # (n, 2) + left_center_pt_tile = np.tile(left_center_pt, (xy_text.shape[0], 1)) # (n, 2) + xy_text_to_left_center = xy_text - left_center_pt_tile + proj_value = np.sum(xy_text_to_left_center * proj_unit_vec_tile, axis=1) + xy_text = xy_text[np.argsort(proj_value)] + + # convert to np and keep the num of point not greater then fixed_point_num + pos_info = np.array(xy_text).reshape(-1, 2)[:, ::-1] # xy-> yx + point_num = len(pos_info) + if point_num > fixed_point_num: + keep_ids = [ + int((point_num * 1.0 / fixed_point_num) * x) + for x in range(fixed_point_num) + ] + pos_info = pos_info[keep_ids, :] + + keep = int(min(len(pos_info), fixed_point_num)) + if np.random.rand() < 0.2 and reference_height >= 3: + dl = (np.random.rand(keep) - 0.5) * reference_height * 0.3 + random_float = np.array([1, 0]).reshape([1, 2]) * dl.reshape([keep, 1]) + pos_info += random_float + pos_info[:, 0] = np.clip(pos_info[:, 0], 0, max_h - 1) + pos_info[:, 1] = np.clip(pos_info[:, 1], 0, max_w - 1) + + # padding to fixed length + pos_l = np.zeros((self.tcl_len, 3), dtype=np.int32) + pos_l[:, 0] = np.ones((self.tcl_len,)) * img_id + pos_m = np.zeros((self.tcl_len, 1), dtype=np.float32) + pos_l[:keep, 1:] = np.round(pos_info).astype(np.int32) + pos_m[:keep] = 1.0 + return pos_l, pos_m + + def fit_and_gather_tcl_points_v3( + self, + min_area_quad, + poly, + max_h, + max_w, + fixed_point_num=64, + img_id=0, + reference_height=3, + ): + """ + Find the center point of poly as key_points, then fit and gather. + """ + det_mask = np.zeros( + (int(max_h / self.ds_ratio), int(max_w / self.ds_ratio)) + ).astype(np.float32) + + # score_big_map + cv2.fillPoly(det_mask, np.round(poly / self.ds_ratio).astype(np.int32), 1.0) + det_mask = cv2.resize(det_mask, dsize=None, fx=self.ds_ratio, fy=self.ds_ratio) + det_mask = np.array(det_mask > 1e-3, dtype="float32") + + f_direction = self.f_direction + skeleton_map = thin(det_mask.astype(np.uint8)) + instance_count, instance_label_map = cv2.connectedComponents( + skeleton_map.astype(np.uint8), connectivity=8 + ) + + ys, xs = np.where(instance_label_map == 1) + pos_list = list(zip(ys, xs)) + if len(pos_list) < 3: + return None + pos_list_sorted = sort_and_expand_with_direction_v2( + pos_list, f_direction, det_mask + ) + + pos_list_sorted = np.array(pos_list_sorted) + length = len(pos_list_sorted) - 1 + insert_num = 0 + for index in range(length): + stride_y = np.abs( + pos_list_sorted[index + insert_num][0] + - pos_list_sorted[index + 1 + insert_num][0] + ) + stride_x = np.abs( + pos_list_sorted[index + insert_num][1] + - pos_list_sorted[index + 1 + insert_num][1] + ) + max_points = int(max(stride_x, stride_y)) + + stride = ( + pos_list_sorted[index + insert_num] + - pos_list_sorted[index + 1 + insert_num] + ) / (max_points) + insert_num_temp = max_points - 1 + + for i in range(int(insert_num_temp)): + insert_value = pos_list_sorted[index + insert_num] - (i + 1) * stride + insert_index = index + i + 1 + insert_num + pos_list_sorted = np.insert( + pos_list_sorted, insert_index, insert_value, axis=0 + ) + insert_num += insert_num_temp + + pos_info = ( + np.array(pos_list_sorted).reshape(-1, 2).astype(np.float32) + ) # xy-> yx + + point_num = len(pos_info) + if point_num > fixed_point_num: + keep_ids = [ + int((point_num * 1.0 / fixed_point_num) * x) + for x in range(fixed_point_num) + ] + pos_info = pos_info[keep_ids, :] + + keep = int(min(len(pos_info), fixed_point_num)) + reference_width = ( + np.abs(poly[0, 0, 0] - poly[-1, 1, 0]) + + np.abs(poly[0, 3, 0] - poly[-1, 2, 0]) + ) // 2 + if np.random.rand() < 1: + dh = (np.random.rand(keep) - 0.5) * reference_height + offset = np.random.rand() - 0.5 + dw = np.array([[0, offset * reference_width * 0.2]]) + random_float_h = np.array([1, 0]).reshape([1, 2]) * dh.reshape([keep, 1]) + random_float_w = dw.repeat(keep, axis=0) + pos_info += random_float_h + pos_info += random_float_w + pos_info[:, 0] = np.clip(pos_info[:, 0], 0, max_h - 1) + pos_info[:, 1] = np.clip(pos_info[:, 1], 0, max_w - 1) + + # padding to fixed length + pos_l = np.zeros((self.tcl_len, 3), dtype=np.int32) + pos_l[:, 0] = np.ones((self.tcl_len,)) * img_id + pos_m = np.zeros((self.tcl_len, 1), dtype=np.float32) + pos_l[:keep, 1:] = np.round(pos_info).astype(np.int32) + pos_m[:keep] = 1.0 + return pos_l, pos_m + + def generate_direction_map(self, poly_quads, n_char, direction_map): + """ """ + width_list = [] + height_list = [] + for quad in poly_quads: + quad_w = ( + np.linalg.norm(quad[0] - quad[1]) + np.linalg.norm(quad[2] - quad[3]) + ) / 2.0 + quad_h = ( + np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[2] - quad[1]) + ) / 2.0 + width_list.append(quad_w) + height_list.append(quad_h) + norm_width = max(sum(width_list) / n_char, 1.0) + average_height = max(sum(height_list) / len(height_list), 1.0) + k = 1 + for quad in poly_quads: + direct_vector_full = ((quad[1] + quad[2]) - (quad[0] + quad[3])) / 2.0 + direct_vector = ( + direct_vector_full + / (np.linalg.norm(direct_vector_full) + 1e-6) + * norm_width + ) + direction_label = tuple( + map(float, [direct_vector[0], direct_vector[1], 1.0 / average_height]) + ) + cv2.fillPoly( + direction_map, + quad.round().astype(np.int32)[np.newaxis, :, :], + direction_label, + ) + k += 1 + return direction_map + + def calculate_average_height(self, poly_quads): + """ """ + height_list = [] + for quad in poly_quads: + quad_h = ( + np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[2] - quad[1]) + ) / 2.0 + height_list.append(quad_h) + average_height = max(sum(height_list) / len(height_list), 1.0) + return average_height + + def generate_tcl_ctc_label( + self, + h, + w, + polys, + tags, + text_strs, + ds_ratio, + tcl_ratio=0.3, + shrink_ratio_of_width=0.15, + ): + """ + Generate polygon. + """ + self.ds_ratio = ds_ratio + score_map_big = np.zeros( + ( + h, + w, + ), + dtype=np.float32, + ) + h, w = int(h * ds_ratio), int(w * ds_ratio) + polys = polys * ds_ratio + + score_map = np.zeros( + ( + h, + w, + ), + dtype=np.float32, + ) + score_label_map = np.zeros( + ( + h, + w, + ), + dtype=np.float32, + ) + tbo_map = np.zeros((h, w, 5), dtype=np.float32) + training_mask = np.ones( + ( + h, + w, + ), + dtype=np.float32, + ) + direction_map = np.ones((h, w, 3)) * np.array([0, 0, 1]).reshape( + [1, 1, 3] + ).astype(np.float32) + + label_idx = 0 + score_label_map_text_label_list = [] + pos_list, pos_mask, label_list = [], [], [] + for poly_idx, poly_tag in enumerate(zip(polys, tags)): + poly = poly_tag[0] + tag = poly_tag[1] + + # generate min_area_quad + min_area_quad, center_point = self.gen_min_area_quad_from_poly(poly) + min_area_quad_h = 0.5 * ( + np.linalg.norm(min_area_quad[0] - min_area_quad[3]) + + np.linalg.norm(min_area_quad[1] - min_area_quad[2]) + ) + min_area_quad_w = 0.5 * ( + np.linalg.norm(min_area_quad[0] - min_area_quad[1]) + + np.linalg.norm(min_area_quad[2] - min_area_quad[3]) + ) + + if ( + min(min_area_quad_h, min_area_quad_w) < self.min_text_size * ds_ratio + or min(min_area_quad_h, min_area_quad_w) > self.max_text_size * ds_ratio + ): + continue + + if tag: + cv2.fillPoly( + training_mask, poly.astype(np.int32)[np.newaxis, :, :], 0.15 + ) + else: + text_label = text_strs[poly_idx] + text_label = self.prepare_text_label(text_label, self.Lexicon_Table) + text_label_index_list = [ + [self.Lexicon_Table.index(c_)] + for c_ in text_label + if c_ in self.Lexicon_Table + ] + if len(text_label_index_list) < 1: + continue + + tcl_poly = self.poly2tcl(poly, tcl_ratio) + tcl_quads = self.poly2quads(tcl_poly) + poly_quads = self.poly2quads(poly) + + stcl_quads, quad_index = self.shrink_poly_along_width( + tcl_quads, + shrink_ratio_of_width=shrink_ratio_of_width, + expand_height_ratio=1.0 / tcl_ratio, + ) + + cv2.fillPoly(score_map, np.round(stcl_quads).astype(np.int32), 1.0) + cv2.fillPoly( + score_map_big, np.round(stcl_quads / ds_ratio).astype(np.int32), 1.0 + ) + + for idx, quad in enumerate(stcl_quads): + quad_mask = np.zeros((h, w), dtype=np.float32) + quad_mask = cv2.fillPoly( + quad_mask, + np.round(quad[np.newaxis, :, :]).astype(np.int32), + 1.0, + ) + tbo_map = self.gen_quad_tbo( + poly_quads[quad_index[idx]], quad_mask, tbo_map + ) + + # score label map and score_label_map_text_label_list for refine + if label_idx == 0: + text_pos_list_ = [ + [len(self.Lexicon_Table)], + ] + score_label_map_text_label_list.append(text_pos_list_) + + label_idx += 1 + cv2.fillPoly( + score_label_map, np.round(poly_quads).astype(np.int32), label_idx + ) + score_label_map_text_label_list.append(text_label_index_list) + + # direction info, fix-me + n_char = len(text_label_index_list) + direction_map = self.generate_direction_map( + poly_quads, n_char, direction_map + ) + + # pos info + average_shrink_height = self.calculate_average_height(stcl_quads) + + if self.point_gather_mode == "align": + self.f_direction = direction_map[:, :, :-1].copy() + pos_res = self.fit_and_gather_tcl_points_v3( + min_area_quad, + stcl_quads, + max_h=h, + max_w=w, + fixed_point_num=64, + img_id=self.img_id, + reference_height=average_shrink_height, + ) + if pos_res is None: + continue + pos_l, pos_m = pos_res[0], pos_res[1] + + else: + pos_l, pos_m = self.fit_and_gather_tcl_points_v2( + min_area_quad, + poly, + max_h=h, + max_w=w, + fixed_point_num=64, + img_id=self.img_id, + reference_height=average_shrink_height, + ) + + label_l = text_label_index_list + if len(text_label_index_list) < 2: + continue + + pos_list.append(pos_l) + pos_mask.append(pos_m) + label_list.append(label_l) + + # use big score_map for smooth tcl lines + score_map_big_resized = cv2.resize( + score_map_big, dsize=None, fx=ds_ratio, fy=ds_ratio + ) + score_map = np.array(score_map_big_resized > 1e-3, dtype="float32") + + return ( + score_map, + score_label_map, + tbo_map, + direction_map, + training_mask, + pos_list, + pos_mask, + label_list, + score_label_map_text_label_list, + ) + + def adjust_point(self, poly): + """ + adjust point order. + """ + point_num = poly.shape[0] + if point_num == 4: + len_1 = np.linalg.norm(poly[0] - poly[1]) + len_2 = np.linalg.norm(poly[1] - poly[2]) + len_3 = np.linalg.norm(poly[2] - poly[3]) + len_4 = np.linalg.norm(poly[3] - poly[0]) + + if (len_1 + len_3) * 1.5 < (len_2 + len_4): + poly = poly[[1, 2, 3, 0], :] + + elif point_num > 4: + vector_1 = poly[0] - poly[1] + vector_2 = poly[1] - poly[2] + cos_theta = np.dot(vector_1, vector_2) / ( + np.linalg.norm(vector_1) * np.linalg.norm(vector_2) + 1e-6 + ) + theta = np.arccos(np.round(cos_theta, decimals=4)) + + if abs(theta) > (70 / 180 * math.pi): + index = list(range(1, point_num)) + [0] + poly = poly[np.array(index), :] + return poly + + def gen_min_area_quad_from_poly(self, poly): + """ + Generate min area quad from poly. + """ + point_num = poly.shape[0] + min_area_quad = np.zeros((4, 2), dtype=np.float32) + if point_num == 4: + min_area_quad = poly + center_point = np.sum(poly, axis=0) / 4 + else: + rect = cv2.minAreaRect( + poly.astype(np.int32) + ) # (center (x,y), (width, height), angle of rotation) + center_point = rect[0] + box = np.array(cv2.boxPoints(rect)) + + first_point_idx = 0 + min_dist = 1e4 + for i in range(4): + dist = ( + np.linalg.norm(box[(i + 0) % 4] - poly[0]) + + np.linalg.norm(box[(i + 1) % 4] - poly[point_num // 2 - 1]) + + np.linalg.norm(box[(i + 2) % 4] - poly[point_num // 2]) + + np.linalg.norm(box[(i + 3) % 4] - poly[-1]) + ) + if dist < min_dist: + min_dist = dist + first_point_idx = i + + for i in range(4): + min_area_quad[i] = box[(first_point_idx + i) % 4] + + return min_area_quad, center_point + + def shrink_quad_along_width(self, quad, begin_width_ratio=0.0, end_width_ratio=1.0): + """ + Generate shrink_quad_along_width. + """ + ratio_pair = np.array( + [[begin_width_ratio], [end_width_ratio]], dtype=np.float32 + ) + p0_1 = quad[0] + (quad[1] - quad[0]) * ratio_pair + p3_2 = quad[3] + (quad[2] - quad[3]) * ratio_pair + return np.array([p0_1[0], p0_1[1], p3_2[1], p3_2[0]]) + + def shrink_poly_along_width( + self, quads, shrink_ratio_of_width, expand_height_ratio=1.0 + ): + """ + shrink poly with given length. + """ + upper_edge_list = [] + + def get_cut_info(edge_len_list, cut_len): + for idx, edge_len in enumerate(edge_len_list): + cut_len -= edge_len + if cut_len <= 0.000001: + ratio = (cut_len + edge_len_list[idx]) / edge_len_list[idx] + return idx, ratio + + for quad in quads: + upper_edge_len = np.linalg.norm(quad[0] - quad[1]) + upper_edge_list.append(upper_edge_len) + + # length of left edge and right edge. + left_length = np.linalg.norm(quads[0][0] - quads[0][3]) * expand_height_ratio + right_length = np.linalg.norm(quads[-1][1] - quads[-1][2]) * expand_height_ratio + + shrink_length = ( + min(left_length, right_length, sum(upper_edge_list)) * shrink_ratio_of_width + ) + # shrinking length + upper_len_left = shrink_length + upper_len_right = sum(upper_edge_list) - shrink_length + + left_idx, left_ratio = get_cut_info(upper_edge_list, upper_len_left) + left_quad = self.shrink_quad_along_width( + quads[left_idx], begin_width_ratio=left_ratio, end_width_ratio=1 + ) + right_idx, right_ratio = get_cut_info(upper_edge_list, upper_len_right) + right_quad = self.shrink_quad_along_width( + quads[right_idx], begin_width_ratio=0, end_width_ratio=right_ratio + ) + + out_quad_list = [] + if left_idx == right_idx: + out_quad_list.append( + [left_quad[0], right_quad[1], right_quad[2], left_quad[3]] + ) + else: + out_quad_list.append(left_quad) + for idx in range(left_idx + 1, right_idx): + out_quad_list.append(quads[idx]) + out_quad_list.append(right_quad) + + return np.array(out_quad_list), list(range(left_idx, right_idx + 1)) + + def prepare_text_label(self, label_str, Lexicon_Table): + """ + Prepare text label by given Lexicon_Table. + """ + if len(Lexicon_Table) == 36: + return label_str.lower() + else: + return label_str + + def vector_angle(self, A, B): + """ + Calculate the angle between vector AB and x-axis positive direction. + """ + AB = np.array([B[1] - A[1], B[0] - A[0]]) + return np.arctan2(*AB) + + def theta_line_cross_point(self, theta, point): + """ + Calculate the line through given point and angle in ax + by + c =0 form. + """ + x, y = point + cos = np.cos(theta) + sin = np.sin(theta) + return [sin, -cos, cos * y - sin * x] + + def line_cross_two_point(self, A, B): + """ + Calculate the line through given point A and B in ax + by + c =0 form. + """ + angle = self.vector_angle(A, B) + return self.theta_line_cross_point(angle, A) + + def average_angle(self, poly): + """ + Calculate the average angle between left and right edge in given poly. + """ + p0, p1, p2, p3 = poly + angle30 = self.vector_angle(p3, p0) + angle21 = self.vector_angle(p2, p1) + return (angle30 + angle21) / 2 + + def line_cross_point(self, line1, line2): + """ + line1 and line2 in 0=ax+by+c form, compute the cross point of line1 and line2 + """ + a1, b1, c1 = line1 + a2, b2, c2 = line2 + d = a1 * b2 - a2 * b1 + + if d == 0: + print("Cross point does not exist") + return np.array([0, 0], dtype=np.float32) + else: + x = (b1 * c2 - b2 * c1) / d + y = (a2 * c1 - a1 * c2) / d + + return np.array([x, y], dtype=np.float32) + + def quad2tcl(self, poly, ratio): + """ + Generate center line by poly clock-wise point. (4, 2) + """ + ratio_pair = np.array([[0.5 - ratio / 2], [0.5 + ratio / 2]], dtype=np.float32) + p0_3 = poly[0] + (poly[3] - poly[0]) * ratio_pair + p1_2 = poly[1] + (poly[2] - poly[1]) * ratio_pair + return np.array([p0_3[0], p1_2[0], p1_2[1], p0_3[1]]) + + def poly2tcl(self, poly, ratio): + """ + Generate center line by poly clock-wise point. + """ + ratio_pair = np.array([[0.5 - ratio / 2], [0.5 + ratio / 2]], dtype=np.float32) + tcl_poly = np.zeros_like(poly) + point_num = poly.shape[0] + + for idx in range(point_num // 2): + point_pair = ( + poly[idx] + (poly[point_num - 1 - idx] - poly[idx]) * ratio_pair + ) + tcl_poly[idx] = point_pair[0] + tcl_poly[point_num - 1 - idx] = point_pair[1] + return tcl_poly + + def gen_quad_tbo(self, quad, tcl_mask, tbo_map): + """ + Generate tbo_map for give quad. + """ + # upper and lower line function: ax + by + c = 0; + up_line = self.line_cross_two_point(quad[0], quad[1]) + lower_line = self.line_cross_two_point(quad[3], quad[2]) + + quad_h = 0.5 * ( + np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[1] - quad[2]) + ) + quad_w = 0.5 * ( + np.linalg.norm(quad[0] - quad[1]) + np.linalg.norm(quad[2] - quad[3]) + ) + + # average angle of left and right line. + angle = self.average_angle(quad) + + xy_in_poly = np.argwhere(tcl_mask == 1) + for y, x in xy_in_poly: + point = (x, y) + line = self.theta_line_cross_point(angle, point) + cross_point_upper = self.line_cross_point(up_line, line) + cross_point_lower = self.line_cross_point(lower_line, line) + ##FIX, offset reverse + upper_offset_x, upper_offset_y = cross_point_upper - point + lower_offset_x, lower_offset_y = cross_point_lower - point + tbo_map[y, x, 0] = upper_offset_y + tbo_map[y, x, 1] = upper_offset_x + tbo_map[y, x, 2] = lower_offset_y + tbo_map[y, x, 3] = lower_offset_x + tbo_map[y, x, 4] = 1.0 / max(min(quad_h, quad_w), 1.0) * 2 + return tbo_map + + def poly2quads(self, poly): + """ + Split poly into quads. + """ + quad_list = [] + point_num = poly.shape[0] + + # point pair + point_pair_list = [] + for idx in range(point_num // 2): + point_pair = [poly[idx], poly[point_num - 1 - idx]] + point_pair_list.append(point_pair) + + quad_num = point_num // 2 - 1 + for idx in range(quad_num): + # reshape and adjust to clock-wise + quad_list.append( + (np.array(point_pair_list)[[idx, idx + 1]]).reshape(4, 2)[[0, 2, 3, 1]] + ) + + return np.array(quad_list) + + def rotate_im_poly(self, im, text_polys): + """ + rotate image with 90 / 180 / 270 degre + """ + im_w, im_h = im.shape[1], im.shape[0] + dst_im = im.copy() + dst_polys = [] + rand_degree_ratio = np.random.rand() + rand_degree_cnt = 1 + if rand_degree_ratio > 0.5: + rand_degree_cnt = 3 + for i in range(rand_degree_cnt): + dst_im = np.rot90(dst_im) + rot_degree = -90 * rand_degree_cnt + rot_angle = rot_degree * math.pi / 180.0 + n_poly = text_polys.shape[0] + cx, cy = 0.5 * im_w, 0.5 * im_h + ncx, ncy = 0.5 * dst_im.shape[1], 0.5 * dst_im.shape[0] + for i in range(n_poly): + wordBB = text_polys[i] + poly = [] + for j in range(4): # 16->4 + sx, sy = wordBB[j][0], wordBB[j][1] + dx = ( + math.cos(rot_angle) * (sx - cx) + - math.sin(rot_angle) * (sy - cy) + + ncx + ) + dy = ( + math.sin(rot_angle) * (sx - cx) + + math.cos(rot_angle) * (sy - cy) + + ncy + ) + poly.append([dx, dy]) + dst_polys.append(poly) + return dst_im, np.array(dst_polys, dtype=np.float32) + + def __call__(self, data): + input_size = 512 + im = data["image"] + text_polys = data["polys"] + text_tags = data["ignore_tags"] + text_strs = data["texts"] + h, w, _ = im.shape + text_polys, text_tags, hv_tags = self.check_and_validate_polys( + text_polys, text_tags, (h, w) + ) + if text_polys.shape[0] <= 0: + return None + # set aspect ratio and keep area fix + asp_scales = np.arange(1.0, 1.55, 0.1) + asp_scale = np.random.choice(asp_scales) + if np.random.rand() < 0.5: + asp_scale = 1.0 / asp_scale + asp_scale = math.sqrt(asp_scale) + + asp_wx = asp_scale + asp_hy = 1.0 / asp_scale + im = cv2.resize(im, dsize=None, fx=asp_wx, fy=asp_hy) + text_polys[:, :, 0] *= asp_wx + text_polys[:, :, 1] *= asp_hy + + if self.use_resize is True: + ori_h, ori_w, _ = im.shape + if max(ori_h, ori_w) < 200: + ratio = 200 / max(ori_h, ori_w) + im = cv2.resize(im, (int(ori_w * ratio), int(ori_h * ratio))) + text_polys[:, :, 0] *= ratio + text_polys[:, :, 1] *= ratio + + if max(ori_h, ori_w) > 512: + ratio = 512 / max(ori_h, ori_w) + im = cv2.resize(im, (int(ori_w * ratio), int(ori_h * ratio))) + text_polys[:, :, 0] *= ratio + text_polys[:, :, 1] *= ratio + elif self.use_random_crop is True: + h, w, _ = im.shape + if max(h, w) > 2048: + rd_scale = 2048.0 / max(h, w) + im = cv2.resize(im, dsize=None, fx=rd_scale, fy=rd_scale) + text_polys *= rd_scale + h, w, _ = im.shape + if min(h, w) < 16: + return None + + # no background + im, text_polys, text_tags, hv_tags, text_strs = self.crop_area( + im, text_polys, text_tags, hv_tags, text_strs, crop_background=False + ) + + if text_polys.shape[0] == 0: + return None + # continue for all ignore case + if np.sum((text_tags * 1.0)) >= text_tags.size: + return None + new_h, new_w, _ = im.shape + if (new_h is None) or (new_w is None): + return None + # resize image + std_ratio = float(input_size) / max(new_w, new_h) + rand_scales = np.array( + [0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0, 1.0, 1.0, 1.0, 1.0] + ) + rz_scale = std_ratio * np.random.choice(rand_scales) + im = cv2.resize(im, dsize=None, fx=rz_scale, fy=rz_scale) + text_polys[:, :, 0] *= rz_scale + text_polys[:, :, 1] *= rz_scale + + # add gaussian blur + if np.random.rand() < 0.1 * 0.5: + ks = np.random.permutation(5)[0] + 1 + ks = int(ks / 2) * 2 + 1 + im = cv2.GaussianBlur(im, ksize=(ks, ks), sigmaX=0, sigmaY=0) + # add brighter + if np.random.rand() < 0.1 * 0.5: + im = im * (1.0 + np.random.rand() * 0.5) + im = np.clip(im, 0.0, 255.0) + # add darker + if np.random.rand() < 0.1 * 0.5: + im = im * (1.0 - np.random.rand() * 0.5) + im = np.clip(im, 0.0, 255.0) + + # Padding the im to [input_size, input_size] + new_h, new_w, _ = im.shape + if min(new_w, new_h) < input_size * 0.5: + return None + im_padded = np.ones((input_size, input_size, 3), dtype=np.float32) + im_padded[:, :, 2] = 0.485 * 255 + im_padded[:, :, 1] = 0.456 * 255 + im_padded[:, :, 0] = 0.406 * 255 + + # Random the start position + del_h = input_size - new_h + del_w = input_size - new_w + sh, sw = 0, 0 + if del_h > 1: + sh = int(np.random.rand() * del_h) + if del_w > 1: + sw = int(np.random.rand() * del_w) + + # Padding + im_padded[sh : sh + new_h, sw : sw + new_w, :] = im.copy() + text_polys[:, :, 0] += sw + text_polys[:, :, 1] += sh + + ( + score_map, + score_label_map, + border_map, + direction_map, + training_mask, + pos_list, + pos_mask, + label_list, + score_label_map_text_label, + ) = self.generate_tcl_ctc_label( + input_size, input_size, text_polys, text_tags, text_strs, 0.25 + ) + if len(label_list) <= 0: # eliminate negative samples + return None + pos_list_temp = np.zeros([64, 3]) + pos_mask_temp = np.zeros([64, 1]) + label_list_temp = np.zeros([self.max_text_length, 1]) + self.pad_num + + for i, label in enumerate(label_list): + n = len(label) + if n > self.max_text_length: + label_list[i] = label[: self.max_text_length] + continue + while n < self.max_text_length: + label.append([self.pad_num]) + n += 1 + + for i in range(len(label_list)): + label_list[i] = np.array(label_list[i]) + + if len(pos_list) <= 0 or len(pos_list) > self.max_text_nums: + return None + for __ in range(self.max_text_nums - len(pos_list), 0, -1): + pos_list.append(pos_list_temp) + pos_mask.append(pos_mask_temp) + label_list.append(label_list_temp) + + if self.img_id == self.batch_size - 1: + self.img_id = 0 + else: + self.img_id += 1 + + im_padded[:, :, 2] -= 0.485 * 255 + im_padded[:, :, 1] -= 0.456 * 255 + im_padded[:, :, 0] -= 0.406 * 255 + im_padded[:, :, 2] /= 255.0 * 0.229 + im_padded[:, :, 1] /= 255.0 * 0.224 + im_padded[:, :, 0] /= 255.0 * 0.225 + im_padded = im_padded.transpose((2, 0, 1)) + images = im_padded[::-1, :, :] + tcl_maps = score_map[np.newaxis, :, :] + tcl_label_maps = score_label_map[np.newaxis, :, :] + border_maps = border_map.transpose((2, 0, 1)) + direction_maps = direction_map.transpose((2, 0, 1)) + training_masks = training_mask[np.newaxis, :, :] + pos_list = np.array(pos_list) + pos_mask = np.array(pos_mask) + label_list = np.array(label_list) + data["images"] = images + data["tcl_maps"] = tcl_maps + data["tcl_label_maps"] = tcl_label_maps + data["border_maps"] = border_maps + data["direction_maps"] = direction_maps + data["training_masks"] = training_masks + data["label_list"] = label_list + data["pos_list"] = pos_list + data["pos_mask"] = pos_mask + return data diff --git a/ppocr/data/imaug/randaugment.py b/ppocr/data/imaug/randaugment.py new file mode 100644 index 0000000..588fb7b --- /dev/null +++ b/ppocr/data/imaug/randaugment.py @@ -0,0 +1,134 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from PIL import Image, ImageEnhance, ImageOps +import numpy as np +import random + + +class RawRandAugment(object): + def __init__(self, num_layers=2, magnitude=5, fillcolor=(128, 128, 128), **kwargs): + self.num_layers = num_layers + self.magnitude = magnitude + self.max_level = 10 + + abso_level = self.magnitude / self.max_level + self.level_map = { + "shearX": 0.3 * abso_level, + "shearY": 0.3 * abso_level, + "translateX": 150.0 / 331 * abso_level, + "translateY": 150.0 / 331 * abso_level, + "rotate": 30 * abso_level, + "color": 0.9 * abso_level, + "posterize": int(4.0 * abso_level), + "solarize": 256.0 * abso_level, + "contrast": 0.9 * abso_level, + "sharpness": 0.9 * abso_level, + "brightness": 0.9 * abso_level, + "autocontrast": 0, + "equalize": 0, + "invert": 0, + } + + # from https://stackoverflow.com/questions/5252170/ + # specify-image-filling-color-when-rotating-in-python-with-pil-and-setting-expand + def rotate_with_fill(img, magnitude): + rot = img.convert("RGBA").rotate(magnitude) + return Image.composite( + rot, Image.new("RGBA", rot.size, (128,) * 4), rot + ).convert(img.mode) + + rnd_ch_op = random.choice + + self.func = { + "shearX": lambda img, magnitude: img.transform( + img.size, + Image.AFFINE, + (1, magnitude * rnd_ch_op([-1, 1]), 0, 0, 1, 0), + Image.BICUBIC, + fillcolor=fillcolor, + ), + "shearY": lambda img, magnitude: img.transform( + img.size, + Image.AFFINE, + (1, 0, 0, magnitude * rnd_ch_op([-1, 1]), 1, 0), + Image.BICUBIC, + fillcolor=fillcolor, + ), + "translateX": lambda img, magnitude: img.transform( + img.size, + Image.AFFINE, + (1, 0, magnitude * img.size[0] * rnd_ch_op([-1, 1]), 0, 1, 0), + fillcolor=fillcolor, + ), + "translateY": lambda img, magnitude: img.transform( + img.size, + Image.AFFINE, + (1, 0, 0, 0, 1, magnitude * img.size[1] * rnd_ch_op([-1, 1])), + fillcolor=fillcolor, + ), + "rotate": lambda img, magnitude: rotate_with_fill(img, magnitude), + "color": lambda img, magnitude: ImageEnhance.Color(img).enhance( + 1 + magnitude * rnd_ch_op([-1, 1]) + ), + "posterize": lambda img, magnitude: ImageOps.posterize(img, magnitude), + "solarize": lambda img, magnitude: ImageOps.solarize(img, magnitude), + "contrast": lambda img, magnitude: ImageEnhance.Contrast(img).enhance( + 1 + magnitude * rnd_ch_op([-1, 1]) + ), + "sharpness": lambda img, magnitude: ImageEnhance.Sharpness(img).enhance( + 1 + magnitude * rnd_ch_op([-1, 1]) + ), + "brightness": lambda img, magnitude: ImageEnhance.Brightness(img).enhance( + 1 + magnitude * rnd_ch_op([-1, 1]) + ), + "autocontrast": lambda img, magnitude: ImageOps.autocontrast(img), + "equalize": lambda img, magnitude: ImageOps.equalize(img), + "invert": lambda img, magnitude: ImageOps.invert(img), + } + + def __call__(self, img): + avaiable_op_names = list(self.level_map.keys()) + for layer_num in range(self.num_layers): + op_name = np.random.choice(avaiable_op_names) + img = self.func[op_name](img, self.level_map[op_name]) + return img + + +class RandAugment(RawRandAugment): + """RandAugment wrapper to auto fit different img types""" + + def __init__(self, prob=0.5, *args, **kwargs): + self.prob = prob + super().__init__(*args, **kwargs) + + def __call__(self, data): + if np.random.rand() > self.prob: + return data + img = data["image"] + if not isinstance(img, Image.Image): + img = np.ascontiguousarray(img) + img = Image.fromarray(img) + + img = super().__call__(img) + + if isinstance(img, Image.Image): + img = np.asarray(img) + data["image"] = img + return data diff --git a/ppocr/data/imaug/random_crop_data.py b/ppocr/data/imaug/random_crop_data.py new file mode 100644 index 0000000..bc61db7 --- /dev/null +++ b/ppocr/data/imaug/random_crop_data.py @@ -0,0 +1,577 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/WenmuZhou/DBNet.pytorch/blob/master/data_loader/modules/random_crop_data.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import numpy as np +import cv2 +import random + +from paddle import get_device + +from shapely.geometry import Polygon, box as shapely_box +from shapely import intersection + + +def is_poly_in_rect(poly, x, y, w, h): + poly = np.array(poly) + if poly[:, 0].min() < x or poly[:, 0].max() > x + w: + return False + if poly[:, 1].min() < y or poly[:, 1].max() > y + h: + return False + return True + + +def is_poly_outside_rect(poly, x, y, w, h): + poly = np.array(poly) + if poly[:, 0].max() < x or poly[:, 0].min() > x + w: + return True + if poly[:, 1].max() < y or poly[:, 1].min() > y + h: + return True + return False + + +def split_regions(axis): + regions = [] + min_axis = 0 + for i in range(1, axis.shape[0]): + if axis[i] != axis[i - 1] + 1: + region = axis[min_axis:i] + min_axis = i + regions.append(region) + return regions + + +def random_select(axis, max_size): + xx = np.random.choice(axis, size=2) + xmin = np.min(xx) + xmax = np.max(xx) + xmin = np.clip(xmin, 0, max_size - 1) + xmax = np.clip(xmax, 0, max_size - 1) + return xmin, xmax + + +def region_wise_random_select(regions, max_size): + selected_index = list(np.random.choice(len(regions), 2)) + selected_values = [] + for index in selected_index: + axis = regions[index] + xx = int(np.random.choice(axis, size=1)) + selected_values.append(xx) + xmin = min(selected_values) + xmax = max(selected_values) + return xmin, xmax + + +def get_min_rotated_rect_side(poly): + """ + Compute the minimum side length of the minimum bounding rotated rectangle of a polygon. + """ + poly = np.array(poly).astype(np.float32) + if len(poly) < 3: + return 0 + rect = cv2.minAreaRect(poly) + width, height = rect[1] + return min(width, height) + + +def get_min_quad_side(quad): + """ + Compute the minimum side length of a quadrilateral. + """ + if len(quad) != 4: + return 0 + quad = np.array(quad) + sides = [] + for i in range(4): + side = np.linalg.norm(quad[i] - quad[(i + 1) % 4]) + sides.append(side) + return min(sides) if sides else 0 + + +def clip_poly_to_rect(poly, x, y, w, h): + """ + Clip a polygon to a rectangular region and return the clipped quadrilateral. + + Args: + poly: Original polygon vertices [[x1, y1], [x2, y2], ...] + x, y, w, h: Position and size of the clipping rectangle + + Returns: + Clipped quadrilateral vertices, or None if the result is invalid. + """ + try: + # Create polygon and clipping rectangle + poly_shape = Polygon(poly) + crop_rect = shapely_box(x, y, x + w, y + h) + + # Compute intersection + clipped = intersection(poly_shape, crop_rect) + + # No intersection or empty + if clipped.is_empty: + return None + + # Get intersection coordinates + if clipped.geom_type == "Polygon": + coords = list(clipped.exterior.coords[:-1]) # Remove duplicate last point + elif clipped.geom_type == "MultiPolygon": + # Multiple polygons, select the largest by area + largest = max(clipped.geoms, key=lambda p: p.area) + coords = list(largest.exterior.coords[:-1]) + elif clipped.geom_type == "GeometryCollection": + # Extract polygons from geometry collection + polygons = [g for g in clipped.geoms if g.geom_type == "Polygon"] + if not polygons: + return None + largest = max(polygons, key=lambda p: p.area) + coords = list(largest.exterior.coords[:-1]) + else: + return None + + # Less than 3 points, invalid + if len(coords) <= 3: + return None + + # Convert to numpy array + coords = np.array(coords) + + # Exactly 4 points, return directly + if len(coords) == 4: + return coords + + # More than 4 points, use Douglas-Peucker to simplify to quadrilateral + # Output points are a subset of original coords (no out-of-bounds), IoU~0.99 + if len(coords) > 4: + poly_cv = coords.reshape(-1, 1, 2).astype(np.float32) + peri = cv2.arcLength(poly_cv, True) + if peri < 1e-6: + return None + lo, hi = 0.0, 0.5 + best = None + for _ in range(50): + mid = (lo + hi) / 2 + approx = cv2.approxPolyDP(poly_cv, mid * peri, True) + if len(approx) <= 4: + best = approx + hi = mid + else: + lo = mid + if best is not None and len(best) >= 3: + return best.reshape(-1, 2) + return None + + return coords + except Exception as e: + return None + + +class RandomCrop(object): + def __init__( + self, + size=(640, 640), + max_tries=10, + min_crop_side_ratio=0.1, + keep_ratio=True, + **kwargs, + ): + self.size = size + self.max_tries = max_tries + self.min_crop_side_ratio = min_crop_side_ratio + self.keep_ratio = keep_ratio + + def __call__(self, data): + img = data["image"] + text_polys = data["polys"] + ignore_tags = data["ignore_tags"] + texts = data["texts"] + + # Separate care and ignore text boxes + care_indices = [i for i, tag in enumerate(ignore_tags) if not tag] + all_care_polys = [text_polys[i] for i in care_indices] + + h, w, _ = img.shape + + # If no valid text boxes, still need to resize and pad the image + if len(all_care_polys) == 0: + # Use entire image as crop region, skip crop loop + crop_x, crop_y, crop_w, crop_h = 0, 0, w, h + valid_care_data = [] + else: + # Pre-compute char heights (min side of min bounding rotated rect) for all care boxes + char_heights = np.array( + [get_min_rotated_rect_side(poly) for poly in all_care_polys] + ) + + # Try to find a suitable crop region + valid_care_data = [] + for attempt in range(self.max_tries): + # Randomly determine crop region width and height + crop_w_min = min(int(w * self.min_crop_side_ratio), self.size[0]) + crop_w_max = int(self.size[0] * 3) + crop_w = ( + w + if crop_w_min >= crop_w_max + else min(random.randint(crop_w_min, crop_w_max), w) + ) + + crop_h_min = min(int(h * self.min_crop_side_ratio), self.size[1]) + crop_h_max = int(self.size[1] * 3) + crop_h = ( + h + if crop_h_min >= crop_h_max + else min(random.randint(crop_h_min, crop_h_max), h) + ) + + # Randomly determine crop region start position + crop_x = 0 if crop_w >= w else random.randint(0, w - crop_w) + crop_y = 0 if crop_h >= h else random.randint(0, h - crop_h) + + # Check each care text box, clip and validate simultaneously (computed once) + valid_care_data = [] + for care_idx, (poly, char_height) in enumerate( + zip(all_care_polys, char_heights) + ): + # Quick check: completely outside, skip + if is_poly_outside_rect(poly, crop_x, crop_y, crop_w, crop_h): + continue + + # Completely inside, no clipping needed + if is_poly_in_rect(poly, crop_x, crop_y, crop_w, crop_h): + valid_care_data.append( + (care_idx, None) + ) # None means no clipping needed + continue + + # Truncated box, clip and validate (executed once) + clipped_poly = clip_poly_to_rect( + poly, crop_x, crop_y, crop_w, crop_h + ) + if clipped_poly is None: + continue + + # Validate clipped polygon - area check + clipped_area = cv2.contourArea(clipped_poly.astype(np.float32)) + if clipped_area < 80: + continue + + # Validate - char height check + clipped_char_height = get_min_rotated_rect_side(clipped_poly) + if clipped_char_height < char_height * 0.35: + continue + + # Validate - min side length check (quadrilaterals only) + if len(clipped_poly) == 4: + min_side = get_min_quad_side(clipped_poly) + if min_side < char_height * 0.35: + continue + + # All validations passed, save the clipped polygon + valid_care_data.append((care_idx, clipped_poly)) + + # At least one valid text box, use this crop region + if len(valid_care_data) >= 1: + break + else: + # All attempts failed, use original region + crop_x, crop_y, crop_w, crop_h = 0, 0, w, h + valid_care_data = [(i, None) for i in range(len(all_care_polys))] + + # Crop and scale image + # Only shrink when crop region is larger than target size, otherwise just pad + need_resize = crop_w > self.size[0] or crop_h > self.size[1] + + if need_resize: + # Crop region larger than target, need to shrink + scale_w = self.size[0] / crop_w + scale_h = self.size[1] / crop_h + scale = min(scale_w, scale_h) + h_resized = int(crop_h * scale) + w_resized = int(crop_w * scale) + else: + # Crop region smaller than or equal to target, no upscaling + scale = 1.0 + h_resized = crop_h + w_resized = crop_w + + if self.keep_ratio: + # Random padding - compute padding size + pad_h = self.size[1] - h_resized + pad_w = self.size[0] - w_resized + + # Randomly distribute padding to each side + pad_top = random.randint(0, pad_h) if pad_h > 0 else 0 + pad_left = random.randint(0, pad_w) if pad_w > 0 else 0 + + # Resize cropped image (only when shrinking is needed) + cropped_img = img[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w] + if need_resize: + resized_img = cv2.resize(cropped_img, (w_resized, h_resized)) + else: + resized_img = cropped_img + + # Create padded image + padimg = np.zeros((self.size[1], self.size[0], img.shape[2]), img.dtype) + padimg[pad_top : pad_top + h_resized, pad_left : pad_left + w_resized] = ( + resized_img + ) + img = padimg + else: + img = cv2.resize( + img[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w], + tuple(self.size), + ) + pad_left = 0 + pad_top = 0 + + # Build fast lookup set of valid care indices + valid_care_indices_set = {care_idx for care_idx, _ in valid_care_data} + + # Build mapping from care_idx to clipped polygon + care_idx_to_clipped = { + care_idx: clipped for care_idx, clipped in valid_care_data + } + + # Build output text box list + text_polys_crop = [] + ignore_tags_crop = [] + texts_crop = [] + + for all_idx, (poly, text, tag) in enumerate( + zip(text_polys, texts, ignore_tags) + ): + if tag: + # Ignore text box, simple processing + if not is_poly_outside_rect(poly, crop_x, crop_y, crop_w, crop_h): + adjusted_poly = (poly - (crop_x, crop_y)) * scale + ( + pad_left, + pad_top, + ) + adjusted_poly[:, 0] = np.clip(adjusted_poly[:, 0], 0, self.size[0]) + adjusted_poly[:, 1] = np.clip(adjusted_poly[:, 1], 0, self.size[1]) + text_polys_crop.append(adjusted_poly.tolist()) + ignore_tags_crop.append(tag) + texts_crop.append(text) + else: + # Care text box, find corresponding care_idx + try: + care_idx = care_indices.index(all_idx) + except ValueError: + continue + + # Check if this is a valid text box + if care_idx not in valid_care_indices_set: + continue + + # Get clipped polygon (if any) + clipped_poly = care_idx_to_clipped[care_idx] + + if clipped_poly is None: + # Completely inside, use original polygon + adjusted_poly = (poly - (crop_x, crop_y)) * scale + ( + pad_left, + pad_top, + ) + else: + # Use clipped polygon + adjusted_poly = (clipped_poly - (crop_x, crop_y)) * scale + ( + pad_left, + pad_top, + ) + + text_polys_crop.append(adjusted_poly.tolist()) + ignore_tags_crop.append(tag) + texts_crop.append(text) + data["image"] = img + # Pad polygons to uniform point count to avoid inhomogeneous array error + if text_polys_crop: + max_points = max(len(p) for p in text_polys_crop) + for i, poly in enumerate(text_polys_crop): + if len(poly) < max_points: + text_polys_crop[i] = poly + [poly[-1]] * (max_points - len(poly)) + data["polys"] = np.array(text_polys_crop, dtype=np.float32) + data["ignore_tags"] = ignore_tags_crop + data["texts"] = texts_crop + return data + + +def crop_area(im, text_polys, min_crop_side_ratio, max_tries): + h, w, _ = im.shape + h_array = np.zeros(h, dtype=np.int32) + w_array = np.zeros(w, dtype=np.int32) + for points in text_polys: + points = np.round(points, decimals=0).astype(np.int32) + minx = np.min(points[:, 0]) + maxx = np.max(points[:, 0]) + w_array[minx:maxx] = 1 + miny = np.min(points[:, 1]) + maxy = np.max(points[:, 1]) + h_array[miny:maxy] = 1 + # ensure the cropped area not across a text + h_axis = np.where(h_array == 0)[0] + w_axis = np.where(w_array == 0)[0] + + if len(h_axis) == 0 or len(w_axis) == 0: + return 0, 0, w, h + + h_regions = split_regions(h_axis) + w_regions = split_regions(w_axis) + + for i in range(max_tries): + if len(w_regions) > 1: + xmin, xmax = region_wise_random_select(w_regions, w) + else: + xmin, xmax = random_select(w_axis, w) + if len(h_regions) > 1: + ymin, ymax = region_wise_random_select(h_regions, h) + else: + ymin, ymax = random_select(h_axis, h) + + if ( + xmax - xmin < min_crop_side_ratio * w + or ymax - ymin < min_crop_side_ratio * h + ): + # area too small + continue + num_poly_in_rect = 0 + for poly in text_polys: + if not is_poly_outside_rect(poly, xmin, ymin, xmax - xmin, ymax - ymin): + num_poly_in_rect += 1 + break + + if num_poly_in_rect > 0: + return xmin, ymin, xmax - xmin, ymax - ymin + + return 0, 0, w, h + + +class EastRandomCropData(object): + def __init__( + self, + size=(640, 640), + max_tries=10, + min_crop_side_ratio=0.1, + keep_ratio=True, + **kwargs, + ): + self.size = size + self.max_tries = max_tries + self.min_crop_side_ratio = min_crop_side_ratio + self.keep_ratio = keep_ratio + + def __call__(self, data): + img = data["image"] + text_polys = data["polys"] + ignore_tags = data["ignore_tags"] + texts = data["texts"] + all_care_polys = [text_polys[i] for i, tag in enumerate(ignore_tags) if not tag] + # 计算crop区域 + crop_x, crop_y, crop_w, crop_h = crop_area( + img, all_care_polys, self.min_crop_side_ratio, self.max_tries + ) + # crop 图片 保持比例填充 + scale_w = self.size[0] / crop_w + scale_h = self.size[1] / crop_h + scale = min(scale_w, scale_h) + h = int(crop_h * scale) + w = int(crop_w * scale) + if self.keep_ratio: + padimg = np.zeros((self.size[1], self.size[0], img.shape[2]), img.dtype) + padimg[:h, :w] = cv2.resize( + img[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w], (w, h) + ) + img = padimg + else: + img = cv2.resize( + img[crop_y : crop_y + crop_h, crop_x : crop_x + crop_w], + tuple(self.size), + ) + # crop 文本框 + text_polys_crop = [] + ignore_tags_crop = [] + texts_crop = [] + for poly, text, tag in zip(text_polys, texts, ignore_tags): + poly = ((poly - (crop_x, crop_y)) * scale).tolist() + if not is_poly_outside_rect(poly, 0, 0, w, h): + text_polys_crop.append(poly) + ignore_tags_crop.append(tag) + texts_crop.append(text) + data["image"] = img + data["polys"] = np.array(text_polys_crop) + if "iluvatar_gpu" in get_device(): + data["polys"] = np.array(text_polys_crop).astype(np.float32) + data["ignore_tags"] = ignore_tags_crop + data["texts"] = texts_crop + return data + + +class RandomCropImgMask(object): + def __init__(self, size, main_key, crop_keys, p=3 / 8, **kwargs): + self.size = size + self.main_key = main_key + self.crop_keys = crop_keys + self.p = p + + def __call__(self, data): + image = data["image"] + + h, w = image.shape[0:2] + th, tw = self.size + if w == tw and h == th: + return data + + mask = data[self.main_key] + if np.max(mask) > 0 and random.random() > self.p: + # make sure to crop the text region + tl = np.min(np.where(mask > 0), axis=1) - (th, tw) + tl[tl < 0] = 0 + br = np.max(np.where(mask > 0), axis=1) - (th, tw) + br[br < 0] = 0 + + br[0] = min(br[0], h - th) + br[1] = min(br[1], w - tw) + + i = random.randint(tl[0], br[0]) if tl[0] < br[0] else 0 + j = random.randint(tl[1], br[1]) if tl[1] < br[1] else 0 + else: + i = random.randint(0, h - th) if h - th > 0 else 0 + j = random.randint(0, w - tw) if w - tw > 0 else 0 + + # return i, j, th, tw + for k in data: + if k in self.crop_keys: + if len(data[k].shape) == 3: + if np.argmin(data[k].shape) == 0: + img = data[k][:, i : i + th, j : j + tw] + if img.shape[1] != img.shape[2]: + a = 1 + elif np.argmin(data[k].shape) == 2: + img = data[k][i : i + th, j : j + tw, :] + if img.shape[1] != img.shape[0]: + a = 1 + else: + img = data[k] + else: + img = data[k][i : i + th, j : j + tw] + if img.shape[0] != img.shape[1]: + a = 1 + data[k] = img + return data diff --git a/ppocr/data/imaug/rec_img_aug.py b/ppocr/data/imaug/rec_img_aug.py new file mode 100644 index 0000000..ad9b7d9 --- /dev/null +++ b/ppocr/data/imaug/rec_img_aug.py @@ -0,0 +1,947 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import cv2 +import numpy as np +import random +import copy +from PIL import Image +import PIL +from .text_image_aug import tia_perspective, tia_stretch, tia_distort +from .abinet_aug import ( + CVGeometry, + CVDeterioration, + CVColorJitter, + SVTRGeometry, + SVTRDeterioration, + ParseQDeterioration, +) +from paddle import get_device +from paddle.vision.transforms import Compose + + +class RecAug(object): + def __init__( + self, + tia_prob=0.4, + crop_prob=0.4, + reverse_prob=0.4, + noise_prob=0.4, + jitter_prob=0.4, + blur_prob=0.4, + hsv_aug_prob=0.4, + **kwargs, + ): + self.tia_prob = tia_prob + self.bda = BaseDataAugmentation( + crop_prob, reverse_prob, noise_prob, jitter_prob, blur_prob, hsv_aug_prob + ) + + def __call__(self, data): + img = data["image"] + h, w, _ = img.shape + + # tia + if random.random() <= self.tia_prob: + if h >= 20 and w >= 20: + img = tia_distort(img, random.randint(3, 6)) + img = tia_stretch(img, random.randint(3, 6)) + img = tia_perspective(img) + + # bda + data["image"] = img + data = self.bda(data) + return data + + +class BaseDataAugmentation(object): + def __init__( + self, + crop_prob=0.4, + reverse_prob=0.4, + noise_prob=0.4, + jitter_prob=0.4, + blur_prob=0.4, + hsv_aug_prob=0.4, + **kwargs, + ): + self.crop_prob = crop_prob + self.reverse_prob = reverse_prob + self.noise_prob = noise_prob + self.jitter_prob = jitter_prob + self.blur_prob = blur_prob + self.hsv_aug_prob = hsv_aug_prob + # for GaussianBlur + self.fil = cv2.getGaussianKernel(ksize=5, sigma=1, ktype=cv2.CV_32F) + + def __call__(self, data): + img = data["image"] + h, w, _ = img.shape + + if random.random() <= self.crop_prob and h >= 20 and w >= 20: + img = get_crop(img) + + if random.random() <= self.blur_prob: + # GaussianBlur + img = cv2.sepFilter2D(img, -1, self.fil, self.fil) + + if random.random() <= self.hsv_aug_prob: + img = hsv_aug(img) + + if random.random() <= self.jitter_prob: + img = jitter(img) + + if random.random() <= self.noise_prob: + img = add_gaussian_noise(img) + + if random.random() <= self.reverse_prob: + img = 255 - img + + data["image"] = img + return data + + +class ABINetRecAug(object): + def __init__( + self, geometry_p=0.5, deterioration_p=0.25, colorjitter_p=0.25, **kwargs + ): + self.transforms = Compose( + [ + CVGeometry( + degrees=45, + translate=(0.0, 0.0), + scale=(0.5, 2.0), + shear=(45, 15), + distortion=0.5, + p=geometry_p, + ), + CVDeterioration(var=20, degrees=6, factor=4, p=deterioration_p), + CVColorJitter( + brightness=0.5, + contrast=0.5, + saturation=0.5, + hue=0.1, + p=colorjitter_p, + ), + ] + ) + + def __call__(self, data): + img = data["image"] + img = self.transforms(img) + data["image"] = img + return data + + +class RecConAug(object): + def __init__( + self, + prob=0.5, + image_shape=(32, 320, 3), + max_text_length=25, + ext_data_num=1, + **kwargs, + ): + self.ext_data_num = ext_data_num + self.prob = prob + self.max_text_length = max_text_length + self.image_shape = image_shape + self.max_wh_ratio = self.image_shape[1] / self.image_shape[0] + + def merge_ext_data(self, data, ext_data): + ori_w = round( + data["image"].shape[1] / data["image"].shape[0] * self.image_shape[0] + ) + ext_w = round( + ext_data["image"].shape[1] + / ext_data["image"].shape[0] + * self.image_shape[0] + ) + data["image"] = cv2.resize(data["image"], (ori_w, self.image_shape[0])) + ext_data["image"] = cv2.resize(ext_data["image"], (ext_w, self.image_shape[0])) + data["image"] = np.concatenate([data["image"], ext_data["image"]], axis=1) + data["label"] += ext_data["label"] + return data + + def __call__(self, data): + rnd_num = random.random() + if rnd_num > self.prob: + return data + for idx, ext_data in enumerate(data["ext_data"]): + if len(data["label"]) + len(ext_data["label"]) > self.max_text_length: + break + concat_ratio = ( + data["image"].shape[1] / data["image"].shape[0] + + ext_data["image"].shape[1] / ext_data["image"].shape[0] + ) + if concat_ratio > self.max_wh_ratio: + break + data = self.merge_ext_data(data, ext_data) + data.pop("ext_data") + return data + + +class SVTRRecAug(object): + def __init__( + self, + aug_type=0, + geometry_p=0.5, + deterioration_p=0.25, + colorjitter_p=0.25, + **kwargs, + ): + self.transforms = Compose( + [ + SVTRGeometry( + aug_type=aug_type, + degrees=45, + translate=(0.0, 0.0), + scale=(0.5, 2.0), + shear=(45, 15), + distortion=0.5, + p=geometry_p, + ), + SVTRDeterioration(var=20, degrees=6, factor=4, p=deterioration_p), + CVColorJitter( + brightness=0.5, + contrast=0.5, + saturation=0.5, + hue=0.1, + p=colorjitter_p, + ), + ] + ) + + def __call__(self, data): + img = data["image"] + img = self.transforms(img) + data["image"] = img + return data + + +class ParseQRecAug(object): + def __init__( + self, + aug_type=0, + geometry_p=0.5, + deterioration_p=0.25, + colorjitter_p=0.25, + **kwargs, + ): + self.transforms = Compose( + [ + SVTRGeometry( + aug_type=aug_type, + degrees=45, + translate=(0.0, 0.0), + scale=(0.5, 2.0), + shear=(45, 15), + distortion=0.5, + p=geometry_p, + ), + ParseQDeterioration( + var=20, degrees=6, lam=20, radius=2.0, factor=4, p=deterioration_p + ), + CVColorJitter( + brightness=0.5, + contrast=0.5, + saturation=0.5, + hue=0.1, + p=colorjitter_p, + ), + ] + ) + + def __call__(self, data): + img = data["image"] + img = self.transforms(img) + data["image"] = img + return data + + +class ClsResizeImg(object): + def __init__(self, image_shape, **kwargs): + self.image_shape = image_shape + + def __call__(self, data): + img = data["image"] + norm_img, _ = resize_norm_img(img, self.image_shape) + data["image"] = norm_img + return data + + +class RecResizeImg(object): + def __init__( + self, + image_shape, + infer_mode=False, + eval_mode=False, + character_dict_path="./ppocr/utils/ppocr_keys_v1.txt", + padding=True, + **kwargs, + ): + self.image_shape = image_shape + self.infer_mode = infer_mode + self.eval_mode = eval_mode + self.character_dict_path = character_dict_path + self.padding = padding + + def __call__(self, data): + img = data["image"] + if self.eval_mode or (self.infer_mode and self.character_dict_path is not None): + norm_img, valid_ratio = resize_norm_img_chinese(img, self.image_shape) + else: + norm_img, valid_ratio = resize_norm_img(img, self.image_shape, self.padding) + data["image"] = norm_img + data["valid_ratio"] = valid_ratio + if "iluvatar_gpu" in get_device(): + data["valid_ratio"] = np.float32(valid_ratio) + return data + + +class VLRecResizeImg(object): + def __init__( + self, + image_shape, + infer_mode=False, + character_dict_path="./ppocr/utils/ppocr_keys_v1.txt", + padding=True, + **kwargs, + ): + self.image_shape = image_shape + self.infer_mode = infer_mode + self.character_dict_path = character_dict_path + self.padding = padding + + def __call__(self, data): + img = data["image"] + + imgC, imgH, imgW = self.image_shape + resized_image = cv2.resize(img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) + resized_w = imgW + resized_image = resized_image.astype("float32") + if self.image_shape[0] == 1: + resized_image = resized_image / 255 + norm_img = resized_image[np.newaxis, :] + else: + norm_img = resized_image.transpose((2, 0, 1)) / 255 + valid_ratio = min(1.0, float(resized_w / imgW)) + + data["image"] = norm_img + data["valid_ratio"] = valid_ratio + if "iluvatar_gpu" in get_device(): + data["valid_ratio"] = np.float32(valid_ratio) + return data + + +class RFLRecResizeImg(object): + def __init__(self, image_shape, padding=True, interpolation=1, **kwargs): + self.image_shape = image_shape + self.padding = padding + + self.interpolation = interpolation + if self.interpolation == 0: + self.interpolation = cv2.INTER_NEAREST + elif self.interpolation == 1: + self.interpolation = cv2.INTER_LINEAR + elif self.interpolation == 2: + self.interpolation = cv2.INTER_CUBIC + elif self.interpolation == 3: + self.interpolation = cv2.INTER_AREA + else: + raise Exception("Unsupported interpolation type !!!") + + def __call__(self, data): + img = data["image"] + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + norm_img, valid_ratio = resize_norm_img( + img, self.image_shape, self.padding, self.interpolation + ) + data["image"] = norm_img + data["valid_ratio"] = valid_ratio + if "iluvatar_gpu" in get_device(): + data["valid_ratio"] = np.float32(valid_ratio) + return data + + +class SRNRecResizeImg(object): + def __init__(self, image_shape, num_heads, max_text_length, **kwargs): + self.image_shape = image_shape + self.num_heads = num_heads + self.max_text_length = max_text_length + + def __call__(self, data): + img = data["image"] + norm_img = resize_norm_img_srn(img, self.image_shape) + data["image"] = norm_img + [ + encoder_word_pos, + gsrm_word_pos, + gsrm_slf_attn_bias1, + gsrm_slf_attn_bias2, + ] = srn_other_inputs(self.image_shape, self.num_heads, self.max_text_length) + + data["encoder_word_pos"] = encoder_word_pos + data["gsrm_word_pos"] = gsrm_word_pos + data["gsrm_slf_attn_bias1"] = gsrm_slf_attn_bias1 + data["gsrm_slf_attn_bias2"] = gsrm_slf_attn_bias2 + return data + + +class SARRecResizeImg(object): + def __init__(self, image_shape, width_downsample_ratio=0.25, **kwargs): + self.image_shape = image_shape + self.width_downsample_ratio = width_downsample_ratio + + def __call__(self, data): + img = data["image"] + norm_img, resize_shape, pad_shape, valid_ratio = resize_norm_img_sar( + img, self.image_shape, self.width_downsample_ratio + ) + data["image"] = norm_img + data["resized_shape"] = resize_shape + data["pad_shape"] = pad_shape + data["valid_ratio"] = valid_ratio + if "iluvatar_gpu" in get_device(): + data["valid_ratio"] = np.float32(valid_ratio) + return data + + +class PRENResizeImg(object): + def __init__(self, image_shape, **kwargs): + """ + According to original paper's realization, it's a hard resize method here. + So maybe you should optimize it to fit for your task better. + """ + self.dst_h, self.dst_w = image_shape + + def __call__(self, data): + img = data["image"] + resized_img = cv2.resize( + img, (self.dst_w, self.dst_h), interpolation=cv2.INTER_LINEAR + ) + resized_img = resized_img.transpose((2, 0, 1)) / 255 + resized_img -= 0.5 + resized_img /= 0.5 + data["image"] = resized_img.astype(np.float32) + return data + + +class SPINRecResizeImg(object): + def __init__( + self, + image_shape, + interpolation=2, + mean=(127.5, 127.5, 127.5), + std=(127.5, 127.5, 127.5), + **kwargs, + ): + self.image_shape = image_shape + + self.mean = np.array(mean, dtype=np.float32) + self.std = np.array(std, dtype=np.float32) + self.interpolation = interpolation + + def __call__(self, data): + img = data["image"] + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + # different interpolation type corresponding the OpenCV + if self.interpolation == 0: + interpolation = cv2.INTER_NEAREST + elif self.interpolation == 1: + interpolation = cv2.INTER_LINEAR + elif self.interpolation == 2: + interpolation = cv2.INTER_CUBIC + elif self.interpolation == 3: + interpolation = cv2.INTER_AREA + else: + raise Exception("Unsupported interpolation type !!!") + # Deal with the image error during image loading + if img is None: + return None + + img = cv2.resize(img, tuple(self.image_shape), interpolation) + img = np.array(img, np.float32) + img = np.expand_dims(img, -1) + img = img.transpose((2, 0, 1)) + # normalize the image + img = img.copy().astype(np.float32) + mean = np.float64(self.mean.reshape(1, -1)) + stdinv = 1 / np.float64(self.std.reshape(1, -1)) + img -= mean + img *= stdinv + data["image"] = img + return data + + +class GrayRecResizeImg(object): + def __init__( + self, + image_shape, + resize_type, + inter_type="Image.Resampling.LANCZOS", + scale=True, + padding=False, + **kwargs, + ): + self.image_shape = image_shape + self.resize_type = resize_type + self.padding = padding + self.inter_type = eval(inter_type) + self.scale = scale + + def __call__(self, data): + img = data["image"] + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + image_shape = self.image_shape + if self.padding: + imgC, imgH, imgW = image_shape + # todo: change to 0 and modified image shape + h = img.shape[0] + w = img.shape[1] + ratio = w / float(h) + if math.ceil(imgH * ratio) > imgW: + resized_w = imgW + else: + resized_w = int(math.ceil(imgH * ratio)) + resized_image = cv2.resize(img, (resized_w, imgH)) + norm_img = np.expand_dims(resized_image, -1) + norm_img = norm_img.transpose((2, 0, 1)) + resized_image = norm_img.astype(np.float32) / 128.0 - 1.0 + padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) + padding_im[:, :, 0:resized_w] = resized_image + data["image"] = padding_im + return data + if self.resize_type == "PIL": + image_pil = Image.fromarray(np.uint8(img)) + img = image_pil.resize(self.image_shape, self.inter_type) + img = np.array(img) + if self.resize_type == "OpenCV": + img = cv2.resize(img, self.image_shape) + norm_img = np.expand_dims(img, -1) + norm_img = norm_img.transpose((2, 0, 1)) + if self.scale: + data["image"] = norm_img.astype(np.float32) / 128.0 - 1.0 + else: + data["image"] = norm_img.astype(np.float32) / 255.0 + return data + + +class ABINetRecResizeImg(object): + def __init__(self, image_shape, **kwargs): + self.image_shape = image_shape + + def __call__(self, data): + img = data["image"] + norm_img, valid_ratio = resize_norm_img_abinet(img, self.image_shape) + data["image"] = norm_img + data["valid_ratio"] = valid_ratio + if "iluvatar_gpu" in get_device(): + data["valid_ratio"] = np.float32(valid_ratio) + return data + + +class SVTRRecResizeImg(object): + def __init__(self, image_shape, padding=True, **kwargs): + self.image_shape = image_shape + self.padding = padding + + def __call__(self, data): + img = data["image"] + + norm_img, valid_ratio = resize_norm_img(img, self.image_shape, self.padding) + data["image"] = norm_img + data["valid_ratio"] = valid_ratio + if "iluvatar_gpu" in get_device(): + data["valid_ratio"] = np.float32(valid_ratio) + return data + + +class RobustScannerRecResizeImg(object): + def __init__( + self, image_shape, max_text_length, width_downsample_ratio=0.25, **kwargs + ): + self.image_shape = image_shape + self.width_downsample_ratio = width_downsample_ratio + self.max_text_length = max_text_length + + def __call__(self, data): + img = data["image"] + norm_img, resize_shape, pad_shape, valid_ratio = resize_norm_img_sar( + img, self.image_shape, self.width_downsample_ratio + ) + word_positons = np.array(range(0, self.max_text_length)).astype("int64") + data["image"] = norm_img + data["resized_shape"] = resize_shape + data["pad_shape"] = pad_shape + data["valid_ratio"] = valid_ratio + if "iluvatar_gpu" in get_device(): + data["valid_ratio"] = np.float32(valid_ratio) + data["word_positons"] = word_positons + return data + + +def resize_norm_img_sar(img, image_shape, width_downsample_ratio=0.25): + imgC, imgH, imgW_min, imgW_max = image_shape + h = img.shape[0] + w = img.shape[1] + valid_ratio = 1.0 + # make sure new_width is an integral multiple of width_divisor. + width_divisor = int(1 / width_downsample_ratio) + # resize + ratio = w / float(h) + resize_w = math.ceil(imgH * ratio) + if resize_w % width_divisor != 0: + resize_w = round(resize_w / width_divisor) * width_divisor + if imgW_min is not None: + resize_w = max(imgW_min, resize_w) + if imgW_max is not None: + valid_ratio = min(1.0, 1.0 * resize_w / imgW_max) + resize_w = min(imgW_max, resize_w) + resized_image = cv2.resize(img, (resize_w, imgH)) + resized_image = resized_image.astype("float32") + # norm + if image_shape[0] == 1: + resized_image = resized_image / 255 + resized_image = resized_image[np.newaxis, :] + else: + resized_image = resized_image.transpose((2, 0, 1)) / 255 + resized_image -= 0.5 + resized_image /= 0.5 + resize_shape = resized_image.shape + padding_im = -1.0 * np.ones((imgC, imgH, imgW_max), dtype=np.float32) + padding_im[:, :, 0:resize_w] = resized_image + pad_shape = padding_im.shape + + return padding_im, resize_shape, pad_shape, valid_ratio + + +def resize_norm_img(img, image_shape, padding=True, interpolation=cv2.INTER_LINEAR): + imgC, imgH, imgW = image_shape + h = img.shape[0] + w = img.shape[1] + if not padding: + resized_image = cv2.resize(img, (imgW, imgH), interpolation=interpolation) + resized_w = imgW + else: + ratio = w / float(h) + if math.ceil(imgH * ratio) > imgW: + resized_w = imgW + else: + resized_w = int(math.ceil(imgH * ratio)) + resized_image = cv2.resize(img, (resized_w, imgH)) + resized_image = resized_image.astype("float32") + if image_shape[0] == 1: + resized_image = resized_image / 255 + resized_image = resized_image[np.newaxis, :] + else: + resized_image = resized_image.transpose((2, 0, 1)) / 255 + resized_image -= 0.5 + resized_image /= 0.5 + padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) + padding_im[:, :, 0:resized_w] = resized_image + valid_ratio = min(1.0, float(resized_w / imgW)) + return padding_im, valid_ratio + + +def resize_norm_img_chinese(img, image_shape): + imgC, imgH, imgW = image_shape + # todo: change to 0 and modified image shape + max_wh_ratio = imgW * 1.0 / imgH + h, w = img.shape[0], img.shape[1] + ratio = w * 1.0 / h + max_wh_ratio = max(max_wh_ratio, ratio) + imgW = int(imgH * max_wh_ratio) + if math.ceil(imgH * ratio) > imgW: + resized_w = imgW + else: + resized_w = int(math.ceil(imgH * ratio)) + resized_image = cv2.resize(img, (resized_w, imgH)) + resized_image = resized_image.astype("float32") + if image_shape[0] == 1: + resized_image = resized_image / 255 + resized_image = resized_image[np.newaxis, :] + else: + resized_image = resized_image.transpose((2, 0, 1)) / 255 + resized_image -= 0.5 + resized_image /= 0.5 + padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) + padding_im[:, :, 0:resized_w] = resized_image + valid_ratio = min(1.0, float(resized_w / imgW)) + return padding_im, valid_ratio + + +def resize_norm_img_srn(img, image_shape): + imgC, imgH, imgW = image_shape + + img_black = np.zeros((imgH, imgW)) + im_hei = img.shape[0] + im_wid = img.shape[1] + + if im_wid <= im_hei * 1: + img_new = cv2.resize(img, (imgH * 1, imgH)) + elif im_wid <= im_hei * 2: + img_new = cv2.resize(img, (imgH * 2, imgH)) + elif im_wid <= im_hei * 3: + img_new = cv2.resize(img, (imgH * 3, imgH)) + else: + img_new = cv2.resize(img, (imgW, imgH)) + + img_np = np.asarray(img_new) + img_np = cv2.cvtColor(img_np, cv2.COLOR_BGR2GRAY) + img_black[:, 0 : img_np.shape[1]] = img_np + img_black = img_black[:, :, np.newaxis] + + row, col, c = img_black.shape + c = 1 + + return np.reshape(img_black, (c, row, col)).astype(np.float32) + + +def resize_norm_img_abinet(img, image_shape): + imgC, imgH, imgW = image_shape + + resized_image = cv2.resize(img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) + resized_w = imgW + resized_image = resized_image.astype("float32") + resized_image = resized_image / 255.0 + + mean = np.array([0.485, 0.456, 0.406]) + std = np.array([0.229, 0.224, 0.225]) + resized_image = (resized_image - mean[None, None, ...]) / std[None, None, ...] + resized_image = resized_image.transpose((2, 0, 1)) + resized_image = resized_image.astype("float32") + + valid_ratio = min(1.0, float(resized_w / imgW)) + return resized_image, valid_ratio + + +def srn_other_inputs(image_shape, num_heads, max_text_length): + imgC, imgH, imgW = image_shape + feature_dim = int((imgH / 8) * (imgW / 8)) + + encoder_word_pos = ( + np.array(range(0, feature_dim)).reshape((feature_dim, 1)).astype("int64") + ) + gsrm_word_pos = ( + np.array(range(0, max_text_length)) + .reshape((max_text_length, 1)) + .astype("int64") + ) + + gsrm_attn_bias_data = np.ones((1, max_text_length, max_text_length)) + gsrm_slf_attn_bias1 = np.triu(gsrm_attn_bias_data, 1).reshape( + [1, max_text_length, max_text_length] + ) + gsrm_slf_attn_bias1 = np.tile(gsrm_slf_attn_bias1, [num_heads, 1, 1]) * [-1e9] + + gsrm_slf_attn_bias2 = np.tril(gsrm_attn_bias_data, -1).reshape( + [1, max_text_length, max_text_length] + ) + gsrm_slf_attn_bias2 = np.tile(gsrm_slf_attn_bias2, [num_heads, 1, 1]) * [-1e9] + + return [encoder_word_pos, gsrm_word_pos, gsrm_slf_attn_bias1, gsrm_slf_attn_bias2] + + +def flag(): + """ + flag + """ + return 1 if random.random() > 0.5000001 else -1 + + +def hsv_aug(img): + """ + cvtColor + """ + hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) + delta = 0.001 * random.random() * flag() + hsv[:, :, 2] = hsv[:, :, 2] * (1 + delta) + new_img = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR) + return new_img + + +def blur(img): + """ + blur + """ + h, w, _ = img.shape + if h > 10 and w > 10: + return cv2.GaussianBlur(img, (5, 5), 1) + else: + return img + + +def jitter(img): + """ + jitter + """ + w, h, _ = img.shape + if h > 10 and w > 10: + thres = min(w, h) + s = int(random.random() * thres * 0.01) + src_img = img.copy() + for i in range(s): + img[i:, i:, :] = src_img[: w - i, : h - i, :] + return img + else: + return img + + +def add_gaussian_noise(image, mean=0, var=0.1): + """ + Gaussian noise + """ + + noise = np.random.normal(mean, var**0.5, image.shape) + out = image + 0.5 * noise + out = np.clip(out, 0, 255) + out = np.uint8(out) + return out + + +def get_crop(image): + """ + random crop + """ + h, w, _ = image.shape + top_min = 1 + top_max = 8 + top_crop = int(random.randint(top_min, top_max)) + top_crop = min(top_crop, h - 1) + crop_img = image.copy() + ratio = random.randint(0, 1) + if ratio: + crop_img = crop_img[top_crop:h, :, :] + else: + crop_img = crop_img[0 : h - top_crop, :, :] + return crop_img + + +def rad(x): + """ + rad + """ + return x * np.pi / 180 + + +def get_warpR(config): + """ + get_warpR + """ + anglex, angley, anglez, fov, w, h, r = ( + config.anglex, + config.angley, + config.anglez, + config.fov, + config.w, + config.h, + config.r, + ) + if w > 69 and w < 112: + anglex = anglex * 1.5 + + z = np.sqrt(w**2 + h**2) / 2 / np.tan(rad(fov / 2)) + # Homogeneous coordinate transformation matrix + rx = np.array( + [ + [1, 0, 0, 0], + [0, np.cos(rad(anglex)), -np.sin(rad(anglex)), 0], + [ + 0, + -np.sin(rad(anglex)), + np.cos(rad(anglex)), + 0, + ], + [0, 0, 0, 1], + ], + np.float32, + ) + ry = np.array( + [ + [np.cos(rad(angley)), 0, np.sin(rad(angley)), 0], + [0, 1, 0, 0], + [ + -np.sin(rad(angley)), + 0, + np.cos(rad(angley)), + 0, + ], + [0, 0, 0, 1], + ], + np.float32, + ) + rz = np.array( + [ + [np.cos(rad(anglez)), np.sin(rad(anglez)), 0, 0], + [-np.sin(rad(anglez)), np.cos(rad(anglez)), 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1], + ], + np.float32, + ) + r = rx.dot(ry).dot(rz) + # generate 4 points + pcenter = np.array([h / 2, w / 2, 0, 0], np.float32) + p1 = np.array([0, 0, 0, 0], np.float32) - pcenter + p2 = np.array([w, 0, 0, 0], np.float32) - pcenter + p3 = np.array([0, h, 0, 0], np.float32) - pcenter + p4 = np.array([w, h, 0, 0], np.float32) - pcenter + dst1 = r.dot(p1) + dst2 = r.dot(p2) + dst3 = r.dot(p3) + dst4 = r.dot(p4) + list_dst = np.array([dst1, dst2, dst3, dst4]) + org = np.array([[0, 0], [w, 0], [0, h], [w, h]], np.float32) + dst = np.zeros((4, 2), np.float32) + # Project onto the image plane + dst[:, 0] = list_dst[:, 0] * z / (z - list_dst[:, 2]) + pcenter[0] + dst[:, 1] = list_dst[:, 1] * z / (z - list_dst[:, 2]) + pcenter[1] + + warpR = cv2.getPerspectiveTransform(org, dst) + + dst1, dst2, dst3, dst4 = dst + r1 = int(min(dst1[1], dst2[1])) + r2 = int(max(dst3[1], dst4[1])) + c1 = int(min(dst1[0], dst3[0])) + c2 = int(max(dst2[0], dst4[0])) + + try: + ratio = min(1.0 * h / (r2 - r1), 1.0 * w / (c2 - c1)) + + dx = -c1 + dy = -r1 + T1 = np.float32([[1.0, 0, dx], [0, 1.0, dy], [0, 0, 1.0 / ratio]]) + ret = T1.dot(warpR) + except: + ratio = 1.0 + T1 = np.float32([[1.0, 0, 0], [0, 1.0, 0], [0, 0, 1.0]]) + ret = T1 + return ret, (-r1, -c1), ratio, dst + + +def get_warpAffine(config): + """ + get_warpAffine + """ + anglez = config.anglez + rz = np.array( + [ + [np.cos(rad(anglez)), np.sin(rad(anglez)), 0], + [-np.sin(rad(anglez)), np.cos(rad(anglez)), 0], + ], + np.float32, + ) + return rz diff --git a/ppocr/data/imaug/sast_process.py b/ppocr/data/imaug/sast_process.py new file mode 100644 index 0000000..caa061b --- /dev/null +++ b/ppocr/data/imaug/sast_process.py @@ -0,0 +1,810 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This part code is referred from: +https://github.com/songdejia/EAST/blob/master/data_utils.py +""" +import math +import cv2 +import numpy as np +import json +import sys +import os + +__all__ = ["SASTProcessTrain"] + + +class SASTProcessTrain(object): + def __init__( + self, + image_shape=[512, 512], + min_crop_size=24, + min_crop_side_ratio=0.3, + min_text_size=10, + max_text_size=512, + **kwargs, + ): + self.input_size = image_shape[1] + self.min_crop_size = min_crop_size + self.min_crop_side_ratio = min_crop_side_ratio + self.min_text_size = min_text_size + self.max_text_size = max_text_size + + def quad_area(self, poly): + """ + compute area of a polygon + :param poly: + :return: + """ + edge = [ + (poly[1][0] - poly[0][0]) * (poly[1][1] + poly[0][1]), + (poly[2][0] - poly[1][0]) * (poly[2][1] + poly[1][1]), + (poly[3][0] - poly[2][0]) * (poly[3][1] + poly[2][1]), + (poly[0][0] - poly[3][0]) * (poly[0][1] + poly[3][1]), + ] + return np.sum(edge) / 2.0 + + def gen_quad_from_poly(self, poly): + """ + Generate min area quad from poly. + """ + point_num = poly.shape[0] + min_area_quad = np.zeros((4, 2), dtype=np.float32) + if True: + rect = cv2.minAreaRect( + poly.astype(np.int32) + ) # (center (x,y), (width, height), angle of rotation) + center_point = rect[0] + box = np.array(cv2.boxPoints(rect)) + + first_point_idx = 0 + min_dist = 1e4 + for i in range(4): + dist = ( + np.linalg.norm(box[(i + 0) % 4] - poly[0]) + + np.linalg.norm(box[(i + 1) % 4] - poly[point_num // 2 - 1]) + + np.linalg.norm(box[(i + 2) % 4] - poly[point_num // 2]) + + np.linalg.norm(box[(i + 3) % 4] - poly[-1]) + ) + if dist < min_dist: + min_dist = dist + first_point_idx = i + for i in range(4): + min_area_quad[i] = box[(first_point_idx + i) % 4] + + return min_area_quad + + def check_and_validate_polys(self, polys, tags, xxx_todo_changeme): + """ + check so that the text poly is in the same direction, + and also filter some invalid polygons + :param polys: + :param tags: + :return: + """ + (h, w) = xxx_todo_changeme + if polys.shape[0] == 0: + return polys, np.array([]), np.array([]) + polys[:, :, 0] = np.clip(polys[:, :, 0], 0, w - 1) + polys[:, :, 1] = np.clip(polys[:, :, 1], 0, h - 1) + + validated_polys = [] + validated_tags = [] + hv_tags = [] + for poly, tag in zip(polys, tags): + quad = self.gen_quad_from_poly(poly) + p_area = self.quad_area(quad) + if abs(p_area) < 1: + print("invalid poly") + continue + if p_area > 0: + if tag == False: + print("poly in wrong direction") + tag = True # reversed cases should be ignore + poly = poly[(0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1), :] + quad = quad[(0, 3, 2, 1), :] + + len_w = np.linalg.norm(quad[0] - quad[1]) + np.linalg.norm( + quad[3] - quad[2] + ) + len_h = np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm( + quad[1] - quad[2] + ) + hv_tag = 1 + + if len_w * 2.0 < len_h: + hv_tag = 0 + + validated_polys.append(poly) + validated_tags.append(tag) + hv_tags.append(hv_tag) + return np.array(validated_polys), np.array(validated_tags), np.array(hv_tags) + + def crop_area(self, im, polys, tags, hv_tags, crop_background=False, max_tries=25): + """ + make random crop from the input image + :param im: + :param polys: + :param tags: + :param crop_background: + :param max_tries: 50 -> 25 + :return: + """ + h, w, _ = im.shape + pad_h = h // 10 + pad_w = w // 10 + h_array = np.zeros((h + pad_h * 2), dtype=np.int32) + w_array = np.zeros((w + pad_w * 2), dtype=np.int32) + for poly in polys: + poly = np.round(poly, decimals=0).astype(np.int32) + minx = np.min(poly[:, 0]) + maxx = np.max(poly[:, 0]) + w_array[minx + pad_w : maxx + pad_w] = 1 + miny = np.min(poly[:, 1]) + maxy = np.max(poly[:, 1]) + h_array[miny + pad_h : maxy + pad_h] = 1 + # ensure the cropped area not across a text + h_axis = np.where(h_array == 0)[0] + w_axis = np.where(w_array == 0)[0] + if len(h_axis) == 0 or len(w_axis) == 0: + return im, polys, tags, hv_tags + for i in range(max_tries): + xx = np.random.choice(w_axis, size=2) + xmin = np.min(xx) - pad_w + xmax = np.max(xx) - pad_w + xmin = np.clip(xmin, 0, w - 1) + xmax = np.clip(xmax, 0, w - 1) + yy = np.random.choice(h_axis, size=2) + ymin = np.min(yy) - pad_h + ymax = np.max(yy) - pad_h + ymin = np.clip(ymin, 0, h - 1) + ymax = np.clip(ymax, 0, h - 1) + # if xmax - xmin < ARGS.min_crop_side_ratio * w or \ + # ymax - ymin < ARGS.min_crop_side_ratio * h: + if xmax - xmin < self.min_crop_size or ymax - ymin < self.min_crop_size: + # area too small + continue + if polys.shape[0] != 0: + poly_axis_in_area = ( + (polys[:, :, 0] >= xmin) + & (polys[:, :, 0] <= xmax) + & (polys[:, :, 1] >= ymin) + & (polys[:, :, 1] <= ymax) + ) + selected_polys = np.where(np.sum(poly_axis_in_area, axis=1) == 4)[0] + else: + selected_polys = [] + if len(selected_polys) == 0: + # no text in this area + if crop_background: + return ( + im[ymin : ymax + 1, xmin : xmax + 1, :], + polys[selected_polys], + tags[selected_polys], + hv_tags[selected_polys], + ) + else: + continue + im = im[ymin : ymax + 1, xmin : xmax + 1, :] + polys = polys[selected_polys] + tags = tags[selected_polys] + hv_tags = hv_tags[selected_polys] + polys[:, :, 0] -= xmin + polys[:, :, 1] -= ymin + return im, polys, tags, hv_tags + + return im, polys, tags, hv_tags + + def generate_direction_map(self, poly_quads, direction_map): + """ """ + width_list = [] + height_list = [] + for quad in poly_quads: + quad_w = ( + np.linalg.norm(quad[0] - quad[1]) + np.linalg.norm(quad[2] - quad[3]) + ) / 2.0 + quad_h = ( + np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[2] - quad[1]) + ) / 2.0 + width_list.append(quad_w) + height_list.append(quad_h) + norm_width = max(sum(width_list) / (len(width_list) + 1e-6), 1.0) + average_height = max(sum(height_list) / (len(height_list) + 1e-6), 1.0) + + for quad in poly_quads: + direct_vector_full = ((quad[1] + quad[2]) - (quad[0] + quad[3])) / 2.0 + direct_vector = ( + direct_vector_full + / (np.linalg.norm(direct_vector_full) + 1e-6) + * norm_width + ) + direction_label = tuple( + map( + float, + [direct_vector[0], direct_vector[1], 1.0 / (average_height + 1e-6)], + ) + ) + cv2.fillPoly( + direction_map, + quad.round().astype(np.int32)[np.newaxis, :, :], + direction_label, + ) + return direction_map + + def calculate_average_height(self, poly_quads): + """ """ + height_list = [] + for quad in poly_quads: + quad_h = ( + np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[2] - quad[1]) + ) / 2.0 + height_list.append(quad_h) + average_height = max(sum(height_list) / len(height_list), 1.0) + return average_height + + def generate_tcl_label( + self, hw, polys, tags, ds_ratio, tcl_ratio=0.3, shrink_ratio_of_width=0.15 + ): + """ + Generate polygon. + """ + h, w = hw + h, w = int(h * ds_ratio), int(w * ds_ratio) + polys = polys * ds_ratio + + score_map = np.zeros( + ( + h, + w, + ), + dtype=np.float32, + ) + tbo_map = np.zeros((h, w, 5), dtype=np.float32) + training_mask = np.ones( + ( + h, + w, + ), + dtype=np.float32, + ) + direction_map = np.ones((h, w, 3)) * np.array([0, 0, 1]).reshape( + [1, 1, 3] + ).astype(np.float32) + + for poly_idx, poly_tag in enumerate(zip(polys, tags)): + poly = poly_tag[0] + tag = poly_tag[1] + + # generate min_area_quad + min_area_quad, center_point = self.gen_min_area_quad_from_poly(poly) + min_area_quad_h = 0.5 * ( + np.linalg.norm(min_area_quad[0] - min_area_quad[3]) + + np.linalg.norm(min_area_quad[1] - min_area_quad[2]) + ) + min_area_quad_w = 0.5 * ( + np.linalg.norm(min_area_quad[0] - min_area_quad[1]) + + np.linalg.norm(min_area_quad[2] - min_area_quad[3]) + ) + + if ( + min(min_area_quad_h, min_area_quad_w) < self.min_text_size * ds_ratio + or min(min_area_quad_h, min_area_quad_w) > self.max_text_size * ds_ratio + ): + continue + + if tag: + # continue + cv2.fillPoly( + training_mask, poly.astype(np.int32)[np.newaxis, :, :], 0.15 + ) + else: + tcl_poly = self.poly2tcl(poly, tcl_ratio) + tcl_quads = self.poly2quads(tcl_poly) + poly_quads = self.poly2quads(poly) + # stcl map + stcl_quads, quad_index = self.shrink_poly_along_width( + tcl_quads, + shrink_ratio_of_width=shrink_ratio_of_width, + expand_height_ratio=1.0 / tcl_ratio, + ) + # generate tcl map + cv2.fillPoly(score_map, np.round(stcl_quads).astype(np.int32), 1.0) + + # generate tbo map + for idx, quad in enumerate(stcl_quads): + quad_mask = np.zeros((h, w), dtype=np.float32) + quad_mask = cv2.fillPoly( + quad_mask, + np.round(quad[np.newaxis, :, :]).astype(np.int32), + 1.0, + ) + tbo_map = self.gen_quad_tbo( + poly_quads[quad_index[idx]], quad_mask, tbo_map + ) + return score_map, tbo_map, training_mask + + def generate_tvo_and_tco(self, hw, polys, tags, tcl_ratio=0.3, ds_ratio=0.25): + """ + Generate tcl map, tvo map and tbo map. + """ + h, w = hw + h, w = int(h * ds_ratio), int(w * ds_ratio) + polys = polys * ds_ratio + poly_mask = np.zeros((h, w), dtype=np.float32) + + tvo_map = np.ones((9, h, w), dtype=np.float32) + tvo_map[0:-1:2] = np.tile(np.arange(0, w), (h, 1)) + tvo_map[1:-1:2] = np.tile(np.arange(0, w), (h, 1)).T + poly_tv_xy_map = np.zeros((8, h, w), dtype=np.float32) + + # tco map + tco_map = np.ones((3, h, w), dtype=np.float32) + tco_map[0] = np.tile(np.arange(0, w), (h, 1)) + tco_map[1] = np.tile(np.arange(0, w), (h, 1)).T + poly_tc_xy_map = np.zeros((2, h, w), dtype=np.float32) + + poly_short_edge_map = np.ones((h, w), dtype=np.float32) + + for poly, poly_tag in zip(polys, tags): + if poly_tag == True: + continue + + # adjust point order for vertical poly + poly = self.adjust_point(poly) + + # generate min_area_quad + min_area_quad, center_point = self.gen_min_area_quad_from_poly(poly) + min_area_quad_h = 0.5 * ( + np.linalg.norm(min_area_quad[0] - min_area_quad[3]) + + np.linalg.norm(min_area_quad[1] - min_area_quad[2]) + ) + min_area_quad_w = 0.5 * ( + np.linalg.norm(min_area_quad[0] - min_area_quad[1]) + + np.linalg.norm(min_area_quad[2] - min_area_quad[3]) + ) + + # generate tcl map and text, 128 * 128 + tcl_poly = self.poly2tcl(poly, tcl_ratio) + + # generate poly_tv_xy_map + for idx in range(4): + cv2.fillPoly( + poly_tv_xy_map[2 * idx], + np.round(tcl_poly[np.newaxis, :, :]).astype(np.int32), + float(min(max(min_area_quad[idx, 0], 0), w)), + ) + cv2.fillPoly( + poly_tv_xy_map[2 * idx + 1], + np.round(tcl_poly[np.newaxis, :, :]).astype(np.int32), + float(min(max(min_area_quad[idx, 1], 0), h)), + ) + + # generate poly_tc_xy_map + for idx in range(2): + cv2.fillPoly( + poly_tc_xy_map[idx], + np.round(tcl_poly[np.newaxis, :, :]).astype(np.int32), + float(center_point[idx]), + ) + + # generate poly_short_edge_map + cv2.fillPoly( + poly_short_edge_map, + np.round(tcl_poly[np.newaxis, :, :]).astype(np.int32), + float(max(min(min_area_quad_h, min_area_quad_w), 1.0)), + ) + + # generate poly_mask and training_mask + cv2.fillPoly( + poly_mask, np.round(tcl_poly[np.newaxis, :, :]).astype(np.int32), 1 + ) + + tvo_map *= poly_mask + tvo_map[:8] -= poly_tv_xy_map + tvo_map[-1] /= poly_short_edge_map + tvo_map = tvo_map.transpose((1, 2, 0)) + + tco_map *= poly_mask + tco_map[:2] -= poly_tc_xy_map + tco_map[-1] /= poly_short_edge_map + tco_map = tco_map.transpose((1, 2, 0)) + + return tvo_map, tco_map + + def adjust_point(self, poly): + """ + adjust point order. + """ + point_num = poly.shape[0] + if point_num == 4: + len_1 = np.linalg.norm(poly[0] - poly[1]) + len_2 = np.linalg.norm(poly[1] - poly[2]) + len_3 = np.linalg.norm(poly[2] - poly[3]) + len_4 = np.linalg.norm(poly[3] - poly[0]) + + if (len_1 + len_3) * 1.5 < (len_2 + len_4): + poly = poly[[1, 2, 3, 0], :] + + elif point_num > 4: + vector_1 = poly[0] - poly[1] + vector_2 = poly[1] - poly[2] + cos_theta = np.dot(vector_1, vector_2) / ( + np.linalg.norm(vector_1) * np.linalg.norm(vector_2) + 1e-6 + ) + theta = np.arccos(np.round(cos_theta, decimals=4)) + + if abs(theta) > (70 / 180 * math.pi): + index = list(range(1, point_num)) + [0] + poly = poly[np.array(index), :] + return poly + + def gen_min_area_quad_from_poly(self, poly): + """ + Generate min area quad from poly. + """ + point_num = poly.shape[0] + min_area_quad = np.zeros((4, 2), dtype=np.float32) + if point_num == 4: + min_area_quad = poly + center_point = np.sum(poly, axis=0) / 4 + else: + rect = cv2.minAreaRect( + poly.astype(np.int32) + ) # (center (x,y), (width, height), angle of rotation) + center_point = rect[0] + box = np.array(cv2.boxPoints(rect)) + + first_point_idx = 0 + min_dist = 1e4 + for i in range(4): + dist = ( + np.linalg.norm(box[(i + 0) % 4] - poly[0]) + + np.linalg.norm(box[(i + 1) % 4] - poly[point_num // 2 - 1]) + + np.linalg.norm(box[(i + 2) % 4] - poly[point_num // 2]) + + np.linalg.norm(box[(i + 3) % 4] - poly[-1]) + ) + if dist < min_dist: + min_dist = dist + first_point_idx = i + + for i in range(4): + min_area_quad[i] = box[(first_point_idx + i) % 4] + + return min_area_quad, center_point + + def shrink_quad_along_width(self, quad, begin_width_ratio=0.0, end_width_ratio=1.0): + """ + Generate shrink_quad_along_width. + """ + ratio_pair = np.array( + [[begin_width_ratio], [end_width_ratio]], dtype=np.float32 + ) + p0_1 = quad[0] + (quad[1] - quad[0]) * ratio_pair + p3_2 = quad[3] + (quad[2] - quad[3]) * ratio_pair + return np.array([p0_1[0], p0_1[1], p3_2[1], p3_2[0]]) + + def shrink_poly_along_width( + self, quads, shrink_ratio_of_width, expand_height_ratio=1.0 + ): + """ + shrink poly with given length. + """ + upper_edge_list = [] + + def get_cut_info(edge_len_list, cut_len): + for idx, edge_len in enumerate(edge_len_list): + cut_len -= edge_len + if cut_len <= 0.000001: + ratio = (cut_len + edge_len_list[idx]) / edge_len_list[idx] + return idx, ratio + + for quad in quads: + upper_edge_len = np.linalg.norm(quad[0] - quad[1]) + upper_edge_list.append(upper_edge_len) + + # length of left edge and right edge. + left_length = np.linalg.norm(quads[0][0] - quads[0][3]) * expand_height_ratio + right_length = np.linalg.norm(quads[-1][1] - quads[-1][2]) * expand_height_ratio + + shrink_length = ( + min(left_length, right_length, sum(upper_edge_list)) * shrink_ratio_of_width + ) + # shrinking length + upper_len_left = shrink_length + upper_len_right = sum(upper_edge_list) - shrink_length + + left_idx, left_ratio = get_cut_info(upper_edge_list, upper_len_left) + left_quad = self.shrink_quad_along_width( + quads[left_idx], begin_width_ratio=left_ratio, end_width_ratio=1 + ) + right_idx, right_ratio = get_cut_info(upper_edge_list, upper_len_right) + right_quad = self.shrink_quad_along_width( + quads[right_idx], begin_width_ratio=0, end_width_ratio=right_ratio + ) + + out_quad_list = [] + if left_idx == right_idx: + out_quad_list.append( + [left_quad[0], right_quad[1], right_quad[2], left_quad[3]] + ) + else: + out_quad_list.append(left_quad) + for idx in range(left_idx + 1, right_idx): + out_quad_list.append(quads[idx]) + out_quad_list.append(right_quad) + + return np.array(out_quad_list), list(range(left_idx, right_idx + 1)) + + def vector_angle(self, A, B): + """ + Calculate the angle between vector AB and x-axis positive direction. + """ + AB = np.array([B[1] - A[1], B[0] - A[0]]) + return np.arctan2(*AB) + + def theta_line_cross_point(self, theta, point): + """ + Calculate the line through given point and angle in ax + by + c =0 form. + """ + x, y = point + cos = np.cos(theta) + sin = np.sin(theta) + return [sin, -cos, cos * y - sin * x] + + def line_cross_two_point(self, A, B): + """ + Calculate the line through given point A and B in ax + by + c =0 form. + """ + angle = self.vector_angle(A, B) + return self.theta_line_cross_point(angle, A) + + def average_angle(self, poly): + """ + Calculate the average angle between left and right edge in given poly. + """ + p0, p1, p2, p3 = poly + angle30 = self.vector_angle(p3, p0) + angle21 = self.vector_angle(p2, p1) + return (angle30 + angle21) / 2 + + def line_cross_point(self, line1, line2): + """ + line1 and line2 in 0=ax+by+c form, compute the cross point of line1 and line2 + """ + a1, b1, c1 = line1 + a2, b2, c2 = line2 + d = a1 * b2 - a2 * b1 + + if d == 0: + # print("line1", line1) + # print("line2", line2) + print("Cross point does not exist") + return np.array([0, 0], dtype=np.float32) + else: + x = (b1 * c2 - b2 * c1) / d + y = (a2 * c1 - a1 * c2) / d + + return np.array([x, y], dtype=np.float32) + + def quad2tcl(self, poly, ratio): + """ + Generate center line by poly clock-wise point. (4, 2) + """ + ratio_pair = np.array([[0.5 - ratio / 2], [0.5 + ratio / 2]], dtype=np.float32) + p0_3 = poly[0] + (poly[3] - poly[0]) * ratio_pair + p1_2 = poly[1] + (poly[2] - poly[1]) * ratio_pair + return np.array([p0_3[0], p1_2[0], p1_2[1], p0_3[1]]) + + def poly2tcl(self, poly, ratio): + """ + Generate center line by poly clock-wise point. + """ + ratio_pair = np.array([[0.5 - ratio / 2], [0.5 + ratio / 2]], dtype=np.float32) + tcl_poly = np.zeros_like(poly) + point_num = poly.shape[0] + + for idx in range(point_num // 2): + point_pair = ( + poly[idx] + (poly[point_num - 1 - idx] - poly[idx]) * ratio_pair + ) + tcl_poly[idx] = point_pair[0] + tcl_poly[point_num - 1 - idx] = point_pair[1] + return tcl_poly + + def gen_quad_tbo(self, quad, tcl_mask, tbo_map): + """ + Generate tbo_map for give quad. + """ + # upper and lower line function: ax + by + c = 0; + up_line = self.line_cross_two_point(quad[0], quad[1]) + lower_line = self.line_cross_two_point(quad[3], quad[2]) + + quad_h = 0.5 * ( + np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[1] - quad[2]) + ) + quad_w = 0.5 * ( + np.linalg.norm(quad[0] - quad[1]) + np.linalg.norm(quad[2] - quad[3]) + ) + + # average angle of left and right line. + angle = self.average_angle(quad) + + xy_in_poly = np.argwhere(tcl_mask == 1) + for y, x in xy_in_poly: + point = (x, y) + line = self.theta_line_cross_point(angle, point) + cross_point_upper = self.line_cross_point(up_line, line) + cross_point_lower = self.line_cross_point(lower_line, line) + ##FIX, offset reverse + upper_offset_x, upper_offset_y = cross_point_upper - point + lower_offset_x, lower_offset_y = cross_point_lower - point + tbo_map[y, x, 0] = upper_offset_y + tbo_map[y, x, 1] = upper_offset_x + tbo_map[y, x, 2] = lower_offset_y + tbo_map[y, x, 3] = lower_offset_x + tbo_map[y, x, 4] = 1.0 / max(min(quad_h, quad_w), 1.0) * 2 + return tbo_map + + def poly2quads(self, poly): + """ + Split poly into quads. + """ + quad_list = [] + point_num = poly.shape[0] + + # point pair + point_pair_list = [] + for idx in range(point_num // 2): + point_pair = [poly[idx], poly[point_num - 1 - idx]] + point_pair_list.append(point_pair) + + quad_num = point_num // 2 - 1 + for idx in range(quad_num): + # reshape and adjust to clock-wise + quad_list.append( + (np.array(point_pair_list)[[idx, idx + 1]]).reshape(4, 2)[[0, 2, 3, 1]] + ) + + return np.array(quad_list) + + def __call__(self, data): + im = data["image"] + text_polys = data["polys"] + text_tags = data["ignore_tags"] + if im is None: + return None + if text_polys.shape[0] == 0: + return None + + h, w, _ = im.shape + text_polys, text_tags, hv_tags = self.check_and_validate_polys( + text_polys, text_tags, (h, w) + ) + + if text_polys.shape[0] == 0: + return None + + # set aspect ratio and keep area fix + asp_scales = np.arange(1.0, 1.55, 0.1) + asp_scale = np.random.choice(asp_scales) + + if np.random.rand() < 0.5: + asp_scale = 1.0 / asp_scale + asp_scale = math.sqrt(asp_scale) + + asp_wx = asp_scale + asp_hy = 1.0 / asp_scale + im = cv2.resize(im, dsize=None, fx=asp_wx, fy=asp_hy) + text_polys[:, :, 0] *= asp_wx + text_polys[:, :, 1] *= asp_hy + + h, w, _ = im.shape + if max(h, w) > 2048: + rd_scale = 2048.0 / max(h, w) + im = cv2.resize(im, dsize=None, fx=rd_scale, fy=rd_scale) + text_polys *= rd_scale + h, w, _ = im.shape + if min(h, w) < 16: + return None + + # no background + im, text_polys, text_tags, hv_tags = self.crop_area( + im, text_polys, text_tags, hv_tags, crop_background=False + ) + + if text_polys.shape[0] == 0: + return None + # continue for all ignore case + if np.sum((text_tags * 1.0)) >= text_tags.size: + return None + new_h, new_w, _ = im.shape + if (new_h is None) or (new_w is None): + return None + # resize image + std_ratio = float(self.input_size) / max(new_w, new_h) + rand_scales = np.array( + [0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0, 1.0, 1.0, 1.0, 1.0] + ) + rz_scale = std_ratio * np.random.choice(rand_scales) + im = cv2.resize(im, dsize=None, fx=rz_scale, fy=rz_scale) + text_polys[:, :, 0] *= rz_scale + text_polys[:, :, 1] *= rz_scale + + # add gaussian blur + if np.random.rand() < 0.1 * 0.5: + ks = np.random.permutation(5)[0] + 1 + ks = int(ks / 2) * 2 + 1 + im = cv2.GaussianBlur(im, ksize=(ks, ks), sigmaX=0, sigmaY=0) + # add brighter + if np.random.rand() < 0.1 * 0.5: + im = im * (1.0 + np.random.rand() * 0.5) + im = np.clip(im, 0.0, 255.0) + # add darker + if np.random.rand() < 0.1 * 0.5: + im = im * (1.0 - np.random.rand() * 0.5) + im = np.clip(im, 0.0, 255.0) + + # Padding the im to [input_size, input_size] + new_h, new_w, _ = im.shape + if min(new_w, new_h) < self.input_size * 0.5: + return None + + im_padded = np.ones((self.input_size, self.input_size, 3), dtype=np.float32) + im_padded[:, :, 2] = 0.485 * 255 + im_padded[:, :, 1] = 0.456 * 255 + im_padded[:, :, 0] = 0.406 * 255 + + # Random the start position + del_h = self.input_size - new_h + del_w = self.input_size - new_w + sh, sw = 0, 0 + if del_h > 1: + sh = int(np.random.rand() * del_h) + if del_w > 1: + sw = int(np.random.rand() * del_w) + + # Padding + im_padded[sh : sh + new_h, sw : sw + new_w, :] = im.copy() + text_polys[:, :, 0] += sw + text_polys[:, :, 1] += sh + + score_map, border_map, training_mask = self.generate_tcl_label( + (self.input_size, self.input_size), text_polys, text_tags, 0.25 + ) + + # SAST head + tvo_map, tco_map = self.generate_tvo_and_tco( + (self.input_size, self.input_size), + text_polys, + text_tags, + tcl_ratio=0.3, + ds_ratio=0.25, + ) + # print("test--------tvo_map shape:", tvo_map.shape) + + im_padded[:, :, 2] -= 0.485 * 255 + im_padded[:, :, 1] -= 0.456 * 255 + im_padded[:, :, 0] -= 0.406 * 255 + im_padded[:, :, 2] /= 255.0 * 0.229 + im_padded[:, :, 1] /= 255.0 * 0.224 + im_padded[:, :, 0] /= 255.0 * 0.225 + im_padded = im_padded.transpose((2, 0, 1)) + + data["image"] = im_padded[::-1, :, :] + data["score_map"] = score_map[np.newaxis, :, :] + data["border_map"] = border_map.transpose((2, 0, 1)) + data["training_mask"] = training_mask[np.newaxis, :, :] + data["tvo_map"] = tvo_map.transpose((2, 0, 1)) + data["tco_map"] = tco_map.transpose((2, 0, 1)) + return data diff --git a/ppocr/data/imaug/ssl_img_aug.py b/ppocr/data/imaug/ssl_img_aug.py new file mode 100644 index 0000000..8162087 --- /dev/null +++ b/ppocr/data/imaug/ssl_img_aug.py @@ -0,0 +1,55 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import cv2 +import numpy as np +import random +from PIL import Image + +from .rec_img_aug import resize_norm_img + + +class SSLRotateResize(object): + def __init__( + self, image_shape, padding=False, select_all=True, mode="train", **kwargs + ): + self.image_shape = image_shape + self.padding = padding + self.select_all = select_all + self.mode = mode + + def __call__(self, data): + img = data["image"] + + data["image_r90"] = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) + data["image_r180"] = cv2.rotate(data["image_r90"], cv2.ROTATE_90_CLOCKWISE) + data["image_r270"] = cv2.rotate(data["image_r180"], cv2.ROTATE_90_CLOCKWISE) + + images = [] + for key in ["image", "image_r90", "image_r180", "image_r270"]: + images.append( + resize_norm_img( + data.pop(key), image_shape=self.image_shape, padding=self.padding + )[0] + ) + data["image"] = np.stack(images, axis=0) + data["label"] = np.array(list(range(4))) + if not self.select_all: + data["image"] = data["image"][0::2] # just choose 0 and 180 + data["label"] = data["label"][0:2] # label needs to be continuous + if self.mode == "test": + data["image"] = data["image"][0] + data["label"] = data["label"][0] + return data diff --git a/ppocr/data/imaug/table_ops.py b/ppocr/data/imaug/table_ops.py new file mode 100644 index 0000000..cc244dd --- /dev/null +++ b/ppocr/data/imaug/table_ops.py @@ -0,0 +1,232 @@ +""" +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import sys +import cv2 +import numpy as np + + +class GenTableMask(object): + """gen table mask""" + + def __init__(self, shrink_h_max, shrink_w_max, mask_type=0, **kwargs): + self.shrink_h_max = 5 + self.shrink_w_max = 5 + self.mask_type = mask_type + + def projection(self, erosion, h, w, spilt_threshold=0): + # 水平投影 + projection_map = np.ones_like(erosion) + project_val_array = [0 for _ in range(0, h)] + + for j in range(0, h): + for i in range(0, w): + if erosion[j, i] == 255: + project_val_array[j] += 1 + # 根据数组,获取切割点 + start_idx = 0 # 记录进入字符区的索引 + end_idx = 0 # 记录进入空白区域的索引 + in_text = False # 是否遍历到了字符区内 + box_list = [] + for i in range(len(project_val_array)): + if ( + in_text == False and project_val_array[i] > spilt_threshold + ): # 进入字符区了 + in_text = True + start_idx = i + elif ( + project_val_array[i] <= spilt_threshold and in_text == True + ): # 进入空白区了 + end_idx = i + in_text = False + if end_idx - start_idx <= 2: + continue + box_list.append((start_idx, end_idx + 1)) + + if in_text: + box_list.append((start_idx, h - 1)) + # 绘制投影直方图 + for j in range(0, h): + for i in range(0, project_val_array[j]): + projection_map[j, i] = 0 + return box_list, projection_map + + def projection_cx(self, box_img): + box_gray_img = cv2.cvtColor(box_img, cv2.COLOR_BGR2GRAY) + h, w = box_gray_img.shape + # 灰度图片进行二值化处理 + ret, thresh1 = cv2.threshold(box_gray_img, 200, 255, cv2.THRESH_BINARY_INV) + # 纵向腐蚀 + if h < w: + kernel = np.ones((2, 1), np.uint8) + erode = cv2.erode(thresh1, kernel, iterations=1) + else: + erode = thresh1 + # 水平膨胀 + kernel = np.ones((1, 5), np.uint8) + erosion = cv2.dilate(erode, kernel, iterations=1) + # 水平投影 + projection_map = np.ones_like(erosion) + project_val_array = [0 for _ in range(0, h)] + + for j in range(0, h): + for i in range(0, w): + if erosion[j, i] == 255: + project_val_array[j] += 1 + # 根据数组,获取切割点 + start_idx = 0 # 记录进入字符区的索引 + end_idx = 0 # 记录进入空白区域的索引 + in_text = False # 是否遍历到了字符区内 + box_list = [] + spilt_threshold = 0 + for i in range(len(project_val_array)): + if ( + in_text == False and project_val_array[i] > spilt_threshold + ): # 进入字符区了 + in_text = True + start_idx = i + elif ( + project_val_array[i] <= spilt_threshold and in_text == True + ): # 进入空白区了 + end_idx = i + in_text = False + if end_idx - start_idx <= 2: + continue + box_list.append((start_idx, end_idx + 1)) + + if in_text: + box_list.append((start_idx, h - 1)) + # 绘制投影直方图 + for j in range(0, h): + for i in range(0, project_val_array[j]): + projection_map[j, i] = 0 + split_bbox_list = [] + if len(box_list) > 1: + for i, (h_start, h_end) in enumerate(box_list): + if i == 0: + h_start = 0 + if i == len(box_list): + h_end = h + word_img = erosion[h_start : h_end + 1, :] + word_h, word_w = word_img.shape + w_split_list, w_projection_map = self.projection( + word_img.T, word_w, word_h + ) + w_start, w_end = w_split_list[0][0], w_split_list[-1][1] + if h_start > 0: + h_start -= 1 + h_end += 1 + word_img = box_img[h_start : h_end + 1 :, w_start : w_end + 1, :] + split_bbox_list.append([w_start, h_start, w_end, h_end]) + else: + split_bbox_list.append([0, 0, w, h]) + return split_bbox_list + + def shrink_bbox(self, bbox): + left, top, right, bottom = bbox + sh_h = min(max(int((bottom - top) * 0.1), 1), self.shrink_h_max) + sh_w = min(max(int((right - left) * 0.1), 1), self.shrink_w_max) + left_new = left + sh_w + right_new = right - sh_w + top_new = top + sh_h + bottom_new = bottom - sh_h + if left_new >= right_new: + left_new = left + right_new = right + if top_new >= bottom_new: + top_new = top + bottom_new = bottom + return [left_new, top_new, right_new, bottom_new] + + def __call__(self, data): + img = data["image"] + cells = data["cells"] + height, width = img.shape[0:2] + if self.mask_type == 1: + mask_img = np.zeros((height, width), dtype=np.float32) + else: + mask_img = np.zeros((height, width, 3), dtype=np.float32) + cell_num = len(cells) + for cno in range(cell_num): + if "bbox" in cells[cno]: + bbox = cells[cno]["bbox"] + left, top, right, bottom = bbox + box_img = img[top:bottom, left:right, :].copy() + split_bbox_list = self.projection_cx(box_img) + for sno in range(len(split_bbox_list)): + split_bbox_list[sno][0] += left + split_bbox_list[sno][1] += top + split_bbox_list[sno][2] += left + split_bbox_list[sno][3] += top + + for sno in range(len(split_bbox_list)): + left, top, right, bottom = split_bbox_list[sno] + left, top, right, bottom = self.shrink_bbox( + [left, top, right, bottom] + ) + if self.mask_type == 1: + mask_img[top:bottom, left:right] = 1.0 + data["mask_img"] = mask_img + else: + mask_img[top:bottom, left:right, :] = (255, 255, 255) + data["image"] = mask_img + return data + + +class ResizeTableImage(object): + def __init__(self, max_len, resize_bboxes=False, infer_mode=False, **kwargs): + super(ResizeTableImage, self).__init__() + self.max_len = max_len + self.resize_bboxes = resize_bboxes + self.infer_mode = infer_mode + + def __call__(self, data): + img = data["image"] + height, width = img.shape[0:2] + ratio = self.max_len / (max(height, width) * 1.0) + resize_h = int(height * ratio) + resize_w = int(width * ratio) + resize_img = cv2.resize(img, (resize_w, resize_h)) + if self.resize_bboxes and not self.infer_mode: + data["bboxes"] = data["bboxes"] * ratio + data["image"] = resize_img + data["src_img"] = img + data["shape"] = np.array([height, width, ratio, ratio]) + data["max_len"] = self.max_len + return data + + +class PaddingTableImage(object): + def __init__(self, size, **kwargs): + super(PaddingTableImage, self).__init__() + self.size = size + + def __call__(self, data): + img = data["image"] + pad_h, pad_w = self.size + padding_img = np.zeros((pad_h, pad_w, 3), dtype=np.float32) + height, width = img.shape[0:2] + padding_img[0:height, 0:width, :] = img.copy() + data["image"] = padding_img + shape = data["shape"].tolist() + shape.extend([pad_h, pad_w]) + data["shape"] = np.array(shape) + return data diff --git a/ppocr/data/imaug/text_image_aug/__init__.py b/ppocr/data/imaug/text_image_aug/__init__.py new file mode 100644 index 0000000..16f179f --- /dev/null +++ b/ppocr/data/imaug/text_image_aug/__init__.py @@ -0,0 +1,17 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .augment import tia_perspective, tia_distort, tia_stretch + +__all__ = ["tia_distort", "tia_stretch", "tia_perspective"] diff --git a/ppocr/data/imaug/text_image_aug/augment.py b/ppocr/data/imaug/text_image_aug/augment.py new file mode 100644 index 0000000..1044abd --- /dev/null +++ b/ppocr/data/imaug/text_image_aug/augment.py @@ -0,0 +1,123 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/RubanSeven/Text-Image-Augmentation-python/blob/master/augment.py +""" + +import numpy as np +from .warp_mls import WarpMLS + + +def tia_distort(src, segment=4): + img_h, img_w = src.shape[:2] + + cut = img_w // segment + thresh = cut // 3 + + src_pts = list() + dst_pts = list() + + src_pts.append([0, 0]) + src_pts.append([img_w, 0]) + src_pts.append([img_w, img_h]) + src_pts.append([0, img_h]) + + dst_pts.append([np.random.randint(thresh), np.random.randint(thresh)]) + dst_pts.append([img_w - np.random.randint(thresh), np.random.randint(thresh)]) + dst_pts.append( + [img_w - np.random.randint(thresh), img_h - np.random.randint(thresh)] + ) + dst_pts.append([np.random.randint(thresh), img_h - np.random.randint(thresh)]) + + half_thresh = thresh * 0.5 + + for cut_idx in np.arange(1, segment, 1): + src_pts.append([cut * cut_idx, 0]) + src_pts.append([cut * cut_idx, img_h]) + dst_pts.append( + [ + cut * cut_idx + np.random.randint(thresh) - half_thresh, + np.random.randint(thresh) - half_thresh, + ] + ) + dst_pts.append( + [ + cut * cut_idx + np.random.randint(thresh) - half_thresh, + img_h + np.random.randint(thresh) - half_thresh, + ] + ) + + trans = WarpMLS(src, src_pts, dst_pts, img_w, img_h) + dst = trans.generate() + + return dst + + +def tia_stretch(src, segment=4): + img_h, img_w = src.shape[:2] + + cut = img_w // segment + thresh = cut * 4 // 5 + + src_pts = list() + dst_pts = list() + + src_pts.append([0, 0]) + src_pts.append([img_w, 0]) + src_pts.append([img_w, img_h]) + src_pts.append([0, img_h]) + + dst_pts.append([0, 0]) + dst_pts.append([img_w, 0]) + dst_pts.append([img_w, img_h]) + dst_pts.append([0, img_h]) + + half_thresh = thresh * 0.5 + + for cut_idx in np.arange(1, segment, 1): + move = np.random.randint(thresh) - half_thresh + src_pts.append([cut * cut_idx, 0]) + src_pts.append([cut * cut_idx, img_h]) + dst_pts.append([cut * cut_idx + move, 0]) + dst_pts.append([cut * cut_idx + move, img_h]) + + trans = WarpMLS(src, src_pts, dst_pts, img_w, img_h) + dst = trans.generate() + + return dst + + +def tia_perspective(src): + img_h, img_w = src.shape[:2] + + thresh = img_h // 2 + + src_pts = list() + dst_pts = list() + + src_pts.append([0, 0]) + src_pts.append([img_w, 0]) + src_pts.append([img_w, img_h]) + src_pts.append([0, img_h]) + + dst_pts.append([0, np.random.randint(thresh)]) + dst_pts.append([img_w, np.random.randint(thresh)]) + dst_pts.append([img_w, img_h - np.random.randint(thresh)]) + dst_pts.append([0, img_h - np.random.randint(thresh)]) + + trans = WarpMLS(src, src_pts, dst_pts, img_w, img_h) + dst = trans.generate() + + return dst diff --git a/ppocr/data/imaug/text_image_aug/warp_mls.py b/ppocr/data/imaug/text_image_aug/warp_mls.py new file mode 100644 index 0000000..2d349a6 --- /dev/null +++ b/ppocr/data/imaug/text_image_aug/warp_mls.py @@ -0,0 +1,187 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/RubanSeven/Text-Image-Augmentation-python/blob/master/warp_mls.py +""" + +import numpy as np + + +class WarpMLS: + def __init__(self, src, src_pts, dst_pts, dst_w, dst_h, trans_ratio=1.0): + self.src = src + self.src_pts = src_pts + self.dst_pts = dst_pts + self.pt_count = len(self.dst_pts) + self.dst_w = dst_w + self.dst_h = dst_h + self.trans_ratio = trans_ratio + self.grid_size = 100 + self.rdx = np.zeros((self.dst_h, self.dst_w)) + self.rdy = np.zeros((self.dst_h, self.dst_w)) + + @staticmethod + def __bilinear_interp(x, y, v11, v12, v21, v22): + return (v11 * (1 - y) + v12 * y) * (1 - x) + (v21 * (1 - y) + v22 * y) * x + + def generate(self): + self.calc_delta() + return self.gen_img() + + def calc_delta(self): + w = np.zeros(self.pt_count, dtype=np.float32) + + if self.pt_count < 2: + return + + i = 0 + while 1: + if self.dst_w <= i < self.dst_w + self.grid_size - 1: + i = self.dst_w - 1 + elif i >= self.dst_w: + break + + j = 0 + while 1: + if self.dst_h <= j < self.dst_h + self.grid_size - 1: + j = self.dst_h - 1 + elif j >= self.dst_h: + break + + sw = 0 + swp = np.zeros(2, dtype=np.float32) + swq = np.zeros(2, dtype=np.float32) + new_pt = np.zeros(2, dtype=np.float32) + cur_pt = np.array([i, j], dtype=np.float32) + + k = 0 + for k in range(self.pt_count): + if i == self.dst_pts[k][0] and j == self.dst_pts[k][1]: + break + + w[k] = 1.0 / ( + (i - self.dst_pts[k][0]) * (i - self.dst_pts[k][0]) + + (j - self.dst_pts[k][1]) * (j - self.dst_pts[k][1]) + ) + + sw += w[k] + swp = swp + w[k] * np.array(self.dst_pts[k]) + swq = swq + w[k] * np.array(self.src_pts[k]) + + if k == self.pt_count - 1: + pstar = 1 / sw * swp + qstar = 1 / sw * swq + + miu_s = 0 + for k in range(self.pt_count): + if i == self.dst_pts[k][0] and j == self.dst_pts[k][1]: + continue + pt_i = self.dst_pts[k] - pstar + miu_s += w[k] * np.sum(pt_i * pt_i) + + cur_pt -= pstar + cur_pt_j = np.array([-cur_pt[1], cur_pt[0]]) + + for k in range(self.pt_count): + if i == self.dst_pts[k][0] and j == self.dst_pts[k][1]: + continue + + pt_i = self.dst_pts[k] - pstar + pt_j = np.array([-pt_i[1], pt_i[0]]) + + tmp_pt = np.zeros(2, dtype=np.float32) + tmp_pt[0] = ( + np.sum(pt_i * cur_pt) * self.src_pts[k][0] + - np.sum(pt_j * cur_pt) * self.src_pts[k][1] + ) + tmp_pt[1] = ( + -np.sum(pt_i * cur_pt_j) * self.src_pts[k][0] + + np.sum(pt_j * cur_pt_j) * self.src_pts[k][1] + ) + tmp_pt *= w[k] / miu_s + new_pt += tmp_pt + + new_pt += qstar + else: + new_pt = self.src_pts[k] + + self.rdx[j, i] = new_pt[0] - i + self.rdy[j, i] = new_pt[1] - j + + j += self.grid_size + i += self.grid_size + + def gen_img(self): + src_h, src_w = self.src.shape[:2] + dst = np.zeros_like(self.src, dtype=np.float32) + + for i in np.arange(0, self.dst_h, self.grid_size): + for j in np.arange(0, self.dst_w, self.grid_size): + ni = i + self.grid_size + nj = j + self.grid_size + w = h = self.grid_size + if ni >= self.dst_h: + ni = self.dst_h - 1 + h = ni - i + 1 + if nj >= self.dst_w: + nj = self.dst_w - 1 + w = nj - j + 1 + + di = np.reshape(np.arange(h), (-1, 1)) + dj = np.reshape(np.arange(w), (1, -1)) + delta_x = self.__bilinear_interp( + di / h, + dj / w, + self.rdx[i, j], + self.rdx[i, nj], + self.rdx[ni, j], + self.rdx[ni, nj], + ) + delta_y = self.__bilinear_interp( + di / h, + dj / w, + self.rdy[i, j], + self.rdy[i, nj], + self.rdy[ni, j], + self.rdy[ni, nj], + ) + nx = j + dj + delta_x * self.trans_ratio + ny = i + di + delta_y * self.trans_ratio + nx = np.clip(nx, 0, src_w - 1) + ny = np.clip(ny, 0, src_h - 1) + nxi = np.array(np.floor(nx), dtype=np.int32) + nyi = np.array(np.floor(ny), dtype=np.int32) + nxi1 = np.array(np.ceil(nx), dtype=np.int32) + nyi1 = np.array(np.ceil(ny), dtype=np.int32) + + if len(self.src.shape) == 3: + x = np.tile(np.expand_dims(ny - nyi, axis=-1), (1, 1, 3)) + y = np.tile(np.expand_dims(nx - nxi, axis=-1), (1, 1, 3)) + else: + x = ny - nyi + y = nx - nxi + dst[i : i + h, j : j + w] = self.__bilinear_interp( + x, + y, + self.src[nyi, nxi], + self.src[nyi, nxi1], + self.src[nyi1, nxi], + self.src[nyi1, nxi1], + ) + + dst = np.clip(dst, 0, 255) + dst = np.array(dst, dtype=np.uint8) + + return dst diff --git a/ppocr/data/imaug/unimernet_aug.py b/ppocr/data/imaug/unimernet_aug.py new file mode 100644 index 0000000..a9fe8f8 --- /dev/null +++ b/ppocr/data/imaug/unimernet_aug.py @@ -0,0 +1,834 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import os + +os.environ["NO_ALBUMENTATIONS_UPDATE"] = "1" + +import cv2 +import math +import numpy as np +from io import BytesIO +import albumentations as A +from PIL import Image, ImageOps, ImageDraw +from scipy.ndimage import zoom as scizoom + + +class Erosion(A.ImageOnlyTransform): + def __init__(self, scale, always_apply=False, p=0.5): + super().__init__(always_apply=always_apply, p=p) + if type(scale) is tuple or type(scale) is list: + assert len(scale) == 2 + self.scale = scale + else: + self.scale = (scale, scale) + + def apply(self, img, **params): + kernel = cv2.getStructuringElement( + cv2.MORPH_ELLIPSE, tuple(np.random.randint(self.scale[0], self.scale[1], 2)) + ) + img = cv2.erode(img, kernel, iterations=1) + return img + + +class Dilation(A.ImageOnlyTransform): + def __init__(self, scale, always_apply=False, p=0.5): + super().__init__(always_apply=always_apply, p=p) + if type(scale) is tuple or type(scale) is list: + assert len(scale) == 2 + self.scale = scale + else: + self.scale = (scale, scale) + + def apply(self, img, **params): + kernel = cv2.getStructuringElement( + cv2.MORPH_ELLIPSE, tuple(np.random.randint(self.scale[0], self.scale[1], 2)) + ) + img = cv2.dilate(img, kernel, iterations=1) + return img + + +class Bitmap(A.ImageOnlyTransform): + def __init__(self, value=0, lower=200, always_apply=False, p=0.5): + super().__init__(always_apply=always_apply, p=p) + self.lower = lower + self.value = value + + def apply(self, img, **params): + img = img.copy() + img[img < self.lower] = self.value + return img + + +def clipped_zoom(img, zoom_factor): + h = img.shape[1] + ch = int(np.ceil(h / float(zoom_factor))) + top = (h - ch) // 2 + img = scizoom( + img[top : top + ch, top : top + ch], (zoom_factor, zoom_factor, 1), order=1 + ) + trim_top = (img.shape[0] - h) // 2 + + return img[trim_top : trim_top + h, trim_top : trim_top + h] + + +def disk(radius, alias_blur=0.1, dtype=np.float32): + if radius <= 8: + coords = np.arange(-8, 8 + 1) + ksize = (3, 3) + else: + coords = np.arange(-radius, radius + 1) + ksize = (5, 5) + x, y = np.meshgrid(coords, coords) + aliased_disk = np.asarray((x**2 + y**2) <= radius**2, dtype=dtype) + aliased_disk /= np.sum(aliased_disk) + + return cv2.GaussianBlur(aliased_disk, ksize=ksize, sigmaX=alias_blur) + + +def plasma_fractal(mapsize=256, wibbledecay=3, rng=None): + """ + Generate a heightmap using diamond-square algorithm. + Return square 2d array, side length 'mapsize', of floats in range 0-255. + 'mapsize' must be a power of two. + """ + assert mapsize & (mapsize - 1) == 0 + maparray = np.empty((mapsize, mapsize), dtype=np.float64) + maparray[0, 0] = 0 + stepsize = mapsize + wibble = 100 + if rng is None: + rng = np.random.default_rng() + + def wibbledmean(array): + return array / 4 + wibble * rng.uniform(-wibble, wibble, array.shape) + + def fillsquares(): + """For each square of points stepsize apart, + calculate middle value as mean of points + wibble""" + cornerref = maparray[0:mapsize:stepsize, 0:mapsize:stepsize] + squareaccum = cornerref + np.roll(cornerref, shift=-1, axis=0) + squareaccum += np.roll(squareaccum, shift=-1, axis=1) + maparray[ + stepsize // 2 : mapsize : stepsize, stepsize // 2 : mapsize : stepsize + ] = wibbledmean(squareaccum) + + def filldiamonds(): + """For each diamond of points stepsize apart, + calculate middle value as mean of points + wibble""" + drgrid = maparray[ + stepsize // 2 : mapsize : stepsize, stepsize // 2 : mapsize : stepsize + ] + ulgrid = maparray[0:mapsize:stepsize, 0:mapsize:stepsize] + ldrsum = drgrid + np.roll(drgrid, 1, axis=0) + lulsum = ulgrid + np.roll(ulgrid, -1, axis=1) + ltsum = ldrsum + lulsum + maparray[0:mapsize:stepsize, stepsize // 2 : mapsize : stepsize] = wibbledmean( + ltsum + ) + tdrsum = drgrid + np.roll(drgrid, 1, axis=1) + tulsum = ulgrid + np.roll(ulgrid, -1, axis=0) + ttsum = tdrsum + tulsum + maparray[stepsize // 2 : mapsize : stepsize, 0:mapsize:stepsize] = wibbledmean( + ttsum + ) + + while stepsize >= 2: + fillsquares() + filldiamonds() + stepsize //= 2 + wibble /= wibbledecay + + maparray -= maparray.min() + return maparray / maparray.max() + + +class Fog(A.ImageOnlyTransform): + def __init__(self, mag=-1, always_apply=False, p=1.0): + super().__init__(always_apply=always_apply, p=p) + self.rng = np.random.default_rng() + self.mag = mag + + def apply(self, img, **params): + img = Image.fromarray(img.astype(np.uint8)) + w, h = img.size + c = [(1.5, 2), (2.0, 2), (2.5, 1.7)] + if self.mag < 0 or self.mag >= len(c): + index = self.rng.integers(0, len(c)) + else: + index = self.mag + c = c[index] + + n_channels = len(img.getbands()) + isgray = n_channels == 1 + + img = np.asarray(img) / 255.0 + max_val = img.max() + max_size = 2 ** math.ceil(math.log2(max(w, h)) + 1) + fog = ( + c[0] + * plasma_fractal(mapsize=max_size, wibbledecay=c[1], rng=self.rng)[:h, :w][ + ..., np.newaxis + ] + ) + if isgray: + fog = np.squeeze(fog) + else: + fog = np.repeat(fog, 3, axis=2) + + img += fog + img = np.clip(img * max_val / (max_val + c[0]), 0, 1) * 255 + return img.astype(np.uint8) + + +class Frost(A.ImageOnlyTransform): + def __init__(self, mag=-1, always_apply=False, p=1.0): + super().__init__(always_apply=always_apply, p=p) + self.rng = np.random.default_rng() + self.mag = mag + + def apply(self, img, **params): + img = Image.fromarray(img.astype(np.uint8)) + w, h = img.size + c = [(0.78, 0.22), (0.64, 0.36), (0.5, 0.5)] + if self.mag < 0 or self.mag >= len(c): + index = self.rng.integers(0, len(c)) + else: + index = self.mag + c = c[index] + + file_dir = os.path.dirname(__file__) + filename = [ + os.path.join(file_dir, "frost_img", "frost1.jpg"), + os.path.join(file_dir, "frost_img", "frost2.png"), + os.path.join(file_dir, "frost_img", "frost3.png"), + os.path.join(file_dir, "frost_img", "frost4.jpg"), + os.path.join(file_dir, "frost_img", "frost5.jpg"), + os.path.join(file_dir, "frost_img", "frost6.jpg"), + ] + index = self.rng.integers(0, len(filename)) + filename = filename[index] + frost = Image.open(filename).convert("RGB") + + f_w, f_h = frost.size + if w / h > f_w / f_h: + f_h = round(f_h * w / f_w) + f_w = w + else: + f_w = round(f_w * h / f_h) + f_h = h + frost = np.asarray(frost.resize((f_w, f_h))) + + # randomly crop + y_start, x_start = self.rng.integers(0, f_h - h + 1), self.rng.integers( + 0, f_w - w + 1 + ) + frost = frost[y_start : y_start + h, x_start : x_start + w] + + n_channels = len(img.getbands()) + isgray = n_channels == 1 + + img = np.asarray(img) + + if isgray: + img = np.expand_dims(img, axis=2) + img = np.repeat(img, 3, axis=2) + + img = np.clip(np.round(c[0] * img + c[1] * frost), 0, 255) + img = img.astype(np.uint8) + if isgray: + img = np.squeeze(img) + return img + + +class Snow(A.ImageOnlyTransform): + def __init__(self, mag=-1, always_apply=False, p=1.0): + super().__init__(always_apply=always_apply, p=p) + self.rng = np.random.default_rng() + self.mag = mag + + def apply(self, img, **params): + from wand.image import Image as WandImage + + img = Image.fromarray(img.astype(np.uint8)) + w, h = img.size + c = [ + (0.1, 0.3, 3, 0.5, 10, 4, 0.8), + (0.2, 0.3, 2, 0.5, 12, 4, 0.7), + (0.55, 0.3, 4, 0.9, 12, 8, 0.7), + ] + if self.mag < 0 or self.mag >= len(c): + index = self.rng.integers(0, len(c)) + else: + index = self.mag + c = c[index] + + n_channels = len(img.getbands()) + isgray = n_channels == 1 + + img = np.asarray(img, dtype=np.float32) / 255.0 + if isgray: + img = np.expand_dims(img, axis=2) + img = np.repeat(img, 3, axis=2) + + snow_layer = self.rng.normal(size=img.shape[:2], loc=c[0], scale=c[1]) + + snow_layer[snow_layer < c[3]] = 0 + + snow_layer = Image.fromarray( + (np.clip(snow_layer.squeeze(), 0, 1) * 255).astype(np.uint8), mode="L" + ) + output = BytesIO() + snow_layer.save(output, format="PNG") + snow_layer = WandImage(blob=output.getvalue()) + + snow_layer.motion_blur( + radius=c[4], sigma=c[5], angle=self.rng.uniform(-135, -45) + ) + + snow_layer = ( + cv2.imdecode( + np.frombuffer(snow_layer.make_blob(), np.uint8), cv2.IMREAD_UNCHANGED + ) + / 255.0 + ) + + snow_layer = snow_layer[..., np.newaxis] + + img = c[6] * img + gray_img = (1 - c[6]) * np.maximum( + img, cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).reshape(h, w, 1) * 1.5 + 0.5 + ) + img += gray_img + img = np.clip(img + snow_layer + np.rot90(snow_layer, k=2), 0, 1) * 255 + img = img.astype(np.uint8) + if isgray: + img = np.squeeze(img) + return img + + +class Rain(A.ImageOnlyTransform): + def __init__(self, mag=-1, always_apply=False, p=1.0): + super().__init__(always_apply=always_apply, p=p) + self.rng = np.random.default_rng() + self.mag = mag + + def apply(self, img, **params): + img = Image.fromarray(img.astype(np.uint8)) + img = img.copy() + w, h = img.size + n_channels = len(img.getbands()) + isgray = n_channels == 1 + line_width = self.rng.integers(1, 2) + + c = [50, 70, 90] + if self.mag < 0 or self.mag >= len(c): + index = 0 + else: + index = self.mag + c = c[index] + + n_rains = self.rng.integers(c, c + 20) + slant = self.rng.integers(-60, 60) + fillcolor = 200 if isgray else (200, 200, 200) + + draw = ImageDraw.Draw(img) + max_length = min(w, h, 10) + for i in range(1, n_rains): + length = self.rng.integers(5, max_length) + x1 = self.rng.integers(0, w - length) + y1 = self.rng.integers(0, h - length) + x2 = x1 + length * math.sin(slant * math.pi / 180.0) + y2 = y1 + length * math.cos(slant * math.pi / 180.0) + x2 = int(x2) + y2 = int(y2) + draw.line([(x1, y1), (x2, y2)], width=line_width, fill=fillcolor) + img = np.asarray(img).astype(np.uint8) + return img + + +class Shadow(A.ImageOnlyTransform): + def __init__(self, mag=-1, always_apply=False, p=1.0): + super().__init__(always_apply=always_apply, p=p) + self.rng = np.random.default_rng() + self.mag = mag + + def apply(self, img, **params): + img = Image.fromarray(img.astype(np.uint8)) + w, h = img.size + n_channels = len(img.getbands()) + isgray = n_channels == 1 + + c = [64, 96, 128] + if self.mag < 0 or self.mag >= len(c): + index = 0 + else: + index = self.mag + c = c[index] + + img = img.convert("RGBA") + overlay = Image.new("RGBA", img.size, (255, 255, 255, 0)) + draw = ImageDraw.Draw(overlay) + transparency = self.rng.integers(c, c + 32) + x1 = self.rng.integers(0, w // 2) + y1 = 0 + + x2 = self.rng.integers(w // 2, w) + y2 = 0 + + x3 = self.rng.integers(w // 2, w) + y3 = h - 1 + + x4 = self.rng.integers(0, w // 2) + y4 = h - 1 + + draw.polygon( + [(x1, y1), (x2, y2), (x3, y3), (x4, y4)], fill=(0, 0, 0, transparency) + ) + + img = Image.alpha_composite(img, overlay) + img = img.convert("RGB") + if isgray: + img = ImageOps.grayscale(img) + img = np.asarray(img).astype(np.uint8) + return img + + +class UniMERNetTrainTransform: + def __init__(self, bitmap_prob=0.04, **kwargs): + self.bitmap_prob = bitmap_prob + if tuple(map(int, A.__version__.split("."))) >= (2, 0, 0): + new_val = (0, (10 / 255) ** 0.5) + GaussNoise = A.GaussNoise(new_val, p=0.2) + ImageCompression = A.ImageCompression(quality_range=(95, 100), p=0.3) + else: + GaussNoise = A.GaussNoise(10, p=0.2) + ImageCompression = A.ImageCompression(95, p=0.3) + self.train_transform = A.Compose( + [ + A.Compose( + [ + Bitmap(p=0.05), + A.OneOf([Fog(), Frost(), Snow(), Rain(), Shadow()], p=0.2), + A.OneOf([Erosion((2, 3)), Dilation((2, 3))], p=0.2), + A.ShiftScaleRotate( + shift_limit=0, + scale_limit=(-0.15, 0), + rotate_limit=1, + border_mode=0, + interpolation=3, + value=[255, 255, 255], + p=1, + ), + A.GridDistortion( + distort_limit=0.1, + border_mode=0, + interpolation=3, + value=[255, 255, 255], + p=0.5, + ), + ], + p=0.15, + ), + A.RGBShift(r_shift_limit=15, g_shift_limit=15, b_shift_limit=15, p=0.3), + GaussNoise, + A.RandomBrightnessContrast(0.05, (-0.2, 0), True, p=0.2), + ImageCompression, + A.ToGray(always_apply=True), + A.Normalize((0.7931, 0.7931, 0.7931), (0.1738, 0.1738, 0.1738)), + ] + ) + + def __call__(self, data): + img = data["image"] + if np.random.random() < self.bitmap_prob: + img[img != 255] = 0 + img = self.train_transform(image=img)["image"] + data["image"] = img + return data + + +class UniMERNetTestTransform: + def __init__(self, **kwargs): + self.test_transform = A.Compose( + [ + A.ToGray(always_apply=True), + A.Normalize((0.7931, 0.7931, 0.7931), (0.1738, 0.1738, 0.1738)), + ] + ) + + def __call__(self, data): + img = data["image"] + img = self.test_transform(image=img)["image"] + data["image"] = img + return data + + +class GoTImgDecode: + def __init__(self, input_size, random_padding=False, **kwargs): + self.input_size = input_size + self.random_padding = random_padding + + def crop_margin(self, img): + data = np.array(img.convert("L")) + data = data.astype(np.uint8) + max_val = data.max() + min_val = data.min() + if max_val == min_val: + return img + data = (data - min_val) / (max_val - min_val) * 255 + gray = 255 * (data < 200).astype(np.uint8) + coords = cv2.findNonZero(gray) # Find all non-zero points (text) + if coords is None: + return img + a, b, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box + if w == 0 or h == 0: + return img + # Avoid extreme aspect ratios that cause errors in downstream processing + if max(w, h) / min(w, h) > 200: + return img + return img.crop((a, b, w + a, h + b)) + + def get_dimensions(self, img): + if hasattr(img, "getbands"): + channels = len(img.getbands()) + else: + channels = img.channels + width, height = img.size + return [channels, height, width] + + def _compute_resized_output_size(self, image_size, size, max_size=None): + if len(size) == 1: # specified size only for the smallest edge + h, w = image_size + short, long = (w, h) if w <= h else (h, w) + requested_new_short = size if isinstance(size, int) else size[0] + + new_short, new_long = requested_new_short, int( + requested_new_short * long / short + ) + + if max_size is not None: + if max_size <= requested_new_short: + raise ValueError( + f"max_size = {max_size} must be strictly greater than the requested " + f"size for the smaller edge size = {size}" + ) + if new_long > max_size: + new_short, new_long = int(max_size * new_short / new_long), max_size + + new_w, new_h = (new_short, new_long) if w <= h else (new_long, new_short) + else: # specified both h and w + new_w, new_h = size[1], size[0] + return [new_h, new_w] + + def resize(self, img, size): + _, image_height, image_width = self.get_dimensions(img) + if isinstance(size, int): + size = [size] + max_size = None + output_size = self._compute_resized_output_size( + (image_height, image_width), size, max_size + ) + img = img.resize(tuple(output_size[::-1]), resample=2) + return img + + def __call__(self, data): + filename = data["filename"] + img = Image.open(filename) + try: + img = self.crop_margin(img.convert("RGB")) + except OSError: + return + if img.height == 0 or img.width == 0: + return + img = self.resize(img, min(self.input_size)) + img.thumbnail((self.input_size[1], self.input_size[0])) + delta_width = self.input_size[1] - img.width + delta_height = self.input_size[0] - img.height + if self.random_padding: + pad_width = np.random.randint(low=0, high=delta_width + 1) + pad_height = np.random.randint(low=0, high=delta_height + 1) + else: + pad_width = delta_width // 2 + pad_height = delta_height // 2 + padding = ( + pad_width, + pad_height, + delta_width - pad_width, + delta_height - pad_height, + ) + + data["image"] = np.array(ImageOps.expand(img, padding)) + return data + + +class UniMERNetImgDecode: + def __init__( + self, + input_size, + random_padding=False, + random_resize=False, + random_crop=False, + **kwargs, + ): + self.input_size = input_size + self.is_random_padding = random_padding + self.is_random_resize = random_resize + self.is_random_crop = random_crop + + def crop_margin(self, img): + data = np.array(img.convert("L")) + data = data.astype(np.uint8) + max_val = data.max() + min_val = data.min() + if max_val == min_val: + return img + data = (data - min_val) / (max_val - min_val) * 255 + gray = 255 * (data < 200).astype(np.uint8) + coords = cv2.findNonZero(gray) # Find all non-zero points (text) + if coords is None: + return img + a, b, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box + if w == 0 or h == 0: + return img + # Avoid extreme aspect ratios that cause errors in downstream processing + if max(w, h) / min(w, h) > 200: + return img + return img.crop((a, b, w + a, h + b)) + + def get_dimensions(self, img): + if hasattr(img, "getbands"): + channels = len(img.getbands()) + else: + channels = img.channels + width, height = img.size + return [channels, height, width] + + def _compute_resized_output_size(self, image_size, size, max_size=None): + if len(size) == 1: # specified size only for the smallest edge + h, w = image_size + short, long = (w, h) if w <= h else (h, w) + requested_new_short = size if isinstance(size, int) else size[0] + + new_short, new_long = requested_new_short, int( + requested_new_short * long / short + ) + + if max_size is not None: + if max_size <= requested_new_short: + raise ValueError( + f"max_size = {max_size} must be strictly greater than the requested " + f"size for the smaller edge size = {size}" + ) + if new_long > max_size: + new_short, new_long = int(max_size * new_short / new_long), max_size + + new_w, new_h = (new_short, new_long) if w <= h else (new_long, new_short) + else: # specified both h and w + new_w, new_h = size[1], size[0] + return [new_h, new_w] + + def resize(self, img, size): + _, image_height, image_width = self.get_dimensions(img) + if isinstance(size, int): + size = [size] + max_size = None + output_size = self._compute_resized_output_size( + (image_height, image_width), size, max_size + ) + img = img.resize(tuple(output_size[::-1]), resample=2) + return img + + def random_resize(self, img): + scale = np.random.uniform(0.5, 1) + img = img.resize([int(scale * s) for s in img.size]) + return img + + def random_crop(self, img, crop_ratio): + width, height = img.width, img.height + max_crop_pixel = min(width, height) * crop_ratio + crop_left = np.random.uniform(0, max_crop_pixel) + crop_right = np.random.uniform(0, max_crop_pixel) + crop_top = np.random.uniform(0, max_crop_pixel) + crop_bottom = np.random.uniform(0, max_crop_pixel) + # 计算裁剪后的边界 + left = crop_left + top = crop_top + right = width - crop_right + bottom = height - crop_bottom + # 裁剪图像 + img = img.crop((left, top, right, bottom)) + + return img + + def __call__(self, data): + filename = data["filename"] + img = Image.open(filename) + try: + if self.is_random_resize: + img = self.random_resize(img) + img = self.crop_margin(img.convert("RGB")) + if "label" in data and self.is_random_crop: + label = data["label"] + equation_length = len(label) + if equation_length < 256: + img = self.random_crop(img, crop_ratio=0.1) + elif 256 < equation_length <= 512: + img = self.random_crop(img, crop_ratio=0.05) + else: + img = self.random_crop(img, crop_ratio=0.03) + except OSError: + return + if img.height == 0 or img.width == 0: + return + img = self.resize(img, min(self.input_size)) + img.thumbnail((self.input_size[1], self.input_size[0])) + delta_width = self.input_size[1] - img.width + delta_height = self.input_size[0] - img.height + if self.is_random_padding: + pad_width = np.random.randint(low=0, high=delta_width + 1) + pad_height = np.random.randint(low=0, high=delta_height + 1) + else: + pad_width = delta_width // 2 + pad_height = delta_height // 2 + padding = ( + pad_width, + pad_height, + delta_width - pad_width, + delta_height - pad_height, + ) + + data["image"] = np.array(ImageOps.expand(img, padding)) + return data + + +class UniMERNetResize: + def __init__(self, input_size, random_padding=False, **kwargs): + self.input_size = input_size + self.random_padding = random_padding + + def crop_margin(self, img): + data = np.array(img.convert("L")) + data = data.astype(np.uint8) + max_val = data.max() + min_val = data.min() + if max_val == min_val: + return img + data = (data - min_val) / (max_val - min_val) * 255 + gray = 255 * (data < 200).astype(np.uint8) + + coords = cv2.findNonZero(gray) # Find all non-zero points (text) + if coords is None: + return img + a, b, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box + if w == 0 or h == 0: + return img + # Avoid extreme aspect ratios that cause errors in downstream processing + if max(w, h) / min(w, h) > 200: + return img + return img.crop((a, b, w + a, h + b)) + + def get_dimensions(self, img): + if hasattr(img, "getbands"): + channels = len(img.getbands()) + else: + channels = img.channels + width, height = img.size + return [channels, height, width] + + def _compute_resized_output_size(self, image_size, size, max_size=None): + if len(size) == 1: # specified size only for the smallest edge + h, w = image_size + short, long = (w, h) if w <= h else (h, w) + requested_new_short = size if isinstance(size, int) else size[0] + + new_short, new_long = requested_new_short, int( + requested_new_short * long / short + ) + + if max_size is not None: + if max_size <= requested_new_short: + raise ValueError( + f"max_size = {max_size} must be strictly greater than the requested " + f"size for the smaller edge size = {size}" + ) + if new_long > max_size: + new_short, new_long = int(max_size * new_short / new_long), max_size + + new_w, new_h = (new_short, new_long) if w <= h else (new_long, new_short) + else: # specified both h and w + new_w, new_h = size[1], size[0] + return [new_h, new_w] + + def resize(self, img, size): + _, image_height, image_width = self.get_dimensions(img) + if isinstance(size, int): + size = [size] + max_size = None + output_size = self._compute_resized_output_size( + (image_height, image_width), size, max_size + ) + img.resize(tuple(output_size[::-1]), resample=2) + return img + + def __call__(self, data): + img = data["image"] + img = Image.fromarray(img) + try: + img = self.crop_margin(img) + except OSError: + return + if img.height == 0 or img.width == 0: + return + img = self.resize(img, min(self.input_size)) + img.thumbnail((self.input_size[1], self.input_size[0])) + delta_width = self.input_size[1] - img.width + delta_height = self.input_size[0] - img.height + if self.random_padding: + pad_width = np.random.randint(low=0, high=delta_width + 1) + pad_height = np.random.randint(low=0, high=delta_height + 1) + else: + pad_width = delta_width // 2 + pad_height = delta_height // 2 + padding = ( + pad_width, + pad_height, + delta_width - pad_width, + delta_height - pad_height, + ) + data["image"] = np.array(ImageOps.expand(img, padding)) + return data + + +class UniMERNetImageFormat: + def __init__(self, **kwargs): + pass + + def __call__(self, data): + img = data["image"] + im_h, im_w = img.shape[:2] + divide_h = math.ceil(im_h / 32) * 32 + divide_w = math.ceil(im_w / 32) * 32 + img = img[:, :, 0] + img = np.pad( + img, ((0, divide_h - im_h), (0, divide_w - im_w)), constant_values=(1, 1) + ) + img_expanded = img[:, :, np.newaxis].transpose(2, 0, 1) + data["image"] = img_expanded + return data diff --git a/ppocr/data/imaug/vqa/__init__.py b/ppocr/data/imaug/vqa/__init__.py new file mode 100644 index 0000000..812f20b --- /dev/null +++ b/ppocr/data/imaug/vqa/__init__.py @@ -0,0 +1,29 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .token import ( + VQATokenPad, + VQASerTokenChunk, + VQAReTokenChunk, + VQAReTokenRelation, + TensorizeEntitiesRelations, +) + +__all__ = [ + "VQATokenPad", + "VQASerTokenChunk", + "VQAReTokenChunk", + "VQAReTokenRelation", + "TensorizeEntitiesRelations", +] diff --git a/ppocr/data/imaug/vqa/augment.py b/ppocr/data/imaug/vqa/augment.py new file mode 100644 index 0000000..d4f4cf2 --- /dev/null +++ b/ppocr/data/imaug/vqa/augment.py @@ -0,0 +1,34 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import numpy as np +import random +from copy import deepcopy + + +def order_by_tbyx(ocr_info): + res = sorted(ocr_info, key=lambda r: (r["bbox"][1], r["bbox"][0])) + for i in range(len(res) - 1): + for j in range(i, 0, -1): + if abs(res[j + 1]["bbox"][1] - res[j]["bbox"][1]) < 20 and ( + res[j + 1]["bbox"][0] < res[j]["bbox"][0] + ): + tmp = deepcopy(res[j]) + res[j] = deepcopy(res[j + 1]) + res[j + 1] = deepcopy(tmp) + else: + break + return res diff --git a/ppocr/data/imaug/vqa/token/__init__.py b/ppocr/data/imaug/vqa/token/__init__.py new file mode 100644 index 0000000..e349dd7 --- /dev/null +++ b/ppocr/data/imaug/vqa/token/__init__.py @@ -0,0 +1,18 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .vqa_token_chunk import VQASerTokenChunk, VQAReTokenChunk +from .vqa_token_pad import VQATokenPad +from .vqa_token_relation import VQAReTokenRelation +from .vqa_re_convert import TensorizeEntitiesRelations diff --git a/ppocr/data/imaug/vqa/token/vqa_re_convert.py b/ppocr/data/imaug/vqa/token/vqa_re_convert.py new file mode 100644 index 0000000..fa14915 --- /dev/null +++ b/ppocr/data/imaug/vqa/token/vqa_re_convert.py @@ -0,0 +1,49 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np + + +class TensorizeEntitiesRelations(object): + def __init__(self, max_seq_len=512, infer_mode=False, **kwargs): + self.max_seq_len = max_seq_len + self.infer_mode = infer_mode + + def __call__(self, data): + entities = data["entities"] + relations = data["relations"] + + entities_new = np.full( + shape=[self.max_seq_len + 1, 3], fill_value=-1, dtype="int64" + ) + entities_new[0, 0] = len(entities["start"]) + entities_new[0, 1] = len(entities["end"]) + entities_new[0, 2] = len(entities["label"]) + entities_new[1 : len(entities["start"]) + 1, 0] = np.array(entities["start"]) + entities_new[1 : len(entities["end"]) + 1, 1] = np.array(entities["end"]) + entities_new[1 : len(entities["label"]) + 1, 2] = np.array(entities["label"]) + + relations_new = np.full( + shape=[self.max_seq_len * self.max_seq_len + 1, 2], + fill_value=-1, + dtype="int64", + ) + relations_new[0, 0] = len(relations["head"]) + relations_new[0, 1] = len(relations["tail"]) + relations_new[1 : len(relations["head"]) + 1, 0] = np.array(relations["head"]) + relations_new[1 : len(relations["tail"]) + 1, 1] = np.array(relations["tail"]) + + data["entities"] = entities_new + data["relations"] = relations_new + return data diff --git a/ppocr/data/imaug/vqa/token/vqa_token_chunk.py b/ppocr/data/imaug/vqa/token/vqa_token_chunk.py new file mode 100644 index 0000000..d46a47d --- /dev/null +++ b/ppocr/data/imaug/vqa/token/vqa_token_chunk.py @@ -0,0 +1,134 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from collections import defaultdict + + +class VQASerTokenChunk(object): + def __init__(self, max_seq_len=512, infer_mode=False, **kwargs): + self.max_seq_len = max_seq_len + self.infer_mode = infer_mode + + def __call__(self, data): + encoded_inputs_all = [] + seq_len = len(data["input_ids"]) + for index in range(0, seq_len, self.max_seq_len): + chunk_beg = index + chunk_end = min(index + self.max_seq_len, seq_len) + encoded_inputs_example = {} + for key in data: + if key in [ + "label", + "input_ids", + "labels", + "token_type_ids", + "bbox", + "attention_mask", + ]: + if self.infer_mode and key == "labels": + encoded_inputs_example[key] = data[key] + else: + encoded_inputs_example[key] = data[key][chunk_beg:chunk_end] + else: + encoded_inputs_example[key] = data[key] + + encoded_inputs_all.append(encoded_inputs_example) + if len(encoded_inputs_all) == 0: + return None + return encoded_inputs_all[0] + + +class VQAReTokenChunk(object): + def __init__( + self, max_seq_len=512, entities_labels=None, infer_mode=False, **kwargs + ): + self.max_seq_len = max_seq_len + self.entities_labels = ( + {"HEADER": 0, "QUESTION": 1, "ANSWER": 2} + if entities_labels is None + else entities_labels + ) + self.infer_mode = infer_mode + + def __call__(self, data): + # prepare data + entities = data.pop("entities") + relations = data.pop("relations") + encoded_inputs_all = [] + for index in range(0, len(data["input_ids"]), self.max_seq_len): + item = {} + for key in data: + if key in [ + "label", + "input_ids", + "labels", + "token_type_ids", + "bbox", + "attention_mask", + ]: + if self.infer_mode and key == "labels": + item[key] = data[key] + else: + item[key] = data[key][index : index + self.max_seq_len] + else: + item[key] = data[key] + # select entity in current chunk + entities_in_this_span = [] + global_to_local_map = {} # + for entity_id, entity in enumerate(entities): + if ( + index <= entity["start"] < index + self.max_seq_len + and index <= entity["end"] < index + self.max_seq_len + ): + entity["start"] = entity["start"] - index + entity["end"] = entity["end"] - index + global_to_local_map[entity_id] = len(entities_in_this_span) + entities_in_this_span.append(entity) + + # select relations in current chunk + relations_in_this_span = [] + for relation in relations: + if ( + index <= relation["start_index"] < index + self.max_seq_len + and index <= relation["end_index"] < index + self.max_seq_len + ): + relations_in_this_span.append( + { + "head": global_to_local_map[relation["head"]], + "tail": global_to_local_map[relation["tail"]], + "start_index": relation["start_index"] - index, + "end_index": relation["end_index"] - index, + } + ) + item.update( + { + "entities": self.reformat(entities_in_this_span), + "relations": self.reformat(relations_in_this_span), + } + ) + if len(item["entities"]) > 0: + item["entities"]["label"] = [ + self.entities_labels[x] for x in item["entities"]["label"] + ] + encoded_inputs_all.append(item) + if len(encoded_inputs_all) == 0: + return None + return encoded_inputs_all[0] + + def reformat(self, data): + new_data = defaultdict(list) + for item in data: + for k, v in item.items(): + new_data[k].append(v) + return new_data diff --git a/ppocr/data/imaug/vqa/token/vqa_token_pad.py b/ppocr/data/imaug/vqa/token/vqa_token_pad.py new file mode 100644 index 0000000..9466e37 --- /dev/null +++ b/ppocr/data/imaug/vqa/token/vqa_token_pad.py @@ -0,0 +1,117 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import paddle +import numpy as np + + +class VQATokenPad(object): + def __init__( + self, + max_seq_len=512, + pad_to_max_seq_len=True, + return_attention_mask=True, + return_token_type_ids=True, + truncation_strategy="longest_first", + return_overflowing_tokens=False, + return_special_tokens_mask=False, + infer_mode=False, + **kwargs, + ): + self.max_seq_len = max_seq_len + self.pad_to_max_seq_len = max_seq_len + self.return_attention_mask = return_attention_mask + self.return_token_type_ids = return_token_type_ids + self.truncation_strategy = truncation_strategy + self.return_overflowing_tokens = return_overflowing_tokens + self.return_special_tokens_mask = return_special_tokens_mask + self.pad_token_label_id = paddle.nn.CrossEntropyLoss().ignore_index + self.infer_mode = infer_mode + + def __call__(self, data): + needs_to_be_padded = ( + self.pad_to_max_seq_len and len(data["input_ids"]) < self.max_seq_len + ) + + if needs_to_be_padded: + if "tokenizer_params" in data: + tokenizer_params = data.pop("tokenizer_params") + else: + tokenizer_params = dict( + padding_side="right", pad_token_type_id=0, pad_token_id=1 + ) + + difference = self.max_seq_len - len(data["input_ids"]) + if tokenizer_params["padding_side"] == "right": + if self.return_attention_mask: + data["attention_mask"] = [1] * len(data["input_ids"]) + [ + 0 + ] * difference + if self.return_token_type_ids: + data["token_type_ids"] = ( + data["token_type_ids"] + + [tokenizer_params["pad_token_type_id"]] * difference + ) + if self.return_special_tokens_mask: + data["special_tokens_mask"] = ( + data["special_tokens_mask"] + [1] * difference + ) + data["input_ids"] = ( + data["input_ids"] + [tokenizer_params["pad_token_id"]] * difference + ) + if not self.infer_mode: + data["labels"] = ( + data["labels"] + [self.pad_token_label_id] * difference + ) + data["bbox"] = data["bbox"] + [[0, 0, 0, 0]] * difference + elif tokenizer_params["padding_side"] == "left": + if self.return_attention_mask: + data["attention_mask"] = [0] * difference + [1] * len( + data["input_ids"] + ) + if self.return_token_type_ids: + data["token_type_ids"] = [ + tokenizer_params["pad_token_type_id"] + ] * difference + data["token_type_ids"] + if self.return_special_tokens_mask: + data["special_tokens_mask"] = [1] * difference + data[ + "special_tokens_mask" + ] + data["input_ids"] = [ + tokenizer_params["pad_token_id"] + ] * difference + data["input_ids"] + if not self.infer_mode: + data["labels"] = [self.pad_token_label_id] * difference + data[ + "labels" + ] + data["bbox"] = [[0, 0, 0, 0]] * difference + data["bbox"] + else: + if self.return_attention_mask: + data["attention_mask"] = [1] * len(data["input_ids"]) + + for key in data: + if key in [ + "input_ids", + "labels", + "token_type_ids", + "bbox", + "attention_mask", + ]: + if self.infer_mode: + if key != "labels": + length = min(len(data[key]), self.max_seq_len) + data[key] = data[key][:length] + else: + continue + data[key] = np.array(data[key], dtype="int64") + return data diff --git a/ppocr/data/imaug/vqa/token/vqa_token_relation.py b/ppocr/data/imaug/vqa/token/vqa_token_relation.py new file mode 100644 index 0000000..1946c58 --- /dev/null +++ b/ppocr/data/imaug/vqa/token/vqa_token_relation.py @@ -0,0 +1,76 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class VQAReTokenRelation(object): + def __init__(self, **kwargs): + pass + + def __call__(self, data): + """ + build relations + """ + entities = data["entities"] + relations = data["relations"] + id2label = data.pop("id2label") + empty_entity = data.pop("empty_entity") + entity_id_to_index_map = data.pop("entity_id_to_index_map") + + relations = list(set(relations)) + relations = [ + rel + for rel in relations + if rel[0] not in empty_entity and rel[1] not in empty_entity + ] + kv_relations = [] + for rel in relations: + pair = [id2label[rel[0]], id2label[rel[1]]] + if pair == ["question", "answer"]: + kv_relations.append( + { + "head": entity_id_to_index_map[rel[0]], + "tail": entity_id_to_index_map[rel[1]], + } + ) + elif pair == ["answer", "question"]: + kv_relations.append( + { + "head": entity_id_to_index_map[rel[1]], + "tail": entity_id_to_index_map[rel[0]], + } + ) + else: + continue + relations = sorted( + [ + { + "head": rel["head"], + "tail": rel["tail"], + "start_index": self.get_relation_span(rel, entities)[0], + "end_index": self.get_relation_span(rel, entities)[1], + } + for rel in kv_relations + ], + key=lambda x: x["head"], + ) + + data["relations"] = relations + return data + + def get_relation_span(self, rel, entities): + bound = [] + for entity_index in [rel["head"], rel["tail"]]: + bound.append(entities[entity_index]["start"]) + bound.append(entities[entity_index]["end"]) + return min(bound), max(bound) diff --git a/ppocr/data/latexocr_dataset.py b/ppocr/data/latexocr_dataset.py new file mode 100644 index 0000000..d748e08 --- /dev/null +++ b/ppocr/data/latexocr_dataset.py @@ -0,0 +1,206 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is refer from: +https://github.com/lukas-blecher/LaTeX-OCR/blob/main/pix2tex/dataset/dataset.py +""" + +import numpy as np +import cv2 +import math +import os +import json +import pickle +import random +import traceback +import paddle +from paddle.io import Dataset +from .imaug.label_ops import LatexOCRLabelEncode +from .imaug import transform, create_operators + + +_ALLOWED_PICKLE_GLOBALS = { + ("builtins", "dict"): dict, + ("builtins", "list"): list, + ("builtins", "tuple"): tuple, + ("builtins", "set"): set, + ("builtins", "frozenset"): frozenset, + ("builtins", "str"): str, + ("builtins", "int"): int, + ("builtins", "float"): float, + ("builtins", "bool"): bool, + ("builtins", "bytes"): bytes, +} + + +class _RestrictedDatasetUnpickler(pickle.Unpickler): + def find_class(self, module, name): + allowed = _ALLOWED_PICKLE_GLOBALS.get((module, name)) + if allowed is None: + raise pickle.UnpicklingError( + "Unsupported pickle global in LaTeXOCR dataset payload" + ) + return allowed + + +def _restricted_pickle_load(file_obj): + return _RestrictedDatasetUnpickler(file_obj).load() + + +class LaTeXOCRDataSet(Dataset): + def __init__(self, config, mode, logger, seed=None): + super(LaTeXOCRDataSet, self).__init__() + self.logger = logger + self.mode = mode.lower() + + global_config = config["Global"] + dataset_config = config[mode]["dataset"] + loader_config = config[mode]["loader"] + + pkl_path = dataset_config.pop("data") + self.data_dir = dataset_config["data_dir"] + self.min_dimensions = dataset_config.pop("min_dimensions") + self.max_dimensions = dataset_config.pop("max_dimensions") + self.batchsize = dataset_config.pop("batch_size_per_pair") + self.keep_smaller_batches = dataset_config.pop("keep_smaller_batches") + self.max_seq_len = global_config.pop("max_seq_len") + self.rec_char_dict_path = global_config.pop("rec_char_dict_path") + self.tokenizer = LatexOCRLabelEncode(self.rec_char_dict_path) + + with open(pkl_path, "rb") as file: + data = _restricted_pickle_load(file) + if not isinstance(data, dict): + raise pickle.UnpicklingError( + "LaTeXOCR dataset payload must deserialize to a dict" + ) + temp = {} + for k in data: + if ( + self.min_dimensions[0] <= k[0] <= self.max_dimensions[0] + and self.min_dimensions[1] <= k[1] <= self.max_dimensions[1] + ): + temp[k] = data[k] + self.data = temp + self.do_shuffle = loader_config["shuffle"] + self.seed = seed + + if self.mode == "train" and self.do_shuffle: + random.seed(self.seed) + self.pairs = [] + for k in self.data: + info = np.array(self.data[k], dtype=object) + p = ( + paddle.randperm(len(info)) + if self.mode == "train" and self.do_shuffle + else paddle.arange(len(info)) + ) + for i in range(0, len(info), self.batchsize): + batch = info[p[i : i + self.batchsize]] + if len(batch.shape) == 1: + batch = batch[None, :] + if len(batch) < self.batchsize and not self.keep_smaller_batches: + continue + self.pairs.append(batch) + if self.do_shuffle: + self.pairs = np.random.permutation(np.array(self.pairs, dtype=object)) + else: + self.pairs = np.array(self.pairs, dtype=object) + + self.size = len(self.pairs) + self.set_epoch_as_seed(self.seed, dataset_config) + + self.ops = create_operators(dataset_config["transforms"], global_config) + self.ext_op_transform_idx = dataset_config.get("ext_op_transform_idx", 2) + self.need_reset = True + + def set_epoch_as_seed(self, seed, dataset_config): + if self.mode == "train": + try: + border_map_id = [ + index + for index, dictionary in enumerate(dataset_config["transforms"]) + if "MakeBorderMap" in dictionary + ][0] + shrink_map_id = [ + index + for index, dictionary in enumerate(dataset_config["transforms"]) + if "MakeShrinkMap" in dictionary + ][0] + dataset_config["transforms"][border_map_id]["MakeBorderMap"][ + "epoch" + ] = (seed if seed is not None else 0) + dataset_config["transforms"][shrink_map_id]["MakeShrinkMap"][ + "epoch" + ] = (seed if seed is not None else 0) + except Exception as E: + print(E) + return + + def shuffle_data_random(self): + random.seed(self.seed) + random.shuffle(self.data_lines) + return + + def __getitem__(self, idx): + batch = self.pairs[idx] + eqs, ims = batch.T + try: + max_width, max_height, max_length = 0, 0, 0 + + images_transform = [] + + for file_name in ims: + img_path = os.path.join(self.data_dir, file_name) + data = { + "img_path": img_path, + } + with open(data["img_path"], "rb") as f: + img = f.read() + data["image"] = img + item = transform(data, self.ops) + images_transform.append(np.array(item[0])) + image_concat = np.concatenate(images_transform, axis=0)[:, np.newaxis, :, :] + images_transform = image_concat.astype(np.float32) + labels, attention_mask, max_length = self.tokenizer(list(eqs)) + if self.max_seq_len < max_length: + rnd_idx = ( + np.random.randint(self.__len__()) + if self.mode == "train" + else (idx + 1) % self.__len__() + ) + return self.__getitem__(rnd_idx) + return (images_transform, labels, attention_mask) + + except: + + self.logger.error( + "When parsing line {}, error happened with msg: {}".format( + data["img_path"], traceback.format_exc() + ) + ) + outs = None + + if outs is None: + # during evaluation, we should fix the idx to get same results for many times of evaluation. + rnd_idx = ( + np.random.randint(self.__len__()) + if self.mode == "train" + else (idx + 1) % self.__len__() + ) + return self.__getitem__(rnd_idx) + return outs + + def __len__(self): + return self.size diff --git a/ppocr/data/lmdb_dataset.py b/ppocr/data/lmdb_dataset.py new file mode 100644 index 0000000..6de2802 --- /dev/null +++ b/ppocr/data/lmdb_dataset.py @@ -0,0 +1,334 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +import io +import os +from paddle.io import Dataset +import lmdb +import cv2 +import string +import pickle +from PIL import Image + +from .imaug import transform, create_operators + +_ALLOWED_PICKLE_GLOBALS = { + ("builtins", "bytes"), + ("builtins", "bytearray"), + ("builtins", "str"), + ("builtins", "tuple"), + ("builtins", "list"), + ("builtins", "dict"), + ("builtins", "set"), + ("builtins", "frozenset"), + ("builtins", "int"), + ("builtins", "float"), + ("builtins", "bool"), +} + + +class _RestrictedDatasetUnpickler(pickle.Unpickler): + def find_class(self, module, name): + if (module, name) in _ALLOWED_PICKLE_GLOBALS: + return super().find_class(module, name) + raise pickle.UnpicklingError( + "Unsupported pickle payload in LMDBDataSetTableMaster dataset" + ) + + +def _restricted_pickle_loads(data): + if data is None: + raise ValueError("Missing LMDB dataset value") + return _RestrictedDatasetUnpickler(io.BytesIO(data)).load() + + +class LMDBDataSet(Dataset): + def __init__(self, config, mode, logger, seed=None): + super(LMDBDataSet, self).__init__() + + global_config = config["Global"] + dataset_config = config[mode]["dataset"] + loader_config = config[mode]["loader"] + batch_size = loader_config["batch_size_per_card"] + data_dir = dataset_config["data_dir"] + self.do_shuffle = loader_config["shuffle"] + + self.lmdb_sets = self.load_hierarchical_lmdb_dataset(data_dir) + logger.info("Initialize indexes of datasets:%s" % data_dir) + self.data_idx_order_list = self.dataset_traversal() + if self.do_shuffle: + np.random.shuffle(self.data_idx_order_list) + self.ops = create_operators(dataset_config["transforms"], global_config) + self.ext_op_transform_idx = dataset_config.get("ext_op_transform_idx", 1) + + ratio_list = dataset_config.get("ratio_list", [1.0]) + self.need_reset = True in [x < 1 for x in ratio_list] + + def load_hierarchical_lmdb_dataset(self, data_dir): + lmdb_sets = {} + dataset_idx = 0 + for dirpath, dirnames, filenames in os.walk(data_dir + "/"): + if not dirnames: + env = lmdb.open( + dirpath, + max_readers=32, + readonly=True, + lock=False, + readahead=False, + meminit=False, + ) + txn = env.begin(write=False) + num_samples = int(txn.get("num-samples".encode())) + lmdb_sets[dataset_idx] = { + "dirpath": dirpath, + "env": env, + "txn": txn, + "num_samples": num_samples, + } + dataset_idx += 1 + return lmdb_sets + + def dataset_traversal(self): + lmdb_num = len(self.lmdb_sets) + total_sample_num = 0 + for lno in range(lmdb_num): + total_sample_num += self.lmdb_sets[lno]["num_samples"] + data_idx_order_list = np.zeros((total_sample_num, 2)) + beg_idx = 0 + for lno in range(lmdb_num): + tmp_sample_num = self.lmdb_sets[lno]["num_samples"] + end_idx = beg_idx + tmp_sample_num + data_idx_order_list[beg_idx:end_idx, 0] = lno + data_idx_order_list[beg_idx:end_idx, 1] = list(range(tmp_sample_num)) + data_idx_order_list[beg_idx:end_idx, 1] += 1 + beg_idx = beg_idx + tmp_sample_num + return data_idx_order_list + + def get_img_data(self, value): + """get_img_data""" + if not value: + return None + imgdata = np.frombuffer(value, dtype="uint8") + if imgdata is None: + return None + imgori = cv2.imdecode(imgdata, 1) + if imgori is None: + return None + return imgori + + def get_ext_data(self): + ext_data_num = 0 + for op in self.ops: + if hasattr(op, "ext_data_num"): + ext_data_num = getattr(op, "ext_data_num") + break + load_data_ops = self.ops[: self.ext_op_transform_idx] + ext_data = [] + + while len(ext_data) < ext_data_num: + lmdb_idx, file_idx = self.data_idx_order_list[np.random.randint(len(self))] + lmdb_idx = int(lmdb_idx) + file_idx = int(file_idx) + sample_info = self.get_lmdb_sample_info( + self.lmdb_sets[lmdb_idx]["txn"], file_idx + ) + if sample_info is None: + continue + img, label = sample_info + data = {"image": img, "label": label} + data = transform(data, load_data_ops) + if data is None: + continue + ext_data.append(data) + return ext_data + + def get_lmdb_sample_info(self, txn, index): + label_key = "label-%09d".encode() % index + label = txn.get(label_key) + if label is None: + return None + label = label.decode("utf-8") + img_key = "image-%09d".encode() % index + imgbuf = txn.get(img_key) + return imgbuf, label + + def __getitem__(self, idx): + lmdb_idx, file_idx = self.data_idx_order_list[idx] + lmdb_idx = int(lmdb_idx) + file_idx = int(file_idx) + sample_info = self.get_lmdb_sample_info( + self.lmdb_sets[lmdb_idx]["txn"], file_idx + ) + if sample_info is None: + return self.__getitem__(np.random.randint(self.__len__())) + img, label = sample_info + data = {"image": img, "label": label} + data["ext_data"] = self.get_ext_data() + outs = transform(data, self.ops) + if outs is None: + return self.__getitem__(np.random.randint(self.__len__())) + return outs + + def __len__(self): + return self.data_idx_order_list.shape[0] + + +class LMDBDataSetSR(LMDBDataSet): + def buf2PIL(self, txn, key, type="RGB"): + imgbuf = txn.get(key) + buf = io.BytesIO() + buf.write(imgbuf) + buf.seek(0) + im = Image.open(buf).convert(type) + return im + + def str_filt(self, str_, voc_type): + alpha_dict = { + "digit": string.digits, + "lower": string.digits + string.ascii_lowercase, + "upper": string.digits + string.ascii_letters, + "all": string.digits + string.ascii_letters + string.punctuation, + } + if voc_type == "lower": + str_ = str_.lower() + for char in str_: + if char not in alpha_dict[voc_type]: + str_ = str_.replace(char, "") + return str_ + + def get_lmdb_sample_info(self, txn, index): + self.voc_type = "upper" + self.max_len = 100 + self.test = False + label_key = b"label-%09d" % index + word = str(txn.get(label_key).decode()) + img_HR_key = b"image_hr-%09d" % index # 128*32 + img_lr_key = b"image_lr-%09d" % index # 64*16 + try: + img_HR = self.buf2PIL(txn, img_HR_key, "RGB") + img_lr = self.buf2PIL(txn, img_lr_key, "RGB") + except IOError or len(word) > self.max_len: + return self[index + 1] + label_str = self.str_filt(word, self.voc_type) + return img_HR, img_lr, label_str + + def __getitem__(self, idx): + lmdb_idx, file_idx = self.data_idx_order_list[idx] + lmdb_idx = int(lmdb_idx) + file_idx = int(file_idx) + sample_info = self.get_lmdb_sample_info( + self.lmdb_sets[lmdb_idx]["txn"], file_idx + ) + if sample_info is None: + return self.__getitem__(np.random.randint(self.__len__())) + img_HR, img_lr, label_str = sample_info + data = {"image_hr": img_HR, "image_lr": img_lr, "label": label_str} + outs = transform(data, self.ops) + if outs is None: + return self.__getitem__(np.random.randint(self.__len__())) + return outs + + +class LMDBDataSetTableMaster(LMDBDataSet): + def load_hierarchical_lmdb_dataset(self, data_dir): + lmdb_sets = {} + dataset_idx = 0 + env = lmdb.open( + data_dir, + max_readers=32, + readonly=True, + lock=False, + readahead=False, + meminit=False, + ) + txn = env.begin(write=False) + num_samples = _restricted_pickle_loads(txn.get(b"__len__")) + if not isinstance(num_samples, int): + raise ValueError("Invalid LMDB dataset length metadata") + lmdb_sets[dataset_idx] = { + "dirpath": data_dir, + "env": env, + "txn": txn, + "num_samples": num_samples, + } + return lmdb_sets + + def get_img_data(self, value): + """get_img_data""" + if not value: + return None + imgdata = np.frombuffer(value, dtype="uint8") + if imgdata is None: + return None + imgori = cv2.imdecode(imgdata, 1) + if imgori is None: + return None + return imgori + + def get_lmdb_sample_info(self, txn, index): + def convert_bbox(bbox_str_list): + bbox_list = [] + for bbox_str in bbox_str_list: + bbox_list.append(int(bbox_str)) + return bbox_list + + try: + data = _restricted_pickle_loads(txn.get(str(index).encode("utf8"))) + except (pickle.UnpicklingError, ValueError, TypeError, EOFError): + return None + if not isinstance(data, (list, tuple)) or len(data) < 3: + return None + + # img_name, img, info_lines + file_name = data[0] + bytes = data[1] + info_lines = data[2] # raw data from TableMASTER annotation file. + # parse info_lines + raw_data = info_lines.strip().split("\n") + raw_name, text = ( + raw_data[0], + raw_data[1], + ) # don't filter the samples's length over max_seq_len. + text = text.split(",") + bbox_str_list = raw_data[2:] + bbox_split = "," + bboxes = [ + {"bbox": convert_bbox(bsl.strip().split(bbox_split)), "tokens": ["1", "2"]} + for bsl in bbox_str_list + ] + + # advance parse bbox + # import pdb;pdb.set_trace() + + line_info = {} + line_info["file_name"] = file_name + line_info["structure"] = text + line_info["cells"] = bboxes + line_info["image"] = bytes + return line_info + + def __getitem__(self, idx): + lmdb_idx, file_idx = self.data_idx_order_list[idx] + lmdb_idx = int(lmdb_idx) + file_idx = int(file_idx) + data = self.get_lmdb_sample_info(self.lmdb_sets[lmdb_idx]["txn"], file_idx) + if data is None: + return self.__getitem__(np.random.randint(self.__len__())) + outs = transform(data, self.ops) + if outs is None: + return self.__getitem__(np.random.randint(self.__len__())) + return outs + + def __len__(self): + return self.data_idx_order_list.shape[0] diff --git a/ppocr/data/multi_scale_sampler.py b/ppocr/data/multi_scale_sampler.py new file mode 100644 index 0000000..4ab38fc --- /dev/null +++ b/ppocr/data/multi_scale_sampler.py @@ -0,0 +1,171 @@ +from paddle.io import Sampler +import paddle.distributed as dist + +import numpy as np +import random +import math + + +class MultiScaleSampler(Sampler): + def __init__( + self, + data_source, + scales, + first_bs=128, + fix_bs=True, + divided_factor=[8, 16], + is_training=True, + ratio_wh=0.8, + max_w=480.0, + seed=None, + ): + """ + multi scale samper + Args: + data_source(dataset) + scales(list): several scales for image resolution + first_bs(int): batch size for the first scale in scales + divided_factor(list[w, h]): ImageNet models down-sample images by a factor, ensure that width and height dimensions are multiples are multiple of devided_factor. + is_training(boolean): mode + """ + # min. and max. spatial dimensions + self.data_source = data_source + self.data_idx_order_list = np.array(data_source.data_idx_order_list) + self.ds_width = data_source.ds_width + self.seed = data_source.seed + if self.ds_width: + self.wh_ratio = data_source.wh_ratio + self.wh_ratio_sort = data_source.wh_ratio_sort + self.n_data_samples = len(self.data_source) + self.ratio_wh = ratio_wh + self.max_w = max_w + + if isinstance(scales[0], list): + width_dims = [i[0] for i in scales] + height_dims = [i[1] for i in scales] + elif isinstance(scales[0], int): + width_dims = scales + height_dims = scales + base_im_w = width_dims[0] + base_im_h = height_dims[0] + base_batch_size = first_bs + + # Get the GPU and node related information + num_replicas = dist.get_world_size() + rank = dist.get_rank() + # adjust the total samples to avoid batch dropping + num_samples_per_replica = int(self.n_data_samples * 1.0 / num_replicas) + + img_indices = [idx for idx in range(self.n_data_samples)] + + self.shuffle = False + if is_training: + # compute the spatial dimensions and corresponding batch size + # ImageNet models down-sample images by a factor of 32. + # Ensure that width and height dimensions are multiples are multiple of 32. + width_dims = [ + int((w // divided_factor[0]) * divided_factor[0]) for w in width_dims + ] + height_dims = [ + int((h // divided_factor[1]) * divided_factor[1]) for h in height_dims + ] + + img_batch_pairs = list() + base_elements = base_im_w * base_im_h * base_batch_size + for h, w in zip(height_dims, width_dims): + if fix_bs: + batch_size = base_batch_size + else: + batch_size = int(max(1, (base_elements / (h * w)))) + img_batch_pairs.append((w, h, batch_size)) + self.img_batch_pairs = img_batch_pairs + self.shuffle = True + else: + self.img_batch_pairs = [(base_im_w, base_im_h, base_batch_size)] + + self.img_indices = img_indices + self.n_samples_per_replica = num_samples_per_replica + self.epoch = 0 + self.rank = rank + self.num_replicas = num_replicas + + self.batch_list = [] + self.current = 0 + last_index = num_samples_per_replica * num_replicas + indices_rank_i = self.img_indices[self.rank : last_index : self.num_replicas] + while self.current < self.n_samples_per_replica: + for curr_w, curr_h, curr_bsz in self.img_batch_pairs: + end_index = min(self.current + curr_bsz, self.n_samples_per_replica) + batch_ids = indices_rank_i[self.current : end_index] + n_batch_samples = len(batch_ids) + if n_batch_samples != curr_bsz: + batch_ids += indices_rank_i[: (curr_bsz - n_batch_samples)] + self.current += curr_bsz + + if len(batch_ids) > 0: + batch = [curr_w, curr_h, len(batch_ids)] + self.batch_list.append(batch) + random.shuffle(self.batch_list) + self.length = len(self.batch_list) + self.batchs_in_one_epoch = self.iter() + self.batchs_in_one_epoch_id = [i for i in range(len(self.batchs_in_one_epoch))] + + def __iter__(self): + if self.seed is None: + random.seed(self.epoch) + self.epoch += 1 + else: + random.seed(self.seed) + random.shuffle(self.batchs_in_one_epoch_id) + for batch_tuple_id in self.batchs_in_one_epoch_id: + yield self.batchs_in_one_epoch[batch_tuple_id] + + def iter(self): + if self.shuffle: + if self.seed is not None: + random.seed(self.seed) + else: + random.seed(self.epoch) + if not self.ds_width: + random.shuffle(self.img_indices) + random.shuffle(self.img_batch_pairs) + indices_rank_i = self.img_indices[ + self.rank : len(self.img_indices) : self.num_replicas + ] + else: + indices_rank_i = self.img_indices[ + self.rank : len(self.img_indices) : self.num_replicas + ] + + start_index = 0 + batchs_in_one_epoch = [] + for batch_tuple in self.batch_list: + curr_w, curr_h, curr_bsz = batch_tuple + end_index = min(start_index + curr_bsz, self.n_samples_per_replica) + batch_ids = indices_rank_i[start_index:end_index] + n_batch_samples = len(batch_ids) + if n_batch_samples != curr_bsz: + batch_ids += indices_rank_i[: (curr_bsz - n_batch_samples)] + start_index += curr_bsz + + if len(batch_ids) > 0: + if self.ds_width: + wh_ratio_current = self.wh_ratio[self.wh_ratio_sort[batch_ids]] + ratio_current = wh_ratio_current.mean() + ratio_current = ( + ratio_current + if ratio_current * curr_h < self.max_w + else self.max_w / curr_h + ) + else: + ratio_current = None + batch = [(curr_w, curr_h, b_id, ratio_current) for b_id in batch_ids] + # yield batch + batchs_in_one_epoch.append(batch) + return batchs_in_one_epoch + + def set_epoch(self, epoch: int): + self.epoch = epoch + + def __len__(self): + return self.length diff --git a/ppocr/data/pgnet_dataset.py b/ppocr/data/pgnet_dataset.py new file mode 100644 index 0000000..fd7a3d1 --- /dev/null +++ b/ppocr/data/pgnet_dataset.py @@ -0,0 +1,107 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +import os +from paddle.io import Dataset +from .imaug import transform, create_operators +import random + + +class PGDataSet(Dataset): + def __init__(self, config, mode, logger, seed=None): + super(PGDataSet, self).__init__() + + self.logger = logger + self.seed = seed + self.mode = mode + global_config = config["Global"] + dataset_config = config[mode]["dataset"] + loader_config = config[mode]["loader"] + + self.delimiter = dataset_config.get("delimiter", "\t") + label_file_list = dataset_config.pop("label_file_list") + data_source_num = len(label_file_list) + ratio_list = dataset_config.get("ratio_list", [1.0]) + if isinstance(ratio_list, (float, int)): + ratio_list = [float(ratio_list)] * int(data_source_num) + assert ( + len(ratio_list) == data_source_num + ), "The length of ratio_list should be the same as the file_list." + self.data_dir = dataset_config["data_dir"] + self.do_shuffle = loader_config["shuffle"] + + logger.info("Initialize indexes of datasets:%s" % label_file_list) + self.data_lines = self.get_image_info_list(label_file_list, ratio_list) + self.data_idx_order_list = list(range(len(self.data_lines))) + if mode.lower() == "train": + self.shuffle_data_random() + + self.ops = create_operators(dataset_config["transforms"], global_config) + + self.need_reset = True in [x < 1 for x in ratio_list] + + def shuffle_data_random(self): + if self.do_shuffle: + random.seed(self.seed) + random.shuffle(self.data_lines) + return + + def get_image_info_list(self, file_list, ratio_list): + if isinstance(file_list, str): + file_list = [file_list] + data_lines = [] + for idx, file in enumerate(file_list): + with open(file, "rb") as f: + lines = f.readlines() + if self.mode == "train" or ratio_list[idx] < 1.0: + random.seed(self.seed) + lines = random.sample(lines, round(len(lines) * ratio_list[idx])) + data_lines.extend(lines) + return data_lines + + def __getitem__(self, idx): + file_idx = self.data_idx_order_list[idx] + data_line = self.data_lines[file_idx] + img_id = 0 + try: + data_line = data_line.decode("utf-8") + substr = data_line.strip("\n").split(self.delimiter) + file_name = substr[0] + label = substr[1] + img_path = os.path.join(self.data_dir, file_name) + if self.mode.lower() == "eval": + try: + img_id = int(data_line.split(".")[0][7:]) + except: + img_id = 0 + data = {"img_path": img_path, "label": label, "img_id": img_id} + if not os.path.exists(img_path): + raise Exception("{} does not exist!".format(img_path)) + with open(data["img_path"], "rb") as f: + img = f.read() + data["image"] = img + outs = transform(data, self.ops) + except Exception as e: + self.logger.error( + "When parsing line {}, error happened with msg: {}".format( + self.data_idx_order_list[idx], e + ) + ) + outs = None + if outs is None: + return self.__getitem__(np.random.randint(self.__len__())) + return outs + + def __len__(self): + return len(self.data_idx_order_list) diff --git a/ppocr/data/pubtab_dataset.py b/ppocr/data/pubtab_dataset.py new file mode 100644 index 0000000..f0f9a04 --- /dev/null +++ b/ppocr/data/pubtab_dataset.py @@ -0,0 +1,138 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +import os +import random +from paddle.io import Dataset +import json +from copy import deepcopy + +from .imaug import transform, create_operators + + +class PubTabDataSet(Dataset): + def __init__(self, config, mode, logger, seed=None): + super(PubTabDataSet, self).__init__() + self.logger = logger + + global_config = config["Global"] + dataset_config = config[mode]["dataset"] + loader_config = config[mode]["loader"] + + label_file_list = dataset_config.pop("label_file_list") + data_source_num = len(label_file_list) + ratio_list = dataset_config.get("ratio_list", [1.0]) + if isinstance(ratio_list, (float, int)): + ratio_list = [float(ratio_list)] * int(data_source_num) + + assert ( + len(ratio_list) == data_source_num + ), "The length of ratio_list should be the same as the file_list." + + self.data_dir = dataset_config["data_dir"] + self.do_shuffle = loader_config["shuffle"] + + self.seed = seed + self.mode = mode.lower() + logger.info("Initialize indexes of datasets:%s" % label_file_list) + self.data_lines = self.get_image_info_list(label_file_list, ratio_list) + # self.check(config['Global']['max_text_length']) + + if mode.lower() == "train" and self.do_shuffle: + self.shuffle_data_random() + self.ops = create_operators(dataset_config["transforms"], global_config) + self.need_reset = True in [x < 1 for x in ratio_list] + + def get_image_info_list(self, file_list, ratio_list): + if isinstance(file_list, str): + file_list = [file_list] + data_lines = [] + for idx, file in enumerate(file_list): + with open(file, "rb") as f: + lines = f.readlines() + if self.mode == "train" or ratio_list[idx] < 1.0: + random.seed(self.seed) + lines = random.sample(lines, round(len(lines) * ratio_list[idx])) + data_lines.extend(lines) + return data_lines + + def check(self, max_text_length): + data_lines = [] + for line in self.data_lines: + data_line = line.decode("utf-8").strip("\n") + info = json.loads(data_line) + file_name = info["filename"] + cells = info["html"]["cells"].copy() + structure = info["html"]["structure"]["tokens"].copy() + + img_path = os.path.join(self.data_dir, file_name) + if not os.path.exists(img_path): + self.logger.warning("{} does not exist!".format(img_path)) + continue + if len(structure) == 0 or len(structure) > max_text_length: + continue + # data = {'img_path': img_path, 'cells': cells, 'structure':structure,'file_name':file_name} + data_lines.append(line) + self.data_lines = data_lines + + def shuffle_data_random(self): + if self.do_shuffle: + random.seed(self.seed) + random.shuffle(self.data_lines) + return + + def __getitem__(self, idx): + try: + data_line = self.data_lines[idx] + data_line = data_line.decode("utf-8").strip("\n") + info = json.loads(data_line) + file_name = info["filename"] + cells = info["html"]["cells"].copy() + structure = info["html"]["structure"]["tokens"].copy() + + img_path = os.path.join(self.data_dir, file_name) + if not os.path.exists(img_path): + raise Exception("{} does not exist!".format(img_path)) + data = { + "img_path": img_path, + "cells": cells, + "structure": structure, + "file_name": file_name, + } + + with open(data["img_path"], "rb") as f: + img = f.read() + data["image"] = img + outs = transform(data, self.ops) + except: + import traceback + + err = traceback.format_exc() + self.logger.error( + "When parsing line {}, error happened with msg: {}".format( + data_line, err + ) + ) + outs = None + if outs is None: + rnd_idx = ( + np.random.randint(self.__len__()) + if self.mode == "train" + else (idx + 1) % self.__len__() + ) + return self.__getitem__(rnd_idx) + return outs + + def __len__(self): + return len(self.data_lines) diff --git a/ppocr/data/simple_dataset.py b/ppocr/data/simple_dataset.py new file mode 100644 index 0000000..57e9b43 --- /dev/null +++ b/ppocr/data/simple_dataset.py @@ -0,0 +1,544 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +import cv2 +import math +import os +import json +import random +import traceback +import multiprocessing +import urllib.request +import urllib.parse +import threading +import concurrent.futures +from collections import OrderedDict +from paddle.io import Dataset +from .imaug import transform, create_operators +from paddle import get_device + +# ------------------------------------------------------------------ # +# Per-worker-process URL prefetch cache +# +# Each DataLoader worker is a forked process with its own copy of +# these globals. The thread pool and LRU cache are therefore +# completely independent across workers — no cross-process locking +# needed, and memory usage is bounded per worker. +# +# Memory budget (per worker): +# _URL_CACHE_MAX × avg_image_size ≈ 200 × 270 KB ≈ 54 MB +# With num_workers=4 the total extra footprint is ~216 MB. +# +# How prefetch works: +# _ensure_index_map() fires inside the worker when an epoch changes. +# It calls _prefetch_epoch_urls(), which scans the new _index_map, +# picks the first _URL_PREFETCH_SUBMIT URL entries (epoch order ≈ +# access order), and submits them to the background thread pool. +# _load_image_bytes() checks the cache / in-flight future before +# falling back to a synchronous download. +# ------------------------------------------------------------------ # + +_URL_CACHE_MAX = 200 # max cached images per worker +_URL_PREFETCH_SUBMIT = 200 # URL items submitted to thread pool per epoch + +# LRU cache: url -> bytes +_url_cache: "OrderedDict[str, bytes]" = OrderedDict() +_url_cache_lock = threading.Lock() + +# In-flight futures: url -> Future +_url_futures: "dict[str, concurrent.futures.Future]" = {} +_url_futures_lock = threading.Lock() + +# Lazily created per-process thread pool +_url_executor: "concurrent.futures.ThreadPoolExecutor | None" = None +_url_executor_lock = threading.Lock() + + +def _get_url_executor(): + global _url_executor + if _url_executor is None: + with _url_executor_lock: + if _url_executor is None: + _url_executor = concurrent.futures.ThreadPoolExecutor( + max_workers=4, + thread_name_prefix="url_prefetch", + ) + return _url_executor + + +def _encode_url(url): + """Percent-encode non-ASCII characters in the URL path so that + urllib can handle URLs containing CJK or other non-ASCII filenames. + Scheme, netloc, query and fragment are left untouched. + """ + parts = urllib.parse.urlparse(url) + encoded_path = urllib.parse.quote(parts.path, safe="/:@!$&'()*+,;=") + return urllib.parse.urlunparse(parts._replace(path=encoded_path)) + + +def _download_url_bytes(url): + """Download *url*, store in LRU cache, remove from futures dict. + The futures entry is always cleaned up (success or failure) so that + a failed URL can be retried on the next access. + """ + encoded = _encode_url(url) + try: + with urllib.request.urlopen(encoded, timeout=30) as resp: + data = resp.read() + except Exception: + with _url_futures_lock: + _url_futures.pop(url, None) + raise + with _url_cache_lock: + if url not in _url_cache: + if len(_url_cache) >= _URL_CACHE_MAX: + _url_cache.popitem(last=False) # evict LRU entry + _url_cache[url] = data + else: + _url_cache.move_to_end(url) + with _url_futures_lock: + _url_futures.pop(url, None) + return data + + +def _submit_url_prefetch(url): + """Submit a background download for *url* if not already cached/in-flight.""" + with _url_cache_lock: + if url in _url_cache: + return + with _url_futures_lock: + if url in _url_futures: + return + future = _get_url_executor().submit(_download_url_bytes, url) + _url_futures[url] = future + + +def _prefetch_epoch_urls(index_map, all_lines, delimiter): + """Scan *index_map* and submit the first _URL_PREFETCH_SUBMIT URL + items for background download. Called inside worker processes.""" + submitted = 0 + for file_idx in index_map: + if submitted >= _URL_PREFETCH_SUBMIT: + break + try: + line = all_lines[file_idx].decode("utf-8") + fname = line.strip("\n").split(delimiter)[0] + if fname and fname[0] == "[": # JSON list — skip + continue + if fname.startswith("http://") or fname.startswith("https://"): + _submit_url_prefetch(fname) + submitted += 1 + except Exception: + pass + + +def _load_image_bytes(img_path): + """Return raw image bytes. For URLs checks prefetch cache/future first.""" + if img_path.startswith("http://") or img_path.startswith("https://"): + # 1. Cache hit — return immediately + with _url_cache_lock: + if img_path in _url_cache: + _url_cache.move_to_end(img_path) + return _url_cache[img_path] + # 2. In-flight future — wait for background download to finish + with _url_futures_lock: + future = _url_futures.get(img_path) + if future is not None: + return future.result(timeout=60) + # 3. Cold miss — download synchronously (also fills cache) + return _download_url_bytes(img_path) + with open(img_path, "rb") as f: + return f.read() + + +def _img_path_exists(img_path): + """Return True if the image source is accessible (local file exists or URL).""" + if img_path.startswith("http://") or img_path.startswith("https://"): + return True + return os.path.exists(img_path) + + +class SimpleDataSet(Dataset): + def __init__(self, config, mode, logger, seed=None): + super(SimpleDataSet, self).__init__() + self.logger = logger + self.mode = mode.lower() + + global_config = config["Global"] + dataset_config = config[mode]["dataset"] + loader_config = config[mode]["loader"] + + self.delimiter = dataset_config.get("delimiter", "\t") + label_file_list = dataset_config.pop("label_file_list") + data_source_num = len(label_file_list) + ratio_list = dataset_config.get("ratio_list", 1.0) + if isinstance(ratio_list, (float, int)): + ratio_list = [float(ratio_list)] * int(data_source_num) + self.label_file_list = label_file_list + self.ratio_list = ratio_list + + assert ( + len(ratio_list) == data_source_num + ), "The length of ratio_list should be the same as the file_list." + self.data_dir = dataset_config["data_dir"] + self.do_shuffle = loader_config["shuffle"] + self.seed = seed + self.need_reset = True in [x < 1 for x in ratio_list] + + logger.info("Initialize indexs of datasets:%s" % label_file_list) + + if self.need_reset: + # Pre-load all lines once (immutable, never re-read from disk). + # Per-epoch ratio sampling is done via _index_map (virtual idx -> global idx). + self._all_lines, self.file_boundaries = self._load_all_lines( + label_file_list + ) + self._index_map = self._generate_index_map(seed) + self._cached_epoch = seed if seed is not None else 0 + # data_lines / data_idx_order_list kept for API compat but NOT used in __getitem__ + self.data_lines = self._all_lines + self.data_idx_order_list = list(range(len(self._index_map))) + else: + self._all_lines = None + self._index_map = None + self._cached_epoch = None + self.file_boundaries = None + self.data_lines = self.get_image_info_list(label_file_list, ratio_list) + self.data_idx_order_list = list(range(len(self.data_lines))) + if self.mode == "train" and self.do_shuffle: + self.shuffle_data_random() + + # Shared epoch value: workers read this via shared memory to detect epoch changes + self._shared_epoch = multiprocessing.Value("i", seed if seed is not None else 0) + + self.ops = create_operators(dataset_config["transforms"], global_config) + self.ext_op_transform_idx = dataset_config.get("ext_op_transform_idx", 2) + + # ------------------------------------------------------------------ # + # Data loading helpers + # ------------------------------------------------------------------ # + + def _load_all_lines(self, file_list): + """Read all label files once. Returns (all_lines, file_boundaries).""" + if isinstance(file_list, str): + file_list = [file_list] + all_lines = [] + boundaries = [0] + for f in file_list: + with open(f, "rb") as fh: + lines = fh.readlines() + all_lines.extend(lines) + boundaries.append(len(all_lines)) + return all_lines, boundaries + + def _generate_index_map(self, seed): + """Generate virtual-index -> global-index mapping. + + Replicates the EXACT sampling logic of original get_image_info_list + + shuffle_data_random: for each file, random.seed(seed) then + random.sample to pick indices, then random.seed(seed) + shuffle. + + Since random.sample(population, k) with the same seed selects the + same POSITIONS regardless of population type, sampling from + range(start, end) yields the same positions as from lines[start:end]. + """ + sampled = [] + for i in range(len(self.ratio_list)): + start = self.file_boundaries[i] + end = self.file_boundaries[i + 1] + file_size = end - start + count = round(file_size * self.ratio_list[i]) + if self.mode == "train" or self.ratio_list[i] < 1.0: + random.seed(seed) + sampled.extend(random.sample(range(start, end), count)) + else: + sampled.extend(range(start, end)) + if self.mode == "train" and self.do_shuffle: + random.seed(seed) + random.shuffle(sampled) + return sampled + + def _ensure_index_map(self): + """Lazily rebuild _index_map when worker detects epoch change via shared memory. + Also triggers URL prefetch on first call (epoch 0) and on every epoch change. + """ + if self._all_lines is None: + return + current_epoch = self._shared_epoch.value + epoch_changed = current_epoch != self._cached_epoch + first_call = not getattr(self, "_url_prefetch_initialized", False) + + if epoch_changed: + self._index_map = self._generate_index_map(current_epoch) + self._cached_epoch = current_epoch + + if epoch_changed or first_call: + self._url_prefetch_initialized = True + _prefetch_epoch_urls(self._index_map, self._all_lines, self.delimiter) + + def get_image_info_list(self, file_list, ratio_list): + if isinstance(file_list, str): + file_list = [file_list] + data_lines = [] + for idx, file in enumerate(file_list): + with open(file, "rb") as f: + lines = f.readlines() + if self.mode == "train" or ratio_list[idx] < 1.0: + random.seed(self.seed) + lines = random.sample(lines, round(len(lines) * ratio_list[idx])) + data_lines.extend(lines) + return data_lines + + def shuffle_data_random(self): + random.seed(self.seed) + random.shuffle(self.data_lines) + return + + # ------------------------------------------------------------------ # + # Epoch update (called from main process each epoch) + # ------------------------------------------------------------------ # + + def reset_data_lines(self, seed=None, epoch=None): + """Signal new epoch to persistent workers via shared memory. + + Workers lazily rebuild their _index_map on next __getitem__ call. + No disk I/O, no dataloader reconstruction. + """ + self.seed = seed + epoch_val = epoch if epoch is not None else (seed if seed is not None else 0) + self._shared_epoch.value = int(epoch_val) + + if self._all_lines is not None: + # Update main-process index_map (used by len() and batch_sampler) + self._index_map = self._generate_index_map(seed) + self._cached_epoch = int(epoch_val) + self.data_idx_order_list = list(range(len(self._index_map))) + else: + # Fallback for non-ratio cases + self.data_lines = self.get_image_info_list( + self.label_file_list, self.ratio_list + ) + self.data_idx_order_list = list(range(len(self.data_lines))) + if self.mode == "train" and self.do_shuffle: + self.shuffle_data_random() + + # ------------------------------------------------------------------ # + # Data access + # ------------------------------------------------------------------ # + + def _try_parse_filename_list(self, file_name): + # multiple images -> one gt label + if len(file_name) > 0 and file_name[0] == "[": + try: + info = json.loads(file_name) + file_name = random.choice(info) + except: + pass + return file_name + + def get_ext_data(self): + ext_data_num = 0 + for op in self.ops: + if hasattr(op, "ext_data_num"): + ext_data_num = getattr(op, "ext_data_num") + break + load_data_ops = self.ops[: self.ext_op_transform_idx] + ext_data = [] + + while len(ext_data) < ext_data_num: + if self._index_map is not None: + # Sample from current epoch's subset (same as original) + self._ensure_index_map() + rand_virtual = np.random.randint(len(self._index_map)) + file_idx = self._index_map[rand_virtual] + data_line = self._all_lines[file_idx] + else: + file_idx = self.data_idx_order_list[np.random.randint(self.__len__())] + data_line = self.data_lines[file_idx] + data_line = data_line.decode("utf-8") + substr = data_line.strip("\n").split(self.delimiter) + file_name = substr[0] + file_name = self._try_parse_filename_list(file_name) + label = substr[1] + img_path = ( + file_name + if file_name.startswith("http://") or file_name.startswith("https://") + else os.path.join(self.data_dir, file_name) + ) + data = {"img_path": img_path, "label": label} + if not _img_path_exists(img_path): + continue + try: + data["image"] = _load_image_bytes(img_path) + except Exception: + continue + data = transform(data, load_data_ops) + + if data is None: + continue + if "polys" in data.keys(): + if data["polys"].shape[1] != 4: + continue + ext_data.append(data) + return ext_data + + def __getitem__(self, idx): + if self._index_map is not None: + self._ensure_index_map() + file_idx = self._index_map[idx] + data_line = self._all_lines[file_idx] + else: + file_idx = self.data_idx_order_list[idx] + data_line = self.data_lines[file_idx] + try: + data_line = data_line.decode("utf-8") + substr = data_line.strip("\n").split(self.delimiter) + file_name = substr[0] + file_name = self._try_parse_filename_list(file_name) + label = substr[1] + img_path = ( + file_name + if file_name.startswith("http://") or file_name.startswith("https://") + else os.path.join(self.data_dir, file_name) + ) + data = {"img_path": img_path, "label": label} + if not _img_path_exists(img_path): + raise Exception("{} does not exist!".format(img_path)) + data["image"] = _load_image_bytes(img_path) + data["ext_data"] = self.get_ext_data() + data["filename"] = data["img_path"] + data["epoch"] = self._shared_epoch.value + outs = transform(data, self.ops) + except: + self.logger.error( + "When parsing line {}, error happened with msg: {}".format( + data_line, traceback.format_exc() + ) + ) + outs = None + if outs is None: + # during evaluation, we should fix the idx to get same results for many times of evaluation. + rnd_idx = ( + np.random.randint(self.__len__()) + if self.mode == "train" + else (idx + 1) % self.__len__() + ) + return self.__getitem__(rnd_idx) + return outs + + def __len__(self): + if self._index_map is not None: + return len(self._index_map) + return len(self.data_idx_order_list) + + +class MultiScaleDataSet(SimpleDataSet): + def __init__(self, config, mode, logger, seed=None): + super(MultiScaleDataSet, self).__init__(config, mode, logger, seed) + self.ds_width = config[mode]["dataset"].get("ds_width", False) + if self.ds_width: + self.wh_aware() + + def wh_aware(self): + data_line_new = [] + wh_ratio = [] + for line in self.data_lines: + data_line_new.append(line) + line = line.decode("utf-8") + name, label, w, h = line.strip("\n").split(self.delimiter) + wh_ratio.append(float(w) / float(h)) + + self.data_lines = data_line_new + self.wh_ratio = np.array(wh_ratio) + self.wh_ratio_sort = np.argsort(self.wh_ratio) + self.data_idx_order_list = list(range(len(self.data_lines))) + + def resize_norm_img(self, data, imgW, imgH, padding=True): + img = data["image"] + h = img.shape[0] + w = img.shape[1] + if not padding: + resized_image = cv2.resize( + img, (imgW, imgH), interpolation=cv2.INTER_LINEAR + ) + resized_w = imgW + else: + ratio = w / float(h) + if math.ceil(imgH * ratio) > imgW: + resized_w = imgW + else: + resized_w = int(math.ceil(imgH * ratio)) + resized_image = cv2.resize(img, (resized_w, imgH)) + resized_image = resized_image.astype("float32") + + resized_image = resized_image.transpose((2, 0, 1)) / 255 + resized_image -= 0.5 + resized_image /= 0.5 + padding_im = np.zeros((3, imgH, imgW), dtype=np.float32) + padding_im[:, :, :resized_w] = resized_image + valid_ratio = min(1.0, float(resized_w / imgW)) + data["image"] = padding_im + data["valid_ratio"] = valid_ratio + if "iluvatar_gpu" in get_device(): + data["valid_ratio"] = np.float32(valid_ratio) + return data + + def __getitem__(self, properties): + # properties is a tuple, contains (width, height, index) + img_height = properties[1] + idx = properties[2] + if self.ds_width and properties[3] is not None: + wh_ratio = properties[3] + img_width = img_height * ( + 1 if int(round(wh_ratio)) == 0 else int(round(wh_ratio)) + ) + file_idx = self.wh_ratio_sort[idx] + else: + file_idx = self.data_idx_order_list[idx] + img_width = properties[0] + wh_ratio = None + + data_line = self.data_lines[file_idx] + try: + data_line = data_line.decode("utf-8") + substr = data_line.strip("\n").split(self.delimiter) + file_name = substr[0] + file_name = self._try_parse_filename_list(file_name) + label = substr[1] + img_path = ( + file_name + if file_name.startswith("http://") or file_name.startswith("https://") + else os.path.join(self.data_dir, file_name) + ) + data = {"img_path": img_path, "label": label} + if not _img_path_exists(img_path): + raise Exception("{} does not exist!".format(img_path)) + data["image"] = _load_image_bytes(img_path) + data["ext_data"] = self.get_ext_data() + outs = transform(data, self.ops[:-1]) + if outs is not None: + outs = self.resize_norm_img(outs, img_width, img_height) + outs = transform(outs, self.ops[-1:]) + except: + self.logger.error( + "When parsing line {}, error happened with msg: {}".format( + data_line, traceback.format_exc() + ) + ) + outs = None + if outs is None: + # during evaluation, we should fix the idx to get same results for many times of evaluation. + rnd_idx = (idx + 1) % self.__len__() + return self.__getitem__([img_width, img_height, rnd_idx, wh_ratio]) + return outs diff --git a/ppocr/ext_op/__init__.py b/ppocr/ext_op/__init__.py new file mode 100644 index 0000000..b0be1ad --- /dev/null +++ b/ppocr/ext_op/__init__.py @@ -0,0 +1 @@ +from .roi_align_rotated.roi_align_rotated import RoIAlignRotated diff --git a/ppocr/ext_op/roi_align_rotated/roi_align_rotated.cc b/ppocr/ext_op/roi_align_rotated/roi_align_rotated.cc new file mode 100644 index 0000000..f6b6217 --- /dev/null +++ b/ppocr/ext_op/roi_align_rotated/roi_align_rotated.cc @@ -0,0 +1,528 @@ + +// This code is refer from: +// https://github.com/open-mmlab/mmcv/blob/master/mmcv/ops/csrc/pytorch/cpu/roi_align_rotated.cpp + +#include +#include +#include + +#include "paddle/extension.h" + +#define PADDLE_WITH_CUDA +#define CHECK_INPUT_SAME(x1, x2) \ + PD_CHECK(x1.place() == x2.place(), "input must be same place.") +#define CHECK_INPUT_CPU(x) PD_CHECK(x.is_cpu(), #x " must be a CPU Tensor.") + +template struct PreCalc { + int pos1; + int pos2; + int pos3; + int pos4; + T w1; + T w2; + T w3; + T w4; +}; + +template +void pre_calc_for_bilinear_interpolate( + const int height, const int width, const int pooled_height, + const int pooled_width, const int iy_upper, const int ix_upper, + T roi_start_h, T roi_start_w, T bin_size_h, T bin_size_w, + int roi_bin_grid_h, int roi_bin_grid_w, T roi_center_h, T roi_center_w, + T cos_theta, T sin_theta, std::vector> &pre_calc) { + int pre_calc_index = 0; + for (int ph = 0; ph < pooled_height; ph++) { + for (int pw = 0; pw < pooled_width; pw++) { + for (int iy = 0; iy < iy_upper; iy++) { + const T yy = roi_start_h + ph * bin_size_h + + static_cast(iy + .5f) * bin_size_h / + static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5 + for (int ix = 0; ix < ix_upper; ix++) { + const T xx = roi_start_w + pw * bin_size_w + + static_cast(ix + .5f) * bin_size_w / + static_cast(roi_bin_grid_w); + + // Rotate by theta around the center and translate + // In image space, (y, x) is the order for Right Handed System, + // and this is essentially multiplying the point by a rotation matrix + // to rotate it counterclockwise through angle theta. + T y = yy * cos_theta - xx * sin_theta + roi_center_h; + T x = yy * sin_theta + xx * cos_theta + roi_center_w; + // deal with: inverse elements are out of feature map boundary + if (y < -1.0 || y > height || x < -1.0 || x > width) { + // empty + PreCalc pc; + pc.pos1 = 0; + pc.pos2 = 0; + pc.pos3 = 0; + pc.pos4 = 0; + pc.w1 = 0; + pc.w2 = 0; + pc.w3 = 0; + pc.w4 = 0; + pre_calc[pre_calc_index] = pc; + pre_calc_index += 1; + continue; + } + + if (y < 0) { + y = 0; + } + if (x < 0) { + x = 0; + } + + int y_low = (int)y; + int x_low = (int)x; + int y_high; + int x_high; + + if (y_low >= height - 1) { + y_high = y_low = height - 1; + y = (T)y_low; + } else { + y_high = y_low + 1; + } + + if (x_low >= width - 1) { + x_high = x_low = width - 1; + x = (T)x_low; + } else { + x_high = x_low + 1; + } + + T ly = y - y_low; + T lx = x - x_low; + T hy = 1. - ly, hx = 1. - lx; + T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx; + + // save weights and indices + PreCalc pc; + pc.pos1 = y_low * width + x_low; + pc.pos2 = y_low * width + x_high; + pc.pos3 = y_high * width + x_low; + pc.pos4 = y_high * width + x_high; + pc.w1 = w1; + pc.w2 = w2; + pc.w3 = w3; + pc.w4 = w4; + pre_calc[pre_calc_index] = pc; + + pre_calc_index += 1; + } + } + } + } +} + +template +void roi_align_rotated_cpu_forward(const int nthreads, const T *input, + const T &spatial_scale, const bool aligned, + const bool clockwise, const int channels, + const int height, const int width, + const int pooled_height, + const int pooled_width, + const int sampling_ratio, const T *rois, + T *output) { + int n_rois = nthreads / channels / pooled_width / pooled_height; + // (n, c, ph, pw) is an element in the pooled output + // can be parallelized using omp + // #pragma omp parallel for num_threads(32) + for (int n = 0; n < n_rois; n++) { + int index_n = n * channels * pooled_width * pooled_height; + + const T *current_roi = rois + n * 6; + int roi_batch_ind = current_roi[0]; + + // Do not use rounding; this implementation detail is critical + T offset = aligned ? (T)0.5 : (T)0.0; + T roi_center_w = current_roi[1] * spatial_scale - offset; + T roi_center_h = current_roi[2] * spatial_scale - offset; + T roi_width = current_roi[3] * spatial_scale; + T roi_height = current_roi[4] * spatial_scale; + T theta = current_roi[5]; + if (clockwise) { + theta = -theta; // If clockwise, the angle needs to be reversed. + } + T cos_theta = cos(theta); + T sin_theta = sin(theta); + + if (aligned) { + assert(roi_width >= 0 && roi_height >= 0); + } else { // for backward-compatibility only + roi_width = std::max(roi_width, (T)1.); + roi_height = std::max(roi_height, (T)1.); + } + + T bin_size_h = static_cast(roi_height) / static_cast(pooled_height); + T bin_size_w = static_cast(roi_width) / static_cast(pooled_width); + + // We use roi_bin_grid to sample the grid and mimic integral + int roi_bin_grid_h = (sampling_ratio > 0) + ? sampling_ratio + : ceilf(roi_height / pooled_height); // e.g., = 2 + int roi_bin_grid_w = + (sampling_ratio > 0) ? sampling_ratio : ceilf(roi_width / pooled_width); + + // We do average (integral) pooling inside a bin + const T count = std::max(roi_bin_grid_h * roi_bin_grid_w, 1); // e.g. = 4 + + // we want to precalculate indices and weights shared by all channels, + // this is the key point of optimization + std::vector> pre_calc(roi_bin_grid_h * roi_bin_grid_w * + pooled_width * pooled_height); + + // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y). + // Appropriate translation needs to be applied after. + T roi_start_h = -roi_height / 2.0; + T roi_start_w = -roi_width / 2.0; + + pre_calc_for_bilinear_interpolate( + height, width, pooled_height, pooled_width, roi_bin_grid_h, + roi_bin_grid_w, roi_start_h, roi_start_w, bin_size_h, bin_size_w, + roi_bin_grid_h, roi_bin_grid_w, roi_center_h, roi_center_w, cos_theta, + sin_theta, pre_calc); + + for (int c = 0; c < channels; c++) { + int index_n_c = index_n + c * pooled_width * pooled_height; + const T *offset_input = + input + (roi_batch_ind * channels + c) * height * width; + int pre_calc_index = 0; + + for (int ph = 0; ph < pooled_height; ph++) { + for (int pw = 0; pw < pooled_width; pw++) { + int index = index_n_c + ph * pooled_width + pw; + + T output_val = 0.; + for (int iy = 0; iy < roi_bin_grid_h; iy++) { + for (int ix = 0; ix < roi_bin_grid_w; ix++) { + PreCalc pc = pre_calc[pre_calc_index]; + output_val += pc.w1 * offset_input[pc.pos1] + + pc.w2 * offset_input[pc.pos2] + + pc.w3 * offset_input[pc.pos3] + + pc.w4 * offset_input[pc.pos4]; + + pre_calc_index += 1; + } + } + output_val /= count; + + output[index] = output_val; + } // for pw + } // for ph + } // for c + } // for n +} + +template +void bilinear_interpolate_gradient(const int height, const int width, T y, T x, + T &w1, T &w2, T &w3, T &w4, int &x_low, + int &x_high, int &y_low, int &y_high) { + // deal with cases that inverse elements are out of feature map boundary + if (y < -1.0 || y > height || x < -1.0 || x > width) { + // empty + w1 = w2 = w3 = w4 = 0.; + x_low = x_high = y_low = y_high = -1; + return; + } + + if (y < 0) { + y = 0; + } + + if (x < 0) { + x = 0; + } + + y_low = (int)y; + x_low = (int)x; + + if (y_low >= height - 1) { + y_high = y_low = height - 1; + y = (T)y_low; + } else { + y_high = y_low + 1; + } + + if (x_low >= width - 1) { + x_high = x_low = width - 1; + x = (T)x_low; + } else { + x_high = x_low + 1; + } + + T ly = y - y_low; + T lx = x - x_low; + T hy = 1. - ly, hx = 1. - lx; + + // reference in forward + // T v1 = input[y_low * width + x_low]; + // T v2 = input[y_low * width + x_high]; + // T v3 = input[y_high * width + x_low]; + // T v4 = input[y_high * width + x_high]; + // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4); + + w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx; + + return; +} + +template inline void add(T *address, const T &val) { + *address += val; +} + +template +void roi_align_rotated_cpu_backward( + const int nthreads, + // may not be contiguous. should index using n_stride, etc + const T *grad_output, const T &spatial_scale, const bool aligned, + const bool clockwise, const int channels, const int height, const int width, + const int pooled_height, const int pooled_width, const int sampling_ratio, + T *grad_input, const T *rois, const int n_stride, const int c_stride, + const int h_stride, const int w_stride) { + for (int index = 0; index < nthreads; index++) { + // (n, c, ph, pw) is an element in the pooled output + int pw = index % pooled_width; + int ph = (index / pooled_width) % pooled_height; + int c = (index / pooled_width / pooled_height) % channels; + int n = index / pooled_width / pooled_height / channels; + + const T *current_roi = rois + n * 6; + int roi_batch_ind = current_roi[0]; + + // Do not use rounding; this implementation detail is critical + T offset = aligned ? (T)0.5 : (T)0.0; + T roi_center_w = current_roi[1] * spatial_scale - offset; + T roi_center_h = current_roi[2] * spatial_scale - offset; + T roi_width = current_roi[3] * spatial_scale; + T roi_height = current_roi[4] * spatial_scale; + T theta = current_roi[5]; + if (clockwise) { + theta = -theta; // If clockwise, the angle needs to be reversed. + } + T cos_theta = cos(theta); + T sin_theta = sin(theta); + + if (aligned) { + assert(roi_width >= 0 && roi_height >= 0); + } else { // for backward-compatibility only + roi_width = std::max(roi_width, (T)1.); + roi_height = std::max(roi_height, (T)1.); + } + + T bin_size_h = static_cast(roi_height) / static_cast(pooled_height); + T bin_size_w = static_cast(roi_width) / static_cast(pooled_width); + + T *offset_grad_input = + grad_input + ((roi_batch_ind * channels + c) * height * width); + + int output_offset = n * n_stride + c * c_stride; + const T *offset_grad_output = grad_output + output_offset; + const T grad_output_this_bin = + offset_grad_output[ph * h_stride + pw * w_stride]; + + // We use roi_bin_grid to sample the grid and mimic integral + int roi_bin_grid_h = (sampling_ratio > 0) + ? sampling_ratio + : ceilf(roi_height / pooled_height); // e.g., = 2 + int roi_bin_grid_w = + (sampling_ratio > 0) ? sampling_ratio : ceilf(roi_width / pooled_width); + + // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y). + // Appropriate translation needs to be applied after. + T roi_start_h = -roi_height / 2.0; + T roi_start_w = -roi_width / 2.0; + + // We do average (integral) pooling inside a bin + const T count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4 + + for (int iy = 0; iy < roi_bin_grid_h; iy++) { + const T yy = roi_start_h + ph * bin_size_h + + static_cast(iy + .5f) * bin_size_h / + static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5 + for (int ix = 0; ix < roi_bin_grid_w; ix++) { + const T xx = roi_start_w + pw * bin_size_w + + static_cast(ix + .5f) * bin_size_w / + static_cast(roi_bin_grid_w); + + // Rotate by theta around the center and translate + T y = yy * cos_theta - xx * sin_theta + roi_center_h; + T x = yy * sin_theta + xx * cos_theta + roi_center_w; + + T w1, w2, w3, w4; + int x_low, x_high, y_low, y_high; + + bilinear_interpolate_gradient(height, width, y, x, w1, w2, w3, w4, + x_low, x_high, y_low, y_high); + + T g1 = grad_output_this_bin * w1 / count; + T g2 = grad_output_this_bin * w2 / count; + T g3 = grad_output_this_bin * w3 / count; + T g4 = grad_output_this_bin * w4 / count; + + if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) { + // atomic add is not needed for now since it is single threaded + add(offset_grad_input + y_low * width + x_low, static_cast(g1)); + add(offset_grad_input + y_low * width + x_high, static_cast(g2)); + add(offset_grad_input + y_high * width + x_low, static_cast(g3)); + add(offset_grad_input + y_high * width + x_high, static_cast(g4)); + } // if + } // ix + } // iy + } // for +} // ROIAlignRotatedBackward + +std::vector +RoIAlignRotatedCPUForward(const paddle::Tensor &input, + const paddle::Tensor &rois, int aligned_height, + int aligned_width, float spatial_scale, + int sampling_ratio, bool aligned, bool clockwise) { + CHECK_INPUT_CPU(input); + CHECK_INPUT_CPU(rois); + + auto num_rois = rois.shape()[0]; + + auto channels = input.shape()[1]; + auto height = input.shape()[2]; + auto width = input.shape()[3]; + + auto output = + paddle::empty({num_rois, channels, aligned_height, aligned_width}, + input.type(), paddle::CPUPlace()); + auto output_size = output.numel(); + + PD_DISPATCH_FLOATING_TYPES( + input.type(), "roi_align_rotated_cpu_forward", ([&] { + roi_align_rotated_cpu_forward( + output_size, input.data(), + static_cast(spatial_scale), aligned, clockwise, channels, + height, width, aligned_height, aligned_width, sampling_ratio, + rois.data(), output.data()); + })); + + return {output}; +} + +std::vector RoIAlignRotatedCPUBackward( + const paddle::Tensor &input, const paddle::Tensor &rois, + const paddle::Tensor &grad_output, int aligned_height, int aligned_width, + float spatial_scale, int sampling_ratio, bool aligned, bool clockwise) { + + auto batch_size = input.shape()[0]; + auto channels = input.shape()[1]; + auto height = input.shape()[2]; + auto width = input.shape()[3]; + + auto grad_input = paddle::full({batch_size, channels, height, width}, 0.0, + input.type(), paddle::CPUPlace()); + + // get stride values to ensure indexing into gradients is correct. + int n_stride = grad_output.shape()[0]; + int c_stride = grad_output.shape()[1]; + int h_stride = grad_output.shape()[2]; + int w_stride = grad_output.shape()[3]; + + PD_DISPATCH_FLOATING_TYPES( + grad_output.type(), "roi_align_rotated_cpu_backward", [&] { + roi_align_rotated_cpu_backward( + grad_output.numel(), grad_output.data(), + static_cast(spatial_scale), aligned, clockwise, channels, + height, width, aligned_height, aligned_width, sampling_ratio, + grad_input.data(), rois.data(), n_stride, c_stride, + h_stride, w_stride); + }); + return {grad_input}; +} + +#ifdef PADDLE_WITH_CUDA +std::vector +RoIAlignRotatedCUDAForward(const paddle::Tensor &input, + const paddle::Tensor &rois, int aligned_height, + int aligned_width, float spatial_scale, + int sampling_ratio, bool aligned, bool clockwise); +#endif + +#ifdef PADDLE_WITH_CUDA +std::vector RoIAlignRotatedCUDABackward( + const paddle::Tensor &input, const paddle::Tensor &rois, + const paddle::Tensor &grad_output, int aligned_height, int aligned_width, + float spatial_scale, int sampling_ratio, bool aligned, bool clockwise); +#endif + +std::vector +RoIAlignRotatedForward(const paddle::Tensor &input, const paddle::Tensor &rois, + int aligned_height, int aligned_width, + float spatial_scale, int sampling_ratio, bool aligned, + bool clockwise) { + CHECK_INPUT_SAME(input, rois); + if (input.is_cpu()) { + return RoIAlignRotatedCPUForward(input, rois, aligned_height, aligned_width, + spatial_scale, sampling_ratio, aligned, + clockwise); +#ifdef PADDLE_WITH_CUDA + } else if (input.is_gpu()) { + return RoIAlignRotatedCUDAForward(input, rois, aligned_height, + aligned_width, spatial_scale, + sampling_ratio, aligned, clockwise); +#endif + } else { + PD_THROW("Unsupported device type for forward function of roi align " + "rotated operator."); + } +} + +std::vector +RoIAlignRotatedBackward(const paddle::Tensor &input, const paddle::Tensor &rois, + const paddle::Tensor &grad_output, int aligned_height, + int aligned_width, float spatial_scale, + int sampling_ratio, bool aligned, bool clockwise) { + CHECK_INPUT_SAME(input, rois); + if (input.is_cpu()) { + return RoIAlignRotatedCPUBackward(input, rois, grad_output, aligned_height, + aligned_width, spatial_scale, + sampling_ratio, aligned, clockwise); +#ifdef PADDLE_WITH_CUDA + } else if (input.is_gpu()) { + return RoIAlignRotatedCUDABackward(input, rois, grad_output, aligned_height, + aligned_width, spatial_scale, + sampling_ratio, aligned, clockwise); +#endif + } else { + PD_THROW("Unsupported device type for forward function of roi align " + "rotated operator."); + } +} + +std::vector> InferShape(std::vector input_shape, + std::vector rois_shape) { + return {{rois_shape[0], input_shape[1], input_shape[2], input_shape[3]}}; +} + +std::vector> +InferBackShape(std::vector input_shape, + std::vector rois_shape) { + return {input_shape}; +} + +std::vector InferDtype(paddle::DataType input_dtype, + paddle::DataType rois_dtype) { + return {input_dtype}; +} + +PD_BUILD_OP(roi_align_rotated) + .Inputs({"Input", "Rois"}) + .Outputs({"Output"}) + .Attrs({"aligned_height: int", "aligned_width: int", "spatial_scale: float", + "sampling_ratio: int", "aligned: bool", "clockwise: bool"}) + .SetKernelFn(PD_KERNEL(RoIAlignRotatedForward)) + .SetInferShapeFn(PD_INFER_SHAPE(InferShape)) + .SetInferDtypeFn(PD_INFER_DTYPE(InferDtype)); + +PD_BUILD_GRAD_OP(roi_align_rotated) + .Inputs({"Input", "Rois", paddle::Grad("Output")}) + .Attrs({"aligned_height: int", "aligned_width: int", "spatial_scale: float", + "sampling_ratio: int", "aligned: bool", "clockwise: bool"}) + .Outputs({paddle::Grad("Input")}) + .SetKernelFn(PD_KERNEL(RoIAlignRotatedBackward)) + .SetInferShapeFn(PD_INFER_SHAPE(InferBackShape)); diff --git a/ppocr/ext_op/roi_align_rotated/roi_align_rotated.cu b/ppocr/ext_op/roi_align_rotated/roi_align_rotated.cu new file mode 100644 index 0000000..d9baefb --- /dev/null +++ b/ppocr/ext_op/roi_align_rotated/roi_align_rotated.cu @@ -0,0 +1,381 @@ + +// This code is refer from: +// https://github.com/open-mmlab/mmcv/blob/master/mmcv/ops/csrc/common/cuda/roi_align_rotated_cuda_kernel.cuh + +#include +#include +#include + +#include "paddle/extension.h" +#include + +#define CUDA_1D_KERNEL_LOOP(i, n) \ + for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < (n); \ + i += blockDim.x * gridDim.x) + +#define THREADS_PER_BLOCK 512 + +inline int GET_BLOCKS(const int N) { + int optimal_block_num = (N + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK; + int max_block_num = 4096; + return min(optimal_block_num, max_block_num); +} + +#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ < 600 + +static __inline__ __device__ double atomicAdd(double *address, double val) { + unsigned long long int *address_as_ull = (unsigned long long int *)address; + unsigned long long int old = *address_as_ull, assumed; + if (val == 0.0) + return __longlong_as_double(old); + do { + assumed = old; + old = atomicCAS(address_as_ull, assumed, + __double_as_longlong(val + __longlong_as_double(assumed))); + } while (assumed != old); + return __longlong_as_double(old); +} + +#endif + +template +__device__ T bilinear_interpolate(const T *input, const int height, + const int width, T y, T x, + const int index /* index for debug only*/) { + // deal with cases that inverse elements are out of feature map boundary + if (y < -1.0 || y > height || x < -1.0 || x > width) + return 0; + + if (y <= 0) + y = 0; + if (x <= 0) + x = 0; + + int y_low = (int)y; + int x_low = (int)x; + int y_high; + int x_high; + + if (y_low >= height - 1) { + y_high = y_low = height - 1; + y = (T)y_low; + } else { + y_high = y_low + 1; + } + + if (x_low >= width - 1) { + x_high = x_low = width - 1; + x = (T)x_low; + } else { + x_high = x_low + 1; + } + + T ly = y - y_low; + T lx = x - x_low; + T hy = 1. - ly, hx = 1. - lx; + // do bilinear interpolation + T v1 = input[y_low * width + x_low]; + T v2 = input[y_low * width + x_high]; + T v3 = input[y_high * width + x_low]; + T v4 = input[y_high * width + x_high]; + T w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx; + + T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4); + + return val; +} + +template +__device__ void +bilinear_interpolate_gradient(const int height, const int width, T y, T x, + T &w1, T &w2, T &w3, T &w4, int &x_low, + int &x_high, int &y_low, int &y_high, + const int index /* index for debug only*/) { + // deal with cases that inverse elements are out of feature map boundary + if (y < -1.0 || y > height || x < -1.0 || x > width) { + // empty + w1 = w2 = w3 = w4 = 0.; + x_low = x_high = y_low = y_high = -1; + return; + } + + if (y <= 0) + y = 0; + if (x <= 0) + x = 0; + + y_low = (int)y; + x_low = (int)x; + + if (y_low >= height - 1) { + y_high = y_low = height - 1; + y = (T)y_low; + } else { + y_high = y_low + 1; + } + + if (x_low >= width - 1) { + x_high = x_low = width - 1; + x = (T)x_low; + } else { + x_high = x_low + 1; + } + + T ly = y - y_low; + T lx = x - x_low; + T hy = 1. - ly, hx = 1. - lx; + + // reference in forward + // T v1 = input[y_low * width + x_low]; + // T v2 = input[y_low * width + x_high]; + // T v3 = input[y_high * width + x_low]; + // T v4 = input[y_high * width + x_high]; + // T val = (w1 * v1 + w2 * v2 + w3 * v3 + w4 * v4); + + w1 = hy * hx, w2 = hy * lx, w3 = ly * hx, w4 = ly * lx; + + return; +} + +/*** Forward ***/ +template +__global__ void roi_align_rotated_cuda_forward_kernel( + const int nthreads, const scalar_t *bottom_data, + const scalar_t *bottom_rois, const scalar_t spatial_scale, + const int sample_num, const bool aligned, const bool clockwise, + const int channels, const int height, const int width, + const int pooled_height, const int pooled_width, scalar_t *top_data) { + CUDA_1D_KERNEL_LOOP(index, nthreads) { + // (n, c, ph, pw) is an element in the pooled output + int pw = index % pooled_width; + int ph = (index / pooled_width) % pooled_height; + int c = (index / pooled_width / pooled_height) % channels; + int n = index / pooled_width / pooled_height / channels; + + const scalar_t *offset_bottom_rois = bottom_rois + n * 6; + int roi_batch_ind = offset_bottom_rois[0]; + + // Do not using rounding; this implementation detail is critical + scalar_t offset = aligned ? (scalar_t)0.5 : (scalar_t)0.0; + scalar_t roi_center_w = offset_bottom_rois[1] * spatial_scale - offset; + scalar_t roi_center_h = offset_bottom_rois[2] * spatial_scale - offset; + scalar_t roi_width = offset_bottom_rois[3] * spatial_scale; + scalar_t roi_height = offset_bottom_rois[4] * spatial_scale; + // scalar_t theta = offset_bottom_rois[5] * M_PI / 180.0; + scalar_t theta = offset_bottom_rois[5]; + if (clockwise) { + theta = -theta; // If clockwise, the angle needs to be reversed. + } + if (!aligned) { // for backward-compatibility only + // Force malformed ROIs to be 1x1 + roi_width = max(roi_width, (scalar_t)1.); + roi_height = max(roi_height, (scalar_t)1.); + } + scalar_t bin_size_h = static_cast(roi_height) / + static_cast(pooled_height); + scalar_t bin_size_w = + static_cast(roi_width) / static_cast(pooled_width); + + const scalar_t *offset_bottom_data = + bottom_data + (roi_batch_ind * channels + c) * height * width; + + // We use roi_bin_grid to sample the grid and mimic integral + int roi_bin_grid_h = (sample_num > 0) + ? sample_num + : ceilf(roi_height / pooled_height); // e.g., = 2 + int roi_bin_grid_w = + (sample_num > 0) ? sample_num : ceilf(roi_width / pooled_width); + + // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y). + // Appropriate translation needs to be applied after. + scalar_t roi_start_h = -roi_height / 2.0; + scalar_t roi_start_w = -roi_width / 2.0; + scalar_t cosscalar_theta = cos(theta); + scalar_t sinscalar_theta = sin(theta); + + // We do average (integral) pooling inside a bin + const scalar_t count = max(roi_bin_grid_h * roi_bin_grid_w, 1); // e.g. = 4 + + scalar_t output_val = 0.; + for (int iy = 0; iy < roi_bin_grid_h; iy++) { // e.g., iy = 0, 1 + const scalar_t yy = + roi_start_h + ph * bin_size_h + + static_cast(iy + .5f) * bin_size_h / + static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5 + for (int ix = 0; ix < roi_bin_grid_w; ix++) { + const scalar_t xx = roi_start_w + pw * bin_size_w + + static_cast(ix + .5f) * bin_size_w / + static_cast(roi_bin_grid_w); + + // Rotate by theta (counterclockwise) around the center and translate + scalar_t y = yy * cosscalar_theta - xx * sinscalar_theta + roi_center_h; + scalar_t x = yy * sinscalar_theta + xx * cosscalar_theta + roi_center_w; + + scalar_t val = bilinear_interpolate( + offset_bottom_data, height, width, y, x, index); + output_val += val; + } + } + output_val /= count; + + top_data[index] = output_val; + } +} + +/*** Backward ***/ +template +__global__ void roi_align_rotated_backward_cuda_kernel( + const int nthreads, const scalar_t *top_diff, const scalar_t *bottom_rois, + const scalar_t spatial_scale, const int sample_num, const bool aligned, + const bool clockwise, const int channels, const int height, const int width, + const int pooled_height, const int pooled_width, scalar_t *bottom_diff) { + CUDA_1D_KERNEL_LOOP(index, nthreads) { + // (n, c, ph, pw) is an element in the pooled output + int pw = index % pooled_width; + int ph = (index / pooled_width) % pooled_height; + int c = (index / pooled_width / pooled_height) % channels; + int n = index / pooled_width / pooled_height / channels; + + const scalar_t *offset_bottom_rois = bottom_rois + n * 6; + int roi_batch_ind = offset_bottom_rois[0]; + + // Do not round + scalar_t offset = aligned ? (scalar_t)0.5 : (scalar_t)0.0; + scalar_t roi_center_w = offset_bottom_rois[1] * spatial_scale - offset; + scalar_t roi_center_h = offset_bottom_rois[2] * spatial_scale - offset; + scalar_t roi_width = offset_bottom_rois[3] * spatial_scale; + scalar_t roi_height = offset_bottom_rois[4] * spatial_scale; + // scalar_t theta = offset_bottom_rois[5] * M_PI / 180.0; + scalar_t theta = offset_bottom_rois[5]; + if (clockwise) { + theta = -theta; // If clockwise, the angle needs to be reversed. + } + if (!aligned) { // for backward-compatibility only + // Force malformed ROIs to be 1x1 + roi_width = max(roi_width, (scalar_t)1.); + roi_height = max(roi_height, (scalar_t)1.); + } + scalar_t bin_size_h = static_cast(roi_height) / + static_cast(pooled_height); + scalar_t bin_size_w = + static_cast(roi_width) / static_cast(pooled_width); + + scalar_t *offset_bottom_diff = + bottom_diff + (roi_batch_ind * channels + c) * height * width; + + int top_offset = (n * channels + c) * pooled_height * pooled_width; + const scalar_t *offset_top_diff = top_diff + top_offset; + const scalar_t top_diff_this_bin = offset_top_diff[ph * pooled_width + pw]; + + // We use roi_bin_grid to sample the grid and mimic integral + int roi_bin_grid_h = (sample_num > 0) + ? sample_num + : ceilf(roi_height / pooled_height); // e.g., = 2 + int roi_bin_grid_w = + (sample_num > 0) ? sample_num : ceilf(roi_width / pooled_width); + + // roi_start_h and roi_start_w are computed wrt the center of RoI (x, y). + // Appropriate translation needs to be applied after. + scalar_t roi_start_h = -roi_height / 2.0; + scalar_t roi_start_w = -roi_width / 2.0; + scalar_t cosTheta = cos(theta); + scalar_t sinTheta = sin(theta); + + // We do average (integral) pooling inside a bin + const scalar_t count = roi_bin_grid_h * roi_bin_grid_w; // e.g. = 4 + + for (int iy = 0; iy < roi_bin_grid_h; iy++) { // e.g., iy = 0, 1 + const scalar_t yy = + roi_start_h + ph * bin_size_h + + static_cast(iy + .5f) * bin_size_h / + static_cast(roi_bin_grid_h); // e.g., 0.5, 1.5 + for (int ix = 0; ix < roi_bin_grid_w; ix++) { + const scalar_t xx = roi_start_w + pw * bin_size_w + + static_cast(ix + .5f) * bin_size_w / + static_cast(roi_bin_grid_w); + + // Rotate by theta around the center and translate + scalar_t y = yy * cosTheta - xx * sinTheta + roi_center_h; + scalar_t x = yy * sinTheta + xx * cosTheta + roi_center_w; + + scalar_t w1, w2, w3, w4; + int x_low, x_high, y_low, y_high; + + bilinear_interpolate_gradient(height, width, y, x, w1, w2, w3, + w4, x_low, x_high, y_low, + y_high, index); + + scalar_t g1 = top_diff_this_bin * w1 / count; + scalar_t g2 = top_diff_this_bin * w2 / count; + scalar_t g3 = top_diff_this_bin * w3 / count; + scalar_t g4 = top_diff_this_bin * w4 / count; + + if (x_low >= 0 && x_high >= 0 && y_low >= 0 && y_high >= 0) { + atomicAdd(offset_bottom_diff + y_low * width + x_low, g1); + atomicAdd(offset_bottom_diff + y_low * width + x_high, g2); + atomicAdd(offset_bottom_diff + y_high * width + x_low, g3); + atomicAdd(offset_bottom_diff + y_high * width + x_high, g4); + } // if + } // ix + } // iy + } // CUDA_1D_KERNEL_LOOP +} // RoIAlignBackward + +std::vector +RoIAlignRotatedCUDAForward(const paddle::Tensor &input, + const paddle::Tensor &rois, int aligned_height, + int aligned_width, float spatial_scale, + int sampling_ratio, bool aligned, bool clockwise) { + + auto num_rois = rois.shape()[0]; + + auto channels = input.shape()[1]; + auto height = input.shape()[2]; + auto width = input.shape()[3]; + + auto output = + paddle::empty({num_rois, channels, aligned_height, aligned_width}, + input.type(), paddle::GPUPlace()); + auto output_size = output.numel(); + + PD_DISPATCH_FLOATING_TYPES( + input.type(), "roi_align_rotated_cuda_forward_kernel", ([&] { + roi_align_rotated_cuda_forward_kernel + <<>>( + output_size, input.data(), rois.data(), + static_cast(spatial_scale), sampling_ratio, aligned, + clockwise, channels, height, width, aligned_height, + aligned_width, output.data()); + })); + + return {output}; +} + +std::vector RoIAlignRotatedCUDABackward( + const paddle::Tensor &input, const paddle::Tensor &rois, + const paddle::Tensor &grad_output, int aligned_height, int aligned_width, + float spatial_scale, int sampling_ratio, bool aligned, bool clockwise) { + + auto num_rois = rois.shape()[0]; + + auto batch_size = input.shape()[0]; + auto channels = input.shape()[1]; + auto height = input.shape()[2]; + auto width = input.shape()[3]; + + auto grad_input = paddle::full({batch_size, channels, height, width}, 0.0, + input.type(), paddle::GPUPlace()); + + const int output_size = num_rois * aligned_height * aligned_width * channels; + + PD_DISPATCH_FLOATING_TYPES( + grad_output.type(), "roi_align_rotated_backward_cuda_kernel", ([&] { + roi_align_rotated_backward_cuda_kernel + <<>>( + output_size, grad_output.data(), rois.data(), + spatial_scale, sampling_ratio, aligned, clockwise, channels, + height, width, aligned_height, aligned_width, + grad_input.data()); + })); + return {grad_input}; +} diff --git a/ppocr/ext_op/roi_align_rotated/roi_align_rotated.py b/ppocr/ext_op/roi_align_rotated/roi_align_rotated.py new file mode 100644 index 0000000..4cc1bf6 --- /dev/null +++ b/ppocr/ext_op/roi_align_rotated/roi_align_rotated.py @@ -0,0 +1,69 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmcv/blob/master/mmcv/ops/roi_align_rotated.py +""" + +import paddle +import paddle.nn as nn +from paddle.utils.cpp_extension import load + +custom_ops = load( + name="custom_jit_ops", + sources=[ + "ppocr/ext_op/roi_align_rotated/roi_align_rotated.cc", + "ppocr/ext_op/roi_align_rotated/roi_align_rotated.cu", + ], +) + +roi_align_rotated = custom_ops.roi_align_rotated + + +class RoIAlignRotated(nn.Layer): + """RoI align pooling layer for rotated proposals.""" + + def __init__( + self, out_size, spatial_scale, sample_num=0, aligned=True, clockwise=False + ): + super(RoIAlignRotated, self).__init__() + + if isinstance(out_size, int): + self.out_h = out_size + self.out_w = out_size + elif isinstance(out_size, tuple): + assert len(out_size) == 2 + assert isinstance(out_size[0], int) + assert isinstance(out_size[1], int) + self.out_h, self.out_w = out_size + else: + raise TypeError('"out_size" must be an integer or tuple of integers') + + self.spatial_scale = float(spatial_scale) + self.sample_num = int(sample_num) + self.aligned = aligned + self.clockwise = clockwise + + def forward(self, feats, rois): + output = roi_align_rotated( + feats, + rois, + self.out_h, + self.out_w, + self.spatial_scale, + self.sample_num, + self.aligned, + self.clockwise, + ) + return output diff --git a/ppocr/losses/__init__.py b/ppocr/losses/__init__.py new file mode 100644 index 0000000..59b7ecf --- /dev/null +++ b/ppocr/losses/__init__.py @@ -0,0 +1,124 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy +import paddle +import paddle.nn as nn + +# basic_loss +from .basic_loss import LossFromOutput + +# det loss +from .det_db_loss import DBLoss +from .det_east_loss import EASTLoss +from .det_sast_loss import SASTLoss +from .det_pse_loss import PSELoss +from .det_fce_loss import FCELoss +from .det_ct_loss import CTLoss +from .det_drrg_loss import DRRGLoss + +# rec loss +from .rec_ctc_loss import CTCLoss +from .rec_att_loss import AttentionLoss +from .rec_srn_loss import SRNLoss +from .rec_ce_loss import CELoss +from .rec_sar_loss import SARLoss +from .rec_aster_loss import AsterLoss +from .rec_pren_loss import PRENLoss +from .rec_multi_loss import MultiLoss +from .rec_vl_loss import VLLoss +from .rec_spin_att_loss import SPINAttentionLoss +from .rec_rfl_loss import RFLLoss +from .rec_can_loss import CANLoss +from .rec_satrn_loss import SATRNLoss +from .rec_nrtr_loss import NRTRLoss +from .rec_parseq_loss import ParseQLoss +from .rec_cppd_loss import CPPDLoss +from .rec_latexocr_loss import LaTeXOCRLoss +from .rec_unimernet_loss import UniMERNetLoss +from .rec_ppformulanet_loss import PPFormulaNet_S_Loss, PPFormulaNet_L_Loss + +# cls loss +from .cls_loss import ClsLoss + +# e2e loss +from .e2e_pg_loss import PGLoss +from .kie_sdmgr_loss import SDMGRLoss + +# basic loss function +from .basic_loss import DistanceLoss + +# combined loss function +from .combined_loss import CombinedLoss + +# table loss +from .table_att_loss import TableAttentionLoss, SLALoss +from .table_master_loss import TableMasterLoss + +# vqa token loss +from .vqa_token_layoutlm_loss import VQASerTokenLayoutLMLoss + +# sr loss +from .stroke_focus_loss import StrokeFocusLoss +from .text_focus_loss import TelescopeLoss + + +def build_loss(config): + support_dict = [ + "DBLoss", + "PSELoss", + "EASTLoss", + "SASTLoss", + "FCELoss", + "CTCLoss", + "ClsLoss", + "AttentionLoss", + "SRNLoss", + "PGLoss", + "CombinedLoss", + "CELoss", + "TableAttentionLoss", + "SARLoss", + "AsterLoss", + "SDMGRLoss", + "VQASerTokenLayoutLMLoss", + "LossFromOutput", + "PRENLoss", + "MultiLoss", + "TableMasterLoss", + "SPINAttentionLoss", + "VLLoss", + "StrokeFocusLoss", + "SLALoss", + "CTLoss", + "RFLLoss", + "DRRGLoss", + "CANLoss", + "TelescopeLoss", + "SATRNLoss", + "NRTRLoss", + "ParseQLoss", + "CPPDLoss", + "LaTeXOCRLoss", + "UniMERNetLoss", + "PPFormulaNet_S_Loss", + "PPFormulaNet_L_Loss", + ] + config = copy.deepcopy(config) + module_name = config.pop("name") + assert module_name in support_dict, Exception( + "loss only support {}".format(support_dict) + ) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/losses/ace_loss.py b/ppocr/losses/ace_loss.py new file mode 100644 index 0000000..961528d --- /dev/null +++ b/ppocr/losses/ace_loss.py @@ -0,0 +1,49 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This code is refer from: https://github.com/viig99/LS-ACELoss + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +import paddle.nn as nn + + +class ACELoss(nn.Layer): + def __init__(self, **kwargs): + super().__init__() + self.loss_func = nn.CrossEntropyLoss( + weight=None, ignore_index=0, reduction="none", soft_label=True, axis=-1 + ) + + def __call__(self, predicts, batch): + if isinstance(predicts, (list, tuple)): + predicts = predicts[-1] + + B, N = predicts.shape[:2] + div = paddle.to_tensor([N]).astype("float32") + + predicts = nn.functional.softmax(predicts, axis=-1) + aggregation_preds = paddle.sum(predicts, axis=1) + aggregation_preds = paddle.divide(aggregation_preds, div) + + length = batch[2].astype("float32") + batch = batch[3].astype("float32") + batch[:, 0] = paddle.subtract(div, length) + batch = paddle.divide(batch, div) + + loss = self.loss_func(aggregation_preds, batch) + return {"loss_ace": loss} diff --git a/ppocr/losses/basic_loss.py b/ppocr/losses/basic_loss.py new file mode 100644 index 0000000..e85911a --- /dev/null +++ b/ppocr/losses/basic_loss.py @@ -0,0 +1,247 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F + +from paddle.nn import L1Loss +from paddle.nn import MSELoss as L2Loss +from paddle.nn import SmoothL1Loss + + +class CELoss(nn.Layer): + def __init__(self, epsilon=None): + super().__init__() + if epsilon is not None and (epsilon <= 0 or epsilon >= 1): + epsilon = None + self.epsilon = epsilon + + def _labelsmoothing(self, target, class_num): + if target.shape[-1] != class_num: + one_hot_target = F.one_hot(target, class_num) + else: + one_hot_target = target + soft_target = F.label_smooth(one_hot_target, epsilon=self.epsilon) + soft_target = paddle.reshape(soft_target, shape=[-1, class_num]) + return soft_target + + def forward(self, x, label): + loss_dict = {} + if self.epsilon is not None: + class_num = x.shape[-1] + label = self._labelsmoothing(label, class_num) + x = -F.log_softmax(x, axis=-1) + loss = paddle.sum(x * label, axis=-1) + else: + if label.shape[-1] == x.shape[-1]: + label = F.softmax(label, axis=-1) + soft_label = True + else: + soft_label = False + loss = F.cross_entropy(x, label=label, soft_label=soft_label) + return loss + + +class KLJSLoss(object): + def __init__(self, mode="kl"): + assert mode in [ + "kl", + "js", + "KL", + "JS", + ], "mode can only be one of ['kl', 'KL', 'js', 'JS']" + self.mode = mode + + def __call__(self, p1, p2, reduction="mean", eps=1e-5): + if self.mode.lower() == "kl": + loss = paddle.multiply(p2, paddle.log((p2 + eps) / (p1 + eps) + eps)) + loss += paddle.multiply(p1, paddle.log((p1 + eps) / (p2 + eps) + eps)) + loss *= 0.5 + elif self.mode.lower() == "js": + loss = paddle.multiply( + p2, paddle.log((2 * p2 + eps) / (p1 + p2 + eps) + eps) + ) + loss += paddle.multiply( + p1, paddle.log((2 * p1 + eps) / (p1 + p2 + eps) + eps) + ) + loss *= 0.5 + else: + raise ValueError( + "The mode.lower() if KLJSLoss should be one of ['kl', 'js']" + ) + + if reduction == "mean": + loss = paddle.mean(loss, axis=[1, 2]) + elif reduction == "none" or reduction is None: + return loss + else: + loss = paddle.sum(loss, axis=[1, 2]) + + return loss + + +class DMLLoss(nn.Layer): + """ + DMLLoss + """ + + def __init__(self, act=None, use_log=False): + super().__init__() + if act is not None: + assert act in ["softmax", "sigmoid"] + if act == "softmax": + self.act = nn.Softmax(axis=-1) + elif act == "sigmoid": + self.act = nn.Sigmoid() + else: + self.act = None + + self.use_log = use_log + self.jskl_loss = KLJSLoss(mode="kl") + + def _kldiv(self, x, target): + eps = 1.0e-10 + loss = target * (paddle.log(target + eps) - x) + # batch mean loss + loss = paddle.sum(loss) / loss.shape[0] + return loss + + def forward(self, out1, out2): + if self.act is not None: + out1 = self.act(out1) + 1e-10 + out2 = self.act(out2) + 1e-10 + if self.use_log: + # for recognition distillation, log is needed for feature map + log_out1 = paddle.log(out1) + log_out2 = paddle.log(out2) + loss = (self._kldiv(log_out1, out2) + self._kldiv(log_out2, out1)) / 2.0 + else: + # for detection distillation log is not needed + loss = self.jskl_loss(out1, out2) + return loss + + +class DistanceLoss(nn.Layer): + """ + DistanceLoss: + mode: loss mode + """ + + def __init__(self, mode="l2", **kargs): + super().__init__() + assert mode in ["l1", "l2", "smooth_l1"] + if mode == "l1": + self.loss_func = nn.L1Loss(**kargs) + elif mode == "l2": + self.loss_func = nn.MSELoss(**kargs) + elif mode == "smooth_l1": + self.loss_func = nn.SmoothL1Loss(**kargs) + + def forward(self, x, y): + return self.loss_func(x, y) + + +class LossFromOutput(nn.Layer): + def __init__(self, key="loss", reduction="none"): + super().__init__() + self.key = key + self.reduction = reduction + + def forward(self, predicts, batch): + loss = predicts + if self.key is not None and isinstance(predicts, dict): + loss = loss[self.key] + if self.reduction == "mean": + loss = paddle.mean(loss) + elif self.reduction == "sum": + loss = paddle.sum(loss) + return {"loss": loss} + + +class KLDivLoss(nn.Layer): + """ + KLDivLoss + """ + + def __init__(self): + super().__init__() + + def _kldiv(self, x, target, mask=None): + eps = 1.0e-10 + loss = target * (paddle.log(target + eps) - x) + if mask is not None: + loss = loss.flatten(0, 1).sum(axis=1) + loss = loss.masked_select(mask).mean() + else: + # batch mean loss + loss = paddle.sum(loss) / loss.shape[0] + return loss + + def forward(self, logits_s, logits_t, mask=None): + log_out_s = F.log_softmax(logits_s, axis=-1) + out_t = F.softmax(logits_t, axis=-1) + loss = self._kldiv(log_out_s, out_t, mask) + return loss + + +class DKDLoss(nn.Layer): + """ + KLDivLoss + """ + + def __init__(self, temperature=1.0, alpha=1.0, beta=1.0): + super().__init__() + self.temperature = temperature + self.alpha = alpha + self.beta = beta + + def _cat_mask(self, t, mask1, mask2): + t1 = (t * mask1).sum(axis=1, keepdim=True) + t2 = (t * mask2).sum(axis=1, keepdim=True) + rt = paddle.concat([t1, t2], axis=1) + return rt + + def _kl_div(self, x, label, mask=None): + y = (label * (paddle.log(label + 1e-10) - x)).sum(axis=1) + if mask is not None: + y = y.masked_select(mask).mean() + else: + y = y.mean() + return y + + def forward(self, logits_student, logits_teacher, target, mask=None): + gt_mask = F.one_hot(target.reshape([-1]), num_classes=logits_student.shape[-1]) + other_mask = 1 - gt_mask + logits_student = logits_student.flatten(0, 1) + logits_teacher = logits_teacher.flatten(0, 1) + pred_student = F.softmax(logits_student / self.temperature, axis=1) + pred_teacher = F.softmax(logits_teacher / self.temperature, axis=1) + pred_student = self._cat_mask(pred_student, gt_mask, other_mask) + pred_teacher = self._cat_mask(pred_teacher, gt_mask, other_mask) + log_pred_student = paddle.log(pred_student) + tckd_loss = self._kl_div(log_pred_student, pred_teacher) * (self.temperature**2) + pred_teacher_part2 = F.softmax( + logits_teacher / self.temperature - 1000.0 * gt_mask, axis=1 + ) + log_pred_student_part2 = F.log_softmax( + logits_student / self.temperature - 1000.0 * gt_mask, axis=1 + ) + nckd_loss = self._kl_div(log_pred_student_part2, pred_teacher_part2) * ( + self.temperature**2 + ) + + loss = self.alpha * tckd_loss + self.beta * nckd_loss + + return loss diff --git a/ppocr/losses/center_loss.py b/ppocr/losses/center_loss.py new file mode 100644 index 0000000..87d4d44 --- /dev/null +++ b/ppocr/losses/center_loss.py @@ -0,0 +1,89 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This code is refer from: https://github.com/KaiyangZhou/pytorch-center-loss + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import os +import pickle + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F + + +class CenterLoss(nn.Layer): + """ + Reference: Wen et al. A Discriminative Feature Learning Approach for Deep Face Recognition. ECCV 2016. + """ + + def __init__(self, num_classes=6625, feat_dim=96, center_file_path=None): + super().__init__() + self.num_classes = num_classes + self.feat_dim = feat_dim + self.centers = paddle.randn(shape=[self.num_classes, self.feat_dim]).astype( + "float64" + ) + + if center_file_path is not None: + assert os.path.exists( + center_file_path + ), f"center path({center_file_path}) must exist when it is not None." + with open(center_file_path, "rb") as f: + char_dict = pickle.load(f) + for key in char_dict.keys(): + self.centers[key] = paddle.to_tensor(char_dict[key]) + + def __call__(self, predicts, batch): + assert isinstance(predicts, (list, tuple)) + features, predicts = predicts + + feats_reshape = paddle.reshape(features, [-1, features.shape[-1]]).astype( + "float64" + ) + label = paddle.argmax(predicts, axis=2) + label = paddle.reshape(label, [label.shape[0] * label.shape[1]]) + + batch_size = feats_reshape.shape[0] + + # calc l2 distance between feats and centers + square_feat = paddle.sum(paddle.square(feats_reshape), axis=1, keepdim=True) + square_feat = paddle.expand(square_feat, [batch_size, self.num_classes]) + + square_center = paddle.sum(paddle.square(self.centers), axis=1, keepdim=True) + square_center = paddle.expand( + square_center, [self.num_classes, batch_size] + ).astype("float64") + square_center = paddle.transpose(square_center, [1, 0]) + + distmat = paddle.add(square_feat, square_center) + feat_dot_center = paddle.matmul( + feats_reshape, paddle.transpose(self.centers, [1, 0]) + ) + distmat = distmat - 2.0 * feat_dot_center + + # generate the mask + classes = paddle.arange(self.num_classes).astype("int64") + label = paddle.expand( + paddle.unsqueeze(label, 1), (batch_size, self.num_classes) + ) + mask = paddle.equal( + paddle.expand(classes, [batch_size, self.num_classes]), label + ).astype("float64") + dist = paddle.multiply(distmat, mask) + + loss = paddle.sum(paddle.clip(dist, min=1e-12, max=1e12)) / batch_size + return {"loss_center": loss} diff --git a/ppocr/losses/cls_loss.py b/ppocr/losses/cls_loss.py new file mode 100755 index 0000000..eb3b17e --- /dev/null +++ b/ppocr/losses/cls_loss.py @@ -0,0 +1,30 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from paddle import nn + + +class ClsLoss(nn.Layer): + def __init__(self, **kwargs): + super(ClsLoss, self).__init__() + self.loss_func = nn.CrossEntropyLoss(reduction="mean") + + def forward(self, predicts, batch): + label = batch[1].astype("int64") + loss = self.loss_func(input=predicts, label=label) + return {"loss": loss} diff --git a/ppocr/losses/combined_loss.py b/ppocr/losses/combined_loss.py new file mode 100644 index 0000000..10cdb01 --- /dev/null +++ b/ppocr/losses/combined_loss.py @@ -0,0 +1,84 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle +import paddle.nn as nn + +from .rec_ctc_loss import CTCLoss +from .center_loss import CenterLoss +from .ace_loss import ACELoss +from .rec_sar_loss import SARLoss + +from .distillation_loss import DistillationCTCLoss, DistillCTCLogits +from .distillation_loss import DistillationSARLoss, DistillationNRTRLoss +from .distillation_loss import ( + DistillationDMLLoss, + DistillationKLDivLoss, + DistillationDKDLoss, +) +from .distillation_loss import ( + DistillationDistanceLoss, + DistillationDBLoss, + DistillationDilaDBLoss, +) +from .distillation_loss import ( + DistillationVQASerTokenLayoutLMLoss, + DistillationSERDMLLoss, +) +from .distillation_loss import DistillationLossFromOutput +from .distillation_loss import DistillationVQADistanceLoss + + +class CombinedLoss(nn.Layer): + """ + CombinedLoss: + a combionation of loss function + """ + + def __init__(self, loss_config_list=None): + super().__init__() + self.loss_func = [] + self.loss_weight = [] + assert isinstance(loss_config_list, list), "operator config should be a list" + for config in loss_config_list: + assert isinstance(config, dict) and len(config) == 1, "yaml format error" + name = list(config)[0] + param = config[name] + assert ( + "weight" in param + ), "weight must be in param, but param just contains {}".format( + param.keys() + ) + self.loss_weight.append(param.pop("weight")) + self.loss_func.append(eval(name)(**param)) + + def forward(self, input, batch, **kargs): + loss_dict = {} + loss_all = 0.0 + for idx, loss_func in enumerate(self.loss_func): + loss = loss_func(input, batch, **kargs) + if isinstance(loss, paddle.Tensor): + loss = {"loss_{}_{}".format(str(loss), idx): loss} + + weight = self.loss_weight[idx] + + loss = {key: loss[key] * weight for key in loss} + + if "loss" in loss: + loss_all += loss["loss"] + else: + loss_all += paddle.add_n(list(loss.values())) + loss_dict.update(loss) + loss_dict["loss"] = loss_all + return loss_dict diff --git a/ppocr/losses/det_basic_loss.py b/ppocr/losses/det_basic_loss.py new file mode 100644 index 0000000..bee82b7 --- /dev/null +++ b/ppocr/losses/det_basic_loss.py @@ -0,0 +1,284 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/WenmuZhou/DBNet.pytorch/blob/master/models/losses/basic_loss.py +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +import paddle +from paddle import nn +import paddle.nn.functional as F + + +class BalanceLoss(nn.Layer): + def __init__( + self, + balance_loss=True, + main_loss_type="DiceLoss", + negative_ratio=3, + return_origin=False, + eps=1e-6, + **kwargs, + ): + """ + The BalanceLoss for Differentiable Binarization text detection + args: + balance_loss (bool): whether balance loss or not, default is True + main_loss_type (str): can only be one of ['CrossEntropy','DiceLoss', + 'Euclidean','BCELoss', 'MaskL1Loss'], default is 'DiceLoss'. + negative_ratio (int|float): float, default is 3. + return_origin (bool): whether return unbalanced loss or not, default is False. + eps (float): default is 1e-6. + """ + super(BalanceLoss, self).__init__() + self.balance_loss = balance_loss + self.main_loss_type = main_loss_type + self.negative_ratio = negative_ratio + self.return_origin = return_origin + self.eps = eps + + if self.main_loss_type == "CrossEntropy": + self.loss = nn.CrossEntropyLoss() + elif self.main_loss_type == "Euclidean": + self.loss = nn.MSELoss() + elif self.main_loss_type == "DiceLoss": + self.loss = DiceLoss(self.eps) + elif self.main_loss_type == "BCELoss": + self.loss = BCELoss(reduction="none") + elif self.main_loss_type == "MaskL1Loss": + self.loss = MaskL1Loss(self.eps) + else: + loss_type = [ + "CrossEntropy", + "DiceLoss", + "Euclidean", + "BCELoss", + "MaskL1Loss", + ] + raise Exception( + "main_loss_type in BalanceLoss() can only be one of {}".format( + loss_type + ) + ) + + def forward(self, pred, gt, mask=None): + """ + The BalanceLoss for Differentiable Binarization text detection + args: + pred (variable): predicted feature maps. + gt (variable): ground truth feature maps. + mask (variable): masked maps. + return: (variable) balanced loss + """ + positive = gt * mask + negative = (1 - gt) * mask + + positive_count = int(positive.sum()) + negative_count = int(min(negative.sum(), positive_count * self.negative_ratio)) + loss = self.loss(pred, gt, mask=mask) + + if not self.balance_loss: + return loss + + positive_loss = positive * loss + negative_loss = negative * loss + negative_loss = paddle.reshape(negative_loss, shape=[-1]) + if negative_count > 0: + sort_loss = negative_loss.sort(descending=True) + negative_loss = sort_loss[:negative_count] + # negative_loss, _ = paddle.topk(negative_loss, k=negative_count_int) + balance_loss = (positive_loss.sum() + negative_loss.sum()) / ( + positive_count + negative_count + self.eps + ) + else: + balance_loss = positive_loss.sum() / (positive_count + self.eps) + if self.return_origin: + return balance_loss, loss + + return balance_loss + + +class DiceLoss(nn.Layer): + def __init__(self, eps=1e-6): + super(DiceLoss, self).__init__() + self.eps = eps + + def forward(self, pred, gt, mask, weights=None): + """ + DiceLoss function. + """ + + assert pred.shape == gt.shape + assert pred.shape == mask.shape + if weights is not None: + assert weights.shape == mask.shape + mask = weights * mask + intersection = paddle.sum(pred * gt * mask) + + union = paddle.sum(pred * mask) + paddle.sum(gt * mask) + self.eps + loss = 1 - 2.0 * intersection / union + assert loss <= 1 + return loss + + +class MaskL1Loss(nn.Layer): + def __init__(self, eps=1e-6): + super(MaskL1Loss, self).__init__() + self.eps = eps + + def forward(self, pred, gt, mask): + """ + Mask L1 Loss + """ + loss = (paddle.abs(pred - gt) * mask).sum() / (mask.sum() + self.eps) + loss = paddle.mean(loss) + return loss + + +class BCELoss(nn.Layer): + def __init__(self, reduction="mean"): + super(BCELoss, self).__init__() + self.reduction = reduction + + def forward(self, input, label, mask=None, weight=None, name=None): + loss = F.binary_cross_entropy(input, label, reduction=self.reduction) + return loss + + +class MaskedFocalLoss(nn.Layer): + """ + Binary Focal Loss with mask support, designed for text segmentation tasks. + + Focal Loss addresses class imbalance by down-weighting easy examples and + focusing training on hard examples: + FL(p_t) = -alpha_t * (1 - p_t)^gamma * log(p_t) + + Compared to OHEM (which hard-selects a fixed ratio of negatives), Focal Loss + applies a continuous per-pixel weight that gracefully scales with difficulty, + making it a strictly superior drop-in for the OHEM + DiceLoss pattern when + DiceLoss returns a scalar and OHEM has no discriminating effect. + + Args: + alpha (float): Balancing factor for the positive (text) class. + Since text pixels are a small minority, alpha > 0.5 gives them + higher weight. Default: 0.75. + gamma (float): Focusing parameter. gamma=0 reduces to masked BCE. + gamma=2 is the standard value from the original Focal Loss paper. + Default: 2.0. + eps (float): Small constant for numerical stability. Default: 1e-6. + """ + + def __init__(self, alpha=0.25, gamma=2.0, eps=1e-6): + super(MaskedFocalLoss, self).__init__() + self.alpha = alpha + self.gamma = gamma + self.eps = eps + + def forward(self, pred, gt, mask): + """ + Args: + pred (Tensor): Predicted probability map, shape (B, H, W), in [0, 1]. + (i.e. after sigmoid — the direct output of DBHead.binarize) + gt (Tensor): Binary ground-truth map, shape (B, H, W), values 0 or 1. + mask (Tensor): Valid-pixel mask, shape (B, H, W), values 0 or 1. + Pixels with mask=0 are ignored regions (e.g. too-small text). + Returns: + Tensor: Scalar focal loss averaged over valid (mask=1) pixels. + """ + # F.sigmoid_focal_loss expects a logit (pre-sigmoid) input and applies + # sigmoid internally using the numerically stable log-sum-exp form: + # log(σ(x)) = -softplus(-x), log(1-σ(x)) = -softplus(x) + # This avoids the log(0) issue of the manual implementation. + # Since pred is already a probability (post-sigmoid from DBHead), we + # invert it: logit = log(p / (1-p)). The round-trip is numerically safe + # after clamping, and the stable path inside paddle takes over from there. + pred = paddle.clip(pred, self.eps, 1.0 - self.eps) + logit = paddle.log(pred / (1.0 - pred)) + + # Per-pixel focal loss, shape (B, H, W) + # reduction='none' so we can apply the mask ourselves + loss = F.sigmoid_focal_loss( + logit, + gt, + normalizer=None, + alpha=self.alpha, + gamma=self.gamma, + reduction="none", + ) + + # Average over valid (mask=1) pixels only + return (loss * mask).sum() / (mask.sum() + self.eps) + + +class DiceFocalLoss(nn.Layer): + """ + Combined DiceLoss + MaskedFocalLoss for binary text segmentation. + + Rationale for the combination: + - DiceLoss optimizes the global F1 / region overlap between prediction and GT. + It is naturally robust to class imbalance (text vs background) because it + normalizes by the sum of both sets, not by pixel count. + - MaskedFocalLoss provides per-pixel supervision with adaptive hard-example + weighting. It compensates for DiceLoss being a global metric that cannot + distinguish which specific pixels are mispredicted. + Together they provide complementary supervision: DiceLoss for global shape + quality, FocalLoss for pixel-level precision on ambiguous boundaries. + + This design follows the Dice + Focal combination used in mmsegmentation and + segmentation_models_pytorch for binary segmentation with class imbalance. + + This class is a drop-in replacement for BalanceLoss when main_loss_type is + 'DiceLoss' — both share the same forward(pred, gt, mask) signature and + return a scalar. + + Args: + dice_weight (float): Weight for the DiceLoss term. Default: 1.0. + focal_weight (float): Weight for the MaskedFocalLoss term. Default: 1.0. + focal_alpha (float): Positive-class balancing factor for FocalLoss. + Default: 0.75. + focal_gamma (float): Focusing exponent for FocalLoss. Default: 2.0. + eps (float): Numerical stability constant. Default: 1e-6. + """ + + def __init__( + self, + dice_weight=1.0, + focal_weight=1.0, + focal_alpha=0.75, + focal_gamma=2.0, + eps=1e-6, + ): + super(DiceFocalLoss, self).__init__() + self.dice_weight = dice_weight + self.focal_weight = focal_weight + self.dice_loss = DiceLoss(eps=eps) + self.focal_loss = MaskedFocalLoss(alpha=focal_alpha, gamma=focal_gamma, eps=eps) + + def forward(self, pred, gt, mask=None): + """ + Args: + pred (Tensor): Predicted probability map, shape (B, H, W), in [0, 1]. + gt (Tensor): Binary ground-truth shrink map, shape (B, H, W). + mask (Tensor): Valid-pixel mask, shape (B, H, W). + Returns: + Tensor: Scalar combined loss. + """ + loss_dice = self.dice_loss(pred, gt, mask) + loss_focal = self.focal_loss(pred, gt, mask) + return self.dice_weight * loss_dice + self.focal_weight * loss_focal diff --git a/ppocr/losses/det_ct_loss.py b/ppocr/losses/det_ct_loss.py new file mode 100755 index 0000000..4655eff --- /dev/null +++ b/ppocr/losses/det_ct_loss.py @@ -0,0 +1,302 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/shengtao96/CentripetalText/tree/main/models/loss +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +import paddle.nn.functional as F +import numpy as np + + +def ohem_single(score, gt_text, training_mask): + # online hard example mining + + pos_num = int(paddle.sum(gt_text > 0.5)) - int( + paddle.sum((gt_text > 0.5) & (training_mask <= 0.5)) + ) + + if pos_num == 0: + # selected_mask = gt_text.copy() * 0 # may be not good + selected_mask = training_mask + selected_mask = paddle.cast( + selected_mask.reshape((1, selected_mask.shape[0], selected_mask.shape[1])), + "float32", + ) + return selected_mask + + neg_num = int(paddle.sum((gt_text <= 0.5) & (training_mask > 0.5))) + neg_num = int(min(pos_num * 3, neg_num)) + + if neg_num == 0: + selected_mask = training_mask + selected_mask = paddle.cast( + selected_mask.reshape((1, selected_mask.shape[0], selected_mask.shape[1])), + "float32", + ) + return selected_mask + + # hard example + neg_score = score[(gt_text <= 0.5) & (training_mask > 0.5)] + neg_score_sorted = paddle.sort(-neg_score) + threshold = -neg_score_sorted[neg_num - 1] + + selected_mask = ((score >= threshold) | (gt_text > 0.5)) & (training_mask > 0.5) + selected_mask = paddle.cast( + selected_mask.reshape((1, selected_mask.shape[0], selected_mask.shape[1])), + "float32", + ) + return selected_mask + + +def ohem_batch(scores, gt_texts, training_masks): + selected_masks = [] + for i in range(scores.shape[0]): + selected_masks.append( + ohem_single(scores[i, :, :], gt_texts[i, :, :], training_masks[i, :, :]) + ) + + selected_masks = paddle.cast(paddle.concat(selected_masks, 0), "float32") + return selected_masks + + +def iou_single(a, b, mask, n_class): + EPS = 1e-6 + valid = mask == 1 + a = a[valid] + b = b[valid] + miou = [] + + # iou of each class + for i in range(n_class): + inter = paddle.cast(((a == i) & (b == i)), "float32") + union = paddle.cast(((a == i) | (b == i)), "float32") + + miou.append(paddle.sum(inter) / (paddle.sum(union) + EPS)) + miou = sum(miou) / len(miou) + return miou + + +def iou(a, b, mask, n_class=2, reduce=True): + batch_size = a.shape[0] + + a = a.reshape((batch_size, -1)) + b = b.reshape((batch_size, -1)) + mask = mask.reshape((batch_size, -1)) + + iou = paddle.zeros((batch_size,), dtype="float32") + for i in range(batch_size): + iou[i] = iou_single(a[i], b[i], mask[i], n_class) + + if reduce: + iou = paddle.mean(iou) + return iou + + +class DiceLoss(nn.Layer): + def __init__(self, loss_weight=1.0): + super(DiceLoss, self).__init__() + self.loss_weight = loss_weight + + def forward(self, input, target, mask, reduce=True): + batch_size = input.shape[0] + input = F.sigmoid(input) # scale to 0-1 + + input = input.reshape((batch_size, -1)) + target = paddle.cast(target.reshape((batch_size, -1)), "float32") + mask = paddle.cast(mask.reshape((batch_size, -1)), "float32") + + input = input * mask + target = target * mask + + a = paddle.sum(input * target, axis=1) + b = paddle.sum(input * input, axis=1) + 0.001 + c = paddle.sum(target * target, axis=1) + 0.001 + d = (2 * a) / (b + c) + loss = 1 - d + + loss = self.loss_weight * loss + + if reduce: + loss = paddle.mean(loss) + + return loss + + +class SmoothL1Loss(nn.Layer): + def __init__(self, beta=1.0, loss_weight=1.0): + super(SmoothL1Loss, self).__init__() + self.beta = beta + self.loss_weight = loss_weight + + np_coord = np.zeros(shape=[640, 640, 2], dtype=np.int64) + for i in range(640): + for j in range(640): + np_coord[i, j, 0] = j + np_coord[i, j, 1] = i + np_coord = np_coord.reshape((-1, 2)) + + self.coord = self.create_parameter( + shape=[640 * 640, 2], + dtype="int32", # NOTE: not support "int64" before paddle 2.3.1 + default_initializer=nn.initializer.Assign(value=np_coord), + ) + self.coord.stop_gradient = True + + def forward_single(self, input, target, mask, beta=1.0, eps=1e-6): + batch_size = input.shape[0] + + diff = paddle.abs(input - target) * mask.unsqueeze(1) + loss = paddle.where(diff < beta, 0.5 * diff * diff / beta, diff - 0.5 * beta) + loss = paddle.cast(loss.reshape((batch_size, -1)), "float32") + mask = paddle.cast(mask.reshape((batch_size, -1)), "float32") + loss = paddle.sum(loss, axis=-1) + loss = loss / (mask.sum(axis=-1) + eps) + + return loss + + def select_single(self, distance, gt_instance, gt_kernel_instance, training_mask): + with paddle.no_grad(): + # paddle 2.3.1, paddle.slice not support: + # distance[:, self.coord[:, 1], self.coord[:, 0]] + select_distance_list = [] + for i in range(2): + tmp1 = distance[i, :] + tmp2 = tmp1[self.coord[:, 1], self.coord[:, 0]] + select_distance_list.append(tmp2.unsqueeze(0)) + select_distance = paddle.concat(select_distance_list, axis=0) + + off_points = paddle.cast( + self.coord, "float32" + ) + 10 * select_distance.transpose((1, 0)) + + off_points = paddle.cast(off_points, "int64") + off_points = paddle.clip(off_points, 0, distance.shape[-1] - 1) + + selected_mask = ( + gt_instance[self.coord[:, 1], self.coord[:, 0]] + != gt_kernel_instance[off_points[:, 1], off_points[:, 0]] + ) + selected_mask = paddle.cast( + selected_mask.reshape((1, -1, distance.shape[-1])), "int64" + ) + selected_training_mask = selected_mask * training_mask + + return selected_training_mask + + def forward( + self, + distances, + gt_instances, + gt_kernel_instances, + training_masks, + gt_distances, + reduce=True, + ): + selected_training_masks = [] + for i in range(distances.shape[0]): + selected_training_masks.append( + self.select_single( + distances[i, :, :, :], + gt_instances[i, :, :], + gt_kernel_instances[i, :, :], + training_masks[i, :, :], + ) + ) + selected_training_masks = paddle.cast( + paddle.concat(selected_training_masks, 0), "float32" + ) + + loss = self.forward_single( + distances, gt_distances, selected_training_masks, self.beta + ) + loss = self.loss_weight * loss + + with paddle.no_grad(): + batch_size = distances.shape[0] + false_num = selected_training_masks.reshape((batch_size, -1)) + false_num = false_num.sum(axis=-1) + total_num = paddle.cast(training_masks.reshape((batch_size, -1)), "float32") + total_num = total_num.sum(axis=-1) + iou_text = (total_num - false_num) / (total_num + 1e-6) + + if reduce: + loss = paddle.mean(loss) + + return loss, iou_text + + +class CTLoss(nn.Layer): + def __init__(self): + super(CTLoss, self).__init__() + self.kernel_loss = DiceLoss() + self.loc_loss = SmoothL1Loss(beta=0.1, loss_weight=0.05) + + def forward(self, preds, batch): + imgs = batch[0] + out = preds["maps"] + ( + gt_kernels, + training_masks, + gt_instances, + gt_kernel_instances, + training_mask_distances, + gt_distances, + ) = batch[1:] + + kernels = out[:, 0, :, :] + distances = out[:, 1:, :, :] + + # kernel loss + selected_masks = ohem_batch(kernels, gt_kernels, training_masks) + + loss_kernel = self.kernel_loss( + kernels, gt_kernels, selected_masks, reduce=False + ) + + iou_kernel = iou( + paddle.cast((kernels > 0), "int64"), + gt_kernels, + training_masks, + reduce=False, + ) + losses = dict( + loss_kernels=loss_kernel, + ) + + # loc loss + loss_loc, iou_text = self.loc_loss( + distances, + gt_instances, + gt_kernel_instances, + training_mask_distances, + gt_distances, + reduce=False, + ) + losses.update( + dict( + loss_loc=loss_loc, + ) + ) + + loss_all = loss_kernel + loss_loc + losses = {"loss": loss_all} + + return losses diff --git a/ppocr/losses/det_db_loss.py b/ppocr/losses/det_db_loss.py new file mode 100755 index 0000000..ff0c6ee --- /dev/null +++ b/ppocr/losses/det_db_loss.py @@ -0,0 +1,146 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/WenmuZhou/DBNet.pytorch/blob/master/models/losses/DB_loss.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + +from .det_basic_loss import BalanceLoss, MaskL1Loss, DiceLoss, DiceFocalLoss + + +class DBLoss(nn.Layer): + """ + Differentiable Binarization (DB) Loss Function + args: + param (dict): the super parameter for DB Loss + """ + + def __init__( + self, + balance_loss=True, + main_loss_type="DiceLoss", + alpha=5, + beta=10, + ohem_ratio=3, + eps=1e-6, + aux_weight_p4=0.0, + aux_weight_p3=0.0, + aux_weight_p2=0.0, + focal_alpha=0.25, + focal_gamma=2.0, + dice_weight=1.0, + focal_weight=1.0, + **kwargs, + ): + super(DBLoss, self).__init__() + self.alpha = alpha + self.beta = beta + self.l1_loss = MaskL1Loss(eps=eps) + if main_loss_type == "DiceFocalLoss": + self.bce_loss = DiceFocalLoss( + dice_weight=dice_weight, + focal_weight=focal_weight, + focal_alpha=focal_alpha, + focal_gamma=focal_gamma, + eps=eps, + ) + self.dice_loss = self.bce_loss + elif main_loss_type == "DiceLoss": + self.bce_loss = BalanceLoss( + balance_loss=balance_loss, + main_loss_type=main_loss_type, + negative_ratio=ohem_ratio, + ) + self.dice_loss = DiceLoss(eps=eps) + else: + raise Exception("[DBLoss]: Unrecognized main loss type!") + self.aux_weight_p4 = aux_weight_p4 + self.aux_weight_p3 = aux_weight_p3 + self.aux_weight_p2 = aux_weight_p2 + + def forward(self, predicts, labels): + predict_maps = predicts["maps"] + ( + label_threshold_map, + label_threshold_mask, + label_shrink_map, + label_shrink_mask, + ) = labels[1:] + shrink_maps = predict_maps[:, 0, :, :] + threshold_maps = predict_maps[:, 1, :, :] + binary_maps = predict_maps[:, 2, :, :] + + loss_shrink_maps = self.bce_loss( + shrink_maps, label_shrink_map, label_shrink_mask + ) + loss_threshold_maps = self.l1_loss( + threshold_maps, label_threshold_map, label_threshold_mask + ) + loss_binary_maps = self.dice_loss( + binary_maps, label_shrink_map, label_shrink_mask + ) + loss_shrink_maps = self.alpha * loss_shrink_maps + loss_threshold_maps = self.beta * loss_threshold_maps + # CBN loss + if "distance_maps" in predicts.keys(): + distance_maps = predicts["distance_maps"] + cbn_maps = predicts["cbn_maps"] + cbn_loss = self.bce_loss( + cbn_maps[:, 0, :, :], label_shrink_map, label_shrink_mask + ) + else: + dis_loss = paddle.to_tensor([0.0]) + cbn_loss = paddle.to_tensor([0.0]) + + loss_all = loss_shrink_maps + loss_threshold_maps + loss_binary_maps + losses = { + "loss": loss_all + cbn_loss, + "loss_shrink_maps": loss_shrink_maps, + "loss_threshold_maps": loss_threshold_maps, + "loss_binary_maps": loss_binary_maps, + "loss_cbn": cbn_loss, + } + + # Auxiliary loss + for aux_key, aux_w in [ + ("aux_maps_p4", self.aux_weight_p4), + ("aux_maps_p3", self.aux_weight_p3), + ("aux_maps_p2", self.aux_weight_p2), + ]: + if aux_w > 0 and aux_key in predicts: + aux_maps = predicts[aux_key] + aux_shrink = aux_maps[:, 0, :, :] + aux_threshold = aux_maps[:, 1, :, :] + aux_binary = aux_maps[:, 2, :, :] + l_shrink = self.alpha * self.bce_loss( + aux_shrink, label_shrink_map, label_shrink_mask + ) + l_threshold = self.beta * self.l1_loss( + aux_threshold, label_threshold_map, label_threshold_mask + ) + l_binary = self.dice_loss( + aux_binary, label_shrink_map, label_shrink_mask + ) + aux_loss = l_shrink + l_threshold + l_binary + losses["loss_{}".format(aux_key)] = aux_loss + losses["loss"] = losses["loss"] + aux_w * aux_loss + + return losses diff --git a/ppocr/losses/det_drrg_loss.py b/ppocr/losses/det_drrg_loss.py new file mode 100644 index 0000000..6e990ce --- /dev/null +++ b/ppocr/losses/det_drrg_loss.py @@ -0,0 +1,234 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textdet/losses/drrg_loss.py +""" + +import paddle +import paddle.nn.functional as F +from paddle import nn + + +class DRRGLoss(nn.Layer): + def __init__(self, ohem_ratio=3.0): + super().__init__() + self.ohem_ratio = ohem_ratio + self.downsample_ratio = 1.0 + + def balance_bce_loss(self, pred, gt, mask): + """Balanced Binary-CrossEntropy Loss. + + Args: + pred (Tensor): Shape of :math:`(1, H, W)`. + gt (Tensor): Shape of :math:`(1, H, W)`. + mask (Tensor): Shape of :math:`(1, H, W)`. + + Returns: + Tensor: Balanced bce loss. + """ + assert pred.shape == gt.shape == mask.shape + assert paddle.all(pred >= 0) and paddle.all(pred <= 1) + assert paddle.all(gt >= 0) and paddle.all(gt <= 1) + positive = gt * mask + negative = (1 - gt) * mask + positive_count = int(positive.sum()) + + if positive_count > 0: + loss = F.binary_cross_entropy(pred, gt, reduction="none") + positive_loss = paddle.sum(loss * positive) + negative_loss = loss * negative + negative_count = min( + int(negative.sum()), int(positive_count * self.ohem_ratio) + ) + else: + positive_loss = paddle.to_tensor(0.0) + loss = F.binary_cross_entropy(pred, gt, reduction="none") + negative_loss = loss * negative + negative_count = 100 + negative_loss, _ = paddle.topk(negative_loss.reshape([-1]), negative_count) + + balance_loss = (positive_loss + paddle.sum(negative_loss)) / ( + float(positive_count + negative_count) + 1e-5 + ) + + return balance_loss + + def gcn_loss(self, gcn_data): + """CrossEntropy Loss from gcn module. + + Args: + gcn_data (tuple(Tensor, Tensor)): The first is the + prediction with shape :math:`(N, 2)` and the + second is the gt label with shape :math:`(m, n)` + where :math:`m * n = N`. + + Returns: + Tensor: CrossEntropy loss. + """ + gcn_pred, gt_labels = gcn_data + gt_labels = gt_labels.reshape([-1]) + loss = F.cross_entropy(gcn_pred, gt_labels) + + return loss + + def bitmasks2tensor(self, bitmasks, target_sz): + """Convert Bitmasks to tensor. + + Args: + bitmasks (list[BitmapMasks]): The BitmapMasks list. Each item is + for one img. + target_sz (tuple(int, int)): The target tensor of size + :math:`(H, W)`. + + Returns: + list[Tensor]: The list of kernel tensors. Each element stands for + one kernel level. + """ + batch_size = len(bitmasks) + results = [] + + kernel = [] + for batch_inx in range(batch_size): + mask = bitmasks[batch_inx] + # hxw + mask_sz = mask.shape + # left, right, top, bottom + pad = [0, target_sz[1] - mask_sz[1], 0, target_sz[0] - mask_sz[0]] + mask = F.pad(mask, pad, mode="constant", value=0) + kernel.append(mask) + kernel = paddle.stack(kernel) + results.append(kernel) + + return results + + def forward(self, preds, labels): + """Compute Drrg loss.""" + + assert isinstance(preds, tuple) + ( + gt_text_mask, + gt_center_region_mask, + gt_mask, + gt_top_height_map, + gt_bot_height_map, + gt_sin_map, + gt_cos_map, + ) = labels[1:8] + + downsample_ratio = self.downsample_ratio + + pred_maps, gcn_data = preds + pred_text_region = pred_maps[:, 0, :, :] + pred_center_region = pred_maps[:, 1, :, :] + pred_sin_map = pred_maps[:, 2, :, :] + pred_cos_map = pred_maps[:, 3, :, :] + pred_top_height_map = pred_maps[:, 4, :, :] + pred_bot_height_map = pred_maps[:, 5, :, :] + feature_sz = pred_maps.shape + + # bitmask 2 tensor + mapping = { + "gt_text_mask": paddle.cast(gt_text_mask, "float32"), + "gt_center_region_mask": paddle.cast(gt_center_region_mask, "float32"), + "gt_mask": paddle.cast(gt_mask, "float32"), + "gt_top_height_map": paddle.cast(gt_top_height_map, "float32"), + "gt_bot_height_map": paddle.cast(gt_bot_height_map, "float32"), + "gt_sin_map": paddle.cast(gt_sin_map, "float32"), + "gt_cos_map": paddle.cast(gt_cos_map, "float32"), + } + gt = {} + for key, value in mapping.items(): + gt[key] = value + if abs(downsample_ratio - 1.0) < 1e-2: + gt[key] = self.bitmasks2tensor(gt[key], feature_sz[2:]) + else: + gt[key] = [item.rescale(downsample_ratio) for item in gt[key]] + gt[key] = self.bitmasks2tensor(gt[key], feature_sz[2:]) + if key in ["gt_top_height_map", "gt_bot_height_map"]: + gt[key] = [item * downsample_ratio for item in gt[key]] + gt[key] = [item for item in gt[key]] + + scale = paddle.sqrt(1.0 / (pred_sin_map**2 + pred_cos_map**2 + 1e-8)) + pred_sin_map = pred_sin_map * scale + pred_cos_map = pred_cos_map * scale + + loss_text = self.balance_bce_loss( + F.sigmoid(pred_text_region), gt["gt_text_mask"][0], gt["gt_mask"][0] + ) + + text_mask = gt["gt_text_mask"][0] * gt["gt_mask"][0] + negative_text_mask = (1 - gt["gt_text_mask"][0]) * gt["gt_mask"][0] + loss_center_map = F.binary_cross_entropy( + F.sigmoid(pred_center_region), + gt["gt_center_region_mask"][0], + reduction="none", + ) + if int(text_mask.sum()) > 0: + loss_center_positive = paddle.sum(loss_center_map * text_mask) / paddle.sum( + text_mask + ) + else: + loss_center_positive = paddle.to_tensor(0.0) + loss_center_negative = paddle.sum( + loss_center_map * negative_text_mask + ) / paddle.sum(negative_text_mask) + loss_center = loss_center_positive + 0.5 * loss_center_negative + + center_mask = gt["gt_center_region_mask"][0] * gt["gt_mask"][0] + if int(center_mask.sum()) > 0: + map_sz = pred_top_height_map.shape + ones = paddle.ones(map_sz, dtype="float32") + loss_top = F.smooth_l1_loss( + pred_top_height_map / (gt["gt_top_height_map"][0] + 1e-2), + ones, + reduction="none", + ) + loss_bot = F.smooth_l1_loss( + pred_bot_height_map / (gt["gt_bot_height_map"][0] + 1e-2), + ones, + reduction="none", + ) + gt_height = gt["gt_top_height_map"][0] + gt["gt_bot_height_map"][0] + loss_height = paddle.sum( + (paddle.log(gt_height + 1) * (loss_top + loss_bot)) * center_mask + ) / paddle.sum(center_mask) + + loss_sin = paddle.sum( + F.smooth_l1_loss(pred_sin_map, gt["gt_sin_map"][0], reduction="none") + * center_mask + ) / paddle.sum(center_mask) + loss_cos = paddle.sum( + F.smooth_l1_loss(pred_cos_map, gt["gt_cos_map"][0], reduction="none") + * center_mask + ) / paddle.sum(center_mask) + else: + loss_height = paddle.to_tensor(0.0) + loss_sin = paddle.to_tensor(0.0) + loss_cos = paddle.to_tensor(0.0) + + loss_gcn = self.gcn_loss(gcn_data) + + loss = loss_text + loss_center + loss_height + loss_sin + loss_cos + loss_gcn + results = dict( + loss=loss, + loss_text=loss_text, + loss_center=loss_center, + loss_height=loss_height, + loss_sin=loss_sin, + loss_cos=loss_cos, + loss_gcn=loss_gcn, + ) + + return results diff --git a/ppocr/losses/det_east_loss.py b/ppocr/losses/det_east_loss.py new file mode 100644 index 0000000..e5725d0 --- /dev/null +++ b/ppocr/losses/det_east_loss.py @@ -0,0 +1,62 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +from .det_basic_loss import DiceLoss + + +class EASTLoss(nn.Layer): + """ """ + + def __init__(self, eps=1e-6, **kwargs): + super(EASTLoss, self).__init__() + self.dice_loss = DiceLoss(eps=eps) + + def forward(self, predicts, labels): + l_score, l_geo, l_mask = labels[1:] + f_score = predicts["f_score"] + f_geo = predicts["f_geo"] + + dice_loss = self.dice_loss(f_score, l_score, l_mask) + + # smoooth_l1_loss + channels = 8 + l_geo_split = paddle.split(l_geo, num_or_sections=channels + 1, axis=1) + f_geo_split = paddle.split(f_geo, num_or_sections=channels, axis=1) + smooth_l1 = 0 + for i in range(0, channels): + geo_diff = l_geo_split[i] - f_geo_split[i] + abs_geo_diff = paddle.abs(geo_diff) + smooth_l1_sign = paddle.less_than(abs_geo_diff, l_score) + smooth_l1_sign = paddle.cast(smooth_l1_sign, dtype="float32") + in_loss = abs_geo_diff * abs_geo_diff * smooth_l1_sign + ( + abs_geo_diff - 0.5 + ) * (1.0 - smooth_l1_sign) + out_loss = l_geo_split[-1] / channels * in_loss * l_score + smooth_l1 += out_loss + smooth_l1_loss = paddle.mean(smooth_l1 * l_score) + + dice_loss = dice_loss * 0.01 + total_loss = dice_loss + smooth_l1_loss + losses = { + "loss": total_loss, + "dice_loss": dice_loss, + "smooth_l1_loss": smooth_l1_loss, + } + return losses diff --git a/ppocr/losses/det_fce_loss.py b/ppocr/losses/det_fce_loss.py new file mode 100644 index 0000000..86b82a8 --- /dev/null +++ b/ppocr/losses/det_fce_loss.py @@ -0,0 +1,240 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textdet/losses/fce_loss.py +""" + +import numpy as np +from paddle import nn +import paddle +import paddle.nn.functional as F +from functools import partial + + +def multi_apply(func, *args, **kwargs): + pfunc = partial(func, **kwargs) if kwargs else func + map_results = map(pfunc, *args) + return tuple(map(list, zip(*map_results))) + + +class FCELoss(nn.Layer): + """The class for implementing FCENet loss + FCENet(CVPR2021): Fourier Contour Embedding for Arbitrary-shaped + Text Detection + + [https://arxiv.org/abs/2104.10442] + + Args: + fourier_degree (int) : The maximum Fourier transform degree k. + num_sample (int) : The sampling points number of regression + loss. If it is too small, fcenet tends to be overfitting. + ohem_ratio (float): the negative/positive ratio in OHEM. + """ + + def __init__(self, fourier_degree, num_sample, ohem_ratio=3.0): + super().__init__() + self.fourier_degree = fourier_degree + self.num_sample = num_sample + self.ohem_ratio = ohem_ratio + + def forward(self, preds, labels): + assert isinstance(preds, dict) + preds = preds["levels"] + + p3_maps, p4_maps, p5_maps = labels[1:] + assert ( + p3_maps[0].shape[0] == 4 * self.fourier_degree + 5 + ), "fourier degree not equal in FCEhead and FCEtarget" + + # to tensor + gts = [p3_maps, p4_maps, p5_maps] + for idx, maps in enumerate(gts): + gts[idx] = paddle.to_tensor(np.stack(maps)) + + losses = multi_apply(self.forward_single, preds, gts) + + loss_tr = paddle.to_tensor(0.0).astype("float32") + loss_tcl = paddle.to_tensor(0.0).astype("float32") + loss_reg_x = paddle.to_tensor(0.0).astype("float32") + loss_reg_y = paddle.to_tensor(0.0).astype("float32") + loss_all = paddle.to_tensor(0.0).astype("float32") + + for idx, loss in enumerate(losses): + loss_all += sum(loss) + if idx == 0: + loss_tr += sum(loss) + elif idx == 1: + loss_tcl += sum(loss) + elif idx == 2: + loss_reg_x += sum(loss) + else: + loss_reg_y += sum(loss) + + results = dict( + loss=loss_all, + loss_text=loss_tr, + loss_center=loss_tcl, + loss_reg_x=loss_reg_x, + loss_reg_y=loss_reg_y, + ) + return results + + def forward_single(self, pred, gt): + cls_pred = paddle.transpose(pred[0], (0, 2, 3, 1)) + reg_pred = paddle.transpose(pred[1], (0, 2, 3, 1)) + gt = paddle.transpose(gt, (0, 2, 3, 1)) + + k = 2 * self.fourier_degree + 1 + tr_pred = paddle.reshape(cls_pred[:, :, :, :2], (-1, 2)) + tcl_pred = paddle.reshape(cls_pred[:, :, :, 2:], (-1, 2)) + x_pred = paddle.reshape(reg_pred[:, :, :, 0:k], (-1, k)) + y_pred = paddle.reshape(reg_pred[:, :, :, k : 2 * k], (-1, k)) + + tr_mask = gt[:, :, :, :1].reshape([-1]) + tcl_mask = gt[:, :, :, 1:2].reshape([-1]) + train_mask = gt[:, :, :, 2:3].reshape([-1]) + x_map = paddle.reshape(gt[:, :, :, 3 : 3 + k], (-1, k)) + y_map = paddle.reshape(gt[:, :, :, 3 + k :], (-1, k)) + + tr_train_mask = (train_mask * tr_mask).astype("bool") + tr_train_mask2 = paddle.concat( + [tr_train_mask.unsqueeze(1), tr_train_mask.unsqueeze(1)], axis=1 + ) + # tr loss + loss_tr = self.ohem(tr_pred, tr_mask, train_mask) + # tcl loss + loss_tcl = paddle.to_tensor(0.0).astype("float32") + tr_neg_mask = tr_train_mask.logical_not() + tr_neg_mask2 = paddle.concat( + [tr_neg_mask.unsqueeze(1), tr_neg_mask.unsqueeze(1)], axis=1 + ) + if tr_train_mask.sum().item() > 0: + loss_tcl_pos = F.cross_entropy( + tcl_pred.masked_select(tr_train_mask2).reshape([-1, 2]), + tcl_mask.masked_select(tr_train_mask).astype("int64"), + ) + loss_tcl_neg = F.cross_entropy( + tcl_pred.masked_select(tr_neg_mask2).reshape([-1, 2]), + tcl_mask.masked_select(tr_neg_mask).astype("int64"), + ) + loss_tcl = loss_tcl_pos + 0.5 * loss_tcl_neg + + # regression loss + loss_reg_x = paddle.to_tensor(0.0).astype("float32") + loss_reg_y = paddle.to_tensor(0.0).astype("float32") + if tr_train_mask.sum().item() > 0: + weight = ( + tr_mask.masked_select(tr_train_mask.astype("bool")).astype("float32") + + tcl_mask.masked_select(tr_train_mask.astype("bool")).astype("float32") + ) / 2 + weight = weight.reshape([-1, 1]) + + ft_x, ft_y = self.fourier2poly(x_map, y_map) + ft_x_pre, ft_y_pre = self.fourier2poly(x_pred, y_pred) + + dim = ft_x.shape[1] + + tr_train_mask3 = paddle.concat( + [tr_train_mask.unsqueeze(1) for i in range(dim)], axis=1 + ) + + loss_reg_x = paddle.mean( + weight + * F.smooth_l1_loss( + ft_x_pre.masked_select(tr_train_mask3).reshape([-1, dim]), + ft_x.masked_select(tr_train_mask3).reshape([-1, dim]), + reduction="none", + ) + ) + loss_reg_y = paddle.mean( + weight + * F.smooth_l1_loss( + ft_y_pre.masked_select(tr_train_mask3).reshape([-1, dim]), + ft_y.masked_select(tr_train_mask3).reshape([-1, dim]), + reduction="none", + ) + ) + + return loss_tr, loss_tcl, loss_reg_x, loss_reg_y + + def ohem(self, predict, target, train_mask): + pos = (target * train_mask).astype("bool") + neg = ((1 - target) * train_mask).astype("bool") + + pos2 = paddle.concat([pos.unsqueeze(1), pos.unsqueeze(1)], axis=1) + neg2 = paddle.concat([neg.unsqueeze(1), neg.unsqueeze(1)], axis=1) + + n_pos = pos.astype("float32").sum() + + if n_pos.item() > 0: + loss_pos = F.cross_entropy( + predict.masked_select(pos2).reshape([-1, 2]), + target.masked_select(pos).astype("int64"), + reduction="sum", + ) + loss_neg = F.cross_entropy( + predict.masked_select(neg2).reshape([-1, 2]), + target.masked_select(neg).astype("int64"), + reduction="none", + ) + n_neg = min( + int(neg.astype("float32").sum().item()), + int(self.ohem_ratio * n_pos.astype("float32")), + ) + else: + loss_pos = paddle.to_tensor(0.0) + loss_neg = F.cross_entropy( + predict.masked_select(neg2).reshape([-1, 2]), + target.masked_select(neg).astype("int64"), + reduction="none", + ) + n_neg = 100 + if len(loss_neg) > n_neg: + loss_neg, _ = paddle.topk(loss_neg, n_neg) + + return (loss_pos + loss_neg.sum()) / (n_pos + n_neg).astype("float32") + + def fourier2poly(self, real_maps, imag_maps): + """Transform Fourier coefficient maps to polygon maps. + + Args: + real_maps (tensor): A map composed of the real parts of the + Fourier coefficients, whose shape is (-1, 2k+1) + imag_maps (tensor):A map composed of the imag parts of the + Fourier coefficients, whose shape is (-1, 2k+1) + + Returns + x_maps (tensor): A map composed of the x value of the polygon + represented by n sample points (xn, yn), whose shape is (-1, n) + y_maps (tensor): A map composed of the y value of the polygon + represented by n sample points (xn, yn), whose shape is (-1, n) + """ + + k_vect = paddle.arange( + -self.fourier_degree, self.fourier_degree + 1, dtype="float32" + ).reshape([-1, 1]) + i_vect = paddle.arange(0, self.num_sample, dtype="float32").reshape([1, -1]) + + transform_matrix = 2 * np.pi / self.num_sample * paddle.matmul(k_vect, i_vect) + + x1 = paddle.einsum("ak, kn-> an", real_maps, paddle.cos(transform_matrix)) + x2 = paddle.einsum("ak, kn-> an", imag_maps, paddle.sin(transform_matrix)) + y1 = paddle.einsum("ak, kn-> an", real_maps, paddle.sin(transform_matrix)) + y2 = paddle.einsum("ak, kn-> an", imag_maps, paddle.cos(transform_matrix)) + + x_maps = x1 - x2 + y_maps = y1 + y2 + + return x_maps, y_maps diff --git a/ppocr/losses/det_pse_loss.py b/ppocr/losses/det_pse_loss.py new file mode 100644 index 0000000..962339b --- /dev/null +++ b/ppocr/losses/det_pse_loss.py @@ -0,0 +1,158 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/whai362/PSENet/blob/python3/models/head/psenet_head.py +""" + +import paddle +from paddle import nn +from paddle.nn import functional as F +import numpy as np +from ppocr.utils.iou import iou + + +class PSELoss(nn.Layer): + def __init__( + self, + alpha, + ohem_ratio=3, + kernel_sample_mask="pred", + reduction="sum", + eps=1e-6, + **kwargs, + ): + """Implement PSE Loss.""" + super(PSELoss, self).__init__() + assert reduction in ["sum", "mean", "none"] + self.alpha = alpha + self.ohem_ratio = ohem_ratio + self.kernel_sample_mask = kernel_sample_mask + self.reduction = reduction + self.eps = eps + + def forward(self, outputs, labels): + predicts = outputs["maps"] + predicts = F.interpolate(predicts, scale_factor=4) + + texts = predicts[:, 0, :, :] + kernels = predicts[:, 1:, :, :] + gt_texts, gt_kernels, training_masks = labels[1:] + + # text loss + selected_masks = self.ohem_batch(texts, gt_texts, training_masks) + + loss_text = self.dice_loss(texts, gt_texts, selected_masks) + iou_text = iou( + (texts > 0).astype("int64"), gt_texts, training_masks, reduce=False + ) + losses = dict(loss_text=loss_text, iou_text=iou_text) + + # kernel loss + loss_kernels = [] + if self.kernel_sample_mask == "gt": + selected_masks = gt_texts * training_masks + elif self.kernel_sample_mask == "pred": + selected_masks = (F.sigmoid(texts) > 0.5).astype("float32") * training_masks + + for i in range(kernels.shape[1]): + kernel_i = kernels[:, i, :, :] + gt_kernel_i = gt_kernels[:, i, :, :] + loss_kernel_i = self.dice_loss(kernel_i, gt_kernel_i, selected_masks) + loss_kernels.append(loss_kernel_i) + loss_kernels = paddle.mean(paddle.stack(loss_kernels, axis=1), axis=1) + iou_kernel = iou( + (kernels[:, -1, :, :] > 0).astype("int64"), + gt_kernels[:, -1, :, :], + training_masks * gt_texts, + reduce=False, + ) + losses.update(dict(loss_kernels=loss_kernels, iou_kernel=iou_kernel)) + loss = self.alpha * loss_text + (1 - self.alpha) * loss_kernels + losses["loss"] = loss + if self.reduction == "sum": + losses = {x: paddle.sum(v) for x, v in losses.items()} + elif self.reduction == "mean": + losses = {x: paddle.mean(v) for x, v in losses.items()} + return losses + + def dice_loss(self, input, target, mask): + input = F.sigmoid(input) + + input = input.reshape([input.shape[0], -1]) + target = target.reshape([target.shape[0], -1]) + mask = mask.reshape([mask.shape[0], -1]) + + input = input * mask + target = target * mask + + a = paddle.sum(input * target, 1) + b = paddle.sum(input * input, 1) + self.eps + c = paddle.sum(target * target, 1) + self.eps + d = (2 * a) / (b + c) + return 1 - d + + def ohem_single(self, score, gt_text, training_mask, ohem_ratio=3): + pos_num = int(paddle.sum((gt_text > 0.5).astype("float32"))) - int( + paddle.sum( + paddle.logical_and((gt_text > 0.5), (training_mask <= 0.5)).astype( + "float32" + ) + ) + ) + + if pos_num == 0: + selected_mask = training_mask + selected_mask = selected_mask.reshape( + [1, selected_mask.shape[0], selected_mask.shape[1]] + ).astype("float32") + return selected_mask + + neg_num = int(paddle.sum((gt_text <= 0.5).astype("float32"))) + neg_num = int(min(pos_num * ohem_ratio, neg_num)) + + if neg_num == 0: + selected_mask = training_mask + selected_mask = selected_mask.reshape( + [1, selected_mask.shape[0], selected_mask.shape[1]] + ).astype("float32") + return selected_mask + + neg_score = paddle.masked_select(score, gt_text <= 0.5) + neg_score_sorted = paddle.sort(-neg_score) + threshold = -neg_score_sorted[neg_num - 1] + + selected_mask = paddle.logical_and( + paddle.logical_or((score >= threshold), (gt_text > 0.5)), + (training_mask > 0.5), + ) + selected_mask = selected_mask.reshape( + [1, selected_mask.shape[0], selected_mask.shape[1]] + ).astype("float32") + return selected_mask + + def ohem_batch(self, scores, gt_texts, training_masks, ohem_ratio=3): + selected_masks = [] + for i in range(scores.shape[0]): + selected_masks.append( + self.ohem_single( + scores[i, :, :], + gt_texts[i, :, :], + training_masks[i, :, :], + ohem_ratio, + ) + ) + + selected_masks = paddle.concat(selected_masks, 0).astype("float32") + return selected_masks diff --git a/ppocr/losses/det_sast_loss.py b/ppocr/losses/det_sast_loss.py new file mode 100644 index 0000000..160eb06 --- /dev/null +++ b/ppocr/losses/det_sast_loss.py @@ -0,0 +1,133 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +from .det_basic_loss import DiceLoss +import numpy as np + + +class SASTLoss(nn.Layer): + """ """ + + def __init__(self, eps=1e-6, **kwargs): + super(SASTLoss, self).__init__() + self.dice_loss = DiceLoss(eps=eps) + + def forward(self, predicts, labels): + """ + tcl_pos: N x 128 x 3 + tcl_mask: N x 128 x 1 + tcl_label: N x X list or LoDTensor + """ + + f_score = predicts["f_score"] + f_border = predicts["f_border"] + f_tvo = predicts["f_tvo"] + f_tco = predicts["f_tco"] + + l_score, l_border, l_mask, l_tvo, l_tco = labels[1:] + + # score_loss + intersection = paddle.sum(f_score * l_score * l_mask) + union = paddle.sum(f_score * l_mask) + paddle.sum(l_score * l_mask) + score_loss = 1.0 - 2 * intersection / (union + 1e-5) + + # border loss + l_border_split, l_border_norm = paddle.split( + l_border, num_or_sections=[4, 1], axis=1 + ) + f_border_split = f_border + border_ex_shape = l_border_norm.shape * np.array([1, 4, 1, 1]) + l_border_norm_split = paddle.expand(x=l_border_norm, shape=border_ex_shape) + l_border_score = paddle.expand(x=l_score, shape=border_ex_shape) + l_border_mask = paddle.expand(x=l_mask, shape=border_ex_shape) + + border_diff = l_border_split - f_border_split + abs_border_diff = paddle.abs(border_diff) + border_sign = abs_border_diff < 1.0 + border_sign = paddle.cast(border_sign, dtype="float32") + border_sign.stop_gradient = True + border_in_loss = 0.5 * abs_border_diff * abs_border_diff * border_sign + ( + abs_border_diff - 0.5 + ) * (1.0 - border_sign) + border_out_loss = l_border_norm_split * border_in_loss + border_loss = paddle.sum(border_out_loss * l_border_score * l_border_mask) / ( + paddle.sum(l_border_score * l_border_mask) + 1e-5 + ) + + # tvo_loss + l_tvo_split, l_tvo_norm = paddle.split(l_tvo, num_or_sections=[8, 1], axis=1) + f_tvo_split = f_tvo + tvo_ex_shape = l_tvo_norm.shape * np.array([1, 8, 1, 1]) + l_tvo_norm_split = paddle.expand(x=l_tvo_norm, shape=tvo_ex_shape) + l_tvo_score = paddle.expand(x=l_score, shape=tvo_ex_shape) + l_tvo_mask = paddle.expand(x=l_mask, shape=tvo_ex_shape) + # + tvo_geo_diff = l_tvo_split - f_tvo_split + abs_tvo_geo_diff = paddle.abs(tvo_geo_diff) + tvo_sign = abs_tvo_geo_diff < 1.0 + tvo_sign = paddle.cast(tvo_sign, dtype="float32") + tvo_sign.stop_gradient = True + tvo_in_loss = 0.5 * abs_tvo_geo_diff * abs_tvo_geo_diff * tvo_sign + ( + abs_tvo_geo_diff - 0.5 + ) * (1.0 - tvo_sign) + tvo_out_loss = l_tvo_norm_split * tvo_in_loss + tvo_loss = paddle.sum(tvo_out_loss * l_tvo_score * l_tvo_mask) / ( + paddle.sum(l_tvo_score * l_tvo_mask) + 1e-5 + ) + + # tco_loss + l_tco_split, l_tco_norm = paddle.split(l_tco, num_or_sections=[2, 1], axis=1) + f_tco_split = f_tco + tco_ex_shape = l_tco_norm.shape * np.array([1, 2, 1, 1]) + l_tco_norm_split = paddle.expand(x=l_tco_norm, shape=tco_ex_shape) + l_tco_score = paddle.expand(x=l_score, shape=tco_ex_shape) + l_tco_mask = paddle.expand(x=l_mask, shape=tco_ex_shape) + + tco_geo_diff = l_tco_split - f_tco_split + abs_tco_geo_diff = paddle.abs(tco_geo_diff) + tco_sign = abs_tco_geo_diff < 1.0 + tco_sign = paddle.cast(tco_sign, dtype="float32") + tco_sign.stop_gradient = True + tco_in_loss = 0.5 * abs_tco_geo_diff * abs_tco_geo_diff * tco_sign + ( + abs_tco_geo_diff - 0.5 + ) * (1.0 - tco_sign) + tco_out_loss = l_tco_norm_split * tco_in_loss + tco_loss = paddle.sum(tco_out_loss * l_tco_score * l_tco_mask) / ( + paddle.sum(l_tco_score * l_tco_mask) + 1e-5 + ) + + # total loss + tvo_lw, tco_lw = 1.5, 1.5 + score_lw, border_lw = 1.0, 1.0 + total_loss = ( + score_loss * score_lw + + border_loss * border_lw + + tvo_loss * tvo_lw + + tco_loss * tco_lw + ) + + losses = { + "loss": total_loss, + "score_loss": score_loss, + "border_loss": border_loss, + "tvo_loss": tvo_loss, + "tco_loss": tco_loss, + } + return losses diff --git a/ppocr/losses/distillation_loss.py b/ppocr/losses/distillation_loss.py new file mode 100644 index 0000000..5b69e1d --- /dev/null +++ b/ppocr/losses/distillation_loss.py @@ -0,0 +1,1192 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +import numpy as np +import cv2 + +from .rec_ctc_loss import CTCLoss +from .rec_sar_loss import SARLoss +from .rec_ce_loss import CELoss +from .basic_loss import DMLLoss, KLDivLoss, DKDLoss +from .basic_loss import DistanceLoss +from .basic_loss import LossFromOutput +from .det_db_loss import DBLoss +from .det_basic_loss import BalanceLoss, MaskL1Loss, DiceLoss +from .vqa_token_layoutlm_loss import VQASerTokenLayoutLMLoss + + +def _sum_loss(loss_dict): + if "loss" in loss_dict.keys(): + return loss_dict + else: + loss_dict["loss"] = 0.0 + for k, value in loss_dict.items(): + if k == "loss": + continue + else: + loss_dict["loss"] += value + return loss_dict + + +class DistillationDMLLoss(DMLLoss): + """ """ + + def __init__( + self, + model_name_pairs=[], + act=None, + use_log=False, + key=None, + multi_head=False, + dis_head="ctc", + maps_name=None, + name="dml", + ): + super().__init__(act=act, use_log=use_log) + assert isinstance(model_name_pairs, list) + self.key = key + self.multi_head = multi_head + self.dis_head = dis_head + self.model_name_pairs = self._check_model_name_pairs(model_name_pairs) + self.name = name + self.maps_name = self._check_maps_name(maps_name) + + def _check_model_name_pairs(self, model_name_pairs): + if not isinstance(model_name_pairs, list): + return [] + elif isinstance(model_name_pairs[0], list) and isinstance( + model_name_pairs[0][0], str + ): + return model_name_pairs + else: + return [model_name_pairs] + + def _check_maps_name(self, maps_name): + if maps_name is None: + return None + elif isinstance(maps_name, str): + return [maps_name] + elif isinstance(maps_name, list): + return [maps_name] + else: + return None + + def _slice_out(self, outs): + new_outs = {} + for k in self.maps_name: + if k == "thrink_maps": + new_outs[k] = outs[:, 0, :, :] + elif k == "threshold_maps": + new_outs[k] = outs[:, 1, :, :] + elif k == "binary_maps": + new_outs[k] = outs[:, 2, :, :] + else: + continue + return new_outs + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, pair in enumerate(self.model_name_pairs): + out1 = predicts[pair[0]] + out2 = predicts[pair[1]] + if self.key is not None: + out1 = out1[self.key] + out2 = out2[self.key] + if self.maps_name is None: + if self.multi_head: + loss = super().forward(out1[self.dis_head], out2[self.dis_head]) + else: + loss = super().forward(out1, out2) + if isinstance(loss, dict): + for key in loss: + loss_dict["{}_{}_{}_{}".format(key, pair[0], pair[1], idx)] = ( + loss[key] + ) + else: + loss_dict["{}_{}".format(self.name, idx)] = loss + else: + outs1 = self._slice_out(out1) + outs2 = self._slice_out(out2) + for _c, k in enumerate(outs1.keys()): + loss = super().forward(outs1[k], outs2[k]) + if isinstance(loss, dict): + for key in loss: + loss_dict[ + "{}_{}_{}_{}_{}".format( + key, pair[0], pair[1], self.maps_name, idx + ) + ] = loss[key] + else: + loss_dict[ + "{}_{}_{}".format(self.name, self.maps_name[_c], idx) + ] = loss + + loss_dict = _sum_loss(loss_dict) + + return loss_dict + + +class DistillationKLDivLoss(KLDivLoss): + """ """ + + def __init__( + self, + model_name_pairs=[], + key=None, + multi_head=False, + dis_head="ctc", + maps_name=None, + name="kl_div", + ): + super().__init__() + assert isinstance(model_name_pairs, list) + self.key = key + self.multi_head = multi_head + self.dis_head = dis_head + self.model_name_pairs = self._check_model_name_pairs(model_name_pairs) + self.name = name + self.maps_name = self._check_maps_name(maps_name) + + def _check_model_name_pairs(self, model_name_pairs): + if not isinstance(model_name_pairs, list): + return [] + elif isinstance(model_name_pairs[0], list) and isinstance( + model_name_pairs[0][0], str + ): + return model_name_pairs + else: + return [model_name_pairs] + + def _check_maps_name(self, maps_name): + if maps_name is None: + return None + elif isinstance(maps_name, str): + return [maps_name] + elif isinstance(maps_name, list): + return [maps_name] + else: + return None + + def _slice_out(self, outs): + new_outs = {} + for k in self.maps_name: + if k == "thrink_maps": + new_outs[k] = outs[:, 0, :, :] + elif k == "threshold_maps": + new_outs[k] = outs[:, 1, :, :] + elif k == "binary_maps": + new_outs[k] = outs[:, 2, :, :] + else: + continue + return new_outs + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, pair in enumerate(self.model_name_pairs): + out1 = predicts[pair[0]] + out2 = predicts[pair[1]] + if self.key is not None: + out1 = out1[self.key] + out2 = out2[self.key] + if self.maps_name is None: + if self.multi_head: + # for nrtr dml loss + max_len = batch[3].max() + tgt = batch[2][:, 1 : 2 + max_len] + tgt = tgt.reshape([-1]) + non_pad_mask = paddle.not_equal( + tgt, paddle.zeros(tgt.shape, dtype=tgt.dtype) + ) + loss = super().forward( + out1[self.dis_head], out2[self.dis_head], non_pad_mask + ) + else: + loss = super().forward(out1, out2) + if isinstance(loss, dict): + for key in loss: + loss_dict["{}_{}_{}_{}".format(key, pair[0], pair[1], idx)] = ( + loss[key] + ) + else: + loss_dict["{}_{}".format(self.name, idx)] = loss + else: + outs1 = self._slice_out(out1) + outs2 = self._slice_out(out2) + for _c, k in enumerate(outs1.keys()): + loss = super().forward(outs1[k], outs2[k]) + if isinstance(loss, dict): + for key in loss: + loss_dict[ + "{}_{}_{}_{}_{}".format( + key, pair[0], pair[1], self.maps_name, idx + ) + ] = loss[key] + else: + loss_dict[ + "{}_{}_{}".format(self.name, self.maps_name[_c], idx) + ] = loss + + loss_dict = _sum_loss(loss_dict) + + return loss_dict + + +class DistillationDKDLoss(DKDLoss): + """ """ + + def __init__( + self, + model_name_pairs=[], + key=None, + multi_head=False, + dis_head="ctc", + maps_name=None, + name="dkd", + temperature=1.0, + alpha=1.0, + beta=1.0, + ): + super().__init__(temperature, alpha, beta) + assert isinstance(model_name_pairs, list) + self.key = key + self.multi_head = multi_head + self.dis_head = dis_head + self.model_name_pairs = self._check_model_name_pairs(model_name_pairs) + self.name = name + self.maps_name = self._check_maps_name(maps_name) + + def _check_model_name_pairs(self, model_name_pairs): + if not isinstance(model_name_pairs, list): + return [] + elif isinstance(model_name_pairs[0], list) and isinstance( + model_name_pairs[0][0], str + ): + return model_name_pairs + else: + return [model_name_pairs] + + def _check_maps_name(self, maps_name): + if maps_name is None: + return None + elif isinstance(maps_name, str): + return [maps_name] + elif isinstance(maps_name, list): + return [maps_name] + else: + return None + + def _slice_out(self, outs): + new_outs = {} + for k in self.maps_name: + if k == "thrink_maps": + new_outs[k] = outs[:, 0, :, :] + elif k == "threshold_maps": + new_outs[k] = outs[:, 1, :, :] + elif k == "binary_maps": + new_outs[k] = outs[:, 2, :, :] + else: + continue + return new_outs + + def forward(self, predicts, batch): + loss_dict = dict() + + for idx, pair in enumerate(self.model_name_pairs): + out1 = predicts[pair[0]] + out2 = predicts[pair[1]] + if self.key is not None: + out1 = out1[self.key] + out2 = out2[self.key] + if self.maps_name is None: + if self.multi_head: + # for nrtr dml loss + max_len = batch[3].max() + tgt = batch[2][:, 1 : 2 + max_len] # [batch_size, max_len + 1] + + tgt = tgt.reshape([-1]) # batch_size * (max_len + 1) + non_pad_mask = paddle.not_equal( + tgt, paddle.zeros(tgt.shape, dtype=tgt.dtype) + ) # batch_size * (max_len + 1) + + loss = super().forward( + out1[self.dis_head], out2[self.dis_head], tgt, non_pad_mask + ) # [batch_size, max_len + 1, num_char] + else: + loss = super().forward(out1, out2) + if isinstance(loss, dict): + for key in loss: + loss_dict["{}_{}_{}_{}".format(key, pair[0], pair[1], idx)] = ( + loss[key] + ) + else: + loss_dict["{}_{}".format(self.name, idx)] = loss + else: + outs1 = self._slice_out(out1) + outs2 = self._slice_out(out2) + for _c, k in enumerate(outs1.keys()): + loss = super().forward(outs1[k], outs2[k]) + if isinstance(loss, dict): + for key in loss: + loss_dict[ + "{}_{}_{}_{}_{}".format( + key, pair[0], pair[1], self.maps_name, idx + ) + ] = loss[key] + else: + loss_dict[ + "{}_{}_{}".format(self.name, self.maps_name[_c], idx) + ] = loss + + loss_dict = _sum_loss(loss_dict) + + return loss_dict + + +class DistillationNRTRDMLLoss(DistillationDMLLoss): + """ """ + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, pair in enumerate(self.model_name_pairs): + out1 = predicts[pair[0]] + out2 = predicts[pair[1]] + if self.key is not None: + out1 = out1[self.key] + out2 = out2[self.key] + + if self.multi_head: + # for nrtr dml loss + max_len = batch[3].max() + tgt = batch[2][:, 1 : 2 + max_len] + tgt = tgt.reshape([-1]) + non_pad_mask = paddle.not_equal( + tgt, paddle.zeros(tgt.shape, dtype=tgt.dtype) + ) + loss = super().forward( + out1[self.dis_head], out2[self.dis_head], non_pad_mask + ) + else: + loss = super().forward(out1, out2) + if isinstance(loss, dict): + for key in loss: + loss_dict["{}_{}_{}_{}".format(key, pair[0], pair[1], idx)] = loss[ + key + ] + else: + loss_dict["{}_{}".format(self.name, idx)] = loss + + loss_dict = _sum_loss(loss_dict) + + return loss_dict + + +class DistillationKLDivLoss(KLDivLoss): + """ """ + + def __init__( + self, + model_name_pairs=[], + key=None, + multi_head=False, + dis_head="ctc", + maps_name=None, + name="kl_div", + ): + super().__init__() + assert isinstance(model_name_pairs, list) + self.key = key + self.multi_head = multi_head + self.dis_head = dis_head + self.model_name_pairs = self._check_model_name_pairs(model_name_pairs) + self.name = name + self.maps_name = self._check_maps_name(maps_name) + + def _check_model_name_pairs(self, model_name_pairs): + if not isinstance(model_name_pairs, list): + return [] + elif isinstance(model_name_pairs[0], list) and isinstance( + model_name_pairs[0][0], str + ): + return model_name_pairs + else: + return [model_name_pairs] + + def _check_maps_name(self, maps_name): + if maps_name is None: + return None + elif isinstance(maps_name, str): + return [maps_name] + elif isinstance(maps_name, list): + return [maps_name] + else: + return None + + def _slice_out(self, outs): + new_outs = {} + for k in self.maps_name: + if k == "thrink_maps": + new_outs[k] = outs[:, 0, :, :] + elif k == "threshold_maps": + new_outs[k] = outs[:, 1, :, :] + elif k == "binary_maps": + new_outs[k] = outs[:, 2, :, :] + else: + continue + return new_outs + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, pair in enumerate(self.model_name_pairs): + out1 = predicts[pair[0]] + out2 = predicts[pair[1]] + if self.key is not None: + out1 = out1[self.key] + out2 = out2[self.key] + if self.maps_name is None: + if self.multi_head: + # for nrtr dml loss + max_len = batch[3].max() + tgt = batch[2][:, 1 : 2 + max_len] + tgt = tgt.reshape([-1]) + non_pad_mask = paddle.not_equal( + tgt, paddle.zeros(tgt.shape, dtype=tgt.dtype) + ) + loss = super().forward( + out1[self.dis_head], out2[self.dis_head], non_pad_mask + ) + else: + loss = super().forward(out1, out2) + if isinstance(loss, dict): + for key in loss: + loss_dict["{}_{}_{}_{}".format(key, pair[0], pair[1], idx)] = ( + loss[key] + ) + else: + loss_dict["{}_{}".format(self.name, idx)] = loss + else: + outs1 = self._slice_out(out1) + outs2 = self._slice_out(out2) + for _c, k in enumerate(outs1.keys()): + loss = super().forward(outs1[k], outs2[k]) + if isinstance(loss, dict): + for key in loss: + loss_dict[ + "{}_{}_{}_{}_{}".format( + key, pair[0], pair[1], self.maps_name, idx + ) + ] = loss[key] + else: + loss_dict[ + "{}_{}_{}".format(self.name, self.maps_name[_c], idx) + ] = loss + + loss_dict = _sum_loss(loss_dict) + + return loss_dict + + +class DistillationDKDLoss(DKDLoss): + """ """ + + def __init__( + self, + model_name_pairs=[], + key=None, + multi_head=False, + dis_head="ctc", + maps_name=None, + name="dkd", + temperature=1.0, + alpha=1.0, + beta=1.0, + ): + super().__init__(temperature, alpha, beta) + assert isinstance(model_name_pairs, list) + self.key = key + self.multi_head = multi_head + self.dis_head = dis_head + self.model_name_pairs = self._check_model_name_pairs(model_name_pairs) + self.name = name + self.maps_name = self._check_maps_name(maps_name) + + def _check_model_name_pairs(self, model_name_pairs): + if not isinstance(model_name_pairs, list): + return [] + elif isinstance(model_name_pairs[0], list) and isinstance( + model_name_pairs[0][0], str + ): + return model_name_pairs + else: + return [model_name_pairs] + + def _check_maps_name(self, maps_name): + if maps_name is None: + return None + elif isinstance(maps_name, str): + return [maps_name] + elif isinstance(maps_name, list): + return [maps_name] + else: + return None + + def _slice_out(self, outs): + new_outs = {} + for k in self.maps_name: + if k == "thrink_maps": + new_outs[k] = outs[:, 0, :, :] + elif k == "threshold_maps": + new_outs[k] = outs[:, 1, :, :] + elif k == "binary_maps": + new_outs[k] = outs[:, 2, :, :] + else: + continue + return new_outs + + def forward(self, predicts, batch): + loss_dict = dict() + + for idx, pair in enumerate(self.model_name_pairs): + out1 = predicts[pair[0]] + out2 = predicts[pair[1]] + if self.key is not None: + out1 = out1[self.key] + out2 = out2[self.key] + if self.maps_name is None: + if self.multi_head: + # for nrtr dml loss + max_len = batch[3].max() + tgt = batch[2][:, 1 : 2 + max_len] # [batch_size, max_len + 1] + + tgt = tgt.reshape([-1]) # batch_size * (max_len + 1) + non_pad_mask = paddle.not_equal( + tgt, paddle.zeros(tgt.shape, dtype=tgt.dtype) + ) # batch_size * (max_len + 1) + + loss = super().forward( + out1[self.dis_head], out2[self.dis_head], tgt, non_pad_mask + ) # [batch_size, max_len + 1, num_char] + else: + loss = super().forward(out1, out2) + if isinstance(loss, dict): + for key in loss: + loss_dict["{}_{}_{}_{}".format(key, pair[0], pair[1], idx)] = ( + loss[key] + ) + else: + loss_dict["{}_{}".format(self.name, idx)] = loss + else: + outs1 = self._slice_out(out1) + outs2 = self._slice_out(out2) + for _c, k in enumerate(outs1.keys()): + loss = super().forward(outs1[k], outs2[k]) + if isinstance(loss, dict): + for key in loss: + loss_dict[ + "{}_{}_{}_{}_{}".format( + key, pair[0], pair[1], self.maps_name, idx + ) + ] = loss[key] + else: + loss_dict[ + "{}_{}_{}".format(self.name, self.maps_name[_c], idx) + ] = loss + + loss_dict = _sum_loss(loss_dict) + + return loss_dict + + +class DistillationCTCLoss(CTCLoss): + def __init__(self, model_name_list=[], key=None, multi_head=False, name="loss_ctc"): + super().__init__() + self.model_name_list = model_name_list + self.key = key + self.name = name + self.multi_head = multi_head + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, model_name in enumerate(self.model_name_list): + out = predicts[model_name] + if self.key is not None: + out = out[self.key] + if self.multi_head: + assert "ctc" in out, "multi head has multi out" + loss = super().forward(out["ctc"], batch[:2] + batch[3:]) + else: + loss = super().forward(out, batch) + if isinstance(loss, dict): + for key in loss: + loss_dict["{}_{}_{}".format(self.name, model_name, idx)] = loss[key] + else: + loss_dict["{}_{}".format(self.name, model_name)] = loss + return loss_dict + + +class DistillationSARLoss(SARLoss): + def __init__( + self, model_name_list=[], key=None, multi_head=False, name="loss_sar", **kwargs + ): + ignore_index = kwargs.get("ignore_index", 92) + super().__init__(ignore_index=ignore_index) + self.model_name_list = model_name_list + self.key = key + self.name = name + self.multi_head = multi_head + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, model_name in enumerate(self.model_name_list): + out = predicts[model_name] + if self.key is not None: + out = out[self.key] + if self.multi_head: + assert "sar" in out, "multi head has multi out" + loss = super().forward(out["sar"], batch[:1] + batch[2:]) + else: + loss = super().forward(out, batch) + if isinstance(loss, dict): + for key in loss: + loss_dict["{}_{}_{}".format(self.name, model_name, idx)] = loss[key] + else: + loss_dict["{}_{}".format(self.name, model_name)] = loss + return loss_dict + + +class DistillationNRTRLoss(CELoss): + def __init__( + self, + model_name_list=[], + key=None, + multi_head=False, + smoothing=True, + name="loss_nrtr", + **kwargs, + ): + super().__init__(smoothing=smoothing) + self.model_name_list = model_name_list + self.key = key + self.name = name + self.multi_head = multi_head + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, model_name in enumerate(self.model_name_list): + out = predicts[model_name] + if self.key is not None: + out = out[self.key] + if self.multi_head: + assert "gtc" in out, "multi head has multi out" + loss = super().forward(out["gtc"], batch[:1] + batch[2:]) + else: + loss = super().forward(out, batch) + if isinstance(loss, dict): + for key in loss: + loss_dict["{}_{}_{}".format(self.name, model_name, idx)] = loss[key] + else: + loss_dict["{}_{}".format(self.name, model_name)] = loss + return loss_dict + + +class DistillationDBLoss(DBLoss): + def __init__( + self, + model_name_list=[], + balance_loss=True, + main_loss_type="DiceLoss", + alpha=5, + beta=10, + ohem_ratio=3, + eps=1e-6, + name="db", + **kwargs, + ): + super().__init__() + self.model_name_list = model_name_list + self.name = name + self.key = None + + def forward(self, predicts, batch): + loss_dict = {} + for idx, model_name in enumerate(self.model_name_list): + out = predicts[model_name] + if self.key is not None: + out = out[self.key] + loss = super().forward(out, batch) + + if isinstance(loss, dict): + for key in loss.keys(): + if key == "loss": + continue + name = "{}_{}_{}".format(self.name, model_name, key) + loss_dict[name] = loss[key] + else: + loss_dict["{}_{}".format(self.name, model_name)] = loss + + loss_dict = _sum_loss(loss_dict) + return loss_dict + + +class DistillationDilaDBLoss(DBLoss): + def __init__( + self, + model_name_pairs=[], + key=None, + balance_loss=True, + main_loss_type="DiceLoss", + alpha=5, + beta=10, + ohem_ratio=3, + eps=1e-6, + name="dila_dbloss", + ): + super().__init__() + self.model_name_pairs = model_name_pairs + self.name = name + self.key = key + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, pair in enumerate(self.model_name_pairs): + stu_outs = predicts[pair[0]] + tch_outs = predicts[pair[1]] + if self.key is not None: + stu_preds = stu_outs[self.key] + tch_preds = tch_outs[self.key] + + stu_shrink_maps = stu_preds[:, 0, :, :] + stu_binary_maps = stu_preds[:, 2, :, :] + + # dilation to teacher prediction + dilation_w = np.array([[1, 1], [1, 1]]) + th_shrink_maps = tch_preds[:, 0, :, :] + if hasattr(paddle.Tensor, "contiguous"): + th_shrink_maps = th_shrink_maps.contiguous() + th_shrink_maps = th_shrink_maps.numpy() > 0.3 # thresh = 0.3 + dilate_maps = np.zeros_like(th_shrink_maps).astype(np.float32) + for i in range(th_shrink_maps.shape[0]): + dilate_maps[i] = cv2.dilate( + th_shrink_maps[i, :, :].astype(np.uint8), dilation_w + ) + th_shrink_maps = paddle.to_tensor(dilate_maps) + + ( + label_threshold_map, + label_threshold_mask, + label_shrink_map, + label_shrink_mask, + ) = batch[1:] + + # calculate the shrink map loss + bce_loss = self.alpha * self.bce_loss( + stu_shrink_maps, th_shrink_maps, label_shrink_mask + ) + loss_binary_maps = self.dice_loss( + stu_binary_maps, th_shrink_maps, label_shrink_mask + ) + + # k = f"{self.name}_{pair[0]}_{pair[1]}" + k = "{}_{}_{}".format(self.name, pair[0], pair[1]) + loss_dict[k] = bce_loss + loss_binary_maps + + loss_dict = _sum_loss(loss_dict) + return loss_dict + + +class DistillationDistanceLoss(DistanceLoss): + """ """ + + def __init__( + self, mode="l2", model_name_pairs=[], key=None, name="loss_distance", **kargs + ): + super().__init__(mode=mode, **kargs) + assert isinstance(model_name_pairs, list) + self.key = key + self.model_name_pairs = model_name_pairs + self.name = name + "_l2" + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, pair in enumerate(self.model_name_pairs): + out1 = predicts[pair[0]] + out2 = predicts[pair[1]] + if self.key is not None: + out1 = out1[self.key] + out2 = out2[self.key] + loss = super().forward(out1, out2) + if isinstance(loss, dict): + for key in loss: + loss_dict["{}_{}_{}".format(self.name, key, idx)] = loss[key] + else: + loss_dict["{}_{}_{}_{}".format(self.name, pair[0], pair[1], idx)] = loss + return loss_dict + + +class DistillationVQASerTokenLayoutLMLoss(VQASerTokenLayoutLMLoss): + def __init__(self, num_classes, model_name_list=[], key=None, name="loss_ser"): + super().__init__(num_classes=num_classes) + self.model_name_list = model_name_list + self.key = key + self.name = name + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, model_name in enumerate(self.model_name_list): + out = predicts[model_name] + if self.key is not None: + out = out[self.key] + loss = super().forward(out, batch) + loss_dict["{}_{}".format(self.name, model_name)] = loss["loss"] + return loss_dict + + +class DistillationLossFromOutput(LossFromOutput): + def __init__( + self, + reduction="none", + model_name_list=[], + dist_key=None, + key="loss", + name="loss_re", + ): + super().__init__(key=key, reduction=reduction) + self.model_name_list = model_name_list + self.name = name + self.dist_key = dist_key + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, model_name in enumerate(self.model_name_list): + out = predicts[model_name] + if self.dist_key is not None: + out = out[self.dist_key] + loss = super().forward(out, batch) + loss_dict["{}_{}".format(self.name, model_name)] = loss["loss"] + return loss_dict + + +class DistillationSERDMLLoss(DMLLoss): + """ """ + + def __init__( + self, + act="softmax", + use_log=True, + num_classes=7, + model_name_pairs=[], + key=None, + name="loss_dml_ser", + ): + super().__init__(act=act, use_log=use_log) + assert isinstance(model_name_pairs, list) + self.key = key + self.name = name + self.num_classes = num_classes + self.model_name_pairs = model_name_pairs + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, pair in enumerate(self.model_name_pairs): + out1 = predicts[pair[0]] + out2 = predicts[pair[1]] + if self.key is not None: + out1 = out1[self.key] + out2 = out2[self.key] + out1 = out1.reshape([-1, out1.shape[-1]]) + out2 = out2.reshape([-1, out2.shape[-1]]) + + attention_mask = batch[2] + if attention_mask is not None: + active_output = ( + attention_mask.reshape( + [ + -1, + ] + ) + == 1 + ) + out1 = out1[active_output] + out2 = out2[active_output] + + loss_dict["{}_{}".format(self.name, idx)] = super().forward(out1, out2) + + return loss_dict + + +class DistillationVQADistanceLoss(DistanceLoss): + def __init__( + self, + mode="l2", + model_name_pairs=[], + key=None, + index=None, + name="loss_distance", + **kargs, + ): + super().__init__(mode=mode, **kargs) + assert isinstance(model_name_pairs, list) + self.key = key + self.index = index + self.model_name_pairs = model_name_pairs + self.name = name + "_l2" + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, pair in enumerate(self.model_name_pairs): + out1 = predicts[pair[0]] + out2 = predicts[pair[1]] + attention_mask = batch[2] + if self.key is not None: + out1 = out1[self.key] + out2 = out2[self.key] + if self.index is not None: + out1 = out1[:, self.index, :, :] + out2 = out2[:, self.index, :, :] + if attention_mask is not None: + max_len = attention_mask.shape[-1] + out1 = out1[:, :max_len] + out2 = out2[:, :max_len] + out1 = out1.reshape([-1, out1.shape[-1]]) + out2 = out2.reshape([-1, out2.shape[-1]]) + if attention_mask is not None: + active_output = ( + attention_mask.reshape( + [ + -1, + ] + ) + == 1 + ) + out1 = out1[active_output] + out2 = out2[active_output] + + loss = super().forward(out1, out2) + if isinstance(loss, dict): + for key in loss: + loss_dict["{}_{}nohu_{}".format(self.name, key, idx)] = loss[key] + else: + loss_dict["{}_{}_{}_{}".format(self.name, pair[0], pair[1], idx)] = loss + return loss_dict + + +class CTCDKDLoss(nn.Layer): + """ + KLDivLoss + """ + + def __init__(self, temperature=0.5, alpha=1.0, beta=1.0): + super().__init__() + self.temperature = temperature + self.alpha = alpha + self.beta = beta + self.eps = 1e-6 + self.t = temperature + self.act = nn.Softmax(axis=-1) + self.use_log = True + + def kl_loss(self, p1, p2): # predict, label + loss = paddle.multiply( + p2, paddle.log((p2 + self.eps) / (p1 + self.eps) + self.eps) + ) + bs = loss.shape[0] + loss = paddle.sum(loss) / bs + return loss + + def _cat_mask(self, t, mask1, mask2): + t1 = (t * mask1).sum(axis=1, keepdim=True) + t2 = (t * mask2).sum(axis=1, keepdim=True) + rt = paddle.concat([t1, t2], axis=1) + return rt + + def multi_label_mask(self, targets): + targets = targets.astype("int32") + res = F.one_hot(targets, num_classes=11465) + mask = paddle.clip(paddle.sum(res, axis=1), 0, 1) + mask[:, 0] = 0 # ignore ctc blank label + return mask + + def forward(self, logits_student, logits_teacher, targets, mask=None): + gt_mask = self.multi_label_mask(targets) + other_mask = paddle.ones_like(gt_mask) - gt_mask + + pred_student = F.softmax(logits_student / self.temperature, axis=-1) + pred_teacher = F.softmax(logits_teacher / self.temperature, axis=-1) + + # differences with dkd + pred_student = paddle.mean(pred_student, axis=1) + pred_teacher = paddle.mean(pred_teacher, axis=1) + + pred_student = self._cat_mask(pred_student, gt_mask, other_mask) + pred_teacher = self._cat_mask(pred_teacher, gt_mask, other_mask) + + # differences with dkd + tckd_loss = self.kl_loss(pred_student, pred_teacher) + + gt_mask_ex = paddle.expand_as(gt_mask.unsqueeze(axis=1), logits_teacher) + pred_teacher_part2 = F.softmax( + logits_teacher / self.temperature - 1000.0 * gt_mask_ex, axis=-1 + ) + pred_student_part2 = F.softmax( + logits_student / self.temperature - 1000.0 * gt_mask_ex, axis=-1 + ) + # differences with dkd + pred_teacher_part2 = paddle.mean(pred_teacher_part2, axis=1) + pred_student_part2 = paddle.mean(pred_student_part2, axis=1) + + # differences with dkd + nckd_loss = self.kl_loss(pred_student_part2, pred_teacher_part2) + loss = self.alpha * tckd_loss + self.beta * nckd_loss + return loss + + +class KLCTCLogits(nn.Layer): + def __init__(self, weight=1.0, reduction="mean", mode="mean"): + super().__init__() + self.weight = weight + self.reduction = reduction + self.eps = 1e-6 + self.t = 0.5 + self.act = nn.Softmax(axis=-1) + self.use_log = True + self.mode = mode + self.ctc_dkd_loss = CTCDKDLoss() + + def kl_loss(self, p1, p2): # predict, label + loss = paddle.multiply( + p2, paddle.log((p2 + self.eps) / (p1 + self.eps) + self.eps) + ) + bs = loss.shape[0] + loss = paddle.sum(loss) / bs + return loss + + def forward_meanmax(self, stu_out, tea_out): + stu_out = paddle.mean(F.softmax(stu_out / self.t, axis=-1), axis=1) + tea_out = paddle.mean(F.softmax(tea_out / self.t, axis=-1), axis=1) + loss = self.kl_loss(stu_out, tea_out) + + return loss + + def forward_meanlog(self, stu_out, tea_out): + stu_out = paddle.mean(F.softmax(stu_out / self.t, axis=-1), axis=1) + tea_out = paddle.mean(F.softmax(tea_out / self.t, axis=-1), axis=1) + if self.use_log is True: + # for recognition distillation, log is needed for feature map + log_out1 = paddle.log(stu_out) + log_out2 = paddle.log(tea_out) + loss = ( + self._kldiv(log_out1, tea_out) + self._kldiv(log_out2, stu_out) + ) / 2.0 + + return loss + + def forward_sum(self, stu_out, tea_out): + stu_out = paddle.sum(F.softmax(stu_out / self.t, axis=-1), axis=1) + tea_out = paddle.sum(F.softmax(tea_out / self.t, axis=-1), axis=1) + stu_out = paddle.log(stu_out) + bs = stu_out.shape[0] + loss = tea_out * (paddle.log(tea_out + self.eps) - stu_out) + loss = paddle.sum(loss, axis=1) / loss.shape[0] + return loss + + def _kldiv(self, x, target): + eps = 1.0e-10 + loss = target * (paddle.log(target + eps) - x) + loss = paddle.sum(paddle.mean(loss, axis=1)) / loss.shape[0] + return loss + + def forward(self, stu_out, tea_out, targets=None): + if self.mode == "log": + return self.forward_log(stu_out, tea_out) + elif self.mode == "mean": + blank_mask = paddle.ones_like(stu_out) + blank_mask.stop_gradient = True + blank_mask[:, :, 0] = -1 + stu_out *= blank_mask + tea_out *= blank_mask + return self.forward_meanmax(stu_out, tea_out) + elif self.mode == "sum": + return self.forward_sum(stu_out, tea_out) + elif self.mode == "meanlog": + blank_mask = paddle.ones_like(stu_out) + blank_mask.stop_gradient = True + blank_mask[:, :, 0] = -1 + stu_out *= blank_mask + tea_out *= blank_mask + return self.forward_meanlog(stu_out, tea_out) + elif self.mode == "ctcdkd": + # ignore ctc blank logits + blank_mask = paddle.ones_like(stu_out) + blank_mask.stop_gradient = True + blank_mask[:, :, 0] = -1 + stu_out *= blank_mask + tea_out *= blank_mask + return self.ctc_dkd_loss(stu_out, tea_out, targets) + else: + raise ValueError("error!!!!!!") + + def forward_log(self, out1, out2): + if self.act is not None: + out1 = self.act(out1) + 1e-10 + out2 = self.act(out2) + 1e-10 + if self.use_log is True: + # for recognition distillation, log is needed for feature map + log_out1 = paddle.log(out1) + log_out2 = paddle.log(out2) + loss = (self._kldiv(log_out1, out2) + self._kldiv(log_out2, out1)) / 2.0 + + return loss + + +class DistillCTCLogits(KLCTCLogits): + def __init__( + self, model_name_pairs=[], key=None, name="ctc_logits", reduction="mean" + ): + super().__init__(reduction=reduction) + self.model_name_pairs = self._check_model_name_pairs(model_name_pairs) + self.key = key + self.name = name + + def _check_model_name_pairs(self, model_name_pairs): + if not isinstance(model_name_pairs, list): + return [] + elif isinstance(model_name_pairs[0], list) and isinstance( + model_name_pairs[0][0], str + ): + return model_name_pairs + else: + return [model_name_pairs] + + def forward(self, predicts, batch): + loss_dict = dict() + for idx, pair in enumerate(self.model_name_pairs): + out1 = predicts[pair[0]] + out2 = predicts[pair[1]] + + if self.key is not None: + out1 = out1[self.key]["ctc"] + out2 = out2[self.key]["ctc"] + + ctc_label = batch[1] + loss = super().forward(out1, out2, ctc_label) + if isinstance(loss, dict): + for key in loss: + loss_dict[ + "{}_{}_{}".format(self.name, self.model_name_pairs, idx) + ] = loss[key] + else: + loss_dict["{}_{}".format(self.name, idx)] = loss + return loss_dict diff --git a/ppocr/losses/e2e_pg_loss.py b/ppocr/losses/e2e_pg_loss.py new file mode 100644 index 0000000..8b667f0 --- /dev/null +++ b/ppocr/losses/e2e_pg_loss.py @@ -0,0 +1,165 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from paddle import nn +import paddle + +from .det_basic_loss import DiceLoss +from ppocr.utils.e2e_utils.extract_batchsize import pre_process + + +class PGLoss(nn.Layer): + def __init__( + self, tcl_bs, max_text_length, max_text_nums, pad_num, eps=1e-6, **kwargs + ): + super(PGLoss, self).__init__() + self.tcl_bs = tcl_bs + self.max_text_nums = max_text_nums + self.max_text_length = max_text_length + self.pad_num = pad_num + self.dice_loss = DiceLoss(eps=eps) + + def border_loss(self, f_border, l_border, l_score, l_mask): + l_border_split, l_border_norm = paddle.tensor.split( + l_border, num_or_sections=[4, 1], axis=1 + ) + f_border_split = f_border + b, c, h, w = l_border_norm.shape + l_border_norm_split = paddle.expand(x=l_border_norm, shape=[b, 4 * c, h, w]) + b, c, h, w = l_score.shape + l_border_score = paddle.expand(x=l_score, shape=[b, 4 * c, h, w]) + b, c, h, w = l_mask.shape + l_border_mask = paddle.expand(x=l_mask, shape=[b, 4 * c, h, w]) + border_diff = l_border_split - f_border_split + abs_border_diff = paddle.abs(border_diff) + border_sign = abs_border_diff < 1.0 + border_sign = paddle.cast(border_sign, dtype="float32") + border_sign.stop_gradient = True + border_in_loss = 0.5 * abs_border_diff * abs_border_diff * border_sign + ( + abs_border_diff - 0.5 + ) * (1.0 - border_sign) + border_out_loss = l_border_norm_split * border_in_loss + border_loss = paddle.sum(border_out_loss * l_border_score * l_border_mask) / ( + paddle.sum(l_border_score * l_border_mask) + 1e-5 + ) + return border_loss + + def direction_loss(self, f_direction, l_direction, l_score, l_mask): + l_direction_split, l_direction_norm = paddle.tensor.split( + l_direction, num_or_sections=[2, 1], axis=1 + ) + f_direction_split = f_direction + b, c, h, w = l_direction_norm.shape + l_direction_norm_split = paddle.expand( + x=l_direction_norm, shape=[b, 2 * c, h, w] + ) + b, c, h, w = l_score.shape + l_direction_score = paddle.expand(x=l_score, shape=[b, 2 * c, h, w]) + b, c, h, w = l_mask.shape + l_direction_mask = paddle.expand(x=l_mask, shape=[b, 2 * c, h, w]) + direction_diff = l_direction_split - f_direction_split + abs_direction_diff = paddle.abs(direction_diff) + direction_sign = abs_direction_diff < 1.0 + direction_sign = paddle.cast(direction_sign, dtype="float32") + direction_sign.stop_gradient = True + direction_in_loss = ( + 0.5 * abs_direction_diff * abs_direction_diff * direction_sign + + (abs_direction_diff - 0.5) * (1.0 - direction_sign) + ) + direction_out_loss = l_direction_norm_split * direction_in_loss + direction_loss = paddle.sum( + direction_out_loss * l_direction_score * l_direction_mask + ) / (paddle.sum(l_direction_score * l_direction_mask) + 1e-5) + return direction_loss + + def ctcloss(self, f_char, tcl_pos, tcl_mask, tcl_label, label_t): + f_char = paddle.transpose(f_char, [0, 2, 3, 1]) + tcl_pos = paddle.reshape(tcl_pos, [-1, 3]) + tcl_pos = paddle.cast(tcl_pos, dtype=int) + f_tcl_char = paddle.gather_nd(f_char, tcl_pos) + f_tcl_char = paddle.reshape( + f_tcl_char, [-1, 64, self.pad_num + 1] + ) # len(Lexicon_Table)+1 + f_tcl_char_fg, f_tcl_char_bg = paddle.split( + f_tcl_char, [self.pad_num, 1], axis=2 + ) + f_tcl_char_bg = f_tcl_char_bg * tcl_mask + (1.0 - tcl_mask) * 20.0 + b, c, l = tcl_mask.shape + tcl_mask_fg = paddle.expand(x=tcl_mask, shape=[b, c, self.pad_num * l]) + tcl_mask_fg.stop_gradient = True + f_tcl_char_fg = f_tcl_char_fg * tcl_mask_fg + (1.0 - tcl_mask_fg) * (-20.0) + f_tcl_char_mask = paddle.concat([f_tcl_char_fg, f_tcl_char_bg], axis=2) + f_tcl_char_ld = paddle.transpose(f_tcl_char_mask, (1, 0, 2)) + N, B, _ = f_tcl_char_ld.shape + input_lengths = paddle.to_tensor([N] * B, dtype="int64") + cost = paddle.nn.functional.ctc_loss( + log_probs=f_tcl_char_ld, + labels=tcl_label, + input_lengths=input_lengths, + label_lengths=label_t, + blank=self.pad_num, + reduction="none", + ) + cost = cost.mean() + return cost + + def forward(self, predicts, labels): + ( + images, + tcl_maps, + tcl_label_maps, + border_maps, + direction_maps, + training_masks, + label_list, + pos_list, + pos_mask, + ) = labels + # for all the batch_size + pos_list, pos_mask, label_list, label_t = pre_process( + label_list, + pos_list, + pos_mask, + self.max_text_length, + self.max_text_nums, + self.pad_num, + self.tcl_bs, + ) + + f_score, f_border, f_direction, f_char = ( + predicts["f_score"], + predicts["f_border"], + predicts["f_direction"], + predicts["f_char"], + ) + score_loss = self.dice_loss(f_score, tcl_maps, training_masks) + border_loss = self.border_loss(f_border, border_maps, tcl_maps, training_masks) + direction_loss = self.direction_loss( + f_direction, direction_maps, tcl_maps, training_masks + ) + ctc_loss = self.ctcloss(f_char, pos_list, pos_mask, label_list, label_t) + loss_all = score_loss + border_loss + direction_loss + 5 * ctc_loss + + losses = { + "loss": loss_all, + "score_loss": score_loss, + "border_loss": border_loss, + "direction_loss": direction_loss, + "ctc_loss": ctc_loss, + } + return losses diff --git a/ppocr/losses/kie_sdmgr_loss.py b/ppocr/losses/kie_sdmgr_loss.py new file mode 100644 index 0000000..685a141 --- /dev/null +++ b/ppocr/losses/kie_sdmgr_loss.py @@ -0,0 +1,116 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# reference from : https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/kie/losses/sdmgr_loss.py + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from paddle import nn +import paddle + + +class SDMGRLoss(nn.Layer): + def __init__(self, node_weight=1.0, edge_weight=1.0, ignore=0): + super().__init__() + self.loss_node = nn.CrossEntropyLoss(ignore_index=ignore) + self.loss_edge = nn.CrossEntropyLoss(ignore_index=-1) + self.node_weight = node_weight + self.edge_weight = edge_weight + self.ignore = ignore + + def pre_process(self, gts, tag): + gts, tag = gts.numpy(), tag.numpy().tolist() + temp_gts = [] + batch = len(tag) + for i in range(batch): + num, recoder_len = tag[i][0], tag[i][1] + temp_gts.append(paddle.to_tensor(gts[i, :num, : num + 1], dtype="int64")) + return temp_gts + + def accuracy(self, pred, target, topk=1, thresh=None): + """Calculate accuracy according to the prediction and target. + + Args: + pred (torch.Tensor): The model prediction, shape (N, num_class) + target (torch.Tensor): The target of each prediction, shape (N, ) + topk (int | tuple[int], optional): If the predictions in ``topk`` + matches the target, the predictions will be regarded as + correct ones. Defaults to 1. + thresh (float, optional): If not None, predictions with scores under + this threshold are considered incorrect. Default to None. + + Returns: + float | tuple[float]: If the input ``topk`` is a single integer, + the function will return a single float as accuracy. If + ``topk`` is a tuple containing multiple integers, the + function will return a tuple containing accuracies of + each ``topk`` number. + """ + assert isinstance(topk, (int, tuple)) + if isinstance(topk, int): + topk = (topk,) + return_single = True + else: + return_single = False + + maxk = max(topk) + if pred.shape[0] == 0: + accu = [pred.new_tensor(0.0) for i in range(len(topk))] + return accu[0] if return_single else accu + pred_value, pred_label = paddle.topk(pred, maxk, axis=1) + pred_label = pred_label.transpose([1, 0]) # transpose to shape (maxk, N) + correct = paddle.equal( + pred_label, (target.reshape([1, -1]).expand_as(pred_label)) + ) + res = [] + for k in topk: + correct_k = paddle.sum( + correct[:k].reshape([-1]).astype("float32"), axis=0, keepdim=True + ) + res.append( + paddle.multiply(correct_k, paddle.to_tensor(100.0 / pred.shape[0])) + ) + return res[0] if return_single else res + + def forward(self, pred, batch): + node_preds, edge_preds = pred + gts, tag = batch[4], batch[5] + gts = self.pre_process(gts, tag) + node_gts, edge_gts = [], [] + for gt in gts: + node_gts.append(gt[:, 0]) + edge_gts.append(gt[:, 1:].reshape([-1])) + node_gts = paddle.concat(node_gts) + edge_gts = paddle.concat(edge_gts) + + node_valids = paddle.nonzero(node_gts != self.ignore).reshape([-1]) + edge_valids = paddle.nonzero(edge_gts != -1).reshape([-1]) + loss_node = self.loss_node(node_preds, node_gts) + loss_edge = self.loss_edge(edge_preds, edge_gts) + loss = self.node_weight * loss_node + self.edge_weight * loss_edge + return dict( + loss=loss, + loss_node=loss_node, + loss_edge=loss_edge, + acc_node=self.accuracy( + paddle.gather(node_preds, node_valids), + paddle.gather(node_gts, node_valids), + ), + acc_edge=self.accuracy( + paddle.gather(edge_preds, edge_valids), + paddle.gather(edge_gts, edge_valids), + ), + ) diff --git a/ppocr/losses/rec_aster_loss.py b/ppocr/losses/rec_aster_loss.py new file mode 100644 index 0000000..8a8faa1 --- /dev/null +++ b/ppocr/losses/rec_aster_loss.py @@ -0,0 +1,103 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + + +class CosineEmbeddingLoss(nn.Layer): + def __init__(self, margin=0.0): + super(CosineEmbeddingLoss, self).__init__() + self.margin = margin + self.epsilon = 1e-12 + + def forward(self, x1, x2, target): + similarity = paddle.sum(x1 * x2, axis=-1) / ( + paddle.norm(x1, axis=-1) * paddle.norm(x2, axis=-1) + self.epsilon + ) + one_list = paddle.full_like(target, fill_value=1) + out = paddle.mean( + paddle.where( + paddle.equal(target, one_list), + 1.0 - similarity, + paddle.maximum(paddle.zeros_like(similarity), similarity - self.margin), + ) + ) + + return out + + +class AsterLoss(nn.Layer): + def __init__( + self, + weight=None, + size_average=True, + ignore_index=-100, + sequence_normalize=False, + sample_normalize=True, + **kwargs, + ): + super(AsterLoss, self).__init__() + self.weight = weight + self.size_average = size_average + self.ignore_index = ignore_index + self.sequence_normalize = sequence_normalize + self.sample_normalize = sample_normalize + self.loss_sem = CosineEmbeddingLoss() + self.is_cosin_loss = True + self.loss_func_rec = nn.CrossEntropyLoss(weight=None, reduction="none") + + def forward(self, predicts, batch): + targets = batch[1].astype("int64") + label_lengths = batch[2].astype("int64") + sem_target = batch[3].astype("float32") + embedding_vectors = predicts["embedding_vectors"] + rec_pred = predicts["rec_pred"] + + if not self.is_cosin_loss: + sem_loss = paddle.sum(self.loss_sem(embedding_vectors, sem_target)) + else: + label_target = paddle.ones([embedding_vectors.shape[0]]) + sem_loss = paddle.sum( + self.loss_sem(embedding_vectors, sem_target, label_target) + ) + + # rec loss + batch_size, def_max_length = targets.shape[0], targets.shape[1] + + mask = paddle.zeros([batch_size, def_max_length]) + for i in range(batch_size): + mask[i, : label_lengths[i]] = 1 + mask = paddle.cast(mask, "float32") + max_length = max(label_lengths) + assert max_length == rec_pred.shape[1] + targets = targets[:, :max_length] + mask = mask[:, :max_length] + rec_pred = paddle.reshape(rec_pred, [-1, rec_pred.shape[2]]) + input = nn.functional.log_softmax(rec_pred, axis=1) + targets = paddle.reshape(targets, [-1, 1]) + mask = paddle.reshape(mask, [-1, 1]) + output = -paddle.index_sample(input, index=targets) * mask + output = paddle.sum(output) + if self.sequence_normalize: + output = output / paddle.sum(mask) + if self.sample_normalize: + output = output / batch_size + + loss = output + sem_loss * 0.1 + return {"loss": loss} diff --git a/ppocr/losses/rec_att_loss.py b/ppocr/losses/rec_att_loss.py new file mode 100644 index 0000000..e0f65d9 --- /dev/null +++ b/ppocr/losses/rec_att_loss.py @@ -0,0 +1,43 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + + +class AttentionLoss(nn.Layer): + def __init__(self, **kwargs): + super(AttentionLoss, self).__init__() + self.loss_func = nn.CrossEntropyLoss(weight=None, reduction="none") + + def forward(self, predicts, batch): + targets = batch[1].astype("int64") + label_lengths = batch[2].astype("int64") + batch_size, num_steps, num_classes = ( + predicts.shape[0], + predicts.shape[1], + predicts.shape[2], + ) + assert ( + len(targets.shape) == len(list(predicts.shape)) - 1 + ), "The target's shape and inputs's shape is [N, d] and [N, num_steps]" + + inputs = paddle.reshape(predicts, [-1, predicts.shape[-1]]) + targets = paddle.reshape(targets, [-1]) + + return {"loss": paddle.sum(self.loss_func(inputs, targets))} diff --git a/ppocr/losses/rec_can_loss.py b/ppocr/losses/rec_can_loss.py new file mode 100644 index 0000000..1bc58f5 --- /dev/null +++ b/ppocr/losses/rec_can_loss.py @@ -0,0 +1,88 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/LBH1024/CAN/models/can.py +""" + +import paddle +import paddle.nn as nn +import numpy as np + + +class CANLoss(nn.Layer): + """ + CANLoss is consist of two part: + word_average_loss: average accuracy of the symbol + counting_loss: counting loss of every symbol + """ + + def __init__(self): + super(CANLoss, self).__init__() + + self.use_label_mask = False + self.out_channel = 111 + self.cross = ( + nn.CrossEntropyLoss(reduction="none") + if self.use_label_mask + else nn.CrossEntropyLoss() + ) + self.counting_loss = nn.SmoothL1Loss(reduction="mean") + self.ratio = 16 + + def forward(self, preds, batch): + word_probs = preds[0] + counting_preds = preds[1] + counting_preds1 = preds[2] + counting_preds2 = preds[3] + labels = batch[2] + labels_mask = batch[3] + counting_labels = gen_counting_label(labels, self.out_channel, True) + counting_loss = ( + self.counting_loss(counting_preds1, counting_labels) + + self.counting_loss(counting_preds2, counting_labels) + + self.counting_loss(counting_preds, counting_labels) + ) + + word_loss = self.cross( + paddle.reshape(word_probs, [-1, word_probs.shape[-1]]), + paddle.reshape(labels, [-1]), + ) + word_average_loss = ( + paddle.sum(paddle.reshape(word_loss * labels_mask, [-1])) + / (paddle.sum(labels_mask) + 1e-10) + if self.use_label_mask + else word_loss + ) + loss = word_average_loss + counting_loss + return {"loss": loss} + + +def gen_counting_label(labels, channel, tag): + b, t = labels.shape + counting_labels = np.zeros([b, channel]) + + if tag: + ignore = [0, 1, 107, 108, 109, 110] + else: + ignore = [] + for i in range(b): + for j in range(t): + k = labels[i][j] + if k in ignore: + continue + else: + counting_labels[i][k] += 1 + counting_labels = paddle.to_tensor(counting_labels, dtype="float32") + return counting_labels diff --git a/ppocr/losses/rec_ce_loss.py b/ppocr/losses/rec_ce_loss.py new file mode 100644 index 0000000..45906fd --- /dev/null +++ b/ppocr/losses/rec_ce_loss.py @@ -0,0 +1,61 @@ +import paddle +from paddle import nn +import paddle.nn.functional as F + + +class CELoss(nn.Layer): + def __init__(self, smoothing=False, with_all=False, ignore_index=-1, **kwargs): + super(CELoss, self).__init__() + if ignore_index >= 0: + self.loss_func = nn.CrossEntropyLoss( + reduction="mean", ignore_index=ignore_index + ) + else: + self.loss_func = nn.CrossEntropyLoss(reduction="mean") + self.smoothing = smoothing + self.with_all = with_all + + def forward(self, pred, batch): + if isinstance(pred, dict): # for ABINet + loss = {} + loss_sum = [] + for name, logits in pred.items(): + if isinstance(logits, list): + logit_num = len(logits) + all_tgt = paddle.concat([batch[1]] * logit_num, 0) + all_logits = paddle.concat(logits, 0) + flt_logtis = all_logits.reshape([-1, all_logits.shape[2]]) + flt_tgt = all_tgt.reshape([-1]) + else: + flt_logtis = logits.reshape([-1, logits.shape[2]]) + flt_tgt = batch[1].reshape([-1]) + loss[name + "_loss"] = self.loss_func(flt_logtis, flt_tgt) + loss_sum.append(loss[name + "_loss"]) + loss["loss"] = sum(loss_sum) + return loss + else: + if self.with_all: # for ViTSTR + tgt = batch[1] + pred = pred.reshape([-1, pred.shape[2]]) + tgt = tgt.reshape([-1]) + loss = self.loss_func(pred, tgt) + return {"loss": loss} + else: # for NRTR + max_len = batch[2].max() + tgt = batch[1][:, 1 : 2 + max_len] + pred = pred.reshape([-1, pred.shape[2]]) + tgt = tgt.reshape([-1]) + if self.smoothing: + eps = 0.1 + n_class = pred.shape[1] + one_hot = F.one_hot(tgt, pred.shape[1]) + one_hot = one_hot * (1 - eps) + (1 - one_hot) * eps / (n_class - 1) + log_prb = F.log_softmax(pred, axis=1) + non_pad_mask = paddle.not_equal( + tgt, paddle.zeros(tgt.shape, dtype=tgt.dtype) + ) + loss = -(one_hot * log_prb).sum(axis=1) + loss = loss.masked_select(non_pad_mask).mean() + else: + loss = self.loss_func(pred, tgt) + return {"loss": loss} diff --git a/ppocr/losses/rec_cppd_loss.py b/ppocr/losses/rec_cppd_loss.py new file mode 100755 index 0000000..1e884ce --- /dev/null +++ b/ppocr/losses/rec_cppd_loss.py @@ -0,0 +1,76 @@ +# copyright (c) 2023 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle +from paddle import nn +import paddle.nn.functional as F + + +class CPPDLoss(nn.Layer): + def __init__( + self, smoothing=False, ignore_index=100, sideloss_weight=1.0, **kwargs + ): + super(CPPDLoss, self).__init__() + self.edge_ce = nn.CrossEntropyLoss(reduction="mean", ignore_index=ignore_index) + self.char_node_ce = nn.CrossEntropyLoss(reduction="mean") + self.pos_node_ce = nn.BCEWithLogitsLoss(reduction="mean") + self.smoothing = smoothing + self.ignore_index = ignore_index + self.sideloss_weight = sideloss_weight + + def label_smoothing_ce(self, preds, targets): + non_pad_mask = paddle.not_equal( + targets, + paddle.zeros(targets.shape, dtype=targets.dtype) + self.ignore_index, + ) + tgts = paddle.where( + targets + == (paddle.zeros(targets.shape, dtype=targets.dtype) + self.ignore_index), + paddle.zeros(targets.shape, dtype=targets.dtype), + targets, + ) + eps = 0.1 + n_class = preds.shape[1] + one_hot = F.one_hot(tgts, preds.shape[1]) + one_hot = one_hot * (1 - eps) + (1 - one_hot) * eps / (n_class - 1) + log_prb = F.log_softmax(preds, axis=1) + loss = -(one_hot * log_prb).sum(axis=1) + loss = loss.masked_select(non_pad_mask).mean() + return loss + + def forward(self, pred, batch): + node_feats, edge_feats = pred + node_tgt = batch[2] + char_tgt = batch[1] + + loss_char_node = self.char_node_ce( + node_feats[0].flatten(0, 1), node_tgt[:, :-26].flatten(0, 1) + ) + loss_pos_node = self.pos_node_ce( + node_feats[1].flatten(0, 1), node_tgt[:, -26:].flatten(0, 1).cast("float32") + ) + loss_node = loss_char_node + loss_pos_node + + edge_feats = edge_feats.flatten(0, 1) + char_tgt = char_tgt.flatten(0, 1) + if self.smoothing: + loss_edge = self.label_smoothing_ce(edge_feats, char_tgt) + else: + loss_edge = self.edge_ce(edge_feats, char_tgt) + + return { + "loss": self.sideloss_weight * loss_node + loss_edge, + "loss_node": self.sideloss_weight * loss_node, + "loss_edge": loss_edge, + } diff --git a/ppocr/losses/rec_ctc_loss.py b/ppocr/losses/rec_ctc_loss.py new file mode 100755 index 0000000..c701fef --- /dev/null +++ b/ppocr/losses/rec_ctc_loss.py @@ -0,0 +1,46 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + + +class CTCLoss(nn.Layer): + def __init__(self, use_focal_loss=False, **kwargs): + super(CTCLoss, self).__init__() + self.loss_func = nn.CTCLoss(blank=0, reduction="none") + self.use_focal_loss = use_focal_loss + + def forward(self, predicts, batch): + if isinstance(predicts, (list, tuple)): + predicts = predicts[-1] + predicts = predicts.transpose((1, 0, 2)) + N, B, _ = predicts.shape + preds_lengths = paddle.to_tensor( + [N] * B, dtype="int64", place=paddle.CPUPlace() + ) + labels = batch[1].astype("int32") + label_lengths = batch[2].astype("int64") + loss = self.loss_func(predicts, labels, preds_lengths, label_lengths) + if self.use_focal_loss: + weight = paddle.exp(-loss) + weight = paddle.subtract(paddle.to_tensor([1.0]), weight) + weight = paddle.square(weight) + loss = paddle.multiply(loss, weight) + loss = loss.mean() + return {"loss": loss} diff --git a/ppocr/losses/rec_enhanced_ctc_loss.py b/ppocr/losses/rec_enhanced_ctc_loss.py new file mode 100644 index 0000000..e7e0696 --- /dev/null +++ b/ppocr/losses/rec_enhanced_ctc_loss.py @@ -0,0 +1,76 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +from .ace_loss import ACELoss +from .center_loss import CenterLoss +from .rec_ctc_loss import CTCLoss + + +class EnhancedCTCLoss(nn.Layer): + def __init__( + self, + use_focal_loss=False, + use_ace_loss=False, + ace_loss_weight=0.1, + use_center_loss=False, + center_loss_weight=0.05, + num_classes=6625, + feat_dim=96, + init_center=False, + center_file_path=None, + **kwargs, + ): + super(EnhancedCTCLoss, self).__init__() + self.ctc_loss_func = CTCLoss(use_focal_loss=use_focal_loss) + + self.use_ace_loss = False + if use_ace_loss: + self.use_ace_loss = use_ace_loss + self.ace_loss_func = ACELoss() + self.ace_loss_weight = ace_loss_weight + + self.use_center_loss = False + if use_center_loss: + self.use_center_loss = use_center_loss + self.center_loss_func = CenterLoss( + num_classes=num_classes, + feat_dim=feat_dim, + init_center=init_center, + center_file_path=center_file_path, + ) + self.center_loss_weight = center_loss_weight + + def __call__(self, predicts, batch): + loss = self.ctc_loss_func(predicts, batch)["loss"] + + if self.use_center_loss: + center_loss = ( + self.center_loss_func(predicts, batch)["loss_center"] + * self.center_loss_weight + ) + loss = loss + center_loss + + if self.use_ace_loss: + ace_loss = ( + self.ace_loss_func(predicts, batch)["loss_ace"] * self.ace_loss_weight + ) + loss = loss + ace_loss + + return {"enhanced_ctc_loss": loss} diff --git a/ppocr/losses/rec_latexocr_loss.py b/ppocr/losses/rec_latexocr_loss.py new file mode 100644 index 0000000..d209c04 --- /dev/null +++ b/ppocr/losses/rec_latexocr_loss.py @@ -0,0 +1,47 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is refer from: +https://github.com/lucidrains/x-transformers/blob/main/x_transformers/autoregressive_wrapper.py +""" + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +import numpy as np + + +class LaTeXOCRLoss(nn.Layer): + """ + LaTeXOCR adopt CrossEntropyLoss for network training. + """ + + def __init__(self): + super(LaTeXOCRLoss, self).__init__() + self.ignore_index = -100 + self.cross = nn.CrossEntropyLoss( + reduction="mean", ignore_index=self.ignore_index + ) + + def forward(self, preds, batch): + word_probs = preds + labels = batch[1][:, 1:] + word_loss = self.cross( + paddle.reshape(word_probs, [-1, word_probs.shape[-1]]), + paddle.reshape(labels, [-1]), + ) + + loss = word_loss + return {"loss": loss} diff --git a/ppocr/losses/rec_multi_loss.py b/ppocr/losses/rec_multi_loss.py new file mode 100644 index 0000000..74be385 --- /dev/null +++ b/ppocr/losses/rec_multi_loss.py @@ -0,0 +1,68 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + +from .rec_ctc_loss import CTCLoss +from .rec_sar_loss import SARLoss +from .rec_nrtr_loss import NRTRLoss + + +class MultiLoss(nn.Layer): + def __init__(self, **kwargs): + super().__init__() + self.loss_funcs = {} + self.loss_list = kwargs.pop("loss_config_list") + self.weight_1 = kwargs.get("weight_1", 1.0) + self.weight_2 = kwargs.get("weight_2", 1.0) + for loss_info in self.loss_list: + for name, param in loss_info.items(): + if param is not None: + kwargs.update(param) + loss = eval(name)(**kwargs) + self.loss_funcs[name] = loss + + def forward(self, predicts, batch): + self.total_loss = {} + total_loss = 0.0 + # batch [image, label_ctc, label_sar, length, valid_ratio] + for name, loss_func in self.loss_funcs.items(): + if name == "CTCLoss": + loss = ( + loss_func(predicts["ctc"], batch[:2] + batch[3:])["loss"] + * self.weight_1 + ) + elif name == "SARLoss": + loss = ( + loss_func(predicts["sar"], batch[:1] + batch[2:])["loss"] + * self.weight_2 + ) + elif name == "NRTRLoss": + loss = ( + loss_func(predicts["gtc"], batch[:1] + batch[2:])["loss"] + * self.weight_2 + ) + else: + raise NotImplementedError( + "{} is not supported in MultiLoss yet".format(name) + ) + self.total_loss[name] = loss + total_loss += loss + self.total_loss["loss"] = total_loss + return self.total_loss diff --git a/ppocr/losses/rec_nrtr_loss.py b/ppocr/losses/rec_nrtr_loss.py new file mode 100644 index 0000000..a4f56de --- /dev/null +++ b/ppocr/losses/rec_nrtr_loss.py @@ -0,0 +1,33 @@ +import paddle +from paddle import nn +import paddle.nn.functional as F + + +class NRTRLoss(nn.Layer): + def __init__(self, smoothing=True, ignore_index=0, **kwargs): + super(NRTRLoss, self).__init__() + if ignore_index >= 0 and not smoothing: + self.loss_func = nn.CrossEntropyLoss( + reduction="mean", ignore_index=ignore_index + ) + self.smoothing = smoothing + + def forward(self, pred, batch): + max_len = batch[2].max() + tgt = batch[1][:, 1 : 2 + max_len] + pred = pred.reshape([-1, pred.shape[2]]) + tgt = tgt.reshape([-1]) + if self.smoothing: + eps = 0.1 + n_class = pred.shape[1] + one_hot = F.one_hot(tgt, pred.shape[1]) + one_hot = one_hot * (1 - eps) + (1 - one_hot) * eps / (n_class - 1) + log_prb = F.log_softmax(pred, axis=1) + non_pad_mask = paddle.not_equal( + tgt, paddle.zeros(tgt.shape, dtype=tgt.dtype) + ) + loss = -(one_hot * log_prb).sum(axis=1) + loss = loss.masked_select(non_pad_mask).mean() + else: + loss = self.loss_func(pred, tgt) + return {"loss": loss} diff --git a/ppocr/losses/rec_parseq_loss.py b/ppocr/losses/rec_parseq_loss.py new file mode 100644 index 0000000..a1731e8 --- /dev/null +++ b/ppocr/losses/rec_parseq_loss.py @@ -0,0 +1,52 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + + +class ParseQLoss(nn.Layer): + def __init__(self, **kwargs): + super(ParseQLoss, self).__init__() + + def forward(self, predicts, targets): + label = targets[1] # label + label_len = targets[2] + max_step = paddle.max(label_len).cpu().numpy()[0] + 2 + tgt = label[:, :max_step] + + logits_list = predicts["logits_list"] + pad_id = predicts["pad_id"] + eos_id = predicts["eos_id"] + + tgt_out = tgt[:, 1:] + loss = 0 + loss_numel = 0 + n = (tgt_out != pad_id).sum().item() + + for i, logits in enumerate(logits_list): + loss += n * paddle.nn.functional.cross_entropy( + input=logits, label=tgt_out.flatten(), ignore_index=pad_id + ) + loss_numel += n + if i == 1: + tgt_out = paddle.where(condition=tgt_out == eos_id, x=pad_id, y=tgt_out) + n = (tgt_out != pad_id).sum().item() + loss /= loss_numel + + return {"loss": loss} diff --git a/ppocr/losses/rec_ppformulanet_loss.py b/ppocr/losses/rec_ppformulanet_loss.py new file mode 100644 index 0000000..95af575 --- /dev/null +++ b/ppocr/losses/rec_ppformulanet_loss.py @@ -0,0 +1,74 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import paddle +import paddle.nn as nn + + +class PPFormulaNet_S_Loss(nn.Layer): + """ + PP=FormulaNet-S adopt CrossEntropyLoss for network training. + """ + + def __init__(self, vocab_size=50000, parallel_step=1): + super(PPFormulaNet_S_Loss, self).__init__() + self.ignore_index = -100 + self.vocab_size = vocab_size + self.parallel_step = int(parallel_step) + self.pad_token_id = 1 + # ignore padding characters during training + self.cross = nn.CrossEntropyLoss( + reduction="mean", ignore_index=self.ignore_index + ) + + def forward(self, preds, batch): + logits, masked_label = preds + + word_loss = self.cross( + paddle.reshape(logits, [-1, logits.shape[-1]]), + paddle.reshape(masked_label[:, self.parallel_step :], [-1]), + ) + loss = word_loss + return { + "loss": loss, + "word_loss": word_loss, + } + + +class PPFormulaNet_L_Loss(nn.Layer): + """ + PPFormulaNet_L adopt CrossEntropyLoss for network training. + """ + + def __init__(self, vocab_size=50000): + super(PPFormulaNet_L_Loss, self).__init__() + self.ignore_index = -100 + self.vocab_size = vocab_size + self.pad_token_id = 1 + # ignore padding characters during training + self.cross = nn.CrossEntropyLoss( + reduction="mean", ignore_index=self.ignore_index + ) + + def forward(self, preds, batch): + logits, masked_label = preds + + word_loss = self.cross( + paddle.reshape(logits, [-1, logits.shape[-1]]), + paddle.reshape(masked_label[:, 1:], [-1]), + ) + loss = word_loss + return { + "loss": loss, + "word_loss": word_loss, + } diff --git a/ppocr/losses/rec_pren_loss.py b/ppocr/losses/rec_pren_loss.py new file mode 100644 index 0000000..d08b27a --- /dev/null +++ b/ppocr/losses/rec_pren_loss.py @@ -0,0 +1,30 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from paddle import nn + + +class PRENLoss(nn.Layer): + def __init__(self, **kwargs): + super(PRENLoss, self).__init__() + # note: 0 is padding idx + self.loss_func = nn.CrossEntropyLoss(reduction="mean", ignore_index=0) + + def forward(self, predicts, batch): + loss = self.loss_func(predicts, batch[1].astype("int64")) + return {"loss": loss} diff --git a/ppocr/losses/rec_rfl_loss.py b/ppocr/losses/rec_rfl_loss.py new file mode 100644 index 0000000..b2b1856 --- /dev/null +++ b/ppocr/losses/rec_rfl_loss.py @@ -0,0 +1,70 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/hikopensource/DAVAR-Lab-OCR/blob/main/davarocr/davar_common/models/loss/cross_entropy_loss.py +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + +from .basic_loss import CELoss, DistanceLoss + + +class RFLLoss(nn.Layer): + def __init__(self, ignore_index=-100, **kwargs): + super().__init__() + + self.cnt_loss = nn.MSELoss(**kwargs) + self.seq_loss = nn.CrossEntropyLoss(ignore_index=ignore_index) + + def forward(self, predicts, batch): + self.total_loss = {} + total_loss = 0.0 + if isinstance(predicts, tuple) or isinstance(predicts, list): + cnt_outputs, seq_outputs = predicts + else: + cnt_outputs, seq_outputs = predicts, None + # batch [image, label, length, cnt_label] + if cnt_outputs is not None: + cnt_loss = self.cnt_loss(cnt_outputs, paddle.cast(batch[3], paddle.float32)) + self.total_loss["cnt_loss"] = cnt_loss + total_loss += cnt_loss + + if seq_outputs is not None: + targets = batch[1].astype("int64") + label_lengths = batch[2].astype("int64") + batch_size, num_steps, num_classes = ( + seq_outputs.shape[0], + seq_outputs.shape[1], + seq_outputs.shape[2], + ) + assert ( + len(targets.shape) == len(list(seq_outputs.shape)) - 1 + ), "The target's shape and inputs's shape is [N, d] and [N, num_steps]" + + inputs = seq_outputs[:, :-1, :] + targets = targets[:, 1:] + + inputs = paddle.reshape(inputs, [-1, inputs.shape[-1]]) + targets = paddle.reshape(targets, [-1]) + seq_loss = self.seq_loss(inputs, targets) + self.total_loss["seq_loss"] = seq_loss + total_loss += seq_loss + + self.total_loss["loss"] = total_loss + return self.total_loss diff --git a/ppocr/losses/rec_sar_loss.py b/ppocr/losses/rec_sar_loss.py new file mode 100644 index 0000000..a2cba69 --- /dev/null +++ b/ppocr/losses/rec_sar_loss.py @@ -0,0 +1,36 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + + +class SARLoss(nn.Layer): + def __init__(self, **kwargs): + super(SARLoss, self).__init__() + ignore_index = kwargs.get("ignore_index", 92) # 6626 + self.loss_func = paddle.nn.loss.CrossEntropyLoss( + reduction="mean", ignore_index=ignore_index + ) + + def forward(self, predicts, batch): + predict = predicts[ + :, :-1, : + ] # ignore last index of outputs to be in same seq_len with targets + label = batch[1].astype("int64")[ + :, 1: + ] # ignore first index of target in loss calculation + batch_size, num_steps, num_classes = ( + predict.shape[0], + predict.shape[1], + predict.shape[2], + ) + assert ( + len(label.shape) == len(list(predict.shape)) - 1 + ), "The target's shape and inputs's shape is [N, d] and [N, num_steps]" + + inputs = paddle.reshape(predict, [-1, num_classes]) + targets = paddle.reshape(label, [-1]) + loss = self.loss_func(inputs, targets) + return {"loss": loss} diff --git a/ppocr/losses/rec_satrn_loss.py b/ppocr/losses/rec_satrn_loss.py new file mode 100644 index 0000000..16f1b6a --- /dev/null +++ b/ppocr/losses/rec_satrn_loss.py @@ -0,0 +1,53 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/1.x/mmocr/models/textrecog/module_losses/ce_module_loss.py +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + + +class SATRNLoss(nn.Layer): + def __init__(self, **kwargs): + super(SATRNLoss, self).__init__() + ignore_index = kwargs.get("ignore_index", 92) # 6626 + self.loss_func = paddle.nn.loss.CrossEntropyLoss( + reduction="none", ignore_index=ignore_index + ) + + def forward(self, predicts, batch): + predict = predicts[ + :, :-1, : + ] # ignore last index of outputs to be in same seq_len with targets + label = batch[1].astype("int64")[ + :, 1: + ] # ignore first index of target in loss calculation + batch_size, num_steps, num_classes = ( + predict.shape[0], + predict.shape[1], + predict.shape[2], + ) + assert ( + len(label.shape) == len(list(predict.shape)) - 1 + ), "The target's shape and inputs's shape is [N, d] and [N, num_steps]" + + inputs = paddle.reshape(predict, [-1, num_classes]) + targets = paddle.reshape(label, [-1]) + loss = self.loss_func(inputs, targets) + return {"loss": loss.mean()} diff --git a/ppocr/losses/rec_spin_att_loss.py b/ppocr/losses/rec_spin_att_loss.py new file mode 100644 index 0000000..591ae08 --- /dev/null +++ b/ppocr/losses/rec_spin_att_loss.py @@ -0,0 +1,52 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + +"""This code is refer from: +https://github.com/hikopensource/DAVAR-Lab-OCR +""" + + +class SPINAttentionLoss(nn.Layer): + def __init__(self, reduction="mean", ignore_index=-100, **kwargs): + super(SPINAttentionLoss, self).__init__() + self.loss_func = nn.CrossEntropyLoss( + weight=None, reduction=reduction, ignore_index=ignore_index + ) + + def forward(self, predicts, batch): + targets = batch[1].astype("int64") + targets = targets[:, 1:] # remove [eos] in label + + label_lengths = batch[2].astype("int64") + batch_size, num_steps, num_classes = ( + predicts.shape[0], + predicts.shape[1], + predicts.shape[2], + ) + assert ( + len(targets.shape) == len(list(predicts.shape)) - 1 + ), "The target's shape and inputs's shape is [N, d] and [N, num_steps]" + + inputs = paddle.reshape(predicts, [-1, predicts.shape[-1]]) + targets = paddle.reshape(targets, [-1]) + + return {"loss": self.loss_func(inputs, targets)} diff --git a/ppocr/losses/rec_srn_loss.py b/ppocr/losses/rec_srn_loss.py new file mode 100644 index 0000000..cb034f3 --- /dev/null +++ b/ppocr/losses/rec_srn_loss.py @@ -0,0 +1,47 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + + +class SRNLoss(nn.Layer): + def __init__(self, **kwargs): + super(SRNLoss, self).__init__() + self.loss_func = paddle.nn.loss.CrossEntropyLoss(reduction="sum") + + def forward(self, predicts, batch): + predict = predicts["predict"] + word_predict = predicts["word_out"] + gsrm_predict = predicts["gsrm_out"] + label = batch[1] + + casted_label = paddle.cast(x=label, dtype="int64") + casted_label = paddle.reshape(x=casted_label, shape=[-1, 1]) + + cost_word = self.loss_func(word_predict, label=casted_label) + cost_gsrm = self.loss_func(gsrm_predict, label=casted_label) + cost_vsfd = self.loss_func(predict, label=casted_label) + + cost_word = paddle.reshape(x=paddle.sum(cost_word), shape=[1]) + cost_gsrm = paddle.reshape(x=paddle.sum(cost_gsrm), shape=[1]) + cost_vsfd = paddle.reshape(x=paddle.sum(cost_vsfd), shape=[1]) + + sum_cost = cost_word * 3.0 + cost_vsfd + cost_gsrm * 0.15 + + return {"loss": sum_cost, "word_loss": cost_word, "img_loss": cost_vsfd} diff --git a/ppocr/losses/rec_unimernet_loss.py b/ppocr/losses/rec_unimernet_loss.py new file mode 100644 index 0000000..2611cf8 --- /dev/null +++ b/ppocr/losses/rec_unimernet_loss.py @@ -0,0 +1,54 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +import numpy as np + + +class UniMERNetLoss(nn.Layer): + def __init__(self, length_aware=True, vocab_size=50000): + super(UniMERNetLoss, self).__init__() + self.ignore_index = -100 + self.vocab_size = vocab_size + self.pad_token_id = 1 + self.length_aware = length_aware + self.cross = nn.CrossEntropyLoss( + reduction="mean", ignore_index=self.ignore_index + ) + self.counting_loss_fct = nn.SmoothL1Loss() + + def _get_count_gt(self, labels): + mask = (labels != self.pad_token_id).cast("float32") + one_hot_labels = F.one_hot( + labels, num_classes=self.vocab_size + ) * mask.unsqueeze(-1) + count_gt = paddle.sum(one_hot_labels, axis=1) + return count_gt + + def forward(self, preds, batch): + logits, count_pred, masked_label = preds + labels = batch[1][:, 1:] + word_loss = self.cross( + paddle.reshape(logits, [-1, logits.shape[-1]]), + paddle.reshape(masked_label[:, 1:], [-1]), + ) + loss = word_loss + if self.length_aware: + count_gt = self._get_count_gt(labels) + count_gt = paddle.log(count_gt.cast(paddle.float32) + 1) + count_loss = self.counting_loss_fct(count_pred, count_gt) + loss += 0.5 * count_loss + return {"loss": loss, "word_loss": word_loss, "count_loss": count_loss} diff --git a/ppocr/losses/rec_vl_loss.py b/ppocr/losses/rec_vl_loss.py new file mode 100644 index 0000000..6c41177 --- /dev/null +++ b/ppocr/losses/rec_vl_loss.py @@ -0,0 +1,70 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/wangyuxin87/VisionLAN +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn + + +class VLLoss(nn.Layer): + def __init__(self, mode="LF_1", weight_res=0.5, weight_mas=0.5, **kwargs): + super(VLLoss, self).__init__() + self.loss_func = paddle.nn.loss.CrossEntropyLoss(reduction="mean") + assert mode in ["LF_1", "LF_2", "LA"] + self.mode = mode + self.weight_res = weight_res + self.weight_mas = weight_mas + + def flatten_label(self, target): + label_flatten = [] + label_length = [] + for i in range(0, target.shape[0]): + cur_label = target[i].tolist() + label_flatten += cur_label[: cur_label.index(0) + 1] + label_length.append(cur_label.index(0) + 1) + label_flatten = paddle.to_tensor(label_flatten, dtype="int64") + label_length = paddle.to_tensor(label_length, dtype="int32") + return (label_flatten, label_length) + + def _flatten(self, sources, lengths): + return paddle.concat([t[:l] for t, l in zip(sources, lengths)]) + + def forward(self, predicts, batch): + text_pre = predicts[0] + target = batch[1].astype("int64") + label_flatten, length = self.flatten_label(target) + text_pre = self._flatten(text_pre, length) + if self.mode == "LF_1": + loss = self.loss_func(text_pre, label_flatten) + else: + text_rem = predicts[1] + text_mas = predicts[2] + target_res = batch[2].astype("int64") + target_sub = batch[3].astype("int64") + label_flatten_res, length_res = self.flatten_label(target_res) + label_flatten_sub, length_sub = self.flatten_label(target_sub) + text_rem = self._flatten(text_rem, length_res) + text_mas = self._flatten(text_mas, length_sub) + loss_ori = self.loss_func(text_pre, label_flatten) + loss_res = self.loss_func(text_rem, label_flatten_res) + loss_mas = self.loss_func(text_mas, label_flatten_sub) + loss = loss_ori + loss_res * self.weight_res + loss_mas * self.weight_mas + return {"loss": loss} diff --git a/ppocr/losses/stroke_focus_loss.py b/ppocr/losses/stroke_focus_loss.py new file mode 100644 index 0000000..9b7850c --- /dev/null +++ b/ppocr/losses/stroke_focus_loss.py @@ -0,0 +1,63 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/FudanVI/FudanOCR/blob/main/text-gestalt/loss/stroke_focus_loss.py +""" +import cv2 +import sys +import time +import string +import random +import numpy as np +import paddle.nn as nn +import paddle + + +class StrokeFocusLoss(nn.Layer): + def __init__(self, character_dict_path=None, **kwargs): + super(StrokeFocusLoss, self).__init__(character_dict_path) + self.mse_loss = nn.MSELoss() + self.ce_loss = nn.CrossEntropyLoss() + self.l1_loss = nn.L1Loss() + self.english_stroke_alphabet = "0123456789" + self.english_stroke_dict = {} + for index in range(len(self.english_stroke_alphabet)): + self.english_stroke_dict[self.english_stroke_alphabet[index]] = index + + stroke_decompose_lines = open(character_dict_path, "r").readlines() + self.dic = {} + for line in stroke_decompose_lines: + line = line.strip() + character, sequence = line.split() + self.dic[character] = sequence + + def forward(self, pred, data): + sr_img = pred["sr_img"] + hr_img = pred["hr_img"] + + mse_loss = self.mse_loss(sr_img, hr_img) + word_attention_map_gt = pred["word_attention_map_gt"] + word_attention_map_pred = pred["word_attention_map_pred"] + + hr_pred = pred["hr_pred"] + sr_pred = pred["sr_pred"] + + attention_loss = paddle.nn.functional.l1_loss( + word_attention_map_gt, word_attention_map_pred + ) + + loss = (mse_loss + attention_loss * 50) * 100 + + return {"mse_loss": mse_loss, "attention_loss": attention_loss, "loss": loss} diff --git a/ppocr/losses/table_att_loss.py b/ppocr/losses/table_att_loss.py new file mode 100644 index 0000000..72b737d --- /dev/null +++ b/ppocr/losses/table_att_loss.py @@ -0,0 +1,100 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +from paddle.nn import functional as F + + +class TableAttentionLoss(nn.Layer): + def __init__(self, structure_weight=1.0, loc_weight=0.0, **kwargs): + super(TableAttentionLoss, self).__init__() + self.loss_func = nn.CrossEntropyLoss(weight=None, reduction="none") + self.structure_weight = structure_weight + self.loc_weight = loc_weight + + def forward(self, predicts, batch): + structure_probs = predicts["structure_probs"] + structure_targets = batch[1].astype("int64") + structure_targets = structure_targets[:, 1:] + structure_probs = paddle.reshape( + structure_probs, [-1, structure_probs.shape[-1]] + ) + structure_targets = paddle.reshape(structure_targets, [-1]) + structure_loss = self.loss_func(structure_probs, structure_targets) + + structure_loss = paddle.mean(structure_loss) * self.structure_weight + + loc_preds = predicts["loc_preds"] + loc_targets = batch[2].astype("float32") + loc_targets_mask = batch[3].astype("float32") + loc_targets = loc_targets[:, 1:, :] + loc_targets_mask = loc_targets_mask[:, 1:, :] + loc_loss = ( + F.mse_loss(loc_preds * loc_targets_mask, loc_targets) * self.loc_weight + ) + + total_loss = structure_loss + loc_loss + return { + "loss": total_loss, + "structure_loss": structure_loss, + "loc_loss": loc_loss, + } + + +class SLALoss(nn.Layer): + def __init__(self, structure_weight=1.0, loc_weight=0.0, loc_loss="mse", **kwargs): + super(SLALoss, self).__init__() + self.loss_func = nn.CrossEntropyLoss(weight=None, reduction="mean") + self.structure_weight = structure_weight + self.loc_weight = loc_weight + self.loc_loss = loc_loss + self.eps = 1e-12 + + def forward(self, predicts, batch): + structure_probs = predicts["structure_probs"] + structure_targets = batch[1].astype("int64") + max_len = batch[-2].max().astype("int32") + structure_targets = structure_targets[:, 1 : max_len + 2] + + structure_loss = self.loss_func(structure_probs, structure_targets) + + structure_loss = paddle.mean(structure_loss) * self.structure_weight + + loc_preds = predicts["loc_preds"] + loc_targets = batch[2].astype("float32") + loc_targets_mask = batch[3].astype("float32") + loc_targets = loc_targets[:, 1 : max_len + 2] + loc_targets_mask = loc_targets_mask[:, 1 : max_len + 2] + + loc_loss = ( + F.smooth_l1_loss( + loc_preds * loc_targets_mask, + loc_targets * loc_targets_mask, + reduction="sum", + ) + * self.loc_weight + ) + + loc_loss = loc_loss / (loc_targets_mask.sum() + self.eps) + total_loss = structure_loss + loc_loss + return { + "loss": total_loss, + "structure_loss": structure_loss, + "loc_loss": loc_loss, + } diff --git a/ppocr/losses/table_master_loss.py b/ppocr/losses/table_master_loss.py new file mode 100644 index 0000000..08b1c29 --- /dev/null +++ b/ppocr/losses/table_master_loss.py @@ -0,0 +1,74 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/JiaquanYe/TableMASTER-mmocr/tree/master/mmocr/models/textrecog/losses +""" + +import paddle +from paddle import nn + + +class TableMasterLoss(nn.Layer): + def __init__(self, ignore_index=-1): + super(TableMasterLoss, self).__init__() + self.structure_loss = nn.CrossEntropyLoss( + ignore_index=ignore_index, reduction="mean" + ) + self.box_loss = nn.L1Loss(reduction="sum") + self.eps = 1e-12 + + def forward(self, predicts, batch): + # structure_loss + structure_probs = predicts["structure_probs"] + structure_targets = batch[1] + structure_targets = structure_targets[:, 1:] + structure_probs = structure_probs.reshape([-1, structure_probs.shape[-1]]) + structure_targets = structure_targets.reshape([-1]) + + structure_loss = self.structure_loss(structure_probs, structure_targets) + structure_loss = structure_loss.mean() + losses = dict(structure_loss=structure_loss) + + # box loss + bboxes_preds = predicts["loc_preds"] + bboxes_targets = batch[2][:, 1:, :] + bbox_masks = batch[3][:, 1:] + # mask empty-bbox or non-bbox structure token's bbox. + + masked_bboxes_preds = bboxes_preds * bbox_masks + masked_bboxes_targets = bboxes_targets * bbox_masks + + # horizon loss (x and width) + horizon_sum_loss = self.box_loss( + masked_bboxes_preds[:, :, 0::2], masked_bboxes_targets[:, :, 0::2] + ) + horizon_loss = horizon_sum_loss / (bbox_masks.sum() + self.eps) + # vertical loss (y and height) + vertical_sum_loss = self.box_loss( + masked_bboxes_preds[:, :, 1::2], masked_bboxes_targets[:, :, 1::2] + ) + vertical_loss = vertical_sum_loss / (bbox_masks.sum() + self.eps) + + horizon_loss = horizon_loss.mean() + vertical_loss = vertical_loss.mean() + all_loss = structure_loss + horizon_loss + vertical_loss + losses.update( + { + "loss": all_loss, + "horizon_bbox_loss": horizon_loss, + "vertical_bbox_loss": vertical_loss, + } + ) + return losses diff --git a/ppocr/losses/text_focus_loss.py b/ppocr/losses/text_focus_loss.py new file mode 100644 index 0000000..310140b --- /dev/null +++ b/ppocr/losses/text_focus_loss.py @@ -0,0 +1,91 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/FudanVI/FudanOCR/blob/main/scene-text-telescope/loss/text_focus_loss.py +""" + +import paddle.nn as nn +import paddle +import numpy as np +import pickle as pkl + +standard_alphebet = "-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +standard_dict = {} +for index in range(len(standard_alphebet)): + standard_dict[standard_alphebet[index]] = index + + +def load_confuse_matrix(confuse_dict_path): + f = open(confuse_dict_path, "rb") + data = pkl.load(f) + f.close() + number = data[:10] + upper = data[10:36] + lower = data[36:] + end = np.ones((1, 62)) + pad = np.ones((63, 1)) + rearrange_data = np.concatenate((end, number, lower, upper), axis=0) + rearrange_data = np.concatenate((pad, rearrange_data), axis=1) + rearrange_data = 1 / rearrange_data + rearrange_data[rearrange_data == np.inf] = 1 + rearrange_data = paddle.to_tensor(rearrange_data) + + lower_alpha = "abcdefghijklmnopqrstuvwxyz" + # upper_alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + for i in range(63): + for j in range(63): + if i != j and standard_alphebet[j] in lower_alpha: + rearrange_data[i][j] = max( + rearrange_data[i][j], rearrange_data[i][j + 26] + ) + rearrange_data = rearrange_data[:37, :37] + + return rearrange_data + + +def weight_cross_entropy(pred, gt, weight_table): + batch = gt.shape[0] + weight = weight_table[gt] + pred_exp = paddle.exp(pred) + pred_exp_weight = weight * pred_exp + loss = 0 + for i in range(len(gt)): + loss -= paddle.log( + pred_exp_weight[i][gt[i]] / paddle.sum(pred_exp_weight, 1)[i] + ) + return loss / batch + + +class TelescopeLoss(nn.Layer): + def __init__(self, confuse_dict_path): + super(TelescopeLoss, self).__init__() + self.weight_table = load_confuse_matrix(confuse_dict_path) + self.mse_loss = nn.MSELoss() + self.ce_loss = nn.CrossEntropyLoss() + self.l1_loss = nn.L1Loss() + + def forward(self, pred, data): + sr_img = pred["sr_img"] + hr_img = pred["hr_img"] + sr_pred = pred["sr_pred"] + text_gt = pred["text_gt"] + + word_attention_map_gt = pred["word_attention_map_gt"] + word_attention_map_pred = pred["word_attention_map_pred"] + mse_loss = self.mse_loss(sr_img, hr_img) + attention_loss = self.l1_loss(word_attention_map_gt, word_attention_map_pred) + recognition_loss = weight_cross_entropy(sr_pred, text_gt, self.weight_table) + loss = mse_loss + attention_loss * 10 + recognition_loss * 0.0005 + return {"mse_loss": mse_loss, "attention_loss": attention_loss, "loss": loss} diff --git a/ppocr/losses/vqa_token_layoutlm_loss.py b/ppocr/losses/vqa_token_layoutlm_loss.py new file mode 100755 index 0000000..d01c091 --- /dev/null +++ b/ppocr/losses/vqa_token_layoutlm_loss.py @@ -0,0 +1,61 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from paddle import nn +from ppocr.losses.basic_loss import DMLLoss + + +class VQASerTokenLayoutLMLoss(nn.Layer): + def __init__(self, num_classes, key=None): + super().__init__() + self.loss_class = nn.CrossEntropyLoss() + self.num_classes = num_classes + self.ignore_index = self.loss_class.ignore_index + self.key = key + + def forward(self, predicts, batch): + if isinstance(predicts, dict) and self.key is not None: + predicts = predicts[self.key] + labels = batch[5] + attention_mask = batch[2] + if attention_mask is not None: + active_loss = ( + attention_mask.reshape( + [ + -1, + ] + ) + == 1 + ) + active_output = predicts.reshape([-1, self.num_classes])[active_loss] + active_label = labels.reshape( + [ + -1, + ] + )[active_loss] + loss = self.loss_class(active_output, active_label) + else: + loss = self.loss_class( + predicts.reshape([-1, self.num_classes]), + labels.reshape( + [ + -1, + ] + ), + ) + return {"loss": loss} diff --git a/ppocr/metrics/__init__.py b/ppocr/metrics/__init__.py new file mode 100644 index 0000000..dd28d73 --- /dev/null +++ b/ppocr/metrics/__init__.py @@ -0,0 +1,62 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import copy + +__all__ = ["build_metric"] + +from .det_metric import DetMetric, DetFCEMetric +from .rec_metric import RecMetric, CNTMetric, CANMetric, LaTeXOCRMetric +from .cls_metric import ClsMetric +from .e2e_metric import E2EMetric +from .distillation_metric import DistillationMetric +from .table_metric import TableMetric +from .kie_metric import KIEMetric +from .vqa_token_ser_metric import VQASerTokenMetric +from .vqa_token_re_metric import VQAReTokenMetric +from .sr_metric import SRMetric +from .ct_metric import CTMetric + + +def build_metric(config): + support_dict = [ + "DetMetric", + "DetFCEMetric", + "RecMetric", + "ClsMetric", + "E2EMetric", + "DistillationMetric", + "TableMetric", + "KIEMetric", + "VQASerTokenMetric", + "VQAReTokenMetric", + "SRMetric", + "CTMetric", + "CNTMetric", + "CANMetric", + "LaTeXOCRMetric", + ] + + config = copy.deepcopy(config) + module_name = config.pop("name") + assert module_name in support_dict, Exception( + "metric only support {}".format(support_dict) + ) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/metrics/bleu.py b/ppocr/metrics/bleu.py new file mode 100644 index 0000000..c68e624 --- /dev/null +++ b/ppocr/metrics/bleu.py @@ -0,0 +1,243 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is refer from: +https://github.com/tensorflow/nmt/blob/master/nmt/scripts/bleu.py +""" + +import re +import math +import collections +from functools import lru_cache + + +def _get_ngrams(segment, max_order): + """Extracts all n-grams upto a given maximum order from an input segment. + + Args: + segment: text segment from which n-grams will be extracted. + max_order: maximum length in tokens of the n-grams returned by this + methods. + + Returns: + The Counter containing all n-grams upto max_order in segment + with a count of how many times each n-gram occurred. + """ + ngram_counts = collections.Counter() + for order in range(1, max_order + 1): + for i in range(0, len(segment) - order + 1): + ngram = tuple(segment[i : i + order]) + ngram_counts[ngram] += 1 + return ngram_counts + + +def compute_bleu(reference_corpus, translation_corpus, max_order=4, smooth=False): + """Computes BLEU score of translated segments against one or more references. + + Args: + reference_corpus: list of lists of references for each translation. Each + reference should be tokenized into a list of tokens. + translation_corpus: list of translations to score. Each translation + should be tokenized into a list of tokens. + max_order: Maximum n-gram order to use when computing BLEU score. + smooth: Whether or not to apply Lin et al. 2004 smoothing. + + Returns: + 3-Tuple with the BLEU score, n-gram precisions, geometric mean of n-gram + precisions and brevity penalty. + """ + matches_by_order = [0] * max_order + possible_matches_by_order = [0] * max_order + reference_length = 0 + translation_length = 0 + for references, translation in zip(reference_corpus, translation_corpus): + reference_length += min(len(r) for r in references) + translation_length += len(translation) + + merged_ref_ngram_counts = collections.Counter() + for reference in references: + merged_ref_ngram_counts |= _get_ngrams(reference, max_order) + translation_ngram_counts = _get_ngrams(translation, max_order) + overlap = translation_ngram_counts & merged_ref_ngram_counts + for ngram in overlap: + matches_by_order[len(ngram) - 1] += overlap[ngram] + for order in range(1, max_order + 1): + possible_matches = len(translation) - order + 1 + if possible_matches > 0: + possible_matches_by_order[order - 1] += possible_matches + + precisions = [0] * max_order + for i in range(0, max_order): + if smooth: + precisions[i] = (matches_by_order[i] + 1.0) / ( + possible_matches_by_order[i] + 1.0 + ) + else: + if possible_matches_by_order[i] > 0: + precisions[i] = ( + float(matches_by_order[i]) / possible_matches_by_order[i] + ) + else: + precisions[i] = 0.0 + + if min(precisions) > 0: + p_log_sum = sum((1.0 / max_order) * math.log(p) for p in precisions) + geo_mean = math.exp(p_log_sum) + else: + geo_mean = 0 + + if float(translation_length) == 0 or float(reference_length) == 0: + ratio = 1e-5 + else: + ratio = float(translation_length) / reference_length + + if ratio > 1.0: + bp = 1.0 + else: + bp = math.exp(1 - 1.0 / ratio) + + bleu = geo_mean * bp + + return (bleu, precisions, bp, ratio, translation_length, reference_length) + + +class BaseTokenizer: + """A base dummy tokenizer to derive from.""" + + def signature(self): + """ + Returns a signature for the tokenizer. + :return: signature string + """ + return "none" + + def __call__(self, line): + """ + Tokenizes an input line with the tokenizer. + :param line: a segment to tokenize + :return: the tokenized line + """ + return line + + +class TokenizerRegexp(BaseTokenizer): + def signature(self): + return "re" + + def __init__(self): + self._re = [ + # language-dependent part (assuming Western languages) + (re.compile(r"([\{-\~\[-\` -\&\(-\+\:-\@\/])"), r" \1 "), + # tokenize period and comma unless preceded by a digit + (re.compile(r"([^0-9])([\.,])"), r"\1 \2 "), + # tokenize period and comma unless followed by a digit + (re.compile(r"([\.,])([^0-9])"), r" \1 \2"), + # tokenize dash when preceded by a digit + (re.compile(r"([0-9])(-)"), r"\1 \2 "), + # one space only between words + # NOTE: Doing this in Python (below) is faster + # (re.compile(r'\s+'), r' '), + ] + + @lru_cache(maxsize=2**16) + def __call__(self, line): + """Common post-processing tokenizer for `13a` and `zh` tokenizers. + :param line: a segment to tokenize + :return: the tokenized line + """ + for _re, repl in self._re: + line = _re.sub(repl, line) + + # no leading or trailing spaces, single space within words + # return ' '.join(line.split()) + # This line is changed with regards to the original tokenizer (seen above) to return individual words + return line.split() + + +class Tokenizer13a(BaseTokenizer): + def signature(self): + return "13a" + + def __init__(self): + self._post_tokenizer = TokenizerRegexp() + + @lru_cache(maxsize=2**16) + def __call__(self, line): + """Tokenizes an input line using a relatively minimal tokenization + that is however equivalent to mteval-v13a, used by WMT. + + :param line: a segment to tokenize + :return: the tokenized line + """ + + # language-independent part: + line = line.replace("", "") + line = line.replace("-\n", "") + line = line.replace("\n", " ") + + if "&" in line: + line = line.replace(""", '"') + line = line.replace("&", "&") + line = line.replace("<", "<") + line = line.replace(">", ">") + + return self._post_tokenizer(f" {line} ") + + +def compute_bleu_score( + predictions, references, tokenizer=Tokenizer13a(), max_order=4, smooth=False +): + # if only one reference is provided make sure we still use list of lists + if isinstance(references[0], str): + references = [[ref] for ref in references] + + references = [[tokenizer(r) for r in ref] for ref in references] + predictions = [tokenizer(p) for p in predictions] + score = compute_bleu( + reference_corpus=references, + translation_corpus=predictions, + max_order=max_order, + smooth=smooth, + ) + (bleu, precisions, bp, ratio, translation_length, reference_length) = score + return bleu + + +def cal_distance(word1, word2): + m = len(word1) + n = len(word2) + if m * n == 0: + return m + n + dp = [[0] * (n + 1) for _ in range(m + 1)] + for i in range(m + 1): + dp[i][0] = i + for j in range(n + 1): + dp[0][j] = j + for i in range(1, m + 1): + for j in range(1, n + 1): + a = dp[i - 1][j] + 1 + b = dp[i][j - 1] + 1 + c = dp[i - 1][j - 1] + if word1[i - 1] != word2[j - 1]: + c += 1 + dp[i][j] = min(a, b, c) + return dp[m][n] + + +def compute_edit_distance(prediction, label): + prediction = prediction.strip().split(" ") + label = label.strip().split(" ") + distance = cal_distance(prediction, label) + return distance diff --git a/ppocr/metrics/cls_metric.py b/ppocr/metrics/cls_metric.py new file mode 100644 index 0000000..820fe75 --- /dev/null +++ b/ppocr/metrics/cls_metric.py @@ -0,0 +1,48 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +class ClsMetric(object): + def __init__(self, main_indicator="acc", **kwargs): + self.main_indicator = main_indicator + self.eps = 1e-5 + self.reset() + + def __call__(self, pred_label, *args, **kwargs): + preds, labels = pred_label + correct_num = 0 + all_num = 0 + for (pred, pred_conf), (target, _) in zip(preds, labels): + if pred == target: + correct_num += 1 + all_num += 1 + self.correct_num += correct_num + self.all_num += all_num + return { + "acc": correct_num / (all_num + self.eps), + } + + def get_metric(self): + """ + return metrics { + 'acc': 0 + } + """ + acc = self.correct_num / (self.all_num + self.eps) + self.reset() + return {"acc": acc} + + def reset(self): + self.correct_num = 0 + self.all_num = 0 diff --git a/ppocr/metrics/ct_metric.py b/ppocr/metrics/ct_metric.py new file mode 100644 index 0000000..bec3506 --- /dev/null +++ b/ppocr/metrics/ct_metric.py @@ -0,0 +1,51 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +from scipy import io +import numpy as np + +from ppocr.utils.e2e_metric.Deteval import combine_results, get_score_C + + +class CTMetric(object): + def __init__(self, main_indicator, delimiter="\t", **kwargs): + self.delimiter = delimiter + self.main_indicator = main_indicator + self.reset() + + def reset(self): + self.results = [] # clear results + + def __call__(self, preds, batch, **kwargs): + # NOTE: only support bs=1 now, as the label length of different sample is Unequal + assert len(preds) == 1, "CentripetalText test now only support batch_size=1." + label = batch[2] + text = batch[3] + pred = preds[0]["points"] + result = get_score_C(label, text, pred) + + self.results.append(result) + + def get_metric(self): + """ + Input format: y0,x0, ..... yn,xn. Each detection is separated by the end of line token ('\n')' + """ + metrics = combine_results(self.results, rec_flag=False) + self.reset() + return metrics diff --git a/ppocr/metrics/det_metric.py b/ppocr/metrics/det_metric.py new file mode 100644 index 0000000..be95ec3 --- /dev/null +++ b/ppocr/metrics/det_metric.py @@ -0,0 +1,153 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__all__ = ["DetMetric", "DetFCEMetric"] + +from .eval_det_iou import DetectionIoUEvaluator + + +class DetMetric(object): + def __init__(self, main_indicator="hmean", **kwargs): + self.evaluator = DetectionIoUEvaluator() + self.main_indicator = main_indicator + self.reset() + + def __call__(self, preds, batch, **kwargs): + """ + batch: a list produced by dataloaders. + image: np.ndarray of shape (N, C, H, W). + ratio_list: np.ndarray of shape(N,2) + polygons: np.ndarray of shape (N, K, 4, 2), the polygons of objective regions. + ignore_tags: np.ndarray of shape (N, K), indicates whether a region is ignorable or not. + preds: a list of dict produced by post process + points: np.ndarray of shape (N, K, 4, 2), the polygons of objective regions. + """ + gt_polyons_batch = batch[2] + ignore_tags_batch = batch[3] + for pred, gt_polyons, ignore_tags in zip( + preds, gt_polyons_batch, ignore_tags_batch + ): + # prepare gt + gt_info_list = [ + {"points": gt_polyon, "text": "", "ignore": ignore_tag} + for gt_polyon, ignore_tag in zip(gt_polyons, ignore_tags) + ] + # prepare det + det_info_list = [ + {"points": det_polyon, "text": ""} for det_polyon in pred["points"] + ] + result = self.evaluator.evaluate_image(gt_info_list, det_info_list) + self.results.append(result) + + def get_metric(self): + """ + return metrics { + 'precision': 0, + 'recall': 0, + 'hmean': 0 + } + """ + + metrics = self.evaluator.combine_results(self.results) + self.reset() + return metrics + + def reset(self): + self.results = [] # clear results + + +class DetFCEMetric(object): + def __init__(self, main_indicator="hmean", **kwargs): + self.evaluator = DetectionIoUEvaluator() + self.main_indicator = main_indicator + self.reset() + + def __call__(self, preds, batch, **kwargs): + """ + batch: a list produced by dataloaders. + image: np.ndarray of shape (N, C, H, W). + ratio_list: np.ndarray of shape(N,2) + polygons: np.ndarray of shape (N, K, 4, 2), the polygons of objective regions. + ignore_tags: np.ndarray of shape (N, K), indicates whether a region is ignorable or not. + preds: a list of dict produced by post process + points: np.ndarray of shape (N, K, 4, 2), the polygons of objective regions. + """ + gt_polyons_batch = batch[2] + ignore_tags_batch = batch[3] + + for pred, gt_polyons, ignore_tags in zip( + preds, gt_polyons_batch, ignore_tags_batch + ): + # prepare gt + gt_info_list = [ + {"points": gt_polyon, "text": "", "ignore": ignore_tag} + for gt_polyon, ignore_tag in zip(gt_polyons, ignore_tags) + ] + # prepare det + det_info_list = [ + {"points": det_polyon, "text": "", "score": score} + for det_polyon, score in zip(pred["points"], pred["scores"]) + ] + + for score_thr in self.results.keys(): + det_info_list_thr = [ + det_info + for det_info in det_info_list + if det_info["score"] >= score_thr + ] + result = self.evaluator.evaluate_image(gt_info_list, det_info_list_thr) + self.results[score_thr].append(result) + + def get_metric(self): + """ + return metrics {'heman':0, + 'thr 0.3':'precision: 0 recall: 0 hmean: 0', + 'thr 0.4':'precision: 0 recall: 0 hmean: 0', + 'thr 0.5':'precision: 0 recall: 0 hmean: 0', + 'thr 0.6':'precision: 0 recall: 0 hmean: 0', + 'thr 0.7':'precision: 0 recall: 0 hmean: 0', + 'thr 0.8':'precision: 0 recall: 0 hmean: 0', + 'thr 0.9':'precision: 0 recall: 0 hmean: 0', + } + """ + metrics = {} + hmean = 0 + for score_thr in self.results.keys(): + metric = self.evaluator.combine_results(self.results[score_thr]) + # for key, value in metric.items(): + # metrics['{}_{}'.format(key, score_thr)] = value + metric_str = "precision:{:.5f} recall:{:.5f} hmean:{:.5f}".format( + metric["precision"], metric["recall"], metric["hmean"] + ) + metrics["thr {}".format(score_thr)] = metric_str + hmean = max(hmean, metric["hmean"]) + metrics["hmean"] = hmean + + self.reset() + return metrics + + def reset(self): + self.results = { + 0.3: [], + 0.4: [], + 0.5: [], + 0.6: [], + 0.7: [], + 0.8: [], + 0.9: [], + } # clear results diff --git a/ppocr/metrics/distillation_metric.py b/ppocr/metrics/distillation_metric.py new file mode 100644 index 0000000..8e0bcf1 --- /dev/null +++ b/ppocr/metrics/distillation_metric.py @@ -0,0 +1,72 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib +import copy + +from .rec_metric import RecMetric +from .det_metric import DetMetric +from .e2e_metric import E2EMetric +from .cls_metric import ClsMetric +from .vqa_token_ser_metric import VQASerTokenMetric +from .vqa_token_re_metric import VQAReTokenMetric + + +class DistillationMetric(object): + def __init__(self, key=None, base_metric_name=None, main_indicator=None, **kwargs): + self.main_indicator = main_indicator + self.key = key + self.main_indicator = main_indicator + self.base_metric_name = base_metric_name + self.kwargs = kwargs + self.metrics = None + + def _init_metrcis(self, preds): + self.metrics = dict() + mod = importlib.import_module(__name__) + for key in preds: + self.metrics[key] = getattr(mod, self.base_metric_name)( + main_indicator=self.main_indicator, **self.kwargs + ) + self.metrics[key].reset() + + def __call__(self, preds, batch, **kwargs): + assert isinstance(preds, dict) + if self.metrics is None: + self._init_metrcis(preds) + output = dict() + for key in preds: + self.metrics[key].__call__(preds[key], batch, **kwargs) + + def get_metric(self): + """ + return metrics { + 'acc': 0, + 'norm_edit_dis': 0, + } + """ + output = dict() + for key in self.metrics: + metric = self.metrics[key].get_metric() + # main indicator + if key == self.key: + output.update(metric) + else: + for sub_key in metric: + output["{}_{}".format(key, sub_key)] = metric[sub_key] + return output + + def reset(self): + for key in self.metrics: + self.metrics[key].reset() diff --git a/ppocr/metrics/e2e_metric.py b/ppocr/metrics/e2e_metric.py new file mode 100644 index 0000000..97796c4 --- /dev/null +++ b/ppocr/metrics/e2e_metric.py @@ -0,0 +1,88 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__all__ = ["E2EMetric"] + +from ppocr.utils.e2e_metric.Deteval import get_socre_A, get_socre_B, combine_results +from ppocr.utils.e2e_utils.extract_textpoint_slow import get_dict + + +class E2EMetric(object): + def __init__( + self, + mode, + gt_mat_dir, + character_dict_path, + main_indicator="f_score_e2e", + **kwargs, + ): + self.mode = mode + self.gt_mat_dir = gt_mat_dir + self.label_list = get_dict(character_dict_path) + self.max_index = len(self.label_list) + self.main_indicator = main_indicator + self.reset() + + def __call__(self, preds, batch, **kwargs): + if self.mode == "A": + gt_polyons_batch = batch[2] + temp_gt_strs_batch = batch[3][0] + ignore_tags_batch = batch[4] + gt_strs_batch = [] + + for temp_list in temp_gt_strs_batch: + t = "" + for index in temp_list: + if index < self.max_index: + t += self.label_list[index] + gt_strs_batch.append(t) + + for pred, gt_polyons, gt_strs, ignore_tags in zip( + [preds], gt_polyons_batch, [gt_strs_batch], ignore_tags_batch + ): + # prepare gt + gt_info_list = [ + {"points": gt_polyon, "text": gt_str, "ignore": ignore_tag} + for gt_polyon, gt_str, ignore_tag in zip( + gt_polyons, gt_strs, ignore_tags + ) + ] + # prepare det + e2e_info_list = [ + {"points": det_polyon, "texts": pred_str} + for det_polyon, pred_str in zip(pred["points"], pred["texts"]) + ] + + result = get_socre_A(gt_info_list, e2e_info_list) + self.results.append(result) + else: + img_id = batch[5][0] + e2e_info_list = [ + {"points": det_polyon, "texts": pred_str} + for det_polyon, pred_str in zip(preds["points"], preds["texts"]) + ] + result = get_socre_B(self.gt_mat_dir, img_id, e2e_info_list) + self.results.append(result) + + def get_metric(self): + metrics = combine_results(self.results) + self.reset() + return metrics + + def reset(self): + self.results = [] # clear results diff --git a/ppocr/metrics/eval_det_iou.py b/ppocr/metrics/eval_det_iou.py new file mode 100644 index 0000000..21d5c18 --- /dev/null +++ b/ppocr/metrics/eval_det_iou.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from collections import namedtuple +import numpy as np +from shapely.geometry import Polygon + +""" +reference from : +https://github.com/MhLiao/DB/blob/3c32b808d4412680310d3d28eeb6a2d5bf1566c5/concern/icdar2015_eval/detection/iou.py#L8 +""" + + +class DetectionIoUEvaluator(object): + def __init__(self, iou_constraint=0.5, area_precision_constraint=0.5): + self.iou_constraint = iou_constraint + self.area_precision_constraint = area_precision_constraint + + def evaluate_image(self, gt, pred): + def get_union(pD, pG): + return Polygon(pD).union(Polygon(pG)).area + + def get_intersection_over_union(pD, pG): + return get_intersection(pD, pG) / get_union(pD, pG) + + def get_intersection(pD, pG): + return Polygon(pD).intersection(Polygon(pG)).area + + def compute_ap(confList, matchList, numGtCare): + correct = 0 + AP = 0 + if len(confList) > 0: + confList = np.array(confList) + matchList = np.array(matchList) + sorted_ind = np.argsort(-confList) + confList = confList[sorted_ind] + matchList = matchList[sorted_ind] + for n in range(len(confList)): + match = matchList[n] + if match: + correct += 1 + AP += float(correct) / (n + 1) + + if numGtCare > 0: + AP /= numGtCare + + return AP + + perSampleMetrics = {} + + matchedSum = 0 + + Rectangle = namedtuple("Rectangle", "xmin ymin xmax ymax") + + numGlobalCareGt = 0 + numGlobalCareDet = 0 + + arrGlobalConfidences = [] + arrGlobalMatches = [] + + recall = 0 + precision = 0 + hmean = 0 + + detMatched = 0 + + iouMat = np.empty([1, 1]) + + gtPols = [] + detPols = [] + + gtPolPoints = [] + detPolPoints = [] + + # Array of Ground Truth Polygons' keys marked as don't Care + gtDontCarePolsNum = [] + # Array of Detected Polygons' matched with a don't Care GT + detDontCarePolsNum = [] + + pairs = [] + detMatchedNums = [] + + arrSampleConfidences = [] + arrSampleMatch = [] + + evaluationLog = "" + + for n in range(len(gt)): + points = gt[n]["points"] + dontCare = gt[n]["ignore"] + if not Polygon(points).is_valid: + continue + + gtPol = points + gtPols.append(gtPol) + gtPolPoints.append(points) + if dontCare: + gtDontCarePolsNum.append(len(gtPols) - 1) + + evaluationLog += ( + "GT polygons: " + + str(len(gtPols)) + + ( + " (" + str(len(gtDontCarePolsNum)) + " don't care)\n" + if len(gtDontCarePolsNum) > 0 + else "\n" + ) + ) + + for n in range(len(pred)): + points = pred[n]["points"] + if not Polygon(points).is_valid: + continue + + detPol = points + detPols.append(detPol) + detPolPoints.append(points) + if len(gtDontCarePolsNum) > 0: + for dontCarePol in gtDontCarePolsNum: + dontCarePol = gtPols[dontCarePol] + intersected_area = get_intersection(dontCarePol, detPol) + pdDimensions = Polygon(detPol).area + precision = ( + 0 if pdDimensions == 0 else intersected_area / pdDimensions + ) + if precision > self.area_precision_constraint: + detDontCarePolsNum.append(len(detPols) - 1) + break + + evaluationLog += ( + "DET polygons: " + + str(len(detPols)) + + ( + " (" + str(len(detDontCarePolsNum)) + " don't care)\n" + if len(detDontCarePolsNum) > 0 + else "\n" + ) + ) + + if len(gtPols) > 0 and len(detPols) > 0: + # Calculate IoU and precision matrixs + outputShape = [len(gtPols), len(detPols)] + iouMat = np.empty(outputShape) + gtRectMat = np.zeros(len(gtPols), np.int8) + detRectMat = np.zeros(len(detPols), np.int8) + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + pG = gtPols[gtNum] + pD = detPols[detNum] + iouMat[gtNum, detNum] = get_intersection_over_union(pD, pG) + + for gtNum in range(len(gtPols)): + for detNum in range(len(detPols)): + if ( + gtRectMat[gtNum] == 0 + and detRectMat[detNum] == 0 + and gtNum not in gtDontCarePolsNum + and detNum not in detDontCarePolsNum + ): + if iouMat[gtNum, detNum] > self.iou_constraint: + gtRectMat[gtNum] = 1 + detRectMat[detNum] = 1 + detMatched += 1 + pairs.append({"gt": gtNum, "det": detNum}) + detMatchedNums.append(detNum) + evaluationLog += ( + "Match GT #" + + str(gtNum) + + " with Det #" + + str(detNum) + + "\n" + ) + + numGtCare = len(gtPols) - len(gtDontCarePolsNum) + numDetCare = len(detPols) - len(detDontCarePolsNum) + if numGtCare == 0: + recall = float(1) + precision = float(0) if numDetCare > 0 else float(1) + else: + recall = float(detMatched) / numGtCare + precision = 0 if numDetCare == 0 else float(detMatched) / numDetCare + + hmean = ( + 0 + if (precision + recall) == 0 + else 2.0 * precision * recall / (precision + recall) + ) + + matchedSum += detMatched + numGlobalCareGt += numGtCare + numGlobalCareDet += numDetCare + + perSampleMetrics = { + "gtCare": numGtCare, + "detCare": numDetCare, + "detMatched": detMatched, + } + return perSampleMetrics + + def combine_results(self, results): + numGlobalCareGt = 0 + numGlobalCareDet = 0 + matchedSum = 0 + for result in results: + numGlobalCareGt += result["gtCare"] + numGlobalCareDet += result["detCare"] + matchedSum += result["detMatched"] + + methodRecall = ( + 0 if numGlobalCareGt == 0 else float(matchedSum) / numGlobalCareGt + ) + methodPrecision = ( + 0 if numGlobalCareDet == 0 else float(matchedSum) / numGlobalCareDet + ) + methodHmean = ( + 0 + if methodRecall + methodPrecision == 0 + else 2 * methodRecall * methodPrecision / (methodRecall + methodPrecision) + ) + methodMetrics = { + "precision": methodPrecision, + "recall": methodRecall, + "hmean": methodHmean, + } + + return methodMetrics + + +if __name__ == "__main__": + evaluator = DetectionIoUEvaluator() + gts = [ + [ + { + "points": [(0, 0), (1, 0), (1, 1), (0, 1)], + "text": 1234, + "ignore": False, + }, + { + "points": [(2, 2), (3, 2), (3, 3), (2, 3)], + "text": 5678, + "ignore": False, + }, + ] + ] + preds = [ + [ + { + "points": [(0.1, 0.1), (1, 0), (1, 1), (0, 1)], + "text": 123, + "ignore": False, + } + ] + ] + results = [] + for gt, pred in zip(gts, preds): + results.append(evaluator.evaluate_image(gt, pred)) + metrics = evaluator.combine_results(results) + print(metrics) diff --git a/ppocr/metrics/kie_metric.py b/ppocr/metrics/kie_metric.py new file mode 100644 index 0000000..0c83756 --- /dev/null +++ b/ppocr/metrics/kie_metric.py @@ -0,0 +1,72 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# The code is refer from: https://github.com/open-mmlab/mmocr/blob/main/mmocr/core/evaluation/kie_metric.py + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import paddle + +__all__ = ["KIEMetric"] + + +class KIEMetric(object): + def __init__(self, main_indicator="hmean", **kwargs): + self.main_indicator = main_indicator + self.reset() + self.node = [] + self.gt = [] + + def __call__(self, preds, batch, **kwargs): + nodes, _ = preds + gts, tag = batch[4].squeeze(0), batch[5].tolist()[0] + gts = gts[: tag[0], :1].reshape([-1]) + self.node.append(nodes.numpy()) + self.gt.append(gts) + # result = self.compute_f1_score(nodes, gts) + # self.results.append(result) + + def compute_f1_score(self, preds, gts): + ignores = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 25] + C = preds.shape[1] + classes = np.array(sorted(set(range(C)) - set(ignores))) + hist = ( + np.bincount((gts * C).astype("int64") + preds.argmax(1), minlength=C**2) + .reshape([C, C]) + .astype("float32") + ) + diag = np.diag(hist) + recalls = diag / hist.sum(1).clip(min=1) + precisions = diag / hist.sum(0).clip(min=1) + f1 = 2 * recalls * precisions / (recalls + precisions).clip(min=1e-8) + return f1[classes] + + def combine_results(self, results): + node = np.concatenate(self.node, 0) + gts = np.concatenate(self.gt, 0) + results = self.compute_f1_score(node, gts) + data = {"hmean": results.mean()} + return data + + def get_metric(self): + metrics = self.combine_results(self.results) + self.reset() + return metrics + + def reset(self): + self.results = [] # clear results + self.node = [] + self.gt = [] diff --git a/ppocr/metrics/rec_metric.py b/ppocr/metrics/rec_metric.py new file mode 100644 index 0000000..eef0a9a --- /dev/null +++ b/ppocr/metrics/rec_metric.py @@ -0,0 +1,297 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from rapidfuzz.distance import Levenshtein +from difflib import SequenceMatcher + +import numpy as np +import string +from .bleu import compute_bleu_score, compute_edit_distance + + +class RecMetric(object): + def __init__( + self, main_indicator="acc", is_filter=False, ignore_space=True, **kwargs + ): + self.main_indicator = main_indicator + self.is_filter = is_filter + self.ignore_space = ignore_space + self.eps = 1e-5 + self.reset() + + def _normalize_text(self, text): + text = "".join( + filter(lambda x: x in (string.digits + string.ascii_letters), text) + ) + return text.lower() + + def __call__(self, pred_label, *args, **kwargs): + preds, labels = pred_label + correct_num = 0 + all_num = 0 + norm_edit_dis = 0.0 + for (pred, pred_conf), (target, _) in zip(preds, labels): + if self.ignore_space: + pred = pred.replace(" ", "") + target = target.replace(" ", "") + if self.is_filter: + pred = self._normalize_text(pred) + target = self._normalize_text(target) + norm_edit_dis += Levenshtein.normalized_distance(pred, target) + if pred == target: + correct_num += 1 + all_num += 1 + self.correct_num += correct_num + self.all_num += all_num + self.norm_edit_dis += norm_edit_dis + return { + "acc": correct_num / (all_num + self.eps), + "norm_edit_dis": 1 - norm_edit_dis / (all_num + self.eps), + } + + def get_metric(self): + """ + return metrics { + 'acc': 0, + 'norm_edit_dis': 0, + } + """ + acc = 1.0 * self.correct_num / (self.all_num + self.eps) + norm_edit_dis = 1 - self.norm_edit_dis / (self.all_num + self.eps) + self.reset() + return {"acc": acc, "norm_edit_dis": norm_edit_dis} + + def reset(self): + self.correct_num = 0 + self.all_num = 0 + self.norm_edit_dis = 0 + + +class CNTMetric(object): + def __init__(self, main_indicator="acc", **kwargs): + self.main_indicator = main_indicator + self.eps = 1e-5 + self.reset() + + def __call__(self, pred_label, *args, **kwargs): + preds, labels = pred_label + correct_num = 0 + all_num = 0 + for pred, target in zip(preds, labels): + if pred == target: + correct_num += 1 + all_num += 1 + self.correct_num += correct_num + self.all_num += all_num + return { + "acc": correct_num / (all_num + self.eps), + } + + def get_metric(self): + """ + return metrics { + 'acc': 0, + } + """ + acc = 1.0 * self.correct_num / (self.all_num + self.eps) + self.reset() + return {"acc": acc} + + def reset(self): + self.correct_num = 0 + self.all_num = 0 + + +class CANMetric(object): + def __init__(self, main_indicator="exp_rate", **kwargs): + self.main_indicator = main_indicator + self.word_right = [] + self.exp_right = [] + self.word_total_length = 0 + self.exp_total_num = 0 + self.word_rate = 0 + self.exp_rate = 0 + self.reset() + self.epoch_reset() + + def __call__(self, preds, batch, **kwargs): + for k, v in kwargs.items(): + epoch_reset = v + if epoch_reset: + self.epoch_reset() + word_probs = preds + word_label, word_label_mask = batch + line_right = 0 + if word_probs is not None: + word_pred = word_probs.argmax(2) + word_pred = word_pred.cpu().detach().numpy() + word_scores = [ + SequenceMatcher( + None, s1[: int(np.sum(s3))], s2[: int(np.sum(s3))], autojunk=False + ).ratio() + * (len(s1[: int(np.sum(s3))]) + len(s2[: int(np.sum(s3))])) + / len(s1[: int(np.sum(s3))]) + / 2 + for s1, s2, s3 in zip(word_label, word_pred, word_label_mask) + ] + batch_size = len(word_scores) + for i in range(batch_size): + if word_scores[i] == 1: + line_right += 1 + self.word_rate = np.mean(word_scores) # float + self.exp_rate = line_right / batch_size # float + exp_length, word_length = word_label.shape[:2] + self.word_right.append(self.word_rate * word_length) + self.exp_right.append(self.exp_rate * exp_length) + self.word_total_length = self.word_total_length + word_length + self.exp_total_num = self.exp_total_num + exp_length + + def get_metric(self): + """ + return { + 'word_rate': 0, + "exp_rate": 0, + } + """ + cur_word_rate = sum(self.word_right) / self.word_total_length + cur_exp_rate = sum(self.exp_right) / self.exp_total_num + self.reset() + return {"word_rate": cur_word_rate, "exp_rate": cur_exp_rate} + + def reset(self): + self.word_rate = 0 + self.exp_rate = 0 + + def epoch_reset(self): + self.word_right = [] + self.exp_right = [] + self.word_total_length = 0 + self.exp_total_num = 0 + + +class LaTeXOCRMetric(object): + def __init__(self, main_indicator="exp_rate", cal_bleu_score=False, **kwargs): + self.main_indicator = main_indicator + self.cal_bleu_score = cal_bleu_score + self.edit_right = [] + self.exp_right = [] + self.bleu_right = [] + self.e1_right = [] + self.e2_right = [] + self.e3_right = [] + self.exp_total_num = 0 + self.edit_dist = 0 + self.exp_rate = 0 + if self.cal_bleu_score: + self.bleu_score = 0 + self.e1 = 0 + self.e2 = 0 + self.e3 = 0 + self.reset() + self.epoch_reset() + + def __call__(self, preds, batch, **kwargs): + for k, v in kwargs.items(): + epoch_reset = v + if epoch_reset: + self.epoch_reset() + word_pred = preds + word_label = batch + line_right, e1, e2, e3 = 0, 0, 0, 0 + bleu_list, lev_dist = [], [] + for labels, prediction in zip(word_label, word_pred): + if prediction == labels: + line_right += 1 + distance = compute_edit_distance(prediction, labels) + bleu_list.append(compute_bleu_score([prediction], [labels])) + lev_dist.append(Levenshtein.normalized_distance(prediction, labels)) + if distance <= 1: + e1 += 1 + if distance <= 2: + e2 += 1 + if distance <= 3: + e3 += 1 + + batch_size = len(lev_dist) + + self.edit_dist = sum(lev_dist) # float + self.exp_rate = line_right # float + if self.cal_bleu_score: + self.bleu_score = sum(bleu_list) + self.bleu_right.append(self.bleu_score) + self.e1 = e1 + self.e2 = e2 + self.e3 = e3 + exp_length = len(word_label) + self.edit_right.append(self.edit_dist) + self.exp_right.append(self.exp_rate) + self.e1_right.append(self.e1) + self.e2_right.append(self.e2) + self.e3_right.append(self.e3) + self.exp_total_num = self.exp_total_num + exp_length + + def get_metric(self): + """ + return { + 'edit distance': 0, + "bleu_score": 0, + "exp_rate": 0, + } + """ + cur_edit_distance = sum(self.edit_right) / self.exp_total_num + cur_exp_rate = sum(self.exp_right) / self.exp_total_num + if self.cal_bleu_score: + cur_bleu_score = sum(self.bleu_right) / self.exp_total_num + cur_exp_1 = sum(self.e1_right) / self.exp_total_num + cur_exp_2 = sum(self.e2_right) / self.exp_total_num + cur_exp_3 = sum(self.e3_right) / self.exp_total_num + self.reset() + if self.cal_bleu_score: + return { + "bleu_score": cur_bleu_score, + "edit distance": cur_edit_distance, + "exp_rate": cur_exp_rate, + "exp_rate<=1 ": cur_exp_1, + "exp_rate<=2 ": cur_exp_2, + "exp_rate<=3 ": cur_exp_3, + } + else: + + return { + "edit distance": cur_edit_distance, + "exp_rate": cur_exp_rate, + "exp_rate<=1 ": cur_exp_1, + "exp_rate<=2 ": cur_exp_2, + "exp_rate<=3 ": cur_exp_3, + } + + def reset(self): + self.edit_dist = 0 + self.exp_rate = 0 + if self.cal_bleu_score: + self.bleu_score = 0 + self.e1 = 0 + self.e2 = 0 + self.e3 = 0 + + def epoch_reset(self): + self.edit_right = [] + self.exp_right = [] + if self.cal_bleu_score: + self.bleu_right = [] + self.e1_right = [] + self.e2_right = [] + self.e3_right = [] + self.editdistance_total_length = 0 + self.exp_total_num = 0 diff --git a/ppocr/metrics/sr_metric.py b/ppocr/metrics/sr_metric.py new file mode 100644 index 0000000..ef9ef96 --- /dev/null +++ b/ppocr/metrics/sr_metric.py @@ -0,0 +1,161 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +https://github.com/FudanVI/FudanOCR/blob/main/text-gestalt/utils/ssim_psnr.py +""" + +from math import exp + +import paddle +import paddle.nn.functional as F +import paddle.nn as nn +import string + + +class SSIM(nn.Layer): + def __init__(self, window_size=11, size_average=True): + super(SSIM, self).__init__() + self.window_size = window_size + self.size_average = size_average + self.channel = 1 + self.window = self.create_window(window_size, self.channel) + + def gaussian(self, window_size, sigma): + gauss = paddle.to_tensor( + [ + exp(-((x - window_size // 2) ** 2) / float(2 * sigma**2)) + for x in range(window_size) + ] + ) + return gauss / gauss.sum() + + def create_window(self, window_size, channel): + _1D_window = self.gaussian(window_size, 1.5).unsqueeze(1) + _2D_window = _1D_window.mm(_1D_window.t()).unsqueeze(0).unsqueeze(0) + window = _2D_window.expand([channel, 1, window_size, window_size]) + return window + + def _ssim(self, img1, img2, window, window_size, channel, size_average=True): + mu1 = F.conv2d(img1, window, padding=window_size // 2, groups=channel) + mu2 = F.conv2d(img2, window, padding=window_size // 2, groups=channel) + + mu1_sq = mu1.pow(2) + mu2_sq = mu2.pow(2) + mu1_mu2 = mu1 * mu2 + + sigma1_sq = ( + F.conv2d(img1 * img1, window, padding=window_size // 2, groups=channel) + - mu1_sq + ) + sigma2_sq = ( + F.conv2d(img2 * img2, window, padding=window_size // 2, groups=channel) + - mu2_sq + ) + sigma12 = ( + F.conv2d(img1 * img2, window, padding=window_size // 2, groups=channel) + - mu1_mu2 + ) + + C1 = 0.01**2 + C2 = 0.03**2 + + ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ( + (mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2) + ) + + if size_average: + return ssim_map.mean() + else: + return ssim_map.mean([1, 2, 3]) + + def ssim(self, img1, img2, window_size=11, size_average=True): + (_, channel, _, _) = img1.shape + window = self.create_window(window_size, channel) + + return self._ssim(img1, img2, window, window_size, channel, size_average) + + def forward(self, img1, img2): + (_, channel, _, _) = img1.shape + + if channel == self.channel and self.window.dtype == img1.dtype: + window = self.window + else: + window = self.create_window(self.window_size, channel) + + self.window = window + self.channel = channel + + return self._ssim( + img1, img2, window, self.window_size, channel, self.size_average + ) + + +class SRMetric(object): + def __init__(self, main_indicator="all", **kwargs): + self.main_indicator = main_indicator + self.eps = 1e-5 + self.psnr_result = [] + self.ssim_result = [] + self.calculate_ssim = SSIM() + self.reset() + + def reset(self): + self.correct_num = 0 + self.all_num = 0 + self.norm_edit_dis = 0 + self.psnr_result = [] + self.ssim_result = [] + + def calculate_psnr(self, img1, img2): + # img1 and img2 have range [0, 1] + mse = ((img1 * 255 - img2 * 255) ** 2).mean() + if mse == 0: + return float("inf") + return 20 * paddle.log10(255.0 / paddle.sqrt(mse)) + + def _normalize_text(self, text): + text = "".join( + filter(lambda x: x in (string.digits + string.ascii_letters), text) + ) + return text.lower() + + def __call__(self, pred_label, *args, **kwargs): + metric = {} + images_sr = pred_label["sr_img"] + images_hr = pred_label["hr_img"] + psnr = self.calculate_psnr(images_sr, images_hr) + ssim = self.calculate_ssim(images_sr, images_hr) + self.psnr_result.append(psnr) + self.ssim_result.append(ssim) + + def get_metric(self): + """ + return metrics { + 'acc': 0, + 'norm_edit_dis': 0, + } + """ + self.psnr_avg = sum(self.psnr_result) / len(self.psnr_result) + self.psnr_avg = round(self.psnr_avg.item(), 6) + self.ssim_avg = sum(self.ssim_result) / len(self.ssim_result) + self.ssim_avg = round(self.ssim_avg.item(), 6) + + self.all_avg = self.psnr_avg + self.ssim_avg + + self.reset() + return { + "psnr_avg": self.psnr_avg, + "ssim_avg": self.ssim_avg, + "all": self.all_avg, + } diff --git a/ppocr/metrics/table_metric.py b/ppocr/metrics/table_metric.py new file mode 100644 index 0000000..98847e1 --- /dev/null +++ b/ppocr/metrics/table_metric.py @@ -0,0 +1,161 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +from ppocr.metrics.det_metric import DetMetric + + +class TableStructureMetric(object): + def __init__(self, main_indicator="acc", eps=1e-6, del_thead_tbody=False, **kwargs): + self.main_indicator = main_indicator + self.eps = eps + self.del_thead_tbody = del_thead_tbody + self.reset() + + def __call__(self, pred_label, batch=None, *args, **kwargs): + preds, labels = pred_label + pred_structure_batch_list = preds["structure_batch_list"] + gt_structure_batch_list = labels["structure_batch_list"] + correct_num = 0 + all_num = 0 + for (pred, pred_conf), target in zip( + pred_structure_batch_list, gt_structure_batch_list + ): + pred_str = "".join(pred) + target_str = "".join(target) + if self.del_thead_tbody: + pred_str = ( + pred_str.replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + ) + target_str = ( + target_str.replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + ) + if pred_str == target_str: + correct_num += 1 + all_num += 1 + self.correct_num += correct_num + self.all_num += all_num + + def get_metric(self): + """ + return metrics { + 'acc': 0, + } + """ + acc = 1.0 * self.correct_num / (self.all_num + self.eps) + self.reset() + return {"acc": acc} + + def reset(self): + self.correct_num = 0 + self.all_num = 0 + self.len_acc_num = 0 + self.token_nums = 0 + self.anys_dict = dict() + + +class TableMetric(object): + def __init__( + self, + main_indicator="acc", + compute_bbox_metric=False, + box_format="xyxy", + del_thead_tbody=False, + **kwargs, + ): + """ + + @param sub_metrics: configs of sub_metric + @param main_matric: main_matric for save best_model + @param kwargs: + """ + self.structure_metric = TableStructureMetric(del_thead_tbody=del_thead_tbody) + self.bbox_metric = DetMetric() if compute_bbox_metric else None + self.main_indicator = main_indicator + self.box_format = box_format + self.reset() + + def __call__(self, pred_label, batch=None, *args, **kwargs): + self.structure_metric(pred_label) + if self.bbox_metric is not None: + self.bbox_metric(*self.prepare_bbox_metric_input(pred_label)) + + def prepare_bbox_metric_input(self, pred_label): + pred_bbox_batch_list = [] + gt_ignore_tags_batch_list = [] + gt_bbox_batch_list = [] + preds, labels = pred_label + + batch_num = len(preds["bbox_batch_list"]) + for batch_idx in range(batch_num): + # pred + pred_bbox_list = [ + self.format_box(pred_box) + for pred_box in preds["bbox_batch_list"][batch_idx] + ] + pred_bbox_batch_list.append({"points": pred_bbox_list}) + + # gt + gt_bbox_list = [] + gt_ignore_tags_list = [] + for gt_box in labels["bbox_batch_list"][batch_idx]: + gt_bbox_list.append(self.format_box(gt_box)) + gt_ignore_tags_list.append(0) + gt_bbox_batch_list.append(gt_bbox_list) + gt_ignore_tags_batch_list.append(gt_ignore_tags_list) + + return [ + pred_bbox_batch_list, + [0, 0, gt_bbox_batch_list, gt_ignore_tags_batch_list], + ] + + def get_metric(self): + structure_metric = self.structure_metric.get_metric() + if self.bbox_metric is None: + return structure_metric + bbox_metric = self.bbox_metric.get_metric() + if self.main_indicator == self.bbox_metric.main_indicator: + output = bbox_metric + for sub_key in structure_metric: + output["structure_metric_{}".format(sub_key)] = structure_metric[ + sub_key + ] + else: + output = structure_metric + for sub_key in bbox_metric: + output["bbox_metric_{}".format(sub_key)] = bbox_metric[sub_key] + return output + + def reset(self): + self.structure_metric.reset() + if self.bbox_metric is not None: + self.bbox_metric.reset() + + def format_box(self, box): + if self.box_format == "xyxy": + x1, y1, x2, y2 = box + box = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]] + elif self.box_format == "xywh": + x, y, w, h = box + x1, y1, x2, y2 = x - w // 2, y - h // 2, x + w // 2, y + h // 2 + box = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]] + elif self.box_format == "xyxyxyxy": + x1, y1, x2, y2, x3, y3, x4, y4 = box + box = [[x1, y1], [x2, y2], [x3, y3], [x4, y4]] + return box diff --git a/ppocr/metrics/vqa_token_re_metric.py b/ppocr/metrics/vqa_token_re_metric.py new file mode 100644 index 0000000..8c85be5 --- /dev/null +++ b/ppocr/metrics/vqa_token_re_metric.py @@ -0,0 +1,191 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import paddle + +__all__ = ["VQAReTokenMetric"] + + +class VQAReTokenMetric(object): + def __init__(self, main_indicator="hmean", **kwargs): + self.main_indicator = main_indicator + self.reset() + + def __call__(self, preds, batch, **kwargs): + pred_relations, relations, entities = preds + self.pred_relations_list.extend(pred_relations) + self.relations_list.extend(relations) + self.entities_list.extend(entities) + + def get_metric(self): + gt_relations = [] + for b in range(len(self.relations_list)): + rel_sent = [] + relation_list = self.relations_list[b] + entitie_list = self.entities_list[b] + head_len = relation_list[0, 0] + if head_len > 0: + entitie_start_list = entitie_list[1 : entitie_list[0, 0] + 1, 0] + entitie_end_list = entitie_list[1 : entitie_list[0, 1] + 1, 1] + entitie_label_list = entitie_list[1 : entitie_list[0, 2] + 1, 2] + for head, tail in zip( + relation_list[1 : head_len + 1, 0], + relation_list[1 : head_len + 1, 1], + ): + rel = {} + rel["head_id"] = head + rel["head"] = (entitie_start_list[head], entitie_end_list[head]) + rel["head_type"] = entitie_label_list[head] + + rel["tail_id"] = tail + rel["tail"] = (entitie_start_list[tail], entitie_end_list[tail]) + rel["tail_type"] = entitie_label_list[tail] + + rel["type"] = 1 + rel_sent.append(rel) + gt_relations.append(rel_sent) + re_metrics = self.re_score( + self.pred_relations_list, gt_relations, mode="boundaries" + ) + metrics = { + "precision": re_metrics["ALL"]["p"], + "recall": re_metrics["ALL"]["r"], + "hmean": re_metrics["ALL"]["f1"], + } + self.reset() + return metrics + + def reset(self): + self.pred_relations_list = [] + self.relations_list = [] + self.entities_list = [] + + def re_score(self, pred_relations, gt_relations, mode="strict"): + """Evaluate RE predictions + + Args: + pred_relations (list) : list of list of predicted relations (several relations in each sentence) + gt_relations (list) : list of list of ground truth relations + + rel = { "head": (start_idx (inclusive), end_idx (exclusive)), + "tail": (start_idx (inclusive), end_idx (exclusive)), + "head_type": ent_type, + "tail_type": ent_type, + "type": rel_type} + + vocab (Vocab) : dataset vocabulary + mode (str) : in 'strict' or 'boundaries'""" + + assert mode in ["strict", "boundaries"] + + relation_types = [v for v in [0, 1] if not v == 0] + scores = {rel: {"tp": 0, "fp": 0, "fn": 0} for rel in relation_types + ["ALL"]} + + # Count GT relations and Predicted relations + n_sents = len(gt_relations) + n_rels = sum([len([rel for rel in sent]) for sent in gt_relations]) + n_found = sum([len([rel for rel in sent]) for sent in pred_relations]) + + # Count TP, FP and FN per type + for pred_sent, gt_sent in zip(pred_relations, gt_relations): + for rel_type in relation_types: + # strict mode takes argument types into account + if mode == "strict": + pred_rels = { + (rel["head"], rel["head_type"], rel["tail"], rel["tail_type"]) + for rel in pred_sent + if rel["type"] == rel_type + } + gt_rels = { + (rel["head"], rel["head_type"], rel["tail"], rel["tail_type"]) + for rel in gt_sent + if rel["type"] == rel_type + } + + # boundaries mode only takes argument spans into account + elif mode == "boundaries": + pred_rels = { + (rel["head"], rel["tail"]) + for rel in pred_sent + if rel["type"] == rel_type + } + gt_rels = { + (rel["head"], rel["tail"]) + for rel in gt_sent + if rel["type"] == rel_type + } + + scores[rel_type]["tp"] += len(pred_rels & gt_rels) + scores[rel_type]["fp"] += len(pred_rels - gt_rels) + scores[rel_type]["fn"] += len(gt_rels - pred_rels) + + # Compute per entity Precision / Recall / F1 + for rel_type in scores.keys(): + if scores[rel_type]["tp"]: + scores[rel_type]["p"] = scores[rel_type]["tp"] / ( + scores[rel_type]["fp"] + scores[rel_type]["tp"] + ) + scores[rel_type]["r"] = scores[rel_type]["tp"] / ( + scores[rel_type]["fn"] + scores[rel_type]["tp"] + ) + else: + scores[rel_type]["p"], scores[rel_type]["r"] = 0, 0 + + if not scores[rel_type]["p"] + scores[rel_type]["r"] == 0: + scores[rel_type]["f1"] = ( + 2 + * scores[rel_type]["p"] + * scores[rel_type]["r"] + / (scores[rel_type]["p"] + scores[rel_type]["r"]) + ) + else: + scores[rel_type]["f1"] = 0 + + # Compute micro F1 Scores + tp = sum([scores[rel_type]["tp"] for rel_type in relation_types]) + fp = sum([scores[rel_type]["fp"] for rel_type in relation_types]) + fn = sum([scores[rel_type]["fn"] for rel_type in relation_types]) + + if tp: + precision = tp / (tp + fp) + recall = tp / (tp + fn) + f1 = 2 * precision * recall / (precision + recall) + + else: + precision, recall, f1 = 0, 0, 0 + + scores["ALL"]["p"] = precision + scores["ALL"]["r"] = recall + scores["ALL"]["f1"] = f1 + scores["ALL"]["tp"] = tp + scores["ALL"]["fp"] = fp + scores["ALL"]["fn"] = fn + + # Compute Macro F1 Scores + scores["ALL"]["Macro_f1"] = np.mean( + [scores[ent_type]["f1"] for ent_type in relation_types] + ) + scores["ALL"]["Macro_p"] = np.mean( + [scores[ent_type]["p"] for ent_type in relation_types] + ) + scores["ALL"]["Macro_r"] = np.mean( + [scores[ent_type]["r"] for ent_type in relation_types] + ) + + return scores diff --git a/ppocr/metrics/vqa_token_ser_metric.py b/ppocr/metrics/vqa_token_ser_metric.py new file mode 100644 index 0000000..3afcb05 --- /dev/null +++ b/ppocr/metrics/vqa_token_ser_metric.py @@ -0,0 +1,48 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import paddle + +__all__ = ["VQASerTokenMetric"] + + +class VQASerTokenMetric(object): + def __init__(self, main_indicator="hmean", **kwargs): + self.main_indicator = main_indicator + self.reset() + + def __call__(self, preds, batch, **kwargs): + preds, labels = preds + self.pred_list.extend(preds) + self.gt_list.extend(labels) + + def get_metric(self): + from seqeval.metrics import f1_score, precision_score, recall_score + + metrics = { + "precision": precision_score(self.gt_list, self.pred_list), + "recall": recall_score(self.gt_list, self.pred_list), + "hmean": f1_score(self.gt_list, self.pred_list), + } + self.reset() + return metrics + + def reset(self): + self.pred_list = [] + self.gt_list = [] diff --git a/ppocr/modeling/architectures/__init__.py b/ppocr/modeling/architectures/__init__.py new file mode 100755 index 0000000..65b6318 --- /dev/null +++ b/ppocr/modeling/architectures/__init__.py @@ -0,0 +1,145 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy +import importlib + +from paddle.jit import to_static +from paddle.static import InputSpec + +from .base_model import BaseModel +from .distillation_model import DistillationModel + +__all__ = ["build_model", "apply_to_static"] + + +def build_model(config): + config = copy.deepcopy(config) + if not "name" in config: + arch = BaseModel(config) + else: + name = config.pop("name") + mod = importlib.import_module(__name__) + arch = getattr(mod, name)(config) + return arch + + +def apply_to_static(model, config, logger): + if config["Global"].get("to_static", False) is not True: + return model + assert ( + "d2s_train_image_shape" in config["Global"] + ), "d2s_train_image_shape must be assigned for static training mode..." + supported_list = [ + "DB", + "SVTR_LCNet", + "TableMaster", + "LayoutXLM", + "SLANet", + "SVTR", + "SVTR_HGNet", + "LaTeXOCR", + "UniMERNet", + "PP-FormulaNet-S", + "PP-FormulaNet-L", + ] + if config["Architecture"]["algorithm"] in ["Distillation"]: + algo = list(config["Architecture"]["Models"].values())[0]["algorithm"] + else: + algo = config["Architecture"]["algorithm"] + assert ( + algo in supported_list + ), f"algorithms that supports static training must in in {supported_list} but got {algo}" + + specs = [ + InputSpec([None] + config["Global"]["d2s_train_image_shape"], dtype="float32") + ] + + if algo == "SVTR_LCNet": + specs.append( + [ + InputSpec([None, config["Global"]["max_text_length"]], dtype="int64"), + InputSpec([None, config["Global"]["max_text_length"]], dtype="int64"), + InputSpec([None], dtype="int64"), + InputSpec([None], dtype="float64"), + ] + ) + elif algo == "TableMaster": + specs.append( + [ + InputSpec([None, config["Global"]["max_text_length"]], dtype="int64"), + InputSpec( + [None, config["Global"]["max_text_length"], 4], dtype="float32" + ), + InputSpec( + [None, config["Global"]["max_text_length"], 1], dtype="float32" + ), + InputSpec([None, 6], dtype="float32"), + ] + ) + elif algo == "LayoutXLM": + specs = [ + [ + InputSpec(shape=[None, 512], dtype="int64"), # input_ids + InputSpec(shape=[None, 512, 4], dtype="int64"), # bbox + InputSpec(shape=[None, 512], dtype="int64"), # attention_mask + InputSpec(shape=[None, 512], dtype="int64"), # token_type_ids + InputSpec(shape=[None, 3, 224, 224], dtype="float32"), # image + InputSpec(shape=[None, 512], dtype="int64"), # label + ] + ] + elif algo == "SLANet": + specs.append( + [ + InputSpec( + [None, config["Global"]["max_text_length"] + 2], dtype="int64" + ), + InputSpec( + [None, config["Global"]["max_text_length"] + 2, 4], dtype="float32" + ), + InputSpec( + [None, config["Global"]["max_text_length"] + 2, 1], dtype="float32" + ), + InputSpec([None], dtype="int64"), + InputSpec([None, 6], dtype="float64"), + ] + ) + elif algo == "SVTR": + specs.append( + [ + InputSpec([None, config["Global"]["max_text_length"]], dtype="int64"), + InputSpec([None], dtype="int64"), + ] + ) + elif algo == "LaTeXOCR": + specs = [ + [ + InputSpec(shape=[None, 1, None, None], dtype="float32"), + InputSpec(shape=[None, None], dtype="float32"), + InputSpec(shape=[None, None], dtype="float32"), + ] + ] + elif algo in ["UniMERNet", "PP-FormulaNet-S", "PP-FormulaNet-L"]: + specs = [ + [ + InputSpec( + [None] + config["Global"]["d2s_train_image_shape"], dtype="float32" + ), + InputSpec(shape=[None, None], dtype="float32"), + InputSpec(shape=[None, None], dtype="float32"), + ] + ] + model = to_static(model, input_spec=specs) + logger.info("Successfully to apply @to_static with specs: {}".format(specs)) + return model diff --git a/ppocr/modeling/architectures/base_model.py b/ppocr/modeling/architectures/base_model.py new file mode 100644 index 0000000..036f90c --- /dev/null +++ b/ppocr/modeling/architectures/base_model.py @@ -0,0 +1,117 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from paddle import nn +from ppocr.modeling.transforms import build_transform +from ppocr.modeling.backbones import build_backbone +from ppocr.modeling.necks import build_neck +from ppocr.modeling.heads import build_head + +__all__ = ["BaseModel"] + + +class BaseModel(nn.Layer): + def __init__(self, config): + """ + the module for OCR. + args: + config (dict): the super parameters for module. + """ + super(BaseModel, self).__init__() + in_channels = config.get("in_channels", 3) + model_type = config["model_type"] + # build transform, + # for rec, transform can be TPS,None + # for det and cls, transform should to be None, + # if you make model differently, you can use transform in det and cls + if "Transform" not in config or config["Transform"] is None: + self.use_transform = False + else: + self.use_transform = True + config["Transform"]["in_channels"] = in_channels + self.transform = build_transform(config["Transform"]) + in_channels = self.transform.out_channels + + # build backbone, backbone is need for del, rec and cls + if "Backbone" not in config or config["Backbone"] is None: + self.use_backbone = False + else: + self.use_backbone = True + config["Backbone"]["in_channels"] = in_channels + self.backbone = build_backbone(config["Backbone"], model_type) + in_channels = self.backbone.out_channels + + # build neck + # for rec, neck can be cnn,rnn or reshape(None) + # for det, neck can be FPN, BIFPN and so on. + # for cls, neck should be none + if "Neck" not in config or config["Neck"] is None: + self.use_neck = False + else: + self.use_neck = True + config["Neck"]["in_channels"] = in_channels + self.neck = build_neck(config["Neck"]) + in_channels = self.neck.out_channels + + # # build head, head is need for det, rec and cls + if "Head" not in config or config["Head"] is None: + self.use_head = False + else: + self.use_head = True + config["Head"]["in_channels"] = in_channels + self.head = build_head(config["Head"]) + + self.return_all_feats = config.get("return_all_feats", False) + + def forward(self, x, data=None): + y = dict() + if self.use_transform: + x = self.transform(x) + if self.use_backbone: + x = self.backbone(x) + if isinstance(x, dict): + y.update(x) + else: + y["backbone_out"] = x + final_name = "backbone_out" + if self.use_neck: + x = self.neck(x) + if isinstance(x, dict): + y.update(x) + else: + y["neck_out"] = x + final_name = "neck_out" + if self.use_head: + x = self.head(x, targets=data) + # for multi head, save ctc neck out for udml + if isinstance(x, dict) and "ctc_neck" in x.keys(): + y["neck_out"] = x["ctc_neck"] + y["head_out"] = x + elif isinstance(x, dict): + y.update(x) + else: + y["head_out"] = x + final_name = "head_out" + if self.return_all_feats: + if self.training: + return y + elif isinstance(x, dict): + return x + else: + return {final_name: x} + else: + return x diff --git a/ppocr/modeling/architectures/distillation_model.py b/ppocr/modeling/architectures/distillation_model.py new file mode 100644 index 0000000..2309d2e --- /dev/null +++ b/ppocr/modeling/architectures/distillation_model.py @@ -0,0 +1,60 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from paddle import nn +from ppocr.modeling.transforms import build_transform +from ppocr.modeling.backbones import build_backbone +from ppocr.modeling.necks import build_neck +from ppocr.modeling.heads import build_head +from .base_model import BaseModel +from ppocr.utils.save_load import load_pretrained_params + +__all__ = ["DistillationModel"] + + +class DistillationModel(nn.Layer): + def __init__(self, config): + """ + the module for OCR distillation. + args: + config (dict): the super parameters for module. + """ + super().__init__() + self.model_list = [] + self.model_name_list = [] + for key in config["Models"]: + model_config = config["Models"][key] + freeze_params = False + pretrained = None + if "freeze_params" in model_config: + freeze_params = model_config.pop("freeze_params") + if "pretrained" in model_config: + pretrained = model_config.pop("pretrained") + model = BaseModel(model_config) + if pretrained is not None: + load_pretrained_params(model, pretrained) + if freeze_params: + for param in model.parameters(): + param.trainable = False + self.model_list.append(self.add_sublayer(key, model)) + self.model_name_list.append(key) + + def forward(self, x, data=None): + result_dict = dict() + for idx, model_name in enumerate(self.model_name_list): + result_dict[model_name] = self.model_list[idx](x, data) + return result_dict diff --git a/ppocr/modeling/backbones/__init__.py b/ppocr/modeling/backbones/__init__.py new file mode 100755 index 0000000..69da404 --- /dev/null +++ b/ppocr/modeling/backbones/__init__.py @@ -0,0 +1,156 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = ["build_backbone"] + + +def build_backbone(config, model_type): + if model_type == "det" or model_type == "table": + from .det_mobilenet_v3 import MobileNetV3 + from .det_resnet import ResNet + from .det_resnet_vd import ResNet_vd + from .det_resnet_vd_sast import ResNet_SAST + from .det_pp_lcnet import PPLCNet + from .rec_lcnetv3 import PPLCNetV3 + from .rec_lcnetv4 import PPLCNetV4 + from .rec_hgnet import PPHGNet_small + from .rec_vit import ViT + from .det_pp_lcnet_v2 import PPLCNetV2_base + from .rec_repvit import RepSVTR_det + from .rec_vary_vit import Vary_VIT_B + from .rec_pphgnetv2 import PPHGNetV2_B4 + + support_dict = [ + "MobileNetV3", + "ResNet", + "ResNet_vd", + "ResNet_SAST", + "PPLCNet", + "PPLCNetV3", + "PPLCNetV4", + "PPHGNet_small", + "PPLCNetV2_base", + "RepSVTR_det", + "Vary_VIT_B", + "PPHGNetV2_B4", + ] + if model_type == "table": + from .table_master_resnet import TableResNetExtra + + support_dict.append("TableResNetExtra") + elif model_type == "rec" or model_type == "cls": + from .rec_mobilenet_v3 import MobileNetV3 + from .rec_resnet_vd import ResNet + from .rec_resnet_fpn import ResNetFPN + from .rec_mv1_enhance import MobileNetV1Enhance + from .rec_nrtr_mtb import MTB + from .rec_resnet_31 import ResNet31 + from .rec_resnet_32 import ResNet32 + from .rec_resnet_45 import ResNet45 + from .rec_resnet_aster import ResNet_ASTER + from .rec_micronet import MicroNet + from .rec_efficientb3_pren import EfficientNetb3_PREN + from .rec_svtrnet import SVTRNet + from .rec_vitstr import ViTSTR + from .rec_resnet_rfl import ResNetRFL + from .rec_densenet import DenseNet + from .rec_resnetv2 import ResNetV2 + from .rec_hybridvit import HybridTransformer + from .rec_donut_swin import DonutSwinModel + from .rec_shallow_cnn import ShallowCNN + from .rec_lcnetv3 import PPLCNetV3 + from .rec_lcnetv4 import PPLCNetV4 + from .rec_hgnet import PPHGNet_small + from .rec_vit_parseq import ViTParseQ + from .rec_repvit import RepSVTR + from .rec_svtrv2 import SVTRv2 + from .rec_vary_vit import Vary_VIT_B, Vary_VIT_B_Formula + from .rec_pphgnetv2 import ( + PPHGNetV2_B4, + PPHGNetV2_B4_Formula, + PPHGNetV2_B6_Formula, + ) + + support_dict = [ + "MobileNetV1Enhance", + "MobileNetV3", + "ResNet", + "ResNetFPN", + "MTB", + "ResNet31", + "ResNet45", + "ResNet_ASTER", + "MicroNet", + "EfficientNetb3_PREN", + "SVTRNet", + "ViTSTR", + "ResNet32", + "ResNetRFL", + "DenseNet", + "ShallowCNN", + "PPLCNetV3", + "PPLCNetV4", + "PPHGNet_small", + "ViTParseQ", + "ViT", + "RepSVTR", + "SVTRv2", + "ResNetV2", + "HybridTransformer", + "DonutSwinModel", + "Vary_VIT_B", + "PPHGNetV2_B4", + "PPHGNetV2_B4_Formula", + "PPHGNetV2_B6_Formula", + "Vary_VIT_B_Formula", + ] + elif model_type == "e2e": + from .e2e_resnet_vd_pg import ResNet + + support_dict = ["ResNet"] + elif model_type == "kie": + from .kie_unet_sdmgr import Kie_backbone + from .vqa_layoutlm import ( + LayoutLMForSer, + LayoutLMv2ForSer, + LayoutLMv2ForRe, + LayoutXLMForSer, + LayoutXLMForRe, + ) + + support_dict = [ + "Kie_backbone", + "LayoutLMForSer", + "LayoutLMv2ForSer", + "LayoutLMv2ForRe", + "LayoutXLMForSer", + "LayoutXLMForRe", + ] + elif model_type == "table": + from .table_resnet_vd import ResNet + from .table_mobilenet_v3 import MobileNetV3 + from .rec_vary_vit import Vary_VIT_B + + support_dict = ["ResNet", "MobileNetV3", "Vary_VIT_B"] + else: + raise NotImplementedError + + module_name = config.pop("name") + assert module_name in support_dict, Exception( + "when model typs is {}, backbone only support {}".format( + model_type, support_dict + ) + ) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/modeling/backbones/det_mobilenet_v3.py b/ppocr/modeling/backbones/det_mobilenet_v3.py new file mode 100755 index 0000000..5ff08c6 --- /dev/null +++ b/ppocr/modeling/backbones/det_mobilenet_v3.py @@ -0,0 +1,289 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr +from ppocr.modeling.backbones.rec_hgnet import MeanPool2D + +__all__ = ["MobileNetV3"] + + +def make_divisible(v, divisor=8, min_value=None): + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class MobileNetV3(nn.Layer): + def __init__( + self, in_channels=3, model_name="large", scale=0.5, disable_se=False, **kwargs + ): + """ + the MobilenetV3 backbone network for detection module. + Args: + params(dict): the super parameters for build network + """ + super(MobileNetV3, self).__init__() + + self.disable_se = disable_se + + if model_name == "large": + cfg = [ + # k, exp, c, se, nl, s, + [3, 16, 16, False, "relu", 1], + [3, 64, 24, False, "relu", 2], + [3, 72, 24, False, "relu", 1], + [5, 72, 40, True, "relu", 2], + [5, 120, 40, True, "relu", 1], + [5, 120, 40, True, "relu", 1], + [3, 240, 80, False, "hardswish", 2], + [3, 200, 80, False, "hardswish", 1], + [3, 184, 80, False, "hardswish", 1], + [3, 184, 80, False, "hardswish", 1], + [3, 480, 112, True, "hardswish", 1], + [3, 672, 112, True, "hardswish", 1], + [5, 672, 160, True, "hardswish", 2], + [5, 960, 160, True, "hardswish", 1], + [5, 960, 160, True, "hardswish", 1], + ] + cls_ch_squeeze = 960 + elif model_name == "small": + cfg = [ + # k, exp, c, se, nl, s, + [3, 16, 16, True, "relu", 2], + [3, 72, 24, False, "relu", 2], + [3, 88, 24, False, "relu", 1], + [5, 96, 40, True, "hardswish", 2], + [5, 240, 40, True, "hardswish", 1], + [5, 240, 40, True, "hardswish", 1], + [5, 120, 48, True, "hardswish", 1], + [5, 144, 48, True, "hardswish", 1], + [5, 288, 96, True, "hardswish", 2], + [5, 576, 96, True, "hardswish", 1], + [5, 576, 96, True, "hardswish", 1], + ] + cls_ch_squeeze = 576 + else: + raise NotImplementedError( + "mode[" + model_name + "_model] is not implemented!" + ) + + supported_scale = [0.35, 0.5, 0.75, 1.0, 1.25] + assert ( + scale in supported_scale + ), "supported scale are {} but input scale is {}".format(supported_scale, scale) + inplanes = 16 + # conv1 + self.conv = ConvBNLayer( + in_channels=in_channels, + out_channels=make_divisible(inplanes * scale), + kernel_size=3, + stride=2, + padding=1, + groups=1, + if_act=True, + act="hardswish", + ) + + self.stages = [] + self.out_channels = [] + block_list = [] + i = 0 + inplanes = make_divisible(inplanes * scale) + for k, exp, c, se, nl, s in cfg: + se = se and not self.disable_se + start_idx = 2 if model_name == "large" else 0 + if s == 2 and i > start_idx: + self.out_channels.append(inplanes) + self.stages.append(nn.Sequential(*block_list)) + block_list = [] + block_list.append( + ResidualUnit( + in_channels=inplanes, + mid_channels=make_divisible(scale * exp), + out_channels=make_divisible(scale * c), + kernel_size=k, + stride=s, + use_se=se, + act=nl, + ) + ) + inplanes = make_divisible(scale * c) + i += 1 + block_list.append( + ConvBNLayer( + in_channels=inplanes, + out_channels=make_divisible(scale * cls_ch_squeeze), + kernel_size=1, + stride=1, + padding=0, + groups=1, + if_act=True, + act="hardswish", + ) + ) + self.stages.append(nn.Sequential(*block_list)) + self.out_channels.append(make_divisible(scale * cls_ch_squeeze)) + for i, stage in enumerate(self.stages): + self.add_sublayer(sublayer=stage, name="stage{}".format(i)) + + def forward(self, x): + x = self.conv(x) + out_list = [] + for stage in self.stages: + x = stage(x) + out_list.append(x) + return out_list + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride, + padding, + groups=1, + if_act=True, + act=None, + ): + super(ConvBNLayer, self).__init__() + self.if_act = if_act + self.act = act + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + groups=groups, + bias_attr=False, + ) + + self.bn = nn.BatchNorm(num_channels=out_channels, act=None) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + if self.if_act: + if self.act == "relu": + x = F.relu(x) + elif self.act == "hardswish": + x = F.hardswish(x) + else: + print( + "The activation function({}) is selected incorrectly.".format( + self.act + ) + ) + exit() + return x + + +class ResidualUnit(nn.Layer): + def __init__( + self, + in_channels, + mid_channels, + out_channels, + kernel_size, + stride, + use_se, + act=None, + ): + super(ResidualUnit, self).__init__() + self.if_shortcut = stride == 1 and in_channels == out_channels + self.if_se = use_se + + self.expand_conv = ConvBNLayer( + in_channels=in_channels, + out_channels=mid_channels, + kernel_size=1, + stride=1, + padding=0, + if_act=True, + act=act, + ) + self.bottleneck_conv = ConvBNLayer( + in_channels=mid_channels, + out_channels=mid_channels, + kernel_size=kernel_size, + stride=stride, + padding=int((kernel_size - 1) // 2), + groups=mid_channels, + if_act=True, + act=act, + ) + if self.if_se: + self.mid_se = SEModule(mid_channels) + self.linear_conv = ConvBNLayer( + in_channels=mid_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + padding=0, + if_act=False, + act=None, + ) + + def forward(self, inputs): + x = self.expand_conv(inputs) + x = self.bottleneck_conv(x) + if self.if_se: + x = self.mid_se(x) + x = self.linear_conv(x) + if self.if_shortcut: + x = paddle.add(inputs, x) + return x + + +class SEModule(nn.Layer): + def __init__(self, in_channels, reduction=4): + super(SEModule, self).__init__() + if "npu" in paddle.device.get_device(): + self.avg_pool = MeanPool2D(1, 1) + else: + self.avg_pool = nn.AdaptiveAvgPool2D(1) + self.conv1 = nn.Conv2D( + in_channels=in_channels, + out_channels=in_channels // reduction, + kernel_size=1, + stride=1, + padding=0, + ) + self.conv2 = nn.Conv2D( + in_channels=in_channels // reduction, + out_channels=in_channels, + kernel_size=1, + stride=1, + padding=0, + ) + + def forward(self, inputs): + outputs = self.avg_pool(inputs) + outputs = self.conv1(outputs) + outputs = F.relu(outputs) + outputs = self.conv2(outputs) + outputs = F.hardsigmoid(outputs, slope=0.2, offset=0.5) + return inputs * outputs diff --git a/ppocr/modeling/backbones/det_pp_lcnet.py b/ppocr/modeling/backbones/det_pp_lcnet.py new file mode 100644 index 0000000..bf557a4 --- /dev/null +++ b/ppocr/modeling/backbones/det_pp_lcnet.py @@ -0,0 +1,274 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function + +import os +import paddle +import paddle.nn as nn +from paddle import ParamAttr +from paddle.nn import AdaptiveAvgPool2D, BatchNorm, Conv2D, Dropout, Linear +from paddle.regularizer import L2Decay +from paddle.nn.initializer import KaimingNormal +from paddle.utils.download import get_path_from_url + +MODEL_URLS = { + "PPLCNet_x0.25": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_25_pretrained.pdparams", + "PPLCNet_x0.35": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_35_pretrained.pdparams", + "PPLCNet_x0.5": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_5_pretrained.pdparams", + "PPLCNet_x0.75": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x0_75_pretrained.pdparams", + "PPLCNet_x1.0": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x1_0_pretrained.pdparams", + "PPLCNet_x1.5": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x1_5_pretrained.pdparams", + "PPLCNet_x2.0": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x2_0_pretrained.pdparams", + "PPLCNet_x2.5": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNet_x2_5_pretrained.pdparams", +} + +MODEL_STAGES_PATTERN = { + "PPLCNet": ["blocks2", "blocks3", "blocks4", "blocks5", "blocks6"] +} + +__all__ = list(MODEL_URLS.keys()) + +# Each element(list) represents a depthwise block, which is composed of k, in_c, out_c, s, use_se. +# k: kernel_size +# in_c: input channel number in depthwise block +# out_c: output channel number in depthwise block +# s: stride in depthwise block +# use_se: whether to use SE block + +NET_CONFIG = { + "blocks2": + # k, in_c, out_c, s, use_se + [[3, 16, 32, 1, False]], + "blocks3": [[3, 32, 64, 2, False], [3, 64, 64, 1, False]], + "blocks4": [[3, 64, 128, 2, False], [3, 128, 128, 1, False]], + "blocks5": [ + [3, 128, 256, 2, False], + [5, 256, 256, 1, False], + [5, 256, 256, 1, False], + [5, 256, 256, 1, False], + [5, 256, 256, 1, False], + [5, 256, 256, 1, False], + ], + "blocks6": [[5, 256, 512, 2, True], [5, 512, 512, 1, True]], +} + + +def make_divisible(v, divisor=8, min_value=None): + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class ConvBNLayer(nn.Layer): + def __init__(self, num_channels, filter_size, num_filters, stride, num_groups=1): + super().__init__() + + self.conv = Conv2D( + in_channels=num_channels, + out_channels=num_filters, + kernel_size=filter_size, + stride=stride, + padding=(filter_size - 1) // 2, + groups=num_groups, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False, + ) + + self.bn = BatchNorm( + num_filters, + param_attr=ParamAttr(regularizer=L2Decay(0.0)), + bias_attr=ParamAttr(regularizer=L2Decay(0.0)), + ) + self.hardswish = nn.Hardswish() + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + x = self.hardswish(x) + return x + + +class DepthwiseSeparable(nn.Layer): + def __init__(self, num_channels, num_filters, stride, dw_size=3, use_se=False): + super().__init__() + self.use_se = use_se + self.dw_conv = ConvBNLayer( + num_channels=num_channels, + num_filters=num_channels, + filter_size=dw_size, + stride=stride, + num_groups=num_channels, + ) + if use_se: + self.se = SEModule(num_channels) + self.pw_conv = ConvBNLayer( + num_channels=num_channels, filter_size=1, num_filters=num_filters, stride=1 + ) + + def forward(self, x): + x = self.dw_conv(x) + if self.use_se: + x = self.se(x) + x = self.pw_conv(x) + return x + + +class SEModule(nn.Layer): + def __init__(self, channel, reduction=4): + super().__init__() + self.avg_pool = AdaptiveAvgPool2D(1) + self.conv1 = Conv2D( + in_channels=channel, + out_channels=channel // reduction, + kernel_size=1, + stride=1, + padding=0, + ) + self.relu = nn.ReLU() + self.conv2 = Conv2D( + in_channels=channel // reduction, + out_channels=channel, + kernel_size=1, + stride=1, + padding=0, + ) + self.hardsigmoid = nn.Hardsigmoid() + + def forward(self, x): + identity = x + x = self.avg_pool(x) + x = self.conv1(x) + x = self.relu(x) + x = self.conv2(x) + x = self.hardsigmoid(x) + x = paddle.multiply(x=identity, y=x) + return x + + +class PPLCNet(nn.Layer): + def __init__(self, in_channels=3, scale=1.0, pretrained=False, use_ssld=False): + super().__init__() + self.out_channels = [ + int(NET_CONFIG["blocks3"][-1][2] * scale), + int(NET_CONFIG["blocks4"][-1][2] * scale), + int(NET_CONFIG["blocks5"][-1][2] * scale), + int(NET_CONFIG["blocks6"][-1][2] * scale), + ] + self.scale = scale + + self.conv1 = ConvBNLayer( + num_channels=in_channels, + filter_size=3, + num_filters=make_divisible(16 * scale), + stride=2, + ) + + self.blocks2 = nn.Sequential( + *[ + DepthwiseSeparable( + num_channels=make_divisible(in_c * scale), + num_filters=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se, + ) + for i, (k, in_c, out_c, s, se) in enumerate(NET_CONFIG["blocks2"]) + ] + ) + + self.blocks3 = nn.Sequential( + *[ + DepthwiseSeparable( + num_channels=make_divisible(in_c * scale), + num_filters=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se, + ) + for i, (k, in_c, out_c, s, se) in enumerate(NET_CONFIG["blocks3"]) + ] + ) + + self.blocks4 = nn.Sequential( + *[ + DepthwiseSeparable( + num_channels=make_divisible(in_c * scale), + num_filters=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se, + ) + for i, (k, in_c, out_c, s, se) in enumerate(NET_CONFIG["blocks4"]) + ] + ) + + self.blocks5 = nn.Sequential( + *[ + DepthwiseSeparable( + num_channels=make_divisible(in_c * scale), + num_filters=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se, + ) + for i, (k, in_c, out_c, s, se) in enumerate(NET_CONFIG["blocks5"]) + ] + ) + + self.blocks6 = nn.Sequential( + *[ + DepthwiseSeparable( + num_channels=make_divisible(in_c * scale), + num_filters=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se, + ) + for i, (k, in_c, out_c, s, se) in enumerate(NET_CONFIG["blocks6"]) + ] + ) + + if pretrained: + self._load_pretrained( + MODEL_URLS["PPLCNet_x{}".format(scale)], use_ssld=use_ssld + ) + + def forward(self, x): + outs = [] + x = self.conv1(x) + x = self.blocks2(x) + x = self.blocks3(x) + outs.append(x) + x = self.blocks4(x) + outs.append(x) + x = self.blocks5(x) + outs.append(x) + x = self.blocks6(x) + outs.append(x) + return outs + + def _load_pretrained(self, pretrained_url, use_ssld=False): + if use_ssld: + pretrained_url = pretrained_url.replace("_pretrained", "_ssld_pretrained") + print(pretrained_url) + local_weight_path = get_path_from_url( + pretrained_url, os.path.expanduser("~/.paddleclas/weights") + ) + param_state_dict = paddle.load(local_weight_path) + self.set_dict(param_state_dict) + return diff --git a/ppocr/modeling/backbones/det_pp_lcnet_v2.py b/ppocr/modeling/backbones/det_pp_lcnet_v2.py new file mode 100644 index 0000000..5b5a568 --- /dev/null +++ b/ppocr/modeling/backbones/det_pp_lcnet_v2.py @@ -0,0 +1,358 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import, division, print_function +import os + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle import ParamAttr +from paddle.nn import AdaptiveAvgPool2D, BatchNorm2D, Conv2D, Dropout, Linear +from paddle.regularizer import L2Decay +from paddle.nn.initializer import KaimingNormal +from paddle.utils.download import get_path_from_url + +MODEL_URLS = { + "PPLCNetV2_small": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNetV2_small_ssld_pretrained.pdparams", + "PPLCNetV2_base": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNetV2_base_ssld_pretrained.pdparams", + "PPLCNetV2_large": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPLCNetV2_large_ssld_pretrained.pdparams", +} + +__all__ = list(MODEL_URLS.keys()) + +NET_CONFIG = { + # in_channels, kernel_size, split_pw, use_rep, use_se, use_shortcut + "stage1": [64, 3, False, False, False, False], + "stage2": [128, 3, False, False, False, False], + "stage3": [256, 5, True, True, True, False], + "stage4": [512, 5, False, True, False, True], +} + + +def make_divisible(v, divisor=8, min_value=None): + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class ConvBNLayer(nn.Layer): + def __init__( + self, in_channels, out_channels, kernel_size, stride, groups=1, use_act=True + ): + super().__init__() + self.use_act = use_act + self.conv = Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False, + ) + + self.bn = BatchNorm2D( + out_channels, + weight_attr=ParamAttr(regularizer=L2Decay(0.0)), + bias_attr=ParamAttr(regularizer=L2Decay(0.0)), + ) + if self.use_act: + self.act = nn.ReLU() + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + if self.use_act: + x = self.act(x) + return x + + +class SEModule(nn.Layer): + def __init__(self, channel, reduction=4): + super().__init__() + self.avg_pool = AdaptiveAvgPool2D(1) + self.conv1 = Conv2D( + in_channels=channel, + out_channels=channel // reduction, + kernel_size=1, + stride=1, + padding=0, + ) + self.relu = nn.ReLU() + self.conv2 = Conv2D( + in_channels=channel // reduction, + out_channels=channel, + kernel_size=1, + stride=1, + padding=0, + ) + self.hardsigmoid = nn.Sigmoid() + + def forward(self, x): + identity = x + x = self.avg_pool(x) + x = self.conv1(x) + x = self.relu(x) + x = self.conv2(x) + x = self.hardsigmoid(x) + x = paddle.multiply(x=identity, y=x) + return x + + +class RepDepthwiseSeparable(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride, + dw_size=3, + split_pw=False, + use_rep=False, + use_se=False, + use_shortcut=False, + ): + super().__init__() + self.in_channels = in_channels + self.out_channels = out_channels + self.is_repped = False + + self.dw_size = dw_size + self.split_pw = split_pw + self.use_rep = use_rep + self.use_se = use_se + self.use_shortcut = ( + True + if use_shortcut and stride == 1 and in_channels == out_channels + else False + ) + + if self.use_rep: + self.dw_conv_list = nn.LayerList() + for kernel_size in range(self.dw_size, 0, -2): + if kernel_size == 1 and stride != 1: + continue + dw_conv = ConvBNLayer( + in_channels=in_channels, + out_channels=in_channels, + kernel_size=kernel_size, + stride=stride, + groups=in_channels, + use_act=False, + ) + self.dw_conv_list.append(dw_conv) + self.dw_conv = nn.Conv2D( + in_channels=in_channels, + out_channels=in_channels, + kernel_size=dw_size, + stride=stride, + padding=(dw_size - 1) // 2, + groups=in_channels, + ) + else: + self.dw_conv = ConvBNLayer( + in_channels=in_channels, + out_channels=in_channels, + kernel_size=dw_size, + stride=stride, + groups=in_channels, + ) + + self.act = nn.ReLU() + + if use_se: + self.se = SEModule(in_channels) + + if self.split_pw: + pw_ratio = 0.5 + self.pw_conv_1 = ConvBNLayer( + in_channels=in_channels, + kernel_size=1, + out_channels=int(out_channels * pw_ratio), + stride=1, + ) + self.pw_conv_2 = ConvBNLayer( + in_channels=int(out_channels * pw_ratio), + kernel_size=1, + out_channels=out_channels, + stride=1, + ) + else: + self.pw_conv = ConvBNLayer( + in_channels=in_channels, + kernel_size=1, + out_channels=out_channels, + stride=1, + ) + + def forward(self, x): + if self.use_rep: + input_x = x + if self.is_repped: + x = self.act(self.dw_conv(x)) + else: + y = self.dw_conv_list[0](x) + for dw_conv in self.dw_conv_list[1:]: + y += dw_conv(x) + x = self.act(y) + else: + x = self.dw_conv(x) + + if self.use_se: + x = self.se(x) + if self.split_pw: + x = self.pw_conv_1(x) + x = self.pw_conv_2(x) + else: + x = self.pw_conv(x) + if self.use_shortcut: + x = x + input_x + return x + + def re_parameterize(self): + if self.use_rep: + self.is_repped = True + kernel, bias = self._get_equivalent_kernel_bias() + self.dw_conv.weight.set_value(kernel) + self.dw_conv.bias.set_value(bias) + + def _get_equivalent_kernel_bias(self): + kernel_sum = 0 + bias_sum = 0 + for dw_conv in self.dw_conv_list: + kernel, bias = self._fuse_bn_tensor(dw_conv) + kernel = self._pad_tensor(kernel, to_size=self.dw_size) + kernel_sum += kernel + bias_sum += bias + return kernel_sum, bias_sum + + def _fuse_bn_tensor(self, branch): + kernel = branch.conv.weight + running_mean = branch.bn._mean + running_var = branch.bn._variance + gamma = branch.bn.weight + beta = branch.bn.bias + eps = branch.bn._epsilon + std = (running_var + eps).sqrt() + t = (gamma / std).reshape((-1, 1, 1, 1)) + return kernel * t, beta - running_mean * gamma / std + + def _pad_tensor(self, tensor, to_size): + from_size = tensor.shape[-1] + if from_size == to_size: + return tensor + pad = (to_size - from_size) // 2 + return F.pad(tensor, [pad, pad, pad, pad]) + + +class PPLCNetV2(nn.Layer): + def __init__(self, scale, depths, out_indx=[1, 2, 3, 4], **kwargs): + super().__init__(**kwargs) + self.scale = scale + self.out_channels = [ + # int(NET_CONFIG["blocks3"][-1][2] * scale), + int(NET_CONFIG["stage1"][0] * scale * 2), + int(NET_CONFIG["stage2"][0] * scale * 2), + int(NET_CONFIG["stage3"][0] * scale * 2), + int(NET_CONFIG["stage4"][0] * scale * 2), + ] + self.stem = nn.Sequential( + *[ + ConvBNLayer( + in_channels=3, + kernel_size=3, + out_channels=make_divisible(32 * scale), + stride=2, + ), + RepDepthwiseSeparable( + in_channels=make_divisible(32 * scale), + out_channels=make_divisible(64 * scale), + stride=1, + dw_size=3, + ), + ] + ) + self.out_indx = out_indx + # stages + self.stages = nn.LayerList() + for depth_idx, k in enumerate(NET_CONFIG): + ( + in_channels, + kernel_size, + split_pw, + use_rep, + use_se, + use_shortcut, + ) = NET_CONFIG[k] + self.stages.append( + nn.Sequential( + *[ + RepDepthwiseSeparable( + in_channels=make_divisible( + (in_channels if i == 0 else in_channels * 2) * scale + ), + out_channels=make_divisible(in_channels * 2 * scale), + stride=2 if i == 0 else 1, + dw_size=kernel_size, + split_pw=split_pw, + use_rep=use_rep, + use_se=use_se, + use_shortcut=use_shortcut, + ) + for i in range(depths[depth_idx]) + ] + ) + ) + + # if pretrained: + self._load_pretrained(MODEL_URLS["PPLCNetV2_base"], use_ssld=True) + + def forward(self, x): + x = self.stem(x) + i = 1 + outs = [] + for stage in self.stages: + x = stage(x) + if i in self.out_indx: + outs.append(x) + i += 1 + return outs + + def _load_pretrained(self, pretrained_url, use_ssld=False): + print(pretrained_url) + local_weight_path = get_path_from_url( + pretrained_url, os.path.expanduser("~/.paddleclas/weights") + ) + param_state_dict = paddle.load(local_weight_path) + self.set_dict(param_state_dict) + print("load pretrain ssd success!") + return + + +def PPLCNetV2_base(in_channels=3, **kwargs): + """ + PPLCNetV2_base + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `PPLCNetV2_base` model depends on args. + """ + model = PPLCNetV2(scale=1.0, depths=[2, 2, 6, 2], **kwargs) + return model diff --git a/ppocr/modeling/backbones/det_resnet.py b/ppocr/modeling/backbones/det_resnet.py new file mode 100644 index 0000000..3842829 --- /dev/null +++ b/ppocr/modeling/backbones/det_resnet.py @@ -0,0 +1,235 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F +from paddle.nn import Conv2D, BatchNorm, Linear, Dropout +from paddle.nn import AdaptiveAvgPool2D, MaxPool2D, AvgPool2D +from paddle.nn.initializer import Uniform + +import math + +from paddle.vision.ops import DeformConv2D +from paddle.regularizer import L2Decay +from paddle.nn.initializer import Normal, Constant, XavierUniform +from .det_resnet_vd import DeformableConvV2, ConvBNLayer + + +class BottleneckBlock(nn.Layer): + def __init__(self, num_channels, num_filters, stride, shortcut=True, is_dcn=False): + super(BottleneckBlock, self).__init__() + + self.conv0 = ConvBNLayer( + in_channels=num_channels, + out_channels=num_filters, + kernel_size=1, + act="relu", + ) + self.conv1 = ConvBNLayer( + in_channels=num_filters, + out_channels=num_filters, + kernel_size=3, + stride=stride, + act="relu", + is_dcn=is_dcn, + dcn_groups=1, + ) + self.conv2 = ConvBNLayer( + in_channels=num_filters, + out_channels=num_filters * 4, + kernel_size=1, + act=None, + ) + + if not shortcut: + self.short = ConvBNLayer( + in_channels=num_channels, + out_channels=num_filters * 4, + kernel_size=1, + stride=stride, + ) + + self.shortcut = shortcut + + self._num_channels_out = num_filters * 4 + + def forward(self, inputs): + y = self.conv0(inputs) + conv1 = self.conv1(y) + conv2 = self.conv2(conv1) + + if self.shortcut: + short = inputs + else: + short = self.short(inputs) + + y = paddle.add(x=short, y=conv2) + y = F.relu(y) + return y + + +class BasicBlock(nn.Layer): + def __init__(self, num_channels, num_filters, stride, shortcut=True, name=None): + super(BasicBlock, self).__init__() + self.stride = stride + self.conv0 = ConvBNLayer( + in_channels=num_channels, + out_channels=num_filters, + kernel_size=3, + stride=stride, + act="relu", + ) + self.conv1 = ConvBNLayer( + in_channels=num_filters, out_channels=num_filters, kernel_size=3, act=None + ) + + if not shortcut: + self.short = ConvBNLayer( + in_channels=num_channels, + out_channels=num_filters, + kernel_size=1, + stride=stride, + ) + + self.shortcut = shortcut + + def forward(self, inputs): + y = self.conv0(inputs) + conv1 = self.conv1(y) + + if self.shortcut: + short = inputs + else: + short = self.short(inputs) + y = paddle.add(x=short, y=conv1) + y = F.relu(y) + return y + + +class ResNet(nn.Layer): + def __init__(self, in_channels=3, layers=50, out_indices=None, dcn_stage=None): + super(ResNet, self).__init__() + + self.layers = layers + self.input_image_channel = in_channels + + supported_layers = [18, 34, 50, 101, 152] + assert ( + layers in supported_layers + ), "supported layers are {} but input layer is {}".format( + supported_layers, layers + ) + + if layers == 18: + depth = [2, 2, 2, 2] + elif layers == 34 or layers == 50: + depth = [3, 4, 6, 3] + elif layers == 101: + depth = [3, 4, 23, 3] + elif layers == 152: + depth = [3, 8, 36, 3] + num_channels = [64, 256, 512, 1024] if layers >= 50 else [64, 64, 128, 256] + num_filters = [64, 128, 256, 512] + + self.dcn_stage = ( + dcn_stage if dcn_stage is not None else [False, False, False, False] + ) + self.out_indices = out_indices if out_indices is not None else [0, 1, 2, 3] + + self.conv = ConvBNLayer( + in_channels=self.input_image_channel, + out_channels=64, + kernel_size=7, + stride=2, + act="relu", + ) + self.pool2d_max = MaxPool2D( + kernel_size=3, + stride=2, + padding=1, + ) + + self.stages = [] + self.out_channels = [] + if layers >= 50: + for block in range(len(depth)): + shortcut = False + block_list = [] + is_dcn = self.dcn_stage[block] + for i in range(depth[block]): + if layers in [101, 152] and block == 2: + if i == 0: + conv_name = "res" + str(block + 2) + "a" + else: + conv_name = "res" + str(block + 2) + "b" + str(i) + else: + conv_name = "res" + str(block + 2) + chr(97 + i) + bottleneck_block = self.add_sublayer( + conv_name, + BottleneckBlock( + num_channels=( + num_channels[block] + if i == 0 + else num_filters[block] * 4 + ), + num_filters=num_filters[block], + stride=2 if i == 0 and block != 0 else 1, + shortcut=shortcut, + is_dcn=is_dcn, + ), + ) + block_list.append(bottleneck_block) + shortcut = True + if block in self.out_indices: + self.out_channels.append(num_filters[block] * 4) + self.stages.append(nn.Sequential(*block_list)) + else: + for block in range(len(depth)): + shortcut = False + block_list = [] + for i in range(depth[block]): + conv_name = "res" + str(block + 2) + chr(97 + i) + basic_block = self.add_sublayer( + conv_name, + BasicBlock( + num_channels=( + num_channels[block] if i == 0 else num_filters[block] + ), + num_filters=num_filters[block], + stride=2 if i == 0 and block != 0 else 1, + shortcut=shortcut, + ), + ) + block_list.append(basic_block) + shortcut = True + if block in self.out_indices: + self.out_channels.append(num_filters[block]) + self.stages.append(nn.Sequential(*block_list)) + + def forward(self, inputs): + y = self.conv(inputs) + y = self.pool2d_max(y) + out = [] + for i, block in enumerate(self.stages): + y = block(y) + if i in self.out_indices: + out.append(y) + return out diff --git a/ppocr/modeling/backbones/det_resnet_vd.py b/ppocr/modeling/backbones/det_resnet_vd.py new file mode 100644 index 0000000..070ba3c --- /dev/null +++ b/ppocr/modeling/backbones/det_resnet_vd.py @@ -0,0 +1,369 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F + +from paddle.vision.ops import DeformConv2D +from paddle.regularizer import L2Decay +from paddle.nn.initializer import Normal, Constant, XavierUniform + +__all__ = ["ResNet_vd", "ConvBNLayer", "DeformableConvV2"] + + +class DeformableConvV2(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + weight_attr=None, + bias_attr=None, + lr_scale=1, + regularizer=None, + skip_quant=False, + dcn_bias_regularizer=L2Decay(0.0), + dcn_bias_lr_scale=2.0, + ): + super(DeformableConvV2, self).__init__() + self.offset_channel = 2 * kernel_size**2 * groups + self.mask_channel = kernel_size**2 * groups + + if bias_attr: + # in FCOS-DCN head, specifically need learning_rate and regularizer + dcn_bias_attr = ParamAttr( + initializer=Constant(value=0), + regularizer=dcn_bias_regularizer, + learning_rate=dcn_bias_lr_scale, + ) + else: + # in ResNet backbone, do not need bias + dcn_bias_attr = False + self.conv_dcn = DeformConv2D( + in_channels, + out_channels, + kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2 * dilation, + dilation=dilation, + deformable_groups=groups, + weight_attr=weight_attr, + bias_attr=dcn_bias_attr, + ) + + if lr_scale == 1 and regularizer is None: + offset_bias_attr = ParamAttr(initializer=Constant(0.0)) + else: + offset_bias_attr = ParamAttr( + initializer=Constant(0.0), + learning_rate=lr_scale, + regularizer=regularizer, + ) + self.conv_offset = nn.Conv2D( + in_channels, + groups * 3 * kernel_size**2, + kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + weight_attr=ParamAttr(initializer=Constant(0.0)), + bias_attr=offset_bias_attr, + ) + if skip_quant: + self.conv_offset.skip_quant = True + + def forward(self, x): + offset_mask = self.conv_offset(x) + offset, mask = paddle.split( + offset_mask, + num_or_sections=[self.offset_channel, self.mask_channel], + axis=1, + ) + mask = F.sigmoid(mask) + y = self.conv_dcn(x, offset, mask=mask) + return y + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + dcn_groups=1, + is_vd_mode=False, + act=None, + is_dcn=False, + ): + super(ConvBNLayer, self).__init__() + + self.is_vd_mode = is_vd_mode + self._pool2d_avg = nn.AvgPool2D( + kernel_size=2, stride=2, padding=0, ceil_mode=True + ) + if not is_dcn: + self._conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=groups, + bias_attr=False, + ) + else: + self._conv = DeformableConvV2( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=dcn_groups, # groups, + bias_attr=False, + ) + self._batch_norm = nn.BatchNorm(out_channels, act=act) + + def forward(self, inputs): + if self.is_vd_mode: + inputs = self._pool2d_avg(inputs) + y = self._conv(inputs) + y = self._batch_norm(y) + return y + + +class BottleneckBlock(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride, + shortcut=True, + if_first=False, + is_dcn=False, + ): + super(BottleneckBlock, self).__init__() + + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + act="relu", + ) + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + act="relu", + is_dcn=is_dcn, + dcn_groups=2, + ) + self.conv2 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels * 4, + kernel_size=1, + act=None, + ) + + if not shortcut: + self.short = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels * 4, + kernel_size=1, + stride=1, + is_vd_mode=False if if_first else True, + ) + + self.shortcut = shortcut + + def forward(self, inputs): + y = self.conv0(inputs) + conv1 = self.conv1(y) + conv2 = self.conv2(conv1) + + if self.shortcut: + short = inputs + else: + short = self.short(inputs) + y = paddle.add(x=short, y=conv2) + y = F.relu(y) + return y + + +class BasicBlock(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride, + shortcut=True, + if_first=False, + ): + super(BasicBlock, self).__init__() + self.stride = stride + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + act="relu", + ) + self.conv1 = ConvBNLayer( + in_channels=out_channels, out_channels=out_channels, kernel_size=3, act=None + ) + + if not shortcut: + self.short = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + is_vd_mode=False if if_first else True, + ) + + self.shortcut = shortcut + + def forward(self, inputs): + y = self.conv0(inputs) + conv1 = self.conv1(y) + + if self.shortcut: + short = inputs + else: + short = self.short(inputs) + y = paddle.add(x=short, y=conv1) + y = F.relu(y) + return y + + +class ResNet_vd(nn.Layer): + def __init__( + self, in_channels=3, layers=50, dcn_stage=None, out_indices=None, **kwargs + ): + super(ResNet_vd, self).__init__() + + self.layers = layers + supported_layers = [18, 34, 50, 101, 152, 200] + assert ( + layers in supported_layers + ), "supported layers are {} but input layer is {}".format( + supported_layers, layers + ) + + if layers == 18: + depth = [2, 2, 2, 2] + elif layers == 34 or layers == 50: + depth = [3, 4, 6, 3] + elif layers == 101: + depth = [3, 4, 23, 3] + elif layers == 152: + depth = [3, 8, 36, 3] + elif layers == 200: + depth = [3, 12, 48, 3] + num_channels = [64, 256, 512, 1024] if layers >= 50 else [64, 64, 128, 256] + num_filters = [64, 128, 256, 512] + + self.dcn_stage = ( + dcn_stage if dcn_stage is not None else [False, False, False, False] + ) + self.out_indices = out_indices if out_indices is not None else [0, 1, 2, 3] + + self.conv1_1 = ConvBNLayer( + in_channels=in_channels, + out_channels=32, + kernel_size=3, + stride=2, + act="relu", + ) + self.conv1_2 = ConvBNLayer( + in_channels=32, out_channels=32, kernel_size=3, stride=1, act="relu" + ) + self.conv1_3 = ConvBNLayer( + in_channels=32, out_channels=64, kernel_size=3, stride=1, act="relu" + ) + self.pool2d_max = nn.MaxPool2D(kernel_size=3, stride=2, padding=1) + + self.stages = [] + self.out_channels = [] + if layers >= 50: + for block in range(len(depth)): + block_list = [] + shortcut = False + is_dcn = self.dcn_stage[block] + for i in range(depth[block]): + bottleneck_block = self.add_sublayer( + "bb_%d_%d" % (block, i), + BottleneckBlock( + in_channels=( + num_channels[block] + if i == 0 + else num_filters[block] * 4 + ), + out_channels=num_filters[block], + stride=2 if i == 0 and block != 0 else 1, + shortcut=shortcut, + if_first=block == i == 0, + is_dcn=is_dcn, + ), + ) + shortcut = True + block_list.append(bottleneck_block) + if block in self.out_indices: + self.out_channels.append(num_filters[block] * 4) + self.stages.append(nn.Sequential(*block_list)) + else: + for block in range(len(depth)): + block_list = [] + shortcut = False + for i in range(depth[block]): + basic_block = self.add_sublayer( + "bb_%d_%d" % (block, i), + BasicBlock( + in_channels=( + num_channels[block] if i == 0 else num_filters[block] + ), + out_channels=num_filters[block], + stride=2 if i == 0 and block != 0 else 1, + shortcut=shortcut, + if_first=block == i == 0, + ), + ) + shortcut = True + block_list.append(basic_block) + if block in self.out_indices: + self.out_channels.append(num_filters[block]) + self.stages.append(nn.Sequential(*block_list)) + + def forward(self, inputs): + y = self.conv1_1(inputs) + y = self.conv1_2(y) + y = self.conv1_3(y) + y = self.pool2d_max(y) + out = [] + for i, block in enumerate(self.stages): + y = block(y) + if i in self.out_indices: + out.append(y) + return out diff --git a/ppocr/modeling/backbones/det_resnet_vd_sast.py b/ppocr/modeling/backbones/det_resnet_vd_sast.py new file mode 100644 index 0000000..6769dd9 --- /dev/null +++ b/ppocr/modeling/backbones/det_resnet_vd_sast.py @@ -0,0 +1,314 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F + +__all__ = ["ResNet_SAST"] + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + is_vd_mode=False, + act=None, + name=None, + ): + super(ConvBNLayer, self).__init__() + + self.is_vd_mode = is_vd_mode + self._pool2d_avg = nn.AvgPool2D( + kernel_size=2, stride=2, padding=0, ceil_mode=True + ) + self._conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + if name == "conv1": + bn_name = "bn_" + name + else: + bn_name = "bn" + name[3:] + self._batch_norm = nn.BatchNorm( + out_channels, + act=act, + param_attr=ParamAttr(name=bn_name + "_scale"), + bias_attr=ParamAttr(bn_name + "_offset"), + moving_mean_name=bn_name + "_mean", + moving_variance_name=bn_name + "_variance", + ) + + def forward(self, inputs): + if self.is_vd_mode: + inputs = self._pool2d_avg(inputs) + y = self._conv(inputs) + y = self._batch_norm(y) + return y + + +class BottleneckBlock(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride, + shortcut=True, + if_first=False, + name=None, + ): + super(BottleneckBlock, self).__init__() + + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + act="relu", + name=name + "_branch2a", + ) + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + act="relu", + name=name + "_branch2b", + ) + self.conv2 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels * 4, + kernel_size=1, + act=None, + name=name + "_branch2c", + ) + + if not shortcut: + self.short = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels * 4, + kernel_size=1, + stride=1, + is_vd_mode=False if if_first else True, + name=name + "_branch1", + ) + + self.shortcut = shortcut + + def forward(self, inputs): + y = self.conv0(inputs) + conv1 = self.conv1(y) + conv2 = self.conv2(conv1) + + if self.shortcut: + short = inputs + else: + short = self.short(inputs) + y = paddle.add(x=short, y=conv2) + y = F.relu(y) + return y + + +class BasicBlock(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride, + shortcut=True, + if_first=False, + name=None, + ): + super(BasicBlock, self).__init__() + self.stride = stride + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + act="relu", + name=name + "_branch2a", + ) + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, + act=None, + name=name + "_branch2b", + ) + + if not shortcut: + self.short = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + is_vd_mode=False if if_first else True, + name=name + "_branch1", + ) + + self.shortcut = shortcut + + def forward(self, inputs): + y = self.conv0(inputs) + conv1 = self.conv1(y) + + if self.shortcut: + short = inputs + else: + short = self.short(inputs) + y = paddle.add(x=short, y=conv1) + y = F.relu(y) + return y + + +class ResNet_SAST(nn.Layer): + def __init__(self, in_channels=3, layers=50, **kwargs): + super(ResNet_SAST, self).__init__() + + self.layers = layers + supported_layers = [18, 34, 50, 101, 152, 200] + assert ( + layers in supported_layers + ), "supported layers are {} but input layer is {}".format( + supported_layers, layers + ) + + if layers == 18: + depth = [2, 2, 2, 2] + elif layers == 34 or layers == 50: + # depth = [3, 4, 6, 3] + depth = [3, 4, 6, 3, 3] + elif layers == 101: + depth = [3, 4, 23, 3] + elif layers == 152: + depth = [3, 8, 36, 3] + elif layers == 200: + depth = [3, 12, 48, 3] + # num_channels = [64, 256, 512, + # 1024] if layers >= 50 else [64, 64, 128, 256] + # num_filters = [64, 128, 256, 512] + num_channels = ( + [64, 256, 512, 1024, 2048] if layers >= 50 else [64, 64, 128, 256] + ) + num_filters = [64, 128, 256, 512, 512] + + self.conv1_1 = ConvBNLayer( + in_channels=in_channels, + out_channels=32, + kernel_size=3, + stride=2, + act="relu", + name="conv1_1", + ) + self.conv1_2 = ConvBNLayer( + in_channels=32, + out_channels=32, + kernel_size=3, + stride=1, + act="relu", + name="conv1_2", + ) + self.conv1_3 = ConvBNLayer( + in_channels=32, + out_channels=64, + kernel_size=3, + stride=1, + act="relu", + name="conv1_3", + ) + self.pool2d_max = nn.MaxPool2D(kernel_size=3, stride=2, padding=1) + + self.stages = [] + self.out_channels = [3, 64] + if layers >= 50: + for block in range(len(depth)): + block_list = [] + shortcut = False + for i in range(depth[block]): + if layers in [101, 152] and block == 2: + if i == 0: + conv_name = "res" + str(block + 2) + "a" + else: + conv_name = "res" + str(block + 2) + "b" + str(i) + else: + conv_name = "res" + str(block + 2) + chr(97 + i) + bottleneck_block = self.add_sublayer( + "bb_%d_%d" % (block, i), + BottleneckBlock( + in_channels=( + num_channels[block] + if i == 0 + else num_filters[block] * 4 + ), + out_channels=num_filters[block], + stride=2 if i == 0 and block != 0 else 1, + shortcut=shortcut, + if_first=block == i == 0, + name=conv_name, + ), + ) + shortcut = True + block_list.append(bottleneck_block) + self.out_channels.append(num_filters[block] * 4) + self.stages.append(nn.Sequential(*block_list)) + else: + for block in range(len(depth)): + block_list = [] + shortcut = False + for i in range(depth[block]): + conv_name = "res" + str(block + 2) + chr(97 + i) + basic_block = self.add_sublayer( + "bb_%d_%d" % (block, i), + BasicBlock( + in_channels=( + num_channels[block] if i == 0 else num_filters[block] + ), + out_channels=num_filters[block], + stride=2 if i == 0 and block != 0 else 1, + shortcut=shortcut, + if_first=block == i == 0, + name=conv_name, + ), + ) + shortcut = True + block_list.append(basic_block) + self.out_channels.append(num_filters[block]) + self.stages.append(nn.Sequential(*block_list)) + + def forward(self, inputs): + out = [inputs] + y = self.conv1_1(inputs) + y = self.conv1_2(y) + y = self.conv1_3(y) + out.append(y) + y = self.pool2d_max(y) + for block in self.stages: + y = block(y) + out.append(y) + return out diff --git a/ppocr/modeling/backbones/e2e_resnet_vd_pg.py b/ppocr/modeling/backbones/e2e_resnet_vd_pg.py new file mode 100644 index 0000000..25738cd --- /dev/null +++ b/ppocr/modeling/backbones/e2e_resnet_vd_pg.py @@ -0,0 +1,292 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F + +__all__ = ["ResNet"] + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + is_vd_mode=False, + act=None, + name=None, + ): + super(ConvBNLayer, self).__init__() + + self.is_vd_mode = is_vd_mode + self._pool2d_avg = nn.AvgPool2D( + kernel_size=2, stride=2, padding=0, ceil_mode=True + ) + self._conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + if name == "conv1": + bn_name = "bn_" + name + else: + bn_name = "bn" + name[3:] + self._batch_norm = nn.BatchNorm( + out_channels, + act=act, + param_attr=ParamAttr(name=bn_name + "_scale"), + bias_attr=ParamAttr(bn_name + "_offset"), + moving_mean_name=bn_name + "_mean", + moving_variance_name=bn_name + "_variance", + ) + + def forward(self, inputs): + y = self._conv(inputs) + y = self._batch_norm(y) + return y + + +class BottleneckBlock(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride, + shortcut=True, + if_first=False, + name=None, + ): + super(BottleneckBlock, self).__init__() + + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + act="relu", + name=name + "_branch2a", + ) + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + act="relu", + name=name + "_branch2b", + ) + self.conv2 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels * 4, + kernel_size=1, + act=None, + name=name + "_branch2c", + ) + + if not shortcut: + self.short = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels * 4, + kernel_size=1, + stride=stride, + is_vd_mode=False if if_first else True, + name=name + "_branch1", + ) + + self.shortcut = shortcut + + def forward(self, inputs): + y = self.conv0(inputs) + conv1 = self.conv1(y) + conv2 = self.conv2(conv1) + + if self.shortcut: + short = inputs + else: + short = self.short(inputs) + y = paddle.add(x=short, y=conv2) + y = F.relu(y) + return y + + +class BasicBlock(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride, + shortcut=True, + if_first=False, + name=None, + ): + super(BasicBlock, self).__init__() + self.stride = stride + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + act="relu", + name=name + "_branch2a", + ) + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, + act=None, + name=name + "_branch2b", + ) + + if not shortcut: + self.short = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + is_vd_mode=False if if_first else True, + name=name + "_branch1", + ) + + self.shortcut = shortcut + + def forward(self, inputs): + y = self.conv0(inputs) + conv1 = self.conv1(y) + + if self.shortcut: + short = inputs + else: + short = self.short(inputs) + y = paddle.add(x=short, y=conv1) + y = F.relu(y) + return y + + +class ResNet(nn.Layer): + def __init__(self, in_channels=3, layers=50, **kwargs): + super(ResNet, self).__init__() + + self.layers = layers + supported_layers = [18, 34, 50, 101, 152, 200] + assert ( + layers in supported_layers + ), "supported layers are {} but input layer is {}".format( + supported_layers, layers + ) + + if layers == 18: + depth = [2, 2, 2, 2] + elif layers == 34 or layers == 50: + # depth = [3, 4, 6, 3] + depth = [3, 4, 6, 3, 3] + elif layers == 101: + depth = [3, 4, 23, 3] + elif layers == 152: + depth = [3, 8, 36, 3] + elif layers == 200: + depth = [3, 12, 48, 3] + num_channels = ( + [64, 256, 512, 1024, 2048] if layers >= 50 else [64, 64, 128, 256] + ) + num_filters = [64, 128, 256, 512, 512] + + self.conv1_1 = ConvBNLayer( + in_channels=in_channels, + out_channels=64, + kernel_size=7, + stride=2, + act="relu", + name="conv1_1", + ) + self.pool2d_max = nn.MaxPool2D(kernel_size=3, stride=2, padding=1) + + self.stages = [] + self.out_channels = [3, 64] + # num_filters = [64, 128, 256, 512, 512] + if layers >= 50: + for block in range(len(depth)): + block_list = [] + shortcut = False + for i in range(depth[block]): + if layers in [101, 152] and block == 2: + if i == 0: + conv_name = "res" + str(block + 2) + "a" + else: + conv_name = "res" + str(block + 2) + "b" + str(i) + else: + conv_name = "res" + str(block + 2) + chr(97 + i) + bottleneck_block = self.add_sublayer( + "bb_%d_%d" % (block, i), + BottleneckBlock( + in_channels=( + num_channels[block] + if i == 0 + else num_filters[block] * 4 + ), + out_channels=num_filters[block], + stride=2 if i == 0 and block != 0 else 1, + shortcut=shortcut, + if_first=block == i == 0, + name=conv_name, + ), + ) + shortcut = True + block_list.append(bottleneck_block) + self.out_channels.append(num_filters[block] * 4) + self.stages.append(nn.Sequential(*block_list)) + else: + for block in range(len(depth)): + block_list = [] + shortcut = False + for i in range(depth[block]): + conv_name = "res" + str(block + 2) + chr(97 + i) + basic_block = self.add_sublayer( + "bb_%d_%d" % (block, i), + BasicBlock( + in_channels=( + num_channels[block] if i == 0 else num_filters[block] + ), + out_channels=num_filters[block], + stride=2 if i == 0 and block != 0 else 1, + shortcut=shortcut, + if_first=block == i == 0, + name=conv_name, + ), + ) + shortcut = True + block_list.append(basic_block) + self.out_channels.append(num_filters[block]) + self.stages.append(nn.Sequential(*block_list)) + + def forward(self, inputs): + out = [inputs] + y = self.conv1_1(inputs) + out.append(y) + y = self.pool2d_max(y) + for block in self.stages: + y = block(y) + out.append(y) + return out diff --git a/ppocr/modeling/backbones/kie_unet_sdmgr.py b/ppocr/modeling/backbones/kie_unet_sdmgr.py new file mode 100644 index 0000000..7b0c2b5 --- /dev/null +++ b/ppocr/modeling/backbones/kie_unet_sdmgr.py @@ -0,0 +1,199 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +import numpy as np +import cv2 + +__all__ = ["Kie_backbone"] + + +class Encoder(nn.Layer): + def __init__(self, num_channels, num_filters): + super(Encoder, self).__init__() + self.conv1 = nn.Conv2D( + num_channels, + num_filters, + kernel_size=3, + stride=1, + padding=1, + bias_attr=False, + ) + self.bn1 = nn.BatchNorm(num_filters, act="relu") + + self.conv2 = nn.Conv2D( + num_filters, + num_filters, + kernel_size=3, + stride=1, + padding=1, + bias_attr=False, + ) + self.bn2 = nn.BatchNorm(num_filters, act="relu") + + self.pool = nn.MaxPool2D(kernel_size=3, stride=2, padding=1) + + def forward(self, inputs): + x = self.conv1(inputs) + x = self.bn1(x) + x = self.conv2(x) + x = self.bn2(x) + x_pooled = self.pool(x) + return x, x_pooled + + +class Decoder(nn.Layer): + def __init__(self, num_channels, num_filters): + super(Decoder, self).__init__() + + self.conv1 = nn.Conv2D( + num_channels, + num_filters, + kernel_size=3, + stride=1, + padding=1, + bias_attr=False, + ) + self.bn1 = nn.BatchNorm(num_filters, act="relu") + + self.conv2 = nn.Conv2D( + num_filters, + num_filters, + kernel_size=3, + stride=1, + padding=1, + bias_attr=False, + ) + self.bn2 = nn.BatchNorm(num_filters, act="relu") + + self.conv0 = nn.Conv2D( + num_channels, + num_filters, + kernel_size=1, + stride=1, + padding=0, + bias_attr=False, + ) + self.bn0 = nn.BatchNorm(num_filters, act="relu") + + def forward(self, inputs_prev, inputs): + x = self.conv0(inputs) + x = self.bn0(x) + x = paddle.nn.functional.interpolate( + x, scale_factor=2, mode="bilinear", align_corners=False + ) + x = paddle.concat([inputs_prev, x], axis=1) + x = self.conv1(x) + x = self.bn1(x) + x = self.conv2(x) + x = self.bn2(x) + return x + + +class UNet(nn.Layer): + def __init__(self): + super(UNet, self).__init__() + self.down1 = Encoder(num_channels=3, num_filters=16) + self.down2 = Encoder(num_channels=16, num_filters=32) + self.down3 = Encoder(num_channels=32, num_filters=64) + self.down4 = Encoder(num_channels=64, num_filters=128) + self.down5 = Encoder(num_channels=128, num_filters=256) + + self.up1 = Decoder(32, 16) + self.up2 = Decoder(64, 32) + self.up3 = Decoder(128, 64) + self.up4 = Decoder(256, 128) + self.out_channels = 16 + + def forward(self, inputs): + x1, _ = self.down1(inputs) + _, x2 = self.down2(x1) + _, x3 = self.down3(x2) + _, x4 = self.down4(x3) + _, x5 = self.down5(x4) + + x = self.up4(x4, x5) + x = self.up3(x3, x) + x = self.up2(x2, x) + x = self.up1(x1, x) + return x + + +class Kie_backbone(nn.Layer): + def __init__(self, in_channels, **kwargs): + super(Kie_backbone, self).__init__() + self.out_channels = 16 + self.img_feat = UNet() + self.maxpool = nn.MaxPool2D(kernel_size=7) + + def bbox2roi(self, bbox_list): + rois_list = [] + rois_num = [] + for img_id, bboxes in enumerate(bbox_list): + rois_num.append(bboxes.shape[0]) + rois_list.append(bboxes) + rois = paddle.concat(rois_list, 0) + rois_num = paddle.to_tensor(rois_num, dtype="int32") + return rois, rois_num + + def pre_process(self, img, relations, texts, gt_bboxes, tag, img_size): + img, relations, texts, gt_bboxes, tag, img_size = ( + img.numpy(), + relations.numpy(), + texts.numpy(), + gt_bboxes.numpy(), + tag.numpy().tolist(), + img_size.numpy(), + ) + temp_relations, temp_texts, temp_gt_bboxes = [], [], [] + h, w = int(np.max(img_size[:, 0])), int(np.max(img_size[:, 1])) + img = paddle.to_tensor(img[:, :, :h, :w]) + batch = len(tag) + for i in range(batch): + num, recoder_len = tag[i][0], tag[i][1] + temp_relations.append( + paddle.to_tensor(relations[i, :num, :num, :], dtype="float32") + ) + temp_texts.append( + paddle.to_tensor(texts[i, :num, :recoder_len], dtype="float32") + ) + temp_gt_bboxes.append( + paddle.to_tensor(gt_bboxes[i, :num, ...], dtype="float32") + ) + return img, temp_relations, temp_texts, temp_gt_bboxes + + def forward(self, inputs): + img = inputs[0] + relations, texts, gt_bboxes, tag, img_size = ( + inputs[1], + inputs[2], + inputs[3], + inputs[5], + inputs[-1], + ) + img, relations, texts, gt_bboxes = self.pre_process( + img, relations, texts, gt_bboxes, tag, img_size + ) + x = self.img_feat(img) + boxes, rois_num = self.bbox2roi(gt_bboxes) + feats = paddle.vision.ops.roi_align( + x, boxes, spatial_scale=1.0, output_size=7, boxes_num=rois_num + ) + feats = self.maxpool(feats).squeeze(-1).squeeze(-1) + return [relations, texts, feats] diff --git a/ppocr/modeling/backbones/rec_densenet.py b/ppocr/modeling/backbones/rec_densenet.py new file mode 100644 index 0000000..3e64584 --- /dev/null +++ b/ppocr/modeling/backbones/rec_densenet.py @@ -0,0 +1,150 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/LBH1024/CAN/models/densenet.py + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +import paddle.nn as nn +import paddle.nn.functional as F + + +class Bottleneck(nn.Layer): + def __init__(self, nChannels, growthRate, use_dropout): + super(Bottleneck, self).__init__() + interChannels = 4 * growthRate + self.bn1 = nn.BatchNorm2D(interChannels) + self.conv1 = nn.Conv2D( + nChannels, interChannels, kernel_size=1, bias_attr=None + ) # Xavier initialization + self.bn2 = nn.BatchNorm2D(growthRate) + self.conv2 = nn.Conv2D( + interChannels, growthRate, kernel_size=3, padding=1, bias_attr=None + ) # Xavier initialization + self.use_dropout = use_dropout + self.dropout = nn.Dropout(p=0.2) + + def forward(self, x): + out = F.relu(self.bn1(self.conv1(x))) + if self.use_dropout: + out = self.dropout(out) + out = F.relu(self.bn2(self.conv2(out))) + if self.use_dropout: + out = self.dropout(out) + out = paddle.concat([x, out], 1) + return out + + +class SingleLayer(nn.Layer): + def __init__(self, nChannels, growthRate, use_dropout): + super(SingleLayer, self).__init__() + self.bn1 = nn.BatchNorm2D(nChannels) + self.conv1 = nn.Conv2D( + nChannels, growthRate, kernel_size=3, padding=1, bias_attr=False + ) + + self.use_dropout = use_dropout + self.dropout = nn.Dropout(p=0.2) + + def forward(self, x): + out = self.conv1(F.relu(x)) + if self.use_dropout: + out = self.dropout(out) + + out = paddle.concat([x, out], 1) + return out + + +class Transition(nn.Layer): + def __init__(self, nChannels, out_channels, use_dropout): + super(Transition, self).__init__() + self.bn1 = nn.BatchNorm2D(out_channels) + self.conv1 = nn.Conv2D(nChannels, out_channels, kernel_size=1, bias_attr=False) + self.use_dropout = use_dropout + self.dropout = nn.Dropout(p=0.2) + + def forward(self, x): + out = F.relu(self.bn1(self.conv1(x))) + if self.use_dropout: + out = self.dropout(out) + out = F.avg_pool2d(out, 2, ceil_mode=True, exclusive=False) + return out + + +class DenseNet(nn.Layer): + def __init__( + self, growthRate, reduction, bottleneck, use_dropout, input_channel, **kwargs + ): + super(DenseNet, self).__init__() + + nDenseBlocks = 16 + nChannels = 2 * growthRate + + self.conv1 = nn.Conv2D( + input_channel, + nChannels, + kernel_size=7, + padding=3, + stride=2, + bias_attr=False, + ) + self.dense1 = self._make_dense( + nChannels, growthRate, nDenseBlocks, bottleneck, use_dropout + ) + nChannels += nDenseBlocks * growthRate + out_channels = int(math.floor(nChannels * reduction)) + self.trans1 = Transition(nChannels, out_channels, use_dropout) + + nChannels = out_channels + self.dense2 = self._make_dense( + nChannels, growthRate, nDenseBlocks, bottleneck, use_dropout + ) + nChannels += nDenseBlocks * growthRate + out_channels = int(math.floor(nChannels * reduction)) + self.trans2 = Transition(nChannels, out_channels, use_dropout) + + nChannels = out_channels + self.dense3 = self._make_dense( + nChannels, growthRate, nDenseBlocks, bottleneck, use_dropout + ) + self.out_channels = out_channels + + def _make_dense(self, nChannels, growthRate, nDenseBlocks, bottleneck, use_dropout): + layers = [] + for i in range(int(nDenseBlocks)): + if bottleneck: + layers.append(Bottleneck(nChannels, growthRate, use_dropout)) + else: + layers.append(SingleLayer(nChannels, growthRate, use_dropout)) + nChannels += growthRate + return nn.Sequential(*layers) + + def forward(self, inputs): + x, x_m, y = inputs + out = self.conv1(x) + out = F.relu(out) + out = F.max_pool2d(out, 2, ceil_mode=True) + out = self.dense1(out) + out = self.trans1(out) + out = self.dense2(out) + out = self.trans2(out) + out = self.dense3(out) + return out, x_m, y diff --git a/ppocr/modeling/backbones/rec_donut_swin.py b/ppocr/modeling/backbones/rec_donut_swin.py new file mode 100644 index 0000000..accad00 --- /dev/null +++ b/ppocr/modeling/backbones/rec_donut_swin.py @@ -0,0 +1,1296 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/huggingface/transformers/blob/main/src/transformers/models/donut/modeling_donut_swin.py + +""" + +import collections.abc +from collections import OrderedDict +import math +import numpy as np +from dataclasses import dataclass +from typing import Optional, Tuple, Union + +import paddle +from paddle import nn +import paddle.nn.functional as F + +from paddle.nn.initializer import ( + TruncatedNormal, + Constant, + Normal, + KaimingUniform, + XavierUniform, +) + +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) +kaiming_normal_ = KaimingUniform(nonlinearity="relu") +trunc_normal_ = TruncatedNormal(std=0.02) +xavier_uniform_ = XavierUniform() + +# General docstring +_CONFIG_FOR_DOC = "DonutSwinConfig" + +# Base docstring +_CHECKPOINT_FOR_DOC = "https://huggingface.co/naver-clova-ix/donut-base" +_EXPECTED_OUTPUT_SHAPE = [1, 49, 768] + + +class DonutSwinConfig(object): + model_type = "donut-swin" + + attribute_map = { + "num_attention_heads": "num_heads", + "num_hidden_layers": "num_layers", + } + + def __init__( + self, + image_size=224, + patch_size=4, + num_channels=3, + embed_dim=96, + depths=[2, 2, 6, 2], + num_heads=[3, 6, 12, 24], + window_size=7, + mlp_ratio=4.0, + qkv_bias=True, + hidden_dropout_prob=0.0, + attention_probs_dropout_prob=0.0, + drop_path_rate=0.1, + hidden_act="gelu", + use_absolute_embeddings=False, + initializer_range=0.02, + layer_norm_eps=1e-5, + **kwargs, + ): + super().__init__() + + self.image_size = image_size + self.patch_size = patch_size + self.num_channels = num_channels + self.embed_dim = embed_dim + self.depths = depths + self.num_layers = len(depths) + self.num_heads = num_heads + self.window_size = window_size + self.mlp_ratio = mlp_ratio + self.qkv_bias = qkv_bias + self.hidden_dropout_prob = hidden_dropout_prob + self.attention_probs_dropout_prob = attention_probs_dropout_prob + self.drop_path_rate = drop_path_rate + self.hidden_act = hidden_act + self.use_absolute_embeddings = use_absolute_embeddings + self.layer_norm_eps = layer_norm_eps + self.initializer_range = initializer_range + self.hidden_size = int(embed_dim * 2 ** (len(depths) - 1)) + + for key, value in kwargs.items(): + try: + setattr(self, key, value) + except AttributeError as err: + print(f"Can't set {key} with value {value} for {self}") + raise err + + +@dataclass +# Copied from transformers.models.swin.modeling_swin.SwinEncoderOutput with Swin->DonutSwin +class DonutSwinEncoderOutput(OrderedDict): + last_hidden_state = None + hidden_states = None + attentions = None + reshaped_hidden_states = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def __getitem__(self, k): + if isinstance(k, str): + inner_dict = dict(self.items()) + return inner_dict[k] + else: + return self.to_tuple()[k] + + def __setattr__(self, name, value): + if name in self.keys() and value is not None: + super().__setitem__(name, value) + super().__setattr__(name, value) + + def __setitem__(self, key, value): + super().__setitem__(key, value) + super().__setattr__(key, value) + + def to_tuple(self): + """ + Convert self to a tuple containing all the attributes/keys that are not `None`. + """ + return tuple(self[k] for k in self.keys()) + + +@dataclass +# Copied from transformers.models.swin.modeling_swin.SwinModelOutput with Swin->DonutSwin +class DonutSwinModelOutput(OrderedDict): + last_hidden_state = None + pooler_output = None + hidden_states = None + attentions = None + reshaped_hidden_states = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def __getitem__(self, k): + if isinstance(k, str): + inner_dict = dict(self.items()) + return inner_dict[k] + else: + return self.to_tuple()[k] + + def __setattr__(self, name, value): + if name in self.keys() and value is not None: + super().__setitem__(name, value) + super().__setattr__(name, value) + + def __setitem__(self, key, value): + super().__setitem__(key, value) + super().__setattr__(key, value) + + def to_tuple(self): + """ + Convert self to a tuple containing all the attributes/keys that are not `None`. + """ + return tuple(self[k] for k in self.keys()) + + +# Copied from transformers.models.swin.modeling_swin.window_partition +def window_partition(input_feature, window_size): + """ + Partitions the given input into windows. + """ + batch_size, height, width, num_channels = input_feature.shape + input_feature = input_feature.reshape( + [ + batch_size, + height // window_size, + window_size, + width // window_size, + window_size, + num_channels, + ] + ) + windows = input_feature.transpose([0, 1, 3, 2, 4, 5]).reshape( + [-1, window_size, window_size, num_channels] + ) + return windows + + +# Copied from transformers.models.swin.modeling_swin.window_reverse +def window_reverse(windows, window_size, height, width): + """ + Merges windows to produce higher resolution features. + """ + num_channels = windows.shape[-1] + windows = windows.reshape( + [ + -1, + height // window_size, + width // window_size, + window_size, + window_size, + num_channels, + ] + ) + windows = windows.transpose([0, 1, 3, 2, 4, 5]).reshape( + [-1, height, width, num_channels] + ) + return windows + + +# Copied from transformers.models.swin.modeling_swin.SwinEmbeddings with Swin->DonutSwin +class DonutSwinEmbeddings(nn.Layer): + """ + Construct the patch and position embeddings. Optionally, also the mask token. + """ + + def __init__(self, config, use_mask_token=False): + super().__init__() + + self.patch_embeddings = DonutSwinPatchEmbeddings(config) + num_patches = self.patch_embeddings.num_patches + self.patch_grid = self.patch_embeddings.grid_size + if use_mask_token: + self.mask_token = paddle.create_parameter( + [1, 1, config.embed_dim], dtype="float32" + ) + zeros_(self.mask_token) + else: + self.mask_token = None + if config.use_absolute_embeddings: + self.position_embeddings = paddle.create_parameter( + [1, num_patches + 1, config.embed_dim], dtype="float32" + ) + zeros_(self.position_embedding) + else: + self.position_embeddings = None + + self.norm = nn.LayerNorm(config.embed_dim) + self.dropout = nn.Dropout(config.hidden_dropout_prob) + + def forward(self, pixel_values, bool_masked_pos=None): + + embeddings, output_dimensions = self.patch_embeddings(pixel_values) + embeddings = self.norm(embeddings) + + batch_size, seq_len, _ = embeddings.shape + + if bool_masked_pos is not None: + mask_tokens = self.mask_token.expand(batch_size, seq_len, -1) + mask = bool_masked_pos.unsqueeze(-1).type_as(mask_tokens) + embeddings = embeddings * (1.0 - mask) + mask_tokens * mask + + if self.position_embeddings is not None: + embeddings = embeddings + self.position_embeddings + embeddings = self.dropout(embeddings) + return embeddings, output_dimensions + + +class MyConv2d(nn.Conv2D): + def __init__( + self, + in_channel, + out_channels, + kernel_size, + stride=1, + padding="SAME", + dilation=1, + groups=1, + bias_attr=False, + eps=1e-6, + ): + super().__init__( + in_channel, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias_attr=bias_attr, + ) + self.weight = paddle.create_parameter( + [out_channels, in_channel, kernel_size[0], kernel_size[1]], dtype="float32" + ) + self.bias = paddle.create_parameter([out_channels], dtype="float32") + ones_(self.weight) + zeros_(self.bias) + + def forward(self, x): + x = F.conv2d( + x, + self.weight, + self.bias, + self._stride, + self._padding, + self._dilation, + self._groups, + ) + return x + + +# Copied from transformers.models.swin.modeling_swin.SwinPatchEmbeddings +class DonutSwinPatchEmbeddings(nn.Layer): + """ + This class turns `pixel_values` of shape `(batch_size, num_channels, height, width)` into the initial + `hidden_states` (patch embeddings) of shape `(batch_size, seq_length, hidden_size)` to be consumed by a + Transformer. + """ + + def __init__(self, config): + super().__init__() + image_size, patch_size = config.image_size, config.patch_size + num_channels, hidden_size = config.num_channels, config.embed_dim + image_size = ( + image_size + if isinstance(image_size, collections.abc.Iterable) + else (image_size, image_size) + ) + patch_size = ( + patch_size + if isinstance(patch_size, collections.abc.Iterable) + else (patch_size, patch_size) + ) + num_patches = (image_size[1] // patch_size[1]) * ( + image_size[0] // patch_size[0] + ) + self.image_size = image_size + self.patch_size = patch_size + self.num_channels = num_channels + self.num_patches = num_patches + self.is_export = config.is_export + self.grid_size = ( + image_size[0] // patch_size[0], + image_size[1] // patch_size[1], + ) + self.projection = nn.Conv2D( + num_channels, hidden_size, kernel_size=patch_size, stride=patch_size + ) + + def maybe_pad(self, pixel_values, height, width): + if width % self.patch_size[1] != 0: + pad_values = (0, self.patch_size[1] - width % self.patch_size[1]) + if self.is_export: + pad_values = paddle.to_tensor(pad_values, dtype="int32") + pixel_values = nn.functional.pad(pixel_values, pad_values) + if height % self.patch_size[0] != 0: + pad_values = (0, 0, 0, self.patch_size[0] - height % self.patch_size[0]) + if self.is_export: + pad_values = paddle.to_tensor(pad_values, dtype="int32") + pixel_values = nn.functional.pad(pixel_values, pad_values) + return pixel_values + + def forward(self, pixel_values) -> Tuple[paddle.Tensor, Tuple[int]]: + _, num_channels, height, width = pixel_values.shape + if num_channels != self.num_channels: + raise ValueError( + "Make sure that the channel dimension of the pixel values match with the one set in the configuration." + ) + pixel_values = self.maybe_pad(pixel_values, height, width) + embeddings = self.projection(pixel_values) + + _, _, height, width = embeddings.shape + output_dimensions = (height, width) + embeddings = embeddings.flatten(2).transpose([0, 2, 1]) + + return embeddings, output_dimensions + + +# Copied from transformers.models.swin.modeling_swin.SwinPatchMerging +class DonutSwinPatchMerging(nn.Layer): + """ + Patch Merging Layer. + + Args: + input_resolution (`Tuple[int]`): + Resolution of input feature. + dim (`int`): + Number of input channels. + norm_layer (`nn.Layer`, *optional*, defaults to `nn.LayerNorm`): + Normalization layer class. + """ + + def __init__( + self, + input_resolution: Tuple[int], + dim: int, + norm_layer: nn.Layer = nn.LayerNorm, + is_export=False, + ): + super().__init__() + self.input_resolution = input_resolution + self.dim = dim + self.reduction = nn.Linear(4 * dim, 2 * dim, bias_attr=False) + self.norm = norm_layer(4 * dim) + self.is_export = is_export + + def maybe_pad(self, input_feature, height, width): + should_pad = (height % 2 == 1) or (width % 2 == 1) + if should_pad: + pad_values = (0, 0, 0, width % 2, 0, height % 2) + if self.is_export: + pad_values = paddle.to_tensor(pad_values, dtype="int32") + input_feature = nn.functional.pad(input_feature, pad_values) + + return input_feature + + def forward( + self, input_feature: paddle.Tensor, input_dimensions: Tuple[int, int] + ) -> paddle.Tensor: + height, width = input_dimensions + batch_size, dim, num_channels = input_feature.shape + + input_feature = input_feature.reshape([batch_size, height, width, num_channels]) + + input_feature = self.maybe_pad(input_feature, height, width) + input_feature_0 = input_feature[:, 0::2, 0::2, :] + input_feature_1 = input_feature[:, 1::2, 0::2, :] + input_feature_2 = input_feature[:, 0::2, 1::2, :] + input_feature_3 = input_feature[:, 1::2, 1::2, :] + input_feature = paddle.concat( + [input_feature_0, input_feature_1, input_feature_2, input_feature_3], -1 + ) + input_feature = input_feature.reshape( + [batch_size, -1, 4 * num_channels] + ) # batch_size height/2*width/2 4*C + + input_feature = self.norm(input_feature) + input_feature = self.reduction(input_feature) + + return input_feature + + +# Copied from transformers.models.beit.modeling_beit.drop_path +def drop_path( + input: paddle.Tensor, drop_prob: float = 0.0, training: bool = False +) -> paddle.Tensor: + if drop_prob == 0.0 or not training: + return input + keep_prob = 1 - drop_prob + shape = (input.shape[0],) + (1,) * ( + input.ndim - 1 + ) # work with diff dim tensors, not just 2D ConvNets + random_tensor = keep_prob + paddle.rand( + shape, + dtype=input.dtype, + ) + random_tensor.floor_() # binarize + output = input / keep_prob * random_tensor + return output + + +# Copied from transformers.models.swin.modeling_swin.SwinDropPath +class DonutSwinDropPath(nn.Layer): + """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).""" + + def __init__(self, drop_prob: Optional[float] = None) -> None: + super().__init__() + self.drop_prob = drop_prob + + def forward(self, hidden_states: paddle.Tensor) -> paddle.Tensor: + return drop_path(hidden_states, self.drop_prob, self.training) + + def extra_repr(self) -> str: + return "p={}".format(self.drop_prob) + + +class DonutSwinSelfAttention(nn.Layer): + def __init__(self, config, dim, num_heads, window_size): + super().__init__() + if dim % num_heads != 0: + raise ValueError( + f"The hidden size ({dim}) is not a multiple of the number of attention heads ({num_heads})" + ) + + self.num_attention_heads = num_heads + self.attention_head_size = int(dim / num_heads) + self.all_head_size = self.num_attention_heads * self.attention_head_size + self.window_size = ( + window_size + if isinstance(window_size, collections.abc.Iterable) + else (window_size, window_size) + ) + self.relative_position_bias_table = paddle.create_parameter( + [(2 * self.window_size[0] - 1) * (2 * self.window_size[1] - 1), num_heads], + dtype="float32", + ) + zeros_(self.relative_position_bias_table) + + # get pair-wise relative position index for each token inside the window + coords_h = paddle.arange(self.window_size[0]) + coords_w = paddle.arange(self.window_size[1]) + coords = paddle.stack(paddle.meshgrid(coords_h, coords_w, indexing="ij")) + coords_flatten = paddle.flatten(coords, 1) + relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] + relative_coords = relative_coords.transpose([1, 2, 0]) + relative_coords[:, :, 0] += self.window_size[0] - 1 + relative_coords[:, :, 1] += self.window_size[1] - 1 + relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1 + relative_position_index = relative_coords.sum(-1) + self.register_buffer("relative_position_index", relative_position_index) + + self.query = nn.Linear( + self.all_head_size, self.all_head_size, bias_attr=config.qkv_bias + ) + self.key = nn.Linear( + self.all_head_size, self.all_head_size, bias_attr=config.qkv_bias + ) + self.value = nn.Linear( + self.all_head_size, self.all_head_size, bias_attr=config.qkv_bias + ) + + self.dropout = nn.Dropout(config.attention_probs_dropout_prob) + + def transpose_for_scores(self, x): + new_x_shape = x.shape[:-1] + [ + self.num_attention_heads, + self.attention_head_size, + ] + x = x.reshape(new_x_shape) + return x.transpose([0, 2, 1, 3]) + + def forward( + self, + hidden_states: paddle.Tensor, + attention_mask=None, + head_mask=None, + output_attentions=False, + ) -> Tuple[paddle.Tensor]: + batch_size, dim, num_channels = hidden_states.shape + mixed_query_layer = self.query(hidden_states) + key_layer = self.transpose_for_scores(self.key(hidden_states)) + value_layer = self.transpose_for_scores(self.value(hidden_states)) + query_layer = self.transpose_for_scores(mixed_query_layer) + + # Take the dot product between "query" and "key" to get the raw attention scores. + attention_scores = paddle.matmul(query_layer, key_layer.transpose([0, 1, 3, 2])) + + attention_scores = attention_scores / math.sqrt(self.attention_head_size) + + relative_position_bias = self.relative_position_bias_table[ + self.relative_position_index.reshape([-1]) + ] + relative_position_bias = relative_position_bias.reshape( + [ + self.window_size[0] * self.window_size[1], + self.window_size[0] * self.window_size[1], + -1, + ] + ) + + relative_position_bias = relative_position_bias.transpose([2, 0, 1]) + attention_scores = attention_scores + relative_position_bias.unsqueeze(0) + + if attention_mask is not None: + # Apply the attention mask is (precomputed for all layers in DonutSwinModel forward() function) + mask_shape = attention_mask.shape[0] + attention_scores = attention_scores.reshape( + [ + batch_size // mask_shape, + mask_shape, + self.num_attention_heads, + dim, + dim, + ] + ) + attention_scores = attention_scores + attention_mask.unsqueeze(1).unsqueeze( + 0 + ) + attention_scores = attention_scores.reshape( + [-1, self.num_attention_heads, dim, dim] + ) + + # Normalize the attention scores to probabilities. + attention_probs = nn.functional.softmax(attention_scores, axis=-1) + + # This is actually dropping out entire tokens to attend to, which might + # seem a bit unusual, but is taken from the original Transformer paper. + attention_probs = self.dropout(attention_probs) + + # Mask heads if we want to + if head_mask is not None: + attention_probs = attention_probs * head_mask + + context_layer = paddle.matmul(attention_probs, value_layer) + context_layer = context_layer.transpose([0, 2, 1, 3]) + new_context_layer_shape = tuple(context_layer.shape[:-2]) + ( + self.all_head_size, + ) + context_layer = context_layer.reshape(new_context_layer_shape) + outputs = ( + (context_layer, attention_probs) if output_attentions else (context_layer,) + ) + return outputs + + +# Copied from transformers.models.swin.modeling_swin.SwinSelfOutput +class DonutSwinSelfOutput(nn.Layer): + def __init__(self, config, dim): + super().__init__() + self.dense = nn.Linear(dim, dim) + self.dropout = nn.Dropout(config.attention_probs_dropout_prob) + + def forward( + self, hidden_states: paddle.Tensor, input_tensor: paddle.Tensor + ) -> paddle.Tensor: + hidden_states = self.dense(hidden_states) + hidden_states = self.dropout(hidden_states) + + return hidden_states + + +# Copied from transformers.models.swin.modeling_swin.SwinAttention with Swin->DonutSwin +class DonutSwinAttention(nn.Layer): + def __init__(self, config, dim, num_heads, window_size): + super().__init__() + self.self = DonutSwinSelfAttention(config, dim, num_heads, window_size) + self.output = DonutSwinSelfOutput(config, dim) + self.pruned_heads = set() + + def forward( + self, + hidden_states: paddle.Tensor, + attention_mask=None, + head_mask=None, + output_attentions=False, + ) -> Tuple[paddle.Tensor]: + self_outputs = self.self( + hidden_states, attention_mask, head_mask, output_attentions + ) + attention_output = self.output(self_outputs[0], hidden_states) + outputs = (attention_output,) + self_outputs[ + 1: + ] # add attentions if we output them + return outputs + + +# Copied from transformers.models.swin.modeling_swin.SwinIntermediate +class DonutSwinIntermediate(nn.Layer): + def __init__(self, config, dim): + super().__init__() + self.dense = nn.Linear(dim, int(config.mlp_ratio * dim)) + self.intermediate_act_fn = F.gelu + + def forward(self, hidden_states: paddle.Tensor) -> paddle.Tensor: + hidden_states = self.dense(hidden_states) + hidden_states = self.intermediate_act_fn(hidden_states) + return hidden_states + + +# Copied from transformers.models.swin.modeling_swin.SwinOutput +class DonutSwinOutput(nn.Layer): + def __init__(self, config, dim): + super().__init__() + self.dense = nn.Linear(int(config.mlp_ratio * dim), dim) + self.dropout = nn.Dropout(config.hidden_dropout_prob) + + def forward(self, hidden_states: paddle.Tensor) -> paddle.Tensor: + hidden_states = self.dense(hidden_states) + hidden_states = self.dropout(hidden_states) + return hidden_states + + +# Copied from transformers.models.swin.modeling_swin.SwinLayer with Swin->DonutSwin +class DonutSwinLayer(nn.Layer): + def __init__(self, config, dim, input_resolution, num_heads, shift_size=0): + super().__init__() + self.chunk_size_feed_forward = config.chunk_size_feed_forward + self.shift_size = shift_size + self.window_size = config.window_size + self.input_resolution = input_resolution + self.layernorm_before = nn.LayerNorm(dim, epsilon=config.layer_norm_eps) + self.attention = DonutSwinAttention( + config, dim, num_heads, window_size=self.window_size + ) + self.drop_path = ( + DonutSwinDropPath(config.drop_path_rate) + if config.drop_path_rate > 0.0 + else nn.Identity() + ) + self.layernorm_after = nn.LayerNorm(dim, epsilon=config.layer_norm_eps) + self.intermediate = DonutSwinIntermediate(config, dim) + self.output = DonutSwinOutput(config, dim) + self.is_export = config.is_export + + def set_shift_and_window_size(self, input_resolution): + if min(input_resolution) <= self.window_size: + # if window size is larger than input resolution, we don't partition windows + self.shift_size = 0 + self.window_size = min(input_resolution) + + def get_attn_mask_export(self, height, width, dtype): + + attn_mask = None + height_slices = ( + slice(0, -self.window_size), + slice(-self.window_size, -self.shift_size), + slice(-self.shift_size, None), + ) + width_slices = ( + slice(0, -self.window_size), + slice(-self.window_size, -self.shift_size), + slice(-self.shift_size, None), + ) + img_mask = paddle.zeros((1, height, width, 1), dtype=dtype) + count = 0 + for height_slice in height_slices: + for width_slice in width_slices: + if self.shift_size > 0: + img_mask[:, height_slice, width_slice, :] = count + count += 1 + if paddle.to_tensor(self.shift_size > 0).cast(paddle.bool): + # calculate attention mask for SW-MSA + mask_windows = window_partition(img_mask, self.window_size) + mask_windows = mask_windows.reshape( + [-1, self.window_size * self.window_size] + ) + attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2) + attn_mask = attn_mask.masked_fill( + attn_mask != 0, float(-100.0) + ).masked_fill(attn_mask == 0, float(0.0)) + + return attn_mask + + def get_attn_mask(self, height, width, dtype): + if self.shift_size > 0: + # calculate attention mask for SW-MSA + img_mask = paddle.zeros((1, height, width, 1), dtype=dtype) + height_slices = ( + slice(0, -self.window_size), + slice(-self.window_size, -self.shift_size), + slice(-self.shift_size, None), + ) + width_slices = ( + slice(0, -self.window_size), + slice(-self.window_size, -self.shift_size), + slice(-self.shift_size, None), + ) + + count = 0 + for height_slice in height_slices: + for width_slice in width_slices: + img_mask[:, height_slice, width_slice, :] = count + count += 1 + + mask_windows = window_partition(img_mask, self.window_size) + mask_windows = mask_windows.reshape( + [-1, self.window_size * self.window_size] + ) + attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2) + attn_mask = attn_mask.masked_fill( + attn_mask != 0, float(-100.0) + ).masked_fill(attn_mask == 0, float(0.0)) + else: + attn_mask = None + return attn_mask + + def maybe_pad(self, hidden_states, height, width): + pad_right = (self.window_size - width % self.window_size) % self.window_size + pad_bottom = (self.window_size - height % self.window_size) % self.window_size + pad_values = (0, 0, 0, pad_bottom, 0, pad_right, 0, 0) + hidden_states = nn.functional.pad(hidden_states, pad_values) + return hidden_states, pad_values + + def forward( + self, + hidden_states: paddle.Tensor, + input_dimensions: Tuple[int, int], + head_mask=None, + output_attentions=False, + always_partition=False, + ) -> Tuple[paddle.Tensor, paddle.Tensor]: + if not always_partition: + self.set_shift_and_window_size(input_dimensions) + else: + pass + height, width = input_dimensions + batch_size, _, channels = hidden_states.shape + shortcut = hidden_states + + hidden_states = self.layernorm_before(hidden_states) + + hidden_states = hidden_states.reshape([batch_size, height, width, channels]) + + # pad hidden_states to multiples of window size + hidden_states, pad_values = self.maybe_pad(hidden_states, height, width) + + _, height_pad, width_pad, _ = hidden_states.shape + + # cyclic shift + if self.shift_size > 0: + shift_value = (-self.shift_size, -self.shift_size) + if self.is_export: + shift_value = paddle.to_tensor(shift_value, dtype="int32") + shifted_hidden_states = paddle.roll( + hidden_states, shifts=shift_value, axis=(1, 2) + ) + else: + shifted_hidden_states = hidden_states + + # partition windows + hidden_states_windows = window_partition( + shifted_hidden_states, self.window_size + ) + hidden_states_windows = hidden_states_windows.reshape( + [-1, self.window_size * self.window_size, channels] + ) + attn_mask = self.get_attn_mask(height_pad, width_pad, dtype=hidden_states.dtype) + + attention_outputs = self.attention( + hidden_states_windows, + attn_mask, + head_mask, + output_attentions=output_attentions, + ) + attention_output = attention_outputs[0] + + attention_windows = attention_output.reshape( + [-1, self.window_size, self.window_size, channels] + ) + shifted_windows = window_reverse( + attention_windows, self.window_size, height_pad, width_pad + ) + # reverse cyclic shift + if self.shift_size > 0: + shift_value = (self.shift_size, self.shift_size) + if self.is_export: + shift_value = paddle.to_tensor(shift_value, dtype="int32") + attention_windows = paddle.roll( + shifted_windows, shifts=shift_value, axis=(1, 2) + ) + else: + attention_windows = shifted_windows + + was_padded = pad_values[3] > 0 or pad_values[5] > 0 + if was_padded: + attention_windows = attention_windows[:, :height, :width, :].contiguous() + + attention_windows = attention_windows.reshape( + [batch_size, height * width, channels] + ) + hidden_states = shortcut + self.drop_path(attention_windows) + layer_output = self.layernorm_after(hidden_states) + layer_output = self.intermediate(layer_output) + layer_output = hidden_states + self.output(layer_output) + layer_outputs = ( + (layer_output, attention_outputs[1]) + if output_attentions + else (layer_output,) + ) + return layer_outputs + + +# Copied from transformers.models.swin.modeling_swin.SwinStage with Swin->DonutSwin +class DonutSwinStage(nn.Layer): + def __init__( + self, config, dim, input_resolution, depth, num_heads, drop_path, downsample + ): + super().__init__() + self.config = config + self.dim = dim + self.blocks = nn.LayerList( + [ + DonutSwinLayer( + config=config, + dim=dim, + input_resolution=input_resolution, + num_heads=num_heads, + shift_size=0 if (i % 2 == 0) else config.window_size // 2, + ) + for i in range(depth) + ] + ) + self.is_export = config.is_export + + # patch merging layer + if downsample is not None: + self.downsample = downsample( + input_resolution, + dim=dim, + norm_layer=nn.LayerNorm, + is_export=self.is_export, + ) + else: + self.downsample = None + + self.pointing = False + + def forward( + self, + hidden_states: paddle.Tensor, + input_dimensions: Tuple[int, int], + head_mask=None, + output_attentions=False, + always_partition=False, + ) -> Tuple[paddle.Tensor]: + height, width = input_dimensions + + for i, layer_module in enumerate(self.blocks): + layer_head_mask = head_mask[i] if head_mask is not None else None + + layer_outputs = layer_module( + hidden_states, + input_dimensions, + layer_head_mask, + output_attentions, + always_partition, + ) + + hidden_states = layer_outputs[0] + + hidden_states_before_downsampling = hidden_states + if self.downsample is not None: + height_downsampled, width_downsampled = (height + 1) // 2, (width + 1) // 2 + output_dimensions = (height, width, height_downsampled, width_downsampled) + hidden_states = self.downsample( + hidden_states_before_downsampling, input_dimensions + ) + else: + output_dimensions = (height, width, height, width) + + stage_outputs = ( + hidden_states, + hidden_states_before_downsampling, + output_dimensions, + ) + + if output_attentions: + stage_outputs += layer_outputs[1:] + return stage_outputs + + +# Copied from transformers.models.swin.modeling_swin.SwinEncoder with Swin->DonutSwin +class DonutSwinEncoder(nn.Layer): + def __init__(self, config, grid_size): + super().__init__() + self.num_layers = len(config.depths) + self.config = config + dpr = [ + x.item() + for x in paddle.linspace(0, config.drop_path_rate, sum(config.depths)) + ] + self.layers = nn.LayerList( + [ + DonutSwinStage( + config=config, + dim=int(config.embed_dim * 2**i_layer), + input_resolution=( + grid_size[0] // (2**i_layer), + grid_size[1] // (2**i_layer), + ), + depth=config.depths[i_layer], + num_heads=config.num_heads[i_layer], + drop_path=dpr[ + sum(config.depths[:i_layer]) : sum(config.depths[: i_layer + 1]) + ], + downsample=( + DonutSwinPatchMerging + if (i_layer < self.num_layers - 1) + else None + ), + ) + for i_layer in range(self.num_layers) + ] + ) + + self.gradient_checkpointing = False + + def forward( + self, + hidden_states: paddle.Tensor, + input_dimensions: Tuple[int, int], + head_mask=None, + output_attentions=False, + output_hidden_states=False, + output_hidden_states_before_downsampling=False, + always_partition=False, + return_dict=True, + ): + all_hidden_states = () if output_hidden_states else None + all_reshaped_hidden_states = () if output_hidden_states else None + all_self_attentions = () if output_attentions else None + + if output_hidden_states: + batch_size, _, hidden_size = hidden_states.shape + reshaped_hidden_state = hidden_states.view( + batch_size, *input_dimensions, hidden_size + ) + reshaped_hidden_state = reshaped_hidden_state.permute(0, 3, 1, 2) + all_hidden_states += (hidden_states,) + all_reshaped_hidden_states += (reshaped_hidden_state,) + + for i, layer_module in enumerate(self.layers): + layer_head_mask = head_mask[i] if head_mask is not None else None + + if self.gradient_checkpointing and self.training: + layer_outputs = self._gradient_checkpointing_func( + layer_module.__call__, + hidden_states, + input_dimensions, + layer_head_mask, + output_attentions, + always_partition, + ) + else: + layer_outputs = layer_module( + hidden_states, + input_dimensions, + layer_head_mask, + output_attentions, + always_partition, + ) + + hidden_states = layer_outputs[0] + + hidden_states_before_downsampling = layer_outputs[1] + output_dimensions = layer_outputs[2] + + input_dimensions = (output_dimensions[-2], output_dimensions[-1]) + + if output_hidden_states and output_hidden_states_before_downsampling: + batch_size, _, hidden_size = hidden_states_before_downsampling.shape + reshaped_hidden_state = hidden_states_before_downsampling.reshape( + [ + batch_size, + *(output_dimensions[0], output_dimensions[1]), + hidden_size, + ] + ) + reshaped_hidden_state = reshaped_hidden_state.transpose([0, 3, 1, 2]) + all_hidden_states += (hidden_states_before_downsampling,) + all_reshaped_hidden_states += (reshaped_hidden_state,) + elif output_hidden_states and not output_hidden_states_before_downsampling: + batch_size, _, hidden_size = hidden_states.shape + reshaped_hidden_state = hidden_states.reshape( + [batch_size, *input_dimensions, hidden_size] + ) + reshaped_hidden_state = reshaped_hidden_state.transpose([0, 3, 1, 2]) + all_hidden_states += (hidden_states,) + all_reshaped_hidden_states += (reshaped_hidden_state,) + + if output_attentions: + all_self_attentions += layer_outputs[3:] + + if not return_dict: + return tuple( + v + for v in [hidden_states, all_hidden_states, all_self_attentions] + if v is not None + ) + + return DonutSwinEncoderOutput( + last_hidden_state=hidden_states, + hidden_states=all_hidden_states, + attentions=all_self_attentions, + reshaped_hidden_states=all_reshaped_hidden_states, + ) + + +class DonutSwinPreTrainedModel(nn.Layer): + """ + An abstract class to handle weights initialization and a simple interface for downloading and loading pretrained + models. + """ + + config_class = DonutSwinConfig + base_model_prefix = "swin" + main_input_name = "pixel_values" + supports_gradient_checkpointing = True + + def _init_weights(self, module): + """Initialize the weights""" + if isinstance(module, (nn.Linear, nn.Conv2D)): + normal_ = Normal(mean=0.0, std=self.config.initializer_range) + normal_(module.weight) + if module.bias is not None: + zeros_(module.bias) + elif isinstance(module, nn.LayerNorm): + zeros_(module.bias) + ones_(module.weight) + + def _initialize_weights(self, module): + """ + Initialize the weights if they are not already initialized. + """ + if getattr(module, "_is_hf_initialized", False): + return + self._init_weights(module) + + def post_init(self): + self.apply(self._initialize_weights) + + def get_head_mask(self, head_mask, num_hidden_layers, is_attention_chunked=False): + if head_mask is not None: + head_mask = self._convert_head_mask_to_5d(head_mask, num_hidden_layers) + if is_attention_chunked is True: + head_mask = head_mask.unsqueeze(-1) + else: + head_mask = [None] * num_hidden_layers + + return head_mask + + +class DonutSwinModel(DonutSwinPreTrainedModel): + def __init__( + self, + in_channels=3, + hidden_size=1024, + num_layers=4, + num_heads=[4, 8, 16, 32], + add_pooling_layer=True, + use_mask_token=False, + is_export=False, + ): + super().__init__() + donut_swin_config = { + "return_dict": True, + "output_hidden_states": False, + "output_attentions": False, + "use_bfloat16": False, + "tf_legacy_loss": False, + "pruned_heads": {}, + "tie_word_embeddings": True, + "chunk_size_feed_forward": 0, + "is_encoder_decoder": False, + "is_decoder": False, + "cross_attention_hidden_size": None, + "add_cross_attention": False, + "tie_encoder_decoder": False, + "max_length": 20, + "min_length": 0, + "do_sample": False, + "early_stopping": False, + "num_beams": 1, + "num_beam_groups": 1, + "diversity_penalty": 0.0, + "temperature": 1.0, + "top_k": 50, + "top_p": 1.0, + "typical_p": 1.0, + "repetition_penalty": 1.0, + "length_penalty": 1.0, + "no_repeat_ngram_size": 0, + "encoder_no_repeat_ngram_size": 0, + "bad_words_ids": None, + "num_return_sequences": 1, + "output_scores": False, + "return_dict_in_generate": False, + "forced_bos_token_id": None, + "forced_eos_token_id": None, + "remove_invalid_values": False, + "exponential_decay_length_penalty": None, + "suppress_tokens": None, + "begin_suppress_tokens": None, + "architectures": None, + "finetuning_task": None, + "id2label": {0: "LABEL_0", 1: "LABEL_1"}, + "label2id": {"LABEL_0": 0, "LABEL_1": 1}, + "tokenizer_class": None, + "prefix": None, + "bos_token_id": None, + "pad_token_id": None, + "eos_token_id": None, + "sep_token_id": None, + "decoder_start_token_id": None, + "task_specific_params": None, + "problem_type": None, + "_name_or_path": "", + "_commit_hash": None, + "_attn_implementation_internal": None, + "transformers_version": None, + "hidden_size": hidden_size, + "num_layers": num_layers, + "path_norm": True, + "use_2d_embeddings": False, + "image_size": [420, 420], + "patch_size": 4, + "num_channels": in_channels, + "embed_dim": 128, + "depths": [2, 2, 14, 2], + "num_heads": num_heads, + "window_size": 5, + "mlp_ratio": 4.0, + "qkv_bias": True, + "hidden_dropout_prob": 0.0, + "attention_probs_dropout_prob": 0.0, + "drop_path_rate": 0.1, + "hidden_act": "gelu", + "use_absolute_embeddings": False, + "layer_norm_eps": 1e-05, + "initializer_range": 0.02, + "is_export": is_export, + } + + config = DonutSwinConfig(**donut_swin_config) + self.config = config + self.num_layers = len(config.depths) + self.num_features = int(config.embed_dim * 2 ** (self.num_layers - 1)) + + self.embeddings = DonutSwinEmbeddings(config, use_mask_token=use_mask_token) + self.encoder = DonutSwinEncoder(config, self.embeddings.patch_grid) + + self.pooler = nn.AdaptiveAvgPool1D(1) if add_pooling_layer else None + self.out_channels = hidden_size + self.post_init() + + def get_input_embeddings(self): + return self.embeddings.patch_embeddings + + def forward( + self, + input_data=None, + bool_masked_pos=None, + head_mask=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + ) -> Union[Tuple, DonutSwinModelOutput]: + r""" + bool_masked_pos (`paddle.BoolTensor` of shape `(batch_size, num_patches)`): + Boolean masked positions. Indicates which patches are masked (1) and which aren't (0). + """ + if self.training: + pixel_values, label, attention_mask = input_data + else: + if isinstance(input_data, list): + pixel_values = input_data[0] + else: + pixel_values = input_data + output_attentions = ( + output_attentions + if output_attentions is not None + else self.config.output_attentions + ) + output_hidden_states = ( + output_hidden_states + if output_hidden_states is not None + else self.config.output_hidden_states + ) + return_dict = ( + return_dict if return_dict is not None else self.config.return_dict + ) + + if pixel_values is None: + raise ValueError("You have to specify pixel_values") + num_channels = pixel_values.shape[1] + if num_channels == 1: + pixel_values = paddle.repeat_interleave(pixel_values, repeats=3, axis=1) + + head_mask = self.get_head_mask(head_mask, len(self.config.depths)) + + embedding_output, input_dimensions = self.embeddings( + pixel_values, bool_masked_pos=bool_masked_pos + ) + + encoder_outputs = self.encoder( + embedding_output, + input_dimensions, + head_mask=head_mask, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + sequence_output = encoder_outputs[0] + + pooled_output = None + if self.pooler is not None: + pooled_output = self.pooler(sequence_output.transpose([0, 2, 1])) + pooled_output = paddle.flatten(pooled_output, 1) + + if not return_dict: + output = (sequence_output, pooled_output) + encoder_outputs[1:] + return output + + donut_swin_output = DonutSwinModelOutput( + last_hidden_state=sequence_output, + pooler_output=pooled_output, + hidden_states=encoder_outputs.hidden_states, + attentions=encoder_outputs.attentions, + reshaped_hidden_states=encoder_outputs.reshaped_hidden_states, + ) + if self.training: + return donut_swin_output, label, attention_mask + else: + return donut_swin_output diff --git a/ppocr/modeling/backbones/rec_efficientb3_pren.py b/ppocr/modeling/backbones/rec_efficientb3_pren.py new file mode 100644 index 0000000..5334cc3 --- /dev/null +++ b/ppocr/modeling/backbones/rec_efficientb3_pren.py @@ -0,0 +1,305 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Code is refer from: +https://github.com/RuijieJ/pren/blob/main/Nets/EfficientNet.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import re +import collections +import paddle +import paddle.nn as nn +import paddle.nn.functional as F + +__all__ = ["EfficientNetb3_PREN"] + +GlobalParams = collections.namedtuple( + "GlobalParams", + [ + "batch_norm_momentum", + "batch_norm_epsilon", + "dropout_rate", + "num_classes", + "width_coefficient", + "depth_coefficient", + "depth_divisor", + "min_depth", + "drop_connect_rate", + "image_size", + ], +) + +BlockArgs = collections.namedtuple( + "BlockArgs", + [ + "kernel_size", + "num_repeat", + "input_filters", + "output_filters", + "expand_ratio", + "id_skip", + "stride", + "se_ratio", + ], +) + + +class BlockDecoder: + @staticmethod + def _decode_block_string(block_string): + assert isinstance(block_string, str) + + ops = block_string.split("_") + options = {} + for op in ops: + splits = re.split(r"(\d.*)", op) + if len(splits) >= 2: + key, value = splits[:2] + options[key] = value + + assert ("s" in options and len(options["s"]) == 1) or ( + len(options["s"]) == 2 and options["s"][0] == options["s"][1] + ) + + return BlockArgs( + kernel_size=int(options["k"]), + num_repeat=int(options["r"]), + input_filters=int(options["i"]), + output_filters=int(options["o"]), + expand_ratio=int(options["e"]), + id_skip=("noskip" not in block_string), + se_ratio=float(options["se"]) if "se" in options else None, + stride=[int(options["s"][0])], + ) + + @staticmethod + def decode(string_list): + assert isinstance(string_list, list) + blocks_args = [] + for block_string in string_list: + blocks_args.append(BlockDecoder._decode_block_string(block_string)) + return blocks_args + + +def efficientnet( + width_coefficient=None, + depth_coefficient=None, + dropout_rate=0.2, + drop_connect_rate=0.2, + image_size=None, + num_classes=1000, +): + blocks_args = [ + "r1_k3_s11_e1_i32_o16_se0.25", + "r2_k3_s22_e6_i16_o24_se0.25", + "r2_k5_s22_e6_i24_o40_se0.25", + "r3_k3_s22_e6_i40_o80_se0.25", + "r3_k5_s11_e6_i80_o112_se0.25", + "r4_k5_s22_e6_i112_o192_se0.25", + "r1_k3_s11_e6_i192_o320_se0.25", + ] + blocks_args = BlockDecoder.decode(blocks_args) + + global_params = GlobalParams( + batch_norm_momentum=0.99, + batch_norm_epsilon=1e-3, + dropout_rate=dropout_rate, + drop_connect_rate=drop_connect_rate, + num_classes=num_classes, + width_coefficient=width_coefficient, + depth_coefficient=depth_coefficient, + depth_divisor=8, + min_depth=None, + image_size=image_size, + ) + return blocks_args, global_params + + +class EffUtils: + @staticmethod + def round_filters(filters, global_params): + """Calculate and round number of filters based on depth multiplier.""" + multiplier = global_params.width_coefficient + if not multiplier: + return filters + divisor = global_params.depth_divisor + min_depth = global_params.min_depth + filters *= multiplier + min_depth = min_depth or divisor + new_filters = max(min_depth, int(filters + divisor / 2) // divisor * divisor) + if new_filters < 0.9 * filters: + new_filters += divisor + return int(new_filters) + + @staticmethod + def round_repeats(repeats, global_params): + """Round number of filters based on depth multiplier.""" + multiplier = global_params.depth_coefficient + if not multiplier: + return repeats + return int(math.ceil(multiplier * repeats)) + + +class MbConvBlock(nn.Layer): + def __init__(self, block_args): + super(MbConvBlock, self).__init__() + self._block_args = block_args + self.has_se = (self._block_args.se_ratio is not None) and ( + 0 < self._block_args.se_ratio <= 1 + ) + self.id_skip = block_args.id_skip + + # expansion phase + self.inp = self._block_args.input_filters + oup = self._block_args.input_filters * self._block_args.expand_ratio + if self._block_args.expand_ratio != 1: + self._expand_conv = nn.Conv2D(self.inp, oup, 1, bias_attr=False) + self._bn0 = nn.BatchNorm(oup) + + # depthwise conv phase + k = self._block_args.kernel_size + s = self._block_args.stride + if isinstance(s, list): + s = s[0] + self._depthwise_conv = nn.Conv2D( + oup, + oup, + groups=oup, + kernel_size=k, + stride=s, + padding="same", + bias_attr=False, + ) + self._bn1 = nn.BatchNorm(oup) + + # squeeze and excitation layer, if desired + if self.has_se: + num_squeezed_channels = max( + 1, int(self._block_args.input_filters * self._block_args.se_ratio) + ) + self._se_reduce = nn.Conv2D(oup, num_squeezed_channels, 1) + self._se_expand = nn.Conv2D(num_squeezed_channels, oup, 1) + + # output phase and some util class + self.final_oup = self._block_args.output_filters + self._project_conv = nn.Conv2D(oup, self.final_oup, 1, bias_attr=False) + self._bn2 = nn.BatchNorm(self.final_oup) + self._swish = nn.Swish() + + def _drop_connect(self, inputs, p, training): + if not training: + return inputs + batch_size = inputs.shape[0] + keep_prob = 1 - p + random_tensor = keep_prob + random_tensor += paddle.rand([batch_size, 1, 1, 1], dtype=inputs.dtype) + random_tensor = paddle.to_tensor(random_tensor, place=inputs.place) + binary_tensor = paddle.floor(random_tensor) + output = inputs / keep_prob * binary_tensor + return output + + def forward(self, inputs, drop_connect_rate=None): + # expansion and depthwise conv + x = inputs + if self._block_args.expand_ratio != 1: + x = self._swish(self._bn0(self._expand_conv(inputs))) + x = self._swish(self._bn1(self._depthwise_conv(x))) + + # squeeze and excitation + if self.has_se: + x_squeezed = F.adaptive_avg_pool2d(x, 1) + x_squeezed = self._se_expand(self._swish(self._se_reduce(x_squeezed))) + x = F.sigmoid(x_squeezed) * x + x = self._bn2(self._project_conv(x)) + + # skip connection and drop connect + if self.id_skip and self._block_args.stride == 1 and self.inp == self.final_oup: + if drop_connect_rate: + x = self._drop_connect(x, p=drop_connect_rate, training=self.training) + x = x + inputs + return x + + +class EfficientNetb3_PREN(nn.Layer): + def __init__(self, in_channels): + super(EfficientNetb3_PREN, self).__init__() + """ + the fllowing are efficientnetb3's superparams, + they means efficientnetb3 network's width, depth, resolution and + dropout respectively, to fit for text recognition task, the resolution + here is changed from 300 to 64. + """ + w, d, s, p = 1.2, 1.4, 64, 0.3 + self._blocks_args, self._global_params = efficientnet( + width_coefficient=w, depth_coefficient=d, dropout_rate=p, image_size=s + ) + self.out_channels = [] + # stem + out_channels = EffUtils.round_filters(32, self._global_params) + self._conv_stem = nn.Conv2D( + in_channels, out_channels, 3, 2, padding="same", bias_attr=False + ) + self._bn0 = nn.BatchNorm(out_channels) + + # build blocks + self._blocks = [] + # to extract three feature maps for fpn based on efficientnetb3 backbone + self._concerned_block_idxes = [7, 17, 25] + _concerned_idx = 0 + for i, block_args in enumerate(self._blocks_args): + block_args = block_args._replace( + input_filters=EffUtils.round_filters( + block_args.input_filters, self._global_params + ), + output_filters=EffUtils.round_filters( + block_args.output_filters, self._global_params + ), + num_repeat=EffUtils.round_repeats( + block_args.num_repeat, self._global_params + ), + ) + self._blocks.append(self.add_sublayer(f"{i}-0", MbConvBlock(block_args))) + _concerned_idx += 1 + if _concerned_idx in self._concerned_block_idxes: + self.out_channels.append(block_args.output_filters) + if block_args.num_repeat > 1: + block_args = block_args._replace( + input_filters=block_args.output_filters, stride=1 + ) + for j in range(block_args.num_repeat - 1): + self._blocks.append( + self.add_sublayer(f"{i}-{j+1}", MbConvBlock(block_args)) + ) + _concerned_idx += 1 + if _concerned_idx in self._concerned_block_idxes: + self.out_channels.append(block_args.output_filters) + + self._swish = nn.Swish() + + def forward(self, inputs): + outs = [] + x = self._swish(self._bn0(self._conv_stem(inputs))) + for idx, block in enumerate(self._blocks): + drop_connect_rate = self._global_params.drop_connect_rate + if drop_connect_rate: + drop_connect_rate *= float(idx) / len(self._blocks) + x = block(x, drop_connect_rate=drop_connect_rate) + if idx in self._concerned_block_idxes: + outs.append(x) + return outs diff --git a/ppocr/modeling/backbones/rec_hgnet.py b/ppocr/modeling/backbones/rec_hgnet.py new file mode 100644 index 0000000..d33e1dc --- /dev/null +++ b/ppocr/modeling/backbones/rec_hgnet.py @@ -0,0 +1,385 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle.nn.initializer import KaimingNormal, Constant +from paddle.nn import Conv2D, BatchNorm2D, ReLU, AdaptiveAvgPool2D, MaxPool2D +from paddle.regularizer import L2Decay +from paddle import ParamAttr + +kaiming_normal_ = KaimingNormal() +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) + + +class MeanPool2D(nn.Layer): + def __init__(self, w, h): + super().__init__() + self.w = w + self.h = h + + def forward(self, feat): + batch_size, channels, _, _ = feat.shape + feat_flat = paddle.reshape(feat, [batch_size, channels, -1]) + feat_mean = paddle.mean(feat_flat, axis=2) + feat_mean = paddle.reshape(feat_mean, [batch_size, channels, self.w, self.h]) + return feat_mean + + +class ConvBNAct(nn.Layer): + def __init__( + self, in_channels, out_channels, kernel_size, stride, groups=1, use_act=True + ): + super().__init__() + self.use_act = use_act + self.conv = Conv2D( + in_channels, + out_channels, + kernel_size, + stride, + padding=(kernel_size - 1) // 2, + groups=groups, + bias_attr=False, + ) + self.bn = BatchNorm2D( + out_channels, + weight_attr=ParamAttr(regularizer=L2Decay(0.0)), + bias_attr=ParamAttr(regularizer=L2Decay(0.0)), + ) + if self.use_act: + self.act = ReLU() + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + if self.use_act: + x = self.act(x) + return x + + +class ESEModule(nn.Layer): + def __init__(self, channels): + super().__init__() + if "npu" in paddle.device.get_device(): + self.avg_pool = MeanPool2D(1, 1) + else: + self.avg_pool = AdaptiveAvgPool2D(1) + self.conv = Conv2D( + in_channels=channels, + out_channels=channels, + kernel_size=1, + stride=1, + padding=0, + ) + self.sigmoid = nn.Sigmoid() + + def forward(self, x): + identity = x + x = self.avg_pool(x) + x = self.conv(x) + x = self.sigmoid(x) + return paddle.multiply(x=identity, y=x) + + +class HG_Block(nn.Layer): + def __init__( + self, + in_channels, + mid_channels, + out_channels, + layer_num, + identity=False, + ): + super().__init__() + self.identity = identity + + self.layers = nn.LayerList() + self.layers.append( + ConvBNAct( + in_channels=in_channels, + out_channels=mid_channels, + kernel_size=3, + stride=1, + ) + ) + for _ in range(layer_num - 1): + self.layers.append( + ConvBNAct( + in_channels=mid_channels, + out_channels=mid_channels, + kernel_size=3, + stride=1, + ) + ) + + # feature aggregation + total_channels = in_channels + layer_num * mid_channels + self.aggregation_conv = ConvBNAct( + in_channels=total_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + ) + self.att = ESEModule(out_channels) + + def forward(self, x): + identity = x + output = [] + output.append(x) + for layer in self.layers: + x = layer(x) + output.append(x) + x = paddle.concat(output, axis=1) + x = self.aggregation_conv(x) + x = self.att(x) + if self.identity: + x += identity + return x + + +class HG_Stage(nn.Layer): + def __init__( + self, + in_channels, + mid_channels, + out_channels, + block_num, + layer_num, + downsample=True, + stride=[2, 1], + ): + super().__init__() + self.downsample = downsample + if downsample: + self.downsample = ConvBNAct( + in_channels=in_channels, + out_channels=in_channels, + kernel_size=3, + stride=stride, + groups=in_channels, + use_act=False, + ) + + blocks_list = [] + blocks_list.append( + HG_Block(in_channels, mid_channels, out_channels, layer_num, identity=False) + ) + for _ in range(block_num - 1): + blocks_list.append( + HG_Block( + out_channels, mid_channels, out_channels, layer_num, identity=True + ) + ) + self.blocks = nn.Sequential(*blocks_list) + + def forward(self, x): + if self.downsample: + x = self.downsample(x) + x = self.blocks(x) + return x + + +class PPHGNet(nn.Layer): + """ + PPHGNet + Args: + stem_channels: list. Stem channel list of PPHGNet. + stage_config: dict. The configuration of each stage of PPHGNet. such as the number of channels, stride, etc. + layer_num: int. Number of layers of HG_Block. + use_last_conv: boolean. Whether to use a 1x1 convolutional layer before the classification layer. + class_expand: int=2048. Number of channels for the last 1x1 convolutional layer. + dropout_prob: float. Parameters of dropout, 0.0 means dropout is not used. + class_num: int=1000. The number of classes. + Returns: + model: nn.Layer. Specific PPHGNet model depends on args. + """ + + def __init__( + self, + stem_channels, + stage_config, + layer_num, + in_channels=3, + det=False, + out_indices=None, + ): + super().__init__() + self.det = det + self.out_indices = out_indices if out_indices is not None else [0, 1, 2, 3] + + # stem + stem_channels.insert(0, in_channels) + self.stem = nn.Sequential( + *[ + ConvBNAct( + in_channels=stem_channels[i], + out_channels=stem_channels[i + 1], + kernel_size=3, + stride=2 if i == 0 else 1, + ) + for i in range(len(stem_channels) - 1) + ] + ) + + if self.det: + self.pool = nn.MaxPool2D(kernel_size=3, stride=2, padding=1) + # stages + self.stages = nn.LayerList() + self.out_channels = [] + for block_id, k in enumerate(stage_config): + ( + in_channels, + mid_channels, + out_channels, + block_num, + downsample, + stride, + ) = stage_config[k] + self.stages.append( + HG_Stage( + in_channels, + mid_channels, + out_channels, + block_num, + layer_num, + downsample, + stride, + ) + ) + if block_id in self.out_indices: + self.out_channels.append(out_channels) + + if not self.det: + self.out_channels = stage_config["stage4"][2] + + self._init_weights() + + def _init_weights(self): + for m in self.sublayers(): + if isinstance(m, nn.Conv2D): + kaiming_normal_(m.weight) + elif isinstance(m, (nn.BatchNorm2D)): + ones_(m.weight) + zeros_(m.bias) + elif isinstance(m, nn.Linear): + zeros_(m.bias) + + def forward(self, x): + x = self.stem(x) + if self.det: + x = self.pool(x) + + out = [] + for i, stage in enumerate(self.stages): + x = stage(x) + if self.det and i in self.out_indices: + out.append(x) + if self.det: + return out + + if self.training: + x = F.adaptive_avg_pool2d(x, [1, 40]) + else: + x = F.avg_pool2d(x, [3, 2]) + return x + + +def PPHGNet_tiny(pretrained=False, use_ssld=False, **kwargs): + """ + PPHGNet_tiny + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `PPHGNet_tiny` model depends on args. + """ + stage_config = { + # in_channels, mid_channels, out_channels, blocks, downsample + "stage1": [96, 96, 224, 1, False, [2, 1]], + "stage2": [224, 128, 448, 1, True, [1, 2]], + "stage3": [448, 160, 512, 2, True, [2, 1]], + "stage4": [512, 192, 768, 1, True, [2, 1]], + } + + model = PPHGNet( + stem_channels=[48, 48, 96], stage_config=stage_config, layer_num=5, **kwargs + ) + return model + + +def PPHGNet_small(pretrained=False, use_ssld=False, det=False, **kwargs): + """ + PPHGNet_small + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `PPHGNet_small` model depends on args. + """ + stage_config_det = { + # in_channels, mid_channels, out_channels, blocks, downsample + "stage1": [128, 128, 256, 1, False, 2], + "stage2": [256, 160, 512, 1, True, 2], + "stage3": [512, 192, 768, 2, True, 2], + "stage4": [768, 224, 1024, 1, True, 2], + } + + stage_config_rec = { + # in_channels, mid_channels, out_channels, blocks, downsample + "stage1": [128, 128, 256, 1, True, [2, 1]], + "stage2": [256, 160, 512, 1, True, [1, 2]], + "stage3": [512, 192, 768, 2, True, [2, 1]], + "stage4": [768, 224, 1024, 1, True, [2, 1]], + } + + model = PPHGNet( + stem_channels=[64, 64, 128], + stage_config=stage_config_det if det else stage_config_rec, + layer_num=6, + det=det, + **kwargs, + ) + return model + + +def PPHGNet_base(pretrained=False, use_ssld=True, **kwargs): + """ + PPHGNet_base + Args: + pretrained: bool=False or str. If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld: bool=False. Whether using distillation pretrained model when pretrained=True. + Returns: + model: nn.Layer. Specific `PPHGNet_base` model depends on args. + """ + stage_config = { + # in_channels, mid_channels, out_channels, blocks, downsample + "stage1": [160, 192, 320, 1, False, [2, 1]], + "stage2": [320, 224, 640, 2, True, [1, 2]], + "stage3": [640, 256, 960, 3, True, [2, 1]], + "stage4": [960, 288, 1280, 2, True, [2, 1]], + } + + model = PPHGNet( + stem_channels=[96, 96, 160], + stage_config=stage_config, + layer_num=7, + dropout_prob=0.2, + **kwargs, + ) + return model diff --git a/ppocr/modeling/backbones/rec_hybridvit.py b/ppocr/modeling/backbones/rec_hybridvit.py new file mode 100644 index 0000000..02aa305 --- /dev/null +++ b/ppocr/modeling/backbones/rec_hybridvit.py @@ -0,0 +1,528 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is refer from: +https://github.com/huggingface/pytorch-image-models/blob/main/timm/models/vision_transformer_hybrid.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from itertools import repeat +import collections +import math +from functools import partial + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from ppocr.modeling.backbones.rec_resnetv2 import ( + ResNetV2, + StdConv2dSame, + DropPath, + get_padding, +) +from paddle.nn.initializer import ( + TruncatedNormal, + Constant, + Normal, + KaimingUniform, + XavierUniform, +) + +normal_ = Normal(mean=0.0, std=1e-6) +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) +kaiming_normal_ = KaimingUniform(nonlinearity="relu") +trunc_normal_ = TruncatedNormal(std=0.02) +xavier_uniform_ = XavierUniform() + + +def _ntuple(n): + def parse(x): + if isinstance(x, collections.abc.Iterable): + return x + return tuple(repeat(x, n)) + + return parse + + +to_1tuple = _ntuple(1) +to_2tuple = _ntuple(2) +to_3tuple = _ntuple(3) +to_4tuple = _ntuple(4) +to_ntuple = _ntuple + + +class Conv2dAlign(nn.Conv2D): + """Conv2d with Weight Standardization. Used for BiT ResNet-V2 models. + + Paper: `Micro-Batch Training with Batch-Channel Normalization and Weight Standardization` - + https://arxiv.org/abs/1903.10520v2 + """ + + def __init__( + self, + in_channel, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + bias=True, + eps=1e-6, + ): + + super().__init__( + in_channel, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias_attr=bias, + weight_attr=True, + ) + self.eps = eps + + def forward(self, x): + x = F.conv2d( + x, + self.weight, + self.bias, + self._stride, + self._padding, + self._dilation, + self._groups, + ) + return x + + +class HybridEmbed(nn.Layer): + """CNN Feature Map Embedding + Extract feature map from CNN, flatten, project to embedding dim. + """ + + def __init__( + self, + backbone, + img_size=224, + patch_size=1, + feature_size=None, + in_chans=3, + embed_dim=768, + ): + super().__init__() + assert isinstance(backbone, nn.Layer) + img_size = to_2tuple(img_size) + patch_size = to_2tuple(patch_size) + self.img_size = img_size + self.patch_size = patch_size + self.backbone = backbone + feature_dim = 1024 + feature_size = (42, 12) + patch_size = (1, 1) + assert ( + feature_size[0] % patch_size[0] == 0 + and feature_size[1] % patch_size[1] == 0 + ) + + self.grid_size = ( + feature_size[0] // patch_size[0], + feature_size[1] // patch_size[1], + ) + self.num_patches = self.grid_size[0] * self.grid_size[1] + self.proj = nn.Conv2D( + feature_dim, + embed_dim, + kernel_size=patch_size, + stride=patch_size, + weight_attr=True, + bias_attr=True, + ) + + def forward(self, x): + + x = self.backbone(x) + if isinstance(x, (list, tuple)): + x = x[-1] # last feature if backbone outputs list/tuple of features + x = self.proj(x).flatten(2).transpose([0, 2, 1]) + + return x + + +class myLinear(nn.Linear): + def __init__(self, in_channel, out_channels, weight_attr=True, bias_attr=True): + super().__init__( + in_channel, out_channels, weight_attr=weight_attr, bias_attr=bias_attr + ) + + def forward(self, x): + return paddle.matmul(x, self.weight, transpose_y=True) + self.bias + + +class Attention(nn.Layer): + def __init__(self, dim, num_heads=8, qkv_bias=False, attn_drop=0.0, proj_drop=0.0): + super().__init__() + self.num_heads = num_heads + head_dim = dim // num_heads + self.scale = head_dim**-0.5 + + self.qkv = nn.Linear(dim, dim * 3, bias_attr=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop) + self.proj = myLinear(dim, dim, weight_attr=True, bias_attr=True) + self.proj_drop = nn.Dropout(proj_drop) + + def forward(self, x): + B, N, C = x.shape + qkv = ( + self.qkv(x) + .reshape([B, N, 3, self.num_heads, C // self.num_heads]) + .transpose([2, 0, 3, 1, 4]) + ) + q, k, v = qkv.unbind(0) # make torchscript happy (cannot use tensor as tuple) + + attn = (q @ k.transpose([0, 1, 3, 2])) * self.scale + + attn = F.softmax(attn, axis=-1) + attn = self.attn_drop(attn) + + x = (attn @ v).transpose([0, 2, 1, 3]).reshape([B, N, C]) + + x = self.proj(x) + x = self.proj_drop(x) + return x + + +class Mlp(nn.Layer): + """MLP as used in Vision Transformer, MLP-Mixer and related networks""" + + def __init__( + self, + in_features, + hidden_features=None, + out_features=None, + act_layer=nn.GELU, + drop=0.0, + ): + super().__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + drop_probs = to_2tuple(drop) + + self.fc1 = nn.Linear(in_features, hidden_features) + self.act = act_layer() + self.drop1 = nn.Dropout(drop_probs[0]) + self.fc2 = nn.Linear(hidden_features, out_features) + self.drop2 = nn.Dropout(drop_probs[1]) + + def forward(self, x): + x = self.fc1(x) + x = self.act(x) + x = self.drop1(x) + x = self.fc2(x) + x = self.drop2(x) + return x + + +class Block(nn.Layer): + def __init__( + self, + dim, + num_heads, + mlp_ratio=4.0, + qkv_bias=False, + drop=0.0, + attn_drop=0.0, + drop_path=0.0, + act_layer=nn.GELU, + norm_layer=nn.LayerNorm, + ): + super().__init__() + self.norm1 = norm_layer(dim) + self.attn = Attention( + dim, + num_heads=num_heads, + qkv_bias=qkv_bias, + attn_drop=attn_drop, + proj_drop=drop, + ) + # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity() + self.norm2 = norm_layer(dim) + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp = Mlp( + in_features=dim, + hidden_features=mlp_hidden_dim, + act_layer=act_layer, + drop=drop, + ) + + def forward(self, x): + + x = x + self.drop_path(self.attn(self.norm1(x))) + x = x + self.drop_path(self.mlp(self.norm2(x))) + return x + + +class HybridTransformer(nn.Layer): + """Implementation of HybridTransformer. + + Args: + x: input images with shape [N, 1, H, W] + label: LaTeX-OCR labels with shape [N, L] , L is the max sequence length + attention_mask: LaTeX-OCR attention mask with shape [N, L] , L is the max sequence length + + Returns: + The encoded features with shape [N, 1, H//16, W//16] + """ + + def __init__( + self, + backbone_layers=[2, 3, 7], + input_channel=1, + is_predict=False, + is_export=False, + img_size=(224, 224), + patch_size=16, + num_classes=1000, + embed_dim=768, + depth=12, + num_heads=12, + mlp_ratio=4.0, + qkv_bias=True, + representation_size=None, + distilled=False, + drop_rate=0.0, + attn_drop_rate=0.0, + drop_path_rate=0.0, + embed_layer=None, + norm_layer=None, + act_layer=None, + weight_init="", + **kwargs, + ): + super(HybridTransformer, self).__init__() + self.num_classes = num_classes + self.num_features = self.embed_dim = ( + embed_dim # num_features for consistency with other models + ) + self.num_tokens = 2 if distilled else 1 + norm_layer = norm_layer or partial(nn.LayerNorm, epsilon=1e-6) + act_layer = act_layer or nn.GELU + self.height, self.width = img_size + self.patch_size = patch_size + backbone = ResNetV2( + layers=backbone_layers, + num_classes=0, + global_pool="", + in_chans=input_channel, + preact=False, + stem_type="same", + conv_layer=StdConv2dSame, + is_export=is_export, + ) + min_patch_size = 2 ** (len(backbone_layers) + 1) + self.patch_embed = HybridEmbed( + img_size=img_size, + patch_size=patch_size // min_patch_size, + in_chans=input_channel, + embed_dim=embed_dim, + backbone=backbone, + ) + num_patches = self.patch_embed.num_patches + + self.cls_token = paddle.create_parameter([1, 1, embed_dim], dtype="float32") + self.dist_token = ( + paddle.create_parameter( + [1, 1, embed_dim], + dtype="float32", + ) + if distilled + else None + ) + self.pos_embed = paddle.create_parameter( + [1, num_patches + self.num_tokens, embed_dim], dtype="float32" + ) + self.pos_drop = nn.Dropout(p=drop_rate) + zeros_(self.cls_token) + if self.dist_token is not None: + zeros_(self.dist_token) + zeros_(self.pos_embed) + + dpr = [ + x.item() for x in paddle.linspace(0, drop_path_rate, depth) + ] # stochastic depth decay rule + self.blocks = nn.Sequential( + *[ + Block( + dim=embed_dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + drop=drop_rate, + attn_drop=attn_drop_rate, + drop_path=dpr[i], + norm_layer=norm_layer, + act_layer=act_layer, + ) + for i in range(depth) + ] + ) + self.norm = norm_layer(embed_dim) + + # Representation layer + if representation_size and not distilled: + self.num_features = representation_size + self.pre_logits = nn.Sequential( + ("fc", nn.Linear(embed_dim, representation_size)), ("act", nn.Tanh()) + ) + else: + self.pre_logits = nn.Identity() + + # Classifier head(s) + self.head = ( + nn.Linear(self.num_features, num_classes) + if num_classes > 0 + else nn.Identity() + ) + self.head_dist = None + if distilled: + self.head_dist = ( + nn.Linear(self.embed_dim, self.num_classes) + if num_classes > 0 + else nn.Identity() + ) + self.init_weights(weight_init) + self.out_channels = embed_dim + self.is_predict = is_predict + self.is_export = is_export + + def init_weights(self, mode=""): + assert mode in ("jax", "jax_nlhb", "nlhb", "") + head_bias = -math.log(self.num_classes) if "nlhb" in mode else 0.0 + trunc_normal_(self.pos_embed) + trunc_normal_(self.cls_token) + self.apply(_init_vit_weights) + + def _init_weights(self, m): + # this fn left here for compat with downstream users + _init_vit_weights(m) + + def load_pretrained(self, checkpoint_path, prefix=""): + raise NotImplementedError + + def no_weight_decay(self): + return {"pos_embed", "cls_token", "dist_token"} + + def get_classifier(self): + if self.dist_token is None: + return self.head + else: + return self.head, self.head_dist + + def reset_classifier(self, num_classes, global_pool=""): + self.num_classes = num_classes + self.head = ( + nn.Linear(self.embed_dim, num_classes) if num_classes > 0 else nn.Identity() + ) + if self.num_tokens == 2: + self.head_dist = ( + nn.Linear(self.embed_dim, self.num_classes) + if num_classes > 0 + else nn.Identity() + ) + + def forward_features(self, x): + B, c, h, w = x.shape + x = self.patch_embed(x) + cls_tokens = self.cls_token.expand( + [B, -1, -1] + ) # stole cls_tokens impl from Phil Wang, thanks + x = paddle.concat((cls_tokens, x), axis=1) + h, w = h // self.patch_size, w // self.patch_size + repeat_tensor = ( + paddle.arange(h) * (self.width // self.patch_size - w) + ).reshape([-1, 1]) + repeat_tensor = paddle.repeat_interleave( + repeat_tensor, paddle.to_tensor(w), axis=1 + ).reshape([-1]) + pos_emb_ind = repeat_tensor + paddle.arange(h * w) + pos_emb_ind = paddle.concat( + (paddle.zeros([1], dtype="int64"), pos_emb_ind + 1), axis=0 + ).cast(paddle.int64) + x += self.pos_embed[:, pos_emb_ind] + x = self.pos_drop(x) + + for blk in self.blocks: + x = blk(x) + + x = self.norm(x) + return x + + def forward(self, input_data): + + if self.training: + x, label, attention_mask = input_data + else: + if isinstance(input_data, list): + x = input_data[0] + else: + x = input_data + x = self.forward_features(x) + x = self.head(x) + if self.training: + return x, label, attention_mask + else: + return x + + +def _init_vit_weights( + module: nn.Layer, name: str = "", head_bias: float = 0.0, jax_impl: bool = False +): + """ViT weight initialization + * When called without n, head_bias, jax_impl args it will behave exactly the same + as my original init for compatibility with prev hparam / downstream use cases (ie DeiT). + * When called w/ valid n (module name) and jax_impl=True, will (hopefully) match JAX impl + """ + if isinstance(module, nn.Linear): + if name.startswith("head"): + zeros_(module.weight) + constant_ = Constant(value=head_bias) + constant_(module.bias, head_bias) + elif name.startswith("pre_logits"): + zeros_(module.bias) + else: + if jax_impl: + xavier_uniform_(module.weight) + if module.bias is not None: + if "mlp" in name: + normal_(module.bias) + else: + zeros_(module.bias) + else: + trunc_normal_(module.weight) + if module.bias is not None: + zeros_(module.bias) + elif jax_impl and isinstance(module, nn.Conv2D): + # NOTE conv was left to pytorch default in my original init + if module.bias is not None: + zeros_(module.bias) + elif isinstance(module, (nn.LayerNorm, nn.GroupNorm, nn.BatchNorm2D)): + zeros_(module.bias) + ones_(module.weight) diff --git a/ppocr/modeling/backbones/rec_lcnetv3.py b/ppocr/modeling/backbones/rec_lcnetv3.py new file mode 100644 index 0000000..387c761 --- /dev/null +++ b/ppocr/modeling/backbones/rec_lcnetv3.py @@ -0,0 +1,567 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle import ParamAttr +from paddle.nn.initializer import Constant, KaimingNormal +from paddle.nn import ( + AdaptiveAvgPool2D, + BatchNorm2D, + Conv2D, + Dropout, + Hardsigmoid, + Hardswish, + Identity, + Linear, + ReLU, +) +from paddle.regularizer import L2Decay +from ppocr.modeling.backbones.rec_hgnet import MeanPool2D + +NET_CONFIG_det = { + "blocks2": + # k, in_c, out_c, s, use_se + [[3, 16, 32, 1, False]], + "blocks3": [[3, 32, 64, 2, False], [3, 64, 64, 1, False]], + "blocks4": [[3, 64, 128, 2, False], [3, 128, 128, 1, False]], + "blocks5": [ + [3, 128, 256, 2, False], + [5, 256, 256, 1, False], + [5, 256, 256, 1, False], + [5, 256, 256, 1, False], + [5, 256, 256, 1, False], + ], + "blocks6": [ + [5, 256, 512, 2, True], + [5, 512, 512, 1, True], + [5, 512, 512, 1, False], + [5, 512, 512, 1, False], + ], +} + +NET_CONFIG_rec = { + "blocks2": + # k, in_c, out_c, s, use_se + [[3, 16, 32, 1, False]], + "blocks3": [[3, 32, 64, 1, False], [3, 64, 64, 1, False]], + "blocks4": [[3, 64, 128, (2, 1), False], [3, 128, 128, 1, False]], + "blocks5": [ + [3, 128, 256, (1, 2), False], + [5, 256, 256, 1, False], + [5, 256, 256, 1, False], + [5, 256, 256, 1, False], + [5, 256, 256, 1, False], + ], + "blocks6": [ + [5, 256, 512, (2, 1), True], + [5, 512, 512, 1, True], + [5, 512, 512, (2, 1), False], + [5, 512, 512, 1, False], + ], +} + + +def make_divisible(v, divisor=16, min_value=None): + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class LearnableAffineBlock(nn.Layer): + def __init__(self, scale_value=1.0, bias_value=0.0, lr_mult=1.0, lab_lr=0.1): + super().__init__() + self.scale = self.create_parameter( + shape=[ + 1, + ], + default_initializer=Constant(value=scale_value), + attr=ParamAttr(learning_rate=lr_mult * lab_lr), + ) + self.add_parameter("scale", self.scale) + self.bias = self.create_parameter( + shape=[ + 1, + ], + default_initializer=Constant(value=bias_value), + attr=ParamAttr(learning_rate=lr_mult * lab_lr), + ) + self.add_parameter("bias", self.bias) + + def forward(self, x): + return self.scale * x + self.bias + + +class ConvBNLayer(nn.Layer): + def __init__( + self, in_channels, out_channels, kernel_size, stride, groups=1, lr_mult=1.0 + ): + super().__init__() + self.conv = Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(initializer=KaimingNormal(), learning_rate=lr_mult), + bias_attr=False, + ) + + self.bn = BatchNorm2D( + out_channels, + weight_attr=ParamAttr(regularizer=L2Decay(0.0), learning_rate=lr_mult), + bias_attr=ParamAttr(regularizer=L2Decay(0.0), learning_rate=lr_mult), + ) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + return x + + +class Act(nn.Layer): + def __init__(self, act="hswish", lr_mult=1.0, lab_lr=0.1): + super().__init__() + if act == "hswish": + self.act = Hardswish() + else: + assert act == "relu" + self.act = ReLU() + self.lab = LearnableAffineBlock(lr_mult=lr_mult, lab_lr=lab_lr) + + def forward(self, x): + return self.lab(self.act(x)) + + +class LearnableRepLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + num_conv_branches=1, + lr_mult=1.0, + lab_lr=0.1, + ): + super().__init__() + self.is_repped = False + self.groups = groups + self.stride = stride + self.kernel_size = kernel_size + self.in_channels = in_channels + self.out_channels = out_channels + self.num_conv_branches = num_conv_branches + self.padding = (kernel_size - 1) // 2 + + self.identity = ( + BatchNorm2D( + num_features=in_channels, + weight_attr=ParamAttr(learning_rate=lr_mult), + bias_attr=ParamAttr(learning_rate=lr_mult), + ) + if out_channels == in_channels and stride == 1 + else None + ) + + self.conv_kxk = nn.LayerList( + [ + ConvBNLayer( + in_channels, + out_channels, + kernel_size, + stride, + groups=groups, + lr_mult=lr_mult, + ) + for _ in range(self.num_conv_branches) + ] + ) + + self.conv_1x1 = ( + ConvBNLayer( + in_channels, out_channels, 1, stride, groups=groups, lr_mult=lr_mult + ) + if kernel_size > 1 + else None + ) + + self.lab = LearnableAffineBlock(lr_mult=lr_mult, lab_lr=lab_lr) + self.act = Act(lr_mult=lr_mult, lab_lr=lab_lr) + + def forward(self, x): + # for export + if self.is_repped: + out = self.lab(self.reparam_conv(x)) + if self.stride != 2: + out = self.act(out) + return out + + out = 0 + if self.identity is not None: + out += self.identity(x) + + if self.conv_1x1 is not None: + out += self.conv_1x1(x) + + for conv in self.conv_kxk: + out += conv(x) + + out = self.lab(out) + if self.stride != 2: + out = self.act(out) + return out + + @paddle.no_grad() + def rep(self): + if self.is_repped: + return + kernel, bias = self._get_kernel_bias() + self.reparam_conv = Conv2D( + in_channels=self.in_channels, + out_channels=self.out_channels, + kernel_size=self.kernel_size, + stride=self.stride, + padding=self.padding, + groups=self.groups, + ) + self.reparam_conv.weight.set_value(kernel) + self.reparam_conv.bias.set_value(bias) + # Remove training branches to avoid duplicate parameter counting + del self.conv_kxk + if self.conv_1x1 is not None: + del self.conv_1x1 + if self.identity is not None: + del self.identity + if hasattr(self, "id_tensor"): + del self.id_tensor + self.is_repped = True + + def _pad_kernel_1x1_to_kxk(self, kernel1x1, pad): + if not isinstance(kernel1x1, paddle.Tensor): + return 0 + else: + return nn.functional.pad(kernel1x1, [pad, pad, pad, pad]) + + def _get_kernel_bias(self): + kernel_conv_1x1, bias_conv_1x1 = self._fuse_bn_tensor(self.conv_1x1) + kernel_conv_1x1 = self._pad_kernel_1x1_to_kxk( + kernel_conv_1x1, self.kernel_size // 2 + ) + + kernel_identity, bias_identity = self._fuse_bn_tensor(self.identity) + + kernel_conv_kxk = 0 + bias_conv_kxk = 0 + for conv in self.conv_kxk: + kernel, bias = self._fuse_bn_tensor(conv) + kernel_conv_kxk += kernel + bias_conv_kxk += bias + + kernel_reparam = kernel_conv_kxk + kernel_conv_1x1 + kernel_identity + bias_reparam = bias_conv_kxk + bias_conv_1x1 + bias_identity + return kernel_reparam, bias_reparam + + def _fuse_bn_tensor(self, branch): + if not branch: + return 0, 0 + elif isinstance(branch, ConvBNLayer): + kernel = branch.conv.weight + running_mean = branch.bn._mean + running_var = branch.bn._variance + gamma = branch.bn.weight + beta = branch.bn.bias + eps = branch.bn._epsilon + else: + assert isinstance(branch, BatchNorm2D) + if not hasattr(self, "id_tensor"): + input_dim = self.in_channels // self.groups + kernel_value = paddle.zeros( + (self.in_channels, input_dim, self.kernel_size, self.kernel_size), + dtype=branch.weight.dtype, + ) + for i in range(self.in_channels): + kernel_value[ + i, i % input_dim, self.kernel_size // 2, self.kernel_size // 2 + ] = 1 + self.id_tensor = kernel_value + kernel = self.id_tensor + running_mean = branch._mean + running_var = branch._variance + gamma = branch.weight + beta = branch.bias + eps = branch._epsilon + std = (running_var + eps).sqrt() + t = (gamma / std).reshape((-1, 1, 1, 1)) + return kernel * t, beta - running_mean * gamma / std + + +class SELayer(nn.Layer): + def __init__(self, channel, reduction=4, lr_mult=1.0): + super().__init__() + if "npu" in paddle.device.get_device(): + self.avg_pool = MeanPool2D(1, 1) + else: + self.avg_pool = AdaptiveAvgPool2D(1) + self.conv1 = Conv2D( + in_channels=channel, + out_channels=channel // reduction, + kernel_size=1, + stride=1, + padding=0, + weight_attr=ParamAttr(learning_rate=lr_mult), + bias_attr=ParamAttr(learning_rate=lr_mult), + ) + self.relu = ReLU() + self.conv2 = Conv2D( + in_channels=channel // reduction, + out_channels=channel, + kernel_size=1, + stride=1, + padding=0, + weight_attr=ParamAttr(learning_rate=lr_mult), + bias_attr=ParamAttr(learning_rate=lr_mult), + ) + self.hardsigmoid = Hardsigmoid() + + def forward(self, x): + identity = x + x = self.avg_pool(x) + x = self.conv1(x) + x = self.relu(x) + x = self.conv2(x) + x = self.hardsigmoid(x) + x = paddle.multiply(x=identity, y=x) + return x + + +class LCNetV3Block(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride, + dw_size, + use_se=False, + conv_kxk_num=4, + lr_mult=1.0, + lab_lr=0.1, + ): + super().__init__() + self.use_se = use_se + self.dw_conv = LearnableRepLayer( + in_channels=in_channels, + out_channels=in_channels, + kernel_size=dw_size, + stride=stride, + groups=in_channels, + num_conv_branches=conv_kxk_num, + lr_mult=lr_mult, + lab_lr=lab_lr, + ) + if use_se: + self.se = SELayer(in_channels, lr_mult=lr_mult) + self.pw_conv = LearnableRepLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + num_conv_branches=conv_kxk_num, + lr_mult=lr_mult, + lab_lr=lab_lr, + ) + + def forward(self, x): + x = self.dw_conv(x) + if self.use_se: + x = self.se(x) + x = self.pw_conv(x) + return x + + +class PPLCNetV3(nn.Layer): + def __init__( + self, + scale=1.0, + conv_kxk_num=4, + lr_mult_list=[1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + lab_lr=0.1, + det=False, + **kwargs, + ): + super().__init__() + self.scale = scale + self.lr_mult_list = lr_mult_list + self.det = det + + self.net_config = NET_CONFIG_det if self.det else NET_CONFIG_rec + + assert isinstance( + self.lr_mult_list, (list, tuple) + ), "lr_mult_list should be in (list, tuple) but got {}".format( + type(self.lr_mult_list) + ) + assert ( + len(self.lr_mult_list) == 6 + ), "lr_mult_list length should be 6 but got {}".format(len(self.lr_mult_list)) + + self.conv1 = ConvBNLayer( + in_channels=3, + out_channels=make_divisible(16 * scale), + kernel_size=3, + stride=2, + lr_mult=self.lr_mult_list[0], + ) + + self.blocks2 = nn.Sequential( + *[ + LCNetV3Block( + in_channels=make_divisible(in_c * scale), + out_channels=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se, + conv_kxk_num=conv_kxk_num, + lr_mult=self.lr_mult_list[1], + lab_lr=lab_lr, + ) + for i, (k, in_c, out_c, s, se) in enumerate(self.net_config["blocks2"]) + ] + ) + + self.blocks3 = nn.Sequential( + *[ + LCNetV3Block( + in_channels=make_divisible(in_c * scale), + out_channels=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se, + conv_kxk_num=conv_kxk_num, + lr_mult=self.lr_mult_list[2], + lab_lr=lab_lr, + ) + for i, (k, in_c, out_c, s, se) in enumerate(self.net_config["blocks3"]) + ] + ) + + self.blocks4 = nn.Sequential( + *[ + LCNetV3Block( + in_channels=make_divisible(in_c * scale), + out_channels=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se, + conv_kxk_num=conv_kxk_num, + lr_mult=self.lr_mult_list[3], + lab_lr=lab_lr, + ) + for i, (k, in_c, out_c, s, se) in enumerate(self.net_config["blocks4"]) + ] + ) + + self.blocks5 = nn.Sequential( + *[ + LCNetV3Block( + in_channels=make_divisible(in_c * scale), + out_channels=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se, + conv_kxk_num=conv_kxk_num, + lr_mult=self.lr_mult_list[4], + lab_lr=lab_lr, + ) + for i, (k, in_c, out_c, s, se) in enumerate(self.net_config["blocks5"]) + ] + ) + + self.blocks6 = nn.Sequential( + *[ + LCNetV3Block( + in_channels=make_divisible(in_c * scale), + out_channels=make_divisible(out_c * scale), + dw_size=k, + stride=s, + use_se=se, + conv_kxk_num=conv_kxk_num, + lr_mult=self.lr_mult_list[5], + lab_lr=lab_lr, + ) + for i, (k, in_c, out_c, s, se) in enumerate(self.net_config["blocks6"]) + ] + ) + self.out_channels = make_divisible(512 * scale) + + if self.det: + mv_c = [16, 24, 56, 480] + self.out_channels = [ + make_divisible(self.net_config["blocks3"][-1][2] * scale), + make_divisible(self.net_config["blocks4"][-1][2] * scale), + make_divisible(self.net_config["blocks5"][-1][2] * scale), + make_divisible(self.net_config["blocks6"][-1][2] * scale), + ] + + self.layer_list = nn.LayerList( + [ + nn.Conv2D(self.out_channels[0], int(mv_c[0] * scale), 1, 1, 0), + nn.Conv2D(self.out_channels[1], int(mv_c[1] * scale), 1, 1, 0), + nn.Conv2D(self.out_channels[2], int(mv_c[2] * scale), 1, 1, 0), + nn.Conv2D(self.out_channels[3], int(mv_c[3] * scale), 1, 1, 0), + ] + ) + self.out_channels = [ + int(mv_c[0] * scale), + int(mv_c[1] * scale), + int(mv_c[2] * scale), + int(mv_c[3] * scale), + ] + + def forward(self, x): + out_list = [] + x = self.conv1(x) + + x = self.blocks2(x) + x = self.blocks3(x) + out_list.append(x) + x = self.blocks4(x) + out_list.append(x) + x = self.blocks5(x) + out_list.append(x) + x = self.blocks6(x) + out_list.append(x) + + if self.det: + out_list[0] = self.layer_list[0](out_list[0]) + out_list[1] = self.layer_list[1](out_list[1]) + out_list[2] = self.layer_list[2](out_list[2]) + out_list[3] = self.layer_list[3](out_list[3]) + return out_list + + if self.training: + x = F.adaptive_avg_pool2d(x, [1, 40]) + else: + x = F.avg_pool2d(x, [3, 2]) + return x diff --git a/ppocr/modeling/backbones/rec_lcnetv4.py b/ppocr/modeling/backbones/rec_lcnetv4.py new file mode 100644 index 0000000..405da04 --- /dev/null +++ b/ppocr/modeling/backbones/rec_lcnetv4.py @@ -0,0 +1,669 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle import ParamAttr +from paddle.nn.initializer import Constant +from paddle.nn import ( + BatchNorm2D, + Conv2D, + GELU, + Hardsigmoid, + Hardswish, + ReLU, +) +from paddle.regularizer import L2Decay + + +NET_CONFIG_DET = { + "tiny": { + # stem(mid=16, out=32) channels: 32 → 48 → 64 → 160 + "stem": (16, 32), + "blocks_s1": [[3, 32, 32, 1, True], [3, 32, 32, 1, False]], + "blocks_s2": [ + [3, 32, 48, 2, False], + [3, 48, 48, 1, True], + [3, 48, 48, 1, False], + ], + "blocks_s3": [ + [3, 48, 64, 2, False], + [3, 64, 64, 1, True], + [3, 64, 64, 1, False], + [3, 64, 64, 1, True], + [3, 64, 64, 1, False], + ], + "blocks_s4": [ + [3, 64, 160, 2, False], + [3, 160, 160, 1, True], + [3, 160, 160, 1, False], + ], + }, + "small": { + # stem(mid=24, out=48) channels: 48 → 96 → 192 → 384 + "stem": (24, 48), + "blocks_s1": [[3, 48, 48, 1, True], [3, 48, 48, 1, False]], + "blocks_s2": [ + [3, 48, 96, 2, False], + [3, 96, 96, 1, True], + [3, 96, 96, 1, False], + ], + "blocks_s3": [ + [3, 96, 192, 2, False], + [3, 192, 192, 1, True], + [3, 192, 192, 1, False], + [3, 192, 192, 1, True], + [3, 192, 192, 1, False], + ], + "blocks_s4": [ + [3, 192, 384, 2, False], + [3, 384, 384, 1, True], + [3, 384, 384, 1, False], + ], + }, + "medium": { + # stem(mid=64, out=128) channels: 128 → 256 → 512 → 896 + "stem": (64, 128), + "blocks_s1": [[3, 128, 128, 1, True], [3, 128, 128, 1, False]], + "blocks_s2": [ + [3, 128, 256, 2, False], + [3, 256, 256, 1, True], + [3, 256, 256, 1, False], + ], + "blocks_s3": [ + [3, 256, 512, 2, False], + [3, 512, 512, 1, True], + [3, 512, 512, 1, False], + [3, 512, 512, 1, True], + [3, 512, 512, 1, False], + ], + "blocks_s4": [ + [3, 512, 896, 2, False], + [3, 896, 896, 1, True], + [3, 896, 896, 1, False], + ], + }, +} + + +NET_CONFIG_REC = { + "tiny": { + # stem: simple (2×Conv2D_BN+GELU, mid=24, out=48) channels: 48 → 96 → 160 + "stem": (24, 48), + "stem_type": "simple", + "blocks2": [[3, 48, 48, 1, True]], + "blocks3": [[3, 48, 48, 1, False]], + "blocks4": [ + [3, 48, 96, (2, 1), False], + [3, 96, 96, 1, True], + [3, 96, 96, 1, False], + ], + "blocks5": [ + [3, 96, 160, (2, 1), False], + [3, 160, 160, 1, True], + [3, 160, 160, 1, False], + [3, 160, 160, 1, False], + ], + "blocks6": [], + }, + "small": { + # stem: branch StemBlock (mid=48, out=96) channels: 96 → 192 → 384 + "stem": (48, 96), + "stem_type": "branch", + "blocks2": [[3, 96, 96, 1, True]], + "blocks3": [[3, 96, 96, 1, False], [3, 96, 96, 1, False]], + "blocks4": [ + [3, 96, 192, (2, 1), False], + [3, 192, 192, 1, True], + [3, 192, 192, 1, False], + [3, 192, 192, 1, True], + [3, 192, 192, 1, False], + [3, 192, 192, 1, True], + [3, 192, 192, 1, False], + ], + "blocks5": [ + [3, 192, 384, (2, 1), False], + [3, 384, 384, 1, True], + [3, 384, 384, 1, False], + ], + "blocks6": [], + }, + "medium": { + # stem: branch StemBlock (mid=64, out=128) channels: 128 → 256 → 512 → 768 + "stem": (64, 128), + "stem_type": "branch", + "blocks2": [[3, 128, 128, 1, True]], + "blocks3": [ + [3, 128, 256, 1, False], + [3, 256, 256, 1, False], + [3, 256, 256, 1, True], + ], + "blocks4": [ + [3, 256, 512, (2, 1), False], + [3, 512, 512, 1, True], + [3, 512, 512, 1, False], + [3, 512, 512, 1, True], + [3, 512, 512, 1, False], + [3, 512, 512, 1, True], + [3, 512, 512, 1, False], + ], + "blocks5": [ + [3, 512, 768, (2, 1), False], + [3, 768, 768, 1, True], + [3, 768, 768, 1, False], + ], + "blocks6": [], + }, +} + + +class Conv2D_BN(nn.Sequential): + def __init__( + self, + in_channels, + out_channels, + kernel_size=1, + stride=1, + padding=0, + groups=1, + bn_weight_init=1.0, + ): + super().__init__() + self.add_sublayer( + "conv", + Conv2D( + in_channels, + out_channels, + kernel_size, + stride, + padding, + groups=groups, + bias_attr=False, + ), + ) + bn = BatchNorm2D(out_channels) + Constant(1.0 if bn_weight_init == 1.0 else 0.0)(bn.weight) + Constant(0.0)(bn.bias) + self.add_sublayer("bn", bn) + + @paddle.no_grad() + def fuse(self): + c, bn = self.conv, self.bn + w = bn.weight / (bn._variance + bn._epsilon) ** 0.5 + w = c.weight * w[:, None, None, None] + b = bn.bias - bn._mean * bn.weight / (bn._variance + bn._epsilon) ** 0.5 + m = Conv2D( + w.shape[1] * c._groups, + w.shape[0], + w.shape[2:], + stride=c._stride, + padding=c._padding, + groups=c._groups, + ) + m.weight.set_value(w) + m.bias.set_value(b) + return m + + +class ConvBNAct(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1, + groups=1, + use_act=True, + lr_mult=1.0, + ): + super().__init__() + self.use_act = use_act + self.is_repped = False + self.conv = Conv2D( + in_channels, + out_channels, + kernel_size, + stride, + padding=padding if isinstance(padding, str) else (kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(learning_rate=lr_mult), + bias_attr=False, + ) + self.bn = BatchNorm2D( + out_channels, + weight_attr=ParamAttr(regularizer=L2Decay(0.0), learning_rate=lr_mult), + bias_attr=ParamAttr(regularizer=L2Decay(0.0), learning_rate=lr_mult), + ) + if self.use_act: + self.act = ReLU() + + def forward(self, x): + x = self.conv(x) + if not self.is_repped: + x = self.bn(x) + if self.use_act: + x = self.act(x) + return x + + @paddle.no_grad() + def rep(self): + if self.is_repped: + return + c, bn = self.conv, self.bn + w = bn.weight / (bn._variance + bn._epsilon) ** 0.5 + fused_w = c.weight * w[:, None, None, None] + fused_b = bn.bias - bn._mean * bn.weight / (bn._variance + bn._epsilon) ** 0.5 + m = Conv2D( + c._in_channels, + c._out_channels, + c._kernel_size, + stride=c._stride, + padding=c._padding, + dilation=c._dilation, + groups=c._groups, + ) + m.weight.set_value(fused_w) + m.bias.set_value(fused_b) + self.conv = m + del self.bn + self.is_repped = True + + +class StemBlock(nn.Layer): + """Multi-branch stem with total stride 4 (stem1 stride=2 + stem3 stride=2).""" + + def __init__(self, in_channels=3, mid_channels=48, out_channels=96, lr_mult=1.0): + super().__init__() + self.is_repped = False + self.stem1 = ConvBNAct( + in_channels, mid_channels, 3, 2, use_act=True, lr_mult=lr_mult + ) + self.stem2a = ConvBNAct( + mid_channels, + mid_channels // 2, + 2, + 1, + padding="SAME", + use_act=True, + lr_mult=lr_mult, + ) + self.stem2b = ConvBNAct( + mid_channels // 2, + mid_channels, + 2, + 1, + padding="SAME", + use_act=True, + lr_mult=lr_mult, + ) + self.stem3 = ConvBNAct( + mid_channels * 2, mid_channels, 3, 2, use_act=True, lr_mult=lr_mult + ) + self.stem4 = ConvBNAct( + mid_channels, out_channels, 1, 1, use_act=True, lr_mult=lr_mult + ) + self.pool = nn.MaxPool2D(kernel_size=2, stride=1, padding="SAME") + + def forward(self, x): + x = self.stem1(x) + x2 = self.stem2b(self.stem2a(x)) + x1 = self.pool(x) + x = self.stem4(self.stem3(paddle.concat([x1, x2], axis=1))) + return x + + def rep(self, fuse_lab=None): + if self.is_repped: + return + for attr in ("stem1", "stem2a", "stem2b", "stem3", "stem4"): + getattr(self, attr).rep() + self.is_repped = True + + +class SELayer(nn.Layer): + def __init__(self, channel, reduction=4, lr_mult=1.0): + super().__init__() + self.conv1 = Conv2D( + channel, + channel // reduction, + 1, + weight_attr=ParamAttr(learning_rate=lr_mult), + bias_attr=ParamAttr(learning_rate=lr_mult), + ) + self.relu = ReLU() + self.conv2 = Conv2D( + channel // reduction, + channel, + 1, + weight_attr=ParamAttr(learning_rate=lr_mult), + bias_attr=ParamAttr(learning_rate=lr_mult), + ) + self.hardsigmoid = Hardsigmoid() + + def forward(self, x): + identity = x + x = x.mean(axis=[2, 3], keepdim=True) + x = self.relu(self.conv1(x)) + x = self.hardsigmoid(self.conv2(x)) + return paddle.multiply(x=identity, y=x) + + +class RepDWConv(nn.Layer): + """Reparameterizable depthwise convolution. + + Training: 3-branch (3×3 DW + 1×1 DW + identity BN) + Inference: fused into a single 3×3 DW Conv + """ + + def __init__(self, channels, kernel_size=3): + super().__init__() + self.channels = channels + self.kernel_size = kernel_size + padding = (kernel_size - 1) // 2 + + self.conv = Conv2D_BN( + channels, channels, kernel_size, 1, padding, groups=channels + ) + self.conv1 = Conv2D( + channels, channels, 1, 1, 0, groups=channels, bias_attr=False + ) + self.bn = BatchNorm2D(channels) + Constant(1.0)(self.bn.weight) + Constant(0.0)(self.bn.bias) + + self.is_repped = False + self.reparam_conv = None + + def forward(self, x): + if self.is_repped: + return self.reparam_conv(x) + return self.bn(self.conv(x) + self.conv1(x) + x) + + def rep(self, fuse_lab=None): + if self.is_repped: + return + fused = self._fuse_conv() + padding = (self.kernel_size - 1) // 2 + self.reparam_conv = Conv2D( + self.channels, + self.channels, + self.kernel_size, + 1, + padding, + groups=self.channels, + ) + self.reparam_conv.weight.set_value(fused.weight) + self.reparam_conv.bias.set_value(fused.bias) + self.__delattr__("conv") + self.__delattr__("conv1") + self.__delattr__("bn") + self.is_repped = True + + @paddle.no_grad() + def _fuse_conv(self): + conv = self.conv.fuse() + pad_size = self.kernel_size // 2 + conv1_w = F.pad(self.conv1.weight, [pad_size, pad_size, pad_size, pad_size]) + identity = F.pad( + paddle.ones([self.conv1.weight.shape[0], self.conv1.weight.shape[1], 1, 1]), + [pad_size, pad_size, pad_size, pad_size], + ) + w = conv.weight + conv1_w + identity + conv.weight.set_value(w) + bn = self.bn + scale = bn.weight / (bn._variance + bn._epsilon) ** 0.5 + conv.weight.set_value(conv.weight * scale[:, None, None, None]) + conv.bias.set_value(bn.bias + (conv.bias - bn._mean) * scale) + return conv + + def fuse(self): + return self._fuse_conv() + + +class LCNetV4Block(nn.Layer): + """LCNetV4 block for detection and recognition. + + Token mixer: RepDWConv when stride=1 and in==out, else plain Conv2D_BN DW conv. + Channel mixer: expand → act → compress (+ residual when stride=1 and in==out) + rep() fuses all Conv2D_BN layers (mathematically exact, no accuracy change). + """ + + def __init__( + self, + in_channels, + out_channels, + stride, + dw_size, + use_se=False, + lr_mult=1.0, + expand_ratio=2, + act_type="gelu", + ): + super().__init__() + self.is_repped = False + self.has_residual = in_channels == out_channels and stride == 1 + self.use_rep_dw = stride == 1 and in_channels == out_channels + + self.token_mixer = nn.Sequential() + if self.use_rep_dw: + self.token_mixer.add_sublayer("rep_dw", RepDWConv(in_channels, dw_size)) + else: + padding = (dw_size - 1) // 2 + self.token_mixer.add_sublayer( + "dw_conv", + Conv2D_BN( + in_channels, + in_channels, + dw_size, + stride, + padding, + groups=in_channels, + ), + ) + if use_se: + self.token_mixer.add_sublayer("se", SELayer(in_channels, lr_mult=lr_mult)) + + hidden_channels = int(in_channels * expand_ratio) + compress_bn_init = 0.0 if self.has_residual else 1.0 + self.channel_mixer = nn.Sequential() + self.channel_mixer.add_sublayer( + "expand", Conv2D_BN(in_channels, hidden_channels, 1, 1, 0) + ) + if act_type == "gelu": + self.channel_mixer.add_sublayer("act", GELU()) + elif act_type == "hswish": + self.channel_mixer.add_sublayer("act", Hardswish()) + elif act_type == "relu": + self.channel_mixer.add_sublayer("act", ReLU()) + self.channel_mixer.add_sublayer( + "compress", + Conv2D_BN( + hidden_channels, out_channels, 1, 1, 0, bn_weight_init=compress_bn_init + ), + ) + + def forward(self, x): + x = self.token_mixer(x) + if self.has_residual: + return x + self.channel_mixer(x) + return self.channel_mixer(x) + + def rep(self, fuse_lab=None): + if self.is_repped: + return + if self.use_rep_dw: + self.token_mixer.rep_dw.rep(fuse_lab=fuse_lab) + else: + self.token_mixer.dw_conv = self.token_mixer.dw_conv.fuse() + for name in ("expand", "compress"): + m = getattr(self.channel_mixer, name, None) + if isinstance(m, Conv2D_BN): + setattr(self.channel_mixer, name, m.fuse()) + self.is_repped = True + + +class PPLCNetV4(nn.Layer): + """Unified PPLCNetV4 backbone for text detection and recognition. + + Detection (det=True): + model_size in {'tiny', 'small', 'medium'} — see NET_CONFIG_DET. + Returns 4-level feature list [s1_out, s2_out, s3_out, s4_out]. + + Recognition (det=False): + model_size in {'tiny', 'small', 'medium'} — see NET_CONFIG_REC. + Returns pooled feature tensor [B, C, 1, W]. + """ + + def __init__( + self, + det=False, + model_size="small", + in_channels=3, + lr_mult_list=[1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + **kwargs, + ): + super().__init__() + self.det = det + self.is_repped = False + + if det: + assert ( + model_size in NET_CONFIG_DET + ), "det model_size must be one of {} but got '{}'".format( + list(NET_CONFIG_DET.keys()), model_size + ) + cfg = NET_CONFIG_DET[model_size] + stem_mid, stem_out = cfg["stem"] + self.stem = StemBlock(in_channels, stem_mid, stem_out) + + def make_stage(key): + return nn.Sequential( + *[ + LCNetV4Block(in_c, out_c, s, k, se, expand_ratio=2) + for k, in_c, out_c, s, se in cfg[key] + ] + ) + + self.blocks_s1 = make_stage("blocks_s1") + self.blocks_s2 = make_stage("blocks_s2") + self.blocks_s3 = make_stage("blocks_s3") + self.blocks_s4 = make_stage("blocks_s4") + self.out_channels = [ + cfg["blocks_s1"][-1][2], + cfg["blocks_s2"][-1][2], + cfg["blocks_s3"][-1][2], + cfg["blocks_s4"][-1][2], + ] + + else: + assert isinstance(lr_mult_list, (list, tuple)) and len(lr_mult_list) == 6 + assert ( + model_size in NET_CONFIG_REC + ), "rec model_size must be one of {} but got '{}'".format( + list(NET_CONFIG_REC.keys()), model_size + ) + self.lr_mult_list = lr_mult_list + cfg = NET_CONFIG_REC[model_size] + stem_mid, stem_out = cfg["stem"] + if cfg["stem_type"] == "branch": + self.conv1 = StemBlock(3, stem_mid, stem_out, lr_mult=lr_mult_list[0]) + else: + self.conv1 = nn.Sequential( + Conv2D_BN(3, stem_mid, 3, 2, 1), + GELU(), + Conv2D_BN(stem_mid, stem_out, 3, 2, 1), + ) + + def make_stage(stage_name, lr_idx): + return nn.Sequential( + *[ + LCNetV4Block( + in_c, + out_c, + s, + k, + se, + lr_mult=lr_mult_list[lr_idx], + expand_ratio=2, + ) + for k, in_c, out_c, s, se in cfg.get(stage_name, []) + ] + ) + + self.blocks2 = make_stage("blocks2", 1) + self.blocks3 = make_stage("blocks3", 2) + self.blocks4 = make_stage("blocks4", 3) + self.blocks5 = make_stage("blocks5", 4) + self.blocks6 = make_stage("blocks6", 5) + + for sname in reversed( + ["blocks2", "blocks3", "blocks4", "blocks5", "blocks6"] + ): + if cfg.get(sname): + self.out_channels = cfg[sname][-1][2] + break + + def forward(self, x): + if self.det: + x = self.stem(x) + o1 = self.blocks_s1(x) + o2 = self.blocks_s2(o1) + o3 = self.blocks_s3(o2) + o4 = self.blocks_s4(o3) + return [o1, o2, o3, o4] + else: + x = self.conv1(x) + x = self.blocks2(x) + x = self.blocks3(x) + x = self.blocks4(x) + x = self.blocks5(x) + x = self.blocks6(x) + if self.training: + x = F.adaptive_avg_pool2d(x, [1, 40]) + else: + assert x.shape[2] >= 3, f"Feature height {x.shape[2]} < pool kernel 3." + x = F.avg_pool2d(x, [3, 2]) + return x + + def rep(self, fuse_lab=None): + if self.is_repped: + return + if self.det: + self.stem.rep() + for stage in [ + self.blocks_s1, + self.blocks_s2, + self.blocks_s3, + self.blocks_s4, + ]: + for block in stage: + block.rep(fuse_lab=fuse_lab) + else: + if hasattr(self.conv1, "rep"): + self.conv1.rep() + for stage in [ + self.blocks2, + self.blocks3, + self.blocks4, + self.blocks5, + self.blocks6, + ]: + for block in stage: + block.rep() + self.is_repped = True diff --git a/ppocr/modeling/backbones/rec_micronet.py b/ppocr/modeling/backbones/rec_micronet.py new file mode 100644 index 0000000..f554a7d --- /dev/null +++ b/ppocr/modeling/backbones/rec_micronet.py @@ -0,0 +1,605 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/liyunsheng13/micronet/blob/main/backbone/micronet.py +https://github.com/liyunsheng13/micronet/blob/main/backbone/activation.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +import paddle.nn as nn + +from ppocr.modeling.backbones.det_mobilenet_v3 import make_divisible + +M0_cfgs = [ + # s, n, c, ks, c1, c2, g1, g2, c3, g3, g4, y1, y2, y3, r + [2, 1, 8, 3, 2, 2, 0, 4, 8, 2, 2, 2, 0, 1, 1], + [2, 1, 12, 3, 2, 2, 0, 8, 12, 4, 4, 2, 2, 1, 1], + [2, 1, 16, 5, 2, 2, 0, 12, 16, 4, 4, 2, 2, 1, 1], + [1, 1, 32, 5, 1, 4, 4, 4, 32, 4, 4, 2, 2, 1, 1], + [2, 1, 64, 5, 1, 4, 8, 8, 64, 8, 8, 2, 2, 1, 1], + [1, 1, 96, 3, 1, 4, 8, 8, 96, 8, 8, 2, 2, 1, 2], + [1, 1, 384, 3, 1, 4, 12, 12, 0, 0, 0, 2, 2, 1, 2], +] +M1_cfgs = [ + # s, n, c, ks, c1, c2, g1, g2, c3, g3, g4 + [2, 1, 8, 3, 2, 2, 0, 6, 8, 2, 2, 2, 0, 1, 1], + [2, 1, 16, 3, 2, 2, 0, 8, 16, 4, 4, 2, 2, 1, 1], + [2, 1, 16, 5, 2, 2, 0, 16, 16, 4, 4, 2, 2, 1, 1], + [1, 1, 32, 5, 1, 6, 4, 4, 32, 4, 4, 2, 2, 1, 1], + [2, 1, 64, 5, 1, 6, 8, 8, 64, 8, 8, 2, 2, 1, 1], + [1, 1, 96, 3, 1, 6, 8, 8, 96, 8, 8, 2, 2, 1, 2], + [1, 1, 576, 3, 1, 6, 12, 12, 0, 0, 0, 2, 2, 1, 2], +] +M2_cfgs = [ + # s, n, c, ks, c1, c2, g1, g2, c3, g3, g4 + [2, 1, 12, 3, 2, 2, 0, 8, 12, 4, 4, 2, 0, 1, 1], + [2, 1, 16, 3, 2, 2, 0, 12, 16, 4, 4, 2, 2, 1, 1], + [1, 1, 24, 3, 2, 2, 0, 16, 24, 4, 4, 2, 2, 1, 1], + [2, 1, 32, 5, 1, 6, 6, 6, 32, 4, 4, 2, 2, 1, 1], + [1, 1, 32, 5, 1, 6, 8, 8, 32, 4, 4, 2, 2, 1, 2], + [1, 1, 64, 5, 1, 6, 8, 8, 64, 8, 8, 2, 2, 1, 2], + [2, 1, 96, 5, 1, 6, 8, 8, 96, 8, 8, 2, 2, 1, 2], + [1, 1, 128, 3, 1, 6, 12, 12, 128, 8, 8, 2, 2, 1, 2], + [1, 1, 768, 3, 1, 6, 16, 16, 0, 0, 0, 2, 2, 1, 2], +] +M3_cfgs = [ + # s, n, c, ks, c1, c2, g1, g2, c3, g3, g4 + [2, 1, 16, 3, 2, 2, 0, 12, 16, 4, 4, 0, 2, 0, 1], + [2, 1, 24, 3, 2, 2, 0, 16, 24, 4, 4, 0, 2, 0, 1], + [1, 1, 24, 3, 2, 2, 0, 24, 24, 4, 4, 0, 2, 0, 1], + [2, 1, 32, 5, 1, 6, 6, 6, 32, 4, 4, 0, 2, 0, 1], + [1, 1, 32, 5, 1, 6, 8, 8, 32, 4, 4, 0, 2, 0, 2], + [1, 1, 64, 5, 1, 6, 8, 8, 48, 8, 8, 0, 2, 0, 2], + [1, 1, 80, 5, 1, 6, 8, 8, 80, 8, 8, 0, 2, 0, 2], + [1, 1, 80, 5, 1, 6, 10, 10, 80, 8, 8, 0, 2, 0, 2], + [1, 1, 120, 5, 1, 6, 10, 10, 120, 10, 10, 0, 2, 0, 2], + [1, 1, 120, 5, 1, 6, 12, 12, 120, 10, 10, 0, 2, 0, 2], + [1, 1, 144, 3, 1, 6, 12, 12, 144, 12, 12, 0, 2, 0, 2], + [1, 1, 432, 3, 1, 3, 12, 12, 0, 0, 0, 0, 2, 0, 2], +] + + +def get_micronet_config(mode): + return eval(mode + "_cfgs") + + +class MaxGroupPooling(nn.Layer): + def __init__(self, channel_per_group=2): + super(MaxGroupPooling, self).__init__() + self.channel_per_group = channel_per_group + + def forward(self, x): + if self.channel_per_group == 1: + return x + # max op + b, c, h, w = x.shape + + # reshape + y = paddle.reshape(x, [b, c // self.channel_per_group, -1, h, w]) + out = paddle.max(y, axis=2) + return out + + +class SpatialSepConvSF(nn.Layer): + def __init__(self, inp, oups, kernel_size, stride): + super(SpatialSepConvSF, self).__init__() + + oup1, oup2 = oups + self.conv = nn.Sequential( + nn.Conv2D( + inp, + oup1, + (kernel_size, 1), + (stride, 1), + (kernel_size // 2, 0), + bias_attr=False, + groups=1, + ), + nn.BatchNorm2D(oup1), + nn.Conv2D( + oup1, + oup1 * oup2, + (1, kernel_size), + (1, stride), + (0, kernel_size // 2), + bias_attr=False, + groups=oup1, + ), + nn.BatchNorm2D(oup1 * oup2), + ChannelShuffle(oup1), + ) + + def forward(self, x): + out = self.conv(x) + return out + + +class ChannelShuffle(nn.Layer): + def __init__(self, groups): + super(ChannelShuffle, self).__init__() + self.groups = groups + + def forward(self, x): + b, c, h, w = x.shape + + channels_per_group = c // self.groups + + # reshape + x = paddle.reshape(x, [b, self.groups, channels_per_group, h, w]) + + x = paddle.transpose(x, (0, 2, 1, 3, 4)) + out = paddle.reshape(x, [b, -1, h, w]) + + return out + + +class StemLayer(nn.Layer): + def __init__(self, inp, oup, stride, groups=(4, 4)): + super(StemLayer, self).__init__() + + g1, g2 = groups + self.stem = nn.Sequential( + SpatialSepConvSF(inp, groups, 3, stride), + MaxGroupPooling(2) if g1 * g2 == 2 * oup else nn.ReLU6(), + ) + + def forward(self, x): + out = self.stem(x) + return out + + +class DepthSpatialSepConv(nn.Layer): + def __init__(self, inp, expand, kernel_size, stride): + super(DepthSpatialSepConv, self).__init__() + + exp1, exp2 = expand + + hidden_dim = inp * exp1 + oup = inp * exp1 * exp2 + + self.conv = nn.Sequential( + nn.Conv2D( + inp, + inp * exp1, + (kernel_size, 1), + (stride, 1), + (kernel_size // 2, 0), + bias_attr=False, + groups=inp, + ), + nn.BatchNorm2D(inp * exp1), + nn.Conv2D( + hidden_dim, + oup, + (1, kernel_size), + 1, + (0, kernel_size // 2), + bias_attr=False, + groups=hidden_dim, + ), + nn.BatchNorm2D(oup), + ) + + def forward(self, x): + x = self.conv(x) + return x + + +class GroupConv(nn.Layer): + def __init__(self, inp, oup, groups=2): + super(GroupConv, self).__init__() + self.inp = inp + self.oup = oup + self.groups = groups + self.conv = nn.Sequential( + nn.Conv2D(inp, oup, 1, 1, 0, bias_attr=False, groups=self.groups[0]), + nn.BatchNorm2D(oup), + ) + + def forward(self, x): + x = self.conv(x) + return x + + +class DepthConv(nn.Layer): + def __init__(self, inp, oup, kernel_size, stride): + super(DepthConv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2D( + inp, + oup, + kernel_size, + stride, + kernel_size // 2, + bias_attr=False, + groups=inp, + ), + nn.BatchNorm2D(oup), + ) + + def forward(self, x): + out = self.conv(x) + return out + + +class DYShiftMax(nn.Layer): + def __init__( + self, + inp, + oup, + reduction=4, + act_max=1.0, + act_relu=True, + init_a=[0.0, 0.0], + init_b=[0.0, 0.0], + relu_before_pool=False, + g=None, + expansion=False, + ): + super(DYShiftMax, self).__init__() + self.oup = oup + self.act_max = act_max * 2 + self.act_relu = act_relu + self.avg_pool = nn.Sequential( + nn.ReLU() if relu_before_pool == True else nn.Sequential(), + nn.AdaptiveAvgPool2D(1), + ) + + self.exp = 4 if act_relu else 2 + self.init_a = init_a + self.init_b = init_b + + # determine squeeze + squeeze = make_divisible(inp // reduction, 4) + if squeeze < 4: + squeeze = 4 + + self.fc = nn.Sequential( + nn.Linear(inp, squeeze), + nn.ReLU(), + nn.Linear(squeeze, oup * self.exp), + nn.Hardsigmoid(), + ) + + if g is None: + g = 1 + self.g = g[1] + if self.g != 1 and expansion: + self.g = inp // self.g + + self.gc = inp // self.g + index = paddle.to_tensor([range(inp)]) + index = paddle.reshape(index, [1, inp, 1, 1]) + index = paddle.reshape(index, [1, self.g, self.gc, 1, 1]) + indexgs = paddle.split(index, [1, self.g - 1], axis=1) + indexgs = paddle.concat((indexgs[1], indexgs[0]), axis=1) + indexes = paddle.split(indexgs, [1, self.gc - 1], axis=2) + indexes = paddle.concat((indexes[1], indexes[0]), axis=2) + self.index = paddle.reshape(indexes, [inp]) + self.expansion = expansion + + def forward(self, x): + x_in = x + x_out = x + + b, c, _, _ = x_in.shape + y = self.avg_pool(x_in) + y = paddle.reshape(y, [b, c]) + y = self.fc(y) + y = paddle.reshape(y, [b, self.oup * self.exp, 1, 1]) + y = (y - 0.5) * self.act_max + + n2, c2, h2, w2 = x_out.shape + x2 = paddle.to_tensor(x_out.numpy()[:, self.index.numpy(), :, :]) + + if self.exp == 4: + temp = y.shape + a1, b1, a2, b2 = paddle.split(y, temp[1] // self.oup, axis=1) + + a1 = a1 + self.init_a[0] + a2 = a2 + self.init_a[1] + + b1 = b1 + self.init_b[0] + b2 = b2 + self.init_b[1] + + z1 = x_out * a1 + x2 * b1 + z2 = x_out * a2 + x2 * b2 + + out = paddle.maximum(z1, z2) + + elif self.exp == 2: + temp = y.shape + a1, b1 = paddle.split(y, temp[1] // self.oup, axis=1) + a1 = a1 + self.init_a[0] + b1 = b1 + self.init_b[0] + out = x_out * a1 + x2 * b1 + + return out + + +class DYMicroBlock(nn.Layer): + def __init__( + self, + inp, + oup, + kernel_size=3, + stride=1, + ch_exp=(2, 2), + ch_per_group=4, + groups_1x1=(1, 1), + depthsep=True, + shuffle=False, + activation_cfg=None, + ): + super(DYMicroBlock, self).__init__() + + self.identity = stride == 1 and inp == oup + + y1, y2, y3 = activation_cfg["dy"] + act_reduction = 8 * activation_cfg["ratio"] + init_a = activation_cfg["init_a"] + init_b = activation_cfg["init_b"] + + t1 = ch_exp + gs1 = ch_per_group + hidden_fft, g1, g2 = groups_1x1 + hidden_dim2 = inp * t1[0] * t1[1] + + if gs1[0] == 0: + self.layers = nn.Sequential( + DepthSpatialSepConv(inp, t1, kernel_size, stride), + ( + DYShiftMax( + hidden_dim2, + hidden_dim2, + act_max=2.0, + act_relu=True if y2 == 2 else False, + init_a=init_a, + reduction=act_reduction, + init_b=init_b, + g=gs1, + expansion=False, + ) + if y2 > 0 + else nn.ReLU6() + ), + ChannelShuffle(gs1[1]) if shuffle else nn.Sequential(), + ( + ChannelShuffle(hidden_dim2 // 2) + if shuffle and y2 != 0 + else nn.Sequential() + ), + GroupConv(hidden_dim2, oup, (g1, g2)), + ( + DYShiftMax( + oup, + oup, + act_max=2.0, + act_relu=False, + init_a=[1.0, 0.0], + reduction=act_reduction // 2, + init_b=[0.0, 0.0], + g=(g1, g2), + expansion=False, + ) + if y3 > 0 + else nn.Sequential() + ), + ChannelShuffle(g2) if shuffle else nn.Sequential(), + ( + ChannelShuffle(oup // 2) + if shuffle and oup % 2 == 0 and y3 != 0 + else nn.Sequential() + ), + ) + elif g2 == 0: + self.layers = nn.Sequential( + GroupConv(inp, hidden_dim2, gs1), + ( + DYShiftMax( + hidden_dim2, + hidden_dim2, + act_max=2.0, + act_relu=False, + init_a=[1.0, 0.0], + reduction=act_reduction, + init_b=[0.0, 0.0], + g=gs1, + expansion=False, + ) + if y3 > 0 + else nn.Sequential() + ), + ) + else: + self.layers = nn.Sequential( + GroupConv(inp, hidden_dim2, gs1), + ( + DYShiftMax( + hidden_dim2, + hidden_dim2, + act_max=2.0, + act_relu=True if y1 == 2 else False, + init_a=init_a, + reduction=act_reduction, + init_b=init_b, + g=gs1, + expansion=False, + ) + if y1 > 0 + else nn.ReLU6() + ), + ChannelShuffle(gs1[1]) if shuffle else nn.Sequential(), + ( + DepthSpatialSepConv(hidden_dim2, (1, 1), kernel_size, stride) + if depthsep + else DepthConv(hidden_dim2, hidden_dim2, kernel_size, stride) + ), + nn.Sequential(), + ( + DYShiftMax( + hidden_dim2, + hidden_dim2, + act_max=2.0, + act_relu=True if y2 == 2 else False, + init_a=init_a, + reduction=act_reduction, + init_b=init_b, + g=gs1, + expansion=True, + ) + if y2 > 0 + else nn.ReLU6() + ), + ( + ChannelShuffle(hidden_dim2 // 4) + if shuffle and y1 != 0 and y2 != 0 + else ( + nn.Sequential() + if y1 == 0 and y2 == 0 + else ChannelShuffle(hidden_dim2 // 2) + ) + ), + GroupConv(hidden_dim2, oup, (g1, g2)), + ( + DYShiftMax( + oup, + oup, + act_max=2.0, + act_relu=False, + init_a=[1.0, 0.0], + reduction=( + act_reduction // 2 if oup < hidden_dim2 else act_reduction + ), + init_b=[0.0, 0.0], + g=(g1, g2), + expansion=False, + ) + if y3 > 0 + else nn.Sequential() + ), + ChannelShuffle(g2) if shuffle else nn.Sequential(), + ChannelShuffle(oup // 2) if shuffle and y3 != 0 else nn.Sequential(), + ) + + def forward(self, x): + identity = x + out = self.layers(x) + + if self.identity: + out = out + identity + + return out + + +class MicroNet(nn.Layer): + """ + the MicroNet backbone network for recognition module. + Args: + mode(str): {'M0', 'M1', 'M2', 'M3'} + Four models are proposed based on four different computational costs (4M, 6M, 12M, 21M MAdds) + Default: 'M3'. + """ + + def __init__(self, mode="M3", **kwargs): + super(MicroNet, self).__init__() + + self.cfgs = get_micronet_config(mode) + + activation_cfg = {} + if mode == "M0": + input_channel = 4 + stem_groups = 2, 2 + out_ch = 384 + activation_cfg["init_a"] = 1.0, 1.0 + activation_cfg["init_b"] = 0.0, 0.0 + elif mode == "M1": + input_channel = 6 + stem_groups = 3, 2 + out_ch = 576 + activation_cfg["init_a"] = 1.0, 1.0 + activation_cfg["init_b"] = 0.0, 0.0 + elif mode == "M2": + input_channel = 8 + stem_groups = 4, 2 + out_ch = 768 + activation_cfg["init_a"] = 1.0, 1.0 + activation_cfg["init_b"] = 0.0, 0.0 + elif mode == "M3": + input_channel = 12 + stem_groups = 4, 3 + out_ch = 432 + activation_cfg["init_a"] = 1.0, 0.5 + activation_cfg["init_b"] = 0.0, 0.5 + else: + raise NotImplementedError("mode[" + mode + "_model] is not implemented!") + + layers = [StemLayer(3, input_channel, stride=2, groups=stem_groups)] + + for idx, val in enumerate(self.cfgs): + s, n, c, ks, c1, c2, g1, g2, c3, g3, g4, y1, y2, y3, r = val + + t1 = (c1, c2) + gs1 = (g1, g2) + gs2 = (c3, g3, g4) + activation_cfg["dy"] = [y1, y2, y3] + activation_cfg["ratio"] = r + + output_channel = c + layers.append( + DYMicroBlock( + input_channel, + output_channel, + kernel_size=ks, + stride=s, + ch_exp=t1, + ch_per_group=gs1, + groups_1x1=gs2, + depthsep=True, + shuffle=True, + activation_cfg=activation_cfg, + ) + ) + input_channel = output_channel + for i in range(1, n): + layers.append( + DYMicroBlock( + input_channel, + output_channel, + kernel_size=ks, + stride=1, + ch_exp=t1, + ch_per_group=gs1, + groups_1x1=gs2, + depthsep=True, + shuffle=True, + activation_cfg=activation_cfg, + ) + ) + input_channel = output_channel + self.features = nn.Sequential(*layers) + + self.pool = nn.MaxPool2D(kernel_size=2, stride=2, padding=0) + + self.out_channels = make_divisible(out_ch) + + def forward(self, x): + x = self.features(x) + x = self.pool(x) + return x diff --git a/ppocr/modeling/backbones/rec_mobilenet_v3.py b/ppocr/modeling/backbones/rec_mobilenet_v3.py new file mode 100644 index 0000000..00ee5a3 --- /dev/null +++ b/ppocr/modeling/backbones/rec_mobilenet_v3.py @@ -0,0 +1,156 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle import nn + +from ppocr.modeling.backbones.det_mobilenet_v3 import ( + ResidualUnit, + ConvBNLayer, + make_divisible, +) + +__all__ = ["MobileNetV3"] + + +class MobileNetV3(nn.Layer): + def __init__( + self, + in_channels=3, + model_name="small", + scale=0.5, + large_stride=None, + small_stride=None, + disable_se=False, + **kwargs, + ): + super(MobileNetV3, self).__init__() + self.disable_se = disable_se + if small_stride is None: + small_stride = [2, 2, 2, 2] + if large_stride is None: + large_stride = [1, 2, 2, 2] + + assert isinstance( + large_stride, list + ), "large_stride type must " "be list but got {}".format(type(large_stride)) + assert isinstance( + small_stride, list + ), "small_stride type must " "be list but got {}".format(type(small_stride)) + assert ( + len(large_stride) == 4 + ), "large_stride length must be " "4 but got {}".format(len(large_stride)) + assert ( + len(small_stride) == 4 + ), "small_stride length must be " "4 but got {}".format(len(small_stride)) + + if model_name == "large": + cfg = [ + # k, exp, c, se, nl, s, + [3, 16, 16, False, "relu", large_stride[0]], + [3, 64, 24, False, "relu", (large_stride[1], 1)], + [3, 72, 24, False, "relu", 1], + [5, 72, 40, True, "relu", (large_stride[2], 1)], + [5, 120, 40, True, "relu", 1], + [5, 120, 40, True, "relu", 1], + [3, 240, 80, False, "hardswish", 1], + [3, 200, 80, False, "hardswish", 1], + [3, 184, 80, False, "hardswish", 1], + [3, 184, 80, False, "hardswish", 1], + [3, 480, 112, True, "hardswish", 1], + [3, 672, 112, True, "hardswish", 1], + [5, 672, 160, True, "hardswish", (large_stride[3], 1)], + [5, 960, 160, True, "hardswish", 1], + [5, 960, 160, True, "hardswish", 1], + ] + cls_ch_squeeze = 960 + elif model_name == "small": + cfg = [ + # k, exp, c, se, nl, s, + [3, 16, 16, True, "relu", (small_stride[0], 1)], + [3, 72, 24, False, "relu", (small_stride[1], 1)], + [3, 88, 24, False, "relu", 1], + [5, 96, 40, True, "hardswish", (small_stride[2], 1)], + [5, 240, 40, True, "hardswish", 1], + [5, 240, 40, True, "hardswish", 1], + [5, 120, 48, True, "hardswish", 1], + [5, 144, 48, True, "hardswish", 1], + [5, 288, 96, True, "hardswish", (small_stride[3], 1)], + [5, 576, 96, True, "hardswish", 1], + [5, 576, 96, True, "hardswish", 1], + ] + cls_ch_squeeze = 576 + else: + raise NotImplementedError( + "mode[" + model_name + "_model] is not implemented!" + ) + + supported_scale = [0.35, 0.5, 0.75, 1.0, 1.25] + assert ( + scale in supported_scale + ), "supported scales are {} but input scale is {}".format( + supported_scale, scale + ) + + inplanes = 16 + # conv1 + self.conv1 = ConvBNLayer( + in_channels=in_channels, + out_channels=make_divisible(inplanes * scale), + kernel_size=3, + stride=2, + padding=1, + groups=1, + if_act=True, + act="hardswish", + ) + i = 0 + block_list = [] + inplanes = make_divisible(inplanes * scale) + for k, exp, c, se, nl, s in cfg: + se = se and not self.disable_se + block_list.append( + ResidualUnit( + in_channels=inplanes, + mid_channels=make_divisible(scale * exp), + out_channels=make_divisible(scale * c), + kernel_size=k, + stride=s, + use_se=se, + act=nl, + ) + ) + inplanes = make_divisible(scale * c) + i += 1 + self.blocks = nn.Sequential(*block_list) + + self.conv2 = ConvBNLayer( + in_channels=inplanes, + out_channels=make_divisible(scale * cls_ch_squeeze), + kernel_size=1, + stride=1, + padding=0, + groups=1, + if_act=True, + act="hardswish", + ) + + self.pool = nn.MaxPool2D(kernel_size=2, stride=2, padding=0) + self.out_channels = make_divisible(scale * cls_ch_squeeze) + + def forward(self, x): + x = self.conv1(x) + x = self.blocks(x) + x = self.conv2(x) + x = self.pool(x) + return x diff --git a/ppocr/modeling/backbones/rec_mv1_enhance.py b/ppocr/modeling/backbones/rec_mv1_enhance.py new file mode 100644 index 0000000..f20fa4c --- /dev/null +++ b/ppocr/modeling/backbones/rec_mv1_enhance.py @@ -0,0 +1,283 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This code is refer from: https://github.com/PaddlePaddle/PaddleClas/blob/develop/ppcls/arch/backbone/legendary_models/pp_lcnet.py + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import numpy as np +import paddle +from paddle import ParamAttr, reshape, transpose +import paddle.nn as nn +import paddle.nn.functional as F +from paddle.nn import Conv2D, BatchNorm, Linear, Dropout +from paddle.nn import AdaptiveAvgPool2D, MaxPool2D, AvgPool2D +from paddle.nn.initializer import KaimingNormal +from paddle.regularizer import L2Decay +from paddle.nn.functional import hardswish, hardsigmoid + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + num_channels, + filter_size, + num_filters, + stride, + padding, + channels=None, + num_groups=1, + act="hard_swish", + ): + super(ConvBNLayer, self).__init__() + + self._conv = Conv2D( + in_channels=num_channels, + out_channels=num_filters, + kernel_size=filter_size, + stride=stride, + padding=padding, + groups=num_groups, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False, + ) + + self._batch_norm = BatchNorm( + num_filters, + act=act, + param_attr=ParamAttr(regularizer=L2Decay(0.0)), + bias_attr=ParamAttr(regularizer=L2Decay(0.0)), + ) + + def forward(self, inputs): + y = self._conv(inputs) + y = self._batch_norm(y) + return y + + +class DepthwiseSeparable(nn.Layer): + def __init__( + self, + num_channels, + num_filters1, + num_filters2, + num_groups, + stride, + scale, + dw_size=3, + padding=1, + use_se=False, + ): + super(DepthwiseSeparable, self).__init__() + self.use_se = use_se + self._depthwise_conv = ConvBNLayer( + num_channels=num_channels, + num_filters=int(num_filters1 * scale), + filter_size=dw_size, + stride=stride, + padding=padding, + num_groups=int(num_groups * scale), + ) + if use_se: + self._se = SEModule(int(num_filters1 * scale)) + self._pointwise_conv = ConvBNLayer( + num_channels=int(num_filters1 * scale), + filter_size=1, + num_filters=int(num_filters2 * scale), + stride=1, + padding=0, + ) + + def forward(self, inputs): + y = self._depthwise_conv(inputs) + if self.use_se: + y = self._se(y) + y = self._pointwise_conv(y) + return y + + +class MobileNetV1Enhance(nn.Layer): + def __init__( + self, + in_channels=3, + scale=0.5, + last_conv_stride=1, + last_pool_type="max", + last_pool_kernel_size=[3, 2], + **kwargs, + ): + super().__init__() + self.scale = scale + self.block_list = [] + + self.conv1 = ConvBNLayer( + num_channels=3, + filter_size=3, + channels=3, + num_filters=int(32 * scale), + stride=2, + padding=1, + ) + + conv2_1 = DepthwiseSeparable( + num_channels=int(32 * scale), + num_filters1=32, + num_filters2=64, + num_groups=32, + stride=1, + scale=scale, + ) + self.block_list.append(conv2_1) + + conv2_2 = DepthwiseSeparable( + num_channels=int(64 * scale), + num_filters1=64, + num_filters2=128, + num_groups=64, + stride=1, + scale=scale, + ) + self.block_list.append(conv2_2) + + conv3_1 = DepthwiseSeparable( + num_channels=int(128 * scale), + num_filters1=128, + num_filters2=128, + num_groups=128, + stride=1, + scale=scale, + ) + self.block_list.append(conv3_1) + + conv3_2 = DepthwiseSeparable( + num_channels=int(128 * scale), + num_filters1=128, + num_filters2=256, + num_groups=128, + stride=(2, 1), + scale=scale, + ) + self.block_list.append(conv3_2) + + conv4_1 = DepthwiseSeparable( + num_channels=int(256 * scale), + num_filters1=256, + num_filters2=256, + num_groups=256, + stride=1, + scale=scale, + ) + self.block_list.append(conv4_1) + + conv4_2 = DepthwiseSeparable( + num_channels=int(256 * scale), + num_filters1=256, + num_filters2=512, + num_groups=256, + stride=(2, 1), + scale=scale, + ) + self.block_list.append(conv4_2) + + for _ in range(5): + conv5 = DepthwiseSeparable( + num_channels=int(512 * scale), + num_filters1=512, + num_filters2=512, + num_groups=512, + stride=1, + dw_size=5, + padding=2, + scale=scale, + use_se=False, + ) + self.block_list.append(conv5) + + conv5_6 = DepthwiseSeparable( + num_channels=int(512 * scale), + num_filters1=512, + num_filters2=1024, + num_groups=512, + stride=(2, 1), + dw_size=5, + padding=2, + scale=scale, + use_se=True, + ) + self.block_list.append(conv5_6) + + conv6 = DepthwiseSeparable( + num_channels=int(1024 * scale), + num_filters1=1024, + num_filters2=1024, + num_groups=1024, + stride=last_conv_stride, + dw_size=5, + padding=2, + use_se=True, + scale=scale, + ) + self.block_list.append(conv6) + + self.block_list = nn.Sequential(*self.block_list) + if last_pool_type == "avg": + self.pool = nn.AvgPool2D( + kernel_size=last_pool_kernel_size, + stride=last_pool_kernel_size, + padding=0, + ) + else: + self.pool = nn.MaxPool2D(kernel_size=2, stride=2, padding=0) + self.out_channels = int(1024 * scale) + + def forward(self, inputs): + y = self.conv1(inputs) + y = self.block_list(y) + y = self.pool(y) + return y + + +class SEModule(nn.Layer): + def __init__(self, channel, reduction=4): + super(SEModule, self).__init__() + self.avg_pool = AdaptiveAvgPool2D(1) + self.conv1 = Conv2D( + in_channels=channel, + out_channels=channel // reduction, + kernel_size=1, + stride=1, + padding=0, + weight_attr=ParamAttr(), + bias_attr=ParamAttr(), + ) + self.conv2 = Conv2D( + in_channels=channel // reduction, + out_channels=channel, + kernel_size=1, + stride=1, + padding=0, + weight_attr=ParamAttr(), + bias_attr=ParamAttr(), + ) + + def forward(self, inputs): + outputs = self.avg_pool(inputs) + outputs = self.conv1(outputs) + outputs = F.relu(outputs) + outputs = self.conv2(outputs) + outputs = hardsigmoid(outputs) + return paddle.multiply(x=inputs, y=outputs) diff --git a/ppocr/modeling/backbones/rec_nrtr_mtb.py b/ppocr/modeling/backbones/rec_nrtr_mtb.py new file mode 100644 index 0000000..608a8a7 --- /dev/null +++ b/ppocr/modeling/backbones/rec_nrtr_mtb.py @@ -0,0 +1,47 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle import nn +import paddle + + +class MTB(nn.Layer): + def __init__(self, cnn_num, in_channels): + super(MTB, self).__init__() + self.block = nn.Sequential() + self.out_channels = in_channels + self.cnn_num = cnn_num + if self.cnn_num == 2: + for i in range(self.cnn_num): + self.block.add_sublayer( + "conv_{}".format(i), + nn.Conv2D( + in_channels=in_channels if i == 0 else 32 * (2 ** (i - 1)), + out_channels=32 * (2**i), + kernel_size=3, + stride=2, + padding=1, + ), + ) + self.block.add_sublayer("relu_{}".format(i), nn.ReLU()) + self.block.add_sublayer("bn_{}".format(i), nn.BatchNorm2D(32 * (2**i))) + + def forward(self, images): + x = self.block(images) + if self.cnn_num == 2: + # (b, w, h, c) + x = paddle.transpose(x, [0, 3, 2, 1]) + x_shape = x.shape + x = paddle.reshape(x, [x_shape[0], x_shape[1], x_shape[2] * x_shape[3]]) + return x diff --git a/ppocr/modeling/backbones/rec_pphgnetv2.py b/ppocr/modeling/backbones/rec_pphgnetv2.py new file mode 100644 index 0000000..a969f52 --- /dev/null +++ b/ppocr/modeling/backbones/rec_pphgnetv2.py @@ -0,0 +1,1713 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/PaddlePaddle/PaddleClas/blob/2f36cab604e439b59d1a854df34ece3b10d888e3/ppcls/arch/backbone/legendary_models/pp_hgnet_v2.py +""" + +from __future__ import absolute_import, division, print_function + +import math +import numpy as np +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle import ParamAttr +from paddle.nn import Conv2D, BatchNorm, Linear, BatchNorm2D, MaxPool2D, AvgPool2D +from paddle.nn.initializer import Uniform +from paddle.regularizer import L2Decay + +from typing import Tuple, List, Dict, Union, Callable, Any +from ppocr.modeling.backbones.rec_donut_swin import DonutSwinModelOutput + + +class IdentityBasedConv1x1(nn.Conv2D): + def __init__(self, channels, groups=1): + super(IdentityBasedConv1x1, self).__init__( + in_channels=channels, + out_channels=channels, + kernel_size=1, + stride=1, + padding=0, + groups=groups, + bias_attr=False, + ) + + assert channels % groups == 0 + input_dim = channels // groups + id_value = np.zeros((channels, input_dim, 1, 1)) + for i in range(channels): + id_value[i, i % input_dim, 0, 0] = 1 + self.id_tensor = paddle.to_tensor(id_value) + self.weight.set_value(paddle.zeros_like(self.weight)) + + def forward(self, input): + kernel = self.weight + self.id_tensor + result = F.conv2d( + input, + kernel, + None, + stride=1, + padding=0, + dilation=self._dilation, + groups=self._groups, + ) + return result + + def get_actual_kernel(self): + return self.weight + self.id_tensor + + +class BNAndPad(nn.Layer): + def __init__( + self, + pad_pixels, + num_features, + epsilon=1e-5, + momentum=0.1, + last_conv_bias=None, + bn=nn.BatchNorm2D, + ): + super().__init__() + self.bn = bn(num_features, momentum=momentum, epsilon=epsilon) + self.pad_pixels = pad_pixels + self.last_conv_bias = last_conv_bias + + def forward(self, input): + output = self.bn(input) + if self.pad_pixels > 0: + bias = -self.bn._mean + if self.last_conv_bias is not None: + bias += self.last_conv_bias + pad_values = self.bn.bias + self.bn.weight * ( + bias / paddle.sqrt(self.bn._variance + self.bn._epsilon) + ) + """ pad """ + # TODO: n,h,w,c format is not supported yet + n, c, h, w = output.shape + values = pad_values.reshape([1, -1, 1, 1]) + w_values = values.expand([n, -1, self.pad_pixels, w]) + x = paddle.concat([w_values, output, w_values], axis=2) + h = h + self.pad_pixels * 2 + h_values = values.expand([n, -1, h, self.pad_pixels]) + x = paddle.concat([h_values, x, h_values], axis=3) + output = x + return output + + @property + def weight(self): + return self.bn.weight + + @property + def bias(self): + return self.bn.bias + + @property + def _mean(self): + return self.bn._mean + + @property + def _variance(self): + return self.bn._variance + + @property + def _epsilon(self): + return self.bn._epsilon + + +def conv_bn( + in_channels, + out_channels, + kernel_size, + stride=1, + padding=0, + dilation=1, + groups=1, + padding_mode="zeros", +): + conv_layer = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias_attr=False, + padding_mode=padding_mode, + ) + bn_layer = nn.BatchNorm2D(num_features=out_channels) + se = nn.Sequential() + se.add_sublayer("conv", conv_layer) + se.add_sublayer("bn", bn_layer) + return se + + +def transI_fusebn(kernel, bn): + gamma = bn.weight + std = (bn._variance + bn._epsilon).sqrt() + return ( + kernel * ((gamma / std).reshape([-1, 1, 1, 1])), + bn.bias - bn._mean * gamma / std, + ) + + +def transII_addbranch(kernels, biases): + return sum(kernels), sum(biases) + + +def transIII_1x1_kxk(k1, b1, k2, b2, groups): + if groups == 1: + k = F.conv2d(k2, k1.transpose([1, 0, 2, 3])) + b_hat = (k2 * b1.reshape([1, -1, 1, 1])).sum((1, 2, 3)) + else: + k_slices = [] + b_slices = [] + k1_T = k1.transpose([1, 0, 2, 3]) + k1_group_width = k1.shape[0] // groups + k2_group_width = k2.shape[0] // groups + for g in range(groups): + k1_T_slice = k1_T[:, g * k1_group_width : (g + 1) * k1_group_width, :, :] + k2_slice = k2[g * k2_group_width : (g + 1) * k2_group_width, :, :, :] + k_slices.append(F.conv2d(k2_slice, k1_T_slice)) + b_slices.append( + ( + k2_slice + * b1[g * k1_group_width : (g + 1) * k1_group_width].reshape( + [1, -1, 1, 1] + ) + ).sum((1, 2, 3)) + ) + k, b_hat = transIV_depthconcat(k_slices, b_slices) + return k, b_hat + b2 + + +def transIV_depthconcat(kernels, biases): + return paddle.cat(kernels, axis=0), paddle.cat(biases) + + +def transV_avg(channels, kernel_size, groups): + input_dim = channels // groups + k = paddle.zeros((channels, input_dim, kernel_size, kernel_size)) + k[np.arange(channels), np.tile(np.arange(input_dim), groups), :, :] = ( + 1.0 / kernel_size**2 + ) + return k + + +def transVI_multiscale(kernel, target_kernel_size): + H_pixels_to_pad = (target_kernel_size - kernel.shape[2]) // 2 + W_pixels_to_pad = (target_kernel_size - kernel.shape[3]) // 2 + return F.pad( + kernel, [H_pixels_to_pad, H_pixels_to_pad, W_pixels_to_pad, W_pixels_to_pad] + ) + + +class DiverseBranchBlock(nn.Layer): + def __init__( + self, + num_channels, + num_filters, + filter_size, + stride=1, + groups=1, + act=None, + is_repped=False, + single_init=False, + **kwargs, + ): + super().__init__() + + padding = (filter_size - 1) // 2 + dilation = 1 + + in_channels = num_channels + out_channels = num_filters + kernel_size = filter_size + internal_channels_1x1_3x3 = None + nonlinear = act + + self.is_repped = is_repped + + if nonlinear is None: + self.nonlinear = nn.Identity() + else: + self.nonlinear = nn.ReLU() + + self.kernel_size = kernel_size + self.out_channels = out_channels + self.groups = groups + assert padding == kernel_size // 2 + + if is_repped: + self.dbb_reparam = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias_attr=True, + ) + else: + self.dbb_origin = conv_bn( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + ) + + self.dbb_avg = nn.Sequential() + if groups < out_channels: + self.dbb_avg.add_sublayer( + "conv", + nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + padding=0, + groups=groups, + bias_attr=False, + ), + ) + self.dbb_avg.add_sublayer( + "bn", BNAndPad(pad_pixels=padding, num_features=out_channels) + ) + self.dbb_avg.add_sublayer( + "avg", + nn.AvgPool2D(kernel_size=kernel_size, stride=stride, padding=0), + ) + self.dbb_1x1 = conv_bn( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=stride, + padding=0, + groups=groups, + ) + else: + self.dbb_avg.add_sublayer( + "avg", + nn.AvgPool2D( + kernel_size=kernel_size, stride=stride, padding=padding + ), + ) + + self.dbb_avg.add_sublayer("avgbn", nn.BatchNorm2D(out_channels)) + + if internal_channels_1x1_3x3 is None: + internal_channels_1x1_3x3 = ( + in_channels if groups < out_channels else 2 * in_channels + ) # For mobilenet, it is better to have 2X internal channels + + self.dbb_1x1_kxk = nn.Sequential() + if internal_channels_1x1_3x3 == in_channels: + self.dbb_1x1_kxk.add_sublayer( + "idconv1", IdentityBasedConv1x1(channels=in_channels, groups=groups) + ) + else: + self.dbb_1x1_kxk.add_sublayer( + "conv1", + nn.Conv2D( + in_channels=in_channels, + out_channels=internal_channels_1x1_3x3, + kernel_size=1, + stride=1, + padding=0, + groups=groups, + bias_attr=False, + ), + ) + self.dbb_1x1_kxk.add_sublayer( + "bn1", + BNAndPad(pad_pixels=padding, num_features=internal_channels_1x1_3x3), + ) + self.dbb_1x1_kxk.add_sublayer( + "conv2", + nn.Conv2D( + in_channels=internal_channels_1x1_3x3, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=0, + groups=groups, + bias_attr=False, + ), + ) + self.dbb_1x1_kxk.add_sublayer("bn2", nn.BatchNorm2D(out_channels)) + + # The experiments reported in the paper used the default initialization of bn.weight (all as 1). But changing the initialization may be useful in some cases. + if single_init: + # Initialize the bn.weight of dbb_origin as 1 and others as 0. This is not the default setting. + self.single_init() + + def forward(self, inputs): + if self.is_repped: + return self.nonlinear(self.dbb_reparam(inputs)) + + out = self.dbb_origin(inputs) + if hasattr(self, "dbb_1x1"): + out += self.dbb_1x1(inputs) + out += self.dbb_avg(inputs) + out += self.dbb_1x1_kxk(inputs) + return self.nonlinear(out) + + def init_gamma(self, gamma_value): + if hasattr(self, "dbb_origin"): + paddle.nn.init.constant_(self.dbb_origin.bn.weight, gamma_value) + if hasattr(self, "dbb_1x1"): + paddle.nn.init.constant_(self.dbb_1x1.bn.weight, gamma_value) + if hasattr(self, "dbb_avg"): + paddle.nn.init.constant_(self.dbb_avg.avgbn.weight, gamma_value) + if hasattr(self, "dbb_1x1_kxk"): + paddle.nn.init.constant_(self.dbb_1x1_kxk.bn2.weight, gamma_value) + + def single_init(self): + self.init_gamma(0.0) + if hasattr(self, "dbb_origin"): + paddle.nn.init.constant_(self.dbb_origin.bn.weight, 1.0) + + def get_equivalent_kernel_bias(self): + k_origin, b_origin = transI_fusebn( + self.dbb_origin.conv.weight, self.dbb_origin.bn + ) + + if hasattr(self, "dbb_1x1"): + k_1x1, b_1x1 = transI_fusebn(self.dbb_1x1.conv.weight, self.dbb_1x1.bn) + k_1x1 = transVI_multiscale(k_1x1, self.kernel_size) + else: + k_1x1, b_1x1 = 0, 0 + + if hasattr(self.dbb_1x1_kxk, "idconv1"): + k_1x1_kxk_first = self.dbb_1x1_kxk.idconv1.get_actual_kernel() + else: + k_1x1_kxk_first = self.dbb_1x1_kxk.conv1.weight + k_1x1_kxk_first, b_1x1_kxk_first = transI_fusebn( + k_1x1_kxk_first, self.dbb_1x1_kxk.bn1 + ) + k_1x1_kxk_second, b_1x1_kxk_second = transI_fusebn( + self.dbb_1x1_kxk.conv2.weight, self.dbb_1x1_kxk.bn2 + ) + k_1x1_kxk_merged, b_1x1_kxk_merged = transIII_1x1_kxk( + k_1x1_kxk_first, + b_1x1_kxk_first, + k_1x1_kxk_second, + b_1x1_kxk_second, + groups=self.groups, + ) + + k_avg = transV_avg(self.out_channels, self.kernel_size, self.groups) + k_1x1_avg_second, b_1x1_avg_second = transI_fusebn(k_avg, self.dbb_avg.avgbn) + if hasattr(self.dbb_avg, "conv"): + k_1x1_avg_first, b_1x1_avg_first = transI_fusebn( + self.dbb_avg.conv.weight, self.dbb_avg.bn + ) + k_1x1_avg_merged, b_1x1_avg_merged = transIII_1x1_kxk( + k_1x1_avg_first, + b_1x1_avg_first, + k_1x1_avg_second, + b_1x1_avg_second, + groups=self.groups, + ) + else: + k_1x1_avg_merged, b_1x1_avg_merged = k_1x1_avg_second, b_1x1_avg_second + + return transII_addbranch( + (k_origin, k_1x1, k_1x1_kxk_merged, k_1x1_avg_merged), + (b_origin, b_1x1, b_1x1_kxk_merged, b_1x1_avg_merged), + ) + + def re_parameterize(self): + if self.is_repped: + return + + kernel, bias = self.get_equivalent_kernel_bias() + self.dbb_reparam = nn.Conv2D( + in_channels=self.dbb_origin.conv._in_channels, + out_channels=self.dbb_origin.conv._out_channels, + kernel_size=self.dbb_origin.conv._kernel_size, + stride=self.dbb_origin.conv._stride, + padding=self.dbb_origin.conv._padding, + dilation=self.dbb_origin.conv._dilation, + groups=self.dbb_origin.conv._groups, + bias_attr=True, + ) + + self.dbb_reparam.weight.set_value(kernel) + self.dbb_reparam.bias.set_value(bias) + + self.__delattr__("dbb_origin") + self.__delattr__("dbb_avg") + if hasattr(self, "dbb_1x1"): + self.__delattr__("dbb_1x1") + self.__delattr__("dbb_1x1_kxk") + self.is_repped = True + + +class Identity(nn.Layer): + def __init__(self): + super(Identity, self).__init__() + + def forward(self, inputs): + return inputs + + +class TheseusLayer(nn.Layer): + def __init__(self, *args, **kwargs): + super().__init__() + self.res_dict = {} + self.res_name = self.full_name() + self.pruner = None + self.quanter = None + + self.init_net(*args, **kwargs) + + def _return_dict_hook(self, layer, input, output): + res_dict = {"logits": output} + # 'list' is needed to avoid error raised by popping self.res_dict + for res_key in list(self.res_dict): + # clear the res_dict because the forward process may change according to input + res_dict[res_key] = self.res_dict.pop(res_key) + return res_dict + + def init_net( + self, + stages_pattern=None, + return_patterns=None, + return_stages=None, + freeze_befor=None, + stop_after=None, + *args, + **kwargs, + ): + # init the output of net + if return_patterns or return_stages: + if return_patterns and return_stages: + msg = f"The 'return_patterns' would be ignored when 'return_stages' is set." + + return_stages = None + + if return_stages is True: + return_patterns = stages_pattern + + # return_stages is int or bool + if type(return_stages) is int: + return_stages = [return_stages] + if isinstance(return_stages, list): + if max(return_stages) > len(stages_pattern) or min(return_stages) < 0: + msg = f"The 'return_stages' set error. Illegal value(s) have been ignored. The stages' pattern list is {stages_pattern}." + + return_stages = [ + val + for val in return_stages + if val >= 0 and val < len(stages_pattern) + ] + return_patterns = [stages_pattern[i] for i in return_stages] + + if return_patterns: + # call update_res function after the __init__ of the object has completed execution, that is, the constructing of layer or model has been completed. + def update_res_hook(layer, input): + self.update_res(return_patterns) + + self.register_forward_pre_hook(update_res_hook) + + # freeze subnet + if freeze_befor is not None: + self.freeze_befor(freeze_befor) + + # set subnet to Identity + if stop_after is not None: + self.stop_after(stop_after) + + def init_res(self, stages_pattern, return_patterns=None, return_stages=None): + + if return_patterns and return_stages: + return_stages = None + + if return_stages is True: + return_patterns = stages_pattern + # return_stages is int or bool + if type(return_stages) is int: + return_stages = [return_stages] + if isinstance(return_stages, list): + if max(return_stages) > len(stages_pattern) or min(return_stages) < 0: + return_stages = [ + val + for val in return_stages + if val >= 0 and val < len(stages_pattern) + ] + return_patterns = [stages_pattern[i] for i in return_stages] + + if return_patterns: + self.update_res(return_patterns) + + def replace_sub(self, *args, **kwargs) -> None: + msg = "The function 'replace_sub()' is deprecated, please use 'upgrade_sublayer()' instead." + raise DeprecationWarning(msg) + + def upgrade_sublayer( + self, + layer_name_pattern: Union[str, List[str]], + handle_func: Callable[[nn.Layer, str], nn.Layer], + ) -> Dict[str, nn.Layer]: + """use 'handle_func' to modify the sub-layer(s) specified by 'layer_name_pattern'. + + Args: + layer_name_pattern (Union[str, List[str]]): The name of layer to be modified by 'handle_func'. + handle_func (Callable[[nn.Layer, str], nn.Layer]): The function to modify target layer specified by 'layer_name_pattern'. The formal params are the layer(nn.Layer) and pattern(str) that is (a member of) layer_name_pattern (when layer_name_pattern is List type). And the return is the layer processed. + + Returns: + Dict[str, nn.Layer]: The key is the pattern and corresponding value is the result returned by 'handle_func()'. + + Examples: + + from paddle import nn + import paddleclas + + def rep_func(layer: nn.Layer, pattern: str): + new_layer = nn.Conv2D( + in_channels=layer._in_channels, + out_channels=layer._out_channels, + kernel_size=5, + padding=2 + ) + return new_layer + + net = paddleclas.MobileNetV1() + res = net.upgrade_sublayer(layer_name_pattern=["blocks[11].depthwise_conv.conv", "blocks[12].depthwise_conv.conv"], handle_func=rep_func) + print(res) + # {'blocks[11].depthwise_conv.conv': the corresponding new_layer, 'blocks[12].depthwise_conv.conv': the corresponding new_layer} + """ + + if not isinstance(layer_name_pattern, list): + layer_name_pattern = [layer_name_pattern] + + hit_layer_pattern_list = [] + for pattern in layer_name_pattern: + # parse pattern to find target layer and its parent + layer_list = parse_pattern_str(pattern=pattern, parent_layer=self) + if not layer_list: + continue + + sub_layer_parent = layer_list[-2]["layer"] if len(layer_list) > 1 else self + sub_layer = layer_list[-1]["layer"] + sub_layer_name = layer_list[-1]["name"] + sub_layer_index_list = layer_list[-1]["index_list"] + + new_sub_layer = handle_func(sub_layer, pattern) + + if sub_layer_index_list: + if len(sub_layer_index_list) > 1: + sub_layer_parent = getattr(sub_layer_parent, sub_layer_name)[ + sub_layer_index_list[0] + ] + for sub_layer_index in sub_layer_index_list[1:-1]: + sub_layer_parent = sub_layer_parent[sub_layer_index] + sub_layer_parent[sub_layer_index_list[-1]] = new_sub_layer + else: + getattr(sub_layer_parent, sub_layer_name)[ + sub_layer_index_list[0] + ] = new_sub_layer + else: + setattr(sub_layer_parent, sub_layer_name, new_sub_layer) + + hit_layer_pattern_list.append(pattern) + return hit_layer_pattern_list + + def stop_after(self, stop_layer_name: str) -> bool: + """stop forward and backward after 'stop_layer_name'. + + Args: + stop_layer_name (str): The name of layer that stop forward and backward after this layer. + + Returns: + bool: 'True' if successful, 'False' otherwise. + """ + + layer_list = parse_pattern_str(stop_layer_name, self) + if not layer_list: + return False + + parent_layer = self + for layer_dict in layer_list: + name, index_list = layer_dict["name"], layer_dict["index_list"] + if not set_identity(parent_layer, name, index_list): + msg = f"Failed to set the layers that after stop_layer_name('{stop_layer_name}') to IdentityLayer. The error layer's name is '{name}'." + return False + parent_layer = layer_dict["layer"] + + return True + + def freeze_befor(self, layer_name: str) -> bool: + """freeze the layer named layer_name and its previous layer. + + Args: + layer_name (str): The name of layer that would be freezed. + + Returns: + bool: 'True' if successful, 'False' otherwise. + """ + + def stop_grad(layer, pattern): + class StopGradLayer(nn.Layer): + def __init__(self): + super().__init__() + self.layer = layer + + def forward(self, x): + x = self.layer(x) + x.stop_gradient = True + return x + + new_layer = StopGradLayer() + return new_layer + + res = self.upgrade_sublayer(layer_name, stop_grad) + if len(res) == 0: + msg = "Failed to stop the gradient before the layer named '{layer_name}'" + return False + return True + + def update_res(self, return_patterns: Union[str, List[str]]) -> Dict[str, nn.Layer]: + """update the result(s) to be returned. + + Args: + return_patterns (Union[str, List[str]]): The name of layer to return output. + + Returns: + Dict[str, nn.Layer]: The pattern(str) and corresponding layer(nn.Layer) that have been set successfully. + """ + + # clear res_dict that could have been set + self.res_dict = {} + + class Handler(object): + def __init__(self, res_dict): + # res_dict is a reference + self.res_dict = res_dict + + def __call__(self, layer, pattern): + layer.res_dict = self.res_dict + layer.res_name = pattern + if hasattr(layer, "hook_remove_helper"): + layer.hook_remove_helper.remove() + layer.hook_remove_helper = layer.register_forward_post_hook( + save_sub_res_hook + ) + return layer + + handle_func = Handler(self.res_dict) + + hit_layer_pattern_list = self.upgrade_sublayer( + return_patterns, handle_func=handle_func + ) + + if hasattr(self, "hook_remove_helper"): + self.hook_remove_helper.remove() + self.hook_remove_helper = self.register_forward_post_hook( + self._return_dict_hook + ) + + return hit_layer_pattern_list + + +def save_sub_res_hook(layer, input, output): + layer.res_dict[layer.res_name] = output + + +def set_identity( + parent_layer: nn.Layer, layer_name: str, layer_index_list: str = None +) -> bool: + """set the layer specified by layer_name and layer_index_list to Identity. + + Args: + parent_layer (nn.Layer): The parent layer of target layer specified by layer_name and layer_index_list. + layer_name (str): The name of target layer to be set to Identity. + layer_index_list (str, optional): The index of target layer to be set to Identity in parent_layer. Defaults to None. + + Returns: + bool: True if successfully, False otherwise. + """ + + stop_after = False + for sub_layer_name in parent_layer._sub_layers: + if stop_after: + parent_layer._sub_layers[sub_layer_name] = Identity() + continue + if sub_layer_name == layer_name: + stop_after = True + + if layer_index_list and stop_after: + layer_container = parent_layer._sub_layers[layer_name] + for num, layer_index in enumerate(layer_index_list): + stop_after = False + for i in range(num): + layer_container = layer_container[layer_index_list[i]] + for sub_layer_index in layer_container._sub_layers: + if stop_after: + parent_layer._sub_layers[layer_name][sub_layer_index] = Identity() + continue + if layer_index == sub_layer_index: + stop_after = True + + return stop_after + + +def parse_pattern_str( + pattern: str, parent_layer: nn.Layer +) -> Union[None, List[Dict[str, Union[nn.Layer, str, None]]]]: + """parse the string type pattern. + + Args: + pattern (str): The pattern to describe layer. + parent_layer (nn.Layer): The root layer relative to the pattern. + + Returns: + Union[None, List[Dict[str, Union[nn.Layer, str, None]]]]: None if failed. If successfully, the members are layers parsed in order: + [ + {"layer": first layer, "name": first layer's name parsed, "index": first layer's index parsed if exist}, + {"layer": second layer, "name": second layer's name parsed, "index": second layer's index parsed if exist}, + ... + ] + """ + + pattern_list = pattern.split(".") + if not pattern_list: + msg = f"The pattern('{pattern}') is illegal. Please check and retry." + return None + + layer_list = [] + while len(pattern_list) > 0: + if "[" in pattern_list[0]: + target_layer_name = pattern_list[0].split("[")[0] + target_layer_index_list = list( + index.split("]")[0] for index in pattern_list[0].split("[")[1:] + ) + else: + target_layer_name = pattern_list[0] + target_layer_index_list = None + + target_layer = getattr(parent_layer, target_layer_name, None) + + if target_layer is None: + msg = f"Not found layer named('{target_layer_name}') specified in pattern('{pattern}')." + return None + + if target_layer_index_list: + for target_layer_index in target_layer_index_list: + if int(target_layer_index) < 0 or int(target_layer_index) >= len( + target_layer + ): + msg = f"Not found layer by index('{target_layer_index}') specified in pattern('{pattern}'). The index should < {len(target_layer)} and > 0." + return None + target_layer = target_layer[target_layer_index] + + layer_list.append( + { + "layer": target_layer, + "name": target_layer_name, + "index_list": target_layer_index_list, + } + ) + + pattern_list = pattern_list[1:] + parent_layer = target_layer + + return layer_list + + +class AdaptiveAvgPool2D(nn.AdaptiveAvgPool2D): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + if paddle.device.get_device().startswith("npu"): + self.device = "npu" + else: + self.device = None + + if isinstance(self._output_size, int) and self._output_size == 1: + self._gap = True + elif ( + isinstance(self._output_size, tuple) + and self._output_size[0] == 1 + and self._output_size[1] == 1 + ): + self._gap = True + else: + self._gap = False + + def forward(self, x): + if self.device == "npu" and self._gap: + # Global Average Pooling + N, C, _, _ = x.shape + x_mean = paddle.mean(x, axis=[2, 3]) + x_mean = paddle.reshape(x_mean, [N, C, 1, 1]) + return x_mean + else: + return F.adaptive_avg_pool2d( + x, + output_size=self._output_size, + data_format=self._data_format, + name=self._name, + ) + + +# copyright (c) 2023 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle.nn.initializer import KaimingNormal, Constant +from paddle.nn import Conv2D, BatchNorm2D, ReLU, AdaptiveAvgPool2D, MaxPool2D +from paddle.regularizer import L2Decay +from paddle import ParamAttr + +MODEL_URLS = { + "PPHGNetV2_B0": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPHGNetV2_B0_ssld_pretrained.pdparams", + "PPHGNetV2_B1": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPHGNetV2_B1_ssld_pretrained.pdparams", + "PPHGNetV2_B2": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPHGNetV2_B2_ssld_pretrained.pdparams", + "PPHGNetV2_B3": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPHGNetV2_B3_ssld_pretrained.pdparams", + "PPHGNetV2_B4": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPHGNetV2_B4_ssld_pretrained.pdparams", + "PPHGNetV2_B5": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPHGNetV2_B5_ssld_pretrained.pdparams", + "PPHGNetV2_B6": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/legendary_models/PPHGNetV2_B6_ssld_pretrained.pdparams", +} + +__all__ = list(MODEL_URLS.keys()) + +kaiming_normal_ = KaimingNormal() +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) + + +class LearnableAffineBlock(TheseusLayer): + """ + Create a learnable affine block module. This module can significantly improve accuracy on smaller models. + + Args: + scale_value (float): The initial value of the scale parameter, default is 1.0. + bias_value (float): The initial value of the bias parameter, default is 0.0. + lr_mult (float): The learning rate multiplier, default is 1.0. + lab_lr (float): The learning rate, default is 0.01. + """ + + def __init__(self, scale_value=1.0, bias_value=0.0, lr_mult=1.0, lab_lr=0.01): + super().__init__() + self.scale = self.create_parameter( + shape=[ + 1, + ], + default_initializer=Constant(value=scale_value), + attr=ParamAttr(learning_rate=lr_mult * lab_lr), + ) + self.add_parameter("scale", self.scale) + self.bias = self.create_parameter( + shape=[ + 1, + ], + default_initializer=Constant(value=bias_value), + attr=ParamAttr(learning_rate=lr_mult * lab_lr), + ) + self.add_parameter("bias", self.bias) + + def forward(self, x): + return self.scale * x + self.bias + + +class ConvBNAct(TheseusLayer): + """ + ConvBNAct is a combination of convolution and batchnorm layers. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + kernel_size (int): Size of the convolution kernel. Defaults to 3. + stride (int): Stride of the convolution. Defaults to 1. + padding (int/str): Padding or padding type for the convolution. Defaults to 1. + groups (int): Number of groups for the convolution. Defaults to 1. + use_act: (bool): Whether to use activation function. Defaults to True. + use_lab (bool): Whether to use the LAB operation. Defaults to False. + lr_mult (float): Learning rate multiplier for the layer. Defaults to 1.0. + """ + + def __init__( + self, + in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=1, + groups=1, + use_act=True, + use_lab=False, + lr_mult=1.0, + ): + super().__init__() + self.use_act = use_act + self.use_lab = use_lab + self.conv = Conv2D( + in_channels, + out_channels, + kernel_size, + stride, + padding=padding if isinstance(padding, str) else (kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(learning_rate=lr_mult), + bias_attr=False, + ) + self.bn = BatchNorm2D( + out_channels, + weight_attr=ParamAttr(regularizer=L2Decay(0.0), learning_rate=lr_mult), + bias_attr=ParamAttr(regularizer=L2Decay(0.0), learning_rate=lr_mult), + ) + if self.use_act: + self.act = ReLU() + if self.use_lab: + self.lab = LearnableAffineBlock(lr_mult=lr_mult) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + if self.use_act: + x = self.act(x) + if self.use_lab: + x = self.lab(x) + return x + + +class LightConvBNAct(TheseusLayer): + """ + LightConvBNAct is a combination of pw and dw layers. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + kernel_size (int): Size of the depth-wise convolution kernel. + use_lab (bool): Whether to use the LAB operation. Defaults to False. + lr_mult (float): Learning rate multiplier for the layer. Defaults to 1.0. + """ + + def __init__( + self, + in_channels, + out_channels, + kernel_size, + use_lab=False, + lr_mult=1.0, + **kwargs, + ): + super().__init__() + self.conv1 = ConvBNAct( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + use_act=False, + use_lab=use_lab, + lr_mult=lr_mult, + ) + self.conv2 = ConvBNAct( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=kernel_size, + groups=out_channels, + use_act=True, + use_lab=use_lab, + lr_mult=lr_mult, + ) + + def forward(self, x): + x = self.conv1(x) + x = self.conv2(x) + return x + + +class StemBlock(TheseusLayer): + """ + StemBlock for PP-HGNetV2. + + Args: + in_channels (int): Number of input channels. + mid_channels (int): Number of middle channels. + out_channels (int): Number of output channels. + use_lab (bool): Whether to use the LAB operation. Defaults to False. + lr_mult (float): Learning rate multiplier for the layer. Defaults to 1.0. + """ + + def __init__( + self, + in_channels, + mid_channels, + out_channels, + use_lab=False, + lr_mult=1.0, + text_rec=False, + ): + super().__init__() + self.stem1 = ConvBNAct( + in_channels=in_channels, + out_channels=mid_channels, + kernel_size=3, + stride=2, + use_lab=use_lab, + lr_mult=lr_mult, + ) + self.stem2a = ConvBNAct( + in_channels=mid_channels, + out_channels=mid_channels // 2, + kernel_size=2, + stride=1, + padding="SAME", + use_lab=use_lab, + lr_mult=lr_mult, + ) + self.stem2b = ConvBNAct( + in_channels=mid_channels // 2, + out_channels=mid_channels, + kernel_size=2, + stride=1, + padding="SAME", + use_lab=use_lab, + lr_mult=lr_mult, + ) + self.stem3 = ConvBNAct( + in_channels=mid_channels * 2, + out_channels=mid_channels, + kernel_size=3, + stride=1 if text_rec else 2, + use_lab=use_lab, + lr_mult=lr_mult, + ) + self.stem4 = ConvBNAct( + in_channels=mid_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + use_lab=use_lab, + lr_mult=lr_mult, + ) + self.pool = nn.MaxPool2D( + kernel_size=2, stride=1, ceil_mode=True, padding="SAME" + ) + + def forward(self, x): + x = self.stem1(x) + x2 = self.stem2a(x) + x2 = self.stem2b(x2) + x1 = self.pool(x) + x = paddle.concat([x1, x2], 1) + x = self.stem3(x) + x = self.stem4(x) + + return x + + +class HGV2_Block(TheseusLayer): + """ + HGV2_Block, the basic unit that constitutes the HGV2_Stage. + + Args: + in_channels (int): Number of input channels. + mid_channels (int): Number of middle channels. + out_channels (int): Number of output channels. + kernel_size (int): Size of the convolution kernel. Defaults to 3. + layer_num (int): Number of layers in the HGV2 block. Defaults to 6. + stride (int): Stride of the convolution. Defaults to 1. + padding (int/str): Padding or padding type for the convolution. Defaults to 1. + groups (int): Number of groups for the convolution. Defaults to 1. + use_act (bool): Whether to use activation function. Defaults to True. + use_lab (bool): Whether to use the LAB operation. Defaults to False. + lr_mult (float): Learning rate multiplier for the layer. Defaults to 1.0. + """ + + def __init__( + self, + in_channels, + mid_channels, + out_channels, + kernel_size=3, + layer_num=6, + identity=False, + light_block=True, + use_lab=False, + lr_mult=1.0, + ): + super().__init__() + self.identity = identity + + self.layers = nn.LayerList() + block_type = "LightConvBNAct" if light_block else "ConvBNAct" + for i in range(layer_num): + self.layers.append( + eval(block_type)( + in_channels=in_channels if i == 0 else mid_channels, + out_channels=mid_channels, + stride=1, + kernel_size=kernel_size, + use_lab=use_lab, + lr_mult=lr_mult, + ) + ) + # feature aggregation + total_channels = in_channels + layer_num * mid_channels + self.aggregation_squeeze_conv = ConvBNAct( + in_channels=total_channels, + out_channels=out_channels // 2, + kernel_size=1, + stride=1, + use_lab=use_lab, + lr_mult=lr_mult, + ) + self.aggregation_excitation_conv = ConvBNAct( + in_channels=out_channels // 2, + out_channels=out_channels, + kernel_size=1, + stride=1, + use_lab=use_lab, + lr_mult=lr_mult, + ) + + def forward(self, x): + identity = x + output = [] + output.append(x) + for layer in self.layers: + x = layer(x) + output.append(x) + x = paddle.concat(output, axis=1) + x = self.aggregation_squeeze_conv(x) + x = self.aggregation_excitation_conv(x) + if self.identity: + x += identity + return x + + +class HGV2_Stage(TheseusLayer): + """ + HGV2_Stage, the basic unit that constitutes the PPHGNetV2. + + Args: + in_channels (int): Number of input channels. + mid_channels (int): Number of middle channels. + out_channels (int): Number of output channels. + block_num (int): Number of blocks in the HGV2 stage. + layer_num (int): Number of layers in the HGV2 block. Defaults to 6. + is_downsample (bool): Whether to use downsampling operation. Defaults to False. + light_block (bool): Whether to use light block. Defaults to True. + kernel_size (int): Size of the convolution kernel. Defaults to 3. + use_lab (bool, optional): Whether to use the LAB operation. Defaults to False. + lr_mult (float, optional): Learning rate multiplier for the layer. Defaults to 1.0. + """ + + def __init__( + self, + in_channels, + mid_channels, + out_channels, + block_num, + layer_num=6, + is_downsample=True, + light_block=True, + kernel_size=3, + use_lab=False, + stride=2, + lr_mult=1.0, + ): + + super().__init__() + self.is_downsample = is_downsample + if self.is_downsample: + self.downsample = ConvBNAct( + in_channels=in_channels, + out_channels=in_channels, + kernel_size=3, + stride=stride, + groups=in_channels, + use_act=False, + use_lab=use_lab, + lr_mult=lr_mult, + ) + + blocks_list = [] + for i in range(block_num): + blocks_list.append( + HGV2_Block( + in_channels=in_channels if i == 0 else out_channels, + mid_channels=mid_channels, + out_channels=out_channels, + kernel_size=kernel_size, + layer_num=layer_num, + identity=False if i == 0 else True, + light_block=light_block, + use_lab=use_lab, + lr_mult=lr_mult, + ) + ) + self.blocks = nn.Sequential(*blocks_list) + + def forward(self, x): + if self.is_downsample: + x = self.downsample(x) + x = self.blocks(x) + return x + + +class PPHGNetV2(TheseusLayer): + """ + PPHGNetV2 + + Args: + stage_config (dict): Config for PPHGNetV2 stages. such as the number of channels, stride, etc. + stem_channels: (list): Number of channels of the stem of the PPHGNetV2. + use_lab (bool): Whether to use the LAB operation. Defaults to False. + use_last_conv (bool): Whether to use the last conv layer as the output channel. Defaults to True. + class_expand (int): Number of channels for the last 1x1 convolutional layer. + drop_prob (float): Dropout probability for the last 1x1 convolutional layer. Defaults to 0.0. + class_num (int): The number of classes for the classification layer. Defaults to 1000. + lr_mult_list (list): Learning rate multiplier for the stages. Defaults to [1.0, 1.0, 1.0, 1.0, 1.0]. + Returns: + model: nn.Layer. Specific PPHGNetV2 model depends on args. + """ + + def __init__( + self, + stage_config, + stem_channels=[3, 32, 64], + use_lab=False, + use_last_conv=True, + class_expand=2048, + dropout_prob=0.0, + class_num=1000, + lr_mult_list=[1.0, 1.0, 1.0, 1.0, 1.0], + det=False, + text_rec=False, + out_indices=None, + **kwargs, + ): + super().__init__() + self.det = det + self.text_rec = text_rec + self.use_lab = use_lab + self.use_last_conv = use_last_conv + self.class_expand = class_expand + self.class_num = class_num + self.out_indices = out_indices if out_indices is not None else [0, 1, 2, 3] + self.out_channels = [] + + # stem + self.stem = StemBlock( + in_channels=stem_channels[0], + mid_channels=stem_channels[1], + out_channels=stem_channels[2], + use_lab=use_lab, + lr_mult=lr_mult_list[0], + text_rec=text_rec, + ) + + # stages + self.stages = nn.LayerList() + for i, k in enumerate(stage_config): + ( + in_channels, + mid_channels, + out_channels, + block_num, + is_downsample, + light_block, + kernel_size, + layer_num, + stride, + ) = stage_config[k] + self.stages.append( + HGV2_Stage( + in_channels, + mid_channels, + out_channels, + block_num, + layer_num, + is_downsample, + light_block, + kernel_size, + use_lab, + stride, + lr_mult=lr_mult_list[i + 1], + ) + ) + if i in self.out_indices: + self.out_channels.append(out_channels) + if not self.det: + self.out_channels = stage_config["stage4"][2] + + self.avg_pool = AdaptiveAvgPool2D(1) + + if self.use_last_conv: + self.last_conv = Conv2D( + in_channels=out_channels, + out_channels=self.class_expand, + kernel_size=1, + stride=1, + padding=0, + bias_attr=False, + ) + self.act = ReLU() + if self.use_lab: + self.lab = LearnableAffineBlock() + self.dropout = nn.Dropout(p=dropout_prob, mode="downscale_in_infer") + + self.flatten = nn.Flatten(start_axis=1, stop_axis=-1) + if not self.det: + self.fc = nn.Linear( + self.class_expand if self.use_last_conv else out_channels, + self.class_num, + ) + + self._init_weights() + + def _init_weights(self): + for m in self.sublayers(): + if isinstance(m, nn.Conv2D): + kaiming_normal_(m.weight) + elif isinstance(m, (nn.BatchNorm2D)): + ones_(m.weight) + zeros_(m.bias) + elif isinstance(m, nn.Linear): + zeros_(m.bias) + + def forward(self, x): + x = self.stem(x) + out = [] + for i, stage in enumerate(self.stages): + x = stage(x) + if self.det and i in self.out_indices: + out.append(x) + if self.det: + return out + + if self.text_rec: + if self.training: + x = F.adaptive_avg_pool2d(x, [1, 40]) + else: + x = F.avg_pool2d(x, [3, 2]) + return x + + +def PPHGNetV2_B0(pretrained=False, use_ssld=False, **kwargs): + """ + PPHGNetV2_B0 + Args: + pretrained (bool/str): If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld (bool) Whether using ssld pretrained model when pretrained is True. + Returns: + model: nn.Layer. Specific `PPHGNetV2_B0` model depends on args. + """ + stage_config = { + # in_channels, mid_channels, out_channels, num_blocks, is_downsample, light_block, kernel_size, layer_num + "stage1": [16, 16, 64, 1, False, False, 3, 3], + "stage2": [64, 32, 256, 1, True, False, 3, 3], + "stage3": [256, 64, 512, 2, True, True, 5, 3], + "stage4": [512, 128, 1024, 1, True, True, 5, 3], + } + + model = PPHGNetV2( + stem_channels=[3, 16, 16], stage_config=stage_config, use_lab=True, **kwargs + ) + return model + + +def PPHGNetV2_B1(pretrained=False, use_ssld=False, **kwargs): + """ + PPHGNetV2_B1 + Args: + pretrained (bool/str): If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld (bool) Whether using ssld pretrained model when pretrained is True. + Returns: + model: nn.Layer. Specific `PPHGNetV2_B1` model depends on args. + """ + stage_config = { + # in_channels, mid_channels, out_channels, num_blocks, is_downsample, light_block, kernel_size, layer_num + "stage1": [32, 32, 64, 1, False, False, 3, 3], + "stage2": [64, 48, 256, 1, True, False, 3, 3], + "stage3": [256, 96, 512, 2, True, True, 5, 3], + "stage4": [512, 192, 1024, 1, True, True, 5, 3], + } + + model = PPHGNetV2( + stem_channels=[3, 24, 32], stage_config=stage_config, use_lab=True, **kwargs + ) + return model + + +def PPHGNetV2_B2(pretrained=False, use_ssld=False, **kwargs): + """ + PPHGNetV2_B2 + Args: + pretrained (bool/str): If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld (bool) Whether using ssld pretrained model when pretrained is True. + Returns: + model: nn.Layer. Specific `PPHGNetV2_B2` model depends on args. + """ + stage_config = { + # in_channels, mid_channels, out_channels, num_blocks, is_downsample, light_block, kernel_size, layer_num + "stage1": [32, 32, 96, 1, False, False, 3, 4], + "stage2": [96, 64, 384, 1, True, False, 3, 4], + "stage3": [384, 128, 768, 3, True, True, 5, 4], + "stage4": [768, 256, 1536, 1, True, True, 5, 4], + } + + model = PPHGNetV2( + stem_channels=[3, 24, 32], stage_config=stage_config, use_lab=True, **kwargs + ) + return model + + +def PPHGNetV2_B3(pretrained=False, use_ssld=False, **kwargs): + """ + PPHGNetV2_B3 + Args: + pretrained (bool/str): If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld (bool) Whether using ssld pretrained model when pretrained is True. + Returns: + model: nn.Layer. Specific `PPHGNetV2_B3` model depends on args. + """ + stage_config = { + # in_channels, mid_channels, out_channels, num_blocks, is_downsample, light_block, kernel_size, layer_num + "stage1": [32, 32, 128, 1, False, False, 3, 5], + "stage2": [128, 64, 512, 1, True, False, 3, 5], + "stage3": [512, 128, 1024, 3, True, True, 5, 5], + "stage4": [1024, 256, 2048, 1, True, True, 5, 5], + } + + model = PPHGNetV2( + stem_channels=[3, 24, 32], stage_config=stage_config, use_lab=True, **kwargs + ) + return model + + +def PPHGNetV2_B4(pretrained=False, use_ssld=False, det=False, text_rec=False, **kwargs): + """ + PPHGNetV2_B4 + Args: + pretrained (bool/str): If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld (bool) Whether using ssld pretrained model when pretrained is True. + Returns: + model: nn.Layer. Specific `PPHGNetV2_B4` model depends on args. + """ + stage_config_rec = { + # in_channels, mid_channels, out_channels, num_blocks, is_downsample, light_block, kernel_size, layer_num, stride + "stage1": [48, 48, 128, 1, True, False, 3, 6, [2, 1]], + "stage2": [128, 96, 512, 1, True, False, 3, 6, [1, 2]], + "stage3": [512, 192, 1024, 3, True, True, 5, 6, [2, 1]], + "stage4": [1024, 384, 2048, 1, True, True, 5, 6, [2, 1]], + } + + stage_config_det = { + # in_channels, mid_channels, out_channels, num_blocks, is_downsample, light_block, kernel_size, layer_num + "stage1": [48, 48, 128, 1, False, False, 3, 6, 2], + "stage2": [128, 96, 512, 1, True, False, 3, 6, 2], + "stage3": [512, 192, 1024, 3, True, True, 5, 6, 2], + "stage4": [1024, 384, 2048, 1, True, True, 5, 6, 2], + } + model = PPHGNetV2( + stem_channels=[3, 32, 48], + stage_config=stage_config_det if det else stage_config_rec, + use_lab=False, + det=det, + text_rec=text_rec, + **kwargs, + ) + return model + + +def PPHGNetV2_B5(pretrained=False, use_ssld=False, **kwargs): + """ + PPHGNetV2_B5 + Args: + pretrained (bool/str): If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld (bool) Whether using ssld pretrained model when pretrained is True. + Returns: + model: nn.Layer. Specific `PPHGNetV2_B5` model depends on args. + """ + stage_config = { + # in_channels, mid_channels, out_channels, num_blocks, is_downsample, light_block, kernel_size, layer_num + "stage1": [64, 64, 128, 1, False, False, 3, 6], + "stage2": [128, 128, 512, 2, True, False, 3, 6], + "stage3": [512, 256, 1024, 5, True, True, 5, 6], + "stage4": [1024, 512, 2048, 2, True, True, 5, 6], + } + + model = PPHGNetV2( + stem_channels=[3, 32, 64], stage_config=stage_config, use_lab=False, **kwargs + ) + return model + + +def PPHGNetV2_B6(pretrained=False, use_ssld=False, **kwargs): + """ + PPHGNetV2_B6 + Args: + pretrained (bool/str): If `True` load pretrained parameters, `False` otherwise. + If str, means the path of the pretrained model. + use_ssld (bool) Whether using ssld pretrained model when pretrained is True. + Returns: + model: nn.Layer. Specific `PPHGNetV2_B6` model depends on args. + """ + stage_config = { + # in_channels, mid_channels, out_channels, num_blocks, is_downsample, light_block, kernel_size, layer_num + "stage1": [96, 96, 192, 2, False, False, 3, 6], + "stage2": [192, 192, 512, 3, True, False, 3, 6], + "stage3": [512, 384, 1024, 6, True, True, 5, 6], + "stage4": [1024, 768, 2048, 3, True, True, 5, 6], + } + + model = PPHGNetV2( + stem_channels=[3, 48, 96], stage_config=stage_config, use_lab=False, **kwargs + ) + return model + + +class PPHGNetV2_B4_Formula(nn.Layer): + """ + PPHGNetV2_B4_Formula + Args: + in_channels (int): Number of input channels. Default is 3 (for RGB images). + class_num (int): Number of classes for classification. Default is 1000. + Returns: + model: nn.Layer. Specific `PPHGNetV2_B4` model with defined architecture. + """ + + def __init__(self, in_channels=3, class_num=1000): + super().__init__() + self.in_channels = in_channels + self.out_channels = 2048 + stage_config = { + # in_channels, mid_channels, out_channels, num_blocks, is_downsample, light_block, kernel_size, layer_num + "stage1": [48, 48, 128, 1, False, False, 3, 6, 2], + "stage2": [128, 96, 512, 1, True, False, 3, 6, 2], + "stage3": [512, 192, 1024, 3, True, True, 5, 6, 2], + "stage4": [1024, 384, 2048, 1, True, True, 5, 6, 2], + } + + self.pphgnet_b4 = PPHGNetV2( + stem_channels=[3, 32, 48], + stage_config=stage_config, + class_num=class_num, + use_lab=False, + ) + + def forward(self, input_data): + if self.training: + pixel_values, label, attention_mask = input_data + else: + if isinstance(input_data, list): + pixel_values = input_data[0] + else: + pixel_values = input_data + num_channels = pixel_values.shape[1] + if num_channels == 1: + pixel_values = paddle.repeat_interleave(pixel_values, repeats=3, axis=1) + pphgnet_b4_output = self.pphgnet_b4(pixel_values) + b, c, h, w = pphgnet_b4_output.shape + pphgnet_b4_output = pphgnet_b4_output.reshape([b, c, h * w]).transpose( + [0, 2, 1] + ) + pphgnet_b4_output = DonutSwinModelOutput( + last_hidden_state=pphgnet_b4_output, + pooler_output=None, + hidden_states=None, + attentions=False, + reshaped_hidden_states=None, + ) + if self.training: + return pphgnet_b4_output, label, attention_mask + else: + return pphgnet_b4_output + + +class PPHGNetV2_B6_Formula(nn.Layer): + """ + PPHGNetV2_B6_Formula + Args: + in_channels (int): Number of input channels. Default is 3 (for RGB images). + class_num (int): Number of classes for classification. Default is 1000. + Returns: + model: nn.Layer. Specific `PPHGNetV2_B6` model with defined architecture. + """ + + def __init__(self, in_channels=3, class_num=1000): + super().__init__() + self.in_channels = in_channels + self.out_channels = 2048 + stage_config = { + # in_channels, mid_channels, out_channels, num_blocks, is_downsample, light_block, kernel_size, layer_num + "stage1": [96, 96, 192, 2, False, False, 3, 6, 2], + "stage2": [192, 192, 512, 3, True, False, 3, 6, 2], + "stage3": [512, 384, 1024, 6, True, True, 5, 6, 2], + "stage4": [1024, 768, 2048, 3, True, True, 5, 6, 2], + } + + self.pphgnet_b6 = PPHGNetV2( + stem_channels=[3, 48, 96], + class_num=class_num, + stage_config=stage_config, + use_lab=False, + ) + + def forward(self, input_data): + if self.training: + pixel_values, label, attention_mask = input_data + else: + if isinstance(input_data, list): + pixel_values = input_data[0] + else: + pixel_values = input_data + num_channels = pixel_values.shape[1] + if num_channels == 1: + pixel_values = paddle.repeat_interleave(pixel_values, repeats=3, axis=1) + pphgnet_b6_output = self.pphgnet_b6(pixel_values) + b, c, h, w = pphgnet_b6_output.shape + pphgnet_b6_output = pphgnet_b6_output.reshape([b, c, h * w]).transpose( + [0, 2, 1] + ) + pphgnet_b6_output = DonutSwinModelOutput( + last_hidden_state=pphgnet_b6_output, + pooler_output=None, + hidden_states=None, + attentions=False, + reshaped_hidden_states=None, + ) + if self.training: + return pphgnet_b6_output, label, attention_mask + else: + return pphgnet_b6_output diff --git a/ppocr/modeling/backbones/rec_repvit.py b/ppocr/modeling/backbones/rec_repvit.py new file mode 100644 index 0000000..0ef66ea --- /dev/null +++ b/ppocr/modeling/backbones/rec_repvit.py @@ -0,0 +1,392 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is refer from: +https://github.com/THU-MIG/RepViT +""" + +import paddle.nn as nn +import paddle +from paddle.nn.initializer import TruncatedNormal, Constant, Normal + +trunc_normal_ = TruncatedNormal(std=0.02) +normal_ = Normal +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) + + +def _make_divisible(v, divisor, min_value=None): + """ + This function is taken from the original tf repo. + It ensures that all layers have a channel number that is divisible by 8 + It can be seen here: + https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py + :param v: + :param divisor: + :param min_value: + :return: + """ + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than 10%. + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +# from timm.models.layers import SqueezeExcite + + +def make_divisible(v, divisor=8, min_value=None, round_limit=0.9): + min_value = min_value or divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than 10%. + if new_v < round_limit * v: + new_v += divisor + return new_v + + +class SEModule(nn.Layer): + """SE Module as defined in original SE-Nets with a few additions + Additions include: + * divisor can be specified to keep channels % div == 0 (default: 8) + * reduction channels can be specified directly by arg (if rd_channels is set) + * reduction channels can be specified by float rd_ratio (default: 1/16) + * global max pooling can be added to the squeeze aggregation + * customizable activation, normalization, and gate layer + """ + + def __init__( + self, + channels, + rd_ratio=1.0 / 16, + rd_channels=None, + rd_divisor=8, + act_layer=nn.ReLU, + ): + super(SEModule, self).__init__() + if not rd_channels: + rd_channels = make_divisible( + channels * rd_ratio, rd_divisor, round_limit=0.0 + ) + self.fc1 = nn.Conv2D(channels, rd_channels, kernel_size=1, bias_attr=True) + self.act = act_layer() + self.fc2 = nn.Conv2D(rd_channels, channels, kernel_size=1, bias_attr=True) + + def forward(self, x): + x_se = x.mean((2, 3), keepdim=True) + x_se = self.fc1(x_se) + x_se = self.act(x_se) + x_se = self.fc2(x_se) + return x * nn.functional.sigmoid(x_se) + + +class Conv2D_BN(nn.Sequential): + def __init__( + self, + a, + b, + ks=1, + stride=1, + pad=0, + dilation=1, + groups=1, + bn_weight_init=1, + resolution=-10000, + ): + super().__init__() + self.add_sublayer( + "c", nn.Conv2D(a, b, ks, stride, pad, dilation, groups, bias_attr=False) + ) + self.add_sublayer("bn", nn.BatchNorm2D(b)) + if bn_weight_init == 1: + ones_(self.bn.weight) + else: + zeros_(self.bn.weight) + zeros_(self.bn.bias) + + @paddle.no_grad() + def fuse(self): + c, bn = self.c, self.bn + w = bn.weight / (bn._variance + bn._epsilon) ** 0.5 + w = c.weight * w[:, None, None, None] + b = bn.bias - bn._mean * bn.weight / (bn._variance + bn._epsilon) ** 0.5 + m = nn.Conv2D( + w.shape[1] * self.c._groups, + w.shape[0], + w.shape[2:], + stride=self.c._stride, + padding=self.c._padding, + dilation=self.c._dilation, + groups=self.c._groups, + ) + m.weight.set_value(w) + m.bias.set_value(b) + return m + + +class Residual(nn.Layer): + def __init__(self, m, drop=0.0): + super().__init__() + self.m = m + self.drop = drop + + def forward(self, x): + if self.training and self.drop > 0: + return ( + x + + self.m(x) + * paddle.rand(x.size(0), 1, 1, 1) + .ge_(self.drop) + .div(1 - self.drop) + .detach() + ) + else: + return x + self.m(x) + + @paddle.no_grad() + def fuse(self): + if isinstance(self.m, Conv2D_BN): + m = self.m.fuse() + assert m._groups == m.in_channels + identity = paddle.ones([m.weight.shape[0], m.weight.shape[1], 1, 1]) + identity = nn.functional.pad(identity, [1, 1, 1, 1]) + m.weight += identity + return m + elif isinstance(self.m, nn.Conv2D): + m = self.m + assert m._groups != m.in_channels + identity = paddle.ones([m.weight.shape[0], m.weight.shape[1], 1, 1]) + identity = nn.functional.pad(identity, [1, 1, 1, 1]) + m.weight += identity + return m + else: + return self + + +class RepVGGDW(nn.Layer): + def __init__(self, ed) -> None: + super().__init__() + self.conv = Conv2D_BN(ed, ed, 3, 1, 1, groups=ed) + self.conv1 = nn.Conv2D(ed, ed, 1, 1, 0, groups=ed) + self.dim = ed + self.bn = nn.BatchNorm2D(ed) + + def forward(self, x): + return self.bn((self.conv(x) + self.conv1(x)) + x) + + @paddle.no_grad() + def fuse(self): + conv = self.conv.fuse() + conv1 = self.conv1 + + conv_w = conv.weight + conv_b = conv.bias + conv1_w = conv1.weight + conv1_b = conv1.bias + + conv1_w = nn.functional.pad(conv1_w, [1, 1, 1, 1]) + + identity = nn.functional.pad( + paddle.ones([conv1_w.shape[0], conv1_w.shape[1], 1, 1]), [1, 1, 1, 1] + ) + + final_conv_w = conv_w + conv1_w + identity + final_conv_b = conv_b + conv1_b + + conv.weight.set_value(final_conv_w) + conv.bias.set_value(final_conv_b) + + bn = self.bn + w = bn.weight / (bn._variance + bn._epsilon) ** 0.5 + w = conv.weight * w[:, None, None, None] + b = ( + bn.bias + + (conv.bias - bn._mean) * bn.weight / (bn._variance + bn._epsilon) ** 0.5 + ) + conv.weight.set_value(w) + conv.bias.set_value(b) + return conv + + +class RepViTBlock(nn.Layer): + def __init__(self, inp, hidden_dim, oup, kernel_size, stride, use_se, use_hs): + super(RepViTBlock, self).__init__() + + self.identity = stride == 1 and inp == oup + assert hidden_dim == 2 * inp + + if stride != 1: + self.token_mixer = nn.Sequential( + Conv2D_BN( + inp, inp, kernel_size, stride, (kernel_size - 1) // 2, groups=inp + ), + SEModule(inp, 0.25) if use_se else nn.Identity(), + Conv2D_BN(inp, oup, ks=1, stride=1, pad=0), + ) + self.channel_mixer = Residual( + nn.Sequential( + # pw + Conv2D_BN(oup, 2 * oup, 1, 1, 0), + nn.GELU() if use_hs else nn.GELU(), + # pw-linear + Conv2D_BN(2 * oup, oup, 1, 1, 0, bn_weight_init=0), + ) + ) + else: + assert self.identity + self.token_mixer = nn.Sequential( + RepVGGDW(inp), + SEModule(inp, 0.25) if use_se else nn.Identity(), + ) + self.channel_mixer = Residual( + nn.Sequential( + # pw + Conv2D_BN(inp, hidden_dim, 1, 1, 0), + nn.GELU() if use_hs else nn.GELU(), + # pw-linear + Conv2D_BN(hidden_dim, oup, 1, 1, 0, bn_weight_init=0), + ) + ) + + def forward(self, x): + return self.channel_mixer(self.token_mixer(x)) + + +class RepViT(nn.Layer): + def __init__(self, cfgs, in_channels=3, out_indices=None): + super(RepViT, self).__init__() + # setting of inverted residual blocks + self.cfgs = cfgs + + # building first layer + input_channel = self.cfgs[0][2] + patch_embed = nn.Sequential( + Conv2D_BN(in_channels, input_channel // 2, 3, 2, 1), + nn.GELU(), + Conv2D_BN(input_channel // 2, input_channel, 3, 2, 1), + ) + layers = [patch_embed] + # building inverted residual blocks + block = RepViTBlock + for k, t, c, use_se, use_hs, s in self.cfgs: + output_channel = _make_divisible(c, 8) + exp_size = _make_divisible(input_channel * t, 8) + layers.append( + block(input_channel, exp_size, output_channel, k, s, use_se, use_hs) + ) + input_channel = output_channel + self.features = nn.LayerList(layers) + self.out_indices = out_indices + if out_indices is not None: + self.out_channels = [self.cfgs[ids - 1][2] for ids in out_indices] + else: + self.out_channels = self.cfgs[-1][2] + self.is_repped = False + + def forward(self, x): + if self.out_indices is not None: + return self.forward_det(x) + return self.forward_rec(x) + + def forward_det(self, x): + outs = [] + for i, f in enumerate(self.features): + x = f(x) + if i in self.out_indices: + outs.append(x) + return outs + + def forward_rec(self, x): + for f in self.features: + x = f(x) + h = x.shape[2] + x = nn.functional.avg_pool2d(x, [h, 2]) + return x + + @paddle.no_grad() + def rep(self): + """Fuse all Conv2D_BN, RepVGGDW, Residual branches for deployment.""" + if self.is_repped: + return + # Snapshot the list first; track replaced prefixes to skip orphaned + # children (e.g. Conv2D_BN inside a RepVGGDW that was already fused). + fusable = [ + (name, sublayer) + for name, sublayer in self.named_sublayers() + if isinstance(sublayer, (Conv2D_BN, RepVGGDW, Residual)) + ] + replaced_prefixes = [] + for name, sublayer in fusable: + if any(name.startswith(pfx + ".") for pfx in replaced_prefixes): + continue + parts = name.rsplit(".", 1) + if len(parts) == 2: + parent = self + for p in parts[0].split("."): + parent = getattr(parent, p) + fused = sublayer.fuse() + setattr(parent, parts[1], fused) + # Only skip children when the module was truly replaced + if fused is not sublayer: + replaced_prefixes.append(name) + self.is_repped = True + + +def RepSVTR(in_channels=3): + """ + Constructs a MobileNetV3-Large model + """ + # k, t, c, SE, HS, s + cfgs = [ + [3, 2, 96, 1, 0, 1], + [3, 2, 96, 0, 0, 1], + [3, 2, 96, 0, 0, 1], + [3, 2, 192, 0, 1, (2, 1)], + [3, 2, 192, 1, 1, 1], + [3, 2, 192, 0, 1, 1], + [3, 2, 192, 1, 1, 1], + [3, 2, 192, 0, 1, 1], + [3, 2, 192, 1, 1, 1], + [3, 2, 192, 0, 1, 1], + [3, 2, 384, 0, 1, (2, 1)], + [3, 2, 384, 1, 1, 1], + [3, 2, 384, 0, 1, 1], + ] + return RepViT(cfgs, in_channels=in_channels) + + +def RepSVTR_det(in_channels=3, out_indices=[2, 5, 10, 13]): + """ + Constructs a MobileNetV3-Large model + """ + # k, t, c, SE, HS, s + cfgs = [ + [3, 2, 48, 1, 0, 1], + [3, 2, 48, 0, 0, 1], + [3, 2, 96, 0, 0, 2], + [3, 2, 96, 1, 0, 1], + [3, 2, 96, 0, 0, 1], + [3, 2, 192, 0, 1, 2], + [3, 2, 192, 1, 1, 1], + [3, 2, 192, 0, 1, 1], + [3, 2, 192, 1, 1, 1], + [3, 2, 192, 0, 1, 1], + [3, 2, 384, 0, 1, 2], + [3, 2, 384, 1, 1, 1], + [3, 2, 384, 0, 1, 1], + ] + return RepViT(cfgs, in_channels=in_channels, out_indices=out_indices) diff --git a/ppocr/modeling/backbones/rec_resnet_31.py b/ppocr/modeling/backbones/rec_resnet_31.py new file mode 100644 index 0000000..c4aa4fc --- /dev/null +++ b/ppocr/modeling/backbones/rec_resnet_31.py @@ -0,0 +1,318 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textrecog/layers/conv_layer.py +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textrecog/backbones/resnet31_ocr.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F +import numpy as np + +__all__ = ["ResNet31"] + + +def conv3x3(in_channel, out_channel, stride=1, conv_weight_attr=None): + return nn.Conv2D( + in_channel, + out_channel, + kernel_size=3, + stride=stride, + padding=1, + weight_attr=conv_weight_attr, + bias_attr=False, + ) + + +class BasicBlock(nn.Layer): + expansion = 1 + + def __init__( + self, + in_channels, + channels, + stride=1, + downsample=False, + conv_weight_attr=None, + bn_weight_attr=None, + ): + super().__init__() + self.conv1 = conv3x3( + in_channels, channels, stride, conv_weight_attr=conv_weight_attr + ) + self.bn1 = nn.BatchNorm2D(channels, weight_attr=bn_weight_attr) + self.relu = nn.ReLU() + self.conv2 = conv3x3(channels, channels, conv_weight_attr=conv_weight_attr) + self.bn2 = nn.BatchNorm2D(channels, weight_attr=bn_weight_attr) + self.downsample = downsample + if downsample: + self.downsample = nn.Sequential( + nn.Conv2D( + in_channels, + channels * self.expansion, + 1, + stride, + weight_attr=conv_weight_attr, + bias_attr=False, + ), + nn.BatchNorm2D(channels * self.expansion, weight_attr=bn_weight_attr), + ) + else: + self.downsample = nn.Sequential() + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class ResNet31(nn.Layer): + """ + Args: + in_channels (int): Number of channels of input image tensor. + layers (list[int]): List of BasicBlock number for each stage. + channels (list[int]): List of out_channels of Conv2d layer. + out_indices (None | Sequence[int]): Indices of output stages. + last_stage_pool (bool): If True, add `MaxPool2d` layer to last stage. + init_type (None | str): the config to control the initialization. + """ + + def __init__( + self, + in_channels=3, + layers=[1, 2, 5, 3], + channels=[64, 128, 256, 256, 512, 512, 512], + out_indices=None, + last_stage_pool=False, + init_type=None, + ): + super(ResNet31, self).__init__() + assert isinstance(in_channels, int) + assert isinstance(last_stage_pool, bool) + + self.out_indices = out_indices + self.last_stage_pool = last_stage_pool + + conv_weight_attr = None + bn_weight_attr = None + + if init_type is not None: + support_dict = ["KaimingNormal"] + assert init_type in support_dict, Exception( + "resnet31 only support {}".format(support_dict) + ) + conv_weight_attr = nn.initializer.KaimingNormal() + bn_weight_attr = ParamAttr( + initializer=nn.initializer.Uniform(), learning_rate=1 + ) + + # conv 1 (Conv Conv) + self.conv1_1 = nn.Conv2D( + in_channels, + channels[0], + kernel_size=3, + stride=1, + padding=1, + weight_attr=conv_weight_attr, + ) + self.bn1_1 = nn.BatchNorm2D(channels[0], weight_attr=bn_weight_attr) + self.relu1_1 = nn.ReLU() + + self.conv1_2 = nn.Conv2D( + channels[0], + channels[1], + kernel_size=3, + stride=1, + padding=1, + weight_attr=conv_weight_attr, + ) + self.bn1_2 = nn.BatchNorm2D(channels[1], weight_attr=bn_weight_attr) + self.relu1_2 = nn.ReLU() + + # conv 2 (Max-pooling, Residual block, Conv) + self.pool2 = nn.MaxPool2D(kernel_size=2, stride=2, padding=0, ceil_mode=True) + self.block2 = self._make_layer( + channels[1], + channels[2], + layers[0], + conv_weight_attr=conv_weight_attr, + bn_weight_attr=bn_weight_attr, + ) + self.conv2 = nn.Conv2D( + channels[2], + channels[2], + kernel_size=3, + stride=1, + padding=1, + weight_attr=conv_weight_attr, + ) + self.bn2 = nn.BatchNorm2D(channels[2], weight_attr=bn_weight_attr) + self.relu2 = nn.ReLU() + + # conv 3 (Max-pooling, Residual block, Conv) + self.pool3 = nn.MaxPool2D(kernel_size=2, stride=2, padding=0, ceil_mode=True) + self.block3 = self._make_layer( + channels[2], + channels[3], + layers[1], + conv_weight_attr=conv_weight_attr, + bn_weight_attr=bn_weight_attr, + ) + self.conv3 = nn.Conv2D( + channels[3], + channels[3], + kernel_size=3, + stride=1, + padding=1, + weight_attr=conv_weight_attr, + ) + self.bn3 = nn.BatchNorm2D(channels[3], weight_attr=bn_weight_attr) + self.relu3 = nn.ReLU() + + # conv 4 (Max-pooling, Residual block, Conv) + self.pool4 = nn.MaxPool2D( + kernel_size=(2, 1), stride=(2, 1), padding=0, ceil_mode=True + ) + self.block4 = self._make_layer( + channels[3], + channels[4], + layers[2], + conv_weight_attr=conv_weight_attr, + bn_weight_attr=bn_weight_attr, + ) + self.conv4 = nn.Conv2D( + channels[4], + channels[4], + kernel_size=3, + stride=1, + padding=1, + weight_attr=conv_weight_attr, + ) + self.bn4 = nn.BatchNorm2D(channels[4], weight_attr=bn_weight_attr) + self.relu4 = nn.ReLU() + + # conv 5 ((Max-pooling), Residual block, Conv) + self.pool5 = None + if self.last_stage_pool: + self.pool5 = nn.MaxPool2D( + kernel_size=2, stride=2, padding=0, ceil_mode=True + ) + self.block5 = self._make_layer( + channels[4], + channels[5], + layers[3], + conv_weight_attr=conv_weight_attr, + bn_weight_attr=bn_weight_attr, + ) + self.conv5 = nn.Conv2D( + channels[5], + channels[5], + kernel_size=3, + stride=1, + padding=1, + weight_attr=conv_weight_attr, + ) + self.bn5 = nn.BatchNorm2D(channels[5], weight_attr=bn_weight_attr) + self.relu5 = nn.ReLU() + + self.out_channels = channels[-1] + + def _make_layer( + self, + input_channels, + output_channels, + blocks, + conv_weight_attr=None, + bn_weight_attr=None, + ): + layers = [] + for _ in range(blocks): + downsample = None + if input_channels != output_channels: + downsample = nn.Sequential( + nn.Conv2D( + input_channels, + output_channels, + kernel_size=1, + stride=1, + weight_attr=conv_weight_attr, + bias_attr=False, + ), + nn.BatchNorm2D(output_channels, weight_attr=bn_weight_attr), + ) + + layers.append( + BasicBlock( + input_channels, + output_channels, + downsample=downsample, + conv_weight_attr=conv_weight_attr, + bn_weight_attr=bn_weight_attr, + ) + ) + input_channels = output_channels + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1_1(x) + x = self.bn1_1(x) + x = self.relu1_1(x) + + x = self.conv1_2(x) + x = self.bn1_2(x) + x = self.relu1_2(x) + + outs = [] + for i in range(4): + layer_index = i + 2 + pool_layer = getattr(self, f"pool{layer_index}") + block_layer = getattr(self, f"block{layer_index}") + conv_layer = getattr(self, f"conv{layer_index}") + bn_layer = getattr(self, f"bn{layer_index}") + relu_layer = getattr(self, f"relu{layer_index}") + + if pool_layer is not None: + x = pool_layer(x) + x = block_layer(x) + x = conv_layer(x) + x = bn_layer(x) + x = relu_layer(x) + + outs.append(x) + + if self.out_indices is not None: + return tuple([outs[i] for i in self.out_indices]) + + return x diff --git a/ppocr/modeling/backbones/rec_resnet_32.py b/ppocr/modeling/backbones/rec_resnet_32.py new file mode 100644 index 0000000..63d78d3 --- /dev/null +++ b/ppocr/modeling/backbones/rec_resnet_32.py @@ -0,0 +1,305 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/hikopensource/DAVAR-Lab-OCR/davarocr/davar_rcg/models/backbones/ResNet32.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle.nn as nn + +__all__ = ["ResNet32"] + +conv_weight_attr = nn.initializer.KaimingNormal() + + +class ResNet32(nn.Layer): + """ + Feature Extractor is proposed in FAN Ref [1] + + Ref [1]: Focusing Attention: Towards Accurate Text Recognition in Neural Images ICCV-2017 + """ + + def __init__(self, in_channels, out_channels=512): + """ + + Args: + in_channels (int): input channel + output_channel (int): output channel + """ + super(ResNet32, self).__init__() + self.out_channels = out_channels + self.ConvNet = ResNet(in_channels, out_channels, BasicBlock, [1, 2, 5, 3]) + + def forward(self, inputs): + """ + Args: + inputs: input feature + + Returns: + output feature + + """ + return self.ConvNet(inputs) + + +class BasicBlock(nn.Layer): + """Res-net Basic Block""" + + expansion = 1 + + def __init__( + self, inplanes, planes, stride=1, downsample=None, norm_type="BN", **kwargs + ): + """ + Args: + inplanes (int): input channel + planes (int): channels of the middle feature + stride (int): stride of the convolution + downsample (int): type of the down_sample + norm_type (str): type of the normalization + **kwargs (None): backup parameter + """ + super(BasicBlock, self).__init__() + self.conv1 = self._conv3x3(inplanes, planes) + self.bn1 = nn.BatchNorm2D(planes) + self.conv2 = self._conv3x3(planes, planes) + self.bn2 = nn.BatchNorm2D(planes) + self.relu = nn.ReLU() + self.downsample = downsample + self.stride = stride + + def _conv3x3(self, in_planes, out_planes, stride=1): + """ + + Args: + in_planes (int): input channel + out_planes (int): channels of the middle feature + stride (int): stride of the convolution + Returns: + nn.Layer: Conv2D with kernel = 3 + + """ + + return nn.Conv2D( + in_planes, + out_planes, + kernel_size=3, + stride=stride, + padding=1, + weight_attr=conv_weight_attr, + bias_attr=False, + ) + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + out += residual + out = self.relu(out) + + return out + + +class ResNet(nn.Layer): + """Res-Net network structure""" + + def __init__(self, input_channel, output_channel, block, layers): + """ + + Args: + input_channel (int): input channel + output_channel (int): output channel + block (BasicBlock): convolution block + layers (list): layers of the block + """ + super(ResNet, self).__init__() + + self.output_channel_block = [ + int(output_channel / 4), + int(output_channel / 2), + output_channel, + output_channel, + ] + + self.inplanes = int(output_channel / 8) + self.conv0_1 = nn.Conv2D( + input_channel, + int(output_channel / 16), + kernel_size=3, + stride=1, + padding=1, + weight_attr=conv_weight_attr, + bias_attr=False, + ) + self.bn0_1 = nn.BatchNorm2D(int(output_channel / 16)) + self.conv0_2 = nn.Conv2D( + int(output_channel / 16), + self.inplanes, + kernel_size=3, + stride=1, + padding=1, + weight_attr=conv_weight_attr, + bias_attr=False, + ) + self.bn0_2 = nn.BatchNorm2D(self.inplanes) + self.relu = nn.ReLU() + + self.maxpool1 = nn.MaxPool2D(kernel_size=2, stride=2, padding=0) + self.layer1 = self._make_layer(block, self.output_channel_block[0], layers[0]) + self.conv1 = nn.Conv2D( + self.output_channel_block[0], + self.output_channel_block[0], + kernel_size=3, + stride=1, + padding=1, + weight_attr=conv_weight_attr, + bias_attr=False, + ) + self.bn1 = nn.BatchNorm2D(self.output_channel_block[0]) + + self.maxpool2 = nn.MaxPool2D(kernel_size=2, stride=2, padding=0) + self.layer2 = self._make_layer( + block, self.output_channel_block[1], layers[1], stride=1 + ) + self.conv2 = nn.Conv2D( + self.output_channel_block[1], + self.output_channel_block[1], + kernel_size=3, + stride=1, + padding=1, + weight_attr=conv_weight_attr, + bias_attr=False, + ) + self.bn2 = nn.BatchNorm2D(self.output_channel_block[1]) + + self.maxpool3 = nn.MaxPool2D(kernel_size=2, stride=(2, 1), padding=(0, 1)) + self.layer3 = self._make_layer( + block, self.output_channel_block[2], layers[2], stride=1 + ) + self.conv3 = nn.Conv2D( + self.output_channel_block[2], + self.output_channel_block[2], + kernel_size=3, + stride=1, + padding=1, + weight_attr=conv_weight_attr, + bias_attr=False, + ) + self.bn3 = nn.BatchNorm2D(self.output_channel_block[2]) + + self.layer4 = self._make_layer( + block, self.output_channel_block[3], layers[3], stride=1 + ) + self.conv4_1 = nn.Conv2D( + self.output_channel_block[3], + self.output_channel_block[3], + kernel_size=2, + stride=(2, 1), + padding=(0, 1), + weight_attr=conv_weight_attr, + bias_attr=False, + ) + self.bn4_1 = nn.BatchNorm2D(self.output_channel_block[3]) + self.conv4_2 = nn.Conv2D( + self.output_channel_block[3], + self.output_channel_block[3], + kernel_size=2, + stride=1, + padding=0, + weight_attr=conv_weight_attr, + bias_attr=False, + ) + self.bn4_2 = nn.BatchNorm2D(self.output_channel_block[3]) + + def _make_layer(self, block, planes, blocks, stride=1): + """ + + Args: + block (block): convolution block + planes (int): input channels + blocks (list): layers of the block + stride (int): stride of the convolution + + Returns: + nn.Sequential: the combination of the convolution block + + """ + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2D( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + weight_attr=conv_weight_attr, + bias_attr=False, + ), + nn.BatchNorm2D(planes * block.expansion), + ) + + layers = list() + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for _ in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv0_1(x) + x = self.bn0_1(x) + x = self.relu(x) + x = self.conv0_2(x) + x = self.bn0_2(x) + x = self.relu(x) + + x = self.maxpool1(x) + x = self.layer1(x) + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + + x = self.maxpool2(x) + x = self.layer2(x) + x = self.conv2(x) + x = self.bn2(x) + x = self.relu(x) + + x = self.maxpool3(x) + x = self.layer3(x) + x = self.conv3(x) + x = self.bn3(x) + x = self.relu(x) + + x = self.layer4(x) + x = self.conv4_1(x) + x = self.bn4_1(x) + x = self.relu(x) + x = self.conv4_2(x) + x = self.bn4_2(x) + x = self.relu(x) + return x diff --git a/ppocr/modeling/backbones/rec_resnet_45.py b/ppocr/modeling/backbones/rec_resnet_45.py new file mode 100644 index 0000000..914d972 --- /dev/null +++ b/ppocr/modeling/backbones/rec_resnet_45.py @@ -0,0 +1,150 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/FangShancheng/ABINet/tree/main/modules +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import ParamAttr +from paddle.nn.initializer import KaimingNormal +import paddle.nn as nn +import paddle.nn.functional as F +import numpy as np +import math + +__all__ = ["ResNet45"] + + +def conv1x1(in_planes, out_planes, stride=1): + return nn.Conv2D( + in_planes, + out_planes, + kernel_size=1, + stride=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False, + ) + + +def conv3x3(in_channel, out_channel, stride=1): + return nn.Conv2D( + in_channel, + out_channel, + kernel_size=3, + stride=stride, + padding=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False, + ) + + +class BasicBlock(nn.Layer): + expansion = 1 + + def __init__(self, in_channels, channels, stride=1, downsample=None): + super().__init__() + self.conv1 = conv1x1(in_channels, channels) + self.bn1 = nn.BatchNorm2D(channels) + self.relu = nn.ReLU() + self.conv2 = conv3x3(channels, channels, stride) + self.bn2 = nn.BatchNorm2D(channels) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + out += residual + out = self.relu(out) + + return out + + +class ResNet45(nn.Layer): + def __init__( + self, + in_channels=3, + block=BasicBlock, + layers=[3, 4, 6, 6, 3], + strides=[2, 1, 2, 1, 1], + ): + self.inplanes = 32 + super(ResNet45, self).__init__() + self.conv1 = nn.Conv2D( + in_channels, + 32, + kernel_size=3, + stride=1, + padding=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False, + ) + self.bn1 = nn.BatchNorm2D(32) + self.relu = nn.ReLU() + + self.layer1 = self._make_layer(block, 32, layers[0], stride=strides[0]) + self.layer2 = self._make_layer(block, 64, layers[1], stride=strides[1]) + self.layer3 = self._make_layer(block, 128, layers[2], stride=strides[2]) + self.layer4 = self._make_layer(block, 256, layers[3], stride=strides[3]) + self.layer5 = self._make_layer(block, 512, layers[4], stride=strides[4]) + self.out_channels = 512 + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + # downsample = True + downsample = nn.Sequential( + nn.Conv2D( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False, + ), + nn.BatchNorm2D(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + x = self.layer5(x) + return x diff --git a/ppocr/modeling/backbones/rec_resnet_aster.py b/ppocr/modeling/backbones/rec_resnet_aster.py new file mode 100644 index 0000000..9b5a15e --- /dev/null +++ b/ppocr/modeling/backbones/rec_resnet_aster.py @@ -0,0 +1,141 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/ayumiymk/aster.pytorch/blob/master/lib/models/resnet_aster.py +""" +import paddle +import paddle.nn as nn + +import sys +import math + + +def conv3x3(in_planes, out_planes, stride=1): + """3x3 convolution with padding""" + return nn.Conv2D( + in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias_attr=False + ) + + +def conv1x1(in_planes, out_planes, stride=1): + """1x1 convolution""" + return nn.Conv2D( + in_planes, out_planes, kernel_size=1, stride=stride, bias_attr=False + ) + + +def get_sinusoid_encoding(n_position, feat_dim, wave_length=10000): + # [n_position] + positions = paddle.arange(0, n_position) + # [feat_dim] + dim_range = paddle.arange(0, feat_dim) + dim_range = paddle.pow(wave_length, 2 * (dim_range // 2) / feat_dim) + # [n_position, feat_dim] + angles = paddle.unsqueeze(positions, axis=1) / paddle.unsqueeze(dim_range, axis=0) + angles = paddle.cast(angles, "float32") + angles[:, 0::2] = paddle.sin(angles[:, 0::2]) + angles[:, 1::2] = paddle.cos(angles[:, 1::2]) + return angles + + +class AsterBlock(nn.Layer): + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(AsterBlock, self).__init__() + self.conv1 = conv1x1(inplanes, planes, stride) + self.bn1 = nn.BatchNorm2D(planes) + self.relu = nn.ReLU() + self.conv2 = conv3x3(planes, planes) + self.bn2 = nn.BatchNorm2D(planes) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + out += residual + out = self.relu(out) + return out + + +class ResNet_ASTER(nn.Layer): + """For aster or crnn""" + + def __init__(self, with_lstm=True, n_group=1, in_channels=3): + super(ResNet_ASTER, self).__init__() + self.with_lstm = with_lstm + self.n_group = n_group + + self.layer0 = nn.Sequential( + nn.Conv2D( + in_channels, + 32, + kernel_size=(3, 3), + stride=1, + padding=1, + bias_attr=False, + ), + nn.BatchNorm2D(32), + nn.ReLU(), + ) + + self.inplanes = 32 + self.layer1 = self._make_layer(32, 3, [2, 2]) # [16, 50] + self.layer2 = self._make_layer(64, 4, [2, 2]) # [8, 25] + self.layer3 = self._make_layer(128, 6, [2, 1]) # [4, 25] + self.layer4 = self._make_layer(256, 6, [2, 1]) # [2, 25] + self.layer5 = self._make_layer(512, 3, [2, 1]) # [1, 25] + + if with_lstm: + self.rnn = nn.LSTM(512, 256, direction="bidirect", num_layers=2) + self.out_channels = 2 * 256 + else: + self.out_channels = 512 + + def _make_layer(self, planes, blocks, stride): + downsample = None + if stride != [1, 1] or self.inplanes != planes: + downsample = nn.Sequential( + conv1x1(self.inplanes, planes, stride), nn.BatchNorm2D(planes) + ) + + layers = [] + layers.append(AsterBlock(self.inplanes, planes, stride, downsample)) + self.inplanes = planes + for _ in range(1, blocks): + layers.append(AsterBlock(self.inplanes, planes)) + return nn.Sequential(*layers) + + def forward(self, x): + x0 = self.layer0(x) + x1 = self.layer1(x0) + x2 = self.layer2(x1) + x3 = self.layer3(x2) + x4 = self.layer4(x3) + x5 = self.layer5(x4) + + cnn_feat = x5.squeeze(2) # [N, c, w] + cnn_feat = paddle.transpose(cnn_feat, perm=[0, 2, 1]) + if self.with_lstm: + rnn_feat, _ = self.rnn(cnn_feat) + return rnn_feat + else: + return cnn_feat diff --git a/ppocr/modeling/backbones/rec_resnet_fpn.py b/ppocr/modeling/backbones/rec_resnet_fpn.py new file mode 100644 index 0000000..d259f1d --- /dev/null +++ b/ppocr/modeling/backbones/rec_resnet_fpn.py @@ -0,0 +1,317 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from paddle import nn, ParamAttr +from paddle.nn import functional as F +import paddle +import numpy as np + +__all__ = ["ResNetFPN"] + + +class ResNetFPN(nn.Layer): + def __init__(self, in_channels=1, layers=50, **kwargs): + super(ResNetFPN, self).__init__() + supported_layers = { + 18: {"depth": [2, 2, 2, 2], "block_class": BasicBlock}, + 34: {"depth": [3, 4, 6, 3], "block_class": BasicBlock}, + 50: {"depth": [3, 4, 6, 3], "block_class": BottleneckBlock}, + 101: {"depth": [3, 4, 23, 3], "block_class": BottleneckBlock}, + 152: {"depth": [3, 8, 36, 3], "block_class": BottleneckBlock}, + } + stride_list = [(2, 2), (2, 2), (1, 1), (1, 1)] + num_filters = [64, 128, 256, 512] + self.depth = supported_layers[layers]["depth"] + self.F = [] + self.conv = ConvBNLayer( + in_channels=in_channels, + out_channels=64, + kernel_size=7, + stride=2, + act="relu", + name="conv1", + ) + self.block_list = [] + in_ch = 64 + if layers >= 50: + for block in range(len(self.depth)): + for i in range(self.depth[block]): + if layers in [101, 152] and block == 2: + if i == 0: + conv_name = "res" + str(block + 2) + "a" + else: + conv_name = "res" + str(block + 2) + "b" + str(i) + else: + conv_name = "res" + str(block + 2) + chr(97 + i) + block_list = self.add_sublayer( + "bottleneckBlock_{}_{}".format(block, i), + BottleneckBlock( + in_channels=in_ch, + out_channels=num_filters[block], + stride=stride_list[block] if i == 0 else 1, + name=conv_name, + ), + ) + in_ch = num_filters[block] * 4 + self.block_list.append(block_list) + self.F.append(block_list) + else: + for block in range(len(self.depth)): + for i in range(self.depth[block]): + conv_name = "res" + str(block + 2) + chr(97 + i) + if i == 0 and block != 0: + stride = (2, 1) + else: + stride = (1, 1) + basic_block = self.add_sublayer( + conv_name, + BasicBlock( + in_channels=in_ch, + out_channels=num_filters[block], + stride=stride_list[block] if i == 0 else 1, + is_first=block == i == 0, + name=conv_name, + ), + ) + in_ch = basic_block.out_channels + self.block_list.append(basic_block) + out_ch_list = [in_ch // 4, in_ch // 2, in_ch] + self.base_block = [] + self.conv_trans = [] + self.bn_block = [] + for i in [-2, -3]: + in_channels = out_ch_list[i + 1] + out_ch_list[i] + + self.base_block.append( + self.add_sublayer( + "F_{}_base_block_0".format(i), + nn.Conv2D( + in_channels=in_channels, + out_channels=out_ch_list[i], + kernel_size=1, + weight_attr=ParamAttr(trainable=True), + bias_attr=ParamAttr(trainable=True), + ), + ) + ) + self.base_block.append( + self.add_sublayer( + "F_{}_base_block_1".format(i), + nn.Conv2D( + in_channels=out_ch_list[i], + out_channels=out_ch_list[i], + kernel_size=3, + padding=1, + weight_attr=ParamAttr(trainable=True), + bias_attr=ParamAttr(trainable=True), + ), + ) + ) + self.base_block.append( + self.add_sublayer( + "F_{}_base_block_2".format(i), + nn.BatchNorm( + num_channels=out_ch_list[i], + act="relu", + param_attr=ParamAttr(trainable=True), + bias_attr=ParamAttr(trainable=True), + ), + ) + ) + self.base_block.append( + self.add_sublayer( + "F_{}_base_block_3".format(i), + nn.Conv2D( + in_channels=out_ch_list[i], + out_channels=512, + kernel_size=1, + bias_attr=ParamAttr(trainable=True), + weight_attr=ParamAttr(trainable=True), + ), + ) + ) + self.out_channels = 512 + + def __call__(self, x): + x = self.conv(x) + fpn_list = [] + F = [] + for i in range(len(self.depth)): + fpn_list.append(np.sum(self.depth[: i + 1])) + + for i, block in enumerate(self.block_list): + x = block(x) + for number in fpn_list: + if i + 1 == number: + F.append(x) + base = F[-1] + + j = 0 + for i, block in enumerate(self.base_block): + if i % 3 == 0 and i < 6: + j = j + 1 + b, c, w, h = F[-j - 1].shape + if [w, h] == list(base.shape[2:]): + base = base + else: + base = self.conv_trans[j - 1](base) + base = self.bn_block[j - 1](base) + base = paddle.concat([base, F[-j - 1]], axis=1) + base = block(base) + return base + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + act=None, + name=None, + ): + super(ConvBNLayer, self).__init__() + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=2 if stride == (1, 1) else kernel_size, + dilation=2 if stride == (1, 1) else 1, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(name=name + ".conv2d.output.1.w_0"), + bias_attr=False, + ) + + if name == "conv1": + bn_name = "bn_" + name + else: + bn_name = "bn" + name[3:] + self.bn = nn.BatchNorm( + num_channels=out_channels, + act=act, + param_attr=ParamAttr(name=name + ".output.1.w_0"), + bias_attr=ParamAttr(name=name + ".output.1.b_0"), + moving_mean_name=bn_name + "_mean", + moving_variance_name=bn_name + "_variance", + ) + + def __call__(self, x): + x = self.conv(x) + x = self.bn(x) + return x + + +class ShortCut(nn.Layer): + def __init__(self, in_channels, out_channels, stride, name, is_first=False): + super(ShortCut, self).__init__() + self.use_conv = True + + if in_channels != out_channels or stride != 1 or is_first == True: + if stride == (1, 1): + self.conv = ConvBNLayer(in_channels, out_channels, 1, 1, name=name) + else: # stride==(2,2) + self.conv = ConvBNLayer(in_channels, out_channels, 1, stride, name=name) + else: + self.use_conv = False + + def forward(self, x): + if self.use_conv: + x = self.conv(x) + return x + + +class BottleneckBlock(nn.Layer): + def __init__(self, in_channels, out_channels, stride, name): + super(BottleneckBlock, self).__init__() + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + act="relu", + name=name + "_branch2a", + ) + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + act="relu", + name=name + "_branch2b", + ) + + self.conv2 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels * 4, + kernel_size=1, + act=None, + name=name + "_branch2c", + ) + + self.short = ShortCut( + in_channels=in_channels, + out_channels=out_channels * 4, + stride=stride, + is_first=False, + name=name + "_branch1", + ) + self.out_channels = out_channels * 4 + + def forward(self, x): + y = self.conv0(x) + y = self.conv1(y) + y = self.conv2(y) + y = y + self.short(x) + y = F.relu(y) + return y + + +class BasicBlock(nn.Layer): + def __init__(self, in_channels, out_channels, stride, name, is_first): + super(BasicBlock, self).__init__() + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + act="relu", + stride=stride, + name=name + "_branch2a", + ) + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, + act=None, + name=name + "_branch2b", + ) + self.short = ShortCut( + in_channels=in_channels, + out_channels=out_channels, + stride=stride, + is_first=is_first, + name=name + "_branch1", + ) + self.out_channels = out_channels + + def forward(self, x): + y = self.conv0(x) + y = self.conv1(y) + y = y + self.short(x) + return F.relu(y) diff --git a/ppocr/modeling/backbones/rec_resnet_rfl.py b/ppocr/modeling/backbones/rec_resnet_rfl.py new file mode 100644 index 0000000..4742ef8 --- /dev/null +++ b/ppocr/modeling/backbones/rec_resnet_rfl.py @@ -0,0 +1,359 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/hikopensource/DAVAR-Lab-OCR/blob/main/davarocr/davar_rcg/models/backbones/ResNetRFL.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +import paddle.nn as nn + +from paddle.nn.initializer import TruncatedNormal, Constant, Normal, KaimingNormal + +kaiming_init_ = KaimingNormal() +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) + + +class BasicBlock(nn.Layer): + """Res-net Basic Block""" + + expansion = 1 + + def __init__( + self, inplanes, planes, stride=1, downsample=None, norm_type="BN", **kwargs + ): + """ + Args: + inplanes (int): input channel + planes (int): channels of the middle feature + stride (int): stride of the convolution + downsample (int): type of the down_sample + norm_type (str): type of the normalization + **kwargs (None): backup parameter + """ + super(BasicBlock, self).__init__() + self.conv1 = self._conv3x3(inplanes, planes) + self.bn1 = nn.BatchNorm(planes) + self.conv2 = self._conv3x3(planes, planes) + self.bn2 = nn.BatchNorm(planes) + self.relu = nn.ReLU() + self.downsample = downsample + self.stride = stride + + def _conv3x3(self, in_planes, out_planes, stride=1): + return nn.Conv2D( + in_planes, + out_planes, + kernel_size=3, + stride=stride, + padding=1, + bias_attr=False, + ) + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + out += residual + out = self.relu(out) + + return out + + +class ResNetRFL(nn.Layer): + def __init__(self, in_channels, out_channels=512, use_cnt=True, use_seq=True): + """ + + Args: + in_channels (int): input channel + out_channels (int): output channel + """ + super(ResNetRFL, self).__init__() + assert use_cnt or use_seq + self.use_cnt, self.use_seq = use_cnt, use_seq + self.backbone = RFLBase(in_channels) + + self.out_channels = out_channels + self.out_channels_block = [ + int(self.out_channels / 4), + int(self.out_channels / 2), + self.out_channels, + self.out_channels, + ] + block = BasicBlock + layers = [1, 2, 5, 3] + self.inplanes = int(self.out_channels // 2) + + self.relu = nn.ReLU() + if self.use_seq: + self.maxpool3 = nn.MaxPool2D(kernel_size=2, stride=(2, 1), padding=(0, 1)) + self.layer3 = self._make_layer( + block, self.out_channels_block[2], layers[2], stride=1 + ) + self.conv3 = nn.Conv2D( + self.out_channels_block[2], + self.out_channels_block[2], + kernel_size=3, + stride=1, + padding=1, + bias_attr=False, + ) + self.bn3 = nn.BatchNorm(self.out_channels_block[2]) + + self.layer4 = self._make_layer( + block, self.out_channels_block[3], layers[3], stride=1 + ) + self.conv4_1 = nn.Conv2D( + self.out_channels_block[3], + self.out_channels_block[3], + kernel_size=2, + stride=(2, 1), + padding=(0, 1), + bias_attr=False, + ) + self.bn4_1 = nn.BatchNorm(self.out_channels_block[3]) + self.conv4_2 = nn.Conv2D( + self.out_channels_block[3], + self.out_channels_block[3], + kernel_size=2, + stride=1, + padding=0, + bias_attr=False, + ) + self.bn4_2 = nn.BatchNorm(self.out_channels_block[3]) + + if self.use_cnt: + self.inplanes = int(self.out_channels // 2) + self.v_maxpool3 = nn.MaxPool2D(kernel_size=2, stride=(2, 1), padding=(0, 1)) + self.v_layer3 = self._make_layer( + block, self.out_channels_block[2], layers[2], stride=1 + ) + self.v_conv3 = nn.Conv2D( + self.out_channels_block[2], + self.out_channels_block[2], + kernel_size=3, + stride=1, + padding=1, + bias_attr=False, + ) + self.v_bn3 = nn.BatchNorm(self.out_channels_block[2]) + + self.v_layer4 = self._make_layer( + block, self.out_channels_block[3], layers[3], stride=1 + ) + self.v_conv4_1 = nn.Conv2D( + self.out_channels_block[3], + self.out_channels_block[3], + kernel_size=2, + stride=(2, 1), + padding=(0, 1), + bias_attr=False, + ) + self.v_bn4_1 = nn.BatchNorm(self.out_channels_block[3]) + self.v_conv4_2 = nn.Conv2D( + self.out_channels_block[3], + self.out_channels_block[3], + kernel_size=2, + stride=1, + padding=0, + bias_attr=False, + ) + self.v_bn4_2 = nn.BatchNorm(self.out_channels_block[3]) + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2D( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias_attr=False, + ), + nn.BatchNorm(planes * block.expansion), + ) + + layers = list() + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for _ in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, inputs): + x_1 = self.backbone(inputs) + + if self.use_cnt: + v_x = self.v_maxpool3(x_1) + v_x = self.v_layer3(v_x) + v_x = self.v_conv3(v_x) + v_x = self.v_bn3(v_x) + visual_feature_2 = self.relu(v_x) + + v_x = self.v_layer4(visual_feature_2) + v_x = self.v_conv4_1(v_x) + v_x = self.v_bn4_1(v_x) + v_x = self.relu(v_x) + v_x = self.v_conv4_2(v_x) + v_x = self.v_bn4_2(v_x) + visual_feature_3 = self.relu(v_x) + else: + visual_feature_3 = None + if self.use_seq: + x = self.maxpool3(x_1) + x = self.layer3(x) + x = self.conv3(x) + x = self.bn3(x) + x_2 = self.relu(x) + + x = self.layer4(x_2) + x = self.conv4_1(x) + x = self.bn4_1(x) + x = self.relu(x) + x = self.conv4_2(x) + x = self.bn4_2(x) + x_3 = self.relu(x) + else: + x_3 = None + + return [visual_feature_3, x_3] + + +class ResNetBase(nn.Layer): + def __init__(self, in_channels, out_channels, block, layers): + super(ResNetBase, self).__init__() + + self.out_channels_block = [ + int(out_channels / 4), + int(out_channels / 2), + out_channels, + out_channels, + ] + + self.inplanes = int(out_channels / 8) + self.conv0_1 = nn.Conv2D( + in_channels, + int(out_channels / 16), + kernel_size=3, + stride=1, + padding=1, + bias_attr=False, + ) + self.bn0_1 = nn.BatchNorm(int(out_channels / 16)) + self.conv0_2 = nn.Conv2D( + int(out_channels / 16), + self.inplanes, + kernel_size=3, + stride=1, + padding=1, + bias_attr=False, + ) + self.bn0_2 = nn.BatchNorm(self.inplanes) + self.relu = nn.ReLU() + + self.maxpool1 = nn.MaxPool2D(kernel_size=2, stride=2, padding=0) + self.layer1 = self._make_layer(block, self.out_channels_block[0], layers[0]) + self.conv1 = nn.Conv2D( + self.out_channels_block[0], + self.out_channels_block[0], + kernel_size=3, + stride=1, + padding=1, + bias_attr=False, + ) + self.bn1 = nn.BatchNorm(self.out_channels_block[0]) + + self.maxpool2 = nn.MaxPool2D(kernel_size=2, stride=2, padding=0) + self.layer2 = self._make_layer( + block, self.out_channels_block[1], layers[1], stride=1 + ) + self.conv2 = nn.Conv2D( + self.out_channels_block[1], + self.out_channels_block[1], + kernel_size=3, + stride=1, + padding=1, + bias_attr=False, + ) + self.bn2 = nn.BatchNorm(self.out_channels_block[1]) + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2D( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias_attr=False, + ), + nn.BatchNorm(planes * block.expansion), + ) + + layers = list() + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for _ in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv0_1(x) + x = self.bn0_1(x) + x = self.relu(x) + x = self.conv0_2(x) + x = self.bn0_2(x) + x = self.relu(x) + + x = self.maxpool1(x) + x = self.layer1(x) + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + + x = self.maxpool2(x) + x = self.layer2(x) + x = self.conv2(x) + x = self.bn2(x) + x = self.relu(x) + + return x + + +class RFLBase(nn.Layer): + """Reciprocal feature learning share backbone network""" + + def __init__(self, in_channels, out_channels=512): + super(RFLBase, self).__init__() + self.ConvNet = ResNetBase(in_channels, out_channels, BasicBlock, [1, 2, 5, 3]) + + def forward(self, inputs): + return self.ConvNet(inputs) diff --git a/ppocr/modeling/backbones/rec_resnet_vd.py b/ppocr/modeling/backbones/rec_resnet_vd.py new file mode 100644 index 0000000..343d5ed --- /dev/null +++ b/ppocr/modeling/backbones/rec_resnet_vd.py @@ -0,0 +1,313 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F + +__all__ = ["ResNet"] + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + is_vd_mode=False, + act=None, + name=None, + ): + super(ConvBNLayer, self).__init__() + + self.is_vd_mode = is_vd_mode + self._pool2d_avg = nn.AvgPool2D( + kernel_size=stride, stride=stride, padding=0, ceil_mode=True + ) + self._conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=1 if is_vd_mode else stride, + padding=(kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + if name == "conv1": + bn_name = "bn_" + name + else: + bn_name = "bn" + name[3:] + self._batch_norm = nn.BatchNorm( + out_channels, + act=act, + param_attr=ParamAttr(name=bn_name + "_scale"), + bias_attr=ParamAttr(bn_name + "_offset"), + moving_mean_name=bn_name + "_mean", + moving_variance_name=bn_name + "_variance", + ) + + def forward(self, inputs): + if self.is_vd_mode: + inputs = self._pool2d_avg(inputs) + y = self._conv(inputs) + y = self._batch_norm(y) + return y + + +class BottleneckBlock(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride, + shortcut=True, + if_first=False, + name=None, + ): + super(BottleneckBlock, self).__init__() + + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + act="relu", + name=name + "_branch2a", + ) + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + act="relu", + name=name + "_branch2b", + ) + self.conv2 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels * 4, + kernel_size=1, + act=None, + name=name + "_branch2c", + ) + + if not shortcut: + self.short = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels * 4, + kernel_size=1, + stride=stride, + is_vd_mode=not if_first and stride[0] != 1, + name=name + "_branch1", + ) + + self.shortcut = shortcut + + def forward(self, inputs): + y = self.conv0(inputs) + + conv1 = self.conv1(y) + conv2 = self.conv2(conv1) + + if self.shortcut: + short = inputs + else: + short = self.short(inputs) + y = paddle.add(x=short, y=conv2) + y = F.relu(y) + return y + + +class BasicBlock(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride, + shortcut=True, + if_first=False, + name=None, + ): + super(BasicBlock, self).__init__() + self.stride = stride + self.conv0 = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=3, + stride=stride, + act="relu", + name=name + "_branch2a", + ) + self.conv1 = ConvBNLayer( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, + act=None, + name=name + "_branch2b", + ) + + if not shortcut: + self.short = ConvBNLayer( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=stride, + is_vd_mode=not if_first and stride[0] != 1, + name=name + "_branch1", + ) + + self.shortcut = shortcut + + def forward(self, inputs): + y = self.conv0(inputs) + conv1 = self.conv1(y) + + if self.shortcut: + short = inputs + else: + short = self.short(inputs) + y = paddle.add(x=short, y=conv1) + y = F.relu(y) + return y + + +class ResNet(nn.Layer): + def __init__(self, in_channels=3, layers=50, **kwargs): + super(ResNet, self).__init__() + + self.layers = layers + supported_layers = [18, 34, 50, 101, 152, 200] + assert ( + layers in supported_layers + ), "supported layers are {} but input layer is {}".format( + supported_layers, layers + ) + + if layers == 18: + depth = [2, 2, 2, 2] + elif layers == 34 or layers == 50: + depth = [3, 4, 6, 3] + elif layers == 101: + depth = [3, 4, 23, 3] + elif layers == 152: + depth = [3, 8, 36, 3] + elif layers == 200: + depth = [3, 12, 48, 3] + num_channels = [64, 256, 512, 1024] if layers >= 50 else [64, 64, 128, 256] + num_filters = [64, 128, 256, 512] + + self.conv1_1 = ConvBNLayer( + in_channels=in_channels, + out_channels=32, + kernel_size=3, + stride=1, + act="relu", + name="conv1_1", + ) + self.conv1_2 = ConvBNLayer( + in_channels=32, + out_channels=32, + kernel_size=3, + stride=1, + act="relu", + name="conv1_2", + ) + self.conv1_3 = ConvBNLayer( + in_channels=32, + out_channels=64, + kernel_size=3, + stride=1, + act="relu", + name="conv1_3", + ) + self.pool2d_max = nn.MaxPool2D(kernel_size=3, stride=2, padding=1) + + self.block_list = [] + if layers >= 50: + for block in range(len(depth)): + shortcut = False + for i in range(depth[block]): + if layers in [101, 152, 200] and block == 2: + if i == 0: + conv_name = "res" + str(block + 2) + "a" + else: + conv_name = "res" + str(block + 2) + "b" + str(i) + else: + conv_name = "res" + str(block + 2) + chr(97 + i) + + if i == 0 and block != 0: + stride = (2, 1) + else: + stride = (1, 1) + bottleneck_block = self.add_sublayer( + "bb_%d_%d" % (block, i), + BottleneckBlock( + in_channels=( + num_channels[block] + if i == 0 + else num_filters[block] * 4 + ), + out_channels=num_filters[block], + stride=stride, + shortcut=shortcut, + if_first=block == i == 0, + name=conv_name, + ), + ) + shortcut = True + self.block_list.append(bottleneck_block) + self.out_channels = num_filters[block] * 4 + else: + for block in range(len(depth)): + shortcut = False + for i in range(depth[block]): + conv_name = "res" + str(block + 2) + chr(97 + i) + if i == 0 and block != 0: + stride = (2, 1) + else: + stride = (1, 1) + + basic_block = self.add_sublayer( + "bb_%d_%d" % (block, i), + BasicBlock( + in_channels=( + num_channels[block] if i == 0 else num_filters[block] + ), + out_channels=num_filters[block], + stride=stride, + shortcut=shortcut, + if_first=block == i == 0, + name=conv_name, + ), + ) + shortcut = True + self.block_list.append(basic_block) + self.out_channels = num_filters[block] + self.out_pool = nn.MaxPool2D(kernel_size=2, stride=2, padding=0) + + def forward(self, inputs): + y = self.conv1_1(inputs) + y = self.conv1_2(y) + y = self.conv1_3(y) + y = self.pool2d_max(y) + for block in self.block_list: + y = block(y) + y = self.out_pool(y) + return y diff --git a/ppocr/modeling/backbones/rec_resnetv2.py b/ppocr/modeling/backbones/rec_resnetv2.py new file mode 100644 index 0000000..c121b6f --- /dev/null +++ b/ppocr/modeling/backbones/rec_resnetv2.py @@ -0,0 +1,1227 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is refer from: +https://github.com/huggingface/pytorch-image-models/blob/main/timm/models/resnetv2.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import collections.abc +from itertools import repeat +from collections import OrderedDict # pylint: disable=g-importing-member + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle.nn.initializer import TruncatedNormal, Constant, Normal, KaimingUniform +from functools import partial +from typing import Union, Callable, Type, List, Tuple + +IMAGENET_INCEPTION_MEAN = (0.5, 0.5, 0.5) +IMAGENET_INCEPTION_STD = (0.5, 0.5, 0.5) +normal_ = Normal(mean=0.0, std=0.01) +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) +kaiming_normal_ = KaimingUniform(nonlinearity="relu") + + +def _ntuple(n): + def parse(x): + if isinstance(x, collections.abc.Iterable): + return x + return tuple(repeat(x, n)) + + return parse + + +to_1tuple = _ntuple(1) +to_2tuple = _ntuple(2) +to_3tuple = _ntuple(3) +to_4tuple = _ntuple(4) +to_ntuple = _ntuple + + +class StdConv2dSame(nn.Conv2D): + def __init__( + self, + in_channel, + out_channels, + kernel_size, + stride=1, + padding="SAME", + dilation=1, + groups=1, + bias_attr=False, + eps=1e-6, + is_export=False, + ): + padding, is_dynamic = get_padding_value( + padding, kernel_size, stride=stride, dilation=dilation + ) + super().__init__( + in_channel, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias_attr=bias_attr, + ) + self.same_pad = is_dynamic + self.export = is_export + self.eps = eps + + self.running_mean = paddle.zeros([self._out_channels], dtype="float32") + self.running_variance = paddle.ones([self._out_channels], dtype="float32") + self.batch_norm = paddle.nn.BatchNorm1D( + self._out_channels, use_global_stats=False + ) + + def forward(self, x): + if not self.training: + self.export = True + if self.same_pad: + if self.export: + x = pad_same_export(x, self._kernel_size, self._stride, self._dilation) + else: + x = pad_same(x, self._kernel_size, self._stride, self._dilation) + if self.export: + weight = paddle.reshape( + self.batch_norm( + self.weight.reshape([1, self._out_channels, -1]).cast( + paddle.float32 + ), + ), + self.weight.shape, + ) + else: + weight = paddle.reshape( + F.batch_norm( + self.weight.reshape([1, self._out_channels, -1]), + self.running_mean, + self.running_variance, + training=True, + momentum=0.0, + epsilon=self.eps, + ), + self.weight.shape, + ) + x = F.conv2d( + x, + weight, + self.bias, + self._stride, + self._padding, + self._dilation, + self._groups, + ) + return x + + +class StdConv2d(nn.Conv2D): + """Conv2d with Weight Standardization. Used for BiT ResNet-V2 models. + + Paper: `Micro-Batch Training with Batch-Channel Normalization and Weight Standardization` - + https://arxiv.org/abs/1903.10520v2 + """ + + def __init__( + self, + in_channel, + out_channels, + kernel_size, + stride=1, + padding=None, + dilation=1, + groups=1, + bias=False, + eps=1e-6, + ): + if padding is None: + padding = get_padding(kernel_size, stride, dilation) + super().__init__( + in_channel, + out_channels, + kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + groups=groups, + bias_attr=bias, + ) + self.eps = eps + + def forward(self, x): + weight = F.batch_norm( + self.weight.reshape(1, self.out_channels, -1), + None, + None, + training=True, + momentum=0.0, + epsilon=self.eps, + ).reshape_as(self.weight) + x = F.conv2d( + x, weight, self.bias, self.stride, self.padding, self.dilation, self.groups + ) + return x + + +class MaxPool2dSame(nn.MaxPool2D): + """Tensorflow like 'SAME' wrapper for 2D max pooling""" + + def __init__( + self, + kernel_size: int, + stride=None, + padding=0, + dilation=1, + ceil_mode=False, + is_export=False, + ): + kernel_size = to_2tuple(kernel_size) + stride = to_2tuple(stride) + dilation = to_2tuple(dilation) + self.export = is_export + super(MaxPool2dSame, self).__init__( + kernel_size, stride, (0, 0), dilation, ceil_mode + ) + + def forward(self, x): + if not self.training: + self.export = True + if self.export: + x = pad_same_export(x, self.ksize, self.stride, value=-float("inf")) + else: + x = pad_same(x, self.ksize, self.stride, value=-float("inf")) + return F.max_pool2d(x, self.ksize, self.stride, (0, 0), self.ceil_mode) + + +def get_padding(kernel_size: int, stride: int = 1, dilation: int = 1, **_) -> int: + padding = ((stride - 1) + dilation * (kernel_size - 1)) // 2 + return padding + + +def is_static_pad(kernel_size: int, stride: int = 1, dilation: int = 1, **_): + return stride == 1 and (dilation * (kernel_size - 1)) % 2 == 0 + + +def get_padding_value(padding, kernel_size, **kwargs) -> Tuple[Tuple, bool]: + dynamic = False + if isinstance(padding, str): + # for any string padding, the padding will be calculated for you, one of three ways + padding = padding.lower() + if padding == "same": + # TF compatible 'SAME' padding, has a performance and GPU memory allocation impact + if is_static_pad(kernel_size, **kwargs): + # static case, no extra overhead + padding = get_padding(kernel_size, **kwargs) + else: + # dynamic 'SAME' padding, has runtime/GPU memory overhead + padding = 0 + dynamic = True + elif padding == "valid": + # 'VALID' padding, same as padding=0 + padding = 0 + else: + # Default to PyTorch style 'same'-ish symmetric padding + padding = get_padding(kernel_size, **kwargs) + return padding, dynamic + + +def create_pool2d(pool_type, kernel_size, stride=None, is_export=False, **kwargs): + stride = stride or kernel_size + padding = kwargs.pop("padding", "") + padding, is_dynamic = get_padding_value( + padding, kernel_size, stride=stride, **kwargs + ) + if is_dynamic: + if pool_type == "avg": + return AvgPool2dSame( + kernel_size, stride=stride, is_export=is_export, **kwargs + ) + elif pool_type == "max": + return MaxPool2dSame( + kernel_size, stride=stride, is_export=is_export, **kwargs + ) + else: + assert False, f"Unsupported pool type {pool_type}" + + +def get_same_padding(x, k, s, d): + return max((math.ceil(x / s) - 1) * s + (k - 1) * d + 1 - x, 0) + + +def get_same_padding_export(x, k, s, d): + x = paddle.to_tensor(x) + k = paddle.to_tensor(k) + s = paddle.to_tensor(s) + d = paddle.to_tensor(d) + return paddle.max((paddle.ceil(x / s) - 1) * s + (k - 1) * d + 1 - x, 0) + + +def pad_same_export(x, k, s, d=(1, 1), value=0): + ih, iw = x.shape[-2:] + pad_h, pad_w = get_same_padding_export( + ih, k[0], s[0], d[0] + ), get_same_padding_export(iw, k[1], s[1], d[1]) + pad_h = pad_h.cast(paddle.int32) + pad_w = pad_w.cast(paddle.int32) + pad_list = paddle.to_tensor( + [ + (pad_w // 2), + (pad_w - pad_w // 2).cast(paddle.int32), + (pad_h // 2).cast(paddle.int32), + (pad_h - pad_h // 2).cast(paddle.int32), + ] + ) + + if pad_h > 0 or pad_w > 0: + if len(pad_list.shape) == 2: + pad_list = pad_list.squeeze(1) + x = F.pad(x, pad_list.cast(paddle.int32), value=value) + return x + + +def pad_same(x, k, s, d=(1, 1), value=0, pad_h=None, pad_w=None): + ih, iw = x.shape[-2:] + + pad_h, pad_w = get_same_padding(ih, k[0], s[0], d[0]), get_same_padding( + iw, k[1], s[1], d[1] + ) + if pad_h > 0 or pad_w > 0: + x = F.pad( + x, + [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2], + value=value, + ) + return x + + +class AvgPool2dSame(nn.AvgPool2D): + """Tensorflow like 'SAME' wrapper for 2D average pooling""" + + def __init__( + self, + kernel_size: int, + stride=None, + padding=0, + ceil_mode=False, + count_include_pad=True, + ): + kernel_size = to_2tuple(kernel_size) + stride = to_2tuple(stride) + super(AvgPool2dSame, self).__init__( + kernel_size, stride, (0, 0), ceil_mode, count_include_pad + ) + + def forward(self, x): + x = pad_same(x, self.kernel_size, self.stride) + return F.avg_pool2d( + x, + self.kernel_size, + self.stride, + self.padding, + self.ceil_mode, + self.count_include_pad, + ) + + +def drop_path( + x, drop_prob: float = 0.0, training: bool = False, scale_by_keep: bool = True +): + if drop_prob == 0.0 or not training: + return x + keep_prob = 1 - drop_prob + shape = (x.shape[0],) + (1,) * ( + x.ndim - 1 + ) # work with diff dim tensors, not just 2D ConvNets + random_tensor = x.new_empty(shape).bernoulli_(keep_prob) + if keep_prob > 0.0 and scale_by_keep: + random_tensor.div_(keep_prob) + return x * random_tensor + + +class DropPath(nn.Layer): + """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).""" + + def __init__(self, drop_prob=None, scale_by_keep=True): + super(DropPath, self).__init__() + self.drop_prob = drop_prob + self.scale_by_keep = scale_by_keep + + def forward(self, x): + return drop_path(x, self.drop_prob, self.training, self.scale_by_keep) + + +def adaptive_pool_feat_mult(pool_type="avg"): + if pool_type == "catavgmax": + return 2 + else: + return 1 + + +class SelectAdaptivePool2d(nn.Layer): + """Selectable global pooling layer with dynamic input kernel size""" + + def __init__(self, output_size=1, pool_type="fast", flatten=False): + super(SelectAdaptivePool2d, self).__init__() + self.pool_type = ( + pool_type or "" + ) # convert other falsy values to empty string for consistent TS typing + self.flatten = nn.Flatten(1) if flatten else nn.Identity() + if pool_type == "": + self.pool = nn.Identity() # pass through + + def is_identity(self): + return not self.pool_type + + def forward(self, x): + x = self.pool(x) + x = self.flatten(x) + return x + + def feat_mult(self): + return adaptive_pool_feat_mult(self.pool_type) + + def __repr__(self): + return ( + self.__class__.__name__ + + " (" + + "pool_type=" + + self.pool_type + + ", flatten=" + + str(self.flatten) + + ")" + ) + + +def _create_pool(num_features, num_classes, pool_type="avg", use_conv=False): + flatten_in_pool = not use_conv # flatten when we use a Linear layer after pooling + if not pool_type: + assert ( + num_classes == 0 or use_conv + ), "Pooling can only be disabled if classifier is also removed or conv classifier is used" + flatten_in_pool = ( + False # disable flattening if pooling is pass-through (no pooling) + ) + global_pool = SelectAdaptivePool2d(pool_type=pool_type, flatten=flatten_in_pool) + num_pooled_features = num_features * global_pool.feat_mult() + return global_pool, num_pooled_features + + +def _create_fc(num_features, num_classes, use_conv=False): + if num_classes <= 0: + fc = nn.Identity() # pass-through (no classifier) + elif use_conv: + fc = nn.Conv2D(num_features, num_classes, 1, bias_attr=True) + else: + fc = nn.Linear(num_features, num_classes, bias_attr=True) + return fc + + +class ClassifierHead(nn.Layer): + """Classifier head w/ configurable global pooling and dropout.""" + + def __init__( + self, in_chs, num_classes, pool_type="avg", drop_rate=0.0, use_conv=False + ): + super(ClassifierHead, self).__init__() + self.drop_rate = drop_rate + self.global_pool, num_pooled_features = _create_pool( + in_chs, num_classes, pool_type, use_conv=use_conv + ) + self.fc = _create_fc(num_pooled_features, num_classes, use_conv=use_conv) + self.flatten = nn.Flatten(1) if use_conv and pool_type else nn.Identity() + + def forward(self, x): + x = self.global_pool(x) + if self.drop_rate: + x = F.dropout(x, p=float(self.drop_rate), training=self.training) + x = self.fc(x) + x = self.flatten(x) + return x + + +class EvoNormBatch2d(nn.Layer): + def __init__( + self, num_features, apply_act=True, momentum=0.1, eps=1e-5, drop_block=None + ): + super(EvoNormBatch2d, self).__init__() + self.apply_act = apply_act # apply activation (non-linearity) + self.momentum = momentum + self.eps = eps + self.weight = paddle.create_parameter( + paddle.ones(num_features), dtype="float32" + ) + self.bias = paddle.create_parameter(paddle.zeros(num_features), dtype="float32") + self.v = ( + paddle.create_parameter(paddle.ones(num_features), dtype="float32") + if apply_act + else None + ) + self.register_buffer("running_var", paddle.ones([num_features])) + self.reset_parameters() + + def reset_parameters(self): + ones_(self.weight) + zeros_(self.bias) + if self.apply_act: + ones_(self.v) + + def forward(self, x): + x_type = x.dtype + if self.v is not None: + running_var = self.running_var.view(1, -1, 1, 1) + if self.training: + var = x.var(dim=(0, 2, 3), unbiased=False, keepdim=True) + n = x.numel() / x.shape[1] + running_var = var.detach() * self.momentum * ( + n / (n - 1) + ) + running_var * (1 - self.momentum) + self.running_var.copy_(running_var.view(self.running_var.shape)) + else: + var = running_var + v = self.v.to(dtype=x_type).reshape(1, -1, 1, 1) + d = x * v + ( + x.var(dim=(2, 3), unbiased=False, keepdim=True) + self.eps + ).sqrt().to(dtype=x_type) + d = d.max((var + self.eps).sqrt().to(dtype=x_type)) + x = x / d + return x * self.weight.view(1, -1, 1, 1) + self.bias.view(1, -1, 1, 1) + + +class EvoNormSample2d(nn.Layer): + def __init__( + self, num_features, apply_act=True, groups=32, eps=1e-5, drop_block=None + ): + super(EvoNormSample2d, self).__init__() + self.apply_act = apply_act + self.groups = groups + self.eps = eps + self.weight = paddle.create_parameter( + paddle.ones(num_features), dtype="float32" + ) + self.bias = paddle.create_parameter(paddle.zeros(num_features), dtype="float32") + self.v = ( + paddle.create_parameter(paddle.ones(num_features), dtype="float32") + if apply_act + else None + ) + self.reset_parameters() + + def reset_parameters(self): + ones_(self.weight) + zeros_(self.bias) + if self.apply_act: + ones_(self.v) + + def forward(self, x): + B, C, H, W = x.shape + if self.v is not None: + n = x * (x * self.v.view(1, -1, 1, 1)).sigmoid() + x = x.reshape(B, self.groups, -1) + x = ( + n.reshape(B, self.groups, -1) + / (x.var(dim=-1, unbiased=False, keepdim=True) + self.eps).sqrt() + ) + x = x.reshape(B, C, H, W) + return x * self.weight.reshape([1, -1, 1, 1]) + self.bias.reshape([1, -1, 1, 1]) + + +class GroupNormAct(nn.GroupNorm): + # NOTE num_channel and num_groups order flipped for easier layer swaps / binding of fixed args + def __init__( + self, + num_channels, + num_groups=32, + eps=1e-5, + affine=True, + apply_act=True, + act_layer=nn.ReLU, + drop_block=None, + ): + super(GroupNormAct, self).__init__(num_groups, num_channels, epsilon=eps) + if affine: + self.weight = paddle.create_parameter([num_channels], dtype="float32") + self.bias = paddle.create_parameter([num_channels], dtype="float32") + ones_(self.weight) + zeros_(self.bias) + if act_layer is not None and apply_act: + act_args = {} + self.act = act_layer(**act_args) + else: + self.act = nn.Identity() + + def forward(self, x): + x = F.group_norm( + x, + num_groups=self._num_groups, + epsilon=self._epsilon, + weight=self.weight, + bias=self.bias, + ) + x = self.act(x) + return x + + +class BatchNormAct2d(nn.BatchNorm2D): + def __init__( + self, + num_features, + eps=1e-5, + momentum=0.1, + affine=True, + track_running_stats=True, + apply_act=True, + act_layer=nn.ReLU, + drop_block=None, + ): + super(BatchNormAct2d, self).__init__( + num_features, + epsilon=eps, + momentum=momentum, + use_global_stats=track_running_stats, + ) + if act_layer is not None and apply_act: + act_args = dict() + self.act = act_layer(**act_args) + else: + self.act = nn.Identity() + + def _forward_python(self, x): + return super(BatchNormAct2d, self).forward(x) + + def forward(self, x): + x = self._forward_python(x) + x = self.act(x) + return x + + +def adapt_input_conv(in_chans, conv_weight): + conv_type = conv_weight.dtype + conv_weight = ( + conv_weight.float() + ) # Some weights are in torch.half, ensure it's float for sum on CPU + O, I, J, K = conv_weight.shape + if in_chans == 1: + if I > 3: + assert conv_weight.shape[1] % 3 == 0 + # For models with space2depth stems + conv_weight = conv_weight.reshape(O, I // 3, 3, J, K) + conv_weight = conv_weight.sum(dim=2, keepdim=False) + else: + conv_weight = conv_weight.sum(dim=1, keepdim=True) + elif in_chans != 3: + if I != 3: + raise NotImplementedError("Weight format not supported by conversion.") + else: + # NOTE this strategy should be better than random init, but there could be other combinations of + # the original RGB input layer weights that'd work better for specific cases. + repeat = int(math.ceil(in_chans / 3)) + conv_weight = conv_weight.repeat(1, repeat, 1, 1)[:, :in_chans, :, :] + conv_weight *= 3 / float(in_chans) + conv_weight = conv_weight.to(conv_type) + return conv_weight + + +def named_apply( + fn: Callable, module: nn.Layer, name="", depth_first=True, include_root=False +) -> nn.Layer: + if not depth_first and include_root: + fn(module=module, name=name) + for child_name, child_module in module.named_children(): + child_name = ".".join((name, child_name)) if name else child_name + named_apply( + fn=fn, + module=child_module, + name=child_name, + depth_first=depth_first, + include_root=True, + ) + if depth_first and include_root: + fn(module=module, name=name) + return module + + +def _cfg(url="", **kwargs): + return { + "url": url, + "num_classes": 1000, + "input_size": (3, 224, 224), + "pool_size": (7, 7), + "crop_pct": 0.875, + "interpolation": "bilinear", + "mean": IMAGENET_INCEPTION_MEAN, + "std": IMAGENET_INCEPTION_STD, + "first_conv": "stem.conv", + "classifier": "head.fc", + **kwargs, + } + + +def make_div(v, divisor=8): + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class PreActBottleneck(nn.Layer): + """Pre-activation (v2) bottleneck block. + + Follows the implementation of "Identity Mappings in Deep Residual Networks": + https://github.com/KaimingHe/resnet-1k-layers/blob/master/resnet-pre-act.lua + + Except it puts the stride on 3x3 conv when available. + """ + + def __init__( + self, + in_chs, + out_chs=None, + bottle_ratio=0.25, + stride=1, + dilation=1, + first_dilation=None, + groups=1, + act_layer=None, + conv_layer=None, + norm_layer=None, + proj_layer=None, + drop_path_rate=0.0, + is_export=False, + ): + super().__init__() + first_dilation = first_dilation or dilation + conv_layer = conv_layer or StdConv2d + norm_layer = norm_layer or partial(GroupNormAct, num_groups=32) + out_chs = out_chs or in_chs + mid_chs = make_div(out_chs * bottle_ratio) + + if proj_layer is not None: + self.downsample = proj_layer( + in_chs, + out_chs, + stride=stride, + dilation=dilation, + first_dilation=first_dilation, + preact=True, + conv_layer=conv_layer, + norm_layer=norm_layer, + ) + else: + self.downsample = None + + self.norm1 = norm_layer(in_chs) + self.conv1 = conv_layer(in_chs, mid_chs, 1, is_export=is_export) + self.norm2 = norm_layer(mid_chs) + self.conv2 = conv_layer( + mid_chs, + mid_chs, + 3, + stride=stride, + dilation=first_dilation, + groups=groups, + is_export=is_export, + ) + self.norm3 = norm_layer(mid_chs) + self.conv3 = conv_layer(mid_chs, out_chs, 1, is_export=is_export) + self.drop_path = ( + DropPath(drop_path_rate) if drop_path_rate > 0 else nn.Identity() + ) + + def zero_init_last(self): + zeros_(self.conv3.weight) + + def forward(self, x): + x_preact = self.norm1(x) + + # shortcut branch + shortcut = x + if self.downsample is not None: + shortcut = self.downsample(x_preact) + + # residual branch + x = self.conv1(x_preact) + x = self.conv2(self.norm2(x)) + x = self.conv3(self.norm3(x)) + x = self.drop_path(x) + return x + shortcut + + +class Bottleneck(nn.Layer): + """Non Pre-activation bottleneck block, equiv to V1.5/V1b Bottleneck. Used for ViT.""" + + def __init__( + self, + in_chs, + out_chs=None, + bottle_ratio=0.25, + stride=1, + dilation=1, + first_dilation=None, + groups=1, + act_layer=None, + conv_layer=None, + norm_layer=None, + proj_layer=None, + drop_path_rate=0.0, + is_export=False, + ): + super().__init__() + first_dilation = first_dilation or dilation + act_layer = act_layer or nn.ReLU + conv_layer = conv_layer or StdConv2d + norm_layer = norm_layer or partial(GroupNormAct, num_groups=32) + out_chs = out_chs or in_chs + mid_chs = make_div(out_chs * bottle_ratio) + + if proj_layer is not None: + self.downsample = proj_layer( + in_chs, + out_chs, + stride=stride, + dilation=dilation, + preact=False, + conv_layer=conv_layer, + norm_layer=norm_layer, + is_export=is_export, + ) + else: + self.downsample = None + + self.conv1 = conv_layer(in_chs, mid_chs, 1, is_export=is_export) + self.norm1 = norm_layer(mid_chs) + self.conv2 = conv_layer( + mid_chs, + mid_chs, + 3, + stride=stride, + dilation=first_dilation, + groups=groups, + is_export=is_export, + ) + self.norm2 = norm_layer(mid_chs) + self.conv3 = conv_layer(mid_chs, out_chs, 1, is_export=is_export) + self.norm3 = norm_layer(out_chs, apply_act=False) + self.drop_path = ( + DropPath(drop_path_rate) if drop_path_rate > 0 else nn.Identity() + ) + self.act3 = act_layer() + + def zero_init_last(self): + zeros_(self.norm3.weight) + + def forward(self, x): + # shortcut branch + shortcut = x + if self.downsample is not None: + shortcut = self.downsample(x) + + # residual + x = self.conv1(x) + x = self.norm1(x) + x = self.conv2(x) + x = self.norm2(x) + x = self.conv3(x) + x = self.norm3(x) + x = self.drop_path(x) + x = self.act3(x + shortcut) + return x + + +class DownsampleConv(nn.Layer): + def __init__( + self, + in_chs, + out_chs, + stride=1, + dilation=1, + first_dilation=None, + preact=True, + conv_layer=None, + norm_layer=None, + is_export=False, + ): + super(DownsampleConv, self).__init__() + self.conv = conv_layer(in_chs, out_chs, 1, stride=stride, is_export=is_export) + self.norm = nn.Identity() if preact else norm_layer(out_chs, apply_act=False) + + def forward(self, x): + return self.norm(self.conv(x)) + + +class DownsampleAvg(nn.Layer): + def __init__( + self, + in_chs, + out_chs, + stride=1, + dilation=1, + first_dilation=None, + preact=True, + conv_layer=None, + norm_layer=None, + is_export=False, + ): + """AvgPool Downsampling as in 'D' ResNet variants. This is not in RegNet space but I might experiment.""" + super(DownsampleAvg, self).__init__() + avg_stride = stride if dilation == 1 else 1 + if stride > 1 or dilation > 1: + avg_pool_fn = ( + AvgPool2dSame if avg_stride == 1 and dilation > 1 else nn.AvgPool2D + ) + self.pool = avg_pool_fn(2, avg_stride, ceil_mode=True, exclusive=False) + else: + self.pool = nn.Identity() + self.conv = conv_layer(in_chs, out_chs, 1, stride=1, is_export=is_export) + self.norm = nn.Identity() if preact else norm_layer(out_chs, apply_act=False) + + def forward(self, x): + return self.norm(self.conv(self.pool(x))) + + +class ResNetStage(nn.Layer): + """ResNet Stage.""" + + def __init__( + self, + in_chs, + out_chs, + stride, + dilation, + depth, + bottle_ratio=0.25, + groups=1, + avg_down=False, + block_dpr=None, + block_fn=PreActBottleneck, + is_export=False, + act_layer=None, + conv_layer=None, + norm_layer=None, + **block_kwargs, + ): + super(ResNetStage, self).__init__() + first_dilation = 1 if dilation in (1, 2) else 2 + layer_kwargs = dict( + act_layer=act_layer, conv_layer=conv_layer, norm_layer=norm_layer + ) + proj_layer = DownsampleAvg if avg_down else DownsampleConv + prev_chs = in_chs + self.blocks = nn.Sequential() + for block_idx in range(depth): + drop_path_rate = block_dpr[block_idx] if block_dpr else 0.0 + stride = stride if block_idx == 0 else 1 + self.blocks.add_sublayer( + str(block_idx), + block_fn( + prev_chs, + out_chs, + stride=stride, + dilation=dilation, + bottle_ratio=bottle_ratio, + groups=groups, + first_dilation=first_dilation, + proj_layer=proj_layer, + drop_path_rate=drop_path_rate, + is_export=is_export, + **layer_kwargs, + **block_kwargs, + ), + ) + prev_chs = out_chs + first_dilation = dilation + proj_layer = None + + def forward(self, x): + x = self.blocks(x) + return x + + +def is_stem_deep(stem_type): + return any([s in stem_type for s in ("deep", "tiered")]) + + +def create_resnetv2_stem( + in_chs, + out_chs=64, + stem_type="", + preact=True, + conv_layer=StdConv2d, + norm_layer=partial(GroupNormAct, num_groups=32), + is_export=False, +): + stem = OrderedDict() + assert stem_type in ( + "", + "fixed", + "same", + "deep", + "deep_fixed", + "deep_same", + "tiered", + ) + + # NOTE conv padding mode can be changed by overriding the conv_layer def + if is_stem_deep(stem_type): + # A 3 deep 3x3 conv stack as in ResNet V1D models + if "tiered" in stem_type: + stem_chs = (3 * out_chs // 8, out_chs // 2) # 'T' resnets in resnet.py + else: + stem_chs = (out_chs // 2, out_chs // 2) # 'D' ResNets + stem["conv1"] = conv_layer( + in_chs, stem_chs[0], kernel_size=3, stride=2, is_export=is_export + ) + stem["norm1"] = norm_layer(stem_chs[0]) + stem["conv2"] = conv_layer( + stem_chs[0], stem_chs[1], kernel_size=3, stride=1, is_export=is_export + ) + stem["norm2"] = norm_layer(stem_chs[1]) + stem["conv3"] = conv_layer( + stem_chs[1], out_chs, kernel_size=3, stride=1, is_export=is_export + ) + if not preact: + stem["norm3"] = norm_layer(out_chs) + else: + # The usual 7x7 stem conv + stem["conv"] = conv_layer( + in_chs, out_chs, kernel_size=7, stride=2, is_export=is_export + ) + if not preact: + stem["norm"] = norm_layer(out_chs) + + if "fixed" in stem_type: + # 'fixed' SAME padding approximation that is used in BiT models + stem["pad"] = paddle.nn.Pad2D( + 1, mode="constant", value=0.0, data_format="NCHW", name=None + ) + stem["pool"] = nn.MaxPool2D(kernel_size=3, stride=2, padding=0) + elif "same" in stem_type: + # full, input size based 'SAME' padding, used in ViT Hybrid model + stem["pool"] = create_pool2d( + "max", kernel_size=3, stride=2, padding="same", is_export=is_export + ) + else: + # the usual Pypaddle symmetric padding + stem["pool"] = nn.MaxPool2D(kernel_size=3, stride=2, padding=1) + stem_seq = nn.Sequential() + for key, value in stem.items(): + stem_seq.add_sublayer(key, value) + + return stem_seq + + +class ResNetV2(nn.Layer): + """Implementation of Pre-activation (v2) ResNet mode. + + Args: + x: input images with shape [N, 1, H, W] + + Returns: + The extracted features [N, 1, H//16, W//16] + """ + + def __init__( + self, + layers, + channels=(256, 512, 1024, 2048), + num_classes=1000, + in_chans=3, + global_pool="avg", + output_stride=32, + width_factor=1, + stem_chs=64, + stem_type="", + avg_down=False, + preact=True, + act_layer=nn.ReLU, + conv_layer=StdConv2d, + norm_layer=partial(GroupNormAct, num_groups=32), + drop_rate=0.0, + drop_path_rate=0.0, + zero_init_last=False, + is_export=False, + ): + super().__init__() + self.num_classes = num_classes + self.drop_rate = drop_rate + self.is_export = is_export + wf = width_factor + self.feature_info = [] + stem_chs = make_div(stem_chs * wf) + self.stem = create_resnetv2_stem( + in_chans, + stem_chs, + stem_type, + preact, + conv_layer=conv_layer, + norm_layer=norm_layer, + is_export=is_export, + ) + stem_feat = ( + ("stem.conv3" if is_stem_deep(stem_type) else "stem.conv") + if preact + else "stem.norm" + ) + self.feature_info.append(dict(num_chs=stem_chs, reduction=2, module=stem_feat)) + + prev_chs = stem_chs + curr_stride = 4 + dilation = 1 + block_dprs = [ + x.tolist() + for x in paddle.linspace(0, drop_path_rate, sum(layers)).split(layers) + ] + block_fn = PreActBottleneck if preact else Bottleneck + self.stages = nn.Sequential() + for stage_idx, (d, c, bdpr) in enumerate(zip(layers, channels, block_dprs)): + out_chs = make_div(c * wf) + stride = 1 if stage_idx == 0 else 2 + if curr_stride >= output_stride: + dilation *= stride + stride = 1 + stage = ResNetStage( + prev_chs, + out_chs, + stride=stride, + dilation=dilation, + depth=d, + avg_down=avg_down, + act_layer=act_layer, + conv_layer=conv_layer, + norm_layer=norm_layer, + block_dpr=bdpr, + block_fn=block_fn, + is_export=is_export, + ) + prev_chs = out_chs + curr_stride *= stride + self.feature_info += [ + dict( + num_chs=prev_chs, + reduction=curr_stride, + module=f"stages.{stage_idx}", + ) + ] + self.stages.add_sublayer(str(stage_idx), stage) + + self.num_features = prev_chs + self.norm = norm_layer(self.num_features) if preact else nn.Identity() + self.head = ClassifierHead( + self.num_features, + num_classes, + pool_type=global_pool, + drop_rate=self.drop_rate, + use_conv=True, + ) + + self.init_weights(zero_init_last=zero_init_last) + + def init_weights(self, zero_init_last=True): + named_apply(partial(_init_weights, zero_init_last=zero_init_last), self) + + def load_pretrained(self, checkpoint_path, prefix="resnet/"): + _load_weights(self, checkpoint_path, prefix) + + def get_classifier(self): + return self.head.fc + + def reset_classifier(self, num_classes, global_pool="avg"): + self.num_classes = num_classes + self.head = ClassifierHead( + self.num_features, + num_classes, + pool_type=global_pool, + drop_rate=self.drop_rate, + use_conv=True, + ) + + def forward_features(self, x): + x = self.stem(x) + x = self.stages(x) + x = self.norm(x) + return x + + def forward(self, x): + x = self.forward_features(x) + x = self.head(x) + return x + + +def _init_weights(module: nn.Layer, name: str = "", zero_init_last=True): + if isinstance(module, nn.Linear) or ( + "head.fc" in name and isinstance(module, nn.Conv2D) + ): + normal_(module.weight) + zeros_(module.bias) + elif isinstance(module, nn.Conv2D): + kaiming_normal_(module.weight) + if module.bias is not None: + zeros_(module.bias) + elif isinstance(module, (nn.BatchNorm2D, nn.LayerNorm, nn.GroupNorm)): + ones_(module.weight) + zeros_(module.bias) + elif zero_init_last and hasattr(module, "zero_init_last"): + module.zero_init_last() + + +@paddle.no_grad() +def _load_weights(model: nn.Layer, checkpoint_path: str, prefix: str = "resnet/"): + import numpy as np + + def t2p(conv_weights): + """Possibly convert HWIO to OIHW.""" + if conv_weights.ndim == 4: + conv_weights = conv_weights.transpose([3, 2, 0, 1]) + return paddle.to_tensor(conv_weights) + + weights = np.load(checkpoint_path) + stem_conv_w = adapt_input_conv( + model.stem.conv.weight.shape[1], + t2p(weights[f"{prefix}root_block/standardized_conv2d/kernel"]), + ) + model.stem.conv.weight.copy_(stem_conv_w) + model.norm.weight.copy_(t2p(weights[f"{prefix}group_norm/gamma"])) + model.norm.bias.copy_(t2p(weights[f"{prefix}group_norm/beta"])) + if ( + isinstance(getattr(model.head, "fc", None), nn.Conv2D) + and model.head.fc.weight.shape[0] + == weights[f"{prefix}head/conv2d/kernel"].shape[-1] + ): + model.head.fc.weight.copy_(t2p(weights[f"{prefix}head/conv2d/kernel"])) + model.head.fc.bias.copy_(t2p(weights[f"{prefix}head/conv2d/bias"])) + for i, (sname, stage) in enumerate(model.stages.named_children()): + for j, (bname, block) in enumerate(stage.blocks.named_children()): + cname = "standardized_conv2d" + block_prefix = f"{prefix}block{i + 1}/unit{j + 1:02d}/" + block.conv1.weight.copy_(t2p(weights[f"{block_prefix}a/{cname}/kernel"])) + block.conv2.weight.copy_(t2p(weights[f"{block_prefix}b/{cname}/kernel"])) + block.conv3.weight.copy_(t2p(weights[f"{block_prefix}c/{cname}/kernel"])) + block.norm1.weight.copy_(t2p(weights[f"{block_prefix}a/group_norm/gamma"])) + block.norm2.weight.copy_(t2p(weights[f"{block_prefix}b/group_norm/gamma"])) + block.norm3.weight.copy_(t2p(weights[f"{block_prefix}c/group_norm/gamma"])) + block.norm1.bias.copy_(t2p(weights[f"{block_prefix}a/group_norm/beta"])) + block.norm2.bias.copy_(t2p(weights[f"{block_prefix}b/group_norm/beta"])) + block.norm3.bias.copy_(t2p(weights[f"{block_prefix}c/group_norm/beta"])) + if block.downsample is not None: + w = weights[f"{block_prefix}a/proj/{cname}/kernel"] + block.downsample.conv.weight.copy_(t2p(w)) diff --git a/ppocr/modeling/backbones/rec_shallow_cnn.py b/ppocr/modeling/backbones/rec_shallow_cnn.py new file mode 100644 index 0000000..e5a8b65 --- /dev/null +++ b/ppocr/modeling/backbones/rec_shallow_cnn.py @@ -0,0 +1,82 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/1.x/mmocr/models/textrecog/backbones/shallow_cnn.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import numpy as np +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F +from paddle.nn import MaxPool2D +from paddle.nn.initializer import KaimingNormal, Uniform, Constant + + +class ConvBNLayer(nn.Layer): + def __init__( + self, num_channels, filter_size, num_filters, stride, padding, num_groups=1 + ): + super(ConvBNLayer, self).__init__() + + self.conv = nn.Conv2D( + in_channels=num_channels, + out_channels=num_filters, + kernel_size=filter_size, + stride=stride, + padding=padding, + groups=num_groups, + weight_attr=ParamAttr(initializer=KaimingNormal()), + bias_attr=False, + ) + + self.bn = nn.BatchNorm2D( + num_filters, + weight_attr=ParamAttr(initializer=Uniform(0, 1)), + bias_attr=ParamAttr(initializer=Constant(0)), + ) + self.relu = nn.ReLU() + + def forward(self, inputs): + y = self.conv(inputs) + y = self.bn(y) + y = self.relu(y) + return y + + +class ShallowCNN(nn.Layer): + def __init__(self, in_channels=1, hidden_dim=512): + super().__init__() + assert isinstance(in_channels, int) + assert isinstance(hidden_dim, int) + + self.conv1 = ConvBNLayer(in_channels, 3, hidden_dim // 2, stride=1, padding=1) + self.conv2 = ConvBNLayer(hidden_dim // 2, 3, hidden_dim, stride=1, padding=1) + self.pool = nn.MaxPool2D(kernel_size=2, stride=2, padding=0) + self.out_channels = hidden_dim + + def forward(self, x): + x = self.conv1(x) + x = self.pool(x) + + x = self.conv2(x) + x = self.pool(x) + + return x diff --git a/ppocr/modeling/backbones/rec_svtrnet.py b/ppocr/modeling/backbones/rec_svtrnet.py new file mode 100644 index 0000000..427c87b --- /dev/null +++ b/ppocr/modeling/backbones/rec_svtrnet.py @@ -0,0 +1,642 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle import ParamAttr +from paddle.nn.initializer import KaimingNormal +import numpy as np +import paddle +import paddle.nn as nn +from paddle.nn.initializer import TruncatedNormal, Constant, Normal + +trunc_normal_ = TruncatedNormal(std=0.02) +normal_ = Normal +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) + + +def drop_path(x, drop_prob=0.0, training=False): + """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). + the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper... + See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... + """ + if drop_prob == 0.0 or not training: + return x + keep_prob = paddle.to_tensor(1 - drop_prob, dtype=x.dtype) + shape = (x.shape[0],) + (1,) * (x.ndim - 1) + random_tensor = keep_prob + paddle.rand(shape, dtype=x.dtype) + random_tensor = paddle.floor(random_tensor) # binarize + output = x.divide(keep_prob) * random_tensor + return output + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=0, + bias_attr=False, + groups=1, + act=nn.GELU, + ): + super().__init__() + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + groups=groups, + weight_attr=paddle.ParamAttr(initializer=nn.initializer.KaimingUniform()), + bias_attr=bias_attr, + ) + self.norm = nn.BatchNorm2D(out_channels) + self.act = act() + + def forward(self, inputs): + out = self.conv(inputs) + out = self.norm(out) + out = self.act(out) + return out + + +class DropPath(nn.Layer): + """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).""" + + def __init__(self, drop_prob=None): + super(DropPath, self).__init__() + self.drop_prob = drop_prob + + def forward(self, x): + return drop_path(x, self.drop_prob, self.training) + + +class Identity(nn.Layer): + def __init__(self): + super(Identity, self).__init__() + + def forward(self, input): + return input + + +class Mlp(nn.Layer): + def __init__( + self, + in_features, + hidden_features=None, + out_features=None, + act_layer=nn.GELU, + drop=0.0, + ): + super().__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + self.fc1 = nn.Linear(in_features, hidden_features) + self.act = act_layer() + self.fc2 = nn.Linear(hidden_features, out_features) + self.drop = nn.Dropout(drop) + + def forward(self, x): + x = self.fc1(x) + x = self.act(x) + x = self.drop(x) + x = self.fc2(x) + x = self.drop(x) + return x + + +class ConvMixer(nn.Layer): + def __init__( + self, + dim, + num_heads=8, + HW=[8, 25], + local_k=[3, 3], + ): + super().__init__() + self.HW = HW + self.dim = dim + self.local_mixer = nn.Conv2D( + dim, + dim, + local_k, + 1, + [local_k[0] // 2, local_k[1] // 2], + groups=num_heads, + weight_attr=ParamAttr(initializer=KaimingNormal()), + ) + + def forward(self, x): + h = self.HW[0] + w = self.HW[1] + x = x.transpose([0, 2, 1]).reshape([0, self.dim, h, w]) + x = self.local_mixer(x) + x = x.flatten(2).transpose([0, 2, 1]) + return x + + +class Attention(nn.Layer): + def __init__( + self, + dim, + num_heads=8, + mixer="Global", + HW=None, + local_k=[7, 11], + qkv_bias=False, + qk_scale=None, + attn_drop=0.0, + proj_drop=0.0, + ): + super().__init__() + self.num_heads = num_heads + self.dim = dim + self.head_dim = dim // num_heads + self.scale = qk_scale or self.head_dim**-0.5 + + self.qkv = nn.Linear(dim, dim * 3, bias_attr=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop) + self.proj = nn.Linear(dim, dim) + self.proj_drop = nn.Dropout(proj_drop) + self.HW = HW + if HW is not None: + H = HW[0] + W = HW[1] + self.N = H * W + self.C = dim + if mixer == "Local" and HW is not None: + hk = local_k[0] + wk = local_k[1] + mask = paddle.ones([H * W, H + hk - 1, W + wk - 1], dtype="float32") + for h in range(0, H): + for w in range(0, W): + mask[h * W + w, h : h + hk, w : w + wk] = 0.0 + mask_paddle = mask[:, hk // 2 : H + hk // 2, wk // 2 : W + wk // 2].flatten( + 1 + ) + mask_inf = paddle.full([H * W, H * W], "-inf", dtype="float32") + mask = paddle.where(mask_paddle < 1, mask_paddle, mask_inf) + self.mask = mask.unsqueeze([0, 1]) + self.mixer = mixer + + def forward(self, x): + qkv = ( + self.qkv(x) + .reshape((0, -1, 3, self.num_heads, self.head_dim)) + .transpose((2, 0, 3, 1, 4)) + ) + q, k, v = qkv[0] * self.scale, qkv[1], qkv[2] + + attn = q.matmul(k.transpose((0, 1, 3, 2))) + if self.mixer == "Local": + attn += self.mask + attn = nn.functional.softmax(attn, axis=-1) + attn = self.attn_drop(attn) + + x = (attn.matmul(v)).transpose((0, 2, 1, 3)).reshape((0, -1, self.dim)) + x = self.proj(x) + x = self.proj_drop(x) + return x + + +class Block(nn.Layer): + def __init__( + self, + dim, + num_heads, + mixer="Global", + local_mixer=[7, 11], + HW=None, + mlp_ratio=4.0, + qkv_bias=False, + qk_scale=None, + drop=0.0, + attn_drop=0.0, + drop_path=0.0, + act_layer=nn.GELU, + norm_layer="nn.LayerNorm", + epsilon=1e-6, + prenorm=True, + ): + super().__init__() + if isinstance(norm_layer, str): + self.norm1 = eval(norm_layer)(dim, epsilon=epsilon) + else: + self.norm1 = norm_layer(dim) + if mixer == "Global" or mixer == "Local": + self.mixer = Attention( + dim, + num_heads=num_heads, + mixer=mixer, + HW=HW, + local_k=local_mixer, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop=attn_drop, + proj_drop=drop, + ) + elif mixer == "Conv": + self.mixer = ConvMixer(dim, num_heads=num_heads, HW=HW, local_k=local_mixer) + else: + raise TypeError("The mixer must be one of [Global, Local, Conv]") + + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else Identity() + if isinstance(norm_layer, str): + self.norm2 = eval(norm_layer)(dim, epsilon=epsilon) + else: + self.norm2 = norm_layer(dim) + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp_ratio = mlp_ratio + self.mlp = Mlp( + in_features=dim, + hidden_features=mlp_hidden_dim, + act_layer=act_layer, + drop=drop, + ) + self.prenorm = prenorm + + def forward(self, x): + if self.prenorm: + x = self.norm1(x + self.drop_path(self.mixer(x))) + x = self.norm2(x + self.drop_path(self.mlp(x))) + else: + x = x + self.drop_path(self.mixer(self.norm1(x))) + x = x + self.drop_path(self.mlp(self.norm2(x))) + return x + + +class PatchEmbed(nn.Layer): + """Image to Patch Embedding""" + + def __init__( + self, + img_size=[32, 100], + in_channels=3, + embed_dim=768, + sub_num=2, + patch_size=[4, 4], + mode="pope", + ): + super().__init__() + num_patches = (img_size[1] // (2**sub_num)) * (img_size[0] // (2**sub_num)) + self.img_size = img_size + self.num_patches = num_patches + self.embed_dim = embed_dim + self.norm = None + if mode == "pope": + if sub_num == 2: + self.proj = nn.Sequential( + ConvBNLayer( + in_channels=in_channels, + out_channels=embed_dim // 2, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None, + ), + ConvBNLayer( + in_channels=embed_dim // 2, + out_channels=embed_dim, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None, + ), + ) + if sub_num == 3: + self.proj = nn.Sequential( + ConvBNLayer( + in_channels=in_channels, + out_channels=embed_dim // 4, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None, + ), + ConvBNLayer( + in_channels=embed_dim // 4, + out_channels=embed_dim // 2, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None, + ), + ConvBNLayer( + in_channels=embed_dim // 2, + out_channels=embed_dim, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None, + ), + ) + elif mode == "linear": + self.proj = nn.Conv2D( + 1, embed_dim, kernel_size=patch_size, stride=patch_size + ) + self.num_patches = ( + img_size[0] // patch_size[0] * img_size[1] // patch_size[1] + ) + + def forward(self, x): + B, C, H, W = x.shape + assert ( + H == self.img_size[0] and W == self.img_size[1] + ), f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})." + x = self.proj(x).flatten(2).transpose((0, 2, 1)) + return x + + +class SubSample(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + types="Pool", + stride=[2, 1], + sub_norm="nn.LayerNorm", + act=None, + ): + super().__init__() + self.types = types + if types == "Pool": + self.avgpool = nn.AvgPool2D( + kernel_size=[3, 5], stride=stride, padding=[1, 2] + ) + self.maxpool = nn.MaxPool2D( + kernel_size=[3, 5], stride=stride, padding=[1, 2] + ) + self.proj = nn.Linear(in_channels, out_channels) + else: + self.conv = nn.Conv2D( + in_channels, + out_channels, + kernel_size=3, + stride=stride, + padding=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + ) + self.norm = eval(sub_norm)(out_channels) + if act is not None: + self.act = act() + else: + self.act = None + + def forward(self, x): + if self.types == "Pool": + x1 = self.avgpool(x) + x2 = self.maxpool(x) + x = (x1 + x2) * 0.5 + out = self.proj(x.flatten(2).transpose((0, 2, 1))) + else: + x = self.conv(x) + out = x.flatten(2).transpose((0, 2, 1)) + out = self.norm(out) + if self.act is not None: + out = self.act(out) + + return out + + +class SVTRNet(nn.Layer): + def __init__( + self, + img_size=[32, 100], + in_channels=3, + embed_dim=[64, 128, 256], + depth=[3, 6, 3], + num_heads=[2, 4, 8], + mixer=["Local"] * 6 + ["Global"] * 6, # Local atten, Global atten, Conv + local_mixer=[[7, 11], [7, 11], [7, 11]], + patch_merging="Conv", # Conv, Pool, None + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0.0, + last_drop=0.1, + attn_drop_rate=0.0, + drop_path_rate=0.1, + norm_layer="nn.LayerNorm", + sub_norm="nn.LayerNorm", + epsilon=1e-6, + out_channels=192, + out_char_num=25, + block_unit="Block", + act="nn.GELU", + last_stage=True, + sub_num=2, + prenorm=True, + use_lenhead=False, + **kwargs, + ): + super().__init__() + self.img_size = img_size + self.embed_dim = embed_dim + self.out_channels = out_channels + self.prenorm = prenorm + patch_merging = ( + None + if patch_merging != "Conv" and patch_merging != "Pool" + else patch_merging + ) + self.patch_embed = PatchEmbed( + img_size=img_size, + in_channels=in_channels, + embed_dim=embed_dim[0], + sub_num=sub_num, + ) + num_patches = self.patch_embed.num_patches + self.HW = [img_size[0] // (2**sub_num), img_size[1] // (2**sub_num)] + self.pos_embed = self.create_parameter( + shape=[1, num_patches, embed_dim[0]], default_initializer=zeros_ + ) + self.add_parameter("pos_embed", self.pos_embed) + self.pos_drop = nn.Dropout(p=drop_rate) + Block_unit = eval(block_unit) + + dpr = np.linspace(0, drop_path_rate, sum(depth)) + self.blocks1 = nn.LayerList( + [ + Block_unit( + dim=embed_dim[0], + num_heads=num_heads[0], + mixer=mixer[0 : depth[0]][i], + HW=self.HW, + local_mixer=local_mixer[0], + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + act_layer=eval(act), + attn_drop=attn_drop_rate, + drop_path=dpr[0 : depth[0]][i], + norm_layer=norm_layer, + epsilon=epsilon, + prenorm=prenorm, + ) + for i in range(depth[0]) + ] + ) + if patch_merging is not None: + self.sub_sample1 = SubSample( + embed_dim[0], + embed_dim[1], + sub_norm=sub_norm, + stride=[2, 1], + types=patch_merging, + ) + HW = [self.HW[0] // 2, self.HW[1]] + else: + HW = self.HW + self.patch_merging = patch_merging + self.blocks2 = nn.LayerList( + [ + Block_unit( + dim=embed_dim[1], + num_heads=num_heads[1], + mixer=mixer[depth[0] : depth[0] + depth[1]][i], + HW=HW, + local_mixer=local_mixer[1], + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + act_layer=eval(act), + attn_drop=attn_drop_rate, + drop_path=dpr[depth[0] : depth[0] + depth[1]][i], + norm_layer=norm_layer, + epsilon=epsilon, + prenorm=prenorm, + ) + for i in range(depth[1]) + ] + ) + if patch_merging is not None: + self.sub_sample2 = SubSample( + embed_dim[1], + embed_dim[2], + sub_norm=sub_norm, + stride=[2, 1], + types=patch_merging, + ) + HW = [self.HW[0] // 4, self.HW[1]] + else: + HW = self.HW + self.blocks3 = nn.LayerList( + [ + Block_unit( + dim=embed_dim[2], + num_heads=num_heads[2], + mixer=mixer[depth[0] + depth[1] :][i], + HW=HW, + local_mixer=local_mixer[2], + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + act_layer=eval(act), + attn_drop=attn_drop_rate, + drop_path=dpr[depth[0] + depth[1] :][i], + norm_layer=norm_layer, + epsilon=epsilon, + prenorm=prenorm, + ) + for i in range(depth[2]) + ] + ) + self.last_stage = last_stage + if last_stage: + self.avg_pool = nn.AdaptiveAvgPool2D([1, out_char_num]) + self.last_conv = nn.Conv2D( + in_channels=embed_dim[2], + out_channels=self.out_channels, + kernel_size=1, + stride=1, + padding=0, + bias_attr=False, + ) + self.hardswish = nn.Hardswish() + self.dropout = nn.Dropout(p=last_drop, mode="downscale_in_infer") + if not prenorm: + self.norm = eval(norm_layer)(embed_dim[-1], epsilon=epsilon) + self.use_lenhead = use_lenhead + if use_lenhead: + self.len_conv = nn.Linear(embed_dim[2], self.out_channels) + self.hardswish_len = nn.Hardswish() + self.dropout_len = nn.Dropout(p=last_drop, mode="downscale_in_infer") + + trunc_normal_(self.pos_embed) + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight) + if isinstance(m, nn.Linear) and m.bias is not None: + zeros_(m.bias) + elif isinstance(m, nn.LayerNorm): + zeros_(m.bias) + ones_(m.weight) + + def forward_features(self, x): + x = self.patch_embed(x) + x = x + self.pos_embed + x = self.pos_drop(x) + for blk in self.blocks1: + x = blk(x) + if self.patch_merging is not None: + x = self.sub_sample1( + x.transpose([0, 2, 1]).reshape( + [0, self.embed_dim[0], self.HW[0], self.HW[1]] + ) + ) + for blk in self.blocks2: + x = blk(x) + if self.patch_merging is not None: + x = self.sub_sample2( + x.transpose([0, 2, 1]).reshape( + [0, self.embed_dim[1], self.HW[0] // 2, self.HW[1]] + ) + ) + for blk in self.blocks3: + x = blk(x) + if not self.prenorm: + x = self.norm(x) + return x + + def forward(self, x): + x = self.forward_features(x) + if self.use_lenhead: + len_x = self.len_conv(x.mean(1)) + len_x = self.dropout_len(self.hardswish_len(len_x)) + if self.last_stage: + if self.patch_merging is not None: + h = self.HW[0] // 4 + else: + h = self.HW[0] + x = self.avg_pool( + x.transpose([0, 2, 1]).reshape([0, self.embed_dim[2], h, self.HW[1]]) + ) + x = self.last_conv(x) + x = self.hardswish(x) + x = self.dropout(x) + if self.use_lenhead: + return x, len_x + return x diff --git a/ppocr/modeling/backbones/rec_svtrv2.py b/ppocr/modeling/backbones/rec_svtrv2.py new file mode 100644 index 0000000..03281b3 --- /dev/null +++ b/ppocr/modeling/backbones/rec_svtrv2.py @@ -0,0 +1,575 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle import ParamAttr +from paddle.nn.initializer import KaimingNormal +import numpy as np +import paddle +import paddle.nn as nn +from paddle.nn.initializer import TruncatedNormal, Constant, Normal + +trunc_normal_ = TruncatedNormal(std=0.02) +normal_ = Normal +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) + + +def drop_path(x, drop_prob=0.0, training=False): + """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). + the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper... + See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... + """ + if drop_prob == 0.0 or not training: + return x + keep_prob = paddle.to_tensor(1 - drop_prob, dtype=x.dtype) + shape = (paddle.shape(x)[0],) + (1,) * (x.ndim - 1) + random_tensor = keep_prob + paddle.rand(shape, dtype=x.dtype) + random_tensor = paddle.floor(random_tensor) # binarize + output = x.divide(keep_prob) * random_tensor + return output + + +class DropPath(nn.Layer): + """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).""" + + def __init__(self, drop_prob=None): + super(DropPath, self).__init__() + self.drop_prob = drop_prob + + def forward(self, x): + return drop_path(x, self.drop_prob, self.training) + + +class Identity(nn.Layer): + def __init__(self): + super(Identity, self).__init__() + + def forward(self, input): + return input + + +class Mlp(nn.Layer): + def __init__( + self, + in_features, + hidden_features=None, + out_features=None, + act_layer=nn.GELU, + drop=0.0, + ): + super().__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + self.fc1 = nn.Linear(in_features, hidden_features) + self.act = act_layer() + self.fc2 = nn.Linear(hidden_features, out_features) + self.drop = nn.Dropout(drop) + + def forward(self, x): + x = self.fc1(x) + x = self.act(x) + x = self.drop(x) + x = self.fc2(x) + x = self.drop(x) + return x + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size=3, + stride=1, + padding=0, + bias_attr=False, + groups=1, + act=nn.GELU, + ): + super().__init__() + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + groups=groups, + weight_attr=paddle.ParamAttr(initializer=nn.initializer.KaimingUniform()), + bias_attr=bias_attr, + ) + self.norm = nn.BatchNorm2D(out_channels) + self.act = act() + + def forward(self, inputs): + out = self.conv(inputs) + out = self.norm(out) + out = self.act(out) + return out + + +class Attention(nn.Layer): + def __init__( + self, + dim, + num_heads=8, + qkv_bias=False, + qk_scale=None, + attn_drop=0.0, + proj_drop=0.0, + ): + super().__init__() + self.num_heads = num_heads + self.dim = dim + self.head_dim = dim // num_heads + self.scale = qk_scale or self.head_dim**-0.5 + + self.qkv = nn.Linear(dim, dim * 3, bias_attr=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop) + self.proj = nn.Linear(dim, dim) + self.proj_drop = nn.Dropout(proj_drop) + + def forward(self, x): + qkv = ( + self.qkv(x) + .reshape((0, -1, 3, self.num_heads, self.head_dim)) + .transpose((2, 0, 3, 1, 4)) + ) + q, k, v = qkv[0], qkv[1], qkv[2] + + attn = (q.matmul(k.transpose((0, 1, 3, 2)))) * self.scale + attn = nn.functional.softmax(attn, axis=-1) + attn = self.attn_drop(attn) + x = (attn.matmul(v)).transpose((0, 2, 1, 3)).reshape((0, -1, self.dim)) + x = self.proj(x) + x = self.proj_drop(x) + return x + + +class Block(nn.Layer): + def __init__( + self, + dim, + num_heads, + mlp_ratio=4.0, + qkv_bias=False, + qk_scale=None, + drop=0.0, + attn_drop=0.0, + drop_path=0.0, + act_layer=nn.GELU, + norm_layer=nn.LayerNorm, + epsilon=1e-6, + ): + super().__init__() + self.norm1 = norm_layer(dim, epsilon=epsilon) + self.mixer = Attention( + dim, + num_heads=num_heads, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop=attn_drop, + proj_drop=drop, + ) + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else Identity() + self.norm2 = norm_layer(dim, epsilon=epsilon) + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp_ratio = mlp_ratio + self.mlp = Mlp( + in_features=dim, + hidden_features=mlp_hidden_dim, + act_layer=act_layer, + drop=drop, + ) + + def forward(self, x): + x = self.norm1(x + self.drop_path(self.mixer(x))) + x = self.norm2(x + self.drop_path(self.mlp(x))) + return x + + +class ConvBlock(nn.Layer): + def __init__( + self, + dim, + num_heads, + mlp_ratio=4.0, + drop=0.0, + drop_path=0.0, + act_layer=nn.GELU, + norm_layer=nn.LayerNorm, + epsilon=1e-6, + ): + super().__init__() + mlp_hidden_dim = int(dim * mlp_ratio) + self.norm1 = norm_layer(dim, epsilon=epsilon) + self.mixer = nn.Conv2D( + dim, + dim, + 5, + 1, + 2, + groups=num_heads, + weight_attr=ParamAttr(initializer=KaimingNormal()), + ) + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else Identity() + self.norm2 = norm_layer(dim, epsilon=epsilon) + self.mlp = Mlp( + in_features=dim, + hidden_features=mlp_hidden_dim, + act_layer=act_layer, + drop=drop, + ) + + def forward(self, x): + C, H, W = x.shape[1:] + x = x + self.drop_path(self.mixer(x)) + x = self.norm1(x.flatten(2).transpose([0, 2, 1])) + x = self.norm2(x + self.drop_path(self.mlp(x))) + x = x.transpose([0, 2, 1]).reshape([0, C, H, W]) + return x + + +class FlattenTranspose(nn.Layer): + def forward(self, x): + return x.flatten(2).transpose([0, 2, 1]) + + +class SubSample2D(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride=[2, 1], + ): + super().__init__() + self.conv = nn.Conv2D( + in_channels, + out_channels, + kernel_size=3, + stride=stride, + padding=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + ) + self.norm = nn.LayerNorm(out_channels) + + def forward(self, x, sz): + # print(x.shape) + x = self.conv(x) + C, H, W = x.shape[1:] + x = self.norm(x.flatten(2).transpose([0, 2, 1])) + x = x.transpose([0, 2, 1]).reshape([0, C, H, W]) + return x, [H, W] + + +class SubSample1D(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + stride=[2, 1], + ): + super().__init__() + self.conv = nn.Conv2D( + in_channels, + out_channels, + kernel_size=3, + stride=stride, + padding=1, + weight_attr=ParamAttr(initializer=KaimingNormal()), + ) + self.norm = nn.LayerNorm(out_channels) + + def forward(self, x, sz): + C = x.shape[-1] + x = x.transpose([0, 2, 1]).reshape([0, C, sz[0], sz[1]]) + x = self.conv(x) + C, H, W = x.shape[1:] + x = self.norm(x.flatten(2).transpose([0, 2, 1])) + return x, [H, W] + + +class IdentitySize(nn.Layer): + def forward(self, x, sz): + return x, sz + + +class SVTRStage(nn.Layer): + def __init__( + self, + dim=64, + out_dim=256, + depth=3, + mixer=["Local"] * 3, + sub_k=[2, 1], + num_heads=2, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0.0, + attn_drop_rate=0.0, + drop_path=[0.1] * 3, + norm_layer=nn.LayerNorm, + act=nn.GELU, + eps=1e-6, + downsample=None, + **kwargs, + ): + super().__init__() + self.dim = dim + + conv_block_num = sum([1 if mix == "Conv" else 0 for mix in mixer]) + blocks = [] + for i in range(depth): + if mixer[i] == "Conv": + blocks.append( + ConvBlock( + dim=dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + drop=drop_rate, + act_layer=act, + drop_path=drop_path[i], + norm_layer=norm_layer, + epsilon=eps, + ) + ) + else: + blocks.append( + Block( + dim=dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + act_layer=act, + attn_drop=attn_drop_rate, + drop_path=drop_path[i], + norm_layer=norm_layer, + epsilon=eps, + ) + ) + if i == conv_block_num - 1 and mixer[-1] != "Conv": + blocks.append(FlattenTranspose()) + self.blocks = nn.Sequential(*blocks) + if downsample: + if mixer[-1] == "Conv": + self.downsample = SubSample2D(dim, out_dim, stride=sub_k) + elif mixer[-1] == "Global": + self.downsample = SubSample1D(dim, out_dim, stride=sub_k) + else: + self.downsample = IdentitySize() + + def forward(self, x, sz): + x = self.blocks(x) + x, sz = self.downsample(x, sz) + return x, sz + + +class ADDPosEmbed(nn.Layer): + def __init__(self, feat_max_size=[8, 32], embed_dim=768): + super().__init__() + pos_embed = paddle.zeros( + [1, feat_max_size[0] * feat_max_size[1], embed_dim], dtype=paddle.float32 + ) + trunc_normal_(pos_embed) + pos_embed = pos_embed.transpose([0, 2, 1]).reshape( + [1, embed_dim, feat_max_size[0], feat_max_size[1]] + ) + self.pos_embed = self.create_parameter( + [1, embed_dim, feat_max_size[0], feat_max_size[1]] + ) + self.add_parameter("pos_embed", self.pos_embed) + self.pos_embed.set_value(pos_embed) + + def forward(self, x): + sz = x.shape[2:] + x = x + self.pos_embed[:, :, : sz[0], : sz[1]] + return x + + +class POPatchEmbed(nn.Layer): + """Image to Patch Embedding""" + + def __init__( + self, + in_channels=3, + feat_max_size=[8, 32], + embed_dim=768, + use_pos_embed=False, + flatten=False, + ): + super().__init__() + patch_embed = [ + ConvBNLayer( + in_channels=in_channels, + out_channels=embed_dim // 2, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None, + ), + ConvBNLayer( + in_channels=embed_dim // 2, + out_channels=embed_dim, + kernel_size=3, + stride=2, + padding=1, + act=nn.GELU, + bias_attr=None, + ), + ] + if use_pos_embed: + patch_embed.append(ADDPosEmbed(feat_max_size, embed_dim)) + if flatten: + patch_embed.append(FlattenTranspose()) + self.patch_embed = nn.Sequential(*patch_embed) + + def forward(self, x): + sz = x.shape[2:] + x = self.patch_embed(x) + return x, [sz[0] // 4, sz[1] // 4] + + +class LastStage(nn.Layer): + def __init__(self, in_channels, out_channels, last_drop, out_char_num): + super().__init__() + self.last_conv = nn.Linear(in_channels, out_channels, bias_attr=False) + self.hardswish = nn.Hardswish() + self.dropout = nn.Dropout(p=last_drop, mode="downscale_in_infer") + + def forward(self, x, sz): + x = x.reshape([0, sz[0], sz[1], x.shape[-1]]) + x = x.mean(1) + x = self.last_conv(x) + x = self.hardswish(x) + x = self.dropout(x) + return x, [1, sz[1]] + + +class OutPool(nn.Layer): + def __init__(self): + super().__init__() + + def forward(self, x, sz): + C = x.shape[-1] + x = x.transpose([0, 2, 1]).reshape([0, C, sz[0], sz[1]]) + x = nn.functional.avg_pool2d(x, [sz[0], 2]) + return x, [1, sz[1] // 2] + + +class Feat2D(nn.Layer): + def __init__(self): + super().__init__() + + def forward(self, x, sz): + C = x.shape[-1] + x = x.transpose([0, 2, 1]).reshape([0, C, sz[0], sz[1]]) + return x, sz + + +class SVTRv2(nn.Layer): + def __init__( + self, + max_sz=[32, 128], + in_channels=3, + out_channels=192, + out_char_num=25, + depths=[3, 6, 3], + dims=[64, 128, 256], + mixer=[["Conv"] * 3, ["Conv"] * 3 + ["Global"] * 3, ["Global"] * 3], + use_pos_embed=False, + sub_k=[[1, 1], [2, 1], [1, 1]], + num_heads=[2, 4, 8], + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_rate=0.0, + last_drop=0.1, + attn_drop_rate=0.0, + drop_path_rate=0.1, + norm_layer=nn.LayerNorm, + act=nn.GELU, + last_stage=False, + eps=1e-6, + use_pool=False, + feat2d=False, + **kwargs, + ): + super().__init__() + num_stages = len(depths) + self.num_features = dims[-1] + + feat_max_size = [max_sz[0] // 4, max_sz[1] // 4] + self.pope = POPatchEmbed( + in_channels=in_channels, + feat_max_size=feat_max_size, + embed_dim=dims[0], + use_pos_embed=use_pos_embed, + flatten=mixer[0][0] != "Conv", + ) + + dpr = np.linspace(0, drop_path_rate, sum(depths)) # stochastic depth decay rule + + self.stages = nn.LayerList() + for i_stage in range(num_stages): + stage = SVTRStage( + dim=dims[i_stage], + out_dim=dims[i_stage + 1] if i_stage < num_stages - 1 else 0, + depth=depths[i_stage], + mixer=mixer[i_stage], + sub_k=sub_k[i_stage], + num_heads=num_heads[i_stage], + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + attn_drop=attn_drop_rate, + drop_path=dpr[sum(depths[:i_stage]) : sum(depths[: i_stage + 1])], + norm_layer=norm_layer, + act=act, + downsample=False if i_stage == num_stages - 1 else True, + eps=eps, + ) + self.stages.append(stage) + + self.out_channels = self.num_features + self.last_stage = last_stage + if last_stage: + self.out_channels = out_channels + self.stages.append( + LastStage(self.num_features, out_channels, last_drop, out_char_num) + ) + if use_pool: + self.stages.append(OutPool()) + + if feat2d: + self.stages.append(Feat2D()) + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight) + if isinstance(m, nn.Linear) and m.bias is not None: + zeros_(m.bias) + elif isinstance(m, nn.LayerNorm): + zeros_(m.bias) + ones_(m.weight) + + def forward(self, x): + x, sz = self.pope(x) + for stage in self.stages: + x, sz = stage(x, sz) + return x diff --git a/ppocr/modeling/backbones/rec_vary_vit.py b/ppocr/modeling/backbones/rec_vary_vit.py new file mode 100644 index 0000000..d912ecf --- /dev/null +++ b/ppocr/modeling/backbones/rec_vary_vit.py @@ -0,0 +1,616 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +from functools import partial +from typing import Optional, Tuple, Type + +import numpy as np +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle.nn.initializer import ( + Constant, + KaimingUniform, + Normal, + TruncatedNormal, + XavierUniform, +) +from ppocr.modeling.backbones.rec_donut_swin import DonutSwinModelOutput + +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) +kaiming_normal_ = KaimingUniform(nonlinearity="relu") +trunc_normal_ = TruncatedNormal(std=0.02) +xavier_uniform_ = XavierUniform() + + +class MLPBlock(nn.Layer): + def __init__( + self, + embedding_dim: int, + mlp_dim: int, + act: Type[nn.Layer] = nn.GELU, + ) -> None: + super().__init__() + self.lin1 = nn.Linear(embedding_dim, mlp_dim) + self.lin2 = nn.Linear(mlp_dim, embedding_dim) + self.act = act() + + def forward(self, x): + return self.lin2(self.act(self.lin1(x))) + + +# From https://github.com/facebookresearch/detectron2/blob/main/detectron2/layers/batch_norm.py # noqa +# Itself from https://github.com/facebookresearch/ConvNeXt/blob/d1fa8f6fef0a165b27399986cc2bdacc92777e40/models/convnext.py#L119 # noqa +class LayerNorm2d(nn.Layer): + def __init__(self, num_channels: int, epsilon: float = 1e-6) -> None: + super().__init__() + self.weight = paddle.create_parameter([num_channels], dtype="float32") + ones_(self.weight) + self.bias = paddle.create_parameter([num_channels], dtype="float32") + zeros_(self.bias) + self.epsilon = epsilon + + def forward(self, x): + u = x.mean(1, keepdim=True) + s = (x - u).pow(2).mean(1, keepdim=True) + x = (x - u) / paddle.sqrt(s + self.epsilon) + x = self.weight[:, None, None] * x + self.bias[:, None, None] + return x + + +# This class and its supporting functions below lightly adapted from the ViTDet backbone available at: https://github.com/facebookresearch/detectron2/blob/main/detectron2/modeling/backbone/vit.py # noqa +class ImageEncoderViT(nn.Layer): + def __init__( + self, + img_size: int = 1024, + patch_size: int = 16, + in_chans: int = 3, + embed_dim: int = 768, + depth: int = 12, + num_heads: int = 12, + mlp_ratio: float = 4.0, + out_chans: int = 256, + qkv_bias: bool = True, + norm_layer: Type[nn.Layer] = nn.LayerNorm, + act_layer: Type[nn.Layer] = nn.GELU, + use_abs_pos: bool = True, + use_rel_pos: bool = False, + rel_pos_zero_init: bool = True, + window_size: int = 0, + global_attn_indexes: Tuple[int, ...] = (), + is_formula: bool = False, + ) -> None: + """ + Args: + img_size (int): Input image size. + patch_size (int): Patch size. + in_chans (int): Number of input image channels. + embed_dim (int): Patch embedding dimension. + depth (int): Depth of ViT. + num_heads (int): Number of attention heads in each ViT block. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. + qkv_bias (bool): If True, add a learnable bias to query, key, value. + norm_layer (nn.Layer): Normalization layer. + act_layer (nn.Layer): Activation layer. + use_abs_pos (bool): If True, use absolute positional embeddings. + use_rel_pos (bool): If True, add relative positional embeddings to the attention map. + rel_pos_zero_init (bool): If True, zero initialize relative positional parameters. + window_size (int): Window size for window attention blocks. + global_attn_indexes (list): Indexes for blocks using global attention. + """ + super().__init__() + self.img_size = img_size + + self.patch_embed = PatchEmbed( + kernel_size=(patch_size, patch_size), + stride=(patch_size, patch_size), + in_chans=in_chans, + embed_dim=embed_dim, + ) + + self.pos_embed = None + if use_abs_pos: + # Initialize absolute positional embedding with pretrain image size. + self.pos_embed = paddle.create_parameter( + shape=(1, img_size // patch_size, img_size // patch_size, embed_dim), + dtype="float32", + ) + zeros_(self.pos_embed) + + self.blocks = nn.LayerList() + for i in range(depth): + block = Block( + dim=embed_dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + norm_layer=norm_layer, + act_layer=act_layer, + use_rel_pos=use_rel_pos, + rel_pos_zero_init=rel_pos_zero_init, + window_size=window_size if i not in global_attn_indexes else 0, + input_size=(img_size // patch_size, img_size // patch_size), + ) + self.blocks.append(block) + + self.neck = nn.Sequential( + nn.Conv2D( + embed_dim, + out_chans, + kernel_size=1, + bias_attr=False, + ), + LayerNorm2d(out_chans), + nn.Conv2D( + out_chans, + out_chans, + kernel_size=3, + padding=1, + bias_attr=False, + ), + LayerNorm2d(out_chans), + ) + + self.net_2 = nn.Conv2D( + 256, 512, kernel_size=3, stride=2, padding=1, bias_attr=False + ) + self.net_3 = nn.Conv2D( + 512, 1024, kernel_size=3, stride=2, padding=1, bias_attr=False + ) + self.is_formula = is_formula + + def forward(self, x): + x = self.patch_embed(x) + if self.pos_embed is not None: + x = x + self.pos_embed + for blk in self.blocks: + x = blk(x) + x = self.neck(x.transpose([0, 3, 1, 2])) + x = self.net_2(x) + if self.is_formula: + x = self.net_3(x) + return x + + +class Block(nn.Layer): + """Transformer blocks with support of window attention and residual propagation blocks""" + + def __init__( + self, + dim: int, + num_heads: int, + mlp_ratio: float = 4.0, + qkv_bias: bool = True, + norm_layer: Type[nn.Layer] = nn.LayerNorm, + act_layer: Type[nn.Layer] = nn.GELU, + use_rel_pos: bool = False, + rel_pos_zero_init: bool = True, + window_size: int = 0, + input_size: Optional[Tuple[int, int]] = None, + ) -> None: + """ + Args: + dim (int): Number of input channels. + num_heads (int): Number of attention heads in each ViT block. + mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. + qkv_bias (bool): If True, add a learnable bias to query, key, value. + norm_layer (nn.Layer): Normalization layer. + act_layer (nn.Layer): Activation layer. + use_rel_pos (bool): If True, add relative positional embeddings to the attention map. + rel_pos_zero_init (bool): If True, zero initialize relative positional parameters. + window_size (int): Window size for window attention blocks. If it equals 0, then + use global attention. + input_size (tuple(int, int) or None): Input resolution for calculating the relative + positional parameter size. + """ + super().__init__() + self.norm1 = norm_layer(dim) + self.attn = Attention( + dim, + num_heads=num_heads, + qkv_bias=qkv_bias, + use_rel_pos=use_rel_pos, + rel_pos_zero_init=rel_pos_zero_init, + input_size=input_size if window_size == 0 else (window_size, window_size), + ) + + self.norm2 = norm_layer(dim) + self.mlp = MLPBlock( + embedding_dim=dim, mlp_dim=int(dim * mlp_ratio), act=act_layer + ) + + self.window_size = window_size + + def forward(self, x): + shortcut = x + + x = self.norm1(x) + # Window partition + if self.window_size > 0: + H, W = x.shape[1], x.shape[2] + x, pad_hw = window_partition(x, self.window_size) + x = self.attn(x) + # Reverse window partition + if self.window_size > 0: + x = window_unpartition(x, self.window_size, pad_hw, (H, W)) + x = shortcut + x + x = x + self.mlp(self.norm2(x)) + + return x + + +class Attention(nn.Layer): + """Multi-head Attention block with relative position embeddings.""" + + def __init__( + self, + dim: int, + num_heads: int = 8, + qkv_bias: bool = True, + use_rel_pos: bool = False, + rel_pos_zero_init: bool = True, + input_size: Optional[Tuple[int, int]] = None, + ) -> None: + """ + Args: + dim (int): Number of input channels. + num_heads (int): Number of attention heads. + qkv_bias (bool): If True, add a learnable bias to query, key, value. + rel_pos (bool): If True, add relative positional embeddings to the attention map. + rel_pos_zero_init (bool): If True, zero initialize relative positional parameters. + input_size (tuple(int, int) or None): Input resolution for calculating the relative + positional parameter size. + """ + super().__init__() + self.num_heads = num_heads + head_dim = dim // num_heads + self.scale = head_dim**-0.5 + + self.qkv = nn.Linear(dim, dim * 3, bias_attr=qkv_bias) + self.proj = nn.Linear(dim, dim) + + self.use_rel_pos = use_rel_pos + if self.use_rel_pos: + assert ( + input_size is not None + ), "Input size must be provided if using relative positional encoding." + # initialize relative positional embeddings + self.rel_pos_h = paddle.create_parameter( + [2 * input_size[0] - 1, head_dim], dtype="float32" + ) + zeros_(self.rel_pos_h) + self.rel_pos_w = paddle.create_parameter( + [2 * input_size[1] - 1, head_dim], dtype="float32" + ) + zeros_(self.rel_pos_w) + + def forward(self, x): + + B, H, W, _ = x.shape + qkv = ( + self.qkv(x) + .reshape([B, H * W, 3, self.num_heads, -1]) + .transpose([2, 0, 3, 1, 4]) + ) + q, k, v = qkv.reshape([3, B * self.num_heads, H * W, -1]).unbind(0) + attn = (q * self.scale) @ k.transpose([0, 2, 1]) + + if self.use_rel_pos: + attn = add_decomposed_rel_pos( + attn, q, self.rel_pos_h, self.rel_pos_w, (H, W), (H, W) + ) + attn = F.softmax(attn, axis=-1) + x = ( + (attn @ v) + .reshape([B, self.num_heads, H, W, -1]) + .transpose([0, 2, 3, 1, 4]) + .reshape([B, H, W, -1]) + ) + x = self.proj(x) + + return x + + +def window_partition(x, window_size: int): + """ + Partition into non-overlapping windows with padding if needed. + Args: + x (tensor): input tokens with [B, H, W, C]. + window_size (int): window size. + + Returns: + windows: windows after partition with [B * num_windows, window_size, window_size, C]. + (Hp, Wp): padded height and width before partition + """ + B, H, W, C = x.shape + + pad_h = (window_size - H % window_size) % window_size + pad_w = (window_size - W % window_size) % window_size + if pad_h > 0 or pad_w > 0: + x = F.pad(x, (0, 0, 0, pad_w, 0, pad_h, 0, 0)) + Hp, Wp = H + pad_h, W + pad_w + + x = x.reshape( + [B, Hp // window_size, window_size, Wp // window_size, window_size, C] + ) + windows = x.transpose([0, 1, 3, 2, 4, 5]).reshape([-1, window_size, window_size, C]) + return windows, (Hp, Wp) + + +def window_unpartition( + windows, window_size: int, pad_hw: Tuple[int, int], hw: Tuple[int, int] +): + """ + Window unpartition into original sequences and removing padding. + Args: + windows (tensor): input tokens with [B * num_windows, window_size, window_size, C]. + window_size (int): window size. + pad_hw (Tuple): padded height and width (Hp, Wp). + hw (Tuple): original height and width (H, W) before padding. + + Returns: + x: unpartitioned sequences with [B, H, W, C]. + """ + Hp, Wp = pad_hw + H, W = hw + B = windows.shape[0] // (Hp * Wp // window_size // window_size) + x = windows.reshape( + [B, Hp // window_size, Wp // window_size, window_size, window_size, -1] + ) + x = x.transpose([0, 1, 3, 2, 4, 5]).contiguous().reshape([B, Hp, Wp, -1]) + + if Hp > H or Wp > W: + x = x[:, :H, :W, :].contiguous() + return x + + +def get_rel_pos(q_size: int, k_size: int, rel_pos): + """ + Get relative positional embeddings according to the relative positions of + query and key sizes. + Args: + q_size (int): size of query q. + k_size (int): size of key k. + rel_pos (Tensor): relative position embeddings (L, C). + + Returns: + Extracted positional embeddings according to relative positions. + """ + max_rel_dist = int(2 * max(q_size, k_size) - 1) + # Interpolate rel pos if needed. + if rel_pos.shape[0] != max_rel_dist: + # Interpolate rel pos. + rel_pos_resized = F.interpolate( + rel_pos.reshape(1, rel_pos.shape[0], -1).transpose(0, 2, 1), + size=max_rel_dist, + mode="linear", + ) + rel_pos_resized = rel_pos_resized.reshape(-1, max_rel_dist).transpose(1, 0) + else: + rel_pos_resized = rel_pos + + # Scale the coords with short length if shapes for q and k are different. + q_coords = paddle.arange(q_size)[:, None] * max(k_size / q_size, 1.0) + k_coords = paddle.arange(k_size)[None, :] * max(q_size / k_size, 1.0) + relative_coords = (q_coords - k_coords) + (k_size - 1) * max(q_size / k_size, 1.0) + + return rel_pos_resized[relative_coords.cast(paddle.int64)] + + +def add_decomposed_rel_pos( + attn, + q, + rel_pos_h, + rel_pos_w, + q_size: Tuple[int, int], + k_size: Tuple[int, int], +): + """ + Calculate decomposed Relative Positional Embeddings from :paper:`mvitv2`. + https://github.com/facebookresearch/mvit/blob/19786631e330df9f3622e5402b4a419a263a2c80/mvit/models/attention.py # noqa B950 + Args: + attn (Tensor): attention map. + q (Tensor): query q in the attention layer with shape (B, q_h * q_w, C). + rel_pos_h (Tensor): relative position embeddings (Lh, C) for height axis. + rel_pos_w (Tensor): relative position embeddings (Lw, C) for width axis. + q_size (Tuple): spatial sequence size of query q with (q_h, q_w). + k_size (Tuple): spatial sequence size of key k with (k_h, k_w). + + Returns: + attn (Tensor): attention map with added relative positional embeddings. + """ + q_h, q_w = q_size + k_h, k_w = k_size + Rh = get_rel_pos(q_h, k_h, rel_pos_h) + Rw = get_rel_pos(q_w, k_w, rel_pos_w) + + B, _, dim = q.shape + r_q = q.reshape([B, q_h, q_w, dim]) + rel_h = paddle.einsum("bhwc,hkc->bhwk", r_q, Rh) + rel_w = paddle.einsum("bhwc,wkc->bhwk", r_q, Rw) + + attn = ( + attn.reshape([B, q_h, q_w, k_h, k_w]) + + rel_h[:, :, :, :, None] + + rel_w[:, :, :, None, :] + ).reshape([B, q_h * q_w, k_h * k_w]) + + return attn + + +class PatchEmbed(nn.Layer): + """ + Image to Patch Embedding. + """ + + def __init__( + self, + kernel_size: Tuple[int, int] = (16, 16), + stride: Tuple[int, int] = (16, 16), + padding: Tuple[int, int] = (0, 0), + in_chans: int = 3, + embed_dim: int = 768, + ) -> None: + """ + Args: + kernel_size (Tuple): kernel size of the projection layer. + stride (Tuple): stride of the projection layer. + padding (Tuple): padding size of the projection layer. + in_chans (int): Number of input image channels. + embed_dim (int): Patch embedding dimension. + """ + super().__init__() + + self.proj = nn.Conv2D( + in_chans, + embed_dim, + kernel_size=kernel_size, + stride=stride, + padding=padding, + weight_attr=True, + bias_attr=True, + ) + + def forward(self, x): + x = self.proj(x) + # B C H W -> B H W C + x = x.transpose([0, 2, 3, 1]) + return x + + +def _build_vary( + encoder_embed_dim, + encoder_depth, + encoder_num_heads, + encoder_global_attn_indexes, + image_size, + is_formula=False, +): + prompt_embed_dim = 256 + vit_patch_size = 16 + image_embedding_size = image_size // vit_patch_size + image_encoder = ImageEncoderViT( + depth=encoder_depth, + embed_dim=encoder_embed_dim, + img_size=image_size, + mlp_ratio=4, + norm_layer=partial(paddle.nn.LayerNorm, epsilon=1e-6), + num_heads=encoder_num_heads, + patch_size=vit_patch_size, + qkv_bias=True, + use_rel_pos=True, + global_attn_indexes=encoder_global_attn_indexes, + window_size=14, + out_chans=prompt_embed_dim, + is_formula=is_formula, + ) + return image_encoder + + +class Vary_VIT_B(nn.Layer): + def __init__( + self, + in_channels=3, + image_size=768, + encoder_embed_dim=768, + encoder_depth=12, + encoder_num_heads=12, + encoder_global_attn_indexes=[2, 5, 8, 11], + ): + super().__init__() + + self.vision_tower_high = _build_vary( + encoder_embed_dim=768, + encoder_depth=12, + encoder_num_heads=12, + encoder_global_attn_indexes=[2, 5, 8, 11], + image_size=image_size, + ) + + self.out_channels = 1024 + + def forward(self, input_data): + pixel_values = input_data + num_channels = pixel_values.shape[1] + if num_channels == 1: + pixel_values = paddle.repeat_interleave(pixel_values, repeats=3, axis=1) + cnn_feature = self.vision_tower_high(pixel_values) + cnn_feature = cnn_feature.flatten(2).transpose([0, 2, 1]) + return cnn_feature + + +class Vary_VIT_B_Formula(nn.Layer): + def __init__( + self, + in_channels=3, + image_size=768, + encoder_embed_dim=768, + encoder_depth=12, + encoder_num_heads=12, + encoder_global_attn_indexes=[2, 5, 8, 11], + ): + """ + Vary_VIT_B_Formula + Args: + in_channels (int): Number of input channels. Default is 3 (for RGB images). + image_size (int): Size of the input image. Default is 768. + encoder_embed_dim (int): Dimension of the encoder's embedding. Default is 768. + encoder_depth (int): Number of layers (depth) in the encoder. Default is 12. + encoder_num_heads (int): Number of attention heads in the encoder. Default is 12. + encoder_global_attn_indexes (list): List of indices specifying which encoder layers use global attention. Default is [2, 5, 8, 11]. + Returns: + model: nn.Layer. Specific `Vary_VIT_B_Formula` model with defined architecture. + """ + super(Vary_VIT_B_Formula, self).__init__() + + self.vision_tower_high = _build_vary( + encoder_embed_dim=encoder_embed_dim, + encoder_depth=encoder_depth, + encoder_num_heads=encoder_num_heads, + encoder_global_attn_indexes=[2, 5, 8, 11], + image_size=image_size, + is_formula=True, + ) + self.mm_projector_vary = nn.Linear(1024, 1024) + self.out_channels = 1024 + + def forward(self, input_data): + if self.training: + pixel_values, label, attention_mask = input_data + else: + if isinstance(input_data, list): + pixel_values = input_data[0] + else: + pixel_values = input_data + num_channels = pixel_values.shape[1] + if num_channels == 1: + pixel_values = paddle.repeat_interleave(pixel_values, repeats=3, axis=1) + + cnn_feature = self.vision_tower_high(pixel_values) + cnn_feature = cnn_feature.flatten(2).transpose([0, 2, 1]) + + cnn_feature = self.mm_projector_vary(cnn_feature) + donut_swin_output = DonutSwinModelOutput( + last_hidden_state=cnn_feature, + pooler_output=None, + hidden_states=None, + attentions=None, + reshaped_hidden_states=None, + ) + if self.training: + return donut_swin_output, label, attention_mask + else: + return donut_swin_output diff --git a/ppocr/modeling/backbones/rec_vit.py b/ppocr/modeling/backbones/rec_vit.py new file mode 100644 index 0000000..165b868 --- /dev/null +++ b/ppocr/modeling/backbones/rec_vit.py @@ -0,0 +1,273 @@ +# copyright (c) 2023 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddle import ParamAttr +from paddle.nn.initializer import KaimingNormal +import numpy as np +import paddle +import paddle.nn as nn +from paddle.nn.initializer import TruncatedNormal, Constant, Normal + +trunc_normal_ = TruncatedNormal(std=0.02) +normal_ = Normal +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) + + +def drop_path(x, drop_prob=0.0, training=False): + """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). + the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper... + See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... + """ + if drop_prob == 0.0 or not training: + return x + keep_prob = paddle.to_tensor(1 - drop_prob) + shape = (x.shape[0],) + (1,) * (x.ndim - 1) + random_tensor = keep_prob + paddle.rand(shape, dtype=x.dtype) + random_tensor = paddle.floor(random_tensor) # binarize + output = x.divide(keep_prob) * random_tensor + return output + + +class DropPath(nn.Layer): + """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).""" + + def __init__(self, drop_prob=None): + super(DropPath, self).__init__() + self.drop_prob = drop_prob + + def forward(self, x): + return drop_path(x, self.drop_prob, self.training) + + +class Identity(nn.Layer): + def __init__(self): + super(Identity, self).__init__() + + def forward(self, input): + return input + + +class Mlp(nn.Layer): + def __init__( + self, + in_features, + hidden_features=None, + out_features=None, + act_layer=nn.GELU, + drop=0.0, + ): + super().__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + self.fc1 = nn.Linear(in_features, hidden_features) + self.act = act_layer() + self.fc2 = nn.Linear(hidden_features, out_features) + self.drop = nn.Dropout(drop) + + def forward(self, x): + x = self.fc1(x) + x = self.act(x) + x = self.drop(x) + x = self.fc2(x) + x = self.drop(x) + return x + + +class Attention(nn.Layer): + def __init__( + self, + dim, + num_heads=8, + qkv_bias=False, + qk_scale=None, + attn_drop=0.0, + proj_drop=0.0, + ): + super().__init__() + self.num_heads = num_heads + self.dim = dim + head_dim = dim // num_heads + self.scale = qk_scale or head_dim**-0.5 + + self.qkv = nn.Linear(dim, dim * 3, bias_attr=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop) + self.proj = nn.Linear(dim, dim) + self.proj_drop = nn.Dropout(proj_drop) + + def forward(self, x): + qkv = paddle.reshape( + self.qkv(x), (0, -1, 3, self.num_heads, self.dim // self.num_heads) + ).transpose((2, 0, 3, 1, 4)) + q, k, v = qkv[0] * self.scale, qkv[1], qkv[2] + + attn = q.matmul(k.transpose((0, 1, 3, 2))) + attn = nn.functional.softmax(attn, axis=-1) + attn = self.attn_drop(attn) + + x = (attn.matmul(v)).transpose((0, 2, 1, 3)).reshape((0, -1, self.dim)) + x = self.proj(x) + x = self.proj_drop(x) + return x + + +class Block(nn.Layer): + def __init__( + self, + dim, + num_heads, + mlp_ratio=4.0, + qkv_bias=False, + qk_scale=None, + drop=0.0, + attn_drop=0.0, + drop_path=0.0, + act_layer=nn.GELU, + norm_layer="nn.LayerNorm", + epsilon=1e-6, + prenorm=True, + ): + super().__init__() + if isinstance(norm_layer, str): + self.norm1 = eval(norm_layer)(dim, epsilon=epsilon) + else: + self.norm1 = norm_layer(dim) + self.mixer = Attention( + dim, + num_heads=num_heads, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop=attn_drop, + proj_drop=drop, + ) + + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else Identity() + if isinstance(norm_layer, str): + self.norm2 = eval(norm_layer)(dim, epsilon=epsilon) + else: + self.norm2 = norm_layer(dim) + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp_ratio = mlp_ratio + self.mlp = Mlp( + in_features=dim, + hidden_features=mlp_hidden_dim, + act_layer=act_layer, + drop=drop, + ) + self.prenorm = prenorm + + def forward(self, x): + if self.prenorm: + x = self.norm1(x + self.drop_path(self.mixer(x))) + x = self.norm2(x + self.drop_path(self.mlp(x))) + else: + x = x + self.drop_path(self.mixer(self.norm1(x))) + x = x + self.drop_path(self.mlp(self.norm2(x))) + return x + + +class ViT(nn.Layer): + def __init__( + self, + img_size=[32, 128], + patch_size=[4, 4], + in_channels=3, + embed_dim=384, + depth=12, + num_heads=6, + mlp_ratio=4, + qkv_bias=False, + qk_scale=None, + drop_rate=0.0, + attn_drop_rate=0.0, + drop_path_rate=0.1, + norm_layer="nn.LayerNorm", + epsilon=1e-6, + act="nn.GELU", + prenorm=False, + **kwargs, + ): + super().__init__() + self.embed_dim = embed_dim + self.out_channels = embed_dim + self.prenorm = prenorm + self.patch_embed = nn.Conv2D( + in_channels, embed_dim, patch_size, patch_size, padding=(0, 0) + ) + self.pos_embed = self.create_parameter( + shape=[1, 257, embed_dim], default_initializer=zeros_ + ) + self.add_parameter("pos_embed", self.pos_embed) + self.pos_drop = nn.Dropout(p=drop_rate) + dpr = np.linspace(0, drop_path_rate, depth) + self.blocks1 = nn.LayerList( + [ + Block( + dim=embed_dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + act_layer=eval(act), + attn_drop=attn_drop_rate, + drop_path=dpr[i], + norm_layer=norm_layer, + epsilon=epsilon, + prenorm=prenorm, + ) + for i in range(depth) + ] + ) + if not prenorm: + self.norm = eval(norm_layer)(embed_dim, epsilon=epsilon) + + self.avg_pool = nn.AdaptiveAvgPool2D([1, 25]) + self.last_conv = nn.Conv2D( + in_channels=embed_dim, + out_channels=self.out_channels, + kernel_size=1, + stride=1, + padding=0, + bias_attr=False, + ) + self.hardswish = nn.Hardswish() + self.dropout = nn.Dropout(p=0.1, mode="downscale_in_infer") + + trunc_normal_(self.pos_embed) + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight) + if isinstance(m, nn.Linear) and m.bias is not None: + zeros_(m.bias) + elif isinstance(m, nn.LayerNorm): + zeros_(m.bias) + ones_(m.weight) + + def forward(self, x): + x = self.patch_embed(x).flatten(2).transpose((0, 2, 1)) + x = x + self.pos_embed[:, 1:, :] # [:, :x.shape[1], :] + x = self.pos_drop(x) + for blk in self.blocks1: + x = blk(x) + if not self.prenorm: + x = self.norm(x) + + x = self.avg_pool(x.transpose([0, 2, 1]).reshape([0, self.embed_dim, -1, 25])) + x = self.last_conv(x) + x = self.hardswish(x) + x = self.dropout(x) + return x diff --git a/ppocr/modeling/backbones/rec_vit_parseq.py b/ppocr/modeling/backbones/rec_vit_parseq.py new file mode 100644 index 0000000..049733f --- /dev/null +++ b/ppocr/modeling/backbones/rec_vit_parseq.py @@ -0,0 +1,348 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is refer from: +https://github.com/PaddlePaddle/PaddleClas/blob/release%2F2.5/ppcls/arch/backbone/model_zoo/vision_transformer.py +""" + +from collections.abc import Callable + +import numpy as np +import paddle +import paddle.nn as nn +from paddle.nn.initializer import TruncatedNormal, Constant, Normal + + +trunc_normal_ = TruncatedNormal(std=0.02) +normal_ = Normal +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) + + +def to_2tuple(x): + return tuple([x] * 2) + + +def drop_path(x, drop_prob=0.0, training=False): + """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). + the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper... + See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... + """ + if drop_prob == 0.0 or not training: + return x + keep_prob = paddle.to_tensor(1 - drop_prob, dtype=x.dtype) + shape = (x.shape[0],) + (1,) * (x.ndim - 1) + random_tensor = keep_prob + paddle.rand(shape).astype(x.dtype) + random_tensor = paddle.floor(random_tensor) # binarize + output = x.divide(keep_prob) * random_tensor + return output + + +class DropPath(nn.Layer): + """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).""" + + def __init__(self, drop_prob=None): + super(DropPath, self).__init__() + self.drop_prob = drop_prob + + def forward(self, x): + return drop_path(x, self.drop_prob, self.training) + + +class Identity(nn.Layer): + def __init__(self): + super(Identity, self).__init__() + + def forward(self, input): + return input + + +class Mlp(nn.Layer): + def __init__( + self, + in_features, + hidden_features=None, + out_features=None, + act_layer=nn.GELU, + drop=0.0, + ): + super().__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + self.fc1 = nn.Linear(in_features, hidden_features) + self.act = act_layer() + self.fc2 = nn.Linear(hidden_features, out_features) + self.drop = nn.Dropout(drop) + + def forward(self, x): + x = self.fc1(x) + x = self.act(x) + x = self.drop(x) + x = self.fc2(x) + x = self.drop(x) + return x + + +class Attention(nn.Layer): + def __init__( + self, + dim, + num_heads=8, + qkv_bias=False, + qk_scale=None, + attn_drop=0.0, + proj_drop=0.0, + ): + super().__init__() + self.num_heads = num_heads + head_dim = dim // num_heads + self.scale = qk_scale or head_dim**-0.5 + + self.qkv = nn.Linear(dim, dim * 3, bias_attr=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop) + self.proj = nn.Linear(dim, dim) + self.proj_drop = nn.Dropout(proj_drop) + + def forward(self, x): + # B= x.shape[0] + N, C = x.shape[1:] + qkv = ( + self.qkv(x) + .reshape((-1, N, 3, self.num_heads, C // self.num_heads)) + .transpose((2, 0, 3, 1, 4)) + ) + q, k, v = qkv[0], qkv[1], qkv[2] + + attn = (q.matmul(k.transpose((0, 1, 3, 2)))) * self.scale + attn = nn.functional.softmax(attn, axis=-1) + attn = self.attn_drop(attn) + + x = (attn.matmul(v)).transpose((0, 2, 1, 3)).reshape((-1, N, C)) + x = self.proj(x) + x = self.proj_drop(x) + return x + + +class Block(nn.Layer): + def __init__( + self, + dim, + num_heads, + mlp_ratio=4.0, + qkv_bias=False, + qk_scale=None, + drop=0.0, + attn_drop=0.0, + drop_path=0.0, + act_layer=nn.GELU, + norm_layer="nn.LayerNorm", + epsilon=1e-5, + ): + super().__init__() + if isinstance(norm_layer, str): + self.norm1 = eval(norm_layer)(dim, epsilon=epsilon) + elif isinstance(norm_layer, Callable): + self.norm1 = norm_layer(dim) + else: + raise TypeError("The norm_layer must be str or paddle.nn.layer.Layer class") + self.attn = Attention( + dim, + num_heads=num_heads, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop=attn_drop, + proj_drop=drop, + ) + # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else Identity() + if isinstance(norm_layer, str): + self.norm2 = eval(norm_layer)(dim, epsilon=epsilon) + elif isinstance(norm_layer, Callable): + self.norm2 = norm_layer(dim) + else: + raise TypeError("The norm_layer must be str or paddle.nn.layer.Layer class") + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp = Mlp( + in_features=dim, + hidden_features=mlp_hidden_dim, + act_layer=act_layer, + drop=drop, + ) + + def forward(self, x): + x = x + self.drop_path(self.attn(self.norm1(x))) + x = x + self.drop_path(self.mlp(self.norm2(x))) + return x + + +class PatchEmbed(nn.Layer): + """Image to Patch Embedding""" + + def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768): + super().__init__() + if isinstance(img_size, int): + img_size = to_2tuple(img_size) + if isinstance(patch_size, int): + patch_size = to_2tuple(patch_size) + num_patches = (img_size[1] // patch_size[1]) * (img_size[0] // patch_size[0]) + self.img_size = img_size + self.patch_size = patch_size + self.num_patches = num_patches + + self.proj = nn.Conv2D( + in_chans, embed_dim, kernel_size=patch_size, stride=patch_size + ) + + def forward(self, x): + B, C, H, W = x.shape + assert ( + H == self.img_size[0] and W == self.img_size[1] + ), f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})." + + x = self.proj(x).flatten(2).transpose((0, 2, 1)) + return x + + +class VisionTransformer(nn.Layer): + """Vision Transformer with support for patch input""" + + def __init__( + self, + img_size=224, + patch_size=16, + in_channels=3, + class_num=1000, + embed_dim=768, + depth=12, + num_heads=12, + mlp_ratio=4, + qkv_bias=False, + qk_scale=None, + drop_rate=0.0, + attn_drop_rate=0.0, + drop_path_rate=0.0, + norm_layer="nn.LayerNorm", + epsilon=1e-5, + **kwargs, + ): + super().__init__() + self.class_num = class_num + + self.num_features = self.embed_dim = embed_dim + + self.patch_embed = PatchEmbed( + img_size=img_size, + patch_size=patch_size, + in_chans=in_channels, + embed_dim=embed_dim, + ) + num_patches = self.patch_embed.num_patches + + self.pos_embed = self.create_parameter( + shape=(1, num_patches, embed_dim), default_initializer=zeros_ + ) + self.add_parameter("pos_embed", self.pos_embed) + self.cls_token = self.create_parameter( + shape=(1, 1, embed_dim), default_initializer=zeros_ + ) + self.add_parameter("cls_token", self.cls_token) + self.pos_drop = nn.Dropout(p=drop_rate) + + dpr = np.linspace(0, drop_path_rate, depth) + + self.blocks = nn.LayerList( + [ + Block( + dim=embed_dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + attn_drop=attn_drop_rate, + drop_path=dpr[i], + norm_layer=norm_layer, + epsilon=epsilon, + ) + for i in range(depth) + ] + ) + + self.norm = eval(norm_layer)(embed_dim, epsilon=epsilon) + + # Classifier head + self.head = nn.Linear(embed_dim, class_num) if class_num > 0 else Identity() + + trunc_normal_(self.pos_embed) + self.out_channels = embed_dim + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight) + if isinstance(m, nn.Linear) and m.bias is not None: + zeros_(m.bias) + elif isinstance(m, nn.LayerNorm): + zeros_(m.bias) + ones_(m.weight) + + def forward_features(self, x): + B = x.shape[0] + x = self.patch_embed(x) + x = x + self.pos_embed + x = self.pos_drop(x) + for blk in self.blocks: + x = blk(x) + x = self.norm(x) + return x + + def forward(self, x): + x = self.forward_features(x) + x = self.head(x) + return x + + +class ViTParseQ(VisionTransformer): + def __init__( + self, + img_size=[224, 224], + patch_size=[16, 16], + in_channels=3, + embed_dim=768, + depth=12, + num_heads=12, + mlp_ratio=4.0, + qkv_bias=True, + drop_rate=0.0, + attn_drop_rate=0.0, + drop_path_rate=0.0, + ): + super().__init__( + img_size, + patch_size, + in_channels, + embed_dim=embed_dim, + depth=depth, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + drop_rate=drop_rate, + attn_drop_rate=attn_drop_rate, + drop_path_rate=drop_path_rate, + class_num=0, + ) + + def forward(self, x): + return self.forward_features(x) diff --git a/ppocr/modeling/backbones/rec_vitstr.py b/ppocr/modeling/backbones/rec_vitstr.py new file mode 100644 index 0000000..9a273da --- /dev/null +++ b/ppocr/modeling/backbones/rec_vitstr.py @@ -0,0 +1,133 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/roatienza/deep-text-recognition-benchmark/blob/master/modules/vitstr.py +""" + +import numpy as np +import paddle +import paddle.nn as nn +from ppocr.modeling.backbones.rec_svtrnet import ( + Block, + PatchEmbed, + zeros_, + trunc_normal_, + ones_, +) + +scale_dim_heads = {"tiny": [192, 3], "small": [384, 6], "base": [768, 12]} + + +class ViTSTR(nn.Layer): + def __init__( + self, + img_size=[224, 224], + in_channels=1, + scale="tiny", + seqlen=27, + patch_size=[16, 16], + embed_dim=None, + depth=12, + num_heads=None, + mlp_ratio=4, + qkv_bias=True, + qk_scale=None, + drop_path_rate=0.0, + drop_rate=0.0, + attn_drop_rate=0.0, + norm_layer="nn.LayerNorm", + act_layer="nn.GELU", + epsilon=1e-6, + out_channels=None, + **kwargs, + ): + super().__init__() + self.seqlen = seqlen + embed_dim = embed_dim if embed_dim is not None else scale_dim_heads[scale][0] + num_heads = num_heads if num_heads is not None else scale_dim_heads[scale][1] + out_channels = out_channels if out_channels is not None else embed_dim + self.patch_embed = PatchEmbed( + img_size=img_size, + in_channels=in_channels, + embed_dim=embed_dim, + patch_size=patch_size, + mode="linear", + ) + num_patches = self.patch_embed.num_patches + + self.pos_embed = self.create_parameter( + shape=[1, num_patches + 1, embed_dim], default_initializer=zeros_ + ) + self.add_parameter("pos_embed", self.pos_embed) + self.cls_token = self.create_parameter( + shape=[1, 1, embed_dim], default_initializer=zeros_ + ) + self.add_parameter("cls_token", self.cls_token) + + self.pos_drop = nn.Dropout(p=drop_rate) + + dpr = np.linspace(0, drop_path_rate, depth) + self.blocks = nn.LayerList( + [ + Block( + dim=embed_dim, + num_heads=num_heads, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + attn_drop=attn_drop_rate, + drop_path=dpr[i], + norm_layer=norm_layer, + act_layer=eval(act_layer), + epsilon=epsilon, + prenorm=False, + ) + for i in range(depth) + ] + ) + self.norm = eval(norm_layer)(embed_dim, epsilon=epsilon) + + self.out_channels = out_channels + + trunc_normal_(self.pos_embed) + trunc_normal_(self.cls_token) + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight) + if isinstance(m, nn.Linear) and m.bias is not None: + zeros_(m.bias) + elif isinstance(m, nn.LayerNorm): + zeros_(m.bias) + ones_(m.weight) + + def forward_features(self, x): + B = x.shape[0] + x = self.patch_embed(x) + cls_tokens = paddle.tile(self.cls_token, repeat_times=[B, 1, 1]) + x = paddle.concat((cls_tokens, x), axis=1) + x = x + self.pos_embed + x = self.pos_drop(x) + for blk in self.blocks: + x = blk(x) + x = self.norm(x) + return x + + def forward(self, x): + x = self.forward_features(x) + x = x[:, : self.seqlen] + return x.transpose([0, 2, 1]).unsqueeze(2) diff --git a/ppocr/modeling/backbones/table_master_resnet.py b/ppocr/modeling/backbones/table_master_resnet.py new file mode 100644 index 0000000..6880ae3 --- /dev/null +++ b/ppocr/modeling/backbones/table_master_resnet.py @@ -0,0 +1,365 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/JiaquanYe/TableMASTER-mmocr/blob/master/mmocr/models/textrecog/backbones/table_resnet_extra.py +""" + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F + + +class BasicBlock(nn.Layer): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None, gcb_config=None): + super(BasicBlock, self).__init__() + self.conv1 = nn.Conv2D( + inplanes, planes, kernel_size=3, stride=stride, padding=1, bias_attr=False + ) + self.bn1 = nn.BatchNorm2D(planes, momentum=0.9) + self.relu = nn.ReLU() + self.conv2 = nn.Conv2D( + planes, planes, kernel_size=3, stride=1, padding=1, bias_attr=False + ) + self.bn2 = nn.BatchNorm2D(planes, momentum=0.9) + self.downsample = downsample + self.stride = stride + self.gcb_config = gcb_config + + if self.gcb_config is not None: + gcb_ratio = gcb_config["ratio"] + gcb_headers = gcb_config["headers"] + att_scale = gcb_config["att_scale"] + fusion_type = gcb_config["fusion_type"] + self.context_block = MultiAspectGCAttention( + inplanes=planes, + ratio=gcb_ratio, + headers=gcb_headers, + att_scale=att_scale, + fusion_type=fusion_type, + ) + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.gcb_config is not None: + out = self.context_block(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +def get_gcb_config(gcb_config, layer): + if gcb_config is None or not gcb_config["layers"][layer]: + return None + else: + return gcb_config + + +class TableResNetExtra(nn.Layer): + def __init__(self, layers, in_channels=3, gcb_config=None): + assert len(layers) >= 4 + + super(TableResNetExtra, self).__init__() + self.inplanes = 128 + self.conv1 = nn.Conv2D( + in_channels, 64, kernel_size=3, stride=1, padding=1, bias_attr=False + ) + self.bn1 = nn.BatchNorm2D(64) + self.relu1 = nn.ReLU() + + self.conv2 = nn.Conv2D( + 64, 128, kernel_size=3, stride=1, padding=1, bias_attr=False + ) + self.bn2 = nn.BatchNorm2D(128) + self.relu2 = nn.ReLU() + + self.maxpool1 = nn.MaxPool2D(kernel_size=2, stride=2) + + self.layer1 = self._make_layer( + BasicBlock, + 256, + layers[0], + stride=1, + gcb_config=get_gcb_config(gcb_config, 0), + ) + + self.conv3 = nn.Conv2D( + 256, 256, kernel_size=3, stride=1, padding=1, bias_attr=False + ) + self.bn3 = nn.BatchNorm2D(256) + self.relu3 = nn.ReLU() + + self.maxpool2 = nn.MaxPool2D(kernel_size=2, stride=2) + + self.layer2 = self._make_layer( + BasicBlock, + 256, + layers[1], + stride=1, + gcb_config=get_gcb_config(gcb_config, 1), + ) + + self.conv4 = nn.Conv2D( + 256, 256, kernel_size=3, stride=1, padding=1, bias_attr=False + ) + self.bn4 = nn.BatchNorm2D(256) + self.relu4 = nn.ReLU() + + self.maxpool3 = nn.MaxPool2D(kernel_size=2, stride=2) + + self.layer3 = self._make_layer( + BasicBlock, + 512, + layers[2], + stride=1, + gcb_config=get_gcb_config(gcb_config, 2), + ) + + self.conv5 = nn.Conv2D( + 512, 512, kernel_size=3, stride=1, padding=1, bias_attr=False + ) + self.bn5 = nn.BatchNorm2D(512) + self.relu5 = nn.ReLU() + + self.layer4 = self._make_layer( + BasicBlock, + 512, + layers[3], + stride=1, + gcb_config=get_gcb_config(gcb_config, 3), + ) + + self.conv6 = nn.Conv2D( + 512, 512, kernel_size=3, stride=1, padding=1, bias_attr=False + ) + self.bn6 = nn.BatchNorm2D(512) + self.relu6 = nn.ReLU() + + self.out_channels = [256, 256, 512] + + def _make_layer(self, block, planes, blocks, stride=1, gcb_config=None): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2D( + self.inplanes, + planes * block.expansion, + kernel_size=1, + stride=stride, + bias_attr=False, + ), + nn.BatchNorm2D(planes * block.expansion), + ) + + layers = [] + layers.append( + block(self.inplanes, planes, stride, downsample, gcb_config=gcb_config) + ) + self.inplanes = planes * block.expansion + for _ in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + f = [] + x = self.conv1(x) + + x = self.bn1(x) + x = self.relu1(x) + + x = self.conv2(x) + x = self.bn2(x) + x = self.relu2(x) + + x = self.maxpool1(x) + x = self.layer1(x) + + x = self.conv3(x) + x = self.bn3(x) + x = self.relu3(x) + f.append(x) + + x = self.maxpool2(x) + x = self.layer2(x) + + x = self.conv4(x) + x = self.bn4(x) + x = self.relu4(x) + f.append(x) + + x = self.maxpool3(x) + + x = self.layer3(x) + x = self.conv5(x) + x = self.bn5(x) + x = self.relu5(x) + + x = self.layer4(x) + x = self.conv6(x) + x = self.bn6(x) + x = self.relu6(x) + f.append(x) + return f + + +class MultiAspectGCAttention(nn.Layer): + def __init__( + self, + inplanes, + ratio, + headers, + pooling_type="att", + att_scale=False, + fusion_type="channel_add", + ): + super(MultiAspectGCAttention, self).__init__() + assert pooling_type in ["avg", "att"] + + assert fusion_type in ["channel_add", "channel_mul", "channel_concat"] + assert ( + inplanes % headers == 0 and inplanes >= 8 + ) # inplanes must be divided by headers evenly + + self.headers = headers + self.inplanes = inplanes + self.ratio = ratio + self.planes = int(inplanes * ratio) + self.pooling_type = pooling_type + self.fusion_type = fusion_type + self.att_scale = False + + self.single_header_inplanes = int(inplanes / headers) + + if pooling_type == "att": + self.conv_mask = nn.Conv2D(self.single_header_inplanes, 1, kernel_size=1) + self.softmax = nn.Softmax(axis=2) + else: + self.avg_pool = nn.AdaptiveAvgPool2D(1) + + if fusion_type == "channel_add": + self.channel_add_conv = nn.Sequential( + nn.Conv2D(self.inplanes, self.planes, kernel_size=1), + nn.LayerNorm([self.planes, 1, 1]), + nn.ReLU(), + nn.Conv2D(self.planes, self.inplanes, kernel_size=1), + ) + elif fusion_type == "channel_concat": + self.channel_concat_conv = nn.Sequential( + nn.Conv2D(self.inplanes, self.planes, kernel_size=1), + nn.LayerNorm([self.planes, 1, 1]), + nn.ReLU(), + nn.Conv2D(self.planes, self.inplanes, kernel_size=1), + ) + # for concat + self.cat_conv = nn.Conv2D(2 * self.inplanes, self.inplanes, kernel_size=1) + elif fusion_type == "channel_mul": + self.channel_mul_conv = nn.Sequential( + nn.Conv2D(self.inplanes, self.planes, kernel_size=1), + nn.LayerNorm([self.planes, 1, 1]), + nn.ReLU(), + nn.Conv2D(self.planes, self.inplanes, kernel_size=1), + ) + + def spatial_pool(self, x): + batch, channel, height, width = x.shape + if self.pooling_type == "att": + # [N*headers, C', H , W] C = headers * C' + x = x.reshape( + [batch * self.headers, self.single_header_inplanes, height, width] + ) + input_x = x + + # [N*headers, C', H * W] C = headers * C' + # input_x = input_x.view(batch, channel, height * width) + input_x = input_x.reshape( + [batch * self.headers, self.single_header_inplanes, height * width] + ) + + # [N*headers, 1, C', H * W] + input_x = input_x.unsqueeze(1) + # [N*headers, 1, H, W] + context_mask = self.conv_mask(x) + # [N*headers, 1, H * W] + context_mask = context_mask.reshape( + [batch * self.headers, 1, height * width] + ) + + # scale variance + if self.att_scale and self.headers > 1: + context_mask = context_mask / paddle.sqrt(self.single_header_inplanes) + + # [N*headers, 1, H * W] + context_mask = self.softmax(context_mask) + + # [N*headers, 1, H * W, 1] + context_mask = context_mask.unsqueeze(-1) + # [N*headers, 1, C', 1] = [N*headers, 1, C', H * W] * [N*headers, 1, H * W, 1] + context = paddle.matmul(input_x, context_mask) + + # [N, headers * C', 1, 1] + context = context.reshape( + [batch, self.headers * self.single_header_inplanes, 1, 1] + ) + else: + # [N, C, 1, 1] + context = self.avg_pool(x) + + return context + + def forward(self, x): + # [N, C, 1, 1] + context = self.spatial_pool(x) + + out = x + + if self.fusion_type == "channel_mul": + # [N, C, 1, 1] + channel_mul_term = F.sigmoid(self.channel_mul_conv(context)) + out = out * channel_mul_term + elif self.fusion_type == "channel_add": + # [N, C, 1, 1] + channel_add_term = self.channel_add_conv(context) + out = out + channel_add_term + else: + # [N, C, 1, 1] + channel_concat_term = self.channel_concat_conv(context) + + # use concat + _, C1, _, _ = channel_concat_term.shape + N, C2, H, W = out.shape + + out = paddle.concat( + [out, channel_concat_term.expand([-1, -1, H, W])], axis=1 + ) + out = self.cat_conv(out) + out = F.layer_norm(out, [self.inplanes, H, W]) + out = F.relu(out) + + return out diff --git a/ppocr/modeling/backbones/vqa_layoutlm.py b/ppocr/modeling/backbones/vqa_layoutlm.py new file mode 100644 index 0000000..c80cb14 --- /dev/null +++ b/ppocr/modeling/backbones/vqa_layoutlm.py @@ -0,0 +1,260 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +from paddle import nn + +from paddlenlp.transformers import ( + LayoutXLMModel, + LayoutXLMForTokenClassification, + LayoutXLMForRelationExtraction, +) +from paddlenlp.transformers import LayoutLMModel, LayoutLMForTokenClassification +from paddlenlp.transformers import ( + LayoutLMv2Model, + LayoutLMv2ForTokenClassification, + LayoutLMv2ForRelationExtraction, +) +from paddlenlp.transformers import AutoModel + +__all__ = ["LayoutXLMForSer", "LayoutLMForSer"] + +pretrained_model_dict = { + LayoutXLMModel: { + "base": "layoutxlm-base-uncased", + "vi": "vi-layoutxlm-base-uncased", + }, + LayoutLMModel: { + "base": "layoutlm-base-uncased", + }, + LayoutLMv2Model: { + "base": "layoutlmv2-base-uncased", + "vi": "vi-layoutlmv2-base-uncased", + }, +} + + +class NLPBaseModel(nn.Layer): + def __init__( + self, + base_model_class, + model_class, + mode="base", + type="ser", + pretrained=True, + checkpoints=None, + **kwargs, + ): + super(NLPBaseModel, self).__init__() + if checkpoints is not None: # load the trained model + self.model = model_class.from_pretrained(checkpoints) + else: # load the pretrained-model + pretrained_model_name = pretrained_model_dict[base_model_class][mode] + if type == "ser": + self.model = model_class.from_pretrained( + pretrained_model_name, num_classes=kwargs["num_classes"], dropout=0 + ) + else: + self.model = model_class.from_pretrained( + pretrained_model_name, dropout=0 + ) + self.out_channels = 1 + self.use_visual_backbone = True + + +class LayoutLMForSer(NLPBaseModel): + def __init__( + self, num_classes, pretrained=True, checkpoints=None, mode="base", **kwargs + ): + super(LayoutLMForSer, self).__init__( + LayoutLMModel, + LayoutLMForTokenClassification, + mode, + "ser", + pretrained, + checkpoints, + num_classes=num_classes, + ) + self.use_visual_backbone = False + + def forward(self, x): + x = self.model( + input_ids=x[0], + bbox=x[1], + attention_mask=x[2], + token_type_ids=x[3], + position_ids=None, + output_hidden_states=False, + ) + return x + + +class LayoutLMv2ForSer(NLPBaseModel): + def __init__( + self, num_classes, pretrained=True, checkpoints=None, mode="base", **kwargs + ): + super(LayoutLMv2ForSer, self).__init__( + LayoutLMv2Model, + LayoutLMv2ForTokenClassification, + mode, + "ser", + pretrained, + checkpoints, + num_classes=num_classes, + ) + if ( + hasattr(self.model.layoutlmv2, "use_visual_backbone") + and self.model.layoutlmv2.use_visual_backbone is False + ): + self.use_visual_backbone = False + + def forward(self, x): + if self.use_visual_backbone is True: + image = x[4] + else: + image = None + x = self.model( + input_ids=x[0], + bbox=x[1], + attention_mask=x[2], + token_type_ids=x[3], + image=image, + position_ids=None, + head_mask=None, + labels=None, + ) + if self.training: + res = {"backbone_out": x[0]} + res.update(x[1]) + return res + else: + return x + + +class LayoutXLMForSer(NLPBaseModel): + def __init__( + self, num_classes, pretrained=True, checkpoints=None, mode="base", **kwargs + ): + super(LayoutXLMForSer, self).__init__( + LayoutXLMModel, + LayoutXLMForTokenClassification, + mode, + "ser", + pretrained, + checkpoints, + num_classes=num_classes, + ) + if ( + hasattr(self.model.layoutxlm, "use_visual_backbone") + and self.model.layoutxlm.use_visual_backbone is False + ): + self.use_visual_backbone = False + + def forward(self, x): + if self.use_visual_backbone is True: + image = x[4] + else: + image = None + x = self.model( + input_ids=x[0], + bbox=x[1], + attention_mask=x[2], + token_type_ids=x[3], + image=image, + position_ids=None, + head_mask=None, + labels=None, + ) + if self.training: + res = {"backbone_out": x[0]} + res.update(x[1]) + return res + else: + return x + + +class LayoutLMv2ForRe(NLPBaseModel): + def __init__(self, pretrained=True, checkpoints=None, mode="base", **kwargs): + super(LayoutLMv2ForRe, self).__init__( + LayoutLMv2Model, + LayoutLMv2ForRelationExtraction, + mode, + "re", + pretrained, + checkpoints, + ) + if ( + hasattr(self.model.layoutlmv2, "use_visual_backbone") + and self.model.layoutlmv2.use_visual_backbone is False + ): + self.use_visual_backbone = False + + def forward(self, x): + x = self.model( + input_ids=x[0], + bbox=x[1], + attention_mask=x[2], + token_type_ids=x[3], + image=x[4], + position_ids=None, + head_mask=None, + labels=None, + entities=x[5], + relations=x[6], + ) + return x + + +class LayoutXLMForRe(NLPBaseModel): + def __init__(self, pretrained=True, checkpoints=None, mode="base", **kwargs): + super(LayoutXLMForRe, self).__init__( + LayoutXLMModel, + LayoutXLMForRelationExtraction, + mode, + "re", + pretrained, + checkpoints, + ) + if ( + hasattr(self.model.layoutxlm, "use_visual_backbone") + and self.model.layoutxlm.use_visual_backbone is False + ): + self.use_visual_backbone = False + + def forward(self, x): + if self.use_visual_backbone is True: + image = x[4] + entities = x[5] + relations = x[6] + else: + image = None + entities = x[4] + relations = x[5] + x = self.model( + input_ids=x[0], + bbox=x[1], + attention_mask=x[2], + token_type_ids=x[3], + image=image, + position_ids=None, + head_mask=None, + labels=None, + entities=entities, + relations=relations, + ) + return x diff --git a/ppocr/modeling/heads/__init__.py b/ppocr/modeling/heads/__init__.py new file mode 100755 index 0000000..c410ccc --- /dev/null +++ b/ppocr/modeling/heads/__init__.py @@ -0,0 +1,108 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = ["build_head"] + + +def build_head(config): + # det head + from .det_db_head import DBHead, PFHeadLocal + from .det_east_head import EASTHead + from .det_sast_head import SASTHead + from .det_pse_head import PSEHead + from .det_fce_head import FCEHead + from .e2e_pg_head import PGHead + from .det_ct_head import CT_Head + + # rec head + from .rec_ctc_head import CTCHead + from .rec_att_head import AttentionHead + from .rec_srn_head import SRNHead + from .rec_nrtr_head import Transformer + from .rec_sar_head import SARHead + from .rec_aster_head import AsterHead + from .rec_pren_head import PRENHead + from .rec_multi_head import MultiHead + from .rec_spin_att_head import SPINAttentionHead + from .rec_abinet_head import ABINetHead + from .rec_robustscanner_head import RobustScannerHead + from .rec_visionlan_head import VLHead + from .rec_rfl_head import RFLHead + from .rec_can_head import CANHead + from .rec_latexocr_head import LaTeXOCRHead + from .rec_satrn_head import SATRNHead + from .rec_parseq_head import ParseQHead + from .rec_cppd_head import CPPDHead + from .rec_unimernet_head import UniMERNetHead + from .rec_ppformulanet_head import PPFormulaNet_Head + + # cls head + from .cls_head import ClsHead + + # kie head + from .kie_sdmgr_head import SDMGRHead + + from .table_att_head import TableAttentionHead, SLAHead + from .table_master_head import TableMasterHead + + support_dict = [ + "DBHead", + "PSEHead", + "FCEHead", + "EASTHead", + "SASTHead", + "CTCHead", + "ClsHead", + "AttentionHead", + "SRNHead", + "PGHead", + "Transformer", + "TableAttentionHead", + "SARHead", + "AsterHead", + "SDMGRHead", + "PRENHead", + "MultiHead", + "ABINetHead", + "TableMasterHead", + "SPINAttentionHead", + "VLHead", + "SLAHead", + "RobustScannerHead", + "CT_Head", + "RFLHead", + "DRRGHead", + "CANHead", + "LaTeXOCRHead", + "SATRNHead", + "PFHeadLocal", + "ParseQHead", + "CPPDHead", + "UniMERNetHead", + "PPFormulaNet_Head", + ] + + if config["name"] == "DRRGHead": + from .det_drrg_head import DRRGHead + + support_dict.append("DRRGHead") + + # table head + + module_name = config.pop("name") + assert module_name in support_dict, Exception( + "head only support {}".format(support_dict) + ) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/modeling/heads/cls_head.py b/ppocr/modeling/heads/cls_head.py new file mode 100644 index 0000000..867e960 --- /dev/null +++ b/ppocr/modeling/heads/cls_head.py @@ -0,0 +1,53 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn, ParamAttr +import paddle.nn.functional as F + + +class ClsHead(nn.Layer): + """ + Class orientation + + Args: + + params(dict): super parameters for build Class network + """ + + def __init__(self, in_channels, class_dim, **kwargs): + super(ClsHead, self).__init__() + self.pool = nn.AdaptiveAvgPool2D(1) + stdv = 1.0 / math.sqrt(in_channels * 1.0) + self.fc = nn.Linear( + in_channels, + class_dim, + weight_attr=ParamAttr( + name="fc_0.w_0", initializer=nn.initializer.Uniform(-stdv, stdv) + ), + bias_attr=ParamAttr(name="fc_0.b_0"), + ) + + def forward(self, x, targets=None): + x = self.pool(x) + x = paddle.reshape(x, shape=[x.shape[0], x.shape[1]]) + x = self.fc(x) + if not self.training: + x = F.softmax(x, axis=1) + return x diff --git a/ppocr/modeling/heads/det_ct_head.py b/ppocr/modeling/heads/det_ct_head.py new file mode 100644 index 0000000..cd050fc --- /dev/null +++ b/ppocr/modeling/heads/det_ct_head.py @@ -0,0 +1,69 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr + +import math +from paddle.nn.initializer import TruncatedNormal, Constant, Normal + +ones_ = Constant(value=1.0) +zeros_ = Constant(value=0.0) + + +class CT_Head(nn.Layer): + def __init__( + self, in_channels, hidden_dim, num_classes, loss_kernel=None, loss_loc=None + ): + super(CT_Head, self).__init__() + self.conv1 = nn.Conv2D( + in_channels, hidden_dim, kernel_size=3, stride=1, padding=1 + ) + self.bn1 = nn.BatchNorm2D(hidden_dim) + self.relu1 = nn.ReLU() + + self.conv2 = nn.Conv2D( + hidden_dim, num_classes, kernel_size=1, stride=1, padding=0 + ) + + for m in self.sublayers(): + if isinstance(m, nn.Conv2D): + n = m._kernel_size[0] * m._kernel_size[1] * m._out_channels + normal_ = Normal(mean=0.0, std=math.sqrt(2.0 / n)) + normal_(m.weight) + elif isinstance(m, nn.BatchNorm2D): + zeros_(m.bias) + ones_(m.weight) + + def _upsample(self, x, scale=1): + return F.upsample(x, scale_factor=scale, mode="bilinear") + + def forward(self, f, targets=None): + out = self.conv1(f) + out = self.relu1(self.bn1(out)) + out = self.conv2(out) + + if self.training: + out = self._upsample(out, scale=4) + return {"maps": out} + else: + score = F.sigmoid(out[:, 0, :, :]) + return {"maps": out, "score": score} diff --git a/ppocr/modeling/heads/det_db_head.py b/ppocr/modeling/heads/det_db_head.py new file mode 100644 index 0000000..51aa256 --- /dev/null +++ b/ppocr/modeling/heads/det_db_head.py @@ -0,0 +1,283 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr +from ppocr.modeling.backbones.det_mobilenet_v3 import ConvBNLayer + + +def get_bias_attr(k): + stdv = 1.0 / math.sqrt(k * 1.0) + initializer = paddle.nn.initializer.Uniform(-stdv, stdv) + bias_attr = ParamAttr(initializer=initializer) + return bias_attr + + +class Head(nn.Layer): + def __init__(self, in_channels, kernel_list=[3, 2, 2], fix_nan=False, **kwargs): + super(Head, self).__init__() + + self.conv1 = nn.Conv2D( + in_channels=in_channels, + out_channels=in_channels // 4, + kernel_size=kernel_list[0], + padding=int(kernel_list[0] // 2), + weight_attr=ParamAttr(), + bias_attr=False, + ) + self.conv_bn1 = nn.BatchNorm( + num_channels=in_channels // 4, + param_attr=ParamAttr(initializer=paddle.nn.initializer.Constant(value=1.0)), + bias_attr=ParamAttr(initializer=paddle.nn.initializer.Constant(value=1e-4)), + act="relu", + ) + + self.conv2 = nn.Conv2DTranspose( + in_channels=in_channels // 4, + out_channels=in_channels // 4, + kernel_size=kernel_list[1], + stride=2, + weight_attr=ParamAttr(initializer=paddle.nn.initializer.KaimingUniform()), + bias_attr=get_bias_attr(in_channels // 4), + ) + self.conv_bn2 = nn.BatchNorm( + num_channels=in_channels // 4, + param_attr=ParamAttr(initializer=paddle.nn.initializer.Constant(value=1.0)), + bias_attr=ParamAttr(initializer=paddle.nn.initializer.Constant(value=1e-4)), + act="relu", + ) + self.conv3 = nn.Conv2DTranspose( + in_channels=in_channels // 4, + out_channels=1, + kernel_size=kernel_list[2], + stride=2, + weight_attr=ParamAttr(initializer=paddle.nn.initializer.KaimingUniform()), + bias_attr=get_bias_attr(in_channels // 4), + ) + + self.fix_nan = fix_nan + self.is_repped = False + + def forward(self, x, return_f=False): + x = self.conv1(x) + x = self.conv_bn1(x) + if self.fix_nan and self.training: + x = paddle.where(paddle.isnan(x), paddle.zeros_like(x), x) + x = self.conv2(x) + x = self.conv_bn2(x) + if self.fix_nan and self.training: + x = paddle.where(paddle.isnan(x), paddle.zeros_like(x), x) + if return_f is True: + f = x + x = self.conv3(x) + x = F.sigmoid(x) + if return_f is True: + return x, f + return x + + @paddle.no_grad() + def rep(self): + """Fuse Conv+BN and ConvTranspose+BN pairs for deployment.""" + if self.is_repped: + return + + # conv1 (Conv2D, no bias) + conv_bn1 (BatchNorm, act=relu) + self.conv1 = self._fuse_conv_bn(self.conv1, self.conv_bn1) + self.conv_bn1 = nn.ReLU() + + # conv2 (Conv2DTranspose, has bias) + conv_bn2 (BatchNorm, act=relu) + self.conv2 = self._fuse_convtranspose_bn(self.conv2, self.conv_bn2) + self.conv_bn2 = nn.ReLU() + + self.is_repped = True + + @staticmethod + @paddle.no_grad() + def _fuse_conv_bn(conv, bn): + """Fuse Conv2D + BatchNorm into Conv2D with bias.""" + gamma = bn.weight + std = paddle.sqrt(bn._variance + bn._epsilon) + scale = gamma / std + + w = conv.weight * scale[:, None, None, None] + b = bn.bias - bn._mean * scale + + fused = nn.Conv2D( + conv._in_channels, + conv._out_channels, + conv._kernel_size, + stride=conv._stride, + padding=conv._padding, + dilation=conv._dilation, + groups=conv._groups, + ) + fused.weight.set_value(w) + fused.bias.set_value(b) + return fused + + @staticmethod + @paddle.no_grad() + def _fuse_convtranspose_bn(conv, bn): + """Fuse Conv2DTranspose + BatchNorm into Conv2DTranspose with bias. + + Conv2DTranspose weight shape: [in_ch, out_ch/groups, kH, kW] + BN scale applies on out_ch, i.e. axis=1. + """ + gamma = bn.weight + std = paddle.sqrt(bn._variance + bn._epsilon) + scale = gamma / std + + # axis=1 for ConvTranspose (output channel dimension) + w = conv.weight * scale[None, :, None, None] + b = bn.bias - bn._mean * scale + if conv.bias is not None: + b = b + conv.bias * scale + + fused = nn.Conv2DTranspose( + conv._in_channels, + conv._out_channels, + conv._kernel_size, + stride=conv._stride, + padding=conv._padding, + dilation=conv._dilation, + groups=conv._groups, + ) + fused.weight.set_value(w) + fused.bias.set_value(b) + return fused + + +class DBHead(nn.Layer): + """ + Differentiable Binarization (DB) for text detection: + see https://arxiv.org/abs/1911.08947 + args: + params(dict): super parameters for build DB network + """ + + def __init__(self, in_channels, k=50, aux_in_channels=0, **kwargs): + super(DBHead, self).__init__() + self.k = k + self.is_repped = False + self.binarize = Head(in_channels, **kwargs) + self.thresh = Head(in_channels, **kwargs) + self.aux_in_channels = aux_in_channels + + if aux_in_channels > 0: + self._aux_upsample_scale = { + "aux_p4": 4, # 1/16 -> 1/4 + "aux_p3": 2, # 1/8 -> 1/4 + "aux_p2": 1, # 1/4 -> 1/4 (no-op) + } + # Create independent binarize + thresh Head pairs for each scale + self.aux_binarize_p4 = Head(aux_in_channels, **kwargs) + self.aux_thresh_p4 = Head(aux_in_channels, **kwargs) + self.aux_binarize_p3 = Head(aux_in_channels, **kwargs) + self.aux_thresh_p3 = Head(aux_in_channels, **kwargs) + self.aux_binarize_p2 = Head(aux_in_channels, **kwargs) + self.aux_thresh_p2 = Head(aux_in_channels, **kwargs) + + def step_function(self, x, y): + return paddle.reciprocal(1 + paddle.exp(-self.k * (x - y))) + + def forward(self, x, targets=None): + # Compatible with neck returning dict (training) or tensor (inference) + if isinstance(x, dict): + fuse = x["fuse"] + aux_feats = {k: x[k] for k in ("aux_p4", "aux_p3", "aux_p2") if k in x} + else: + fuse = x + aux_feats = {} + + shrink_maps = self.binarize(fuse) + if not self.training: + return {"maps": shrink_maps} + + threshold_maps = self.thresh(fuse) + binary_maps = self.step_function(shrink_maps, threshold_maps) + y = paddle.concat([shrink_maps, threshold_maps, binary_maps], axis=1) + result = {"maps": y} + + if self.aux_in_channels > 0 and aux_feats: + for key, feat in aux_feats.items(): + scale = self._aux_upsample_scale[key] + if scale > 1: + feat = F.interpolate( + feat, scale_factor=scale, mode="bilinear", align_corners=False + ) + level = key[4:] # 'p4', 'p3', 'p2' + aux_binarize = getattr(self, "aux_binarize_" + level) + aux_thresh_head = getattr(self, "aux_thresh_" + level) + aux_shrink = aux_binarize(feat) + aux_thresh = aux_thresh_head(feat) + aux_binary = self.step_function(aux_shrink, aux_thresh) + result["aux_maps_" + level] = paddle.concat( + [aux_shrink, aux_thresh, aux_binary], axis=1 + ) + + return result + + def rep(self): + """Fuse reparam structures in all sub-modules for deployment.""" + if self.is_repped: + return + for layer in self.sublayers(): + if isinstance(layer, Head): + layer.rep() + self.is_repped = True + + +class LocalModule(nn.Layer): + def __init__(self, in_c, mid_c, use_distance=True): + super(self.__class__, self).__init__() + self.last_3 = ConvBNLayer(in_c + 1, mid_c, 3, 1, 1, act="relu") + self.last_1 = nn.Conv2D(mid_c, 1, 1, 1, 0) + + def forward(self, x, init_map, distance_map): + outf = paddle.concat([init_map, x], axis=1) + # last Conv + out = self.last_1(self.last_3(outf)) + return out + + +class PFHeadLocal(DBHead): + def __init__(self, in_channels, k=50, mode="small", **kwargs): + super(PFHeadLocal, self).__init__(in_channels, k, **kwargs) + self.mode = mode + + self.up_conv = nn.Upsample(scale_factor=2, mode="nearest", align_mode=1) + if self.mode == "large": + self.cbn_layer = LocalModule(in_channels // 4, in_channels // 4) + elif self.mode == "small": + self.cbn_layer = LocalModule(in_channels // 4, in_channels // 8) + + def forward(self, x, targets=None): + shrink_maps, f = self.binarize(x, return_f=True) + base_maps = shrink_maps + cbn_maps = self.cbn_layer(self.up_conv(f), shrink_maps, None) + cbn_maps = F.sigmoid(cbn_maps) + if not self.training: + return {"maps": 0.5 * (base_maps + cbn_maps)} + + threshold_maps = self.thresh(x) + binary_maps = self.step_function(shrink_maps, threshold_maps) + y = paddle.concat([cbn_maps, threshold_maps, binary_maps], axis=1) + return {"maps": y, "distance_maps": cbn_maps, "cbn_maps": binary_maps} diff --git a/ppocr/modeling/heads/det_drrg_head.py b/ppocr/modeling/heads/det_drrg_head.py new file mode 100644 index 0000000..4d6f875 --- /dev/null +++ b/ppocr/modeling/heads/det_drrg_head.py @@ -0,0 +1,216 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textdet/dense_heads/drrg_head.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import warnings +import cv2 +import numpy as np +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from .gcn import GCN +from .local_graph import LocalGraphs +from .proposal_local_graph import ProposalLocalGraphs + + +class DRRGHead(nn.Layer): + def __init__( + self, + in_channels, + k_at_hops=(8, 4), + num_adjacent_linkages=3, + node_geo_feat_len=120, + pooling_scale=1.0, + pooling_output_size=(4, 3), + nms_thr=0.3, + min_width=8.0, + max_width=24.0, + comp_shrink_ratio=1.03, + comp_ratio=0.4, + comp_score_thr=0.3, + text_region_thr=0.2, + center_region_thr=0.2, + center_region_area_thr=50, + local_graph_thr=0.7, + **kwargs, + ): + super().__init__() + + assert isinstance(in_channels, int) + assert isinstance(k_at_hops, tuple) + assert isinstance(num_adjacent_linkages, int) + assert isinstance(node_geo_feat_len, int) + assert isinstance(pooling_scale, float) + assert isinstance(pooling_output_size, tuple) + assert isinstance(comp_shrink_ratio, float) + assert isinstance(nms_thr, float) + assert isinstance(min_width, float) + assert isinstance(max_width, float) + assert isinstance(comp_ratio, float) + assert isinstance(comp_score_thr, float) + assert isinstance(text_region_thr, float) + assert isinstance(center_region_thr, float) + assert isinstance(center_region_area_thr, int) + assert isinstance(local_graph_thr, float) + + self.in_channels = in_channels + self.out_channels = 6 + self.downsample_ratio = 1.0 + self.k_at_hops = k_at_hops + self.num_adjacent_linkages = num_adjacent_linkages + self.node_geo_feat_len = node_geo_feat_len + self.pooling_scale = pooling_scale + self.pooling_output_size = pooling_output_size + self.comp_shrink_ratio = comp_shrink_ratio + self.nms_thr = nms_thr + self.min_width = min_width + self.max_width = max_width + self.comp_ratio = comp_ratio + self.comp_score_thr = comp_score_thr + self.text_region_thr = text_region_thr + self.center_region_thr = center_region_thr + self.center_region_area_thr = center_region_area_thr + self.local_graph_thr = local_graph_thr + + self.out_conv = nn.Conv2D( + in_channels=self.in_channels, + out_channels=self.out_channels, + kernel_size=1, + stride=1, + padding=0, + ) + + self.graph_train = LocalGraphs( + self.k_at_hops, + self.num_adjacent_linkages, + self.node_geo_feat_len, + self.pooling_scale, + self.pooling_output_size, + self.local_graph_thr, + ) + + self.graph_test = ProposalLocalGraphs( + self.k_at_hops, + self.num_adjacent_linkages, + self.node_geo_feat_len, + self.pooling_scale, + self.pooling_output_size, + self.nms_thr, + self.min_width, + self.max_width, + self.comp_shrink_ratio, + self.comp_ratio, + self.comp_score_thr, + self.text_region_thr, + self.center_region_thr, + self.center_region_area_thr, + ) + + pool_w, pool_h = self.pooling_output_size + node_feat_len = (pool_w * pool_h) * ( + self.in_channels + self.out_channels + ) + self.node_geo_feat_len + self.gcn = GCN(node_feat_len) + + def forward(self, inputs, targets=None): + """ + Args: + inputs (Tensor): Shape of :math:`(N, C, H, W)`. + gt_comp_attribs (list[ndarray]): The padded text component + attributes. Shape: (num_component, 8). + + Returns: + tuple: Returns (pred_maps, (gcn_pred, gt_labels)). + + - | pred_maps (Tensor): Prediction map with shape + :math:`(N, C_{out}, H, W)`. + - | gcn_pred (Tensor): Prediction from GCN module, with + shape :math:`(N, 2)`. + - | gt_labels (Tensor): Ground-truth label with shape + :math:`(N, 8)`. + """ + if self.training: + assert targets is not None + gt_comp_attribs = targets[7] + pred_maps = self.out_conv(inputs) + feat_maps = paddle.concat([inputs, pred_maps], axis=1) + node_feats, adjacent_matrices, knn_inds, gt_labels = self.graph_train( + feat_maps, np.stack(gt_comp_attribs) + ) + + gcn_pred = self.gcn(node_feats, adjacent_matrices, knn_inds) + + return pred_maps, (gcn_pred, gt_labels) + else: + return self.single_test(inputs) + + def single_test(self, feat_maps): + r""" + Args: + feat_maps (Tensor): Shape of :math:`(N, C, H, W)`. + + Returns: + tuple: Returns (edge, score, text_comps). + + - | edge (ndarray): The edge array of shape :math:`(N, 2)` + where each row is a pair of text component indices + that makes up an edge in graph. + - | score (ndarray): The score array of shape :math:`(N,)`, + corresponding to the edge above. + - | text_comps (ndarray): The text components of shape + :math:`(N, 9)` where each row corresponds to one box and + its score: (x1, y1, x2, y2, x3, y3, x4, y4, score). + """ + pred_maps = self.out_conv(feat_maps) + feat_maps = paddle.concat([feat_maps, pred_maps], axis=1) + + none_flag, graph_data = self.graph_test(pred_maps, feat_maps) + + ( + local_graphs_node_feat, + adjacent_matrices, + pivots_knn_inds, + pivot_local_graphs, + text_comps, + ) = graph_data + + if none_flag: + return None, None, None + gcn_pred = self.gcn(local_graphs_node_feat, adjacent_matrices, pivots_knn_inds) + pred_labels = F.softmax(gcn_pred, axis=1) + + edges = [] + scores = [] + pivot_local_graphs = pivot_local_graphs.squeeze().numpy() + + for pivot_ind, pivot_local_graph in enumerate(pivot_local_graphs): + pivot = pivot_local_graph[0] + for k_ind, neighbor_ind in enumerate(pivots_knn_inds[pivot_ind]): + neighbor = pivot_local_graph[neighbor_ind.item()] + edges.append([pivot, neighbor]) + scores.append( + pred_labels[pivot_ind * pivots_knn_inds.shape[1] + k_ind, 1].item() + ) + + edges = np.asarray(edges) + scores = np.asarray(scores) + + return edges, scores, text_comps diff --git a/ppocr/modeling/heads/det_east_head.py b/ppocr/modeling/heads/det_east_head.py new file mode 100644 index 0000000..c0ad6e8 --- /dev/null +++ b/ppocr/modeling/heads/det_east_head.py @@ -0,0 +1,129 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride, + padding, + groups=1, + if_act=True, + act=None, + name=None, + ): + super(ConvBNLayer, self).__init__() + self.if_act = if_act + self.act = act + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + + self.bn = nn.BatchNorm( + num_channels=out_channels, + act=act, + param_attr=ParamAttr(name="bn_" + name + "_scale"), + bias_attr=ParamAttr(name="bn_" + name + "_offset"), + moving_mean_name="bn_" + name + "_mean", + moving_variance_name="bn_" + name + "_variance", + ) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + return x + + +class EASTHead(nn.Layer): + """ """ + + def __init__(self, in_channels, model_name, **kwargs): + super(EASTHead, self).__init__() + self.model_name = model_name + if self.model_name == "large": + num_outputs = [128, 64, 1, 8] + else: + num_outputs = [64, 32, 1, 8] + + self.det_conv1 = ConvBNLayer( + in_channels=in_channels, + out_channels=num_outputs[0], + kernel_size=3, + stride=1, + padding=1, + if_act=True, + act="relu", + name="det_head1", + ) + self.det_conv2 = ConvBNLayer( + in_channels=num_outputs[0], + out_channels=num_outputs[1], + kernel_size=3, + stride=1, + padding=1, + if_act=True, + act="relu", + name="det_head2", + ) + self.score_conv = ConvBNLayer( + in_channels=num_outputs[1], + out_channels=num_outputs[2], + kernel_size=1, + stride=1, + padding=0, + if_act=False, + act=None, + name="f_score", + ) + self.geo_conv = ConvBNLayer( + in_channels=num_outputs[1], + out_channels=num_outputs[3], + kernel_size=1, + stride=1, + padding=0, + if_act=False, + act=None, + name="f_geo", + ) + + def forward(self, x, targets=None): + f_det = self.det_conv1(x) + f_det = self.det_conv2(f_det) + f_score = self.score_conv(f_det) + f_score = F.sigmoid(f_score) + f_geo = self.geo_conv(f_det) + f_geo = (F.sigmoid(f_geo) - 0.5) * 2 * 800 + + pred = {"f_score": f_score, "f_geo": f_geo} + return pred diff --git a/ppocr/modeling/heads/det_fce_head.py b/ppocr/modeling/heads/det_fce_head.py new file mode 100644 index 0000000..1a90a9a --- /dev/null +++ b/ppocr/modeling/heads/det_fce_head.py @@ -0,0 +1,100 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textdet/dense_heads/fce_head.py +""" + +from paddle import nn +from paddle import ParamAttr +import paddle.nn.functional as F +from paddle.nn.initializer import Normal +import paddle +from functools import partial + + +def multi_apply(func, *args, **kwargs): + pfunc = partial(func, **kwargs) if kwargs else func + map_results = map(pfunc, *args) + return tuple(map(list, zip(*map_results))) + + +class FCEHead(nn.Layer): + """The class for implementing FCENet head. + FCENet(CVPR2021): Fourier Contour Embedding for Arbitrary-shaped Text + Detection. + + [https://arxiv.org/abs/2104.10442] + + Args: + in_channels (int): The number of input channels. + scales (list[int]) : The scale of each layer. + fourier_degree (int) : The maximum Fourier transform degree k. + """ + + def __init__(self, in_channels, fourier_degree=5): + super().__init__() + assert isinstance(in_channels, int) + + self.downsample_ratio = 1.0 + self.in_channels = in_channels + self.fourier_degree = fourier_degree + self.out_channels_cls = 4 + self.out_channels_reg = (2 * self.fourier_degree + 1) * 2 + + self.out_conv_cls = nn.Conv2D( + in_channels=self.in_channels, + out_channels=self.out_channels_cls, + kernel_size=3, + stride=1, + padding=1, + groups=1, + weight_attr=ParamAttr( + name="cls_weights", initializer=Normal(mean=0.0, std=0.01) + ), + bias_attr=True, + ) + self.out_conv_reg = nn.Conv2D( + in_channels=self.in_channels, + out_channels=self.out_channels_reg, + kernel_size=3, + stride=1, + padding=1, + groups=1, + weight_attr=ParamAttr( + name="reg_weights", initializer=Normal(mean=0.0, std=0.01) + ), + bias_attr=True, + ) + + def forward(self, feats, targets=None): + cls_res, reg_res = multi_apply(self.forward_single, feats) + level_num = len(cls_res) + outs = {} + if not self.training: + for i in range(level_num): + tr_pred = F.softmax(cls_res[i][:, 0:2, :, :], axis=1) + tcl_pred = F.softmax(cls_res[i][:, 2:, :, :], axis=1) + outs["level_{}".format(i)] = paddle.concat( + [tr_pred, tcl_pred, reg_res[i]], axis=1 + ) + else: + preds = [[cls_res[i], reg_res[i]] for i in range(level_num)] + outs["levels"] = preds + return outs + + def forward_single(self, x): + cls_predict = self.out_conv_cls(x) + reg_predict = self.out_conv_reg(x) + return cls_predict, reg_predict diff --git a/ppocr/modeling/heads/det_pse_head.py b/ppocr/modeling/heads/det_pse_head.py new file mode 100644 index 0000000..2f51621 --- /dev/null +++ b/ppocr/modeling/heads/det_pse_head.py @@ -0,0 +1,39 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/whai362/PSENet/blob/python3/models/head/psenet_head.py +""" + +from paddle import nn + + +class PSEHead(nn.Layer): + def __init__(self, in_channels, hidden_dim=256, out_channels=7, **kwargs): + super(PSEHead, self).__init__() + self.conv1 = nn.Conv2D( + in_channels, hidden_dim, kernel_size=3, stride=1, padding=1 + ) + self.bn1 = nn.BatchNorm2D(hidden_dim) + self.relu1 = nn.ReLU() + + self.conv2 = nn.Conv2D( + hidden_dim, out_channels, kernel_size=1, stride=1, padding=0 + ) + + def forward(self, x, **kwargs): + out = self.conv1(x) + out = self.relu1(self.bn1(out)) + out = self.conv2(out) + return {"maps": out} diff --git a/ppocr/modeling/heads/det_sast_head.py b/ppocr/modeling/heads/det_sast_head.py new file mode 100644 index 0000000..9246355 --- /dev/null +++ b/ppocr/modeling/heads/det_sast_head.py @@ -0,0 +1,152 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride, + groups=1, + if_act=True, + act=None, + name=None, + ): + super(ConvBNLayer, self).__init__() + self.if_act = if_act + self.act = act + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + + self.bn = nn.BatchNorm( + num_channels=out_channels, + act=act, + param_attr=ParamAttr(name="bn_" + name + "_scale"), + bias_attr=ParamAttr(name="bn_" + name + "_offset"), + moving_mean_name="bn_" + name + "_mean", + moving_variance_name="bn_" + name + "_variance", + ) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + return x + + +class SAST_Header1(nn.Layer): + def __init__(self, in_channels, **kwargs): + super(SAST_Header1, self).__init__() + out_channels = [64, 64, 128] + self.score_conv = nn.Sequential( + ConvBNLayer( + in_channels, out_channels[0], 1, 1, act="relu", name="f_score1" + ), + ConvBNLayer( + out_channels[0], out_channels[1], 3, 1, act="relu", name="f_score2" + ), + ConvBNLayer( + out_channels[1], out_channels[2], 1, 1, act="relu", name="f_score3" + ), + ConvBNLayer(out_channels[2], 1, 3, 1, act=None, name="f_score4"), + ) + self.border_conv = nn.Sequential( + ConvBNLayer( + in_channels, out_channels[0], 1, 1, act="relu", name="f_border1" + ), + ConvBNLayer( + out_channels[0], out_channels[1], 3, 1, act="relu", name="f_border2" + ), + ConvBNLayer( + out_channels[1], out_channels[2], 1, 1, act="relu", name="f_border3" + ), + ConvBNLayer(out_channels[2], 4, 3, 1, act=None, name="f_border4"), + ) + + def forward(self, x): + f_score = self.score_conv(x) + f_score = F.sigmoid(f_score) + f_border = self.border_conv(x) + return f_score, f_border + + +class SAST_Header2(nn.Layer): + def __init__(self, in_channels, **kwargs): + super(SAST_Header2, self).__init__() + out_channels = [64, 64, 128] + self.tvo_conv = nn.Sequential( + ConvBNLayer(in_channels, out_channels[0], 1, 1, act="relu", name="f_tvo1"), + ConvBNLayer( + out_channels[0], out_channels[1], 3, 1, act="relu", name="f_tvo2" + ), + ConvBNLayer( + out_channels[1], out_channels[2], 1, 1, act="relu", name="f_tvo3" + ), + ConvBNLayer(out_channels[2], 8, 3, 1, act=None, name="f_tvo4"), + ) + self.tco_conv = nn.Sequential( + ConvBNLayer(in_channels, out_channels[0], 1, 1, act="relu", name="f_tco1"), + ConvBNLayer( + out_channels[0], out_channels[1], 3, 1, act="relu", name="f_tco2" + ), + ConvBNLayer( + out_channels[1], out_channels[2], 1, 1, act="relu", name="f_tco3" + ), + ConvBNLayer(out_channels[2], 2, 3, 1, act=None, name="f_tco4"), + ) + + def forward(self, x): + f_tvo = self.tvo_conv(x) + f_tco = self.tco_conv(x) + return f_tvo, f_tco + + +class SASTHead(nn.Layer): + """ """ + + def __init__(self, in_channels, **kwargs): + super(SASTHead, self).__init__() + + self.head1 = SAST_Header1(in_channels) + self.head2 = SAST_Header2(in_channels) + + def forward(self, x, targets=None): + f_score, f_border = self.head1(x) + f_tvo, f_tco = self.head2(x) + + predicts = {} + predicts["f_score"] = f_score + predicts["f_border"] = f_border + predicts["f_tvo"] = f_tvo + predicts["f_tco"] = f_tco + return predicts diff --git a/ppocr/modeling/heads/e2e_pg_head.py b/ppocr/modeling/heads/e2e_pg_head.py new file mode 100644 index 0000000..27b0472 --- /dev/null +++ b/ppocr/modeling/heads/e2e_pg_head.py @@ -0,0 +1,282 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride, + padding, + groups=1, + if_act=True, + act=None, + name=None, + ): + super(ConvBNLayer, self).__init__() + self.if_act = if_act + self.act = act + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + + self.bn = nn.BatchNorm( + num_channels=out_channels, + act=act, + param_attr=ParamAttr(name="bn_" + name + "_scale"), + bias_attr=ParamAttr(name="bn_" + name + "_offset"), + moving_mean_name="bn_" + name + "_mean", + moving_variance_name="bn_" + name + "_variance", + use_global_stats=False, + ) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + return x + + +class PGHead(nn.Layer): + """ """ + + def __init__( + self, in_channels, character_dict_path="ppocr/utils/ic15_dict.txt", **kwargs + ): + super(PGHead, self).__init__() + + # get character_length + with open(character_dict_path, "rb") as fin: + lines = fin.readlines() + character_length = len(lines) + 1 + + self.conv_f_score1 = ConvBNLayer( + in_channels=in_channels, + out_channels=64, + kernel_size=1, + stride=1, + padding=0, + act="relu", + name="conv_f_score{}".format(1), + ) + self.conv_f_score2 = ConvBNLayer( + in_channels=64, + out_channels=64, + kernel_size=3, + stride=1, + padding=1, + act="relu", + name="conv_f_score{}".format(2), + ) + self.conv_f_score3 = ConvBNLayer( + in_channels=64, + out_channels=128, + kernel_size=1, + stride=1, + padding=0, + act="relu", + name="conv_f_score{}".format(3), + ) + + self.conv1 = nn.Conv2D( + in_channels=128, + out_channels=1, + kernel_size=3, + stride=1, + padding=1, + groups=1, + weight_attr=ParamAttr(name="conv_f_score{}".format(4)), + bias_attr=False, + ) + + self.conv_f_boder1 = ConvBNLayer( + in_channels=in_channels, + out_channels=64, + kernel_size=1, + stride=1, + padding=0, + act="relu", + name="conv_f_boder{}".format(1), + ) + self.conv_f_boder2 = ConvBNLayer( + in_channels=64, + out_channels=64, + kernel_size=3, + stride=1, + padding=1, + act="relu", + name="conv_f_boder{}".format(2), + ) + self.conv_f_boder3 = ConvBNLayer( + in_channels=64, + out_channels=128, + kernel_size=1, + stride=1, + padding=0, + act="relu", + name="conv_f_boder{}".format(3), + ) + self.conv2 = nn.Conv2D( + in_channels=128, + out_channels=4, + kernel_size=3, + stride=1, + padding=1, + groups=1, + weight_attr=ParamAttr(name="conv_f_boder{}".format(4)), + bias_attr=False, + ) + self.conv_f_char1 = ConvBNLayer( + in_channels=in_channels, + out_channels=128, + kernel_size=1, + stride=1, + padding=0, + act="relu", + name="conv_f_char{}".format(1), + ) + self.conv_f_char2 = ConvBNLayer( + in_channels=128, + out_channels=128, + kernel_size=3, + stride=1, + padding=1, + act="relu", + name="conv_f_char{}".format(2), + ) + self.conv_f_char3 = ConvBNLayer( + in_channels=128, + out_channels=256, + kernel_size=1, + stride=1, + padding=0, + act="relu", + name="conv_f_char{}".format(3), + ) + self.conv_f_char4 = ConvBNLayer( + in_channels=256, + out_channels=256, + kernel_size=3, + stride=1, + padding=1, + act="relu", + name="conv_f_char{}".format(4), + ) + self.conv_f_char5 = ConvBNLayer( + in_channels=256, + out_channels=256, + kernel_size=1, + stride=1, + padding=0, + act="relu", + name="conv_f_char{}".format(5), + ) + self.conv3 = nn.Conv2D( + in_channels=256, + out_channels=character_length, + kernel_size=3, + stride=1, + padding=1, + groups=1, + weight_attr=ParamAttr(name="conv_f_char{}".format(6)), + bias_attr=False, + ) + + self.conv_f_direc1 = ConvBNLayer( + in_channels=in_channels, + out_channels=64, + kernel_size=1, + stride=1, + padding=0, + act="relu", + name="conv_f_direc{}".format(1), + ) + self.conv_f_direc2 = ConvBNLayer( + in_channels=64, + out_channels=64, + kernel_size=3, + stride=1, + padding=1, + act="relu", + name="conv_f_direc{}".format(2), + ) + self.conv_f_direc3 = ConvBNLayer( + in_channels=64, + out_channels=128, + kernel_size=1, + stride=1, + padding=0, + act="relu", + name="conv_f_direc{}".format(3), + ) + self.conv4 = nn.Conv2D( + in_channels=128, + out_channels=2, + kernel_size=3, + stride=1, + padding=1, + groups=1, + weight_attr=ParamAttr(name="conv_f_direc{}".format(4)), + bias_attr=False, + ) + + def forward(self, x, targets=None): + f_score = self.conv_f_score1(x) + f_score = self.conv_f_score2(f_score) + f_score = self.conv_f_score3(f_score) + f_score = self.conv1(f_score) + f_score = F.sigmoid(f_score) + + # f_border + f_border = self.conv_f_boder1(x) + f_border = self.conv_f_boder2(f_border) + f_border = self.conv_f_boder3(f_border) + f_border = self.conv2(f_border) + + f_char = self.conv_f_char1(x) + f_char = self.conv_f_char2(f_char) + f_char = self.conv_f_char3(f_char) + f_char = self.conv_f_char4(f_char) + f_char = self.conv_f_char5(f_char) + f_char = self.conv3(f_char) + + f_direction = self.conv_f_direc1(x) + f_direction = self.conv_f_direc2(f_direction) + f_direction = self.conv_f_direc3(f_direction) + f_direction = self.conv4(f_direction) + + predicts = {} + predicts["f_score"] = f_score + predicts["f_border"] = f_border + predicts["f_char"] = f_char + predicts["f_direction"] = f_direction + return predicts diff --git a/ppocr/modeling/heads/gcn.py b/ppocr/modeling/heads/gcn.py new file mode 100644 index 0000000..6e6e2ea --- /dev/null +++ b/ppocr/modeling/heads/gcn.py @@ -0,0 +1,118 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textdet/modules/gcn.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F + + +class BatchNorm1D(nn.BatchNorm1D): + def __init__( + self, + num_features, + eps=1e-05, + momentum=0.1, + affine=True, + track_running_stats=True, + ): + momentum = 1 - momentum + weight_attr = None + bias_attr = None + if not affine: + weight_attr = paddle.ParamAttr(learning_rate=0.0) + bias_attr = paddle.ParamAttr(learning_rate=0.0) + super().__init__( + num_features, + momentum=momentum, + epsilon=eps, + weight_attr=weight_attr, + bias_attr=bias_attr, + use_global_stats=track_running_stats, + ) + + +class MeanAggregator(nn.Layer): + def forward(self, features, A): + x = paddle.bmm(A, features) + return x + + +class GraphConv(nn.Layer): + def __init__(self, in_dim, out_dim): + super().__init__() + self.in_dim = in_dim + self.out_dim = out_dim + self.weight = self.create_parameter( + [in_dim * 2, out_dim], default_initializer=nn.initializer.XavierUniform() + ) + self.bias = self.create_parameter( + [out_dim], + is_bias=True, + default_initializer=nn.initializer.Assign([0] * out_dim), + ) + + self.aggregator = MeanAggregator() + + def forward(self, features, A): + b, n, d = features.shape + assert d == self.in_dim + agg_feats = self.aggregator(features, A) + cat_feats = paddle.concat([features, agg_feats], axis=2) + out = paddle.einsum("bnd,df->bnf", cat_feats, self.weight) + out = F.relu(out + self.bias) + return out + + +class GCN(nn.Layer): + def __init__(self, feat_len): + super(GCN, self).__init__() + self.bn0 = BatchNorm1D(feat_len, affine=False) + self.conv1 = GraphConv(feat_len, 512) + self.conv2 = GraphConv(512, 256) + self.conv3 = GraphConv(256, 128) + self.conv4 = GraphConv(128, 64) + self.classifier = nn.Sequential( + nn.Linear(64, 32), nn.PReLU(32), nn.Linear(32, 2) + ) + + def forward(self, x, A, knn_inds): + num_local_graphs, num_max_nodes, feat_len = x.shape + + x = x.reshape([-1, feat_len]) + x = self.bn0(x) + x = x.reshape([num_local_graphs, num_max_nodes, feat_len]) + + x = self.conv1(x, A) + x = self.conv2(x, A) + x = self.conv3(x, A) + x = self.conv4(x, A) + k = knn_inds.shape[-1] + mid_feat_len = x.shape[-1] + edge_feat = paddle.zeros([num_local_graphs, k, mid_feat_len]) + for graph_ind in range(num_local_graphs): + edge_feat[graph_ind, :, :] = x[graph_ind][ + paddle.to_tensor(knn_inds[graph_ind]) + ] + edge_feat = edge_feat.reshape([-1, mid_feat_len]) + pred = self.classifier(edge_feat) + + return pred diff --git a/ppocr/modeling/heads/kie_sdmgr_head.py b/ppocr/modeling/heads/kie_sdmgr_head.py new file mode 100644 index 0000000..eca8efd --- /dev/null +++ b/ppocr/modeling/heads/kie_sdmgr_head.py @@ -0,0 +1,223 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# reference from : https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/kie/heads/sdmgr_head.py + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr + + +class SDMGRHead(nn.Layer): + def __init__( + self, + in_channels, + num_chars=92, + visual_dim=16, + fusion_dim=1024, + node_input=32, + node_embed=256, + edge_input=5, + edge_embed=256, + num_gnn=2, + num_classes=26, + bidirectional=False, + ): + super().__init__() + + self.fusion = Block([visual_dim, node_embed], node_embed, fusion_dim) + self.node_embed = nn.Embedding(num_chars, node_input, 0) + hidden = node_embed // 2 if bidirectional else node_embed + self.rnn = nn.LSTM(input_size=node_input, hidden_size=hidden, num_layers=1) + self.edge_embed = nn.Linear(edge_input, edge_embed) + self.gnn_layers = nn.LayerList( + [GNNLayer(node_embed, edge_embed) for _ in range(num_gnn)] + ) + self.node_cls = nn.Linear(node_embed, num_classes) + self.edge_cls = nn.Linear(edge_embed, 2) + + def forward(self, input, targets): + relations, texts, x = input + node_nums, char_nums = [], [] + for text in texts: + node_nums.append(text.shape[0]) + char_nums.append(paddle.sum((text > -1).astype(int), axis=-1)) + + max_num = max([char_num.max() for char_num in char_nums]) + all_nodes = paddle.concat( + [ + paddle.concat( + [text, paddle.zeros((text.shape[0], max_num - text.shape[1]))], -1 + ) + for text in texts + ] + ) + temp = paddle.clip(all_nodes, min=0).astype(int) + embed_nodes = self.node_embed(temp) + rnn_nodes, _ = self.rnn(embed_nodes) + + b, h, w = rnn_nodes.shape + nodes = paddle.zeros([b, w]) + all_nums = paddle.concat(char_nums) + valid = paddle.nonzero((all_nums > 0).astype(int)) + temp_all_nums = (paddle.gather(all_nums, valid) - 1).unsqueeze(-1).unsqueeze(-1) + temp_all_nums = paddle.expand( + temp_all_nums, + [temp_all_nums.shape[0], temp_all_nums.shape[1], rnn_nodes.shape[-1]], + ) + temp_all_nodes = paddle.gather(rnn_nodes, valid) + N, C, A = temp_all_nodes.shape + one_hot = F.one_hot(temp_all_nums[:, 0, :], num_classes=C).transpose([0, 2, 1]) + one_hot = paddle.multiply(temp_all_nodes, one_hot.astype("float32")).sum( + axis=1, keepdim=True + ) + t = one_hot.expand([N, 1, A]).squeeze(1) + nodes = paddle.scatter(nodes, valid.squeeze(1), t) + + if x is not None: + nodes = self.fusion([x, nodes]) + + all_edges = paddle.concat( + [rel.reshape([-1, rel.shape[-1]]) for rel in relations] + ) + embed_edges = self.edge_embed(all_edges.astype("float32")) + embed_edges = F.normalize(embed_edges) + + for gnn_layer in self.gnn_layers: + nodes, cat_nodes = gnn_layer(nodes, embed_edges, node_nums) + + node_cls, edge_cls = self.node_cls(nodes), self.edge_cls(cat_nodes) + return node_cls, edge_cls + + +class GNNLayer(nn.Layer): + def __init__(self, node_dim=256, edge_dim=256): + super().__init__() + self.in_fc = nn.Linear(node_dim * 2 + edge_dim, node_dim) + self.coef_fc = nn.Linear(node_dim, 1) + self.out_fc = nn.Linear(node_dim, node_dim) + self.relu = nn.ReLU() + + def forward(self, nodes, edges, nums): + start, cat_nodes = 0, [] + for num in nums: + sample_nodes = nodes[start : start + num] + cat_nodes.append( + paddle.concat( + [ + paddle.expand(sample_nodes.unsqueeze(1), [-1, num, -1]), + paddle.expand(sample_nodes.unsqueeze(0), [num, -1, -1]), + ], + -1, + ).reshape([num**2, -1]) + ) + start += num + cat_nodes = paddle.concat([paddle.concat(cat_nodes), edges], -1) + cat_nodes = self.relu(self.in_fc(cat_nodes)) + coefs = self.coef_fc(cat_nodes) + + start, residuals = 0, [] + for num in nums: + residual = F.softmax( + -paddle.eye(num).unsqueeze(-1) * 1e9 + + coefs[start : start + num**2].reshape([num, num, -1]), + 1, + ) + residuals.append( + ( + residual * cat_nodes[start : start + num**2].reshape([num, num, -1]) + ).sum(1) + ) + start += num**2 + + nodes += self.relu(self.out_fc(paddle.concat(residuals))) + return [nodes, cat_nodes] + + +class Block(nn.Layer): + def __init__( + self, + input_dims, + output_dim, + mm_dim=1600, + chunks=20, + rank=15, + shared=False, + dropout_input=0.0, + dropout_pre_lin=0.0, + dropout_output=0.0, + pos_norm="before_cat", + ): + super().__init__() + self.rank = rank + self.dropout_input = dropout_input + self.dropout_pre_lin = dropout_pre_lin + self.dropout_output = dropout_output + assert pos_norm in ["before_cat", "after_cat"] + self.pos_norm = pos_norm + # Modules + self.linear0 = nn.Linear(input_dims[0], mm_dim) + self.linear1 = self.linear0 if shared else nn.Linear(input_dims[1], mm_dim) + self.merge_linears0 = nn.LayerList() + self.merge_linears1 = nn.LayerList() + self.chunks = self.chunk_sizes(mm_dim, chunks) + for size in self.chunks: + ml0 = nn.Linear(size, size * rank) + self.merge_linears0.append(ml0) + ml1 = ml0 if shared else nn.Linear(size, size * rank) + self.merge_linears1.append(ml1) + self.linear_out = nn.Linear(mm_dim, output_dim) + + def forward(self, x): + x0 = self.linear0(x[0]) + x1 = self.linear1(x[1]) + bs = x1.shape[0] + if self.dropout_input > 0: + x0 = F.dropout(x0, p=self.dropout_input, training=self.training) + x1 = F.dropout(x1, p=self.dropout_input, training=self.training) + x0_chunks = paddle.split(x0, self.chunks, -1) + x1_chunks = paddle.split(x1, self.chunks, -1) + zs = [] + for x0_c, x1_c, m0, m1 in zip( + x0_chunks, x1_chunks, self.merge_linears0, self.merge_linears1 + ): + m = m0(x0_c) * m1(x1_c) # bs x split_size*rank + m = m.reshape([bs, self.rank, -1]) + z = paddle.sum(m, 1) + if self.pos_norm == "before_cat": + z = paddle.sqrt(F.relu(z)) - paddle.sqrt(F.relu(-z)) + z = F.normalize(z) + zs.append(z) + z = paddle.concat(zs, 1) + if self.pos_norm == "after_cat": + z = paddle.sqrt(F.relu(z)) - paddle.sqrt(F.relu(-z)) + z = F.normalize(z) + + if self.dropout_pre_lin > 0: + z = F.dropout(z, p=self.dropout_pre_lin, training=self.training) + z = self.linear_out(z) + if self.dropout_output > 0: + z = F.dropout(z, p=self.dropout_output, training=self.training) + return z + + def chunk_sizes(self, dim, chunks): + split_size = (dim + chunks - 1) // chunks + sizes_list = [split_size] * chunks + sizes_list[-1] = sizes_list[-1] - (sum(sizes_list) - dim) + return sizes_list diff --git a/ppocr/modeling/heads/local_graph.py b/ppocr/modeling/heads/local_graph.py new file mode 100644 index 0000000..f65c9aa --- /dev/null +++ b/ppocr/modeling/heads/local_graph.py @@ -0,0 +1,425 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textdet/modules/local_graph.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import paddle +import paddle.nn as nn +from ppocr.ext_op import RoIAlignRotated + + +def normalize_adjacent_matrix(A): + assert A.ndim == 2 + assert A.shape[0] == A.shape[1] + + A = A + np.eye(A.shape[0]) + d = np.sum(A, axis=0) + d = np.clip(d, 0, None) + d_inv = np.power(d, -0.5).flatten() + d_inv[np.isinf(d_inv)] = 0.0 + d_inv = np.diag(d_inv) + G = A.dot(d_inv).transpose().dot(d_inv) + return G + + +def euclidean_distance_matrix(A, B): + """Calculate the Euclidean distance matrix. + + Args: + A (ndarray): The point sequence. + B (ndarray): The point sequence with the same dimensions as A. + + returns: + D (ndarray): The Euclidean distance matrix. + """ + assert A.ndim == 2 + assert B.ndim == 2 + assert A.shape[1] == B.shape[1] + + m = A.shape[0] + n = B.shape[0] + + A_dots = (A * A).sum(axis=1).reshape((m, 1)) * np.ones(shape=(1, n)) + B_dots = (B * B).sum(axis=1) * np.ones(shape=(m, 1)) + D_squared = A_dots + B_dots - 2 * A.dot(B.T) + + zero_mask = np.less(D_squared, 0.0) + D_squared[zero_mask] = 0.0 + D = np.sqrt(D_squared) + return D + + +def feature_embedding(input_feats, out_feat_len): + """Embed features. This code was partially adapted from + https://github.com/GXYM/DRRG licensed under the MIT license. + + Args: + input_feats (ndarray): The input features of shape (N, d), where N is + the number of nodes in graph, d is the input feature vector length. + out_feat_len (int): The length of output feature vector. + + Returns: + embedded_feats (ndarray): The embedded features. + """ + assert input_feats.ndim == 2 + assert isinstance(out_feat_len, int) + assert out_feat_len >= input_feats.shape[1] + + num_nodes = input_feats.shape[0] + feat_dim = input_feats.shape[1] + feat_repeat_times = out_feat_len // feat_dim + residue_dim = out_feat_len % feat_dim + + if residue_dim > 0: + embed_wave = np.array( + [ + np.power(1000, 2.0 * (j // 2) / feat_repeat_times + 1) + for j in range(feat_repeat_times + 1) + ] + ).reshape((feat_repeat_times + 1, 1, 1)) + repeat_feats = np.repeat( + np.expand_dims(input_feats, axis=0), feat_repeat_times, axis=0 + ) + residue_feats = np.hstack( + [ + input_feats[:, 0:residue_dim], + np.zeros((num_nodes, feat_dim - residue_dim)), + ] + ) + residue_feats = np.expand_dims(residue_feats, axis=0) + repeat_feats = np.concatenate([repeat_feats, residue_feats], axis=0) + embedded_feats = repeat_feats / embed_wave + embedded_feats[:, 0::2] = np.sin(embedded_feats[:, 0::2]) + embedded_feats[:, 1::2] = np.cos(embedded_feats[:, 1::2]) + embedded_feats = np.transpose(embedded_feats, (1, 0, 2)).reshape( + (num_nodes, -1) + )[:, 0:out_feat_len] + else: + embed_wave = np.array( + [ + np.power(1000, 2.0 * (j // 2) / feat_repeat_times) + for j in range(feat_repeat_times) + ] + ).reshape((feat_repeat_times, 1, 1)) + repeat_feats = np.repeat( + np.expand_dims(input_feats, axis=0), feat_repeat_times, axis=0 + ) + embedded_feats = repeat_feats / embed_wave + embedded_feats[:, 0::2] = np.sin(embedded_feats[:, 0::2]) + embedded_feats[:, 1::2] = np.cos(embedded_feats[:, 1::2]) + embedded_feats = ( + np.transpose(embedded_feats, (1, 0, 2)) + .reshape((num_nodes, -1)) + .astype(np.float32) + ) + + return embedded_feats + + +class LocalGraphs: + def __init__( + self, + k_at_hops, + num_adjacent_linkages, + node_geo_feat_len, + pooling_scale, + pooling_output_size, + local_graph_thr, + ): + assert len(k_at_hops) == 2 + assert all(isinstance(n, int) for n in k_at_hops) + assert isinstance(num_adjacent_linkages, int) + assert isinstance(node_geo_feat_len, int) + assert isinstance(pooling_scale, float) + assert all(isinstance(n, int) for n in pooling_output_size) + assert isinstance(local_graph_thr, float) + + self.k_at_hops = k_at_hops + self.num_adjacent_linkages = num_adjacent_linkages + self.node_geo_feat_dim = node_geo_feat_len + self.pooling = RoIAlignRotated(pooling_output_size, pooling_scale) + self.local_graph_thr = local_graph_thr + + def generate_local_graphs(self, sorted_dist_inds, gt_comp_labels): + """Generate local graphs for GCN to predict which instance a text + component belongs to. + + Args: + sorted_dist_inds (ndarray): The complete graph node indices, which + is sorted according to the Euclidean distance. + gt_comp_labels(ndarray): The ground truth labels define the + instance to which the text components (nodes in graphs) belong. + + Returns: + pivot_local_graphs(list[list[int]]): The list of local graph + neighbor indices of pivots. + pivot_knns(list[list[int]]): The list of k-nearest neighbor indices + of pivots. + """ + + assert sorted_dist_inds.ndim == 2 + assert ( + sorted_dist_inds.shape[0] + == sorted_dist_inds.shape[1] + == gt_comp_labels.shape[0] + ) + + knn_graph = sorted_dist_inds[:, 1 : self.k_at_hops[0] + 1] + pivot_local_graphs = [] + pivot_knns = [] + for pivot_ind, knn in enumerate(knn_graph): + local_graph_neighbors = set(knn) + + for neighbor_ind in knn: + local_graph_neighbors.update( + set(sorted_dist_inds[neighbor_ind, 1 : self.k_at_hops[1] + 1]) + ) + + local_graph_neighbors.discard(pivot_ind) + pivot_local_graph = list(local_graph_neighbors) + pivot_local_graph.insert(0, pivot_ind) + pivot_knn = [pivot_ind] + list(knn) + + if pivot_ind < 1: + pivot_local_graphs.append(pivot_local_graph) + pivot_knns.append(pivot_knn) + else: + add_flag = True + for graph_ind, added_knn in enumerate(pivot_knns): + added_pivot_ind = added_knn[0] + added_local_graph = pivot_local_graphs[graph_ind] + + union = len( + set(pivot_local_graph[1:]).union(set(added_local_graph[1:])) + ) + intersect = len( + set(pivot_local_graph[1:]).intersection( + set(added_local_graph[1:]) + ) + ) + local_graph_iou = intersect / (union + 1e-8) + + if ( + local_graph_iou > self.local_graph_thr + and pivot_ind in added_knn + and gt_comp_labels[added_pivot_ind] == gt_comp_labels[pivot_ind] + and gt_comp_labels[pivot_ind] != 0 + ): + add_flag = False + break + if add_flag: + pivot_local_graphs.append(pivot_local_graph) + pivot_knns.append(pivot_knn) + + return pivot_local_graphs, pivot_knns + + def generate_gcn_input( + self, + node_feat_batch, + node_label_batch, + local_graph_batch, + knn_batch, + sorted_dist_ind_batch, + ): + """Generate graph convolution network input data. + + Args: + node_feat_batch (List[Tensor]): The batched graph node features. + node_label_batch (List[ndarray]): The batched text component + labels. + local_graph_batch (List[List[list[int]]]): The local graph node + indices of image batch. + knn_batch (List[List[list[int]]]): The knn graph node indices of + image batch. + sorted_dist_ind_batch (list[ndarray]): The node indices sorted + according to the Euclidean distance. + + Returns: + local_graphs_node_feat (Tensor): The node features of graph. + adjacent_matrices (Tensor): The adjacent matrices of local graphs. + pivots_knn_inds (Tensor): The k-nearest neighbor indices in + local graph. + gt_linkage (Tensor): The surpervision signal of GCN for linkage + prediction. + """ + assert isinstance(node_feat_batch, list) + assert isinstance(node_label_batch, list) + assert isinstance(local_graph_batch, list) + assert isinstance(knn_batch, list) + assert isinstance(sorted_dist_ind_batch, list) + + num_max_nodes = max( + [ + len(pivot_local_graph) + for pivot_local_graphs in local_graph_batch + for pivot_local_graph in pivot_local_graphs + ] + ) + + local_graphs_node_feat = [] + adjacent_matrices = [] + pivots_knn_inds = [] + pivots_gt_linkage = [] + + for batch_ind, sorted_dist_inds in enumerate(sorted_dist_ind_batch): + node_feats = node_feat_batch[batch_ind] + pivot_local_graphs = local_graph_batch[batch_ind] + pivot_knns = knn_batch[batch_ind] + node_labels = node_label_batch[batch_ind] + + for graph_ind, pivot_knn in enumerate(pivot_knns): + pivot_local_graph = pivot_local_graphs[graph_ind] + num_nodes = len(pivot_local_graph) + pivot_ind = pivot_local_graph[0] + node2ind_map = {j: i for i, j in enumerate(pivot_local_graph)} + + knn_inds = paddle.to_tensor([node2ind_map[i] for i in pivot_knn[1:]]) + pivot_feats = node_feats[pivot_ind] + normalized_feats = ( + node_feats[paddle.to_tensor(pivot_local_graph)] - pivot_feats + ) + + adjacent_matrix = np.zeros((num_nodes, num_nodes), dtype=np.float32) + for node in pivot_local_graph: + neighbors = sorted_dist_inds[ + node, 1 : self.num_adjacent_linkages + 1 + ] + for neighbor in neighbors: + if neighbor in pivot_local_graph: + adjacent_matrix[ + node2ind_map[node], node2ind_map[neighbor] + ] = 1 + adjacent_matrix[ + node2ind_map[neighbor], node2ind_map[node] + ] = 1 + + adjacent_matrix = normalize_adjacent_matrix(adjacent_matrix) + pad_adjacent_matrix = paddle.zeros((num_max_nodes, num_max_nodes)) + pad_adjacent_matrix[:num_nodes, :num_nodes] = paddle.cast( + paddle.to_tensor(adjacent_matrix), "float32" + ) + + pad_normalized_feats = paddle.concat( + [ + normalized_feats, + paddle.zeros( + (num_max_nodes - num_nodes, normalized_feats.shape[1]) + ), + ], + axis=0, + ) + local_graph_labels = node_labels[pivot_local_graph] + knn_labels = local_graph_labels[knn_inds.numpy()] + link_labels = ( + (node_labels[pivot_ind] == knn_labels) + & (node_labels[pivot_ind] > 0) + ).astype(np.int64) + link_labels = paddle.to_tensor(link_labels) + + local_graphs_node_feat.append(pad_normalized_feats) + adjacent_matrices.append(pad_adjacent_matrix) + pivots_knn_inds.append(knn_inds) + pivots_gt_linkage.append(link_labels) + + local_graphs_node_feat = paddle.stack(local_graphs_node_feat, 0) + adjacent_matrices = paddle.stack(adjacent_matrices, 0) + pivots_knn_inds = paddle.stack(pivots_knn_inds, 0) + pivots_gt_linkage = paddle.stack(pivots_gt_linkage, 0) + + return ( + local_graphs_node_feat, + adjacent_matrices, + pivots_knn_inds, + pivots_gt_linkage, + ) + + def __call__(self, feat_maps, comp_attribs): + """Generate local graphs as GCN input. + + Args: + feat_maps (Tensor): The feature maps to extract the content + features of text components. + comp_attribs (ndarray): The text component attributes. + + Returns: + local_graphs_node_feat (Tensor): The node features of graph. + adjacent_matrices (Tensor): The adjacent matrices of local graphs. + pivots_knn_inds (Tensor): The k-nearest neighbor indices in local + graph. + gt_linkage (Tensor): The surpervision signal of GCN for linkage + prediction. + """ + + assert isinstance(feat_maps, paddle.Tensor) + assert comp_attribs.ndim == 3 + assert comp_attribs.shape[2] == 8 + + sorted_dist_inds_batch = [] + local_graph_batch = [] + knn_batch = [] + node_feat_batch = [] + node_label_batch = [] + + for batch_ind in range(comp_attribs.shape[0]): + num_comps = int(comp_attribs[batch_ind, 0, 0]) + comp_geo_attribs = comp_attribs[batch_ind, :num_comps, 1:7] + node_labels = comp_attribs[batch_ind, :num_comps, 7].astype(np.int32) + + comp_centers = comp_geo_attribs[:, 0:2] + distance_matrix = euclidean_distance_matrix(comp_centers, comp_centers) + + batch_id = ( + np.zeros((comp_geo_attribs.shape[0], 1), dtype=np.float32) * batch_ind + ) + comp_geo_attribs[:, -2] = np.clip(comp_geo_attribs[:, -2], -1, 1) + angle = np.arccos(comp_geo_attribs[:, -2]) * np.sign( + comp_geo_attribs[:, -1] + ) + angle = angle.reshape((-1, 1)) + rotated_rois = np.hstack([batch_id, comp_geo_attribs[:, :-2], angle]) + rois = paddle.to_tensor(rotated_rois) + content_feats = self.pooling(feat_maps[batch_ind].unsqueeze(0), rois) + + content_feats = content_feats.reshape([content_feats.shape[0], -1]) + geo_feats = feature_embedding(comp_geo_attribs, self.node_geo_feat_dim) + geo_feats = paddle.to_tensor(geo_feats) + node_feats = paddle.concat([content_feats, geo_feats], axis=-1) + + sorted_dist_inds = np.argsort(distance_matrix, axis=1) + pivot_local_graphs, pivot_knns = self.generate_local_graphs( + sorted_dist_inds, node_labels + ) + + node_feat_batch.append(node_feats) + node_label_batch.append(node_labels) + local_graph_batch.append(pivot_local_graphs) + knn_batch.append(pivot_knns) + sorted_dist_inds_batch.append(sorted_dist_inds) + + (node_feats, adjacent_matrices, knn_inds, gt_linkage) = self.generate_gcn_input( + node_feat_batch, + node_label_batch, + local_graph_batch, + knn_batch, + sorted_dist_inds_batch, + ) + + return node_feats, adjacent_matrices, knn_inds, gt_linkage diff --git a/ppocr/modeling/heads/proposal_local_graph.py b/ppocr/modeling/heads/proposal_local_graph.py new file mode 100644 index 0000000..7bf0765 --- /dev/null +++ b/ppocr/modeling/heads/proposal_local_graph.py @@ -0,0 +1,476 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textdet/modules/proposal_local_graph.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import cv2 +import numpy as np +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from lanms import merge_quadrangle_n9 as la_nms + +from ppocr.ext_op import RoIAlignRotated +from .local_graph import ( + euclidean_distance_matrix, + feature_embedding, + normalize_adjacent_matrix, +) + + +def fill_hole(input_mask): + h, w = input_mask.shape + canvas = np.zeros((h + 2, w + 2), np.uint8) + canvas[1 : h + 1, 1 : w + 1] = input_mask.copy() + + mask = np.zeros((h + 4, w + 4), np.uint8) + + cv2.floodFill(canvas, mask, (0, 0), 1) + canvas = canvas[1 : h + 1, 1 : w + 1].astype(np.bool_) + + return ~canvas | input_mask + + +class ProposalLocalGraphs: + def __init__( + self, + k_at_hops, + num_adjacent_linkages, + node_geo_feat_len, + pooling_scale, + pooling_output_size, + nms_thr, + min_width, + max_width, + comp_shrink_ratio, + comp_w_h_ratio, + comp_score_thr, + text_region_thr, + center_region_thr, + center_region_area_thr, + ): + assert len(k_at_hops) == 2 + assert isinstance(k_at_hops, tuple) + assert isinstance(num_adjacent_linkages, int) + assert isinstance(node_geo_feat_len, int) + assert isinstance(pooling_scale, float) + assert isinstance(pooling_output_size, tuple) + assert isinstance(nms_thr, float) + assert isinstance(min_width, float) + assert isinstance(max_width, float) + assert isinstance(comp_shrink_ratio, float) + assert isinstance(comp_w_h_ratio, float) + assert isinstance(comp_score_thr, float) + assert isinstance(text_region_thr, float) + assert isinstance(center_region_thr, float) + assert isinstance(center_region_area_thr, int) + + self.k_at_hops = k_at_hops + self.active_connection = num_adjacent_linkages + self.local_graph_depth = len(self.k_at_hops) + self.node_geo_feat_dim = node_geo_feat_len + self.pooling = RoIAlignRotated(pooling_output_size, pooling_scale) + self.nms_thr = nms_thr + self.min_width = min_width + self.max_width = max_width + self.comp_shrink_ratio = comp_shrink_ratio + self.comp_w_h_ratio = comp_w_h_ratio + self.comp_score_thr = comp_score_thr + self.text_region_thr = text_region_thr + self.center_region_thr = center_region_thr + self.center_region_area_thr = center_region_area_thr + + def propose_comps( + self, + score_map, + top_height_map, + bot_height_map, + sin_map, + cos_map, + comp_score_thr, + min_width, + max_width, + comp_shrink_ratio, + comp_w_h_ratio, + ): + """Propose text components. + + Args: + score_map (ndarray): The score map for NMS. + top_height_map (ndarray): The predicted text height map from each + pixel in text center region to top sideline. + bot_height_map (ndarray): The predicted text height map from each + pixel in text center region to bottom sideline. + sin_map (ndarray): The predicted sin(theta) map. + cos_map (ndarray): The predicted cos(theta) map. + comp_score_thr (float): The score threshold of text component. + min_width (float): The minimum width of text components. + max_width (float): The maximum width of text components. + comp_shrink_ratio (float): The shrink ratio of text components. + comp_w_h_ratio (float): The width to height ratio of text + components. + + Returns: + text_comps (ndarray): The text components. + """ + + comp_centers = np.argwhere(score_map > comp_score_thr) + comp_centers = comp_centers[np.argsort(comp_centers[:, 0])] + y = comp_centers[:, 0] + x = comp_centers[:, 1] + + top_height = top_height_map[y, x].reshape((-1, 1)) * comp_shrink_ratio + bot_height = bot_height_map[y, x].reshape((-1, 1)) * comp_shrink_ratio + sin = sin_map[y, x].reshape((-1, 1)) + cos = cos_map[y, x].reshape((-1, 1)) + + top_mid_pts = comp_centers + np.hstack([top_height * sin, top_height * cos]) + bot_mid_pts = comp_centers - np.hstack([bot_height * sin, bot_height * cos]) + + width = (top_height + bot_height) * comp_w_h_ratio + width = np.clip(width, min_width, max_width) + r = width / 2 + + tl = top_mid_pts[:, ::-1] - np.hstack([-r * sin, r * cos]) + tr = top_mid_pts[:, ::-1] + np.hstack([-r * sin, r * cos]) + br = bot_mid_pts[:, ::-1] + np.hstack([-r * sin, r * cos]) + bl = bot_mid_pts[:, ::-1] - np.hstack([-r * sin, r * cos]) + text_comps = np.hstack([tl, tr, br, bl]).astype(np.float32) + + score = score_map[y, x].reshape((-1, 1)) + text_comps = np.hstack([text_comps, score]) + + return text_comps + + def propose_comps_and_attribs( + self, + text_region_map, + center_region_map, + top_height_map, + bot_height_map, + sin_map, + cos_map, + ): + """Generate text components and attributes. + + Args: + text_region_map (ndarray): The predicted text region probability + map. + center_region_map (ndarray): The predicted text center region + probability map. + top_height_map (ndarray): The predicted text height map from each + pixel in text center region to top sideline. + bot_height_map (ndarray): The predicted text height map from each + pixel in text center region to bottom sideline. + sin_map (ndarray): The predicted sin(theta) map. + cos_map (ndarray): The predicted cos(theta) map. + + Returns: + comp_attribs (ndarray): The text component attributes. + text_comps (ndarray): The text components. + """ + + assert ( + text_region_map.shape + == center_region_map.shape + == top_height_map.shape + == bot_height_map.shape + == sin_map.shape + == cos_map.shape + ) + text_mask = text_region_map > self.text_region_thr + center_region_mask = (center_region_map > self.center_region_thr) * text_mask + + scale = np.sqrt(1.0 / (sin_map**2 + cos_map**2 + 1e-8)) + sin_map, cos_map = sin_map * scale, cos_map * scale + + center_region_mask = fill_hole(center_region_mask) + center_region_contours, _ = cv2.findContours( + center_region_mask.astype(np.uint8), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE + ) + + mask_sz = center_region_map.shape + comp_list = [] + for contour in center_region_contours: + current_center_mask = np.zeros(mask_sz) + cv2.drawContours(current_center_mask, [contour], -1, 1, -1) + if current_center_mask.sum() <= self.center_region_area_thr: + continue + score_map = text_region_map * current_center_mask + + text_comps = self.propose_comps( + score_map, + top_height_map, + bot_height_map, + sin_map, + cos_map, + self.comp_score_thr, + self.min_width, + self.max_width, + self.comp_shrink_ratio, + self.comp_w_h_ratio, + ) + + text_comps = la_nms(text_comps, self.nms_thr) + text_comp_mask = np.zeros(mask_sz) + text_comp_boxes = text_comps[:, :8].reshape((-1, 4, 2)).astype(np.int32) + + cv2.drawContours(text_comp_mask, text_comp_boxes, -1, 1, -1) + if (text_comp_mask * text_mask).sum() < text_comp_mask.sum() * 0.5: + continue + if text_comps.shape[-1] > 0: + comp_list.append(text_comps) + + if len(comp_list) <= 0: + return None, None + + text_comps = np.vstack(comp_list) + text_comp_boxes = text_comps[:, :8].reshape((-1, 4, 2)) + centers = np.mean(text_comp_boxes, axis=1).astype(np.int32) + x = centers[:, 0] + y = centers[:, 1] + + scores = [] + for text_comp_box in text_comp_boxes: + text_comp_box[:, 0] = np.clip(text_comp_box[:, 0], 0, mask_sz[1] - 1) + text_comp_box[:, 1] = np.clip(text_comp_box[:, 1], 0, mask_sz[0] - 1) + min_coord = np.min(text_comp_box, axis=0).astype(np.int32) + max_coord = np.max(text_comp_box, axis=0).astype(np.int32) + text_comp_box = text_comp_box - min_coord + box_sz = max_coord - min_coord + 1 + temp_comp_mask = np.zeros((box_sz[1], box_sz[0]), dtype=np.uint8) + cv2.fillPoly(temp_comp_mask, [text_comp_box.astype(np.int32)], 1) + temp_region_patch = text_region_map[ + min_coord[1] : (max_coord[1] + 1), min_coord[0] : (max_coord[0] + 1) + ] + score = cv2.mean(temp_region_patch, temp_comp_mask)[0] + scores.append(score) + scores = np.array(scores).reshape((-1, 1)) + text_comps = np.hstack([text_comps[:, :-1], scores]) + + h = top_height_map[y, x].reshape((-1, 1)) + bot_height_map[y, x].reshape( + (-1, 1) + ) + w = np.clip(h * self.comp_w_h_ratio, self.min_width, self.max_width) + sin = sin_map[y, x].reshape((-1, 1)) + cos = cos_map[y, x].reshape((-1, 1)) + + x = x.reshape((-1, 1)) + y = y.reshape((-1, 1)) + comp_attribs = np.hstack([x, y, h, w, cos, sin]) + + return comp_attribs, text_comps + + def generate_local_graphs(self, sorted_dist_inds, node_feats): + """Generate local graphs and graph convolution network input data. + + Args: + sorted_dist_inds (ndarray): The node indices sorted according to + the Euclidean distance. + node_feats (tensor): The features of nodes in graph. + + Returns: + local_graphs_node_feats (tensor): The features of nodes in local + graphs. + adjacent_matrices (tensor): The adjacent matrices. + pivots_knn_inds (tensor): The k-nearest neighbor indices in + local graphs. + pivots_local_graphs (tensor): The indices of nodes in local + graphs. + """ + + assert sorted_dist_inds.ndim == 2 + assert ( + sorted_dist_inds.shape[0] + == sorted_dist_inds.shape[1] + == node_feats.shape[0] + ) + + knn_graph = sorted_dist_inds[:, 1 : self.k_at_hops[0] + 1] + pivot_local_graphs = [] + pivot_knns = [] + + for pivot_ind, knn in enumerate(knn_graph): + local_graph_neighbors = set(knn) + + for neighbor_ind in knn: + local_graph_neighbors.update( + set(sorted_dist_inds[neighbor_ind, 1 : self.k_at_hops[1] + 1]) + ) + + local_graph_neighbors.discard(pivot_ind) + pivot_local_graph = list(local_graph_neighbors) + pivot_local_graph.insert(0, pivot_ind) + pivot_knn = [pivot_ind] + list(knn) + + pivot_local_graphs.append(pivot_local_graph) + pivot_knns.append(pivot_knn) + + num_max_nodes = max( + [len(pivot_local_graph) for pivot_local_graph in pivot_local_graphs] + ) + + local_graphs_node_feat = [] + adjacent_matrices = [] + pivots_knn_inds = [] + pivots_local_graphs = [] + + for graph_ind, pivot_knn in enumerate(pivot_knns): + pivot_local_graph = pivot_local_graphs[graph_ind] + num_nodes = len(pivot_local_graph) + pivot_ind = pivot_local_graph[0] + node2ind_map = {j: i for i, j in enumerate(pivot_local_graph)} + + knn_inds = paddle.cast( + paddle.to_tensor([node2ind_map[i] for i in pivot_knn[1:]]), "int64" + ) + pivot_feats = node_feats[pivot_ind] + normalized_feats = ( + node_feats[paddle.to_tensor(pivot_local_graph)] - pivot_feats + ) + + adjacent_matrix = np.zeros((num_nodes, num_nodes), dtype=np.float32) + for node in pivot_local_graph: + neighbors = sorted_dist_inds[node, 1 : self.active_connection + 1] + for neighbor in neighbors: + if neighbor in pivot_local_graph: + adjacent_matrix[node2ind_map[node], node2ind_map[neighbor]] = 1 + adjacent_matrix[node2ind_map[neighbor], node2ind_map[node]] = 1 + + adjacent_matrix = normalize_adjacent_matrix(adjacent_matrix) + pad_adjacent_matrix = paddle.zeros( + (num_max_nodes, num_max_nodes), + ) + pad_adjacent_matrix[:num_nodes, :num_nodes] = paddle.cast( + paddle.to_tensor(adjacent_matrix), "float32" + ) + + pad_normalized_feats = paddle.concat( + [ + normalized_feats, + paddle.zeros( + (num_max_nodes - num_nodes, normalized_feats.shape[1]), + ), + ], + axis=0, + ) + + local_graph_nodes = paddle.to_tensor(pivot_local_graph) + local_graph_nodes = paddle.concat( + [ + local_graph_nodes, + paddle.zeros([num_max_nodes - num_nodes], dtype="int64"), + ], + axis=-1, + ) + + local_graphs_node_feat.append(pad_normalized_feats) + adjacent_matrices.append(pad_adjacent_matrix) + pivots_knn_inds.append(knn_inds) + pivots_local_graphs.append(local_graph_nodes) + + local_graphs_node_feat = paddle.stack(local_graphs_node_feat, 0) + adjacent_matrices = paddle.stack(adjacent_matrices, 0) + pivots_knn_inds = paddle.stack(pivots_knn_inds, 0) + pivots_local_graphs = paddle.stack(pivots_local_graphs, 0) + + return ( + local_graphs_node_feat, + adjacent_matrices, + pivots_knn_inds, + pivots_local_graphs, + ) + + def __call__(self, preds, feat_maps): + """Generate local graphs and graph convolutional network input data. + + Args: + preds (tensor): The predicted maps. + feat_maps (tensor): The feature maps to extract content feature of + text components. + + Returns: + none_flag (bool): The flag showing whether the number of proposed + text components is 0. + local_graphs_node_feats (tensor): The features of nodes in local + graphs. + adjacent_matrices (tensor): The adjacent matrices. + pivots_knn_inds (tensor): The k-nearest neighbor indices in + local graphs. + pivots_local_graphs (tensor): The indices of nodes in local + graphs. + text_comps (ndarray): The predicted text components. + """ + if preds.ndim == 4: + assert preds.shape[0] == 1 + preds = paddle.squeeze(preds) + pred_text_region = F.sigmoid(preds[0]).numpy() + pred_center_region = F.sigmoid(preds[1]).numpy() + pred_sin_map = preds[2].numpy() + pred_cos_map = preds[3].numpy() + pred_top_height_map = preds[4].numpy() + pred_bot_height_map = preds[5].numpy() + + comp_attribs, text_comps = self.propose_comps_and_attribs( + pred_text_region, + pred_center_region, + pred_top_height_map, + pred_bot_height_map, + pred_sin_map, + pred_cos_map, + ) + + if comp_attribs is None or len(comp_attribs) < 2: + none_flag = True + return none_flag, (0, 0, 0, 0, 0) + + comp_centers = comp_attribs[:, 0:2] + distance_matrix = euclidean_distance_matrix(comp_centers, comp_centers) + + geo_feats = feature_embedding(comp_attribs, self.node_geo_feat_dim) + geo_feats = paddle.to_tensor(geo_feats) + + batch_id = np.zeros((comp_attribs.shape[0], 1), dtype=np.float32) + comp_attribs = comp_attribs.astype(np.float32) + angle = np.arccos(comp_attribs[:, -2]) * np.sign(comp_attribs[:, -1]) + angle = angle.reshape((-1, 1)) + rotated_rois = np.hstack([batch_id, comp_attribs[:, :-2], angle]) + rois = paddle.to_tensor(rotated_rois) + + content_feats = self.pooling(feat_maps, rois) + content_feats = content_feats.reshape([content_feats.shape[0], -1]) + node_feats = paddle.concat([content_feats, geo_feats], axis=-1) + + sorted_dist_inds = np.argsort(distance_matrix, axis=1) + ( + local_graphs_node_feat, + adjacent_matrices, + pivots_knn_inds, + pivots_local_graphs, + ) = self.generate_local_graphs(sorted_dist_inds, node_feats) + + none_flag = False + return none_flag, ( + local_graphs_node_feat, + adjacent_matrices, + pivots_knn_inds, + pivots_local_graphs, + text_comps, + ) diff --git a/ppocr/modeling/heads/rec_abinet_head.py b/ppocr/modeling/heads/rec_abinet_head.py new file mode 100644 index 0000000..15628c9 --- /dev/null +++ b/ppocr/modeling/heads/rec_abinet_head.py @@ -0,0 +1,299 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/FangShancheng/ABINet/tree/main/modules +""" + +import math +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle.nn import LayerList +from ppocr.modeling.heads.rec_nrtr_head import TransformerBlock, PositionalEncoding + + +class BCNLanguage(nn.Layer): + def __init__( + self, + d_model=512, + nhead=8, + num_layers=4, + dim_feedforward=2048, + dropout=0.0, + max_length=25, + detach=True, + num_classes=37, + ): + super().__init__() + + self.d_model = d_model + self.detach = detach + self.max_length = max_length + 1 # additional stop token + self.proj = nn.Linear(num_classes, d_model, bias_attr=False) + self.token_encoder = PositionalEncoding( + dropout=0.1, dim=d_model, max_len=self.max_length + ) + self.pos_encoder = PositionalEncoding( + dropout=0, dim=d_model, max_len=self.max_length + ) + + self.decoder = nn.LayerList( + [ + TransformerBlock( + d_model=d_model, + nhead=nhead, + dim_feedforward=dim_feedforward, + attention_dropout_rate=dropout, + residual_dropout_rate=dropout, + with_self_attn=False, + with_cross_attn=True, + ) + for i in range(num_layers) + ] + ) + + self.cls = nn.Linear(d_model, num_classes) + + def forward(self, tokens, lengths): + """ + Args: + tokens: (B, N, C) where N is length, B is batch size and C is classes number + lengths: (B,) + """ + if self.detach: + tokens = tokens.detach() + embed = self.proj(tokens) # (B, N, C) + embed = self.token_encoder(embed) # (B, N, C) + padding_mask = _get_mask(lengths, self.max_length) + zeros = paddle.zeros_like(embed) # (B, N, C) + query = self.pos_encoder(zeros) + for decoder_layer in self.decoder: + query = decoder_layer(query, embed, cross_mask=padding_mask) + output = query # (B, N, C) + + logits = self.cls(output) # (B, N, C) + + return output, logits + + +def encoder_layer(in_c, out_c, k=3, s=2, p=1): + return nn.Sequential( + nn.Conv2D(in_c, out_c, k, s, p), nn.BatchNorm2D(out_c), nn.ReLU() + ) + + +def decoder_layer( + in_c, out_c, k=3, s=1, p=1, mode="nearest", scale_factor=None, size=None +): + align_corners = False if mode == "nearest" else True + return nn.Sequential( + nn.Upsample( + size=size, scale_factor=scale_factor, mode=mode, align_corners=align_corners + ), + nn.Conv2D(in_c, out_c, k, s, p), + nn.BatchNorm2D(out_c), + nn.ReLU(), + ) + + +class PositionAttention(nn.Layer): + def __init__( + self, + max_length, + in_channels=512, + num_channels=64, + h=8, + w=32, + mode="nearest", + **kwargs, + ): + super().__init__() + self.max_length = max_length + self.k_encoder = nn.Sequential( + encoder_layer(in_channels, num_channels, s=(1, 2)), + encoder_layer(num_channels, num_channels, s=(2, 2)), + encoder_layer(num_channels, num_channels, s=(2, 2)), + encoder_layer(num_channels, num_channels, s=(2, 2)), + ) + self.k_decoder = nn.Sequential( + decoder_layer(num_channels, num_channels, scale_factor=2, mode=mode), + decoder_layer(num_channels, num_channels, scale_factor=2, mode=mode), + decoder_layer(num_channels, num_channels, scale_factor=2, mode=mode), + decoder_layer(num_channels, in_channels, size=(h, w), mode=mode), + ) + + self.pos_encoder = PositionalEncoding( + dropout=0, dim=in_channels, max_len=max_length + ) + self.project = nn.Linear(in_channels, in_channels) + + def forward(self, x): + B, C, H, W = x.shape + k, v = x, x + + # calculate key vector + features = [] + for i in range(0, len(self.k_encoder)): + k = self.k_encoder[i](k) + features.append(k) + for i in range(0, len(self.k_decoder) - 1): + k = self.k_decoder[i](k) + # print(k.shape, features[len(self.k_decoder) - 2 - i].shape) + k = k + features[len(self.k_decoder) - 2 - i] + k = self.k_decoder[-1](k) + + # calculate query vector + # TODO q=f(q,k) + zeros = paddle.zeros((B, self.max_length, C), dtype=x.dtype) # (B, N, C) + q = self.pos_encoder(zeros) # (B, N, C) + q = self.project(q) # (B, N, C) + + # calculate attention + attn_scores = q @ k.flatten(2) # (B, N, (H*W)) + attn_scores = attn_scores / (C**0.5) + attn_scores = F.softmax(attn_scores, axis=-1) + + v = v.flatten(2).transpose([0, 2, 1]) # (B, (H*W), C) + attn_vecs = attn_scores @ v # (B, N, C) + + return attn_vecs, attn_scores.reshape([0, self.max_length, H, W]) + + +class ABINetHead(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + d_model=512, + nhead=8, + num_layers=3, + dim_feedforward=2048, + dropout=0.1, + max_length=25, + use_lang=False, + iter_size=1, + image_size=(32, 128), + ): + super().__init__() + self.max_length = max_length + 1 + h, w = image_size[0] // 4, image_size[1] // 4 + self.pos_encoder = PositionalEncoding(dropout=0.1, dim=d_model, max_len=h * w) + self.encoder = nn.LayerList( + [ + TransformerBlock( + d_model=d_model, + nhead=nhead, + dim_feedforward=dim_feedforward, + attention_dropout_rate=dropout, + residual_dropout_rate=dropout, + with_self_attn=True, + with_cross_attn=False, + ) + for i in range(num_layers) + ] + ) + self.decoder = PositionAttention( + max_length=max_length + 1, mode="nearest", h=h, w=w # additional stop token + ) + self.out_channels = out_channels + self.cls = nn.Linear(d_model, self.out_channels) + self.use_lang = use_lang + if use_lang: + self.iter_size = iter_size + self.language = BCNLanguage( + d_model=d_model, + nhead=nhead, + num_layers=4, + dim_feedforward=dim_feedforward, + dropout=dropout, + max_length=max_length, + num_classes=self.out_channels, + ) + # alignment + self.w_att_align = nn.Linear(2 * d_model, d_model) + self.cls_align = nn.Linear(d_model, self.out_channels) + + def forward(self, x, targets=None): + x = x.transpose([0, 2, 3, 1]) + _, H, W, C = x.shape + feature = x.flatten(1, 2) + feature = self.pos_encoder(feature) + for encoder_layer in self.encoder: + feature = encoder_layer(feature) + feature = feature.reshape([0, H, W, C]).transpose([0, 3, 1, 2]) + v_feature, attn_scores = self.decoder(feature) # (B, N, C), (B, C, H, W) + vis_logits = self.cls(v_feature) # (B, N, C) + logits = vis_logits + vis_lengths = _get_length(vis_logits) + if self.use_lang: + align_logits = vis_logits + align_lengths = vis_lengths + all_l_res, all_a_res = [], [] + for i in range(self.iter_size): + tokens = F.softmax(align_logits, axis=-1) + lengths = align_lengths + lengths = paddle.clip( + lengths, 2, self.max_length + ) # TODO:move to language model + l_feature, l_logits = self.language(tokens, lengths) + + # alignment + all_l_res.append(l_logits) + fuse = paddle.concat((l_feature, v_feature), -1) + f_att = F.sigmoid(self.w_att_align(fuse)) + output = f_att * v_feature + (1 - f_att) * l_feature + align_logits = self.cls_align(output) # (B, N, C) + + align_lengths = _get_length(align_logits) + all_a_res.append(align_logits) + if self.training: + return {"align": all_a_res, "lang": all_l_res, "vision": vis_logits} + else: + logits = align_logits + if self.training: + return logits + else: + return F.softmax(logits, -1) + + +def _get_length(logit): + """Greed decoder to obtain length from logit""" + out = logit.argmax(-1) == 0 + abn = out.any(-1) + out_int = out.cast("int32") + out = (out_int.cumsum(-1) == 1) & out + out = out.cast("int32") + out = out.argmax(-1) + out = out + 1 + len_seq = paddle.zeros_like(out) + logit.shape[1] + out = paddle.where(abn, out, len_seq) + return out + + +def _get_mask(length, max_length): + """Generate a square mask for the sequence. The masked positions are filled with float('-inf'). + Unmasked positions are filled with float(0.0). + """ + length = length.unsqueeze(-1) + B = length.shape[0] + grid = paddle.arange(0, max_length).unsqueeze(0).tile([B, 1]) + zero_mask = paddle.zeros([B, max_length], dtype="float32") + inf_mask = paddle.full([B, max_length], "-inf", dtype="float32") + diag_mask = paddle.diag( + paddle.full([max_length], "-inf", dtype=paddle.float32), offset=0, name=None + ) + mask = paddle.where(grid >= length, inf_mask, zero_mask) + mask = mask.unsqueeze(1) + diag_mask + return mask.unsqueeze(1) diff --git a/ppocr/modeling/heads/rec_aster_head.py b/ppocr/modeling/heads/rec_aster_head.py new file mode 100644 index 0000000..60eb995 --- /dev/null +++ b/ppocr/modeling/heads/rec_aster_head.py @@ -0,0 +1,404 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/ayumiymk/aster.pytorch/blob/master/lib/models/attention_recognition_head.py +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import sys + +import paddle +from paddle import nn +from paddle.nn import functional as F + + +class AsterHead(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + sDim, + attDim, + max_len_labels, + time_step=25, + beam_width=5, + **kwargs, + ): + super(AsterHead, self).__init__() + self.num_classes = out_channels + self.in_planes = in_channels + self.sDim = sDim + self.attDim = attDim + self.max_len_labels = max_len_labels + self.decoder = AttentionRecognitionHead( + in_channels, out_channels, sDim, attDim, max_len_labels + ) + self.time_step = time_step + self.embedder = Embedding(self.time_step, in_channels) + self.beam_width = beam_width + self.eos = self.num_classes - 3 + + def forward(self, x, targets=None, embed=None): + return_dict = {} + embedding_vectors = self.embedder(x) + + if self.training: + rec_targets, rec_lengths, _ = targets + rec_pred = self.decoder([x, rec_targets, rec_lengths], embedding_vectors) + return_dict["rec_pred"] = rec_pred + return_dict["embedding_vectors"] = embedding_vectors + else: + rec_pred, rec_pred_scores = self.decoder.beam_search( + x, self.beam_width, self.eos, embedding_vectors + ) + return_dict["rec_pred"] = rec_pred + return_dict["rec_pred_scores"] = rec_pred_scores + return_dict["embedding_vectors"] = embedding_vectors + + return return_dict + + +class Embedding(nn.Layer): + def __init__(self, in_timestep, in_planes, mid_dim=4096, embed_dim=300): + super(Embedding, self).__init__() + self.in_timestep = in_timestep + self.in_planes = in_planes + self.embed_dim = embed_dim + self.mid_dim = mid_dim + self.eEmbed = nn.Linear( + in_timestep * in_planes, self.embed_dim + ) # Embed encoder output to a word-embedding like + + def forward(self, x): + x = paddle.reshape(x, [x.shape[0], -1]) + x = self.eEmbed(x) + return x + + +class AttentionRecognitionHead(nn.Layer): + """ + input: [b x 16 x 64 x in_planes] + output: probability sequence: [b x T x num_classes] + """ + + def __init__(self, in_channels, out_channels, sDim, attDim, max_len_labels): + super(AttentionRecognitionHead, self).__init__() + self.num_classes = ( + out_channels # this is the output classes. So it includes the . + ) + self.in_planes = in_channels + self.sDim = sDim + self.attDim = attDim + self.max_len_labels = max_len_labels + + self.decoder = DecoderUnit( + sDim=sDim, xDim=in_channels, yDim=self.num_classes, attDim=attDim + ) + + def forward(self, x, embed): + x, targets, lengths = x + batch_size = x.shape[0] + # Decoder + state = self.decoder.get_initial_state(embed) + outputs = [] + for i in range(max(lengths)): + if i == 0: + y_prev = paddle.full(shape=[batch_size], fill_value=self.num_classes) + else: + y_prev = targets[:, i - 1] + output, state = self.decoder(x, state, y_prev) + outputs.append(output) + outputs = paddle.concat([_.unsqueeze(1) for _ in outputs], 1) + return outputs + + # inference stage. + def sample(self, x): + x, _, _ = x + batch_size = x.size(0) + # Decoder + state = paddle.zeros([1, batch_size, self.sDim]) + + predicted_ids, predicted_scores, predicted = [], [], None + for i in range(self.max_len_labels): + if i == 0: + y_prev = paddle.full(shape=[batch_size], fill_value=self.num_classes) + else: + y_prev = predicted + + output, state = self.decoder(x, state, y_prev) + output = F.softmax(output, axis=1) + score, predicted = output.max(1) + predicted_ids.append(predicted.unsqueeze(1)) + predicted_scores.append(score.unsqueeze(1)) + predicted_ids = paddle.concat([predicted_ids, 1]) + predicted_scores = paddle.concat([predicted_scores, 1]) + # return predicted_ids.squeeze(), predicted_scores.squeeze() + return predicted_ids, predicted_scores + + def beam_search(self, x, beam_width, eos, embed): + def _inflate(tensor, times, dim): + repeat_dims = [1] * tensor.dim() + repeat_dims[dim] = times + output = paddle.tile(tensor, repeat_dims) + return output + + # https://github.com/IBM/pytorch-seq2seq/blob/fede87655ddce6c94b38886089e05321dc9802af/seq2seq/models/TopKDecoder.py + batch_size, l, d = x.shape + x = paddle.tile( + paddle.transpose(x.unsqueeze(1), perm=[1, 0, 2, 3]), [beam_width, 1, 1, 1] + ) + inflated_encoder_feats = paddle.reshape( + paddle.transpose(x, perm=[1, 0, 2, 3]), [-1, l, d] + ) + + # Initialize the decoder + state = self.decoder.get_initial_state(embed, tile_times=beam_width) + + pos_index = paddle.reshape( + paddle.arange(batch_size) * beam_width, shape=[-1, 1] + ) + + # Initialize the scores + sequence_scores = paddle.full( + shape=[batch_size * beam_width, 1], fill_value=-float("Inf") + ) + index = [i * beam_width for i in range(0, batch_size)] + sequence_scores[index] = 0.0 + + # Initialize the input vector + y_prev = paddle.full( + shape=[batch_size * beam_width], fill_value=self.num_classes + ) + + # Store decisions for backtracking + stored_scores = list() + stored_predecessors = list() + stored_emitted_symbols = list() + + for i in range(self.max_len_labels): + output, state = self.decoder(inflated_encoder_feats, state, y_prev) + state = paddle.unsqueeze(state, axis=0) + log_softmax_output = paddle.nn.functional.log_softmax(output, axis=1) + + sequence_scores = _inflate(sequence_scores, self.num_classes, 1) + sequence_scores += log_softmax_output + scores, candidates = paddle.topk( + paddle.reshape(sequence_scores, [batch_size, -1]), beam_width, axis=1 + ) + + # Reshape input = (bk, 1) and sequence_scores = (bk, 1) + y_prev = paddle.reshape( + candidates % self.num_classes, shape=[batch_size * beam_width] + ) + sequence_scores = paddle.reshape(scores, shape=[batch_size * beam_width, 1]) + + # Update fields for next timestep + pos_index = paddle.expand_as(pos_index, candidates) + predecessors = paddle.cast( + candidates / self.num_classes + pos_index, dtype="int64" + ) + predecessors = paddle.reshape( + predecessors, shape=[batch_size * beam_width, 1] + ) + state = paddle.index_select(state, index=predecessors.squeeze(), axis=1) + + # Update sequence scores and erase scores for symbol so that they aren't expanded + stored_scores.append(sequence_scores.clone()) + y_prev = paddle.reshape(y_prev, shape=[-1, 1]) + eos_prev = paddle.full_like(y_prev, fill_value=eos) + mask = eos_prev == y_prev + mask = paddle.nonzero(mask) + if mask.dim() > 0: + sequence_scores = sequence_scores.numpy() + mask = mask.numpy() + sequence_scores[mask] = -float("inf") + sequence_scores = paddle.to_tensor(sequence_scores) + + # Cache results for backtracking + stored_predecessors.append(predecessors) + y_prev = paddle.squeeze(y_prev) + stored_emitted_symbols.append(y_prev) + + # Do backtracking to return the optimal values + # ====== backtrak ======# + # Initialize return variables given different types + p = list() + l = [ + [self.max_len_labels] * beam_width for _ in range(batch_size) + ] # Placeholder for lengths of top-k sequences + + # the last step output of the beams are not sorted + # thus they are sorted here + sorted_score, sorted_idx = paddle.topk( + paddle.reshape(stored_scores[-1], shape=[batch_size, beam_width]), + beam_width, + ) + + # initialize the sequence scores with the sorted last step beam scores + s = sorted_score.clone() + + batch_eos_found = [0] * batch_size # the number of EOS found + # in the backward loop below for each batch + t = self.max_len_labels - 1 + # initialize the back pointer with the sorted order of the last step beams. + # add pos_index for indexing variable with b*k as the first dimension. + t_predecessors = paddle.reshape( + sorted_idx + pos_index.expand_as(sorted_idx), + shape=[batch_size * beam_width], + ) + while t >= 0: + # Re-order the variables with the back pointer + current_symbol = paddle.index_select( + stored_emitted_symbols[t], index=t_predecessors, axis=0 + ) + t_predecessors = paddle.index_select( + stored_predecessors[t].squeeze(), index=t_predecessors, axis=0 + ) + eos_indices = stored_emitted_symbols[t] == eos + eos_indices = paddle.nonzero(eos_indices) + + if eos_indices.dim() > 0: + for i in range(eos_indices.shape[0] - 1, -1, -1): + # Indices of the EOS symbol for both variables + # with b*k as the first dimension, and b, k for + # the first two dimensions + idx = eos_indices[i] + b_idx = int(idx[0] / beam_width) + # The indices of the replacing position + # according to the replacement strategy noted above + res_k_idx = beam_width - (batch_eos_found[b_idx] % beam_width) - 1 + batch_eos_found[b_idx] += 1 + res_idx = b_idx * beam_width + res_k_idx + + # Replace the old information in return variables + # with the new ended sequence information + t_predecessors[res_idx] = stored_predecessors[t][idx[0]] + current_symbol[res_idx] = stored_emitted_symbols[t][idx[0]] + s[b_idx, res_k_idx] = stored_scores[t][idx[0], 0] + l[b_idx][res_k_idx] = t + 1 + + # record the back tracked results + p.append(current_symbol) + t -= 1 + + # Sort and re-order again as the added ended sequences may change + # the order (very unlikely) + s, re_sorted_idx = s.topk(beam_width) + for b_idx in range(batch_size): + l[b_idx] = [l[b_idx][k_idx.item()] for k_idx in re_sorted_idx[b_idx, :]] + + re_sorted_idx = paddle.reshape( + re_sorted_idx + pos_index.expand_as(re_sorted_idx), + [batch_size * beam_width], + ) + + # Reverse the sequences and re-order at the same time + # It is reversed because the backtracking happens in reverse time order + p = [ + paddle.reshape( + paddle.index_select(step, re_sorted_idx, 0), + shape=[batch_size, beam_width, -1], + ) + for step in reversed(p) + ] + p = paddle.concat(p, -1)[:, 0, :] + return p, paddle.ones_like(p) + + +class AttentionUnit(nn.Layer): + def __init__(self, sDim, xDim, attDim): + super(AttentionUnit, self).__init__() + + self.sDim = sDim + self.xDim = xDim + self.attDim = attDim + + self.sEmbed = nn.Linear(sDim, attDim) + self.xEmbed = nn.Linear(xDim, attDim) + self.wEmbed = nn.Linear(attDim, 1) + + def forward(self, x, sPrev): + batch_size, T, _ = x.shape # [b x T x xDim] + x = paddle.reshape(x, [-1, self.xDim]) # [(b x T) x xDim] + xProj = self.xEmbed(x) # [(b x T) x attDim] + xProj = paddle.reshape(xProj, [batch_size, T, -1]) # [b x T x attDim] + + sPrev = sPrev.squeeze(0) + sProj = self.sEmbed(sPrev) # [b x attDim] + sProj = paddle.unsqueeze(sProj, 1) # [b x 1 x attDim] + sProj = paddle.expand(sProj, [batch_size, T, self.attDim]) # [b x T x attDim] + + sumTanh = paddle.tanh(sProj + xProj) + sumTanh = paddle.reshape(sumTanh, [-1, self.attDim]) + + vProj = self.wEmbed(sumTanh) # [(b x T) x 1] + vProj = paddle.reshape(vProj, [batch_size, T]) + alpha = F.softmax( + vProj, axis=1 + ) # attention weights for each sample in the minibatch + return alpha + + +class DecoderUnit(nn.Layer): + def __init__(self, sDim, xDim, yDim, attDim): + super(DecoderUnit, self).__init__() + self.sDim = sDim + self.xDim = xDim + self.yDim = yDim + self.attDim = attDim + self.emdDim = attDim + + self.attention_unit = AttentionUnit(sDim, xDim, attDim) + self.tgt_embedding = nn.Embedding( + yDim + 1, self.emdDim, weight_attr=nn.initializer.Normal(std=0.01) + ) # the last is used for + self.gru = nn.GRUCell(input_size=xDim + self.emdDim, hidden_size=sDim) + self.fc = nn.Linear( + sDim, + yDim, + weight_attr=nn.initializer.Normal(std=0.01), + bias_attr=nn.initializer.Constant(value=0), + ) + self.embed_fc = nn.Linear(300, self.sDim) + + def get_initial_state(self, embed, tile_times=1): + assert embed.shape[1] == 300 + state = self.embed_fc(embed) # N * sDim + if tile_times != 1: + state = state.unsqueeze(1) + trans_state = paddle.transpose(state, perm=[1, 0, 2]) + state = paddle.tile(trans_state, repeat_times=[tile_times, 1, 1]) + trans_state = paddle.transpose(state, perm=[1, 0, 2]) + state = paddle.reshape(trans_state, shape=[-1, self.sDim]) + state = state.unsqueeze(0) # 1 * N * sDim + return state + + def forward(self, x, sPrev, yPrev): + # x: feature sequence from the image decoder. + batch_size, T, _ = x.shape + alpha = self.attention_unit(x, sPrev) + context = paddle.squeeze(paddle.matmul(alpha.unsqueeze(1), x), axis=1) + yPrev = paddle.cast(yPrev, dtype="int64") + yProj = self.tgt_embedding(yPrev) + + concat_context = paddle.concat([yProj, context], 1) + concat_context = paddle.squeeze(concat_context, 1) + sPrev = paddle.squeeze(sPrev, 0) + output, state = self.gru(concat_context, sPrev) + output = paddle.squeeze(output, axis=1) + output = self.fc(output) + return output, state diff --git a/ppocr/modeling/heads/rec_att_head.py b/ppocr/modeling/heads/rec_att_head.py new file mode 100644 index 0000000..2c952ce --- /dev/null +++ b/ppocr/modeling/heads/rec_att_head.py @@ -0,0 +1,215 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +import numpy as np + + +class AttentionHead(nn.Layer): + def __init__(self, in_channels, out_channels, hidden_size, **kwargs): + super(AttentionHead, self).__init__() + self.input_size = in_channels + self.hidden_size = hidden_size + self.num_classes = out_channels + + self.attention_cell = AttentionGRUCell( + in_channels, hidden_size, out_channels, use_gru=False + ) + self.generator = nn.Linear(hidden_size, out_channels) + + def _char_to_onehot(self, input_char, onehot_dim): + input_ont_hot = F.one_hot(input_char, onehot_dim) + return input_ont_hot + + def forward(self, inputs, targets=None, batch_max_length=25): + batch_size = inputs.shape[0] + num_steps = batch_max_length + + hidden = paddle.zeros((batch_size, self.hidden_size)) + output_hiddens = [] + + if targets is not None: + for i in range(num_steps): + char_onehots = self._char_to_onehot( + targets[:, i], onehot_dim=self.num_classes + ) + (outputs, hidden), alpha = self.attention_cell( + hidden, inputs, char_onehots + ) + output_hiddens.append(paddle.unsqueeze(outputs, axis=1)) + output = paddle.concat(output_hiddens, axis=1) + probs = self.generator(output) + else: + targets = paddle.zeros(shape=[batch_size], dtype="int32") + probs = None + char_onehots = None + outputs = None + alpha = None + + for i in range(num_steps): + char_onehots = self._char_to_onehot( + targets, onehot_dim=self.num_classes + ) + (outputs, hidden), alpha = self.attention_cell( + hidden, inputs, char_onehots + ) + probs_step = self.generator(outputs) + if probs is None: + probs = paddle.unsqueeze(probs_step, axis=1) + else: + probs = paddle.concat( + [probs, paddle.unsqueeze(probs_step, axis=1)], axis=1 + ) + next_input = probs_step.argmax(axis=1) + targets = next_input + if not self.training: + probs = paddle.nn.functional.softmax(probs, axis=2) + return probs + + +class AttentionGRUCell(nn.Layer): + def __init__(self, input_size, hidden_size, num_embeddings, use_gru=False): + super(AttentionGRUCell, self).__init__() + self.i2h = nn.Linear(input_size, hidden_size, bias_attr=False) + self.h2h = nn.Linear(hidden_size, hidden_size) + self.score = nn.Linear(hidden_size, 1, bias_attr=False) + + self.rnn = nn.GRUCell( + input_size=input_size + num_embeddings, hidden_size=hidden_size + ) + + self.hidden_size = hidden_size + + def forward(self, prev_hidden, batch_H, char_onehots): + batch_H_proj = self.i2h(batch_H) + prev_hidden_proj = paddle.unsqueeze(self.h2h(prev_hidden), axis=1) + + res = paddle.add(batch_H_proj, prev_hidden_proj) + res = paddle.tanh(res) + e = self.score(res) + + alpha = F.softmax(e, axis=1) + alpha = paddle.transpose(alpha, [0, 2, 1]) + context = paddle.squeeze(paddle.mm(alpha, batch_H), axis=1) + concat_context = paddle.concat([context, char_onehots], 1) + + cur_hidden = self.rnn(concat_context, prev_hidden) + + return cur_hidden, alpha + + +class AttentionLSTM(nn.Layer): + def __init__(self, in_channels, out_channels, hidden_size, **kwargs): + super(AttentionLSTM, self).__init__() + self.input_size = in_channels + self.hidden_size = hidden_size + self.num_classes = out_channels + + self.attention_cell = AttentionLSTMCell( + in_channels, hidden_size, out_channels, use_gru=False + ) + self.generator = nn.Linear(hidden_size, out_channels) + + def _char_to_onehot(self, input_char, onehot_dim): + input_ont_hot = F.one_hot(input_char, onehot_dim) + return input_ont_hot + + def forward(self, inputs, targets=None, batch_max_length=25): + batch_size = inputs.shape[0] + num_steps = batch_max_length + + hidden = ( + paddle.zeros((batch_size, self.hidden_size)), + paddle.zeros((batch_size, self.hidden_size)), + ) + output_hiddens = [] + + if targets is not None: + for i in range(num_steps): + # one-hot vectors for a i-th char + char_onehots = self._char_to_onehot( + targets[:, i], onehot_dim=self.num_classes + ) + hidden, alpha = self.attention_cell(hidden, inputs, char_onehots) + + hidden = (hidden[1][0], hidden[1][1]) + output_hiddens.append(paddle.unsqueeze(hidden[0], axis=1)) + output = paddle.concat(output_hiddens, axis=1) + probs = self.generator(output) + + else: + targets = paddle.zeros(shape=[batch_size], dtype="int32") + probs = None + char_onehots = None + alpha = None + + for i in range(num_steps): + char_onehots = self._char_to_onehot( + targets, onehot_dim=self.num_classes + ) + hidden, alpha = self.attention_cell(hidden, inputs, char_onehots) + probs_step = self.generator(hidden[0]) + hidden = (hidden[1][0], hidden[1][1]) + if probs is None: + probs = paddle.unsqueeze(probs_step, axis=1) + else: + probs = paddle.concat( + [probs, paddle.unsqueeze(probs_step, axis=1)], axis=1 + ) + + next_input = probs_step.argmax(axis=1) + + targets = next_input + if not self.training: + probs = paddle.nn.functional.softmax(probs, axis=2) + return probs + + +class AttentionLSTMCell(nn.Layer): + def __init__(self, input_size, hidden_size, num_embeddings, use_gru=False): + super(AttentionLSTMCell, self).__init__() + self.i2h = nn.Linear(input_size, hidden_size, bias_attr=False) + self.h2h = nn.Linear(hidden_size, hidden_size) + self.score = nn.Linear(hidden_size, 1, bias_attr=False) + if not use_gru: + self.rnn = nn.LSTMCell( + input_size=input_size + num_embeddings, hidden_size=hidden_size + ) + else: + self.rnn = nn.GRUCell( + input_size=input_size + num_embeddings, hidden_size=hidden_size + ) + + self.hidden_size = hidden_size + + def forward(self, prev_hidden, batch_H, char_onehots): + batch_H_proj = self.i2h(batch_H) + prev_hidden_proj = paddle.unsqueeze(self.h2h(prev_hidden[0]), axis=1) + res = paddle.add(batch_H_proj, prev_hidden_proj) + res = paddle.tanh(res) + e = self.score(res) + + alpha = F.softmax(e, axis=1) + alpha = paddle.transpose(alpha, [0, 2, 1]) + context = paddle.squeeze(paddle.mm(alpha, batch_H), axis=1) + concat_context = paddle.concat([context, char_onehots], 1) + cur_hidden = self.rnn(concat_context, prev_hidden) + + return cur_hidden, alpha diff --git a/ppocr/modeling/heads/rec_can_head.py b/ppocr/modeling/heads/rec_can_head.py new file mode 100644 index 0000000..e80951c --- /dev/null +++ b/ppocr/modeling/heads/rec_can_head.py @@ -0,0 +1,338 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/LBH1024/CAN/models/can.py +https://github.com/LBH1024/CAN/models/counting.py +https://github.com/LBH1024/CAN/models/decoder.py +https://github.com/LBH1024/CAN/models/attention.py + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle.nn as nn +import paddle +import math + +""" +Counting Module +""" + + +class ChannelAtt(nn.Layer): + def __init__(self, channel, reduction): + super(ChannelAtt, self).__init__() + self.avg_pool = nn.AdaptiveAvgPool2D(1) + + self.fc = nn.Sequential( + nn.Linear(channel, channel // reduction), + nn.ReLU(), + nn.Linear(channel // reduction, channel), + nn.Sigmoid(), + ) + + def forward(self, x): + b, c, _, _ = x.shape + y = paddle.reshape(self.avg_pool(x), [b, c]) + y = paddle.reshape(self.fc(y), [b, c, 1, 1]) + return x * y + + +class CountingDecoder(nn.Layer): + def __init__(self, in_channel, out_channel, kernel_size): + super(CountingDecoder, self).__init__() + self.in_channel = in_channel + self.out_channel = out_channel + + self.trans_layer = nn.Sequential( + nn.Conv2D( + self.in_channel, + 512, + kernel_size=kernel_size, + padding=kernel_size // 2, + bias_attr=False, + ), + nn.BatchNorm2D(512), + ) + + self.channel_att = ChannelAtt(512, 16) + + self.pred_layer = nn.Sequential( + nn.Conv2D(512, self.out_channel, kernel_size=1, bias_attr=False), + nn.Sigmoid(), + ) + + def forward(self, x, mask): + b, _, h, w = x.shape + x = self.trans_layer(x) + x = self.channel_att(x) + x = self.pred_layer(x) + + if mask is not None: + x = x * mask + x = paddle.reshape(x, [b, self.out_channel, -1]) + x1 = paddle.sum(x, axis=-1) + + return x1, paddle.reshape(x, [b, self.out_channel, h, w]) + + +""" +Attention Decoder +""" + + +class PositionEmbeddingSine(nn.Layer): + def __init__( + self, num_pos_feats=64, temperature=10000, normalize=False, scale=None + ): + super().__init__() + self.num_pos_feats = num_pos_feats + self.temperature = temperature + self.normalize = normalize + if scale is not None and normalize is False: + raise ValueError("normalize should be True if scale is passed") + if scale is None: + scale = 2 * math.pi + self.scale = scale + + def forward(self, x, mask): + y_embed = paddle.cumsum(mask, 1, dtype="float32") + x_embed = paddle.cumsum(mask, 2, dtype="float32") + + if self.normalize: + eps = 1e-6 + y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale + x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale + dim_t = paddle.arange(self.num_pos_feats, dtype="float32") + dim_d = paddle.expand(paddle.to_tensor(2), dim_t.shape) + dim_t = self.temperature ** ( + 2 * (dim_t / dim_d).astype("int64") / self.num_pos_feats + ) + + pos_x = paddle.unsqueeze(x_embed, [3]) / dim_t + pos_y = paddle.unsqueeze(y_embed, [3]) / dim_t + + pos_x = paddle.flatten( + paddle.stack( + [paddle.sin(pos_x[:, :, :, 0::2]), paddle.cos(pos_x[:, :, :, 1::2])], + axis=4, + ), + 3, + ) + pos_y = paddle.flatten( + paddle.stack( + [paddle.sin(pos_y[:, :, :, 0::2]), paddle.cos(pos_y[:, :, :, 1::2])], + axis=4, + ), + 3, + ) + + pos = paddle.transpose(paddle.concat([pos_y, pos_x], axis=3), [0, 3, 1, 2]) + + return pos + + +class AttDecoder(nn.Layer): + def __init__( + self, + ratio, + is_train, + input_size, + hidden_size, + encoder_out_channel, + dropout, + dropout_ratio, + word_num, + counting_decoder_out_channel, + attention, + ): + super(AttDecoder, self).__init__() + self.input_size = input_size + self.hidden_size = hidden_size + self.out_channel = encoder_out_channel + self.attention_dim = attention["attention_dim"] + self.dropout_prob = dropout + self.ratio = ratio + self.word_num = word_num + + self.counting_num = counting_decoder_out_channel + self.is_train = is_train + + self.init_weight = nn.Linear(self.out_channel, self.hidden_size) + self.embedding = nn.Embedding(self.word_num, self.input_size) + self.word_input_gru = nn.GRUCell(self.input_size, self.hidden_size) + self.word_attention = Attention(hidden_size, attention["attention_dim"]) + + self.encoder_feature_conv = nn.Conv2D( + self.out_channel, + self.attention_dim, + kernel_size=attention["word_conv_kernel"], + padding=attention["word_conv_kernel"] // 2, + ) + + self.word_state_weight = nn.Linear(self.hidden_size, self.hidden_size) + self.word_embedding_weight = nn.Linear(self.input_size, self.hidden_size) + self.word_context_weight = nn.Linear(self.out_channel, self.hidden_size) + self.counting_context_weight = nn.Linear(self.counting_num, self.hidden_size) + self.word_convert = nn.Linear(self.hidden_size, self.word_num) + + if dropout: + self.dropout = nn.Dropout(dropout_ratio) + + def forward(self, cnn_features, labels, counting_preds, images_mask): + if self.is_train: + _, num_steps = labels.shape + else: + num_steps = 36 + + batch_size, _, height, width = cnn_features.shape + images_mask = images_mask[:, :, :: self.ratio, :: self.ratio] + + word_probs = paddle.zeros((batch_size, num_steps, self.word_num)) + word_alpha_sum = paddle.zeros((batch_size, 1, height, width)) + + hidden = self.init_hidden(cnn_features, images_mask) + counting_context_weighted = self.counting_context_weight(counting_preds) + cnn_features_trans = self.encoder_feature_conv(cnn_features) + + position_embedding = PositionEmbeddingSine(256, normalize=True) + pos = position_embedding(cnn_features_trans, images_mask[:, 0, :, :]) + + cnn_features_trans = cnn_features_trans + pos + + word = paddle.ones([batch_size, 1], dtype="int64") # init word as sos + word = word.squeeze(axis=1) + for i in range(num_steps): + word_embedding = self.embedding(word) + _, hidden = self.word_input_gru(word_embedding, hidden) + word_context_vec, _, word_alpha_sum = self.word_attention( + cnn_features, cnn_features_trans, hidden, word_alpha_sum, images_mask + ) + + current_state = self.word_state_weight(hidden) + word_weighted_embedding = self.word_embedding_weight(word_embedding) + word_context_weighted = self.word_context_weight(word_context_vec) + + if self.dropout_prob: + word_out_state = self.dropout( + current_state + + word_weighted_embedding + + word_context_weighted + + counting_context_weighted + ) + else: + word_out_state = ( + current_state + + word_weighted_embedding + + word_context_weighted + + counting_context_weighted + ) + + word_prob = self.word_convert(word_out_state) + word_probs[:, i] = word_prob + + if self.is_train: + word = labels[:, i] + else: + word = word_prob.argmax(1) + word = paddle.multiply( + word, labels[:, i] + ) # labels are oneslike tensor in infer/predict mode + + return word_probs + + def init_hidden(self, features, feature_mask): + average = paddle.sum( + paddle.sum(features * feature_mask, axis=-1), axis=-1 + ) / paddle.sum((paddle.sum(feature_mask, axis=-1)), axis=-1) + average = self.init_weight(average) + return paddle.tanh(average) + + +""" +Attention Module +""" + + +class Attention(nn.Layer): + def __init__(self, hidden_size, attention_dim): + super(Attention, self).__init__() + self.hidden = hidden_size + self.attention_dim = attention_dim + self.hidden_weight = nn.Linear(self.hidden, self.attention_dim) + self.attention_conv = nn.Conv2D( + 1, 512, kernel_size=11, padding=5, bias_attr=False + ) + self.attention_weight = nn.Linear(512, self.attention_dim, bias_attr=False) + self.alpha_convert = nn.Linear(self.attention_dim, 1) + + def forward( + self, cnn_features, cnn_features_trans, hidden, alpha_sum, image_mask=None + ): + query = self.hidden_weight(hidden) + alpha_sum_trans = self.attention_conv(alpha_sum) + coverage_alpha = self.attention_weight( + paddle.transpose(alpha_sum_trans, [0, 2, 3, 1]) + ) + alpha_score = paddle.tanh( + paddle.unsqueeze(query, [1, 2]) + + coverage_alpha + + paddle.transpose(cnn_features_trans, [0, 2, 3, 1]) + ) + energy = self.alpha_convert(alpha_score) + energy = energy - energy.max() + energy_exp = paddle.exp(paddle.squeeze(energy, -1)) + + if image_mask is not None: + energy_exp = energy_exp * paddle.squeeze(image_mask, 1) + alpha = energy_exp / ( + paddle.unsqueeze(paddle.sum(paddle.sum(energy_exp, -1), -1), [1, 2]) + 1e-10 + ) + alpha_sum = paddle.unsqueeze(alpha, 1) + alpha_sum + context_vector = paddle.sum( + paddle.sum((paddle.unsqueeze(alpha, 1) * cnn_features), -1), -1 + ) + + return context_vector, alpha, alpha_sum + + +class CANHead(nn.Layer): + def __init__(self, in_channel, out_channel, ratio, attdecoder, **kwargs): + super(CANHead, self).__init__() + + self.in_channel = in_channel + self.out_channel = out_channel + + self.counting_decoder1 = CountingDecoder( + self.in_channel, self.out_channel, 3 + ) # mscm + self.counting_decoder2 = CountingDecoder(self.in_channel, self.out_channel, 5) + + self.decoder = AttDecoder(ratio, **attdecoder) + + self.ratio = ratio + + def forward(self, inputs, targets=None): + cnn_features, images_mask, labels = inputs + + counting_mask = images_mask[:, :, :: self.ratio, :: self.ratio] + counting_preds1, _ = self.counting_decoder1(cnn_features, counting_mask) + counting_preds2, _ = self.counting_decoder2(cnn_features, counting_mask) + counting_preds = (counting_preds1 + counting_preds2) / 2 + + word_probs = self.decoder(cnn_features, labels, counting_preds, images_mask) + return word_probs, counting_preds, counting_preds1, counting_preds2 diff --git a/ppocr/modeling/heads/rec_cppd_head.py b/ppocr/modeling/heads/rec_cppd_head.py new file mode 100644 index 0000000..c29946b --- /dev/null +++ b/ppocr/modeling/heads/rec_cppd_head.py @@ -0,0 +1,387 @@ +# copyright (c) 2023 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +try: + from collections import Callable +except: + from collections.abc import Callable + +import numpy as np +import paddle +from paddle import nn +from paddle.nn import functional as F +from ppocr.modeling.heads.rec_nrtr_head import Embeddings +from ppocr.modeling.backbones.rec_svtrnet import ( + DropPath, + Identity, + trunc_normal_, + zeros_, + ones_, + Mlp, +) + + +class Attention(nn.Layer): + def __init__( + self, + dim, + num_heads=8, + qkv_bias=False, + qk_scale=None, + attn_drop=0.0, + proj_drop=0.0, + ): + super().__init__() + self.num_heads = num_heads + head_dim = dim // num_heads + self.scale = qk_scale or head_dim**-0.5 + + self.q = nn.Linear(dim, dim, bias_attr=qkv_bias) + self.kv = nn.Linear(dim, dim * 2, bias_attr=qkv_bias) + self.attn_drop = nn.Dropout(attn_drop) + self.proj = nn.Linear(dim, dim) + self.proj_drop = nn.Dropout(proj_drop) + + def forward(self, q, kv): + N, C = kv.shape[1:] + QN = q.shape[1] + q = ( + self.q(q) + .reshape([-1, QN, self.num_heads, C // self.num_heads]) + .transpose([0, 2, 1, 3]) + ) + k, v = ( + self.kv(kv) + .reshape([-1, N, 2, self.num_heads, C // self.num_heads]) + .transpose((2, 0, 3, 1, 4)) + ) + attn = q.matmul(k.transpose((0, 1, 3, 2))) * self.scale + attn = F.softmax(attn, axis=-1) + attn = self.attn_drop(attn) + x = (attn.matmul(v)).transpose((0, 2, 1, 3)).reshape((-1, QN, C)) + x = self.proj(x) + x = self.proj_drop(x) + return x + + +class EdgeDecoderLayer(nn.Layer): + def __init__( + self, + dim, + num_heads, + mlp_ratio=4.0, + qkv_bias=False, + qk_scale=None, + drop=0.0, + attn_drop=0.0, + drop_path=[0.0, 0.0], + act_layer=nn.GELU, + norm_layer="nn.LayerNorm", + epsilon=1e-6, + ): + super().__init__() + + self.head_dim = dim // num_heads + self.scale = qk_scale or self.head_dim**-0.5 + + # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here + self.drop_path1 = DropPath(drop_path[0]) if drop_path[0] > 0.0 else Identity() + self.norm1 = eval(norm_layer)(dim, epsilon=epsilon) + self.norm2 = eval(norm_layer)(dim, epsilon=epsilon) + + self.p = nn.Linear(dim, dim) + self.cv = nn.Linear(dim, dim) + self.pv = nn.Linear(dim, dim) + + self.dim = dim + self.num_heads = num_heads + self.p_proj = nn.Linear(dim, dim) + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp_ratio = mlp_ratio + self.mlp = Mlp( + in_features=dim, + hidden_features=mlp_hidden_dim, + act_layer=act_layer, + drop=drop, + ) + + def forward(self, p, cv, pv): + pN = p.shape[1] + vN = cv.shape[1] + p_shortcut = p + + p1 = ( + self.p(p) + .reshape([-1, pN, self.num_heads, self.dim // self.num_heads]) + .transpose([0, 2, 1, 3]) + ) + cv1 = ( + self.cv(cv) + .reshape([-1, vN, self.num_heads, self.dim // self.num_heads]) + .transpose([0, 2, 1, 3]) + ) + pv1 = ( + self.pv(pv) + .reshape([-1, vN, self.num_heads, self.dim // self.num_heads]) + .transpose([0, 2, 1, 3]) + ) + + edge = F.softmax(p1.matmul(pv1.transpose((0, 1, 3, 2))), -1) # B h N N + p_c = (edge @ cv1).transpose((0, 2, 1, 3)).reshape((-1, pN, self.dim)) + + x1 = self.norm1(p_shortcut + self.drop_path1(self.p_proj(p_c))) + + x = self.norm2(x1 + self.drop_path1(self.mlp(x1))) + return x + + +class DecoderLayer(nn.Layer): + def __init__( + self, + dim, + num_heads, + mlp_ratio=4.0, + qkv_bias=False, + qk_scale=None, + drop=0.0, + attn_drop=0.0, + drop_path=0.0, + act_layer=nn.GELU, + norm_layer="nn.LayerNorm", + epsilon=1e-6, + ): + super().__init__() + if isinstance(norm_layer, str): + self.norm1 = eval(norm_layer)(dim, epsilon=epsilon) + self.normkv = eval(norm_layer)(dim, epsilon=epsilon) + elif isinstance(norm_layer, Callable): + self.norm1 = norm_layer(dim) + self.normkv = norm_layer(dim) + else: + raise TypeError("The norm_layer must be str or paddle.nn.LayerNorm class") + self.mixer = Attention( + dim, + num_heads=num_heads, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + attn_drop=attn_drop, + proj_drop=drop, + ) + + # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else Identity() + if isinstance(norm_layer, str): + self.norm2 = eval(norm_layer)(dim, epsilon=epsilon) + elif isinstance(norm_layer, Callable): + self.norm2 = norm_layer(dim) + else: + raise TypeError("The norm_layer must be str or paddle.nn.layer.Layer class") + mlp_hidden_dim = int(dim * mlp_ratio) + self.mlp_ratio = mlp_ratio + self.mlp = Mlp( + in_features=dim, + hidden_features=mlp_hidden_dim, + act_layer=act_layer, + drop=drop, + ) + + def forward(self, q, kv): + x1 = self.norm1(q + self.drop_path(self.mixer(q, kv))) + x = self.norm2(x1 + self.drop_path(self.mlp(x1))) + return x + + +class CPPDHead(nn.Layer): + def __init__( + self, + in_channels, + dim, + out_channels, + num_layer=2, + drop_path_rate=0.1, + max_len=25, + vis_seq=50, + ch=False, + **kwargs, + ): + super(CPPDHead, self).__init__() + + self.out_channels = out_channels # none + 26 + 10 + self.dim = dim + self.ch = ch + self.max_len = max_len + 1 # max_len + eos + self.char_node_embed = Embeddings( + d_model=dim, vocab=self.out_channels, scale_embedding=True + ) + self.pos_node_embed = Embeddings( + d_model=dim, vocab=self.max_len, scale_embedding=True + ) + dpr = np.linspace(0, drop_path_rate, num_layer + 1) + + self.char_node_decoder = nn.LayerList( + [ + DecoderLayer( + dim=dim, + num_heads=dim // 32, + mlp_ratio=4.0, + qkv_bias=True, + drop_path=dpr[i], + ) + for i in range(num_layer) + ] + ) + self.pos_node_decoder = nn.LayerList( + [ + DecoderLayer( + dim=dim, + num_heads=dim // 32, + mlp_ratio=4.0, + qkv_bias=True, + drop_path=dpr[i], + ) + for i in range(num_layer) + ] + ) + + self.edge_decoder = EdgeDecoderLayer( + dim=dim, + num_heads=dim // 32, + mlp_ratio=4.0, + qkv_bias=True, + drop_path=dpr[num_layer : num_layer + 1], + ) + + self.char_pos_embed = self.create_parameter( + shape=[1, self.max_len, dim], default_initializer=zeros_ + ) + self.add_parameter("char_pos_embed", self.char_pos_embed) + self.vis_pos_embed = self.create_parameter( + shape=[1, vis_seq, dim], default_initializer=zeros_ + ) + self.add_parameter("vis_pos_embed", self.vis_pos_embed) + + self.char_node_fc1 = nn.Linear(dim, max_len) + self.pos_node_fc1 = nn.Linear(dim, self.max_len) + + self.edge_fc = nn.Linear(dim, self.out_channels) + trunc_normal_(self.char_pos_embed) + trunc_normal_(self.vis_pos_embed) + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight) + if isinstance(m, nn.Linear) and m.bias is not None: + zeros_(m.bias) + elif isinstance(m, nn.LayerNorm): + zeros_(m.bias) + ones_(m.weight) + + def forward(self, x, targets=None, epoch=0): + if self.training: + return self.forward_train(x, targets, epoch) + else: + return self.forward_test(x) + + def forward_test(self, x): + visual_feats = x + self.vis_pos_embed + bs = visual_feats.shape[0] + pos_node_embed = ( + self.pos_node_embed(paddle.arange(self.max_len)).unsqueeze(0) + + self.char_pos_embed + ) + pos_node_embed = paddle.tile(pos_node_embed, [bs, 1, 1]) + char_vis_node_query = visual_feats + pos_vis_node_query = paddle.concat([pos_node_embed, visual_feats], 1) + + for char_decoder_layer, pos_decoder_layer in zip( + self.char_node_decoder, self.pos_node_decoder + ): + char_vis_node_query = char_decoder_layer( + char_vis_node_query, char_vis_node_query + ) + pos_vis_node_query = pos_decoder_layer( + pos_vis_node_query, pos_vis_node_query[:, self.max_len :, :] + ) + pos_node_query = pos_vis_node_query[:, : self.max_len, :] + char_vis_feats = char_vis_node_query + + pos_node_feats = self.edge_decoder( + pos_node_query, char_vis_feats, char_vis_feats + ) # B, 26, dim + edge_feats = self.edge_fc(pos_node_feats) # B, 26, 37 + edge_logits = F.softmax(edge_feats, -1) + + return edge_logits + + def forward_train(self, x, targets=None, epoch=0): + visual_feats = x + self.vis_pos_embed + bs = visual_feats.shape[0] + + if self.ch: + char_node_embed = self.char_node_embed(targets[-2]) + else: + char_node_embed = self.char_node_embed( + paddle.arange(self.out_channels) + ).unsqueeze(0) + char_node_embed = paddle.tile(char_node_embed, [bs, 1, 1]) + counting_char_num = char_node_embed.shape[1] + pos_node_embed = ( + self.pos_node_embed(paddle.arange(self.max_len)).unsqueeze(0) + + self.char_pos_embed + ) + pos_node_embed = paddle.tile(pos_node_embed, [bs, 1, 1]) + + node_feats = [] + + char_vis_node_query = paddle.concat([char_node_embed, visual_feats], 1) + pos_vis_node_query = paddle.concat([pos_node_embed, visual_feats], 1) + + for char_decoder_layer, pos_decoder_layer in zip( + self.char_node_decoder, self.pos_node_decoder + ): + char_vis_node_query = char_decoder_layer( + char_vis_node_query, char_vis_node_query[:, counting_char_num:, :] + ) + pos_vis_node_query = pos_decoder_layer( + pos_vis_node_query, pos_vis_node_query[:, self.max_len :, :] + ) + + char_node_query = char_vis_node_query[:, :counting_char_num, :] + pos_node_query = pos_vis_node_query[:, : self.max_len, :] + + char_vis_feats = char_vis_node_query[:, counting_char_num:, :] + char_node_feats1 = self.char_node_fc1(char_node_query) + + pos_node_feats1 = self.pos_node_fc1(pos_node_query) + diag_mask = ( + paddle.eye(pos_node_feats1.shape[1]) + .unsqueeze(0) + .tile([pos_node_feats1.shape[0], 1, 1]) + ) + pos_node_feats1 = (pos_node_feats1 * diag_mask).sum(-1) + + node_feats.append(char_node_feats1) + node_feats.append(pos_node_feats1) + + pos_node_feats = self.edge_decoder( + pos_node_query, char_vis_feats, char_vis_feats + ) # B, 26, dim + edge_feats = self.edge_fc(pos_node_feats) # B, 26, 37 + + return node_feats, edge_feats diff --git a/ppocr/modeling/heads/rec_ctc_head.py b/ppocr/modeling/heads/rec_ctc_head.py new file mode 100755 index 0000000..565f167 --- /dev/null +++ b/ppocr/modeling/heads/rec_ctc_head.py @@ -0,0 +1,120 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math + +import paddle +from paddle import ParamAttr, nn +from paddle.nn import functional as F + + +def get_para_bias_attr(l2_decay, k): + regularizer = paddle.regularizer.L2Decay(l2_decay) + stdv = 1.0 / math.sqrt(k * 1.0) + initializer = nn.initializer.Uniform(-stdv, stdv) + weight_attr = ParamAttr(regularizer=regularizer, initializer=initializer) + bias_attr = ParamAttr(regularizer=regularizer, initializer=initializer) + return [weight_attr, bias_attr] + + +class CTCHead(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + fc_decay=0.0004, + mid_channels=None, + return_feats=False, + use_guide=False, + **kwargs, + ): + super(CTCHead, self).__init__() + self.use_guide = use_guide + if use_guide: + self.guide_layer = nn.Sequential( + nn.Conv1D( + in_channels, + in_channels, + kernel_size=5, + padding=2, + groups=in_channels, + bias_attr=False, + ), + nn.BatchNorm1D(in_channels), + nn.Hardswish(), + nn.Conv1D( + in_channels, + in_channels, + kernel_size=1, + bias_attr=False, + ), + nn.BatchNorm1D(in_channels), + nn.Hardswish(), + ) + if mid_channels is None: + weight_attr, bias_attr = get_para_bias_attr( + l2_decay=fc_decay, k=in_channels + ) + self.fc = nn.Linear( + in_channels, out_channels, weight_attr=weight_attr, bias_attr=bias_attr + ) + else: + weight_attr1, bias_attr1 = get_para_bias_attr( + l2_decay=fc_decay, k=in_channels + ) + self.fc1 = nn.Linear( + in_channels, + mid_channels, + weight_attr=weight_attr1, + bias_attr=bias_attr1, + ) + + weight_attr2, bias_attr2 = get_para_bias_attr( + l2_decay=fc_decay, k=mid_channels + ) + self.fc2 = nn.Linear( + mid_channels, + out_channels, + weight_attr=weight_attr2, + bias_attr=bias_attr2, + ) + self.out_channels = out_channels + self.mid_channels = mid_channels + self.return_feats = return_feats + + def forward(self, x, targets=None): + if self.use_guide: + x = x.transpose([0, 2, 1]) + x = self.guide_layer(x) + x = x.transpose([0, 2, 1]) + + if self.mid_channels is None: + predicts = self.fc(x) + else: + x = self.fc1(x) + predicts = self.fc2(x) + + if self.return_feats: + result = (x, predicts) + else: + result = predicts + if not self.training: + predicts = F.softmax(predicts, axis=2) + result = predicts + + return result diff --git a/ppocr/modeling/heads/rec_latexocr_head.py b/ppocr/modeling/heads/rec_latexocr_head.py new file mode 100644 index 0000000..1b01d9d --- /dev/null +++ b/ppocr/modeling/heads/rec_latexocr_head.py @@ -0,0 +1,1030 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is refer from: +https://github.com/lukas-blecher/LaTeX-OCR/blob/main/pix2tex/models/transformer.py +""" + +import math +import paddle +from paddle import nn, einsum +import paddle.nn.functional as F +from functools import partial +from inspect import isfunction +from collections import namedtuple + +from paddle.nn.initializer import ( + TruncatedNormal, + Constant, + Normal, + KaimingUniform, + XavierUniform, +) + +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) +normal_ = Normal(std=0.02) +DEFAULT_DIM_HEAD = 64 + +Intermediates = namedtuple("Intermediates", ["pre_softmax_attn", "post_softmax_attn"]) + +LayerIntermediates = namedtuple("Intermediates", ["hiddens", "attn_intermediates"]) + +# helpers + + +def exists(val): + return val is not None + + +def default(val, d): + if exists(val): + return val + return d() if isfunction(d) else d + + +class always: + def __init__(self, val): + self.val = val + + def __call__(self, *args, **kwargs): + return self.val + + +class not_equals: + def __init__(self, val): + self.val = val + + def __call__(self, x, *args, **kwargs): + return x != self.val + + +class equals: + def __init__(self, val): + self.val = val + + def __call__(self, x, *args, **kwargs): + return x == self.val + + +def max_neg_value(tensor): + return -paddle.finfo(tensor.dtype).max + + +def pick_and_pop(keys, d): + values = list(map(lambda key: d.pop(key), keys)) + return dict(zip(keys, values)) + + +def group_dict_by_key(cond, d): + return_val = [dict(), dict()] + for key in d.keys(): + match = bool(cond(key)) + ind = int(not match) + return_val[ind][key] = d[key] + return (*return_val,) + + +def string_begins_with(prefix, str): + return str.startswith(prefix) + + +def group_by_key_prefix(prefix, d): + return group_dict_by_key(partial(string_begins_with, prefix), d) + + +def groupby_prefix_and_trim(prefix, d): + kwargs_with_prefix, kwargs = group_dict_by_key( + partial(string_begins_with, prefix), d + ) + kwargs_without_prefix = dict( + map(lambda x: (x[0][len(prefix) :], x[1]), tuple(kwargs_with_prefix.items())) + ) + return kwargs_without_prefix, kwargs + + +# positional embeddings + + +class DepthWiseConv1d(nn.Layer): + def __init__( + self, dim_in, dim_out, kernel_size, padding=0, stride=1, bias=True, groups=False + ): + super().__init__() + groups = default(groups, dim_in) + self.net = nn.Sequential( + nn.Conv1D( + dim_in, + dim_in, + kernel_size=kernel_size, + padding=padding, + groups=dim_in, + stride=stride, + bias_attr=bias, + ), + nn.Conv1D(dim_in, dim_out, 1), + ) + + def forward(self, x): + return self.net(x) + + +class AbsolutePositionalEmbedding(nn.Layer): + def __init__(self, dim, max_seq_len): + super().__init__() + self.emb = nn.Embedding(max_seq_len, dim) + self.init_() + + def init_(self): + + normal_(self.emb.weight) + + def forward(self, x): + n = paddle.arange(x.shape[1]) + return self.emb(n)[None, :, :] + + +class FixedPositionalEmbedding(nn.Layer): + def __init__(self, dim): + super().__init__() + inv_freq = 1.0 / (10000 ** (paddle.arange(0, dim, 2).float() / dim)) + self.register_buffer("inv_freq", inv_freq) + + def forward(self, x, seq_dim=1, offset=0): + t = ( + paddle.arange( + x.shape[seq_dim], + ).type_as(self.inv_freq) + + offset + ) + sinusoid_inp = paddle.einsum("i , j -> i j", t, self.inv_freq) + emb = paddle.concat((sinusoid_inp.sin(), sinusoid_inp.cos()), axis=-1) + return emb[None, :, :] + + +class Scale(nn.Layer): + def __init__(self, value, fn): + super().__init__() + self.value = value + self.fn = fn + + def forward(self, x, **kwargs): + x, *rest = self.fn(x, **kwargs) + return (x * self.value, *rest) + + +class Rezero(nn.Layer): + def __init__(self, fn): + super().__init__() + self.fn = fn + self.g = paddle.create_parameter([1], dtype="float32") + zeros_(self.g) + + def forward(self, x, **kwargs): + x, *rest = self.fn(x, **kwargs) + return (x * self.g, *rest) + + +class ScaleNorm(nn.Layer): + def __init__(self, dim, eps=1e-5): + super().__init__() + self.scale = dim**-0.5 + self.eps = eps + self.g = paddle.create_parameter([1], dtype="float32") + ones_(self.g) + + def forward(self, x): + norm = paddle.norm(x, axis=-1, keepdim=True) * self.scale + return x / norm.clamp(min=self.eps) * self.g + + +class RMSNorm(nn.Layer): + def __init__(self, dim, eps=1e-8): + super().__init__() + self.scale = dim**-0.5 + self.eps = eps + self.g = paddle.create_parameter([dim]) + ones_(self.g) + + def forward(self, x): + norm = paddle.norm(x, axis=-1, keepdim=True) * self.scale + return x / norm.clamp(min=self.eps) * self.g + + +class Residual(nn.Layer): + def forward(self, x, residual): + return x + residual + + +class GEGLU(nn.Layer): + def __init__(self, dim_in, dim_out): + super().__init__() + self.proj = nn.Linear(dim_in, dim_out * 2) + + def forward(self, x): + x, gate = self.proj(x).chunk(2, axis=-1) + return x * F.gelu(gate) + + +class FeedForward(nn.Layer): + def __init__(self, dim, dim_out=None, mult=4, glu=False, dropout=0.0): + super().__init__() + inner_dim = int(dim * mult) + dim_out = default(dim_out, dim) + project_in = ( + nn.Sequential(nn.Linear(dim, inner_dim), nn.GELU()) + if not glu + else GEGLU(dim, inner_dim) + ) + + self.net = nn.Sequential( + project_in, nn.Dropout(dropout), nn.Linear(inner_dim, dim_out) + ) + + def forward(self, x): + return self.net(x) + + +class Attention(nn.Layer): + def __init__( + self, + dim, + dim_head=DEFAULT_DIM_HEAD, + heads=8, + causal=False, + mask=None, + talking_heads=False, + collab_heads=False, + collab_compression=0.3, + sparse_topk=None, + use_entmax15=False, + num_mem_kv=0, + dropout=0.0, + on_attn=False, + gate_values=False, + is_export=False, + ): + super().__init__() + self.scale = dim_head**-0.5 + self.heads = heads + self.causal = causal + self.mask = mask + self.is_export = is_export + + qk_dim = v_dim = dim_head * heads + + # collaborative heads + self.collab_heads = collab_heads + if self.collab_heads: + qk_dim = int(collab_compression * qk_dim) + self.collab_mixing = nn.Parameter(paddle.randn(heads, qk_dim)) + + self.to_q = nn.Linear(dim, qk_dim, bias_attr=False) + self.to_k = nn.Linear(dim, qk_dim, bias_attr=False) + self.to_v = nn.Linear(dim, v_dim, bias_attr=False) + + self.dropout = nn.Dropout(dropout) + + # add GLU gating for aggregated values, from alphafold2 + self.to_v_gate = None + if gate_values: + self.to_v_gate = nn.Linear(dim, v_dim) + zeros_(self.to_v_gate.weight) + ones_(self.to_v_gate.bias) + + # talking heads + self.talking_heads = talking_heads + if talking_heads: + self.pre_softmax_proj = nn.Parameter(paddle.randn(heads, heads)) + self.post_softmax_proj = nn.Parameter(paddle.randn(heads, heads)) + + # explicit topk sparse attention + self.sparse_topk = sparse_topk + + self.attn_fn = F.softmax + + # add memory key / values + self.num_mem_kv = num_mem_kv + if num_mem_kv > 0: + self.mem_k = nn.Parameter(paddle.randn(heads, num_mem_kv, dim_head)) + self.mem_v = nn.Parameter(paddle.randn(heads, num_mem_kv, dim_head)) + + # attention on attention + self.attn_on_attn = on_attn + self.to_out = ( + nn.Sequential(nn.Linear(v_dim, dim * 2), nn.GLU()) + if on_attn + else nn.Linear(v_dim, dim) + ) + + def forward( + self, + x, + context=None, + mask=None, + context_mask=None, + rel_pos=None, + sinusoidal_emb=None, + rotary_pos_emb=None, + prev_attn=None, + mem=None, + seq_len=0, + ): + if not self.training: + self.is_export = True + b, n, _, h, talking_heads, collab_heads, has_context = ( + *x.shape, + self.heads, + self.talking_heads, + self.collab_heads, + exists(context), + ) + kv_input = default(context, x) + + q_input = x + k_input = kv_input + v_input = kv_input + + if exists(mem): + k_input = paddle.concat((mem, k_input), axis=-2) + v_input = paddle.concat((mem, v_input), axis=-2) + + if exists(sinusoidal_emb): + # in shortformer, the query would start at a position offset depending on the past cached memory + offset = k_input.shape[-2] - q_input.shape[-2] + q_input = q_input + sinusoidal_emb(q_input, offset=offset) + k_input = k_input + sinusoidal_emb(k_input) + q = self.to_q(q_input) + k = self.to_k(k_input) + v = self.to_v(v_input) + + def rearrange_q_k_v(x, h, is_export): + if is_export: + b, n, h_d = paddle.shape(x) + else: + b, n, h_d = x.shape + d = h_d // h + return x.reshape([b, n, h, d]).transpose([0, 2, 1, 3]) + + q, k, v = map( + lambda t: rearrange_q_k_v(t, h, is_export=self.is_export), (q, k, v) + ) + + input_mask = None + if any(map(exists, (mask, context_mask))): + q_mask = default( + mask, + lambda: paddle.ones( + (b, n), + ).cast(paddle.bool), + ) + k_mask = q_mask if not exists(context) else context_mask + k_mask = default( + k_mask, lambda: paddle.ones((b, k.shape[-2])).cast(paddle.bool) + ) + + q_mask = q_mask.reshape([q_mask.shape[0], 1, q_mask.shape[1], 1]) + k_mask = k_mask.reshape([k_mask.shape[0], 1, 1, k_mask.shape[1]]) + input_mask = q_mask * k_mask + + if collab_heads: + k = k.expand(-1, h, -1, -1) + dots = einsum("b h i d, b h j d -> b h i j", q, k) * self.scale + + mask_value = max_neg_value(dots) + + if exists(prev_attn): + dots = dots + prev_attn + + pre_softmax_attn = dots.clone() + + if talking_heads: + dots = einsum( + "b h i j, h k -> b k i j", dots, self.pre_softmax_proj + ).contiguous() + + if exists(rel_pos): + dots = rel_pos(dots) + + input_mask = input_mask.cast(paddle.bool) + if exists(input_mask): + + dots.masked_fill_(~input_mask, mask_value) + del input_mask + + if self.causal: + i, j = dots.shape[-2:] + r = paddle.arange(i) + r_shape = r.shape[0] + mask = r.reshape([1, 1, r_shape, 1]) < r.reshape([1, 1, 1, r_shape]) + + if self.is_export: + pad_list = [ + paddle.to_tensor(0, dtype="int32"), + paddle.to_tensor(0, dtype="int32"), + paddle.to_tensor(j - i, dtype="int32"), + paddle.to_tensor(0, dtype="int32"), + ] + mask = F.pad( + mask.cast(paddle.int32), + paddle.to_tensor(pad_list).cast(paddle.int32), + value=False, + ).cast(paddle.bool) + dots = dots.masked_fill_(mask, mask_value) + else: + mask = F.pad(mask.cast(paddle.int32), (0, 0, j - i, 0), value=False) + dots.masked_fill_(mask, mask_value) + del mask + if exists(self.sparse_topk) and self.sparse_topk < dots.shape[-1]: + top, _ = dots.topk(self.sparse_topk, dim=-1) + vk = top[..., -1].unsqueeze(-1).expand_as(dots) + mask = dots < vk + dots.masked_fill_(mask, mask_value) + del mask + + attn = self.attn_fn(dots, axis=-1) + post_softmax_attn = attn.clone() + + attn = self.dropout(attn) + + if talking_heads: + attn = einsum( + "b h i j, h k -> b k i j", attn, self.post_softmax_proj + ).contiguous() + out = einsum("b h i j, b h j d -> b h i d", attn, v) + + b, h, n, d = out.shape + out = out.transpose([0, 2, 1, 3]).reshape([b, n, h * d]) + + if exists(self.to_v_gate): + gates = self.gate_v(x) + out = out * gates.sigmoid() + + intermediates = Intermediates( + pre_softmax_attn=pre_softmax_attn, post_softmax_attn=post_softmax_attn + ) + + return self.to_out(out), intermediates + + +class AttentionLayers(nn.Layer): + def __init__( + self, + dim, + depth, + heads=8, + causal=False, + cross_attend=False, + only_cross=False, + use_scalenorm=False, + use_rmsnorm=False, + use_rezero=False, + rel_pos_bias=False, + rel_pos_num_buckets=32, + rel_pos_max_distance=128, + position_infused_attn=False, + rotary_pos_emb=False, + rotary_emb_dim=None, + custom_layers=None, + sandwich_coef=None, + par_ratio=None, + residual_attn=False, + cross_residual_attn=False, + macaron=False, + pre_norm=True, + gate_residual=False, + is_export=False, + **kwargs, + ): + super().__init__() + ff_kwargs, kwargs = groupby_prefix_and_trim("ff_", kwargs) + attn_kwargs, _ = groupby_prefix_and_trim("attn_", kwargs) + + dim_head = attn_kwargs.get("dim_head", DEFAULT_DIM_HEAD) + + self.dim = dim + self.depth = depth + self.layers = nn.LayerList([]) + + self.has_pos_emb = position_infused_attn or rel_pos_bias or rotary_pos_emb + self.pia_pos_emb = ( + FixedPositionalEmbedding(dim) if position_infused_attn else None + ) + + assert ( + rel_pos_num_buckets <= rel_pos_max_distance + ), "number of relative position buckets must be less than the relative position max distance" + + self.pre_norm = pre_norm + + self.residual_attn = residual_attn + self.cross_residual_attn = cross_residual_attn + self.cross_attend = cross_attend + self.rel_pos = None + + norm_class = ScaleNorm if use_scalenorm else nn.LayerNorm + norm_class = RMSNorm if use_rmsnorm else norm_class + norm_fn = partial(norm_class, dim) + + norm_fn = nn.Identity if use_rezero else norm_fn + branch_fn = Rezero if use_rezero else None + + if cross_attend and not only_cross: + default_block = ("a", "c", "f") + elif cross_attend and only_cross: + default_block = ("c", "f") + else: + default_block = ("a", "f") + if macaron: + default_block = ("f",) + default_block + + if exists(custom_layers): + layer_types = custom_layers + elif exists(par_ratio): + par_depth = depth * len(default_block) + assert 1 < par_ratio <= par_depth, "par ratio out of range" + default_block = tuple(filter(not_equals("f"), default_block)) + par_attn = par_depth // par_ratio + depth_cut = ( + par_depth * 2 // 3 + ) # 2 / 3 attention layer cutoff suggested by PAR paper + par_width = (depth_cut + depth_cut // par_attn) // par_attn + assert ( + len(default_block) <= par_width + ), "default block is too large for par_ratio" + par_block = default_block + ("f",) * (par_width - len(default_block)) + par_head = par_block * par_attn + layer_types = par_head + ("f",) * (par_depth - len(par_head)) + elif exists(sandwich_coef): + assert ( + sandwich_coef > 0 and sandwich_coef <= depth + ), "sandwich coefficient should be less than the depth" + layer_types = ( + ("a",) * sandwich_coef + + default_block * (depth - sandwich_coef) + + ("f",) * sandwich_coef + ) + else: + layer_types = default_block * depth + + self.layer_types = layer_types + self.num_attn_layers = len(list(filter(equals("a"), layer_types))) + for layer_type in self.layer_types: + if layer_type == "a": + layer = Attention( + dim, heads=heads, causal=causal, is_export=is_export, **attn_kwargs + ) + elif layer_type == "c": + layer = Attention(dim, heads=heads, is_export=is_export, **attn_kwargs) + elif layer_type == "f": + layer = FeedForward(dim, **ff_kwargs) + layer = layer if not macaron else Scale(0.5, layer) + else: + raise Exception(f"invalid layer type {layer_type}") + if isinstance(layer, Attention) and exists(branch_fn): + layer = branch_fn(layer) + residual_fn = Residual() + self.layers.append(nn.LayerList([norm_fn(), layer, residual_fn])) + + def forward( + self, + x, + context=None, + mask=None, + context_mask=None, + mems=None, + seq_len=0, + return_hiddens=False, + ): + assert not ( + self.cross_attend ^ exists(context) + ), "context must be passed in if cross_attend is set to True" + + hiddens = [] + intermediates = [] + prev_attn = None + prev_cross_attn = None + rotary_pos_emb = None + + mems = mems.copy() if exists(mems) else [None] * self.num_attn_layers + + for ind, (layer_type, (norm, block, residual_fn)) in enumerate( + zip(self.layer_types, self.layers) + ): + is_last = ind == (len(self.layers) - 1) + + if layer_type == "a": + hiddens.append(x) + layer_mem = mems.pop(0) + + residual = x + + if self.pre_norm: + x = norm(x) + + if layer_type == "a": + out, inter = block( + x, + mask=mask, + sinusoidal_emb=self.pia_pos_emb, + rel_pos=self.rel_pos, + rotary_pos_emb=rotary_pos_emb, + prev_attn=prev_attn, + mem=layer_mem, + ) + elif layer_type == "c": + out, inter = block( + x, + context=context, + mask=mask, + context_mask=context_mask, + prev_attn=prev_cross_attn, + ) + elif layer_type == "f": + out = block(x) + + x = residual_fn(out, residual) + + if layer_type in ("a", "c"): + intermediates.append(inter) + + if layer_type == "a" and self.residual_attn: + prev_attn = inter.pre_softmax_attn + elif layer_type == "c" and self.cross_residual_attn: + prev_cross_attn = inter.pre_softmax_attn + + if not self.pre_norm and not is_last: + x = norm(x) + + if return_hiddens: + intermediates = LayerIntermediates( + hiddens=hiddens, attn_intermediates=intermediates + ) + + return x, intermediates + + return x + + +class Encoder(AttentionLayers): + def __init__(self, **kwargs): + assert "causal" not in kwargs, "cannot set causality on encoder" + super().__init__(causal=False, **kwargs) + + +class Decoder(AttentionLayers): + def __init__(self, **kwargs): + assert "causal" not in kwargs, "cannot set causality on decoder" + super().__init__(causal=True, **kwargs) + + +class CrossAttender(AttentionLayers): + def __init__(self, **kwargs): + super().__init__(cross_attend=True, only_cross=True, **kwargs) + + +def create_latex_parameter(shape): + return paddle.create_parameter( + shape=shape, + dtype="float32", + default_initializer=paddle.nn.initializer.Assign(paddle.randn(shape)), + ) + + +class TransformerDecoder(nn.Layer): + def __init__( + self, + *, + num_tokens, + max_seq_len, + attn_layers, + emb_dim=None, + max_mem_len=0.0, + emb_dropout=0.0, + num_memory_tokens=None, + tie_embedding=False, + use_pos_emb=True, + is_export=False, + ): + super().__init__() + assert isinstance( + attn_layers, AttentionLayers + ), "attention layers must be one of Encoder or Decoder" + + dim = attn_layers.dim + emb_dim = default(emb_dim, dim) + + self.max_seq_len = max_seq_len + self.max_mem_len = max_mem_len + + self.token_emb = nn.Embedding(num_tokens, emb_dim) + self.pos_emb = ( + AbsolutePositionalEmbedding(emb_dim, max_seq_len) + if (use_pos_emb and not attn_layers.has_pos_emb) + else always(0) + ) + self.emb_dropout = nn.Dropout(emb_dropout) + + self.project_emb = nn.Linear(emb_dim, dim) if emb_dim != dim else nn.Identity() + self.attn_layers = attn_layers + self.norm = nn.LayerNorm(dim) + self.is_export = is_export + + self.init_() + + self.to_logits = ( + nn.Linear(dim, num_tokens) + if not tie_embedding + else lambda t: t @ self.token_emb.weight.t() + ) + + # memory tokens (like [cls]) from Memory Transformers paper + num_memory_tokens = default(num_memory_tokens, 0) + self.num_memory_tokens = num_memory_tokens + if num_memory_tokens > 0: + self.memory_tokens = create_latex_parameter([num_memory_tokens, dim]) + + # let funnel encoder know number of memory tokens, if specified + # TODO: think of a cleaner solution + if hasattr(attn_layers, "num_memory_tokens"): + attn_layers.num_memory_tokens = num_memory_tokens + + def init_(self): + normal_(self.token_emb.weight) + + def forward( + self, + x, + return_embeddings=False, + mask=None, + return_mems=False, + return_attn=False, + seq_len=0, + mems=None, + **kwargs, + ): + b, n, num_mem = *x.shape, self.num_memory_tokens + x = self.token_emb(x) + x = x + self.pos_emb(x) + + x = self.emb_dropout(x) + x = self.project_emb(x) + + x, intermediates = self.attn_layers( + x, mask=mask, mems=mems, return_hiddens=True, seq_len=seq_len, **kwargs + ) + x = self.norm(x) + if paddle.device.get_device().startswith("npu"): + x = x[:, num_mem:] + else: + mem, x = x[:, :num_mem], x[:, num_mem:] + out = self.to_logits(x) if not return_embeddings else x + if return_mems: + hiddens = intermediates.hiddens + new_mems = ( + list(map(lambda pair: paddle.concat(pair, axis=-2), zip(mems, hiddens))) + if exists(mems) + else hiddens + ) + new_mems = list( + map(lambda t: t[..., -self.max_mem_len :, :].detach(), new_mems) + ) + return out, new_mems + + if return_attn: + attn_maps = list( + map(lambda t: t.post_softmax_attn, intermediates.attn_intermediates) + ) + return out, attn_maps + + return out + + +def top_p(logits, thres=0.9): + sorted_logits, sorted_indices = paddle.sort(logits, descending=True) + cum_probs = paddle.cumsum(F.softmax(sorted_logits, axis=-1), axis=-1) + + sorted_indices_to_remove = cum_probs > (1 - thres) + sorted_indices_to_remove[:, 1:] = sorted_indices_to_remove[:, :-1].clone() + sorted_indices_to_remove[:, 0] = 0 + + sorted_logits[sorted_indices_to_remove] = float("-inf") + return sorted_logits.scatter(1, sorted_indices, sorted_logits) + + +# topk + + +def top_k(logits, thres=0.9): + k = int((1 - thres) * logits.shape[-1]) + val, ind = paddle.topk(logits, k) + probs = paddle.full_like(logits, float("-inf")) + probs = paddle.put_along_axis(probs, ind, val, 1) + return probs + + +class LaTeXOCRHead(nn.Layer): + """Implementation of LaTeX OCR decoder. + + Args: + encoded_feat: The encoded features with shape[N, 1, H//16, W//16] + tgt_seq: LaTeX-OCR labels with shape [N, L] , L is the max sequence length + xi: The first N-1 LaTeX-OCR sequences in tgt_seq with shape [N, L-1] + mask: The first N-1 LaTeX-OCR attention mask with shape [N, L-1] , L is the max sequence length + + Returns: + The predicted LaTeX sequences with shape [N, L-1, C], C is the number of LaTeX classes + """ + + def __init__( + self, + net=None, + in_channels=256, + out_channels=256, + pad_value=0, + decoder_args=None, + is_export=False, + ): + super().__init__() + decoder = Decoder( + dim=256, depth=4, heads=8, is_export=is_export, **decoder_args + ) + transformer_decoder = TransformerDecoder( + num_tokens=8000, + max_seq_len=512, + attn_layers=decoder, + is_export=is_export, + ) + self.temperature = 0.333 + self.bos_token = 1 + self.eos_token = 2 + self.max_length = 512 + self.pad_value = pad_value + + self.net = transformer_decoder + self.max_seq_len = self.net.max_seq_len + self.is_export = is_export + + @paddle.no_grad() + def generate( + self, + start_tokens, + seq_len, + eos_token=None, + temperature=1.0, + filter_logits_fn=top_k, + filter_thres=0.9, + **kwargs, + ): + was_training = self.net.training + num_dims = len(start_tokens.shape) + + if num_dims == 1: + start_tokens = start_tokens[None, :] + + b, t = start_tokens.shape + + self.net.eval() + out = start_tokens + mask = kwargs.pop("mask", None) + + if mask is None: + mask = paddle.full_like(out, True, dtype=paddle.bool) + + for _ in range(seq_len): + x = out[:, -self.max_seq_len :] + mask = mask[:, -self.max_seq_len :] + logits = self.net(x, mask=mask, **kwargs)[:, -1, :] + if filter_logits_fn in {top_k, top_p}: + filtered_logits = filter_logits_fn(logits, thres=filter_thres) + + probs = F.softmax(filtered_logits / temperature, axis=-1) + else: + raise NotImplementedError("The filter_logits_fn is not supported ") + + sample = paddle.multinomial(probs, 1) + out = paddle.concat((out, sample), axis=-1) + pad_mask = paddle.full(shape=[mask.shape[0], 1], fill_value=1, dtype="bool") + mask = paddle.concat((mask, pad_mask), axis=1) + if ( + eos_token is not None + and ( + paddle.cumsum((out == eos_token).cast(paddle.int64), 1)[:, -1] >= 1 + ).all() + ): + break + out = out[:, t:] + if num_dims == 1: + out = out.squeeze(0) + return out + + @paddle.no_grad() + def generate_export( + self, + start_tokens, + seq_len, + eos_token=None, + context=None, + temperature=1.0, + filter_logits_fn=None, + filter_thres=0.9, + **kwargs, + ): + was_training = self.net.training + num_dims = len(start_tokens.shape) + + if num_dims == 1: + start_tokens = start_tokens[None, :] + + b, t = start_tokens.shape + + self.net.eval() + out = start_tokens + mask = kwargs.pop("mask", None) + + if mask is None: + mask = paddle.full_like(out, True, dtype=paddle.bool) + + i_idx = paddle.full([], 0) + while i_idx < paddle.to_tensor(seq_len): + x = out[:, -self.max_seq_len :] + paddle.jit.api.set_dynamic_shape(x, [-1, -1]) + mask = mask[:, -self.max_seq_len :] + paddle.jit.api.set_dynamic_shape(mask, [-1, -1]) + logits = self.net(x, mask=mask, context=context, seq_len=i_idx, **kwargs)[ + :, -1, : + ] + if filter_logits_fn in {top_k, top_p}: + filtered_logits = filter_logits_fn(logits, thres=filter_thres) + + probs = F.softmax(filtered_logits / temperature, axis=-1) + + sample = paddle.multinomial(probs, 1) + out = paddle.concat((out, sample), axis=-1) + + pad_mask = paddle.full(shape=[mask.shape[0], 1], fill_value=1, dtype="bool") + mask = paddle.concat((mask, pad_mask), axis=1) + if ( + eos_token is not None + and ( + paddle.cumsum((out == eos_token).cast(paddle.int64), 1)[:, -1] >= 1 + ).all() + ): + break + i_idx += 1 + out = out[:, t:] + if num_dims == 1: + out = out.squeeze(0) + return out + + # forward for export + def forward(self, inputs, targets=None): + if not self.training: + self.is_export = True + encoded_feat = inputs + batch_num = encoded_feat.shape[0] + bos_tensor = paddle.full([batch_num, 1], self.bos_token, dtype=paddle.int64) + if self.is_export: + word_pred = self.generate_export( + bos_tensor, + self.max_seq_len, + eos_token=self.eos_token, + context=encoded_feat, + temperature=self.temperature, + filter_logits_fn=top_k, + ) + else: + word_pred = self.generate( + bos_tensor, + self.max_seq_len, + eos_token=self.eos_token, + context=encoded_feat, + temperature=self.temperature, + filter_logits_fn=top_k, + ) + return word_pred + + encoded_feat, tgt_seq, mask = inputs + kwargs = {"context": encoded_feat, "mask": mask.cast(paddle.bool)} + x = tgt_seq + xi = x[:, :-1] + + mask = kwargs.get("mask", None) + if mask is not None and mask.shape[1] == x.shape[1]: + mask = mask[:, :-1] + kwargs["mask"] = mask + out = self.net(xi, **kwargs) + + return out diff --git a/ppocr/modeling/heads/rec_multi_head.py b/ppocr/modeling/heads/rec_multi_head.py new file mode 100644 index 0000000..be44615 --- /dev/null +++ b/ppocr/modeling/heads/rec_multi_head.py @@ -0,0 +1,153 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F + +from ppocr.modeling.necks.rnn import ( + Im2Seq, + EncoderWithRNN, + EncoderWithFC, + SequenceEncoder, + EncoderWithSVTR, + trunc_normal_, + zeros_, +) +from .rec_ctc_head import CTCHead +from .rec_sar_head import SARHead +from .rec_nrtr_head import Transformer + + +class FCTranspose(nn.Layer): + def __init__(self, in_channels, out_channels, only_transpose=False): + super().__init__() + self.only_transpose = only_transpose + if not self.only_transpose: + self.fc = nn.Linear(in_channels, out_channels, bias_attr=False) + + def forward(self, x): + if self.only_transpose: + return x.transpose([0, 2, 1]) + else: + return self.fc(x.transpose([0, 2, 1])) + + +class AddPos(nn.Layer): + def __init__(self, dim, w): + super().__init__() + self.dec_pos_embed = self.create_parameter( + shape=[1, w, dim], default_initializer=zeros_ + ) + self.add_parameter("dec_pos_embed", self.dec_pos_embed) + trunc_normal_(self.dec_pos_embed) + + def forward(self, x): + x = x + self.dec_pos_embed[:, : x.shape[1], :] + return x + + +class MultiHead(nn.Layer): + def __init__(self, in_channels, out_channels_list, **kwargs): + super().__init__() + self.head_list = kwargs.pop("head_list") + self.use_pool = kwargs.get("use_pool", False) + self.use_pos = kwargs.get("use_pos", False) + self.in_channels = in_channels + if self.use_pool: + self.pool = nn.AvgPool2D(kernel_size=[3, 2], stride=[3, 2], padding=0) + self.gtc_head = "sar" + assert len(self.head_list) >= 2 + for idx, head_name in enumerate(self.head_list): + name = list(head_name)[0] + if name == "SARHead": + # sar head + sar_args = self.head_list[idx][name] + self.sar_head = eval(name)( + in_channels=in_channels, + out_channels=out_channels_list["SARLabelDecode"], + **sar_args, + ) + elif name == "NRTRHead": + gtc_args = self.head_list[idx][name] + max_text_length = gtc_args.get("max_text_length", 25) + nrtr_dim = gtc_args.get("nrtr_dim", 256) + num_decoder_layers = gtc_args.get("num_decoder_layers", 4) + if self.use_pos: + self.before_gtc = nn.Sequential( + nn.Flatten(2), + FCTranspose(in_channels, nrtr_dim), + AddPos(nrtr_dim, 80), + ) + else: + self.before_gtc = nn.Sequential( + nn.Flatten(2), FCTranspose(in_channels, nrtr_dim) + ) + + self.gtc_head = Transformer( + d_model=nrtr_dim, + nhead=nrtr_dim // 32, + num_encoder_layers=-1, + beam_size=-1, + num_decoder_layers=num_decoder_layers, + max_len=max_text_length, + dim_feedforward=nrtr_dim * 4, + out_channels=out_channels_list["NRTRLabelDecode"], + ) + elif name == "CTCHead": + # ctc neck + self.encoder_reshape = Im2Seq(in_channels) + neck_args = self.head_list[idx][name]["Neck"] + encoder_type = neck_args.pop("name") + self.ctc_encoder = SequenceEncoder( + in_channels=in_channels, encoder_type=encoder_type, **neck_args + ) + # ctc head + head_args = self.head_list[idx][name]["Head"] + self.ctc_head = eval(name)( + in_channels=self.ctc_encoder.out_channels, + out_channels=out_channels_list["CTCLabelDecode"], + **head_args, + ) + else: + raise NotImplementedError( + "{} is not supported in MultiHead yet".format(name) + ) + + def forward(self, x, targets=None): + if self.use_pool: + x = self.pool( + x.reshape([0, 3, -1, self.in_channels]).transpose([0, 3, 1, 2]) + ) + ctc_encoder = self.ctc_encoder(x) + ctc_out = self.ctc_head(ctc_encoder, targets) + head_out = dict() + head_out["ctc"] = ctc_out + head_out["ctc_neck"] = ctc_encoder + # eval mode + if not self.training: + return ctc_out + if self.gtc_head == "sar": + sar_out = self.sar_head(x, targets[1:]) + head_out["sar"] = sar_out + else: + gtc_out = self.gtc_head(self.before_gtc(x), targets[1:]) + head_out["gtc"] = gtc_out + return head_out diff --git a/ppocr/modeling/heads/rec_nrtr_head.py b/ppocr/modeling/heads/rec_nrtr_head.py new file mode 100644 index 0000000..34f911c --- /dev/null +++ b/ppocr/modeling/heads/rec_nrtr_head.py @@ -0,0 +1,705 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle.nn import Dropout, LayerNorm +import numpy as np +from ppocr.modeling.backbones.rec_svtrnet import Mlp, zeros_ +from paddle.nn.initializer import XavierNormal as xavier_normal_ + + +class Transformer(nn.Layer): + """A transformer model. User is able to modify the attributes as needed. The architecture + is based on the paper "Attention Is All You Need". Ashish Vaswani, Noam Shazeer, + Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N Gomez, Lukasz Kaiser, and + Illia Polosukhin. 2017. Attention is all you need. In Advances in Neural Information + Processing Systems, pages 6000-6010. + + Args: + d_model: the number of expected features in the encoder/decoder inputs (default=512). + nhead: the number of heads in the multiheadattention models (default=8). + num_encoder_layers: the number of sub-encoder-layers in the encoder (default=6). + num_decoder_layers: the number of sub-decoder-layers in the decoder (default=6). + dim_feedforward: the dimension of the feedforward network model (default=2048). + dropout: the dropout value (default=0.1). + custom_encoder: custom encoder (default=None). + custom_decoder: custom decoder (default=None). + """ + + def __init__( + self, + d_model=512, + nhead=8, + num_encoder_layers=6, + beam_size=0, + num_decoder_layers=6, + max_len=25, + dim_feedforward=1024, + attention_dropout_rate=0.0, + residual_dropout_rate=0.1, + in_channels=0, + out_channels=0, + scale_embedding=True, + ): + super(Transformer, self).__init__() + self.out_channels = out_channels + 1 + self.max_len = max_len + self.embedding = Embeddings( + d_model=d_model, + vocab=self.out_channels, + padding_idx=0, + scale_embedding=scale_embedding, + ) + self.positional_encoding = PositionalEncoding( + dropout=residual_dropout_rate, dim=d_model + ) + + if num_encoder_layers > 0: + self.encoder = nn.LayerList( + [ + TransformerBlock( + d_model, + nhead, + dim_feedforward, + attention_dropout_rate, + residual_dropout_rate, + with_self_attn=True, + with_cross_attn=False, + ) + for i in range(num_encoder_layers) + ] + ) + else: + self.encoder = None + + self.decoder = nn.LayerList( + [ + TransformerBlock( + d_model, + nhead, + dim_feedforward, + attention_dropout_rate, + residual_dropout_rate, + with_self_attn=True, + with_cross_attn=True, + ) + for i in range(num_decoder_layers) + ] + ) + + self.beam_size = beam_size + self.d_model = d_model + self.nhead = nhead + self.tgt_word_prj = nn.Linear(d_model, self.out_channels, bias_attr=False) + w0 = np.random.normal( + 0.0, d_model**-0.5, (d_model, self.out_channels) + ).astype(np.float32) + self.tgt_word_prj.weight.set_value(w0) + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + xavier_normal_(m.weight) + if m.bias is not None: + zeros_(m.bias) + + def forward_train(self, src, tgt): + tgt = tgt[:, :-1] + + tgt = self.embedding(tgt) + tgt = self.positional_encoding(tgt) + tgt_mask = self.generate_square_subsequent_mask(tgt.shape[1]) + + if self.encoder is not None: + src = self.positional_encoding(src) + for encoder_layer in self.encoder: + src = encoder_layer(src) + memory = src # B N C + else: + memory = src # B N C + for decoder_layer in self.decoder: + tgt = decoder_layer(tgt, memory, self_mask=tgt_mask) + output = tgt + logit = self.tgt_word_prj(output) + return logit + + def forward(self, src, targets=None): + """Take in and process masked source/target sequences. + Args: + src: the sequence to the encoder (required). + tgt: the sequence to the decoder (required). + Shape: + - src: :math:`(B, sN, C)`. + - tgt: :math:`(B, tN, C)`. + Examples: + >>> output = transformer_model(src, tgt) + """ + + if self.training: + max_len = targets[1].max() + tgt = targets[0][:, : 2 + max_len] + return self.forward_train(src, tgt) + else: + if self.beam_size > 0: + return self.forward_beam(src) + else: + return self.forward_test(src) + + def forward_test(self, src): + bs = src.shape[0] + if self.encoder is not None: + src = self.positional_encoding(src) + for encoder_layer in self.encoder: + src = encoder_layer(src) + memory = src # B N C + else: + memory = src + dec_seq = paddle.full((bs, 1), 2, dtype=paddle.int64) + dec_prob = paddle.full((bs, 1), 1.0, dtype=paddle.float32) + for len_dec_seq in range(1, paddle.to_tensor(self.max_len)): + dec_seq_embed = self.embedding(dec_seq) + dec_seq_embed = self.positional_encoding(dec_seq_embed) + tgt_mask = self.generate_square_subsequent_mask(dec_seq_embed.shape[1]) + tgt = dec_seq_embed + for decoder_layer in self.decoder: + tgt = decoder_layer(tgt, memory, self_mask=tgt_mask) + dec_output = tgt + dec_output = dec_output[:, -1, :] + word_prob = F.softmax(self.tgt_word_prj(dec_output), axis=-1) + preds_idx = paddle.argmax(word_prob, axis=-1) + if paddle.equal_all( + preds_idx, paddle.full(preds_idx.shape, 3, dtype="int64") + ): + break + preds_prob = paddle.max(word_prob, axis=-1) + dec_seq = paddle.concat( + [dec_seq, paddle.reshape(preds_idx, [-1, 1])], axis=1 + ) + dec_prob = paddle.concat( + [dec_prob, paddle.reshape(preds_prob, [-1, 1])], axis=1 + ) + return [dec_seq, dec_prob] + + def forward_beam(self, images): + """Translation work in one batch""" + + def get_inst_idx_to_tensor_position_map(inst_idx_list): + """Indicate the position of an instance in a tensor.""" + return { + inst_idx: tensor_position + for tensor_position, inst_idx in enumerate(inst_idx_list) + } + + def collect_active_part( + beamed_tensor, curr_active_inst_idx, n_prev_active_inst, n_bm + ): + """Collect tensor parts associated to active instances.""" + + beamed_tensor_shape = beamed_tensor.shape + n_curr_active_inst = len(curr_active_inst_idx) + new_shape = ( + n_curr_active_inst * n_bm, + beamed_tensor_shape[1], + beamed_tensor_shape[2], + ) + + beamed_tensor = beamed_tensor.reshape([n_prev_active_inst, -1]) + beamed_tensor = beamed_tensor.index_select(curr_active_inst_idx, axis=0) + beamed_tensor = beamed_tensor.reshape(new_shape) + + return beamed_tensor + + def collate_active_info( + src_enc, inst_idx_to_position_map, active_inst_idx_list + ): + # Sentences which are still active are collected, + # so the decoder will not run on completed sentences. + + n_prev_active_inst = len(inst_idx_to_position_map) + active_inst_idx = [ + inst_idx_to_position_map[k] for k in active_inst_idx_list + ] + active_inst_idx = paddle.to_tensor(active_inst_idx, dtype="int64") + active_src_enc = collect_active_part( + src_enc.transpose([1, 0, 2]), active_inst_idx, n_prev_active_inst, n_bm + ).transpose([1, 0, 2]) + active_inst_idx_to_position_map = get_inst_idx_to_tensor_position_map( + active_inst_idx_list + ) + return active_src_enc, active_inst_idx_to_position_map + + def beam_decode_step( + inst_dec_beams, len_dec_seq, enc_output, inst_idx_to_position_map, n_bm + ): + """Decode and update beam status, and then return active beam idx""" + + def prepare_beam_dec_seq(inst_dec_beams, len_dec_seq): + dec_partial_seq = [ + b.get_current_state() for b in inst_dec_beams if not b.done + ] + dec_partial_seq = paddle.stack(dec_partial_seq) + dec_partial_seq = dec_partial_seq.reshape([-1, len_dec_seq]) + return dec_partial_seq + + def predict_word(dec_seq, enc_output, n_active_inst, n_bm): + dec_seq = self.embedding(dec_seq) + dec_seq = self.positional_encoding(dec_seq) + tgt_mask = self.generate_square_subsequent_mask(dec_seq.shape[1]) + tgt = dec_seq + for decoder_layer in self.decoder: + tgt = decoder_layer(tgt, enc_output, self_mask=tgt_mask) + dec_output = tgt + dec_output = dec_output[:, -1, :] # Pick the last step: (bh * bm) * d_h + word_prob = F.softmax(self.tgt_word_prj(dec_output), axis=1) + word_prob = paddle.reshape(word_prob, [n_active_inst, n_bm, -1]) + return word_prob + + def collect_active_inst_idx_list( + inst_beams, word_prob, inst_idx_to_position_map + ): + active_inst_idx_list = [] + for inst_idx, inst_position in inst_idx_to_position_map.items(): + is_inst_complete = inst_beams[inst_idx].advance( + word_prob[inst_position] + ) + if not is_inst_complete: + active_inst_idx_list += [inst_idx] + + return active_inst_idx_list + + n_active_inst = len(inst_idx_to_position_map) + dec_seq = prepare_beam_dec_seq(inst_dec_beams, len_dec_seq) + word_prob = predict_word(dec_seq, enc_output, n_active_inst, n_bm) + # Update the beam with predicted word prob information and collect incomplete instances + active_inst_idx_list = collect_active_inst_idx_list( + inst_dec_beams, word_prob, inst_idx_to_position_map + ) + return active_inst_idx_list + + def collect_hypothesis_and_scores(inst_dec_beams, n_best): + all_hyp, all_scores = [], [] + for inst_idx in range(len(inst_dec_beams)): + scores, tail_idxs = inst_dec_beams[inst_idx].sort_scores() + all_scores += [scores[:n_best]] + hyps = [ + inst_dec_beams[inst_idx].get_hypothesis(i) + for i in tail_idxs[:n_best] + ] + all_hyp += [hyps] + return all_hyp, all_scores + + with paddle.no_grad(): + # -- Encode + if self.encoder is not None: + src = self.positional_encoding(images) + src_enc = self.encoder(src) + else: + src_enc = images + + n_bm = self.beam_size + src_shape = src_enc.shape + inst_dec_beams = [Beam(n_bm) for _ in range(1)] + active_inst_idx_list = list(range(1)) + # Repeat data for beam search + src_enc = paddle.tile(src_enc, [1, n_bm, 1]) + inst_idx_to_position_map = get_inst_idx_to_tensor_position_map( + active_inst_idx_list + ) + # Decode + for len_dec_seq in range(1, paddle.to_tensor(self.max_len)): + src_enc_copy = src_enc.clone() + active_inst_idx_list = beam_decode_step( + inst_dec_beams, + len_dec_seq, + src_enc_copy, + inst_idx_to_position_map, + n_bm, + ) + if not active_inst_idx_list: + break # all instances have finished their path to + src_enc, inst_idx_to_position_map = collate_active_info( + src_enc_copy, inst_idx_to_position_map, active_inst_idx_list + ) + batch_hyp, batch_scores = collect_hypothesis_and_scores(inst_dec_beams, 1) + result_hyp = [] + hyp_scores = [] + for bs_hyp, score in zip(batch_hyp, batch_scores): + l = len(bs_hyp[0]) + bs_hyp_pad = bs_hyp[0] + [3] * (25 - l) + result_hyp.append(bs_hyp_pad) + score = float(score) / l + hyp_score = [score for _ in range(25)] + hyp_scores.append(hyp_score) + return [ + paddle.to_tensor(np.array(result_hyp), dtype=paddle.int64), + paddle.to_tensor(hyp_scores), + ] + + def generate_square_subsequent_mask(self, sz): + """Generate a square mask for the sequence. The masked positions are filled with float('-inf'). + Unmasked positions are filled with float(0.0). + """ + mask = paddle.zeros([sz, sz], dtype="float32") + mask_inf = paddle.triu( + paddle.full(shape=[sz, sz], dtype="float32", fill_value=float("-inf")), + diagonal=1, + ) + mask = mask + mask_inf + return mask.unsqueeze([0, 1]) + + +class MultiheadAttention(nn.Layer): + """Allows the model to jointly attend to information + from different representation subspaces. + See reference: Attention Is All You Need + + .. math:: + \text{MultiHead}(Q, K, V) = \text{Concat}(head_1,\dots,head_h)W^O + \text{where} head_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V) + + Args: + embed_dim: total dimension of the model + num_heads: parallel attention layers, or heads + + """ + + def __init__(self, embed_dim, num_heads, dropout=0.0, self_attn=False): + super(MultiheadAttention, self).__init__() + self.embed_dim = embed_dim + self.num_heads = num_heads + # self.dropout = dropout + self.head_dim = embed_dim // num_heads + assert ( + self.head_dim * num_heads == self.embed_dim + ), "embed_dim must be divisible by num_heads" + self.scale = self.head_dim**-0.5 + self.self_attn = self_attn + if self_attn: + self.qkv = nn.Linear(embed_dim, embed_dim * 3) + else: + self.q = nn.Linear(embed_dim, embed_dim) + self.kv = nn.Linear(embed_dim, embed_dim * 2) + self.attn_drop = nn.Dropout(dropout) + self.out_proj = nn.Linear(embed_dim, embed_dim) + + def forward(self, query, key=None, attn_mask=None): + qN = query.shape[1] + + if self.self_attn: + qkv = ( + self.qkv(query) + .reshape((0, qN, 3, self.num_heads, self.head_dim)) + .transpose((2, 0, 3, 1, 4)) + ) + q, k, v = qkv[0], qkv[1], qkv[2] + else: + kN = key.shape[1] + q = ( + self.q(query) + .reshape([0, qN, self.num_heads, self.head_dim]) + .transpose([0, 2, 1, 3]) + ) + kv = ( + self.kv(key) + .reshape((0, kN, 2, self.num_heads, self.head_dim)) + .transpose((2, 0, 3, 1, 4)) + ) + k, v = kv[0], kv[1] + + attn = (q.matmul(k.transpose((0, 1, 3, 2)))) * self.scale + + if attn_mask is not None: + attn += attn_mask + + attn = F.softmax(attn, axis=-1) + attn = self.attn_drop(attn) + + x = (attn.matmul(v)).transpose((0, 2, 1, 3)).reshape((0, qN, self.embed_dim)) + x = self.out_proj(x) + + return x + + +class TransformerBlock(nn.Layer): + def __init__( + self, + d_model, + nhead, + dim_feedforward=2048, + attention_dropout_rate=0.0, + residual_dropout_rate=0.1, + with_self_attn=True, + with_cross_attn=False, + epsilon=1e-5, + ): + super(TransformerBlock, self).__init__() + self.with_self_attn = with_self_attn + if with_self_attn: + self.self_attn = MultiheadAttention( + d_model, nhead, dropout=attention_dropout_rate, self_attn=with_self_attn + ) + self.norm1 = LayerNorm(d_model, epsilon=epsilon) + self.dropout1 = Dropout(residual_dropout_rate) + self.with_cross_attn = with_cross_attn + if with_cross_attn: + self.cross_attn = ( + MultiheadAttention( # for self_attn of encoder or cross_attn of decoder + d_model, nhead, dropout=attention_dropout_rate + ) + ) + self.norm2 = LayerNorm(d_model, epsilon=epsilon) + self.dropout2 = Dropout(residual_dropout_rate) + + self.mlp = Mlp( + in_features=d_model, + hidden_features=dim_feedforward, + act_layer=nn.ReLU, + drop=residual_dropout_rate, + ) + + self.norm3 = LayerNorm(d_model, epsilon=epsilon) + + self.dropout3 = Dropout(residual_dropout_rate) + + def forward(self, tgt, memory=None, self_mask=None, cross_mask=None): + if self.with_self_attn: + tgt1 = self.self_attn(tgt, attn_mask=self_mask) + tgt = self.norm1(tgt + self.dropout1(tgt1)) + + if self.with_cross_attn: + tgt2 = self.cross_attn(tgt, key=memory, attn_mask=cross_mask) + tgt = self.norm2(tgt + self.dropout2(tgt2)) + tgt = self.norm3(tgt + self.dropout3(self.mlp(tgt))) + return tgt + + +class PositionalEncoding(nn.Layer): + """Inject some information about the relative or absolute position of the tokens + in the sequence. The positional encodings have the same dimension as + the embeddings, so that the two can be summed. Here, we use sine and cosine + functions of different frequencies. + .. math:: + \text{PosEncoder}(pos, 2i) = sin(pos/10000^(2i/d_model)) + \text{PosEncoder}(pos, 2i+1) = cos(pos/10000^(2i/d_model)) + \text{where pos is the word position and i is the embed idx) + Args: + d_model: the embed dim (required). + dropout: the dropout value (default=0.1). + max_len: the max. length of the incoming sequence (default=5000). + Examples: + >>> pos_encoder = PositionalEncoding(d_model) + """ + + def __init__(self, dropout, dim, max_len=5000): + super(PositionalEncoding, self).__init__() + self.dropout = nn.Dropout(p=dropout) + + pe = paddle.zeros([max_len, dim]) + position = paddle.arange(0, max_len, dtype=paddle.float32).unsqueeze(1) + div_term = paddle.exp( + paddle.arange(0, dim, 2).astype("float32") * (-math.log(10000.0) / dim) + ) + pe[:, 0::2] = paddle.sin(position * div_term) + pe[:, 1::2] = paddle.cos(position * div_term) + pe = paddle.unsqueeze(pe, 0) + pe = paddle.transpose(pe, [1, 0, 2]) + self.register_buffer("pe", pe) + + def forward(self, x): + """Inputs of forward function + Args: + x: the sequence fed to the positional encoder model (required). + Shape: + x: [sequence length, batch size, embed dim] + output: [sequence length, batch size, embed dim] + Examples: + >>> output = pos_encoder(x) + """ + x = x.transpose([1, 0, 2]) + x = x + self.pe[: x.shape[0], :] + return self.dropout(x).transpose([1, 0, 2]) + + +class PositionalEncoding_2d(nn.Layer): + """Inject some information about the relative or absolute position of the tokens + in the sequence. The positional encodings have the same dimension as + the embeddings, so that the two can be summed. Here, we use sine and cosine + functions of different frequencies. + .. math:: + \text{PosEncoder}(pos, 2i) = sin(pos/10000^(2i/d_model)) + \text{PosEncoder}(pos, 2i+1) = cos(pos/10000^(2i/d_model)) + \text{where pos is the word position and i is the embed idx) + Args: + d_model: the embed dim (required). + dropout: the dropout value (default=0.1). + max_len: the max. length of the incoming sequence (default=5000). + Examples: + >>> pos_encoder = PositionalEncoding(d_model) + """ + + def __init__(self, dropout, dim, max_len=5000): + super(PositionalEncoding_2d, self).__init__() + self.dropout = nn.Dropout(p=dropout) + + pe = paddle.zeros([max_len, dim]) + position = paddle.arange(0, max_len, dtype=paddle.float32).unsqueeze(1) + div_term = paddle.exp( + paddle.arange(0, dim, 2).astype("float32") * (-math.log(10000.0) / dim) + ) + pe[:, 0::2] = paddle.sin(position * div_term) + pe[:, 1::2] = paddle.cos(position * div_term) + pe = paddle.transpose(paddle.unsqueeze(pe, 0), [1, 0, 2]) + self.register_buffer("pe", pe) + + self.avg_pool_1 = nn.AdaptiveAvgPool2D((1, 1)) + self.linear1 = nn.Linear(dim, dim) + self.linear1.weight.data.fill_(1.0) + self.avg_pool_2 = nn.AdaptiveAvgPool2D((1, 1)) + self.linear2 = nn.Linear(dim, dim) + self.linear2.weight.data.fill_(1.0) + + def forward(self, x): + """Inputs of forward function + Args: + x: the sequence fed to the positional encoder model (required). + Shape: + x: [sequence length, batch size, embed dim] + output: [sequence length, batch size, embed dim] + Examples: + >>> output = pos_encoder(x) + """ + w_pe = self.pe[: x.shape[-1], :] + w1 = self.linear1(self.avg_pool_1(x).squeeze()).unsqueeze(0) + w_pe = w_pe * w1 + w_pe = paddle.transpose(w_pe, [1, 2, 0]) + w_pe = paddle.unsqueeze(w_pe, 2) + + h_pe = self.pe[: x.shape.shape[-2], :] + w2 = self.linear2(self.avg_pool_2(x).squeeze()).unsqueeze(0) + h_pe = h_pe * w2 + h_pe = paddle.transpose(h_pe, [1, 2, 0]) + h_pe = paddle.unsqueeze(h_pe, 3) + + x = x + w_pe + h_pe + x = paddle.transpose( + paddle.reshape(x, [x.shape[0], x.shape[1], x.shape[2] * x.shape[3]]), + [2, 0, 1], + ) + + return self.dropout(x) + + +class Embeddings(nn.Layer): + def __init__(self, d_model, vocab, padding_idx=None, scale_embedding=True): + super(Embeddings, self).__init__() + self.embedding = nn.Embedding(vocab, d_model, padding_idx=padding_idx) + w0 = np.random.normal(0.0, d_model**-0.5, (vocab, d_model)).astype(np.float32) + self.embedding.weight.set_value(w0) + self.d_model = d_model + self.scale_embedding = scale_embedding + + def forward(self, x): + if self.scale_embedding: + x = self.embedding(x) + return x * math.sqrt(self.d_model) + return self.embedding(x) + + +class Beam: + """Beam search""" + + def __init__(self, size, device=False): + self.size = size + self._done = False + # The score for each translation on the beam. + self.scores = paddle.zeros((size,), dtype=paddle.float32) + self.all_scores = [] + # The backpointers at each time-step. + self.prev_ks = [] + # The outputs at each time-step. + self.next_ys = [paddle.full((size,), 0, dtype=paddle.int64)] + self.next_ys[0][0] = 2 + + def get_current_state(self): + "Get the outputs for the current timestep." + return self.get_tentative_hypothesis() + + def get_current_origin(self): + "Get the backpointers for the current timestep." + return self.prev_ks[-1] + + @property + def done(self): + return self._done + + def advance(self, word_prob): + "Update beam status and check if finished or not." + num_words = word_prob.shape[1] + + # Sum the previous scores. + if len(self.prev_ks) > 0: + beam_lk = word_prob + self.scores.unsqueeze(1).expand_as(word_prob) + else: + beam_lk = word_prob[0] + + flat_beam_lk = beam_lk.reshape([-1]) + best_scores, best_scores_id = flat_beam_lk.topk( + self.size, 0, True, True + ) # 1st sort + self.all_scores.append(self.scores) + self.scores = best_scores + # bestScoresId is flattened as a (beam x word) array, + # so we need to calculate which word and beam each score came from + prev_k = best_scores_id // num_words + self.prev_ks.append(prev_k) + self.next_ys.append(best_scores_id - prev_k * num_words) + # End condition is when top-of-beam is EOS. + if self.next_ys[-1][0] == 3: + self._done = True + self.all_scores.append(self.scores) + + return self._done + + def sort_scores(self): + "Sort the scores." + return self.scores, paddle.to_tensor( + [i for i in range(int(self.scores.shape[0]))], dtype="int32" + ) + + def get_the_best_score_and_idx(self): + "Get the score of the best in the beam." + scores, ids = self.sort_scores() + return scores[1], ids[1] + + def get_tentative_hypothesis(self): + "Get the decoded sequence for the current timestep." + if len(self.next_ys) == 1: + dec_seq = self.next_ys[0].unsqueeze(1) + else: + _, keys = self.sort_scores() + hyps = [self.get_hypothesis(k) for k in keys] + hyps = [[2] + h for h in hyps] + dec_seq = paddle.to_tensor(hyps, dtype="int64") + return dec_seq + + def get_hypothesis(self, k): + """Walk back to construct the full hypothesis.""" + hyp = [] + for j in range(len(self.prev_ks) - 1, -1, -1): + hyp.append(self.next_ys[j + 1][k]) + k = self.prev_ks[j][k] + return list(map(lambda x: x.item(), hyp[::-1])) diff --git a/ppocr/modeling/heads/rec_parseq_head.py b/ppocr/modeling/heads/rec_parseq_head.py new file mode 100644 index 0000000..cf79c7e --- /dev/null +++ b/ppocr/modeling/heads/rec_parseq_head.py @@ -0,0 +1,504 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Code was based on https://github.com/baudm/parseq/blob/main/strhub/models/parseq/system.py +# reference: https://arxiv.org/abs/2207.06966 + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn, ParamAttr +from paddle.nn import functional as F +import numpy as np +from .self_attention import WrapEncoderForFeature +from .self_attention import WrapEncoder +from collections import OrderedDict +from typing import Optional +import copy +from itertools import permutations + + +class DecoderLayer(paddle.nn.Layer): + """A Transformer decoder layer supporting two-stream attention (XLNet) + This implements a pre-LN decoder, as opposed to the post-LN default in PyTorch.""" + + def __init__( + self, + d_model, + nhead, + dim_feedforward=2048, + dropout=0.1, + activation="gelu", + layer_norm_eps=1e-05, + ): + super().__init__() + self.self_attn = paddle.nn.MultiHeadAttention( + d_model, nhead, dropout=dropout, need_weights=True + ) # paddle.nn.MultiHeadAttention默认为batch_first模式 + self.cross_attn = paddle.nn.MultiHeadAttention( + d_model, nhead, dropout=dropout, need_weights=True + ) + self.linear1 = paddle.nn.Linear( + in_features=d_model, out_features=dim_feedforward + ) + self.dropout = paddle.nn.Dropout(p=dropout) + self.linear2 = paddle.nn.Linear( + in_features=dim_feedforward, out_features=d_model + ) + self.norm1 = paddle.nn.LayerNorm( + normalized_shape=d_model, epsilon=layer_norm_eps + ) + self.norm2 = paddle.nn.LayerNorm( + normalized_shape=d_model, epsilon=layer_norm_eps + ) + self.norm_q = paddle.nn.LayerNorm( + normalized_shape=d_model, epsilon=layer_norm_eps + ) + self.norm_c = paddle.nn.LayerNorm( + normalized_shape=d_model, epsilon=layer_norm_eps + ) + self.dropout1 = paddle.nn.Dropout(p=dropout) + self.dropout2 = paddle.nn.Dropout(p=dropout) + self.dropout3 = paddle.nn.Dropout(p=dropout) + if activation == "gelu": + self.activation = paddle.nn.GELU() + + def __setstate__(self, state): + if "activation" not in state: + state["activation"] = paddle.nn.functional.gelu + super().__setstate__(state) + + def forward_stream( + self, tgt, tgt_norm, tgt_kv, memory, tgt_mask, tgt_key_padding_mask + ): + """Forward pass for a single stream (i.e. content or query) + tgt_norm is just a LayerNorm'd tgt. Added as a separate parameter for efficiency. + Both tgt_kv and memory are expected to be LayerNorm'd too. + memory is LayerNorm'd by ViT. + """ + if tgt_key_padding_mask is not None: + tgt_mask1 = (tgt_mask != float("-inf"))[None, None, :, :] & ( + tgt_key_padding_mask[:, None, None, :] == False + ) + tgt2, sa_weights = self.self_attn( + tgt_norm, tgt_kv, tgt_kv, attn_mask=tgt_mask1 + ) + else: + tgt2, sa_weights = self.self_attn( + tgt_norm, tgt_kv, tgt_kv, attn_mask=tgt_mask + ) + + tgt = tgt + self.dropout1(tgt2) + tgt2, ca_weights = self.cross_attn(self.norm1(tgt), memory, memory) + tgt = tgt + self.dropout2(tgt2) + tgt2 = self.linear2( + self.dropout(self.activation(self.linear1(self.norm2(tgt)))) + ) + tgt = tgt + self.dropout3(tgt2) + return tgt, sa_weights, ca_weights + + def forward( + self, + query, + content, + memory, + query_mask=None, + content_mask=None, + content_key_padding_mask=None, + update_content=True, + ): + query_norm = self.norm_q(query) + content_norm = self.norm_c(content) + query = self.forward_stream( + query, + query_norm, + content_norm, + memory, + query_mask, + content_key_padding_mask, + )[0] + if update_content: + content = self.forward_stream( + content, + content_norm, + content_norm, + memory, + content_mask, + content_key_padding_mask, + )[0] + return query, content + + +def get_clones(module, N): + return paddle.nn.LayerList([copy.deepcopy(module) for i in range(N)]) + + +class Decoder(paddle.nn.Layer): + __constants__ = ["norm"] + + def __init__(self, decoder_layer, num_layers, norm): + super().__init__() + self.layers = get_clones(decoder_layer, num_layers) + self.num_layers = num_layers + self.norm = norm + + def forward( + self, + query, + content, + memory, + query_mask: Optional[paddle.Tensor] = None, + content_mask: Optional[paddle.Tensor] = None, + content_key_padding_mask: Optional[paddle.Tensor] = None, + ): + for i, mod in enumerate(self.layers): + last = i == len(self.layers) - 1 + query, content = mod( + query, + content, + memory, + query_mask, + content_mask, + content_key_padding_mask, + update_content=not last, + ) + query = self.norm(query) + return query + + +class TokenEmbedding(paddle.nn.Layer): + def __init__(self, charset_size: int, embed_dim: int): + super().__init__() + self.embedding = paddle.nn.Embedding( + num_embeddings=charset_size, embedding_dim=embed_dim + ) + self.embed_dim = embed_dim + + def forward(self, tokens: paddle.Tensor): + return math.sqrt(self.embed_dim) * self.embedding(tokens.astype(paddle.int64)) + + +def trunc_normal_init(param, **kwargs): + initializer = nn.initializer.TruncatedNormal(**kwargs) + initializer(param, param.block) + + +def constant_init(param, **kwargs): + initializer = nn.initializer.Constant(**kwargs) + initializer(param, param.block) + + +def kaiming_normal_init(param, **kwargs): + initializer = nn.initializer.KaimingNormal(**kwargs) + initializer(param, param.block) + + +class ParseQHead(nn.Layer): + def __init__( + self, + out_channels, + max_text_length, + embed_dim, + dec_num_heads, + dec_mlp_ratio, + dec_depth, + perm_num, + perm_forward, + perm_mirrored, + decode_ar, + refine_iters, + dropout, + **kwargs, + ): + super().__init__() + + self.bos_id = out_channels - 2 + self.eos_id = 0 + self.pad_id = out_channels - 1 + + self.max_label_length = max_text_length + self.decode_ar = decode_ar + self.refine_iters = refine_iters + decoder_layer = DecoderLayer( + embed_dim, dec_num_heads, embed_dim * dec_mlp_ratio, dropout + ) + self.decoder = Decoder( + decoder_layer, + num_layers=dec_depth, + norm=paddle.nn.LayerNorm(normalized_shape=embed_dim), + ) + self.rng = np.random.default_rng() + self.max_gen_perms = perm_num // 2 if perm_mirrored else perm_num + self.perm_forward = perm_forward + self.perm_mirrored = perm_mirrored + self.head = paddle.nn.Linear( + in_features=embed_dim, out_features=out_channels - 2 + ) + self.text_embed = TokenEmbedding(out_channels, embed_dim) + self.pos_queries = paddle.create_parameter( + shape=paddle.empty(shape=[1, max_text_length + 1, embed_dim]).shape, + dtype=paddle.empty(shape=[1, max_text_length + 1, embed_dim]).numpy().dtype, + default_initializer=paddle.nn.initializer.Assign( + paddle.empty(shape=[1, max_text_length + 1, embed_dim]) + ), + ) + self.pos_queries.stop_gradient = not True + self.dropout = paddle.nn.Dropout(p=dropout) + self._device = self.parameters()[0].place + trunc_normal_init(self.pos_queries, std=0.02) + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, paddle.nn.Linear): + trunc_normal_init(m.weight, std=0.02) + if m.bias is not None: + constant_init(m.bias, value=0.0) + elif isinstance(m, paddle.nn.Embedding): + trunc_normal_init(m.weight, std=0.02) + if m._padding_idx is not None: + m.weight.data[m._padding_idx].zero_() + elif isinstance(m, paddle.nn.Conv2D): + kaiming_normal_init(m.weight, fan_in=None, nonlinearity="relu") + if m.bias is not None: + constant_init(m.bias, value=0.0) + elif isinstance( + m, (paddle.nn.LayerNorm, paddle.nn.BatchNorm2D, paddle.nn.GroupNorm) + ): + constant_init(m.weight, value=1.0) + constant_init(m.bias, value=0.0) + + def no_weight_decay(self): + param_names = {"text_embed.embedding.weight", "pos_queries"} + enc_param_names = {("encoder." + n) for n in self.encoder.no_weight_decay()} + return param_names.union(enc_param_names) + + def encode(self, img): + return self.encoder(img) + + def decode( + self, + tgt, + memory, + tgt_mask=None, + tgt_padding_mask=None, + tgt_query=None, + tgt_query_mask=None, + ): + N, L = tgt.shape + null_ctx = self.text_embed(tgt[:, :1]) + if L != 1: + tgt_emb = self.pos_queries[:, : L - 1] + self.text_embed(tgt[:, 1:]) + tgt_emb = self.dropout(paddle.concat(x=[null_ctx, tgt_emb], axis=1)) + else: + tgt_emb = self.dropout(null_ctx) + if tgt_query is None: + tgt_query = self.pos_queries[:, :L].expand(shape=[N, -1, -1]) + tgt_query = self.dropout(tgt_query) + return self.decoder( + tgt_query, tgt_emb, memory, tgt_query_mask, tgt_mask, tgt_padding_mask + ) + + def forward_test(self, memory, max_length=None): + testing = max_length is None + max_length = ( + self.max_label_length + if max_length is None + else min(max_length, self.max_label_length) + ) + bs = memory.shape[0] + num_steps = max_length + 1 + + pos_queries = self.pos_queries[:, :num_steps].expand(shape=[bs, -1, -1]) + tgt_mask = query_mask = paddle.triu( + x=paddle.full(shape=(num_steps, num_steps), fill_value=float("-inf")), + diagonal=1, + ) + if self.decode_ar: + tgt_in = paddle.full(shape=(bs, num_steps), fill_value=self.pad_id).astype( + "int64" + ) + tgt_in[:, (0)] = self.bos_id + + logits = [] + for i in range(paddle.to_tensor(num_steps)): + j = i + 1 + tgt_out = self.decode( + tgt_in[:, :j], + memory, + tgt_mask[:j, :j], + tgt_query=pos_queries[:, i:j], + tgt_query_mask=query_mask[i:j, :j], + ) + p_i = self.head(tgt_out) + logits.append(p_i) + if j < num_steps: + tgt_in[:, (j)] = p_i.squeeze().argmax(axis=-1) + if ( + testing + and (tgt_in == self.eos_id) + .astype("bool") + .any(axis=-1) + .astype("bool") + .all() + ): + break + logits = paddle.concat(x=logits, axis=1) + else: + tgt_in = paddle.full(shape=(bs, 1), fill_value=self.bos_id).astype("int64") + tgt_out = self.decode(tgt_in, memory, tgt_query=pos_queries) + logits = self.head(tgt_out) + if self.refine_iters: + temp = paddle.triu( + x=paddle.ones(shape=[num_steps, num_steps], dtype="bool"), diagonal=2 + ) + posi = paddle.where(temp == True) + query_mask[posi] = 0 + bos = paddle.full(shape=(bs, 1), fill_value=self.bos_id).astype("int64") + for i in range(self.refine_iters): + tgt_in = paddle.concat(x=[bos, logits[:, :-1].argmax(axis=-1)], axis=1) + tgt_padding_mask = (tgt_in == self.eos_id).astype(dtype="int32") + tgt_padding_mask = tgt_padding_mask.cpu() + tgt_padding_mask = tgt_padding_mask.cumsum(axis=-1) > 0 + tgt_padding_mask = ( + tgt_padding_mask.cuda().astype(dtype="float32") == 1.0 + ) + tgt_out = self.decode( + tgt_in, + memory, + tgt_mask, + tgt_padding_mask, + tgt_query=pos_queries, + tgt_query_mask=query_mask[:, : tgt_in.shape[1]], + ) + logits = self.head(tgt_out) + + # transfer to probability + logits = F.softmax(logits, axis=-1) + + final_output = {"predict": logits} + + return final_output + + def gen_tgt_perms(self, tgt): + """Generate shared permutations for the whole batch. + This works because the same attention mask can be used for the shorter sequences + because of the padding mask. + """ + max_num_chars = tgt.shape[1] - 2 + if max_num_chars == 1: + return paddle.arange(end=3).unsqueeze(axis=0) + perms = [paddle.arange(end=max_num_chars)] if self.perm_forward else [] + max_perms = math.factorial(max_num_chars) + if self.perm_mirrored: + max_perms //= 2 + num_gen_perms = min(self.max_gen_perms, max_perms) + if max_num_chars < 5: + if max_num_chars == 4 and self.perm_mirrored: + selector = [0, 3, 4, 6, 9, 10, 12, 16, 17, 18, 19, 21] + else: + selector = list(range(max_perms)) + perm_pool = paddle.to_tensor( + data=list(permutations(range(max_num_chars), max_num_chars)), + place=self._device, + )[selector] + if self.perm_forward: + perm_pool = perm_pool[1:] + perms = paddle.stack(x=perms) + if len(perm_pool): + i = self.rng.choice( + len(perm_pool), size=num_gen_perms - len(perms), replace=False + ) + perms = paddle.concat(x=[perms, perm_pool[i]]) + else: + perms.extend( + [ + paddle.randperm(n=max_num_chars) + for _ in range(num_gen_perms - len(perms)) + ] + ) + perms = paddle.stack(x=perms) + if self.perm_mirrored: + comp = perms.flip(axis=-1) + x = paddle.stack(x=[perms, comp]) + perm_2 = list(range(x.ndim)) + perm_2[0] = 1 + perm_2[1] = 0 + perms = x.transpose(perm=perm_2).reshape((-1, max_num_chars)) + bos_idx = paddle.zeros(shape=(len(perms), 1), dtype=perms.dtype) + eos_idx = paddle.full( + shape=(len(perms), 1), fill_value=max_num_chars + 1, dtype=perms.dtype + ) + perms = paddle.concat(x=[bos_idx, perms + 1, eos_idx], axis=1) + if len(perms) > 1: + perms[(1), 1:] = max_num_chars + 1 - paddle.arange(end=max_num_chars + 1) + return perms + + def generate_attn_masks(self, perm): + """Generate attention masks given a sequence permutation (includes pos. for bos and eos tokens) + :param perm: the permutation sequence. i = 0 is always the BOS + :return: lookahead attention masks + """ + sz = perm.shape[0] + mask = paddle.zeros(shape=(sz, sz)) + for i in range(sz): + query_idx = perm[i].cpu().numpy().tolist() + masked_keys = perm[i + 1 :].cpu().numpy().tolist() + if len(masked_keys) == 0: + break + mask[query_idx, masked_keys] = float("-inf") + content_mask = mask[:-1, :-1].clone() + mask[paddle.eye(num_rows=sz).astype("bool")] = float("-inf") + query_mask = mask[1:, :-1] + return content_mask, query_mask + + def forward_train(self, memory, tgt): + tgt_perms = self.gen_tgt_perms(tgt) + tgt_in = tgt[:, :-1] + tgt_padding_mask = (tgt_in == self.pad_id) | (tgt_in == self.eos_id) + logits_list = [] + final_out = {} + for i, perm in enumerate(tgt_perms): + tgt_mask, query_mask = self.generate_attn_masks(perm) + out = self.decode( + tgt_in, memory, tgt_mask, tgt_padding_mask, tgt_query_mask=query_mask + ) + logits = self.head(out) + if i == 0: + final_out["predict"] = logits + logits = logits.flatten(stop_axis=1) + logits_list.append(logits) + + final_out["logits_list"] = logits_list + final_out["pad_id"] = self.pad_id + final_out["eos_id"] = self.eos_id + + return final_out + + def forward(self, feat, targets=None): + # feat : B, N, C + # targets : labels, labels_len + + if self.training: + label = targets[0] # label + label_len = targets[1] + max_step = paddle.max(label_len).cpu().numpy()[0] + 2 + crop_label = label[:, :max_step] + final_out = self.forward_train(feat, crop_label) + else: + final_out = self.forward_test(feat) + + return final_out diff --git a/ppocr/modeling/heads/rec_ppformulanet_head.py b/ppocr/modeling/heads/rec_ppformulanet_head.py new file mode 100644 index 0000000..644dc2a --- /dev/null +++ b/ppocr/modeling/heads/rec_ppformulanet_head.py @@ -0,0 +1,1391 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +import re +import numpy as np +import inspect +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle.nn import CrossEntropyLoss +from paddle import Tensor +from collections import OrderedDict +from typing import Optional, Tuple, Union, List, Dict, Any +from dataclasses import dataclass, fields, is_dataclass +from ppocr.modeling.backbones.rec_donut_swin import DonutSwinModelOutput +from ppocr.modeling.heads.rec_unimernet_head import ( + MBartForCausalLM, + MBartDecoder, + MBartConfig, + ModelOutput, + BaseModelOutputWithPastAndCrossAttentions, + Seq2SeqLMOutput, + zeros_, + ones_, + kaiming_normal_, + trunc_normal_, + xavier_uniform_, + CausalLMOutputWithCrossAttentions, + LogitsProcessorList, + ForcedEOSTokenLogitsProcessor, + UniMERNetHead, +) + + +@dataclass +class AttentionMaskConverter: + """ + A class to convert attention masks based on specific configurations. + + This class is designed to handle the conversion of attention masks with options for causal masking + and sliding window attention, which are commonly used in transformer models. + + Attributes: + is_causal (bool): Flag indicating whether the attention mask should enforce causal masking, + which ensures each position can only attend to previous positions. + sliding_window (int, optional): Size of the sliding window for local attention. If set, + attention is restricted to a local window of this size. + + """ + + is_causal: bool + sliding_window: int + + def __init__(self, is_causal: bool, sliding_window=None): + self.is_causal = is_causal + self.sliding_window = sliding_window + + if self.sliding_window is not None and self.sliding_window <= 0: + raise ValueError( + f"Make sure that when passing `sliding_window` that its value is a strictly positive integer, not `{self.sliding_window}`" + ) + + @staticmethod + def _make_causal_mask( + input_ids_shape, + dtype, + past_key_values_length=0, + sliding_window=None, + is_export=False, + ): + """ + Make causal mask used for bi-directional self-attention. + """ + bsz, tgt_len = input_ids_shape + if is_export: + mask = paddle.full( + (tgt_len, tgt_len), paddle.finfo(dtype).min, dtype="float64" + ) + mask_cond = paddle.arange(mask.shape[-1]) + mask.masked_fill_( + mask_cond < (mask_cond + 1).reshape([mask.shape[-1], 1]), 0 + ) + else: + mask = paddle.full((tgt_len, tgt_len), paddle.finfo(dtype).min) + mask_cond = paddle.arange(mask.shape[-1]) + mask.masked_fill_( + mask_cond < (mask_cond + 1).reshape([mask.shape[-1], 1]), 0 + ) + mask = mask.cast(dtype) + + if past_key_values_length > 0: + mask = paddle.concat( + [paddle.zeros(tgt_len, past_key_values_length, dtype=dtype), mask], + axis=-1, + ) + + # add lower triangular sliding window mask if necessary + if sliding_window is not None: + diagonal = past_key_values_length - sliding_window - 1 + + context_mask = paddle.tril( + paddle.ones_like(mask, dtype=paddle.bool), diagonal=diagonal + ) + mask.masked_fill_(context_mask, paddle.finfo(dtype).min) + + return mask[None, None, :, :].expand( + [bsz, 1, tgt_len, tgt_len + past_key_values_length] + ) + + @staticmethod + def _make_causal_mask_parallel( + input_ids_shape, + dtype, + past_key_values_length=0, + sliding_window=None, + parallel_step=1, + is_export=False, + ): + """ + Make causal mask used for bi-directional self-attention. + """ + bsz, tgt_len = input_ids_shape + mask = paddle.full((tgt_len, tgt_len), paddle.finfo(dtype).min) + mask_cond = paddle.arange(mask.shape[-1]) + mask_cond_parallel = paddle.arange(mask.shape[-1]) + + mask_parallel = paddle.arange(0, tgt_len, step=parallel_step).reshape([1, -1]) + mask_parallel = paddle.repeat_interleave(mask_parallel, parallel_step, 1)[ + :, :tgt_len + ] + mask.masked_fill_( + mask_cond < (mask_parallel + parallel_step).reshape([mask.shape[-1], 1]), 0 + ) + mask = mask.cast(dtype) + + if past_key_values_length > 0: + mask = paddle.concat( + [paddle.zeros([tgt_len, past_key_values_length], dtype=dtype), mask], + axis=-1, + ) + + # add lower triangular sliding window mask if necessary + if sliding_window is not None: + diagonal = past_key_values_length - sliding_window - 1 + + context_mask = paddle.tril( + paddle.ones_like(mask, dtype=paddle.bool), diagonal=diagonal + ) + mask.masked_fill_(context_mask, paddle.finfo(dtype).min) + + return mask[None, None, :, :].expand( + [bsz, 1, tgt_len, tgt_len + past_key_values_length] + ) + + def to_4d( + self, + attention_mask_2d, + query_length, + dtype, + key_value_length, + use_parallel=False, + parallel_step=3, + is_export=False, + ): + """ + Converts 2D attention mask to 4D attention mask by expanding mask to (bsz, head_dim=1, query_length, + key_value_length) shape and by adding a large negative bias to not-attended positions. If attention_mask is + causal, a causal mask will be added. + """ + input_shape = (attention_mask_2d.shape[0], query_length) + + causal_4d_mask = None + if use_parallel: + step = parallel_step + else: + step = 1 + if ( + input_shape[-1] > step or self.sliding_window is not None + ) and self.is_causal: + + if key_value_length is None: + raise ValueError( + "This attention mask converter is causal. Make sure to pass `key_value_length` to correctly create a causal mask." + ) + + past_key_values_length = key_value_length - query_length + + if use_parallel: + causal_4d_mask = self._make_causal_mask_parallel( + input_shape, + dtype, + past_key_values_length=past_key_values_length, + sliding_window=self.sliding_window, + parallel_step=parallel_step, + is_export=is_export, + ) + else: + causal_4d_mask = self._make_causal_mask( + input_shape, + dtype, + past_key_values_length=past_key_values_length, + sliding_window=self.sliding_window, + is_export=is_export, + ) + + elif self.sliding_window is not None: + raise NotImplementedError( + "Sliding window is currently only implemented for causal masking" + ) + + expanded_attn_mask = self._expand_mask( + attention_mask_2d, dtype, tgt_len=input_shape[-1] + ) + + if causal_4d_mask is not None: + expanded_attn_mask = causal_4d_mask.masked_fill_( + expanded_attn_mask.cast(paddle.bool), paddle.finfo(dtype).min + ) + + expanded_4d_mask = expanded_attn_mask + return expanded_4d_mask + + def to_4d_export( + self, + attention_mask_2d, + query_length, + dtype, + key_value_length, + use_parallel=False, + parallel_step=3, + is_export=False, + ): + input_shape = (attention_mask_2d.shape[0], query_length) + + expanded_attn_mask = self._expand_mask_export( + attention_mask_2d, dtype, tgt_len=input_shape[-1] + ) + expanded_4d_mask = expanded_attn_mask + + return expanded_4d_mask + + def _expand_mask(self, mask, dtype, tgt_len=None): + """ + Expands attention_mask from `[bsz, seq_len]` to `[bsz, 1, tgt_seq_len, src_seq_len]`. + """ + bsz, src_len = mask.shape + tgt_len = tgt_len if tgt_len is not None else src_len + expanded_mask = ( + mask[:, None, None, :].expand([bsz, 1, tgt_len, src_len]).cast(dtype) + ) + + inverted_mask = 1.0 - expanded_mask + + return inverted_mask.masked_fill_( + inverted_mask.cast(paddle.bool), paddle.finfo(dtype).min + ) + + def _expand_mask_export(self, mask, dtype, tgt_len=None): + """ + Expands attention_mask from `[bsz, seq_len]` to `[bsz, 1, tgt_seq_len, src_seq_len]`. + """ + bsz, src_len = paddle.shape(mask) + expanded_mask = ( + mask[:, None, None, :].expand([bsz, 1, tgt_len, src_len]).cast(dtype) + ) + paddle.jit.api.set_dynamic_shape(expanded_mask, [-1, -1, -1, -1]) + inverted_mask = 1.0 - expanded_mask + return inverted_mask.masked_fill_( + inverted_mask.cast(paddle.bool), paddle.finfo(dtype).min + ) + + +def _prepare_4d_attention_mask(mask, dtype, tgt_len=None): + return AttentionMaskConverter._expand_mask(mask=mask, dtype=dtype, tgt_len=tgt_len) + + +def _prepare_4d_causal_attention_mask( + attention_mask, + input_shape, + inputs_embeds, + past_key_values_length, + sliding_window=None, + use_parallel=False, + parallel_step=3, + is_export=False, +): + """ + Creates a causal 4D mask of shape `(batch_size, 1, query_length, key_value_length)` from a 2D mask of shape + `(batch_size, key_value_length)` + + Args: + attention_mask (`paddle.Tensor` or `None`): + A 2D attention mask of shape `(batch_size, key_value_length)` + input_shape (`tuple(int)` or `list(int)` or `paddle.Size`): + The input shape should be a tuple that defines `(batch_size, query_length)`. + inputs_embeds (`paddle.Tensor`): + The embedded inputs as a paddle Tensor. + past_key_values_length (`int`): + The length of the key value cache. + sliding_window (`int`, *optional*): + If the model uses windowed attention, a sliding window should be passed. + """ + attn_mask_converter = AttentionMaskConverter( + is_causal=True, sliding_window=sliding_window + ) + + key_value_length = input_shape[-1] + past_key_values_length + + # 4d mask is passed through the layers + if attention_mask is not None and len(attention_mask.shape) == 2: + attention_mask = attn_mask_converter.to_4d( + attention_mask, + input_shape[-1], + key_value_length=key_value_length, + dtype=inputs_embeds.dtype, + use_parallel=use_parallel, + parallel_step=parallel_step, + is_export=is_export, + ) + elif attention_mask is not None and len(attention_mask.shape) == 4: + expected_shape = (input_shape[0], 1, input_shape[1], key_value_length) + if tuple(attention_mask.shape) != expected_shape: + raise ValueError( + f"Incorrect 4D attention_mask shape: {tuple(attention_mask.shape)}; expected: {expected_shape}." + ) + else: + # if the 4D mask has correct shape - invert it and fill with negative infinity + inverted_mask = 1.0 - attention_mask + attention_mask = inverted_mask.masked_fill_( + inverted_mask.to(paddle.bool), paddle.finfo(inputs_embeds.dtype).min + ) + else: + attention_mask = attn_mask_converter.to_causal_4d( + input_shape[0], + input_shape[-1], + key_value_length, + dtype=inputs_embeds.dtype, + ) + + return attention_mask + + +def _prepare_4d_causal_attention_mask_export( + attention_mask, + input_shape, + inputs_embeds, + past_key_values_length, + sliding_window=None, + use_parallel=False, + parallel_step=3, + is_export=False, +): + """ + Prepare a 4D causal attention mask for export. + + This function prepares a 4-dimensional causal attention mask, which is used to ensure that each position in the + sequence can only attend to previous positions. It is specifically designed to handle scenarios where the model + is being exported, potentially with additional options like sliding window or parallel processing. + + Args: + attention_mask: The initial attention mask, typically used to avoid attending to padding tokens. + input_shape: Shape of the input tensor, usually in the form (batch_size, sequence_length). + inputs_embeds: Embeddings of the input sequence, used to derive certain dimensions if needed. + past_key_values_length: Length of past key values, used in contexts like transformer decoders with caching. + sliding_window: Optional parameter. If provided, specifies the size of a sliding window for local attention. + use_parallel: Flag indicating whether to use parallel processing for attention computation. + parallel_step: Number of steps to use in parallel processing, relevant if `use_parallel` is True. + is_export: Flag indicating whether the attention mask is being prepared for model export. + + Returns: + A 4D causal attention mask suitable for use in transformer models, ensuring correct causal masking. + """ + attn_mask_converter = AttentionMaskConverter( + is_causal=True, sliding_window=sliding_window + ) + key_value_length = input_shape[-1] + past_key_values_length + + shape = attention_mask.shape + len_shape = len(shape) + + attention_mask = attn_mask_converter.to_4d_export( + attention_mask, + input_shape[-1], + key_value_length=key_value_length, + dtype=inputs_embeds.dtype, + use_parallel=use_parallel, + parallel_step=parallel_step, + is_export=is_export, + ) + return attention_mask + + +class CustomMBartDecoder(MBartDecoder): + def __init__(self, config): + super().__init__(config) + hidden_size = config.d_model + self.is_export = config.is_export + self.config_decoder = config + + def forward( + self, + input_ids=None, + attention_mask=None, + encoder_hidden_states=None, + encoder_attention_mask=None, + head_mask=None, + cross_attn_head_mask=None, + past_key_values=None, + inputs_embeds=None, + use_cache=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + ): + self.is_export = False if self.training else True + + output_attentions = ( + output_attentions + if output_attentions is not None + else self.config.output_attentions + ) + output_hidden_states = ( + output_hidden_states + if output_hidden_states is not None + else self.config.output_hidden_states + ) + use_cache = use_cache if use_cache is not None else self.config.use_cache + return_dict = ( + return_dict if return_dict is not None else self.config.use_return_dict + ) + + # retrieve input_ids and inputs_embeds + if input_ids is not None and inputs_embeds is not None: + raise ValueError( + "You cannot specify both decoder_input_ids and decoder_inputs_embeds at the same time" + ) + elif input_ids is not None: + input = input_ids + input_shape = input.shape + input_ids = input_ids.reshape([-1, input_shape[-1]]) + elif inputs_embeds is not None: + input_shape = inputs_embeds.shape[:-1] + input = inputs_embeds[:, :, -1] + else: + raise ValueError( + "You have to specify either decoder_input_ids or decoder_inputs_embeds" + ) + + # past_key_values_length + past_key_values_length = ( + past_key_values[0][0].shape[2] if past_key_values is not None else 0 + ) + + if inputs_embeds is None: + inputs_embeds = self.embed_tokens(input_ids) * self.embed_scale + + if self._use_flash_attention_2: + # 2d mask is passed through the layers + attention_mask = ( + attention_mask + if (attention_mask is not None and 0 in attention_mask) + else None + ) + else: + # 4d mask is passed through the layers + if self.is_export: + attention_mask = _prepare_4d_causal_attention_mask_export( + attention_mask, + input_shape, + inputs_embeds, + past_key_values_length, + use_parallel=self.config_decoder.use_parallel, + parallel_step=self.config_decoder.parallel_step, + is_export=self.is_export, + ) + else: + attention_mask = _prepare_4d_causal_attention_mask( + attention_mask, + input_shape, + inputs_embeds, + past_key_values_length, + use_parallel=self.config_decoder.use_parallel, + parallel_step=self.config_decoder.parallel_step, + is_export=self.is_export, + ) + + # expand encoder attention mask + if encoder_hidden_states is not None and encoder_attention_mask is not None: + if self._use_flash_attention_2: + encoder_attention_mask = ( + encoder_attention_mask if 0 in encoder_attention_mask else None + ) + else: + # [bsz, seq_len] -> [bsz, 1, tgt_seq_len, src_seq_len] + encoder_attention_mask = _prepare_4d_attention_mask( + encoder_attention_mask, inputs_embeds.dtype, tgt_len=input_shape[-1] + ) + + # embed positions + positions = self.embed_positions(input, past_key_values_length) + + hidden_states = inputs_embeds + positions + + hidden_states = self.layernorm_embedding(hidden_states) + hidden_states = nn.functional.dropout( + hidden_states, p=self.dropout, training=self.training + ) + if self.gradient_checkpointing and self.training: + if use_cache: + print( + "`use_cache=True` is incompatible with gradient checkpointing`. Setting `use_cache=False`..." + ) + use_cache = False + + # decoder layers + all_hidden_states = () if output_hidden_states else None + all_self_attns = () if output_attentions else None + all_cross_attentions = ( + () if (output_attentions and encoder_hidden_states is not None) else None + ) + next_decoder_cache = () if use_cache else None + + # check if head_mask/cross_attn_head_mask has a correct number of layers specified if desired + for attn_mask, mask_name in zip( + [head_mask, cross_attn_head_mask], ["head_mask", "cross_attn_head_mask"] + ): + if attn_mask is not None: + if attn_mask.size()[0] != len(self.layers): + raise ValueError( + f"The `{mask_name}` should be specified for {len(self.layers)} layers, but it is for" + f" {attn_mask.size()[0]}." + ) + for idx, decoder_layer in enumerate(self.layers): + # add LayerDrop (see https://arxiv.org/abs/1909.11556 for description) + if output_hidden_states: + all_hidden_states += (hidden_states,) + if self.training: + dropout_probability = paddle.rand([]) + if dropout_probability < self.layerdrop: + continue + + past_key_value = ( + past_key_values[idx] if past_key_values is not None else None + ) + if self.gradient_checkpointing and self.training: + layer_outputs = self._gradient_checkpointing_func( + decoder_layer.__call__, + hidden_states, + attention_mask, + encoder_hidden_states, + encoder_attention_mask, + head_mask[idx] if head_mask is not None else None, + ( + cross_attn_head_mask[idx] + if cross_attn_head_mask is not None + else None + ), + None, + output_attentions, + use_cache, + ) + else: + layer_outputs = decoder_layer( + hidden_states, + attention_mask=attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=encoder_attention_mask, + layer_head_mask=(head_mask[idx] if head_mask is not None else None), + cross_attn_layer_head_mask=( + cross_attn_head_mask[idx] + if cross_attn_head_mask is not None + else None + ), + past_key_value=past_key_value, + output_attentions=output_attentions, + use_cache=use_cache, + ) + hidden_states = layer_outputs[0] + + if self.is_export: + next_decoder_cache += (layer_outputs[3 if output_attentions else 1],) + else: + if use_cache: + next_decoder_cache += ( + layer_outputs[3 if output_attentions else 1], + ) + + if output_attentions: + all_self_attns += (layer_outputs[1],) + + if encoder_hidden_states is not None: + all_cross_attentions += (layer_outputs[2],) + + hidden_states = self.layer_norm(hidden_states) + + # add hidden states from the last decoder layer + if output_hidden_states: + all_hidden_states += (hidden_states,) + + if self.is_export: + next_cache = next_decoder_cache + else: + next_cache = next_decoder_cache if use_cache else None + if not return_dict: + return tuple( + v + for v in [ + hidden_states, + next_cache, + all_hidden_states, + all_self_attns, + all_cross_attentions, + ] + if v is not None + ) + + return BaseModelOutputWithPastAndCrossAttentions( + last_hidden_state=hidden_states, + past_key_values=next_cache, + hidden_states=all_hidden_states, + attentions=all_self_attns, + cross_attentions=all_cross_attentions, + ) + + +class CustomMBartForCausalLM(MBartForCausalLM): + def __init__(self, config): + super().__init__(config) + # Modify the decoder within MBartDecoderWrapper + self.model.decoder = CustomMBartDecoder(config) + + def forward( + self, + input_ids=None, + attention_mask=None, + encoder_hidden_states=None, + encoder_attention_mask=None, + head_mask=None, + cross_attn_head_mask=None, + past_key_values=None, + inputs_embeds=None, + labels=None, + use_cache=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + ): + output_attentions = ( + output_attentions + if output_attentions is not None + else self.config.output_attentions + ) + output_hidden_states = ( + output_hidden_states + if output_hidden_states is not None + else self.config.output_hidden_states + ) + return_dict = ( + return_dict if return_dict is not None else self.config.use_return_dict + ) + + # decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn) + outputs = self.model.decoder( + input_ids=input_ids, + attention_mask=attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=encoder_attention_mask, + head_mask=head_mask, + cross_attn_head_mask=cross_attn_head_mask, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + logits = self.lm_head(outputs[0]) + + return CausalLMOutputWithCrossAttentions( + logits=logits, + past_key_values=outputs.past_key_values, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + cross_attentions=outputs.cross_attentions, + ) + + +class PPFormulaNet_Head(UniMERNetHead): + """ + PPFormulaNet_Head + Args: + max_new_tokens (int): Maximum number of new tokens to generate. Default is 1536. + decoder_start_token_id (int): Start token ID for the decoder. Default is 0. + temperature (float): Temperature parameter for controlling randomness in sampling. Default is 0.2. + do_sample (bool): Flag to determine whether to use sampling for generation. Default is False. + top_p (float): Top-p (nucleus) sampling parameter for controlling diversity. Default is 0.95. + in_channels (int): Number of input channels for the model. Default is 1024. + decoder_layers (int): Number of layers in the decoder. Default is 8. + encoder_hidden_size (int): Size of the hidden layer in the encoder. Default is 1024. + decoder_ffn_dim (int): Dimension of the feed-forward network in the decoder. Default is 4096. + decoder_hidden_size (int): Size of the hidden layer in the decoder. Default is 1024. + is_export (bool): Flag indicating whether the model is to be exported. Default is False. + length_aware (bool): Flag to determine if the model should be aware of input sequence length. Default is True. + use_parallel (bool): Flag to enable or disable parallel processing. Default is False. + parallel_step (int): Number of steps to use in parallel processing. Default is 3. + """ + + def __init__( + self, + max_new_tokens=1536, + decoder_start_token_id=0, + temperature=0.2, + do_sample=False, + top_p=0.95, + in_channels=1024, + decoder_layers=8, + encoder_hidden_size=1024, + decoder_ffn_dim=4096, + decoder_hidden_size=1024, + is_export=False, + length_aware=True, + use_parallel=False, + parallel_step=3, + ): + + super().__init__() + + mbart_config_dict = { + "activation_dropout": 0.0, + "activation_function": "gelu", + "add_cross_attention": True, + "add_final_layer_norm": True, + "attention_dropout": 0.0, + "bos_token_id": 0, + "classifier_dropout": 0.0, + "d_model": decoder_hidden_size, + "decoder_attention_heads": 16, + "decoder_ffn_dim": decoder_ffn_dim, + "decoder_layerdrop": 0.0, + "decoder_layers": decoder_layers, + "dropout": 0.1, + "encoder_attention_heads": 16, + "encoder_ffn_dim": 4096, + "encoder_layerdrop": 0.0, + "encoder_layers": 12, + "eos_token_id": 2, + "forced_eos_token_id": 2, + "init_std": 0.02, + "is_decoder": True, + "is_encoder_decoder": False, + "output_hidden_states": False, + "max_position_embeddings": ( + max_new_tokens + parallel_step if use_parallel else max_new_tokens + ), + "model_type": "mbart", + "num_hidden_layers": 12, + "pad_token_id": 1, + "scale_embedding": True, + "tie_word_embeddings": False, + "transformers_version": "4.40.0", + "use_cache": True, + "use_return_dict": True, + "vocab_size": 50000, + "_attn_implementation": "eager", + "hidden_size": decoder_hidden_size, + "use_parallel": use_parallel, + "parallel_step": int(parallel_step), + "is_export": is_export, + } + self.decoder_start_token_id = decoder_start_token_id + self.temperature = temperature + self.do_sample = do_sample + self.top_p = top_p + self.is_export = is_export + self.max_seq_len = max_new_tokens + self.config_decoder = MBartConfig(**mbart_config_dict) + self.encoder_hidden_size = encoder_hidden_size + self.decoder = CustomMBartForCausalLM(self.config_decoder) + if self.config_decoder.hidden_size != self.encoder_hidden_size: + self.enc_to_dec_proj = nn.Linear( + self.encoder_hidden_size, self.config_decoder.hidden_size + ) + generation_config = { + "max_length": 1537, + "forced_eos_token_id": 2, + } + self.eos_token_id = generation_config["forced_eos_token_id"] + self.pad_token_id = self.config_decoder.pad_token_id + self.logits_processor = LogitsProcessorList() + self.logits_processor.append( + ForcedEOSTokenLogitsProcessor( + generation_config["max_length"], + generation_config["forced_eos_token_id"], + ) + ) + + def prepare_inputs_for_generation( + self, + input_ids, + past_key_values=None, + attention_mask=None, + use_cache=None, + encoder_outputs=None, + **kwargs, + ): + decoder_inputs = self.prepare_inputs_for_generation_mbart( + input_ids, past_key_values=past_key_values + ) + decoder_attention_mask = ( + decoder_inputs["attention_mask"] + if "attention_mask" in decoder_inputs + else None + ) + input_dict = { + "attention_mask": attention_mask, + "decoder_attention_mask": decoder_attention_mask, + "decoder_input_ids": decoder_inputs["input_ids"], + "past_key_values": decoder_inputs["past_key_values"], + "use_cache": use_cache, + } + return input_dict + + def _extract_past_from_model_output( + self, outputs: ModelOutput, standardize_cache_format: bool = False + ): + past_key_values = None + if "past_key_values" in outputs: + past_key_values = outputs.past_key_values + elif "mems" in outputs: + past_key_values = outputs.mems + elif "past_buckets_states" in outputs: + past_key_values = outputs.past_buckets_states + return past_key_values + + def _update_model_kwargs_for_generation( + self, + outputs: ModelOutput, + model_kwargs: Dict[str, Any], + is_encoder_decoder: bool = False, + standardize_cache_format: bool = False, + ) -> Dict[str, Any]: + # update past_key_values + model_kwargs["past_key_values"] = self._extract_past_from_model_output( + outputs, standardize_cache_format=standardize_cache_format + ) + if getattr(outputs, "state", None) is not None: + model_kwargs["state"] = outputs.state + + # update token_type_ids with last value + if "token_type_ids" in model_kwargs: + token_type_ids = model_kwargs["token_type_ids"] + model_kwargs["token_type_ids"] = paddle.concat( + [token_type_ids, token_type_ids[:, -1].unsqueeze(-1)], axis=-1 + ) + + if not is_encoder_decoder: + # update attention mask + if "attention_mask" in model_kwargs: + attention_mask = model_kwargs["attention_mask"] + model_kwargs["attention_mask"] = paddle.concat( + [ + attention_mask, + attention_mask.new_ones((attention_mask.shape[0], 1)), + ], + axis=-1, + ) + else: + # update decoder attention mask + if "decoder_attention_mask" in model_kwargs: + decoder_attention_mask = model_kwargs["decoder_attention_mask"] + model_kwargs["decoder_attention_mask"] = paddle.concat( + [ + decoder_attention_mask, + decoder_attention_mask.new_ones( + (decoder_attention_mask.shape[0], 1) + ), + ], + dim=-1, + ) + + if ( + "cache_position" in model_kwargs + and model_kwargs["cache_position"] is not None + ): + model_kwargs["cache_position"] = model_kwargs["cache_position"][-1:] + 1 + return model_kwargs + + def stopping_criteria(self, input_ids): + if self.is_export: + return input_ids[:, -1] == paddle.to_tensor([self.eos_token_id]) + is_done = paddle.isin(input_ids[:, -1], paddle.to_tensor([self.eos_token_id])) + return is_done + + def stopping_criteria_parallel(self, input_ids): + parallel_step = self.config_decoder.parallel_step + + if self.is_export: + is_done_list = [] + for i in range(parallel_step, 0, -1): + cur_is_done = input_ids[:, -i] == paddle.to_tensor([self.eos_token_id]) + is_done_list.append(cur_is_done) + is_done_list = paddle.to_tensor(is_done_list).transpose([1, 0]) + return is_done_list + else: + is_done = paddle.isin( + input_ids[:, -parallel_step:], + paddle.to_tensor([self.eos_token_id]).reshape([1, 1]), + ) + return paddle.to_tensor(is_done) + + def generate_single_iter( + self, + decoder_input_ids=None, + decoder_attention_mask=None, + encoder_outputs=None, + past_key_values=None, + decoder_inputs_embeds=None, + labels=None, + use_cache=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + **kwargs, + ): + + encoder_hidden_states = encoder_outputs[0] + if self.config_decoder.hidden_size != self.encoder_hidden_size: + encoder_hidden_states = self.enc_to_dec_proj(encoder_hidden_states) + kwargs_decoder = {} + decoder_outputs = self.decoder( + input_ids=decoder_input_ids, + attention_mask=decoder_attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=None, + inputs_embeds=None, + output_attentions=False, + output_hidden_states=output_hidden_states, + use_cache=use_cache, + past_key_values=past_key_values, + return_dict=return_dict, + **kwargs_decoder, + ) + + return Seq2SeqLMOutput( + loss=None, + logits=decoder_outputs.logits, + past_key_values=decoder_outputs.past_key_values, + decoder_hidden_states=decoder_outputs.hidden_states, + decoder_attentions=decoder_outputs.attentions, + cross_attentions=decoder_outputs.cross_attentions, + encoder_last_hidden_state=encoder_outputs.last_hidden_state, + encoder_hidden_states=encoder_outputs.hidden_states, + encoder_attentions=encoder_outputs.attentions, + ) + + def _prepare_decoder_input_ids_for_generation( + self, + batch_size, + model_kwargs, + decoder_start_token_id=None, + bos_token_id=None, + ): + + # 1. Check whether the user has defined `decoder_input_ids` manually. To facilitate in terms of input naming, + # we also allow the user to pass it under `input_ids`, if the encoder does not use it as the main input. + if model_kwargs is not None and "decoder_input_ids" in model_kwargs: + decoder_input_ids = model_kwargs.pop("decoder_input_ids") + elif "input_ids" in model_kwargs: + decoder_input_ids = model_kwargs.pop("input_ids") + else: + decoder_input_ids = None + + # 2. Encoder-decoder models expect the `decoder_input_ids` to start with a special token. Let's ensure that. + decoder_start_token_id = self._get_decoder_start_token_id( + decoder_start_token_id, bos_token_id + ) + + if isinstance(decoder_start_token_id, list): + if len(decoder_start_token_id) != batch_size: + raise ValueError( + f"`decoder_start_token_id` expected to have length {batch_size} but got {len(decoder_start_token_id)}" + ) + decoder_input_ids_start = paddle.to_tensor( + decoder_start_token_id, + dtype=paddle.int64, + ) + decoder_input_ids_start = decoder_input_ids_start.view(-1, 1) + else: + use_parallel = self.config_decoder.use_parallel + parallel_step = self.config_decoder.parallel_step + + if use_parallel: + decoder_input_ids_start = ( + paddle.ones( + (batch_size, parallel_step), + dtype=paddle.int64, + ) + * decoder_start_token_id + ) + else: + decoder_input_ids_start = ( + paddle.ones( + (batch_size, 1), + dtype=paddle.int64, + ) + * decoder_start_token_id + ) + # no user input -> use decoder_start_token_id as decoder_input_ids + if decoder_input_ids is None: + decoder_input_ids = decoder_input_ids_start + # exception: Donut checkpoints have task-specific decoder starts and don't expect a BOS token + elif ( + self.config.model_type == "vision-encoder-decoder" + and "donut" in self.name_or_path.lower() + ): + pass + elif self.config.model_type in ["whisper"]: + pass + # user input but doesn't start with decoder_start_token_id -> prepend decoder_start_token_id (and adjust + # decoder_attention_mask if provided) + elif ( + isinstance(decoder_start_token_id, int) + and (decoder_input_ids[:, 0] != decoder_start_token_id).all().item() + ) or ( + isinstance(decoder_start_token_id, paddle.Tensor) + and (decoder_input_ids[:, 0] != decoder_start_token_id[:, 0]).all().item() + ): + decoder_input_ids = paddle.concat( + [decoder_input_ids_start, decoder_input_ids], axis=-1 + ) + if "decoder_attention_mask" in model_kwargs: + decoder_attention_mask = model_kwargs["decoder_attention_mask"] + decoder_attention_mask = paddle.cat( + ( + paddle.ones_like(decoder_attention_mask)[:, :1], + decoder_attention_mask, + ), + dim=-1, + ) + model_kwargs["decoder_attention_mask"] = decoder_attention_mask + + return decoder_input_ids, model_kwargs + + @paddle.no_grad() + def generate_export( + self, + encoder_outputs, + model_kwargs, + ): + use_parallel = self.config_decoder.use_parallel + parallel_step = self.config_decoder.parallel_step + batch_size = encoder_outputs["last_hidden_state"].shape[0] + generation_config = { + "decoder_start_token_id": 0, + "bos_token_id": 0, + } + input_ids, model_kwargs = self._prepare_decoder_input_ids_for_generation( + batch_size=batch_size, + model_kwargs=model_kwargs, + decoder_start_token_id=generation_config["decoder_start_token_id"], + bos_token_id=generation_config["bos_token_id"], + ) + if not use_parallel: + input_ids = input_ids.reshape([-1, 1]) + decoder_input_ids = input_ids + model_kwargs["key use_cache"] = True + batch_size, cur_len = input_ids.shape + + if "inputs_embeds" in model_kwargs: + cur_len = model_kwargs["inputs_embeds"].shape[1] + + cache_position = paddle.arange(cur_len) + pad_token_id = self.pad_token_id + eos_token_id = [self.eos_token_id] + eos_token = self.eos_token_id + if use_parallel: + unfinished_sequences = paddle.ones( + [batch_size, parallel_step], dtype=paddle.int64 + ) + parallel_length = math.ceil(self.max_seq_len // parallel_step) + else: + unfinished_sequences = paddle.ones(batch_size, dtype=paddle.int64) + parallel_length = self.max_seq_len + + i_idx = paddle.full([], 0) + past_key_values = [] + decoder_attention_heads = self.config_decoder.decoder_attention_heads + decoder_attention_heads_dim = int( + self.config_decoder.d_model / decoder_attention_heads + ) + for i in range(self.config_decoder.decoder_layers): + init_arr = paddle.zeros( + [batch_size, decoder_attention_heads, 0, decoder_attention_heads_dim] + ) + paddle.jit.api.set_dynamic_shape(init_arr, [-1, -1, -1, -1]) + cache = (init_arr, init_arr, init_arr, init_arr) + past_key_values.append(cache) + + while i_idx < paddle.to_tensor(parallel_length): + + model_inputs = self.prepare_inputs_for_generation_export( + past_key_values=past_key_values, **model_kwargs + ) + decoder_attention_mask = paddle.ones(paddle.shape(input_ids)) + paddle.jit.api.set_dynamic_shape(decoder_input_ids, [-1, -1]) + paddle.jit.api.set_dynamic_shape(decoder_attention_mask, [-1, -1]) + + outputs = self.generate_single_iter( + decoder_input_ids=decoder_input_ids, + decoder_attention_mask=decoder_attention_mask, + encoder_outputs=encoder_outputs, + past_key_values=past_key_values, + return_dict=True, + output_attentions=False, + output_hidden_states=False, + ) + + if use_parallel: + next_token_logits = outputs.logits[:, -parallel_step:, :] + else: + next_token_logits = outputs.logits[:, -1, :] + next_tokens_scores = self.logits_processor(input_ids, next_token_logits) + next_tokens = paddle.argmax(next_tokens_scores, axis=-1) + + if eos_token_id is not None: + # False + if pad_token_id is None: + raise ValueError( + "If `eos_token_id` is defined, make sure that `pad_token_id` is defined." + ) + next_tokens = next_tokens * unfinished_sequences + pad_token_id * ( + 1 - unfinished_sequences + ) + if use_parallel: + input_ids = paddle.concat([input_ids, next_tokens], axis=-1) + decoder_input_ids = next_tokens + else: + input_ids = paddle.concat( + [input_ids, next_tokens.unsqueeze(1)], axis=-1 + ) + decoder_input_ids = next_tokens.unsqueeze(1) + + past_length = past_key_values[0][0].shape[2] + + past_key_values = outputs.past_key_values + cache_position = cache_position[-1:] + 1 + if use_parallel: + unfinished_sequences = ( + unfinished_sequences + & ~self.stopping_criteria_parallel(input_ids).cast(paddle.int64) + ) + else: + unfinished_sequences = unfinished_sequences & ~self.stopping_criteria( + input_ids + ).cast(paddle.int64) + + if ( + eos_token is not None + and ( + paddle.cumsum((input_ids == eos_token).cast(paddle.int64), 1)[:, -1] + >= 1 + ).all() + ): + break + i_idx += 1 + # break + + return input_ids + + @paddle.no_grad() + def generate( + self, + encoder_outputs, + model_kwargs, + ): + """ + Generate sequences from the model without computing gradients. + + This method is used to generate sequences from the model based on the given encoder outputs. + It does not compute gradients, making it suitable for inference. + + Args: + encoder_outputs: The outputs from the encoder, typically including hidden states necessary for generation. + model_kwargs: Additional keyword arguments that may include parameters such as maximum length, + temperature, top-k/top-p sampling parameters, and other generation-specific settings. + + Returns: + Generated sequences based on the encoder outputs and specified generation parameters. + """ + use_parallel = self.config_decoder.use_parallel + parallel_step = self.config_decoder.parallel_step + batch_size = encoder_outputs["last_hidden_state"].shape[0] + generation_config = { + "decoder_start_token_id": 0, + "bos_token_id": 0, + } + + input_ids, model_kwargs = self._prepare_decoder_input_ids_for_generation( + batch_size=batch_size, + model_kwargs=model_kwargs, + decoder_start_token_id=generation_config["decoder_start_token_id"], + bos_token_id=generation_config["bos_token_id"], + ) + + decoder_input_ids = input_ids + model_kwargs["key use_cache"] = True + batch_size, cur_len = input_ids.shape + + if "inputs_embeds" in model_kwargs: + cur_len = model_kwargs["inputs_embeds"].shape[1] + model_kwargs["cache_position"] = paddle.arange(cur_len) + pad_token_id = self.pad_token_id + eos_token_id = [self.eos_token_id] + eos_token = self.eos_token_id + if use_parallel: + unfinished_sequences = paddle.ones( + [batch_size, parallel_step], dtype=paddle.int64 + ) + parallel_length = math.ceil(self.max_seq_len // parallel_step) + else: + unfinished_sequences = paddle.ones(batch_size, dtype=paddle.int64) + parallel_length = self.max_seq_len + past_key_values = [] + + for idx in range(parallel_length): + + model_inputs = self.prepare_inputs_for_generation(input_ids, **model_kwargs) + outputs = self.generate_single_iter( + **model_inputs, + encoder_outputs=encoder_outputs, + return_dict=True, + output_attentions=False, + output_hidden_states=False, + ) + + if use_parallel: + next_token_logits = outputs.logits[:, :, :] + else: + next_token_logits = outputs.logits[:, -1, :] + + next_tokens_scores = self.logits_processor(input_ids, next_token_logits) + next_tokens = paddle.argmax(next_tokens_scores, axis=-1) + if eos_token_id is not None: + # False + if pad_token_id is None: + raise ValueError( + "If `eos_token_id` is defined, make sure that `pad_token_id` is defined." + ) + next_tokens = next_tokens * unfinished_sequences + pad_token_id * ( + 1 - unfinished_sequences + ) + if use_parallel: + input_ids = paddle.concat([input_ids, next_tokens], axis=-1) + else: + input_ids = paddle.concat([input_ids, next_tokens[:, None]], axis=-1) + + model_kwargs = self._update_model_kwargs_for_generation( + outputs, + model_kwargs, + is_encoder_decoder=self.config_decoder.is_encoder_decoder, + ) + if use_parallel: + unfinished_sequences = ( + unfinished_sequences + & ~self.stopping_criteria_parallel(input_ids).cast(paddle.int64) + ) + else: + unfinished_sequences = unfinished_sequences & ~self.stopping_criteria( + input_ids + ).cast(paddle.int64) + + if ( + eos_token is not None + and ( + paddle.cumsum((input_ids == eos_token).cast(paddle.int64), 1)[:, -1] + >= 1 + ).all() + ): + break + return input_ids + + def forwad_train( + self, + encoder_outputs, + decoder_input_ids, + decoder_attention_mask, + past_key_values=None, + decoder_inputs_embeds=None, + labels=None, + use_cache=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + **kwargs, + ): + """ + Forward pass for training the model. + + Args: + encoder_outputs: The outputs from the encoder, typically including hidden states. + decoder_input_ids: Input IDs for the decoder. + decoder_attention_mask: Attention mask for the decoder inputs to avoid attending to padding tokens. + past_key_values: Previously computed key and value states for the decoder, used for fast generation. + decoder_inputs_embeds: Optional embeddings for decoder inputs, used instead of decoder_input_ids if provided. + labels: Labels for computing the training loss. + use_cache: Whether to use a cache of past key values for faster generation. + output_attentions: Whether to output attention weights. + output_hidden_states: Whether to output hidden states of all layers. + return_dict: Whether to return the output as a dictionary. + **kwargs: Additional keyword arguments. + + Returns: + Depending on the `return_dict` flag, returns either a dictionary of model outputs or a tuple. + """ + if self.config_decoder.use_parallel: + batch = decoder_input_ids.shape[0] + add_sos_token = self.config_decoder.parallel_step - 1 + start_token = paddle.zeros([batch, add_sos_token]).cast(paddle.int64) + start_mask = paddle.ones([batch, add_sos_token]).cast(paddle.int64) + decoder_input_ids = paddle.concat([start_token, decoder_input_ids], axis=1) + decoder_attention_mask = paddle.concat( + [start_mask, decoder_attention_mask], axis=1 + ) + + labels = decoder_input_ids * 1 + labels = labels.masked_fill_(labels == self.pad_token_id, -100) + if self.config_decoder.use_parallel: + input_decoder_input_ids = decoder_input_ids[ + :, : -self.config_decoder.parallel_step + ] + input_decoder_attention_mask = decoder_attention_mask[ + :, : -self.config_decoder.parallel_step + ] + else: + input_decoder_input_ids = decoder_input_ids[:, :-1] + input_decoder_attention_mask = decoder_attention_mask[:, :-1] + + encoder_hidden_states = encoder_outputs[0] + kwargs_decoder = {} + if self.config_decoder.hidden_size != self.encoder_hidden_size: + encoder_hidden_states = self.enc_to_dec_proj(encoder_hidden_states) + + decoder_outputs = self.decoder( + input_ids=input_decoder_input_ids, + attention_mask=input_decoder_attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=None, + inputs_embeds=None, + output_attentions=False, + output_hidden_states=output_hidden_states, + use_cache=use_cache, + past_key_values=past_key_values, + return_dict=return_dict, + **kwargs_decoder, + ) + + logits = decoder_outputs.logits + return logits, labels + + # forward for export + def forward(self, inputs, targets=None): + self.is_export = False if self.training else True + if not self.training: + encoder_outputs = inputs + model_kwargs = { + "output_attentions": False, + "output_hidden_states": False, + "use_cache": True, + } + if self.is_export: + word_pred = self.generate_export(encoder_outputs, model_kwargs) + else: + word_pred = self.generate(encoder_outputs, model_kwargs) + + return word_pred + encoder_outputs, tgt_seq, mask = inputs + logits, masked_labels = self.forwad_train(encoder_outputs, tgt_seq, mask) + + return logits, masked_labels diff --git a/ppocr/modeling/heads/rec_pren_head.py b/ppocr/modeling/heads/rec_pren_head.py new file mode 100644 index 0000000..c9e4b3e --- /dev/null +++ b/ppocr/modeling/heads/rec_pren_head.py @@ -0,0 +1,34 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from paddle import nn +from paddle.nn import functional as F + + +class PRENHead(nn.Layer): + def __init__(self, in_channels, out_channels, **kwargs): + super(PRENHead, self).__init__() + self.linear = nn.Linear(in_channels, out_channels) + + def forward(self, x, targets=None): + predicts = self.linear(x) + + if not self.training: + predicts = F.softmax(predicts, axis=2) + + return predicts diff --git a/ppocr/modeling/heads/rec_rfl_head.py b/ppocr/modeling/heads/rec_rfl_head.py new file mode 100644 index 0000000..bd7efd4 --- /dev/null +++ b/ppocr/modeling/heads/rec_rfl_head.py @@ -0,0 +1,106 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/hikopensource/DAVAR-Lab-OCR/blob/main/davarocr/davar_rcg/models/sequence_heads/counting_head.py +""" +import paddle +import paddle.nn as nn +from paddle.nn.initializer import TruncatedNormal, Constant, Normal, KaimingNormal + +from .rec_att_head import AttentionLSTM + +kaiming_init_ = KaimingNormal() +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) + + +class CNTHead(nn.Layer): + def __init__(self, embed_size=512, encode_length=26, out_channels=38, **kwargs): + super(CNTHead, self).__init__() + + self.out_channels = out_channels + + self.Wv_fusion = nn.Linear(embed_size, embed_size, bias_attr=False) + self.Prediction_visual = nn.Linear( + encode_length * embed_size, self.out_channels + ) + + def forward(self, visual_feature): + b, c, h, w = visual_feature.shape + visual_feature = visual_feature.reshape([b, c, h * w]).transpose([0, 2, 1]) + visual_feature_num = self.Wv_fusion(visual_feature) # batch * 26 * 512 + b, n, c = visual_feature_num.shape + # using visual feature directly calculate the text length + visual_feature_num = visual_feature_num.reshape([b, n * c]) + prediction_visual = self.Prediction_visual(visual_feature_num) + + return prediction_visual + + +class RFLHead(nn.Layer): + def __init__( + self, + in_channels=512, + hidden_size=256, + batch_max_legnth=25, + out_channels=38, + use_cnt=True, + use_seq=True, + **kwargs, + ): + super(RFLHead, self).__init__() + assert use_cnt or use_seq + self.use_cnt = use_cnt + self.use_seq = use_seq + if self.use_cnt: + self.cnt_head = CNTHead( + embed_size=in_channels, + encode_length=batch_max_legnth + 1, + out_channels=out_channels, + **kwargs, + ) + if self.use_seq: + self.seq_head = AttentionLSTM( + in_channels=in_channels, + out_channels=out_channels, + hidden_size=hidden_size, + **kwargs, + ) + self.batch_max_legnth = batch_max_legnth + self.num_class = out_channels + self.apply(self.init_weights) + + def init_weights(self, m): + if isinstance(m, nn.Linear): + kaiming_init_(m.weight) + if isinstance(m, nn.Linear) and m.bias is not None: + zeros_(m.bias) + + def forward(self, x, targets=None): + cnt_inputs, seq_inputs = x + if self.use_cnt: + cnt_outputs = self.cnt_head(cnt_inputs) + else: + cnt_outputs = None + if self.use_seq: + if self.training: + seq_outputs = self.seq_head( + seq_inputs, targets[0], self.batch_max_legnth + ) + else: + seq_outputs = self.seq_head(seq_inputs, None, self.batch_max_legnth) + return cnt_outputs, seq_outputs + else: + return cnt_outputs diff --git a/ppocr/modeling/heads/rec_robustscanner_head.py b/ppocr/modeling/heads/rec_robustscanner_head.py new file mode 100644 index 0000000..5510ee8 --- /dev/null +++ b/ppocr/modeling/heads/rec_robustscanner_head.py @@ -0,0 +1,748 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textrecog/encoders/channel_reduction_encoder.py +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textrecog/decoders/robust_scanner_decoder.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F + + +class BaseDecoder(nn.Layer): + def __init__(self, **kwargs): + super().__init__() + + def forward_train(self, feat, out_enc, targets, img_metas): + raise NotImplementedError + + def forward_test(self, feat, out_enc, img_metas): + raise NotImplementedError + + def forward( + self, + feat, + out_enc, + label=None, + valid_ratios=None, + word_positions=None, + train_mode=True, + ): + self.train_mode = train_mode + + if train_mode: + return self.forward_train( + feat, out_enc, label, valid_ratios, word_positions + ) + return self.forward_test(feat, out_enc, valid_ratios, word_positions) + + +class ChannelReductionEncoder(nn.Layer): + """Change the channel number with a one by one convoluational layer. + + Args: + in_channels (int): Number of input channels. + out_channels (int): Number of output channels. + """ + + def __init__(self, in_channels, out_channels, **kwargs): + super(ChannelReductionEncoder, self).__init__() + + self.layer = nn.Conv2D( + in_channels, + out_channels, + kernel_size=1, + stride=1, + padding=0, + weight_attr=nn.initializer.XavierNormal(), + ) + + def forward(self, feat): + """ + Args: + feat (Tensor): Image features with the shape of + :math:`(N, C_{in}, H, W)`. + + Returns: + Tensor: A tensor of shape :math:`(N, C_{out}, H, W)`. + """ + return self.layer(feat) + + +def masked_fill(x, mask, value): + y = paddle.full(x.shape, value, x.dtype) + return paddle.where(mask, y, x) + + +class DotProductAttentionLayer(nn.Layer): + def __init__(self, dim_model=None): + super().__init__() + + self.scale = dim_model**-0.5 if dim_model is not None else 1.0 + + def forward(self, query, key, value, h, w, valid_ratios=None): + query = paddle.transpose(query, (0, 2, 1)) + logits = paddle.matmul(query, key) * self.scale + n, c, t = logits.shape + # reshape to (n, c, h, w) + logits = paddle.reshape(logits, [n, c, h, w]) + if valid_ratios is not None: + # cal mask of attention weight + with paddle.base.framework._stride_in_no_check_dy2st_diff(): + for i, valid_ratio in enumerate(valid_ratios): + valid_width = min(w, int(w * valid_ratio + 0.5)) + if valid_width < w: + logits[i, :, :, valid_width:] = float("-inf") + + # reshape to (n, c, h, w) + logits = paddle.reshape(logits, [n, c, t]) + weights = F.softmax(logits, axis=2) + value = paddle.transpose(value, (0, 2, 1)) + glimpse = paddle.matmul(weights, value) + glimpse = paddle.transpose(glimpse, (0, 2, 1)) + return glimpse + + +class SequenceAttentionDecoder(BaseDecoder): + """Sequence attention decoder for RobustScanner. + + RobustScanner: `RobustScanner: Dynamically Enhancing Positional Clues for + Robust Text Recognition `_ + + Args: + num_classes (int): Number of output classes :math:`C`. + rnn_layers (int): Number of RNN layers. + dim_input (int): Dimension :math:`D_i` of input vector ``feat``. + dim_model (int): Dimension :math:`D_m` of the model. Should also be the + same as encoder output vector ``out_enc``. + max_seq_len (int): Maximum output sequence length :math:`T`. + start_idx (int): The index of ``. + mask (bool): Whether to mask input features according to + ``img_meta['valid_ratio']``. + padding_idx (int): The index of ``. + dropout (float): Dropout rate. + return_feature (bool): Return feature or logits as the result. + encode_value (bool): Whether to use the output of encoder ``out_enc`` + as `value` of attention layer. If False, the original feature + ``feat`` will be used. + + Warning: + This decoder will not predict the final class which is assumed to be + ``. Therefore, its output size is always :math:`C - 1`. `` + is also ignored by loss as specified in + :obj:`mmocr.models.textrecog.recognizer.EncodeDecodeRecognizer`. + """ + + def __init__( + self, + num_classes=None, + rnn_layers=2, + dim_input=512, + dim_model=128, + max_seq_len=40, + start_idx=0, + mask=True, + padding_idx=None, + dropout=0, + return_feature=False, + encode_value=False, + ): + super().__init__() + + self.num_classes = num_classes + self.dim_input = dim_input + self.dim_model = dim_model + self.return_feature = return_feature + self.encode_value = encode_value + self.max_seq_len = max_seq_len + self.start_idx = start_idx + self.mask = mask + + self.embedding = nn.Embedding( + self.num_classes, self.dim_model, padding_idx=padding_idx + ) + + self.sequence_layer = nn.LSTM( + input_size=dim_model, + hidden_size=dim_model, + num_layers=rnn_layers, + time_major=False, + dropout=dropout, + ) + + self.attention_layer = DotProductAttentionLayer() + + self.prediction = None + if not self.return_feature: + pred_num_classes = num_classes - 1 + self.prediction = nn.Linear( + dim_model if encode_value else dim_input, pred_num_classes + ) + + def forward_train(self, feat, out_enc, targets, valid_ratios): + """ + Args: + feat (Tensor): Tensor of shape :math:`(N, D_i, H, W)`. + out_enc (Tensor): Encoder output of shape + :math:`(N, D_m, H, W)`. + targets (Tensor): a tensor of shape :math:`(N, T)`. Each element is the index of a + character. + valid_ratios (Tensor): valid length ratio of img. + Returns: + Tensor: A raw logit tensor of shape :math:`(N, T, C-1)` if + ``return_feature=False``. Otherwise it would be the hidden feature + before the prediction projection layer, whose shape is + :math:`(N, T, D_m)`. + """ + + tgt_embedding = self.embedding(targets) + + n, c_enc, h, w = out_enc.shape + assert c_enc == self.dim_model + _, c_feat, _, _ = feat.shape + assert c_feat == self.dim_input + _, len_q, c_q = tgt_embedding.shape + assert c_q == self.dim_model + assert len_q <= self.max_seq_len + + query, _ = self.sequence_layer(tgt_embedding) + query = paddle.transpose(query, (0, 2, 1)) + key = paddle.reshape(out_enc, [n, c_enc, h * w]) + if self.encode_value: + value = key + else: + value = paddle.reshape(feat, [n, c_feat, h * w]) + + attn_out = self.attention_layer(query, key, value, h, w, valid_ratios) + attn_out = paddle.transpose(attn_out, (0, 2, 1)) + + if self.return_feature: + return attn_out + + out = self.prediction(attn_out) + + return out + + def forward_test(self, feat, out_enc, valid_ratios): + """ + Args: + feat (Tensor): Tensor of shape :math:`(N, D_i, H, W)`. + out_enc (Tensor): Encoder output of shape + :math:`(N, D_m, H, W)`. + valid_ratios (Tensor): valid length ratio of img. + + Returns: + Tensor: The output logit sequence tensor of shape + :math:`(N, T, C-1)`. + """ + seq_len = self.max_seq_len + batch_size = feat.shape[0] + + decode_sequence = ( + paddle.ones((batch_size, seq_len), dtype="int64") * self.start_idx + ) + + outputs = [] + for i in range(seq_len): + step_out = self.forward_test_step( + feat, out_enc, decode_sequence, i, valid_ratios + ) + outputs.append(step_out) + max_idx = paddle.argmax(step_out, axis=1, keepdim=False) + if i < seq_len - 1: + decode_sequence[:, i + 1] = max_idx + + outputs = paddle.stack(outputs, 1) + + return outputs + + def forward_test_step( + self, feat, out_enc, decode_sequence, current_step, valid_ratios + ): + """ + Args: + feat (Tensor): Tensor of shape :math:`(N, D_i, H, W)`. + out_enc (Tensor): Encoder output of shape + :math:`(N, D_m, H, W)`. + decode_sequence (Tensor): Shape :math:`(N, T)`. The tensor that + stores history decoding result. + current_step (int): Current decoding step. + valid_ratios (Tensor): valid length ratio of img + + Returns: + Tensor: Shape :math:`(N, C-1)`. The logit tensor of predicted + tokens at current time step. + """ + + embed = self.embedding(decode_sequence) + + n, c_enc, h, w = out_enc.shape + assert c_enc == self.dim_model + _, c_feat, _, _ = feat.shape + assert c_feat == self.dim_input + _, _, c_q = embed.shape + assert c_q == self.dim_model + + query, _ = self.sequence_layer(embed) + query = paddle.transpose(query, (0, 2, 1)) + key = paddle.reshape(out_enc, [n, c_enc, h * w]) + if self.encode_value: + value = key + else: + value = paddle.reshape(feat, [n, c_feat, h * w]) + + # [n, c, l] + attn_out = self.attention_layer(query, key, value, h, w, valid_ratios) + out = attn_out[:, :, current_step] + + if self.return_feature: + return out + + out = self.prediction(out) + out = F.softmax(out, dim=-1) + + return out + + +class PositionAwareLayer(nn.Layer): + def __init__(self, dim_model, rnn_layers=2): + super().__init__() + + self.dim_model = dim_model + + self.rnn = nn.LSTM( + input_size=dim_model, + hidden_size=dim_model, + num_layers=rnn_layers, + time_major=False, + ) + + self.mixer = nn.Sequential( + nn.Conv2D(dim_model, dim_model, kernel_size=3, stride=1, padding=1), + nn.ReLU(), + nn.Conv2D(dim_model, dim_model, kernel_size=3, stride=1, padding=1), + ) + + def forward(self, img_feature): + n, c, h, w = img_feature.shape + rnn_input = paddle.transpose(img_feature, (0, 2, 3, 1)) + rnn_input = paddle.reshape(rnn_input, (n * h, w, c)) + rnn_output, _ = self.rnn(rnn_input) + rnn_output = paddle.reshape(rnn_output, (n, h, w, c)) + rnn_output = paddle.transpose(rnn_output, (0, 3, 1, 2)) + out = self.mixer(rnn_output) + return out + + +class PositionAttentionDecoder(BaseDecoder): + """Position attention decoder for RobustScanner. + + RobustScanner: `RobustScanner: Dynamically Enhancing Positional Clues for + Robust Text Recognition `_ + + Args: + num_classes (int): Number of output classes :math:`C`. + rnn_layers (int): Number of RNN layers. + dim_input (int): Dimension :math:`D_i` of input vector ``feat``. + dim_model (int): Dimension :math:`D_m` of the model. Should also be the + same as encoder output vector ``out_enc``. + max_seq_len (int): Maximum output sequence length :math:`T`. + mask (bool): Whether to mask input features according to + ``img_meta['valid_ratio']``. + return_feature (bool): Return feature or logits as the result. + encode_value (bool): Whether to use the output of encoder ``out_enc`` + as `value` of attention layer. If False, the original feature + ``feat`` will be used. + + Warning: + This decoder will not predict the final class which is assumed to be + ``. Therefore, its output size is always :math:`C - 1`. `` + is also ignored by loss + + """ + + def __init__( + self, + num_classes=None, + rnn_layers=2, + dim_input=512, + dim_model=128, + max_seq_len=40, + mask=True, + return_feature=False, + encode_value=False, + ): + super().__init__() + + self.num_classes = num_classes + self.dim_input = dim_input + self.dim_model = dim_model + self.max_seq_len = max_seq_len + self.return_feature = return_feature + self.encode_value = encode_value + self.mask = mask + + self.embedding = nn.Embedding(self.max_seq_len + 1, self.dim_model) + + self.position_aware_module = PositionAwareLayer(self.dim_model, rnn_layers) + + self.attention_layer = DotProductAttentionLayer() + + self.prediction = None + if not self.return_feature: + pred_num_classes = num_classes - 1 + self.prediction = nn.Linear( + dim_model if encode_value else dim_input, pred_num_classes + ) + + def _get_position_index(self, length, batch_size): + position_index_list = [] + for i in range(batch_size): + position_index = paddle.arange(0, end=length, step=1, dtype="int64") + position_index_list.append(position_index) + batch_position_index = paddle.stack(position_index_list, axis=0) + return batch_position_index + + def forward_train(self, feat, out_enc, targets, valid_ratios, position_index): + """ + Args: + feat (Tensor): Tensor of shape :math:`(N, D_i, H, W)`. + out_enc (Tensor): Encoder output of shape + :math:`(N, D_m, H, W)`. + targets (dict): A dict with the key ``padded_targets``, a + tensor of shape :math:`(N, T)`. Each element is the index of a + character. + valid_ratios (Tensor): valid length ratio of img. + position_index (Tensor): The position of each word. + + Returns: + Tensor: A raw logit tensor of shape :math:`(N, T, C-1)` if + ``return_feature=False``. Otherwise it will be the hidden feature + before the prediction projection layer, whose shape is + :math:`(N, T, D_m)`. + """ + n, c_enc, h, w = out_enc.shape + assert c_enc == self.dim_model + _, c_feat, _, _ = feat.shape + assert c_feat == self.dim_input + _, len_q = targets.shape + assert len_q <= self.max_seq_len + + position_out_enc = self.position_aware_module(out_enc) + + query = self.embedding(position_index) + query = paddle.transpose(query, (0, 2, 1)) + key = paddle.reshape(position_out_enc, (n, c_enc, h * w)) + if self.encode_value: + value = paddle.reshape(out_enc, (n, c_enc, h * w)) + else: + value = paddle.reshape(feat, (n, c_feat, h * w)) + + attn_out = self.attention_layer(query, key, value, h, w, valid_ratios) + attn_out = paddle.transpose(attn_out, (0, 2, 1)) # [n, len_q, dim_v] + + if self.return_feature: + return attn_out + + return self.prediction(attn_out) + + def forward_test(self, feat, out_enc, valid_ratios, position_index): + """ + Args: + feat (Tensor): Tensor of shape :math:`(N, D_i, H, W)`. + out_enc (Tensor): Encoder output of shape + :math:`(N, D_m, H, W)`. + valid_ratios (Tensor): valid length ratio of img + position_index (Tensor): The position of each word. + + Returns: + Tensor: A raw logit tensor of shape :math:`(N, T, C-1)` if + ``return_feature=False``. Otherwise it would be the hidden feature + before the prediction projection layer, whose shape is + :math:`(N, T, D_m)`. + """ + n, c_enc, h, w = out_enc.shape + assert c_enc == self.dim_model + _, c_feat, _, _ = feat.shape + assert c_feat == self.dim_input + + position_out_enc = self.position_aware_module(out_enc) + + query = self.embedding(position_index) + query = paddle.transpose(query, (0, 2, 1)) + key = paddle.reshape(position_out_enc, (n, c_enc, h * w)) + if self.encode_value: + value = paddle.reshape(out_enc, (n, c_enc, h * w)) + else: + value = paddle.reshape(feat, (n, c_feat, h * w)) + + attn_out = self.attention_layer(query, key, value, h, w, valid_ratios) + attn_out = paddle.transpose(attn_out, (0, 2, 1)) # [n, len_q, dim_v] + + if self.return_feature: + return attn_out + + return self.prediction(attn_out) + + +class RobustScannerFusionLayer(nn.Layer): + def __init__(self, dim_model, dim=-1): + super(RobustScannerFusionLayer, self).__init__() + + self.dim_model = dim_model + self.dim = dim + self.linear_layer = nn.Linear(dim_model * 2, dim_model * 2) + + def forward(self, x0, x1): + assert x0.shape == x1.shape + fusion_input = paddle.concat([x0, x1], self.dim) + output = self.linear_layer(fusion_input) + output = F.glu(output, self.dim) + return output + + +class RobustScannerDecoder(BaseDecoder): + """Decoder for RobustScanner. + + RobustScanner: `RobustScanner: Dynamically Enhancing Positional Clues for + Robust Text Recognition `_ + + Args: + num_classes (int): Number of output classes :math:`C`. + dim_input (int): Dimension :math:`D_i` of input vector ``feat``. + dim_model (int): Dimension :math:`D_m` of the model. Should also be the + same as encoder output vector ``out_enc``. + max_seq_len (int): Maximum output sequence length :math:`T`. + start_idx (int): The index of ``. + mask (bool): Whether to mask input features according to + ``img_meta['valid_ratio']``. + padding_idx (int): The index of ``. + encode_value (bool): Whether to use the output of encoder ``out_enc`` + as `value` of attention layer. If False, the original feature + ``feat`` will be used. + + Warning: + This decoder will not predict the final class which is assumed to be + ``. Therefore, its output size is always :math:`C - 1`. `` + is also ignored by loss as specified in + :obj:`mmocr.models.textrecog.recognizer.EncodeDecodeRecognizer`. + """ + + def __init__( + self, + num_classes=None, + dim_input=512, + dim_model=128, + hybrid_decoder_rnn_layers=2, + hybrid_decoder_dropout=0, + position_decoder_rnn_layers=2, + max_seq_len=40, + start_idx=0, + mask=True, + padding_idx=None, + encode_value=False, + ): + super().__init__() + self.num_classes = num_classes + self.dim_input = dim_input + self.dim_model = dim_model + self.max_seq_len = max_seq_len + self.encode_value = encode_value + self.start_idx = start_idx + self.padding_idx = padding_idx + self.mask = mask + + # init hybrid decoder + self.hybrid_decoder = SequenceAttentionDecoder( + num_classes=num_classes, + rnn_layers=hybrid_decoder_rnn_layers, + dim_input=dim_input, + dim_model=dim_model, + max_seq_len=max_seq_len, + start_idx=start_idx, + mask=mask, + padding_idx=padding_idx, + dropout=hybrid_decoder_dropout, + encode_value=encode_value, + return_feature=True, + ) + + # init position decoder + self.position_decoder = PositionAttentionDecoder( + num_classes=num_classes, + rnn_layers=position_decoder_rnn_layers, + dim_input=dim_input, + dim_model=dim_model, + max_seq_len=max_seq_len, + mask=mask, + encode_value=encode_value, + return_feature=True, + ) + + self.fusion_module = RobustScannerFusionLayer( + self.dim_model if encode_value else dim_input + ) + + pred_num_classes = num_classes - 1 + self.prediction = nn.Linear( + dim_model if encode_value else dim_input, pred_num_classes + ) + + def forward_train(self, feat, out_enc, target, valid_ratios, word_positions): + """ + Args: + feat (Tensor): Tensor of shape :math:`(N, D_i, H, W)`. + out_enc (Tensor): Encoder output of shape + :math:`(N, D_m, H, W)`. + target (dict): A dict with the key ``padded_targets``, a + tensor of shape :math:`(N, T)`. Each element is the index of a + character. + valid_ratios (Tensor): + word_positions (Tensor): The position of each word. + + Returns: + Tensor: A raw logit tensor of shape :math:`(N, T, C-1)`. + """ + hybrid_glimpse = self.hybrid_decoder.forward_train( + feat, out_enc, target, valid_ratios + ) + position_glimpse = self.position_decoder.forward_train( + feat, out_enc, target, valid_ratios, word_positions + ) + + fusion_out = self.fusion_module(hybrid_glimpse, position_glimpse) + + out = self.prediction(fusion_out) + + return out + + def forward_test(self, feat, out_enc, valid_ratios, word_positions): + """ + Args: + feat (Tensor): Tensor of shape :math:`(N, D_i, H, W)`. + out_enc (Tensor): Encoder output of shape + :math:`(N, D_m, H, W)`. + valid_ratios (Tensor): + word_positions (Tensor): The position of each word. + Returns: + Tensor: The output logit sequence tensor of shape + :math:`(N, T, C-1)`. + """ + seq_len = self.max_seq_len + batch_size = feat.shape[0] + + decode_sequence = ( + paddle.ones((batch_size, seq_len), dtype="int64") * self.start_idx + ) + + position_glimpse = self.position_decoder.forward_test( + feat, out_enc, valid_ratios, word_positions + ) + + outputs = [] + for i in range(seq_len): + hybrid_glimpse_step = self.hybrid_decoder.forward_test_step( + feat, out_enc, decode_sequence, i, valid_ratios + ) + + fusion_out = self.fusion_module( + hybrid_glimpse_step, position_glimpse[:, i, :] + ) + + char_out = self.prediction(fusion_out) + char_out = F.softmax(char_out, -1) + outputs.append(char_out) + max_idx = paddle.argmax(char_out, axis=1, keepdim=False) + if i < seq_len - 1: + decode_sequence[:, i + 1] = max_idx + + outputs = paddle.stack(outputs, 1) + + return outputs + + +class RobustScannerHead(nn.Layer): + def __init__( + self, + out_channels, # 90 + unknown + start + padding + in_channels, + enc_outchannles=128, + hybrid_dec_rnn_layers=2, + hybrid_dec_dropout=0, + position_dec_rnn_layers=2, + start_idx=0, + max_text_length=40, + mask=True, + padding_idx=None, + encode_value=False, + **kwargs, + ): + super(RobustScannerHead, self).__init__() + + # encoder module + self.encoder = ChannelReductionEncoder( + in_channels=in_channels, out_channels=enc_outchannles + ) + + # decoder module + self.decoder = RobustScannerDecoder( + num_classes=out_channels, + dim_input=in_channels, + dim_model=enc_outchannles, + hybrid_decoder_rnn_layers=hybrid_dec_rnn_layers, + hybrid_decoder_dropout=hybrid_dec_dropout, + position_decoder_rnn_layers=position_dec_rnn_layers, + max_seq_len=max_text_length, + start_idx=start_idx, + mask=mask, + padding_idx=padding_idx, + encode_value=encode_value, + ) + + def forward(self, inputs, targets=None): + """ + targets: [label, valid_ratio, word_positions] + """ + out_enc = self.encoder(inputs) + valid_ratios = None + word_positions = targets[-1] + + if len(targets) > 1: + valid_ratios = targets[-2] + + if self.training: + label = targets[0] # label + label = paddle.to_tensor(label, dtype="int64") + final_out = self.decoder( + inputs, out_enc, label, valid_ratios, word_positions + ) + if not self.training: + final_out = self.decoder( + inputs, + out_enc, + label=None, + valid_ratios=valid_ratios, + word_positions=word_positions, + train_mode=False, + ) + return final_out diff --git a/ppocr/modeling/heads/rec_sar_head.py b/ppocr/modeling/heads/rec_sar_head.py new file mode 100644 index 0000000..1e01999 --- /dev/null +++ b/ppocr/modeling/heads/rec_sar_head.py @@ -0,0 +1,407 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textrecog/encoders/sar_encoder.py +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textrecog/decoders/sar_decoder.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F + + +class SAREncoder(nn.Layer): + """ + Args: + enc_bi_rnn (bool): If True, use bidirectional RNN in encoder. + enc_drop_rnn (float): Dropout probability of RNN layer in encoder. + enc_gru (bool): If True, use GRU, else LSTM in encoder. + d_model (int): Dim of channels from backbone. + d_enc (int): Dim of encoder RNN layer. + mask (bool): If True, mask padding in RNN sequence. + """ + + def __init__( + self, + enc_bi_rnn=False, + enc_drop_rnn=0.1, + enc_gru=False, + d_model=512, + d_enc=512, + mask=True, + **kwargs, + ): + super().__init__() + assert isinstance(enc_bi_rnn, bool) + assert isinstance(enc_drop_rnn, (int, float)) + assert 0 <= enc_drop_rnn < 1.0 + assert isinstance(enc_gru, bool) + assert isinstance(d_model, int) + assert isinstance(d_enc, int) + assert isinstance(mask, bool) + + self.enc_bi_rnn = enc_bi_rnn + self.enc_drop_rnn = enc_drop_rnn + self.mask = mask + + # LSTM Encoder + if enc_bi_rnn: + direction = "bidirectional" + else: + direction = "forward" + kwargs = dict( + input_size=d_model, + hidden_size=d_enc, + num_layers=2, + time_major=False, + dropout=enc_drop_rnn, + direction=direction, + ) + if enc_gru: + self.rnn_encoder = nn.GRU(**kwargs) + else: + self.rnn_encoder = nn.LSTM(**kwargs) + + # global feature transformation + encoder_rnn_out_size = d_enc * (int(enc_bi_rnn) + 1) + self.linear = nn.Linear(encoder_rnn_out_size, encoder_rnn_out_size) + + def forward(self, feat, img_metas=None): + if img_metas is not None: + assert len(img_metas[0]) == feat.shape[0] + + valid_ratios = None + if img_metas is not None and self.mask: + valid_ratios = img_metas[-1] + + h_feat = feat.shape[2] # bsz c h w + feat_v = F.max_pool2d(feat, kernel_size=(h_feat, 1), stride=1, padding=0) + feat_v = feat_v.squeeze(2) # bsz * C * W + feat_v = paddle.transpose(feat_v, perm=[0, 2, 1]) # bsz * W * C + holistic_feat = self.rnn_encoder(feat_v)[0] # bsz * T * C + + if valid_ratios is not None: + valid_hf = [] + T = paddle.shape(holistic_feat)[1] + for i in range(valid_ratios.shape[0]): + valid_step = ( + paddle.minimum(T, paddle.ceil(valid_ratios[i] * T).astype(T.dtype)) + - 1 + ) + valid_hf.append(holistic_feat[i, valid_step, :]) + valid_hf = paddle.stack(valid_hf, axis=0) + else: + valid_hf = holistic_feat[:, -1, :] # bsz * C + holistic_feat = self.linear(valid_hf) # bsz * C + + return holistic_feat + + +class BaseDecoder(nn.Layer): + def __init__(self, **kwargs): + super().__init__() + + def forward_train(self, feat, out_enc, targets, img_metas): + raise NotImplementedError + + def forward_test(self, feat, out_enc, img_metas): + raise NotImplementedError + + def forward(self, feat, out_enc, label=None, img_metas=None, train_mode=True): + self.train_mode = train_mode + + if train_mode: + return self.forward_train(feat, out_enc, label, img_metas) + return self.forward_test(feat, out_enc, img_metas) + + +class ParallelSARDecoder(BaseDecoder): + """ + Args: + out_channels (int): Output class number. + enc_bi_rnn (bool): If True, use bidirectional RNN in encoder. + dec_bi_rnn (bool): If True, use bidirectional RNN in decoder. + dec_drop_rnn (float): Dropout of RNN layer in decoder. + dec_gru (bool): If True, use GRU, else LSTM in decoder. + d_model (int): Dim of channels from backbone. + d_enc (int): Dim of encoder RNN layer. + d_k (int): Dim of channels of attention module. + pred_dropout (float): Dropout probability of prediction layer. + max_seq_len (int): Maximum sequence length for decoding. + mask (bool): If True, mask padding in feature map. + start_idx (int): Index of start token. + padding_idx (int): Index of padding token. + pred_concat (bool): If True, concat glimpse feature from + attention with holistic feature and hidden state. + """ + + def __init__( + self, + out_channels, # 90 + unknown + start + padding + enc_bi_rnn=False, + dec_bi_rnn=False, + dec_drop_rnn=0.0, + dec_gru=False, + d_model=512, + d_enc=512, + d_k=64, + pred_dropout=0.1, + max_text_length=30, + mask=True, + pred_concat=True, + **kwargs, + ): + super().__init__() + + self.num_classes = out_channels + self.enc_bi_rnn = enc_bi_rnn + self.d_k = d_k + self.start_idx = out_channels - 2 + self.padding_idx = out_channels - 1 + self.max_seq_len = max_text_length + self.mask = mask + self.pred_concat = pred_concat + + encoder_rnn_out_size = d_enc * (int(enc_bi_rnn) + 1) + decoder_rnn_out_size = encoder_rnn_out_size * (int(dec_bi_rnn) + 1) + + # 2D attention layer + self.conv1x1_1 = nn.Linear(decoder_rnn_out_size, d_k) + self.conv3x3_1 = nn.Conv2D(d_model, d_k, kernel_size=3, stride=1, padding=1) + self.conv1x1_2 = nn.Linear(d_k, 1) + + # Decoder RNN layer + if dec_bi_rnn: + direction = "bidirectional" + else: + direction = "forward" + + kwargs = dict( + input_size=encoder_rnn_out_size, + hidden_size=encoder_rnn_out_size, + num_layers=2, + time_major=False, + dropout=dec_drop_rnn, + direction=direction, + ) + if dec_gru: + self.rnn_decoder = nn.GRU(**kwargs) + else: + self.rnn_decoder = nn.LSTM(**kwargs) + + # Decoder input embedding + self.embedding = nn.Embedding( + self.num_classes, encoder_rnn_out_size, padding_idx=self.padding_idx + ) + + # Prediction layer + self.pred_dropout = nn.Dropout(pred_dropout) + pred_num_classes = self.num_classes - 1 + if pred_concat: + fc_in_channel = decoder_rnn_out_size + d_model + encoder_rnn_out_size + else: + fc_in_channel = d_model + self.prediction = nn.Linear(fc_in_channel, pred_num_classes) + + def _2d_attention(self, decoder_input, feat, holistic_feat, valid_ratios=None): + y = self.rnn_decoder(decoder_input)[0] + # y: bsz * (seq_len + 1) * hidden_size + + attn_query = self.conv1x1_1(y) # bsz * (seq_len + 1) * attn_size + bsz, seq_len, attn_size = attn_query.shape + attn_query = paddle.unsqueeze(attn_query, axis=[3, 4]) + # (bsz, seq_len + 1, attn_size, 1, 1) + + attn_key = self.conv3x3_1(feat) + # bsz * attn_size * h * w + attn_key = attn_key.unsqueeze(1) + # bsz * 1 * attn_size * h * w + + attn_weight = paddle.tanh(paddle.add(attn_key, attn_query)) + + # bsz * (seq_len + 1) * attn_size * h * w + attn_weight = paddle.transpose(attn_weight, perm=[0, 1, 3, 4, 2]) + # bsz * (seq_len + 1) * h * w * attn_size + attn_weight = self.conv1x1_2(attn_weight) + # bsz * (seq_len + 1) * h * w * 1 + bsz, T, h, w, c = paddle.shape(attn_weight) + assert c == 1 + + if valid_ratios is not None: + # cal mask of attention weight + for i in range(valid_ratios.shape[0]): + valid_width = paddle.minimum( + w.astype("int64"), paddle.ceil(valid_ratios[i] * w).astype("int64") + ) + if valid_width < w: + attn_weight[i, :, :, valid_width:, :] = float("-inf") + + attn_weight = paddle.reshape(attn_weight, [bsz, T, -1]) + attn_weight = F.softmax(attn_weight, axis=-1) + + attn_weight = paddle.reshape(attn_weight, [bsz, T, h, w, c]) + attn_weight = paddle.transpose(attn_weight, perm=[0, 1, 4, 2, 3]) + # attn_weight: bsz * T * c * h * w + # feat: bsz * c * h * w + attn_feat = paddle.sum( + paddle.multiply(feat.unsqueeze(1), attn_weight), (3, 4), keepdim=False + ) + # bsz * (seq_len + 1) * C + + # Linear transformation + if self.pred_concat: + hf_c = holistic_feat.shape[-1] + holistic_feat = paddle.expand(holistic_feat, shape=[bsz, seq_len, hf_c]) + y = self.prediction( + paddle.concat( + (y, attn_feat.astype(y.dtype), holistic_feat.astype(y.dtype)), 2 + ) + ) + else: + y = self.prediction(attn_feat) + # bsz * (seq_len + 1) * num_classes + if self.train_mode: + y = self.pred_dropout(y) + + return y + + def forward_train(self, feat, out_enc, label, img_metas): + """ + img_metas: [label, valid_ratio] + """ + if img_metas is not None: + assert img_metas[0].shape[0] == feat.shape[0] + + valid_ratios = None + if img_metas is not None and self.mask: + valid_ratios = img_metas[-1] + + lab_embedding = self.embedding(label) + # bsz * seq_len * emb_dim + out_enc = out_enc.unsqueeze(1).astype(lab_embedding.dtype) + # bsz * 1 * emb_dim + in_dec = paddle.concat((out_enc, lab_embedding), axis=1) + # bsz * (seq_len + 1) * C + out_dec = self._2d_attention(in_dec, feat, out_enc, valid_ratios=valid_ratios) + + return out_dec[:, 1:, :] # bsz * seq_len * num_classes + + def forward_test(self, feat, out_enc, img_metas): + if img_metas is not None: + assert len(img_metas[0]) == feat.shape[0] + + valid_ratios = None + if img_metas is not None and self.mask: + valid_ratios = img_metas[-1] + + seq_len = self.max_seq_len + bsz = feat.shape[0] + start_token = paddle.full((bsz,), fill_value=self.start_idx, dtype="int64") + # bsz + start_token = self.embedding(start_token) + # bsz * emb_dim + emb_dim = start_token.shape[1] + start_token = start_token.unsqueeze(1) + start_token = paddle.expand(start_token, shape=[bsz, seq_len, emb_dim]) + # bsz * seq_len * emb_dim + out_enc = out_enc.unsqueeze(1) + # bsz * 1 * emb_dim + decoder_input = paddle.concat((out_enc, start_token), axis=1) + # bsz * (seq_len + 1) * emb_dim + + outputs = [] + for i in range(1, seq_len + 1): + decoder_output = self._2d_attention( + decoder_input, feat, out_enc, valid_ratios=valid_ratios + ) + char_output = decoder_output[:, i, :] # bsz * num_classes + char_output = F.softmax(char_output, -1) + outputs.append(char_output) + max_idx = paddle.argmax(char_output, axis=1, keepdim=False) + char_embedding = self.embedding(max_idx) # bsz * emb_dim + if i < seq_len: + decoder_input[:, i + 1, :] = char_embedding + + outputs = paddle.stack(outputs, 1) # bsz * seq_len * num_classes + + return outputs + + +class SARHead(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + enc_dim=512, + max_text_length=30, + enc_bi_rnn=False, + enc_drop_rnn=0.1, + enc_gru=False, + dec_bi_rnn=False, + dec_drop_rnn=0.0, + dec_gru=False, + d_k=512, + pred_dropout=0.1, + pred_concat=True, + **kwargs, + ): + super(SARHead, self).__init__() + + # encoder module + self.encoder = SAREncoder( + enc_bi_rnn=enc_bi_rnn, + enc_drop_rnn=enc_drop_rnn, + enc_gru=enc_gru, + d_model=in_channels, + d_enc=enc_dim, + ) + + # decoder module + self.decoder = ParallelSARDecoder( + out_channels=out_channels, + enc_bi_rnn=enc_bi_rnn, + dec_bi_rnn=dec_bi_rnn, + dec_drop_rnn=dec_drop_rnn, + dec_gru=dec_gru, + d_model=in_channels, + d_enc=enc_dim, + d_k=d_k, + pred_dropout=pred_dropout, + max_text_length=max_text_length, + pred_concat=pred_concat, + ) + + def forward(self, feat, targets=None): + """ + img_metas: [label, valid_ratio] + """ + holistic_feat = self.encoder(feat, targets) # bsz c + + if self.training: + label = targets[0] # label + final_out = self.decoder(feat, holistic_feat, label, img_metas=targets) + else: + final_out = self.decoder( + feat, holistic_feat, label=None, img_metas=targets, train_mode=False + ) + # (bsz, seq_len, num_classes) + + return final_out diff --git a/ppocr/modeling/heads/rec_satrn_head.py b/ppocr/modeling/heads/rec_satrn_head.py new file mode 100644 index 0000000..8f20474 --- /dev/null +++ b/ppocr/modeling/heads/rec_satrn_head.py @@ -0,0 +1,592 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/1.x/mmocr/models/textrecog/encoders/satrn_encoder.py +https://github.com/open-mmlab/mmocr/blob/1.x/mmocr/models/textrecog/decoders/nrtr_decoder.py +""" + +import math +import numpy as np +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle import ParamAttr, reshape, transpose +from paddle.nn import Conv2D, BatchNorm, Linear, Dropout +from paddle.nn import AdaptiveAvgPool2D, MaxPool2D, AvgPool2D +from paddle.nn.initializer import KaimingNormal, Uniform, Constant + + +class ConvBNLayer(nn.Layer): + def __init__( + self, num_channels, filter_size, num_filters, stride, padding, num_groups=1 + ): + super(ConvBNLayer, self).__init__() + + self.conv = nn.Conv2D( + in_channels=num_channels, + out_channels=num_filters, + kernel_size=filter_size, + stride=stride, + padding=padding, + groups=num_groups, + bias_attr=False, + ) + + self.bn = nn.BatchNorm2D( + num_filters, + weight_attr=ParamAttr(initializer=Constant(1)), + bias_attr=ParamAttr(initializer=Constant(0)), + ) + self.relu = nn.ReLU() + + def forward(self, inputs): + y = self.conv(inputs) + y = self.bn(y) + y = self.relu(y) + return y + + +class SATRNEncoderLayer(nn.Layer): + def __init__( + self, + d_model=512, + d_inner=512, + n_head=8, + d_k=64, + d_v=64, + dropout=0.1, + qkv_bias=False, + ): + super().__init__() + self.norm1 = nn.LayerNorm(d_model) + self.attn = MultiHeadAttention( + n_head, d_model, d_k, d_v, qkv_bias=qkv_bias, dropout=dropout + ) + self.norm2 = nn.LayerNorm(d_model) + self.feed_forward = LocalityAwareFeedforward(d_model, d_inner, dropout=dropout) + + def forward(self, x, h, w, mask=None): + n, hw, c = x.shape + residual = x + x = self.norm1(x) + x = residual + self.attn(x, x, x, mask) + residual = x + x = self.norm2(x) + x = x.transpose([0, 2, 1]).reshape([n, c, h, w]) + x = self.feed_forward(x) + x = x.reshape([n, c, hw]).transpose([0, 2, 1]) + x = residual + x + return x + + +class LocalityAwareFeedforward(nn.Layer): + def __init__( + self, + d_in, + d_hid, + dropout=0.1, + ): + super().__init__() + self.conv1 = ConvBNLayer(d_in, 1, d_hid, stride=1, padding=0) + + self.depthwise_conv = ConvBNLayer( + d_hid, 3, d_hid, stride=1, padding=1, num_groups=d_hid + ) + + self.conv2 = ConvBNLayer(d_hid, 1, d_in, stride=1, padding=0) + + def forward(self, x): + x = self.conv1(x) + x = self.depthwise_conv(x) + x = self.conv2(x) + + return x + + +class Adaptive2DPositionalEncoding(nn.Layer): + def __init__(self, d_hid=512, n_height=100, n_width=100, dropout=0.1): + super().__init__() + + h_position_encoder = self._get_sinusoid_encoding_table(n_height, d_hid) + h_position_encoder = h_position_encoder.transpose([1, 0]) + h_position_encoder = h_position_encoder.reshape([1, d_hid, n_height, 1]) + + w_position_encoder = self._get_sinusoid_encoding_table(n_width, d_hid) + w_position_encoder = w_position_encoder.transpose([1, 0]) + w_position_encoder = w_position_encoder.reshape([1, d_hid, 1, n_width]) + + self.register_buffer("h_position_encoder", h_position_encoder) + self.register_buffer("w_position_encoder", w_position_encoder) + + self.h_scale = self.scale_factor_generate(d_hid) + self.w_scale = self.scale_factor_generate(d_hid) + self.pool = nn.AdaptiveAvgPool2D(1) + self.dropout = nn.Dropout(p=dropout) + + def _get_sinusoid_encoding_table(self, n_position, d_hid): + """Sinusoid position encoding table.""" + denominator = paddle.to_tensor( + [1.0 / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)] + ) + denominator = denominator.reshape([1, -1]) + pos_tensor = paddle.cast(paddle.arange(n_position).unsqueeze(-1), "float32") + sinusoid_table = pos_tensor * denominator + sinusoid_table[:, 0::2] = paddle.sin(sinusoid_table[:, 0::2]) + sinusoid_table[:, 1::2] = paddle.cos(sinusoid_table[:, 1::2]) + + return sinusoid_table + + def scale_factor_generate(self, d_hid): + scale_factor = nn.Sequential( + nn.Conv2D(d_hid, d_hid, 1), + nn.ReLU(), + nn.Conv2D(d_hid, d_hid, 1), + nn.Sigmoid(), + ) + + return scale_factor + + def forward(self, x): + b, c, h, w = x.shape + + avg_pool = self.pool(x) + + h_pos_encoding = self.h_scale(avg_pool) * self.h_position_encoder[:, :, :h, :] + w_pos_encoding = self.w_scale(avg_pool) * self.w_position_encoder[:, :, :, :w] + + out = x + h_pos_encoding + w_pos_encoding + + out = self.dropout(out) + + return out + + +class ScaledDotProductAttention(nn.Layer): + def __init__(self, temperature, attn_dropout=0.1): + super().__init__() + self.temperature = temperature + self.dropout = nn.Dropout(attn_dropout) + + def forward(self, q, k, v, mask=None): + def masked_fill(x, mask, value): + y = paddle.full(x.shape, value, x.dtype) + return paddle.where(mask, y, x) + + attn = paddle.matmul(q / self.temperature, k.transpose([0, 1, 3, 2])) + if mask is not None: + attn = masked_fill(attn, mask == 0, -1e9) + # attn = attn.masked_fill(mask == 0, float('-inf')) + # attn += mask + + attn = self.dropout(F.softmax(attn, axis=-1)) + output = paddle.matmul(attn, v) + + return output, attn + + +class MultiHeadAttention(nn.Layer): + def __init__( + self, n_head=8, d_model=512, d_k=64, d_v=64, dropout=0.1, qkv_bias=False + ): + super().__init__() + self.n_head = n_head + self.d_k = d_k + self.d_v = d_v + + self.dim_k = n_head * d_k + self.dim_v = n_head * d_v + + self.linear_q = nn.Linear(self.dim_k, self.dim_k, bias_attr=qkv_bias) + self.linear_k = nn.Linear(self.dim_k, self.dim_k, bias_attr=qkv_bias) + self.linear_v = nn.Linear(self.dim_v, self.dim_v, bias_attr=qkv_bias) + + self.attention = ScaledDotProductAttention(d_k**0.5, dropout) + + self.fc = nn.Linear(self.dim_v, d_model, bias_attr=qkv_bias) + self.proj_drop = nn.Dropout(dropout) + + def forward(self, q, k, v, mask=None): + batch_size, len_q, _ = q.shape + _, len_k, _ = k.shape + + q = self.linear_q(q).reshape([batch_size, len_q, self.n_head, self.d_k]) + k = self.linear_k(k).reshape([batch_size, len_k, self.n_head, self.d_k]) + v = self.linear_v(v).reshape([batch_size, len_k, self.n_head, self.d_v]) + + q, k, v = ( + q.transpose([0, 2, 1, 3]), + k.transpose([0, 2, 1, 3]), + v.transpose([0, 2, 1, 3]), + ) + + if mask is not None: + if mask.dim() == 3: + mask = mask.unsqueeze(1) + elif mask.dim() == 2: + mask = mask.unsqueeze(1).unsqueeze(1) + + attn_out, _ = self.attention(q, k, v, mask=mask) + + attn_out = attn_out.transpose([0, 2, 1, 3]).reshape( + [batch_size, len_q, self.dim_v] + ) + + attn_out = self.fc(attn_out) + attn_out = self.proj_drop(attn_out) + + return attn_out + + +class SATRNEncoder(nn.Layer): + def __init__( + self, + n_layers=12, + n_head=8, + d_k=64, + d_v=64, + d_model=512, + n_position=100, + d_inner=256, + dropout=0.1, + ): + super().__init__() + self.d_model = d_model + self.position_enc = Adaptive2DPositionalEncoding( + d_hid=d_model, n_height=n_position, n_width=n_position, dropout=dropout + ) + self.layer_stack = nn.LayerList( + [ + SATRNEncoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout) + for _ in range(n_layers) + ] + ) + self.layer_norm = nn.LayerNorm(d_model) + + def forward(self, feat, valid_ratios=None): + """ + Args: + feat (Tensor): Feature tensor of shape :math:`(N, D_m, H, W)`. + img_metas (dict): A dict that contains meta information of input + images. Preferably with the key ``valid_ratio``. + + Returns: + Tensor: A tensor of shape :math:`(N, T, D_m)`. + """ + if valid_ratios is None: + bs = feat.shape[0] + valid_ratios = paddle.full((bs, 1), 1.0, dtype=paddle.float32) + + feat = self.position_enc(feat) + n, c, h, w = feat.shape + + mask = paddle.zeros((n, h, w)) + for i, valid_ratio in enumerate(valid_ratios): + valid_width = int(min(w, paddle.ceil(w * valid_ratio))) + mask[i, :, :valid_width] = 1 + + mask = mask.reshape([n, h * w]) + feat = feat.reshape([n, c, h * w]) + + output = feat.transpose([0, 2, 1]) + for enc_layer in self.layer_stack: + output = enc_layer(output, h, w, mask) + output = self.layer_norm(output) + + return output + + +class PositionwiseFeedForward(nn.Layer): + def __init__(self, d_in, d_hid, dropout=0.1): + super().__init__() + self.w_1 = nn.Linear(d_in, d_hid) + self.w_2 = nn.Linear(d_hid, d_in) + self.act = nn.GELU() + self.dropout = nn.Dropout(dropout) + + def forward(self, x): + x = self.w_1(x) + x = self.act(x) + x = self.w_2(x) + x = self.dropout(x) + + return x + + +class PositionalEncoding(nn.Layer): + def __init__(self, d_hid=512, n_position=200, dropout=0): + super().__init__() + self.dropout = nn.Dropout(p=dropout) + + # Not a parameter + # Position table of shape (1, n_position, d_hid) + self.register_buffer( + "position_table", self._get_sinusoid_encoding_table(n_position, d_hid) + ) + + def _get_sinusoid_encoding_table(self, n_position, d_hid): + """Sinusoid position encoding table.""" + denominator = paddle.to_tensor( + [1.0 / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)] + ) + denominator = denominator.reshape([1, -1]) + pos_tensor = paddle.cast(paddle.arange(n_position).unsqueeze(-1), "float32") + sinusoid_table = pos_tensor * denominator + sinusoid_table[:, 0::2] = paddle.sin(sinusoid_table[:, 0::2]) + sinusoid_table[:, 1::2] = paddle.cos(sinusoid_table[:, 1::2]) + + return sinusoid_table.unsqueeze(0) + + def forward(self, x): + x = x + self.position_table[:, : x.shape[1]].clone().detach() + return self.dropout(x) + + +class TFDecoderLayer(nn.Layer): + def __init__( + self, + d_model=512, + d_inner=256, + n_head=8, + d_k=64, + d_v=64, + dropout=0.1, + qkv_bias=False, + operation_order=None, + ): + super().__init__() + + self.norm1 = nn.LayerNorm(d_model) + self.norm2 = nn.LayerNorm(d_model) + self.norm3 = nn.LayerNorm(d_model) + + self.self_attn = MultiHeadAttention( + n_head, d_model, d_k, d_v, dropout=dropout, qkv_bias=qkv_bias + ) + + self.enc_attn = MultiHeadAttention( + n_head, d_model, d_k, d_v, dropout=dropout, qkv_bias=qkv_bias + ) + + self.mlp = PositionwiseFeedForward(d_model, d_inner, dropout=dropout) + + self.operation_order = operation_order + if self.operation_order is None: + self.operation_order = ( + "norm", + "self_attn", + "norm", + "enc_dec_attn", + "norm", + "ffn", + ) + assert self.operation_order in [ + ("norm", "self_attn", "norm", "enc_dec_attn", "norm", "ffn"), + ("self_attn", "norm", "enc_dec_attn", "norm", "ffn", "norm"), + ] + + def forward( + self, dec_input, enc_output, self_attn_mask=None, dec_enc_attn_mask=None + ): + if self.operation_order == ( + "self_attn", + "norm", + "enc_dec_attn", + "norm", + "ffn", + "norm", + ): + dec_attn_out = self.self_attn( + dec_input, dec_input, dec_input, self_attn_mask + ) + dec_attn_out += dec_input + dec_attn_out = self.norm1(dec_attn_out) + + enc_dec_attn_out = self.enc_attn( + dec_attn_out, enc_output, enc_output, dec_enc_attn_mask + ) + enc_dec_attn_out += dec_attn_out + enc_dec_attn_out = self.norm2(enc_dec_attn_out) + + mlp_out = self.mlp(enc_dec_attn_out) + mlp_out += enc_dec_attn_out + mlp_out = self.norm3(mlp_out) + elif self.operation_order == ( + "norm", + "self_attn", + "norm", + "enc_dec_attn", + "norm", + "ffn", + ): + dec_input_norm = self.norm1(dec_input) + dec_attn_out = self.self_attn( + dec_input_norm, dec_input_norm, dec_input_norm, self_attn_mask + ) + dec_attn_out += dec_input + + enc_dec_attn_in = self.norm2(dec_attn_out) + enc_dec_attn_out = self.enc_attn( + enc_dec_attn_in, enc_output, enc_output, dec_enc_attn_mask + ) + enc_dec_attn_out += dec_attn_out + + mlp_out = self.mlp(self.norm3(enc_dec_attn_out)) + mlp_out += enc_dec_attn_out + + return mlp_out + + +class SATRNDecoder(nn.Layer): + def __init__( + self, + n_layers=6, + d_embedding=512, + n_head=8, + d_k=64, + d_v=64, + d_model=512, + d_inner=256, + n_position=200, + dropout=0.1, + num_classes=93, + max_seq_len=40, + start_idx=1, + padding_idx=92, + ): + super().__init__() + + self.padding_idx = padding_idx + self.start_idx = start_idx + self.max_seq_len = max_seq_len + + self.trg_word_emb = nn.Embedding( + num_classes, d_embedding, padding_idx=padding_idx + ) + + self.position_enc = PositionalEncoding(d_embedding, n_position=n_position) + self.dropout = nn.Dropout(p=dropout) + + self.layer_stack = nn.LayerList( + [ + TFDecoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout) + for _ in range(n_layers) + ] + ) + self.layer_norm = nn.LayerNorm(d_model, epsilon=1e-6) + + pred_num_class = num_classes - 1 # ignore padding_idx + self.classifier = nn.Linear(d_model, pred_num_class) + + @staticmethod + def get_pad_mask(seq, pad_idx): + return (seq != pad_idx).unsqueeze(-2) + + @staticmethod + def get_subsequent_mask(seq): + """For masking out the subsequent info.""" + len_s = seq.shape[1] + subsequent_mask = 1 - paddle.triu(paddle.ones((len_s, len_s)), diagonal=1) + subsequent_mask = paddle.cast(subsequent_mask.unsqueeze(0), "bool") + + return subsequent_mask + + def _attention(self, trg_seq, src, src_mask=None): + trg_embedding = self.trg_word_emb(trg_seq) + trg_pos_encoded = self.position_enc(trg_embedding) + tgt = self.dropout(trg_pos_encoded) + + trg_mask = self.get_pad_mask( + trg_seq, pad_idx=self.padding_idx + ) & self.get_subsequent_mask(trg_seq) + output = tgt + for dec_layer in self.layer_stack: + output = dec_layer( + output, src, self_attn_mask=trg_mask, dec_enc_attn_mask=src_mask + ) + output = self.layer_norm(output) + + return output + + def _get_mask(self, logit, valid_ratios): + N, T, _ = logit.shape + mask = None + if valid_ratios is not None: + mask = paddle.zeros((N, T)) + for i, valid_ratio in enumerate(valid_ratios): + valid_width = min(T, math.ceil(T * valid_ratio)) + mask[i, :valid_width] = 1 + + return mask + + def forward_train(self, feat, out_enc, targets, valid_ratio): + src_mask = self._get_mask(out_enc, valid_ratio) + attn_output = self._attention(targets, out_enc, src_mask=src_mask) + outputs = self.classifier(attn_output) + + return outputs + + def forward_test(self, feat, out_enc, valid_ratio): + src_mask = self._get_mask(out_enc, valid_ratio) + N = out_enc.shape[0] + init_target_seq = paddle.full( + (N, self.max_seq_len + 1), self.padding_idx, dtype="int64" + ) + # bsz * seq_len + init_target_seq[:, 0] = self.start_idx + + outputs = [] + for step in range(0, paddle.to_tensor(self.max_seq_len)): + decoder_output = self._attention( + init_target_seq, out_enc, src_mask=src_mask + ) + # bsz * seq_len * C + step_result = F.softmax( + self.classifier(decoder_output[:, step, :]), axis=-1 + ) + # bsz * num_classes + outputs.append(step_result) + step_max_index = paddle.argmax(step_result, axis=-1) + init_target_seq[:, step + 1] = step_max_index + + outputs = paddle.stack(outputs, axis=1) + + return outputs + + def forward(self, feat, out_enc, targets=None, valid_ratio=None): + if self.training: + return self.forward_train(feat, out_enc, targets, valid_ratio) + else: + return self.forward_test(feat, out_enc, valid_ratio) + + +class SATRNHead(nn.Layer): + def __init__(self, enc_cfg, dec_cfg, **kwargs): + super(SATRNHead, self).__init__() + + # encoder module + self.encoder = SATRNEncoder(**enc_cfg) + + # decoder module + self.decoder = SATRNDecoder(**dec_cfg) + + def forward(self, feat, targets=None): + if targets is not None: + targets, valid_ratio = targets + else: + targets, valid_ratio = None, None + holistic_feat = self.encoder(feat, valid_ratio) # bsz c + final_out = self.decoder(feat, holistic_feat, targets, valid_ratio) + + return final_out diff --git a/ppocr/modeling/heads/rec_spin_att_head.py b/ppocr/modeling/heads/rec_spin_att_head.py new file mode 100644 index 0000000..930d970 --- /dev/null +++ b/ppocr/modeling/heads/rec_spin_att_head.py @@ -0,0 +1,124 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This code is refer from: +https://github.com/hikopensource/DAVAR-Lab-OCR/davarocr/davar_rcg/models/sequence_heads/att_head.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F + + +class SPINAttentionHead(nn.Layer): + def __init__(self, in_channels, out_channels, hidden_size, **kwargs): + super(SPINAttentionHead, self).__init__() + self.input_size = in_channels + self.hidden_size = hidden_size + self.num_classes = out_channels + + self.attention_cell = AttentionLSTMCell( + in_channels, hidden_size, out_channels, use_gru=False + ) + self.generator = nn.Linear(hidden_size, out_channels) + + def _char_to_onehot(self, input_char, onehot_dim): + input_ont_hot = F.one_hot(input_char, onehot_dim) + return input_ont_hot + + def forward(self, inputs, targets=None, batch_max_length=25): + batch_size = inputs.shape[0] + num_steps = batch_max_length + 1 # +1 for [sos] at end of sentence + + hidden = ( + paddle.zeros((batch_size, self.hidden_size)), + paddle.zeros((batch_size, self.hidden_size)), + ) + output_hiddens = [] + if self.training: # for train + targets = targets[0] + for i in range(num_steps): + char_onehots = self._char_to_onehot( + targets[:, i], onehot_dim=self.num_classes + ) + (outputs, hidden), alpha = self.attention_cell( + hidden, inputs, char_onehots + ) + output_hiddens.append(paddle.unsqueeze(outputs, axis=1)) + output = paddle.concat(output_hiddens, axis=1) + probs = self.generator(output) + else: + targets = paddle.zeros(shape=[batch_size], dtype="int32") + probs = None + char_onehots = None + outputs = None + alpha = None + + for i in range(num_steps): + char_onehots = self._char_to_onehot( + targets, onehot_dim=self.num_classes + ) + (outputs, hidden), alpha = self.attention_cell( + hidden, inputs, char_onehots + ) + probs_step = self.generator(outputs) + if probs is None: + probs = paddle.unsqueeze(probs_step, axis=1) + else: + probs = paddle.concat( + [probs, paddle.unsqueeze(probs_step, axis=1)], axis=1 + ) + next_input = probs_step.argmax(axis=1) + targets = next_input + if not self.training: + probs = paddle.nn.functional.softmax(probs, axis=2) + return probs + + +class AttentionLSTMCell(nn.Layer): + def __init__(self, input_size, hidden_size, num_embeddings, use_gru=False): + super(AttentionLSTMCell, self).__init__() + self.i2h = nn.Linear(input_size, hidden_size, bias_attr=False) + self.h2h = nn.Linear(hidden_size, hidden_size) + self.score = nn.Linear(hidden_size, 1, bias_attr=False) + if not use_gru: + self.rnn = nn.LSTMCell( + input_size=input_size + num_embeddings, hidden_size=hidden_size + ) + else: + self.rnn = nn.GRUCell( + input_size=input_size + num_embeddings, hidden_size=hidden_size + ) + + self.hidden_size = hidden_size + + def forward(self, prev_hidden, batch_H, char_onehots): + batch_H_proj = self.i2h(batch_H) + prev_hidden_proj = paddle.unsqueeze(self.h2h(prev_hidden[0]), axis=1) + res = paddle.add(batch_H_proj, prev_hidden_proj) + res = paddle.tanh(res) + e = self.score(res) + + alpha = F.softmax(e, axis=1) + alpha = paddle.transpose(alpha, [0, 2, 1]) + context = paddle.squeeze(paddle.mm(alpha, batch_H), axis=1) + concat_context = paddle.concat([context, char_onehots], 1) + cur_hidden = self.rnn(concat_context, prev_hidden) + + return cur_hidden, alpha diff --git a/ppocr/modeling/heads/rec_srn_head.py b/ppocr/modeling/heads/rec_srn_head.py new file mode 100644 index 0000000..8f6f139 --- /dev/null +++ b/ppocr/modeling/heads/rec_srn_head.py @@ -0,0 +1,315 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn, ParamAttr +from paddle.nn import functional as F +import numpy as np +from .self_attention import WrapEncoderForFeature +from .self_attention import WrapEncoder +from paddle.static import Program +from ppocr.modeling.backbones.rec_resnet_fpn import ResNetFPN + +from collections import OrderedDict + +gradient_clip = 10 + + +class PVAM(nn.Layer): + def __init__( + self, + in_channels, + char_num, + max_text_length, + num_heads, + num_encoder_tus, + hidden_dims, + ): + super(PVAM, self).__init__() + self.char_num = char_num + self.max_length = max_text_length + self.num_heads = num_heads + self.num_encoder_TUs = num_encoder_tus + self.hidden_dims = hidden_dims + # Transformer encoder + t = 256 + c = 512 + self.wrap_encoder_for_feature = WrapEncoderForFeature( + src_vocab_size=1, + max_length=t, + n_layer=self.num_encoder_TUs, + n_head=self.num_heads, + d_key=int(self.hidden_dims / self.num_heads), + d_value=int(self.hidden_dims / self.num_heads), + d_model=self.hidden_dims, + d_inner_hid=self.hidden_dims, + prepostprocess_dropout=0.1, + attention_dropout=0.1, + relu_dropout=0.1, + preprocess_cmd="n", + postprocess_cmd="da", + weight_sharing=True, + ) + + # PVAM + self.flatten0 = paddle.nn.Flatten(start_axis=0, stop_axis=1) + self.fc0 = paddle.nn.Linear( + in_features=in_channels, + out_features=in_channels, + ) + self.emb = paddle.nn.Embedding( + num_embeddings=self.max_length, embedding_dim=in_channels + ) + self.flatten1 = paddle.nn.Flatten(start_axis=0, stop_axis=2) + self.fc1 = paddle.nn.Linear( + in_features=in_channels, out_features=1, bias_attr=False + ) + + def forward(self, inputs, encoder_word_pos, gsrm_word_pos): + b, c, h, w = inputs.shape + conv_features = paddle.reshape(inputs, shape=[-1, c, h * w]) + conv_features = paddle.transpose(conv_features, perm=[0, 2, 1]) + # transformer encoder + b, t, c = conv_features.shape + + enc_inputs = [conv_features, encoder_word_pos, None] + word_features = self.wrap_encoder_for_feature(enc_inputs) + + # pvam + b, t, c = word_features.shape + word_features = self.fc0(word_features) + word_features_ = paddle.reshape(word_features, [-1, 1, t, c]) + word_features_ = paddle.tile(word_features_, [1, self.max_length, 1, 1]) + word_pos_feature = self.emb(gsrm_word_pos) + word_pos_feature_ = paddle.reshape( + word_pos_feature, [-1, self.max_length, 1, c] + ) + word_pos_feature_ = paddle.tile(word_pos_feature_, [1, 1, t, 1]) + y = word_pos_feature_ + word_features_ + y = F.tanh(y) + attention_weight = self.fc1(y) + attention_weight = paddle.reshape( + attention_weight, shape=[-1, self.max_length, t] + ) + attention_weight = F.softmax(attention_weight, axis=-1) + pvam_features = paddle.matmul( + attention_weight, word_features + ) # [b, max_length, c] + return pvam_features + + +class GSRM(nn.Layer): + def __init__( + self, + in_channels, + char_num, + max_text_length, + num_heads, + num_encoder_tus, + num_decoder_tus, + hidden_dims, + ): + super(GSRM, self).__init__() + self.char_num = char_num + self.max_length = max_text_length + self.num_heads = num_heads + self.num_encoder_TUs = num_encoder_tus + self.num_decoder_TUs = num_decoder_tus + self.hidden_dims = hidden_dims + + self.fc0 = paddle.nn.Linear(in_features=in_channels, out_features=self.char_num) + self.wrap_encoder0 = WrapEncoder( + src_vocab_size=self.char_num + 1, + max_length=self.max_length, + n_layer=self.num_decoder_TUs, + n_head=self.num_heads, + d_key=int(self.hidden_dims / self.num_heads), + d_value=int(self.hidden_dims / self.num_heads), + d_model=self.hidden_dims, + d_inner_hid=self.hidden_dims, + prepostprocess_dropout=0.1, + attention_dropout=0.1, + relu_dropout=0.1, + preprocess_cmd="n", + postprocess_cmd="da", + weight_sharing=True, + ) + + self.wrap_encoder1 = WrapEncoder( + src_vocab_size=self.char_num + 1, + max_length=self.max_length, + n_layer=self.num_decoder_TUs, + n_head=self.num_heads, + d_key=int(self.hidden_dims / self.num_heads), + d_value=int(self.hidden_dims / self.num_heads), + d_model=self.hidden_dims, + d_inner_hid=self.hidden_dims, + prepostprocess_dropout=0.1, + attention_dropout=0.1, + relu_dropout=0.1, + preprocess_cmd="n", + postprocess_cmd="da", + weight_sharing=True, + ) + + self.mul = lambda x: paddle.matmul( + x=x, y=self.wrap_encoder0.prepare_decoder.emb0.weight, transpose_y=True + ) + + def forward(self, inputs, gsrm_word_pos, gsrm_slf_attn_bias1, gsrm_slf_attn_bias2): + # ===== GSRM Visual-to-semantic embedding block ===== + b, t, c = inputs.shape + pvam_features = paddle.reshape(inputs, [-1, c]) + word_out = self.fc0(pvam_features) + word_ids = paddle.argmax(F.softmax(word_out), axis=1) + word_ids = paddle.reshape(x=word_ids, shape=[-1, t, 1]) + + # ===== GSRM Semantic reasoning block ===== + """ + This module is achieved through bi-transformers, + ngram_feature1 is the forward one, ngram_fetaure2 is the backward one + """ + pad_idx = self.char_num + + word1 = paddle.cast(word_ids, "float32") + word1 = F.pad(word1, [1, 0], value=1.0 * pad_idx, data_format="NLC") + word1 = paddle.cast(word1, "int64") + word1 = word1[:, :-1, :] + word2 = word_ids + + enc_inputs_1 = [word1, gsrm_word_pos, gsrm_slf_attn_bias1] + enc_inputs_2 = [word2, gsrm_word_pos, gsrm_slf_attn_bias2] + + gsrm_feature1 = self.wrap_encoder0(enc_inputs_1) + gsrm_feature2 = self.wrap_encoder1(enc_inputs_2) + + gsrm_feature2 = F.pad(gsrm_feature2, [0, 1], value=0.0, data_format="NLC") + gsrm_feature2 = gsrm_feature2[ + :, + 1:, + ] + gsrm_features = gsrm_feature1 + gsrm_feature2 + + gsrm_out = self.mul(gsrm_features) + + b, t, c = gsrm_out.shape + gsrm_out = paddle.reshape(gsrm_out, [-1, c]) + + return gsrm_features, word_out, gsrm_out + + +class VSFD(nn.Layer): + def __init__(self, in_channels=512, pvam_ch=512, char_num=38): + super(VSFD, self).__init__() + self.char_num = char_num + self.fc0 = paddle.nn.Linear(in_features=in_channels * 2, out_features=pvam_ch) + self.fc1 = paddle.nn.Linear(in_features=pvam_ch, out_features=self.char_num) + + def forward(self, pvam_feature, gsrm_feature): + b, t, c1 = pvam_feature.shape + b, t, c2 = gsrm_feature.shape + combine_feature_ = paddle.concat([pvam_feature, gsrm_feature], axis=2) + img_comb_feature_ = paddle.reshape(combine_feature_, shape=[-1, c1 + c2]) + img_comb_feature_map = self.fc0(img_comb_feature_) + img_comb_feature_map = F.sigmoid(img_comb_feature_map) + img_comb_feature_map = paddle.reshape(img_comb_feature_map, shape=[-1, t, c1]) + combine_feature = ( + img_comb_feature_map * pvam_feature + + (1.0 - img_comb_feature_map) * gsrm_feature + ) + img_comb_feature = paddle.reshape(combine_feature, shape=[-1, c1]) + + out = self.fc1(img_comb_feature) + return out + + +class SRNHead(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + max_text_length, + num_heads, + num_encoder_TUs, + num_decoder_TUs, + hidden_dims, + **kwargs, + ): + super(SRNHead, self).__init__() + self.char_num = out_channels + self.max_length = max_text_length + self.num_heads = num_heads + self.num_encoder_TUs = num_encoder_TUs + self.num_decoder_TUs = num_decoder_TUs + self.hidden_dims = hidden_dims + + self.pvam = PVAM( + in_channels=in_channels, + char_num=self.char_num, + max_text_length=self.max_length, + num_heads=self.num_heads, + num_encoder_tus=self.num_encoder_TUs, + hidden_dims=self.hidden_dims, + ) + + self.gsrm = GSRM( + in_channels=in_channels, + char_num=self.char_num, + max_text_length=self.max_length, + num_heads=self.num_heads, + num_encoder_tus=self.num_encoder_TUs, + num_decoder_tus=self.num_decoder_TUs, + hidden_dims=self.hidden_dims, + ) + self.vsfd = VSFD(in_channels=in_channels, char_num=self.char_num) + + self.gsrm.wrap_encoder1.prepare_decoder.emb0 = ( + self.gsrm.wrap_encoder0.prepare_decoder.emb0 + ) + + def forward(self, inputs, targets=None): + others = targets[-4:] + encoder_word_pos = others[0] + gsrm_word_pos = others[1] + gsrm_slf_attn_bias1 = others[2] + gsrm_slf_attn_bias2 = others[3] + + pvam_feature = self.pvam(inputs, encoder_word_pos, gsrm_word_pos) + + gsrm_feature, word_out, gsrm_out = self.gsrm( + pvam_feature, gsrm_word_pos, gsrm_slf_attn_bias1, gsrm_slf_attn_bias2 + ) + + final_out = self.vsfd(pvam_feature, gsrm_feature) + if not self.training: + final_out = F.softmax(final_out, axis=1) + + _, decoded_out = paddle.topk(final_out, k=1) + + predicts = OrderedDict( + [ + ("predict", final_out), + ("pvam_feature", pvam_feature), + ("decoded_out", decoded_out), + ("word_out", word_out), + ("gsrm_out", gsrm_out), + ] + ) + + return predicts diff --git a/ppocr/modeling/heads/rec_unimernet_head.py b/ppocr/modeling/heads/rec_unimernet_head.py new file mode 100644 index 0000000..fb9f4ac --- /dev/null +++ b/ppocr/modeling/heads/rec_unimernet_head.py @@ -0,0 +1,2674 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/opendatalab/UniMERNet/blob/main/unimernet/models/unimernet/configuration_unimernet_decoder.py +""" + +import copy +import math +import re +import numpy as np +import inspect +import warnings +from collections import OrderedDict +from typing import Optional, Tuple, Union, List, Dict, Any +from dataclasses import dataclass, fields, is_dataclass + +import paddle +import paddle.nn as nn +from paddle import Tensor +import paddle.nn.functional as F +from paddle.nn import CrossEntropyLoss +from paddle.nn.initializer import ( + TruncatedNormal, + Constant, + Normal, + KaimingUniform, + XavierUniform, + XavierNormal, +) + +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) +kaiming_normal_ = KaimingUniform(nonlinearity="relu") +trunc_normal_ = TruncatedNormal(std=0.02) +xavier_uniform_ = XavierUniform() +xavier_normal_ = XavierNormal() + + +class ModelOutput(OrderedDict): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def __post_init__(self): + class_fields = fields(self) + + if not len(class_fields): + raise ValueError(f"{self.__class__.__name__} has no fields.") + if not all(field.default is None for field in class_fields[1:]): + raise ValueError( + f"{self.__class__.__name__} should not have more than one required field." + ) + + first_field = getattr(self, class_fields[0].name) + other_fields_are_none = all( + getattr(self, field.name) is None for field in class_fields[1:] + ) + if other_fields_are_none: + if isinstance(first_field, dict): + iterator = first_field.items() + first_field_iterator = True + else: + try: + iterator = iter(first_field) + first_field_iterator = True + except TypeError: + first_field_iterator = False + + if first_field_iterator: + for idx, element in enumerate(iterator): + if ( + not isinstance(element, (list, tuple)) + or not len(element) == 2 + or not isinstance(element[0], str) + ): + if idx == 0: + self[class_fields[0].name] = first_field + else: + raise ValueError( + f"Cannot set key/value for {element}. It needs to be a tuple (key, value)." + ) + break + setattr(self, element[0], element[1]) + if element[1] is not None: + self[element[0]] = element[1] + elif first_field is not None: + self[class_fields[0].name] = first_field + else: + for field in class_fields: + v = getattr(self, field.name) + if v is not None: + self[field.name] = v + + def __delitem__(self, *args, **kwargs): + raise Exception( + f"You cannot use ``__delitem__`` on a {self.__class__.__name__} instance." + ) + + def setdefault(self, *args, **kwargs): + raise Exception( + f"You cannot use ``setdefault`` on a {self.__class__.__name__} instance." + ) + + def pop(self, *args, **kwargs): + raise Exception( + f"You cannot use ``pop`` on a {self.__class__.__name__} instance." + ) + + def update(self, *args, **kwargs): + raise Exception( + f"You cannot use ``update`` on a {self.__class__.__name__} instance." + ) + + def __getitem__(self, k): + if isinstance(k, str): + inner_dict = dict(self.items()) + return inner_dict[k] + else: + return self.to_tuple()[k] + + def __setattr__(self, name, value): + if name in self.keys() and value is not None: + super().__setitem__(name, value) + super().__setattr__(name, value) + + def __setitem__(self, key, value): + super().__setitem__(key, value) + super().__setattr__(key, value) + + def __reduce__(self): + if not is_dataclass(self): + return super().__reduce__() + callable, _args, *remaining = super().__reduce__() + args = tuple(getattr(self, field.name) for field in fields(self)) + return callable, args, *remaining + + def to_tuple(self): + return tuple(self[k] for k in self.keys()) + + +@dataclass +class BaseModelOutputWithPastAndCrossAttentions(ModelOutput): + last_hidden_state = None + past_key_values = None + hidden_states = None + attentions = None + cross_attentions = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +@dataclass +class Seq2SeqLMOutput(ModelOutput): + loss = None + logits = None + past_key_values = None + decoder_hidden_states = None + decoder_attentions = None + cross_attentions = None + encoder_last_hidden_state = None + encoder_hidden_states = None + encoder_attentions = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class MBartConfig(object): + + model_type = "mbart" + keys_to_ignore_at_inference = ["past_key_values"] + attribute_map = { + "num_attention_heads": "encoder_attention_heads", + "hidden_size": "d_model", + } + + def __init__( + self, + vocab_size=50265, + max_position_embeddings=1024, + encoder_layers=12, + encoder_ffn_dim=4096, + encoder_attention_heads=16, + decoder_layers=12, + decoder_ffn_dim=4096, + decoder_attention_heads=16, + encoder_layerdrop=0.0, + decoder_layerdrop=0.0, + use_cache=True, + is_encoder_decoder=True, + activation_function="gelu", + d_model=1024, + dropout=0.1, + output_hidden_states=False, + use_return_dict=True, + attention_dropout=0.0, + activation_dropout=0.0, + init_std=0.02, + classifier_dropout=0.0, + scale_embedding=False, + pad_token_id=1, + bos_token_id=0, + eos_token_id=2, + forced_eos_token_id=2, + _attn_implementation="eager", + hidden_size=1024, + use_parallel=False, + parallel_step=2, + is_export=False, + **kwargs, + ): + self.vocab_size = vocab_size + self.hidden_size = hidden_size + self.max_position_embeddings = max_position_embeddings + self.d_model = d_model + self.encoder_ffn_dim = encoder_ffn_dim + self.encoder_layers = encoder_layers + self.encoder_attention_heads = encoder_attention_heads + self.decoder_ffn_dim = decoder_ffn_dim + self.decoder_layers = decoder_layers + self.decoder_attention_heads = decoder_attention_heads + self.dropout = dropout + self.output_hidden_states = output_hidden_states + self.use_return_dict = use_return_dict + self.attention_dropout = attention_dropout + self.activation_dropout = activation_dropout + self.activation_function = activation_function + self.init_std = init_std + self.encoder_layerdrop = encoder_layerdrop + self.decoder_layerdrop = decoder_layerdrop + self.classifier_dropout = classifier_dropout + self.use_cache = use_cache + self.num_hidden_layers = encoder_layers + self.scale_embedding = ( + scale_embedding # scale factor will be sqrt(d_model) if True + ) + self.pad_token_id = pad_token_id + self.bos_token_id = bos_token_id + self.eos_token_id = eos_token_id + self.is_encoder_decoder = is_encoder_decoder + self.forced_eos_token_id = forced_eos_token_id + self._attn_implementation = _attn_implementation + self.use_parallel = use_parallel + self.parallel_step = parallel_step + self.is_export = is_export + super().__init__() + + +@dataclass +class AttentionMaskConverter: + """ + A utility class for converting attention masks used in transformer models. + + This class handles the conversion of attention masks based on whether the + attention mechanism is causal (i.e., preventing information flow from future + tokens to past tokens) and whether a sliding window approach is used. + + Attributes: + is_causal (bool): Indicates if the attention mechanism is causal. + sliding_window (Optional[int]): Specifies the size of the sliding window + for local attention, if applicable. + + Args: + is_causal (bool): Determines if the attention mask should enforce causality. + sliding_window (Optional[int], optional): The size of the sliding window + for local attention. Default is None. + """ + + is_causal: bool + sliding_window: int + + def __init__(self, is_causal: bool, sliding_window=None): + self.is_causal = is_causal + self.sliding_window = sliding_window + + if self.sliding_window is not None and self.sliding_window <= 0: + raise ValueError( + f"Make sure that when passing `sliding_window` that its value is a strictly positive integer, not `{self.sliding_window}`" + ) + + @staticmethod + def _make_causal_mask( + input_ids_shape, + dtype, + past_key_values_length=0, + sliding_window=None, + is_export=False, + ): + bsz, tgt_len = input_ids_shape + if is_export: + mask = paddle.full( + (tgt_len, tgt_len), paddle.finfo(dtype).min, dtype="float64" + ) + else: + mask = paddle.full((tgt_len, tgt_len), paddle.finfo(dtype).min) + mask_cond = paddle.arange(mask.shape[-1]) + mask = mask.masked_fill_( + mask_cond < (mask_cond + 1).reshape([mask.shape[-1], 1]), 0 + ) + return mask[None, None, :, :].expand( + [bsz, 1, tgt_len, tgt_len + past_key_values_length] + ) + + def to_4d_export( + self, + attention_mask_2d, + query_length, + dtype, + key_value_length, + is_export=False, + ): + input_shape = (attention_mask_2d.shape[0], query_length) + expanded_attn_mask = self._expand_mask( + attention_mask_2d, dtype, tgt_len=input_shape[-1] + ) + expanded_4d_mask = expanded_attn_mask + + return expanded_4d_mask + + def to_4d( + self, + attention_mask_2d, + query_length, + dtype, + key_value_length, + is_export=False, + ): + + input_shape = (attention_mask_2d.shape[0], query_length) + causal_4d_mask = None + if (input_shape[-1] > 1 or self.sliding_window is not None) and self.is_causal: + if key_value_length is None: + raise ValueError( + "This attention mask converter is causal. Make sure to pass `key_value_length` to correctly create a causal mask." + ) + + past_key_values_length = key_value_length - query_length + + causal_4d_mask = self._make_causal_mask( + input_shape, + dtype, + past_key_values_length=past_key_values_length, + sliding_window=self.sliding_window, + is_export=is_export, + ) + elif self.sliding_window is not None: + raise NotImplementedError( + "Sliding window is currently only implemented for causal masking" + ) + + expanded_attn_mask = self._expand_mask( + attention_mask_2d, dtype, tgt_len=input_shape[-1] + ) + + if causal_4d_mask is not None: + if is_export: + expanded_attn_mask = causal_4d_mask + return expanded_attn_mask + else: + expanded_attn_mask = causal_4d_mask.masked_fill_( + expanded_attn_mask.cast(paddle.bool), paddle.finfo(dtype).min + ) + + expanded_4d_mask = expanded_attn_mask + + return expanded_4d_mask + + def _expand_mask(self, mask, dtype, tgt_len=None): + bsz, src_len = mask.shape + tgt_len = tgt_len if tgt_len is not None else src_len + expanded_mask = ( + mask[:, None, None, :].expand([bsz, 1, tgt_len, src_len]).cast(dtype) + ) + inverted_mask = 1.0 - expanded_mask + return inverted_mask.masked_fill_( + inverted_mask.cast(paddle.bool), paddle.finfo(dtype).min + ) + + +def _prepare_4d_attention_mask(mask, dtype, tgt_len=None): + return AttentionMaskConverter._expand_mask(mask=mask, dtype=dtype, tgt_len=tgt_len) + + +def _prepare_4d_causal_attention_mask_export( + attention_mask, + input_shape, + inputs_embeds, + past_key_values_length, + sliding_window=None, + is_export=False, +): + + attn_mask_converter = AttentionMaskConverter( + is_causal=True, sliding_window=sliding_window + ) + key_value_length = input_shape[-1] + past_key_values_length + + shape = attention_mask.shape + len_shape = len(shape) + + attention_mask = attn_mask_converter.to_4d_export( + attention_mask, + input_shape[-1], + key_value_length=key_value_length, + dtype=inputs_embeds.dtype, + is_export=is_export, + ) + return attention_mask + + +def _prepare_4d_causal_attention_mask( + attention_mask, + input_shape, + inputs_embeds, + past_key_values_length, + sliding_window=None, + is_export=False, +): + + attn_mask_converter = AttentionMaskConverter( + is_causal=True, sliding_window=sliding_window + ) + key_value_length = input_shape[-1] + past_key_values_length + + shape = attention_mask.shape + len_shape = len(shape) + if (attention_mask is not None) and (len_shape == 2): + attention_mask = attn_mask_converter.to_4d( + attention_mask, + input_shape[-1], + key_value_length=key_value_length, + dtype=inputs_embeds.dtype, + is_export=is_export, + ) + + return attention_mask + elif attention_mask is not None and len(attention_mask.shape) == 4: + expected_shape = (input_shape[0], 1, input_shape[1], key_value_length) + if tuple(attention_mask.shape) != expected_shape: + raise ValueError( + f"Incorrect 4D attention_mask shape: {tuple(attention_mask.shape)}; expected: {expected_shape}." + ) + else: + inverted_mask = 1.0 - attention_mask + attention_mask = inverted_mask.masked_fill_( + inverted_mask.to(paddle.bool), paddle.finfo(inputs_embeds.dtype).min + ) + else: + attention_mask = attn_mask_converter.to_causal_4d( + input_shape[0], + input_shape[-1], + key_value_length, + dtype=inputs_embeds.dtype, + ) + + return attention_mask + + +class MBartLearnedPositionalEmbedding(nn.Embedding): + """ + This module learns positional embeddings up to a fixed maximum size. + """ + + def __init__(self, num_embeddings, embedding_dim): + self.offset = 2 + super().__init__(num_embeddings + self.offset, embedding_dim) + + def forward(self, input_ids, past_key_values_length=0): + """`input_ids' shape is expected to be [bsz x seqlen].""" + bsz, seq_len = input_ids.shape[:2] + positions = paddle.arange( + past_key_values_length, past_key_values_length + seq_len, dtype=paddle.int64 + ).expand([bsz, -1]) + return nn.Embedding.forward(self, positions + self.offset) + + +class MBartPreTrainedModel(nn.Layer): + base_model_prefix = "model" + supports_gradient_checkpointing = True + _no_split_modules = ["MBartDecoderLayer", "MBartAttention"] + _supports_flash_attn_2 = True + + def __init__(self, config): + super().__init__() + self.config = config + + def _initialize_weights(self, module): + """ + Initialize the weights if they are not already initialized. + """ + if getattr(module, "_is_hf_initialized", False): + return + self._init_weights(module) + + def post_init(self): + self.apply(self._initialize_weights) + + def _init_weights(self, module): + std = self.config.init_std + normal_ = Normal(mean=0.0, std=std) + if isinstance(module, nn.Linear): + normal_(module.weight) + if module.bias is not None: + zeros_(module.bias) + elif isinstance(module, nn.Embedding): + normal_(module.weight) + if module._padding_idx is not None: + zeros_(module.weight[module._padding_idx]) + + @property + def dummy_inputs(self): + pad_token = self.config.pad_token_id + input_ids = paddle.tensor([[0, 6, 10, 4, 2], [0, 8, 12, 2, pad_token]]) + dummy_inputs = { + "attention_mask": input_ids.ne(pad_token), + "input_ids": input_ids, + } + return dummy_inputs + + +class MBartAttention(nn.Layer): + """Multi-headed attention from 'Attention Is All You Need' paper""" + + def __init__( + self, + embed_dim, + num_heads, + dropout: float = 0.0, + is_decoder: bool = False, + bias: bool = True, + is_causal: bool = False, + config=None, + ): + super().__init__() + self.embed_dim = embed_dim + self.num_heads = num_heads + self.dropout = dropout + self.head_dim = embed_dim // num_heads + self.config = config + + if (self.head_dim * num_heads) != self.embed_dim: + raise ValueError( + f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim}" + f" and `num_heads`: {num_heads})." + ) + self.scaling = self.head_dim**-0.5 + self.is_decoder = is_decoder + self.is_causal = is_causal + + self.k_proj = nn.Linear(embed_dim, embed_dim, bias_attr=bias) + self.v_proj = nn.Linear(embed_dim, embed_dim, bias_attr=bias) + self.q_proj = nn.Linear(embed_dim, embed_dim, bias_attr=bias) + self.out_proj = nn.Linear(embed_dim, embed_dim, bias_attr=bias) + + def _shape(self, tensor, seq_len, bsz): + return tensor.reshape([bsz, seq_len, self.num_heads, self.head_dim]).transpose( + [0, 2, 1, 3] + ) + + def forward( + self, + hidden_states, + key_value_states=None, + past_key_value=None, + attention_mask=None, + layer_head_mask=None, + output_attentions=False, + ): + + is_cross_attention = key_value_states is not None + + bsz, tgt_len, _ = paddle.shape(hidden_states) + query_states = self.q_proj(hidden_states) * self.scaling + if ( + is_cross_attention + and past_key_value is not None + and past_key_value[0].shape[2] == key_value_states.shape[1] + ): + key_states = past_key_value[0] + value_states = past_key_value[1] + elif is_cross_attention: + key_states = self._shape(self.k_proj(key_value_states), -1, bsz) + value_states = self._shape(self.v_proj(key_value_states), -1, bsz) + elif past_key_value is not None: + key_states = self._shape(self.k_proj(hidden_states), -1, bsz) + value_states = self._shape(self.v_proj(hidden_states), -1, bsz) + key_states = paddle.concat([past_key_value[0], key_states], axis=2) + value_states = paddle.concat([past_key_value[1], value_states], axis=2) + else: + key_states = self._shape(self.k_proj(hidden_states), -1, bsz) + value_states = self._shape(self.v_proj(hidden_states), -1, bsz) + + if self.is_decoder: + past_key_value = (key_states, value_states) + + proj_shape = (bsz * self.num_heads, -1, self.head_dim) + query_states = self._shape(query_states, tgt_len, bsz).reshape(proj_shape) + key_states = key_states.reshape(proj_shape) + value_states = value_states.reshape(proj_shape) + + src_len = key_states.shape[1] + attn_weights = paddle.bmm(query_states, key_states.transpose([0, 2, 1])) + + if attention_mask is not None: + attn_weights = ( + attn_weights.reshape([bsz, self.num_heads, tgt_len, src_len]) + + attention_mask + ) + attn_weights = attn_weights.reshape( + [bsz * self.num_heads, tgt_len, src_len] + ) + + attn_weights = nn.functional.softmax(attn_weights, axis=-1) + if layer_head_mask is not None: + if tuple(layer_head_mask.shape) != (self.num_heads,): + raise ValueError( + f"Head mask for a single layer should be of shape {(self.num_heads,)}, but is" + f" {layer_head_mask.shape}" + ) + attn_weights = layer_head_mask.reshape( + [1, -1, 1, 1] + ) * attn_weights.reshape([bsz, self.num_heads, tgt_len, src_len]) + attn_weights = attn_weights.reshape( + [bsz * self.num_heads, tgt_len, src_len] + ) + + if output_attentions: + attn_weights_reshaped = attn_weights.reshape( + [bsz, self.num_heads, tgt_len, src_len] + ) + attn_weights = attn_weights_reshaped.reshape( + [bsz * self.num_heads, tgt_len, src_len] + ) + else: + attn_weights_reshaped = None + attn_probs = nn.functional.dropout( + attn_weights, p=self.dropout, training=self.training + ) + attn_output = paddle.bmm(attn_probs, value_states) + + attn_output = attn_output.reshape([bsz, self.num_heads, tgt_len, self.head_dim]) + attn_output = attn_output.transpose([0, 2, 1, 3]) + + attn_output = attn_output.reshape([bsz, tgt_len, self.embed_dim]) + attn_output = self.out_proj(attn_output) + return attn_output, attn_weights_reshaped, past_key_value + + +MBART_ATTENTION_CLASSES = { + "eager": MBartAttention, +} + + +class MBartDecoderLayer(nn.Layer): + def __init__(self, config): + super().__init__() + self.embed_dim = config.d_model + self.self_attn = MBART_ATTENTION_CLASSES[config._attn_implementation]( + embed_dim=self.embed_dim, + num_heads=config.decoder_attention_heads, + dropout=config.attention_dropout, + is_decoder=True, + is_causal=True, + config=config, + ) + self.is_export = config.is_export + self.dropout = config.dropout + self.activation_fn = F.gelu + self.activation_dropout = config.activation_dropout + + self.self_attn_layer_norm = nn.LayerNorm(self.embed_dim) + self.encoder_attn = MBART_ATTENTION_CLASSES[config._attn_implementation]( + self.embed_dim, + config.decoder_attention_heads, + dropout=config.attention_dropout, + is_decoder=True, + config=config, + ) + self.encoder_attn_layer_norm = nn.LayerNorm(self.embed_dim) + self.fc1 = nn.Linear(self.embed_dim, config.decoder_ffn_dim) + self.fc2 = nn.Linear(config.decoder_ffn_dim, self.embed_dim) + self.final_layer_norm = nn.LayerNorm(self.embed_dim) + + def forward( + self, + hidden_states, + attention_mask=None, + encoder_hidden_states=None, + encoder_attention_mask=None, + layer_head_mask=None, + cross_attn_layer_head_mask=None, + past_key_value: Optional[Tuple[paddle.Tensor]] = None, + output_attentions: Optional[bool] = False, + use_cache: Optional[bool] = True, + ) -> paddle.Tensor: + + residual = hidden_states + hidden_states = self.self_attn_layer_norm(hidden_states) + self_attn_past_key_value = ( + past_key_value[:2] if past_key_value is not None else None + ) + + hidden_states, self_attn_weights, present_key_value = self.self_attn( + hidden_states=hidden_states, + past_key_value=self_attn_past_key_value, + attention_mask=attention_mask, + layer_head_mask=layer_head_mask, + output_attentions=output_attentions, + ) + hidden_states = nn.functional.dropout( + hidden_states, p=self.dropout, training=self.training + ) + hidden_states = residual + hidden_states + + cross_attn_present_key_value = None + cross_attn_weights = None + if encoder_hidden_states is not None: + residual = hidden_states + hidden_states = self.encoder_attn_layer_norm(hidden_states) + cross_attn_past_key_value = ( + past_key_value[-2:] if past_key_value is not None else None + ) + ( + hidden_states, + cross_attn_weights, + cross_attn_present_key_value, + ) = self.encoder_attn( + hidden_states=hidden_states, + key_value_states=encoder_hidden_states, + attention_mask=encoder_attention_mask, + layer_head_mask=cross_attn_layer_head_mask, + past_key_value=cross_attn_past_key_value, + output_attentions=output_attentions, + ) + hidden_states = nn.functional.dropout( + hidden_states, p=self.dropout, training=self.training + ) + hidden_states = residual + hidden_states + + present_key_value = present_key_value + cross_attn_present_key_value + + residual = hidden_states + hidden_states = self.final_layer_norm(hidden_states) + hidden_states = self.activation_fn(self.fc1(hidden_states)) + hidden_states = nn.functional.dropout( + hidden_states, p=self.activation_dropout, training=self.training + ) + hidden_states = self.fc2(hidden_states) + hidden_states = nn.functional.dropout( + hidden_states, p=self.dropout, training=self.training + ) + hidden_states = residual + hidden_states + outputs = (hidden_states,) + + if output_attentions: + outputs += (self_attn_weights, cross_attn_weights) + + if self.is_export: + outputs += (present_key_value,) + else: + if use_cache: + outputs += (present_key_value,) + return outputs + + +class MBartForCausalLM(MBartPreTrainedModel): + _tied_weights_keys = ["lm_head.weight"] + + def __init__(self, config): + config = copy.deepcopy(config) + config.is_decoder = True + config.is_encoder_decoder = False + super().__init__(config) + self.model = MBartDecoderWrapper(config) + self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias_attr=False) + + self.post_init() + + def get_input_embeddings(self): + return self.model.decoder.embed_tokens + + def set_input_embeddings(self, value): + self.model.decoder.embed_tokens = value + + def get_output_embeddings(self): + return self.lm_head + + def set_output_embeddings(self, new_embeddings): + self.lm_head = new_embeddings + + def set_decoder(self, decoder): + self.model.decoder = decoder + + def get_decoder(self): + return self.model.decoder + + def forward( + self, + input_ids=None, + attention_mask=None, + encoder_hidden_states=None, + encoder_attention_mask=None, + head_mask=None, + cross_attn_head_mask=None, + past_key_values=None, + inputs_embeds=None, + labels=None, + use_cache=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + ): + + output_attentions = ( + output_attentions + if output_attentions is not None + else self.config.output_attentions + ) + output_hidden_states = ( + output_hidden_states + if output_hidden_states is not None + else self.config.output_hidden_states + ) + return_dict = ( + return_dict if return_dict is not None else self.config.use_return_dict + ) + + outputs = self.model.decoder( + input_ids=input_ids, + attention_mask=attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=encoder_attention_mask, + head_mask=head_mask, + cross_attn_head_mask=cross_attn_head_mask, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + + logits = self.lm_head(outputs[0]) + + loss = None + if labels is not None: + labels = labels + loss_fct = CrossEntropyLoss() + loss = loss_fct( + logits.reshape([-1, self.config.vocab_size]), labels.reshape([-1]) + ) + + if not return_dict: + output = (logits,) + outputs[1:] + return (loss,) + output if loss is not None else output + + return CausalLMOutputWithCrossAttentions( + loss=loss, + logits=logits, + past_key_values=outputs.past_key_values, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + cross_attentions=outputs.cross_attentions, + ) + + def prepare_inputs_for_generation( + self, + input_ids, + past_key_values=None, + attention_mask=None, + use_cache=None, + **kwargs, + ): + if attention_mask is None: + attention_mask = input_ids.new_ones(input_ids.shape) + + if past_key_values: + past_length = past_key_values[0][0].shape[2] + + if input_ids.shape[1] > past_length: + remove_prefix_length = past_length + else: + remove_prefix_length = input_ids.shape[1] - 1 + + input_ids = input_ids[:, remove_prefix_length:] + return { + "input_ids": input_ids, + "attention_mask": attention_mask, + "past_key_values": past_key_values, + "use_cache": use_cache, + } + + @staticmethod + def _reorder_cache(past_key_values, beam_idx): + reordered_past = () + for layer_past in past_key_values: + reordered_past += ( + tuple( + past_state.index_select(0, beam_idx) for past_state in layer_past + ), + ) + return reordered_past + + +class myLayerNorm(nn.LayerNorm): + """ + Custom implementation of Layer Normalization, with additional options. + + This class extends the standard LayerNorm to include optional features, + such as drop block regularization, which might be used for improving + model generalization. + + Args: + num_channels (int): The number of features or channels in the input. + eps (float, optional): A small value added to the denominator for numerical stability. Default is 1e-5. + affine (bool, optional): If True, this module has learnable affine parameters (gamma and beta). Default is True. + drop_block (optional): Additional regularization technique that might be applied. Default is None. + + """ + + def __init__( + self, + num_channels, + eps=1e-5, + affine=True, + drop_block=None, + ): + super(nn.LayerNorm, self).__init__() + self._epsilon = eps + self.num_channels = num_channels + if affine: + self.weight = paddle.create_parameter([num_channels], dtype="float32") + self.bias = paddle.create_parameter([num_channels], dtype="float32") + ones_(self.weight) + zeros_(self.bias) + + def forward(self, x): + x = F.layer_norm( + x, + self.num_channels, + weight=self.weight, + bias=self.bias, + epsilon=self._epsilon, + ) + return x + + +class MBartDecoder(MBartPreTrainedModel): + """ + Transformer decoder consisting of *config.decoder_layers* layers. Each layer is a [`MBartDecoderLayer`] + + Args: + config + embed_tokens (nn.Embedding): output embedding + """ + + def __init__(self, config, embed_tokens=None): + super().__init__(config) + self.dropout = config.dropout + self.layerdrop = config.decoder_layerdrop + self.padding_idx = config.pad_token_id + self.max_target_positions = config.max_position_embeddings + self.embed_scale = math.sqrt(config.d_model) if config.scale_embedding else 1.0 + + self.embed_tokens = nn.Embedding( + config.vocab_size, config.d_model, self.padding_idx + ) + + if embed_tokens is not None: + self.embed_tokens.weight = embed_tokens.weight + + self.embed_positions = MBartLearnedPositionalEmbedding( + config.max_position_embeddings, + config.d_model, + ) + self.layers = nn.LayerList( + [MBartDecoderLayer(config) for _ in range(config.decoder_layers)] + ) + self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2" + self.layernorm_embedding = myLayerNorm(config.d_model, affine=True) + self.layer_norm = nn.LayerNorm(config.d_model) + + self.gradient_checkpointing = False + # Initialize weights and apply final processing + self.post_init() + self.is_export = config.is_export + + def get_input_embeddings(self): + return self.embed_tokens + + def set_input_embeddings(self, value): + self.embed_tokens = value + + def forward( + self, + input_ids=None, + attention_mask=None, + encoder_hidden_states=None, + encoder_attention_mask=None, + head_mask=None, + cross_attn_head_mask=None, + past_key_values=None, + inputs_embeds=None, + use_cache=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + ): + + output_attentions = ( + output_attentions + if output_attentions is not None + else self.config.output_attentions + ) + output_hidden_states = ( + output_hidden_states + if output_hidden_states is not None + else self.config.output_hidden_states + ) + use_cache = use_cache if use_cache is not None else self.config.use_cache + return_dict = ( + return_dict if return_dict is not None else self.config.use_return_dict + ) + + if input_ids is not None and inputs_embeds is not None: + raise ValueError( + "You cannot specify both decoder_input_ids and decoder_inputs_embeds at the same time" + ) + elif input_ids is not None: + input = input_ids + input_shape = input.shape + input_ids = input_ids.reshape([-1, input_shape[-1]]) + elif inputs_embeds is not None: + input_shape = inputs_embeds.shape[:-1] + input = inputs_embeds[:, :, -1] + else: + raise ValueError( + "You have to specify either decoder_input_ids or decoder_inputs_embeds" + ) + + past_key_values_length = ( + past_key_values[0][0].shape[2] if past_key_values is not None else 0 + ) + + if inputs_embeds is None: + inputs_embeds = self.embed_tokens(input_ids) * self.embed_scale + + if self._use_flash_attention_2: + attention_mask = ( + attention_mask + if (attention_mask is not None and 0 in attention_mask) + else None + ) + else: + attention_mask = _prepare_4d_causal_attention_mask( + attention_mask, + input_shape, + inputs_embeds, + past_key_values_length, + is_export=self.is_export, + ) + + if encoder_hidden_states is not None and encoder_attention_mask is not None: + if self._use_flash_attention_2: + encoder_attention_mask = ( + encoder_attention_mask if 0 in encoder_attention_mask else None + ) + else: + encoder_attention_mask = _prepare_4d_attention_mask( + encoder_attention_mask, inputs_embeds.dtype, tgt_len=input_shape[-1] + ) + + # embed positions + positions = self.embed_positions(input, past_key_values_length) + + hidden_states = inputs_embeds + positions + hidden_states = self.layernorm_embedding(hidden_states) + + hidden_states = nn.functional.dropout( + hidden_states, p=self.dropout, training=self.training + ) + + if self.gradient_checkpointing and self.training: + if use_cache: + print( + "`use_cache=True` is incompatible with gradient checkpointing`. Setting `use_cache=False`..." + ) + use_cache = False + + all_hidden_states = () if output_hidden_states else None + all_self_attns = () if output_attentions else None + all_cross_attentions = ( + () if (output_attentions and encoder_hidden_states is not None) else None + ) + next_decoder_cache = () if use_cache else None + + for attn_mask, mask_name in zip( + [head_mask, cross_attn_head_mask], ["head_mask", "cross_attn_head_mask"] + ): + if attn_mask is not None: + if attn_mask.shape[0] != len(self.layers): + raise ValueError( + f"The `{mask_name}` should be specified for {len(self.layers)} layers, but it is for" + f" {attn_mask.shape[0]}." + ) + + for idx, decoder_layer in enumerate(self.layers): + if output_hidden_states: + all_hidden_states += (hidden_states,) + if self.training: + dropout_probability = paddle.rand([]) + if dropout_probability < self.layerdrop: + continue + + past_key_value = ( + past_key_values[idx] if past_key_values is not None else None + ) + + if self.gradient_checkpointing and self.training: + layer_outputs = self._gradient_checkpointing_func( + decoder_layer.__call__, + hidden_states, + attention_mask, + encoder_hidden_states, + encoder_attention_mask, + head_mask[idx] if head_mask is not None else None, + ( + cross_attn_head_mask[idx] + if cross_attn_head_mask is not None + else None + ), + None, + output_attentions, + use_cache, + ) + else: + layer_outputs = decoder_layer( + hidden_states, + attention_mask=attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=encoder_attention_mask, + layer_head_mask=(head_mask[idx] if head_mask is not None else None), + cross_attn_layer_head_mask=( + cross_attn_head_mask[idx] + if cross_attn_head_mask is not None + else None + ), + past_key_value=past_key_value, + output_attentions=output_attentions, + use_cache=use_cache, + ) + hidden_states = layer_outputs[0] + + if use_cache: + next_decoder_cache += (layer_outputs[3 if output_attentions else 1],) + + if output_attentions: + all_self_attns += (layer_outputs[1],) + + if encoder_hidden_states is not None: + all_cross_attentions += (layer_outputs[2],) + + hidden_states = self.layer_norm(hidden_states) + + if output_hidden_states: + all_hidden_states += (hidden_states,) + + next_cache = next_decoder_cache if use_cache else None + if not return_dict: + return tuple( + v + for v in [ + hidden_states, + next_cache, + all_hidden_states, + all_self_attns, + all_cross_attentions, + ] + if v is not None + ) + return BaseModelOutputWithPastAndCrossAttentions( + last_hidden_state=hidden_states, + past_key_values=next_cache, + hidden_states=all_hidden_states, + attentions=all_self_attns, + cross_attentions=all_cross_attentions, + ) + + +class MBartDecoderWrapper(MBartPreTrainedModel): + """ + This wrapper class is a helper class to correctly load pretrained checkpoints when the causal language model is + used in combination with the [`EncoderDecoderModel`] framework. + """ + + def __init__(self, config): + super().__init__(config) + self.decoder = MBartDecoder(config) + + def forward(self, *args, **kwargs): + return self.decoder(*args, **kwargs) + + +def _in_projection( + q: paddle.Tensor, + k: paddle.Tensor, + v: paddle.Tensor, + w_q: paddle.Tensor, + w_k: paddle.Tensor, + w_v: paddle.Tensor, + b_q: Optional[paddle.Tensor] = None, + b_k: Optional[paddle.Tensor] = None, + b_v: Optional[paddle.Tensor] = None, +) -> Tuple[paddle.Tensor, paddle.Tensor, paddle.Tensor]: + + Eq, Ek, Ev = q.shape[-1], k.shape[-1], v.shape[-1] + assert w_q.shape == ( + Eq, + Eq, + ), f"expecting query weights shape of {(Eq, Eq)}, but got {w_q.shape}" + assert w_k.shape == ( + Eq, + Ek, + ), f"expecting key weights shape of {(Eq, Ek)}, but got {w_k.shape}" + assert w_v.shape == ( + Eq, + Ev, + ), f"expecting value weights shape of {(Eq, Ev)}, but got {w_v.shape}" + assert b_q is None or b_q.shape == ( + Eq, + ), f"expecting query bias shape of {(Eq,)}, but got {b_q.shape}" + assert b_k is None or b_k.shape == ( + Eq, + ), f"expecting key bias shape of {(Eq,)}, but got {b_k.shape}" + assert b_v is None or b_v.shape == ( + Eq, + ), f"expecting value bias shape of {(Eq,)}, but got {b_v.shape}" + return linear(q, w_q.T, b_q), linear(k, w_k.T, b_k), linear(v, w_v.T, b_v) + + +def _scaled_dot_product_attention( + q: paddle.Tensor, + k: paddle.Tensor, + v: paddle.Tensor, + attn_mask: Optional[paddle.Tensor] = None, + dropout_p: float = 0.0, +) -> Tuple[paddle.Tensor, paddle.Tensor]: + + B, Nt, E = q.shape + q = q / math.sqrt(E) + attn = paddle.bmm(q, k.transpose([0, 2, 1])) + if attn_mask is not None: + attn += attn_mask + attn = F.softmax(attn, axis=-1) + if dropout_p > 0.0: + attn = F.dropout(attn, p=dropout_p) + output = paddle.bmm(attn, v) + return output, attn + + +def linear(x, w, b, is_transpose): + + if b is not None: + return paddle.matmul(x, w, transpose_y=is_transpose) + b + else: + return paddle.matmul(x, w, transpose_y=is_transpose) + + +def _in_projection_packed( + q: Tensor, + k: Tensor, + v: Tensor, + w: Tensor, + b: Optional[Tensor] = None, + is_export=False, +) -> List[Tensor]: + + E = paddle.shape(q)[-1] + if k is v: + if q is k: + proj = linear(q, w, b, is_transpose=True) + if is_export: + B, D, L = paddle.shape(proj) + proj = proj.reshape([B, D, 3, E]) + proj = ( + proj.unsqueeze(0) + .transpose([3, 1, 2, 0, 4]) + .squeeze(-2) + .contiguous() + ) + else: + proj = ( + proj.unflatten(-1, (3, E)) + .unsqueeze(0) + .transpose([3, 1, 2, 0, 4]) + .squeeze(-2) + .contiguous() + ) + return proj[0], proj[1], proj[2] + else: + w_q, w_k, w_v = w.chunk(3) + if b is None: + b_q = b_k = b_v = None + else: + b_q, b_k, b_v = b.chunk(3) + return linear(q, w_q, b_q), linear(k, w_k, b_k), linear(v, w_v, b_v) + + +def multi_head_attention_forward( + query: paddle.Tensor, + key: paddle.Tensor, + value: paddle.Tensor, + embed_dim_to_check: int, + num_heads: int, + in_proj_weight: paddle.Tensor, + in_proj_bias: Optional[paddle.Tensor], + bias_k: Optional[paddle.Tensor], + bias_v: Optional[paddle.Tensor], + add_zero_attn: bool, + dropout_p: float, + out_proj_weight: paddle.Tensor, + out_proj_bias: Optional[paddle.Tensor], + training: bool = True, + key_padding_mask: Optional[paddle.Tensor] = None, + need_weights: bool = True, + attn_mask: Optional[paddle.Tensor] = None, + use_separate_proj_weight: bool = False, + q_proj_weight: Optional[paddle.Tensor] = None, + k_proj_weight: Optional[paddle.Tensor] = None, + v_proj_weight: Optional[paddle.Tensor] = None, + static_k: Optional[paddle.Tensor] = None, + static_v: Optional[paddle.Tensor] = None, + is_export=False, +): + + tgt_len, bsz, embed_dim = query.shape + src_len, _, _ = key.shape + + if isinstance(embed_dim, paddle.Tensor): + head_dim = embed_dim.div(num_heads, rounding_mode="trunc") + else: + head_dim = embed_dim // num_heads + q, k, v = _in_projection_packed( + query, key, value, in_proj_weight, in_proj_bias, is_export + ) + + if key_padding_mask is not None and key_padding_mask.dtype == paddle.uint8: + warnings.warn( + "Byte tensor for key_padding_mask in nn.MultiheadAttention is deprecated. Use bool tensor instead." + ) + key_padding_mask = key_padding_mask.to(paddle.bool) + + if bias_k is not None and bias_v is not None: # False + assert static_k is None, "bias cannot be added to static key." + assert static_v is None, "bias cannot be added to static value." + k = paddle.concat([k, bias_k.repeat(1, bsz, 1)]) + v = paddle.concat([v, bias_v.repeat(1, bsz, 1)]) + else: + assert bias_k is None + assert bias_v is None + + q = q.reshape([tgt_len, bsz * num_heads, head_dim]).transpose([1, 0, 2]) + if static_k is None: # True + k = k.reshape([k.shape[0], bsz * num_heads, head_dim]).transpose([1, 0, 2]) + else: + assert ( + static_k.shape[0] == bsz * num_heads + ), f"expecting static_k.size(0) of {bsz * num_heads}, but got {static_k.shape[0]}" + assert ( + static_k.shape[2] == head_dim + ), f"expecting static_k.size(2) of {head_dim}, but got {static_k.shape[2]}" + k = static_k + if static_v is None: # True + v = v.reshape([v.shape[0], bsz * num_heads, head_dim]).transpose([1, 0, 2]) + else: + assert ( + static_v.shape[0] == bsz * num_heads + ), f"expecting static_v.size(0) of {bsz * num_heads}, but got {static_v.shape[0]}" + assert ( + static_v.shape[2] == head_dim + ), f"expecting static_v.size(2) of {head_dim}, but got {static_v.shape[2]}" + v = static_v + + src_len = k.shape[1] + + if not training: + dropout_p = 0.0 + + attn_output, attn_output_weights = _scaled_dot_product_attention( + q, k, v, attn_mask, dropout_p + ) + + attn_output = attn_output.transpose([1, 0, 2]).reshape([tgt_len, bsz, embed_dim]) + attn_output = linear( + attn_output, out_proj_weight, out_proj_bias, is_transpose=False + ) + + if need_weights: + attn_output_weights = attn_output_weights.reshape( + [bsz, num_heads, tgt_len, src_len] + ) + return attn_output, attn_output_weights.sum(axis=1) / num_heads + else: + return attn_output, None + + +class MyMultiheadAttention(nn.Layer): + """ + Custom implementation of a multi-head attention layer. + + Attributes: + __constants__ (list): List of constant attributes. + bias_k (Optional[paddle.Tensor]): Optional tensor for key bias. + bias_v (Optional[paddle.Tensor]): Optional tensor for value bias. + + Args: + embed_dim (int): Total dimension of the model. This is the size of the input feature vectors. + num_heads (int): Number of parallel attention heads. The input dimension must be divisible by the number of heads. + dropout (float, optional): Dropout probability on the attention weights. Default is 0.0. + bias (bool, optional): If True, adds a learnable bias to the output. Default is True. + add_bias_kv (bool, optional): If True, adds bias to the key and value sequences. Default is False. + add_zero_attn (bool, optional): If True, adds a zero attention head. Default is False. + kdim (int, optional): Total number of features for keys. If None, defaults to embed_dim. + vdim (int, optional): Total number of features for values. If None, defaults to embed_dim. + batch_first (bool, optional): If True, the input and output tensors are provided as (batch, seq, feature). Default is False. + device (optional): The device on which the layer's parameters should be initialized. Default is None. + dtype (optional): The data type for the parameters. Default is None. + is_export (bool, optional): If True, the layer is set up for export, potentially changing behavior for compatibility. Default is False. + """ + + __constants__ = ["batch_first"] + bias_k: Optional[paddle.Tensor] + bias_v: Optional[paddle.Tensor] + + def __init__( + self, + embed_dim, + num_heads, + dropout=0.0, + bias=True, + add_bias_kv=False, + add_zero_attn=False, + kdim=None, + vdim=None, + batch_first=False, + device=None, + dtype=None, + is_export=False, + ) -> None: + super(MyMultiheadAttention, self).__init__() + self.embed_dim = embed_dim + self.kdim = kdim if kdim is not None else embed_dim + self.vdim = vdim if vdim is not None else embed_dim + self._qkv_same_embed_dim = self.kdim == embed_dim and self.vdim == embed_dim + + self.num_heads = num_heads + self.dropout = dropout + self.batch_first = batch_first + self.head_dim = embed_dim // num_heads + self.is_export = is_export + assert ( + self.head_dim * num_heads == self.embed_dim + ), "embed_dim must be divisible by num_heads" + + if self._qkv_same_embed_dim is False: + pass + else: + if dtype is None: + dtype = paddle.float32 + self.in_proj_weight = paddle.create_parameter( + (3 * embed_dim, embed_dim), dtype + ) + self.q_proj_weight = None + self.k_proj_weight = None + self.v_proj_weight = None + + if bias: + self.in_proj_bias = paddle.create_parameter((3 * embed_dim,), dtype) + zeros_(self.in_proj_bias) + else: + self.in_proj_bias = None + self.out_proj = nn.Linear(embed_dim, embed_dim, bias_attr=bias) + + if add_bias_kv: + pass + else: + self.bias_k = self.bias_v = None + + self.add_zero_attn = add_zero_attn + + self._reset_parameters() + + def _reset_parameters(self): + + if self._qkv_same_embed_dim: + xavier_uniform_(self.in_proj_weight) + else: + xavier_uniform_(self.q_proj_weight) + xavier_uniform_(self.k_proj_weight) + xavier_uniform_(self.v_proj_weight) + + if self.in_proj_bias is not None: + zeros_(self.in_proj_bias) + zeros_(self.out_proj.bias) + if self.bias_k is not None: + xavier_normal_(self.bias_k) + if self.bias_v is not None: + xavier_normal_(self.bias_v) + + def forward( + self, + query: paddle.Tensor, + key: paddle.Tensor, + value: paddle.Tensor, + key_padding_mask: Optional[paddle.Tensor] = None, + need_weights: bool = True, + attn_mask: Optional[paddle.Tensor] = None, + ) -> Tuple[paddle.Tensor, Optional[paddle.Tensor]]: + + attn_output, attn_output_weights = multi_head_attention_forward( + query, + key, + value, + self.embed_dim, + self.num_heads, + self.in_proj_weight, + self.in_proj_bias, + self.bias_k, + self.bias_v, + self.add_zero_attn, + self.dropout, + self.out_proj.weight, + self.out_proj.bias, + training=self.training, + key_padding_mask=key_padding_mask, + need_weights=need_weights, + attn_mask=attn_mask, + is_export=self.is_export, + ) + + return attn_output, attn_output_weights + + +class LogitsProcessorList(list): + """ + A list of logits processors that can be applied sequentially. + + Methods: + __call__(input_ids, scores, **kwargs): Apply all processors to the given inputs. + """ + + def __call__(self, input_ids, scores, **kwargs): + for processor in self: + function_args = inspect.signature(processor.__call__).parameters + if len(function_args) > 2: + if not all(arg in kwargs for arg in list(function_args.keys())[2:]): + raise ValueError( + f"Make sure that all the required parameters: {list(function_args.keys())} for " + f"{processor.__class__} are passed to the logits processor." + ) + scores = processor(input_ids, scores, **kwargs) + else: + scores = processor(input_ids, scores) + return scores + + +class ForcedEOSTokenLogitsProcessor(object): + """ + A processor that forces the generation of an end-of-sequence (EOS) token + at a specified position in the sequence. + + This is typically used in language generation tasks to ensure that the + generated sequence ends properly when it reaches a certain length. + + Args: + max_length (int): The maximum length of the sequence. Forces EOS when this length is reached. + eos_token_id (Union[int, List[int]]): The ID(s) of the EOS token(s) to be forced in the sequence. + """ + + def __init__(self, max_length: int, eos_token_id: Union[int, List[int]]): + self.max_length = max_length + if isinstance(eos_token_id, int): + eos_token_id = [eos_token_id] + self.eos_token_id = eos_token_id + + def __call__(self, input_ids, scores): + cur_len = input_ids.shape[-1] + scores_processed = scores + if cur_len == self.max_length - 1: + scores_processed = paddle.full_like(scores, -math.inf) + scores_processed[:, self.eos_token_id] = 0 + return scores_processed + + +@dataclass +class CausalLMOutputWithCrossAttentions(ModelOutput): + + loss = None + logits = None + past_key_values = None + hidden_states = None + attentions = None + cross_attentions = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +@dataclass +class CausalLMOutputWithCrossAttentionsAndCounting(ModelOutput): + """ + Base class for causal language model (or autoregressive) outputs. + """ + + logits = None + counting = None + past_key_values = None + hidden_states = None + attentions = None + cross_attentions = None + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + +class CustomMBartDecoder(MBartDecoder): + """ + A custom MBartDecoder that includes additional processing layers. + + This class extends the MBartDecoder by adding a customizable neural network + component called `counting_context_weight`, which applies a series of linear + transformations followed by ReLU activations. This can be used to modify or + enhance the decoder's behavior for specific tasks. + + Args: + config: The configuration object containing model parameters. + """ + + def __init__(self, config): + super().__init__(config) + hidden_size = config.d_model + self.is_export = config.is_export + self.counting_context_weight = nn.Sequential( + nn.Linear(config.vocab_size, hidden_size), + nn.ReLU(), + nn.Linear(hidden_size, hidden_size), + nn.ReLU(), + nn.Linear(hidden_size, config.d_model), + ) + + def forward( + self, + input_ids=None, + attention_mask=None, + count_pred=None, + encoder_hidden_states=None, + encoder_attention_mask=None, + head_mask=None, + cross_attn_head_mask=None, + past_key_values=None, + inputs_embeds=None, + use_cache=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + ): + self.is_export = False if self.training else True + output_attentions = ( + output_attentions + if output_attentions is not None + else self.config.output_attentions + ) + output_hidden_states = ( + output_hidden_states + if output_hidden_states is not None + else self.config.output_hidden_states + ) + use_cache = use_cache if use_cache is not None else self.config.use_cache + return_dict = ( + return_dict if return_dict is not None else self.config.use_return_dict + ) + + if input_ids is not None and inputs_embeds is not None: + raise ValueError( + "You cannot specify both decoder_input_ids and decoder_inputs_embeds at the same time" + ) + elif input_ids is not None: + input = input_ids + input_shape = input.shape + input_ids = input_ids.reshape([-1, input_shape[-1]]) + elif inputs_embeds is not None: + input_shape = inputs_embeds.shape[:-1] + input = inputs_embeds[:, :, -1] + else: + raise ValueError( + "You have to specify either decoder_input_ids or decoder_inputs_embeds" + ) + + past_key_values_length = ( + past_key_values[0][0].shape[2] if past_key_values is not None else 0 + ) + + if inputs_embeds is None: + inputs_embeds = self.embed_tokens(input_ids) * self.embed_scale + + if self._use_flash_attention_2: + attention_mask = ( + attention_mask + if (attention_mask is not None and 0 in attention_mask) + else None + ) + else: + if self.is_export: + attention_mask = _prepare_4d_causal_attention_mask_export( + attention_mask, + input_shape, + inputs_embeds, + past_key_values_length, + is_export=self.is_export, + ).cast(paddle.float32) + else: + attention_mask = _prepare_4d_causal_attention_mask( + attention_mask, + input_shape, + inputs_embeds, + past_key_values_length, + is_export=self.is_export, + ) + + if encoder_hidden_states is not None and encoder_attention_mask is not None: + if self._use_flash_attention_2: + encoder_attention_mask = ( + encoder_attention_mask if 0 in encoder_attention_mask else None + ) + else: + encoder_attention_mask = _prepare_4d_attention_mask( + encoder_attention_mask, inputs_embeds.dtype, tgt_len=input_shape[-1] + ) + + # embed positions + positions = self.embed_positions(input, past_key_values_length) + + hidden_states = inputs_embeds + positions + + # TODO: add counting context weight to hidden_states + if count_pred is not None: + count_context_weight = self.counting_context_weight(count_pred) + hidden_states = hidden_states + 0.5 * count_context_weight.unsqueeze(1) + + hidden_states = self.layernorm_embedding(hidden_states) + hidden_states = nn.functional.dropout( + hidden_states, p=self.dropout, training=self.training + ) + + if self.gradient_checkpointing and self.training: + if use_cache: + print( + "`use_cache=True` is incompatible with gradient checkpointing`. Setting `use_cache=False`..." + ) + use_cache = False + + # decoder layers + all_hidden_states = () if output_hidden_states else None + all_self_attns = () if output_attentions else None + all_cross_attentions = ( + () if (output_attentions and encoder_hidden_states is not None) else None + ) + next_decoder_cache = () if use_cache else None + + # check if head_mask/cross_attn_head_mask has a correct number of layers specified if desired + for attn_mask, mask_name in zip( + [head_mask, cross_attn_head_mask], ["head_mask", "cross_attn_head_mask"] + ): + if attn_mask is not None: + if attn_mask.size()[0] != len(self.layers): + raise ValueError( + f"The `{mask_name}` should be specified for {len(self.layers)} layers, but it is for" + f" {attn_mask.size()[0]}." + ) + + for idx, decoder_layer in enumerate(self.layers): + if output_hidden_states: + all_hidden_states += (hidden_states,) + if self.training: + dropout_probability = paddle.rand([]) + if dropout_probability < self.layerdrop: + continue + + past_key_value = ( + past_key_values[idx] if past_key_values is not None else None + ) + + if self.gradient_checkpointing and self.training: + layer_outputs = self._gradient_checkpointing_func( + decoder_layer.__call__, + hidden_states, + attention_mask, + encoder_hidden_states, + encoder_attention_mask, + head_mask[idx] if head_mask is not None else None, + ( + cross_attn_head_mask[idx] + if cross_attn_head_mask is not None + else None + ), + None, + output_attentions, + use_cache, + ) + else: + layer_outputs = decoder_layer( + hidden_states, + attention_mask=attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=encoder_attention_mask, + layer_head_mask=(head_mask[idx] if head_mask is not None else None), + cross_attn_layer_head_mask=( + cross_attn_head_mask[idx] + if cross_attn_head_mask is not None + else None + ), + past_key_value=past_key_value, + output_attentions=output_attentions, + use_cache=use_cache, + ) + hidden_states = layer_outputs[0] + if self.is_export: + next_decoder_cache += (layer_outputs[3 if output_attentions else 1],) + else: + if use_cache: + next_decoder_cache += ( + layer_outputs[3 if output_attentions else 1], + ) + + if output_attentions: + all_self_attns += (layer_outputs[1],) + + if encoder_hidden_states is not None: + all_cross_attentions += (layer_outputs[2],) + + hidden_states = self.layer_norm(hidden_states) + + if output_hidden_states: + all_hidden_states += (hidden_states,) + if self.is_export: + next_cache = next_decoder_cache + else: + next_cache = next_decoder_cache if use_cache else None + if not self.is_export: + if not return_dict: + return tuple( + v + for v in [ + hidden_states, + next_cache, + all_hidden_states, + all_self_attns, + all_cross_attentions, + ] + if v is not None + ) + return BaseModelOutputWithPastAndCrossAttentions( + last_hidden_state=hidden_states, + past_key_values=next_cache, + hidden_states=all_hidden_states, + attentions=all_self_attns, + cross_attentions=all_cross_attentions, + ) + + +class SelfAttentionBlock(nn.Layer): + """ + A self-attention block that implements multi-head self-attention + followed by a feed-forward network, typically used in transformer architectures. + + Args: + embed_size (int): The size of the embedding vector. + num_heads (int): The number of attention heads. + is_export (bool): Flag indicating whether to configure the layer for export. + """ + + def __init__(self, embed_size, num_heads, is_export): + super(SelfAttentionBlock, self).__init__() + self.self_attention = MyMultiheadAttention( + embed_dim=embed_size, num_heads=num_heads, is_export=is_export + ) + self.norm = nn.LayerNorm(embed_size) + + def forward(self, x): + attn_output, _ = self.self_attention(x, x, x) + x = self.norm(attn_output + x) + return x + + +class SeqCountingDecoder(nn.Layer): + """ + A custom sequence counting decoder that incorporates multi-head attention layers + and feed-forward networks to process sequences, potentially for latex code counting . + + Args: + in_features (int): The number of input features. + out_features (int): The number of output features. + num_heads (int): The number of attention heads. Defaults to 8. + num_layers (int): The number of attention layers. Defaults to 4. + is_export (bool): Flag indicating whether to configure the layer for export. + """ + + def __init__( + self, in_features, out_features, num_heads=8, num_layers=4, is_export=False + ): + super(SeqCountingDecoder, self).__init__() + + self.attention_blocks = nn.LayerList( + [ + SelfAttentionBlock( + embed_size=in_features, num_heads=num_heads, is_export=is_export + ) + for i in range(num_layers) + ] + ) + self.fc1 = nn.Linear(in_features, in_features // 2) + self.relu = nn.ReLU() + self.global_avg_pool = nn.AdaptiveAvgPool1D(1) + self.fc2 = nn.Linear(in_features // 2, out_features) + + def forward(self, x): + for block in self.attention_blocks: + x = block(x) + x = self.fc1(x) + x = self.relu(x) + x = x.transpose([0, 2, 1]) + x = self.global_avg_pool(x) + x = x.squeeze(-1) + x = self.fc2(x) + return x + + +class CustomMBartForCausalLM(MBartForCausalLM): + """ + Custom MBart model for causal language modeling with a custom decoder. + + This class extends the MBartForCausalLM by replacing its decoder with a + custom decoder, allowing for additional flexibility and features in the + decoding process. + + Args: + config: The configuration object containing model parameters. + length_aware (bool): A flag to enable or configure length-aware mechanisms. + """ + + def __init__(self, config, length_aware=True): + super().__init__(config) + self.model.decoder = CustomMBartDecoder(config) + self.counting_decoder = SeqCountingDecoder( + config.d_model, config.vocab_size, is_export=config.is_export + ) + self.length_aware = length_aware + + def forward( + self, + input_ids=None, + attention_mask=None, + encoder_hidden_states=None, + encoder_attention_mask=None, + head_mask=None, + cross_attn_head_mask=None, + past_key_values=None, + inputs_embeds=None, + labels=None, + use_cache=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + count_gt=None, + ): + output_attentions = ( + output_attentions + if output_attentions is not None + else self.config.output_attentions + ) + output_hidden_states = ( + output_hidden_states + if output_hidden_states is not None + else self.config.output_hidden_states + ) + return_dict = ( + return_dict if return_dict is not None else self.config.use_return_dict + ) + + if self.length_aware: + count_pred = self.counting_decoder(encoder_hidden_states) + else: + count_pred = None + + outputs = self.model.decoder( + input_ids=input_ids, + attention_mask=attention_mask, + count_pred=count_pred, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=encoder_attention_mask, + head_mask=head_mask, + cross_attn_head_mask=cross_attn_head_mask, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + logits = self.lm_head(outputs[0]) + + return CausalLMOutputWithCrossAttentionsAndCounting( + logits=logits, + counting=count_pred, + past_key_values=outputs.past_key_values, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + cross_attentions=outputs.cross_attentions, + ) + + +class UniMERNetHead(nn.Layer): + """Implementation of UniMERNetHead decoder. + + Args: + max_new_tokens (int): Maximum number of new tokens to generate. + decoder_start_token_id (int): ID of the token that starts the decoding. + temperature (float): Sampling temperature for generation. + do_sample (bool): Whether to use sampling; if False, uses greedy decoding. + top_p (float): Top-p (nucleus) sampling parameter. + in_channels (int): Number of input channels/features. + encoder_hidden_size (int): Hidden size of the encoder. + decoder_hidden_size (int): Hidden size of the decoder. + decoder_ffn_dim (int): Dimension of the decoder's feed-forward network. + decoder_layers (int): Number of layers in the decoder. + is_export (bool): Flag indicating if the model is being prepared for export. + length_aware (bool): Flag to enable length-aware mechanisms. + """ + + def __init__( + self, + max_new_tokens=1536, + decoder_start_token_id=0, + temperature=0.2, + do_sample=False, + top_p=0.95, + in_channels=1024, + encoder_hidden_size=1024, + decoder_hidden_size=1024, + decoder_ffn_dim=4096, + decoder_layers=8, + is_export=False, + length_aware=True, + ): + super().__init__() + mbart_config_dict = { + "activation_dropout": 0.0, + "activation_function": "gelu", + "add_cross_attention": True, + "add_final_layer_norm": True, + "attention_dropout": 0.0, + "bos_token_id": 0, + "classifier_dropout": 0.0, + "d_model": decoder_hidden_size, + "decoder_attention_heads": 16, + "decoder_ffn_dim": decoder_ffn_dim, + "decoder_layerdrop": 0.0, + "decoder_layers": decoder_layers, + "dropout": 0.1, + "encoder_attention_heads": 16, + "encoder_ffn_dim": 4096, + "encoder_layerdrop": 0.0, + "encoder_layers": 12, + "eos_token_id": 2, + "forced_eos_token_id": 2, + "init_std": 0.02, + "is_decoder": True, + "is_encoder_decoder": False, + "output_hidden_states": False, + "max_position_embeddings": max_new_tokens, + "model_type": "mbart", + "num_hidden_layers": 12, + "pad_token_id": 1, + "scale_embedding": True, + "tie_word_embeddings": False, + "transformers_version": "4.40.0", + "use_cache": True, + "use_return_dict": True, + "vocab_size": 50000, + "_attn_implementation": "eager", + "hidden_size": decoder_hidden_size, + "is_export": is_export, + } + + self.max_new_tokens = max_new_tokens + self.decoder_start_token_id = decoder_start_token_id + self.temperature = temperature + self.do_sample = do_sample + self.top_p = top_p + self.max_seq_len = max_new_tokens + self.config_decoder = MBartConfig(**mbart_config_dict) + self.encoder_hidden_size = encoder_hidden_size + self.is_export = self.config_decoder.is_export + self.decoder = CustomMBartForCausalLM( + self.config_decoder, length_aware=length_aware + ) + if self.config_decoder.hidden_size != self.encoder_hidden_size: + self.enc_to_dec_proj = nn.Linear( + self.encoder_hidden_size, self.config_decoder.hidden_size + ) + generation_config = { + "max_length": 1537, + "forced_eos_token_id": 2, + } + self.eos_token_id = generation_config["forced_eos_token_id"] + self.pad_token_id = self.config_decoder.pad_token_id + self.logits_processor = LogitsProcessorList() + self.logits_processor.append( + ForcedEOSTokenLogitsProcessor( + generation_config["max_length"], + generation_config["forced_eos_token_id"], + ) + ) + + def _get_decoder_start_token_id( + self, decoder_start_token_id=None, bos_token_id=None + ) -> int: + decoder_start_token_id = ( + decoder_start_token_id + if decoder_start_token_id is not None + else self.generation_config.decoder_start_token_id + ) + bos_token_id = ( + bos_token_id + if bos_token_id is not None + else self.generation_config.bos_token_id + ) + if decoder_start_token_id is not None: + return decoder_start_token_id + elif bos_token_id is not None: + return bos_token_id + raise ValueError( + "`decoder_start_token_id` or `bos_token_id` has to be defined for encoder-decoder generation." + ) + + def _prepare_decoder_input_ids_for_generation( + self, + batch_size, + model_kwargs, + decoder_start_token_id=None, + bos_token_id=None, + ): + if model_kwargs is not None and "decoder_input_ids" in model_kwargs: + decoder_input_ids = model_kwargs.pop("decoder_input_ids") + elif "input_ids" in model_kwargs: + decoder_input_ids = model_kwargs.pop("input_ids") + else: + decoder_input_ids = None + + decoder_start_token_id = self._get_decoder_start_token_id( + decoder_start_token_id, bos_token_id + ) + + if isinstance(decoder_start_token_id, list): + if len(decoder_start_token_id) != batch_size: + raise ValueError( + f"`decoder_start_token_id` expected to have length {batch_size} but got {len(decoder_start_token_id)}" + ) + decoder_input_ids_start = paddle.to_tensor( + decoder_start_token_id, + dtype=paddle.int64, + ) + decoder_input_ids_start = decoder_input_ids_start.view(-1, 1) + else: + decoder_input_ids_start = ( + paddle.ones( + (batch_size, 1), + dtype=paddle.int64, + ) + * decoder_start_token_id + ) + + if decoder_input_ids is None: + decoder_input_ids = decoder_input_ids_start + elif ( + self.config.model_type == "vision-encoder-decoder" + and "donut" in self.name_or_path.lower() + ): + pass + elif self.config.model_type in ["whisper"]: + pass + elif ( + isinstance(decoder_start_token_id, int) + and (decoder_input_ids[:, 0] != decoder_start_token_id).all().item() + ) or ( + isinstance(decoder_start_token_id, paddle.Tensor) + and (decoder_input_ids[:, 0] != decoder_start_token_id[:, 0]).all().item() + ): + decoder_input_ids = paddle.concat( + [decoder_input_ids_start, decoder_input_ids], axis=-1 + ) + if "decoder_attention_mask" in model_kwargs: + decoder_attention_mask = model_kwargs["decoder_attention_mask"] + decoder_attention_mask = paddle.cat( + ( + paddle.ones_like(decoder_attention_mask)[:, :1], + decoder_attention_mask, + ), + dim=-1, + ) + model_kwargs["decoder_attention_mask"] = decoder_attention_mask + + return decoder_input_ids, model_kwargs + + def prepare_inputs_for_generation_mbart( + self, + input_ids, + past_key_values=None, + attention_mask=None, + use_cache=None, + **kwargs, + ): + + if attention_mask is None: + attention_mask = paddle.ones(input_ids.shape) + + if past_key_values: + past_length = past_key_values[0][0].shape[2] + + if input_ids.shape[1] > past_length: + remove_prefix_length = past_length + else: + remove_prefix_length = input_ids.shape[1] - 1 + + input_ids = input_ids[:, remove_prefix_length:] + return { + "input_ids": input_ids, + "attention_mask": attention_mask, + "past_key_values": past_key_values, + "use_cache": use_cache, + } + + def prepare_inputs_for_generation( + self, + input_ids, + past_key_values=None, + attention_mask=None, + use_cache=None, + encoder_outputs=None, + **kwargs, + ): + decoder_inputs = self.prepare_inputs_for_generation_mbart( + input_ids, past_key_values=past_key_values + ) + decoder_attention_mask = ( + decoder_inputs["attention_mask"] + if "attention_mask" in decoder_inputs + else None + ) + input_dict = { + "attention_mask": attention_mask, + "decoder_attention_mask": decoder_attention_mask, + "decoder_input_ids": decoder_inputs["input_ids"], + "encoder_outputs": encoder_outputs, + "past_key_values": decoder_inputs["past_key_values"], + "use_cache": use_cache, + } + return input_dict + + def prepare_inputs_for_generation_export( + self, + past_key_values=None, + attention_mask=None, + use_cache=None, + encoder_outputs=None, + **kwargs, + ): + + input_dict = { + "decoder_attention_mask": None, + "use_cache": use_cache, + } + return input_dict + + def _extract_past_from_model_output( + self, outputs: ModelOutput, standardize_cache_format: bool = False + ): + past_key_values = None + if "past_key_values" in outputs: + past_key_values = outputs.past_key_values + elif "mems" in outputs: + past_key_values = outputs.mems + elif "past_buckets_states" in outputs: + past_key_values = outputs.past_buckets_states + + return past_key_values + + def _update_model_kwargs_for_generation( + self, + outputs: ModelOutput, + model_kwargs: Dict[str, Any], + is_encoder_decoder: bool = False, + standardize_cache_format: bool = False, + ) -> Dict[str, Any]: + model_kwargs["past_key_values"] = self._extract_past_from_model_output( + outputs, standardize_cache_format=standardize_cache_format + ) + if getattr(outputs, "state", None) is not None: + model_kwargs["state"] = outputs.state + + if "token_type_ids" in model_kwargs: + token_type_ids = model_kwargs["token_type_ids"] + model_kwargs["token_type_ids"] = paddle.concat( + [token_type_ids, token_type_ids[:, -1].unsqueeze(-1)], axis=-1 + ) + + if not is_encoder_decoder: + if "attention_mask" in model_kwargs: + attention_mask = model_kwargs["attention_mask"] + model_kwargs["attention_mask"] = paddle.concat( + [ + attention_mask, + attention_mask.new_ones((attention_mask.shape[0], 1)), + ], + axis=-1, + ) + else: + if "decoder_attention_mask" in model_kwargs: + decoder_attention_mask = model_kwargs["decoder_attention_mask"] + model_kwargs["decoder_attention_mask"] = paddle.concat( + [ + decoder_attention_mask, + decoder_attention_mask.new_ones( + (decoder_attention_mask.shape[0], 1) + ), + ], + axis=-1, + ) + + if ( + "cache_position" in model_kwargs + and model_kwargs["cache_position"] is not None + ): + model_kwargs["cache_position"] = model_kwargs["cache_position"][-1:] + 1 + + return model_kwargs + + def stopping_criteria(self, input_ids): + if self.is_export: + return input_ids[:, -1] == paddle.to_tensor([self.eos_token_id]) + is_done = paddle.isin(input_ids[:, -1], paddle.to_tensor([self.eos_token_id])) + return is_done + + def generate_single_iter( + self, + decoder_input_ids=None, + decoder_attention_mask=None, + encoder_outputs=None, + past_key_values=None, + decoder_inputs_embeds=None, + labels=None, + use_cache=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + **kwargs, + ): + encoder_hidden_states = encoder_outputs[0] + if self.config_decoder.hidden_size != self.encoder_hidden_size: + encoder_hidden_states = self.enc_to_dec_proj(encoder_hidden_states) + kwargs_decoder = {} + + decoder_outputs = self.decoder( + input_ids=decoder_input_ids, + attention_mask=decoder_attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=None, + inputs_embeds=None, + output_attentions=False, + output_hidden_states=output_hidden_states, + use_cache=use_cache, + past_key_values=past_key_values, + return_dict=return_dict, + **kwargs_decoder, + ) + + return Seq2SeqLMOutput( + loss=None, + logits=decoder_outputs.logits, + past_key_values=decoder_outputs.past_key_values, + decoder_hidden_states=decoder_outputs.hidden_states, + decoder_attentions=decoder_outputs.attentions, + cross_attentions=decoder_outputs.cross_attentions, + encoder_last_hidden_state=encoder_outputs.last_hidden_state, + encoder_hidden_states=encoder_outputs.hidden_states, + encoder_attentions=encoder_outputs.attentions, + ) + + @paddle.no_grad() + def generate( + self, + model_kwargs, + ): + """ + Generate sequences using the UniMERNetHead for inference tasks. + + Args: + model_kwargs (dict): A dictionary of model configurations and inputs, which typically include: + - encoder_outputs: Outputs from the encoder. + - use_cache: Boolean flag to indicate if caching should be used. + - output_attentions: Boolean flag for outputting attention scores. + - output_hidden_states: Boolean flag for outputting hidden states. + + Returns: + A tensor containing the generated sequences. + """ + batch_size = model_kwargs["encoder_outputs"]["last_hidden_state"].shape[0] + generation_config = { + "decoder_start_token_id": 0, + "bos_token_id": 0, + } + input_ids, model_kwargs = self._prepare_decoder_input_ids_for_generation( + batch_size=batch_size, + model_kwargs=model_kwargs, + decoder_start_token_id=generation_config["decoder_start_token_id"], + bos_token_id=generation_config["bos_token_id"], + ) + model_kwargs["key use_cache"] = True + batch_size, cur_len = input_ids.shape + + if "inputs_embeds" in model_kwargs: + cur_len = model_kwargs["inputs_embeds"].shape[1] + model_kwargs["cache_position"] = paddle.arange(cur_len) + pad_token_id = self.pad_token_id + eos_token_id = [self.eos_token_id] + eos_token = self.eos_token_id + unfinished_sequences = paddle.ones(batch_size, dtype=paddle.int64) + for idx in range(self.max_seq_len): + model_inputs = self.prepare_inputs_for_generation(input_ids, **model_kwargs) + outputs = self.generate_single_iter( + **model_inputs, + return_dict=True, + output_attentions=False, + output_hidden_states=False, + ) + next_token_logits = outputs.logits[:, -1, :] + + next_tokens_scores = self.logits_processor(input_ids, next_token_logits) + next_tokens = paddle.argmax(next_tokens_scores, axis=-1) + if eos_token_id is not None: + if pad_token_id is None: + raise ValueError( + "If `eos_token_id` is defined, make sure that `pad_token_id` is defined." + ) + next_tokens = next_tokens * unfinished_sequences + pad_token_id * ( + 1 - unfinished_sequences + ) + input_ids = paddle.concat([input_ids, next_tokens[:, None]], axis=-1) + model_kwargs = self._update_model_kwargs_for_generation( + outputs, + model_kwargs, + is_encoder_decoder=self.config_decoder.is_encoder_decoder, + ) + unfinished_sequences = unfinished_sequences & ~self.stopping_criteria( + input_ids + ).cast(paddle.int64) + + if ( + eos_token is not None + and ( + paddle.cumsum((input_ids == eos_token).cast(paddle.int64), 1)[:, -1] + >= 1 + ).all() + ): + break + + return input_ids + + @paddle.no_grad() + def generate_export( + self, + encoder_outputs, + model_kwargs, + ): + batch_size = encoder_outputs["last_hidden_state"].shape[0] + generation_config = { + "decoder_start_token_id": 0, + "bos_token_id": 0, + } + input_ids, model_kwargs = self._prepare_decoder_input_ids_for_generation( + batch_size=batch_size, + model_kwargs=model_kwargs, + decoder_start_token_id=generation_config["decoder_start_token_id"], + bos_token_id=generation_config["bos_token_id"], + ) + input_ids = input_ids.reshape([-1, 1]) + decoder_input_ids = input_ids + model_kwargs["key use_cache"] = True + batch_size, cur_len = input_ids.shape + + if "inputs_embeds" in model_kwargs: + cur_len = model_kwargs["inputs_embeds"].shape[1] + cache_position = paddle.arange(cur_len) + pad_token_id = self.pad_token_id + eos_token_id = [self.eos_token_id] + eos_token = self.eos_token_id + unfinished_sequences = paddle.ones([batch_size], dtype=paddle.int64) + i_idx = paddle.full([], 0) + past_key_values = [] + for i in range(8): + init_arr = paddle.zeros([batch_size, 16, 0, 64]) + paddle.jit.api.set_dynamic_shape(init_arr, [-1, -1, -1, -1]) + cache = (init_arr, init_arr, init_arr, init_arr) + past_key_values.append(cache) + idx = 0 + while i_idx < paddle.to_tensor(self.max_seq_len): + + model_inputs = self.prepare_inputs_for_generation_export( + past_key_values=past_key_values, **model_kwargs + ) + decoder_attention_mask = model_inputs["decoder_attention_mask"] + decoder_attention_mask = paddle.ones(input_ids.shape) + paddle.jit.api.set_dynamic_shape(decoder_input_ids, [-1, -1]) + paddle.jit.api.set_dynamic_shape(decoder_attention_mask, [-1, -1]) + + outputs = self.generate_single_iter( + decoder_input_ids=decoder_input_ids, + decoder_attention_mask=decoder_attention_mask, + encoder_outputs=encoder_outputs, + past_key_values=past_key_values, + return_dict=True, + output_attentions=False, + output_hidden_states=False, + ) + + next_token_logits = outputs.logits[:, -1, :] + + next_tokens_scores = self.logits_processor(input_ids, next_token_logits) + next_tokens = paddle.argmax(next_tokens_scores, axis=-1) + if eos_token_id is not None: + next_tokens = next_tokens * unfinished_sequences + pad_token_id * ( + 1 - unfinished_sequences + ) + input_ids = paddle.concat([input_ids, next_tokens.unsqueeze(1)], axis=-1) + past_length = past_key_values[0][0].shape[2] + decoder_input_ids = next_tokens.unsqueeze(1) + past_key_values = outputs.past_key_values + cache_position = cache_position[-1:] + 1 + unfinished_sequences = unfinished_sequences & ~self.stopping_criteria( + input_ids + ).cast(paddle.int64) + if ( + eos_token is not None + and ( + paddle.cumsum((input_ids == eos_token).cast(paddle.int64), 1)[:, -1] + >= 1 + ).all() + ): + break + + i_idx += 1 + return input_ids + + def forwad_train( + self, + encoder_outputs, + decoder_input_ids, + decoder_attention_mask, + past_key_values=None, + decoder_inputs_embeds=None, + labels=None, + use_cache=None, + output_attentions=None, + output_hidden_states=None, + return_dict=None, + **kwargs, + ): + """ + Training for the UniMERNetHead. + + Args: + encoder_outputs: Outputs from the encoder, used as input to the decoder. + decoder_input_ids: Input IDs for the decoder. + decoder_attention_mask: Attention mask for the decoder inputs. + past_key_values: Cached key/values for faster decoding. + decoder_inputs_embeds: Optional embeddings for the decoder inputs. + labels: Target labels for calculating loss. + use_cache: Whether to use cache during decoding. + output_attentions: Whether to return attention scores. + output_hidden_states: Whether to return hidden states. + return_dict: Whether to return a dictionary of outputs. + **kwargs: Additional keyword arguments. + + Returns: + logits: The raw, unnormalized predictions from the model. + count_pred: Optional prediction related to sequence length or other counts. + masked_labels: The labels used during training, possibly masked. + """ + labels = decoder_input_ids * 1 + labels = labels.masked_fill_(labels == self.pad_token_id, -100) + input_decoder_input_ids = decoder_input_ids[:, :-1] + input_decoder_attention_mask = decoder_attention_mask[:, :-1] + encoder_hidden_states = encoder_outputs[0] + if self.config_decoder.hidden_size != self.encoder_hidden_size: + encoder_hidden_states = self.enc_to_dec_proj(encoder_hidden_states) + kwargs_decoder = {} + decoder_outputs = self.decoder( + input_ids=input_decoder_input_ids, + attention_mask=input_decoder_attention_mask, + encoder_hidden_states=encoder_hidden_states, + encoder_attention_mask=None, + inputs_embeds=None, + output_attentions=False, + output_hidden_states=output_hidden_states, + use_cache=use_cache, + past_key_values=past_key_values, + return_dict=return_dict, + **kwargs_decoder, + ) + + logits = decoder_outputs.logits + count_pred = decoder_outputs.counting + return logits, count_pred, labels + + def forward(self, inputs, targets=None): + """ + Forward pass for the UniMERNetHead, handling both training and inference. + + Args: + inputs: The input data, which can vary based on training or inference. + targets: The target labels, used only during training. + + Returns: + During inference: Returns predicted latex code. + During training: Returns logits, predicted counts, and masked labels. + """ + self.is_export = False if self.training else True + if not self.training: + encoder_outputs = inputs + if self.is_export: + model_kwargs = { + "output_attentions": False, + "output_hidden_states": False, + "use_cache": True, + } + word_pred = self.generate_export(encoder_outputs, model_kwargs) + else: + model_kwargs = { + "output_attentions": False, + "output_hidden_states": False, + "use_cache": True, + "encoder_outputs": encoder_outputs, + } + word_pred = self.generate(model_kwargs) + + return word_pred + + encoder_outputs, tgt_seq, mask = inputs + logits, count_pred, masked_labels = self.forwad_train( + encoder_outputs, tgt_seq, mask + ) + return logits, count_pred, masked_labels diff --git a/ppocr/modeling/heads/rec_visionlan_head.py b/ppocr/modeling/heads/rec_visionlan_head.py new file mode 100644 index 0000000..bb34dc3 --- /dev/null +++ b/ppocr/modeling/heads/rec_visionlan_head.py @@ -0,0 +1,474 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/wangyuxin87/VisionLAN +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F +from paddle.nn.initializer import Normal, XavierNormal +import numpy as np + + +class PositionalEncoding(nn.Layer): + def __init__(self, d_hid, n_position=200): + super(PositionalEncoding, self).__init__() + self.register_buffer( + "pos_table", self._get_sinusoid_encoding_table(n_position, d_hid) + ) + + def _get_sinusoid_encoding_table(self, n_position, d_hid): + """Sinusoid position encoding table""" + + def get_position_angle_vec(position): + return [ + position / np.power(10000, 2 * (hid_j // 2) / d_hid) + for hid_j in range(d_hid) + ] + + sinusoid_table = np.array( + [get_position_angle_vec(pos_i) for pos_i in range(n_position)] + ) + sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2]) # dim 2i + sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2]) # dim 2i+1 + sinusoid_table = paddle.to_tensor(sinusoid_table, dtype="float32") + sinusoid_table = paddle.unsqueeze(sinusoid_table, axis=0) + return sinusoid_table + + def forward(self, x): + return x + self.pos_table[:, : x.shape[1]].clone().detach() + + +class ScaledDotProductAttention(nn.Layer): + "Scaled Dot-Product Attention" + + def __init__(self, temperature, attn_dropout=0.1): + super(ScaledDotProductAttention, self).__init__() + self.temperature = temperature + self.dropout = nn.Dropout(attn_dropout) + self.softmax = nn.Softmax(axis=2) + + def forward(self, q, k, v, mask=None): + k = paddle.transpose(k, perm=[0, 2, 1]) + attn = paddle.bmm(q, k) + attn = attn / self.temperature + if mask is not None: + attn = attn.masked_fill(mask, -1e9) + if mask.dim() == 3: + mask = paddle.unsqueeze(mask, axis=1) + elif mask.dim() == 2: + mask = paddle.unsqueeze(mask, axis=1) + mask = paddle.unsqueeze(mask, axis=1) + repeat_times = [ + attn.shape[1] // mask.shape[1], + attn.shape[2] // mask.shape[2], + ] + mask = paddle.tile(mask, [1, repeat_times[0], repeat_times[1], 1]) + attn[mask == 0] = -1e9 + attn = self.softmax(attn) + attn = self.dropout(attn) + output = paddle.bmm(attn, v) + return output + + +class MultiHeadAttention(nn.Layer): + "Multi-Head Attention module" + + def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1): + super(MultiHeadAttention, self).__init__() + self.n_head = n_head + self.d_k = d_k + self.d_v = d_v + self.w_qs = nn.Linear( + d_model, + n_head * d_k, + weight_attr=ParamAttr( + initializer=Normal(mean=0, std=np.sqrt(2.0 / (d_model + d_k))) + ), + ) + self.w_ks = nn.Linear( + d_model, + n_head * d_k, + weight_attr=ParamAttr( + initializer=Normal(mean=0, std=np.sqrt(2.0 / (d_model + d_k))) + ), + ) + self.w_vs = nn.Linear( + d_model, + n_head * d_v, + weight_attr=ParamAttr( + initializer=Normal(mean=0, std=np.sqrt(2.0 / (d_model + d_v))) + ), + ) + + self.attention = ScaledDotProductAttention(temperature=np.power(d_k, 0.5)) + self.layer_norm = nn.LayerNorm(d_model) + self.fc = nn.Linear( + n_head * d_v, d_model, weight_attr=ParamAttr(initializer=XavierNormal()) + ) + self.dropout = nn.Dropout(dropout) + + def forward(self, q, k, v, mask=None): + d_k, d_v, n_head = self.d_k, self.d_v, self.n_head + sz_b, len_q, _ = q.shape + sz_b, len_k, _ = k.shape + sz_b, len_v, _ = v.shape + residual = q + + q = self.w_qs(q) + q = paddle.reshape(q, shape=[-1, len_q, n_head, d_k]) # 4*21*512 ---- 4*21*8*64 + k = self.w_ks(k) + k = paddle.reshape(k, shape=[-1, len_k, n_head, d_k]) + v = self.w_vs(v) + v = paddle.reshape(v, shape=[-1, len_v, n_head, d_v]) + + q = paddle.transpose(q, perm=[2, 0, 1, 3]) + q = paddle.reshape(q, shape=[-1, len_q, d_k]) # (n*b) x lq x dk + k = paddle.transpose(k, perm=[2, 0, 1, 3]) + k = paddle.reshape(k, shape=[-1, len_k, d_k]) # (n*b) x lk x dk + v = paddle.transpose(v, perm=[2, 0, 1, 3]) + v = paddle.reshape(v, shape=[-1, len_v, d_v]) # (n*b) x lv x dv + + mask = ( + paddle.tile(mask, [n_head, 1, 1]) if mask is not None else None + ) # (n*b) x .. x .. + output = self.attention(q, k, v, mask=mask) + output = paddle.reshape(output, shape=[n_head, -1, len_q, d_v]) + output = paddle.transpose(output, perm=[1, 2, 0, 3]) + output = paddle.reshape( + output, shape=[-1, len_q, n_head * d_v] + ) # b x lq x (n*dv) + output = self.dropout(self.fc(output)) + output = self.layer_norm(output + residual) + return output + + +class PositionwiseFeedForward(nn.Layer): + def __init__(self, d_in, d_hid, dropout=0.1): + super(PositionwiseFeedForward, self).__init__() + self.w_1 = nn.Conv1D(d_in, d_hid, 1) # position-wise + self.w_2 = nn.Conv1D(d_hid, d_in, 1) # position-wise + self.layer_norm = nn.LayerNorm(d_in) + self.dropout = nn.Dropout(dropout) + + def forward(self, x): + residual = x + x = paddle.transpose(x, perm=[0, 2, 1]) + x = self.w_2(F.relu(self.w_1(x))) + x = paddle.transpose(x, perm=[0, 2, 1]) + x = self.dropout(x) + x = self.layer_norm(x + residual) + return x + + +class EncoderLayer(nn.Layer): + """Compose with two layers""" + + def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1): + super(EncoderLayer, self).__init__() + self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout) + self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout) + + def forward(self, enc_input, slf_attn_mask=None): + enc_output = self.slf_attn(enc_input, enc_input, enc_input, mask=slf_attn_mask) + enc_output = self.pos_ffn(enc_output) + return enc_output + + +class Transformer_Encoder(nn.Layer): + def __init__( + self, + n_layers=2, + n_head=8, + d_word_vec=512, + d_k=64, + d_v=64, + d_model=512, + d_inner=2048, + dropout=0.1, + n_position=256, + ): + super(Transformer_Encoder, self).__init__() + self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position) + self.dropout = nn.Dropout(p=dropout) + self.layer_stack = nn.LayerList( + [ + EncoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout) + for _ in range(n_layers) + ] + ) + self.layer_norm = nn.LayerNorm(d_model, epsilon=1e-6) + + def forward(self, enc_output, src_mask, return_attns=False): + enc_output = self.dropout(self.position_enc(enc_output)) # position embedding + for enc_layer in self.layer_stack: + enc_output = enc_layer(enc_output, slf_attn_mask=src_mask) + enc_output = self.layer_norm(enc_output) + return enc_output + + +class PP_layer(nn.Layer): + def __init__(self, n_dim=512, N_max_character=25, n_position=256): + super(PP_layer, self).__init__() + self.character_len = N_max_character + self.f0_embedding = nn.Embedding(N_max_character, n_dim) + self.w0 = nn.Linear(N_max_character, n_position) + self.wv = nn.Linear(n_dim, n_dim) + self.we = nn.Linear(n_dim, N_max_character) + self.active = nn.Tanh() + self.softmax = nn.Softmax(axis=2) + + def forward(self, enc_output): + # enc_output: b,256,512 + reading_order = paddle.arange(self.character_len, dtype="int64") + reading_order = reading_order.unsqueeze(0).expand( + [enc_output.shape[0], self.character_len] + ) # (S,) -> (B, S) + reading_order = self.f0_embedding(reading_order) # b,25,512 + + # calculate attention + reading_order = paddle.transpose(reading_order, perm=[0, 2, 1]) + t = self.w0(reading_order) # b,512,256 + t = self.active( + paddle.transpose(t, perm=[0, 2, 1]) + self.wv(enc_output) + ) # b,256,512 + t = self.we(t) # b,256,25 + t = self.softmax(paddle.transpose(t, perm=[0, 2, 1])) # b,25,256 + g_output = paddle.bmm(t, enc_output) # b,25,512 + return g_output + + +class Prediction(nn.Layer): + def __init__(self, n_dim=512, n_position=256, N_max_character=25, n_class=37): + super(Prediction, self).__init__() + self.pp = PP_layer( + n_dim=n_dim, N_max_character=N_max_character, n_position=n_position + ) + self.pp_share = PP_layer( + n_dim=n_dim, N_max_character=N_max_character, n_position=n_position + ) + self.w_vrm = nn.Linear(n_dim, n_class) # output layer + self.w_share = nn.Linear(n_dim, n_class) # output layer + self.nclass = n_class + + def forward(self, cnn_feature, f_res, f_sub, train_mode=False, use_mlm=True): + if train_mode: + if not use_mlm: + g_output = self.pp(cnn_feature) # b,25,512 + g_output = self.w_vrm(g_output) + f_res = 0 + f_sub = 0 + return g_output, f_res, f_sub + g_output = self.pp(cnn_feature) # b,25,512 + f_res = self.pp_share(f_res) + f_sub = self.pp_share(f_sub) + g_output = self.w_vrm(g_output) + f_res = self.w_share(f_res) + f_sub = self.w_share(f_sub) + return g_output, f_res, f_sub + else: + g_output = self.pp(cnn_feature) # b,25,512 + g_output = self.w_vrm(g_output) + return g_output + + +class MLM(nn.Layer): + "Architecture of MLM" + + def __init__(self, n_dim=512, n_position=256, max_text_length=25): + super(MLM, self).__init__() + self.MLM_SequenceModeling_mask = Transformer_Encoder( + n_layers=2, n_position=n_position + ) + self.MLM_SequenceModeling_WCL = Transformer_Encoder( + n_layers=1, n_position=n_position + ) + self.pos_embedding = nn.Embedding(max_text_length, n_dim) + self.w0_linear = nn.Linear(1, n_position) + self.wv = nn.Linear(n_dim, n_dim) + self.active = nn.Tanh() + self.we = nn.Linear(n_dim, 1) + self.sigmoid = nn.Sigmoid() + + def forward(self, x, label_pos): + # transformer unit for generating mask_c + feature_v_seq = self.MLM_SequenceModeling_mask(x, src_mask=None) + # position embedding layer + label_pos = paddle.to_tensor(label_pos, dtype="int64") + pos_emb = self.pos_embedding(label_pos) + pos_emb = self.w0_linear(paddle.unsqueeze(pos_emb, axis=2)) + pos_emb = paddle.transpose(pos_emb, perm=[0, 2, 1]) + # fusion position embedding with features V & generate mask_c + att_map_sub = self.active(pos_emb + self.wv(feature_v_seq)) + att_map_sub = self.we(att_map_sub) # b,256,1 + att_map_sub = paddle.transpose(att_map_sub, perm=[0, 2, 1]) + att_map_sub = self.sigmoid(att_map_sub) # b,1,256 + # WCL + ## generate inputs for WCL + att_map_sub = paddle.transpose(att_map_sub, perm=[0, 2, 1]) + f_res = x * (1 - att_map_sub) # second path with remaining string + f_sub = x * att_map_sub # first path with occluded character + ## transformer units in WCL + f_res = self.MLM_SequenceModeling_WCL(f_res, src_mask=None) + f_sub = self.MLM_SequenceModeling_WCL(f_sub, src_mask=None) + return f_res, f_sub, att_map_sub + + +def trans_1d_2d(x): + b, w_h, c = x.shape # b, 256, 512 + x = paddle.transpose(x, perm=[0, 2, 1]) + x = paddle.reshape(x, [-1, c, 32, 8]) + x = paddle.transpose(x, perm=[0, 1, 3, 2]) # [b, c, 8, 32] + return x + + +class MLM_VRM(nn.Layer): + """ + MLM+VRM, MLM is only used in training. + ratio controls the occluded number in a batch. + The pipeline of VisionLAN in testing is very concise with only a backbone + sequence modeling(transformer unit) + prediction layer(pp layer). + x: input image + label_pos: character index + training_step: LF or LA process + output + text_pre: prediction of VRM + test_rem: prediction of remaining string in MLM + text_mas: prediction of occluded character in MLM + mask_c_show: visualization of Mask_c + """ + + def __init__( + self, n_layers=3, n_position=256, n_dim=512, max_text_length=25, nclass=37 + ): + super(MLM_VRM, self).__init__() + self.MLM = MLM( + n_dim=n_dim, n_position=n_position, max_text_length=max_text_length + ) + self.SequenceModeling = Transformer_Encoder( + n_layers=n_layers, n_position=n_position + ) + self.Prediction = Prediction( + n_dim=n_dim, + n_position=n_position, + N_max_character=max_text_length + + 1, # N_max_character = 1 eos + 25 characters + n_class=nclass, + ) + self.nclass = nclass + self.max_text_length = max_text_length + + def forward(self, x, label_pos, training_step, train_mode=False): + b, c, h, w = x.shape + nT = self.max_text_length + x = paddle.transpose(x, perm=[0, 1, 3, 2]) + x = paddle.reshape(x, [-1, c, h * w]) + x = paddle.transpose(x, perm=[0, 2, 1]) + if train_mode: + if training_step == "LF_1": + f_res = 0 + f_sub = 0 + x = self.SequenceModeling(x, src_mask=None) + text_pre, test_rem, text_mas = self.Prediction( + x, f_res, f_sub, train_mode=True, use_mlm=False + ) + return text_pre, text_pre, text_pre, text_pre + elif training_step == "LF_2": + # MLM + f_res, f_sub, mask_c = self.MLM(x, label_pos) + x = self.SequenceModeling(x, src_mask=None) + text_pre, test_rem, text_mas = self.Prediction( + x, f_res, f_sub, train_mode=True + ) + mask_c_show = trans_1d_2d(mask_c) + return text_pre, test_rem, text_mas, mask_c_show + elif training_step == "LA": + # MLM + f_res, f_sub, mask_c = self.MLM(x, label_pos) + ## use the mask_c (1 for occluded character and 0 for remaining characters) to occlude input + ## ratio controls the occluded number in a batch + character_mask = paddle.zeros_like(mask_c) + + ratio = b // 2 + if ratio >= 1: + with paddle.no_grad(): + character_mask[0:ratio, :, :] = mask_c[0:ratio, :, :] + else: + character_mask = mask_c + x = x * (1 - character_mask) + # VRM + ## transformer unit for VRM + x = self.SequenceModeling(x, src_mask=None) + ## prediction layer for MLM and VSR + text_pre, test_rem, text_mas = self.Prediction( + x, f_res, f_sub, train_mode=True + ) + mask_c_show = trans_1d_2d(mask_c) + return text_pre, test_rem, text_mas, mask_c_show + else: + raise NotImplementedError + else: # VRM is only used in the testing stage + f_res = 0 + f_sub = 0 + contextual_feature = self.SequenceModeling(x, src_mask=None) + text_pre = self.Prediction( + contextual_feature, f_res, f_sub, train_mode=False, use_mlm=False + ) + text_pre = paddle.transpose(text_pre, perm=[1, 0, 2]) # (26, b, 37)) + return text_pre, x + + +class VLHead(nn.Layer): + """ + Architecture of VisionLAN + """ + + def __init__( + self, + in_channels, + out_channels=36, + n_layers=3, + n_position=256, + n_dim=512, + max_text_length=25, + training_step="LA", + ): + super(VLHead, self).__init__() + self.MLM_VRM = MLM_VRM( + n_layers=n_layers, + n_position=n_position, + n_dim=n_dim, + max_text_length=max_text_length, + nclass=out_channels + 1, + ) + self.training_step = training_step + + def forward(self, feat, targets=None): + if self.training: + label_pos = targets[-2] + text_pre, test_rem, text_mas, mask_map = self.MLM_VRM( + feat, label_pos, self.training_step, train_mode=True + ) + return text_pre, test_rem, text_mas, mask_map + else: + text_pre, x = self.MLM_VRM( + feat, targets, self.training_step, train_mode=False + ) + return text_pre, x diff --git a/ppocr/modeling/heads/self_attention.py b/ppocr/modeling/heads/self_attention.py new file mode 100644 index 0000000..c02b9cd --- /dev/null +++ b/ppocr/modeling/heads/self_attention.py @@ -0,0 +1,460 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math + +import paddle +from paddle import ParamAttr, nn +from paddle import nn, ParamAttr +from paddle.nn import functional as F +import numpy as np + +gradient_clip = 10 + + +class WrapEncoderForFeature(nn.Layer): + def __init__( + self, + src_vocab_size, + max_length, + n_layer, + n_head, + d_key, + d_value, + d_model, + d_inner_hid, + prepostprocess_dropout, + attention_dropout, + relu_dropout, + preprocess_cmd, + postprocess_cmd, + weight_sharing, + bos_idx=0, + ): + super(WrapEncoderForFeature, self).__init__() + + self.prepare_encoder = PrepareEncoder( + src_vocab_size, + d_model, + max_length, + prepostprocess_dropout, + bos_idx=bos_idx, + word_emb_param_name="src_word_emb_table", + ) + self.encoder = Encoder( + n_layer, + n_head, + d_key, + d_value, + d_model, + d_inner_hid, + prepostprocess_dropout, + attention_dropout, + relu_dropout, + preprocess_cmd, + postprocess_cmd, + ) + + def forward(self, enc_inputs): + conv_features, src_pos, src_slf_attn_bias = enc_inputs + enc_input = self.prepare_encoder(conv_features, src_pos) + enc_output = self.encoder(enc_input, src_slf_attn_bias) + return enc_output + + +class WrapEncoder(nn.Layer): + """ + embedder + encoder + """ + + def __init__( + self, + src_vocab_size, + max_length, + n_layer, + n_head, + d_key, + d_value, + d_model, + d_inner_hid, + prepostprocess_dropout, + attention_dropout, + relu_dropout, + preprocess_cmd, + postprocess_cmd, + weight_sharing, + bos_idx=0, + ): + super(WrapEncoder, self).__init__() + + self.prepare_decoder = PrepareDecoder( + src_vocab_size, d_model, max_length, prepostprocess_dropout, bos_idx=bos_idx + ) + self.encoder = Encoder( + n_layer, + n_head, + d_key, + d_value, + d_model, + d_inner_hid, + prepostprocess_dropout, + attention_dropout, + relu_dropout, + preprocess_cmd, + postprocess_cmd, + ) + + def forward(self, enc_inputs): + src_word, src_pos, src_slf_attn_bias = enc_inputs + enc_input = self.prepare_decoder(src_word, src_pos) + enc_output = self.encoder(enc_input, src_slf_attn_bias) + return enc_output + + +class Encoder(nn.Layer): + """ + encoder + """ + + def __init__( + self, + n_layer, + n_head, + d_key, + d_value, + d_model, + d_inner_hid, + prepostprocess_dropout, + attention_dropout, + relu_dropout, + preprocess_cmd="n", + postprocess_cmd="da", + ): + super(Encoder, self).__init__() + + self.encoder_layers = list() + for i in range(n_layer): + self.encoder_layers.append( + self.add_sublayer( + "layer_%d" % i, + EncoderLayer( + n_head, + d_key, + d_value, + d_model, + d_inner_hid, + prepostprocess_dropout, + attention_dropout, + relu_dropout, + preprocess_cmd, + postprocess_cmd, + ), + ) + ) + self.processor = PrePostProcessLayer( + preprocess_cmd, d_model, prepostprocess_dropout + ) + + def forward(self, enc_input, attn_bias): + for encoder_layer in self.encoder_layers: + enc_output = encoder_layer(enc_input, attn_bias) + enc_input = enc_output + enc_output = self.processor(enc_output) + return enc_output + + +class EncoderLayer(nn.Layer): + """ + EncoderLayer + """ + + def __init__( + self, + n_head, + d_key, + d_value, + d_model, + d_inner_hid, + prepostprocess_dropout, + attention_dropout, + relu_dropout, + preprocess_cmd="n", + postprocess_cmd="da", + ): + super(EncoderLayer, self).__init__() + self.preprocesser1 = PrePostProcessLayer( + preprocess_cmd, d_model, prepostprocess_dropout + ) + self.self_attn = MultiHeadAttention( + d_key, d_value, d_model, n_head, attention_dropout + ) + self.postprocesser1 = PrePostProcessLayer( + postprocess_cmd, d_model, prepostprocess_dropout + ) + + self.preprocesser2 = PrePostProcessLayer( + preprocess_cmd, d_model, prepostprocess_dropout + ) + self.ffn = FFN(d_inner_hid, d_model, relu_dropout) + self.postprocesser2 = PrePostProcessLayer( + postprocess_cmd, d_model, prepostprocess_dropout + ) + + def forward(self, enc_input, attn_bias): + attn_output = self.self_attn( + self.preprocesser1(enc_input), None, None, attn_bias + ) + attn_output = self.postprocesser1(attn_output, enc_input) + ffn_output = self.ffn(self.preprocesser2(attn_output)) + ffn_output = self.postprocesser2(ffn_output, attn_output) + return ffn_output + + +class MultiHeadAttention(nn.Layer): + """ + Multi-Head Attention + """ + + def __init__(self, d_key, d_value, d_model, n_head=1, dropout_rate=0.0): + super(MultiHeadAttention, self).__init__() + self.n_head = n_head + self.d_key = d_key + self.d_value = d_value + self.d_model = d_model + self.dropout_rate = dropout_rate + self.q_fc = paddle.nn.Linear( + in_features=d_model, out_features=d_key * n_head, bias_attr=False + ) + self.k_fc = paddle.nn.Linear( + in_features=d_model, out_features=d_key * n_head, bias_attr=False + ) + self.v_fc = paddle.nn.Linear( + in_features=d_model, out_features=d_value * n_head, bias_attr=False + ) + self.proj_fc = paddle.nn.Linear( + in_features=d_value * n_head, out_features=d_model, bias_attr=False + ) + + def _prepare_qkv(self, queries, keys, values, cache=None): + if keys is None: # self-attention + keys, values = queries, queries + static_kv = False + else: # cross-attention + static_kv = True + + q = self.q_fc(queries) + q = paddle.reshape(x=q, shape=[0, 0, self.n_head, self.d_key]) + q = paddle.transpose(x=q, perm=[0, 2, 1, 3]) + + if cache is not None and static_kv and "static_k" in cache: + # for encoder-decoder attention in inference and has cached + k = cache["static_k"] + v = cache["static_v"] + else: + k = self.k_fc(keys) + v = self.v_fc(values) + k = paddle.reshape(x=k, shape=[0, 0, self.n_head, self.d_key]) + k = paddle.transpose(x=k, perm=[0, 2, 1, 3]) + v = paddle.reshape(x=v, shape=[0, 0, self.n_head, self.d_value]) + v = paddle.transpose(x=v, perm=[0, 2, 1, 3]) + + if cache is not None: + if static_kv and not "static_k" in cache: + # for encoder-decoder attention in inference and has not cached + cache["static_k"], cache["static_v"] = k, v + elif not static_kv: + # for decoder self-attention in inference + cache_k, cache_v = cache["k"], cache["v"] + k = paddle.concat([cache_k, k], axis=2) + v = paddle.concat([cache_v, v], axis=2) + cache["k"], cache["v"] = k, v + + return q, k, v + + def forward(self, queries, keys, values, attn_bias, cache=None): + # compute q ,k ,v + keys = queries if keys is None else keys + values = keys if values is None else values + q, k, v = self._prepare_qkv(queries, keys, values, cache) + + # scale dot product attention + product = paddle.matmul(x=q, y=k, transpose_y=True) + product = product * self.d_model**-0.5 + if attn_bias is not None: + product += attn_bias.astype(product.dtype) + weights = F.softmax(product) + if self.dropout_rate: + weights = F.dropout(weights, p=self.dropout_rate, mode="downscale_in_infer") + out = paddle.matmul(weights, v) + + # combine heads + out = paddle.transpose(out, perm=[0, 2, 1, 3]) + out = paddle.reshape(x=out, shape=[0, 0, out.shape[2] * out.shape[3]]) + + # project to output + out = self.proj_fc(out) + + return out + + +class PrePostProcessLayer(nn.Layer): + """ + PrePostProcessLayer + """ + + def __init__(self, process_cmd, d_model, dropout_rate): + super(PrePostProcessLayer, self).__init__() + self.process_cmd = process_cmd + self.functors = [] + for cmd in self.process_cmd: + if cmd == "a": # add residual connection + self.functors.append(lambda x, y: x + y if y is not None else x) + elif cmd == "n": # add layer normalization + self.functors.append( + self.add_sublayer( + "layer_norm_%d" % len(self.sublayers()), + paddle.nn.LayerNorm( + normalized_shape=d_model, + weight_attr=paddle.ParamAttr( + initializer=paddle.nn.initializer.Constant(1.0) + ), + bias_attr=paddle.ParamAttr( + initializer=paddle.nn.initializer.Constant(0.0) + ), + ), + ) + ) + elif cmd == "d": # add dropout + self.functors.append( + lambda x: ( + F.dropout(x, p=dropout_rate, mode="downscale_in_infer") + if dropout_rate + else x + ) + ) + + def forward(self, x, residual=None): + for i, cmd in enumerate(self.process_cmd): + if cmd == "a": + x = self.functors[i](x, residual) + else: + x = self.functors[i](x) + return x + + +class PrepareEncoder(nn.Layer): + def __init__( + self, + src_vocab_size, + src_emb_dim, + src_max_len, + dropout_rate=0, + bos_idx=0, + word_emb_param_name=None, + pos_enc_param_name=None, + ): + super(PrepareEncoder, self).__init__() + self.src_emb_dim = src_emb_dim + self.src_max_len = src_max_len + self.emb = paddle.nn.Embedding( + num_embeddings=self.src_max_len, embedding_dim=self.src_emb_dim + ) + self.dropout_rate = dropout_rate + + def forward(self, src_word, src_pos): + src_word_emb = src_word + src_word_emb = paddle.cast(src_word_emb, "float32") + src_word_emb = paddle.scale(x=src_word_emb, scale=self.src_emb_dim**0.5) + src_pos = paddle.squeeze(src_pos, axis=-1) + src_pos_enc = self.emb(src_pos) + src_pos_enc.stop_gradient = True + enc_input = src_word_emb + src_pos_enc + if self.dropout_rate: + out = F.dropout(x=enc_input, p=self.dropout_rate, mode="downscale_in_infer") + else: + out = enc_input + return out + + +class PrepareDecoder(nn.Layer): + def __init__( + self, + src_vocab_size, + src_emb_dim, + src_max_len, + dropout_rate=0, + bos_idx=0, + word_emb_param_name=None, + pos_enc_param_name=None, + ): + super(PrepareDecoder, self).__init__() + self.src_emb_dim = src_emb_dim + """ + self.emb0 = Embedding(num_embeddings=src_vocab_size, + embedding_dim=src_emb_dim) + """ + self.emb0 = paddle.nn.Embedding( + num_embeddings=src_vocab_size, + embedding_dim=self.src_emb_dim, + padding_idx=bos_idx, + weight_attr=paddle.ParamAttr( + name=word_emb_param_name, + initializer=nn.initializer.Normal(0.0, src_emb_dim**-0.5), + ), + ) + self.emb1 = paddle.nn.Embedding( + num_embeddings=src_max_len, + embedding_dim=self.src_emb_dim, + weight_attr=paddle.ParamAttr(name=pos_enc_param_name), + ) + self.dropout_rate = dropout_rate + + def forward(self, src_word, src_pos): + src_word = paddle.cast(src_word, "int64") + src_word = paddle.squeeze(src_word, axis=-1) + src_word_emb = self.emb0(src_word) + src_word_emb = paddle.scale(x=src_word_emb, scale=self.src_emb_dim**0.5) + src_pos = paddle.squeeze(src_pos, axis=-1) + src_pos_enc = self.emb1(src_pos) + src_pos_enc.stop_gradient = True + enc_input = src_word_emb + src_pos_enc + if self.dropout_rate: + out = F.dropout(x=enc_input, p=self.dropout_rate, mode="downscale_in_infer") + else: + out = enc_input + return out + + +class FFN(nn.Layer): + """ + Feed-Forward Network + """ + + def __init__(self, d_inner_hid, d_model, dropout_rate): + super(FFN, self).__init__() + self.dropout_rate = dropout_rate + self.fc1 = paddle.nn.Linear(in_features=d_model, out_features=d_inner_hid) + self.fc2 = paddle.nn.Linear(in_features=d_inner_hid, out_features=d_model) + + def forward(self, x): + hidden = self.fc1(x) + hidden = F.relu(hidden) + if self.dropout_rate: + hidden = F.dropout(hidden, p=self.dropout_rate, mode="downscale_in_infer") + out = self.fc2(hidden) + return out diff --git a/ppocr/modeling/heads/sr_rensnet_transformer.py b/ppocr/modeling/heads/sr_rensnet_transformer.py new file mode 100644 index 0000000..dcb8bfb --- /dev/null +++ b/ppocr/modeling/heads/sr_rensnet_transformer.py @@ -0,0 +1,427 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/FudanVI/FudanOCR/blob/main/text-gestalt/loss/transformer_english_decomposition.py +""" +import copy +import math + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F + + +def subsequent_mask(size): + """Generate a square mask for the sequence. The masked positions are filled with float('-inf'). + Unmasked positions are filled with float(0.0). + """ + mask = paddle.ones([1, size, size], dtype="float32") + mask_inf = paddle.triu( + paddle.full(shape=[1, size, size], dtype="float32", fill_value="-inf"), + diagonal=1, + ) + mask = mask + mask_inf + padding_mask = paddle.equal(mask, paddle.to_tensor(1, dtype=mask.dtype)) + return padding_mask + + +def clones(module, N): + return nn.LayerList([copy.deepcopy(module) for _ in range(N)]) + + +def masked_fill(x, mask, value): + y = paddle.full(x.shape, value, x.dtype) + return paddle.where(mask, y, x) + + +def attention(query, key, value, mask=None, dropout=None, attention_map=None): + d_k = query.shape[-1] + scores = paddle.matmul(query, paddle.transpose(key, [0, 1, 3, 2])) / math.sqrt(d_k) + + if mask is not None: + scores = masked_fill(scores, mask == 0, float("-inf")) + else: + pass + + p_attn = F.softmax(scores, axis=-1) + + if dropout is not None: + p_attn = dropout(p_attn) + return paddle.matmul(p_attn, value), p_attn + + +class MultiHeadedAttention(nn.Layer): + def __init__(self, h, d_model, dropout=0.1, compress_attention=False): + super(MultiHeadedAttention, self).__init__() + assert d_model % h == 0 + self.d_k = d_model // h + self.h = h + self.linears = clones(nn.Linear(d_model, d_model), 4) + self.attn = None + self.dropout = nn.Dropout(p=dropout, mode="downscale_in_infer") + self.compress_attention = compress_attention + self.compress_attention_linear = nn.Linear(h, 1) + + def forward(self, query, key, value, mask=None, attention_map=None): + if mask is not None: + mask = mask.unsqueeze(1) + nbatches = query.shape[0] + + query, key, value = [ + paddle.transpose( + l(x).reshape([nbatches, -1, self.h, self.d_k]), [0, 2, 1, 3] + ) + for l, x in zip(self.linears, (query, key, value)) + ] + + x, attention_map = attention( + query, + key, + value, + mask=mask, + dropout=self.dropout, + attention_map=attention_map, + ) + + x = paddle.reshape( + paddle.transpose(x, [0, 2, 1, 3]), [nbatches, -1, self.h * self.d_k] + ) + + return self.linears[-1](x), attention_map + + +class ResNet(nn.Layer): + def __init__(self, num_in, block, layers): + super(ResNet, self).__init__() + + self.conv1 = nn.Conv2D(num_in, 64, kernel_size=3, stride=1, padding=1) + self.bn1 = nn.BatchNorm2D(64, use_global_stats=True) + self.relu1 = nn.ReLU() + self.pool = nn.MaxPool2D((2, 2), (2, 2)) + + self.conv2 = nn.Conv2D(64, 128, kernel_size=3, stride=1, padding=1) + self.bn2 = nn.BatchNorm2D(128, use_global_stats=True) + self.relu2 = nn.ReLU() + + self.layer1_pool = nn.MaxPool2D((2, 2), (2, 2)) + self.layer1 = self._make_layer(block, 128, 256, layers[0]) + self.layer1_conv = nn.Conv2D(256, 256, 3, 1, 1) + self.layer1_bn = nn.BatchNorm2D(256, use_global_stats=True) + self.layer1_relu = nn.ReLU() + + self.layer2_pool = nn.MaxPool2D((2, 2), (2, 2)) + self.layer2 = self._make_layer(block, 256, 256, layers[1]) + self.layer2_conv = nn.Conv2D(256, 256, 3, 1, 1) + self.layer2_bn = nn.BatchNorm2D(256, use_global_stats=True) + self.layer2_relu = nn.ReLU() + + self.layer3_pool = nn.MaxPool2D((2, 2), (2, 2)) + self.layer3 = self._make_layer(block, 256, 512, layers[2]) + self.layer3_conv = nn.Conv2D(512, 512, 3, 1, 1) + self.layer3_bn = nn.BatchNorm2D(512, use_global_stats=True) + self.layer3_relu = nn.ReLU() + + self.layer4_pool = nn.MaxPool2D((2, 2), (2, 2)) + self.layer4 = self._make_layer(block, 512, 512, layers[3]) + self.layer4_conv2 = nn.Conv2D(512, 1024, 3, 1, 1) + self.layer4_conv2_bn = nn.BatchNorm2D(1024, use_global_stats=True) + self.layer4_conv2_relu = nn.ReLU() + + def _make_layer(self, block, inplanes, planes, blocks): + if inplanes != planes: + downsample = nn.Sequential( + nn.Conv2D(inplanes, planes, 3, 1, 1), + nn.BatchNorm2D(planes, use_global_stats=True), + ) + else: + downsample = None + layers = [] + layers.append(block(inplanes, planes, downsample)) + for i in range(1, blocks): + layers.append(block(planes, planes, downsample=None)) + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu1(x) + x = self.pool(x) + + x = self.conv2(x) + x = self.bn2(x) + x = self.relu2(x) + + x = self.layer1_pool(x) + x = self.layer1(x) + x = self.layer1_conv(x) + x = self.layer1_bn(x) + x = self.layer1_relu(x) + + x = self.layer2(x) + x = self.layer2_conv(x) + x = self.layer2_bn(x) + x = self.layer2_relu(x) + + x = self.layer3(x) + x = self.layer3_conv(x) + x = self.layer3_bn(x) + x = self.layer3_relu(x) + + x = self.layer4(x) + x = self.layer4_conv2(x) + x = self.layer4_conv2_bn(x) + x = self.layer4_conv2_relu(x) + + return x + + +class Bottleneck(nn.Layer): + def __init__(self, input_dim): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2D(input_dim, input_dim, 1) + self.bn1 = nn.BatchNorm2D(input_dim, use_global_stats=True) + self.relu = nn.ReLU() + + self.conv2 = nn.Conv2D(input_dim, input_dim, 3, 1, 1) + self.bn2 = nn.BatchNorm2D(input_dim, use_global_stats=True) + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + out += residual + out = self.relu(out) + + return out + + +class PositionalEncoding(nn.Layer): + "Implement the PE function." + + def __init__(self, dropout, dim, max_len=5000): + super(PositionalEncoding, self).__init__() + self.dropout = nn.Dropout(p=dropout, mode="downscale_in_infer") + + pe = paddle.zeros([max_len, dim]) + position = paddle.arange(0, max_len, dtype=paddle.float32).unsqueeze(1) + div_term = paddle.exp( + paddle.arange(0, dim, 2).astype("float32") * (-math.log(10000.0) / dim) + ) + pe[:, 0::2] = paddle.sin(position * div_term) + pe[:, 1::2] = paddle.cos(position * div_term) + pe = paddle.unsqueeze(pe, 0) + self.register_buffer("pe", pe) + + def forward(self, x): + x = x + self.pe[:, : x.shape[1]] + return self.dropout(x) + + +class PositionwiseFeedForward(nn.Layer): + "Implements FFN equation." + + def __init__(self, d_model, d_ff, dropout=0.1): + super(PositionwiseFeedForward, self).__init__() + self.w_1 = nn.Linear(d_model, d_ff) + self.w_2 = nn.Linear(d_ff, d_model) + self.dropout = nn.Dropout(dropout, mode="downscale_in_infer") + + def forward(self, x): + return self.w_2(self.dropout(F.relu(self.w_1(x)))) + + +class Generator(nn.Layer): + "Define standard linear + softmax generation step." + + def __init__(self, d_model, vocab): + super(Generator, self).__init__() + self.proj = nn.Linear(d_model, vocab) + self.relu = nn.ReLU() + + def forward(self, x): + out = self.proj(x) + return out + + +class Embeddings(nn.Layer): + def __init__(self, d_model, vocab): + super(Embeddings, self).__init__() + self.lut = nn.Embedding(vocab, d_model) + self.d_model = d_model + + def forward(self, x): + embed = self.lut(x) * math.sqrt(self.d_model) + return embed + + +class LayerNorm(nn.Layer): + "Construct a layernorm module (See citation for details)." + + def __init__(self, features, eps=1e-6): + super(LayerNorm, self).__init__() + self.a_2 = self.create_parameter( + shape=[features], default_initializer=paddle.nn.initializer.Constant(1.0) + ) + self.b_2 = self.create_parameter( + shape=[features], default_initializer=paddle.nn.initializer.Constant(0.0) + ) + self.eps = eps + + def forward(self, x): + mean = x.mean(-1, keepdim=True) + std = x.std(-1, keepdim=True) + return self.a_2 * (x - mean) / (std + self.eps) + self.b_2 + + +class Decoder(nn.Layer): + def __init__(self): + super(Decoder, self).__init__() + + self.mask_multihead = MultiHeadedAttention(h=16, d_model=1024, dropout=0.1) + self.mul_layernorm1 = LayerNorm(1024) + + self.multihead = MultiHeadedAttention(h=16, d_model=1024, dropout=0.1) + self.mul_layernorm2 = LayerNorm(1024) + + self.pff = PositionwiseFeedForward(1024, 2048) + self.mul_layernorm3 = LayerNorm(1024) + + def forward(self, text, conv_feature, attention_map=None): + text_max_length = text.shape[1] + mask = subsequent_mask(text_max_length) + result = text + result = self.mul_layernorm1( + result + self.mask_multihead(text, text, text, mask=mask)[0] + ) + b, c, h, w = conv_feature.shape + conv_feature = paddle.transpose(conv_feature.reshape([b, c, h * w]), [0, 2, 1]) + word_image_align, attention_map = self.multihead( + result, conv_feature, conv_feature, mask=None, attention_map=attention_map + ) + result = self.mul_layernorm2(result + word_image_align) + result = self.mul_layernorm3(result + self.pff(result)) + + return result, attention_map + + +class BasicBlock(nn.Layer): + def __init__(self, inplanes, planes, downsample): + super(BasicBlock, self).__init__() + self.conv1 = nn.Conv2D(inplanes, planes, kernel_size=3, stride=1, padding=1) + self.bn1 = nn.BatchNorm2D(planes, use_global_stats=True) + self.relu = nn.ReLU() + self.conv2 = nn.Conv2D(planes, planes, kernel_size=3, stride=1, padding=1) + self.bn2 = nn.BatchNorm2D(planes, use_global_stats=True) + self.downsample = downsample + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample != None: + residual = self.downsample(residual) + + out += residual + out = self.relu(out) + + return out + + +class Encoder(nn.Layer): + def __init__(self): + super(Encoder, self).__init__() + self.cnn = ResNet(num_in=1, block=BasicBlock, layers=[1, 2, 5, 3]) + + def forward(self, input): + conv_result = self.cnn(input) + return conv_result + + +class Transformer(nn.Layer): + def __init__(self, in_channels=1, alphabet="0123456789"): + super(Transformer, self).__init__() + self.alphabet = alphabet + word_n_class = self.get_alphabet_len() + self.embedding_word_with_upperword = Embeddings(512, word_n_class) + self.pe = PositionalEncoding(dim=512, dropout=0.1, max_len=5000) + + self.encoder = Encoder() + self.decoder = Decoder() + self.generator_word_with_upperword = Generator(1024, word_n_class) + + for p in self.parameters(): + if p.dim() > 1: + nn.initializer.XavierNormal(p) + + def get_alphabet_len(self): + return len(self.alphabet) + + def forward(self, image, text_length, text_input, attention_map=None): + if image.shape[1] == 3: + R = image[:, 0:1, :, :] + G = image[:, 1:2, :, :] + B = image[:, 2:3, :, :] + image = 0.299 * R + 0.587 * G + 0.114 * B + + conv_feature = self.encoder(image) # batch, 1024, 8, 32 + max_length = max(text_length) + text_input = text_input[:, :max_length] + + text_embedding = self.embedding_word_with_upperword( + text_input + ) # batch, text_max_length, 512 + postion_embedding = self.pe( + paddle.zeros(text_embedding.shape) + ) # batch, text_max_length, 512 + text_input_with_pe = paddle.concat( + [text_embedding, postion_embedding], 2 + ) # batch, text_max_length, 1024 + batch, seq_len, _ = text_input_with_pe.shape + + text_input_with_pe, word_attention_map = self.decoder( + text_input_with_pe, conv_feature + ) + + word_decoder_result = self.generator_word_with_upperword(text_input_with_pe) + + if self.training: + total_length = paddle.sum(text_length) + probs_res = paddle.zeros([total_length, self.get_alphabet_len()]) + start = 0 + + for index, length in enumerate(text_length): + length = int(length.numpy()) + probs_res[start : start + length, :] = word_decoder_result[ + index, 0 : 0 + length, : + ] + + start = start + length + + return probs_res, word_attention_map, None + else: + return word_decoder_result diff --git a/ppocr/modeling/heads/table_att_head.py b/ppocr/modeling/heads/table_att_head.py new file mode 100644 index 0000000..1f43af1 --- /dev/null +++ b/ppocr/modeling/heads/table_att_head.py @@ -0,0 +1,421 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +import paddle.nn as nn +from paddle import ParamAttr +import paddle.nn.functional as F +import numpy as np + +from .rec_att_head import AttentionGRUCell +from ppocr.modeling.backbones.rec_svtrnet import DropPath, Identity, Mlp + + +def get_para_bias_attr(l2_decay, k): + if l2_decay > 0: + regularizer = paddle.regularizer.L2Decay(l2_decay) + stdv = 1.0 / math.sqrt(k * 1.0) + initializer = nn.initializer.Uniform(-stdv, stdv) + else: + regularizer = None + initializer = None + weight_attr = ParamAttr(regularizer=regularizer, initializer=initializer) + bias_attr = ParamAttr(regularizer=regularizer, initializer=initializer) + return [weight_attr, bias_attr] + + +class TableAttentionHead(nn.Layer): + def __init__( + self, + in_channels, + hidden_size, + in_max_len=488, + max_text_length=800, + out_channels=30, + loc_reg_num=4, + **kwargs, + ): + super(TableAttentionHead, self).__init__() + self.input_size = in_channels[-1] + self.hidden_size = hidden_size + self.out_channels = out_channels + self.max_text_length = max_text_length + + self.structure_attention_cell = AttentionGRUCell( + self.input_size, hidden_size, self.out_channels, use_gru=False + ) + self.structure_generator = nn.Linear(hidden_size, self.out_channels) + self.in_max_len = in_max_len + + if self.in_max_len == 640: + self.loc_fea_trans = nn.Linear(400, self.max_text_length + 1) + elif self.in_max_len == 800: + self.loc_fea_trans = nn.Linear(625, self.max_text_length + 1) + else: + self.loc_fea_trans = nn.Linear(256, self.max_text_length + 1) + self.loc_generator = nn.Linear(self.input_size + hidden_size, loc_reg_num) + + def _char_to_onehot(self, input_char, onehot_dim): + input_ont_hot = F.one_hot(input_char, onehot_dim) + return input_ont_hot + + def forward(self, inputs, targets=None): + # if and else branch are both needed when you want to assign a variable + # if you modify the var in just one branch, then the modification will not work. + fea = inputs[-1] + last_shape = int(np.prod(fea.shape[2:])) # gry added + fea = paddle.reshape(fea, [fea.shape[0], fea.shape[1], last_shape]) + fea = fea.transpose([0, 2, 1]) # (NTC)(batch, width, channels) + batch_size = fea.shape[0] + + hidden = paddle.zeros((batch_size, self.hidden_size)) + output_hiddens = paddle.zeros( + (batch_size, self.max_text_length + 1, self.hidden_size) + ) + if self.training and targets is not None: + structure = targets[0] + for i in range(self.max_text_length + 1): + elem_onehots = self._char_to_onehot( + structure[:, i], onehot_dim=self.out_channels + ) + (outputs, hidden), alpha = self.structure_attention_cell( + hidden, fea, elem_onehots + ) + output_hiddens[:, i, :] = outputs + structure_probs = self.structure_generator(output_hiddens) + loc_fea = fea.transpose([0, 2, 1]) + loc_fea = self.loc_fea_trans(loc_fea) + loc_fea = loc_fea.transpose([0, 2, 1]) + loc_concat = paddle.concat([output_hiddens, loc_fea], axis=2) + loc_preds = self.loc_generator(loc_concat) + loc_preds = F.sigmoid(loc_preds) + else: + temp_elem = paddle.zeros(shape=[batch_size], dtype="int32") + structure_probs = None + loc_preds = None + elem_onehots = None + outputs = None + alpha = None + max_text_length = paddle.to_tensor(self.max_text_length) + for i in range(max_text_length + 1): + elem_onehots = self._char_to_onehot( + temp_elem, onehot_dim=self.out_channels + ) + (outputs, hidden), alpha = self.structure_attention_cell( + hidden, fea, elem_onehots + ) + output_hiddens[:, i, :] = outputs + structure_probs_step = self.structure_generator(outputs) + temp_elem = structure_probs_step.argmax(axis=1, dtype="int32") + + structure_probs = self.structure_generator(output_hiddens) + structure_probs = F.softmax(structure_probs) + loc_fea = fea.transpose([0, 2, 1]) + loc_fea = self.loc_fea_trans(loc_fea) + loc_fea = loc_fea.transpose([0, 2, 1]) + loc_concat = paddle.concat([output_hiddens, loc_fea], axis=2) + loc_preds = self.loc_generator(loc_concat) + loc_preds = F.sigmoid(loc_preds) + return {"structure_probs": structure_probs, "loc_preds": loc_preds} + + +class HWAttention(nn.Layer): + def __init__( + self, + head_dim=32, + qk_scale=None, + attn_drop=0.0, + ): + super().__init__() + self.head_dim = head_dim + self.scale = qk_scale or self.head_dim**-0.5 + self.attn_drop = nn.Dropout(attn_drop) + + def forward(self, x): + B, N, C = x.shape + C = C // 3 + qkv = x.reshape([B, N, 3, C // self.head_dim, self.head_dim]).transpose( + [2, 0, 3, 1, 4] + ) + q, k, v = qkv.unbind(0) + attn = q @ k.transpose([0, 1, 3, 2]) * self.scale + attn = F.softmax(attn, -1) + attn = self.attn_drop(attn) + x = attn @ v + x = x.transpose([0, 2, 1]).reshape([B, N, C]) + return x + + +def img2windows(img, H_sp, W_sp): + """ + img: B C H W + """ + B, H, W, C = img.shape + img_reshape = img.reshape([B, H // H_sp, H_sp, W // W_sp, W_sp, C]) + img_perm = img_reshape.transpose([0, 1, 3, 2, 4, 5]).reshape([-1, H_sp * W_sp, C]) + return img_perm + + +def windows2img(img_splits_hw, H_sp, W_sp, H, W): + """ + img_splits_hw: B' H W C + """ + B = int(img_splits_hw.shape[0] / (H * W / H_sp / W_sp)) + + img = img_splits_hw.reshape([B, H // H_sp, W // W_sp, H_sp, W_sp, -1]) + img = img.transpose([0, 1, 3, 2, 4, 5]).flatten(1, 4) + return img + + +class Block(nn.Layer): + def __init__( + self, + dim, + num_heads, + split_h=4, + split_w=4, + h_num_heads=None, + w_num_heads=None, + mlp_ratio=4.0, + qkv_bias=False, + qk_scale=None, + drop=0.0, + attn_drop=0.0, + drop_path=0.0, + act_layer=nn.GELU, + norm_layer=nn.LayerNorm, + eps=1e-6, + ): + super().__init__() + self.qkv = nn.Linear(dim, dim * 3, bias_attr=qkv_bias) + self.proj = nn.Linear(dim, dim) + self.split_h = split_h + self.split_w = split_w + mlp_hidden_dim = int(dim * mlp_ratio) + self.norm1 = norm_layer(dim, epsilon=eps) + self.h_num_heads = h_num_heads if h_num_heads is not None else num_heads // 2 + self.w_num_heads = w_num_heads if w_num_heads is not None else num_heads // 2 + self.head_dim = dim // num_heads + self.mixer = HWAttention( + head_dim=dim // num_heads, + qk_scale=qk_scale, + attn_drop=attn_drop, + ) + self.drop_path = DropPath(drop_path) if drop_path > 0.0 else Identity() + self.norm2 = norm_layer(dim, epsilon=eps) + self.mlp = Mlp( + in_features=dim, + hidden_features=mlp_hidden_dim, + act_layer=act_layer, + drop=drop, + ) + + def forward(self, x): + B, C, H, W = x.shape + x = x.flatten(2).transpose([0, 2, 1]) + + qkv = self.qkv(x).reshape([B, H, W, 3 * C]) + + x1 = qkv[:, :, :, : 3 * self.h_num_heads * self.head_dim] # b, h, w, 3ch + x2 = qkv[:, :, :, 3 * self.h_num_heads * self.head_dim :] # b, h, w, 3cw + + x1 = self.mixer(img2windows(x1, self.split_h, W)) # b*splith, W, 3ch + x2 = self.mixer(img2windows(x2, H, self.split_w)) # b*splitw, h, 3ch + x1 = windows2img(x1, self.split_h, W, H, W) + x2 = windows2img(x2, H, self.split_w, H, W) + + attened_x = paddle.concat([x1, x2], 2) + attened_x = self.proj(attened_x) + + x = self.norm1(x + self.drop_path(attened_x)) + x = self.norm2(x + self.drop_path(self.mlp(x))) + x = x.transpose([0, 2, 1]).reshape([-1, C, H, W]) + return x + + +class SLAHead(nn.Layer): + def __init__( + self, + in_channels, + hidden_size, + out_channels=30, + max_text_length=500, + loc_reg_num=4, + fc_decay=0.0, + use_attn=False, + **kwargs, + ): + """ + @param in_channels: input shape + @param hidden_size: hidden_size for RNN and Embedding + @param out_channels: num_classes to rec + @param max_text_length: max text pred + """ + super().__init__() + + if isinstance(in_channels, int): + self.is_next = True + in_channels = 512 + else: + self.is_next = False + in_channels = in_channels[-1] + self.hidden_size = hidden_size + self.max_text_length = max_text_length + self.emb = self._char_to_onehot + self.num_embeddings = out_channels + self.loc_reg_num = loc_reg_num + self.eos = self.num_embeddings - 1 + + # structure + self.structure_attention_cell = AttentionGRUCell( + in_channels, hidden_size, self.num_embeddings + ) + weight_attr, bias_attr = get_para_bias_attr(l2_decay=fc_decay, k=hidden_size) + weight_attr1_1, bias_attr1_1 = get_para_bias_attr( + l2_decay=fc_decay, k=hidden_size + ) + weight_attr1_2, bias_attr1_2 = get_para_bias_attr( + l2_decay=fc_decay, k=hidden_size + ) + self.structure_generator = nn.Sequential( + nn.Linear( + self.hidden_size, + self.hidden_size, + weight_attr=weight_attr1_2, + bias_attr=bias_attr1_2, + ), + nn.Linear( + hidden_size, out_channels, weight_attr=weight_attr, bias_attr=bias_attr + ), + ) + dpr = np.linspace(0, 0.1, 2) + + self.use_attn = use_attn + if use_attn: + layer_list = [ + Block( + in_channels, + num_heads=2, + mlp_ratio=4.0, + qkv_bias=True, + drop_path=dpr[i], + ) + for i in range(2) + ] + self.cross_atten = nn.Sequential(*layer_list) + # loc + weight_attr1, bias_attr1 = get_para_bias_attr( + l2_decay=fc_decay, k=self.hidden_size + ) + weight_attr2, bias_attr2 = get_para_bias_attr( + l2_decay=fc_decay, k=self.hidden_size + ) + self.loc_generator = nn.Sequential( + nn.Linear( + self.hidden_size, + self.hidden_size, + weight_attr=weight_attr1, + bias_attr=bias_attr1, + ), + nn.Linear( + self.hidden_size, + loc_reg_num, + weight_attr=weight_attr2, + bias_attr=bias_attr2, + ), + nn.Sigmoid(), + ) + + def forward(self, inputs, targets=None): + if self.is_next == True: + fea = inputs + batch_size = fea.shape[0] + else: + fea = inputs[-1] + batch_size = fea.shape[0] + if self.use_attn: + fea = fea + self.cross_atten(fea) + # reshape + fea = paddle.reshape(fea, [fea.shape[0], fea.shape[1], -1]) + fea = fea.transpose([0, 2, 1]) # (NTC)(batch, width, channels) + + hidden = paddle.zeros((batch_size, self.hidden_size)) + structure_preds = paddle.zeros( + (batch_size, self.max_text_length + 1, self.num_embeddings) + ) + loc_preds = paddle.zeros( + (batch_size, self.max_text_length + 1, self.loc_reg_num) + ) + structure_preds.stop_gradient = True + loc_preds.stop_gradient = True + + if self.training and targets is not None: + structure = targets[0] + max_len = targets[-2].max().astype("int32") + for i in range(max_len + 1): + hidden, structure_step, loc_step = self._decode( + structure[:, i], fea, hidden + ) + structure_preds[:, i, :] = structure_step + loc_preds[:, i, :] = loc_step + structure_preds = structure_preds[:, : max_len + 1] + loc_preds = loc_preds[:, : max_len + 1] + else: + structure_ids = paddle.zeros( + (batch_size, self.max_text_length + 1), dtype="int32" + ) + pre_chars = paddle.zeros(shape=[batch_size], dtype="int32") + max_text_length = paddle.to_tensor(self.max_text_length) + for i in range(max_text_length + 1): + hidden, structure_step, loc_step = self._decode(pre_chars, fea, hidden) + pre_chars = structure_step.argmax(axis=1, dtype="int32") + structure_preds[:, i, :] = structure_step + loc_preds[:, i, :] = loc_step + + structure_ids[:, i] = pre_chars + if (structure_ids == self.eos).any(-1).all(): + break + if not self.training: + structure_preds = F.softmax(structure_preds[:, : i + 1]) + loc_preds = loc_preds[:, : i + 1] + return {"structure_probs": structure_preds, "loc_preds": loc_preds} + + def _decode(self, pre_chars, features, hidden): + """ + Predict table label and coordinates for each step + @param pre_chars: Table label in previous step + @param features: + @param hidden: hidden status in previous step + @return: + """ + emb_feature = self.emb(pre_chars) + # output shape is b * self.hidden_size + (output, hidden), alpha = self.structure_attention_cell( + hidden, features, emb_feature + ) + + # structure + structure_step = self.structure_generator(output) + # loc + loc_step = self.loc_generator(output) + return hidden, structure_step, loc_step + + def _char_to_onehot(self, input_char): + input_ont_hot = F.one_hot(input_char, self.num_embeddings) + return input_ont_hot diff --git a/ppocr/modeling/heads/table_master_head.py b/ppocr/modeling/heads/table_master_head.py new file mode 100644 index 0000000..66ad7a4 --- /dev/null +++ b/ppocr/modeling/heads/table_master_head.py @@ -0,0 +1,285 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/JiaquanYe/TableMASTER-mmocr/blob/master/mmocr/models/textrecog/decoders/master_decoder.py +""" + +import copy +import math +import paddle +from paddle import nn +from paddle.nn import functional as F + + +class TableMasterHead(nn.Layer): + """ + Split to two transformer header at the last layer. + Cls_layer is used to structure token classification. + Bbox_layer is used to regress bbox coord. + """ + + def __init__( + self, + in_channels, + out_channels=30, + headers=8, + d_ff=2048, + dropout=0, + max_text_length=500, + loc_reg_num=4, + **kwargs, + ): + super(TableMasterHead, self).__init__() + hidden_size = in_channels[-1] + self.layers = clones(DecoderLayer(headers, hidden_size, dropout, d_ff), 2) + self.cls_layer = clones(DecoderLayer(headers, hidden_size, dropout, d_ff), 1) + self.bbox_layer = clones(DecoderLayer(headers, hidden_size, dropout, d_ff), 1) + self.cls_fc = nn.Linear(hidden_size, out_channels) + self.bbox_fc = nn.Sequential( + # nn.Linear(hidden_size, hidden_size), + nn.Linear(hidden_size, loc_reg_num), + nn.Sigmoid(), + ) + self.norm = nn.LayerNorm(hidden_size) + self.embedding = Embeddings(d_model=hidden_size, vocab=out_channels) + self.positional_encoding = PositionalEncoding(d_model=hidden_size) + + self.SOS = out_channels - 3 + self.PAD = out_channels - 1 + self.out_channels = out_channels + self.loc_reg_num = loc_reg_num + self.max_text_length = max_text_length + + def make_mask(self, tgt): + """ + Make mask for self attention. + :param src: [b, c, h, l_src] + :param tgt: [b, l_tgt] + :return: + """ + trg_pad_mask = (tgt != self.PAD).unsqueeze(1).unsqueeze(3) + + tgt_len = tgt.shape[1] + trg_sub_mask = paddle.tril( + paddle.ones(([tgt_len, tgt_len]), dtype=paddle.float32) + ) + + tgt_mask = paddle.logical_and(trg_pad_mask.astype(paddle.float32), trg_sub_mask) + return tgt_mask.astype(paddle.float32) + + def decode(self, input, feature, src_mask, tgt_mask): + # main process of transformer decoder. + x = self.embedding(input) # x: 1*x*512, feature: 1*3600,512 + x = self.positional_encoding(x) + + # origin transformer layers + for i, layer in enumerate(self.layers): + x = layer(x, feature, src_mask, tgt_mask) + + # cls head + cls_x = x + for layer in self.cls_layer: + cls_x = layer(x, feature, src_mask, tgt_mask) + cls_x = self.norm(cls_x) + + # bbox head + bbox_x = x + for layer in self.bbox_layer: + bbox_x = layer(x, feature, src_mask, tgt_mask) + bbox_x = self.norm(bbox_x) + return self.cls_fc(cls_x), self.bbox_fc(bbox_x) + + def greedy_forward(self, SOS, feature): + input = SOS + output = paddle.zeros( + [input.shape[0], self.max_text_length + 1, self.out_channels] + ) + bbox_output = paddle.zeros( + [input.shape[0], self.max_text_length + 1, self.loc_reg_num] + ) + max_text_length = paddle.to_tensor(self.max_text_length) + for i in range(max_text_length + 1): + target_mask = self.make_mask(input) + out_step, bbox_output_step = self.decode(input, feature, None, target_mask) + prob = F.softmax(out_step, axis=-1) + next_word = prob.argmax(axis=2, dtype="int64") + input = paddle.concat([input, next_word[:, -1].unsqueeze(-1)], axis=1) + if i == self.max_text_length: + output = out_step + bbox_output = bbox_output_step + return output, bbox_output + + def forward_train(self, out_enc, targets): + # x is token of label + # feat is feature after backbone before pe. + # out_enc is feature after pe. + padded_targets = targets[0] + src_mask = None + tgt_mask = self.make_mask(padded_targets[:, :-1]) + output, bbox_output = self.decode( + padded_targets[:, :-1], out_enc, src_mask, tgt_mask + ) + return {"structure_probs": output, "loc_preds": bbox_output} + + def forward_test(self, out_enc): + batch_size = out_enc.shape[0] + SOS = paddle.zeros([batch_size, 1], dtype="int64") + self.SOS + output, bbox_output = self.greedy_forward(SOS, out_enc) + output = F.softmax(output) + return {"structure_probs": output, "loc_preds": bbox_output} + + def forward(self, feat, targets=None): + feat = feat[-1] + b, c, h, w = feat.shape + feat = feat.reshape([b, c, h * w]) # flatten 2D feature map + feat = feat.transpose((0, 2, 1)) + out_enc = self.positional_encoding(feat) + if self.training: + return self.forward_train(out_enc, targets) + + return self.forward_test(out_enc) + + +class DecoderLayer(nn.Layer): + """ + Decoder is made of self attention, source attention and feed forward. + """ + + def __init__(self, headers, d_model, dropout, d_ff): + super(DecoderLayer, self).__init__() + self.self_attn = MultiHeadAttention(headers, d_model, dropout) + self.src_attn = MultiHeadAttention(headers, d_model, dropout) + self.feed_forward = FeedForward(d_model, d_ff, dropout) + self.sublayer = clones(SubLayerConnection(d_model, dropout), 3) + + def forward(self, x, feature, src_mask, tgt_mask): + x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask)) + x = self.sublayer[1](x, lambda x: self.src_attn(x, feature, feature, src_mask)) + return self.sublayer[2](x, self.feed_forward) + + +class MultiHeadAttention(nn.Layer): + def __init__(self, headers, d_model, dropout): + super(MultiHeadAttention, self).__init__() + + assert d_model % headers == 0 + self.d_k = int(d_model / headers) + self.headers = headers + self.linears = clones(nn.Linear(d_model, d_model), 4) + self.attn = None + self.dropout = nn.Dropout(dropout) + + def forward(self, query, key, value, mask=None): + B = query.shape[0] + + # 1) Do all the linear projections in batch from d_model => h x d_k + query, key, value = [ + l(x).reshape([B, 0, self.headers, self.d_k]).transpose([0, 2, 1, 3]) + for l, x in zip(self.linears, (query, key, value)) + ] + # 2) Apply attention on all the projected vectors in batch + x, self.attn = self_attention( + query, key, value, mask=mask, dropout=self.dropout + ) + x = x.transpose([0, 2, 1, 3]).reshape([B, 0, self.headers * self.d_k]) + return self.linears[-1](x) + + +class FeedForward(nn.Layer): + def __init__(self, d_model, d_ff, dropout): + super(FeedForward, self).__init__() + self.w_1 = nn.Linear(d_model, d_ff) + self.w_2 = nn.Linear(d_ff, d_model) + self.dropout = nn.Dropout(dropout) + + def forward(self, x): + return self.w_2(self.dropout(F.relu(self.w_1(x)))) + + +class SubLayerConnection(nn.Layer): + """ + A residual connection followed by a layer norm. + Note for code simplicity the norm is first as opposed to last. + """ + + def __init__(self, size, dropout): + super(SubLayerConnection, self).__init__() + self.norm = nn.LayerNorm(size) + self.dropout = nn.Dropout(dropout) + + def forward(self, x, sublayer): + return x + self.dropout(sublayer(self.norm(x))) + + +def masked_fill(x, mask, value): + mask = mask.astype(x.dtype) + return x * paddle.logical_not(mask).astype(x.dtype) + mask * value + + +def self_attention(query, key, value, mask=None, dropout=None): + """ + Compute 'Scale Dot Product Attention' + """ + d_k = value.shape[-1] + + score = paddle.matmul(query, key.transpose([0, 1, 3, 2]) / math.sqrt(d_k)) + if mask is not None: + # score = score.masked_fill(mask == 0, -1e9) # b, h, L, L + score = masked_fill(score, mask == 0, -6.55e4) # for fp16 + + p_attn = F.softmax(score, axis=-1) + + if dropout is not None: + p_attn = dropout(p_attn) + return paddle.matmul(p_attn, value), p_attn + + +def clones(module, N): + """Produce N identical layers""" + return nn.LayerList([copy.deepcopy(module) for _ in range(N)]) + + +class Embeddings(nn.Layer): + def __init__(self, d_model, vocab): + super(Embeddings, self).__init__() + self.lut = nn.Embedding(vocab, d_model) + self.d_model = d_model + + def forward(self, *input): + x = input[0] + return self.lut(x) * math.sqrt(self.d_model) + + +class PositionalEncoding(nn.Layer): + """Implement the PE function.""" + + def __init__(self, d_model, dropout=0.0, max_len=5000): + super(PositionalEncoding, self).__init__() + self.dropout = nn.Dropout(p=dropout) + + # Compute the positional encodings once in log space. + pe = paddle.zeros([max_len, d_model]) + position = paddle.arange(0, max_len).unsqueeze(1).astype("float32") + div_term = paddle.exp( + paddle.arange(0, d_model, 2) * -math.log(10000.0) / d_model + ) + pe[:, 0::2] = paddle.sin(position * div_term) + pe[:, 1::2] = paddle.cos(position * div_term) + pe = pe.unsqueeze(0) + self.register_buffer("pe", pe) + + def forward(self, feat, **kwargs): + feat = feat + self.pe[:, : feat.shape[1]] # pe 1*5000*512 + return self.dropout(feat) diff --git a/ppocr/modeling/necks/__init__.py b/ppocr/modeling/necks/__init__.py new file mode 100644 index 0000000..bfc6bed --- /dev/null +++ b/ppocr/modeling/necks/__init__.py @@ -0,0 +1,59 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = ["build_neck"] + + +def build_neck(config): + from .db_fpn import DBFPN, RSEFPN, RepLKFPN, LKPAN, RepLKPAN + from .east_fpn import EASTFPN + from .sast_fpn import SASTFPN + from .rnn import SequenceEncoder + from .pg_fpn import PGFPN + from .table_fpn import TableFPN + from .fpn import FPN + from .fce_fpn import FCEFPN + from .pren_fpn import PRENFPN + from .csp_pan import CSPPAN + from .ct_fpn import CTFPN + from .fpn_unet import FPN_UNet + from .rf_adaptor import RFAdaptor + + support_dict = [ + "FPN", + "FCEFPN", + "LKPAN", + "RepLKPAN", + "DBFPN", + "RSEFPN", + "RepLKFPN", + "EASTFPN", + "SASTFPN", + "SequenceEncoder", + "PGFPN", + "TableFPN", + "PRENFPN", + "CSPPAN", + "CTFPN", + "RFAdaptor", + "FPN_UNet", + ] + + module_name = config.pop("name") + assert module_name in support_dict, Exception( + "neck only support {}".format(support_dict) + ) + + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/modeling/necks/csp_pan.py b/ppocr/modeling/necks/csp_pan.py new file mode 100755 index 0000000..5e8464d --- /dev/null +++ b/ppocr/modeling/necks/csp_pan.py @@ -0,0 +1,337 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The code is based on: +# https://github.com/PaddlePaddle/PaddleDetection/blob/release%2F2.3/ppdet/modeling/necks/csp_pan.py + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +from paddle import ParamAttr + +__all__ = ["CSPPAN"] + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channel=96, + out_channel=96, + kernel_size=3, + stride=1, + groups=1, + act="leaky_relu", + ): + super(ConvBNLayer, self).__init__() + initializer = nn.initializer.KaimingUniform() + self.act = act + assert self.act in ["leaky_relu", "hard_swish"] + self.conv = nn.Conv2D( + in_channels=in_channel, + out_channels=out_channel, + kernel_size=kernel_size, + groups=groups, + padding=(kernel_size - 1) // 2, + stride=stride, + weight_attr=ParamAttr(initializer=initializer), + bias_attr=False, + ) + self.bn = nn.BatchNorm2D(out_channel) + + def forward(self, x): + x = self.bn(self.conv(x)) + if self.act == "leaky_relu": + x = F.leaky_relu(x) + elif self.act == "hard_swish": + x = F.hardswish(x) + return x + + +class DPModule(nn.Layer): + """ + Depth-wise and point-wise module. + Args: + in_channel (int): The input channels of this Module. + out_channel (int): The output channels of this Module. + kernel_size (int): The conv2d kernel size of this Module. + stride (int): The conv2d's stride of this Module. + act (str): The activation function of this Module, + Now support `leaky_relu` and `hard_swish`. + """ + + def __init__( + self, in_channel=96, out_channel=96, kernel_size=3, stride=1, act="leaky_relu" + ): + super(DPModule, self).__init__() + initializer = nn.initializer.KaimingUniform() + self.act = act + self.dwconv = nn.Conv2D( + in_channels=in_channel, + out_channels=out_channel, + kernel_size=kernel_size, + groups=out_channel, + padding=(kernel_size - 1) // 2, + stride=stride, + weight_attr=ParamAttr(initializer=initializer), + bias_attr=False, + ) + self.bn1 = nn.BatchNorm2D(out_channel) + self.pwconv = nn.Conv2D( + in_channels=out_channel, + out_channels=out_channel, + kernel_size=1, + groups=1, + padding=0, + weight_attr=ParamAttr(initializer=initializer), + bias_attr=False, + ) + self.bn2 = nn.BatchNorm2D(out_channel) + + def act_func(self, x): + if self.act == "leaky_relu": + x = F.leaky_relu(x) + elif self.act == "hard_swish": + x = F.hardswish(x) + return x + + def forward(self, x): + x = self.act_func(self.bn1(self.dwconv(x))) + x = self.act_func(self.bn2(self.pwconv(x))) + return x + + +class DarknetBottleneck(nn.Layer): + """The basic bottleneck block used in Darknet. + Each Block consists of two ConvModules and the input is added to the + final output. Each ConvModule is composed of Conv, BN, and act. + The first convLayer has filter size of 1x1 and the second one has the + filter size of 3x3. + Args: + in_channels (int): The input channels of this Module. + out_channels (int): The output channels of this Module. + expansion (int): The kernel size of the convolution. Default: 0.5 + add_identity (bool): Whether to add identity to the out. + Default: True + use_depthwise (bool): Whether to use depthwise separable convolution. + Default: False + """ + + def __init__( + self, + in_channels, + out_channels, + kernel_size=3, + expansion=0.5, + add_identity=True, + use_depthwise=False, + act="leaky_relu", + ): + super(DarknetBottleneck, self).__init__() + hidden_channels = int(out_channels * expansion) + conv_func = DPModule if use_depthwise else ConvBNLayer + self.conv1 = ConvBNLayer( + in_channel=in_channels, out_channel=hidden_channels, kernel_size=1, act=act + ) + self.conv2 = conv_func( + in_channel=hidden_channels, + out_channel=out_channels, + kernel_size=kernel_size, + stride=1, + act=act, + ) + self.add_identity = add_identity and in_channels == out_channels + + def forward(self, x): + identity = x + out = self.conv1(x) + out = self.conv2(out) + + if self.add_identity: + return out + identity + else: + return out + + +class CSPLayer(nn.Layer): + """Cross Stage Partial Layer. + Args: + in_channels (int): The input channels of the CSP layer. + out_channels (int): The output channels of the CSP layer. + expand_ratio (float): Ratio to adjust the number of channels of the + hidden layer. Default: 0.5 + num_blocks (int): Number of blocks. Default: 1 + add_identity (bool): Whether to add identity in blocks. + Default: True + use_depthwise (bool): Whether to depthwise separable convolution in + blocks. Default: False + """ + + def __init__( + self, + in_channels, + out_channels, + kernel_size=3, + expand_ratio=0.5, + num_blocks=1, + add_identity=True, + use_depthwise=False, + act="leaky_relu", + ): + super().__init__() + mid_channels = int(out_channels * expand_ratio) + self.main_conv = ConvBNLayer(in_channels, mid_channels, 1, act=act) + self.short_conv = ConvBNLayer(in_channels, mid_channels, 1, act=act) + self.final_conv = ConvBNLayer(2 * mid_channels, out_channels, 1, act=act) + + self.blocks = nn.Sequential( + *[ + DarknetBottleneck( + mid_channels, + mid_channels, + kernel_size, + 1.0, + add_identity, + use_depthwise, + act=act, + ) + for _ in range(num_blocks) + ] + ) + + def forward(self, x): + x_short = self.short_conv(x) + + x_main = self.main_conv(x) + x_main = self.blocks(x_main) + + x_final = paddle.concat((x_main, x_short), axis=1) + return self.final_conv(x_final) + + +class Channel_T(nn.Layer): + def __init__(self, in_channels=[116, 232, 464], out_channels=96, act="leaky_relu"): + super(Channel_T, self).__init__() + self.convs = nn.LayerList() + for i in range(len(in_channels)): + self.convs.append(ConvBNLayer(in_channels[i], out_channels, 1, act=act)) + + def forward(self, x): + outs = [self.convs[i](x[i]) for i in range(len(x))] + return outs + + +class CSPPAN(nn.Layer): + """Path Aggregation Network with CSP module. + Args: + in_channels (List[int]): Number of input channels per scale. + out_channels (int): Number of output channels (used at each scale) + kernel_size (int): The conv2d kernel size of this Module. + num_csp_blocks (int): Number of bottlenecks in CSPLayer. Default: 1 + use_depthwise (bool): Whether to depthwise separable convolution in + blocks. Default: True + """ + + def __init__( + self, + in_channels, + out_channels, + kernel_size=5, + num_csp_blocks=1, + use_depthwise=True, + act="hard_swish", + ): + super(CSPPAN, self).__init__() + self.in_channels = in_channels + self.out_channels = [out_channels] * len(in_channels) + conv_func = DPModule if use_depthwise else ConvBNLayer + + self.conv_t = Channel_T(in_channels, out_channels, act=act) + + # build top-down blocks + self.upsample = nn.Upsample(scale_factor=2, mode="nearest") + self.top_down_blocks = nn.LayerList() + for idx in range(len(in_channels) - 1, 0, -1): + self.top_down_blocks.append( + CSPLayer( + out_channels * 2, + out_channels, + kernel_size=kernel_size, + num_blocks=num_csp_blocks, + add_identity=False, + use_depthwise=use_depthwise, + act=act, + ) + ) + + # build bottom-up blocks + self.downsamples = nn.LayerList() + self.bottom_up_blocks = nn.LayerList() + for idx in range(len(in_channels) - 1): + self.downsamples.append( + conv_func( + out_channels, + out_channels, + kernel_size=kernel_size, + stride=2, + act=act, + ) + ) + self.bottom_up_blocks.append( + CSPLayer( + out_channels * 2, + out_channels, + kernel_size=kernel_size, + num_blocks=num_csp_blocks, + add_identity=False, + use_depthwise=use_depthwise, + act=act, + ) + ) + + def forward(self, inputs): + """ + Args: + inputs (tuple[Tensor]): input features. + Returns: + tuple[Tensor]: CSPPAN features. + """ + assert len(inputs) == len(self.in_channels) + inputs = self.conv_t(inputs) + + # top-down path + inner_outs = [inputs[-1]] + for idx in range(len(self.in_channels) - 1, 0, -1): + feat_heigh = inner_outs[0] + feat_low = inputs[idx - 1] + upsample_feat = F.upsample( + feat_heigh, size=feat_low.shape[2:4], mode="nearest" + ) + + inner_out = self.top_down_blocks[len(self.in_channels) - 1 - idx]( + paddle.concat([upsample_feat, feat_low], 1) + ) + inner_outs.insert(0, inner_out) + + # bottom-up path + outs = [inner_outs[0]] + for idx in range(len(self.in_channels) - 1): + feat_low = outs[-1] + feat_height = inner_outs[idx + 1] + downsample_feat = self.downsamples[idx](feat_low) + out = self.bottom_up_blocks[idx]( + paddle.concat([downsample_feat, feat_height], 1) + ) + outs.append(out) + + return tuple(outs) diff --git a/ppocr/modeling/necks/ct_fpn.py b/ppocr/modeling/necks/ct_fpn.py new file mode 100644 index 0000000..c7d5773 --- /dev/null +++ b/ppocr/modeling/necks/ct_fpn.py @@ -0,0 +1,188 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr +import os +import sys + +import math +from paddle.nn.initializer import TruncatedNormal, Constant, Normal + +ones_ = Constant(value=1.0) +zeros_ = Constant(value=0.0) + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../../.."))) + + +class Conv_BN_ReLU(nn.Layer): + def __init__(self, in_planes, out_planes, kernel_size=1, stride=1, padding=0): + super(Conv_BN_ReLU, self).__init__() + self.conv = nn.Conv2D( + in_planes, + out_planes, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias_attr=False, + ) + self.bn = nn.BatchNorm2D(out_planes) + self.relu = nn.ReLU() + + for m in self.sublayers(): + if isinstance(m, nn.Conv2D): + n = m._kernel_size[0] * m._kernel_size[1] * m._out_channels + normal_ = Normal(mean=0.0, std=math.sqrt(2.0 / n)) + normal_(m.weight) + elif isinstance(m, nn.BatchNorm2D): + zeros_(m.bias) + ones_(m.weight) + + def forward(self, x): + return self.relu(self.bn(self.conv(x))) + + +class FPEM(nn.Layer): + def __init__(self, in_channels, out_channels): + super(FPEM, self).__init__() + planes = out_channels + self.dwconv3_1 = nn.Conv2D( + planes, + planes, + kernel_size=3, + stride=1, + padding=1, + groups=planes, + bias_attr=False, + ) + self.smooth_layer3_1 = Conv_BN_ReLU(planes, planes) + + self.dwconv2_1 = nn.Conv2D( + planes, + planes, + kernel_size=3, + stride=1, + padding=1, + groups=planes, + bias_attr=False, + ) + self.smooth_layer2_1 = Conv_BN_ReLU(planes, planes) + + self.dwconv1_1 = nn.Conv2D( + planes, + planes, + kernel_size=3, + stride=1, + padding=1, + groups=planes, + bias_attr=False, + ) + self.smooth_layer1_1 = Conv_BN_ReLU(planes, planes) + + self.dwconv2_2 = nn.Conv2D( + planes, + planes, + kernel_size=3, + stride=2, + padding=1, + groups=planes, + bias_attr=False, + ) + self.smooth_layer2_2 = Conv_BN_ReLU(planes, planes) + + self.dwconv3_2 = nn.Conv2D( + planes, + planes, + kernel_size=3, + stride=2, + padding=1, + groups=planes, + bias_attr=False, + ) + self.smooth_layer3_2 = Conv_BN_ReLU(planes, planes) + + self.dwconv4_2 = nn.Conv2D( + planes, + planes, + kernel_size=3, + stride=2, + padding=1, + groups=planes, + bias_attr=False, + ) + self.smooth_layer4_2 = Conv_BN_ReLU(planes, planes) + + def _upsample_add(self, x, y): + return F.upsample(x, scale_factor=2, mode="bilinear") + y + + def forward(self, f1, f2, f3, f4): + # up-down + f3 = self.smooth_layer3_1(self.dwconv3_1(self._upsample_add(f4, f3))) + f2 = self.smooth_layer2_1(self.dwconv2_1(self._upsample_add(f3, f2))) + f1 = self.smooth_layer1_1(self.dwconv1_1(self._upsample_add(f2, f1))) + + # down-up + f2 = self.smooth_layer2_2(self.dwconv2_2(self._upsample_add(f2, f1))) + f3 = self.smooth_layer3_2(self.dwconv3_2(self._upsample_add(f3, f2))) + f4 = self.smooth_layer4_2(self.dwconv4_2(self._upsample_add(f4, f3))) + + return f1, f2, f3, f4 + + +class CTFPN(nn.Layer): + def __init__(self, in_channels, out_channel=128): + super(CTFPN, self).__init__() + self.out_channels = out_channel * 4 + + self.reduce_layer1 = Conv_BN_ReLU(in_channels[0], 128) + self.reduce_layer2 = Conv_BN_ReLU(in_channels[1], 128) + self.reduce_layer3 = Conv_BN_ReLU(in_channels[2], 128) + self.reduce_layer4 = Conv_BN_ReLU(in_channels[3], 128) + + self.fpem1 = FPEM(in_channels=(64, 128, 256, 512), out_channels=128) + self.fpem2 = FPEM(in_channels=(64, 128, 256, 512), out_channels=128) + + def _upsample(self, x, scale=1): + return F.upsample(x, scale_factor=scale, mode="bilinear") + + def forward(self, f): + # # reduce channel + f1 = self.reduce_layer1(f[0]) # N,64,160,160 --> N, 128, 160, 160 + f2 = self.reduce_layer2(f[1]) # N, 128, 80, 80 --> N, 128, 80, 80 + f3 = self.reduce_layer3(f[2]) # N, 256, 40, 40 --> N, 128, 40, 40 + f4 = self.reduce_layer4(f[3]) # N, 512, 20, 20 --> N, 128, 20, 20 + + # FPEM + f1_1, f2_1, f3_1, f4_1 = self.fpem1(f1, f2, f3, f4) + f1_2, f2_2, f3_2, f4_2 = self.fpem2(f1_1, f2_1, f3_1, f4_1) + + # FFM + f1 = f1_1 + f1_2 + f2 = f2_1 + f2_2 + f3 = f3_1 + f3_2 + f4 = f4_1 + f4_2 + + f2 = self._upsample(f2, scale=2) + f3 = self._upsample(f3, scale=4) + f4 = self._upsample(f4, scale=8) + ff = paddle.concat((f1, f2, f3, f4), 1) # N,512, 160,160 + return ff diff --git a/ppocr/modeling/necks/db_fpn.py b/ppocr/modeling/necks/db_fpn.py new file mode 100644 index 0000000..f8fe496 --- /dev/null +++ b/ppocr/modeling/necks/db_fpn.py @@ -0,0 +1,1018 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr +import os +import sys +from ppocr.modeling.necks.intracl import IntraCLBlock + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../../.."))) + +from ppocr.modeling.backbones.det_mobilenet_v3 import SEModule + + +class DSConv(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + padding, + stride=1, + groups=None, + if_act=True, + act="relu", + **kwargs, + ): + super(DSConv, self).__init__() + if groups == None: + groups = in_channels + self.if_act = if_act + self.act = act + self.conv1 = nn.Conv2D( + in_channels=in_channels, + out_channels=in_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + groups=groups, + bias_attr=False, + ) + + self.bn1 = nn.BatchNorm(num_channels=in_channels, act=None) + + self.conv2 = nn.Conv2D( + in_channels=in_channels, + out_channels=int(in_channels * 4), + kernel_size=1, + stride=1, + bias_attr=False, + ) + + self.bn2 = nn.BatchNorm(num_channels=int(in_channels * 4), act=None) + + self.conv3 = nn.Conv2D( + in_channels=int(in_channels * 4), + out_channels=out_channels, + kernel_size=1, + stride=1, + bias_attr=False, + ) + self._c = [in_channels, out_channels] + if in_channels != out_channels: + self.conv_end = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + stride=1, + bias_attr=False, + ) + + def forward(self, inputs): + x = self.conv1(inputs) + x = self.bn1(x) + + x = self.conv2(x) + x = self.bn2(x) + if self.if_act: + if self.act == "relu": + x = F.relu(x) + elif self.act == "hardswish": + x = F.hardswish(x) + else: + print( + "The activation function({}) is selected incorrectly.".format( + self.act + ) + ) + exit() + + x = self.conv3(x) + if self._c[0] != self._c[1]: + x = x + self.conv_end(inputs) + return x + + +class DBFPN(nn.Layer): + def __init__(self, in_channels, out_channels, use_asf=False, **kwargs): + super(DBFPN, self).__init__() + self.out_channels = out_channels + self.use_asf = use_asf + weight_attr = paddle.nn.initializer.KaimingUniform() + + self.in2_conv = nn.Conv2D( + in_channels=in_channels[0], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.in3_conv = nn.Conv2D( + in_channels=in_channels[1], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.in4_conv = nn.Conv2D( + in_channels=in_channels[2], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.in5_conv = nn.Conv2D( + in_channels=in_channels[3], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.p5_conv = nn.Conv2D( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.p4_conv = nn.Conv2D( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.p3_conv = nn.Conv2D( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.p2_conv = nn.Conv2D( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + + if self.use_asf is True: + self.asf = ASFBlock(self.out_channels, self.out_channels // 4) + + def forward(self, x): + c2, c3, c4, c5 = x + + in5 = self.in5_conv(c5) + in4 = self.in4_conv(c4) + in3 = self.in3_conv(c3) + in2 = self.in2_conv(c2) + + out4 = in4 + F.upsample( + in5, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/16 + out3 = in3 + F.upsample( + out4, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/8 + out2 = in2 + F.upsample( + out3, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/4 + + p5 = self.p5_conv(in5) + p4 = self.p4_conv(out4) + p3 = self.p3_conv(out3) + p2 = self.p2_conv(out2) + p5 = F.upsample(p5, scale_factor=8, mode="nearest", align_mode=1) + p4 = F.upsample(p4, scale_factor=4, mode="nearest", align_mode=1) + p3 = F.upsample(p3, scale_factor=2, mode="nearest", align_mode=1) + + fuse = paddle.concat([p5, p4, p3, p2], axis=1) + + if self.use_asf is True: + fuse = self.asf(fuse, [p5, p4, p3, p2]) + + return fuse + + +class RSELayer(nn.Layer): + def __init__(self, in_channels, out_channels, kernel_size, shortcut=True): + super(RSELayer, self).__init__() + weight_attr = paddle.nn.initializer.KaimingUniform() + self.out_channels = out_channels + self.in_conv = nn.Conv2D( + in_channels=in_channels, + out_channels=self.out_channels, + kernel_size=kernel_size, + padding=int(kernel_size // 2), + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.se_block = SEModule(self.out_channels) + self.shortcut = shortcut + + def forward(self, ins): + x = self.in_conv(ins) + if self.shortcut: + out = x + self.se_block(x) + else: + out = self.se_block(x) + return out + + +class RSEFPN(nn.Layer): + def __init__(self, in_channels, out_channels, shortcut=True, **kwargs): + super(RSEFPN, self).__init__() + self.out_channels = out_channels + self.ins_conv = nn.LayerList() + self.inp_conv = nn.LayerList() + self.intracl = False + if "intracl" in kwargs.keys() and kwargs["intracl"] is True: + self.intracl = kwargs["intracl"] + self.incl1 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl2 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl3 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl4 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + + for i in range(len(in_channels)): + self.ins_conv.append( + RSELayer(in_channels[i], out_channels, kernel_size=1, shortcut=shortcut) + ) + self.inp_conv.append( + RSELayer( + out_channels, out_channels // 4, kernel_size=3, shortcut=shortcut + ) + ) + + def forward(self, x): + c2, c3, c4, c5 = x + + in5 = self.ins_conv[3](c5) + in4 = self.ins_conv[2](c4) + in3 = self.ins_conv[1](c3) + in2 = self.ins_conv[0](c2) + + out4 = in4 + F.upsample( + in5, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/16 + out3 = in3 + F.upsample( + out4, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/8 + out2 = in2 + F.upsample( + out3, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/4 + + p5 = self.inp_conv[3](in5) + p4 = self.inp_conv[2](out4) + p3 = self.inp_conv[1](out3) + p2 = self.inp_conv[0](out2) + + if self.intracl is True: + p5 = self.incl4(p5) + p4 = self.incl3(p4) + p3 = self.incl2(p3) + p2 = self.incl1(p2) + + p5 = F.upsample(p5, scale_factor=8, mode="nearest", align_mode=1) + p4 = F.upsample(p4, scale_factor=4, mode="nearest", align_mode=1) + p3 = F.upsample(p3, scale_factor=2, mode="nearest", align_mode=1) + + fuse = paddle.concat([p5, p4, p3, p2], axis=1) + return fuse + + +class RepLKFPN(nn.Layer): + """Optimized RSEFPN: replaces 3x3 standard Conv in inp_conv with + DilatedReparamBlock (DW, 5x5) + PWConv 1x1 + SE. + + Uses the existing DilatedReparamBlock from UniRepLKNet to provide + multi-branch dilated training with single-conv inference. + + Changes vs RSEFPN: + - inp_conv: RSELayer(3x3 std Conv + SE) + → DilatedReparamBlock(5x5 DW) + PWConv(1x1) + SE + - ins_conv: unchanged (1x1 Conv, no benefit from DW decomposition) + + Parameter comparison (out_channels=96, 4 levels): + RSEFPN inp_conv: 4 × (96×24×9 + SE) = 4 × 21,054 = 84,216 + RepLKFPN inp_conv: 4 × (DilReparam96 + 96×24 + SE) + = 4 × (96×25 + 96×2 + 2×(96×9+96×2) + 96×24 + SE) + = 4 × (2,400 + 192 + 2×(864+192) + 2,304 + 318) + = 4 × 7,326 = 29,304 + inp_conv reduction: 65.2% + Receptive field: 3×3 → 5×5 (with multi-dilation 3,5 patterns in training) + + Inference: DilatedReparamBlock merges to single 5x5 DWConv, zero extra cost. + """ + + def __init__( + self, in_channels, out_channels, shortcut=True, dilated_kernel_size=7, **kwargs + ): + super(RepLKFPN, self).__init__() + self.out_channels = out_channels + self.is_repped = False + self.ins_conv = nn.LayerList() + self.inp_conv_dw = nn.LayerList() + self.inp_conv_pw = nn.LayerList() + self.inp_conv_se = nn.LayerList() + self.shortcut = shortcut + + self.intracl = False + if "intracl" in kwargs.keys() and kwargs["intracl"] is True: + self.intracl = kwargs["intracl"] + self.incl1 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl2 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl3 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl4 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + + weight_attr = paddle.nn.initializer.KaimingUniform() + + for i in range(len(in_channels)): + self.ins_conv.append( + RSELayer(in_channels[i], out_channels, kernel_size=1, shortcut=shortcut) + ) + + self.inp_conv_dw.append( + DilatedReparamBlock( + channels=out_channels, kernel_size=dilated_kernel_size + ) + ) + + self.inp_conv_pw.append( + nn.Conv2D( + in_channels=out_channels, + out_channels=out_channels // 4, + kernel_size=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + ) + + self.inp_conv_se.append(SEModule(out_channels // 4)) + + def _inp_forward(self, x, idx): + x = self.inp_conv_dw[idx](x) + x = self.inp_conv_pw[idx](x) + if self.shortcut: + x = x + self.inp_conv_se[idx](x) + else: + x = self.inp_conv_se[idx](x) + return x + + def forward(self, x): + c2, c3, c4, c5 = x + + in5 = self.ins_conv[3](c5) + in4 = self.ins_conv[2](c4) + in3 = self.ins_conv[1](c3) + in2 = self.ins_conv[0](c2) + + out4 = in4 + F.upsample( + in5, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/16 + out3 = in3 + F.upsample( + out4, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/8 + out2 = in2 + F.upsample( + out3, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/4 + + p5 = self._inp_forward(in5, 3) + p4 = self._inp_forward(out4, 2) + p3 = self._inp_forward(out3, 1) + p2 = self._inp_forward(out2, 0) + + if self.intracl is True: + p5 = self.incl4(p5) + p4 = self.incl3(p4) + p3 = self.incl2(p3) + p2 = self.incl1(p2) + + p5 = F.upsample(p5, scale_factor=8, mode="nearest", align_mode=1) + p4 = F.upsample(p4, scale_factor=4, mode="nearest", align_mode=1) + p3 = F.upsample(p3, scale_factor=2, mode="nearest", align_mode=1) + + fuse = paddle.concat([p5, p4, p3, p2], axis=1) + if self.training: + return {"fuse": fuse, "aux_p4": out4, "aux_p3": out3, "aux_p2": out2} + return fuse + + def rep(self): + """Merge DilatedReparamBlock branches for inference deployment.""" + if self.is_repped: + return + for i in range(len(self.inp_conv_dw)): + self.inp_conv_dw[i].rep() + self.is_repped = True + + +class LKPAN(nn.Layer): + def __init__(self, in_channels, out_channels, mode="large", **kwargs): + super(LKPAN, self).__init__() + self.out_channels = out_channels + weight_attr = paddle.nn.initializer.KaimingUniform() + + self.ins_conv = nn.LayerList() + self.inp_conv = nn.LayerList() + # pan head + self.pan_head_conv = nn.LayerList() + self.pan_lat_conv = nn.LayerList() + + if mode.lower() == "lite": + p_layer = DSConv + elif mode.lower() == "large": + p_layer = nn.Conv2D + else: + raise ValueError( + "mode can only be one of ['lite', 'large'], but received {}".format( + mode + ) + ) + + for i in range(len(in_channels)): + self.ins_conv.append( + nn.Conv2D( + in_channels=in_channels[i], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + ) + + self.inp_conv.append( + p_layer( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=9, + padding=4, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + ) + + if i > 0: + self.pan_head_conv.append( + nn.Conv2D( + in_channels=self.out_channels // 4, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + stride=2, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + ) + self.pan_lat_conv.append( + p_layer( + in_channels=self.out_channels // 4, + out_channels=self.out_channels // 4, + kernel_size=9, + padding=4, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + ) + + self.intracl = False + if "intracl" in kwargs.keys() and kwargs["intracl"] is True: + self.intracl = kwargs["intracl"] + self.incl1 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl2 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl3 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl4 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + + def forward(self, x): + c2, c3, c4, c5 = x + + in5 = self.ins_conv[3](c5) + in4 = self.ins_conv[2](c4) + in3 = self.ins_conv[1](c3) + in2 = self.ins_conv[0](c2) + + out4 = in4 + F.upsample( + in5, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/16 + out3 = in3 + F.upsample( + out4, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/8 + out2 = in2 + F.upsample( + out3, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/4 + + f5 = self.inp_conv[3](in5) + f4 = self.inp_conv[2](out4) + f3 = self.inp_conv[1](out3) + f2 = self.inp_conv[0](out2) + + pan3 = f3 + self.pan_head_conv[0](f2) + pan4 = f4 + self.pan_head_conv[1](pan3) + pan5 = f5 + self.pan_head_conv[2](pan4) + + p2 = self.pan_lat_conv[0](f2) + p3 = self.pan_lat_conv[1](pan3) + p4 = self.pan_lat_conv[2](pan4) + p5 = self.pan_lat_conv[3](pan5) + + if self.intracl is True: + p5 = self.incl4(p5) + p4 = self.incl3(p4) + p3 = self.incl2(p3) + p2 = self.incl1(p2) + + p5 = F.upsample(p5, scale_factor=8, mode="nearest", align_mode=1) + p4 = F.upsample(p4, scale_factor=4, mode="nearest", align_mode=1) + p3 = F.upsample(p3, scale_factor=2, mode="nearest", align_mode=1) + + fuse = paddle.concat([p5, p4, p3, p2], axis=1) + return fuse + + +class DilatedReparamBlock(nn.Layer): + """ + Dilated Reparam Block from UniRepLKNet. + Reference: https://github.com/AILab-CVC/UniRepLKNet + + Training: uses multiple parallel dilated depthwise convolutions + BN + Inference: all branches merge into a single large-kernel depthwise conv + + For kernel_size=9, the branches are: + - origin: 9x9 DW Conv (dil=1) + - branch1: 5x5 DW Conv (dil=1, equiv RF=5) + - branch2: 5x5 DW Conv (dil=2, equiv RF=9) + - branch3: 3x3 DW Conv (dil=3, equiv RF=7) + - branch4: 3x3 DW Conv (dil=4, equiv RF=9) + """ + + def __init__(self, channels, kernel_size=9, deploy=False): + super(DilatedReparamBlock, self).__init__() + self.channels = channels + self.kernel_size = kernel_size + self.is_repped = deploy + + if kernel_size == 9: + self.kernel_sizes = [5, 5, 3, 3] + self.dilates = [1, 2, 3, 4] + elif kernel_size == 7: + self.kernel_sizes = [5, 3, 3] + self.dilates = [1, 2, 3] + elif kernel_size == 5: + self.kernel_sizes = [3, 3] + self.dilates = [1, 2] + elif kernel_size == 11: + self.kernel_sizes = [5, 5, 3, 3, 3] + self.dilates = [1, 2, 3, 4, 5] + elif kernel_size == 13: + self.kernel_sizes = [5, 7, 3, 3, 3] + self.dilates = [1, 2, 3, 4, 5] + else: + raise ValueError( + "DilatedReparamBlock requires kernel_size in [5,7,9,11,13], " + "but got {}".format(kernel_size) + ) + + if not self.is_repped: + self.lk_origin = nn.Conv2D( + in_channels=channels, + out_channels=channels, + kernel_size=kernel_size, + stride=1, + padding=kernel_size // 2, + groups=channels, + bias_attr=False, + ) + self.origin_bn = nn.BatchNorm2D(channels) + + for k, r in zip(self.kernel_sizes, self.dilates): + equiv_ks = r * (k - 1) + 1 + p = equiv_ks // 2 + conv = nn.Conv2D( + in_channels=channels, + out_channels=channels, + kernel_size=k, + stride=1, + padding=p, + dilation=r, + groups=channels, + bias_attr=False, + ) + bn = nn.BatchNorm2D(channels) + setattr(self, "dil_conv_k{}_{}".format(k, r), conv) + setattr(self, "dil_bn_k{}_{}".format(k, r), bn) + else: + self.lk_origin = nn.Conv2D( + in_channels=channels, + out_channels=channels, + kernel_size=kernel_size, + stride=1, + padding=kernel_size // 2, + groups=channels, + bias_attr=True, + ) + + def forward(self, x): + if self.is_repped: + return self.lk_origin(x) + out = self.origin_bn(self.lk_origin(x)) + for k, r in zip(self.kernel_sizes, self.dilates): + conv = getattr(self, "dil_conv_k{}_{}".format(k, r)) + bn = getattr(self, "dil_bn_k{}_{}".format(k, r)) + out = out + bn(conv(x)) + return out + + @staticmethod + def _fuse_bn(conv, bn): + """Fuse Conv2D + BatchNorm2D into a single Conv2D (weight, bias).""" + kernel = conv.weight + gamma = bn.weight + beta = bn.bias + running_mean = bn._mean + running_var = bn._variance + eps = bn._epsilon + std = paddle.sqrt(running_var + eps) + fused_weight = kernel * (gamma / std).reshape([-1, 1, 1, 1]) + fused_bias = beta - running_mean * gamma / std + return fused_weight, fused_bias + + @staticmethod + def _convert_dilated_to_nondilated(kernel, dilate_rate): + """Convert dilated conv kernel to equivalent non-dilated (sparse) kernel + by inserting zeros between kernel elements using transposed convolution.""" + if dilate_rate == 1: + return kernel + identity = paddle.ones(shape=[1, 1, 1, 1], dtype=kernel.dtype) + # F.conv2d_transpose with stride=dilate_rate inserts zeros + # Process each channel independently + C = kernel.shape[0] + result_list = [] + for i in range(C): + k_i = kernel[i : i + 1] # (1, 1, kH, kW) + dilated = F.conv2d_transpose(k_i, identity, stride=dilate_rate) + result_list.append(dilated) + return paddle.concat(result_list, axis=0) + + @staticmethod + def _merge_dilated_into_large_kernel(large_kernel, dilated_kernel, dilated_r): + """Pad dilated equivalent kernel to large kernel size and add.""" + large_k = large_kernel.shape[2] + dilated_k = dilated_kernel.shape[2] + equiv_ks = dilated_r * (dilated_k - 1) + 1 + equiv_kernel = DilatedReparamBlock._convert_dilated_to_nondilated( + dilated_kernel, dilated_r + ) + rows_to_pad = large_k // 2 - equiv_ks // 2 + if rows_to_pad > 0: + merged = large_kernel + F.pad( + equiv_kernel, [rows_to_pad, rows_to_pad, rows_to_pad, rows_to_pad] + ) + else: + merged = large_kernel + equiv_kernel + return merged + + @paddle.no_grad() + def rep(self): + """Merge all parallel branches into a single large-kernel DW conv. + Call this before switching to deploy/inference mode.""" + if self.is_repped: + return + origin_k, origin_b = self._fuse_bn(self.lk_origin, self.origin_bn) + for k, r in zip(self.kernel_sizes, self.dilates): + conv = getattr(self, "dil_conv_k{}_{}".format(k, r)) + bn = getattr(self, "dil_bn_k{}_{}".format(k, r)) + branch_k, branch_b = self._fuse_bn(conv, bn) + origin_k = self._merge_dilated_into_large_kernel(origin_k, branch_k, r) + origin_b = origin_b + branch_b + + merged_conv = nn.Conv2D( + in_channels=self.channels, + out_channels=self.channels, + kernel_size=self.kernel_size, + stride=1, + padding=self.kernel_size // 2, + groups=self.channels, + bias_attr=True, + ) + merged_conv.weight.set_value(origin_k) + merged_conv.bias.set_value(origin_b) + self.lk_origin = merged_conv + self.is_repped = True + + delattr(self, "origin_bn") + for k, r in zip(self.kernel_sizes, self.dilates): + delattr(self, "dil_conv_k{}_{}".format(k, r)) + delattr(self, "dil_bn_k{}_{}".format(k, r)) + + +class DilatedReparamConv(nn.Layer): + """ + A drop-in replacement for standard Conv2D (in_ch → out_ch, large kernel) + using DilatedReparamBlock (depthwise) + 1x1 pointwise convolution. + + Architecture: + input(in_ch) → DilatedReparamBlock(in_ch, DW, kernel_size) → 1x1 Conv(in_ch→out_ch) → BN + + This decomposition replaces a single large standard conv with DW + PW, + drastically reducing parameters while maintaining the large receptive field. + """ + + def __init__( + self, in_channels, out_channels, kernel_size=9, deploy=False, **kwargs + ): + super(DilatedReparamConv, self).__init__() + self.is_repped = False + weight_attr = paddle.nn.initializer.KaimingUniform() + self.dw = DilatedReparamBlock( + channels=in_channels, kernel_size=kernel_size, deploy=deploy + ) + self.pw = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.bn = nn.BatchNorm2D(out_channels) + + def forward(self, x): + x = self.dw(x) + x = self.pw(x) + if not self.is_repped: + x = self.bn(x) + return x + + @paddle.no_grad() + def rep(self): + """Fuse DW branches + PW Conv + BN for deployment.""" + if self.is_repped: + return + self.dw.rep() + # Fuse pw(Conv2D, no bias) + bn(BatchNorm2D) into single Conv2D with bias + conv, bn = self.pw, self.bn + gamma = bn.weight + std = paddle.sqrt(bn._variance + bn._epsilon) + scale = gamma / std + w = conv.weight * scale[:, None, None, None] + b = bn.bias - bn._mean * scale + fused = nn.Conv2D( + conv._in_channels, + conv._out_channels, + conv._kernel_size, + stride=conv._stride, + padding=conv._padding, + dilation=conv._dilation, + groups=conv._groups, + ) + fused.weight.set_value(w) + fused.bias.set_value(b) + self.pw = fused + del self.bn + self.is_repped = True + + +class RepLKPAN(nn.Layer): + """ + Optimized LKPAN using UniRepLKNet's DilatedReparamBlock. + + Replaces the 8 standard 9x9 Conv2D in LKPAN (4 inp_conv + 4 pan_lat_conv) + with DilatedReparamConv (DW large-kernel reparam + 1x1 pointwise). + + Parameter comparison (out_channels=256, i.e. inner_ch=64): + ┌──────────────────────────────────────────────────────────────────────────┐ + │ Component │ Original (Std 9x9 Conv) │ UniRepLK (DW+PW) │ + ├──────────────────────────────────────────────────────────────────────────┤ + │ inp_conv[i]: │ 256×64×9×9 = 1,327,104 │ DW: 256×1×9×9 = 20,736 │ + │ (256→64, ×4) │ │ +4 dilated DW branches │ + │ │ │ ≈ 256×(25+25+9+9) │ + │ │ │ = 17,408 (training) │ + │ │ │ PW: 256×64×1×1 = 16,384 │ + │ │ │ BN: 64×2 = 128 │ + │ │ │ Subtotal/layer ≈ 54,656 │ + │ │ │ (vs 1,327,104 original) │ + ├──────────────────────────────────────────────────────────────────────────┤ + │ pan_lat_conv[i]: │ 64×64×9×9 = 331,776 │ DW: 64×1×9×9 = 5,184 │ + │ (64→64, ×4) │ │ +4 dilated DW branches │ + │ │ │ ≈ 64×(25+25+9+9) │ + │ │ │ = 4,352 (training) │ + │ │ │ PW: 64×64×1×1 = 4,096 │ + │ │ │ BN: 64×2 = 128 │ + │ │ │ Subtotal/layer ≈ 13,760 │ + │ │ │ (vs 331,776 original) │ + ├──────────────────────────────────────────────────────────────────────────┤ + │ Total 9x9 params │ 4×1,327,104 + 4×331,776 │ 4×54,656 + 4×13,760 │ + │ (8 layers) │ = 6,635,520 │ = 273,664 │ + │ │ │ 95.9% reduction │ + ├──────────────────────────────────────────────────────────────────────────┤ + │ Inference(reparam)│ Same as above │ DW 9x9 merged (no extra │ + │ │ │ branch overhead)+PW 1x1 │ + │ │ │ FLOPs also greatly reduced│ + └──────────────────────────────────────────────────────────────────────────┘ + + Note: BN params in dilated branches are only present during training and + merged into the DW conv weights at inference. The training param count + includes these BN params; inference param count is slightly lower. + """ + + def __init__(self, in_channels, out_channels, mode="large", **kwargs): + super(RepLKPAN, self).__init__() + self.out_channels = out_channels + self.is_repped = False + weight_attr = paddle.nn.initializer.KaimingUniform() + + self.ins_conv = nn.LayerList() + self.inp_conv = nn.LayerList() + # pan head + self.pan_head_conv = nn.LayerList() + self.pan_lat_conv = nn.LayerList() + + for i in range(len(in_channels)): + self.ins_conv.append( + nn.Conv2D( + in_channels=in_channels[i], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + ) + + self.inp_conv.append( + DilatedReparamConv( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=9, + ) + ) + + if i > 0: + self.pan_head_conv.append( + nn.Conv2D( + in_channels=self.out_channels // 4, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + stride=2, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + ) + self.pan_lat_conv.append( + DilatedReparamConv( + in_channels=self.out_channels // 4, + out_channels=self.out_channels // 4, + kernel_size=9, + ) + ) + + self.intracl = False + if "intracl" in kwargs.keys() and kwargs["intracl"] is True: + self.intracl = kwargs["intracl"] + self.incl1 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl2 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl3 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + self.incl4 = IntraCLBlock(self.out_channels // 4, reduce_factor=2) + + def forward(self, x): + c2, c3, c4, c5 = x + + in5 = self.ins_conv[3](c5) + in4 = self.ins_conv[2](c4) + in3 = self.ins_conv[1](c3) + in2 = self.ins_conv[0](c2) + + out4 = in4 + F.upsample( + in5, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/16 + out3 = in3 + F.upsample( + out4, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/8 + out2 = in2 + F.upsample( + out3, scale_factor=2, mode="nearest", align_mode=1 + ) # 1/4 + + f5 = self.inp_conv[3](in5) + f4 = self.inp_conv[2](out4) + f3 = self.inp_conv[1](out3) + f2 = self.inp_conv[0](out2) + + pan3 = f3 + self.pan_head_conv[0](f2) + pan4 = f4 + self.pan_head_conv[1](pan3) + pan5 = f5 + self.pan_head_conv[2](pan4) + + p2 = self.pan_lat_conv[0](f2) + p3 = self.pan_lat_conv[1](pan3) + p4 = self.pan_lat_conv[2](pan4) + p5 = self.pan_lat_conv[3](pan5) + + if self.intracl is True: + p5 = self.incl4(p5) + p4 = self.incl3(p4) + p3 = self.incl2(p3) + p2 = self.incl1(p2) + + p5 = F.upsample(p5, scale_factor=8, mode="nearest", align_mode=1) + p4 = F.upsample(p4, scale_factor=4, mode="nearest", align_mode=1) + p3 = F.upsample(p3, scale_factor=2, mode="nearest", align_mode=1) + + fuse = paddle.concat([p5, p4, p3, p2], axis=1) + if self.training: + return {"fuse": fuse, "aux_p4": out4, "aux_p3": out3, "aux_p2": out2} + return fuse + + def rep(self): + """Merge all DilatedReparamBlock branches and fuse PW+BN for deployment.""" + if self.is_repped: + return + for i in range(len(self.inp_conv)): + self.inp_conv[i].rep() + for i in range(len(self.pan_lat_conv)): + self.pan_lat_conv[i].rep() + self.is_repped = True + + +class ASFBlock(nn.Layer): + """ + This code is referred from: + https://github.com/MhLiao/DB/blob/master/decoders/feature_attention.py + """ + + def __init__(self, in_channels, inter_channels, out_features_num=4): + """ + Adaptive Scale Fusion (ASF) block of DBNet++ + Args: + in_channels: the number of channels in the input data + inter_channels: the number of middle channels + out_features_num: the number of fused stages + """ + super(ASFBlock, self).__init__() + weight_attr = paddle.nn.initializer.KaimingUniform() + self.in_channels = in_channels + self.inter_channels = inter_channels + self.out_features_num = out_features_num + self.conv = nn.Conv2D(in_channels, inter_channels, 3, padding=1) + + self.spatial_scale = nn.Sequential( + # Nx1xHxW + nn.Conv2D( + in_channels=1, + out_channels=1, + kernel_size=3, + bias_attr=False, + padding=1, + weight_attr=ParamAttr(initializer=weight_attr), + ), + nn.ReLU(), + nn.Conv2D( + in_channels=1, + out_channels=1, + kernel_size=1, + bias_attr=False, + weight_attr=ParamAttr(initializer=weight_attr), + ), + nn.Sigmoid(), + ) + + self.channel_scale = nn.Sequential( + nn.Conv2D( + in_channels=inter_channels, + out_channels=out_features_num, + kernel_size=1, + bias_attr=False, + weight_attr=ParamAttr(initializer=weight_attr), + ), + nn.Sigmoid(), + ) + + def forward(self, fuse_features, features_list): + fuse_features = self.conv(fuse_features) + spatial_x = paddle.mean(fuse_features, axis=1, keepdim=True) + attention_scores = self.spatial_scale(spatial_x) + fuse_features + attention_scores = self.channel_scale(attention_scores) + assert len(features_list) == self.out_features_num + + out_list = [] + for i in range(self.out_features_num): + out_list.append(attention_scores[:, i : i + 1] * features_list[i]) + return paddle.concat(out_list, axis=1) diff --git a/ppocr/modeling/necks/east_fpn.py b/ppocr/modeling/necks/east_fpn.py new file mode 100644 index 0000000..1b7f50d --- /dev/null +++ b/ppocr/modeling/necks/east_fpn.py @@ -0,0 +1,203 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride, + padding, + groups=1, + if_act=True, + act=None, + name=None, + ): + super(ConvBNLayer, self).__init__() + self.if_act = if_act + self.act = act + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + + self.bn = nn.BatchNorm( + num_channels=out_channels, + act=act, + param_attr=ParamAttr(name="bn_" + name + "_scale"), + bias_attr=ParamAttr(name="bn_" + name + "_offset"), + moving_mean_name="bn_" + name + "_mean", + moving_variance_name="bn_" + name + "_variance", + ) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + return x + + +class DeConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride, + padding, + groups=1, + if_act=True, + act=None, + name=None, + ): + super(DeConvBNLayer, self).__init__() + self.if_act = if_act + self.act = act + self.deconv = nn.Conv2DTranspose( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + self.bn = nn.BatchNorm( + num_channels=out_channels, + act=act, + param_attr=ParamAttr(name="bn_" + name + "_scale"), + bias_attr=ParamAttr(name="bn_" + name + "_offset"), + moving_mean_name="bn_" + name + "_mean", + moving_variance_name="bn_" + name + "_variance", + ) + + def forward(self, x): + x = self.deconv(x) + x = self.bn(x) + return x + + +class EASTFPN(nn.Layer): + def __init__(self, in_channels, model_name, **kwargs): + super(EASTFPN, self).__init__() + self.model_name = model_name + if self.model_name == "large": + self.out_channels = 128 + else: + self.out_channels = 64 + self.in_channels = in_channels[::-1] + self.h1_conv = ConvBNLayer( + in_channels=self.out_channels + self.in_channels[1], + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + if_act=True, + act="relu", + name="unet_h_1", + ) + self.h2_conv = ConvBNLayer( + in_channels=self.out_channels + self.in_channels[2], + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + if_act=True, + act="relu", + name="unet_h_2", + ) + self.h3_conv = ConvBNLayer( + in_channels=self.out_channels + self.in_channels[3], + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + if_act=True, + act="relu", + name="unet_h_3", + ) + self.g0_deconv = DeConvBNLayer( + in_channels=self.in_channels[0], + out_channels=self.out_channels, + kernel_size=4, + stride=2, + padding=1, + if_act=True, + act="relu", + name="unet_g_0", + ) + self.g1_deconv = DeConvBNLayer( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=4, + stride=2, + padding=1, + if_act=True, + act="relu", + name="unet_g_1", + ) + self.g2_deconv = DeConvBNLayer( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=4, + stride=2, + padding=1, + if_act=True, + act="relu", + name="unet_g_2", + ) + self.g3_conv = ConvBNLayer( + in_channels=self.out_channels, + out_channels=self.out_channels, + kernel_size=3, + stride=1, + padding=1, + if_act=True, + act="relu", + name="unet_g_3", + ) + + def forward(self, x): + f = x[::-1] + + h = f[0] + g = self.g0_deconv(h) + h = paddle.concat([g, f[1]], axis=1) + h = self.h1_conv(h) + g = self.g1_deconv(h) + h = paddle.concat([g, f[2]], axis=1) + h = self.h2_conv(h) + g = self.g2_deconv(h) + h = paddle.concat([g, f[3]], axis=1) + h = self.h3_conv(h) + g = self.g3_conv(h) + + return g diff --git a/ppocr/modeling/necks/fce_fpn.py b/ppocr/modeling/necks/fce_fpn.py new file mode 100644 index 0000000..a456fd1 --- /dev/null +++ b/ppocr/modeling/necks/fce_fpn.py @@ -0,0 +1,304 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.3/ppdet/modeling/necks/fpn.py +""" + +import paddle.nn as nn +import paddle.nn.functional as F +from paddle import ParamAttr +from paddle.nn.initializer import XavierUniform +from paddle.nn.initializer import Normal +from paddle.regularizer import L2Decay + +__all__ = ["FCEFPN"] + + +class ConvNormLayer(nn.Layer): + def __init__( + self, + ch_in, + ch_out, + filter_size, + stride, + groups=1, + norm_type="bn", + norm_decay=0.0, + norm_groups=32, + lr_scale=1.0, + freeze_norm=False, + initializer=Normal(mean=0.0, std=0.01), + ): + super(ConvNormLayer, self).__init__() + assert norm_type in ["bn", "sync_bn", "gn"] + + bias_attr = False + + self.conv = nn.Conv2D( + in_channels=ch_in, + out_channels=ch_out, + kernel_size=filter_size, + stride=stride, + padding=(filter_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(initializer=initializer, learning_rate=1.0), + bias_attr=bias_attr, + ) + + norm_lr = 0.0 if freeze_norm else 1.0 + param_attr = ParamAttr( + learning_rate=norm_lr, + regularizer=L2Decay(norm_decay) if norm_decay is not None else None, + ) + bias_attr = ParamAttr( + learning_rate=norm_lr, + regularizer=L2Decay(norm_decay) if norm_decay is not None else None, + ) + if norm_type == "bn": + self.norm = nn.BatchNorm2D( + ch_out, weight_attr=param_attr, bias_attr=bias_attr + ) + elif norm_type == "sync_bn": + self.norm = nn.SyncBatchNorm( + ch_out, weight_attr=param_attr, bias_attr=bias_attr + ) + elif norm_type == "gn": + self.norm = nn.GroupNorm( + num_groups=norm_groups, + num_channels=ch_out, + weight_attr=param_attr, + bias_attr=bias_attr, + ) + + def forward(self, inputs): + out = self.conv(inputs) + out = self.norm(out) + return out + + +class FCEFPN(nn.Layer): + """ + Feature Pyramid Network, see https://arxiv.org/abs/1612.03144 + Args: + in_channels (list[int]): input channels of each level which can be + derived from the output shape of backbone by from_config + out_channels (list[int]): output channel of each level + spatial_scales (list[float]): the spatial scales between input feature + maps and original input image which can be derived from the output + shape of backbone by from_config + has_extra_convs (bool): whether to add extra conv to the last level. + default False + extra_stage (int): the number of extra stages added to the last level. + default 1 + use_c5 (bool): Whether to use c5 as the input of extra stage, + otherwise p5 is used. default True + norm_type (string|None): The normalization type in FPN module. If + norm_type is None, norm will not be used after conv and if + norm_type is string, bn, gn, sync_bn are available. default None + norm_decay (float): weight decay for normalization layer weights. + default 0. + freeze_norm (bool): whether to freeze normalization layer. + default False + relu_before_extra_convs (bool): whether to add relu before extra convs. + default False + + """ + + def __init__( + self, + in_channels, + out_channels, + spatial_scales=[0.25, 0.125, 0.0625, 0.03125], + has_extra_convs=False, + extra_stage=1, + use_c5=True, + norm_type=None, + norm_decay=0.0, + freeze_norm=False, + relu_before_extra_convs=True, + ): + super(FCEFPN, self).__init__() + self.out_channels = out_channels + for s in range(extra_stage): + spatial_scales = spatial_scales + [spatial_scales[-1] / 2.0] + self.spatial_scales = spatial_scales + self.has_extra_convs = has_extra_convs + self.extra_stage = extra_stage + self.use_c5 = use_c5 + self.relu_before_extra_convs = relu_before_extra_convs + self.norm_type = norm_type + self.norm_decay = norm_decay + self.freeze_norm = freeze_norm + + self.lateral_convs = [] + self.fpn_convs = [] + fan = out_channels * 3 * 3 + + # stage index 0,1,2,3 stands for res2,res3,res4,res5 on ResNet Backbone + # 0 <= st_stage < ed_stage <= 3 + st_stage = 4 - len(in_channels) + ed_stage = st_stage + len(in_channels) - 1 + for i in range(st_stage, ed_stage + 1): + if i == 3: + lateral_name = "fpn_inner_res5_sum" + else: + lateral_name = "fpn_inner_res{}_sum_lateral".format(i + 2) + in_c = in_channels[i - st_stage] + if self.norm_type is not None: + lateral = self.add_sublayer( + lateral_name, + ConvNormLayer( + ch_in=in_c, + ch_out=out_channels, + filter_size=1, + stride=1, + norm_type=self.norm_type, + norm_decay=self.norm_decay, + freeze_norm=self.freeze_norm, + initializer=XavierUniform(fan_out=in_c), + ), + ) + else: + lateral = self.add_sublayer( + lateral_name, + nn.Conv2D( + in_channels=in_c, + out_channels=out_channels, + kernel_size=1, + weight_attr=ParamAttr(initializer=XavierUniform(fan_out=in_c)), + ), + ) + self.lateral_convs.append(lateral) + + for i in range(st_stage, ed_stage + 1): + fpn_name = "fpn_res{}_sum".format(i + 2) + if self.norm_type is not None: + fpn_conv = self.add_sublayer( + fpn_name, + ConvNormLayer( + ch_in=out_channels, + ch_out=out_channels, + filter_size=3, + stride=1, + norm_type=self.norm_type, + norm_decay=self.norm_decay, + freeze_norm=self.freeze_norm, + initializer=XavierUniform(fan_out=fan), + ), + ) + else: + fpn_conv = self.add_sublayer( + fpn_name, + nn.Conv2D( + in_channels=out_channels, + out_channels=out_channels, + kernel_size=3, + padding=1, + weight_attr=ParamAttr(initializer=XavierUniform(fan_out=fan)), + ), + ) + self.fpn_convs.append(fpn_conv) + + # add extra conv levels for RetinaNet(use_c5)/FCOS(use_p5) + if self.has_extra_convs: + for i in range(self.extra_stage): + lvl = ed_stage + 1 + i + if i == 0 and self.use_c5: + in_c = in_channels[-1] + else: + in_c = out_channels + extra_fpn_name = "fpn_{}".format(lvl + 2) + if self.norm_type is not None: + extra_fpn_conv = self.add_sublayer( + extra_fpn_name, + ConvNormLayer( + ch_in=in_c, + ch_out=out_channels, + filter_size=3, + stride=2, + norm_type=self.norm_type, + norm_decay=self.norm_decay, + freeze_norm=self.freeze_norm, + initializer=XavierUniform(fan_out=fan), + ), + ) + else: + extra_fpn_conv = self.add_sublayer( + extra_fpn_name, + nn.Conv2D( + in_channels=in_c, + out_channels=out_channels, + kernel_size=3, + stride=2, + padding=1, + weight_attr=ParamAttr( + initializer=XavierUniform(fan_out=fan) + ), + ), + ) + self.fpn_convs.append(extra_fpn_conv) + + @classmethod + def from_config(cls, cfg, input_shape): + return { + "in_channels": [i.channels for i in input_shape], + "spatial_scales": [1.0 / i.stride for i in input_shape], + } + + def forward(self, body_feats): + laterals = [] + num_levels = len(body_feats) + + for i in range(num_levels): + laterals.append(self.lateral_convs[i](body_feats[i])) + + for i in range(1, num_levels): + lvl = num_levels - i + upsample = F.interpolate( + laterals[lvl], + scale_factor=2.0, + mode="nearest", + ) + laterals[lvl - 1] += upsample + + fpn_output = [] + for lvl in range(num_levels): + fpn_output.append(self.fpn_convs[lvl](laterals[lvl])) + + if self.extra_stage > 0: + # use max pool to get more levels on top of outputs (Faster R-CNN, Mask R-CNN) + if not self.has_extra_convs: + assert ( + self.extra_stage == 1 + ), "extra_stage should be 1 if FPN has not extra convs" + fpn_output.append(F.max_pool2d(fpn_output[-1], 1, stride=2)) + # add extra conv levels for RetinaNet(use_c5)/FCOS(use_p5) + else: + if self.use_c5: + extra_source = body_feats[-1] + else: + extra_source = fpn_output[-1] + fpn_output.append(self.fpn_convs[num_levels](extra_source)) + + for i in range(1, self.extra_stage): + if self.relu_before_extra_convs: + fpn_output.append( + self.fpn_convs[num_levels + i](F.relu(fpn_output[-1])) + ) + else: + fpn_output.append( + self.fpn_convs[num_levels + i](fpn_output[-1]) + ) + return fpn_output diff --git a/ppocr/modeling/necks/fpn.py b/ppocr/modeling/necks/fpn.py new file mode 100644 index 0000000..ea5253c --- /dev/null +++ b/ppocr/modeling/necks/fpn.py @@ -0,0 +1,149 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/whai362/PSENet/blob/python3/models/neck/fpn.py +""" + +import paddle.nn as nn +import paddle +import math +import paddle.nn.functional as F + + +class Conv_BN_ReLU(nn.Layer): + def __init__(self, in_planes, out_planes, kernel_size=1, stride=1, padding=0): + super(Conv_BN_ReLU, self).__init__() + self.conv = nn.Conv2D( + in_planes, + out_planes, + kernel_size=kernel_size, + stride=stride, + padding=padding, + bias_attr=False, + ) + self.bn = nn.BatchNorm2D(out_planes, momentum=0.1) + self.relu = nn.ReLU() + + for m in self.sublayers(): + if isinstance(m, nn.Conv2D): + n = m._kernel_size[0] * m._kernel_size[1] * m._out_channels + m.weight = paddle.create_parameter( + shape=m.weight.shape, + dtype="float32", + default_initializer=paddle.nn.initializer.Normal( + 0, math.sqrt(2.0 / n) + ), + ) + elif isinstance(m, nn.BatchNorm2D): + m.weight = paddle.create_parameter( + shape=m.weight.shape, + dtype="float32", + default_initializer=paddle.nn.initializer.Constant(1.0), + ) + m.bias = paddle.create_parameter( + shape=m.bias.shape, + dtype="float32", + default_initializer=paddle.nn.initializer.Constant(0.0), + ) + + def forward(self, x): + return self.relu(self.bn(self.conv(x))) + + +class FPN(nn.Layer): + def __init__(self, in_channels, out_channels): + super(FPN, self).__init__() + + # Top layer + self.toplayer_ = Conv_BN_ReLU( + in_channels[3], out_channels, kernel_size=1, stride=1, padding=0 + ) + # Lateral layers + self.latlayer1_ = Conv_BN_ReLU( + in_channels[2], out_channels, kernel_size=1, stride=1, padding=0 + ) + + self.latlayer2_ = Conv_BN_ReLU( + in_channels[1], out_channels, kernel_size=1, stride=1, padding=0 + ) + + self.latlayer3_ = Conv_BN_ReLU( + in_channels[0], out_channels, kernel_size=1, stride=1, padding=0 + ) + + # Smooth layers + self.smooth1_ = Conv_BN_ReLU( + out_channels, out_channels, kernel_size=3, stride=1, padding=1 + ) + + self.smooth2_ = Conv_BN_ReLU( + out_channels, out_channels, kernel_size=3, stride=1, padding=1 + ) + + self.smooth3_ = Conv_BN_ReLU( + out_channels, out_channels, kernel_size=3, stride=1, padding=1 + ) + + self.out_channels = out_channels * 4 + for m in self.sublayers(): + if isinstance(m, nn.Conv2D): + n = m._kernel_size[0] * m._kernel_size[1] * m._out_channels + m.weight = paddle.create_parameter( + shape=m.weight.shape, + dtype="float32", + default_initializer=paddle.nn.initializer.Normal( + 0, math.sqrt(2.0 / n) + ), + ) + elif isinstance(m, nn.BatchNorm2D): + m.weight = paddle.create_parameter( + shape=m.weight.shape, + dtype="float32", + default_initializer=paddle.nn.initializer.Constant(1.0), + ) + m.bias = paddle.create_parameter( + shape=m.bias.shape, + dtype="float32", + default_initializer=paddle.nn.initializer.Constant(0.0), + ) + + def _upsample(self, x, scale=1): + return F.upsample(x, scale_factor=scale, mode="bilinear") + + def _upsample_add(self, x, y, scale=1): + return F.upsample(x, scale_factor=scale, mode="bilinear") + y + + def forward(self, x): + f2, f3, f4, f5 = x + p5 = self.toplayer_(f5) + + f4 = self.latlayer1_(f4) + p4 = self._upsample_add(p5, f4, 2) + p4 = self.smooth1_(p4) + + f3 = self.latlayer2_(f3) + p3 = self._upsample_add(p4, f3, 2) + p3 = self.smooth2_(p3) + + f2 = self.latlayer3_(f2) + p2 = self._upsample_add(p3, f2, 2) + p2 = self.smooth3_(p2) + + p3 = self._upsample(p3, 2) + p4 = self._upsample(p4, 4) + p5 = self._upsample(p5, 8) + + fuse = paddle.concat([p2, p3, p4, p5], axis=1) + return fuse diff --git a/ppocr/modeling/necks/fpn_unet.py b/ppocr/modeling/necks/fpn_unet.py new file mode 100644 index 0000000..e560cdb --- /dev/null +++ b/ppocr/modeling/necks/fpn_unet.py @@ -0,0 +1,103 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textdet/necks/fpn_unet.py +""" + +import paddle +import paddle.nn as nn +import paddle.nn.functional as F + + +class UpBlock(nn.Layer): + def __init__(self, in_channels, out_channels): + super().__init__() + + assert isinstance(in_channels, int) + assert isinstance(out_channels, int) + + self.conv1x1 = nn.Conv2D( + in_channels, in_channels, kernel_size=1, stride=1, padding=0 + ) + self.conv3x3 = nn.Conv2D( + in_channels, out_channels, kernel_size=3, stride=1, padding=1 + ) + self.deconv = nn.Conv2DTranspose( + out_channels, out_channels, kernel_size=4, stride=2, padding=1 + ) + + def forward(self, x): + x = F.relu(self.conv1x1(x)) + x = F.relu(self.conv3x3(x)) + x = self.deconv(x) + return x + + +class FPN_UNet(nn.Layer): + def __init__(self, in_channels, out_channels): + super().__init__() + + assert len(in_channels) == 4 + assert isinstance(out_channels, int) + self.out_channels = out_channels + + blocks_out_channels = [out_channels] + [ + min(out_channels * 2**i, 256) for i in range(4) + ] + blocks_in_channels = ( + [blocks_out_channels[1]] + + [in_channels[i] + blocks_out_channels[i + 2] for i in range(3)] + + [in_channels[3]] + ) + + self.up4 = nn.Conv2DTranspose( + blocks_in_channels[4], + blocks_out_channels[4], + kernel_size=4, + stride=2, + padding=1, + ) + self.up_block3 = UpBlock(blocks_in_channels[3], blocks_out_channels[3]) + self.up_block2 = UpBlock(blocks_in_channels[2], blocks_out_channels[2]) + self.up_block1 = UpBlock(blocks_in_channels[1], blocks_out_channels[1]) + self.up_block0 = UpBlock(blocks_in_channels[0], blocks_out_channels[0]) + + def forward(self, x): + """ + Args: + x (list[Tensor] | tuple[Tensor]): A list of four tensors of shape + :math:`(N, C_i, H_i, W_i)`, representing C2, C3, C4, C5 + features respectively. :math:`C_i` should matches the number in + ``in_channels``. + + Returns: + Tensor: Shape :math:`(N, C, H, W)` where :math:`H=4H_0` and + :math:`W=4W_0`. + """ + c2, c3, c4, c5 = x + + x = F.relu(self.up4(c5)) + + x = paddle.concat([x, c4], axis=1) + x = F.relu(self.up_block3(x)) + + x = paddle.concat([x, c3], axis=1) + x = F.relu(self.up_block2(x)) + + x = paddle.concat([x, c2], axis=1) + x = F.relu(self.up_block1(x)) + + x = self.up_block0(x) + return x diff --git a/ppocr/modeling/necks/intracl.py b/ppocr/modeling/necks/intracl.py new file mode 100644 index 0000000..2c4809c --- /dev/null +++ b/ppocr/modeling/necks/intracl.py @@ -0,0 +1,121 @@ +import paddle +from paddle import nn + +# refer from: https://github.com/ViTAE-Transformer/I3CL/blob/736c80237f66d352d488e83b05f3e33c55201317/mmdet/models/detectors/intra_cl_module.py + + +class IntraCLBlock(nn.Layer): + def __init__(self, in_channels=96, reduce_factor=4): + super(IntraCLBlock, self).__init__() + self.channels = in_channels + self.rf = reduce_factor + weight_attr = paddle.nn.initializer.KaimingUniform() + self.conv1x1_reduce_channel = nn.Conv2D( + self.channels, self.channels // self.rf, kernel_size=1, stride=1, padding=0 + ) + self.conv1x1_return_channel = nn.Conv2D( + self.channels // self.rf, self.channels, kernel_size=1, stride=1, padding=0 + ) + + self.v_layer_7x1 = nn.Conv2D( + self.channels // self.rf, + self.channels // self.rf, + kernel_size=(7, 1), + stride=(1, 1), + padding=(3, 0), + ) + self.v_layer_5x1 = nn.Conv2D( + self.channels // self.rf, + self.channels // self.rf, + kernel_size=(5, 1), + stride=(1, 1), + padding=(2, 0), + ) + self.v_layer_3x1 = nn.Conv2D( + self.channels // self.rf, + self.channels // self.rf, + kernel_size=(3, 1), + stride=(1, 1), + padding=(1, 0), + ) + + self.q_layer_1x7 = nn.Conv2D( + self.channels // self.rf, + self.channels // self.rf, + kernel_size=(1, 7), + stride=(1, 1), + padding=(0, 3), + ) + self.q_layer_1x5 = nn.Conv2D( + self.channels // self.rf, + self.channels // self.rf, + kernel_size=(1, 5), + stride=(1, 1), + padding=(0, 2), + ) + self.q_layer_1x3 = nn.Conv2D( + self.channels // self.rf, + self.channels // self.rf, + kernel_size=(1, 3), + stride=(1, 1), + padding=(0, 1), + ) + + # base + self.c_layer_7x7 = nn.Conv2D( + self.channels // self.rf, + self.channels // self.rf, + kernel_size=(7, 7), + stride=(1, 1), + padding=(3, 3), + ) + self.c_layer_5x5 = nn.Conv2D( + self.channels // self.rf, + self.channels // self.rf, + kernel_size=(5, 5), + stride=(1, 1), + padding=(2, 2), + ) + self.c_layer_3x3 = nn.Conv2D( + self.channels // self.rf, + self.channels // self.rf, + kernel_size=(3, 3), + stride=(1, 1), + padding=(1, 1), + ) + + self.bn = nn.BatchNorm2D(self.channels) + self.relu = nn.ReLU() + + def forward(self, x): + x_new = self.conv1x1_reduce_channel(x) + + x_7_c = self.c_layer_7x7(x_new) + x_7_v = self.v_layer_7x1(x_new) + x_7_q = self.q_layer_1x7(x_new) + x_7 = x_7_c + x_7_v + x_7_q + + x_5_c = self.c_layer_5x5(x_7) + x_5_v = self.v_layer_5x1(x_7) + x_5_q = self.q_layer_1x5(x_7) + x_5 = x_5_c + x_5_v + x_5_q + + x_3_c = self.c_layer_3x3(x_5) + x_3_v = self.v_layer_3x1(x_5) + x_3_q = self.q_layer_1x3(x_5) + x_3 = x_3_c + x_3_v + x_3_q + + x_relation = self.conv1x1_return_channel(x_3) + + x_relation = self.bn(x_relation) + x_relation = self.relu(x_relation) + + return x + x_relation + + +def build_intraclblock_list(num_block): + IntraCLBlock_list = nn.LayerList() + for i in range(num_block): + IntraCLBlock_list.append(IntraCLBlock()) + + return IntraCLBlock_list diff --git a/ppocr/modeling/necks/pg_fpn.py b/ppocr/modeling/necks/pg_fpn.py new file mode 100644 index 0000000..cae4b22 --- /dev/null +++ b/ppocr/modeling/necks/pg_fpn.py @@ -0,0 +1,345 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + is_vd_mode=False, + act=None, + name=None, + ): + super(ConvBNLayer, self).__init__() + + self.is_vd_mode = is_vd_mode + self._pool2d_avg = nn.AvgPool2D( + kernel_size=2, stride=2, padding=0, ceil_mode=True + ) + self._conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + if name == "conv1": + bn_name = "bn_" + name + else: + bn_name = "bn" + name[3:] + self._batch_norm = nn.BatchNorm( + out_channels, + act=act, + param_attr=ParamAttr(name=bn_name + "_scale"), + bias_attr=ParamAttr(bn_name + "_offset"), + moving_mean_name=bn_name + "_mean", + moving_variance_name=bn_name + "_variance", + use_global_stats=False, + ) + + def forward(self, inputs): + y = self._conv(inputs) + y = self._batch_norm(y) + return y + + +class DeConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size=4, + stride=2, + padding=1, + groups=1, + if_act=True, + act=None, + name=None, + ): + super(DeConvBNLayer, self).__init__() + + self.if_act = if_act + self.act = act + self.deconv = nn.Conv2DTranspose( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + self.bn = nn.BatchNorm( + num_channels=out_channels, + act=act, + param_attr=ParamAttr(name="bn_" + name + "_scale"), + bias_attr=ParamAttr(name="bn_" + name + "_offset"), + moving_mean_name="bn_" + name + "_mean", + moving_variance_name="bn_" + name + "_variance", + use_global_stats=False, + ) + + def forward(self, x): + x = self.deconv(x) + x = self.bn(x) + return x + + +class PGFPN(nn.Layer): + def __init__(self, in_channels, **kwargs): + super(PGFPN, self).__init__() + num_inputs = [2048, 2048, 1024, 512, 256] + num_outputs = [256, 256, 192, 192, 128] + self.out_channels = 128 + self.conv_bn_layer_1 = ConvBNLayer( + in_channels=3, + out_channels=32, + kernel_size=3, + stride=1, + act=None, + name="FPN_d1", + ) + self.conv_bn_layer_2 = ConvBNLayer( + in_channels=64, + out_channels=64, + kernel_size=3, + stride=1, + act=None, + name="FPN_d2", + ) + self.conv_bn_layer_3 = ConvBNLayer( + in_channels=256, + out_channels=128, + kernel_size=3, + stride=1, + act=None, + name="FPN_d3", + ) + self.conv_bn_layer_4 = ConvBNLayer( + in_channels=32, + out_channels=64, + kernel_size=3, + stride=2, + act=None, + name="FPN_d4", + ) + self.conv_bn_layer_5 = ConvBNLayer( + in_channels=64, + out_channels=64, + kernel_size=3, + stride=1, + act="relu", + name="FPN_d5", + ) + self.conv_bn_layer_6 = ConvBNLayer( + in_channels=64, + out_channels=128, + kernel_size=3, + stride=2, + act=None, + name="FPN_d6", + ) + self.conv_bn_layer_7 = ConvBNLayer( + in_channels=128, + out_channels=128, + kernel_size=3, + stride=1, + act="relu", + name="FPN_d7", + ) + self.conv_bn_layer_8 = ConvBNLayer( + in_channels=128, + out_channels=128, + kernel_size=1, + stride=1, + act=None, + name="FPN_d8", + ) + + self.conv_h0 = ConvBNLayer( + in_channels=num_inputs[0], + out_channels=num_outputs[0], + kernel_size=1, + stride=1, + act=None, + name="conv_h{}".format(0), + ) + self.conv_h1 = ConvBNLayer( + in_channels=num_inputs[1], + out_channels=num_outputs[1], + kernel_size=1, + stride=1, + act=None, + name="conv_h{}".format(1), + ) + self.conv_h2 = ConvBNLayer( + in_channels=num_inputs[2], + out_channels=num_outputs[2], + kernel_size=1, + stride=1, + act=None, + name="conv_h{}".format(2), + ) + self.conv_h3 = ConvBNLayer( + in_channels=num_inputs[3], + out_channels=num_outputs[3], + kernel_size=1, + stride=1, + act=None, + name="conv_h{}".format(3), + ) + self.conv_h4 = ConvBNLayer( + in_channels=num_inputs[4], + out_channels=num_outputs[4], + kernel_size=1, + stride=1, + act=None, + name="conv_h{}".format(4), + ) + + self.dconv0 = DeConvBNLayer( + in_channels=num_outputs[0], + out_channels=num_outputs[0 + 1], + name="dconv_{}".format(0), + ) + self.dconv1 = DeConvBNLayer( + in_channels=num_outputs[1], + out_channels=num_outputs[1 + 1], + act=None, + name="dconv_{}".format(1), + ) + self.dconv2 = DeConvBNLayer( + in_channels=num_outputs[2], + out_channels=num_outputs[2 + 1], + act=None, + name="dconv_{}".format(2), + ) + self.dconv3 = DeConvBNLayer( + in_channels=num_outputs[3], + out_channels=num_outputs[3 + 1], + act=None, + name="dconv_{}".format(3), + ) + self.conv_g1 = ConvBNLayer( + in_channels=num_outputs[1], + out_channels=num_outputs[1], + kernel_size=3, + stride=1, + act="relu", + name="conv_g{}".format(1), + ) + self.conv_g2 = ConvBNLayer( + in_channels=num_outputs[2], + out_channels=num_outputs[2], + kernel_size=3, + stride=1, + act="relu", + name="conv_g{}".format(2), + ) + self.conv_g3 = ConvBNLayer( + in_channels=num_outputs[3], + out_channels=num_outputs[3], + kernel_size=3, + stride=1, + act="relu", + name="conv_g{}".format(3), + ) + self.conv_g4 = ConvBNLayer( + in_channels=num_outputs[4], + out_channels=num_outputs[4], + kernel_size=3, + stride=1, + act="relu", + name="conv_g{}".format(4), + ) + self.convf = ConvBNLayer( + in_channels=num_outputs[4], + out_channels=num_outputs[4], + kernel_size=1, + stride=1, + act=None, + name="conv_f{}".format(4), + ) + + def forward(self, x): + c0, c1, c2, c3, c4, c5, c6 = x + # FPN_Down_Fusion + f = [c0, c1, c2] + g = [None, None, None] + h = [None, None, None] + h[0] = self.conv_bn_layer_1(f[0]) + h[1] = self.conv_bn_layer_2(f[1]) + h[2] = self.conv_bn_layer_3(f[2]) + + g[0] = self.conv_bn_layer_4(h[0]) + g[1] = paddle.add(g[0], h[1]) + g[1] = F.relu(g[1]) + g[1] = self.conv_bn_layer_5(g[1]) + g[1] = self.conv_bn_layer_6(g[1]) + + g[2] = paddle.add(g[1], h[2]) + g[2] = F.relu(g[2]) + g[2] = self.conv_bn_layer_7(g[2]) + f_down = self.conv_bn_layer_8(g[2]) + + # FPN UP Fusion + f1 = [c6, c5, c4, c3, c2] + g = [None, None, None, None, None] + h = [None, None, None, None, None] + h[0] = self.conv_h0(f1[0]) + h[1] = self.conv_h1(f1[1]) + h[2] = self.conv_h2(f1[2]) + h[3] = self.conv_h3(f1[3]) + h[4] = self.conv_h4(f1[4]) + + g[0] = self.dconv0(h[0]) + g[1] = paddle.add(g[0], h[1]) + g[1] = F.relu(g[1]) + g[1] = self.conv_g1(g[1]) + g[1] = self.dconv1(g[1]) + + g[2] = paddle.add(g[1], h[2]) + g[2] = F.relu(g[2]) + g[2] = self.conv_g2(g[2]) + g[2] = self.dconv2(g[2]) + + g[3] = paddle.add(g[2], h[3]) + g[3] = F.relu(g[3]) + g[3] = self.conv_g3(g[3]) + g[3] = self.dconv3(g[3]) + + g[4] = paddle.add(x=g[3], y=h[4]) + g[4] = F.relu(g[4]) + g[4] = self.conv_g4(g[4]) + f_up = self.convf(g[4]) + f_common = paddle.add(f_down, f_up) + f_common = F.relu(f_common) + return f_common diff --git a/ppocr/modeling/necks/pren_fpn.py b/ppocr/modeling/necks/pren_fpn.py new file mode 100644 index 0000000..29c98e9 --- /dev/null +++ b/ppocr/modeling/necks/pren_fpn.py @@ -0,0 +1,177 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Code is refer from: +https://github.com/RuijieJ/pren/blob/main/Nets/Aggregation.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +import paddle.nn.functional as F + + +class PoolAggregate(nn.Layer): + def __init__(self, n_r, d_in, d_middle=None, d_out=None): + super(PoolAggregate, self).__init__() + if not d_middle: + d_middle = d_in + if not d_out: + d_out = d_in + + self.d_in = d_in + self.d_middle = d_middle + self.d_out = d_out + self.act = nn.Swish() + + self.n_r = n_r + self.aggs = self._build_aggs() + + def _build_aggs(self): + aggs = [] + for i in range(self.n_r): + aggs.append( + self.add_sublayer( + "{}".format(i), + nn.Sequential( + ( + "conv1", + nn.Conv2D( + self.d_in, self.d_middle, 3, 2, 1, bias_attr=False + ), + ), + ("bn1", nn.BatchNorm(self.d_middle)), + ("act", self.act), + ( + "conv2", + nn.Conv2D( + self.d_middle, self.d_out, 3, 2, 1, bias_attr=False + ), + ), + ("bn2", nn.BatchNorm(self.d_out)), + ), + ) + ) + return aggs + + def forward(self, x): + b = x.shape[0] + outs = [] + for agg in self.aggs: + y = agg(x) + p = F.adaptive_avg_pool2d(y, 1) + outs.append(p.reshape((b, 1, self.d_out))) + out = paddle.concat(outs, 1) + return out + + +class WeightAggregate(nn.Layer): + def __init__(self, n_r, d_in, d_middle=None, d_out=None): + super(WeightAggregate, self).__init__() + if not d_middle: + d_middle = d_in + if not d_out: + d_out = d_in + + self.n_r = n_r + self.d_out = d_out + self.act = nn.Swish() + + self.conv_n = nn.Sequential( + ("conv1", nn.Conv2D(d_in, d_in, 3, 1, 1, bias_attr=False)), + ("bn1", nn.BatchNorm(d_in)), + ("act1", self.act), + ("conv2", nn.Conv2D(d_in, n_r, 1, bias_attr=False)), + ("bn2", nn.BatchNorm(n_r)), + ("act2", nn.Sigmoid()), + ) + self.conv_d = nn.Sequential( + ("conv1", nn.Conv2D(d_in, d_middle, 3, 1, 1, bias_attr=False)), + ("bn1", nn.BatchNorm(d_middle)), + ("act1", self.act), + ("conv2", nn.Conv2D(d_middle, d_out, 1, bias_attr=False)), + ("bn2", nn.BatchNorm(d_out)), + ) + + def forward(self, x): + b, _, h, w = x.shape + + hmaps = self.conv_n(x) + fmaps = self.conv_d(x) + r = paddle.bmm( + hmaps.reshape((b, self.n_r, h * w)), + fmaps.reshape((b, self.d_out, h * w)).transpose((0, 2, 1)), + ) + return r + + +class GCN(nn.Layer): + def __init__(self, d_in, n_in, d_out=None, n_out=None, dropout=0.1): + super(GCN, self).__init__() + if not d_out: + d_out = d_in + if not n_out: + n_out = d_in + + self.conv_n = nn.Conv1D(n_in, n_out, 1) + self.linear = nn.Linear(d_in, d_out) + self.dropout = nn.Dropout(dropout) + self.act = nn.Swish() + + def forward(self, x): + x = self.conv_n(x) + x = self.dropout(self.linear(x)) + return self.act(x) + + +class PRENFPN(nn.Layer): + def __init__(self, in_channels, n_r, d_model, max_len, dropout): + super(PRENFPN, self).__init__() + assert len(in_channels) == 3, "in_channels' length must be 3." + c1, c2, c3 = in_channels # the depths are from big to small + # build fpn + assert d_model % 3 == 0, "{} can't be divided by 3.".format(d_model) + self.agg_p1 = PoolAggregate(n_r, c1, d_out=d_model // 3) + self.agg_p2 = PoolAggregate(n_r, c2, d_out=d_model // 3) + self.agg_p3 = PoolAggregate(n_r, c3, d_out=d_model // 3) + + self.agg_w1 = WeightAggregate(n_r, c1, 4 * c1, d_model // 3) + self.agg_w2 = WeightAggregate(n_r, c2, 4 * c2, d_model // 3) + self.agg_w3 = WeightAggregate(n_r, c3, 4 * c3, d_model // 3) + + self.gcn_pool = GCN(d_model, n_r, d_model, max_len, dropout) + self.gcn_weight = GCN(d_model, n_r, d_model, max_len, dropout) + + self.out_channels = d_model + + def forward(self, inputs): + f3, f5, f7 = inputs + + rp1 = self.agg_p1(f3) + rp2 = self.agg_p2(f5) + rp3 = self.agg_p3(f7) + rp = paddle.concat([rp1, rp2, rp3], 2) # [b,nr,d] + + rw1 = self.agg_w1(f3) + rw2 = self.agg_w2(f5) + rw3 = self.agg_w3(f7) + rw = paddle.concat([rw1, rw2, rw3], 2) # [b,nr,d] + + y1 = self.gcn_pool(rp) + y2 = self.gcn_weight(rw) + y = 0.5 * (y1 + y2) + return y # [b,max_len,d] diff --git a/ppocr/modeling/necks/rf_adaptor.py b/ppocr/modeling/necks/rf_adaptor.py new file mode 100644 index 0000000..e27e894 --- /dev/null +++ b/ppocr/modeling/necks/rf_adaptor.py @@ -0,0 +1,146 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/hikopensource/DAVAR-Lab-OCR/blob/main/davarocr/davar_rcg/models/connects/single_block/RFAdaptor.py +""" + +import paddle +import paddle.nn as nn +from paddle.nn.initializer import TruncatedNormal, Constant, Normal, KaimingNormal + +kaiming_init_ = KaimingNormal() +zeros_ = Constant(value=0.0) +ones_ = Constant(value=1.0) + + +class S2VAdaptor(nn.Layer): + """Semantic to Visual adaptation module""" + + def __init__(self, in_channels=512): + super(S2VAdaptor, self).__init__() + + self.in_channels = in_channels # 512 + + # feature strengthen module, channel attention + self.channel_inter = nn.Linear( + self.in_channels, self.in_channels, bias_attr=False + ) + self.channel_bn = nn.BatchNorm1D(self.in_channels) + self.channel_act = nn.ReLU() + self.apply(self.init_weights) + + def init_weights(self, m): + if isinstance(m, nn.Conv2D): + kaiming_init_(m.weight) + if isinstance(m, nn.Conv2D) and m.bias is not None: + zeros_(m.bias) + elif isinstance(m, (nn.BatchNorm, nn.BatchNorm2D, nn.BatchNorm1D)): + zeros_(m.bias) + ones_(m.weight) + + def forward(self, semantic): + semantic_source = semantic # batch, channel, height, width + + # feature transformation + semantic = semantic.squeeze(2).transpose([0, 2, 1]) # batch, width, channel + channel_att = self.channel_inter(semantic) # batch, width, channel + channel_att = channel_att.transpose([0, 2, 1]) # batch, channel, width + channel_bn = self.channel_bn(channel_att) # batch, channel, width + channel_att = self.channel_act(channel_bn) # batch, channel, width + + # Feature enhancement + channel_output = semantic_source * channel_att.unsqueeze( + -2 + ) # batch, channel, 1, width + + return channel_output + + +class V2SAdaptor(nn.Layer): + """Visual to Semantic adaptation module""" + + def __init__(self, in_channels=512, return_mask=False): + super(V2SAdaptor, self).__init__() + + # parameter initialization + self.in_channels = in_channels + self.return_mask = return_mask + + # output transformation + self.channel_inter = nn.Linear( + self.in_channels, self.in_channels, bias_attr=False + ) + self.channel_bn = nn.BatchNorm1D(self.in_channels) + self.channel_act = nn.ReLU() + + def forward(self, visual): + # Feature enhancement + visual = visual.squeeze(2).transpose([0, 2, 1]) # batch, width, channel + channel_att = self.channel_inter(visual) # batch, width, channel + channel_att = channel_att.transpose([0, 2, 1]) # batch, channel, width + channel_bn = self.channel_bn(channel_att) # batch, channel, width + channel_att = self.channel_act(channel_bn) # batch, channel, width + + # size alignment + channel_output = channel_att.unsqueeze(-2) # batch, width, channel + + if self.return_mask: + return channel_output, channel_att + return channel_output + + +class RFAdaptor(nn.Layer): + def __init__(self, in_channels=512, use_v2s=True, use_s2v=True, **kwargs): + super(RFAdaptor, self).__init__() + if use_v2s is True: + self.neck_v2s = V2SAdaptor(in_channels=in_channels, **kwargs) + else: + self.neck_v2s = None + if use_s2v is True: + self.neck_s2v = S2VAdaptor(in_channels=in_channels, **kwargs) + else: + self.neck_s2v = None + self.out_channels = in_channels + + def forward(self, x): + visual_feature, rcg_feature = x + if visual_feature is not None: + ( + batch, + source_channels, + v_source_height, + v_source_width, + ) = visual_feature.shape + visual_feature = visual_feature.reshape( + [batch, source_channels, 1, v_source_height * v_source_width] + ) + + if self.neck_v2s is not None: + v_rcg_feature = rcg_feature * self.neck_v2s(visual_feature) + else: + v_rcg_feature = rcg_feature + + if self.neck_s2v is not None: + v_visual_feature = visual_feature + self.neck_s2v(rcg_feature) + else: + v_visual_feature = visual_feature + if v_rcg_feature is not None: + batch, source_channels, source_height, source_width = v_rcg_feature.shape + v_rcg_feature = v_rcg_feature.reshape( + [batch, source_channels, 1, source_height * source_width] + ) + + v_rcg_feature = v_rcg_feature.squeeze(2).transpose([0, 2, 1]) + return v_visual_feature, v_rcg_feature diff --git a/ppocr/modeling/necks/rnn.py b/ppocr/modeling/necks/rnn.py new file mode 100644 index 0000000..1d935be --- /dev/null +++ b/ppocr/modeling/necks/rnn.py @@ -0,0 +1,392 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +from paddle.nn.initializer import KaimingNormal + +from ppocr.modeling.heads.rec_ctc_head import get_para_bias_attr +from ppocr.modeling.backbones.rec_svtrnet import ( + Block, + ConvBNLayer, + trunc_normal_, + zeros_, + ones_, +) + + +class Im2Seq(nn.Layer): + def __init__(self, in_channels, **kwargs): + super().__init__() + self.out_channels = in_channels + + def forward(self, x): + B, C, H, W = x.shape + assert H == 1 + x = x.squeeze(axis=2) + x = x.transpose([0, 2, 1]) # (NTC)(batch, width, channels) + return x + + +class EncoderWithRNN(nn.Layer): + def __init__(self, in_channels, hidden_size): + super(EncoderWithRNN, self).__init__() + self.out_channels = hidden_size * 2 + self.lstm = nn.LSTM( + in_channels, hidden_size, direction="bidirectional", num_layers=2 + ) + + def forward(self, x): + x, _ = self.lstm(x) + return x + + +class BidirectionalLSTM(nn.Layer): + def __init__( + self, + input_size, + hidden_size, + output_size=None, + num_layers=1, + dropout=0, + direction=False, + time_major=False, + with_linear=False, + ): + super(BidirectionalLSTM, self).__init__() + self.with_linear = with_linear + self.rnn = nn.LSTM( + input_size, + hidden_size, + num_layers=num_layers, + dropout=dropout, + direction=direction, + time_major=time_major, + ) + + # text recognition the specified structure LSTM with linear + if self.with_linear: + self.linear = nn.Linear(hidden_size * 2, output_size) + + def forward(self, input_feature): + recurrent, _ = self.rnn( + input_feature + ) # batch_size x T x input_size -> batch_size x T x (2*hidden_size) + if self.with_linear: + output = self.linear(recurrent) # batch_size x T x output_size + return output + return recurrent + + +class EncoderWithCascadeRNN(nn.Layer): + def __init__( + self, in_channels, hidden_size, out_channels, num_layers=2, with_linear=False + ): + super(EncoderWithCascadeRNN, self).__init__() + self.out_channels = out_channels[-1] + self.encoder = nn.LayerList( + [ + BidirectionalLSTM( + in_channels if i == 0 else out_channels[i - 1], + hidden_size, + output_size=out_channels[i], + num_layers=1, + direction="bidirectional", + with_linear=with_linear, + ) + for i in range(num_layers) + ] + ) + + def forward(self, x): + for i, l in enumerate(self.encoder): + x = l(x) + return x + + +class EncoderWithFC(nn.Layer): + def __init__(self, in_channels, hidden_size): + super(EncoderWithFC, self).__init__() + self.out_channels = hidden_size + weight_attr, bias_attr = get_para_bias_attr(l2_decay=0.00001, k=in_channels) + self.fc = nn.Linear( + in_channels, + hidden_size, + weight_attr=weight_attr, + bias_attr=bias_attr, + name="reduce_encoder_fea", + ) + + def forward(self, x): + x = self.fc(x) + return x + + +class EncoderWithSVTR(nn.Layer): + def __init__( + self, + in_channels, + dims=64, # XS + depth=2, + hidden_dims=120, + use_guide=False, + num_heads=8, + qkv_bias=True, + mlp_ratio=2.0, + drop_rate=0.1, + attn_drop_rate=0.1, + drop_path=0.0, + kernel_size=[3, 3], + qk_scale=None, + ): + super(EncoderWithSVTR, self).__init__() + self.depth = depth + self.use_guide = use_guide + self.conv1 = ConvBNLayer( + in_channels, + in_channels // 8, + kernel_size=kernel_size, + padding=[kernel_size[0] // 2, kernel_size[1] // 2], + act=nn.Swish, + ) + self.conv2 = ConvBNLayer( + in_channels // 8, hidden_dims, kernel_size=1, act=nn.Swish + ) + + self.svtr_block = nn.LayerList( + [ + Block( + dim=hidden_dims, + num_heads=num_heads, + mixer="Global", + HW=None, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + act_layer=nn.Swish, + attn_drop=attn_drop_rate, + drop_path=drop_path, + norm_layer="nn.LayerNorm", + epsilon=1e-05, + prenorm=False, + ) + for i in range(depth) + ] + ) + self.norm = nn.LayerNorm(hidden_dims, epsilon=1e-6) + self.conv3 = ConvBNLayer(hidden_dims, in_channels, kernel_size=1, act=nn.Swish) + # last conv-nxn, the input is concat of input tensor and conv3 output tensor + self.conv4 = ConvBNLayer( + 2 * in_channels, + in_channels // 8, + kernel_size=kernel_size, + padding=[kernel_size[0] // 2, kernel_size[1] // 2], + act=nn.Swish, + ) + + self.conv1x1 = ConvBNLayer(in_channels // 8, dims, kernel_size=1, act=nn.Swish) + self.out_channels = dims + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight) + if isinstance(m, nn.Linear) and m.bias is not None: + zeros_(m.bias) + elif isinstance(m, nn.LayerNorm): + zeros_(m.bias) + ones_(m.weight) + + def forward(self, x): + # for use guide + if self.use_guide: + z = x.clone() + z.stop_gradient = True + else: + z = x + # for short cut + h = z + # reduce dim + z = self.conv1(z) + z = self.conv2(z) + # SVTR global block + B, C, H, W = z.shape + z = z.flatten(2).transpose([0, 2, 1]) + for blk in self.svtr_block: + z = blk(z) + z = self.norm(z) + # last stage + z = z.reshape([0, H, W, C]).transpose([0, 3, 1, 2]) + z = self.conv3(z) + z = paddle.concat((h, z), axis=1) + z = self.conv1x1(self.conv4(z)) + return z + + +class EncoderWithLightSVTR(nn.Layer): + """Light SVTR neck: attention with lightweight skip connection. + + Replaces heavy guide fusion with a lightweight 1x1 skip conv + add. + Adds a DWConv before attention for local context priming. + + When use_guide=True, applies stop_gradient to input so that neck + gradients do not propagate back to the backbone (implicit regularization). + """ + + def __init__( + self, + in_channels, + dims=64, + depth=1, + num_heads=8, + qkv_bias=True, + mlp_ratio=4.0, + drop_rate=0.1, + attn_drop_rate=0.1, + drop_path=0.0, + qk_scale=None, + local_kernel=7, + use_guide=False, + ): + super().__init__() + self.use_guide = use_guide + self.conv_reduce = ConvBNLayer( + in_channels, + dims, + kernel_size=1, + act=nn.Swish, + ) + self.local_conv = nn.Sequential( + nn.Conv2D( + dims, + dims, + [1, local_kernel], + padding=[0, local_kernel // 2], + groups=dims, + bias_attr=False, + weight_attr=paddle.ParamAttr(initializer=KaimingNormal()), + ), + nn.BatchNorm2D(dims), + nn.Swish(), + ) + + self.svtr_block = nn.LayerList( + [ + Block( + dim=dims, + num_heads=num_heads, + mixer="Global", + HW=None, + mlp_ratio=mlp_ratio, + qkv_bias=qkv_bias, + qk_scale=qk_scale, + drop=drop_rate, + act_layer=nn.Swish, + attn_drop=attn_drop_rate, + drop_path=drop_path, + norm_layer="nn.LayerNorm", + epsilon=1e-05, + prenorm=False, + ) + for _ in range(depth) + ] + ) + self.norm = nn.LayerNorm(dims, epsilon=1e-6) + + self.skip_conv = ConvBNLayer( + in_channels, + dims, + kernel_size=1, + act=nn.Swish, + ) + + self.out_channels = dims + self.apply(self._init_weights) + + def _init_weights(self, m): + if isinstance(m, nn.Linear): + trunc_normal_(m.weight) + if m.bias is not None: + zeros_(m.bias) + elif isinstance(m, nn.LayerNorm): + zeros_(m.bias) + ones_(m.weight) + + def forward(self, x): + if self.use_guide: + x = x.clone() + x.stop_gradient = True + skip = self.skip_conv(x) + z = self.conv_reduce(x) + z = z + self.local_conv(z) + B, C, H, W = z.shape + z = z.flatten(2).transpose([0, 2, 1]) + for blk in self.svtr_block: + z = blk(z) + z = self.norm(z) + z = z.reshape([0, H, W, C]).transpose([0, 3, 1, 2]) + z = z + skip + return z + + +class SequenceEncoder(nn.Layer): + def __init__(self, in_channels, encoder_type, hidden_size=48, **kwargs): + super(SequenceEncoder, self).__init__() + self.encoder_reshape = Im2Seq(in_channels) + self.out_channels = self.encoder_reshape.out_channels + self.encoder_type = encoder_type + if encoder_type == "reshape": + self.only_reshape = True + else: + support_encoder_dict = { + "reshape": Im2Seq, + "fc": EncoderWithFC, + "rnn": EncoderWithRNN, + "svtr": EncoderWithSVTR, + "lightsvtr": EncoderWithLightSVTR, + "cascadernn": EncoderWithCascadeRNN, + } + assert encoder_type in support_encoder_dict, "{} must in {}".format( + encoder_type, support_encoder_dict.keys() + ) + if encoder_type in ("svtr", "lightsvtr"): + self.encoder = support_encoder_dict[encoder_type]( + self.encoder_reshape.out_channels, **kwargs + ) + elif encoder_type == "cascadernn": + self.encoder = support_encoder_dict[encoder_type]( + self.encoder_reshape.out_channels, hidden_size, **kwargs + ) + else: + self.encoder = support_encoder_dict[encoder_type]( + self.encoder_reshape.out_channels, hidden_size + ) + self.out_channels = self.encoder.out_channels + self.only_reshape = False + + def forward(self, x): + if self.encoder_type not in ("svtr", "lightsvtr"): + x = self.encoder_reshape(x) + if not self.only_reshape: + x = self.encoder(x) + return x + else: + x = self.encoder(x) + x = self.encoder_reshape(x) + return x diff --git a/ppocr/modeling/necks/sast_fpn.py b/ppocr/modeling/necks/sast_fpn.py new file mode 100644 index 0000000..4804806 --- /dev/null +++ b/ppocr/modeling/necks/sast_fpn.py @@ -0,0 +1,368 @@ +# copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride, + groups=1, + if_act=True, + act=None, + name=None, + ): + super(ConvBNLayer, self).__init__() + self.if_act = if_act + self.act = act + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + + self.bn = nn.BatchNorm( + num_channels=out_channels, + act=act, + param_attr=ParamAttr(name="bn_" + name + "_scale"), + bias_attr=ParamAttr(name="bn_" + name + "_offset"), + moving_mean_name="bn_" + name + "_mean", + moving_variance_name="bn_" + name + "_variance", + ) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + return x + + +class DeConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride, + groups=1, + if_act=True, + act=None, + name=None, + ): + super(DeConvBNLayer, self).__init__() + self.if_act = if_act + self.act = act + self.deconv = nn.Conv2DTranspose( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + self.bn = nn.BatchNorm( + num_channels=out_channels, + act=act, + param_attr=ParamAttr(name="bn_" + name + "_scale"), + bias_attr=ParamAttr(name="bn_" + name + "_offset"), + moving_mean_name="bn_" + name + "_mean", + moving_variance_name="bn_" + name + "_variance", + ) + + def forward(self, x): + x = self.deconv(x) + x = self.bn(x) + return x + + +class FPN_Up_Fusion(nn.Layer): + def __init__(self, in_channels): + super(FPN_Up_Fusion, self).__init__() + in_channels = in_channels[::-1] + out_channels = [256, 256, 192, 192, 128] + + self.h0_conv = ConvBNLayer( + in_channels[0], out_channels[0], 1, 1, act=None, name="fpn_up_h0" + ) + self.h1_conv = ConvBNLayer( + in_channels[1], out_channels[1], 1, 1, act=None, name="fpn_up_h1" + ) + self.h2_conv = ConvBNLayer( + in_channels[2], out_channels[2], 1, 1, act=None, name="fpn_up_h2" + ) + self.h3_conv = ConvBNLayer( + in_channels[3], out_channels[3], 1, 1, act=None, name="fpn_up_h3" + ) + self.h4_conv = ConvBNLayer( + in_channels[4], out_channels[4], 1, 1, act=None, name="fpn_up_h4" + ) + + self.g0_conv = DeConvBNLayer( + out_channels[0], out_channels[1], 4, 2, act=None, name="fpn_up_g0" + ) + + self.g1_conv = nn.Sequential( + ConvBNLayer( + out_channels[1], out_channels[1], 3, 1, act="relu", name="fpn_up_g1_1" + ), + DeConvBNLayer( + out_channels[1], out_channels[2], 4, 2, act=None, name="fpn_up_g1_2" + ), + ) + self.g2_conv = nn.Sequential( + ConvBNLayer( + out_channels[2], out_channels[2], 3, 1, act="relu", name="fpn_up_g2_1" + ), + DeConvBNLayer( + out_channels[2], out_channels[3], 4, 2, act=None, name="fpn_up_g2_2" + ), + ) + self.g3_conv = nn.Sequential( + ConvBNLayer( + out_channels[3], out_channels[3], 3, 1, act="relu", name="fpn_up_g3_1" + ), + DeConvBNLayer( + out_channels[3], out_channels[4], 4, 2, act=None, name="fpn_up_g3_2" + ), + ) + + self.g4_conv = nn.Sequential( + ConvBNLayer( + out_channels[4], + out_channels[4], + 3, + 1, + act="relu", + name="fpn_up_fusion_1", + ), + ConvBNLayer( + out_channels[4], out_channels[4], 1, 1, act=None, name="fpn_up_fusion_2" + ), + ) + + def _add_relu(self, x1, x2): + x = paddle.add(x=x1, y=x2) + x = F.relu(x) + return x + + def forward(self, x): + f = x[2:][::-1] + h0 = self.h0_conv(f[0]) + h1 = self.h1_conv(f[1]) + h2 = self.h2_conv(f[2]) + h3 = self.h3_conv(f[3]) + h4 = self.h4_conv(f[4]) + + g0 = self.g0_conv(h0) + g1 = self._add_relu(g0, h1) + g1 = self.g1_conv(g1) + g2 = self.g2_conv(self._add_relu(g1, h2)) + g3 = self.g3_conv(self._add_relu(g2, h3)) + g4 = self.g4_conv(self._add_relu(g3, h4)) + + return g4 + + +class FPN_Down_Fusion(nn.Layer): + def __init__(self, in_channels): + super(FPN_Down_Fusion, self).__init__() + out_channels = [32, 64, 128] + + self.h0_conv = ConvBNLayer( + in_channels[0], out_channels[0], 3, 1, act=None, name="fpn_down_h0" + ) + self.h1_conv = ConvBNLayer( + in_channels[1], out_channels[1], 3, 1, act=None, name="fpn_down_h1" + ) + self.h2_conv = ConvBNLayer( + in_channels[2], out_channels[2], 3, 1, act=None, name="fpn_down_h2" + ) + + self.g0_conv = ConvBNLayer( + out_channels[0], out_channels[1], 3, 2, act=None, name="fpn_down_g0" + ) + + self.g1_conv = nn.Sequential( + ConvBNLayer( + out_channels[1], out_channels[1], 3, 1, act="relu", name="fpn_down_g1_1" + ), + ConvBNLayer( + out_channels[1], out_channels[2], 3, 2, act=None, name="fpn_down_g1_2" + ), + ) + + self.g2_conv = nn.Sequential( + ConvBNLayer( + out_channels[2], + out_channels[2], + 3, + 1, + act="relu", + name="fpn_down_fusion_1", + ), + ConvBNLayer( + out_channels[2], + out_channels[2], + 1, + 1, + act=None, + name="fpn_down_fusion_2", + ), + ) + + def forward(self, x): + f = x[:3] + h0 = self.h0_conv(f[0]) + h1 = self.h1_conv(f[1]) + h2 = self.h2_conv(f[2]) + g0 = self.g0_conv(h0) + g1 = paddle.add(x=g0, y=h1) + g1 = F.relu(g1) + g1 = self.g1_conv(g1) + g2 = paddle.add(x=g1, y=h2) + g2 = F.relu(g2) + g2 = self.g2_conv(g2) + return g2 + + +class Cross_Attention(nn.Layer): + def __init__(self, in_channels): + super(Cross_Attention, self).__init__() + self.theta_conv = ConvBNLayer( + in_channels, in_channels, 1, 1, act="relu", name="f_theta" + ) + self.phi_conv = ConvBNLayer( + in_channels, in_channels, 1, 1, act="relu", name="f_phi" + ) + self.g_conv = ConvBNLayer( + in_channels, in_channels, 1, 1, act="relu", name="f_g" + ) + + self.fh_weight_conv = ConvBNLayer( + in_channels, in_channels, 1, 1, act=None, name="fh_weight" + ) + self.fh_sc_conv = ConvBNLayer( + in_channels, in_channels, 1, 1, act=None, name="fh_sc" + ) + + self.fv_weight_conv = ConvBNLayer( + in_channels, in_channels, 1, 1, act=None, name="fv_weight" + ) + self.fv_sc_conv = ConvBNLayer( + in_channels, in_channels, 1, 1, act=None, name="fv_sc" + ) + + self.f_attn_conv = ConvBNLayer( + in_channels * 2, in_channels, 1, 1, act="relu", name="f_attn" + ) + + def _cal_fweight(self, f, shape): + f_theta, f_phi, f_g = f + # flatten + f_theta = paddle.transpose(f_theta, [0, 2, 3, 1]) + f_theta = paddle.reshape(f_theta, [shape[0] * shape[1], shape[2], 128]) + f_phi = paddle.transpose(f_phi, [0, 2, 3, 1]) + f_phi = paddle.reshape(f_phi, [shape[0] * shape[1], shape[2], 128]) + f_g = paddle.transpose(f_g, [0, 2, 3, 1]) + f_g = paddle.reshape(f_g, [shape[0] * shape[1], shape[2], 128]) + # correlation + f_attn = paddle.matmul(f_theta, paddle.transpose(f_phi, [0, 2, 1])) + # scale + f_attn = f_attn / (128**0.5) + f_attn = F.softmax(f_attn) + # weighted sum + f_weight = paddle.matmul(f_attn, f_g) + f_weight = paddle.reshape(f_weight, [shape[0], shape[1], shape[2], 128]) + return f_weight + + def forward(self, f_common): + f_shape = f_common.shape + # print('f_shape: ', f_shape) + + f_theta = self.theta_conv(f_common) + f_phi = self.phi_conv(f_common) + f_g = self.g_conv(f_common) + + ######## horizon ######## + fh_weight = self._cal_fweight( + [f_theta, f_phi, f_g], [f_shape[0], f_shape[2], f_shape[3]] + ) + fh_weight = paddle.transpose(fh_weight, [0, 3, 1, 2]) + fh_weight = self.fh_weight_conv(fh_weight) + # short cut + fh_sc = self.fh_sc_conv(f_common) + f_h = F.relu(fh_weight + fh_sc) + + ######## vertical ######## + fv_theta = paddle.transpose(f_theta, [0, 1, 3, 2]) + fv_phi = paddle.transpose(f_phi, [0, 1, 3, 2]) + fv_g = paddle.transpose(f_g, [0, 1, 3, 2]) + fv_weight = self._cal_fweight( + [fv_theta, fv_phi, fv_g], [f_shape[0], f_shape[3], f_shape[2]] + ) + fv_weight = paddle.transpose(fv_weight, [0, 3, 2, 1]) + fv_weight = self.fv_weight_conv(fv_weight) + # short cut + fv_sc = self.fv_sc_conv(f_common) + f_v = F.relu(fv_weight + fv_sc) + + ######## merge ######## + f_attn = paddle.concat([f_h, f_v], axis=1) + f_attn = self.f_attn_conv(f_attn) + return f_attn + + +class SASTFPN(nn.Layer): + def __init__(self, in_channels, with_cab=False, **kwargs): + super(SASTFPN, self).__init__() + self.in_channels = in_channels + self.with_cab = with_cab + self.FPN_Down_Fusion = FPN_Down_Fusion(self.in_channels) + self.FPN_Up_Fusion = FPN_Up_Fusion(self.in_channels) + self.out_channels = 128 + self.cross_attention = Cross_Attention(self.out_channels) + + def forward(self, x): + # down fpn + f_down = self.FPN_Down_Fusion(x) + + # up fpn + f_up = self.FPN_Up_Fusion(x) + + # fusion + f_common = paddle.add(x=f_down, y=f_up) + f_common = F.relu(f_common) + + if self.with_cab: + # print('enhence f_common with CAB.') + f_common = self.cross_attention(f_common) + + return f_common diff --git a/ppocr/modeling/necks/table_fpn.py b/ppocr/modeling/necks/table_fpn.py new file mode 100644 index 0000000..d273965 --- /dev/null +++ b/ppocr/modeling/necks/table_fpn.py @@ -0,0 +1,123 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import paddle +from paddle import nn +import paddle.nn.functional as F +from paddle import ParamAttr + + +class TableFPN(nn.Layer): + def __init__(self, in_channels, out_channels, **kwargs): + super(TableFPN, self).__init__() + self.out_channels = 512 + weight_attr = paddle.nn.initializer.KaimingUniform() + self.in2_conv = nn.Conv2D( + in_channels=in_channels[0], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.in3_conv = nn.Conv2D( + in_channels=in_channels[1], + out_channels=self.out_channels, + kernel_size=1, + stride=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.in4_conv = nn.Conv2D( + in_channels=in_channels[2], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.in5_conv = nn.Conv2D( + in_channels=in_channels[3], + out_channels=self.out_channels, + kernel_size=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.p5_conv = nn.Conv2D( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.p4_conv = nn.Conv2D( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.p3_conv = nn.Conv2D( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.p2_conv = nn.Conv2D( + in_channels=self.out_channels, + out_channels=self.out_channels // 4, + kernel_size=3, + padding=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + self.fuse_conv = nn.Conv2D( + in_channels=self.out_channels * 4, + out_channels=512, + kernel_size=3, + padding=1, + weight_attr=ParamAttr(initializer=weight_attr), + bias_attr=False, + ) + + def forward(self, x): + c2, c3, c4, c5 = x + + in5 = self.in5_conv(c5) + in4 = self.in4_conv(c4) + in3 = self.in3_conv(c3) + in2 = self.in2_conv(c2) + + out4 = in4 + F.upsample( + in5, size=in4.shape[2:4], mode="nearest", align_mode=1 + ) # 1/16 + out3 = in3 + F.upsample( + out4, size=in3.shape[2:4], mode="nearest", align_mode=1 + ) # 1/8 + out2 = in2 + F.upsample( + out3, size=in2.shape[2:4], mode="nearest", align_mode=1 + ) # 1/4 + + p4 = F.upsample(out4, size=in5.shape[2:4], mode="nearest", align_mode=1) + p3 = F.upsample(out3, size=in5.shape[2:4], mode="nearest", align_mode=1) + p2 = F.upsample(out2, size=in5.shape[2:4], mode="nearest", align_mode=1) + fuse = paddle.concat([in5, p4, p3, p2], axis=1) + fuse_conv = self.fuse_conv(fuse) * 0.005 + return [c5 + fuse_conv] diff --git a/ppocr/modeling/transforms/__init__.py b/ppocr/modeling/transforms/__init__.py new file mode 100755 index 0000000..24aeca3 --- /dev/null +++ b/ppocr/modeling/transforms/__init__.py @@ -0,0 +1,32 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = ["build_transform"] + + +def build_transform(config): + from .tps import TPS + from .stn import STN_ON + from .tsrn import TSRN + from .tbsrn import TBSRN + from .gaspin_transformer import GA_SPIN_Transformer as GA_SPIN + + support_dict = ["TPS", "STN_ON", "GA_SPIN", "TSRN", "TBSRN"] + + module_name = config.pop("name") + assert module_name in support_dict, Exception( + "transform only support {}".format(support_dict) + ) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/modeling/transforms/gaspin_transformer.py b/ppocr/modeling/transforms/gaspin_transformer.py new file mode 100644 index 0000000..22e9242 --- /dev/null +++ b/ppocr/modeling/transforms/gaspin_transformer.py @@ -0,0 +1,319 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn, ParamAttr +from paddle.nn import functional as F +import numpy as np +import functools +from .tps import GridGenerator + +"""This code is refer from: +https://github.com/hikopensource/DAVAR-Lab-OCR/davarocr/davar_rcg/models/transformations/gaspin_transformation.py +""" + + +class SP_TransformerNetwork(nn.Layer): + """ + Sturture-Preserving Transformation (SPT) as Equa. (2) in Ref. [1] + Ref: [1] SPIN: Structure-Preserving Inner Offset Network for Scene Text Recognition. AAAI-2021. + """ + + def __init__(self, nc=1, default_type=5): + """Based on SPIN + Args: + nc (int): number of input channels (usually in 1 or 3) + default_type (int): the complexity of transformation intensities (by default set to 6 as the paper) + """ + super(SP_TransformerNetwork, self).__init__() + self.power_list = self.cal_K(default_type) + self.sigmoid = nn.Sigmoid() + self.bn = nn.InstanceNorm2D(nc) + + def cal_K(self, k=5): + """ + + Args: + k (int): the complexity of transformation intensities (by default set to 6 as the paper) + + Returns: + List: the normalized intensity of each pixel in [0,1], denoted as \beta [1x(2K+1)] + + """ + from math import log + + x = [] + if k != 0: + for i in range(1, k + 1): + lower = round( + log(1 - (0.5 / (k + 1)) * i) / log((0.5 / (k + 1)) * i), 2 + ) + upper = round(1 / lower, 2) + x.append(lower) + x.append(upper) + x.append(1.00) + return x + + def forward(self, batch_I, weights, offsets, lambda_color=None): + """ + + Args: + batch_I (Tensor): batch of input images [batch_size x nc x I_height x I_width] + weights: + offsets: the predicted offset by AIN, a scalar + lambda_color: the learnable update gate \alpha in Equa. (5) as + g(x) = (1 - \alpha) \odot x + \alpha \odot x_{offsets} + + Returns: + Tensor: transformed images by SPN as Equa. (4) in Ref. [1] + [batch_size x I_channel_num x I_r_height x I_r_width] + + """ + batch_I = (batch_I + 1) * 0.5 + if offsets is not None: + batch_I = batch_I * (1 - lambda_color) + offsets * lambda_color + batch_weight_params = paddle.unsqueeze(paddle.unsqueeze(weights, -1), -1) + batch_I_power = paddle.stack([batch_I.pow(p) for p in self.power_list], axis=1) + + batch_weight_sum = paddle.sum(batch_I_power * batch_weight_params, axis=1) + batch_weight_sum = self.bn(batch_weight_sum) + batch_weight_sum = self.sigmoid(batch_weight_sum) + batch_weight_sum = batch_weight_sum * 2 - 1 + return batch_weight_sum + + +class GA_SPIN_Transformer(nn.Layer): + """ + Geometric-Absorbed SPIN Transformation (GA-SPIN) proposed in Ref. [1] + + + Ref: [1] SPIN: Structure-Preserving Inner Offset Network for Scene Text Recognition. AAAI-2021. + """ + + def __init__( + self, + in_channels=1, + I_r_size=(32, 100), + offsets=False, + norm_type="BN", + default_type=6, + loc_lr=1, + stn=True, + ): + """ + Args: + in_channels (int): channel of input features, + set it to 1 if the grayscale images and 3 if RGB input + I_r_size (tuple): size of rectified images (used in STN transformations) + offsets (bool): set it to False if use SPN w.o. AIN, + and set it to True if use SPIN (both with SPN and AIN) + norm_type (str): the normalization type of the module, + set it to 'BN' by default, 'IN' optionally + default_type (int): the K chromatic space, + set it to 3/5/6 depend on the complexity of transformation intensities + loc_lr (float): learning rate of location network + stn (bool): whether to use stn. + + """ + super(GA_SPIN_Transformer, self).__init__() + self.nc = in_channels + self.spt = True + self.offsets = offsets + self.stn = stn # set to True in GA-SPIN, while set it to False in SPIN + self.I_r_size = I_r_size + self.out_channels = in_channels + if norm_type == "BN": + norm_layer = functools.partial(nn.BatchNorm2D, use_global_stats=True) + elif norm_type == "IN": + norm_layer = functools.partial( + nn.InstanceNorm2D, weight_attr=False, use_global_stats=False + ) + else: + raise NotImplementedError( + "normalization layer [%s] is not found" % norm_type + ) + + if self.spt: + self.sp_net = SP_TransformerNetwork(in_channels, default_type) + self.spt_convnet = nn.Sequential( + # 32*100 + nn.Conv2D(in_channels, 32, 3, 1, 1, bias_attr=False), + norm_layer(32), + nn.ReLU(), + nn.MaxPool2D(kernel_size=2, stride=2), + # 16*50 + nn.Conv2D(32, 64, 3, 1, 1, bias_attr=False), + norm_layer(64), + nn.ReLU(), + nn.MaxPool2D(kernel_size=2, stride=2), + # 8*25 + nn.Conv2D(64, 128, 3, 1, 1, bias_attr=False), + norm_layer(128), + nn.ReLU(), + nn.MaxPool2D(kernel_size=2, stride=2), + # 4*12 + ) + self.stucture_fc1 = nn.Sequential( + nn.Conv2D(128, 256, 3, 1, 1, bias_attr=False), + norm_layer(256), + nn.ReLU(), + nn.MaxPool2D(kernel_size=2, stride=2), + nn.Conv2D(256, 256, 3, 1, 1, bias_attr=False), + norm_layer(256), + nn.ReLU(), # 2*6 + nn.MaxPool2D(kernel_size=2, stride=2), + nn.Conv2D(256, 512, 3, 1, 1, bias_attr=False), + norm_layer(512), + nn.ReLU(), # 1*3 + nn.AdaptiveAvgPool2D(1), + nn.Flatten(1, -1), # batch_size x 512 + nn.Linear(512, 256, weight_attr=nn.initializer.Normal(0.001)), + nn.BatchNorm1D(256), + nn.ReLU(), + ) + self.out_weight = 2 * default_type + 1 + self.spt_length = 2 * default_type + 1 + if offsets: + self.out_weight += 1 + if self.stn: + self.F = 20 + self.out_weight += self.F * 2 + self.GridGenerator = GridGenerator(self.F * 2, self.F) + + # self.out_weight*=nc + # Init structure_fc2 in LocalizationNetwork + initial_bias = self.init_spin(default_type * 2) + initial_bias = initial_bias.reshape(-1) + param_attr = ParamAttr( + learning_rate=loc_lr, + initializer=nn.initializer.Assign(np.zeros([256, self.out_weight])), + ) + bias_attr = ParamAttr( + learning_rate=loc_lr, initializer=nn.initializer.Assign(initial_bias) + ) + self.stucture_fc2 = nn.Linear( + 256, self.out_weight, weight_attr=param_attr, bias_attr=bias_attr + ) + self.sigmoid = nn.Sigmoid() + + if offsets: + self.offset_fc1 = nn.Sequential( + nn.Conv2D(128, 16, 3, 1, 1, bias_attr=False), + norm_layer(16), + nn.ReLU(), + ) + self.offset_fc2 = nn.Conv2D(16, in_channels, 3, 1, 1) + self.pool = nn.MaxPool2D(2, 2) + + def init_spin(self, nz): + """ + Args: + nz (int): number of paired \betas exponents, which means the value of K x 2 + + """ + init_id = [0.00] * nz + [5.00] + if self.offsets: + init_id += [-5.00] + # init_id *=3 + init = np.array(init_id) + + if self.stn: + F = self.F + ctrl_pts_x = np.linspace(-1.0, 1.0, int(F / 2)) + ctrl_pts_y_top = np.linspace(0.0, -1.0, num=int(F / 2)) + ctrl_pts_y_bottom = np.linspace(1.0, 0.0, num=int(F / 2)) + ctrl_pts_top = np.stack([ctrl_pts_x, ctrl_pts_y_top], axis=1) + ctrl_pts_bottom = np.stack([ctrl_pts_x, ctrl_pts_y_bottom], axis=1) + initial_bias = np.concatenate([ctrl_pts_top, ctrl_pts_bottom], axis=0) + initial_bias = initial_bias.reshape(-1) + init = np.concatenate([init, initial_bias], axis=0) + return init + + def forward(self, x, return_weight=False): + """ + Args: + x (Tensor): input image batch + return_weight (bool): set to False by default, + if set to True return the predicted offsets of AIN, denoted as x_{offsets} + + Returns: + Tensor: rectified image [batch_size x I_channel_num x I_height x I_width], the same as the input size + """ + + if self.spt: + feat = self.spt_convnet(x) + fc1 = self.stucture_fc1(feat) + sp_weight_fusion = self.stucture_fc2(fc1) + sp_weight_fusion = sp_weight_fusion.reshape( + [x.shape[0], self.out_weight, 1] + ) + if self.offsets: # SPIN w. AIN + lambda_color = sp_weight_fusion[:, self.spt_length, 0] + lambda_color = ( + self.sigmoid(lambda_color).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1) + ) + sp_weight = sp_weight_fusion[:, : self.spt_length, :] + offsets = self.pool(self.offset_fc2(self.offset_fc1(feat))) + + assert offsets.shape[2] == 2 # 2 + assert offsets.shape[3] == 6 # 16 + offsets = self.sigmoid(offsets) # v12 + + if return_weight: + return offsets + offsets = nn.functional.upsample( + offsets, size=(x.shape[2], x.shape[3]), mode="bilinear" + ) + + if self.stn: + batch_C_prime = sp_weight_fusion[ + :, (self.spt_length + 1) :, : + ].reshape([x.shape[0], self.F, 2]) + build_P_prime = self.GridGenerator(batch_C_prime, self.I_r_size) + build_P_prime_reshape = build_P_prime.reshape( + [build_P_prime.shape[0], self.I_r_size[0], self.I_r_size[1], 2] + ) + + else: # SPIN w.o. AIN + sp_weight = sp_weight_fusion[:, : self.spt_length, :] + lambda_color, offsets = None, None + + if self.stn: + batch_C_prime = sp_weight_fusion[:, self.spt_length :, :].reshape( + [x.shape[0], self.F, 2] + ) + build_P_prime = self.GridGenerator(batch_C_prime, self.I_r_size) + build_P_prime_reshape = build_P_prime.reshape( + [build_P_prime.shape[0], self.I_r_size[0], self.I_r_size[1], 2] + ) + + x = self.sp_net(x, sp_weight, offsets, lambda_color) + if self.stn: + is_fp16 = False + if build_P_prime_reshape.dtype != paddle.float32: + data_type = build_P_prime_reshape.dtype + x = x.cast(paddle.float32) + build_P_prime_reshape = build_P_prime_reshape.cast(paddle.float32) + is_fp16 = True + x = F.grid_sample( + x=x, grid=build_P_prime_reshape, padding_mode="border" + ) + if is_fp16: + x = x.cast(data_type) + return x diff --git a/ppocr/modeling/transforms/stn.py b/ppocr/modeling/transforms/stn.py new file mode 100644 index 0000000..a721184 --- /dev/null +++ b/ppocr/modeling/transforms/stn.py @@ -0,0 +1,147 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/ayumiymk/aster.pytorch/blob/master/lib/models/stn_head.py +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn, ParamAttr +from paddle.nn import functional as F +import numpy as np + +from .tps_spatial_transformer import TPSSpatialTransformer + + +def conv3x3_block(in_channels, out_channels, stride=1): + n = 3 * 3 * out_channels + w = math.sqrt(2.0 / n) + conv_layer = nn.Conv2D( + in_channels, + out_channels, + kernel_size=3, + stride=stride, + padding=1, + weight_attr=nn.initializer.Normal(mean=0.0, std=w), + bias_attr=nn.initializer.Constant(0), + ) + block = nn.Sequential(conv_layer, nn.BatchNorm2D(out_channels), nn.ReLU()) + return block + + +class STN(nn.Layer): + def __init__(self, in_channels, num_ctrlpoints, activation="none"): + super(STN, self).__init__() + self.in_channels = in_channels + self.num_ctrlpoints = num_ctrlpoints + self.activation = activation + self.stn_convnet = nn.Sequential( + conv3x3_block(in_channels, 32), # 32x64 + nn.MaxPool2D(kernel_size=2, stride=2), + conv3x3_block(32, 64), # 16x32 + nn.MaxPool2D(kernel_size=2, stride=2), + conv3x3_block(64, 128), # 8*16 + nn.MaxPool2D(kernel_size=2, stride=2), + conv3x3_block(128, 256), # 4*8 + nn.MaxPool2D(kernel_size=2, stride=2), + conv3x3_block(256, 256), # 2*4, + nn.MaxPool2D(kernel_size=2, stride=2), + conv3x3_block(256, 256), + ) # 1*2 + self.stn_fc1 = nn.Sequential( + nn.Linear( + 2 * 256, + 512, + weight_attr=nn.initializer.Normal(0, 0.001), + bias_attr=nn.initializer.Constant(0), + ), + nn.BatchNorm1D(512), + nn.ReLU(), + ) + fc2_bias = self.init_stn() + self.stn_fc2 = nn.Linear( + 512, + num_ctrlpoints * 2, + weight_attr=nn.initializer.Constant(0.0), + bias_attr=nn.initializer.Assign(fc2_bias), + ) + + def init_stn(self): + margin = 0.01 + sampling_num_per_side = int(self.num_ctrlpoints / 2) + ctrl_pts_x = np.linspace(margin, 1.0 - margin, sampling_num_per_side) + ctrl_pts_y_top = np.ones(sampling_num_per_side) * margin + ctrl_pts_y_bottom = np.ones(sampling_num_per_side) * (1 - margin) + ctrl_pts_top = np.stack([ctrl_pts_x, ctrl_pts_y_top], axis=1) + ctrl_pts_bottom = np.stack([ctrl_pts_x, ctrl_pts_y_bottom], axis=1) + ctrl_points = np.concatenate([ctrl_pts_top, ctrl_pts_bottom], axis=0).astype( + np.float32 + ) + if self.activation == "none": + pass + elif self.activation == "sigmoid": + ctrl_points = -np.log(1.0 / ctrl_points - 1.0) + ctrl_points = paddle.to_tensor(ctrl_points) + fc2_bias = paddle.reshape( + ctrl_points, shape=[ctrl_points.shape[0] * ctrl_points.shape[1]] + ) + return fc2_bias + + def forward(self, x): + x = self.stn_convnet(x) + batch_size, _, h, w = x.shape + x = paddle.reshape(x, shape=(batch_size, -1)) + img_feat = self.stn_fc1(x) + x = self.stn_fc2(0.1 * img_feat) + if self.activation == "sigmoid": + x = F.sigmoid(x) + x = paddle.reshape(x, shape=[-1, self.num_ctrlpoints, 2]) + return img_feat, x + + +class STN_ON(nn.Layer): + def __init__( + self, + in_channels, + tps_inputsize, + tps_outputsize, + num_control_points, + tps_margins, + stn_activation, + ): + super(STN_ON, self).__init__() + self.tps = TPSSpatialTransformer( + output_image_size=tuple(tps_outputsize), + num_control_points=num_control_points, + margins=tuple(tps_margins), + ) + self.stn_head = STN( + in_channels=in_channels, + num_ctrlpoints=num_control_points, + activation=stn_activation, + ) + self.tps_inputsize = tps_inputsize + self.out_channels = in_channels + + def forward(self, image): + stn_input = paddle.nn.functional.interpolate( + image, self.tps_inputsize, mode="bilinear", align_corners=True + ) + stn_img_feat, ctrl_points = self.stn_head(stn_input) + x, _ = self.tps(image, ctrl_points) + return x diff --git a/ppocr/modeling/transforms/tbsrn.py b/ppocr/modeling/transforms/tbsrn.py new file mode 100644 index 0000000..a1fa167 --- /dev/null +++ b/ppocr/modeling/transforms/tbsrn.py @@ -0,0 +1,298 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/FudanVI/FudanOCR/blob/main/scene-text-telescope/model/tbsrn.py +""" + +import math +import warnings +import numpy as np +import paddle +from paddle import nn +import string + +warnings.filterwarnings("ignore") + +from .tps_spatial_transformer import TPSSpatialTransformer +from .stn import STN as STNHead +from .tsrn import GruBlock, mish, UpsampleBLock +from ppocr.modeling.heads.sr_rensnet_transformer import ( + Transformer, + LayerNorm, + PositionwiseFeedForward, + MultiHeadedAttention, +) + + +def positionalencoding2d(d_model, height, width): + """ + :param d_model: dimension of the model + :param height: height of the positions + :param width: width of the positions + :return: d_model*height*width position matrix + """ + if d_model % 4 != 0: + raise ValueError( + "Cannot use sin/cos positional encoding with " + "odd dimension (got dim={:d})".format(d_model) + ) + pe = paddle.zeros([d_model, height, width]) + # Each dimension use half of d_model + d_model = int(d_model / 2) + div_term = paddle.exp( + paddle.arange(0.0, d_model, 2, dtype="int64") * -(math.log(10000.0) / d_model) + ) + pos_w = paddle.arange(0.0, width, dtype="float32").unsqueeze(1) + pos_h = paddle.arange(0.0, height, dtype="float32").unsqueeze(1) + + pe[0:d_model:2, :, :] = ( + paddle.sin(pos_w * div_term).transpose([1, 0]).unsqueeze(1).tile([1, height, 1]) + ) + pe[1:d_model:2, :, :] = ( + paddle.cos(pos_w * div_term).transpose([1, 0]).unsqueeze(1).tile([1, height, 1]) + ) + pe[d_model::2, :, :] = ( + paddle.sin(pos_h * div_term).transpose([1, 0]).unsqueeze(2).tile([1, 1, width]) + ) + pe[d_model + 1 :: 2, :, :] = ( + paddle.cos(pos_h * div_term).transpose([1, 0]).unsqueeze(2).tile([1, 1, width]) + ) + + return pe + + +class FeatureEnhancer(nn.Layer): + def __init__(self): + super(FeatureEnhancer, self).__init__() + + self.multihead = MultiHeadedAttention(h=4, d_model=128, dropout=0.1) + self.mul_layernorm1 = LayerNorm(features=128) + + self.pff = PositionwiseFeedForward(128, 128) + self.mul_layernorm3 = LayerNorm(features=128) + + self.linear = nn.Linear(128, 64) + + def forward(self, conv_feature): + """ + text : (batch, seq_len, embedding_size) + global_info: (batch, embedding_size, 1, 1) + conv_feature: (batch, channel, H, W) + """ + batch = conv_feature.shape[0] + position2d = ( + positionalencoding2d(64, 16, 64) + .cast("float32") + .unsqueeze(0) + .reshape([1, 64, 1024]) + ) + position2d = position2d.tile([batch, 1, 1]) + conv_feature = paddle.concat( + [conv_feature, position2d], 1 + ) # batch, 128(64+64), 32, 128 + result = conv_feature.transpose([0, 2, 1]) + origin_result = result + result = self.mul_layernorm1( + origin_result + self.multihead(result, result, result, mask=None)[0] + ) + origin_result = result + result = self.mul_layernorm3(origin_result + self.pff(result)) + result = self.linear(result) + return result.transpose([0, 2, 1]) + + +def str_filt(str_, voc_type): + alpha_dict = { + "digit": string.digits, + "lower": string.digits + string.ascii_lowercase, + "upper": string.digits + string.ascii_letters, + "all": string.digits + string.ascii_letters + string.punctuation, + } + if voc_type == "lower": + str_ = str_.lower() + for char in str_: + if char not in alpha_dict[voc_type]: + str_ = str_.replace(char, "") + str_ = str_.lower() + return str_ + + +class TBSRN(nn.Layer): + def __init__( + self, + in_channels=3, + scale_factor=2, + width=128, + height=32, + STN=True, + srb_nums=5, + mask=False, + hidden_units=32, + infer_mode=False, + ): + super(TBSRN, self).__init__() + in_planes = 3 + if mask: + in_planes = 4 + assert math.log(scale_factor, 2) % 1 == 0 + upsample_block_num = int(math.log(scale_factor, 2)) + self.block1 = nn.Sequential( + nn.Conv2D(in_planes, 2 * hidden_units, kernel_size=9, padding=4), + nn.PReLU(), + # nn.ReLU() + ) + self.srb_nums = srb_nums + for i in range(srb_nums): + setattr(self, "block%d" % (i + 2), RecurrentResidualBlock(2 * hidden_units)) + + setattr( + self, + "block%d" % (srb_nums + 2), + nn.Sequential( + nn.Conv2D(2 * hidden_units, 2 * hidden_units, kernel_size=3, padding=1), + nn.BatchNorm2D(2 * hidden_units), + ), + ) + + # self.non_local = NonLocalBlock2D(64, 64) + block_ = [UpsampleBLock(2 * hidden_units, 2) for _ in range(upsample_block_num)] + block_.append(nn.Conv2D(2 * hidden_units, in_planes, kernel_size=9, padding=4)) + setattr(self, "block%d" % (srb_nums + 3), nn.Sequential(*block_)) + self.tps_inputsize = [height // scale_factor, width // scale_factor] + tps_outputsize = [height // scale_factor, width // scale_factor] + num_control_points = 20 + tps_margins = [0.05, 0.05] + self.stn = STN + self.out_channels = in_channels + if self.stn: + self.tps = TPSSpatialTransformer( + output_image_size=tuple(tps_outputsize), + num_control_points=num_control_points, + margins=tuple(tps_margins), + ) + + self.stn_head = STNHead( + in_channels=in_planes, + num_ctrlpoints=num_control_points, + activation="none", + ) + self.infer_mode = infer_mode + + self.english_alphabet = ( + "-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + ) + self.english_dict = {} + for index in range(len(self.english_alphabet)): + self.english_dict[self.english_alphabet[index]] = index + transformer = Transformer(alphabet="-0123456789abcdefghijklmnopqrstuvwxyz") + self.transformer = transformer + for param in self.transformer.parameters(): + param.trainable = False + + def label_encoder(self, label): + batch = len(label) + + length = [len(i) for i in label] + length_tensor = paddle.to_tensor(length, dtype="int64") + + max_length = max(length) + input_tensor = np.zeros((batch, max_length)) + for i in range(batch): + for j in range(length[i] - 1): + input_tensor[i][j + 1] = self.english_dict[label[i][j]] + + text_gt = [] + for i in label: + for j in i: + text_gt.append(self.english_dict[j]) + text_gt = paddle.to_tensor(text_gt, dtype="int64") + + input_tensor = paddle.to_tensor(input_tensor, dtype="int64") + return length_tensor, input_tensor, text_gt + + def forward(self, x): + output = {} + if self.infer_mode: + output["lr_img"] = x + y = x + else: + output["lr_img"] = x[0] + output["hr_img"] = x[1] + y = x[0] + if self.stn and self.training: + _, ctrl_points_x = self.stn_head(y) + y, _ = self.tps(y, ctrl_points_x) + block = {"1": self.block1(y)} + for i in range(self.srb_nums + 1): + block[str(i + 2)] = getattr(self, "block%d" % (i + 2))(block[str(i + 1)]) + + block[str(self.srb_nums + 3)] = getattr(self, "block%d" % (self.srb_nums + 3))( + (block["1"] + block[str(self.srb_nums + 2)]) + ) + + sr_img = paddle.tanh(block[str(self.srb_nums + 3)]) + output["sr_img"] = sr_img + + if self.training: + hr_img = x[1] + + # add transformer + label = [str_filt(i, "lower") + "-" for i in x[2]] + length_tensor, input_tensor, text_gt = self.label_encoder(label) + hr_pred, word_attention_map_gt, hr_correct_list = self.transformer( + hr_img, length_tensor, input_tensor + ) + sr_pred, word_attention_map_pred, sr_correct_list = self.transformer( + sr_img, length_tensor, input_tensor + ) + output["hr_img"] = hr_img + output["hr_pred"] = hr_pred + output["text_gt"] = text_gt + output["word_attention_map_gt"] = word_attention_map_gt + output["sr_pred"] = sr_pred + output["word_attention_map_pred"] = word_attention_map_pred + + return output + + +class RecurrentResidualBlock(nn.Layer): + def __init__(self, channels): + super(RecurrentResidualBlock, self).__init__() + self.conv1 = nn.Conv2D(channels, channels, kernel_size=3, padding=1) + self.bn1 = nn.BatchNorm2D(channels) + self.gru1 = GruBlock(channels, channels) + # self.prelu = nn.ReLU() + self.prelu = mish() + self.conv2 = nn.Conv2D(channels, channels, kernel_size=3, padding=1) + self.bn2 = nn.BatchNorm2D(channels) + self.gru2 = GruBlock(channels, channels) + self.feature_enhancer = FeatureEnhancer() + + for p in self.parameters(): + if p.dim() > 1: + paddle.nn.initializer.XavierUniform(p) + + def forward(self, x): + residual = self.conv1(x) + residual = self.bn1(residual) + residual = self.prelu(residual) + residual = self.conv2(residual) + residual = self.bn2(residual) + + size = residual.shape + residual = residual.reshape([size[0], size[1], -1]) + residual = self.feature_enhancer(residual) + residual = residual.reshape([size[0], size[1], size[2], size[3]]) + return x + residual diff --git a/ppocr/modeling/transforms/tps.py b/ppocr/modeling/transforms/tps.py new file mode 100644 index 0000000..d5681db --- /dev/null +++ b/ppocr/modeling/transforms/tps.py @@ -0,0 +1,321 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/clovaai/deep-text-recognition-benchmark/blob/master/modules/transformation.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn, ParamAttr +from paddle.nn import functional as F +import numpy as np + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_channels, + out_channels, + kernel_size, + stride=1, + groups=1, + act=None, + name=None, + ): + super(ConvBNLayer, self).__init__() + self.conv = nn.Conv2D( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + padding=(kernel_size - 1) // 2, + groups=groups, + weight_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + ) + bn_name = "bn_" + name + self.bn = nn.BatchNorm( + out_channels, + act=act, + param_attr=ParamAttr(name=bn_name + "_scale"), + bias_attr=ParamAttr(bn_name + "_offset"), + moving_mean_name=bn_name + "_mean", + moving_variance_name=bn_name + "_variance", + ) + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + return x + + +class LocalizationNetwork(nn.Layer): + def __init__(self, in_channels, num_fiducial, loc_lr, model_name): + super(LocalizationNetwork, self).__init__() + self.F = num_fiducial + F = num_fiducial + if model_name == "large": + num_filters_list = [64, 128, 256, 512] + fc_dim = 256 + else: + num_filters_list = [16, 32, 64, 128] + fc_dim = 64 + + self.block_list = [] + for fno in range(0, len(num_filters_list)): + num_filters = num_filters_list[fno] + name = "loc_conv%d" % fno + conv = self.add_sublayer( + name, + ConvBNLayer( + in_channels=in_channels, + out_channels=num_filters, + kernel_size=3, + act="relu", + name=name, + ), + ) + self.block_list.append(conv) + if fno == len(num_filters_list) - 1: + pool = nn.AdaptiveAvgPool2D(1) + else: + pool = nn.MaxPool2D(kernel_size=2, stride=2, padding=0) + in_channels = num_filters + self.block_list.append(pool) + name = "loc_fc1" + stdv = 1.0 / math.sqrt(num_filters_list[-1] * 1.0) + self.fc1 = nn.Linear( + in_channels, + fc_dim, + weight_attr=ParamAttr( + learning_rate=loc_lr, + name=name + "_w", + initializer=nn.initializer.Uniform(-stdv, stdv), + ), + bias_attr=ParamAttr(name=name + ".b_0"), + name=name, + ) + + # Init fc2 in LocalizationNetwork + initial_bias = self.get_initial_fiducials() + initial_bias = initial_bias.reshape(-1) + name = "loc_fc2" + param_attr = ParamAttr( + learning_rate=loc_lr, + initializer=nn.initializer.Assign(np.zeros([fc_dim, F * 2])), + name=name + "_w", + ) + bias_attr = ParamAttr( + learning_rate=loc_lr, + initializer=nn.initializer.Assign(initial_bias), + name=name + "_b", + ) + self.fc2 = nn.Linear( + fc_dim, F * 2, weight_attr=param_attr, bias_attr=bias_attr, name=name + ) + self.out_channels = F * 2 + + def forward(self, x): + """ + Estimating parameters of geometric transformation + Args: + image: input + Return: + batch_C_prime: the matrix of the geometric transformation + """ + B = x.shape[0] + i = 0 + for block in self.block_list: + x = block(x) + x = x.squeeze(axis=2).squeeze(axis=2) + x = self.fc1(x) + + x = F.relu(x) + x = self.fc2(x) + x = x.reshape(shape=[-1, self.F, 2]) + return x + + def get_initial_fiducials(self): + """see RARE paper Fig. 6 (a)""" + F = self.F + ctrl_pts_x = np.linspace(-1.0, 1.0, int(F / 2)) + ctrl_pts_y_top = np.linspace(0.0, -1.0, num=int(F / 2)) + ctrl_pts_y_bottom = np.linspace(1.0, 0.0, num=int(F / 2)) + ctrl_pts_top = np.stack([ctrl_pts_x, ctrl_pts_y_top], axis=1) + ctrl_pts_bottom = np.stack([ctrl_pts_x, ctrl_pts_y_bottom], axis=1) + initial_bias = np.concatenate([ctrl_pts_top, ctrl_pts_bottom], axis=0) + return initial_bias + + +class GridGenerator(nn.Layer): + def __init__(self, in_channels, num_fiducial): + super(GridGenerator, self).__init__() + self.eps = 1e-6 + self.F = num_fiducial + + name = "ex_fc" + initializer = nn.initializer.Constant(value=0.0) + param_attr = ParamAttr( + learning_rate=0.0, initializer=initializer, name=name + "_w" + ) + bias_attr = ParamAttr( + learning_rate=0.0, initializer=initializer, name=name + "_b" + ) + self.fc = nn.Linear( + in_channels, 6, weight_attr=param_attr, bias_attr=bias_attr, name=name + ) + + def forward(self, batch_C_prime, I_r_size): + """ + Generate the grid for the grid_sampler. + Args: + batch_C_prime: the matrix of the geometric transformation + I_r_size: the shape of the input image + Return: + batch_P_prime: the grid for the grid_sampler + """ + C = self.build_C_paddle() + P = self.build_P_paddle(I_r_size) + + inv_delta_C_tensor = self.build_inv_delta_C_paddle(C).astype("float32") + P_hat_tensor = self.build_P_hat_paddle(C, paddle.to_tensor(P)).astype("float32") + + inv_delta_C_tensor.stop_gradient = True + P_hat_tensor.stop_gradient = True + + batch_C_ex_part_tensor = self.get_expand_tensor(batch_C_prime) + + batch_C_ex_part_tensor.stop_gradient = True + + batch_C_prime_with_zeros = paddle.concat( + [batch_C_prime, batch_C_ex_part_tensor], axis=1 + ) + batch_T = paddle.matmul(inv_delta_C_tensor, batch_C_prime_with_zeros) + batch_P_prime = paddle.matmul(P_hat_tensor, batch_T) + return batch_P_prime + + def build_C_paddle(self): + """Return coordinates of fiducial points in I_r; C""" + F = self.F + ctrl_pts_x = paddle.linspace(-1.0, 1.0, int(F / 2), dtype="float64") + ctrl_pts_y_top = -1 * paddle.ones([int(F / 2)], dtype="float64") + ctrl_pts_y_bottom = paddle.ones([int(F / 2)], dtype="float64") + ctrl_pts_top = paddle.stack([ctrl_pts_x, ctrl_pts_y_top], axis=1) + ctrl_pts_bottom = paddle.stack([ctrl_pts_x, ctrl_pts_y_bottom], axis=1) + C = paddle.concat([ctrl_pts_top, ctrl_pts_bottom], axis=0) + return C # F x 2 + + def build_P_paddle(self, I_r_size): + I_r_height, I_r_width = I_r_size + I_r_grid_x = ( + paddle.arange(-I_r_width, I_r_width, 2, dtype="float64") + 1.0 + ) / paddle.to_tensor(np.array([I_r_width])).astype("float64") + + I_r_grid_y = ( + paddle.arange(-I_r_height, I_r_height, 2, dtype="float64") + 1.0 + ) / paddle.to_tensor(np.array([I_r_height])).astype("float64") + + # P: self.I_r_width x self.I_r_height x 2 + P = paddle.stack(paddle.meshgrid(I_r_grid_x, I_r_grid_y), axis=2) + P = paddle.transpose(P, perm=[1, 0, 2]) + # n (= self.I_r_width x self.I_r_height) x 2 + return P.reshape([-1, 2]) + + def build_inv_delta_C_paddle(self, C): + """Return inv_delta_C which is needed to calculate T""" + F = self.F + hat_eye = paddle.eye(F, dtype="float64") # F x F + hat_C = ( + paddle.norm(C.reshape([1, F, 2]) - C.reshape([F, 1, 2]), axis=2) + hat_eye + ) + hat_C = (hat_C**2) * paddle.log(hat_C) + delta_C = paddle.concat( # F+3 x F+3 + [ + paddle.concat( + [paddle.ones((F, 1), dtype="float64"), C, hat_C], axis=1 + ), # F x F+3 + paddle.concat( + [ + paddle.zeros((2, 3), dtype="float64"), + paddle.transpose(C, perm=[1, 0]), + ], + axis=1, + ), # 2 x F+3 + paddle.concat( + [ + paddle.zeros((1, 3), dtype="float64"), + paddle.ones((1, F), dtype="float64"), + ], + axis=1, + ), # 1 x F+3 + ], + axis=0, + ) + inv_delta_C = paddle.inverse(delta_C) + return inv_delta_C # F+3 x F+3 + + def build_P_hat_paddle(self, C, P): + F = self.F + eps = self.eps + n = P.shape[0] # n (= self.I_r_width x self.I_r_height) + # P_tile: n x 2 -> n x 1 x 2 -> n x F x 2 + P_tile = paddle.tile(paddle.unsqueeze(P, axis=1), (1, F, 1)) + C_tile = paddle.unsqueeze(C, axis=0) # 1 x F x 2 + P_diff = P_tile - C_tile # n x F x 2 + # rbf_norm: n x F + rbf_norm = paddle.norm(P_diff, p=2, axis=2, keepdim=False) + + # rbf: n x F + rbf = paddle.multiply(paddle.square(rbf_norm), paddle.log(rbf_norm + eps)) + P_hat = paddle.concat([paddle.ones((n, 1), dtype="float64"), P, rbf], axis=1) + return P_hat # n x F+3 + + def get_expand_tensor(self, batch_C_prime): + B, H, C = batch_C_prime.shape + batch_C_prime = batch_C_prime.reshape([B, H * C]) + batch_C_ex_part_tensor = self.fc(batch_C_prime) + batch_C_ex_part_tensor = batch_C_ex_part_tensor.reshape([-1, 3, 2]) + return batch_C_ex_part_tensor + + +class TPS(nn.Layer): + def __init__(self, in_channels, num_fiducial, loc_lr, model_name): + super(TPS, self).__init__() + self.loc_net = LocalizationNetwork( + in_channels, num_fiducial, loc_lr, model_name + ) + self.grid_generator = GridGenerator(self.loc_net.out_channels, num_fiducial) + self.out_channels = in_channels + + def forward(self, image): + image.stop_gradient = False + batch_C_prime = self.loc_net(image) + batch_P_prime = self.grid_generator(batch_C_prime, image.shape[2:]) + batch_P_prime = batch_P_prime.reshape([-1, image.shape[2], image.shape[3], 2]) + is_fp16 = False + if batch_P_prime.dtype != paddle.float32: + data_type = batch_P_prime.dtype + image = image.cast(paddle.float32) + batch_P_prime = batch_P_prime.cast(paddle.float32) + is_fp16 = True + batch_I_r = F.grid_sample(x=image, grid=batch_P_prime) + if is_fp16: + batch_I_r = batch_I_r.cast(data_type) + + return batch_I_r diff --git a/ppocr/modeling/transforms/tps_spatial_transformer.py b/ppocr/modeling/transforms/tps_spatial_transformer.py new file mode 100644 index 0000000..fc230b0 --- /dev/null +++ b/ppocr/modeling/transforms/tps_spatial_transformer.py @@ -0,0 +1,170 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/ayumiymk/aster.pytorch/blob/master/lib/models/tps_spatial_transformer.py +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import math +import paddle +from paddle import nn, ParamAttr +from paddle.nn import functional as F +import numpy as np +import itertools + + +def grid_sample(input, grid, canvas=None): + input.stop_gradient = False + + is_fp16 = False + if grid.dtype != paddle.float32: + data_type = grid.dtype + input = input.cast(paddle.float32) + grid = grid.cast(paddle.float32) + is_fp16 = True + output = F.grid_sample(input, grid) + if is_fp16: + output = output.cast(data_type) + grid = grid.cast(data_type) + + if canvas is None: + return output + else: + input_mask = paddle.ones(shape=input.shape) + if is_fp16: + input_mask = input_mask.cast(paddle.float32) + grid = grid.cast(paddle.float32) + output_mask = F.grid_sample(input_mask, grid) + if is_fp16: + output_mask = output_mask.cast(data_type) + padded_output = output * output_mask + canvas * (1 - output_mask) + return padded_output + + +# phi(x1, x2) = r^2 * log(r), where r = ||x1 - x2||_2 +def compute_partial_repr(input_points, control_points): + N = input_points.shape[0] + M = control_points.shape[0] + pairwise_diff = paddle.reshape(input_points, shape=[N, 1, 2]) - paddle.reshape( + control_points, shape=[1, M, 2] + ) + # original implementation, very slow + # pairwise_dist = torch.sum(pairwise_diff ** 2, dim = 2) # square of distance + pairwise_diff_square = pairwise_diff * pairwise_diff + pairwise_dist = pairwise_diff_square[:, :, 0] + pairwise_diff_square[:, :, 1] + repr_matrix = 0.5 * pairwise_dist * paddle.log(pairwise_dist) + # fix numerical error for 0 * log(0), substitute all nan with 0 + mask = np.array(repr_matrix != repr_matrix) + repr_matrix[mask] = 0 + return repr_matrix + + +# output_ctrl_pts are specified, according to our task. +def build_output_control_points(num_control_points, margins): + margin_x, margin_y = margins + num_ctrl_pts_per_side = num_control_points // 2 + ctrl_pts_x = np.linspace(margin_x, 1.0 - margin_x, num_ctrl_pts_per_side) + ctrl_pts_y_top = np.ones(num_ctrl_pts_per_side) * margin_y + ctrl_pts_y_bottom = np.ones(num_ctrl_pts_per_side) * (1.0 - margin_y) + ctrl_pts_top = np.stack([ctrl_pts_x, ctrl_pts_y_top], axis=1) + ctrl_pts_bottom = np.stack([ctrl_pts_x, ctrl_pts_y_bottom], axis=1) + output_ctrl_pts_arr = np.concatenate([ctrl_pts_top, ctrl_pts_bottom], axis=0) + output_ctrl_pts = paddle.to_tensor(output_ctrl_pts_arr) + return output_ctrl_pts + + +class TPSSpatialTransformer(nn.Layer): + def __init__(self, output_image_size=None, num_control_points=None, margins=None): + super(TPSSpatialTransformer, self).__init__() + self.output_image_size = output_image_size + self.num_control_points = num_control_points + self.margins = margins + + self.target_height, self.target_width = output_image_size + target_control_points = build_output_control_points(num_control_points, margins) + N = num_control_points + + # create padded kernel matrix + forward_kernel = paddle.zeros(shape=[N + 3, N + 3]) + target_control_partial_repr = compute_partial_repr( + target_control_points, target_control_points + ) + target_control_partial_repr = paddle.cast( + target_control_partial_repr, forward_kernel.dtype + ) + forward_kernel[:N, :N] = target_control_partial_repr + forward_kernel[:N, -3] = 1 + forward_kernel[-3, :N] = 1 + target_control_points = paddle.cast(target_control_points, forward_kernel.dtype) + forward_kernel[:N, -2:] = target_control_points + forward_kernel[-2:, :N] = paddle.transpose(target_control_points, perm=[1, 0]) + # compute inverse matrix + inverse_kernel = paddle.inverse(forward_kernel) + + # create target coordinate matrix + HW = self.target_height * self.target_width + target_coordinate = list( + itertools.product(range(self.target_height), range(self.target_width)) + ) + target_coordinate = paddle.to_tensor(target_coordinate) # HW x 2 + Y, X = paddle.split(target_coordinate, target_coordinate.shape[1], axis=1) + Y = Y / (self.target_height - 1) + X = X / (self.target_width - 1) + target_coordinate = paddle.concat( + [X, Y], axis=1 + ) # convert from (y, x) to (x, y) + target_coordinate_partial_repr = compute_partial_repr( + target_coordinate, target_control_points + ) + target_coordinate_repr = paddle.concat( + [ + target_coordinate_partial_repr, + paddle.ones(shape=[HW, 1]), + target_coordinate, + ], + axis=1, + ) + + # register precomputed matrices + self.inverse_kernel = inverse_kernel + self.padding_matrix = paddle.zeros(shape=[3, 2]) + self.target_coordinate_repr = target_coordinate_repr + self.target_control_points = target_control_points + + def forward(self, input, source_control_points): + assert source_control_points.ndimension() == 3 + assert source_control_points.shape[1] == self.num_control_points + assert source_control_points.shape[2] == 2 + batch_size = source_control_points.shape[0] + + padding_matrix = paddle.expand(self.padding_matrix, shape=[batch_size, 3, 2]) + Y = paddle.concat( + [source_control_points.astype(padding_matrix.dtype), padding_matrix], 1 + ) + mapping_matrix = paddle.matmul(self.inverse_kernel, Y) + source_coordinate = paddle.matmul(self.target_coordinate_repr, mapping_matrix) + + grid = paddle.reshape( + source_coordinate, shape=[-1, self.target_height, self.target_width, 2] + ) + grid = paddle.clip( + grid, 0, 1 + ) # the source_control_points may be out of [0, 1]. + # the input to grid_sample is normalized [-1, 1], but what we get is [0, 1] + grid = 2.0 * grid - 1.0 + output_maps = grid_sample(input, grid, canvas=None) + return output_maps, source_coordinate diff --git a/ppocr/modeling/transforms/tsrn.py b/ppocr/modeling/transforms/tsrn.py new file mode 100644 index 0000000..93238fa --- /dev/null +++ b/ppocr/modeling/transforms/tsrn.py @@ -0,0 +1,215 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/FudanVI/FudanOCR/blob/main/text-gestalt/model/tsrn.py +""" + +import math +import paddle +import paddle.nn.functional as F +from paddle import nn +from collections import OrderedDict +import sys +import numpy as np +import warnings +import math, copy +import cv2 + +warnings.filterwarnings("ignore") + +from .tps_spatial_transformer import TPSSpatialTransformer +from .stn import STN as STN_model +from ppocr.modeling.heads.sr_rensnet_transformer import Transformer + + +class TSRN(nn.Layer): + def __init__( + self, + in_channels, + scale_factor=2, + width=128, + height=32, + STN=False, + srb_nums=5, + mask=False, + hidden_units=32, + infer_mode=False, + **kwargs, + ): + super(TSRN, self).__init__() + in_planes = 3 + if mask: + in_planes = 4 + assert math.log(scale_factor, 2) % 1 == 0 + upsample_block_num = int(math.log(scale_factor, 2)) + self.block1 = nn.Sequential( + nn.Conv2D(in_planes, 2 * hidden_units, kernel_size=9, padding=4), nn.PReLU() + ) + self.srb_nums = srb_nums + for i in range(srb_nums): + setattr(self, "block%d" % (i + 2), RecurrentResidualBlock(2 * hidden_units)) + + setattr( + self, + "block%d" % (srb_nums + 2), + nn.Sequential( + nn.Conv2D(2 * hidden_units, 2 * hidden_units, kernel_size=3, padding=1), + nn.BatchNorm2D(2 * hidden_units), + ), + ) + + block_ = [UpsampleBLock(2 * hidden_units, 2) for _ in range(upsample_block_num)] + block_.append(nn.Conv2D(2 * hidden_units, in_planes, kernel_size=9, padding=4)) + setattr(self, "block%d" % (srb_nums + 3), nn.Sequential(*block_)) + self.tps_inputsize = [height // scale_factor, width // scale_factor] + tps_outputsize = [height // scale_factor, width // scale_factor] + num_control_points = 20 + tps_margins = [0.05, 0.05] + self.stn = STN + if self.stn: + self.tps = TPSSpatialTransformer( + output_image_size=tuple(tps_outputsize), + num_control_points=num_control_points, + margins=tuple(tps_margins), + ) + + self.stn_head = STN_model( + in_channels=in_planes, + num_ctrlpoints=num_control_points, + activation="none", + ) + self.out_channels = in_channels + + self.r34_transformer = Transformer() + for param in self.r34_transformer.parameters(): + param.trainable = False + self.infer_mode = infer_mode + + def forward(self, x): + output = {} + if self.infer_mode: + output["lr_img"] = x + y = x + else: + output["lr_img"] = x[0] + output["hr_img"] = x[1] + y = x[0] + if self.stn and self.training: + _, ctrl_points_x = self.stn_head(y) + y, _ = self.tps(y, ctrl_points_x) + block = {"1": self.block1(y)} + for i in range(self.srb_nums + 1): + block[str(i + 2)] = getattr(self, "block%d" % (i + 2))(block[str(i + 1)]) + + block[str(self.srb_nums + 3)] = getattr(self, "block%d" % (self.srb_nums + 3))( + (block["1"] + block[str(self.srb_nums + 2)]) + ) + + sr_img = paddle.tanh(block[str(self.srb_nums + 3)]) + + output["sr_img"] = sr_img + + if self.training: + hr_img = x[1] + length = x[2] + input_tensor = x[3] + + # add transformer + sr_pred, word_attention_map_pred, _ = self.r34_transformer( + sr_img, length, input_tensor + ) + + hr_pred, word_attention_map_gt, _ = self.r34_transformer( + hr_img, length, input_tensor + ) + + output["hr_img"] = hr_img + output["hr_pred"] = hr_pred + output["word_attention_map_gt"] = word_attention_map_gt + output["sr_pred"] = sr_pred + output["word_attention_map_pred"] = word_attention_map_pred + + return output + + +class RecurrentResidualBlock(nn.Layer): + def __init__(self, channels): + super(RecurrentResidualBlock, self).__init__() + self.conv1 = nn.Conv2D(channels, channels, kernel_size=3, padding=1) + self.bn1 = nn.BatchNorm2D(channels) + self.gru1 = GruBlock(channels, channels) + self.prelu = mish() + self.conv2 = nn.Conv2D(channels, channels, kernel_size=3, padding=1) + self.bn2 = nn.BatchNorm2D(channels) + self.gru2 = GruBlock(channels, channels) + + def forward(self, x): + residual = self.conv1(x) + residual = self.bn1(residual) + residual = self.prelu(residual) + residual = self.conv2(residual) + residual = self.bn2(residual) + residual = self.gru1(residual.transpose([0, 1, 3, 2])).transpose([0, 1, 3, 2]) + + return self.gru2(x + residual) + + +class UpsampleBLock(nn.Layer): + def __init__(self, in_channels, up_scale): + super(UpsampleBLock, self).__init__() + self.conv = nn.Conv2D( + in_channels, in_channels * up_scale**2, kernel_size=3, padding=1 + ) + + self.pixel_shuffle = nn.PixelShuffle(up_scale) + self.prelu = mish() + + def forward(self, x): + x = self.conv(x) + x = self.pixel_shuffle(x) + x = self.prelu(x) + return x + + +class mish(nn.Layer): + def __init__( + self, + ): + super(mish, self).__init__() + self.activated = True + + def forward(self, x): + if self.activated: + x = x * (paddle.tanh(F.softplus(x))) + return x + + +class GruBlock(nn.Layer): + def __init__(self, in_channels, out_channels): + super(GruBlock, self).__init__() + assert out_channels % 2 == 0 + self.conv1 = nn.Conv2D(in_channels, out_channels, kernel_size=1, padding=0) + self.gru = nn.GRU(out_channels, out_channels // 2, direction="bidirectional") + + def forward(self, x): + # x: b, c, w, h + x = self.conv1(x) + x = x.transpose([0, 2, 3, 1]) # b, w, h, c + batch_size, w, h, c = x.shape + x = x.reshape([-1, h, c]) # b*w, h, c + x, _ = self.gru(x) + x = x.reshape([-1, w, h, c]) + x = x.transpose([0, 3, 1, 2]) + return x diff --git a/ppocr/optimizer/__init__.py b/ppocr/optimizer/__init__.py new file mode 100644 index 0000000..09b1d98 --- /dev/null +++ b/ppocr/optimizer/__init__.py @@ -0,0 +1,123 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals +import copy +import math +import paddle + +__all__ = ["build_optimizer"] + + +class CosineWeightDecayScheduler(object): + """Cosine-anneal the optimizer's weight decay each step. + + wd(t) = end + 0.5 * (start - end) * (1 + cos(pi * t / T)) + + During warmup the coefficient is held at `start_factor`. + """ + + def __init__( + self, optimizer, start_factor, end_factor, total_steps, warmup_steps=0 + ): + self.optimizer = optimizer + self.start_factor = start_factor + self.end_factor = end_factor + self.total_steps = total_steps + self.warmup_steps = warmup_steps + self._step = 0 + + def step(self): + self._step += 1 + if self._step <= self.warmup_steps: + wd = self.start_factor + else: + progress = (self._step - self.warmup_steps) / max( + 1, self.total_steps - self.warmup_steps + ) + progress = min(progress, 1.0) + wd = self.end_factor + 0.5 * (self.start_factor - self.end_factor) * ( + 1 + math.cos(math.pi * progress) + ) + self.optimizer.regularization._coeff = wd + + def get_wd(self): + return self.optimizer.regularization._coeff + + +def build_lr_scheduler(lr_config, epochs, step_each_epoch): + from . import learning_rate + + lr_config.update({"epochs": epochs, "step_each_epoch": step_each_epoch}) + lr_name = lr_config.pop("name", "Const") + lr = getattr(learning_rate, lr_name)(**lr_config)() + return lr + + +def build_optimizer(config, epochs, step_each_epoch, model): + from . import regularizer, optimizer + + config = copy.deepcopy(config) + # step1 build lr + lr = build_lr_scheduler(config.pop("lr"), epochs, step_each_epoch) + + # step2 build regularization + wd_scheduler = None + if "regularizer" in config and config["regularizer"] is not None: + reg_config = config.pop("regularizer") + reg_name = reg_config.pop("name") + if not hasattr(regularizer, reg_name): + reg_name += "Decay" + reg_obj = getattr(regularizer, reg_name)(**reg_config) + reg = reg_obj() + + # Build weight decay scheduler for CosineL2Decay + if isinstance(reg_obj, regularizer.CosineL2Decay): + warmup_epoch = reg_obj.warmup_epoch + warmup_steps = round(warmup_epoch * step_each_epoch) + total_steps = step_each_epoch * epochs + wd_scheduler = { + "start_factor": reg_obj.start_factor, + "end_factor": reg_obj.end_factor, + "total_steps": total_steps, + "warmup_steps": warmup_steps, + } + elif "weight_decay" in config: + reg = config.pop("weight_decay") + else: + reg = None + + # step3 build optimizer + optim_name = config.pop("name") + if "clip_norm" in config: + clip_norm = config.pop("clip_norm") + grad_clip = paddle.nn.ClipGradByNorm(clip_norm=clip_norm) + elif "clip_norm_global" in config: + clip_norm = config.pop("clip_norm_global") + grad_clip = paddle.nn.ClipGradByGlobalNorm(clip_norm=clip_norm) + else: + grad_clip = None + optim = getattr(optimizer, optim_name)( + learning_rate=lr, weight_decay=reg, grad_clip=grad_clip, **config + ) + built_optim = optim(model) + + # Instantiate the scheduler now that we have the real optimizer + if wd_scheduler is not None: + wd_scheduler = CosineWeightDecayScheduler(built_optim, **wd_scheduler) + + return built_optim, lr, wd_scheduler diff --git a/ppocr/optimizer/learning_rate.py b/ppocr/optimizer/learning_rate.py new file mode 100644 index 0000000..257dd3d --- /dev/null +++ b/ppocr/optimizer/learning_rate.py @@ -0,0 +1,503 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from paddle.optimizer import lr +from .lr_scheduler import CyclicalCosineDecay, OneCycleDecay, TwoStepCosineDecay + + +class Linear(object): + """ + Linear learning rate decay + Args: + lr (float): The initial learning rate. It is a python float number. + epochs(int): The decay step size. It determines the decay cycle. + end_lr(float, optional): The minimum final learning rate. Default: 0.0001. + power(float, optional): Power of polynomial. Default: 1.0. + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__( + self, + learning_rate, + epochs, + step_each_epoch, + end_lr=0.0, + power=1.0, + warmup_epoch=0, + last_epoch=-1, + **kwargs, + ): + super(Linear, self).__init__() + self.learning_rate = learning_rate + self.epochs = epochs * step_each_epoch + self.end_lr = end_lr + self.power = power + self.last_epoch = last_epoch + self.warmup_epoch = round(warmup_epoch * step_each_epoch) + + def __call__(self): + learning_rate = lr.PolynomialDecay( + learning_rate=self.learning_rate, + decay_steps=self.epochs, + end_lr=self.end_lr, + power=self.power, + last_epoch=self.last_epoch, + ) + if self.warmup_epoch > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.learning_rate, + last_epoch=self.last_epoch, + ) + return learning_rate + + +class Cosine(object): + """ + Cosine learning rate decay + lr = 0.05 * (math.cos(epoch * (math.pi / epochs)) + 1) + Args: + lr(float): initial learning rate + step_each_epoch(int): steps each epoch + epochs(int): total training epochs + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__( + self, + learning_rate, + step_each_epoch, + epochs, + warmup_epoch=0, + last_epoch=-1, + **kwargs, + ): + super(Cosine, self).__init__() + self.learning_rate = learning_rate + self.T_max = step_each_epoch * epochs + self.last_epoch = last_epoch + self.warmup_epoch = round(warmup_epoch * step_each_epoch) + + def __call__(self): + learning_rate = lr.CosineAnnealingDecay( + learning_rate=self.learning_rate, + T_max=self.T_max, + last_epoch=self.last_epoch, + ) + if self.warmup_epoch > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.learning_rate, + last_epoch=self.last_epoch, + ) + return learning_rate + + +class LinearWarmupCosine(object): + """ + LinearWarmupCosine learning rate decay + Args: + learning_rate(float): initial learning rate + step_each_epoch(int): steps each epoch + epochs(int): total training epochs + start_lr (float): Initial learning rate of warm up. + min_lr (float): Minimum learning rate in CosineAnnealingDecay. + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__( + self, + learning_rate, + step_each_epoch, + epochs, + warmup_steps=5000, + start_lr=1e-5, + min_lr=1e-8, + last_epoch=-1, + **kwargs, + ): + super(LinearWarmupCosine, self).__init__() + self.learning_rate = float(learning_rate) + self.T_max = step_each_epoch * epochs + self.last_epoch = last_epoch + self.warmup_steps = warmup_steps + self.start_lr = float(start_lr) + self.min_lr = float(min_lr) + + def __call__(self): + learning_rate = lr.CosineAnnealingDecay( + learning_rate=self.learning_rate, + T_max=self.T_max, + eta_min=self.min_lr, + last_epoch=self.last_epoch, + ) + if self.warmup_steps > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_steps, + start_lr=self.start_lr, + end_lr=self.learning_rate, + last_epoch=self.last_epoch, + ) + return learning_rate + + +class Step(object): + """ + Piecewise learning rate decay + Args: + step_each_epoch(int): steps each epoch + learning_rate (float): The initial learning rate. It is a python float number. + step_size (int): the interval to update. + gamma (float, optional): The Ratio that the learning rate will be reduced. ``new_lr = origin_lr * gamma`` . + It should be less than 1.0. Default: 0.1. + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__( + self, + learning_rate, + step_size, + step_each_epoch, + gamma, + warmup_epoch=0, + last_epoch=-1, + **kwargs, + ): + super(Step, self).__init__() + self.step_size = step_each_epoch * step_size + self.learning_rate = learning_rate + self.gamma = gamma + self.last_epoch = last_epoch + self.warmup_epoch = round(warmup_epoch * step_each_epoch) + + def __call__(self): + learning_rate = lr.StepDecay( + learning_rate=self.learning_rate, + step_size=self.step_size, + gamma=self.gamma, + last_epoch=self.last_epoch, + ) + if self.warmup_epoch > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.learning_rate, + last_epoch=self.last_epoch, + ) + return learning_rate + + +class Piecewise(object): + """ + Piecewise learning rate decay + Args: + boundaries(list): A list of steps numbers. The type of element in the list is python int. + values(list): A list of learning rate values that will be picked during different epoch boundaries. + The type of element in the list is python float. + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__( + self, + step_each_epoch, + decay_epochs, + values, + warmup_epoch=0, + last_epoch=-1, + **kwargs, + ): + super(Piecewise, self).__init__() + self.boundaries = [step_each_epoch * e for e in decay_epochs] + self.values = values + self.last_epoch = last_epoch + self.warmup_epoch = round(warmup_epoch * step_each_epoch) + + def __call__(self): + learning_rate = lr.PiecewiseDecay( + boundaries=self.boundaries, values=self.values, last_epoch=self.last_epoch + ) + if self.warmup_epoch > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.values[0], + last_epoch=self.last_epoch, + ) + return learning_rate + + +class CyclicalCosine(object): + """ + Cyclical cosine learning rate decay + Args: + learning_rate(float): initial learning rate + step_each_epoch(int): steps each epoch + epochs(int): total training epochs + cycle(int): period of the cosine learning rate + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__( + self, + learning_rate, + step_each_epoch, + epochs, + cycle, + warmup_epoch=0, + last_epoch=-1, + **kwargs, + ): + super(CyclicalCosine, self).__init__() + self.learning_rate = learning_rate + self.T_max = step_each_epoch * epochs + self.last_epoch = last_epoch + self.warmup_epoch = round(warmup_epoch * step_each_epoch) + self.cycle = round(cycle * step_each_epoch) + + def __call__(self): + learning_rate = CyclicalCosineDecay( + learning_rate=self.learning_rate, + T_max=self.T_max, + cycle=self.cycle, + last_epoch=self.last_epoch, + ) + if self.warmup_epoch > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.learning_rate, + last_epoch=self.last_epoch, + ) + return learning_rate + + +class OneCycle(object): + """ + One Cycle learning rate decay + Args: + max_lr(float): Upper learning rate boundaries + epochs(int): total training epochs + step_each_epoch(int): steps each epoch + anneal_strategy(str): {‘cos’, ‘linear’} Specifies the annealing strategy: “cos” for cosine annealing, “linear” for linear annealing. + Default: ‘cos’ + three_phase(bool): If True, use a third phase of the schedule to annihilate the learning rate according to ‘final_div_factor’ + instead of modifying the second phase (the first two phases will be symmetrical about the step indicated by ‘pct_start’). + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__( + self, + max_lr, + epochs, + step_each_epoch, + anneal_strategy="cos", + three_phase=False, + warmup_epoch=0, + last_epoch=-1, + **kwargs, + ): + super(OneCycle, self).__init__() + self.max_lr = max_lr + self.epochs = epochs + self.steps_per_epoch = step_each_epoch + self.anneal_strategy = anneal_strategy + self.three_phase = three_phase + self.last_epoch = last_epoch + self.warmup_epoch = round(warmup_epoch * step_each_epoch) + + def __call__(self): + learning_rate = OneCycleDecay( + max_lr=self.max_lr, + epochs=self.epochs, + steps_per_epoch=self.steps_per_epoch, + anneal_strategy=self.anneal_strategy, + three_phase=self.three_phase, + last_epoch=self.last_epoch, + ) + if self.warmup_epoch > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.max_lr, + last_epoch=self.last_epoch, + ) + return learning_rate + + +class Const(object): + """ + Const learning rate decay + Args: + learning_rate(float): initial learning rate + step_each_epoch(int): steps each epoch + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__( + self, learning_rate, step_each_epoch, warmup_epoch=0, last_epoch=-1, **kwargs + ): + super(Const, self).__init__() + self.learning_rate = learning_rate + self.last_epoch = last_epoch + self.warmup_epoch = round(warmup_epoch * step_each_epoch) + + def __call__(self): + learning_rate = self.learning_rate + if self.warmup_epoch > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.learning_rate, + last_epoch=self.last_epoch, + ) + return learning_rate + + +class DecayLearningRate(object): + """ + DecayLearningRate learning rate decay + new_lr = (lr - end_lr) * (1 - epoch/decay_steps)**power + end_lr + Args: + learning_rate(float): initial learning rate + step_each_epoch(int): steps each epoch + epochs(int): total training epochs + factor(float): Power of polynomial, should greater than 0.0 to get learning rate decay. Default: 0.9 + end_lr(float): The minimum final learning rate. Default: 0.0. + """ + + def __init__( + self, learning_rate, step_each_epoch, epochs, factor=0.9, end_lr=0, **kwargs + ): + super(DecayLearningRate, self).__init__() + self.learning_rate = learning_rate + self.epochs = epochs + 1 + self.factor = factor + self.end_lr = 0 + self.decay_steps = step_each_epoch * epochs + + def __call__(self): + learning_rate = lr.PolynomialDecay( + learning_rate=self.learning_rate, + decay_steps=self.decay_steps, + power=self.factor, + end_lr=self.end_lr, + ) + return learning_rate + + +class MultiStepDecay(object): + """ + Piecewise learning rate decay + Args: + step_each_epoch(int): steps each epoch + learning_rate (float): The initial learning rate. It is a python float number. + step_size (int): the interval to update. + gamma (float, optional): The Ratio that the learning rate will be reduced. ``new_lr = origin_lr * gamma`` . + It should be less than 1.0. Default: 0.1. + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__( + self, + learning_rate, + milestones, + step_each_epoch, + gamma, + warmup_epoch=0, + last_epoch=-1, + **kwargs, + ): + super(MultiStepDecay, self).__init__() + self.milestones = [step_each_epoch * e for e in milestones] + self.learning_rate = learning_rate + self.gamma = gamma + self.last_epoch = last_epoch + self.warmup_epoch = round(warmup_epoch * step_each_epoch) + + def __call__(self): + learning_rate = lr.MultiStepDecay( + learning_rate=self.learning_rate, + milestones=self.milestones, + gamma=self.gamma, + last_epoch=self.last_epoch, + ) + if self.warmup_epoch > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.learning_rate, + last_epoch=self.last_epoch, + ) + return learning_rate + + +class TwoStepCosine(object): + """ + Cosine learning rate decay + lr = 0.05 * (math.cos(epoch * (math.pi / epochs)) + 1) + Args: + lr(float): initial learning rate + step_each_epoch(int): steps each epoch + epochs(int): total training epochs + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + """ + + def __init__( + self, + learning_rate, + step_each_epoch, + epochs, + warmup_epoch=0, + last_epoch=-1, + **kwargs, + ): + super(TwoStepCosine, self).__init__() + self.learning_rate = learning_rate + self.T_max1 = step_each_epoch * 200 + self.T_max2 = step_each_epoch * epochs + self.last_epoch = last_epoch + self.warmup_epoch = round(warmup_epoch * step_each_epoch) + + def __call__(self): + learning_rate = TwoStepCosineDecay( + learning_rate=self.learning_rate, + T_max1=self.T_max1, + T_max2=self.T_max2, + last_epoch=self.last_epoch, + ) + if self.warmup_epoch > 0: + learning_rate = lr.LinearWarmup( + learning_rate=learning_rate, + warmup_steps=self.warmup_epoch, + start_lr=0.0, + end_lr=self.learning_rate, + last_epoch=self.last_epoch, + ) + return learning_rate diff --git a/ppocr/optimizer/lr_scheduler.py b/ppocr/optimizer/lr_scheduler.py new file mode 100644 index 0000000..1393e40 --- /dev/null +++ b/ppocr/optimizer/lr_scheduler.py @@ -0,0 +1,240 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +from paddle.optimizer.lr import LRScheduler + + +class CyclicalCosineDecay(LRScheduler): + def __init__( + self, learning_rate, T_max, cycle=1, last_epoch=-1, eta_min=0.0, verbose=False + ): + """ + Cyclical cosine learning rate decay + A learning rate which can be referred in https://arxiv.org/pdf/2012.12645.pdf + Args: + learning rate(float): learning rate + T_max(int): maximum epoch num + cycle(int): period of the cosine decay + last_epoch (int, optional): The index of last epoch. Can be set to restart training. Default: -1, means initial learning rate. + eta_min(float): minimum learning rate during training + verbose(bool): whether to print learning rate for each epoch + """ + super(CyclicalCosineDecay, self).__init__(learning_rate, last_epoch, verbose) + self.cycle = cycle + self.eta_min = eta_min + + def get_lr(self): + if self.last_epoch == 0: + return self.base_lr + reletive_epoch = self.last_epoch % self.cycle + lr = self.eta_min + 0.5 * (self.base_lr - self.eta_min) * ( + 1 + math.cos(math.pi * reletive_epoch / self.cycle) + ) + return lr + + +class OneCycleDecay(LRScheduler): + """ + One Cycle learning rate decay + A learning rate which can be referred in https://arxiv.org/abs/1708.07120 + Code referred in https://pytorch.org/docs/stable/_modules/torch/optim/lr_scheduler.html#OneCycleLR + """ + + def __init__( + self, + max_lr, + epochs=None, + steps_per_epoch=None, + pct_start=0.3, + anneal_strategy="cos", + div_factor=25.0, + final_div_factor=1e4, + three_phase=False, + last_epoch=-1, + verbose=False, + ): + # Validate total_steps + if epochs <= 0 or not isinstance(epochs, int): + raise ValueError( + "Expected positive integer epochs, but got {}".format(epochs) + ) + if steps_per_epoch <= 0 or not isinstance(steps_per_epoch, int): + raise ValueError( + "Expected positive integer steps_per_epoch, but got {}".format( + steps_per_epoch + ) + ) + self.total_steps = epochs * steps_per_epoch + + self.max_lr = max_lr + self.initial_lr = self.max_lr / div_factor + self.min_lr = self.initial_lr / final_div_factor + + if three_phase: + self._schedule_phases = [ + { + "end_step": float(pct_start * self.total_steps) - 1, + "start_lr": self.initial_lr, + "end_lr": self.max_lr, + }, + { + "end_step": float(2 * pct_start * self.total_steps) - 2, + "start_lr": self.max_lr, + "end_lr": self.initial_lr, + }, + { + "end_step": self.total_steps - 1, + "start_lr": self.initial_lr, + "end_lr": self.min_lr, + }, + ] + else: + self._schedule_phases = [ + { + "end_step": float(pct_start * self.total_steps) - 1, + "start_lr": self.initial_lr, + "end_lr": self.max_lr, + }, + { + "end_step": self.total_steps - 1, + "start_lr": self.max_lr, + "end_lr": self.min_lr, + }, + ] + + # Validate pct_start + if pct_start < 0 or pct_start > 1 or not isinstance(pct_start, float): + raise ValueError( + "Expected float between 0 and 1 pct_start, but got {}".format(pct_start) + ) + + # Validate anneal_strategy + if anneal_strategy not in ["cos", "linear"]: + raise ValueError( + "anneal_strategy must by one of 'cos' or 'linear', instead got {}".format( + anneal_strategy + ) + ) + elif anneal_strategy == "cos": + self.anneal_func = self._annealing_cos + elif anneal_strategy == "linear": + self.anneal_func = self._annealing_linear + + super(OneCycleDecay, self).__init__(max_lr, last_epoch, verbose) + + def _annealing_cos(self, start, end, pct): + "Cosine anneal from `start` to `end` as pct goes from 0.0 to 1.0." + cos_out = math.cos(math.pi * pct) + 1 + return end + (start - end) / 2.0 * cos_out + + def _annealing_linear(self, start, end, pct): + "Linearly anneal from `start` to `end` as pct goes from 0.0 to 1.0." + return (end - start) * pct + start + + def get_lr(self): + computed_lr = 0.0 + step_num = self.last_epoch + + if step_num > self.total_steps: + raise ValueError( + "Tried to step {} times. The specified number of total steps is {}".format( + step_num + 1, self.total_steps + ) + ) + start_step = 0 + for i, phase in enumerate(self._schedule_phases): + end_step = phase["end_step"] + if step_num <= end_step or i == len(self._schedule_phases) - 1: + pct = (step_num - start_step) / (end_step - start_step) + computed_lr = self.anneal_func(phase["start_lr"], phase["end_lr"], pct) + break + start_step = phase["end_step"] + + return computed_lr + + +class TwoStepCosineDecay(LRScheduler): + def __init__( + self, learning_rate, T_max1, T_max2, eta_min=0, last_epoch=-1, verbose=False + ): + if not isinstance(T_max1, int): + raise TypeError( + "The type of 'T_max1' in 'CosineAnnealingDecay' must be 'int', but received %s." + % type(T_max1) + ) + if not isinstance(T_max2, int): + raise TypeError( + "The type of 'T_max2' in 'CosineAnnealingDecay' must be 'int', but received %s." + % type(T_max2) + ) + if not isinstance(eta_min, (float, int)): + raise TypeError( + "The type of 'eta_min' in 'CosineAnnealingDecay' must be 'float, int', but received %s." + % type(eta_min) + ) + assert T_max1 > 0 and isinstance( + T_max1, int + ), " 'T_max1' must be a positive integer." + assert T_max2 > 0 and isinstance( + T_max2, int + ), " 'T_max1' must be a positive integer." + self.T_max1 = T_max1 + self.T_max2 = T_max2 + self.eta_min = float(eta_min) + super(TwoStepCosineDecay, self).__init__(learning_rate, last_epoch, verbose) + + def get_lr(self): + if self.last_epoch <= self.T_max1: + if self.last_epoch == 0: + return self.base_lr + elif (self.last_epoch - 1 - self.T_max1) % (2 * self.T_max1) == 0: + return ( + self.last_lr + + (self.base_lr - self.eta_min) + * (1 - math.cos(math.pi / self.T_max1)) + / 2 + ) + + return (1 + math.cos(math.pi * self.last_epoch / self.T_max1)) / ( + 1 + math.cos(math.pi * (self.last_epoch - 1) / self.T_max1) + ) * (self.last_lr - self.eta_min) + self.eta_min + else: + if (self.last_epoch - 1 - self.T_max2) % (2 * self.T_max2) == 0: + return ( + self.last_lr + + (self.base_lr - self.eta_min) + * (1 - math.cos(math.pi / self.T_max2)) + / 2 + ) + + return (1 + math.cos(math.pi * self.last_epoch / self.T_max2)) / ( + 1 + math.cos(math.pi * (self.last_epoch - 1) / self.T_max2) + ) * (self.last_lr - self.eta_min) + self.eta_min + + def _get_closed_form_lr(self): + if self.last_epoch <= self.T_max1: + return ( + self.eta_min + + (self.base_lr - self.eta_min) + * (1 + math.cos(math.pi * self.last_epoch / self.T_max1)) + / 2 + ) + else: + return ( + self.eta_min + + (self.base_lr - self.eta_min) + * (1 + math.cos(math.pi * self.last_epoch / self.T_max2)) + / 2 + ) diff --git a/ppocr/optimizer/optimizer.py b/ppocr/optimizer/optimizer.py new file mode 100644 index 0000000..d7f78a5 --- /dev/null +++ b/ppocr/optimizer/optimizer.py @@ -0,0 +1,292 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +from paddle import optimizer as optim + + +class Momentum(object): + """ + Simple Momentum optimizer with velocity state. + Args: + learning_rate (float|Variable) - The learning rate used to update parameters. + Can be a float value or a Variable with one float value as data element. + momentum (float) - Momentum factor. + regularization (WeightDecayRegularizer, optional) - The strategy of regularization. + """ + + def __init__( + self, learning_rate, momentum, weight_decay=None, grad_clip=None, **args + ): + super(Momentum, self).__init__() + self.learning_rate = learning_rate + self.momentum = momentum + self.weight_decay = weight_decay + self.grad_clip = grad_clip + + def __call__(self, model): + train_params = [ + param for param in model.parameters() if param.trainable is True + ] + opt = optim.Momentum( + learning_rate=self.learning_rate, + momentum=self.momentum, + weight_decay=self.weight_decay, + grad_clip=self.grad_clip, + parameters=train_params, + ) + return opt + + +class Adam(object): + def __init__( + self, + learning_rate=0.001, + beta1=0.9, + beta2=0.999, + epsilon=1e-08, + parameter_list=None, + weight_decay=None, + grad_clip=None, + name=None, + lazy_mode=False, + **kwargs, + ): + self.learning_rate = learning_rate + self.beta1 = beta1 + self.beta2 = beta2 + self.epsilon = epsilon + self.parameter_list = parameter_list + self.learning_rate = learning_rate + self.weight_decay = weight_decay + self.grad_clip = grad_clip + self.name = name + self.lazy_mode = lazy_mode + self.group_lr = kwargs.get("group_lr", False) + self.training_step = kwargs.get("training_step", None) + + def __call__(self, model): + if self.group_lr: + if self.training_step == "LF_2": + import paddle + + if isinstance(model, paddle.DataParallel): # multi gpu + mlm = model._layers.head.MLM_VRM.MLM.parameters() + pre_mlm_pp = ( + model._layers.head.MLM_VRM.Prediction.pp_share.parameters() + ) + pre_mlm_w = ( + model._layers.head.MLM_VRM.Prediction.w_share.parameters() + ) + else: # single gpu + mlm = model.head.MLM_VRM.MLM.parameters() + pre_mlm_pp = model.head.MLM_VRM.Prediction.pp_share.parameters() + pre_mlm_w = model.head.MLM_VRM.Prediction.w_share.parameters() + + total = [] + for param in mlm: + total.append(id(param)) + for param in pre_mlm_pp: + total.append(id(param)) + for param in pre_mlm_w: + total.append(id(param)) + + group_base_params = [ + param for param in model.parameters() if id(param) in total + ] + group_small_params = [ + param for param in model.parameters() if id(param) not in total + ] + train_params = [ + {"params": group_base_params}, + { + "params": group_small_params, + "learning_rate": self.learning_rate.values[0] * 0.1, + }, + ] + + else: + print("group lr currently only support VisionLAN in LF_2 training step") + train_params = [ + param for param in model.parameters() if param.trainable is True + ] + else: + train_params = [ + param for param in model.parameters() if param.trainable is True + ] + + opt = optim.Adam( + learning_rate=self.learning_rate, + beta1=self.beta1, + beta2=self.beta2, + epsilon=self.epsilon, + weight_decay=self.weight_decay, + grad_clip=self.grad_clip, + name=self.name, + lazy_mode=self.lazy_mode, + parameters=train_params, + ) + return opt + + +class RMSProp(object): + """ + Root Mean Squared Propagation (RMSProp) is an unpublished, adaptive learning rate method. + Args: + learning_rate (float|Variable) - The learning rate used to update parameters. + Can be a float value or a Variable with one float value as data element. + momentum (float) - Momentum factor. + rho (float) - rho value in equation. + epsilon (float) - avoid division by zero, default is 1e-6. + regularization (WeightDecayRegularizer, optional) - The strategy of regularization. + """ + + def __init__( + self, + learning_rate, + momentum=0.0, + rho=0.95, + epsilon=1e-6, + weight_decay=None, + grad_clip=None, + **args, + ): + super(RMSProp, self).__init__() + self.learning_rate = learning_rate + self.momentum = momentum + self.rho = rho + self.epsilon = epsilon + self.weight_decay = weight_decay + self.grad_clip = grad_clip + + def __call__(self, model): + train_params = [ + param for param in model.parameters() if param.trainable is True + ] + opt = optim.RMSProp( + learning_rate=self.learning_rate, + momentum=self.momentum, + rho=self.rho, + epsilon=self.epsilon, + weight_decay=self.weight_decay, + grad_clip=self.grad_clip, + parameters=train_params, + ) + return opt + + +class Adadelta(object): + def __init__( + self, + learning_rate=0.001, + epsilon=1e-08, + rho=0.95, + parameter_list=None, + weight_decay=None, + grad_clip=None, + name=None, + **kwargs, + ): + self.learning_rate = learning_rate + self.epsilon = epsilon + self.rho = rho + self.parameter_list = parameter_list + self.learning_rate = learning_rate + self.weight_decay = weight_decay + self.grad_clip = grad_clip + self.name = name + + def __call__(self, model): + train_params = [ + param for param in model.parameters() if param.trainable is True + ] + opt = optim.Adadelta( + learning_rate=self.learning_rate, + epsilon=self.epsilon, + rho=self.rho, + weight_decay=self.weight_decay, + grad_clip=self.grad_clip, + name=self.name, + parameters=train_params, + ) + return opt + + +class AdamW(object): + def __init__( + self, + learning_rate=0.001, + beta1=0.9, + beta2=0.999, + epsilon=1e-8, + weight_decay=0.01, + multi_precision=False, + grad_clip=None, + no_weight_decay_name=None, + one_dim_param_no_weight_decay=False, + name=None, + lazy_mode=False, + **args, + ): + super().__init__() + self.learning_rate = learning_rate + self.beta1 = beta1 + self.beta2 = beta2 + self.epsilon = epsilon + self.grad_clip = grad_clip + self.weight_decay = 0.01 if weight_decay is None else weight_decay + self.grad_clip = grad_clip + self.name = name + self.lazy_mode = lazy_mode + self.multi_precision = multi_precision + self.no_weight_decay_name_list = ( + no_weight_decay_name.split() if no_weight_decay_name else [] + ) + self.one_dim_param_no_weight_decay = one_dim_param_no_weight_decay + + def __call__(self, model): + parameters = [param for param in model.parameters() if param.trainable is True] + + self.no_weight_decay_param_name_list = [ + p.name + for n, p in model.named_parameters() + if any(nd in n for nd in self.no_weight_decay_name_list) + ] + + if self.one_dim_param_no_weight_decay: + self.no_weight_decay_param_name_list += [ + p.name for n, p in model.named_parameters() if len(p.shape) == 1 + ] + + opt = optim.AdamW( + learning_rate=self.learning_rate, + beta1=self.beta1, + beta2=self.beta2, + epsilon=self.epsilon, + parameters=parameters, + weight_decay=self.weight_decay, + multi_precision=self.multi_precision, + grad_clip=self.grad_clip, + name=self.name, + lazy_mode=self.lazy_mode, + apply_decay_param_fun=self._apply_decay_param_fun, + ) + return opt + + def _apply_decay_param_fun(self, name): + return name not in self.no_weight_decay_param_name_list diff --git a/ppocr/optimizer/regularizer.py b/ppocr/optimizer/regularizer.py new file mode 100644 index 0000000..611ed99 --- /dev/null +++ b/ppocr/optimizer/regularizer.py @@ -0,0 +1,78 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import paddle + + +class L1Decay(object): + """ + L1 Weight Decay Regularization, which encourages the weights to be sparse. + Args: + factor(float): regularization coeff. Default:0.0. + """ + + def __init__(self, factor=0.0): + super(L1Decay, self).__init__() + self.coeff = factor + + def __call__(self): + reg = paddle.regularizer.L1Decay(self.coeff) + return reg + + +class L2Decay(object): + """ + L2 Weight Decay Regularization, which helps to prevent the model over-fitting. + Args: + factor(float): regularization coeff. Default:0.0. + """ + + def __init__(self, factor=0.0): + super(L2Decay, self).__init__() + self.coeff = float(factor) + + def __call__(self): + return self.coeff + + +class CosineL2Decay(object): + """ + L2 Weight Decay with cosine annealing schedule. + + Anneals the weight decay coefficient from `factor` to `end_factor` + following a cosine curve over total training steps, with optional + linear warmup. Avoids over-regularizing small-capacity models. + + Reference: EfficientNetV2 (Tan & Le, 2021) - "annealing the loss + incurred by weight decay regularization over the course of training". + + Args: + factor(float): initial weight decay coefficient. + end_factor(float): final weight decay coefficient. Default: 0.0. + warmup_epoch(int|float): warmup epochs (same as lr warmup). Default: 0. + """ + + def __init__(self, factor=5e-5, end_factor=0.0, warmup_epoch=0): + super(CosineL2Decay, self).__init__() + self.start_factor = float(factor) + self.end_factor = float(end_factor) + self.warmup_epoch = warmup_epoch + + def __call__(self): + return self.start_factor diff --git a/ppocr/postprocess/__init__.py b/ppocr/postprocess/__init__.py new file mode 100644 index 0000000..e427ee9 --- /dev/null +++ b/ppocr/postprocess/__init__.py @@ -0,0 +1,121 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import os +import copy + +__all__ = ["build_post_process"] + +from .db_postprocess import DBPostProcess, DistillationDBPostProcess +from .east_postprocess import EASTPostProcess +from .sast_postprocess import SASTPostProcess +from .fce_postprocess import FCEPostProcess +from .rec_postprocess import ( + CTCLabelDecode, + AttnLabelDecode, + SRNLabelDecode, + DistillationCTCLabelDecode, + NRTRLabelDecode, + SARLabelDecode, + SEEDLabelDecode, + PRENLabelDecode, + ViTSTRLabelDecode, + ABINetLabelDecode, + SPINLabelDecode, + VLLabelDecode, + RFLLabelDecode, + SATRNLabelDecode, + ParseQLabelDecode, + CPPDLabelDecode, + LaTeXOCRDecode, + UniMERNetDecode, +) +from .cls_postprocess import ClsPostProcess +from .pg_postprocess import PGPostProcess +from .vqa_token_ser_layoutlm_postprocess import ( + VQASerTokenLayoutLMPostProcess, + DistillationSerPostProcess, +) +from .vqa_token_re_layoutlm_postprocess import ( + VQAReTokenLayoutLMPostProcess, + DistillationRePostProcess, +) +from .table_postprocess import TableMasterLabelDecode, TableLabelDecode +from .picodet_postprocess import PicoDetPostProcess +from .ct_postprocess import CTPostProcess +from .drrg_postprocess import DRRGPostprocess +from .rec_postprocess import CANLabelDecode + + +def build_post_process(config, global_config=None): + support_dict = [ + "DBPostProcess", + "EASTPostProcess", + "SASTPostProcess", + "FCEPostProcess", + "CTCLabelDecode", + "AttnLabelDecode", + "ClsPostProcess", + "SRNLabelDecode", + "PGPostProcess", + "DistillationCTCLabelDecode", + "TableLabelDecode", + "DistillationDBPostProcess", + "NRTRLabelDecode", + "SARLabelDecode", + "SEEDLabelDecode", + "VQASerTokenLayoutLMPostProcess", + "VQAReTokenLayoutLMPostProcess", + "PRENLabelDecode", + "DistillationSARLabelDecode", + "ViTSTRLabelDecode", + "ABINetLabelDecode", + "TableMasterLabelDecode", + "SPINLabelDecode", + "DistillationSerPostProcess", + "DistillationRePostProcess", + "VLLabelDecode", + "PicoDetPostProcess", + "CTPostProcess", + "RFLLabelDecode", + "DRRGPostprocess", + "CANLabelDecode", + "SATRNLabelDecode", + "ParseQLabelDecode", + "CPPDLabelDecode", + "LaTeXOCRDecode", + "UniMERNetDecode", + ] + + if config["name"] == "PSEPostProcess": + from .pse_postprocess import PSEPostProcess + + support_dict.append("PSEPostProcess") + + config = copy.deepcopy(config) + module_name = config.pop("name") + if module_name == "None": + return + if global_config is not None: + config.update(global_config) + assert module_name in support_dict, Exception( + "post process only support {}".format(support_dict) + ) + module_class = eval(module_name)(**config) + return module_class diff --git a/ppocr/postprocess/cls_postprocess.py b/ppocr/postprocess/cls_postprocess.py new file mode 100644 index 0000000..06c7693 --- /dev/null +++ b/ppocr/postprocess/cls_postprocess.py @@ -0,0 +1,43 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import paddle + + +class ClsPostProcess(object): + """Convert between text-label and text-index""" + + def __init__(self, label_list=None, key=None, **kwargs): + super(ClsPostProcess, self).__init__() + self.label_list = label_list + self.key = key + + def __call__(self, preds, label=None, *args, **kwargs): + if self.key is not None: + preds = preds[self.key] + + label_list = self.label_list + if label_list is None: + label_list = {idx: idx for idx in range(preds.shape[-1])} + + if isinstance(preds, paddle.Tensor): + preds = preds.numpy() + + pred_idxs = preds.argmax(axis=1) + decode_out = [ + (label_list[idx], preds[i, idx]) for i, idx in enumerate(pred_idxs) + ] + if label is None: + return decode_out + label = [(label_list[idx], 1.0) for idx in label] + return decode_out, label diff --git a/ppocr/postprocess/ct_postprocess.py b/ppocr/postprocess/ct_postprocess.py new file mode 100755 index 0000000..3a9ce5c --- /dev/null +++ b/ppocr/postprocess/ct_postprocess.py @@ -0,0 +1,158 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is referred from: +https://github.com/shengtao96/CentripetalText/blob/main/test.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import os.path as osp +import numpy as np +import cv2 +import paddle +import pyclipper + + +class CTPostProcess(object): + """ + The post process for Centripetal Text (CT). + """ + + def __init__(self, min_score=0.88, min_area=16, box_type="poly", **kwargs): + self.min_score = min_score + self.min_area = min_area + self.box_type = box_type + + self.coord = np.zeros((2, 300, 300), dtype=np.int32) + for i in range(300): + for j in range(300): + self.coord[0, i, j] = j + self.coord[1, i, j] = i + + def __call__(self, preds, batch): + outs = preds["maps"] + out_scores = preds["score"] + + if isinstance(outs, paddle.Tensor): + outs = outs.numpy() + if isinstance(out_scores, paddle.Tensor): + out_scores = out_scores.numpy() + + batch_size = outs.shape[0] + boxes_batch = [] + for idx in range(batch_size): + bboxes = [] + scores = [] + + img_shape = batch[idx] + + org_img_size = img_shape[:3] + img_shape = img_shape[3:] + img_size = img_shape[:2] + + out = np.expand_dims(outs[idx], axis=0) + outputs = dict() + + score = np.expand_dims(out_scores[idx], axis=0) + + kernel = out[:, 0, :, :] > 0.2 + loc = out[:, 1:, :, :].astype("float32") + + score = score[0].astype(np.float32) + kernel = kernel[0].astype(np.uint8) + loc = loc[0].astype(np.float32) + + label_num, label_kernel = cv2.connectedComponents(kernel, connectivity=4) + + for i in range(1, label_num): + ind = label_kernel == i + if ind.sum() < 10: # pixel number less than 10, treated as background + label_kernel[ind] = 0 + + label = np.zeros_like(label_kernel) + h, w = label_kernel.shape + pixels = self.coord[:, :h, :w].reshape(2, -1) + points = pixels.transpose([1, 0]).astype(np.float32) + + off_points = (points + 10.0 / 4.0 * loc[:, pixels[1], pixels[0]].T).astype( + np.int32 + ) + off_points[:, 0] = np.clip(off_points[:, 0], 0, label.shape[1] - 1) + off_points[:, 1] = np.clip(off_points[:, 1], 0, label.shape[0] - 1) + + label[pixels[1], pixels[0]] = label_kernel[ + off_points[:, 1], off_points[:, 0] + ] + label[label_kernel > 0] = label_kernel[label_kernel > 0] + + score_pocket = [0.0] + for i in range(1, label_num): + ind = label_kernel == i + if ind.sum() == 0: + score_pocket.append(0.0) + continue + score_i = np.mean(score[ind]) + score_pocket.append(score_i) + + label_num = np.max(label) + 1 + label = cv2.resize( + label, (img_size[1], img_size[0]), interpolation=cv2.INTER_NEAREST + ) + + scale = ( + float(org_img_size[1]) / float(img_size[1]), + float(org_img_size[0]) / float(img_size[0]), + ) + + for i in range(1, label_num): + ind = label == i + points = np.array(np.where(ind)).transpose((1, 0)) + + if points.shape[0] < self.min_area: + continue + + score_i = score_pocket[i] + if score_i < self.min_score: + continue + + if self.box_type == "rect": + rect = cv2.minAreaRect(points[:, ::-1]) + bbox = cv2.boxPoints(rect) * scale + z = bbox.mean(0) + bbox = z + (bbox - z) * 0.85 + elif self.box_type == "poly": + binary = np.zeros(label.shape, dtype="uint8") + binary[ind] = 1 + try: + _, contours, _ = cv2.findContours( + binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE + ) + except BaseException: + contours, _ = cv2.findContours( + binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE + ) + + bbox = contours[0] * scale + + bbox = bbox.astype("int32") + bboxes.append(bbox.reshape(-1, 2)) + scores.append(score_i) + + boxes_batch.append({"points": bboxes}) + + return boxes_batch diff --git a/ppocr/postprocess/db_postprocess.py b/ppocr/postprocess/db_postprocess.py new file mode 100755 index 0000000..f7e84d3 --- /dev/null +++ b/ppocr/postprocess/db_postprocess.py @@ -0,0 +1,289 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is referred from: +https://github.com/WenmuZhou/DBNet.pytorch/blob/master/post_processing/seg_detector_representer.py +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import cv2 +import paddle +from shapely.geometry import Polygon +import pyclipper + + +class DBPostProcess(object): + """ + The post process for Differentiable Binarization (DB). + """ + + def __init__( + self, + thresh=0.3, + box_thresh=0.7, + max_candidates=1000, + unclip_ratio=2.0, + use_dilation=False, + score_mode="fast", + box_type="quad", + **kwargs, + ): + self.thresh = thresh + self.box_thresh = box_thresh + self.max_candidates = max_candidates + self.unclip_ratio = unclip_ratio + self.min_size = 3 + self.score_mode = score_mode + self.box_type = box_type + assert score_mode in [ + "slow", + "fast", + ], "Score mode must be in [slow, fast] but got: {}".format(score_mode) + + self.dilation_kernel = None if not use_dilation else np.array([[1, 1], [1, 1]]) + + def polygons_from_bitmap(self, pred, _bitmap, dest_width, dest_height): + """ + _bitmap: single map with shape (1, H, W), + whose values are binarized as {0, 1} + """ + + bitmap = _bitmap + height, width = bitmap.shape + + boxes = [] + scores = [] + + contours, _ = cv2.findContours( + (bitmap * 255).astype(np.uint8), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE + ) + + for contour in contours[: self.max_candidates]: + epsilon = 0.002 * cv2.arcLength(contour, True) + approx = cv2.approxPolyDP(contour, epsilon, True) + points = approx.reshape((-1, 2)) + if points.shape[0] < 4: + continue + + score = self.box_score_fast(pred, points.reshape(-1, 2)) + if self.box_thresh > score: + continue + + if points.shape[0] > 2: + box = self.unclip(points, self.unclip_ratio) + if len(box) > 1: + continue + else: + continue + box = np.array(box).reshape(-1, 2) + if len(box) == 0: + continue + + _, sside = self.get_mini_boxes(box.reshape((-1, 1, 2))) + if sside < self.min_size + 2: + continue + + box = np.array(box) + box[:, 0] = np.clip(np.round(box[:, 0] / width * dest_width), 0, dest_width) + box[:, 1] = np.clip( + np.round(box[:, 1] / height * dest_height), 0, dest_height + ) + boxes.append(box.tolist()) + scores.append(score) + return boxes, scores + + def boxes_from_bitmap(self, pred, _bitmap, dest_width, dest_height): + """ + _bitmap: single map with shape (1, H, W), + whose values are binarized as {0, 1} + """ + + bitmap = _bitmap + height, width = bitmap.shape + + outs = cv2.findContours( + (bitmap * 255).astype(np.uint8), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE + ) + if len(outs) == 3: + img, contours, _ = outs[0], outs[1], outs[2] + elif len(outs) == 2: + contours, _ = outs[0], outs[1] + + num_contours = min(len(contours), self.max_candidates) + + boxes = [] + scores = [] + for index in range(num_contours): + contour = contours[index] + points, sside = self.get_mini_boxes(contour) + if sside < self.min_size: + continue + points = np.array(points) + if self.score_mode == "fast": + score = self.box_score_fast(pred, points.reshape(-1, 2)) + else: + score = self.box_score_slow(pred, contour) + if self.box_thresh > score: + continue + + box = self.unclip(points, self.unclip_ratio) + if len(box) > 1: + continue + box = np.array(box).reshape(-1, 1, 2) + box, sside = self.get_mini_boxes(box) + if sside < self.min_size + 2: + continue + box = np.array(box) + + box[:, 0] = np.clip(np.round(box[:, 0] / width * dest_width), 0, dest_width) + box[:, 1] = np.clip( + np.round(box[:, 1] / height * dest_height), 0, dest_height + ) + boxes.append(box.astype("int32")) + scores.append(score) + return np.array(boxes, dtype="int32"), scores + + def unclip(self, box, unclip_ratio): + poly = Polygon(box) + distance = poly.area * unclip_ratio / poly.length + offset = pyclipper.PyclipperOffset() + offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON) + expanded = offset.Execute(distance) + return expanded + + def get_mini_boxes(self, contour): + bounding_box = cv2.minAreaRect(contour) + points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0]) + + index_1, index_2, index_3, index_4 = 0, 1, 2, 3 + if points[1][1] > points[0][1]: + index_1 = 0 + index_4 = 1 + else: + index_1 = 1 + index_4 = 0 + if points[3][1] > points[2][1]: + index_2 = 2 + index_3 = 3 + else: + index_2 = 3 + index_3 = 2 + + box = [points[index_1], points[index_2], points[index_3], points[index_4]] + return box, min(bounding_box[1]) + + def box_score_fast(self, bitmap, _box): + """ + box_score_fast: use bbox mean score as the mean score + """ + h, w = bitmap.shape[:2] + box = _box.copy() + xmin = np.clip(np.floor(box[:, 0].min()).astype("int32"), 0, w - 1) + xmax = np.clip(np.ceil(box[:, 0].max()).astype("int32"), 0, w - 1) + ymin = np.clip(np.floor(box[:, 1].min()).astype("int32"), 0, h - 1) + ymax = np.clip(np.ceil(box[:, 1].max()).astype("int32"), 0, h - 1) + + mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8) + box[:, 0] = box[:, 0] - xmin + box[:, 1] = box[:, 1] - ymin + cv2.fillPoly(mask, box.reshape(1, -1, 2).astype("int32"), 1) + return cv2.mean(bitmap[ymin : ymax + 1, xmin : xmax + 1], mask)[0] + + def box_score_slow(self, bitmap, contour): + """ + box_score_slow: use polyon mean score as the mean score + """ + h, w = bitmap.shape[:2] + contour = contour.copy() + contour = np.reshape(contour, (-1, 2)) + + xmin = np.clip(np.min(contour[:, 0]), 0, w - 1) + xmax = np.clip(np.max(contour[:, 0]), 0, w - 1) + ymin = np.clip(np.min(contour[:, 1]), 0, h - 1) + ymax = np.clip(np.max(contour[:, 1]), 0, h - 1) + + mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8) + + contour[:, 0] = contour[:, 0] - xmin + contour[:, 1] = contour[:, 1] - ymin + + cv2.fillPoly(mask, contour.reshape(1, -1, 2).astype("int32"), 1) + return cv2.mean(bitmap[ymin : ymax + 1, xmin : xmax + 1], mask)[0] + + def __call__(self, outs_dict, shape_list): + pred = outs_dict["maps"] + if isinstance(pred, paddle.Tensor): + pred = pred.numpy() + pred = pred[:, 0, :, :] + segmentation = pred > self.thresh + + boxes_batch = [] + for batch_index in range(pred.shape[0]): + src_h, src_w, ratio_h, ratio_w = shape_list[batch_index] + if self.dilation_kernel is not None: + mask = cv2.dilate( + np.array(segmentation[batch_index]).astype(np.uint8), + self.dilation_kernel, + ) + else: + mask = segmentation[batch_index] + if self.box_type == "poly": + boxes, scores = self.polygons_from_bitmap( + pred[batch_index], mask, src_w, src_h + ) + elif self.box_type == "quad": + boxes, scores = self.boxes_from_bitmap( + pred[batch_index], mask, src_w, src_h + ) + else: + raise ValueError("box_type can only be one of ['quad', 'poly']") + + boxes_batch.append({"points": boxes}) + return boxes_batch + + +class DistillationDBPostProcess(object): + def __init__( + self, + model_name=["student"], + key=None, + thresh=0.3, + box_thresh=0.6, + max_candidates=1000, + unclip_ratio=1.5, + use_dilation=False, + score_mode="fast", + box_type="quad", + **kwargs, + ): + self.model_name = model_name + self.key = key + self.post_process = DBPostProcess( + thresh=thresh, + box_thresh=box_thresh, + max_candidates=max_candidates, + unclip_ratio=unclip_ratio, + use_dilation=use_dilation, + score_mode=score_mode, + box_type=box_type, + ) + + def __call__(self, predicts, shape_list): + results = {} + for k in self.model_name: + results[k] = self.post_process(predicts[k], shape_list=shape_list) + return results diff --git a/ppocr/postprocess/drrg_postprocess.py b/ppocr/postprocess/drrg_postprocess.py new file mode 100644 index 0000000..f1241c4 --- /dev/null +++ b/ppocr/postprocess/drrg_postprocess.py @@ -0,0 +1,338 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/main/mmocr/models/textdet/postprocess/drrg_postprocessor.py +""" + +import functools +import operator + +import numpy as np +import paddle +from numpy.linalg import norm +import cv2 + + +class Node: + def __init__(self, ind): + self.__ind = ind + self.__links = set() + + @property + def ind(self): + return self.__ind + + @property + def links(self): + return set(self.__links) + + def add_link(self, link_node): + self.__links.add(link_node) + link_node.__links.add(self) + + +def graph_propagation(edges, scores, text_comps, edge_len_thr=50.0): + assert edges.ndim == 2 + assert edges.shape[1] == 2 + assert edges.shape[0] == scores.shape[0] + assert text_comps.ndim == 2 + assert isinstance(edge_len_thr, float) + + edges = np.sort(edges, axis=1) + score_dict = {} + for i, edge in enumerate(edges): + if text_comps is not None: + box1 = text_comps[edge[0], :8].reshape(4, 2) + box2 = text_comps[edge[1], :8].reshape(4, 2) + center1 = np.mean(box1, axis=0) + center2 = np.mean(box2, axis=0) + distance = norm(center1 - center2) + if distance > edge_len_thr: + scores[i] = 0 + if (edge[0], edge[1]) in score_dict: + score_dict[edge[0], edge[1]] = 0.5 * ( + score_dict[edge[0], edge[1]] + scores[i] + ) + else: + score_dict[edge[0], edge[1]] = scores[i] + + nodes = np.sort(np.unique(edges.flatten())) + mapping = -1 * np.ones((np.max(nodes) + 1), dtype=np.int32) + mapping[nodes] = np.arange(nodes.shape[0]) + order_inds = mapping[edges] + vertices = [Node(node) for node in nodes] + for ind in order_inds: + vertices[ind[0]].add_link(vertices[ind[1]]) + + return vertices, score_dict + + +def connected_components(nodes, score_dict, link_thr): + assert isinstance(nodes, list) + assert all([isinstance(node, Node) for node in nodes]) + assert isinstance(score_dict, dict) + assert isinstance(link_thr, float) + + clusters = [] + nodes = set(nodes) + while nodes: + node = nodes.pop() + cluster = {node} + node_queue = [node] + while node_queue: + node = node_queue.pop(0) + neighbors = set( + [ + neighbor + for neighbor in node.links + if score_dict[tuple(sorted([node.ind, neighbor.ind]))] >= link_thr + ] + ) + neighbors.difference_update(cluster) + nodes.difference_update(neighbors) + cluster.update(neighbors) + node_queue.extend(neighbors) + clusters.append(list(cluster)) + return clusters + + +def clusters2labels(clusters, num_nodes): + assert isinstance(clusters, list) + assert all([isinstance(cluster, list) for cluster in clusters]) + assert all([isinstance(node, Node) for cluster in clusters for node in cluster]) + assert isinstance(num_nodes, int) + + node_labels = np.zeros(num_nodes) + for cluster_ind, cluster in enumerate(clusters): + for node in cluster: + node_labels[node.ind] = cluster_ind + return node_labels + + +def remove_single(text_comps, comp_pred_labels): + assert text_comps.ndim == 2 + assert text_comps.shape[0] == comp_pred_labels.shape[0] + + single_flags = np.zeros_like(comp_pred_labels) + pred_labels = np.unique(comp_pred_labels) + for label in pred_labels: + current_label_flag = comp_pred_labels == label + if np.sum(current_label_flag) == 1: + single_flags[np.where(current_label_flag)[0][0]] = 1 + keep_ind = [i for i in range(len(comp_pred_labels)) if not single_flags[i]] + filtered_text_comps = text_comps[keep_ind, :] + filtered_labels = comp_pred_labels[keep_ind] + + return filtered_text_comps, filtered_labels + + +def norm2(point1, point2): + return ((point1[0] - point2[0]) ** 2 + (point1[1] - point2[1]) ** 2) ** 0.5 + + +def min_connect_path(points): + assert isinstance(points, list) + assert all([isinstance(point, list) for point in points]) + assert all([isinstance(coord, int) for point in points for coord in point]) + + points_queue = points.copy() + shortest_path = [] + current_edge = [[], []] + + edge_dict0 = {} + edge_dict1 = {} + current_edge[0] = points_queue[0] + current_edge[1] = points_queue[0] + points_queue.remove(points_queue[0]) + while points_queue: + for point in points_queue: + length0 = norm2(point, current_edge[0]) + edge_dict0[length0] = [point, current_edge[0]] + length1 = norm2(current_edge[1], point) + edge_dict1[length1] = [current_edge[1], point] + key0 = min(edge_dict0.keys()) + key1 = min(edge_dict1.keys()) + + if key0 <= key1: + start = edge_dict0[key0][0] + end = edge_dict0[key0][1] + shortest_path.insert(0, [points.index(start), points.index(end)]) + points_queue.remove(start) + current_edge[0] = start + else: + start = edge_dict1[key1][0] + end = edge_dict1[key1][1] + shortest_path.append([points.index(start), points.index(end)]) + points_queue.remove(end) + current_edge[1] = end + + edge_dict0 = {} + edge_dict1 = {} + + shortest_path = functools.reduce(operator.concat, shortest_path) + shortest_path = sorted(set(shortest_path), key=shortest_path.index) + + return shortest_path + + +def in_contour(cont, point): + x, y = point + is_inner = cv2.pointPolygonTest(cont, (int(x), int(y)), False) > 0.5 + return is_inner + + +def fix_corner(top_line, bot_line, start_box, end_box): + assert isinstance(top_line, list) + assert all(isinstance(point, list) for point in top_line) + assert isinstance(bot_line, list) + assert all(isinstance(point, list) for point in bot_line) + assert start_box.shape == end_box.shape == (4, 2) + + contour = np.array(top_line + bot_line[::-1]) + start_left_mid = (start_box[0] + start_box[3]) / 2 + start_right_mid = (start_box[1] + start_box[2]) / 2 + end_left_mid = (end_box[0] + end_box[3]) / 2 + end_right_mid = (end_box[1] + end_box[2]) / 2 + if not in_contour(contour, start_left_mid): + top_line.insert(0, start_box[0].tolist()) + bot_line.insert(0, start_box[3].tolist()) + elif not in_contour(contour, start_right_mid): + top_line.insert(0, start_box[1].tolist()) + bot_line.insert(0, start_box[2].tolist()) + if not in_contour(contour, end_left_mid): + top_line.append(end_box[0].tolist()) + bot_line.append(end_box[3].tolist()) + elif not in_contour(contour, end_right_mid): + top_line.append(end_box[1].tolist()) + bot_line.append(end_box[2].tolist()) + return top_line, bot_line + + +def comps2boundaries(text_comps, comp_pred_labels): + assert text_comps.ndim == 2 + assert len(text_comps) == len(comp_pred_labels) + boundaries = [] + if len(text_comps) < 1: + return boundaries + for cluster_ind in range(0, int(np.max(comp_pred_labels)) + 1): + cluster_comp_inds = np.where(comp_pred_labels == cluster_ind) + text_comp_boxes = ( + text_comps[cluster_comp_inds, :8].reshape((-1, 4, 2)).astype(np.int32) + ) + score = np.mean(text_comps[cluster_comp_inds, -1]) + + if text_comp_boxes.shape[0] < 1: + continue + + elif text_comp_boxes.shape[0] > 1: + centers = np.mean(text_comp_boxes, axis=1).astype(np.int32).tolist() + shortest_path = min_connect_path(centers) + text_comp_boxes = text_comp_boxes[shortest_path] + top_line = ( + np.mean(text_comp_boxes[:, 0:2, :], axis=1).astype(np.int32).tolist() + ) + bot_line = ( + np.mean(text_comp_boxes[:, 2:4, :], axis=1).astype(np.int32).tolist() + ) + top_line, bot_line = fix_corner( + top_line, bot_line, text_comp_boxes[0], text_comp_boxes[-1] + ) + boundary_points = top_line + bot_line[::-1] + + else: + top_line = text_comp_boxes[0, 0:2, :].astype(np.int32).tolist() + bot_line = text_comp_boxes[0, 2:4:-1, :].astype(np.int32).tolist() + boundary_points = top_line + bot_line + + boundary = [p for coord in boundary_points for p in coord] + [score] + boundaries.append(boundary) + + return boundaries + + +class DRRGPostprocess(object): + """Merge text components and construct boundaries of text instances. + + Args: + link_thr (float): The edge score threshold. + """ + + def __init__(self, link_thr, **kwargs): + assert isinstance(link_thr, float) + self.link_thr = link_thr + + def __call__(self, preds, shape_list): + """ + Args: + edges (ndarray): The edge array of shape N * 2, each row is a node + index pair that makes up an edge in graph. + scores (ndarray): The edge score array of shape (N,). + text_comps (ndarray): The text components. + + Returns: + List[list[float]]: The predicted boundaries of text instances. + """ + edges, scores, text_comps = preds + if edges is not None: + if isinstance(edges, paddle.Tensor): + edges = edges.numpy() + if isinstance(scores, paddle.Tensor): + scores = scores.numpy() + if isinstance(text_comps, paddle.Tensor): + text_comps = text_comps.numpy() + assert len(edges) == len(scores) + assert text_comps.ndim == 2 + assert text_comps.shape[1] == 9 + + vertices, score_dict = graph_propagation(edges, scores, text_comps) + clusters = connected_components(vertices, score_dict, self.link_thr) + pred_labels = clusters2labels(clusters, text_comps.shape[0]) + text_comps, pred_labels = remove_single(text_comps, pred_labels) + boundaries = comps2boundaries(text_comps, pred_labels) + else: + boundaries = [] + + boundaries, scores = self.resize_boundary( + boundaries, (1 / shape_list[0, 2:]).tolist()[::-1] + ) + boxes_batch = [dict(points=boundaries, scores=scores)] + return boxes_batch + + def resize_boundary(self, boundaries, scale_factor): + """Rescale boundaries via scale_factor. + + Args: + boundaries (list[list[float]]): The boundary list. Each boundary + with size 2k+1 with k>=4. + scale_factor(ndarray): The scale factor of size (4,). + + Returns: + boundaries (list[list[float]]): The scaled boundaries. + """ + boxes = [] + scores = [] + for b in boundaries: + sz = len(b) + scores.append(b[-1]) + b = ( + ( + np.array(b[: sz - 1]) + * (np.tile(scale_factor[:2], int((sz - 1) / 2)).reshape(1, sz - 1)) + ) + .flatten() + .tolist() + ) + boxes.append(np.array(b).reshape([-1, 2])) + return boxes, scores diff --git a/ppocr/postprocess/east_postprocess.py b/ppocr/postprocess/east_postprocess.py new file mode 100755 index 0000000..3db2fe8 --- /dev/null +++ b/ppocr/postprocess/east_postprocess.py @@ -0,0 +1,141 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +from .locality_aware_nms import nms_locality +import cv2 +import paddle + +import os +from ppocr.utils.utility import check_install +import sys + + +class EASTPostProcess(object): + """ + The post process for EAST. + """ + + def __init__(self, score_thresh=0.8, cover_thresh=0.1, nms_thresh=0.2, **kwargs): + self.score_thresh = score_thresh + self.cover_thresh = cover_thresh + self.nms_thresh = nms_thresh + + def restore_rectangle_quad(self, origin, geometry): + """ + Restore rectangle from quadrangle. + """ + # quad + origin_concat = np.concatenate( + (origin, origin, origin, origin), axis=1 + ) # (n, 8) + pred_quads = origin_concat - geometry + pred_quads = pred_quads.reshape((-1, 4, 2)) # (n, 4, 2) + return pred_quads + + def detect( + self, score_map, geo_map, score_thresh=0.8, cover_thresh=0.1, nms_thresh=0.2 + ): + """ + restore text boxes from score map and geo map + """ + + score_map = score_map[0] + geo_map = np.swapaxes(geo_map, 1, 0) + geo_map = np.swapaxes(geo_map, 1, 2) + # filter the score map + xy_text = np.argwhere(score_map > score_thresh) + if len(xy_text) == 0: + return [] + # sort the text boxes via the y axis + xy_text = xy_text[np.argsort(xy_text[:, 0])] + # restore quad proposals + text_box_restored = self.restore_rectangle_quad( + xy_text[:, ::-1] * 4, geo_map[xy_text[:, 0], xy_text[:, 1], :] + ) + boxes = np.zeros((text_box_restored.shape[0], 9), dtype=np.float32) + boxes[:, :8] = text_box_restored.reshape((-1, 8)) + boxes[:, 8] = score_map[xy_text[:, 0], xy_text[:, 1]] + + try: + check_install("lanms", "lanms-nova") + import lanms + + boxes = lanms.merge_quadrangle_n9(boxes, nms_thresh) + except: + print( + "You should install lanms by pip3 install lanms-nova to speed up nms_locality" + ) + boxes = nms_locality(boxes.astype(np.float64), nms_thresh) + if boxes.shape[0] == 0: + return [] + # Here we filter some low score boxes by the average score map, + # this is different from the original paper. + for i, box in enumerate(boxes): + mask = np.zeros_like(score_map, dtype=np.uint8) + cv2.fillPoly(mask, box[:8].reshape((-1, 4, 2)).astype(np.int32) // 4, 1) + boxes[i, 8] = cv2.mean(score_map, mask)[0] + boxes = boxes[boxes[:, 8] > cover_thresh] + return boxes + + def sort_poly(self, p): + """ + Sort polygons. + """ + min_axis = np.argmin(np.sum(p, axis=1)) + p = p[[min_axis, (min_axis + 1) % 4, (min_axis + 2) % 4, (min_axis + 3) % 4]] + if abs(p[0, 0] - p[1, 0]) > abs(p[0, 1] - p[1, 1]): + return p + else: + return p[[0, 3, 2, 1]] + + def __call__(self, outs_dict, shape_list): + score_list = outs_dict["f_score"] + geo_list = outs_dict["f_geo"] + if isinstance(score_list, paddle.Tensor): + score_list = score_list.numpy() + geo_list = geo_list.numpy() + img_num = len(shape_list) + dt_boxes_list = [] + for ino in range(img_num): + score = score_list[ino] + geo = geo_list[ino] + boxes = self.detect( + score_map=score, + geo_map=geo, + score_thresh=self.score_thresh, + cover_thresh=self.cover_thresh, + nms_thresh=self.nms_thresh, + ) + boxes_norm = [] + if len(boxes) > 0: + h, w = score.shape[1:] + src_h, src_w, ratio_h, ratio_w = shape_list[ino] + boxes = boxes[:, :8].reshape((-1, 4, 2)) + boxes[:, :, 0] /= ratio_w + boxes[:, :, 1] /= ratio_h + for i_box, box in enumerate(boxes): + box = self.sort_poly(box.astype(np.int32)) + if ( + np.linalg.norm(box[0] - box[1]) < 5 + or np.linalg.norm(box[3] - box[0]) < 5 + ): + continue + boxes_norm.append(box) + dt_boxes_list.append({"points": np.array(boxes_norm)}) + return dt_boxes_list diff --git a/ppocr/postprocess/fce_postprocess.py b/ppocr/postprocess/fce_postprocess.py new file mode 100755 index 0000000..bff9316 --- /dev/null +++ b/ppocr/postprocess/fce_postprocess.py @@ -0,0 +1,250 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/open-mmlab/mmocr/blob/v0.3.0/mmocr/models/textdet/postprocess/wrapper.py +""" + +import cv2 +import paddle +import numpy as np +from numpy.fft import ifft +from ppocr.utils.poly_nms import poly_nms, valid_boundary + + +def fill_hole(input_mask): + h, w = input_mask.shape + canvas = np.zeros((h + 2, w + 2), np.uint8) + canvas[1 : h + 1, 1 : w + 1] = input_mask.copy() + + mask = np.zeros((h + 4, w + 4), np.uint8) + + cv2.floodFill(canvas, mask, (0, 0), 1) + canvas = canvas[1 : h + 1, 1 : w + 1].astype(np.bool_) + + return ~canvas | input_mask + + +def fourier2poly(fourier_coeff, num_reconstr_points=50): + """Inverse Fourier transform + Args: + fourier_coeff (ndarray): Fourier coefficients shaped (n, 2k+1), + with n and k being candidates number and Fourier degree + respectively. + num_reconstr_points (int): Number of reconstructed polygon points. + Returns: + Polygons (ndarray): The reconstructed polygons shaped (n, n') + """ + + a = np.zeros((len(fourier_coeff), num_reconstr_points), dtype="complex") + k = (len(fourier_coeff[0]) - 1) // 2 + + a[:, 0 : k + 1] = fourier_coeff[:, k:] + a[:, -k:] = fourier_coeff[:, :k] + + poly_complex = ifft(a) * num_reconstr_points + polygon = np.zeros((len(fourier_coeff), num_reconstr_points, 2)) + polygon[:, :, 0] = poly_complex.real + polygon[:, :, 1] = poly_complex.imag + return polygon.astype("int32").reshape((len(fourier_coeff), -1)) + + +class FCEPostProcess(object): + """ + The post process for FCENet. + """ + + def __init__( + self, + scales, + fourier_degree=5, + num_reconstr_points=50, + decoding_type="fcenet", + score_thr=0.3, + nms_thr=0.1, + alpha=1.0, + beta=1.0, + box_type="poly", + **kwargs, + ): + self.scales = scales + self.fourier_degree = fourier_degree + self.num_reconstr_points = num_reconstr_points + self.decoding_type = decoding_type + self.score_thr = score_thr + self.nms_thr = nms_thr + self.alpha = alpha + self.beta = beta + self.box_type = box_type + + def __call__(self, preds, shape_list): + score_maps = [] + for key, value in preds.items(): + if isinstance(value, paddle.Tensor): + value = value.numpy() + cls_res = value[:, :4, :, :] + reg_res = value[:, 4:, :, :] + score_maps.append([cls_res, reg_res]) + + return self.get_boundary(score_maps, shape_list) + + def resize_boundary(self, boundaries, scale_factor): + """Rescale boundaries via scale_factor. + + Args: + boundaries (list[list[float]]): The boundary list. Each boundary + with size 2k+1 with k>=4. + scale_factor(ndarray): The scale factor of size (4,). + + Returns: + boundaries (list[list[float]]): The scaled boundaries. + """ + boxes = [] + scores = [] + for b in boundaries: + sz = len(b) + valid_boundary(b, True) + scores.append(b[-1]) + b = ( + ( + np.array(b[: sz - 1]) + * (np.tile(scale_factor[:2], int((sz - 1) / 2)).reshape(1, sz - 1)) + ) + .flatten() + .tolist() + ) + boxes.append(np.array(b).reshape([-1, 2])) + + return np.array(boxes, dtype=np.float32), scores + + def get_boundary(self, score_maps, shape_list): + assert len(score_maps) == len(self.scales) + boundaries = [] + for idx, score_map in enumerate(score_maps): + scale = self.scales[idx] + boundaries = boundaries + self._get_boundary_single(score_map, scale) + + # nms + boundaries = poly_nms(boundaries, self.nms_thr) + boundaries, scores = self.resize_boundary( + boundaries, (1 / shape_list[0, 2:]).tolist()[::-1] + ) + + boxes_batch = [dict(points=boundaries, scores=scores)] + return boxes_batch + + def _get_boundary_single(self, score_map, scale): + assert len(score_map) == 2 + assert score_map[1].shape[1] == 4 * self.fourier_degree + 2 + + return self.fcenet_decode( + preds=score_map, + fourier_degree=self.fourier_degree, + num_reconstr_points=self.num_reconstr_points, + scale=scale, + alpha=self.alpha, + beta=self.beta, + box_type=self.box_type, + score_thr=self.score_thr, + nms_thr=self.nms_thr, + ) + + def fcenet_decode( + self, + preds, + fourier_degree, + num_reconstr_points, + scale, + alpha=1.0, + beta=2.0, + box_type="poly", + score_thr=0.3, + nms_thr=0.1, + ): + """Decoding predictions of FCENet to instances. + + Args: + preds (list(Tensor)): The head output tensors. + fourier_degree (int): The maximum Fourier transform degree k. + num_reconstr_points (int): The points number of the polygon + reconstructed from predicted Fourier coefficients. + scale (int): The down-sample scale of the prediction. + alpha (float) : The parameter to calculate final scores. Score_{final} + = (Score_{text region} ^ alpha) + * (Score_{text center region}^ beta) + beta (float) : The parameter to calculate final score. + box_type (str): Boundary encoding type 'poly' or 'quad'. + score_thr (float) : The threshold used to filter out the final + candidates. + nms_thr (float) : The threshold of nms. + + Returns: + boundaries (list[list[float]]): The instance boundary and confidence + list. + """ + assert isinstance(preds, list) + assert len(preds) == 2 + assert box_type in ["poly", "quad"] + + cls_pred = preds[0][0] + tr_pred = cls_pred[0:2] + tcl_pred = cls_pred[2:] + + reg_pred = preds[1][0].transpose([1, 2, 0]) + x_pred = reg_pred[:, :, : 2 * fourier_degree + 1] + y_pred = reg_pred[:, :, 2 * fourier_degree + 1 :] + + score_pred = (tr_pred[1] ** alpha) * (tcl_pred[1] ** beta) + tr_pred_mask = (score_pred) > score_thr + tr_mask = fill_hole(tr_pred_mask) + + tr_contours, _ = cv2.findContours( + tr_mask.astype(np.uint8), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE + ) # opencv4 + + mask = np.zeros_like(tr_mask) + boundaries = [] + for cont in tr_contours: + deal_map = mask.copy().astype(np.int8) + cv2.drawContours(deal_map, [cont], -1, 1, -1) + + score_map = score_pred * deal_map + score_mask = score_map > 0 + xy_text = np.argwhere(score_mask) + dxy = xy_text[:, 1] + xy_text[:, 0] * 1j + + x, y = x_pred[score_mask], y_pred[score_mask] + c = x + y * 1j + c[:, fourier_degree] = c[:, fourier_degree] + dxy + c *= scale + + polygons = fourier2poly(c, num_reconstr_points) + score = score_map[score_mask].reshape(-1, 1) + polygons = poly_nms(np.hstack((polygons, score)).tolist(), nms_thr) + + boundaries = boundaries + polygons + + boundaries = poly_nms(boundaries, nms_thr) + + if box_type == "quad": + new_boundaries = [] + for boundary in boundaries: + poly = np.array(boundary[:-1]).reshape(-1, 2).astype(np.float32) + score = boundary[-1] + points = cv2.boxPoints(cv2.minAreaRect(poly)) + points = np.int64(points) + new_boundaries.append(points.reshape(-1).tolist() + [score]) + boundaries = new_boundaries + + return boundaries diff --git a/ppocr/postprocess/locality_aware_nms.py b/ppocr/postprocess/locality_aware_nms.py new file mode 100644 index 0000000..296ea6f --- /dev/null +++ b/ppocr/postprocess/locality_aware_nms.py @@ -0,0 +1,198 @@ +""" +Locality aware nms. +This code is referred from: https://github.com/songdejia/EAST/blob/master/locality_aware_nms.py +""" + +import numpy as np +from shapely.geometry import Polygon + + +def intersection(g, p): + """ + Intersection. + """ + g = Polygon(g[:8].reshape((4, 2))) + p = Polygon(p[:8].reshape((4, 2))) + g = g.buffer(0) + p = p.buffer(0) + if not g.is_valid or not p.is_valid: + return 0 + inter = Polygon(g).intersection(Polygon(p)).area + union = g.area + p.area - inter + if union == 0: + return 0 + else: + return inter / union + + +def intersection_iog(g, p): + """ + Intersection_iog. + """ + g = Polygon(g[:8].reshape((4, 2))) + p = Polygon(p[:8].reshape((4, 2))) + if not g.is_valid or not p.is_valid: + return 0 + inter = Polygon(g).intersection(Polygon(p)).area + # union = g.area + p.area - inter + union = p.area + if union == 0: + print("p_area is very small") + return 0 + else: + return inter / union + + +def weighted_merge(g, p): + """ + Weighted merge. + """ + g[:8] = (g[8] * g[:8] + p[8] * p[:8]) / (g[8] + p[8]) + g[8] = g[8] + p[8] + return g + + +def standard_nms(S, thres): + """ + Standard nms. + """ + order = np.argsort(S[:, 8])[::-1] + keep = [] + while order.size > 0: + i = order[0] + keep.append(i) + ovr = np.array([intersection(S[i], S[t]) for t in order[1:]]) + + inds = np.where(ovr <= thres)[0] + order = order[inds + 1] + + return S[keep] + + +def standard_nms_inds(S, thres): + """ + Standard nms, return inds. + """ + order = np.argsort(S[:, 8])[::-1] + keep = [] + while order.size > 0: + i = order[0] + keep.append(i) + ovr = np.array([intersection(S[i], S[t]) for t in order[1:]]) + + inds = np.where(ovr <= thres)[0] + order = order[inds + 1] + + return keep + + +def nms(S, thres): + """ + nms. + """ + order = np.argsort(S[:, 8])[::-1] + keep = [] + while order.size > 0: + i = order[0] + keep.append(i) + ovr = np.array([intersection(S[i], S[t]) for t in order[1:]]) + + inds = np.where(ovr <= thres)[0] + order = order[inds + 1] + + return keep + + +def soft_nms(boxes_in, Nt_thres=0.3, threshold=0.8, sigma=0.5, method=2): + """ + soft_nms + :para boxes_in, N x 9 (coords + score) + :para threshould, eliminate cases min score(0.001) + :para Nt_thres, iou_threshi + :para sigma, gaussian weght + :method, linear or gaussian + """ + boxes = boxes_in.copy() + N = boxes.shape[0] + if N is None or N < 1: + return np.array([]) + pos, maxpos = 0, 0 + weight = 0.0 + inds = np.arange(N) + tbox, sbox = boxes[0].copy(), boxes[0].copy() + for i in range(N): + maxscore = boxes[i, 8] + maxpos = i + tbox = boxes[i].copy() + ti = inds[i] + pos = i + 1 + # get max box + while pos < N: + if maxscore < boxes[pos, 8]: + maxscore = boxes[pos, 8] + maxpos = pos + pos = pos + 1 + # add max box as a detection + boxes[i, :] = boxes[maxpos, :] + inds[i] = inds[maxpos] + # swap + boxes[maxpos, :] = tbox + inds[maxpos] = ti + tbox = boxes[i].copy() + pos = i + 1 + # NMS iteration + while pos < N: + sbox = boxes[pos].copy() + ts_iou_val = intersection(tbox, sbox) + if ts_iou_val > 0: + if method == 1: + if ts_iou_val > Nt_thres: + weight = 1 - ts_iou_val + else: + weight = 1 + elif method == 2: + weight = np.exp(-1.0 * ts_iou_val**2 / sigma) + else: + if ts_iou_val > Nt_thres: + weight = 0 + else: + weight = 1 + boxes[pos, 8] = weight * boxes[pos, 8] + # if box score falls below threshold, discard the box by + # swapping last box update N + if boxes[pos, 8] < threshold: + boxes[pos, :] = boxes[N - 1, :] + inds[pos] = inds[N - 1] + N = N - 1 + pos = pos - 1 + pos = pos + 1 + + return boxes[:N] + + +def nms_locality(polys, thres=0.3): + """ + locality aware nms of EAST + :param polys: a N*9 numpy array. first 8 coordinates, then prob + :return: boxes after nms + """ + S = [] + p = None + for g in polys: + if p is not None and intersection(g, p) > thres: + p = weighted_merge(g, p) + else: + if p is not None: + S.append(p) + p = g + if p is not None: + S.append(p) + + if len(S) == 0: + return np.array([]) + return standard_nms(np.array(S), thres) + + +if __name__ == "__main__": + # 343,350,448,135,474,143,369,359 + print(Polygon(np.array([[343, 350], [448, 135], [474, 143], [369, 359]])).area) diff --git a/ppocr/postprocess/pg_postprocess.py b/ppocr/postprocess/pg_postprocess.py new file mode 100644 index 0000000..70d1bdf --- /dev/null +++ b/ppocr/postprocess/pg_postprocess.py @@ -0,0 +1,61 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys + +__dir__ = os.path.dirname(__file__) +sys.path.append(__dir__) +sys.path.append(os.path.join(__dir__, "..")) +from ppocr.utils.e2e_utils.pgnet_pp_utils import PGNet_PostProcess + + +class PGPostProcess(object): + """ + The post process for PGNet. + """ + + def __init__( + self, + character_dict_path, + valid_set, + score_thresh, + mode, + point_gather_mode=None, + **kwargs, + ): + self.character_dict_path = character_dict_path + self.valid_set = valid_set + self.score_thresh = score_thresh + self.mode = mode + self.point_gather_mode = point_gather_mode + + def __call__(self, outs_dict, shape_list): + post = PGNet_PostProcess( + self.character_dict_path, + self.valid_set, + self.score_thresh, + outs_dict, + shape_list, + point_gather_mode=self.point_gather_mode, + ) + if self.mode == "fast": + data = post.pg_postprocess_fast() + else: + data = post.pg_postprocess_slow() + return data diff --git a/ppocr/postprocess/picodet_postprocess.py b/ppocr/postprocess/picodet_postprocess.py new file mode 100644 index 0000000..9189c8e --- /dev/null +++ b/ppocr/postprocess/picodet_postprocess.py @@ -0,0 +1,297 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from scipy.special import softmax + + +def hard_nms(box_scores, iou_threshold, top_k=-1, candidate_size=200): + """ + Args: + box_scores (N, 5): boxes in corner-form and probabilities. + iou_threshold: intersection over union threshold. + top_k: keep top_k results. If k <= 0, keep all the results. + candidate_size: only consider the candidates with the highest scores. + Returns: + picked: a list of indexes of the kept boxes + """ + scores = box_scores[:, -1] + boxes = box_scores[:, :-1] + picked = [] + indexes = np.argsort(scores) + indexes = indexes[-candidate_size:] + while len(indexes) > 0: + current = indexes[-1] + picked.append(current) + if 0 < top_k == len(picked) or len(indexes) == 1: + break + current_box = boxes[current, :] + indexes = indexes[:-1] + rest_boxes = boxes[indexes, :] + iou = iou_of( + rest_boxes, + np.expand_dims(current_box, axis=0), + ) + indexes = indexes[iou <= iou_threshold] + + return box_scores[picked, :] + + +def iou_of(boxes0, boxes1, eps=1e-5): + """Return intersection-over-union (Jaccard index) of boxes. + Args: + boxes0 (N, 4): ground truth boxes. + boxes1 (N or 1, 4): predicted boxes. + eps: a small number to avoid 0 as denominator. + Returns: + iou (N): IoU values. + """ + overlap_left_top = np.maximum(boxes0[..., :2], boxes1[..., :2]) + overlap_right_bottom = np.minimum(boxes0[..., 2:], boxes1[..., 2:]) + + overlap_area = area_of(overlap_left_top, overlap_right_bottom) + area0 = area_of(boxes0[..., :2], boxes0[..., 2:]) + area1 = area_of(boxes1[..., :2], boxes1[..., 2:]) + return overlap_area / (area0 + area1 - overlap_area + eps) + + +def area_of(left_top, right_bottom): + """Compute the areas of rectangles given two corners. + Args: + left_top (N, 2): left top corner. + right_bottom (N, 2): right bottom corner. + Returns: + area (N): return the area. + """ + hw = np.clip(right_bottom - left_top, 0.0, None) + return hw[..., 0] * hw[..., 1] + + +def calculate_containment(boxes0, boxes1): + """ + Calculate the containment of the boxes. + Args: + boxes0 (N, 4): ground truth boxes. + boxes1 (N or 1, 4): predicted boxes. + Returns: + containment (N): containment values. + """ + overlap_left_top = np.maximum(boxes0[..., :2], boxes1[..., :2]) + overlap_right_bottom = np.minimum(boxes0[..., 2:], boxes1[..., 2:]) + + overlap_area = area_of(overlap_left_top, overlap_right_bottom) + area0 = area_of(boxes0[..., :2], boxes0[..., 2:]) + area1 = area_of(boxes1[..., :2], boxes1[..., 2:]) + return overlap_area / np.minimum(area0, np.expand_dims(area1, axis=0)) + + +class PicoDetPostProcess(object): + """ + Args: + input_shape (int): network input image size + ori_shape (int): ori image shape of before padding + scale_factor (float): scale factor of ori image + enable_mkldnn (bool): whether to open MKLDNN + """ + + def __init__( + self, + layout_dict_path, + strides=[8, 16, 32, 64], + score_threshold=0.4, + nms_threshold=0.5, + nms_top_k=1000, + keep_top_k=100, + ): + self.labels = self.load_layout_dict(layout_dict_path) + self.strides = strides + self.score_threshold = score_threshold + self.nms_threshold = nms_threshold + self.nms_top_k = nms_top_k + self.keep_top_k = keep_top_k + + def load_layout_dict(self, layout_dict_path): + with open(layout_dict_path, "r", encoding="utf-8") as fp: + labels = fp.readlines() + return [label.strip("\n") for label in labels] + + def warp_boxes(self, boxes, ori_shape): + """Apply transform to boxes""" + width, height = ori_shape[1], ori_shape[0] + n = len(boxes) + if n: + # warp points + xy = np.ones((n * 4, 3)) + xy[:, :2] = boxes[:, [0, 1, 2, 3, 0, 3, 2, 1]].reshape( + n * 4, 2 + ) # x1y1, x2y2, x1y2, x2y1 + # xy = xy @ M.T # transform + xy = (xy[:, :2] / xy[:, 2:3]).reshape(n, 8) # rescale + # create new boxes + x = xy[:, [0, 2, 4, 6]] + y = xy[:, [1, 3, 5, 7]] + xy = ( + np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T + ) + # clip boxes + xy[:, [0, 2]] = xy[:, [0, 2]].clip(0, width) + xy[:, [1, 3]] = xy[:, [1, 3]].clip(0, height) + return xy.astype(np.float32) + else: + return boxes + + def img_info(self, ori_img, img): + origin_shape = ori_img.shape + resize_shape = img.shape + im_scale_y = resize_shape[2] / float(origin_shape[0]) + im_scale_x = resize_shape[3] / float(origin_shape[1]) + scale_factor = np.array([im_scale_y, im_scale_x], dtype=np.float32) + img_shape = np.array(img.shape[2:], dtype=np.float32) + + input_shape = np.array(img).astype("float32").shape[2:] + ori_shape = np.array((img_shape,)).astype("float32") + scale_factor = np.array((scale_factor,)).astype("float32") + return ori_shape, input_shape, scale_factor + + def __call__(self, ori_img, img, preds): + scores, raw_boxes = preds["boxes"], preds["boxes_num"] + batch_size = raw_boxes[0].shape[0] + reg_max = int(raw_boxes[0].shape[-1] / 4 - 1) + out_boxes_num = [] + out_boxes_list = [] + results = [] + ori_shape, input_shape, scale_factor = self.img_info(ori_img, img) + + for batch_id in range(batch_size): + # generate centers + decode_boxes = [] + select_scores = [] + for stride, box_distribute, score in zip(self.strides, raw_boxes, scores): + box_distribute = box_distribute[batch_id] + score = score[batch_id] + # centers + fm_h = input_shape[0] / stride + fm_w = input_shape[1] / stride + h_range = np.arange(fm_h) + w_range = np.arange(fm_w) + ww, hh = np.meshgrid(w_range, h_range) + ct_row = (hh.flatten() + 0.5) * stride + ct_col = (ww.flatten() + 0.5) * stride + center = np.stack((ct_col, ct_row, ct_col, ct_row), axis=1) + + # box distribution to distance + reg_range = np.arange(reg_max + 1) + box_distance = box_distribute.reshape((-1, reg_max + 1)) + box_distance = softmax(box_distance, axis=1) + box_distance = box_distance * np.expand_dims(reg_range, axis=0) + box_distance = np.sum(box_distance, axis=1).reshape((-1, 4)) + box_distance = box_distance * stride + + # top K candidate + topk_idx = np.argsort(score.max(axis=1))[::-1] + topk_idx = topk_idx[: self.nms_top_k] + center = center[topk_idx] + score = score[topk_idx] + box_distance = box_distance[topk_idx] + + # decode box + decode_box = center + [-1, -1, 1, 1] * box_distance + + select_scores.append(score) + decode_boxes.append(decode_box) + + # nms + bboxes = np.concatenate(decode_boxes, axis=0) + confidences = np.concatenate(select_scores, axis=0) + picked_box_probs = [] + picked_labels = [] + for class_index in range(0, confidences.shape[1]): + probs = confidences[:, class_index] + mask = probs > self.score_threshold + probs = probs[mask] + if probs.shape[0] == 0: + continue + subset_boxes = bboxes[mask, :] + box_probs = np.concatenate([subset_boxes, probs.reshape(-1, 1)], axis=1) + box_probs = hard_nms( + box_probs, + iou_threshold=self.nms_threshold, + top_k=self.keep_top_k, + ) + picked_box_probs.append(box_probs) + picked_labels.extend([class_index] * box_probs.shape[0]) + + if len(picked_box_probs) == 0: + out_boxes_list.append(np.empty((0, 4))) + out_boxes_num.append(0) + + else: + picked_box_probs = np.concatenate(picked_box_probs) + + # resize output boxes + picked_box_probs[:, :4] = self.warp_boxes( + picked_box_probs[:, :4], ori_shape[batch_id] + ) + im_scale = np.concatenate( + [scale_factor[batch_id][::-1], scale_factor[batch_id][::-1]] + ) + picked_box_probs[:, :4] /= im_scale + # clas score box + out_boxes_list.append( + np.concatenate( + [ + np.expand_dims(np.array(picked_labels), axis=-1), + np.expand_dims(picked_box_probs[:, 4], axis=-1), + picked_box_probs[:, :4], + ], + axis=1, + ) + ) + out_boxes_num.append(len(picked_labels)) + + out_boxes_list = np.concatenate(out_boxes_list, axis=0) + out_boxes_num = np.asarray(out_boxes_num).astype(np.int32) + + for dt in out_boxes_list: + clsid, bbox, score = int(dt[0]), dt[2:], dt[1] + label = self.labels[clsid] + result = {"bbox": bbox, "label": label, "score": score} + results.append(result) + + # Handle conflict where a box is simultaneously recognized as multiple labels. + # Use IoU to find similar boxes. Prioritize labels as table, text, and others when deduplicate similar boxes. + bboxes = np.array([x["bbox"] for x in results]) + duplicate_idx = list() + for i in range(len(results)): + if i in duplicate_idx: + continue + containments = calculate_containment(bboxes, bboxes[i, ...]) + overlaps = np.where(containments > 0.5)[0] + if len(overlaps) > 1: + table_box = [x for x in overlaps if results[x]["label"] == "table"] + if len(table_box) > 0: + keep = sorted( + [(x, results[x]) for x in table_box], + key=lambda x: x[1]["score"], + reverse=True, + )[0][0] + else: + keep = sorted( + [(x, results[x]) for x in overlaps], + key=lambda x: x[1]["score"], + reverse=True, + )[0][0] + duplicate_idx.extend([x for x in overlaps if x != keep]) + results = [x for i, x in enumerate(results) if i not in duplicate_idx] + return results diff --git a/ppocr/postprocess/pse_postprocess/__init__.py b/ppocr/postprocess/pse_postprocess/__init__.py new file mode 100644 index 0000000..eed166a --- /dev/null +++ b/ppocr/postprocess/pse_postprocess/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .pse_postprocess import PSEPostProcess diff --git a/ppocr/postprocess/pse_postprocess/pse/README.md b/ppocr/postprocess/pse_postprocess/pse/README.md new file mode 100644 index 0000000..6a19d5d --- /dev/null +++ b/ppocr/postprocess/pse_postprocess/pse/README.md @@ -0,0 +1,6 @@ +## 编译 +This code is refer from: +https://github.com/whai362/PSENet/blob/python3/models/post_processing/pse +```python +python3 setup.py build_ext --inplace +``` diff --git a/ppocr/postprocess/pse_postprocess/pse/__init__.py b/ppocr/postprocess/pse_postprocess/pse/__init__.py new file mode 100644 index 0000000..6028802 --- /dev/null +++ b/ppocr/postprocess/pse_postprocess/pse/__init__.py @@ -0,0 +1,33 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import sys +import os +import subprocess + +python_path = sys.executable + +ori_path = os.getcwd() +os.chdir("ppocr/postprocess/pse_postprocess/pse") +if ( + subprocess.call("{} setup.py build_ext --inplace".format(python_path), shell=True) + != 0 +): + raise RuntimeError( + "Cannot compile pse: {}, if your system is windows, you need to install all the default components of `desktop development using C++` in visual studio 2019+".format( + os.path.dirname(os.path.realpath(__file__)) + ) + ) +os.chdir(ori_path) + +from .pse import pse diff --git a/ppocr/postprocess/pse_postprocess/pse/pse.pyx b/ppocr/postprocess/pse_postprocess/pse/pse.pyx new file mode 100644 index 0000000..dfa90f3 --- /dev/null +++ b/ppocr/postprocess/pse_postprocess/pse/pse.pyx @@ -0,0 +1,70 @@ + +import numpy as np +import cv2 +cimport numpy as np +cimport cython +cimport libcpp +cimport libcpp.pair +cimport libcpp.queue +from libcpp.pair cimport * +from libcpp.queue cimport * + +@cython.boundscheck(False) +@cython.wraparound(False) +cdef np.ndarray[np.int32_t, ndim=2] _pse(np.ndarray[np.uint8_t, ndim=3] kernels, + np.ndarray[np.int32_t, ndim=2] label, + int kernel_num, + int label_num, + float min_area=0): + cdef np.ndarray[np.int32_t, ndim=2] pred + pred = np.zeros((label.shape[0], label.shape[1]), dtype=np.int32) + + for label_idx in range(1, label_num): + if np.sum(label == label_idx) < min_area: + label[label == label_idx] = 0 + + cdef libcpp.queue.queue[libcpp.pair.pair[np.int16_t,np.int16_t]] que = \ + queue[libcpp.pair.pair[np.int16_t,np.int16_t]]() + cdef libcpp.queue.queue[libcpp.pair.pair[np.int16_t,np.int16_t]] nxt_que = \ + queue[libcpp.pair.pair[np.int16_t,np.int16_t]]() + cdef np.int16_t* dx = [-1, 1, 0, 0] + cdef np.int16_t* dy = [0, 0, -1, 1] + cdef np.int16_t tmpx, tmpy + + points = np.array(np.where(label > 0)).transpose((1, 0)) + for point_idx in range(points.shape[0]): + tmpx, tmpy = points[point_idx, 0], points[point_idx, 1] + que.push(pair[np.int16_t,np.int16_t](tmpx, tmpy)) + pred[tmpx, tmpy] = label[tmpx, tmpy] + + cdef libcpp.pair.pair[np.int16_t,np.int16_t] cur + cdef int cur_label + for kernel_idx in range(kernel_num - 1, -1, -1): + while not que.empty(): + cur = que.front() + que.pop() + cur_label = pred[cur.first, cur.second] + + is_edge = True + for j in range(4): + tmpx = cur.first + dx[j] + tmpy = cur.second + dy[j] + if tmpx < 0 or tmpx >= label.shape[0] or tmpy < 0 or tmpy >= label.shape[1]: + continue + if kernels[kernel_idx, tmpx, tmpy] == 0 or pred[tmpx, tmpy] > 0: + continue + + que.push(pair[np.int16_t,np.int16_t](tmpx, tmpy)) + pred[tmpx, tmpy] = cur_label + is_edge = False + if is_edge: + nxt_que.push(cur) + + que, nxt_que = nxt_que, que + + return pred + +def pse(kernels, min_area): + kernel_num = kernels.shape[0] + label_num, label = cv2.connectedComponents(kernels[-1], connectivity=4) + return _pse(kernels[:-1], label, kernel_num, label_num, min_area) diff --git a/ppocr/postprocess/pse_postprocess/pse/setup.py b/ppocr/postprocess/pse_postprocess/pse/setup.py new file mode 100644 index 0000000..2ae2d53 --- /dev/null +++ b/ppocr/postprocess/pse_postprocess/pse/setup.py @@ -0,0 +1,18 @@ +from setuptools import setup, Extension +from Cython.Build import cythonize +import numpy + +setup( + ext_modules=cythonize( + Extension( + "pse", + sources=["pse.pyx"], + language="c++", + include_dirs=[numpy.get_include()], + library_dirs=[], + libraries=[], + extra_compile_args=["-O3"], + extra_link_args=[], + ) + ) +) diff --git a/ppocr/postprocess/pse_postprocess/pse_postprocess.py b/ppocr/postprocess/pse_postprocess/pse_postprocess.py new file mode 100755 index 0000000..4050dce --- /dev/null +++ b/ppocr/postprocess/pse_postprocess/pse_postprocess.py @@ -0,0 +1,122 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/whai362/PSENet/blob/python3/models/head/psenet_head.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import cv2 +import paddle +from paddle.nn import functional as F + +from ppocr.postprocess.pse_postprocess.pse import pse + + +class PSEPostProcess(object): + """ + The post process for PSE. + """ + + def __init__( + self, + thresh=0.5, + box_thresh=0.85, + min_area=16, + box_type="quad", + scale=4, + **kwargs, + ): + assert box_type in ["quad", "poly"], "Only quad and poly is supported" + self.thresh = thresh + self.box_thresh = box_thresh + self.min_area = min_area + self.box_type = box_type + self.scale = scale + + def __call__(self, outs_dict, shape_list): + pred = outs_dict["maps"] + if not isinstance(pred, paddle.Tensor): + pred = paddle.to_tensor(pred) + pred = F.interpolate(pred, scale_factor=4 // self.scale, mode="bilinear") + + score = F.sigmoid(pred[:, 0, :, :]) + + kernels = (pred > self.thresh).astype("float32") + text_mask = kernels[:, 0, :, :] + text_mask = paddle.unsqueeze(text_mask, axis=1) + + kernels[:, 0:, :, :] = kernels[:, 0:, :, :] * text_mask + + score = score.numpy() + kernels = kernels.numpy().astype(np.uint8) + + boxes_batch = [] + for batch_index in range(pred.shape[0]): + boxes, scores = self.boxes_from_bitmap( + score[batch_index], kernels[batch_index], shape_list[batch_index] + ) + + boxes_batch.append({"points": boxes, "scores": scores}) + return boxes_batch + + def boxes_from_bitmap(self, score, kernels, shape): + label = pse(kernels, self.min_area) + return self.generate_box(score, label, shape) + + def generate_box(self, score, label, shape): + src_h, src_w, ratio_h, ratio_w = shape + label_num = np.max(label) + 1 + + boxes = [] + scores = [] + for i in range(1, label_num): + ind = label == i + points = np.array(np.where(ind)).transpose((1, 0))[:, ::-1] + + if points.shape[0] < self.min_area: + label[ind] = 0 + continue + + score_i = np.mean(score[ind]) + if score_i < self.box_thresh: + label[ind] = 0 + continue + + if self.box_type == "quad": + rect = cv2.minAreaRect(points) + bbox = cv2.boxPoints(rect) + elif self.box_type == "poly": + box_height = np.max(points[:, 1]) + 10 + box_width = np.max(points[:, 0]) + 10 + + mask = np.zeros((box_height, box_width), np.uint8) + mask[points[:, 1], points[:, 0]] = 255 + + contours, _ = cv2.findContours( + mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE + ) + bbox = np.squeeze(contours[0], 1) + else: + raise NotImplementedError + + bbox[:, 0] = np.clip(np.round(bbox[:, 0] / ratio_w), 0, src_w) + bbox[:, 1] = np.clip(np.round(bbox[:, 1] / ratio_h), 0, src_h) + boxes.append(bbox) + scores.append(score_i) + return boxes, scores diff --git a/ppocr/postprocess/rec_postprocess.py b/ppocr/postprocess/rec_postprocess.py new file mode 100644 index 0000000..4700bbf --- /dev/null +++ b/ppocr/postprocess/rec_postprocess.py @@ -0,0 +1,1565 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import numpy as np +import paddle +from paddle.nn import functional as F +import re +import json + + +class BaseRecLabelDecode(object): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False): + self.beg_str = "sos" + self.end_str = "eos" + self.reverse = False + self.character_str = [] + + if character_dict_path is None: + self.character_str = "0123456789abcdefghijklmnopqrstuvwxyz" + dict_character = list(self.character_str) + else: + with open(character_dict_path, "rb") as fin: + lines = fin.readlines() + for line in lines: + line = line.decode("utf-8").strip("\n").strip("\r\n") + self.character_str.append(line) + if use_space_char: + self.character_str.append(" ") + dict_character = list(self.character_str) + if "arabic" in character_dict_path: + self.reverse = True + + dict_character = self.add_special_char(dict_character) + self.dict = {} + for i, char in enumerate(dict_character): + self.dict[char] = i + self.character = dict_character + + def pred_reverse(self, pred): + pred_re = [] + c_current = "" + for c in pred: + if not bool(re.search("[a-zA-Z0-9 :*./%+-]", c)): + if c_current != "": + pred_re.append(c_current) + pred_re.append(c) + c_current = "" + else: + c_current += c + if c_current != "": + pred_re.append(c_current) + + return "".join(pred_re[::-1]) + + def add_special_char(self, dict_character): + return dict_character + + def get_word_info(self, text, selection): + """ + Group the decoded characters and record the corresponding decoded positions. + + Args: + text: the decoded text + selection: the bool array that identifies which columns of features are decoded as non-separated characters + Returns: + word_list: list of the grouped words + word_col_list: list of decoding positions corresponding to each character in the grouped word + state_list: list of marker to identify the type of grouping words, including two types of grouping words: + - 'cn': continuous chinese characters (e.g., 你好啊) + - 'en&num': continuous english characters (e.g., hello), number (e.g., 123, 1.123), or mixed of them connected by '-' (e.g., VGG-16) + The remaining characters in text are treated as separators between groups (e.g., space, '(', ')', etc.). + """ + state = None + word_content = [] + word_col_content = [] + word_list = [] + word_col_list = [] + state_list = [] + valid_col = np.where(selection == True)[0] + + for c_i, char in enumerate(text): + if "\u4e00" <= char <= "\u9fff": + c_state = "cn" + # Use \w with UNICODE flag to match letters (including accented chars like ä, ö, ü, é, etc.) and digits + # Exclude underscore since \w includes it but we want to treat it as splitter + elif bool(re.search(r"[\w]", char, re.UNICODE)) and char != "_": + c_state = "en&num" + else: + c_state = "splitter" + + # Handle apostrophes in French words like "n'êtes" + if char == "'" and state == "en&num": + c_state = "en&num" + + if ( + char == "." + and state == "en&num" + and c_i + 1 < len(text) + and bool(re.search("[0-9]", text[c_i + 1])) + ): # grouping floating number + c_state = "en&num" + if ( + char == "-" and state == "en&num" + ): # grouping word with '-', such as 'state-of-the-art' + c_state = "en&num" + + if state == None: + state = c_state + + if state != c_state: + if len(word_content) != 0: + word_list.append(word_content) + word_col_list.append(word_col_content) + state_list.append(state) + word_content = [] + word_col_content = [] + state = c_state + + if state != "splitter": + word_content.append(char) + word_col_content.append(valid_col[c_i]) + + if len(word_content) != 0: + word_list.append(word_content) + word_col_list.append(word_col_content) + state_list.append(state) + + return word_list, word_col_list, state_list + + def decode( + self, + text_index, + text_prob=None, + is_remove_duplicate=False, + return_word_box=False, + ): + """convert text-index into text-label.""" + result_list = [] + ignored_tokens = self.get_ignored_tokens() + batch_size = len(text_index) + for batch_idx in range(batch_size): + selection = np.ones(len(text_index[batch_idx]), dtype=bool) + if is_remove_duplicate: + selection[1:] = text_index[batch_idx][1:] != text_index[batch_idx][:-1] + for ignored_token in ignored_tokens: + selection &= text_index[batch_idx] != ignored_token + + char_list = [ + self.character[text_id] for text_id in text_index[batch_idx][selection] + ] + if text_prob is not None: + conf_list = text_prob[batch_idx][selection] + else: + conf_list = [1] * len(selection) + if len(conf_list) == 0: + conf_list = [0] + + text = "".join(char_list) + + if self.reverse: # for arabic rec + text = self.pred_reverse(text) + + if return_word_box: + word_list, word_col_list, state_list = self.get_word_info( + text, selection + ) + result_list.append( + ( + text, + np.mean(conf_list).tolist(), + [ + len(text_index[batch_idx]), + word_list, + word_col_list, + state_list, + ], + ) + ) + else: + result_list.append((text, np.mean(conf_list).tolist())) + return result_list + + def get_ignored_tokens(self): + return [0] # for ctc blank + + +class CTCLabelDecode(BaseRecLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(CTCLabelDecode, self).__init__(character_dict_path, use_space_char) + + def __call__(self, preds, label=None, return_word_box=False, *args, **kwargs): + if isinstance(preds, tuple) or isinstance(preds, list): + preds = preds[-1] + if isinstance(preds, paddle.Tensor): + preds = preds.numpy() + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode( + preds_idx, + preds_prob, + is_remove_duplicate=True, + return_word_box=return_word_box, + ) + if return_word_box: + for rec_idx, rec in enumerate(text): + wh_ratio = kwargs["wh_ratio_list"][rec_idx] + max_wh_ratio = kwargs["max_wh_ratio"] + rec[2][0] = rec[2][0] * (wh_ratio / max_wh_ratio) + if label is None: + return text + label = self.decode(label) + return text, label + + def add_special_char(self, dict_character): + dict_character = ["blank"] + dict_character + return dict_character + + +class DistillationCTCLabelDecode(CTCLabelDecode): + """ + Convert + Convert between text-label and text-index + """ + + def __init__( + self, + character_dict_path=None, + use_space_char=False, + model_name=["student"], + key=None, + multi_head=False, + **kwargs, + ): + super(DistillationCTCLabelDecode, self).__init__( + character_dict_path, use_space_char + ) + if not isinstance(model_name, list): + model_name = [model_name] + self.model_name = model_name + + self.key = key + self.multi_head = multi_head + + def __call__(self, preds, label=None, *args, **kwargs): + output = dict() + for name in self.model_name: + pred = preds[name] + if self.key is not None: + pred = pred[self.key] + if self.multi_head and isinstance(pred, dict): + pred = pred["ctc"] + output[name] = super().__call__(pred, label=label, *args, **kwargs) + return output + + +class AttnLabelDecode(BaseRecLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(AttnLabelDecode, self).__init__(character_dict_path, use_space_char) + + def add_special_char(self, dict_character): + self.beg_str = "sos" + self.end_str = "eos" + dict_character = dict_character + dict_character = [self.beg_str] + dict_character + [self.end_str] + return dict_character + + def decode(self, text_index, text_prob=None, is_remove_duplicate=False): + """convert text-index into text-label.""" + result_list = [] + ignored_tokens = self.get_ignored_tokens() + [beg_idx, end_idx] = self.get_ignored_tokens() + batch_size = len(text_index) + for batch_idx in range(batch_size): + char_list = [] + conf_list = [] + for idx in range(len(text_index[batch_idx])): + if text_index[batch_idx][idx] in ignored_tokens: + continue + if int(text_index[batch_idx][idx]) == int(end_idx): + break + if is_remove_duplicate: + # only for predict + if ( + idx > 0 + and text_index[batch_idx][idx - 1] == text_index[batch_idx][idx] + ): + continue + char_list.append(self.character[int(text_index[batch_idx][idx])]) + if text_prob is not None: + conf_list.append(text_prob[batch_idx][idx]) + else: + conf_list.append(1) + text = "".join(char_list) + result_list.append((text, np.mean(conf_list).tolist())) + return result_list + + def __call__(self, preds, label=None, *args, **kwargs): + """ + text = self.decode(text) + if label is None: + return text + else: + label = self.decode(label, is_remove_duplicate=False) + return text, label + """ + if isinstance(preds, paddle.Tensor): + preds = preds.numpy() + + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label, is_remove_duplicate=False) + return text, label + + def get_ignored_tokens(self): + beg_idx = self.get_beg_end_flag_idx("beg") + end_idx = self.get_beg_end_flag_idx("end") + return [beg_idx, end_idx] + + def get_beg_end_flag_idx(self, beg_or_end): + if beg_or_end == "beg": + idx = np.array(self.dict[self.beg_str]) + elif beg_or_end == "end": + idx = np.array(self.dict[self.end_str]) + else: + assert False, "unsupported type %s in get_beg_end_flag_idx" % beg_or_end + return idx + + +class RFLLabelDecode(BaseRecLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(RFLLabelDecode, self).__init__(character_dict_path, use_space_char) + + def add_special_char(self, dict_character): + self.beg_str = "sos" + self.end_str = "eos" + dict_character = dict_character + dict_character = [self.beg_str] + dict_character + [self.end_str] + return dict_character + + def decode(self, text_index, text_prob=None, is_remove_duplicate=False): + """convert text-index into text-label.""" + result_list = [] + ignored_tokens = self.get_ignored_tokens() + [beg_idx, end_idx] = self.get_ignored_tokens() + batch_size = len(text_index) + for batch_idx in range(batch_size): + char_list = [] + conf_list = [] + for idx in range(len(text_index[batch_idx])): + if text_index[batch_idx][idx] in ignored_tokens: + continue + if int(text_index[batch_idx][idx]) == int(end_idx): + break + if is_remove_duplicate: + # only for predict + if ( + idx > 0 + and text_index[batch_idx][idx - 1] == text_index[batch_idx][idx] + ): + continue + char_list.append(self.character[int(text_index[batch_idx][idx])]) + if text_prob is not None: + conf_list.append(text_prob[batch_idx][idx]) + else: + conf_list.append(1) + text = "".join(char_list) + result_list.append((text, np.mean(conf_list).tolist())) + return result_list + + def __call__(self, preds, label=None, *args, **kwargs): + # if seq_outputs is not None: + if isinstance(preds, tuple) or isinstance(preds, list): + cnt_outputs, seq_outputs = preds + if isinstance(seq_outputs, paddle.Tensor): + seq_outputs = seq_outputs.numpy() + preds_idx = seq_outputs.argmax(axis=2) + preds_prob = seq_outputs.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + + if label is None: + return text + label = self.decode(label, is_remove_duplicate=False) + return text, label + + else: + cnt_outputs = preds + if isinstance(cnt_outputs, paddle.Tensor): + cnt_outputs = cnt_outputs.numpy() + cnt_length = [] + for lens in cnt_outputs: + length = round(np.sum(lens)) + cnt_length.append(length) + if label is None: + return cnt_length + label = self.decode(label, is_remove_duplicate=False) + length = [len(res[0]) for res in label] + return cnt_length, length + + def get_ignored_tokens(self): + beg_idx = self.get_beg_end_flag_idx("beg") + end_idx = self.get_beg_end_flag_idx("end") + return [beg_idx, end_idx] + + def get_beg_end_flag_idx(self, beg_or_end): + if beg_or_end == "beg": + idx = np.array(self.dict[self.beg_str]) + elif beg_or_end == "end": + idx = np.array(self.dict[self.end_str]) + else: + assert False, "unsupported type %s in get_beg_end_flag_idx" % beg_or_end + return idx + + +class SEEDLabelDecode(BaseRecLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(SEEDLabelDecode, self).__init__(character_dict_path, use_space_char) + + def add_special_char(self, dict_character): + self.padding_str = "padding" + self.end_str = "eos" + self.unknown = "unknown" + dict_character = dict_character + [self.end_str, self.padding_str, self.unknown] + return dict_character + + def get_ignored_tokens(self): + end_idx = self.get_beg_end_flag_idx("eos") + return [end_idx] + + def get_beg_end_flag_idx(self, beg_or_end): + if beg_or_end == "sos": + idx = np.array(self.dict[self.beg_str]) + elif beg_or_end == "eos": + idx = np.array(self.dict[self.end_str]) + else: + assert False, "unsupported type %s in get_beg_end_flag_idx" % beg_or_end + return idx + + def decode(self, text_index, text_prob=None, is_remove_duplicate=False): + """convert text-index into text-label.""" + result_list = [] + [end_idx] = self.get_ignored_tokens() + batch_size = len(text_index) + for batch_idx in range(batch_size): + char_list = [] + conf_list = [] + for idx in range(len(text_index[batch_idx])): + if int(text_index[batch_idx][idx]) == int(end_idx): + break + if is_remove_duplicate: + # only for predict + if ( + idx > 0 + and text_index[batch_idx][idx - 1] == text_index[batch_idx][idx] + ): + continue + char_list.append(self.character[int(text_index[batch_idx][idx])]) + if text_prob is not None: + conf_list.append(text_prob[batch_idx][idx]) + else: + conf_list.append(1) + text = "".join(char_list) + result_list.append((text, np.mean(conf_list).tolist())) + return result_list + + def __call__(self, preds, label=None, *args, **kwargs): + """ + text = self.decode(text) + if label is None: + return text + else: + label = self.decode(label, is_remove_duplicate=False) + return text, label + """ + preds_idx = preds["rec_pred"] + if isinstance(preds_idx, paddle.Tensor): + preds_idx = preds_idx.numpy() + if "rec_pred_scores" in preds: + preds_idx = preds["rec_pred"] + preds_prob = preds["rec_pred_scores"] + else: + preds_idx = preds["rec_pred"].argmax(axis=2) + preds_prob = preds["rec_pred"].max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label, is_remove_duplicate=False) + return text, label + + +class SRNLabelDecode(BaseRecLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(SRNLabelDecode, self).__init__(character_dict_path, use_space_char) + self.max_text_length = kwargs.get("max_text_length", 25) + + def __call__(self, preds, label=None, *args, **kwargs): + pred = preds["predict"] + char_num = len(self.character_str) + 2 + if isinstance(pred, paddle.Tensor): + pred = pred.numpy() + pred = np.reshape(pred, [-1, char_num]) + + preds_idx = np.argmax(pred, axis=1) + preds_prob = np.max(pred, axis=1) + + preds_idx = np.reshape(preds_idx, [-1, self.max_text_length]) + + preds_prob = np.reshape(preds_prob, [-1, self.max_text_length]) + + text = self.decode(preds_idx, preds_prob) + + if label is None: + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + return text + label = self.decode(label) + return text, label + + def decode(self, text_index, text_prob=None, is_remove_duplicate=False): + """convert text-index into text-label.""" + result_list = [] + ignored_tokens = self.get_ignored_tokens() + batch_size = len(text_index) + + for batch_idx in range(batch_size): + char_list = [] + conf_list = [] + for idx in range(len(text_index[batch_idx])): + if text_index[batch_idx][idx] in ignored_tokens: + continue + if is_remove_duplicate: + # only for predict + if ( + idx > 0 + and text_index[batch_idx][idx - 1] == text_index[batch_idx][idx] + ): + continue + char_list.append(self.character[int(text_index[batch_idx][idx])]) + if text_prob is not None: + conf_list.append(text_prob[batch_idx][idx]) + else: + conf_list.append(1) + + text = "".join(char_list) + result_list.append((text, np.mean(conf_list).tolist())) + return result_list + + def add_special_char(self, dict_character): + dict_character = dict_character + [self.beg_str, self.end_str] + return dict_character + + def get_ignored_tokens(self): + beg_idx = self.get_beg_end_flag_idx("beg") + end_idx = self.get_beg_end_flag_idx("end") + return [beg_idx, end_idx] + + def get_beg_end_flag_idx(self, beg_or_end): + if beg_or_end == "beg": + idx = np.array(self.dict[self.beg_str]) + elif beg_or_end == "end": + idx = np.array(self.dict[self.end_str]) + else: + assert False, "unsupported type %s in get_beg_end_flag_idx" % beg_or_end + return idx + + +class ParseQLabelDecode(BaseRecLabelDecode): + """Convert between text-label and text-index""" + + BOS = "[B]" + EOS = "[E]" + PAD = "[P]" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(ParseQLabelDecode, self).__init__(character_dict_path, use_space_char) + self.max_text_length = kwargs.get("max_text_length", 25) + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, dict): + pred = preds["predict"] + else: + pred = preds + + char_num = ( + len(self.character_str) + 1 + ) # We don't predict nor , with only addition + if isinstance(pred, paddle.Tensor): + pred = pred.numpy() + B, L = pred.shape[:2] + pred = np.reshape(pred, [-1, char_num]) + + preds_idx = np.argmax(pred, axis=1) + preds_prob = np.max(pred, axis=1) + + preds_idx = np.reshape(preds_idx, [B, L]) + preds_prob = np.reshape(preds_prob, [B, L]) + + if label is None: + text = self.decode(preds_idx, preds_prob, raw=False) + return text + + text = self.decode(preds_idx, preds_prob, raw=False) + label = self.decode(label, None, False) + + return text, label + + def decode(self, text_index, text_prob=None, raw=False): + """convert text-index into text-label.""" + result_list = [] + ignored_tokens = self.get_ignored_tokens() + batch_size = len(text_index) + + for batch_idx in range(batch_size): + char_list = [] + conf_list = [] + + index = text_index[batch_idx, :] + prob = None + if text_prob is not None: + prob = text_prob[batch_idx, :] + + if not raw: + index, prob = self._filter(index, prob) + + for idx in range(len(index)): + if index[idx] in ignored_tokens: + continue + char_list.append(self.character[int(index[idx])]) + if text_prob is not None: + conf_list.append(prob[idx]) + else: + conf_list.append(1) + + text = "".join(char_list) + result_list.append((text, np.mean(conf_list).tolist())) + + return result_list + + def add_special_char(self, dict_character): + dict_character = [self.EOS] + dict_character + [self.BOS, self.PAD] + return dict_character + + def _filter(self, ids, probs=None): + ids = ids.tolist() + try: + eos_idx = ids.index(self.dict[self.EOS]) + except ValueError: + eos_idx = len(ids) # Nothing to truncate. + # Truncate after EOS + ids = ids[:eos_idx] + if probs is not None: + probs = probs[: eos_idx + 1] # but include prob. for EOS (if it exists) + return ids, probs + + def get_ignored_tokens(self): + return [self.dict[self.BOS], self.dict[self.EOS], self.dict[self.PAD]] + + +class SARLabelDecode(BaseRecLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(SARLabelDecode, self).__init__(character_dict_path, use_space_char) + + self.rm_symbol = kwargs.get("rm_symbol", False) + + def add_special_char(self, dict_character): + beg_end_str = "" + unknown_str = "" + padding_str = "" + dict_character = dict_character + [unknown_str] + self.unknown_idx = len(dict_character) - 1 + dict_character = dict_character + [beg_end_str] + self.start_idx = len(dict_character) - 1 + self.end_idx = len(dict_character) - 1 + dict_character = dict_character + [padding_str] + self.padding_idx = len(dict_character) - 1 + return dict_character + + def decode(self, text_index, text_prob=None, is_remove_duplicate=False): + """convert text-index into text-label.""" + result_list = [] + ignored_tokens = self.get_ignored_tokens() + + batch_size = len(text_index) + for batch_idx in range(batch_size): + char_list = [] + conf_list = [] + for idx in range(len(text_index[batch_idx])): + if text_index[batch_idx][idx] in ignored_tokens: + continue + if int(text_index[batch_idx][idx]) == int(self.end_idx): + if text_prob is None and idx == 0: + continue + else: + break + if is_remove_duplicate: + # only for predict + if ( + idx > 0 + and text_index[batch_idx][idx - 1] == text_index[batch_idx][idx] + ): + continue + char_list.append(self.character[int(text_index[batch_idx][idx])]) + if text_prob is not None: + conf_list.append(text_prob[batch_idx][idx]) + else: + conf_list.append(1) + text = "".join(char_list) + if self.rm_symbol: + comp = re.compile("[^A-Z^a-z^0-9^\u4e00-\u9fa5]") + text = text.lower() + text = comp.sub("", text) + result_list.append((text, np.mean(conf_list).tolist())) + return result_list + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, paddle.Tensor): + preds = preds.numpy() + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + + if label is None: + return text + label = self.decode(label, is_remove_duplicate=False) + return text, label + + def get_ignored_tokens(self): + return [self.padding_idx] + + +class SATRNLabelDecode(BaseRecLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(SATRNLabelDecode, self).__init__(character_dict_path, use_space_char) + + self.rm_symbol = kwargs.get("rm_symbol", False) + + def add_special_char(self, dict_character): + beg_end_str = "" + unknown_str = "" + padding_str = "" + dict_character = dict_character + [unknown_str] + self.unknown_idx = len(dict_character) - 1 + dict_character = dict_character + [beg_end_str] + self.start_idx = len(dict_character) - 1 + self.end_idx = len(dict_character) - 1 + dict_character = dict_character + [padding_str] + self.padding_idx = len(dict_character) - 1 + return dict_character + + def decode(self, text_index, text_prob=None, is_remove_duplicate=False): + """convert text-index into text-label.""" + result_list = [] + ignored_tokens = self.get_ignored_tokens() + + batch_size = len(text_index) + for batch_idx in range(batch_size): + char_list = [] + conf_list = [] + for idx in range(len(text_index[batch_idx])): + if text_index[batch_idx][idx] in ignored_tokens: + continue + if int(text_index[batch_idx][idx]) == int(self.end_idx): + if text_prob is None and idx == 0: + continue + else: + break + if is_remove_duplicate: + # only for predict + if ( + idx > 0 + and text_index[batch_idx][idx - 1] == text_index[batch_idx][idx] + ): + continue + char_list.append(self.character[int(text_index[batch_idx][idx])]) + if text_prob is not None: + conf_list.append(text_prob[batch_idx][idx]) + else: + conf_list.append(1) + text = "".join(char_list) + if self.rm_symbol: + comp = re.compile("[^A-Z^a-z^0-9^\u4e00-\u9fa5]") + text = text.lower() + text = comp.sub("", text) + result_list.append((text, np.mean(conf_list).tolist())) + return result_list + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, paddle.Tensor): + preds = preds.numpy() + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + + if label is None: + return text + label = self.decode(label, is_remove_duplicate=False) + return text, label + + def get_ignored_tokens(self): + return [self.padding_idx] + + +class DistillationSARLabelDecode(SARLabelDecode): + """ + Convert + Convert between text-label and text-index + """ + + def __init__( + self, + character_dict_path=None, + use_space_char=False, + model_name=["student"], + key=None, + multi_head=False, + **kwargs, + ): + super(DistillationSARLabelDecode, self).__init__( + character_dict_path, use_space_char + ) + if not isinstance(model_name, list): + model_name = [model_name] + self.model_name = model_name + + self.key = key + self.multi_head = multi_head + + def __call__(self, preds, label=None, *args, **kwargs): + output = dict() + for name in self.model_name: + pred = preds[name] + if self.key is not None: + pred = pred[self.key] + if self.multi_head and isinstance(pred, dict): + pred = pred["sar"] + output[name] = super().__call__(pred, label=label, *args, **kwargs) + return output + + +class PRENLabelDecode(BaseRecLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(PRENLabelDecode, self).__init__(character_dict_path, use_space_char) + + def add_special_char(self, dict_character): + padding_str = "" # 0 + end_str = "" # 1 + unknown_str = "" # 2 + + dict_character = [padding_str, end_str, unknown_str] + dict_character + self.padding_idx = 0 + self.end_idx = 1 + self.unknown_idx = 2 + + return dict_character + + def decode(self, text_index, text_prob=None): + """convert text-index into text-label.""" + result_list = [] + batch_size = len(text_index) + + for batch_idx in range(batch_size): + char_list = [] + conf_list = [] + for idx in range(len(text_index[batch_idx])): + if text_index[batch_idx][idx] == self.end_idx: + break + if text_index[batch_idx][idx] in [self.padding_idx, self.unknown_idx]: + continue + char_list.append(self.character[int(text_index[batch_idx][idx])]) + if text_prob is not None: + conf_list.append(text_prob[batch_idx][idx]) + else: + conf_list.append(1) + + text = "".join(char_list) + if len(text) > 0: + result_list.append((text, np.mean(conf_list).tolist())) + else: + # here confidence of empty recog result is 1 + result_list.append(("", 1)) + return result_list + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, paddle.Tensor): + preds = preds.numpy() + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob) + if label is None: + return text + label = self.decode(label) + return text, label + + +class NRTRLabelDecode(BaseRecLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=True, **kwargs): + super(NRTRLabelDecode, self).__init__(character_dict_path, use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + if len(preds) == 2: + preds_id = preds[0] + preds_prob = preds[1] + if isinstance(preds_id, paddle.Tensor): + preds_id = preds_id.numpy() + if isinstance(preds_prob, paddle.Tensor): + preds_prob = preds_prob.numpy() + if preds_id[0][0] == 2: + preds_idx = preds_id[:, 1:] + preds_prob = preds_prob[:, 1:] + else: + preds_idx = preds_id + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label[:, 1:]) + else: + if isinstance(preds, paddle.Tensor): + preds = preds.numpy() + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label[:, 1:]) + return text, label + + def add_special_char(self, dict_character): + dict_character = ["blank", "", "", ""] + dict_character + return dict_character + + def decode(self, text_index, text_prob=None, is_remove_duplicate=False): + """convert text-index into text-label.""" + result_list = [] + batch_size = len(text_index) + for batch_idx in range(batch_size): + char_list = [] + conf_list = [] + for idx in range(len(text_index[batch_idx])): + try: + char_idx = self.character[int(text_index[batch_idx][idx])] + except: + continue + if char_idx == "": # end + break + char_list.append(char_idx) + if text_prob is not None: + conf_list.append(text_prob[batch_idx][idx]) + else: + conf_list.append(1) + text = "".join(char_list) + result_list.append((text, np.mean(conf_list).tolist())) + return result_list + + +class ViTSTRLabelDecode(NRTRLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(ViTSTRLabelDecode, self).__init__(character_dict_path, use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, paddle.Tensor): + preds = preds[:, 1:].numpy() + else: + preds = preds[:, 1:] + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label[:, 1:]) + return text, label + + def add_special_char(self, dict_character): + dict_character = ["", ""] + dict_character + return dict_character + + +class ABINetLabelDecode(NRTRLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(ABINetLabelDecode, self).__init__(character_dict_path, use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, dict): + preds = preds["align"][-1].numpy() + elif isinstance(preds, paddle.Tensor): + preds = preds.numpy() + else: + preds = preds + + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label) + return text, label + + def add_special_char(self, dict_character): + dict_character = [""] + dict_character + return dict_character + + +class SPINLabelDecode(AttnLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(SPINLabelDecode, self).__init__(character_dict_path, use_space_char) + + def add_special_char(self, dict_character): + self.beg_str = "sos" + self.end_str = "eos" + dict_character = dict_character + dict_character = [self.beg_str] + [self.end_str] + dict_character + return dict_character + + +class VLLabelDecode(BaseRecLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(VLLabelDecode, self).__init__(character_dict_path, use_space_char) + self.max_text_length = kwargs.get("max_text_length", 25) + self.nclass = len(self.character) + 1 + + def decode(self, text_index, text_prob=None, is_remove_duplicate=False): + """convert text-index into text-label.""" + result_list = [] + ignored_tokens = self.get_ignored_tokens() + batch_size = len(text_index) + for batch_idx in range(batch_size): + selection = np.ones(len(text_index[batch_idx]), dtype=bool) + if is_remove_duplicate: + selection[1:] = text_index[batch_idx][1:] != text_index[batch_idx][:-1] + for ignored_token in ignored_tokens: + selection &= text_index[batch_idx] != ignored_token + + char_list = [ + self.character[text_id - 1] + for text_id in text_index[batch_idx][selection] + ] + if text_prob is not None: + conf_list = text_prob[batch_idx][selection] + else: + conf_list = [1] * len(selection) + if len(conf_list) == 0: + conf_list = [0] + + text = "".join(char_list) + result_list.append((text, np.mean(conf_list).tolist())) + return result_list + + def __call__(self, preds, label=None, length=None, *args, **kwargs): + if len(preds) == 2: # eval mode + text_pre, x = preds + b = text_pre.shape[1] + lenText = self.max_text_length + nsteps = self.max_text_length + + if not isinstance(text_pre, paddle.Tensor): + text_pre = paddle.to_tensor(text_pre, dtype="float32") + + out_res = paddle.zeros(shape=[lenText, b, self.nclass], dtype=x.dtype) + out_length = paddle.zeros(shape=[b], dtype=x.dtype) + now_step = 0 + for _ in range(nsteps): + if 0 in out_length and now_step < nsteps: + tmp_result = text_pre[now_step, :, :] + out_res[now_step] = tmp_result + tmp_result = tmp_result.topk(1)[1].squeeze(axis=1) + for j in range(b): + if out_length[j] == 0 and tmp_result[j] == 0: + out_length[j] = now_step + 1 + now_step += 1 + for j in range(0, b): + if int(out_length[j]) == 0: + out_length[j] = nsteps + start = 0 + output = paddle.zeros( + shape=[int(out_length.sum()), self.nclass], dtype=x.dtype + ) + for i in range(0, b): + cur_length = int(out_length[i]) + output[start : start + cur_length] = out_res[0:cur_length, i, :] + start += cur_length + net_out = output + length = out_length + + else: # train mode + net_out = preds[0] + length = length + net_out = paddle.concat([t[:l] for t, l in zip(net_out, length)]) + text = [] + if not isinstance(net_out, paddle.Tensor): + net_out = paddle.to_tensor(net_out, dtype="float32") + net_out = F.softmax(net_out, axis=1) + for i in range(0, length.shape[0]): + if i == 0: + start_idx = 0 + end_idx = int(length[i]) + else: + start_idx = int(length[:i].sum()) + end_idx = int(length[:i].sum() + length[i]) + preds_idx = net_out[start_idx:end_idx].topk(1)[1][:, 0].tolist() + preds_text = "".join( + [ + ( + self.character[idx - 1] + if idx > 0 and idx <= len(self.character) + else "" + ) + for idx in preds_idx + ] + ) + preds_prob = net_out[start_idx:end_idx].topk(1)[0][:, 0] + preds_prob = paddle.exp( + paddle.log(preds_prob).sum() / (preds_prob.shape[0] + 1e-6) + ) + text.append((preds_text, float(preds_prob))) + if label is None: + return text + label = self.decode(label) + return text, label + + +class CANLabelDecode(BaseRecLabelDecode): + """Convert between latex-symbol and symbol-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(CANLabelDecode, self).__init__(character_dict_path, use_space_char) + + def decode(self, text_index, preds_prob=None): + result_list = [] + batch_size = len(text_index) + for batch_idx in range(batch_size): + seq_end = text_index[batch_idx].argmin(0) + idx_list = text_index[batch_idx][:seq_end].tolist() + symbol_list = [self.character[idx] for idx in idx_list] + probs = [] + if preds_prob is not None: + probs = preds_prob[batch_idx][: len(symbol_list)].tolist() + + result_list.append([" ".join(symbol_list), probs]) + return result_list + + def __call__(self, preds, label=None, *args, **kwargs): + pred_prob, _, _, _ = preds + preds_idx = pred_prob.argmax(axis=2) + + text = self.decode(preds_idx) + if label is None: + return text + label = self.decode(label) + return text, label + + +class CPPDLabelDecode(NRTRLabelDecode): + """Convert between text-label and text-index""" + + def __init__(self, character_dict_path=None, use_space_char=False, **kwargs): + super(CPPDLabelDecode, self).__init__(character_dict_path, use_space_char) + + def __call__(self, preds, label=None, *args, **kwargs): + if isinstance(preds, tuple): + if isinstance(preds[-1], dict): + preds = preds[-1]["align"][-1].numpy() + else: + preds = preds[-1].numpy() + if isinstance(preds, paddle.Tensor): + preds = preds.numpy() + else: + preds = preds + preds_idx = preds.argmax(axis=2) + preds_prob = preds.max(axis=2) + text = self.decode(preds_idx, preds_prob, is_remove_duplicate=False) + if label is None: + return text + label = self.decode(label) + return text, label + + def add_special_char(self, dict_character): + dict_character = [""] + dict_character + return dict_character + + +class LaTeXOCRDecode(object): + """Convert between latex-symbol and symbol-index""" + + def __init__(self, rec_char_dict_path, **kwargs): + # Set the TOKENIZERS_PARALLELISM environment variable to 'false' to suppress + # the warning: "The current process just got forked, Disabling parallelism to avoid deadlocks.. + # To disable this warning, please explicitly set TOKENIZERS_PARALLELISM=(true | false)" from tokenizers + os.environ["TOKENIZERS_PARALLELISM"] = "false" + from tokenizers import Tokenizer as TokenizerFast + + super(LaTeXOCRDecode, self).__init__() + self.tokenizer = TokenizerFast.from_file(rec_char_dict_path) + + def post_process(self, s): + text_reg = r"(\\(operatorname|mathrm|text|mathbf)\s?\*? {.*?})" + letter = "[a-zA-Z]" + noletter = "[\W_^\d]" + names = [x[0].replace(" ", "") for x in re.findall(text_reg, s)] + s = re.sub(text_reg, lambda match: str(names.pop(0)), s) + news = s + while True: + s = news + news = re.sub(r"(?!\\ )(%s)\s+?(%s)" % (noletter, noletter), r"\1\2", s) + news = re.sub(r"(?!\\ )(%s)\s+?(%s)" % (noletter, letter), r"\1\2", news) + news = re.sub(r"(%s)\s+?(%s)" % (letter, noletter), r"\1\2", news) + if news == s: + break + return s + + def decode(self, tokens): + if len(tokens.shape) == 1: + tokens = tokens[None, :] + dec = [self.tokenizer.decode(tok) for tok in tokens] + dec_str_list = [ + "".join(detok.split(" ")) + .replace("Ġ", " ") + .replace("[EOS]", "") + .replace("[BOS]", "") + .replace("[PAD]", "") + .strip() + for detok in dec + ] + return [self.post_process(dec_str) for dec_str in dec_str_list] + + def __call__(self, preds, label=None, mode="eval", *args, **kwargs): + if mode == "train": + preds_idx = np.array(preds.argmax(axis=2)) + text = self.decode(preds_idx) + else: + text = self.decode(np.array(preds)) + if label is None: + return text + label = self.decode(np.array(label)) + return text, label + + +class UniMERNetDecode(object): + + SPECIAL_TOKENS_ATTRIBUTES = [ + "bos_token", + "eos_token", + "unk_token", + "sep_token", + "pad_token", + "cls_token", + "mask_token", + "additional_special_tokens", + ] + + def __init__( + self, + rec_char_dict_path, + is_infer=False, + **kwargs, + ): + # Set the TOKENIZERS_PARALLELISM environment variable to 'false' to suppress + # the warning: "The current process just got forked, Disabling parallelism to avoid deadlocks.. + # To disable this warning, please explicitly set TOKENIZERS_PARALLELISM=(true | false)" from tokenizers + os.environ["TOKENIZERS_PARALLELISM"] = "false" + from tokenizers import Tokenizer as TokenizerFast + from tokenizers import AddedToken + + self.is_infer = is_infer + self._unk_token = "" + self._bos_token = "" + self._eos_token = "" + self._pad_token = "" + self._sep_token = None + self._cls_token = None + self._mask_token = None + self._additional_special_tokens = [] + self.model_input_names = ["input_ids", "token_type_ids", "attention_mask"] + self.max_seq_len = 2048 + self.pad_token_id = 1 + self.bos_token_id = 0 + self.eos_token_id = 2 + self.padding_side = "right" + self.pad_token_id = 1 + self.pad_token = "" + self.pad_token_type_id = 0 + self.pad_to_multiple_of = None + fast_tokenizer_file = os.path.join(rec_char_dict_path, "tokenizer.json") + tokenizer_config_file = os.path.join( + rec_char_dict_path, "tokenizer_config.json" + ) + self.tokenizer = TokenizerFast.from_file(fast_tokenizer_file) + added_tokens_decoder = {} + added_tokens_map = {} + if tokenizer_config_file is not None: + with open( + tokenizer_config_file, encoding="utf-8" + ) as tokenizer_config_handle: + init_kwargs = json.load(tokenizer_config_handle) + if "added_tokens_decoder" in init_kwargs: + for idx, token in init_kwargs["added_tokens_decoder"].items(): + if isinstance(token, dict): + token = AddedToken(**token) + if isinstance(token, AddedToken): + added_tokens_decoder[int(idx)] = token + added_tokens_map[str(token)] = token + else: + raise ValueError( + f"Found a {token.__class__} in the saved `added_tokens_decoder`, should be a dictionary or an AddedToken instance" + ) + init_kwargs["added_tokens_decoder"] = added_tokens_decoder + added_tokens_decoder = init_kwargs.pop("added_tokens_decoder", {}) + tokens_to_add = [ + token + for index, token in sorted( + added_tokens_decoder.items(), key=lambda x: x[0] + ) + if token not in added_tokens_decoder + ] + added_tokens_encoder = self.added_tokens_encoder(added_tokens_decoder) + encoder = list(added_tokens_encoder.keys()) + [ + str(token) for token in tokens_to_add + ] + tokens_to_add += [ + token + for token in self.all_special_tokens_extended + if token not in encoder and token not in tokens_to_add + ] + if len(tokens_to_add) > 0: + is_last_special = None + tokens = [] + special_tokens = self.all_special_tokens + for token in tokens_to_add: + is_special = ( + (token.special or str(token) in special_tokens) + if isinstance(token, AddedToken) + else str(token) in special_tokens + ) + if is_last_special is None or is_last_special == is_special: + tokens.append(token) + else: + self._add_tokens(tokens, special_tokens=is_last_special) + tokens = [token] + is_last_special = is_special + if tokens: + self._add_tokens(tokens, special_tokens=is_last_special) + + def _add_tokens(self, new_tokens, special_tokens=False) -> int: + if special_tokens: + return self.tokenizer.add_special_tokens(new_tokens) + + return self.tokenizer.add_tokens(new_tokens) + + def added_tokens_encoder(self, added_tokens_decoder): + return { + k.content: v + for v, k in sorted(added_tokens_decoder.items(), key=lambda item: item[0]) + } + + @property + def all_special_tokens(self): + + all_toks = [str(s) for s in self.all_special_tokens_extended] + return all_toks + + @property + def all_special_tokens_extended(self): + all_tokens = [] + seen = set() + for value in self.special_tokens_map_extended.values(): + if isinstance(value, (list, tuple)): + tokens_to_add = [token for token in value if str(token) not in seen] + else: + tokens_to_add = [value] if str(value) not in seen else [] + seen.update(map(str, tokens_to_add)) + all_tokens.extend(tokens_to_add) + return all_tokens + + @property + def special_tokens_map_extended(self): + set_attr = {} + for attr in self.SPECIAL_TOKENS_ATTRIBUTES: + attr_value = getattr(self, "_" + attr) + if attr_value: + set_attr[attr] = attr_value + return set_attr + + def convert_ids_to_tokens(self, ids, skip_special_tokens: bool = False): + if isinstance(ids, int): + return self.tokenizer.id_to_token(ids) + tokens = [] + for index in ids: + index = int(index) + if skip_special_tokens and index in self.all_special_ids: + continue + tokens.append(self.tokenizer.id_to_token(index)) + return tokens + + def detokenize(self, tokens): + self.tokenizer.bos_token = "" + self.tokenizer.eos_token = "" + self.tokenizer.pad_token = "" + toks = [self.convert_ids_to_tokens(tok) for tok in tokens] + for b in range(len(toks)): + for i in reversed(range(len(toks[b]))): + if toks[b][i] is None: + toks[b][i] = "" + toks[b][i] = toks[b][i].replace("Ġ", " ").strip() + if toks[b][i] in ( + [ + self.tokenizer.bos_token, + self.tokenizer.eos_token, + self.tokenizer.pad_token, + ] + ): + del toks[b][i] + return toks + + def token2str(self, token_ids) -> list: + generated_text = [] + for tok_id in token_ids: + end_idx = np.argwhere(tok_id == 2) + if len(end_idx) > 0: + end_idx = int(end_idx[0][0]) + tok_id = tok_id[: end_idx + 1] + generated_text.append( + self.tokenizer.decode(tok_id, skip_special_tokens=True) + ) + generated_text = [self.post_process(text) for text in generated_text] + return generated_text + + def normalize_infer(self, s: str) -> str: + """Normalizes a string by removing unnecessary spaces. + + Args: + s (str): String to normalize. + + Returns: + str: Normalized string. + """ + text_reg = r"(\\(operatorname|mathrm|text|mathbf)\s?\*? {.*?})" + letter = "[a-zA-Z]" + noletter = "[\W_^\d]" + names = [] + for x in re.findall(text_reg, s): + pattern = r"\\[a-zA-Z]+" + pattern = r"(\\[a-zA-Z]+)\s(?=\w)|\\[a-zA-Z]+\s(?=})" + matches = re.findall(pattern, x[0]) + for m in matches: + if ( + m + not in [ + "\\operatorname", + "\\mathrm", + "\\text", + "\\mathbf", + ] + and m.strip() != "" + ): + s = s.replace(m, m + "XXXXXXX") + s = s.replace(" ", "") + names.append(s) + if len(names) > 0: + s = re.sub(text_reg, lambda match: str(names.pop(0)), s) + news = s + while True: + s = news + news = re.sub(r"(?!\\ )(%s)\s+?(%s)" % (noletter, noletter), r"\1\2", s) + news = re.sub(r"(?!\\ )(%s)\s+?(%s)" % (noletter, letter), r"\1\2", news) + news = re.sub(r"(%s)\s+?(%s)" % (letter, noletter), r"\1\2", news) + if news == s: + break + return s.replace("XXXXXXX", " ") + + def remove_chinese_text_wrapping(self, formula): + pattern = re.compile(r"\\text\s*{\s*([^}]*?[\u4e00-\u9fff]+[^}]*?)\s*}") + + def replacer(match): + return match.group(1) + + replaced_formula = pattern.sub(replacer, formula) + return replaced_formula.replace('"', "") + + def normalize(self, s): + text_reg = r"(\\(operatorname|mathrm|text|mathbf)\s?\*? {.*?})" + letter = "[a-zA-Z]" + noletter = "[\W_^\d]" + names = [x[0].replace(" ", "") for x in re.findall(text_reg, s)] + s = re.sub(text_reg, lambda match: str(names.pop(0)), s) + news = s + while True: + s = news + news = re.sub(r"(?!\\ )(%s)\s+?(%s)" % (noletter, noletter), r"\1\2", s) + news = re.sub(r"(?!\\ )(%s)\s+?(%s)" % (noletter, letter), r"\1\2", news) + news = re.sub(r"(%s)\s+?(%s)" % (letter, noletter), r"\1\2", news) + if news == s: + break + return s + + def post_process(self, text: str) -> str: + """Post-processes a string by fixing text and normalizing it. + + Args: + text (str): String to post-process. + + Returns: + str: Post-processed string. + """ + from ftfy import fix_text + + if self.is_infer: + text = self.remove_chinese_text_wrapping(text) + text = fix_text(text) + text = self.normalize_infer(text) + else: + text = fix_text(text) + text = self.normalize(text) + return text + + def __call__(self, preds, label=None, mode="eval", *args, **kwargs): + if mode == "train": + preds_idx = np.array(preds.argmax(axis=2)) + text = self.token2str(preds_idx) + else: + text = self.token2str(np.array(preds)) + if label is None: + return text + label = self.token2str(np.array(label)) + return text, label diff --git a/ppocr/postprocess/sast_postprocess.py b/ppocr/postprocess/sast_postprocess.py new file mode 100755 index 0000000..6c8b8e3 --- /dev/null +++ b/ppocr/postprocess/sast_postprocess.py @@ -0,0 +1,358 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys + +__dir__ = os.path.dirname(__file__) +sys.path.append(__dir__) +sys.path.append(os.path.join(__dir__, "..")) + +import numpy as np +from .locality_aware_nms import nms_locality +import paddle +import cv2 +import time + + +class SASTPostProcess(object): + """ + The post process for SAST. + """ + + def __init__( + self, + score_thresh=0.5, + nms_thresh=0.2, + sample_pts_num=2, + shrink_ratio_of_width=0.3, + expand_scale=1.0, + tcl_map_thresh=0.5, + **kwargs, + ): + self.score_thresh = score_thresh + self.nms_thresh = nms_thresh + self.sample_pts_num = sample_pts_num + self.shrink_ratio_of_width = shrink_ratio_of_width + self.expand_scale = expand_scale + self.tcl_map_thresh = tcl_map_thresh + + def point_pair2poly(self, point_pair_list): + """ + Transfer vertical point_pairs into poly point in clockwise. + """ + # construct poly + point_num = len(point_pair_list) * 2 + point_list = [0] * point_num + for idx, point_pair in enumerate(point_pair_list): + point_list[idx] = point_pair[0] + point_list[point_num - 1 - idx] = point_pair[1] + return np.array(point_list).reshape(-1, 2) + + def shrink_quad_along_width(self, quad, begin_width_ratio=0.0, end_width_ratio=1.0): + """ + Generate shrink_quad_along_width. + """ + ratio_pair = np.array( + [[begin_width_ratio], [end_width_ratio]], dtype=np.float32 + ) + p0_1 = quad[0] + (quad[1] - quad[0]) * ratio_pair + p3_2 = quad[3] + (quad[2] - quad[3]) * ratio_pair + return np.array([p0_1[0], p0_1[1], p3_2[1], p3_2[0]]) + + def expand_poly_along_width(self, poly, shrink_ratio_of_width=0.3): + """ + expand poly along width. + """ + point_num = poly.shape[0] + left_quad = np.array([poly[0], poly[1], poly[-2], poly[-1]], dtype=np.float32) + left_ratio = ( + -shrink_ratio_of_width + * np.linalg.norm(left_quad[0] - left_quad[3]) + / (np.linalg.norm(left_quad[0] - left_quad[1]) + 1e-6) + ) + left_quad_expand = self.shrink_quad_along_width(left_quad, left_ratio, 1.0) + right_quad = np.array( + [ + poly[point_num // 2 - 2], + poly[point_num // 2 - 1], + poly[point_num // 2], + poly[point_num // 2 + 1], + ], + dtype=np.float32, + ) + right_ratio = 1.0 + shrink_ratio_of_width * np.linalg.norm( + right_quad[0] - right_quad[3] + ) / (np.linalg.norm(right_quad[0] - right_quad[1]) + 1e-6) + right_quad_expand = self.shrink_quad_along_width(right_quad, 0.0, right_ratio) + poly[0] = left_quad_expand[0] + poly[-1] = left_quad_expand[-1] + poly[point_num // 2 - 1] = right_quad_expand[1] + poly[point_num // 2] = right_quad_expand[2] + return poly + + def restore_quad(self, tcl_map, tcl_map_thresh, tvo_map): + """Restore quad.""" + xy_text = np.argwhere(tcl_map[:, :, 0] > tcl_map_thresh) + xy_text = xy_text[:, ::-1] # (n, 2) + + # Sort the text boxes via the y axis + xy_text = xy_text[np.argsort(xy_text[:, 1])] + + scores = tcl_map[xy_text[:, 1], xy_text[:, 0], 0] + scores = scores[:, np.newaxis] + + # Restore + point_num = int(tvo_map.shape[-1] / 2) + assert point_num == 4 + tvo_map = tvo_map[xy_text[:, 1], xy_text[:, 0], :] + xy_text_tile = np.tile(xy_text, (1, point_num)) # (n, point_num * 2) + quads = xy_text_tile - tvo_map + + return scores, quads, xy_text + + def quad_area(self, quad): + """ + compute area of a quad. + """ + edge = [ + (quad[1][0] - quad[0][0]) * (quad[1][1] + quad[0][1]), + (quad[2][0] - quad[1][0]) * (quad[2][1] + quad[1][1]), + (quad[3][0] - quad[2][0]) * (quad[3][1] + quad[2][1]), + (quad[0][0] - quad[3][0]) * (quad[0][1] + quad[3][1]), + ] + return np.sum(edge) / 2.0 + + def nms(self, dets): + dets = nms_locality(dets, self.nms_thresh) + return dets + + def cluster_by_quads_tco(self, tcl_map, tcl_map_thresh, quads, tco_map): + """ + Cluster pixels in tcl_map based on quads. + """ + instance_count = quads.shape[0] + 1 # contain background + instance_label_map = np.zeros(tcl_map.shape[:2], dtype=np.int32) + if instance_count == 1: + return instance_count, instance_label_map + + # predict text center + xy_text = np.argwhere(tcl_map[:, :, 0] > tcl_map_thresh) + n = xy_text.shape[0] + xy_text = xy_text[:, ::-1] # (n, 2) + tco = tco_map[xy_text[:, 1], xy_text[:, 0], :] # (n, 2) + pred_tc = xy_text - tco + + # get gt text center + m = quads.shape[0] + gt_tc = np.mean(quads, axis=1) # (m, 2) + + pred_tc_tile = np.tile(pred_tc[:, np.newaxis, :], (1, m, 1)) # (n, m, 2) + gt_tc_tile = np.tile(gt_tc[np.newaxis, :, :], (n, 1, 1)) # (n, m, 2) + dist_mat = np.linalg.norm(pred_tc_tile - gt_tc_tile, axis=2) # (n, m) + xy_text_assign = np.argmin(dist_mat, axis=1) + 1 # (n,) + + instance_label_map[xy_text[:, 1], xy_text[:, 0]] = xy_text_assign + return instance_count, instance_label_map + + def estimate_sample_pts_num(self, quad, xy_text): + """ + Estimate sample points number. + """ + eh = ( + np.linalg.norm(quad[0] - quad[3]) + np.linalg.norm(quad[1] - quad[2]) + ) / 2.0 + ew = ( + np.linalg.norm(quad[0] - quad[1]) + np.linalg.norm(quad[2] - quad[3]) + ) / 2.0 + + dense_sample_pts_num = max(2, int(ew)) + dense_xy_center_line = xy_text[ + np.linspace( + 0, + xy_text.shape[0] - 1, + dense_sample_pts_num, + endpoint=True, + dtype=np.float32, + ).astype(np.int32) + ] + + dense_xy_center_line_diff = dense_xy_center_line[1:] - dense_xy_center_line[:-1] + estimate_arc_len = np.sum(np.linalg.norm(dense_xy_center_line_diff, axis=1)) + + sample_pts_num = max(2, int(estimate_arc_len / eh)) + return sample_pts_num + + def detect_sast( + self, + tcl_map, + tvo_map, + tbo_map, + tco_map, + ratio_w, + ratio_h, + src_w, + src_h, + shrink_ratio_of_width=0.3, + tcl_map_thresh=0.5, + offset_expand=1.0, + out_strid=4.0, + ): + """ + first resize the tcl_map, tvo_map and tbo_map to the input_size, then restore the polys + """ + # restore quad + scores, quads, xy_text = self.restore_quad(tcl_map, tcl_map_thresh, tvo_map) + dets = np.hstack((quads, scores)).astype(np.float32, copy=False) + dets = self.nms(dets) + if dets.shape[0] == 0: + return [] + quads = dets[:, :-1].reshape(-1, 4, 2) + + # Compute quad area + quad_areas = [] + for quad in quads: + quad_areas.append(-self.quad_area(quad)) + + # instance segmentation + # instance_count, instance_label_map = cv2.connectedComponents(tcl_map.astype(np.uint8), connectivity=8) + instance_count, instance_label_map = self.cluster_by_quads_tco( + tcl_map, tcl_map_thresh, quads, tco_map + ) + + # restore single poly with tcl instance. + poly_list = [] + for instance_idx in range(1, instance_count): + xy_text = np.argwhere(instance_label_map == instance_idx)[:, ::-1] + quad = quads[instance_idx - 1] + q_area = quad_areas[instance_idx - 1] + if q_area < 5: + continue + + # + len1 = float(np.linalg.norm(quad[0] - quad[1])) + len2 = float(np.linalg.norm(quad[1] - quad[2])) + min_len = min(len1, len2) + if min_len < 3: + continue + + # filter small CC + if xy_text.shape[0] <= 0: + continue + + # filter low confidence instance + xy_text_scores = tcl_map[xy_text[:, 1], xy_text[:, 0], 0] + if np.sum(xy_text_scores) / quad_areas[instance_idx - 1] < 0.1: + # if np.sum(xy_text_scores) / quad_areas[instance_idx - 1] < 0.05: + continue + + # sort xy_text + left_center_pt = np.array( + [[(quad[0, 0] + quad[-1, 0]) / 2.0, (quad[0, 1] + quad[-1, 1]) / 2.0]] + ) # (1, 2) + right_center_pt = np.array( + [[(quad[1, 0] + quad[2, 0]) / 2.0, (quad[1, 1] + quad[2, 1]) / 2.0]] + ) # (1, 2) + proj_unit_vec = (right_center_pt - left_center_pt) / ( + np.linalg.norm(right_center_pt - left_center_pt) + 1e-6 + ) + proj_value = np.sum(xy_text * proj_unit_vec, axis=1) + xy_text = xy_text[np.argsort(proj_value)] + + # Sample pts in tcl map + if self.sample_pts_num == 0: + sample_pts_num = self.estimate_sample_pts_num(quad, xy_text) + else: + sample_pts_num = self.sample_pts_num + xy_center_line = xy_text[ + np.linspace( + 0, + xy_text.shape[0] - 1, + sample_pts_num, + endpoint=True, + dtype=np.float32, + ).astype(np.int32) + ] + + point_pair_list = [] + for x, y in xy_center_line: + # get corresponding offset + offset = tbo_map[y, x, :].reshape(2, 2) + if offset_expand != 1.0: + offset_length = np.linalg.norm(offset, axis=1, keepdims=True) + expand_length = np.clip( + offset_length * (offset_expand - 1), a_min=0.5, a_max=3.0 + ) + offset_detal = offset / offset_length * expand_length + offset = offset + offset_detal + # original point + ori_yx = np.array([y, x], dtype=np.float32) + point_pair = ( + (ori_yx + offset)[:, ::-1] + * out_strid + / np.array([ratio_w, ratio_h]).reshape(-1, 2) + ) + point_pair_list.append(point_pair) + + # ndarry: (x, 2), expand poly along width + detected_poly = self.point_pair2poly(point_pair_list) + detected_poly = self.expand_poly_along_width( + detected_poly, shrink_ratio_of_width + ) + detected_poly[:, 0] = np.clip(detected_poly[:, 0], a_min=0, a_max=src_w) + detected_poly[:, 1] = np.clip(detected_poly[:, 1], a_min=0, a_max=src_h) + poly_list.append(detected_poly) + + return poly_list + + def __call__(self, outs_dict, shape_list): + score_list = outs_dict["f_score"] + border_list = outs_dict["f_border"] + tvo_list = outs_dict["f_tvo"] + tco_list = outs_dict["f_tco"] + if isinstance(score_list, paddle.Tensor): + score_list = score_list.numpy() + border_list = border_list.numpy() + tvo_list = tvo_list.numpy() + tco_list = tco_list.numpy() + + img_num = len(shape_list) + poly_lists = [] + for ino in range(img_num): + p_score = score_list[ino].transpose((1, 2, 0)) + p_border = border_list[ino].transpose((1, 2, 0)) + p_tvo = tvo_list[ino].transpose((1, 2, 0)) + p_tco = tco_list[ino].transpose((1, 2, 0)) + src_h, src_w, ratio_h, ratio_w = shape_list[ino] + + poly_list = self.detect_sast( + p_score, + p_tvo, + p_border, + p_tco, + ratio_w, + ratio_h, + src_w, + src_h, + shrink_ratio_of_width=self.shrink_ratio_of_width, + tcl_map_thresh=self.tcl_map_thresh, + offset_expand=self.expand_scale, + ) + poly_lists.append({"points": np.array(poly_list)}) + + return poly_lists diff --git a/ppocr/postprocess/table_postprocess.py b/ppocr/postprocess/table_postprocess.py new file mode 100644 index 0000000..11a877a --- /dev/null +++ b/ppocr/postprocess/table_postprocess.py @@ -0,0 +1,191 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +import paddle + +from .rec_postprocess import AttnLabelDecode + + +class TableLabelDecode(AttnLabelDecode): + """ """ + + def __init__(self, character_dict_path, merge_no_span_structure=False, **kwargs): + dict_character = [] + with open(character_dict_path, "rb") as fin: + lines = fin.readlines() + for line in lines: + line = line.decode("utf-8").strip("\n").strip("\r\n") + dict_character.append(line) + + if merge_no_span_structure: + if "" not in dict_character: + dict_character.append("") + if "" in dict_character: + dict_character.remove("") + + dict_character = self.add_special_char(dict_character) + self.dict = {} + for i, char in enumerate(dict_character): + self.dict[char] = i + self.character = dict_character + self.td_token = ["", ""] + + def __call__(self, preds, batch=None): + structure_probs = preds["structure_probs"] + bbox_preds = preds["loc_preds"] + if isinstance(structure_probs, paddle.Tensor): + structure_probs = structure_probs.numpy() + if isinstance(bbox_preds, paddle.Tensor): + bbox_preds = bbox_preds.numpy() + shape_list = batch[-1] + result = self.decode(structure_probs, bbox_preds, shape_list) + if len(batch) == 1: # only contains shape + return result + + label_decode_result = self.decode_label(batch) + return result, label_decode_result + + def decode(self, structure_probs, bbox_preds, shape_list): + """convert text-label into text-index.""" + ignored_tokens = self.get_ignored_tokens() + end_idx = self.dict[self.end_str] + + structure_idx = structure_probs.argmax(axis=2) + structure_probs = structure_probs.max(axis=2) + + structure_batch_list = [] + bbox_batch_list = [] + batch_size = len(structure_idx) + for batch_idx in range(batch_size): + structure_list = [] + bbox_list = [] + score_list = [] + for idx in range(len(structure_idx[batch_idx])): + char_idx = int(structure_idx[batch_idx][idx]) + if idx > 0 and char_idx == end_idx: + break + if char_idx in ignored_tokens: + continue + text = self.character[char_idx] + if text in self.td_token: + bbox = bbox_preds[batch_idx, idx] + bbox = self._bbox_decode(bbox, shape_list[batch_idx]) + bbox_list.append(bbox) + structure_list.append(text) + score_list.append(structure_probs[batch_idx, idx]) + structure_batch_list.append([structure_list, np.mean(score_list)]) + bbox_batch_list.append(np.array(bbox_list)) + result = { + "bbox_batch_list": bbox_batch_list, + "structure_batch_list": structure_batch_list, + } + return result + + def decode_label(self, batch): + """convert text-label into text-index.""" + structure_idx = batch[1] + gt_bbox_list = batch[2] + shape_list = batch[-1] + ignored_tokens = self.get_ignored_tokens() + end_idx = self.dict[self.end_str] + + structure_batch_list = [] + bbox_batch_list = [] + batch_size = len(structure_idx) + for batch_idx in range(batch_size): + structure_list = [] + bbox_list = [] + for idx in range(len(structure_idx[batch_idx])): + char_idx = int(structure_idx[batch_idx][idx]) + if idx > 0 and char_idx == end_idx: + break + if char_idx in ignored_tokens: + continue + structure_list.append(self.character[char_idx]) + + bbox = gt_bbox_list[batch_idx][idx] + if bbox.sum() != 0: + bbox = self._bbox_decode(bbox, shape_list[batch_idx]) + bbox_list.append(bbox) + structure_batch_list.append(structure_list) + bbox_batch_list.append(bbox_list) + result = { + "bbox_batch_list": bbox_batch_list, + "structure_batch_list": structure_batch_list, + } + return result + + def _bbox_decode(self, bbox, shape): + h, w, ratio_h, ratio_w, pad_h, pad_w = shape + h, w = pad_h, pad_w + bbox[0::2] *= w + bbox[1::2] *= h + bbox[0::2] /= ratio_w + bbox[1::2] /= ratio_h + return bbox + + +class TableMasterLabelDecode(TableLabelDecode): + """ """ + + def __init__( + self, + character_dict_path, + box_shape="ori", + merge_no_span_structure=True, + **kwargs, + ): + super(TableMasterLabelDecode, self).__init__( + character_dict_path, merge_no_span_structure + ) + self.box_shape = box_shape + assert box_shape in [ + "ori", + "pad", + ], "The shape used for box normalization must be ori or pad" + + def add_special_char(self, dict_character): + self.beg_str = "" + self.end_str = "" + self.unknown_str = "" + self.pad_str = "" + dict_character = dict_character + dict_character = dict_character + [ + self.unknown_str, + self.beg_str, + self.end_str, + self.pad_str, + ] + return dict_character + + def get_ignored_tokens(self): + pad_idx = self.dict[self.pad_str] + start_idx = self.dict[self.beg_str] + end_idx = self.dict[self.end_str] + unknown_idx = self.dict[self.unknown_str] + return [start_idx, end_idx, pad_idx, unknown_idx] + + def _bbox_decode(self, bbox, shape): + h, w, ratio_h, ratio_w, pad_h, pad_w = shape + if self.box_shape == "pad": + h, w = pad_h, pad_w + bbox[0::2] *= w + bbox[1::2] *= h + bbox[0::2] /= ratio_w + bbox[1::2] /= ratio_h + x, y, w, h = bbox + x1, y1, x2, y2 = x - w // 2, y - h // 2, x + w // 2, y + h // 2 + bbox = np.array([x1, y1, x2, y2]) + return bbox diff --git a/ppocr/postprocess/vqa_token_re_layoutlm_postprocess.py b/ppocr/postprocess/vqa_token_re_layoutlm_postprocess.py new file mode 100644 index 0000000..efdfffe --- /dev/null +++ b/ppocr/postprocess/vqa_token_re_layoutlm_postprocess.py @@ -0,0 +1,96 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import paddle + + +class VQAReTokenLayoutLMPostProcess(object): + """Convert between text-label and text-index""" + + def __init__(self, **kwargs): + super(VQAReTokenLayoutLMPostProcess, self).__init__() + + def __call__(self, preds, label=None, *args, **kwargs): + pred_relations = preds["pred_relations"] + if isinstance(preds["pred_relations"], paddle.Tensor): + pred_relations = pred_relations.numpy() + pred_relations = self.decode_pred(pred_relations) + + if label is not None: + return self._metric(pred_relations, label) + else: + return self._infer(pred_relations, *args, **kwargs) + + def _metric(self, pred_relations, label): + return pred_relations, label[-1], label[-2] + + def _infer(self, pred_relations, *args, **kwargs): + ser_results = kwargs["ser_results"] + entity_idx_dict_batch = kwargs["entity_idx_dict_batch"] + + # merge relations and ocr info + results = [] + for pred_relation, ser_result, entity_idx_dict in zip( + pred_relations, ser_results, entity_idx_dict_batch + ): + result = [] + used_tail_id = [] + for relation in pred_relation: + if relation["tail_id"] in used_tail_id: + continue + used_tail_id.append(relation["tail_id"]) + ocr_info_head = ser_result[entity_idx_dict[relation["head_id"]]] + ocr_info_tail = ser_result[entity_idx_dict[relation["tail_id"]]] + result.append((ocr_info_head, ocr_info_tail)) + results.append(result) + return results + + def decode_pred(self, pred_relations): + pred_relations_new = [] + for pred_relation in pred_relations: + pred_relation_new = [] + pred_relation = pred_relation[1 : pred_relation[0, 0, 0] + 1] + for relation in pred_relation: + relation_new = dict() + relation_new["head_id"] = relation[0, 0] + relation_new["head"] = tuple(relation[1]) + relation_new["head_type"] = relation[2, 0] + relation_new["tail_id"] = relation[3, 0] + relation_new["tail"] = tuple(relation[4]) + relation_new["tail_type"] = relation[5, 0] + relation_new["type"] = relation[6, 0] + pred_relation_new.append(relation_new) + pred_relations_new.append(pred_relation_new) + return pred_relations_new + + +class DistillationRePostProcess(VQAReTokenLayoutLMPostProcess): + """ + DistillationRePostProcess + """ + + def __init__(self, model_name=["Student"], key=None, **kwargs): + super().__init__(**kwargs) + if not isinstance(model_name, list): + model_name = [model_name] + self.model_name = model_name + self.key = key + + def __call__(self, preds, *args, **kwargs): + output = dict() + for name in self.model_name: + pred = preds[name] + if self.key is not None: + pred = pred[self.key] + output[name] = super().__call__(pred, *args, **kwargs) + return output diff --git a/ppocr/postprocess/vqa_token_ser_layoutlm_postprocess.py b/ppocr/postprocess/vqa_token_ser_layoutlm_postprocess.py new file mode 100644 index 0000000..a10f070 --- /dev/null +++ b/ppocr/postprocess/vqa_token_ser_layoutlm_postprocess.py @@ -0,0 +1,116 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +import paddle +from ppocr.utils.utility import load_vqa_bio_label_maps + + +class VQASerTokenLayoutLMPostProcess(object): + """Convert between text-label and text-index""" + + def __init__(self, class_path, **kwargs): + super(VQASerTokenLayoutLMPostProcess, self).__init__() + label2id_map, self.id2label_map = load_vqa_bio_label_maps(class_path) + + self.label2id_map_for_draw = dict() + for key in label2id_map: + if key.startswith("I-"): + self.label2id_map_for_draw[key] = label2id_map["B" + key[1:]] + else: + self.label2id_map_for_draw[key] = label2id_map[key] + + self.id2label_map_for_show = dict() + for key in self.label2id_map_for_draw: + val = self.label2id_map_for_draw[key] + if key == "O": + self.id2label_map_for_show[val] = key + if key.startswith("B-") or key.startswith("I-"): + self.id2label_map_for_show[val] = key[2:] + else: + self.id2label_map_for_show[val] = key + + def __call__(self, preds, batch=None, *args, **kwargs): + if isinstance(preds, tuple): + preds = preds[0] + if isinstance(preds, paddle.Tensor): + preds = preds.numpy() + + if batch is not None: + return self._metric(preds, batch[5]) + else: + return self._infer(preds, **kwargs) + + def _metric(self, preds, label): + pred_idxs = preds.argmax(axis=2) + decode_out_list = [[] for _ in range(pred_idxs.shape[0])] + label_decode_out_list = [[] for _ in range(pred_idxs.shape[0])] + + for i in range(pred_idxs.shape[0]): + for j in range(pred_idxs.shape[1]): + if label[i, j] != -100: + label_decode_out_list[i].append(self.id2label_map[label[i, j]]) + decode_out_list[i].append(self.id2label_map[pred_idxs[i, j]]) + return decode_out_list, label_decode_out_list + + def _infer(self, preds, segment_offset_ids, ocr_infos): + results = [] + + for pred, segment_offset_id, ocr_info in zip( + preds, segment_offset_ids, ocr_infos + ): + pred = np.argmax(pred, axis=1) + pred = [self.id2label_map[idx] for idx in pred] + + for idx in range(len(segment_offset_id)): + if idx == 0: + start_id = 0 + else: + start_id = segment_offset_id[idx - 1] + + end_id = segment_offset_id[idx] + + curr_pred = pred[start_id:end_id] + curr_pred = [self.label2id_map_for_draw[p] for p in curr_pred] + + if len(curr_pred) <= 0: + pred_id = 0 + else: + counts = np.bincount(curr_pred) + pred_id = np.argmax(counts) + ocr_info[idx]["pred_id"] = int(pred_id) + ocr_info[idx]["pred"] = self.id2label_map_for_show[int(pred_id)] + results.append(ocr_info) + return results + + +class DistillationSerPostProcess(VQASerTokenLayoutLMPostProcess): + """ + DistillationSerPostProcess + """ + + def __init__(self, class_path, model_name=["Student"], key=None, **kwargs): + super().__init__(class_path, **kwargs) + if not isinstance(model_name, list): + model_name = [model_name] + self.model_name = model_name + self.key = key + + def __call__(self, preds, batch=None, *args, **kwargs): + output = dict() + for name in self.model_name: + pred = preds[name] + if self.key is not None: + pred = pred[self.key] + output[name] = super().__call__(pred, batch=batch, *args, **kwargs) + return output diff --git a/ppocr/utils/EN_symbol_dict.txt b/ppocr/utils/EN_symbol_dict.txt new file mode 100644 index 0000000..87c6d67 --- /dev/null +++ b/ppocr/utils/EN_symbol_dict.txt @@ -0,0 +1,94 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +: +; +< += +> +? +@ +[ +\ +] +^ +_ +` +{ +| +} +~ diff --git a/ppocr/utils/__init__.py b/ppocr/utils/__init__.py new file mode 100755 index 0000000..abf198b --- /dev/null +++ b/ppocr/utils/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/ppocr/utils/dict/README.md b/ppocr/utils/dict/README.md new file mode 100644 index 0000000..7b68200 --- /dev/null +++ b/ppocr/utils/dict/README.md @@ -0,0 +1,5 @@ +## Dictionary and Corpus + +Dictionary files (usually character level vocabulary) are included here for easier configuration. Corpus contributed by OSS contributors are listed here, please respect copyrights when using them at your own risk. + +- Burmese corpus: https://github.com/1chimaruGin/BurmeseCorpus diff --git a/ppocr/utils/dict/ar_dict.txt b/ppocr/utils/dict/ar_dict.txt new file mode 100644 index 0000000..fc63802 --- /dev/null +++ b/ppocr/utils/dict/ar_dict.txt @@ -0,0 +1,117 @@ +a +r +b +i +c +_ +m +g +/ +1 +0 +I +L +S +V +R +C +2 +v +l +6 +3 +9 +. +j +p +ا +ل +م +ر +ج +و +ح +ي +ة +5 +8 +7 +أ +ب +ض +4 +ك +س +ه +ث +ن +ط +ع +ت +غ +خ +ف +ئ +ز +إ +د +ص +ظ +ذ +ش +ى +ق +ؤ +آ +ء +s +e +n +w +t +u +z +d +A +N +G +h +o +E +T +H +O +B +y +F +U +J +X +W +P +Z +M +k +q +Y +Q +D +f +K +x +' +% +- +# +@ +! +& +$ +, +: +é +? ++ +É +( + diff --git a/ppocr/utils/dict/arabic_dict.txt b/ppocr/utils/dict/arabic_dict.txt new file mode 100644 index 0000000..916d421 --- /dev/null +++ b/ppocr/utils/dict/arabic_dict.txt @@ -0,0 +1,161 @@ +! +# +$ +% +& +' +( ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +É +é +ء +آ +أ +ؤ +إ +ئ +ا +ب +ة +ت +ث +ج +ح +خ +د +ذ +ر +ز +س +ش +ص +ض +ط +ظ +ع +غ +ف +ق +ك +ل +م +ن +ه +و +ى +ي +ً +ٌ +ٍ +َ +ُ +ِ +ّ +ْ +ٓ +ٔ +ٰ +ٱ +ٹ +پ +چ +ڈ +ڑ +ژ +ک +ڭ +گ +ں +ھ +ۀ +ہ +ۂ +ۃ +ۆ +ۇ +ۈ +ۋ +ی +ې +ے +ۓ +ە +١ +٢ +٣ +٤ +٥ +٦ +٧ +٨ +٩ diff --git a/ppocr/utils/dict/be_dict.txt b/ppocr/utils/dict/be_dict.txt new file mode 100644 index 0000000..f8458ba --- /dev/null +++ b/ppocr/utils/dict/be_dict.txt @@ -0,0 +1,145 @@ +b +e +_ +i +m +g +/ +2 +0 +I +L +S +V +R +C +1 +v +a +l +6 +9 +4 +3 +. +j +p +п +а +з +б +у +г +н +ц +ь +8 +м +л +і +о +ў +ы +7 +5 +М +х +с +р +ф +я +е +д +ж +ю +ч +й +к +Д +в +Б +т +І +ш +ё +э +К +Л +Н +А +Ж +Г +В +П +З +Е +О +Р +С +У +Ё +Й +Т +Ч +Э +Ц +Ю +Ш +Ф +Х +Я +Ь +Ы +Ў +s +c +n +w +M +o +t +T +E +A +B +u +h +y +k +r +H +d +Y +O +U +F +f +x +D +G +N +K +P +z +J +X +W +Z +Q +% +- +q +@ +' +! +# +& +, +: +$ +( +? +é ++ +É + diff --git a/ppocr/utils/dict/bengali_dict.txt b/ppocr/utils/dict/bengali_dict.txt new file mode 100644 index 0000000..89ffadf --- /dev/null +++ b/ppocr/utils/dict/bengali_dict.txt @@ -0,0 +1,74 @@ +হ +থ +শ +৫ +ক +ও +য +০ +গ +দ +ড় +খ +য় +ঋ +ন +অ +৪ +এ +ব +ঠ +ঢ +৭ +৯ +ধ +ঙ +ট +ঝ +ৎ +ণ +ত +র +২ +চ +ঌ +ড +৬ +ঔ +প +ভ +ম +ঢ় +ঈ +৮ +ঘ +১ +ষ +৩ +ফ +ছ +ল +জ +আ +। +ঊ +ই +স +ঐ +উ +ঞ +া +্ +ু +ী +ে +ং +ি +় +ঁ +ৃ +ো +ূ +ৈ +ৌ +ঃ diff --git a/ppocr/utils/dict/bg_dict.txt b/ppocr/utils/dict/bg_dict.txt new file mode 100644 index 0000000..84713c3 --- /dev/null +++ b/ppocr/utils/dict/bg_dict.txt @@ -0,0 +1,140 @@ +! +# +$ +% +& +' +( ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +É +é +А +Б +В +Г +Д +Е +Ж +З +И +Й +К +Л +М +Н +О +П +Р +С +Т +У +Ф +Х +Ц +Ч +Ш +Щ +Ъ +Ю +Я +а +б +в +г +д +е +ж +з +и +й +к +л +м +н +о +п +р +с +т +у +ф +х +ц +ч +ш +щ +ъ +ь +ю +я + diff --git a/ppocr/utils/dict/bm_dict.txt b/ppocr/utils/dict/bm_dict.txt new file mode 100644 index 0000000..bd68de9 --- /dev/null +++ b/ppocr/utils/dict/bm_dict.txt @@ -0,0 +1,160 @@ +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ diff --git a/ppocr/utils/dict/bm_dict_add.txt b/ppocr/utils/dict/bm_dict_add.txt new file mode 100644 index 0000000..29b6155 --- /dev/null +++ b/ppocr/utils/dict/bm_dict_add.txt @@ -0,0 +1,3219 @@ +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +Ⴀ +Ⴁ +Ⴂ +Ⴃ +Ⴄ +Ⴅ +Ⴆ +Ⴇ +Ⴈ +Ⴉ +Ⴊ +Ⴋ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ + +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +Ⴀ +Ⴁ +Ⴂ +Ⴃ +Ⴄ +Ⴅ +Ⴆ +Ⴇ +Ⴈ +Ⴉ +Ⴊ +Ⴋ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ + +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +Ⴀ +Ⴁ +Ⴂ +Ⴃ +Ⴄ +Ⴅ +Ⴆ +Ⴇ +Ⴈ +Ⴉ +Ⴊ +Ⴋ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ + +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +Ⴀ +Ⴁ +Ⴂ +Ⴃ +Ⴄ +Ⴅ +Ⴆ +Ⴇ +Ⴈ +Ⴉ +Ⴊ +Ⴋ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ + +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +Ⴀ +Ⴁ +Ⴂ +Ⴃ +Ⴄ +Ⴅ +Ⴆ +Ⴇ +Ⴈ +Ⴉ +Ⴊ +Ⴋ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ + +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +Ⴀ +Ⴁ +Ⴂ +Ⴃ +Ⴄ +Ⴅ +Ⴆ +Ⴇ +Ⴈ +Ⴉ +Ⴊ +Ⴋ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ + +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +Ⴀ +Ⴁ +Ⴂ +Ⴃ +Ⴄ +Ⴅ +Ⴆ +Ⴇ +Ⴈ +Ⴉ +Ⴊ +Ⴋ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ + +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +Ⴀ +Ⴁ +Ⴂ +Ⴃ +Ⴄ +Ⴅ +Ⴆ +Ⴇ +Ⴈ +Ⴉ +Ⴊ +Ⴋ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ + +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +Ⴀ +Ⴁ +Ⴂ +Ⴃ +Ⴄ +Ⴅ +Ⴆ +Ⴇ +Ⴈ +Ⴉ +Ⴊ +Ⴋ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ + +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +က +ခ +ဂ +ဃ +င +စ +ဆ +ဇ +ဈ +ဉ +ည +ဋ +ဌ +ဍ +ဎ +ဏ +တ +ထ +ဒ +ဓ +န +ပ +ဖ +ဗ +ဘ +မ +ယ +ရ +လ +ဝ +သ +ဟ +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ + +Ⴀ +Ⴁ +Ⴂ +Ⴃ +Ⴄ +Ⴅ +Ⴆ +Ⴇ +Ⴈ +Ⴉ +Ⴊ +Ⴋ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ + +ဠ +အ +ဢ +ဣ +ဤ +ဥ +ဦ +ဧ +ဨ +ဩ +ဪ +ါ +ာ +ိ +ီ +ု +ူ +ေ +ဲ +ဳ +ဴ +ဵ +ံ +့ +း +္ +် +ျ +ြ +ွ +ှ +ဿ +၀ +၁ +၂ +၃ +၄ +၅ +၆ +၇ +၈ +၉ +၊ +။ +၌ +၍ +၎ +၏ +ၐ +ၑ +ၒ +ၓ +ၔ +ၕ +ၖ +ၗ +ၘ +ၙ +ၚ +ၛ +ၜ +ၝ +ၞ +ၟ +ၠ +ၡ +ၢ +ၣ +ၤ +ၥ +ၦ +ၧ +ၨ +ၩ +ၪ +ၫ +ၬ +ၭ +ၮ +ၯ +ၰ +ၱ +ၲ +ၳ +ၴ +ၵ +ၶ +ၷ +ၸ +ၹ +ၺ +ၻ +ၼ +ၽ +ၾ +ၿ +ႀ +ႁ +ႂ +ႃ +ႄ +ႅ +ႆ +ႇ +ႈ +ႉ +ႊ +ႋ +ႌ +ႍ +ႎ +ႏ +႐ +႑ +႒ +႓ +႔ +႕ +႖ +႗ +႘ +႙ +ႚ +ႛ +ႜ +ႝ +႞ +႟ diff --git a/ppocr/utils/dict/bn_dict.txt b/ppocr/utils/dict/bn_dict.txt new file mode 100644 index 0000000..655c585 --- /dev/null +++ b/ppocr/utils/dict/bn_dict.txt @@ -0,0 +1,477 @@ + +! +# +$ +% +& +' +( ++ + +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +É +é +' +" +ঃ +, +; +! +? +। +— + ঃ- +- +( +) +[ +] +{ +} +√ +... +. +/ +\ += +< +> +৳ +° +% +অ +আ +ই +ঈ +উ +ঊ +ঋ +এ +ঐ +ও +ঔ +্‌ +া +ি +ী +ু +ূ +ে +ৈ +ো +ৌ +ৃ +‍্য +‍্র +‍্ব +ক +খ +গ +ঘ +ঙ +চ +ছ +জ +ঝ +ঞ +ট +ঠ +ড +ঢ +ণ +ত +থ +দ +ধ +ন +প +ফ +ব +ভ +ম +য +র +ল +শ +ষ +স +হ +ড় +ঢ় +য় +ং +ৎ +ঁ +ক্ক +ক্ট +ক্ত +ত্র +ক্ব +ক্ম +ক্য +ক্র +ক্ল +ক্ষ +ক্ষ্ণ +ক্ষ্ব +ক্ষ্ম +ক্ষ্য +ক্স +র্ক +র্ক্য +খ্য +খ্র +র্খ +গ্ণ +গ্ধ +গ্ধ্য +গ্ধ্র +গ্ন +গ্ন্য +গ্ব +গ্ম +গ্য +গ্র +গ্র্য +গ্ল +র্গ +র্গ্য +র্গ্র +ঘ্ন +ঘ্য +ঘ্র +র্ঘ্য +র্ঘ +ঙ্ক +ঙ্ক্য +ঙ্ক্ষ +ঙ্খ +ঙ্খ্য +ঙ্গ +ঙ্গ্য +ঙ্ঘ +ঙ্ঘ্য +ঙ্ঘ্র +ঙ্ম +র্ঙ্গ +চ্চ +চ্ছ +চ্ছ্ব +চ্ছ্র +চ্ঞ +চ্ব +চ্য +র্চ্য +র্চ +র্ছ +জ্জ +জ্জ্ব +জ্ঝ +জ্ঞ +জ্ব +জ্য +জ্র +র্জ্য +র্জ্জ +র্জ্ঞ +র্জ +র্ঝ +ঞ্চ +ঞ্ছ +ঞ্জ +ঞ্ঝ +ট্ট +ট্ব +ট্ম +ট্য +ট্র +র্ট +ড্ড +ড্ব +ড্ম +ড্য +ড্র +র্ড +ঢ্য +ঢ্র +র্ঢ্য +ণ্ট +ণ্ঠ +ণ্ঠ্য +ণ্ড +ণ্ড্য +ণ্ড্র +ণ্ঢ +ণ্ণ +ণ্ব +ণ্ম +ণ্য +র্ণ্য +র্ণ +ত্ত +ত্ত্ব +ত্ত্য +ত্থ +ত্ন +ত্ব +ত্ম +ত্ম্য +ত্য +ত্র +ত্র্য +র্ত্য +র্ত +র্ত্ম +র্ত্র +থ্ব +থ্য +থ্র +র্থ্য +র্থ +দ্গ +দ্ঘ +দ্দ +দ্দ্ব +দ্ধ +দ্ব +দ্ভ +দ্ভ্র +দ্ম +দ্য +দ্র +দ্র্য +র্দ +র্দ্ব +র্দ্র +ধ্ন +ধ্ব +ধ্ম +ধ্য +ধ্র +র্ধ +র্ধ্ব +ন্ট +ন্ট্র +ন্ঠ +ন্ড +ন্ড্র +ন্ত +ন্ত্ব +ন্ত্য +ন্ত্র +ন্ত্র্য +ন্থ +ন্থ্র +ন্দ +ন্দ্য +ন্দ্ব +ন্দ্র +ন্ধ +ন্ধ্য +ন্ধ্র +ন্ন +ন্ব +ন্ম +ন্য +র্ন +প্ট +প্ত +প্ন +প্প +প্য +প্র +প্র্য +প্ল +প্স +র্প +ফ্র +ফ্ল +র্ফ +ব্জ +ব্দ +ব্ধ +ব্ব +ব্য +ব্র +ব্ল +র্ব্য +র্ব +ভ্ব +ভ্য +ভ্র +ভ্ল +র্ভ +ম্ন +ম্প +ম্প্র +ম্ফ +ম্ব +ম্ব্র +ম্ভ +ম্ভ্র +ম্ম +ম্য +ম্র +ম্ল +র্ম্য +র্ম +য্য +র্য +ল্ক +ল্ক্য +ল্গ +ল্ট +ল্ড +ল্প +ল্ফ +ল্ব +ল্ভ +ল্ম +ল্য +ল্ল +র্ল +শ্চ +শ্ছ +শ্ন +শ্ব +শ্ম +শ্য +শ্র +শ্ল +র্শ্য +র্শ +র্শ্ব +ষ্ক +ষ্ক্ব +ষ্ক্র +ষ্ট +ষ্ট্য +ষ্ট্র +ষ্ঠ +ষ্ঠ্য +ষ্ণ +ষ্ণ্ব +ষ্প +ষ্প্র +ষ্ফ +ষ্ব +ষ্ম +ষ্য +র্ষ্য +র্ষ +র্ষ্ট +র্ষ্ণ +র্ষ্ণ্য +স্ক +স্ক্র +স্খ +স্ট +স্ট্র +স্ত +স্ত্ব +স্ত্য +স্ত্র +স্থ +স্থ্য +স্ন +স্ন্য +স্প +স্প্র +স্প্ল +স্ফ +স্ব +স্ম +স্য +স্র +স্ল +স্ক্ল +র্স +হ্ণ +হ্ন +হ্ব +হ্ম +হ্য +হ্র +হ্ল +র্হ্য +র্হ +ড়্গ +র্ৎ +০ +১ +২ +৩ +৪ +৫ +৬ +৭ +৮ +৯ diff --git a/ppocr/utils/dict/chinese_cht_dict.txt b/ppocr/utils/dict/chinese_cht_dict.txt new file mode 100644 index 0000000..cc1aa47 --- /dev/null +++ b/ppocr/utils/dict/chinese_cht_dict.txt @@ -0,0 +1,8421 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +\ +] +^ +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +{ +| +} +~ +¥ +® +° +± +² +´ +· +» +É +Ë +Ó +× +Ü +à +á +ä +è +é +ì +í +ò +ó +÷ +ú +ü +ā +ē +ī +ō +ū +ǐ +ǒ +ɔ +ɡ +ʌ +ˋ +Λ +Ο +Φ +Ω +α +β +ε +θ +μ +π +З +И +Й +П +Я +г +— +‖ +‘ +’ +“ +” +• +… +‧ +′ +″ +※ +℃ +№ +™ +Ⅱ +Ⅲ +Ⅳ +← +↑ +→ +↓ +⇋ +∈ +∑ +√ +∞ +∣ +∧ +∩ +∫ +∶ +≈ +≠ +≤ +≥ +⊙ +⊥ +① +② +③ +④ +⑧ +⑴ +⑵ +⑶ +─ +│ +┅ +┌ +├ +█ +▎ +▏ +▕ +■ +□ +▪ +▲ +△ +▼ +◆ +◇ +○ +◎ +● +◥ +★ +☆ +❋ +❤ +  +、 +。 +〇 +〉 +《 +》 +「 +」 +『 +』 +【 +】 +〔 +〕 +〖 +〗 +の +サ +シ +ジ +マ +ㄱ +ㆍ +㎏ +㎡ +㐂 +㐱 +㙟 +㴪 +㸃 +䖝 +䝉 +䰾 +䲁 +一 +丁 +七 +丄 +丈 +三 +上 +下 +丌 +不 +与 +丏 +丐 +丑 +且 +丕 +世 +丘 +丙 +丞 +丟 +両 +並 +丨 +丫 +中 +丰 +串 +丶 +丸 +丹 +主 +丼 +丿 +乂 +乃 +久 +么 +之 +乍 +乎 +乏 +乒 +乓 +乖 +乗 +乘 +乙 +乚 +乜 +九 +乞 +也 +乩 +乭 +乳 +乸 +乹 +乾 +亀 +亂 +亅 +了 +予 +亊 +事 +二 +亍 +云 +互 +亓 +五 +井 +亘 +些 +亜 +亞 +亟 +亠 +亡 +亢 +交 +亥 +亦 +亨 +享 +京 +亭 +亮 +亰 +亳 +亶 +亹 +人 +亻 +什 +仁 +仂 +仃 +仄 +仇 +仉 +今 +介 +仍 +仏 +仔 +仕 +他 +仗 +付 +仙 +仛 +仝 +仞 +仟 +仡 +代 +令 +以 +仨 +仫 +仮 +仰 +仲 +仳 +仵 +件 +仺 +任 +仼 +份 +仿 +企 +伃 +伈 +伉 +伊 +伋 +伍 +伎 +伏 +伐 +休 +伕 +伙 +伝 +伢 +伯 +估 +伱 +伴 +伶 +伷 +伸 +伺 +似 +伽 +伾 +佀 +佁 +佃 +但 +佇 +佈 +佉 +佋 +位 +低 +住 +佐 +佑 +体 +佔 +何 +佗 +佘 +余 +佚 +佛 +作 +佝 +佞 +佟 +你 +佣 +佤 +佧 +佩 +佬 +佯 +佰 +佳 +併 +佶 +佹 +佺 +佼 +佾 +使 +侁 +侃 +侄 +侅 +來 +侈 +侊 +例 +侍 +侏 +侑 +侖 +侗 +侘 +侚 +供 +依 +侞 +価 +侮 +侯 +侵 +侶 +侷 +侹 +便 +俁 +係 +促 +俄 +俅 +俊 +俋 +俌 +俍 +俎 +俏 +俐 +俑 +俗 +俘 +俚 +俛 +保 +俞 +俟 +俠 +信 +俬 +修 +俯 +俱 +俳 +俴 +俵 +俶 +俸 +俺 +俽 +俾 +倆 +倈 +倉 +個 +倌 +倍 +們 +倒 +倓 +倔 +倖 +倗 +倘 +候 +倚 +倜 +倞 +借 +倡 +倢 +倣 +値 +倦 +倧 +倩 +倪 +倫 +倬 +倭 +倮 +倻 +值 +偁 +偃 +假 +偈 +偉 +偊 +偌 +偍 +偎 +偏 +偓 +偕 +做 +停 +健 +偪 +偲 +側 +偵 +偶 +偷 +偸 +偽 +傀 +傃 +傅 +傈 +傉 +傍 +傑 +傒 +傕 +傖 +傘 +備 +傜 +傢 +傣 +催 +傭 +傲 +傳 +債 +傷 +傻 +傾 +僅 +僉 +僊 +働 +像 +僑 +僔 +僕 +僖 +僙 +僚 +僜 +僡 +僧 +僩 +僭 +僮 +僰 +僱 +僳 +僴 +僵 +價 +僻 +儀 +儁 +儂 +億 +儆 +儇 +儈 +儉 +儋 +儐 +儒 +儔 +儕 +儘 +儚 +儞 +償 +儡 +儥 +儦 +優 +儫 +儱 +儲 +儷 +儺 +儻 +儼 +兀 +允 +元 +兄 +充 +兆 +先 +光 +克 +兌 +免 +児 +兒 +兔 +兕 +兗 +兜 +入 +內 +全 +兩 +兪 +八 +公 +六 +兮 +共 +兵 +其 +具 +典 +兼 +兿 +冀 +冂 +円 +冇 +冉 +冊 +再 +冏 +冑 +冒 +冕 +冖 +冗 +冚 +冠 +冢 +冤 +冥 +冧 +冨 +冪 +冫 +冬 +冮 +冰 +冴 +冶 +冷 +冼 +冽 +凃 +凄 +准 +凈 +凋 +凌 +凍 +凖 +凜 +凝 +凞 +几 +凡 +処 +凪 +凬 +凰 +凱 +凳 +凵 +凶 +凸 +凹 +出 +函 +刀 +刁 +刂 +刃 +刄 +分 +切 +刈 +刊 +刎 +刑 +划 +列 +初 +判 +別 +刦 +刧 +刨 +利 +刪 +刮 +到 +制 +刷 +券 +刺 +刻 +刼 +剁 +剃 +則 +削 +剋 +剌 +前 +剎 +剏 +剔 +剖 +剛 +剝 +剡 +剣 +剩 +剪 +剮 +副 +割 +創 +剿 +劃 +劄 +劇 +劈 +劉 +劊 +劌 +劍 +劑 +劔 +力 +功 +加 +劣 +助 +努 +劫 +劬 +劭 +劵 +効 +劼 +劾 +勁 +勃 +勅 +勇 +勉 +勐 +勑 +勒 +勔 +動 +勖 +勗 +勘 +務 +勛 +勝 +勞 +募 +勢 +勣 +勤 +勦 +勰 +勱 +勲 +勳 +勵 +勷 +勸 +勺 +勻 +勾 +勿 +匂 +匄 +包 +匆 +匈 +匋 +匍 +匏 +匐 +匕 +化 +北 +匙 +匚 +匝 +匠 +匡 +匣 +匪 +匯 +匱 +匸 +匹 +匾 +匿 +區 +十 +千 +卅 +升 +午 +卉 +半 +卋 +卍 +卐 +卑 +卒 +卓 +協 +南 +博 +卜 +卞 +卟 +占 +卡 +卣 +卦 +卧 +卩 +卬 +卮 +卯 +印 +危 +卲 +即 +卵 +卷 +卸 +卹 +卺 +卻 +卽 +卿 +厄 +厓 +厔 +厙 +厚 +厝 +原 +厥 +厭 +厰 +厲 +厴 +厶 +去 +參 +叄 +又 +叉 +及 +友 +反 +収 +叔 +叕 +取 +受 +叛 +叟 +叡 +叢 +口 +古 +句 +另 +叨 +叩 +只 +叫 +召 +叭 +叮 +可 +台 +叱 +史 +右 +叵 +司 +叻 +叼 +吁 +吃 +各 +吆 +合 +吉 +吊 +吋 +同 +名 +后 +吏 +吐 +向 +吒 +吔 +吖 +君 +吝 +吞 +吟 +吠 +吡 +吥 +否 +吧 +吩 +含 +吮 +吱 +吲 +吳 +吵 +吶 +吸 +吹 +吻 +吼 +吾 +呀 +呂 +呃 +呈 +呉 +告 +呋 +呎 +呢 +呤 +呦 +周 +呱 +味 +呵 +呷 +呸 +呼 +命 +呾 +咀 +咁 +咂 +咄 +咅 +咆 +咋 +和 +咎 +咑 +咒 +咔 +咕 +咖 +咗 +咘 +咚 +咟 +咤 +咥 +咧 +咨 +咩 +咪 +咫 +咬 +咭 +咯 +咱 +咲 +咳 +咸 +咻 +咼 +咽 +咾 +咿 +哀 +品 +哂 +哄 +哆 +哇 +哈 +哉 +哌 +哎 +哏 +哐 +哖 +哚 +哞 +員 +哥 +哦 +哨 +哩 +哪 +哭 +哮 +哱 +哲 +哺 +哼 +唃 +唄 +唆 +唇 +唉 +唏 +唐 +唑 +唔 +唘 +唧 +唫 +唬 +唭 +售 +唯 +唱 +唳 +唵 +唷 +唸 +唻 +唾 +啁 +啃 +啄 +商 +啉 +啊 +啍 +問 +啓 +啖 +啚 +啜 +啞 +啟 +啡 +啣 +啤 +啥 +啦 +啪 +啫 +啯 +啰 +啱 +啲 +啵 +啶 +啷 +啻 +啼 +啾 +喀 +喂 +喃 +善 +喆 +喇 +喈 +喉 +喊 +喋 +喏 +喔 +喘 +喙 +喚 +喜 +喝 +喢 +喦 +喧 +喪 +喫 +喬 +單 +喰 +喱 +喲 +喳 +喵 +喹 +喻 +喼 +嗄 +嗅 +嗆 +嗇 +嗊 +嗎 +嗑 +嗒 +嗓 +嗔 +嗖 +嗚 +嗜 +嗝 +嗞 +嗡 +嗢 +嗣 +嗦 +嗨 +嗩 +嗪 +嗮 +嗯 +嗲 +嗶 +嗹 +嗽 +嘀 +嘅 +嘆 +嘉 +嘌 +嘍 +嘎 +嘏 +嘔 +嘗 +嘚 +嘛 +嘜 +嘞 +嘟 +嘢 +嘣 +嘥 +嘧 +嘩 +嘬 +嘮 +嘯 +嘰 +嘲 +嘴 +嘶 +嘸 +嘹 +嘻 +嘿 +噁 +噌 +噍 +噏 +噓 +噗 +噝 +噠 +噢 +噤 +噥 +噦 +器 +噩 +噪 +噬 +噯 +噰 +噲 +噴 +噶 +噸 +噹 +噻 +嚇 +嚈 +嚎 +嚏 +嚐 +嚒 +嚓 +嚕 +嚗 +嚙 +嚞 +嚟 +嚤 +嚦 +嚧 +嚨 +嚩 +嚮 +嚳 +嚴 +嚶 +嚷 +嚼 +嚿 +囀 +囂 +囃 +囉 +囊 +囍 +囑 +囒 +囓 +囗 +囚 +四 +囝 +回 +因 +囡 +団 +囤 +囧 +囪 +囮 +囯 +困 +囲 +図 +囶 +囷 +囹 +固 +囿 +圂 +圃 +圄 +圈 +圉 +國 +圍 +圏 +園 +圓 +圖 +圗 +團 +圜 +土 +圧 +在 +圩 +圪 +圭 +圯 +地 +圳 +圻 +圾 +址 +均 +坊 +坋 +坌 +坍 +坎 +坐 +坑 +坖 +坡 +坣 +坤 +坦 +坨 +坩 +坪 +坫 +坬 +坭 +坮 +坯 +坳 +坵 +坶 +坷 +坻 +垂 +垃 +垈 +型 +垍 +垓 +垕 +垚 +垛 +垞 +垟 +垠 +垢 +垣 +垮 +垯 +垰 +垵 +垸 +垻 +垿 +埃 +埅 +埇 +埈 +埋 +埌 +城 +埏 +埒 +埔 +埕 +埗 +埜 +域 +埠 +埡 +埤 +埧 +埨 +埪 +埭 +埮 +埴 +埵 +執 +培 +基 +埻 +埼 +堀 +堂 +堃 +堅 +堆 +堇 +堈 +堉 +堊 +堍 +堖 +堝 +堡 +堤 +堦 +堪 +堮 +堯 +堰 +報 +場 +堵 +堷 +堺 +塀 +塅 +塆 +塊 +塋 +塌 +塍 +塏 +塑 +塔 +塗 +塘 +塙 +塜 +塞 +塡 +塢 +塤 +塨 +塩 +填 +塬 +塭 +塰 +塱 +塲 +塵 +塹 +塽 +塾 +墀 +境 +墅 +墉 +墊 +墎 +墓 +増 +墘 +墜 +增 +墟 +墡 +墣 +墨 +墩 +墫 +墬 +墮 +墱 +墳 +墺 +墼 +墾 +壁 +壄 +壆 +壇 +壋 +壌 +壎 +壐 +壑 +壓 +壔 +壕 +壘 +壙 +壞 +壟 +壠 +壢 +壤 +壩 +士 +壬 +壯 +壱 +壴 +壹 +壺 +壽 +夀 +夆 +変 +夊 +夋 +夌 +夏 +夔 +夕 +外 +夙 +多 +夜 +夠 +夢 +夤 +夥 +大 +天 +太 +夫 +夬 +夭 +央 +夯 +失 +夷 +夾 +奀 +奄 +奇 +奈 +奉 +奎 +奏 +奐 +契 +奓 +奔 +奕 +套 +奘 +奚 +奠 +奢 +奣 +奧 +奩 +奪 +奫 +奭 +奮 +女 +奴 +奶 +她 +好 +妀 +妁 +如 +妃 +妄 +妊 +妍 +妏 +妑 +妒 +妓 +妖 +妙 +妝 +妞 +妠 +妤 +妥 +妧 +妨 +妭 +妮 +妯 +妲 +妳 +妸 +妹 +妺 +妻 +妾 +姀 +姁 +姃 +姆 +姈 +姉 +姊 +始 +姌 +姍 +姐 +姑 +姒 +姓 +委 +姚 +姜 +姝 +姣 +姥 +姦 +姨 +姪 +姫 +姬 +姮 +姵 +姶 +姸 +姻 +姿 +威 +娃 +娉 +娋 +娌 +娍 +娎 +娑 +娖 +娘 +娛 +娜 +娟 +娠 +娣 +娥 +娩 +娫 +娳 +娶 +娸 +娼 +娽 +婀 +婁 +婆 +婉 +婊 +婑 +婕 +婚 +婢 +婦 +婧 +婪 +婭 +婯 +婷 +婺 +婻 +婼 +婿 +媃 +媄 +媊 +媐 +媒 +媓 +媖 +媗 +媚 +媛 +媜 +媞 +媧 +媭 +媯 +媲 +媳 +媺 +媼 +媽 +媾 +媿 +嫁 +嫂 +嫄 +嫈 +嫉 +嫌 +嫖 +嫘 +嫚 +嫡 +嫣 +嫦 +嫩 +嫪 +嫲 +嫳 +嫵 +嫺 +嫻 +嬅 +嬈 +嬉 +嬋 +嬌 +嬗 +嬛 +嬝 +嬡 +嬤 +嬨 +嬪 +嬬 +嬭 +嬰 +嬴 +嬸 +嬾 +嬿 +孀 +孃 +孆 +孋 +孌 +子 +孑 +孔 +孕 +孖 +字 +存 +孚 +孛 +孜 +孝 +孟 +孢 +季 +孤 +孩 +孫 +孬 +孮 +孰 +孳 +孵 +學 +孺 +孻 +孽 +孿 +宀 +它 +宅 +宇 +守 +安 +宋 +完 +宍 +宏 +宓 +宕 +宗 +官 +宙 +定 +宛 +宜 +実 +客 +宣 +室 +宥 +宦 +宧 +宮 +宰 +害 +宴 +宵 +家 +宸 +容 +宿 +寀 +寁 +寂 +寄 +寅 +密 +寇 +寈 +寊 +富 +寐 +寒 +寓 +寔 +寕 +寖 +寗 +寘 +寛 +寜 +寞 +察 +寡 +寢 +寤 +寥 +實 +寧 +寨 +審 +寫 +寬 +寮 +寯 +寰 +寳 +寵 +寶 +寸 +寺 +対 +封 +専 +尃 +射 +將 +專 +尉 +尊 +尋 +對 +導 +小 +尐 +少 +尓 +尕 +尖 +尗 +尙 +尚 +尢 +尤 +尨 +尪 +尬 +就 +尷 +尹 +尺 +尻 +尼 +尾 +尿 +局 +屁 +屄 +居 +屆 +屇 +屈 +屋 +屌 +屍 +屎 +屏 +屐 +屑 +屓 +展 +屚 +屜 +屠 +屢 +層 +履 +屬 +屭 +屯 +山 +屹 +屺 +屻 +岀 +岈 +岌 +岐 +岑 +岔 +岡 +岢 +岣 +岧 +岩 +岪 +岫 +岬 +岰 +岱 +岳 +岵 +岷 +岸 +岻 +峁 +峅 +峇 +峋 +峍 +峒 +峘 +峙 +峚 +峠 +峨 +峩 +峪 +峭 +峯 +峰 +峴 +島 +峻 +峼 +峽 +崁 +崆 +崇 +崈 +崋 +崍 +崎 +崐 +崑 +崒 +崔 +崖 +崗 +崘 +崙 +崚 +崛 +崞 +崟 +崠 +崢 +崤 +崧 +崩 +崬 +崮 +崱 +崴 +崵 +崶 +崽 +嵇 +嵊 +嵋 +嵌 +嵎 +嵐 +嵒 +嵕 +嵖 +嵗 +嵙 +嵛 +嵜 +嵨 +嵩 +嵬 +嵮 +嵯 +嵰 +嵴 +嵻 +嵿 +嶁 +嶂 +嶃 +嶄 +嶇 +嶋 +嶌 +嶍 +嶒 +嶔 +嶗 +嶝 +嶠 +嶢 +嶦 +嶧 +嶪 +嶬 +嶰 +嶲 +嶴 +嶷 +嶸 +嶺 +嶼 +嶽 +巂 +巄 +巆 +巋 +巌 +巍 +巎 +巑 +巒 +巔 +巖 +巘 +巛 +川 +州 +巡 +巢 +工 +左 +巧 +巨 +巫 +差 +巰 +己 +已 +巳 +巴 +巶 +巷 +巻 +巽 +巾 +巿 +市 +布 +帆 +希 +帑 +帔 +帕 +帖 +帘 +帙 +帚 +帛 +帝 +帡 +帢 +帥 +師 +席 +帯 +帰 +帳 +帶 +帷 +常 +帽 +幀 +幃 +幄 +幅 +幌 +幔 +幕 +幗 +幚 +幛 +幟 +幡 +幢 +幣 +幪 +幫 +干 +平 +年 +幵 +幷 +幸 +幹 +幺 +幻 +幼 +幽 +幾 +庀 +庁 +広 +庇 +床 +序 +底 +庖 +店 +庚 +府 +庠 +庢 +庥 +度 +座 +庫 +庭 +庲 +庵 +庶 +康 +庸 +庹 +庼 +庾 +廁 +廂 +廄 +廆 +廈 +廉 +廊 +廋 +廌 +廍 +廑 +廓 +廔 +廕 +廖 +廙 +廚 +廝 +廞 +廟 +廠 +廡 +廢 +廣 +廧 +廨 +廩 +廬 +廰 +廱 +廳 +延 +廷 +廸 +建 +廻 +廼 +廿 +弁 +弄 +弅 +弇 +弈 +弉 +弊 +弋 +弍 +式 +弐 +弒 +弓 +弔 +引 +弖 +弗 +弘 +弛 +弟 +弢 +弦 +弧 +弨 +弩 +弭 +弱 +張 +強 +弸 +弼 +弾 +彀 +彄 +彅 +彆 +彈 +彊 +彌 +彎 +彐 +彔 +彖 +彗 +彘 +彙 +彜 +彞 +彠 +彡 +形 +彣 +彤 +彥 +彧 +彩 +彪 +彫 +彬 +彭 +彰 +影 +彳 +彷 +役 +彼 +彿 +往 +征 +徂 +待 +徇 +很 +徉 +徊 +律 +後 +徐 +徑 +徒 +得 +徘 +徙 +徜 +從 +徠 +御 +徧 +徨 +復 +循 +徫 +徬 +徭 +微 +徳 +徴 +徵 +德 +徸 +徹 +徽 +心 +忄 +必 +忉 +忌 +忍 +忐 +忑 +忒 +志 +忘 +忙 +応 +忝 +忞 +忠 +快 +忬 +忯 +忱 +忳 +念 +忻 +忽 +忿 +怍 +怎 +怒 +怕 +怖 +怙 +怛 +思 +怠 +怡 +急 +怦 +性 +怨 +怪 +怯 +怵 +恁 +恂 +恃 +恆 +恊 +恍 +恐 +恕 +恙 +恢 +恣 +恤 +恥 +恨 +恩 +恪 +恬 +恭 +息 +恰 +恵 +恿 +悄 +悅 +悆 +悉 +悌 +悍 +悔 +悖 +悚 +悛 +悝 +悞 +悟 +悠 +患 +悧 +您 +悪 +悰 +悲 +悳 +悵 +悶 +悸 +悼 +情 +惆 +惇 +惑 +惔 +惕 +惘 +惚 +惜 +惟 +惠 +惡 +惣 +惦 +惰 +惱 +惲 +想 +惶 +惹 +惺 +愁 +愃 +愆 +愈 +愉 +愍 +意 +愐 +愒 +愔 +愕 +愚 +愛 +愜 +感 +愣 +愧 +愨 +愫 +愭 +愴 +愷 +愼 +愾 +愿 +慄 +慈 +態 +慌 +慎 +慕 +慘 +慚 +慜 +慟 +慢 +慣 +慥 +慧 +慨 +慮 +慰 +慳 +慵 +慶 +慷 +慾 +憂 +憊 +憋 +憍 +憎 +憐 +憑 +憓 +憕 +憙 +憚 +憤 +憧 +憨 +憩 +憫 +憬 +憲 +憶 +憺 +憻 +憾 +懂 +懃 +懇 +懈 +應 +懋 +懌 +懍 +懐 +懣 +懦 +懮 +懲 +懵 +懶 +懷 +懸 +懺 +懼 +懽 +懾 +懿 +戀 +戇 +戈 +戊 +戌 +戍 +戎 +成 +我 +戒 +戔 +戕 +或 +戙 +戚 +戛 +戟 +戡 +戢 +戥 +戦 +戩 +截 +戮 +戰 +戱 +戲 +戳 +戴 +戶 +戸 +戻 +戽 +戾 +房 +所 +扁 +扆 +扇 +扈 +扉 +手 +扌 +才 +扎 +扒 +打 +扔 +托 +扙 +扛 +扞 +扣 +扥 +扦 +扭 +扮 +扯 +扳 +扶 +批 +扼 +找 +承 +技 +抃 +抄 +抇 +抉 +把 +抑 +抒 +抓 +投 +抖 +抗 +折 +抦 +披 +抬 +抱 +抵 +抹 +抻 +押 +抽 +抿 +拂 +拆 +拇 +拈 +拉 +拋 +拌 +拍 +拎 +拏 +拐 +拒 +拓 +拔 +拖 +拗 +拘 +拙 +拚 +招 +拜 +拝 +拡 +括 +拭 +拮 +拯 +拱 +拳 +拴 +拷 +拺 +拼 +拽 +拾 +拿 +持 +指 +按 +挎 +挑 +挖 +挙 +挨 +挪 +挫 +振 +挲 +挵 +挹 +挺 +挻 +挾 +捂 +捆 +捉 +捌 +捍 +捎 +捏 +捐 +捒 +捕 +捜 +捦 +捧 +捨 +捩 +捫 +捭 +捱 +捲 +捶 +捷 +捺 +捻 +掀 +掂 +掃 +掄 +掇 +授 +掉 +掌 +掏 +掐 +排 +掖 +掘 +掙 +掛 +掞 +掟 +掠 +採 +探 +掣 +接 +控 +推 +掩 +措 +掬 +掰 +掾 +揀 +揄 +揆 +揉 +揍 +描 +提 +插 +揔 +揖 +揚 +換 +握 +揪 +揭 +揮 +援 +揸 +揺 +損 +搏 +搐 +搓 +搔 +搖 +搗 +搜 +搞 +搠 +搢 +搪 +搬 +搭 +搳 +搴 +搵 +搶 +搽 +搾 +摂 +摒 +摔 +摘 +摜 +摞 +摟 +摠 +摧 +摩 +摭 +摯 +摳 +摴 +摵 +摶 +摸 +摹 +摺 +摻 +摽 +撃 +撇 +撈 +撐 +撒 +撓 +撕 +撖 +撙 +撚 +撞 +撣 +撤 +撥 +撩 +撫 +撬 +播 +撮 +撰 +撲 +撳 +撻 +撼 +撾 +撿 +擀 +擁 +擂 +擅 +擇 +擊 +擋 +操 +擎 +擒 +擔 +擘 +據 +擠 +擢 +擥 +擦 +擬 +擯 +擰 +擱 +擲 +擴 +擷 +擺 +擼 +擾 +攀 +攏 +攔 +攖 +攘 +攜 +攝 +攞 +攢 +攣 +攤 +攪 +攫 +攬 +支 +攴 +攵 +收 +攷 +攸 +改 +攻 +攽 +放 +政 +故 +效 +敍 +敎 +敏 +救 +敔 +敕 +敖 +敗 +敘 +教 +敝 +敞 +敟 +敢 +散 +敦 +敫 +敬 +敭 +敲 +整 +敵 +敷 +數 +敻 +敾 +斂 +斃 +文 +斌 +斎 +斐 +斑 +斕 +斖 +斗 +料 +斛 +斜 +斝 +斟 +斡 +斤 +斥 +斧 +斬 +斯 +新 +斷 +方 +於 +施 +斿 +旁 +旂 +旃 +旄 +旅 +旉 +旋 +旌 +旎 +族 +旖 +旗 +旙 +旛 +旡 +既 +日 +旦 +旨 +早 +旬 +旭 +旱 +旲 +旳 +旺 +旻 +旼 +旽 +旾 +旿 +昀 +昂 +昃 +昆 +昇 +昉 +昊 +昌 +昍 +明 +昏 +昐 +易 +昔 +昕 +昚 +昛 +昜 +昝 +昞 +星 +映 +昡 +昣 +昤 +春 +昧 +昨 +昪 +昫 +昭 +是 +昰 +昱 +昴 +昵 +昶 +昺 +晁 +時 +晃 +晈 +晉 +晊 +晏 +晗 +晙 +晚 +晛 +晝 +晞 +晟 +晤 +晦 +晧 +晨 +晩 +晪 +晫 +晭 +普 +景 +晰 +晳 +晴 +晶 +晷 +晸 +智 +晾 +暃 +暄 +暅 +暇 +暈 +暉 +暊 +暌 +暎 +暏 +暐 +暑 +暕 +暖 +暗 +暘 +暝 +暟 +暠 +暢 +暦 +暨 +暫 +暮 +暱 +暲 +暴 +暸 +暹 +暻 +暾 +曄 +曅 +曆 +曇 +曉 +曌 +曔 +曖 +曙 +曜 +曝 +曠 +曦 +曧 +曨 +曩 +曬 +曮 +曰 +曲 +曳 +更 +曶 +曷 +書 +曹 +曺 +曼 +曽 +曾 +替 +最 +會 +月 +有 +朊 +朋 +服 +朏 +朐 +朓 +朔 +朕 +朖 +朗 +望 +朝 +期 +朦 +朧 +木 +未 +末 +本 +札 +朱 +朴 +朵 +朶 +朽 +朿 +杁 +杉 +杋 +杌 +李 +杏 +材 +村 +杓 +杖 +杙 +杜 +杞 +束 +杠 +杣 +杤 +杧 +杬 +杭 +杯 +東 +杲 +杳 +杴 +杵 +杷 +杻 +杼 +松 +板 +极 +枇 +枉 +枋 +枏 +析 +枕 +枖 +林 +枚 +枛 +果 +枝 +枠 +枡 +枯 +枰 +枱 +枲 +枳 +架 +枷 +枸 +枹 +枼 +柁 +柃 +柄 +柉 +柊 +柎 +柏 +某 +柑 +柒 +染 +柔 +柘 +柚 +柜 +柝 +柞 +柟 +查 +柩 +柬 +柯 +柰 +柱 +柳 +柴 +柵 +柶 +柷 +査 +柾 +柿 +栃 +栄 +栐 +栒 +栓 +栜 +栝 +栞 +校 +栢 +栨 +栩 +株 +栲 +栴 +核 +根 +栻 +格 +栽 +桀 +桁 +桂 +桃 +桄 +桅 +框 +案 +桉 +桌 +桎 +桐 +桑 +桓 +桔 +桕 +桖 +桙 +桜 +桝 +桫 +桱 +桲 +桴 +桶 +桷 +桼 +桿 +梀 +梁 +梂 +梃 +梅 +梆 +梉 +梏 +梓 +梔 +梗 +梘 +條 +梟 +梠 +梢 +梣 +梧 +梨 +梫 +梭 +梯 +械 +梱 +梳 +梵 +梶 +梽 +棄 +棆 +棉 +棋 +棍 +棐 +棒 +棓 +棕 +棖 +棗 +棘 +棚 +棛 +棟 +棠 +棡 +棣 +棧 +棨 +棩 +棪 +棫 +森 +棱 +棲 +棵 +棶 +棹 +棺 +棻 +棼 +棽 +椅 +椆 +椇 +椋 +植 +椎 +椏 +椒 +椙 +椥 +椪 +椰 +椲 +椴 +椵 +椹 +椽 +椿 +楂 +楊 +楓 +楔 +楗 +楙 +楚 +楝 +楞 +楠 +楡 +楢 +楣 +楤 +楦 +楧 +楨 +楫 +業 +楮 +楯 +楳 +極 +楷 +楸 +楹 +楽 +楿 +概 +榆 +榊 +榍 +榎 +榑 +榔 +榕 +榖 +榗 +榘 +榛 +榜 +榞 +榢 +榣 +榤 +榦 +榧 +榨 +榫 +榭 +榮 +榲 +榴 +榷 +榻 +榿 +槀 +槁 +槃 +槊 +構 +槌 +槍 +槎 +槐 +槓 +槔 +槗 +様 +槙 +槤 +槩 +槭 +槰 +槱 +槲 +槳 +槺 +槻 +槼 +槽 +槿 +樀 +樁 +樂 +樅 +樆 +樊 +樋 +樑 +樓 +樗 +樘 +標 +樞 +樟 +模 +樣 +樨 +権 +樫 +樵 +樸 +樹 +樺 +樻 +樽 +樾 +橄 +橇 +橈 +橋 +橐 +橒 +橓 +橘 +橙 +橚 +機 +橡 +橢 +橪 +橫 +橿 +檀 +檄 +檇 +檉 +檊 +檎 +檐 +檔 +檗 +檜 +檞 +檠 +檡 +檢 +檣 +檦 +檨 +檫 +檬 +檯 +檳 +檵 +檸 +檻 +檽 +櫂 +櫃 +櫆 +櫈 +櫓 +櫚 +櫛 +櫞 +櫟 +櫥 +櫨 +櫪 +櫱 +櫸 +櫻 +櫾 +櫿 +欄 +欉 +權 +欏 +欒 +欖 +欞 +欠 +次 +欣 +欥 +欲 +欸 +欹 +欺 +欽 +款 +歆 +歇 +歉 +歊 +歌 +歎 +歐 +歓 +歙 +歛 +歡 +止 +正 +此 +步 +武 +歧 +歩 +歪 +歲 +歳 +歴 +歷 +歸 +歹 +死 +歿 +殂 +殃 +殄 +殆 +殉 +殊 +殑 +殖 +殘 +殛 +殞 +殟 +殤 +殭 +殮 +殯 +殲 +殳 +段 +殷 +殺 +殻 +殼 +殿 +毀 +毅 +毆 +毉 +毋 +毌 +母 +毎 +每 +毐 +毒 +毓 +比 +毖 +毗 +毘 +毛 +毫 +毬 +毯 +毴 +毸 +毽 +毿 +氂 +氈 +氍 +氏 +氐 +民 +氓 +氖 +気 +氘 +氙 +氚 +氛 +氟 +氣 +氦 +氧 +氨 +氪 +氫 +氬 +氮 +氯 +氰 +水 +氵 +氷 +永 +氹 +氻 +氽 +氾 +汀 +汁 +求 +汊 +汎 +汐 +汕 +汗 +汛 +汜 +汝 +汞 +江 +池 +污 +汧 +汨 +汩 +汪 +汭 +汰 +汲 +汴 +汶 +決 +汽 +汾 +沁 +沂 +沃 +沄 +沅 +沆 +沇 +沈 +沉 +沌 +沍 +沏 +沐 +沒 +沓 +沔 +沖 +沘 +沙 +沚 +沛 +沜 +沢 +沨 +沫 +沭 +沮 +沯 +沱 +河 +沸 +油 +沺 +治 +沼 +沽 +沾 +沿 +況 +泂 +泄 +泆 +泇 +泉 +泊 +泌 +泐 +泓 +泔 +法 +泖 +泗 +泚 +泛 +泠 +泡 +波 +泣 +泥 +泩 +泫 +泮 +泯 +泰 +泱 +泳 +泵 +洄 +洋 +洌 +洎 +洗 +洙 +洛 +洞 +洢 +洣 +洤 +津 +洨 +洩 +洪 +洮 +洱 +洲 +洳 +洵 +洸 +洹 +洺 +活 +洽 +派 +流 +浄 +浙 +浚 +浛 +浜 +浞 +浟 +浠 +浡 +浣 +浤 +浥 +浦 +浩 +浪 +浮 +浯 +浴 +浵 +海 +浸 +浹 +涅 +涇 +消 +涉 +涌 +涎 +涑 +涓 +涔 +涕 +涙 +涪 +涫 +涮 +涯 +液 +涵 +涸 +涼 +涿 +淄 +淅 +淆 +淇 +淋 +淌 +淍 +淎 +淏 +淑 +淓 +淖 +淘 +淙 +淚 +淛 +淝 +淞 +淠 +淡 +淤 +淥 +淦 +淨 +淩 +淪 +淫 +淬 +淮 +淯 +淰 +深 +淳 +淵 +淶 +混 +淸 +淹 +淺 +添 +淼 +淽 +渃 +清 +済 +渉 +渋 +渕 +渙 +渚 +減 +渝 +渟 +渠 +渡 +渣 +渤 +渥 +渦 +渫 +測 +渭 +港 +渲 +渴 +游 +渺 +渼 +渽 +渾 +湃 +湄 +湉 +湊 +湍 +湓 +湔 +湖 +湘 +湛 +湜 +湞 +湟 +湣 +湥 +湧 +湫 +湮 +湯 +湳 +湴 +湼 +満 +溁 +溇 +溈 +溉 +溋 +溎 +溏 +源 +準 +溙 +溜 +溝 +溟 +溢 +溥 +溦 +溧 +溪 +溫 +溯 +溱 +溲 +溴 +溵 +溶 +溺 +溼 +滀 +滁 +滂 +滄 +滅 +滇 +滈 +滉 +滋 +滌 +滎 +滏 +滑 +滓 +滔 +滕 +滘 +滙 +滝 +滬 +滯 +滲 +滴 +滷 +滸 +滹 +滻 +滽 +滾 +滿 +漁 +漂 +漆 +漇 +漈 +漎 +漏 +漓 +演 +漕 +漚 +漠 +漢 +漣 +漩 +漪 +漫 +漬 +漯 +漱 +漲 +漳 +漴 +漵 +漷 +漸 +漼 +漾 +漿 +潁 +潑 +潔 +潘 +潛 +潞 +潟 +潢 +潤 +潭 +潮 +潯 +潰 +潲 +潺 +潼 +潽 +潾 +潿 +澀 +澁 +澂 +澄 +澆 +澇 +澈 +澉 +澋 +澌 +澍 +澎 +澔 +澗 +澠 +澡 +澣 +澤 +澥 +澧 +澪 +澮 +澯 +澱 +澳 +澶 +澹 +澻 +激 +濁 +濂 +濃 +濉 +濊 +濋 +濕 +濘 +濙 +濛 +濞 +濟 +濠 +濡 +濤 +濫 +濬 +濮 +濯 +濰 +濱 +濲 +濶 +濺 +濼 +濾 +瀁 +瀅 +瀆 +瀉 +瀍 +瀏 +瀑 +瀔 +瀕 +瀘 +瀚 +瀛 +瀝 +瀞 +瀟 +瀠 +瀣 +瀦 +瀧 +瀨 +瀬 +瀰 +瀲 +瀴 +瀶 +瀹 +瀾 +灃 +灊 +灌 +灑 +灘 +灝 +灞 +灡 +灣 +灤 +灧 +火 +灰 +灴 +灸 +灼 +災 +炁 +炅 +炆 +炊 +炎 +炒 +炔 +炕 +炘 +炙 +炟 +炣 +炤 +炫 +炬 +炭 +炮 +炯 +炱 +炲 +炳 +炷 +炸 +為 +炻 +烈 +烉 +烊 +烋 +烏 +烒 +烔 +烘 +烙 +烜 +烝 +烤 +烯 +烱 +烴 +烷 +烹 +烺 +烽 +焃 +焄 +焉 +焊 +焌 +焓 +焗 +焙 +焚 +焜 +焞 +無 +焦 +焯 +焰 +焱 +焴 +然 +焻 +焼 +焿 +煇 +煉 +煊 +煌 +煎 +煐 +煒 +煔 +煕 +煖 +煙 +煚 +煜 +煞 +煠 +煤 +煥 +煦 +照 +煨 +煩 +煬 +煮 +煲 +煳 +煵 +煶 +煸 +煽 +熄 +熅 +熇 +熈 +熊 +熏 +熒 +熔 +熖 +熗 +熘 +熙 +熜 +熟 +熠 +熤 +熥 +熨 +熬 +熯 +熱 +熲 +熳 +熵 +熹 +熺 +熼 +熾 +熿 +燁 +燃 +燄 +燈 +燉 +燊 +燎 +燏 +燐 +燒 +燔 +燕 +燘 +燙 +燚 +燜 +燝 +營 +燥 +燦 +燧 +燫 +燬 +燭 +燮 +燴 +燹 +燻 +燼 +燾 +燿 +爀 +爆 +爌 +爍 +爐 +爔 +爚 +爛 +爝 +爨 +爪 +爬 +爭 +爯 +爰 +爲 +爵 +父 +爸 +爹 +爺 +爻 +爽 +爾 +爿 +牁 +牂 +牆 +片 +版 +牌 +牒 +牕 +牖 +牘 +牙 +牛 +牝 +牟 +牠 +牡 +牢 +牧 +物 +牯 +牲 +特 +牻 +牼 +牽 +犀 +犁 +犂 +犇 +犍 +犎 +犖 +犛 +犢 +犧 +犨 +犬 +犯 +犰 +犴 +犽 +狀 +狂 +狄 +狍 +狎 +狐 +狒 +狓 +狗 +狙 +狛 +狟 +狠 +狡 +狦 +狨 +狩 +狳 +狶 +狷 +狸 +狹 +狻 +狼 +猁 +猄 +猇 +猊 +猗 +猙 +猛 +猜 +猝 +猞 +猢 +猥 +猨 +猩 +猳 +猴 +猶 +猷 +猺 +猻 +猾 +猿 +獁 +獃 +獄 +獅 +獇 +獎 +獏 +獐 +獒 +獠 +獢 +獣 +獨 +獬 +獮 +獯 +獰 +獲 +獴 +獵 +獷 +獸 +獺 +獻 +獼 +獾 +玀 +玄 +玆 +率 +玉 +王 +玎 +玏 +玓 +玕 +玖 +玗 +玘 +玙 +玟 +玠 +玡 +玢 +玥 +玧 +玨 +玩 +玫 +玭 +玲 +玳 +玶 +玷 +玹 +玻 +玾 +珀 +珂 +珅 +珈 +珉 +珊 +珌 +珍 +珎 +珏 +珖 +珙 +珝 +珞 +珠 +珡 +珣 +珤 +珥 +珦 +珧 +珩 +珪 +班 +珮 +珵 +珹 +珺 +珽 +現 +琁 +球 +琄 +琅 +理 +琇 +琉 +琊 +琍 +琎 +琚 +琛 +琡 +琢 +琤 +琥 +琦 +琨 +琪 +琬 +琮 +琯 +琰 +琱 +琳 +琴 +琵 +琶 +琹 +琺 +琿 +瑀 +瑁 +瑂 +瑄 +瑅 +瑆 +瑈 +瑊 +瑋 +瑑 +瑒 +瑕 +瑗 +瑙 +瑚 +瑛 +瑜 +瑝 +瑞 +瑟 +瑠 +瑢 +瑣 +瑤 +瑥 +瑧 +瑨 +瑩 +瑪 +瑭 +瑯 +瑰 +瑱 +瑳 +瑴 +瑺 +瑾 +璀 +璁 +璃 +璄 +璆 +璇 +璈 +璉 +璋 +璌 +璐 +璕 +璘 +璙 +璚 +璜 +璞 +璟 +璠 +璡 +璣 +璥 +璦 +璧 +璨 +璩 +璪 +璫 +璬 +璮 +環 +璱 +璵 +璸 +璹 +璽 +璿 +瓈 +瓊 +瓌 +瓏 +瓑 +瓔 +瓖 +瓘 +瓚 +瓛 +瓜 +瓞 +瓠 +瓢 +瓣 +瓤 +瓦 +瓮 +瓴 +瓶 +瓷 +瓿 +甂 +甄 +甌 +甍 +甑 +甕 +甘 +甙 +甚 +甜 +生 +甡 +產 +産 +甥 +甦 +用 +甩 +甪 +甫 +甬 +甯 +田 +由 +甲 +申 +男 +甸 +甹 +町 +甾 +畀 +畇 +畈 +畊 +畋 +界 +畎 +畏 +畐 +畑 +畔 +留 +畜 +畝 +畠 +畢 +略 +畦 +畧 +番 +畫 +畬 +畯 +異 +畲 +畳 +畵 +當 +畷 +畸 +畹 +畿 +疃 +疆 +疇 +疊 +疋 +疌 +疍 +疏 +疑 +疒 +疕 +疙 +疚 +疝 +疣 +疤 +疥 +疫 +疲 +疳 +疵 +疸 +疹 +疼 +疽 +疾 +痂 +病 +症 +痊 +痍 +痔 +痕 +痘 +痙 +痛 +痞 +痟 +痠 +痢 +痣 +痤 +痧 +痩 +痰 +痱 +痲 +痴 +痹 +痺 +痿 +瘀 +瘁 +瘊 +瘋 +瘍 +瘓 +瘙 +瘜 +瘞 +瘟 +瘠 +瘡 +瘢 +瘤 +瘦 +瘧 +瘩 +瘰 +瘴 +瘺 +癀 +療 +癆 +癇 +癌 +癒 +癖 +癘 +癜 +癟 +癡 +癢 +癤 +癥 +癩 +癬 +癭 +癮 +癯 +癰 +癱 +癲 +癸 +発 +登 +發 +白 +百 +皂 +的 +皆 +皇 +皈 +皋 +皎 +皐 +皓 +皖 +皙 +皚 +皛 +皝 +皞 +皮 +皰 +皴 +皷 +皸 +皺 +皿 +盂 +盃 +盅 +盆 +盈 +益 +盋 +盌 +盎 +盒 +盔 +盛 +盜 +盞 +盟 +盡 +監 +盤 +盥 +盦 +盧 +盨 +盩 +盪 +盫 +目 +盯 +盱 +盲 +直 +盷 +相 +盹 +盺 +盼 +盾 +眀 +省 +眉 +看 +県 +眙 +眛 +眜 +眞 +真 +眠 +眥 +眨 +眩 +眭 +眯 +眵 +眶 +眷 +眸 +眺 +眼 +眾 +着 +睇 +睛 +睜 +睞 +睡 +睢 +督 +睥 +睦 +睨 +睪 +睫 +睭 +睹 +睺 +睽 +睾 +睿 +瞄 +瞅 +瞋 +瞌 +瞎 +瞑 +瞓 +瞞 +瞢 +瞥 +瞧 +瞪 +瞫 +瞬 +瞭 +瞰 +瞳 +瞻 +瞼 +瞽 +瞿 +矇 +矍 +矗 +矚 +矛 +矜 +矞 +矢 +矣 +知 +矧 +矩 +短 +矮 +矯 +石 +矸 +矽 +砂 +砋 +砌 +砍 +砒 +研 +砝 +砢 +砥 +砦 +砧 +砩 +砫 +砭 +砮 +砯 +砰 +砲 +砳 +破 +砵 +砷 +砸 +砼 +硂 +硃 +硅 +硇 +硏 +硐 +硒 +硓 +硚 +硜 +硝 +硤 +硨 +硫 +硬 +硭 +硯 +硼 +碁 +碇 +碉 +碌 +碎 +碑 +碓 +碕 +碗 +碘 +碚 +碟 +碡 +碣 +碧 +碩 +碪 +碭 +碰 +碲 +碳 +碴 +碶 +碸 +確 +碻 +碼 +碽 +碾 +磁 +磅 +磊 +磋 +磐 +磔 +磕 +磘 +磙 +磚 +磜 +磡 +磨 +磪 +磬 +磯 +磱 +磲 +磵 +磷 +磺 +磻 +磾 +礁 +礄 +礎 +礐 +礑 +礒 +礙 +礠 +礦 +礪 +礫 +礬 +礮 +礱 +礴 +示 +礻 +礽 +社 +祀 +祁 +祂 +祆 +祇 +祈 +祉 +祋 +祏 +祐 +祓 +祕 +祖 +祗 +祙 +祚 +祛 +祜 +祝 +神 +祟 +祠 +祥 +祧 +票 +祭 +祹 +祺 +祼 +祿 +禁 +禃 +禇 +禍 +禎 +福 +禑 +禓 +禔 +禕 +禘 +禛 +禟 +禠 +禤 +禦 +禧 +禨 +禩 +禪 +禮 +禰 +禱 +禵 +禹 +禺 +禼 +禽 +禾 +禿 +秀 +私 +秈 +秉 +秋 +科 +秒 +秕 +秘 +租 +秠 +秣 +秤 +秦 +秧 +秩 +秭 +秳 +秸 +移 +稀 +稅 +稈 +稉 +程 +稍 +稑 +稔 +稗 +稘 +稙 +稚 +稜 +稞 +稟 +稠 +種 +稱 +稲 +稷 +稹 +稺 +稻 +稼 +稽 +稾 +稿 +穀 +穂 +穆 +穈 +穉 +穌 +積 +穎 +穗 +穟 +穠 +穡 +穢 +穣 +穩 +穫 +穰 +穴 +穵 +究 +穹 +空 +穿 +突 +窄 +窅 +窈 +窋 +窒 +窕 +窖 +窗 +窘 +窟 +窠 +窣 +窨 +窩 +窪 +窮 +窯 +窰 +窶 +窺 +窿 +竄 +竅 +竇 +竈 +竊 +立 +竑 +站 +竜 +竟 +章 +竣 +童 +竦 +竩 +竭 +端 +競 +竹 +竺 +竻 +竿 +笄 +笆 +笈 +笏 +笑 +笘 +笙 +笛 +笞 +笠 +笥 +符 +笨 +笩 +笪 +第 +笭 +笮 +笯 +笱 +笳 +笹 +筅 +筆 +等 +筊 +筋 +筌 +筍 +筏 +筐 +筒 +答 +策 +筘 +筠 +筥 +筦 +筧 +筬 +筭 +筱 +筲 +筳 +筵 +筶 +筷 +筻 +箆 +箇 +箋 +箍 +箏 +箐 +箑 +箒 +箔 +箕 +算 +箜 +管 +箬 +箭 +箱 +箴 +箸 +節 +篁 +範 +篆 +篇 +築 +篊 +篋 +篌 +篔 +篙 +篝 +篠 +篡 +篤 +篥 +篦 +篩 +篪 +篭 +篯 +篳 +篷 +簀 +簃 +簇 +簉 +簋 +簍 +簑 +簕 +簗 +簞 +簠 +簡 +簧 +簪 +簫 +簷 +簸 +簹 +簺 +簽 +簾 +簿 +籀 +籃 +籌 +籍 +籐 +籙 +籛 +籜 +籝 +籟 +籠 +籣 +籤 +籥 +籪 +籬 +籮 +籲 +米 +籽 +籾 +粄 +粉 +粍 +粑 +粒 +粕 +粗 +粘 +粟 +粢 +粥 +粦 +粧 +粩 +粱 +粲 +粳 +粵 +粹 +粼 +粽 +精 +粿 +糀 +糅 +糊 +糌 +糍 +糎 +糕 +糖 +糙 +糜 +糝 +糞 +糟 +糠 +糢 +糧 +糬 +糯 +糰 +糴 +糶 +糸 +糹 +糺 +系 +糾 +紀 +紂 +約 +紅 +紆 +紇 +紈 +紉 +紊 +紋 +納 +紐 +紑 +紓 +純 +紕 +紗 +紘 +紙 +級 +紛 +紜 +紝 +紞 +素 +紡 +索 +紫 +紮 +累 +細 +紱 +紲 +紳 +紵 +紹 +紺 +紿 +終 +絃 +組 +絆 +経 +絎 +結 +絕 +絛 +絜 +絞 +絡 +絢 +給 +絨 +絪 +絮 +統 +絲 +絳 +絵 +絶 +絹 +絺 +綁 +綃 +綈 +綉 +綎 +綏 +經 +綖 +継 +続 +綜 +綝 +綞 +綠 +綢 +綣 +綦 +綧 +綫 +綬 +維 +綮 +綰 +綱 +網 +綳 +綴 +綸 +綺 +綻 +綽 +綾 +綿 +緁 +緃 +緄 +緈 +緊 +緋 +総 +緑 +緒 +緖 +緘 +線 +緜 +緝 +緞 +締 +緡 +緣 +緤 +編 +緩 +緬 +緯 +緱 +緲 +練 +緹 +緻 +縂 +縄 +縈 +縉 +縊 +縕 +縛 +縝 +縞 +縠 +縡 +縣 +縤 +縫 +縮 +縯 +縱 +縴 +縵 +縷 +縹 +縻 +總 +績 +繁 +繃 +繆 +繇 +繒 +織 +繕 +繖 +繙 +繚 +繞 +繡 +繩 +繪 +繫 +繭 +繰 +繳 +繹 +繻 +繼 +繽 +繾 +纁 +纂 +纈 +續 +纍 +纏 +纓 +纔 +纕 +纖 +纘 +纛 +纜 +缐 +缶 +缸 +缺 +缽 +罃 +罄 +罅 +罈 +罉 +罌 +罍 +罐 +罔 +罕 +罘 +罟 +罡 +罨 +罩 +罪 +置 +罰 +罱 +署 +罳 +罵 +罶 +罷 +罹 +罽 +羂 +羅 +羆 +羈 +羊 +羋 +羌 +美 +羔 +羕 +羗 +羙 +羚 +羞 +羡 +羣 +群 +羥 +羧 +羨 +義 +羯 +羰 +羱 +羲 +羸 +羹 +羽 +羿 +翀 +翁 +翂 +翃 +翅 +翊 +翌 +翎 +翏 +習 +翔 +翕 +翙 +翜 +翟 +翠 +翡 +翥 +翦 +翩 +翬 +翮 +翰 +翱 +翳 +翹 +翻 +翼 +耀 +老 +考 +耄 +者 +耆 +而 +耍 +耎 +耐 +耑 +耒 +耔 +耕 +耗 +耘 +耙 +耜 +耦 +耨 +耬 +耳 +耵 +耶 +耷 +耽 +耿 +聃 +聆 +聊 +聒 +聖 +聘 +聚 +聞 +聟 +聨 +聯 +聰 +聱 +聲 +聳 +聴 +聶 +職 +聽 +聾 +聿 +肄 +肅 +肆 +肇 +肉 +肋 +肌 +肏 +肖 +肘 +肚 +肛 +肜 +肝 +肟 +股 +肢 +肥 +肩 +肪 +肫 +肯 +肱 +育 +肸 +肹 +肺 +肼 +肽 +胂 +胃 +胄 +胅 +胇 +胊 +背 +胍 +胎 +胖 +胗 +胙 +胚 +胛 +胝 +胞 +胡 +胤 +胥 +胬 +胭 +胰 +胱 +胳 +胴 +胸 +胺 +胼 +能 +脂 +脅 +脆 +脇 +脈 +脊 +脒 +脖 +脘 +脛 +脣 +脩 +脫 +脬 +脭 +脯 +脲 +脳 +脷 +脹 +脾 +腆 +腈 +腊 +腋 +腌 +腎 +腐 +腑 +腓 +腔 +腕 +腥 +腦 +腧 +腩 +腫 +腮 +腰 +腱 +腳 +腴 +腸 +腹 +腺 +腿 +膀 +膂 +膈 +膊 +膏 +膚 +膛 +膜 +膝 +膠 +膣 +膥 +膦 +膨 +膩 +膮 +膳 +膺 +膽 +膾 +膿 +臀 +臂 +臃 +臆 +臉 +臊 +臍 +臏 +臘 +臚 +臞 +臟 +臠 +臣 +臧 +臨 +自 +臭 +臯 +至 +致 +臺 +臻 +臼 +臾 +舂 +舅 +與 +興 +舉 +舊 +舌 +舍 +舎 +舒 +舔 +舖 +舘 +舛 +舜 +舞 +舟 +舢 +舥 +舨 +舩 +航 +舫 +般 +舲 +舵 +舶 +舷 +舸 +船 +舺 +艅 +艇 +艉 +艋 +艎 +艏 +艔 +艘 +艙 +艚 +艦 +艮 +良 +艱 +色 +艶 +艷 +艸 +艽 +艾 +艿 +芃 +芊 +芋 +芍 +芎 +芑 +芒 +芘 +芙 +芛 +芝 +芡 +芥 +芨 +芩 +芪 +芫 +芬 +芭 +芮 +芯 +花 +芳 +芴 +芷 +芸 +芹 +芻 +芽 +芾 +苄 +苅 +苑 +苒 +苓 +苔 +苕 +苗 +苛 +苜 +苝 +苞 +苟 +苡 +苣 +苤 +若 +苦 +苧 +苪 +苫 +苯 +英 +苳 +苴 +苷 +苺 +苻 +苼 +苾 +茀 +茁 +茂 +范 +茄 +茅 +茆 +茇 +茈 +茉 +茌 +茗 +茘 +茚 +茛 +茜 +茝 +茨 +茫 +茬 +茭 +茮 +茯 +茱 +茲 +茴 +茵 +茶 +茷 +茸 +茹 +茺 +茼 +荀 +荃 +荅 +荇 +草 +荊 +荎 +荏 +荒 +荔 +荖 +荘 +荳 +荷 +荸 +荻 +荼 +荽 +莆 +莉 +莊 +莎 +莒 +莓 +莕 +莖 +莘 +莙 +莛 +莜 +莞 +莠 +莢 +莧 +莨 +莩 +莪 +莫 +莽 +莿 +菀 +菁 +菅 +菇 +菈 +菉 +菊 +菌 +菍 +菏 +菑 +菓 +菔 +菖 +菘 +菜 +菝 +菟 +菠 +菡 +菥 +菩 +菪 +菫 +華 +菰 +菱 +菲 +菴 +菶 +菸 +菹 +菺 +菼 +菽 +菾 +萁 +萃 +萄 +萇 +萊 +萌 +萍 +萎 +萐 +萘 +萜 +萠 +萡 +萣 +萩 +萬 +萭 +萱 +萵 +萸 +萹 +萼 +落 +葃 +葆 +葉 +葊 +葎 +葑 +葒 +著 +葙 +葚 +葛 +葜 +葝 +葡 +董 +葦 +葩 +葫 +葬 +葭 +葯 +葰 +葳 +葵 +葶 +葷 +葺 +蒂 +蒄 +蒍 +蒎 +蒐 +蒓 +蒔 +蒗 +蒙 +蒜 +蒞 +蒟 +蒡 +蒢 +蒤 +蒧 +蒨 +蒭 +蒯 +蒲 +蒴 +蒸 +蒹 +蒺 +蒻 +蒼 +蒽 +蒾 +蒿 +蓀 +蓁 +蓂 +蓄 +蓆 +蓉 +蓋 +蓍 +蓑 +蓓 +蓖 +蓘 +蓚 +蓧 +蓨 +蓪 +蓬 +蓭 +蓮 +蓯 +蓳 +蓼 +蓽 +蓿 +蔆 +蔎 +蔑 +蔓 +蔔 +蔕 +蔗 +蔘 +蔚 +蔝 +蔞 +蔡 +蔣 +蔥 +蔦 +蔬 +蔭 +蔴 +蔵 +蔻 +蔽 +蕁 +蕃 +蕅 +蕈 +蕉 +蕊 +蕎 +蕑 +蕒 +蕖 +蕘 +蕙 +蕚 +蕟 +蕡 +蕢 +蕤 +蕨 +蕩 +蕪 +蕭 +蕷 +蕹 +蕺 +蕻 +蕾 +薀 +薄 +薆 +薇 +薈 +薊 +薌 +薏 +薐 +薑 +薔 +薗 +薘 +薙 +薛 +薜 +薞 +薟 +薡 +薦 +薨 +薩 +薪 +薫 +薬 +薯 +薰 +薲 +薷 +薸 +薹 +薺 +薾 +薿 +藁 +藉 +藍 +藎 +藏 +藐 +藔 +藕 +藜 +藝 +藟 +藤 +藥 +藦 +藨 +藩 +藪 +藶 +藸 +藹 +藺 +藻 +藿 +蘂 +蘄 +蘅 +蘆 +蘇 +蘊 +蘋 +蘐 +蘑 +蘓 +蘗 +蘘 +蘚 +蘞 +蘢 +蘧 +蘩 +蘭 +蘵 +蘶 +蘸 +蘼 +蘿 +虉 +虎 +虐 +虓 +虔 +處 +虖 +虛 +虜 +虞 +號 +虢 +虧 +虨 +虯 +虱 +虵 +虹 +虺 +虻 +蚆 +蚊 +蚋 +蚌 +蚍 +蚓 +蚖 +蚜 +蚝 +蚡 +蚢 +蚣 +蚤 +蚧 +蚨 +蚩 +蚪 +蚯 +蚱 +蚴 +蚵 +蚶 +蚺 +蚼 +蛀 +蛄 +蛇 +蛉 +蛋 +蛍 +蛐 +蛑 +蛔 +蛙 +蛛 +蛞 +蛟 +蛤 +蛭 +蛯 +蛸 +蛹 +蛺 +蛻 +蛾 +蜀 +蜂 +蜃 +蜆 +蜇 +蜈 +蜉 +蜊 +蜍 +蜑 +蜒 +蜓 +蜘 +蜚 +蜛 +蜜 +蜞 +蜢 +蜣 +蜥 +蜨 +蜮 +蜯 +蜱 +蜴 +蜷 +蜻 +蜾 +蜿 +蝀 +蝌 +蝍 +蝎 +蝓 +蝕 +蝗 +蝘 +蝙 +蝚 +蝟 +蝠 +蝣 +蝤 +蝦 +蝨 +蝮 +蝯 +蝰 +蝲 +蝴 +蝶 +蝸 +蝽 +螂 +螃 +螄 +螅 +螈 +螋 +融 +螐 +螔 +螞 +螟 +螠 +螢 +螣 +螥 +螫 +螭 +螯 +螳 +螶 +螺 +螻 +螽 +螾 +蟀 +蟄 +蟅 +蟆 +蟊 +蟋 +蟌 +蟎 +蟑 +蟒 +蟜 +蟠 +蟥 +蟪 +蟫 +蟬 +蟯 +蟲 +蟳 +蟴 +蟶 +蟹 +蟻 +蟾 +蠂 +蠃 +蠄 +蠅 +蠆 +蠊 +蠋 +蠍 +蠐 +蠑 +蠓 +蠔 +蠕 +蠖 +蠘 +蠙 +蠟 +蠡 +蠢 +蠣 +蠱 +蠲 +蠵 +蠶 +蠷 +蠹 +蠻 +血 +衂 +衆 +行 +衍 +衎 +術 +衕 +衖 +街 +衙 +衚 +衛 +衜 +衝 +衞 +衡 +衢 +衣 +表 +衩 +衫 +衰 +衲 +衷 +衽 +衾 +衿 +袁 +袂 +袈 +袋 +袍 +袓 +袖 +袛 +袞 +袤 +袪 +被 +袱 +袴 +袾 +裁 +裂 +裊 +裎 +裒 +裔 +裕 +裖 +裘 +裙 +補 +裝 +裟 +裡 +裨 +裬 +裱 +裳 +裴 +裵 +裸 +裹 +製 +裾 +裿 +褀 +褂 +複 +褌 +褍 +褎 +褐 +褒 +褓 +褔 +褘 +褙 +褚 +褞 +褥 +褧 +褪 +褫 +褭 +褲 +褶 +褸 +褻 +襄 +襌 +襖 +襞 +襟 +襠 +襤 +襦 +襪 +襯 +襲 +襴 +襶 +襻 +襾 +西 +要 +覃 +覆 +覇 +覈 +見 +覌 +規 +覓 +視 +覚 +覡 +覦 +覧 +親 +覬 +覲 +観 +覺 +覽 +覿 +觀 +角 +觔 +觙 +觚 +觜 +解 +觭 +觱 +觴 +觶 +觸 +觿 +言 +訁 +訂 +訃 +訇 +計 +訊 +訌 +討 +訏 +訐 +訒 +訓 +訔 +訕 +訖 +託 +記 +訛 +訝 +訟 +訣 +訥 +訪 +設 +許 +訴 +訶 +診 +註 +証 +訾 +詁 +詆 +詈 +詐 +詒 +詔 +評 +詛 +詞 +詠 +詡 +詢 +詣 +詥 +試 +詧 +詩 +詫 +詭 +詮 +詰 +話 +該 +詳 +詵 +詹 +詼 +誄 +誅 +誇 +誌 +認 +誒 +誓 +誕 +誘 +語 +誠 +誡 +誣 +誤 +誥 +誦 +誨 +說 +説 +読 +誰 +課 +誴 +誹 +誼 +誾 +調 +談 +請 +諍 +諏 +諒 +論 +諗 +諜 +諟 +諠 +諡 +諤 +諦 +諧 +諪 +諫 +諭 +諮 +諱 +諲 +諳 +諴 +諶 +諷 +諸 +諺 +諼 +諾 +謀 +謁 +謂 +謄 +謇 +謊 +謌 +謎 +謏 +謐 +謔 +謖 +謗 +謙 +謚 +講 +謜 +謝 +謠 +謢 +謤 +謨 +謩 +謫 +謬 +謳 +謹 +謾 +證 +譏 +譓 +譔 +識 +譙 +譚 +譜 +譞 +警 +譫 +譬 +譭 +譯 +議 +譲 +譳 +譴 +護 +譽 +譿 +讀 +讃 +變 +讌 +讎 +讓 +讖 +讙 +讚 +讜 +讞 +谷 +谿 +豁 +豆 +豇 +豈 +豉 +豊 +豌 +豎 +豐 +豔 +豕 +豚 +象 +豢 +豨 +豪 +豫 +豬 +豳 +豸 +豹 +豺 +豿 +貂 +貅 +貉 +貊 +貌 +貐 +貒 +貓 +貔 +貘 +貝 +貞 +負 +財 +貢 +貤 +貧 +貨 +販 +貪 +貫 +責 +貭 +貮 +貯 +貲 +貳 +貴 +貶 +買 +貸 +貺 +費 +貼 +貽 +貿 +賀 +賁 +賂 +賃 +賄 +資 +賈 +賊 +賑 +賒 +賓 +賔 +賕 +賚 +賜 +賞 +賠 +賡 +賢 +賣 +賤 +賦 +賨 +質 +賬 +賭 +賴 +賹 +賺 +賻 +購 +賽 +賾 +贄 +贅 +贇 +贈 +贊 +贌 +贍 +贏 +贓 +贔 +贖 +贛 +赤 +赦 +赧 +赫 +赬 +赭 +走 +赳 +赴 +起 +趁 +超 +越 +趐 +趕 +趖 +趙 +趟 +趣 +趨 +足 +趴 +趵 +趺 +趼 +趾 +跅 +跆 +跋 +跌 +跏 +跑 +跖 +跗 +跛 +距 +跟 +跡 +跣 +跤 +跨 +跩 +跪 +路 +跳 +踎 +踏 +踐 +踝 +踞 +踢 +踩 +踰 +踴 +踹 +踺 +蹂 +蹄 +蹇 +蹈 +蹉 +蹊 +蹋 +蹕 +蹙 +蹟 +蹠 +蹤 +蹦 +蹬 +蹭 +蹯 +蹲 +蹴 +蹶 +蹺 +蹻 +蹼 +躁 +躂 +躄 +躉 +躋 +躍 +躑 +躒 +躔 +躝 +躪 +身 +躬 +躰 +躲 +躺 +軀 +車 +軋 +軌 +軍 +軎 +軒 +軔 +軛 +軟 +転 +軫 +軲 +軸 +軹 +軺 +軻 +軼 +軽 +軾 +較 +輄 +輅 +載 +輋 +輒 +輓 +輔 +輕 +輛 +輝 +輞 +輟 +輥 +輦 +輩 +輪 +輬 +輭 +輯 +輶 +輸 +輻 +輾 +輿 +轀 +轂 +轄 +轅 +轆 +轉 +轍 +轎 +轘 +轝 +轟 +轤 +辛 +辜 +辟 +辣 +辦 +辧 +辨 +辭 +辮 +辯 +辰 +辱 +農 +辵 +辺 +辻 +込 +迂 +迄 +迅 +迎 +近 +返 +迢 +迤 +迥 +迦 +迪 +迫 +迭 +迮 +述 +迴 +迵 +迷 +迸 +迺 +追 +退 +送 +逃 +逄 +逅 +逆 +逈 +逋 +逌 +逍 +逎 +透 +逐 +逑 +途 +逕 +逖 +逗 +這 +通 +逛 +逝 +逞 +速 +造 +逢 +連 +逤 +逨 +逮 +逯 +進 +逴 +逵 +逸 +逹 +逺 +逼 +逾 +遁 +遂 +遄 +遇 +遊 +運 +遍 +過 +遏 +遐 +遒 +道 +達 +違 +遘 +遙 +遛 +遜 +遞 +遠 +遢 +遣 +遨 +適 +遭 +遮 +遯 +遲 +遴 +遵 +遶 +遷 +選 +遹 +遺 +遼 +避 +邀 +邁 +邂 +邃 +還 +邇 +邈 +邉 +邊 +邋 +邏 +邑 +邕 +邗 +邙 +邛 +邠 +邡 +邢 +那 +邦 +邨 +邪 +邯 +邰 +邱 +邲 +邳 +邴 +邵 +邸 +邽 +邾 +郁 +郃 +郄 +郅 +郇 +郊 +郋 +郎 +郗 +郛 +郜 +郝 +郞 +郟 +郡 +郢 +郤 +部 +郪 +郫 +郭 +郯 +郳 +郴 +郵 +郷 +都 +郾 +郿 +鄂 +鄃 +鄄 +鄆 +鄉 +鄋 +鄑 +鄒 +鄔 +鄖 +鄗 +鄘 +鄙 +鄚 +鄜 +鄞 +鄠 +鄢 +鄣 +鄤 +鄧 +鄩 +鄫 +鄭 +鄯 +鄰 +鄱 +鄲 +鄳 +鄴 +鄺 +酃 +酆 +酈 +酉 +酊 +酋 +酌 +配 +酎 +酏 +酐 +酒 +酔 +酗 +酚 +酞 +酡 +酢 +酣 +酥 +酩 +酪 +酬 +酮 +酯 +酰 +酴 +酵 +酶 +酷 +酸 +酺 +酼 +醁 +醂 +醃 +醅 +醇 +醉 +醋 +醌 +醍 +醐 +醒 +醚 +醛 +醜 +醞 +醢 +醣 +醪 +醫 +醬 +醮 +醯 +醴 +醺 +醾 +醿 +釀 +釁 +釆 +采 +釉 +釋 +里 +重 +野 +量 +釐 +金 +釒 +釓 +釔 +釕 +釗 +釘 +釙 +釚 +釜 +針 +釣 +釤 +釦 +釧 +釩 +釪 +釭 +釴 +釵 +釷 +釹 +釺 +鈀 +鈁 +鈄 +鈇 +鈈 +鈉 +鈊 +鈍 +鈏 +鈐 +鈑 +鈔 +鈕 +鈖 +鈞 +鈢 +鈣 +鈥 +鈦 +鈫 +鈮 +鈰 +鈳 +鈴 +鈷 +鈸 +鈹 +鈺 +鈾 +鈿 +鉀 +鉄 +鉅 +鉆 +鉈 +鉉 +鉋 +鉌 +鉍 +鉏 +鉑 +鉓 +鉗 +鉚 +鉛 +鉞 +鉟 +鉤 +鉦 +鉬 +鉭 +鉲 +鉶 +鉷 +鉸 +鉻 +鉾 +鉿 +銀 +銂 +銃 +銅 +銋 +銍 +銑 +銓 +銕 +銖 +銘 +銚 +銜 +銠 +銣 +銥 +銦 +銨 +銩 +銪 +銫 +銬 +銭 +銱 +銲 +銳 +銶 +銷 +銹 +銻 +銼 +銾 +鋁 +鋅 +鋆 +鋇 +鋌 +鋏 +鋐 +鋒 +鋕 +鋗 +鋙 +鋡 +鋤 +鋥 +鋦 +鋨 +鋪 +鋮 +鋯 +鋰 +鋱 +鋳 +鋶 +鋸 +鋹 +鋼 +錀 +錄 +錏 +錐 +錒 +錕 +錘 +錚 +錞 +錟 +錠 +錡 +錢 +錦 +錨 +錫 +錬 +錮 +錯 +錳 +錶 +錸 +錻 +鍀 +鍇 +鍈 +鍉 +鍊 +鍋 +鍍 +鍏 +鍔 +鍘 +鍛 +鍝 +鍟 +鍠 +鍥 +鍩 +鍬 +鍱 +鍳 +鍵 +鍶 +鍷 +鍺 +鍼 +鍾 +鎂 +鎅 +鎊 +鎌 +鎏 +鎓 +鎔 +鎖 +鎗 +鎘 +鎚 +鎛 +鎢 +鎣 +鎦 +鎧 +鎪 +鎬 +鎭 +鎮 +鎰 +鎳 +鎵 +鎻 +鏃 +鏇 +鏈 +鏊 +鏌 +鏐 +鏑 +鏓 +鏖 +鏗 +鏘 +鏜 +鏝 +鏞 +鏟 +鏡 +鏢 +鏤 +鏦 +鏳 +鏴 +鏵 +鏷 +鏻 +鏽 +鐃 +鐇 +鐈 +鐓 +鐔 +鐘 +鐙 +鐠 +鐡 +鐤 +鐦 +鐧 +鐫 +鐬 +鐭 +鐮 +鐲 +鐳 +鐵 +鐸 +鐺 +鐽 +鐿 +鑀 +鑁 +鑂 +鑄 +鑅 +鑊 +鑌 +鑑 +鑒 +鑛 +鑠 +鑣 +鑨 +鑪 +鑫 +鑭 +鑰 +鑲 +鑴 +鑷 +鑼 +鑽 +鑾 +鑿 +長 +門 +閂 +閃 +閆 +閉 +開 +閎 +閏 +閑 +閒 +間 +閔 +閘 +閜 +閞 +閟 +関 +閣 +閥 +閦 +閨 +閩 +閬 +閭 +閰 +閱 +閶 +閹 +閻 +閼 +閾 +閿 +闆 +闇 +闈 +闊 +闋 +闌 +闍 +闐 +闓 +闔 +闕 +闖 +闘 +關 +闞 +闡 +闢 +闥 +阜 +阝 +阡 +阪 +阭 +阮 +阯 +阱 +防 +阻 +阿 +陀 +陁 +陂 +附 +陋 +陌 +降 +限 +陔 +陘 +陛 +陜 +陝 +陞 +陟 +陡 +院 +陣 +除 +陪 +陬 +陰 +陲 +陳 +陵 +陶 +陷 +陸 +険 +陽 +隄 +隅 +隆 +隈 +隊 +隋 +隍 +階 +隔 +隕 +隗 +隘 +隙 +際 +障 +隣 +隧 +隨 +險 +隰 +隱 +隲 +隳 +隴 +隷 +隸 +隹 +隻 +隼 +雀 +雁 +雄 +雅 +集 +雇 +雉 +雋 +雌 +雍 +雎 +雑 +雒 +雕 +雖 +雙 +雛 +雜 +雝 +雞 +離 +難 +雨 +雩 +雪 +雫 +雯 +雱 +雲 +零 +雷 +雹 +電 +需 +霄 +霅 +霆 +震 +霈 +霉 +霊 +霍 +霎 +霏 +霑 +霓 +霖 +霙 +霜 +霞 +霤 +霧 +霨 +霰 +露 +霶 +霸 +霹 +霽 +霾 +靁 +靂 +靄 +靈 +靉 +靑 +青 +靖 +靚 +靛 +靜 +非 +靠 +靡 +面 +革 +靫 +靬 +靭 +靳 +靴 +靶 +靺 +靼 +鞅 +鞆 +鞋 +鞍 +鞏 +鞘 +鞞 +鞠 +鞣 +鞥 +鞦 +鞨 +鞭 +鞮 +鞴 +韁 +韃 +韆 +韋 +韌 +韑 +韓 +韙 +韜 +韞 +韠 +韡 +韭 +韮 +音 +韶 +韺 +韻 +韾 +響 +頁 +頂 +頃 +項 +順 +須 +頊 +頌 +頍 +頎 +頏 +預 +頑 +頒 +頓 +頔 +頗 +領 +頜 +頠 +頡 +頤 +頦 +頫 +頭 +頰 +頴 +頵 +頷 +頸 +頹 +頻 +頼 +顆 +題 +額 +顎 +顏 +顒 +顓 +顔 +顕 +顗 +願 +顙 +顛 +類 +顥 +顧 +顫 +顯 +顰 +顱 +顳 +顴 +風 +颮 +颯 +颱 +颶 +颺 +颼 +飄 +飆 +飈 +飛 +食 +飠 +飡 +飢 +飥 +飩 +飪 +飫 +飬 +飭 +飮 +飯 +飲 +飴 +飼 +飽 +飾 +餃 +餄 +餅 +餉 +養 +餌 +餎 +餐 +餒 +餓 +餗 +餘 +餚 +餛 +餞 +餠 +餡 +館 +餮 +餵 +餺 +餾 +餿 +饃 +饅 +饋 +饌 +饑 +饒 +饕 +饗 +饞 +饟 +饢 +首 +馗 +馘 +香 +馛 +馥 +馦 +馨 +馬 +馭 +馮 +馯 +馱 +馳 +馴 +馼 +駁 +駄 +駅 +駆 +駐 +駑 +駒 +駔 +駕 +駘 +駙 +駛 +駝 +駟 +駢 +駭 +駰 +駱 +駿 +騁 +騂 +騄 +騅 +騋 +騎 +騏 +験 +騖 +騙 +騤 +騨 +騫 +騭 +騮 +騰 +騶 +騷 +騾 +驁 +驃 +驄 +驅 +驊 +驌 +驍 +驎 +驒 +驕 +驗 +驚 +驛 +驟 +驢 +驤 +驥 +驩 +驪 +骨 +骯 +骰 +骶 +骷 +骸 +骼 +髀 +髂 +髎 +髏 +髑 +髒 +髓 +體 +高 +髙 +髡 +髦 +髪 +髭 +髮 +髯 +髲 +髷 +髹 +髻 +鬃 +鬄 +鬅 +鬆 +鬍 +鬚 +鬟 +鬢 +鬣 +鬥 +鬧 +鬨 +鬩 +鬪 +鬬 +鬮 +鬯 +鬱 +鬲 +鬹 +鬻 +鬼 +魁 +魂 +魃 +魄 +魅 +魈 +魋 +魍 +魎 +魏 +魔 +魕 +魘 +魚 +魛 +魞 +魟 +魣 +魨 +魩 +魮 +魯 +魴 +魷 +鮀 +鮁 +鮃 +鮄 +鮊 +鮋 +鮍 +鮐 +鮑 +鮒 +鮓 +鮗 +鮜 +鮟 +鮠 +鮡 +鮣 +鮨 +鮪 +鮫 +鮭 +鮮 +鮰 +鮸 +鮹 +鮻 +鯀 +鯁 +鯃 +鯇 +鯉 +鯊 +鯏 +鯒 +鯓 +鯔 +鯕 +鯖 +鯗 +鯙 +鯛 +鯡 +鯢 +鯤 +鯧 +鯨 +鯪 +鯭 +鯮 +鯰 +鯶 +鯷 +鯻 +鯽 +鯿 +鰂 +鰃 +鰆 +鰈 +鰉 +鰍 +鰏 +鰒 +鰓 +鰕 +鰗 +鰛 +鰜 +鰟 +鰣 +鰤 +鰧 +鰨 +鰩 +鰭 +鰮 +鰱 +鰲 +鰳 +鰶 +鰷 +鰹 +鰺 +鰻 +鰼 +鰾 +鱀 +鱂 +鱅 +鱇 +鱈 +鱉 +鱊 +鱒 +鱓 +鱔 +鱖 +鱗 +鱘 +鱚 +鱝 +鱟 +鱠 +鱣 +鱥 +鱧 +鱨 +鱬 +鱮 +鱰 +鱲 +鱵 +鱷 +鱸 +鱺 +鱻 +鳥 +鳧 +鳩 +鳯 +鳰 +鳳 +鳴 +鳶 +鳽 +鴆 +鴇 +鴉 +鴒 +鴓 +鴕 +鴗 +鴛 +鴝 +鴞 +鴟 +鴡 +鴣 +鴦 +鴨 +鴫 +鴯 +鴰 +鴴 +鴻 +鴿 +鵂 +鵄 +鵎 +鵐 +鵑 +鵒 +鵓 +鵙 +鵜 +鵝 +鵞 +鵟 +鵠 +鵡 +鵪 +鵬 +鵯 +鵰 +鵲 +鵵 +鵼 +鵾 +鶆 +鶇 +鶉 +鶏 +鶒 +鶓 +鶘 +鶚 +鶡 +鶥 +鶩 +鶬 +鶯 +鶲 +鶴 +鶹 +鶺 +鶻 +鶼 +鶿 +鷂 +鷄 +鷉 +鷎 +鷓 +鷗 +鷙 +鷚 +鷟 +鷥 +鷦 +鷫 +鷯 +鷲 +鷳 +鷸 +鷹 +鷺 +鸊 +鸌 +鸐 +鸑 +鸕 +鸘 +鸚 +鸛 +鸜 +鸝 +鸞 +鹮 +鹵 +鹹 +鹼 +鹽 +鹿 +麂 +麅 +麇 +麈 +麊 +麋 +麐 +麒 +麓 +麗 +麝 +麞 +麟 +麥 +麩 +麪 +麯 +麴 +麵 +麹 +麺 +麻 +麼 +麽 +麾 +麿 +黁 +黃 +黇 +黌 +黍 +黎 +黏 +黐 +黑 +黒 +黔 +默 +黙 +黛 +黜 +黝 +點 +黟 +黥 +黧 +黨 +黯 +黴 +黶 +黻 +黼 +黽 +黿 +鼂 +鼇 +鼈 +鼉 +鼎 +鼐 +鼒 +鼓 +鼕 +鼙 +鼠 +鼢 +鼩 +鼬 +鼯 +鼱 +鼴 +鼷 +鼻 +鼽 +鼾 +齊 +齋 +齒 +齕 +齡 +齣 +齦 +齧 +齲 +齶 +龍 +龎 +龐 +龑 +龔 +龕 +龜 +龝 +龠 +龢 +郎 +凉 +﹑ +﹗ +﹝ +﹞ +﹢ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +K +L +M +N +O +P +R +S +T +U +V +W +Y +Z +[ +] +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +r +s +t +u +z +{ +| +} +~ +¥ +𣇉 + diff --git a/ppocr/utils/dict/confuse.pkl b/ppocr/utils/dict/confuse.pkl new file mode 100644 index 0000000..e5d4853 Binary files /dev/null and b/ppocr/utils/dict/confuse.pkl differ diff --git a/ppocr/utils/dict/cyrillic_dict.txt b/ppocr/utils/dict/cyrillic_dict.txt new file mode 100644 index 0000000..2b6f664 --- /dev/null +++ b/ppocr/utils/dict/cyrillic_dict.txt @@ -0,0 +1,163 @@ + +! +# +$ +% +& +' +( ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +É +é +Ё +Є +І +Ј +Љ +Ў +А +Б +В +Г +Д +Е +Ж +З +И +Й +К +Л +М +Н +О +П +Р +С +Т +У +Ф +Х +Ц +Ч +Ш +Щ +Ъ +Ы +Ь +Э +Ю +Я +а +б +в +г +д +е +ж +з +и +й +к +л +м +н +о +п +р +с +т +у +ф +х +ц +ч +ш +щ +ъ +ы +ь +э +ю +я +ё +ђ +є +і +ј +љ +њ +ћ +ў +џ +Ґ +ґ diff --git a/ppocr/utils/dict/devanagari_dict.txt b/ppocr/utils/dict/devanagari_dict.txt new file mode 100644 index 0000000..f559230 --- /dev/null +++ b/ppocr/utils/dict/devanagari_dict.txt @@ -0,0 +1,167 @@ + +! +# +$ +% +& +' +( ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +É +é +ँ +ं +ः +अ +आ +इ +ई +उ +ऊ +ऋ +ए +ऐ +ऑ +ओ +औ +क +ख +ग +घ +ङ +च +छ +ज +झ +ञ +ट +ठ +ड +ढ +ण +त +थ +द +ध +न +ऩ +प +फ +ब +भ +म +य +र +ऱ +ल +ळ +व +श +ष +स +ह +़ +ा +ि +ी +ु +ू +ृ +ॅ +े +ै +ॉ +ो +ौ +् +॒ +क़ +ख़ +ग़ +ज़ +ड़ +ढ़ +फ़ +ॠ +। +० +१ +२ +३ +४ +५ +६ +७ +८ +९ +॰ diff --git a/ppocr/utils/dict/en_dict.txt b/ppocr/utils/dict/en_dict.txt new file mode 100644 index 0000000..6fbd99f --- /dev/null +++ b/ppocr/utils/dict/en_dict.txt @@ -0,0 +1,63 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z + diff --git a/ppocr/utils/dict/fa_dict.txt b/ppocr/utils/dict/fa_dict.txt new file mode 100644 index 0000000..2328fbd --- /dev/null +++ b/ppocr/utils/dict/fa_dict.txt @@ -0,0 +1,136 @@ +f +a +_ +i +m +g +/ +1 +3 +I +L +S +V +R +C +2 +0 +v +l +6 +8 +5 +. +j +p +و +د +ر +ك +ن +ش +ه +ا +4 +9 +ی +ج +ِ +7 +غ +ل +س +ز +ّ +ت +ک +گ +ي +م +ب +ف +چ +خ +ق +ژ +آ +ص +پ +َ +ع +ئ +ح +ٔ +ض +ُ +ذ +أ +ى +ط +ظ +ث +ة +ً +ء +ؤ +ْ +ۀ +إ +ٍ +ٌ +ٰ +ٓ +ٱ +s +c +e +n +w +N +E +W +Y +D +O +H +A +d +z +r +T +G +o +t +x +h +b +B +M +Z +u +P +F +y +q +U +K +k +J +Q +' +X +# +? +% +$ +, +: +& +! +- +( +É +@ +é ++ + diff --git a/ppocr/utils/dict/french_dict.txt b/ppocr/utils/dict/french_dict.txt new file mode 100644 index 0000000..e8f657d --- /dev/null +++ b/ppocr/utils/dict/french_dict.txt @@ -0,0 +1,136 @@ +f +e +n +c +h +_ +i +m +g +/ +r +v +a +l +t +w +o +d +6 +1 +. +p +B +u +2 +à +3 +R +y +4 +U +E +A +5 +P +O +S +T +D +7 +Z +8 +I +N +L +G +M +H +0 +J +K +- +9 +F +C +V +é +X +' +s +Q +: +è +x +b +Y +Œ +É +z +W +Ç +È +k +Ô +ô +€ +À +Ê +q +ù +° +ê +î +* + +j +" +, +â +% +û +ç +ü +? +! +; +ö +( +) +ï +º +ó +ø +å ++ +™ +á +Ë +< +² +Á +Î +& +@ +œ +ε +Ü +ë +[ +] +í +ò +Ö +ä +ß +« +» +ú +ñ +æ +µ +³ +Å +$ +# + diff --git a/ppocr/utils/dict/german_dict.txt b/ppocr/utils/dict/german_dict.txt new file mode 100644 index 0000000..5e121af --- /dev/null +++ b/ppocr/utils/dict/german_dict.txt @@ -0,0 +1,143 @@ + +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; += +> +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +£ +§ +­ +° +´ +µ +· +º +¿ +Á +Ä +Å +É +Ï +Ô +Ö +Ü +ß +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +í +ï +ñ +ò +ó +ô +ö +ø +ù +ú +û +ü +ō +Š +Ÿ +ʒ +β +δ +з +Ṡ +‘ +€ +© +ª +« +¬ diff --git a/ppocr/utils/dict/gujarati_dict.txt b/ppocr/utils/dict/gujarati_dict.txt new file mode 100644 index 0000000..36f5589 --- /dev/null +++ b/ppocr/utils/dict/gujarati_dict.txt @@ -0,0 +1,48 @@ +અ +આ +ઇ +ઈ +ઉ +ઊ +ઋ +ઌ +એ +ઐ +ઓ +ઔ +અં +અઃ +ક +ખ +ગ +ઘ +ઙ +ચ +છ +જ +ઝ +ઞ +ટ +ઠ +ડ +ઢ +ણ +ત +થ +દ +ધ +ન +પ +ફ +બ +ભ +મ +ય +ર +લ +ળ +વ +શ +ષ +સ +હ diff --git a/ppocr/utils/dict/hebrew_dict.txt b/ppocr/utils/dict/hebrew_dict.txt new file mode 100644 index 0000000..ed30137 --- /dev/null +++ b/ppocr/utils/dict/hebrew_dict.txt @@ -0,0 +1,214 @@ +! +# +$ +% +& +' +( ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +É +é +֑ +֒ +֓ +֔ +֕ +֖ +֗ +֘ +֙ +֚ +֛ +֜ +֝ +֞ +֟ +֠ +֡ +֢ +֣ +֤ +֥ +֦ +֧ +֨ +֩ +֪ +֫ +֬ +֭ +֮ +֯ +ְ +ֱ +ֲ +ֳ +ִ +ֵ +ֶ +ַ +ָ +ֹ +ֺ +ֻ +ּ +ֽ +־ +ֿ +׀ +ׁ +ׂ +׃ +ׄ +ׅ +׆ +ׇ +א +ב +ג +ד +ה +ו +ז +ח +ט +י +ך +כ +ל +ם +מ +ן +נ +ס +ע +ף +פ +ץ +צ +ק +ר +ש +ת +ׯ +װ +ױ +ײ +׳ +״ +יִ +ﬞ +ײַ +ﬠ +ﬡ +ﬢ +ﬣ +ﬤ +ﬥ +ﬦ +ﬧ +ﬨ +﬩ +שׁ +שׂ +שּׁ +שּׂ +אַ +אָ +אּ +בּ +גּ +דּ +הּ +וּ +זּ +טּ +יּ +ךּ +כּ +לּ +מּ +נּ +סּ +ףּ +פּ +צּ +קּ +רּ +שּ +תּ +וֹ +בֿ +כֿ +פֿ +ﭏ diff --git a/ppocr/utils/dict/hi_dict.txt b/ppocr/utils/dict/hi_dict.txt new file mode 100644 index 0000000..8dfedb5 --- /dev/null +++ b/ppocr/utils/dict/hi_dict.txt @@ -0,0 +1,162 @@ + +! +# +$ +% +& +' +( ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +É +é +ँ +ं +ः +अ +आ +इ +ई +उ +ऊ +ऋ +ए +ऐ +ऑ +ओ +औ +क +ख +ग +घ +ङ +च +छ +ज +झ +ञ +ट +ठ +ड +ढ +ण +त +थ +द +ध +न +प +फ +ब +भ +म +य +र +ल +ळ +व +श +ष +स +ह +़ +ा +ि +ी +ु +ू +ृ +ॅ +े +ै +ॉ +ो +ौ +् +क़ +ख़ +ग़ +ज़ +ड़ +ढ़ +फ़ +० +१ +२ +३ +४ +५ +६ +७ +८ +९ +॰ diff --git a/ppocr/utils/dict/it_dict.txt b/ppocr/utils/dict/it_dict.txt new file mode 100644 index 0000000..e692c6d --- /dev/null +++ b/ppocr/utils/dict/it_dict.txt @@ -0,0 +1,118 @@ +i +t +_ +m +g +/ +5 +I +L +S +V +R +C +2 +0 +1 +v +a +l +7 +8 +9 +6 +. +j +p + +e +r +o +d +s +n +3 +4 +P +u +c +A +- +, +" +z +h +f +b +q +ì +' +à +O +è +G +ù +é +ò +; +F +E +B +N +H +k +: +U +T +X +D +K +? +[ +M +­ +x +y +( +) +W +ö +º +w +] +Q +J ++ +ü +! +È +á +% += +» +ñ +Ö +Y +ä +í +Z +« +@ +ó +ø +ï +ú +ê +ç +Á +É +Å +ß +{ +} +& +` +û +î +# +$ diff --git a/ppocr/utils/dict/japan_dict.txt b/ppocr/utils/dict/japan_dict.txt new file mode 100644 index 0000000..339d4b8 --- /dev/null +++ b/ppocr/utils/dict/japan_dict.txt @@ -0,0 +1,4399 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +© +° +² +´ +½ +Á +Ä +Å +Ç +È +É +Í +Ó +Ö +× +Ü +ß +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +í +ð +ñ +ò +ó +ô +õ +ö +ø +ú +û +ü +ý +ā +ă +ą +ć +Č +č +đ +ē +ė +ę +ğ +ī +ı +Ł +ł +ń +ň +ō +ř +Ş +ş +Š +š +ţ +ū +ż +Ž +ž +Ș +ș +ț +Δ +α +λ +μ +φ +Г +О +а +в +л +о +р +с +т +я +ồ +​ +— +― +’ +“ +” +… +℃ +→ +∇ +− +■ +☆ +  +、 +。 +々 +〆 +〈 +〉 +「 +」 +『 +』 +〔 +〕 +〜 +ぁ +あ +ぃ +い +う +ぇ +え +ぉ +お +か +が +き +ぎ +く +ぐ +け +げ +こ +ご +さ +ざ +し +じ +す +ず +せ +ぜ +そ +ぞ +た +だ +ち +ぢ +っ +つ +づ +て +で +と +ど +な +に +ぬ +ね +の +は +ば +ぱ +ひ +び +ぴ +ふ +ぶ +ぷ +へ +べ +ぺ +ほ +ぼ +ぽ +ま +み +む +め +も +ゃ +や +ゅ +ゆ +ょ +よ +ら +り +る +れ +ろ +わ +ゑ +を +ん +ゝ +ゞ +ァ +ア +ィ +イ +ゥ +ウ +ェ +エ +ォ +オ +カ +ガ +キ +ギ +ク +グ +ケ +ゲ +コ +ゴ +サ +ザ +シ +ジ +ス +ズ +セ +ゼ +ソ +ゾ +タ +ダ +チ +ヂ +ッ +ツ +ヅ +テ +デ +ト +ド +ナ +ニ +ヌ +ネ +ノ +ハ +バ +パ +ヒ +ビ +ピ +フ +ブ +プ +ヘ +ベ +ペ +ホ +ボ +ポ +マ +ミ +ム +メ +モ +ャ +ヤ +ュ +ユ +ョ +ヨ +ラ +リ +ル +レ +ロ +ワ +ヰ +ン +ヴ +ヵ +ヶ +・ +ー +㈱ +一 +丁 +七 +万 +丈 +三 +上 +下 +不 +与 +丑 +且 +世 +丘 +丙 +丞 +両 +並 +中 +串 +丸 +丹 +主 +丼 +丿 +乃 +久 +之 +乎 +乏 +乗 +乘 +乙 +九 +乞 +也 +乱 +乳 +乾 +亀 +了 +予 +争 +事 +二 +于 +互 +五 +井 +亘 +亙 +些 +亜 +亟 +亡 +交 +亥 +亦 +亨 +享 +京 +亭 +亮 +人 +什 +仁 +仇 +今 +介 +仍 +仏 +仔 +仕 +他 +仗 +付 +仙 +代 +令 +以 +仮 +仰 +仲 +件 +任 +企 +伊 +伍 +伎 +伏 +伐 +休 +会 +伝 +伯 +估 +伴 +伶 +伸 +伺 +似 +伽 +佃 +但 +位 +低 +住 +佐 +佑 +体 +何 +余 +佚 +佛 +作 +佩 +佳 +併 +佶 +使 +侈 +例 +侍 +侏 +侑 +侘 +供 +依 +侠 +価 +侮 +侯 +侵 +侶 +便 +係 +促 +俄 +俊 +俔 +俗 +俘 +保 +信 +俣 +俤 +修 +俯 +俳 +俵 +俸 +俺 +倉 +個 +倍 +倒 +候 +借 +倣 +値 +倫 +倭 +倶 +倹 +偃 +假 +偈 +偉 +偏 +偐 +偕 +停 +健 +側 +偵 +偶 +偽 +傀 +傅 +傍 +傑 +傘 +備 +催 +傭 +傲 +傳 +債 +傷 +傾 +僊 +働 +像 +僑 +僕 +僚 +僧 +僭 +僮 +儀 +億 +儇 +儒 +儛 +償 +儡 +優 +儲 +儺 +儼 +兀 +允 +元 +兄 +充 +兆 +先 +光 +克 +兌 +免 +兎 +児 +党 +兜 +入 +全 +八 +公 +六 +共 +兵 +其 +具 +典 +兼 +内 +円 +冊 +再 +冑 +冒 +冗 +写 +冠 +冤 +冥 +冨 +冬 +冲 +决 +冶 +冷 +准 +凉 +凋 +凌 +凍 +凛 +凝 +凞 +几 +凡 +処 +凪 +凰 +凱 +凶 +凸 +凹 +出 +函 +刀 +刃 +分 +切 +刈 +刊 +刎 +刑 +列 +初 +判 +別 +利 +刪 +到 +制 +刷 +券 +刹 +刺 +刻 +剃 +則 +削 +剋 +前 +剖 +剛 +剣 +剤 +剥 +剪 +副 +剰 +割 +創 +剽 +劇 +劉 +劔 +力 +功 +加 +劣 +助 +努 +劫 +劭 +励 +労 +効 +劾 +勃 +勅 +勇 +勉 +勒 +動 +勘 +務 +勝 +募 +勢 +勤 +勧 +勲 +勺 +勾 +勿 +匁 +匂 +包 +匏 +化 +北 +匙 +匝 +匠 +匡 +匣 +匯 +匲 +匹 +区 +医 +匿 +十 +千 +升 +午 +卉 +半 +卍 +卑 +卒 +卓 +協 +南 +単 +博 +卜 +占 +卦 +卯 +印 +危 +即 +却 +卵 +卸 +卿 +厄 +厚 +原 +厠 +厨 +厩 +厭 +厳 +去 +参 +又 +叉 +及 +友 +双 +反 +収 +叔 +取 +受 +叙 +叛 +叟 +叡 +叢 +口 +古 +句 +叩 +只 +叫 +召 +可 +台 +叱 +史 +右 +叶 +号 +司 +吃 +各 +合 +吉 +吊 +同 +名 +后 +吏 +吐 +向 +君 +吝 +吟 +吠 +否 +含 +吸 +吹 +吻 +吽 +吾 +呂 +呆 +呈 +呉 +告 +呑 +周 +呪 +呰 +味 +呼 +命 +咀 +咄 +咋 +和 +咒 +咫 +咲 +咳 +咸 +哀 +品 +哇 +哉 +員 +哨 +哩 +哭 +哲 +哺 +唄 +唆 +唇 +唐 +唖 +唯 +唱 +唳 +唸 +唾 +啄 +商 +問 +啓 +啼 +善 +喋 +喚 +喜 +喝 +喧 +喩 +喪 +喫 +喬 +單 +喰 +営 +嗅 +嗇 +嗔 +嗚 +嗜 +嗣 +嘆 +嘉 +嘗 +嘘 +嘩 +嘯 +嘱 +嘲 +嘴 +噂 +噌 +噛 +器 +噴 +噺 +嚆 +嚢 +囀 +囃 +囉 +囚 +四 +回 +因 +団 +困 +囲 +図 +固 +国 +圀 +圃 +國 +圏 +園 +圓 +團 +圜 +土 +圧 +在 +圭 +地 +址 +坂 +均 +坊 +坐 +坑 +坡 +坤 +坦 +坪 +垂 +型 +垢 +垣 +埃 +埋 +城 +埒 +埔 +域 +埠 +埴 +埵 +執 +培 +基 +埼 +堀 +堂 +堅 +堆 +堕 +堤 +堪 +堯 +堰 +報 +場 +堵 +堺 +塀 +塁 +塊 +塑 +塔 +塗 +塘 +塙 +塚 +塞 +塩 +填 +塵 +塾 +境 +墉 +墓 +増 +墜 +墟 +墨 +墳 +墺 +墻 +墾 +壁 +壇 +壊 +壌 +壕 +士 +壬 +壮 +声 +壱 +売 +壷 +壹 +壺 +壽 +変 +夏 +夕 +外 +夙 +多 +夜 +夢 +夥 +大 +天 +太 +夫 +夬 +夭 +央 +失 +夷 +夾 +奄 +奇 +奈 +奉 +奎 +奏 +契 +奔 +奕 +套 +奘 +奠 +奢 +奥 +奨 +奪 +奮 +女 +奴 +奸 +好 +如 +妃 +妄 +妊 +妍 +妓 +妖 +妙 +妥 +妨 +妬 +妲 +妹 +妻 +妾 +姉 +始 +姐 +姓 +委 +姚 +姜 +姞 +姥 +姦 +姨 +姪 +姫 +姶 +姻 +姿 +威 +娑 +娘 +娟 +娠 +娩 +娯 +娼 +婆 +婉 +婚 +婢 +婦 +婬 +婿 +媄 +媒 +媓 +媚 +媛 +媞 +媽 +嫁 +嫄 +嫉 +嫌 +嫐 +嫗 +嫡 +嬉 +嬌 +嬢 +嬪 +嬬 +嬾 +孁 +子 +孔 +字 +存 +孚 +孝 +孟 +季 +孤 +学 +孫 +孵 +學 +宅 +宇 +守 +安 +宋 +完 +宍 +宏 +宕 +宗 +官 +宙 +定 +宛 +宜 +宝 +実 +客 +宣 +室 +宥 +宮 +宰 +害 +宴 +宵 +家 +宸 +容 +宿 +寂 +寄 +寅 +密 +寇 +富 +寒 +寓 +寔 +寛 +寝 +察 +寡 +實 +寧 +審 +寮 +寵 +寶 +寸 +寺 +対 +寿 +封 +専 +射 +将 +尉 +尊 +尋 +對 +導 +小 +少 +尖 +尚 +尤 +尪 +尭 +就 +尹 +尺 +尻 +尼 +尽 +尾 +尿 +局 +居 +屈 +届 +屋 +屍 +屎 +屏 +屑 +屓 +展 +属 +屠 +層 +履 +屯 +山 +岐 +岑 +岡 +岩 +岫 +岬 +岳 +岷 +岸 +峠 +峡 +峨 +峯 +峰 +島 +峻 +崇 +崋 +崎 +崑 +崖 +崗 +崛 +崩 +嵌 +嵐 +嵩 +嵯 +嶂 +嶋 +嶠 +嶺 +嶼 +嶽 +巀 +巌 +巒 +巖 +川 +州 +巡 +巣 +工 +左 +巧 +巨 +巫 +差 +己 +巳 +巴 +巷 +巻 +巽 +巾 +市 +布 +帆 +希 +帖 +帚 +帛 +帝 +帥 +師 +席 +帯 +帰 +帳 +帷 +常 +帽 +幄 +幅 +幇 +幌 +幔 +幕 +幟 +幡 +幢 +幣 +干 +平 +年 +并 +幸 +幹 +幻 +幼 +幽 +幾 +庁 +広 +庄 +庇 +床 +序 +底 +庖 +店 +庚 +府 +度 +座 +庫 +庭 +庵 +庶 +康 +庸 +廂 +廃 +廉 +廊 +廓 +廟 +廠 +廣 +廬 +延 +廷 +建 +廻 +廼 +廿 +弁 +弄 +弉 +弊 +弌 +式 +弐 +弓 +弔 +引 +弖 +弗 +弘 +弛 +弟 +弥 +弦 +弧 +弱 +張 +強 +弼 +弾 +彈 +彊 +彌 +彎 +当 +彗 +彙 +彝 +形 +彦 +彩 +彫 +彬 +彭 +彰 +影 +彷 +役 +彼 +往 +征 +徂 +径 +待 +律 +後 +徐 +徑 +徒 +従 +得 +徠 +御 +徧 +徨 +復 +循 +徭 +微 +徳 +徴 +德 +徹 +徽 +心 +必 +忉 +忌 +忍 +志 +忘 +忙 +応 +忠 +快 +忯 +念 +忻 +忽 +忿 +怒 +怖 +思 +怠 +怡 +急 +性 +怨 +怪 +怯 +恂 +恋 +恐 +恒 +恕 +恣 +恤 +恥 +恨 +恩 +恬 +恭 +息 +恵 +悉 +悌 +悍 +悔 +悟 +悠 +患 +悦 +悩 +悪 +悲 +悼 +情 +惇 +惑 +惚 +惜 +惟 +惠 +惣 +惧 +惨 +惰 +想 +惹 +惺 +愈 +愉 +愍 +意 +愔 +愚 +愛 +感 +愷 +愿 +慈 +態 +慌 +慎 +慕 +慢 +慣 +慧 +慨 +慮 +慰 +慶 +憂 +憎 +憐 +憑 +憙 +憤 +憧 +憩 +憬 +憲 +憶 +憾 +懇 +應 +懌 +懐 +懲 +懸 +懺 +懽 +懿 +戈 +戊 +戌 +戎 +成 +我 +戒 +戔 +或 +戚 +戟 +戦 +截 +戮 +戯 +戴 +戸 +戻 +房 +所 +扁 +扇 +扈 +扉 +手 +才 +打 +払 +托 +扮 +扱 +扶 +批 +承 +技 +抄 +把 +抑 +抓 +投 +抗 +折 +抜 +択 +披 +抱 +抵 +抹 +押 +抽 +担 +拇 +拈 +拉 +拍 +拏 +拐 +拒 +拓 +拘 +拙 +招 +拝 +拠 +拡 +括 +拭 +拳 +拵 +拶 +拾 +拿 +持 +挂 +指 +按 +挑 +挙 +挟 +挨 +振 +挺 +挽 +挿 +捉 +捕 +捗 +捜 +捧 +捨 +据 +捺 +捻 +掃 +掄 +授 +掌 +排 +掖 +掘 +掛 +掟 +採 +探 +掣 +接 +控 +推 +掩 +措 +掬 +掲 +掴 +掻 +掾 +揃 +揄 +揆 +揉 +描 +提 +揖 +揚 +換 +握 +揮 +援 +揶 +揺 +損 +搦 +搬 +搭 +携 +搾 +摂 +摘 +摩 +摸 +摺 +撃 +撒 +撞 +撤 +撥 +撫 +播 +撮 +撰 +撲 +撹 +擁 +操 +擔 +擦 +擬 +擾 +攘 +攝 +攣 +支 +收 +改 +攻 +放 +政 +故 +敏 +救 +敗 +教 +敢 +散 +敦 +敬 +数 +整 +敵 +敷 +斂 +文 +斉 +斎 +斐 +斑 +斗 +料 +斜 +斟 +斤 +斥 +斧 +斬 +断 +斯 +新 +方 +於 +施 +旁 +旅 +旋 +旌 +族 +旗 +旛 +无 +旡 +既 +日 +旦 +旧 +旨 +早 +旬 +旭 +旺 +旻 +昂 +昆 +昇 +昉 +昌 +明 +昏 +易 +昔 +星 +映 +春 +昧 +昨 +昪 +昭 +是 +昵 +昼 +晁 +時 +晃 +晋 +晏 +晒 +晟 +晦 +晧 +晩 +普 +景 +晴 +晶 +智 +暁 +暇 +暈 +暉 +暑 +暖 +暗 +暘 +暢 +暦 +暫 +暮 +暲 +暴 +暹 +暾 +曄 +曇 +曉 +曖 +曙 +曜 +曝 +曠 +曰 +曲 +曳 +更 +書 +曹 +曼 +曽 +曾 +替 +最 +會 +月 +有 +朋 +服 +朏 +朔 +朕 +朗 +望 +朝 +期 +朧 +木 +未 +末 +本 +札 +朱 +朴 +机 +朽 +杁 +杉 +李 +杏 +材 +村 +杓 +杖 +杜 +杞 +束 +条 +杢 +杣 +来 +杭 +杮 +杯 +東 +杲 +杵 +杷 +杼 +松 +板 +枅 +枇 +析 +枓 +枕 +林 +枚 +果 +枝 +枠 +枡 +枢 +枯 +枳 +架 +柄 +柊 +柏 +某 +柑 +染 +柔 +柘 +柚 +柯 +柱 +柳 +柴 +柵 +査 +柾 +柿 +栂 +栃 +栄 +栖 +栗 +校 +株 +栲 +栴 +核 +根 +栻 +格 +栽 +桁 +桂 +桃 +框 +案 +桐 +桑 +桓 +桔 +桜 +桝 +桟 +桧 +桴 +桶 +桾 +梁 +梅 +梆 +梓 +梔 +梗 +梛 +條 +梟 +梢 +梧 +梨 +械 +梱 +梲 +梵 +梶 +棄 +棋 +棒 +棗 +棘 +棚 +棟 +棠 +森 +棲 +棹 +棺 +椀 +椅 +椋 +植 +椎 +椏 +椒 +椙 +検 +椥 +椹 +椿 +楊 +楓 +楕 +楚 +楞 +楠 +楡 +楢 +楨 +楪 +楫 +業 +楮 +楯 +楳 +極 +楷 +楼 +楽 +概 +榊 +榎 +榕 +榛 +榜 +榮 +榱 +榴 +槃 +槇 +槊 +構 +槌 +槍 +槐 +様 +槙 +槻 +槽 +槿 +樂 +樋 +樓 +樗 +標 +樟 +模 +権 +横 +樫 +樵 +樹 +樺 +樽 +橇 +橋 +橘 +機 +橿 +檀 +檄 +檎 +檐 +檗 +檜 +檣 +檥 +檬 +檮 +檸 +檻 +櫃 +櫓 +櫛 +櫟 +櫨 +櫻 +欄 +欅 +欠 +次 +欣 +欧 +欲 +欺 +欽 +款 +歌 +歎 +歓 +止 +正 +此 +武 +歩 +歪 +歯 +歳 +歴 +死 +殆 +殉 +殊 +残 +殖 +殯 +殴 +段 +殷 +殺 +殻 +殿 +毀 +毅 +母 +毎 +毒 +比 +毘 +毛 +毫 +毬 +氈 +氏 +民 +気 +水 +氷 +永 +氾 +汀 +汁 +求 +汎 +汐 +汗 +汚 +汝 +江 +池 +汪 +汰 +汲 +決 +汽 +沂 +沃 +沅 +沆 +沈 +沌 +沐 +沓 +沖 +沙 +没 +沢 +沱 +河 +沸 +油 +治 +沼 +沽 +沿 +況 +泉 +泊 +泌 +法 +泗 +泡 +波 +泣 +泥 +注 +泯 +泰 +泳 +洋 +洒 +洗 +洛 +洞 +津 +洩 +洪 +洲 +洸 +洹 +活 +洽 +派 +流 +浄 +浅 +浙 +浚 +浜 +浣 +浦 +浩 +浪 +浮 +浴 +海 +浸 +涅 +消 +涌 +涙 +涛 +涯 +液 +涵 +涼 +淀 +淄 +淆 +淇 +淋 +淑 +淘 +淡 +淤 +淨 +淫 +深 +淳 +淵 +混 +淹 +添 +清 +済 +渉 +渋 +渓 +渕 +渚 +減 +渟 +渠 +渡 +渤 +渥 +渦 +温 +渫 +測 +港 +游 +渾 +湊 +湖 +湘 +湛 +湧 +湫 +湯 +湾 +湿 +満 +源 +準 +溜 +溝 +溢 +溥 +溪 +溶 +溺 +滄 +滅 +滋 +滌 +滑 +滕 +滝 +滞 +滴 +滸 +滹 +滿 +漁 +漂 +漆 +漉 +漏 +漑 +演 +漕 +漠 +漢 +漣 +漫 +漬 +漱 +漸 +漿 +潅 +潔 +潙 +潜 +潟 +潤 +潭 +潮 +潰 +潴 +澁 +澂 +澄 +澎 +澗 +澤 +澪 +澱 +澳 +激 +濁 +濃 +濟 +濠 +濡 +濤 +濫 +濯 +濱 +濾 +瀉 +瀋 +瀑 +瀕 +瀞 +瀟 +瀧 +瀬 +瀾 +灌 +灑 +灘 +火 +灯 +灰 +灸 +災 +炉 +炊 +炎 +炒 +炭 +炮 +炷 +点 +為 +烈 +烏 +烙 +烝 +烹 +焔 +焙 +焚 +無 +焦 +然 +焼 +煇 +煉 +煌 +煎 +煕 +煙 +煤 +煥 +照 +煩 +煬 +煮 +煽 +熈 +熊 +熙 +熟 +熨 +熱 +熹 +熾 +燃 +燈 +燎 +燔 +燕 +燗 +燥 +燭 +燻 +爆 +爐 +爪 +爬 +爲 +爵 +父 +爺 +爼 +爽 +爾 +片 +版 +牌 +牒 +牘 +牙 +牛 +牝 +牟 +牡 +牢 +牧 +物 +牲 +特 +牽 +犂 +犠 +犬 +犯 +状 +狂 +狄 +狐 +狗 +狙 +狛 +狡 +狩 +独 +狭 +狷 +狸 +狼 +猊 +猛 +猟 +猥 +猨 +猩 +猪 +猫 +献 +猴 +猶 +猷 +猾 +猿 +獄 +獅 +獏 +獣 +獲 +玄 +玅 +率 +玉 +王 +玖 +玩 +玲 +珀 +珂 +珈 +珉 +珊 +珍 +珎 +珞 +珠 +珣 +珥 +珪 +班 +現 +球 +理 +琉 +琢 +琥 +琦 +琮 +琲 +琳 +琴 +琵 +琶 +瑁 +瑋 +瑙 +瑚 +瑛 +瑜 +瑞 +瑠 +瑤 +瑩 +瑪 +瑳 +瑾 +璃 +璋 +璜 +璞 +璧 +璨 +環 +璵 +璽 +璿 +瓊 +瓔 +瓜 +瓢 +瓦 +瓶 +甍 +甑 +甕 +甘 +甚 +甞 +生 +産 +甥 +用 +甫 +田 +由 +甲 +申 +男 +町 +画 +界 +畏 +畑 +畔 +留 +畜 +畝 +畠 +畢 +略 +番 +異 +畳 +當 +畷 +畸 +畺 +畿 +疆 +疇 +疋 +疎 +疏 +疑 +疫 +疱 +疲 +疹 +疼 +疾 +病 +症 +痒 +痔 +痕 +痘 +痙 +痛 +痢 +痩 +痴 +痺 +瘍 +瘡 +瘧 +療 +癇 +癌 +癒 +癖 +癡 +癪 +発 +登 +白 +百 +的 +皆 +皇 +皋 +皐 +皓 +皮 +皺 +皿 +盂 +盃 +盆 +盈 +益 +盒 +盗 +盛 +盞 +盟 +盡 +監 +盤 +盥 +盧 +目 +盲 +直 +相 +盾 +省 +眉 +看 +県 +眞 +真 +眠 +眷 +眺 +眼 +着 +睡 +督 +睦 +睨 +睿 +瞋 +瞑 +瞞 +瞬 +瞭 +瞰 +瞳 +瞻 +瞼 +瞿 +矍 +矛 +矜 +矢 +知 +矧 +矩 +短 +矮 +矯 +石 +砂 +砌 +研 +砕 +砥 +砦 +砧 +砲 +破 +砺 +硝 +硫 +硬 +硯 +碁 +碇 +碌 +碑 +碓 +碕 +碗 +碣 +碧 +碩 +確 +碾 +磁 +磐 +磔 +磧 +磨 +磬 +磯 +礁 +礎 +礒 +礙 +礫 +礬 +示 +礼 +社 +祀 +祁 +祇 +祈 +祉 +祐 +祓 +祕 +祖 +祗 +祚 +祝 +神 +祟 +祠 +祢 +祥 +票 +祭 +祷 +祺 +禁 +禄 +禅 +禊 +禍 +禎 +福 +禔 +禖 +禛 +禦 +禧 +禮 +禰 +禹 +禽 +禿 +秀 +私 +秋 +科 +秒 +秘 +租 +秤 +秦 +秩 +称 +移 +稀 +程 +税 +稔 +稗 +稙 +稚 +稜 +稠 +種 +稱 +稲 +稷 +稻 +稼 +稽 +稿 +穀 +穂 +穆 +積 +穎 +穏 +穗 +穜 +穢 +穣 +穫 +穴 +究 +空 +突 +窃 +窄 +窒 +窓 +窟 +窠 +窩 +窪 +窮 +窯 +竃 +竄 +竈 +立 +站 +竜 +竝 +竟 +章 +童 +竪 +竭 +端 +竴 +競 +竹 +竺 +竽 +竿 +笄 +笈 +笏 +笑 +笙 +笛 +笞 +笠 +笥 +符 +第 +笹 +筅 +筆 +筇 +筈 +等 +筋 +筌 +筍 +筏 +筐 +筑 +筒 +答 +策 +筝 +筥 +筧 +筬 +筮 +筯 +筰 +筵 +箆 +箇 +箋 +箏 +箒 +箔 +箕 +算 +箙 +箜 +管 +箪 +箭 +箱 +箸 +節 +篁 +範 +篆 +篇 +築 +篋 +篌 +篝 +篠 +篤 +篥 +篦 +篩 +篭 +篳 +篷 +簀 +簒 +簡 +簧 +簪 +簫 +簺 +簾 +簿 +籀 +籃 +籌 +籍 +籐 +籟 +籠 +籤 +籬 +米 +籾 +粂 +粉 +粋 +粒 +粕 +粗 +粘 +粛 +粟 +粥 +粧 +粮 +粳 +精 +糊 +糖 +糜 +糞 +糟 +糠 +糧 +糯 +糸 +糺 +系 +糾 +紀 +約 +紅 +紋 +納 +紐 +純 +紗 +紘 +紙 +級 +紛 +素 +紡 +索 +紫 +紬 +累 +細 +紳 +紵 +紹 +紺 +絁 +終 +絃 +組 +絅 +経 +結 +絖 +絞 +絡 +絣 +給 +統 +絲 +絵 +絶 +絹 +絽 +綏 +經 +継 +続 +綜 +綟 +綬 +維 +綱 +網 +綴 +綸 +綺 +綽 +綾 +綿 +緊 +緋 +総 +緑 +緒 +線 +締 +緥 +編 +緩 +緬 +緯 +練 +緻 +縁 +縄 +縅 +縒 +縛 +縞 +縢 +縣 +縦 +縫 +縮 +縹 +總 +績 +繁 +繊 +繋 +繍 +織 +繕 +繝 +繦 +繧 +繰 +繹 +繼 +纂 +纈 +纏 +纐 +纒 +纛 +缶 +罔 +罠 +罧 +罪 +置 +罰 +署 +罵 +罷 +罹 +羂 +羅 +羆 +羇 +羈 +羊 +羌 +美 +群 +羨 +義 +羯 +羲 +羹 +羽 +翁 +翅 +翌 +習 +翔 +翛 +翠 +翡 +翫 +翰 +翺 +翻 +翼 +耀 +老 +考 +者 +耆 +而 +耐 +耕 +耗 +耨 +耳 +耶 +耽 +聊 +聖 +聘 +聚 +聞 +聟 +聡 +聨 +聯 +聰 +聲 +聴 +職 +聾 +肄 +肆 +肇 +肉 +肋 +肌 +肖 +肘 +肛 +肝 +股 +肢 +肥 +肩 +肪 +肯 +肱 +育 +肴 +肺 +胃 +胆 +背 +胎 +胖 +胚 +胝 +胞 +胡 +胤 +胱 +胴 +胸 +能 +脂 +脅 +脆 +脇 +脈 +脊 +脚 +脛 +脩 +脱 +脳 +腋 +腎 +腐 +腑 +腔 +腕 +腫 +腰 +腱 +腸 +腹 +腺 +腿 +膀 +膏 +膚 +膜 +膝 +膠 +膣 +膨 +膩 +膳 +膵 +膾 +膿 +臂 +臆 +臈 +臍 +臓 +臘 +臚 +臣 +臥 +臨 +自 +臭 +至 +致 +臺 +臼 +舂 +舅 +與 +興 +舌 +舍 +舎 +舒 +舖 +舗 +舘 +舜 +舞 +舟 +舩 +航 +般 +舳 +舶 +船 +艇 +艘 +艦 +艮 +良 +色 +艶 +芋 +芒 +芙 +芝 +芥 +芦 +芬 +芭 +芯 +花 +芳 +芸 +芹 +芻 +芽 +芿 +苅 +苑 +苔 +苗 +苛 +苞 +苡 +若 +苦 +苧 +苫 +英 +苴 +苻 +茂 +范 +茄 +茅 +茎 +茗 +茘 +茜 +茨 +茲 +茵 +茶 +茸 +茹 +草 +荊 +荏 +荒 +荘 +荷 +荻 +荼 +莞 +莪 +莫 +莬 +莱 +莵 +莽 +菅 +菊 +菌 +菓 +菖 +菘 +菜 +菟 +菩 +菫 +華 +菱 +菴 +萄 +萊 +萌 +萍 +萎 +萠 +萩 +萬 +萱 +落 +葉 +著 +葛 +葡 +董 +葦 +葩 +葬 +葭 +葱 +葵 +葺 +蒋 +蒐 +蒔 +蒙 +蒟 +蒡 +蒲 +蒸 +蒻 +蒼 +蒿 +蓄 +蓆 +蓉 +蓋 +蓑 +蓬 +蓮 +蓼 +蔀 +蔑 +蔓 +蔚 +蔡 +蔦 +蔬 +蔭 +蔵 +蔽 +蕃 +蕉 +蕊 +蕎 +蕨 +蕩 +蕪 +蕭 +蕾 +薄 +薇 +薊 +薔 +薗 +薙 +薛 +薦 +薨 +薩 +薪 +薫 +薬 +薭 +薮 +藁 +藉 +藍 +藏 +藐 +藝 +藤 +藩 +藪 +藷 +藹 +藺 +藻 +蘂 +蘆 +蘇 +蘊 +蘭 +虎 +虐 +虔 +虚 +虜 +虞 +號 +虫 +虹 +虻 +蚊 +蚕 +蛇 +蛉 +蛍 +蛎 +蛙 +蛛 +蛟 +蛤 +蛭 +蛮 +蛸 +蛹 +蛾 +蜀 +蜂 +蜃 +蜆 +蜊 +蜘 +蜜 +蜷 +蜻 +蝉 +蝋 +蝕 +蝙 +蝠 +蝦 +蝶 +蝿 +螂 +融 +螣 +螺 +蟄 +蟇 +蟠 +蟷 +蟹 +蟻 +蠢 +蠣 +血 +衆 +行 +衍 +衒 +術 +街 +衙 +衛 +衝 +衞 +衡 +衢 +衣 +表 +衫 +衰 +衵 +衷 +衽 +衾 +衿 +袁 +袈 +袋 +袍 +袒 +袖 +袙 +袞 +袢 +被 +袰 +袱 +袴 +袷 +袿 +裁 +裂 +裃 +装 +裏 +裔 +裕 +裘 +裙 +補 +裟 +裡 +裲 +裳 +裴 +裸 +裹 +製 +裾 +褂 +褄 +複 +褌 +褐 +褒 +褥 +褪 +褶 +褻 +襄 +襖 +襞 +襟 +襠 +襦 +襪 +襲 +襴 +襷 +西 +要 +覆 +覇 +覈 +見 +規 +視 +覗 +覚 +覧 +親 +覲 +観 +覺 +觀 +角 +解 +触 +言 +訂 +計 +討 +訓 +託 +記 +訛 +訟 +訢 +訥 +訪 +設 +許 +訳 +訴 +訶 +診 +註 +証 +詐 +詔 +評 +詛 +詞 +詠 +詢 +詣 +試 +詩 +詫 +詮 +詰 +話 +該 +詳 +誄 +誅 +誇 +誉 +誌 +認 +誓 +誕 +誘 +語 +誠 +誡 +誣 +誤 +誥 +誦 +説 +読 +誰 +課 +誼 +誾 +調 +談 +請 +諌 +諍 +諏 +諒 +論 +諚 +諜 +諟 +諡 +諦 +諧 +諫 +諭 +諮 +諱 +諶 +諷 +諸 +諺 +諾 +謀 +謄 +謌 +謎 +謗 +謙 +謚 +講 +謝 +謡 +謫 +謬 +謹 +證 +識 +譚 +譛 +譜 +警 +譬 +譯 +議 +譲 +譴 +護 +讀 +讃 +讐 +讒 +谷 +谿 +豅 +豆 +豊 +豎 +豐 +豚 +象 +豪 +豫 +豹 +貌 +貝 +貞 +負 +財 +貢 +貧 +貨 +販 +貪 +貫 +責 +貯 +貰 +貴 +買 +貸 +費 +貼 +貿 +賀 +賁 +賂 +賃 +賄 +資 +賈 +賊 +賎 +賑 +賓 +賛 +賜 +賞 +賠 +賢 +賣 +賤 +賦 +質 +賭 +購 +賽 +贄 +贅 +贈 +贋 +贔 +贖 +赤 +赦 +走 +赴 +起 +超 +越 +趙 +趣 +足 +趺 +趾 +跋 +跏 +距 +跡 +跨 +跪 +路 +跳 +践 +踊 +踏 +踐 +踞 +踪 +踵 +蹄 +蹉 +蹊 +蹟 +蹲 +蹴 +躅 +躇 +躊 +躍 +躑 +躙 +躪 +身 +躬 +躯 +躰 +車 +軋 +軌 +軍 +軒 +軟 +転 +軸 +軻 +軽 +軾 +較 +載 +輌 +輔 +輜 +輝 +輦 +輩 +輪 +輯 +輸 +輿 +轄 +轍 +轟 +轢 +辛 +辞 +辟 +辥 +辦 +辨 +辰 +辱 +農 +辺 +辻 +込 +迂 +迅 +迎 +近 +返 +迢 +迦 +迪 +迫 +迭 +述 +迷 +迹 +追 +退 +送 +逃 +逅 +逆 +逍 +透 +逐 +逓 +途 +逕 +逗 +這 +通 +逝 +逞 +速 +造 +逢 +連 +逮 +週 +進 +逸 +逼 +遁 +遂 +遅 +遇 +遊 +運 +遍 +過 +遐 +道 +達 +違 +遙 +遜 +遠 +遡 +遣 +遥 +適 +遭 +遮 +遯 +遵 +遷 +選 +遺 +遼 +避 +邀 +邁 +邂 +邃 +還 +邇 +邉 +邊 +邑 +那 +邦 +邨 +邪 +邯 +邵 +邸 +郁 +郊 +郎 +郡 +郢 +部 +郭 +郴 +郵 +郷 +都 +鄂 +鄙 +鄭 +鄰 +鄲 +酉 +酋 +酌 +配 +酎 +酒 +酔 +酢 +酥 +酪 +酬 +酵 +酷 +酸 +醍 +醐 +醒 +醗 +醜 +醤 +醪 +醵 +醸 +采 +釈 +釉 +釋 +里 +重 +野 +量 +釐 +金 +釘 +釜 +針 +釣 +釧 +釿 +鈍 +鈎 +鈐 +鈔 +鈞 +鈦 +鈴 +鈷 +鈸 +鈿 +鉄 +鉇 +鉉 +鉋 +鉛 +鉢 +鉤 +鉦 +鉱 +鉾 +銀 +銃 +銅 +銈 +銑 +銕 +銘 +銚 +銜 +銭 +鋏 +鋒 +鋤 +鋭 +鋲 +鋳 +鋸 +鋺 +鋼 +錆 +錍 +錐 +錘 +錠 +錣 +錦 +錫 +錬 +錯 +録 +錵 +鍋 +鍍 +鍑 +鍔 +鍛 +鍬 +鍮 +鍵 +鍼 +鍾 +鎌 +鎖 +鎗 +鎚 +鎧 +鎬 +鎮 +鎰 +鎹 +鏃 +鏑 +鏡 +鐃 +鐇 +鐐 +鐔 +鐘 +鐙 +鐚 +鐡 +鐵 +鐸 +鑁 +鑊 +鑑 +鑒 +鑚 +鑠 +鑢 +鑰 +鑵 +鑷 +鑼 +鑽 +鑿 +長 +門 +閃 +閇 +閉 +開 +閏 +閑 +間 +閔 +閘 +関 +閣 +閤 +閥 +閦 +閨 +閬 +閲 +閻 +閼 +閾 +闇 +闍 +闔 +闕 +闘 +關 +闡 +闢 +闥 +阜 +阪 +阮 +阯 +防 +阻 +阿 +陀 +陂 +附 +陌 +降 +限 +陛 +陞 +院 +陣 +除 +陥 +陪 +陬 +陰 +陳 +陵 +陶 +陸 +険 +陽 +隅 +隆 +隈 +隊 +隋 +階 +随 +隔 +際 +障 +隠 +隣 +隧 +隷 +隻 +隼 +雀 +雁 +雄 +雅 +集 +雇 +雉 +雊 +雋 +雌 +雍 +雑 +雖 +雙 +雛 +離 +難 +雨 +雪 +雫 +雰 +雲 +零 +雷 +雹 +電 +需 +震 +霊 +霍 +霖 +霜 +霞 +霧 +霰 +露 +靈 +青 +靖 +静 +靜 +非 +面 +革 +靫 +靭 +靱 +靴 +靺 +鞁 +鞄 +鞆 +鞋 +鞍 +鞏 +鞘 +鞠 +鞨 +鞭 +韋 +韓 +韜 +韮 +音 +韶 +韻 +響 +頁 +頂 +頃 +項 +順 +須 +頌 +預 +頑 +頒 +頓 +領 +頚 +頬 +頭 +頴 +頸 +頻 +頼 +顆 +題 +額 +顎 +顔 +顕 +顗 +願 +顛 +類 +顧 +顯 +風 +飛 +食 +飢 +飩 +飫 +飯 +飲 +飴 +飼 +飽 +飾 +餃 +餅 +餉 +養 +餌 +餐 +餓 +餘 +餝 +餡 +館 +饂 +饅 +饉 +饋 +饌 +饒 +饗 +首 +馗 +香 +馨 +馬 +馳 +馴 +駄 +駅 +駆 +駈 +駐 +駒 +駕 +駝 +駿 +騁 +騎 +騏 +騒 +験 +騙 +騨 +騰 +驕 +驚 +驛 +驢 +骨 +骸 +髄 +體 +高 +髙 +髢 +髪 +髭 +髮 +髷 +髻 +鬘 +鬚 +鬢 +鬨 +鬯 +鬱 +鬼 +魁 +魂 +魄 +魅 +魏 +魔 +魚 +魯 +鮎 +鮑 +鮒 +鮪 +鮫 +鮭 +鮮 +鯉 +鯔 +鯖 +鯛 +鯨 +鯰 +鯱 +鰐 +鰒 +鰭 +鰯 +鰰 +鰹 +鰻 +鱈 +鱒 +鱗 +鱧 +鳥 +鳩 +鳰 +鳳 +鳴 +鳶 +鴈 +鴉 +鴎 +鴛 +鴟 +鴦 +鴨 +鴫 +鴻 +鵄 +鵜 +鵞 +鵡 +鵬 +鵲 +鵺 +鶉 +鶏 +鶯 +鶴 +鷄 +鷙 +鷲 +鷹 +鷺 +鸚 +鸞 +鹸 +鹽 +鹿 +麁 +麒 +麓 +麗 +麝 +麞 +麟 +麦 +麩 +麹 +麺 +麻 +麾 +麿 +黄 +黌 +黍 +黒 +黙 +黛 +黠 +鼈 +鼉 +鼎 +鼓 +鼠 +鼻 +齊 +齋 +齟 +齢 +齬 +龍 +龕 +龗 +! +# +% +& +( +) ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; += +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +R +S +T +U +V +W +X +Z +a +c +d +e +f +h +i +j +k +l +m +n +o +p +r +s +t +u +y +z +~ +・ + diff --git a/ppocr/utils/dict/ka_dict.txt b/ppocr/utils/dict/ka_dict.txt new file mode 100644 index 0000000..d506b69 --- /dev/null +++ b/ppocr/utils/dict/ka_dict.txt @@ -0,0 +1,153 @@ +k +a +_ +i +m +g +/ +1 +2 +I +L +S +V +R +C +0 +v +l +6 +4 +8 +. +j +p +ಗ +ು +ಣ +ಪ +ಡ +ಿ +ಸ +ಲ +ಾ +ದ +್ +7 +5 +3 +ವ +ಷ +ಬ +ಹ +ೆ +9 +ಅ +ಳ +ನ +ರ +ಉ +ಕ +ಎ +ೇ +ಂ +ೈ +ೊ +ೀ +ಯ +ೋ +ತ +ಶ +ಭ +ಧ +ಚ +ಜ +ೂ +ಮ +ಒ +ೃ +ಥ +ಇ +ಟ +ಖ +ಆ +ಞ +ಫ +- +ಢ +ಊ +ಓ +ಐ +ಃ +ಘ +ಝ +ೌ +ಠ +ಛ +ಔ +ಏ +ಈ +ಋ +೨ +೦ +೧ +೮ +೯ +೪ +, +೫ +೭ +೩ +೬ +ಙ +s +c +e +n +w +o +u +t +d +E +A +T +B +Z +N +G +O +q +z +r +x +P +K +M +J +U +D +f +F +h +b +W +Y +y +H +X +Q +' +# +& +! +@ +$ +: +% +é +É +( +? ++ + diff --git a/ppocr/utils/dict/kazakh_dict.txt b/ppocr/utils/dict/kazakh_dict.txt new file mode 100644 index 0000000..007d4f5 --- /dev/null +++ b/ppocr/utils/dict/kazakh_dict.txt @@ -0,0 +1,42 @@ +А +Ә +Б +В +Г +Ғ +Д +Е +Ё +Ж +З +И +Й +К +Қ +Л +М +Н +Ң +О +Ө +П +Р +С +Т +У +Ұ +Ү +Ф +Х +Һ +Ц +Ч +Ш +Щ +Ъ +Ы +І +Ь +Э +Ю +Я diff --git a/ppocr/utils/dict/kie_dict/xfund_class_list.txt b/ppocr/utils/dict/kie_dict/xfund_class_list.txt new file mode 100644 index 0000000..faded9f --- /dev/null +++ b/ppocr/utils/dict/kie_dict/xfund_class_list.txt @@ -0,0 +1,4 @@ +OTHER +QUESTION +ANSWER +HEADER diff --git a/ppocr/utils/dict/korean_dict.txt b/ppocr/utils/dict/korean_dict.txt new file mode 100644 index 0000000..a13899f --- /dev/null +++ b/ppocr/utils/dict/korean_dict.txt @@ -0,0 +1,3688 @@ +! +" +# +$ +% +& +' +* ++ +- +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +\ +] +^ +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +{ +| +} +~ +© +° +² +½ +Á +Ä +Å +Ç +É +Í +Î +Ó +Ö +× +Ü +ß +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ú +û +ü +ý +ā +ă +ą +ć +Č +č +đ +ē +ė +ę +ě +ğ +ī +İ +ı +Ł +ł +ń +ň +ō +ř +Ş +ş +Š +š +ţ +ū +ź +ż +Ž +ž +Ș +ș +Α +Δ +α +λ +φ +Г +О +а +в +л +о +р +с +т +я +​ +’ +“ +” +→ +∇ +∼ +「 +」 +ア +カ +グ +ニ +ラ +ン +ㄱ +ㄴ +ㄷ +ㄸ +ㄹ +ㅂ +ㅅ +ㅆ +ㅇ +ㅈ +ㅊ +ㅋ +ㅌ +ㅎ +ㅓ +ㅜ +ㅣ +一 +丁 +七 +三 +上 +下 +不 +丑 +世 +丘 +丞 +中 +丸 +丹 +主 +乃 +久 +之 +乎 +乘 +九 +也 +乳 +乾 +事 +二 +云 +互 +五 +井 +亞 +亡 +交 +亥 +亨 +享 +京 +亭 +人 +仁 +今 +他 +仙 +代 +令 +以 +仰 +仲 +件 +任 +企 +伊 +伍 +伎 +伏 +伐 +休 +伯 +伴 +伸 +佃 +佈 +位 +低 +住 +佐 +何 +佛 +作 +使 +來 +供 +依 +侯 +侵 +侶 +便 +俗 +保 +俠 +信 +修 +俱 +俳 +倉 +個 +倍 +倒 +候 +借 +値 +倫 +倭 +假 +偈 +偉 +偏 +停 +偶 +傅 +傑 +傳 +傷 +傾 +像 +僞 +僥 +僧 +價 +儀 +儉 +儒 +優 +儼 +兀 +允 +元 +兆 +先 +光 +克 +兒 +入 +內 +全 +八 +公 +六 +共 +兵 +其 +具 +典 +兼 +再 +冠 +冥 +冶 +准 +凞 +凡 +凱 +出 +函 +刀 +分 +刊 +刑 +列 +初 +判 +別 +利 +到 +制 +券 +刺 +刻 +則 +前 +剛 +副 +創 +劃 +劑 +力 +功 +加 +劣 +助 +劫 +勇 +動 +務 +勝 +勢 +勳 +勸 +匈 +化 +北 +匠 +區 +十 +千 +午 +半 +卍 +卑 +卒 +卓 +南 +博 +卜 +占 +卦 +印 +危 +卵 +卷 +卽 +卿 +厄 +原 +厦 +去 +參 +又 +叉 +友 +反 +叔 +受 +口 +古 +句 +可 +台 +史 +右 +司 +各 +合 +吉 +同 +名 +后 +吏 +吐 +君 +吠 +吳 +呂 +告 +周 +味 +呵 +命 +和 +咳 +咸 +咽 +哀 +品 +哨 +哮 +哲 +唐 +唯 +唱 +商 +問 +啼 +善 +喆 +喉 +喜 +喩 +喪 +嘗 +器 +嚴 +囊 +四 +回 +因 +困 +固 +圈 +國 +圍 +園 +圓 +圖 +團 +土 +在 +地 +均 +坊 +坐 +坑 +坵 +型 +垢 +城 +域 +埴 +執 +培 +基 +堂 +堅 +堆 +堤 +堯 +報 +場 +塔 +塚 +塞 +塵 +境 +墜 +墟 +墨 +墳 +墾 +壁 +壇 +壓 +壤 +士 +壬 +壯 +壺 +壽 +夏 +夕 +外 +多 +夜 +夢 +大 +天 +太 +夫 +央 +失 +夷 +奄 +奇 +奉 +奎 +奏 +契 +奔 +奮 +女 +奴 +好 +如 +妄 +妊 +妖 +妙 +始 +姑 +姓 +姚 +姜 +威 +婆 +婚 +婦 +媒 +媚 +子 +孔 +字 +存 +孝 +孟 +季 +孤 +孫 +學 +孺 +宇 +守 +安 +宋 +宗 +官 +宙 +定 +客 +宣 +室 +宮 +害 +家 +容 +寂 +寃 +寄 +寅 +密 +寇 +富 +寒 +寓 +實 +審 +寫 +寬 +寶 +寸 +寺 +封 +將 +專 +尊 +對 +小 +少 +尙 +尹 +尼 +尿 +局 +居 +屈 +屋 +屍 +屎 +屛 +層 +屬 +山 +岐 +岡 +岩 +岳 +岸 +峙 +峰 +島 +峻 +峽 +崇 +崔 +崖 +崩 +嶋 +巖 +川 +州 +巢 +工 +左 +巧 +巨 +巫 +差 +己 +巷 +市 +布 +帝 +師 +帶 +常 +帽 +幕 +干 +平 +年 +幹 +幻 +幼 +幽 +庇 +序 +店 +府 +度 +座 +庫 +庭 +康 +廟 +廣 +廳 +延 +廷 +建 +廻 +弁 +式 +弑 +弓 +引 +弘 +弟 +弱 +張 +强 +弼 +彌 +彛 +形 +彬 +影 +役 +彼 +彿 +往 +征 +待 +律 +後 +徐 +徑 +得 +從 +循 +微 +德 +徹 +心 +必 +忌 +忍 +志 +忠 +思 +怡 +急 +性 +恐 +恒 +恨 +恩 +悅 +悖 +患 +悲 +情 +惑 +惟 +惠 +惡 +想 +惺 +愁 +意 +愚 +愛 +感 +愼 +慈 +態 +慕 +慣 +慧 +慾 +憂 +憤 +憺 +應 +懸 +戎 +成 +我 +戟 +戮 +戰 +戴 +戶 +房 +所 +手 +才 +打 +批 +承 +技 +抄 +把 +抗 +抱 +抽 +拇 +拓 +拘 +拙 +拜 +拾 +持 +指 +捌 +捨 +捿 +授 +掌 +排 +接 +推 +提 +揚 +揭 +援 +損 +搗 +摩 +播 +操 +擒 +擔 +擘 +據 +擧 +攘 +攝 +攬 +支 +改 +攻 +放 +政 +故 +敍 +敎 +救 +敗 +散 +敬 +整 +數 +文 +斗 +料 +斛 +斜 +斧 +斯 +新 +斷 +方 +於 +施 +旋 +族 +旗 +日 +旨 +早 +旱 +昌 +明 +易 +昔 +星 +春 +昧 +昭 +是 +時 +晉 +晋 +晩 +普 +景 +晴 +晶 +智 +暈 +暑 +暗 +暘 +曉 +曜 +曠 +曦 +曰 +曲 +書 +曹 +曼 +曾 +最 +會 +月 +有 +朋 +服 +望 +朝 +期 +木 +未 +末 +本 +朱 +朴 +李 +材 +村 +杖 +杜 +杞 +杭 +杯 +東 +松 +板 +林 +果 +枝 +枯 +枰 +枾 +柏 +柑 +柱 +栗 +校 +栢 +核 +根 +格 +桀 +桂 +案 +桎 +桑 +桓 +桔 +梁 +梏 +梓 +梗 +條 +梨 +梵 +棗 +棟 +森 +植 +椒 +楊 +楓 +楚 +業 +楮 +極 +榮 +槃 +槍 +樂 +樓 +樗 +樣 +樸 +樹 +樺 +樽 +橄 +橋 +橘 +機 +橡 +檀 +檎 +權 +欌 +欖 +次 +欲 +歌 +歐 +止 +正 +此 +步 +武 +歲 +歸 +死 +殖 +段 +殷 +殺 +殿 +毅 +母 +毒 +比 +毛 +氏 +民 +氣 +水 +永 +求 +汎 +汗 +江 +池 +沅 +沒 +沖 +沙 +沛 +河 +油 +治 +沼 +沿 +泉 +泊 +法 +泗 +泡 +波 +注 +泰 +洋 +洙 +洛 +洞 +津 +洲 +活 +派 +流 +浅 +浦 +浮 +浴 +海 +涅 +涇 +消 +涌 +液 +淑 +淡 +淨 +淫 +深 +淳 +淵 +淸 +渠 +渡 +游 +渾 +湖 +湯 +源 +溪 +溫 +溶 +滄 +滅 +滋 +滯 +滿 +漁 +漆 +漢 +漫 +漸 +潑 +潤 +潭 +澄 +澎 +澤 +澳 +澹 +濁 +濕 +濟 +濤 +濯 +瀋 +瀝 +灣 +火 +灰 +灸 +災 +炎 +炭 +点 +烈 +烏 +烙 +焚 +無 +焦 +然 +煌 +煎 +照 +煬 +煮 +熟 +熱 +燁 +燈 +燔 +燕 +燥 +燧 +燮 +爲 +爵 +父 +片 +版 +牌 +牛 +牝 +牟 +牡 +物 +特 +犧 +犬 +狀 +狗 +猥 +猩 +猪 +獨 +獵 +獸 +獻 +玄 +玉 +王 +玲 +珍 +珠 +珪 +班 +現 +球 +理 +琴 +瑞 +瑟 +瑪 +璃 +璋 +璽 +瓜 +瓦 +甑 +甘 +生 +産 +用 +甫 +田 +由 +甲 +申 +男 +界 +畏 +留 +畜 +畢 +略 +番 +異 +畵 +當 +畸 +疏 +疑 +疫 +疹 +疼 +病 +症 +痔 +痛 +痺 +瘀 +瘍 +瘡 +療 +癌 +癖 +登 +發 +白 +百 +的 +皆 +皇 +皮 +盂 +盆 +益 +盛 +盜 +盟 +盡 +盤 +盧 +目 +直 +相 +省 +看 +眞 +眼 +睡 +督 +瞋 +矢 +矣 +知 +短 +石 +破 +碍 +碑 +磁 +磨 +磬 +示 +社 +祇 +祖 +祝 +神 +祥 +祭 +祺 +禁 +禅 +禍 +福 +禦 +禪 +禮 +禹 +禽 +禾 +秀 +私 +秉 +秋 +科 +秘 +秤 +秦 +秩 +移 +稀 +稗 +種 +稱 +稷 +稼 +稽 +穀 +穆 +積 +空 +窮 +竅 +立 +章 +童 +竭 +端 +竹 +笑 +符 +第 +筆 +等 +筍 +答 +策 +箋 +箕 +管 +箱 +節 +篇 +簡 +米 +粉 +粘 +粥 +精 +糖 +糞 +系 +紀 +紂 +約 +紅 +紋 +純 +紙 +級 +素 +索 +紫 +紬 +累 +細 +紳 +終 +組 +結 +絡 +統 +絲 +絶 +絹 +經 +綠 +維 +綱 +網 +綸 +綽 +緖 +線 +緣 +緯 +縣 +縱 +總 +織 +繡 +繩 +繪 +繭 +纂 +續 +罕 +置 +罰 +羅 +羊 +美 +群 +義 +羽 +翁 +習 +翟 +老 +考 +者 +而 +耐 +耕 +耳 +聃 +聖 +聞 +聰 +聲 +職 +肇 +肉 +肖 +肝 +股 +肥 +育 +肺 +胃 +胎 +胚 +胞 +胡 +胥 +能 +脂 +脈 +脚 +脛 +脣 +脩 +脫 +脯 +脾 +腋 +腎 +腫 +腸 +腹 +膜 +膠 +膨 +膽 +臆 +臟 +臣 +臥 +臨 +自 +至 +致 +臺 +臼 +臾 +與 +興 +舊 +舌 +舍 +舒 +舜 +舟 +般 +船 +艦 +良 +色 +芋 +花 +芳 +芽 +苑 +苔 +苕 +苛 +苞 +若 +苦 +英 +茂 +茵 +茶 +茹 +荀 +荇 +草 +荒 +荷 +莊 +莫 +菊 +菌 +菜 +菩 +菫 +華 +菴 +菽 +萊 +萍 +萬 +落 +葉 +著 +葛 +董 +葬 +蒙 +蒜 +蒲 +蒸 +蒿 +蓮 +蔓 +蔘 +蔡 +蔬 +蕃 +蕉 +蕓 +薄 +薑 +薛 +薩 +薪 +薺 +藏 +藝 +藤 +藥 +藩 +藻 +蘆 +蘇 +蘊 +蘚 +蘭 +虎 +處 +虛 +虞 +虹 +蜀 +蜂 +蜜 +蝕 +蝶 +融 +蟬 +蟲 +蠶 +蠻 +血 +衆 +行 +術 +衛 +衡 +衣 +表 +袁 +裔 +裕 +裙 +補 +製 +複 +襄 +西 +要 +見 +視 +親 +覺 +觀 +角 +解 +言 +訂 +訊 +訓 +託 +記 +訣 +設 +診 +註 +評 +詩 +話 +詵 +誅 +誌 +認 +誕 +語 +誠 +誤 +誥 +誦 +說 +調 +談 +諍 +論 +諡 +諫 +諭 +諸 +謙 +講 +謝 +謠 +證 +識 +譚 +譜 +譯 +議 +護 +讀 +變 +谷 +豆 +豊 +豚 +象 +豪 +豫 +貝 +貞 +財 +貧 +貨 +貪 +貫 +貴 +貸 +費 +資 +賊 +賓 +賞 +賢 +賣 +賦 +質 +贍 +赤 +赫 +走 +起 +超 +越 +趙 +趣 +趨 +足 +趾 +跋 +跡 +路 +踏 +蹟 +身 +躬 +車 +軍 +軒 +軟 +載 +輓 +輕 +輪 +輯 +輸 +輻 +輿 +轅 +轉 +辨 +辭 +辯 +辰 +農 +近 +迦 +述 +追 +逆 +透 +逐 +通 +逝 +造 +逢 +連 +進 +逵 +遂 +遊 +運 +遍 +過 +道 +達 +遠 +遡 +適 +遷 +選 +遺 +遽 +還 +邊 +邑 +那 +邪 +郞 +郡 +部 +都 +鄒 +鄕 +鄭 +鄲 +配 +酒 +酸 +醉 +醫 +醯 +釋 +里 +重 +野 +量 +釐 +金 +針 +鈍 +鈴 +鉞 +銀 +銅 +銘 +鋼 +錄 +錢 +錦 +鎭 +鏡 +鐘 +鐵 +鑑 +鑛 +長 +門 +閃 +開 +間 +閔 +閣 +閥 +閭 +閻 +闕 +關 +阪 +防 +阿 +陀 +降 +限 +陝 +院 +陰 +陳 +陵 +陶 +陸 +陽 +隆 +隊 +隋 +階 +際 +障 +隣 +隨 +隱 +隷 +雀 +雄 +雅 +集 +雇 +雌 +雖 +雙 +雜 +離 +難 +雨 +雪 +雲 +電 +霜 +露 +靈 +靑 +靖 +靜 +非 +面 +革 +靴 +鞏 +韓 +音 +韶 +韻 +順 +須 +頊 +頌 +領 +頭 +顔 +願 +顚 +類 +顯 +風 +飛 +食 +飢 +飮 +飯 +飾 +養 +餓 +餘 +首 +香 +馨 +馬 +駒 +騫 +騷 +驕 +骨 +骸 +髓 +體 +高 +髥 +髮 +鬪 +鬱 +鬼 +魏 +魔 +魚 +魯 +鮮 +鰍 +鰐 +鳥 +鳧 +鳳 +鴨 +鵲 +鶴 +鷄 +鷹 +鹽 +鹿 +麗 +麥 +麻 +黃 +黑 +默 +點 +黨 +鼎 +齊 +齋 +齒 +龍 +龜 +가 +각 +간 +갇 +갈 +갉 +감 +갑 +값 +갓 +갔 +강 +갖 +갗 +같 +갚 +갛 +개 +객 +갠 +갤 +갬 +갭 +갯 +갰 +갱 +갸 +걀 +걔 +걘 +거 +걱 +건 +걷 +걸 +검 +겁 +것 +겄 +겅 +겆 +겉 +겊 +겋 +게 +겐 +겔 +겟 +겠 +겡 +겨 +격 +겪 +견 +결 +겸 +겹 +겻 +겼 +경 +곁 +계 +곕 +곗 +고 +곡 +곤 +곧 +골 +곪 +곬 +곯 +곰 +곱 +곳 +공 +곶 +과 +곽 +관 +괄 +괌 +광 +괘 +괜 +괭 +괴 +괸 +굉 +교 +구 +국 +군 +굳 +굴 +굵 +굶 +굼 +굽 +굿 +궁 +궂 +궈 +권 +궐 +궜 +궝 +궤 +귀 +귄 +귈 +귓 +규 +균 +귤 +그 +극 +근 +글 +긁 +금 +급 +긋 +긍 +기 +긴 +길 +김 +깁 +깃 +깅 +깊 +까 +깍 +깎 +깐 +깔 +깜 +깝 +깟 +깡 +깥 +깨 +깬 +깰 +깻 +깼 +깽 +꺄 +꺼 +꺽 +꺾 +껀 +껄 +껌 +껍 +껏 +껐 +껑 +께 +껴 +꼈 +꼍 +꼐 +꼬 +꼭 +꼴 +꼼 +꼽 +꼿 +꽁 +꽂 +꽃 +꽉 +꽝 +꽤 +꽥 +꾀 +꾜 +꾸 +꾹 +꾼 +꿀 +꿇 +꿈 +꿉 +꿋 +꿍 +꿎 +꿔 +꿨 +꿩 +꿰 +꿴 +뀄 +뀌 +뀐 +뀔 +뀜 +뀝 +끄 +끈 +끊 +끌 +끓 +끔 +끕 +끗 +끙 +끝 +끼 +끽 +낀 +낄 +낌 +낍 +낏 +낑 +나 +낙 +낚 +난 +낟 +날 +낡 +남 +납 +낫 +났 +낭 +낮 +낯 +낱 +낳 +내 +낵 +낸 +낼 +냄 +냅 +냇 +냈 +냉 +냐 +냔 +냘 +냥 +너 +넉 +넋 +넌 +널 +넓 +넘 +넙 +넛 +넜 +넝 +넣 +네 +넥 +넨 +넬 +넴 +넵 +넷 +넸 +넹 +녀 +녁 +년 +념 +녔 +녕 +녘 +녜 +노 +녹 +논 +놀 +놈 +놋 +농 +높 +놓 +놔 +놨 +뇌 +뇨 +뇩 +뇽 +누 +눅 +눈 +눌 +눔 +눕 +눗 +눠 +눴 +뉘 +뉜 +뉩 +뉴 +늄 +늅 +늉 +느 +늑 +는 +늘 +늙 +늠 +늡 +능 +늦 +늪 +늬 +니 +닉 +닌 +닐 +님 +닙 +닛 +닝 +닢 +다 +닥 +닦 +단 +닫 +달 +닭 +닮 +닯 +닳 +담 +답 +닷 +당 +닻 +닿 +대 +댁 +댄 +댈 +댐 +댑 +댓 +댔 +댕 +댜 +더 +덕 +덖 +던 +덜 +덟 +덤 +덥 +덧 +덩 +덫 +덮 +데 +덱 +덴 +델 +뎀 +뎃 +뎅 +뎌 +뎠 +뎨 +도 +독 +돈 +돋 +돌 +돔 +돕 +돗 +동 +돛 +돝 +돼 +됐 +되 +된 +될 +됨 +됩 +됴 +두 +둑 +둔 +둘 +둠 +둡 +둣 +둥 +둬 +뒀 +뒤 +뒬 +뒷 +뒹 +듀 +듈 +듐 +드 +득 +든 +듣 +들 +듦 +듬 +듭 +듯 +등 +듸 +디 +딕 +딘 +딛 +딜 +딤 +딥 +딧 +딨 +딩 +딪 +따 +딱 +딴 +딸 +땀 +땄 +땅 +때 +땐 +땔 +땜 +땝 +땠 +땡 +떠 +떡 +떤 +떨 +떫 +떰 +떱 +떳 +떴 +떵 +떻 +떼 +떽 +뗀 +뗄 +뗍 +뗏 +뗐 +뗑 +또 +똑 +똘 +똥 +뙤 +뚜 +뚝 +뚤 +뚫 +뚱 +뛰 +뛴 +뛸 +뜀 +뜁 +뜨 +뜩 +뜬 +뜯 +뜰 +뜸 +뜻 +띄 +띈 +띌 +띔 +띕 +띠 +띤 +띨 +띱 +띵 +라 +락 +란 +랄 +람 +랍 +랏 +랐 +랑 +랒 +랗 +래 +랙 +랜 +랠 +램 +랩 +랫 +랬 +랭 +랴 +략 +량 +러 +럭 +런 +럴 +럼 +럽 +럿 +렀 +렁 +렇 +레 +렉 +렌 +렐 +렘 +렙 +렛 +렝 +려 +력 +련 +렬 +렴 +렵 +렷 +렸 +령 +례 +로 +록 +론 +롤 +롬 +롭 +롯 +롱 +롸 +롹 +뢰 +뢴 +뢸 +룃 +료 +룐 +룡 +루 +룩 +룬 +룰 +룸 +룹 +룻 +룽 +뤄 +뤘 +뤼 +류 +륙 +륜 +률 +륨 +륭 +르 +륵 +른 +를 +름 +릅 +릇 +릉 +릎 +리 +릭 +린 +릴 +림 +립 +릿 +링 +마 +막 +만 +많 +맏 +말 +맑 +맘 +맙 +맛 +망 +맞 +맡 +맣 +매 +맥 +맨 +맬 +맴 +맵 +맷 +맸 +맹 +맺 +먀 +먁 +머 +먹 +먼 +멀 +멈 +멋 +멍 +멎 +메 +멕 +멘 +멜 +멤 +멥 +멧 +멩 +며 +멱 +면 +멸 +몄 +명 +몇 +모 +목 +몫 +몬 +몰 +몸 +몹 +못 +몽 +뫼 +묘 +무 +묵 +묶 +문 +묻 +물 +묽 +뭄 +뭅 +뭇 +뭉 +뭍 +뭏 +뭐 +뭔 +뭘 +뭡 +뭣 +뮈 +뮌 +뮐 +뮤 +뮬 +므 +믈 +믐 +미 +믹 +민 +믿 +밀 +밈 +밉 +밋 +밌 +밍 +및 +밑 +바 +박 +밖 +반 +받 +발 +밝 +밟 +밤 +밥 +밧 +방 +밭 +배 +백 +밴 +밸 +뱀 +뱁 +뱃 +뱄 +뱅 +뱉 +뱍 +뱐 +버 +벅 +번 +벌 +범 +법 +벗 +벙 +벚 +베 +벡 +벤 +벨 +벰 +벱 +벳 +벵 +벼 +벽 +변 +별 +볍 +볏 +볐 +병 +볕 +보 +복 +볶 +본 +볼 +봄 +봅 +봇 +봉 +봐 +봤 +뵈 +뵐 +뵙 +부 +북 +분 +붇 +불 +붉 +붐 +붓 +붕 +붙 +뷔 +뷰 +뷴 +뷸 +브 +븐 +블 +비 +빅 +빈 +빌 +빔 +빕 +빗 +빙 +빚 +빛 +빠 +빡 +빤 +빨 +빳 +빴 +빵 +빻 +빼 +빽 +뺀 +뺄 +뺌 +뺏 +뺐 +뺑 +뺨 +뻐 +뻑 +뻔 +뻗 +뻘 +뻣 +뻤 +뻥 +뻬 +뼈 +뼉 +뼘 +뽀 +뽈 +뽐 +뽑 +뽕 +뾰 +뿌 +뿍 +뿐 +뿔 +뿜 +쁘 +쁜 +쁠 +쁨 +삐 +삔 +삘 +사 +삭 +삯 +산 +살 +삵 +삶 +삼 +삽 +삿 +샀 +상 +샅 +새 +색 +샌 +샐 +샘 +샙 +샛 +샜 +생 +샤 +샨 +샬 +샴 +샵 +샷 +샹 +서 +석 +섞 +선 +섣 +설 +섬 +섭 +섯 +섰 +성 +섶 +세 +섹 +센 +셀 +셈 +셉 +셋 +셌 +셍 +셔 +션 +셜 +셨 +셰 +셴 +셸 +소 +속 +손 +솔 +솜 +솝 +솟 +송 +솥 +쇄 +쇠 +쇤 +쇳 +쇼 +숀 +숄 +숍 +수 +숙 +순 +숟 +술 +숨 +숩 +숫 +숭 +숯 +숱 +숲 +숴 +쉐 +쉘 +쉬 +쉭 +쉰 +쉴 +쉼 +쉽 +슈 +슐 +슘 +슛 +슝 +스 +슥 +슨 +슬 +슭 +슴 +습 +슷 +승 +시 +식 +신 +싣 +실 +싫 +심 +십 +싯 +싱 +싶 +싸 +싹 +싼 +쌀 +쌈 +쌉 +쌌 +쌍 +쌓 +쌔 +쌘 +쌩 +써 +썩 +썬 +썰 +썸 +썹 +썼 +썽 +쎄 +쎈 +쏘 +쏙 +쏜 +쏟 +쏠 +쏭 +쏴 +쐈 +쐐 +쐬 +쑤 +쑥 +쑨 +쒀 +쒔 +쓰 +쓱 +쓴 +쓸 +씀 +씁 +씌 +씨 +씩 +씬 +씰 +씸 +씹 +씻 +씽 +아 +악 +안 +앉 +않 +알 +앎 +앓 +암 +압 +앗 +았 +앙 +앞 +애 +액 +앤 +앨 +앰 +앱 +앳 +앴 +앵 +야 +약 +얀 +얄 +얇 +얌 +얍 +얏 +양 +얕 +얗 +얘 +얜 +어 +억 +언 +얹 +얻 +얼 +얽 +엄 +업 +없 +엇 +었 +엉 +엊 +엌 +엎 +에 +엑 +엔 +엘 +엠 +엡 +엣 +엥 +여 +역 +엮 +연 +열 +엷 +염 +엽 +엾 +엿 +였 +영 +옅 +옆 +옇 +예 +옌 +옐 +옙 +옛 +오 +옥 +온 +올 +옭 +옮 +옳 +옴 +옵 +옷 +옹 +옻 +와 +왁 +완 +왈 +왑 +왓 +왔 +왕 +왜 +왠 +왱 +외 +왼 +요 +욕 +욘 +욜 +욤 +용 +우 +욱 +운 +울 +움 +웁 +웃 +웅 +워 +웍 +원 +월 +웜 +웠 +웡 +웨 +웬 +웰 +웸 +웹 +위 +윅 +윈 +윌 +윔 +윗 +윙 +유 +육 +윤 +율 +윱 +윳 +융 +으 +윽 +은 +을 +읊 +음 +읍 +응 +의 +읜 +읠 +이 +익 +인 +일 +읽 +잃 +임 +입 +잇 +있 +잉 +잊 +잎 +자 +작 +잔 +잖 +잘 +잠 +잡 +잣 +잤 +장 +잦 +재 +잭 +잰 +잴 +잽 +잿 +쟀 +쟁 +쟈 +쟉 +쟤 +저 +적 +전 +절 +젊 +점 +접 +젓 +정 +젖 +제 +젝 +젠 +젤 +젬 +젭 +젯 +져 +젼 +졀 +졌 +졍 +조 +족 +존 +졸 +좀 +좁 +종 +좇 +좋 +좌 +좍 +좽 +죄 +죠 +죤 +주 +죽 +준 +줄 +줌 +줍 +줏 +중 +줘 +줬 +쥐 +쥔 +쥘 +쥬 +쥴 +즈 +즉 +즌 +즐 +즘 +즙 +증 +지 +직 +진 +짇 +질 +짊 +짐 +집 +짓 +징 +짖 +짙 +짚 +짜 +짝 +짠 +짢 +짤 +짧 +짬 +짭 +짰 +짱 +째 +짹 +짼 +쨀 +쨉 +쨋 +쨌 +쨍 +쩄 +쩌 +쩍 +쩐 +쩔 +쩜 +쩝 +쩡 +쩨 +쪄 +쪘 +쪼 +쪽 +쪾 +쫀 +쫄 +쫑 +쫓 +쫙 +쬐 +쭈 +쭉 +쭐 +쭙 +쯔 +쯤 +쯧 +찌 +찍 +찐 +찔 +찜 +찝 +찡 +찢 +찧 +차 +착 +찬 +찮 +찰 +참 +찹 +찻 +찼 +창 +찾 +채 +책 +챈 +챌 +챔 +챕 +챗 +챘 +챙 +챠 +챤 +처 +척 +천 +철 +첨 +첩 +첫 +청 +체 +첵 +첸 +첼 +쳄 +쳇 +쳉 +쳐 +쳔 +쳤 +초 +촉 +촌 +촘 +촛 +총 +촨 +촬 +최 +쵸 +추 +축 +춘 +출 +춤 +춥 +춧 +충 +춰 +췄 +췌 +취 +췬 +츄 +츠 +측 +츨 +츰 +층 +치 +칙 +친 +칠 +칡 +침 +칩 +칫 +칭 +카 +칵 +칸 +칼 +캄 +캅 +캇 +캉 +캐 +캔 +캘 +캠 +캡 +캣 +캤 +캥 +캬 +커 +컥 +컨 +컫 +컬 +컴 +컵 +컷 +컸 +컹 +케 +켄 +켈 +켐 +켓 +켕 +켜 +켠 +켤 +켭 +켯 +켰 +코 +콕 +콘 +콜 +콤 +콥 +콧 +콩 +콰 +콱 +콴 +콸 +쾅 +쾌 +쾡 +쾨 +쾰 +쿄 +쿠 +쿡 +쿤 +쿨 +쿰 +쿵 +쿼 +퀀 +퀄 +퀘 +퀭 +퀴 +퀵 +퀸 +퀼 +큐 +큘 +크 +큰 +클 +큼 +큽 +키 +킥 +킨 +킬 +킴 +킵 +킷 +킹 +타 +탁 +탄 +탈 +탉 +탐 +탑 +탓 +탔 +탕 +태 +택 +탠 +탤 +탬 +탭 +탯 +탰 +탱 +터 +턱 +턴 +털 +텀 +텁 +텃 +텄 +텅 +테 +텍 +텐 +텔 +템 +텝 +텡 +텨 +톈 +토 +톡 +톤 +톨 +톰 +톱 +톳 +통 +퇴 +툇 +투 +툭 +툰 +툴 +툼 +퉁 +퉈 +퉜 +튀 +튄 +튈 +튕 +튜 +튠 +튤 +튬 +트 +특 +튼 +튿 +틀 +틈 +틉 +틋 +틔 +티 +틱 +틴 +틸 +팀 +팁 +팅 +파 +팍 +팎 +판 +팔 +팜 +팝 +팟 +팠 +팡 +팥 +패 +팩 +팬 +팰 +팸 +팻 +팼 +팽 +퍼 +퍽 +펀 +펄 +펌 +펍 +펐 +펑 +페 +펙 +펜 +펠 +펨 +펩 +펫 +펭 +펴 +편 +펼 +폄 +폈 +평 +폐 +포 +폭 +폰 +폴 +폼 +폿 +퐁 +표 +푭 +푸 +푹 +푼 +풀 +품 +풋 +풍 +퓨 +퓬 +퓰 +퓸 +프 +픈 +플 +픔 +픕 +피 +픽 +핀 +필 +핌 +핍 +핏 +핑 +하 +학 +한 +할 +핥 +함 +합 +핫 +항 +해 +핵 +핸 +핼 +햄 +햅 +햇 +했 +행 +햐 +향 +헀 +허 +헉 +헌 +헐 +험 +헙 +헛 +헝 +헤 +헥 +헨 +헬 +헴 +헵 +헷 +헹 +혀 +혁 +현 +혈 +혐 +협 +혓 +혔 +형 +혜 +호 +혹 +혼 +홀 +홈 +홉 +홋 +홍 +홑 +화 +확 +환 +활 +홧 +황 +홰 +홱 +횃 +회 +획 +횝 +횟 +횡 +효 +후 +훅 +훈 +훌 +훑 +훔 +훗 +훤 +훨 +훼 +휄 +휑 +휘 +휙 +휜 +휠 +휩 +휭 +휴 +휼 +흄 +흉 +흐 +흑 +흔 +흘 +흙 +흠 +흡 +흣 +흥 +흩 +희 +흰 +흽 +히 +힉 +힌 +힐 +힘 +힙 +힝 +車 +滑 +金 +奈 +羅 +洛 +卵 +欄 +蘭 +郎 +來 +盧 +老 +魯 +綠 +鹿 +論 +雷 +樓 +縷 +凌 +樂 +不 +參 +葉 +沈 +若 +兩 +凉 +梁 +呂 +女 +廬 +麗 +黎 +曆 +歷 +戀 +蓮 +連 +列 +烈 +裂 +念 +獵 +靈 +領 +例 +禮 +醴 +惡 +尿 +料 +遼 +龍 +暈 +柳 +流 +類 +六 +陸 +倫 +律 +栗 +利 +李 +梨 +理 +離 +燐 +林 +臨 +立 +茶 +切 +宅 + diff --git a/ppocr/utils/dict/latex_ocr_tokenizer.json b/ppocr/utils/dict/latex_ocr_tokenizer.json new file mode 100644 index 0000000..e8fd4f6 --- /dev/null +++ b/ppocr/utils/dict/latex_ocr_tokenizer.json @@ -0,0 +1 @@ +{"version":"1.0","truncation":null,"padding":null,"added_tokens":[{"id":0,"special":true,"content":"[PAD]","single_word":false,"lstrip":false,"rstrip":false,"normalized":false},{"id":1,"special":true,"content":"[BOS]","single_word":false,"lstrip":false,"rstrip":false,"normalized":false},{"id":2,"special":true,"content":"[EOS]","single_word":false,"lstrip":false,"rstrip":false,"normalized":false}],"normalizer":null,"pre_tokenizer":{"type":"ByteLevel","add_prefix_space":false,"trim_offsets":true},"post_processor":null,"decoder":null,"model":{"dropout":null,"unk_token":null,"continuing_subword_prefix":null,"end_of_word_suffix":null,"fuse_unk":false,"vocab":{"[PAD]":0,"[BOS]":1,"[EOS]":2,"!":3,"\"":4,"#":5,"$":6,"&":7,"'":8,"(":9,")":10,"*":11,"+":12,",":13,"-":14,".":15,"/":16,"0":17,"1":18,"2":19,"3":20,"4":21,"5":22,"6":23,"7":24,"8":25,"9":26,":":27,";":28,"<":29,"=":30,">":31,"?":32,"@":33,"A":34,"B":35,"C":36,"D":37,"E":38,"F":39,"G":40,"H":41,"I":42,"J":43,"K":44,"L":45,"M":46,"N":47,"O":48,"P":49,"Q":50,"R":51,"S":52,"T":53,"U":54,"V":55,"W":56,"X":57,"Y":58,"Z":59,"[":60,"\\":61,"]":62,"^":63,"_":64,"`":65,"a":66,"b":67,"c":68,"d":69,"e":70,"f":71,"g":72,"h":73,"i":74,"j":75,"k":76,"l":77,"m":78,"n":79,"o":80,"p":81,"q":82,"r":83,"s":84,"t":85,"u":86,"v":87,"w":88,"x":89,"y":90,"z":91,"{":92,"|":93,"}":94,"~":95,"½":96,"¿":97,"ï":98,"Ċ":99,"č":100,"Ġ":101,"Ġ}":102,"Ġ{":103,"Ġ\\":104,"Ġ_":105,"Ġ^":106,"Ġ2":107,"Ġ)":108,"Ġ(":109,"Ġ1":110,"ra":111,"Ġ=":112,"Ġ-":113,"čĊ":114,"Ġ,":115,"fra":116,"frac":117,"Ġ+":118,"ma":119,"ta":120,"ig":121,"Ġ0":122,"ar":123,"al":124,"le":125,"Ġi":126,"th":127,"Ġx":128,"ft":129,"igh":130,"me":131,"righ":132,"math":133,"Ġn":134,"Ġ.":135,"Ġ\\,":136,"in":137,"ph":138,"Ġd":139,"left":140,"Ġa":141,"right":142,"am":143,"eta":144,"ti":145,"Ġm":146,"mu":147,"Ġ3":148,"Ġk":149,"Ġt":150,"Ġe":151,"Ġr":152,"Ġ&":153,"Ġc":154,"Ġp":155,"si":156,"rm":157,"de":158,"mathrm":159,"Ġ4":160,"Ġs":161,"pr":162,"Ġ~":163,"pha":164,"Ġl":165,"alpha":166,"da":167,"ĠA":168,"Ġ\\;":169,"ot":170,"pi":171,"par":172,"tial":173,"partial":174,"ime":175,"prime":176,"psi":177,"dot":178,"Ġj":179,"Ġb":180,"Ġf":181,"lta":182,"Ġ|":183,"amma":184,"bda":185,"ambda":186,"phi":187,"Ġq":188,"bf":189,"Ġg":190,"nu":191,"Ġz":192,"ray":193,"array":194,"ĠM":195,"ĠT":196,"Ġy":197,"cal":198,"bar":199,"ĠN":200,"igma":201,"ĠR":202,"rt":203,"lambda":204,"int":205,"ĠB":206,"ve":207,"ng":208,"qu":209,"ĠL":210,"Ġ/":211,"lo":212,"beta":213,"ngle":214,"Ġu":215,"delta":216,"sq":217,"sqrt":218,"theta":219,"Ġ\\\\":220,"gamma":221,"Ġ]":222,"sigma":223,"ga":224,"mega":225,"ĠD":226,"ĠF":227,"Ġ[":228,"ĠS":229,"mathbf":230,"su":231,"ĠP":232,"lon":233,"Ġv":234,"sum":235,"psilon":236,"ĠV":237,"ĠC":238,"cdot":239,"epsilon":240,"at":241,"hat":242,"ad":243,"quad":244,"Ġh":245,"ho":246,"rho":247,"hi":248,"to":249,"ĠE":250,"la":251,"ĠH":252,"lde":253,"tilde":254,"ĠQ":255,"Ġ5":256,"var":257,"ĠX":258,"ĠG":259,"be":260,"nd":261,"omega":262,"end":263,"gin":264,"begin":265,"tau":266,"Ġ6":267,"na":268,"vec":269,"ĠI":270,"Ġo":271,"rangle":272,"Ġ*":273,"De":274,"Delta":275,"Gamma":276,"pe":277,"fty":278,"infty":279,"ĠK":280,"xi":281,"Ġ8":282,"ow":283,"ĠJ":284,"ĠU":285,"row":286,"tar":287,"ge":288,"Phi":289,"ap":290,"ĠW":291,"co":292,"mes":293,"times":294,"sin":295,"ĠZ":296,"langle":297,"ope":298,"rna":299,"rato":300,"operato":301,"rname":302,"operatorname":303,"tarrow":304,"lin":305,"line":306,"varphi":307,"pm":308,"rline":309,"Lambda":310,"Ġ\\!":311,"Ġ;":312,"dots":313,"cos":314,"Ġw":315,"rightarrow":316,"big":317,"chi":318,"ove":319,"Ġ\\}":320,"overline":321,"Ġ7":322,"ex":323,"pa":324,"st":325,"pro":326,"qquad":327,"iv":328,"equ":329,"equiv":330,"ĠO":331,"ln":332,"Omega":333,"ll":334,"Ġ9":335,"kap":336,"kappa":337,"Big":338,"Ġ\\{":339,"dag":340,"ĠY":341,"\\{":342,"varepsilon":343,"cdots":344,"Ġ:":345,"mathcal":346,"Psi":347,"Ġ>":348,"bo":349,"bol":350,"Ġ<":351,"ger":352,"dagger":353,"ldots":354,"ell":355,"bla":356,"nabla":357,"exp":358,"yle":359,"style":360,"zeta":361,"Sigma":362,"wi":363,"wide":364,"sim":365,"leq":366,"Ġ!":367,"bigg":368,"mathb":369,"mathbb":370,"Ġ\\:":371,"hbar":372,"otimes":373,"bold":374,"\\}":375,"mi":376,"prox":377,"approx":378,"Pi":379,"log":380,"mid":381,"sp":382,"vert":383,"di":384,"prod":385,"per":386,"perp":387,"ystyle":388,"laystyle":389,"splaystyle":390,"displaystyle":391,"meq":392,"simeq":393,"ed":394,"wed":395,"wedge":396,"widetilde":397,"sy":398,"sym":399,"symbol":400,"boldsymbol":401,"ck":402,"tex":403,"text":404,"ri":405,"Th":406,"Theta":407,"geq":408,"se":409,"eq":410,"nde":411,"unde":412,"tan":413,"sc":414,"ast":415,"rc":416,"set":417,"pt":418,"widehat":419,"ci":420,"circ":421,"re":422,"ript":423,"script":424,"underline":425,"Ġ\\|":426,"rel":427,"neq":428,"sta":429,"stack":430,"stackrel":431,"sinh":432,"op":433,"us":434,"cosh":435,"Bigg":436,"ce":437,"textstyle":438,"star":439,"not":440,"frak":441,"mathfrak":442,"mp":443,"biggr":444,"lus":445,"oplus":446,"vartheta":447,"biggl":448,"Bigr":449,"bra":450,"Bigl":451,"fo":452,"sf":453,"sub":454,"subset":455,"ngrightarrow":456,"ec":457,"boldmath":458,"rall":459,"forall":460,"scriptstyle":461,"ect":462,"parrow":463,"uparrow":464,"bj":465,"bject":466,"pto":467,"propto":468,"Ġ'":469,"longrightarrow":470,"bigl":471,"bigr":472,"oint":473,"ps":474,"maps":475,"mapsto":476,"om":477,"lle":478,"\\|":479,"ddot":480,"cu":481,"bin":482,"binom":483,"vdots":484,"angle":485,"leftrightarrow":486,"over":487,"or":488,"mathsf":489,"cup":490,"brace":491,"no":492,"arc":493,"flo":494,"floor":495,"tri":496,"triangle":497,"Xi":498,"cot":499,"bot":500,"cong":501,"it":502,"mbe":503,"numbe":504,"nonumbe":505,"nonumber":506,"cap":507,"Righ":508,"Rightarrow":509,"ze":510,"size":511,"textrm":512,"ne":513,"arctan":514,"ralle":515,"paralle":516,"parallel":517,"cfrac":518,"Ġ--":519,"object":520,"ĠObject":521,"brack":522,"sh":523,"arrow":524,"own":525,"varrho":526,"subseteq":527,"rbrace":528,"textbf":529,"imath":530,"od":531,"down":532,"he":533,"land":534,"scriptscriptstyle":535,"scriptsize":536,"che":537,"check":538,"sla":539,"overrightarrow":540,"downarrow":541,"Biggl":542,"gg":543,"nto":544,"phanto":545,"phantom":546,"exi":547,"hline":548,"sts":549,"exists":550,"Biggr":551,"bu":552,"rfloor":553,"ddots":554,"io":555,"iota":556,"llet":557,"bullet":558,"colon":559,"inus":560,"Upsilon":561,"lfloor":562,"lbrack":563,"underbrace":564,"neg":565,"Im":566,"mathit":567,"tin":568,"tiny":569,"jmath":570,"lef":571,"slash":572,"vee":573,"minus":574,"setminus":575,"Re":576,"iint":577,"leftarrow":578,"Ve":579,"Vert":580,"atop":581,"sup":582,"bigcup":583,"wp":584,"dim":585,"sec":586,"supset":587,"Lo":588,"lor":589,"pmod":590,"mod":591,"bigoplus":592,"il":593,"bmod":594,"coth":595,"Le":596,"ftrightarrow":597,"Leftrightarrow":598,"ngleftrightarrow":599,"sma":600,"upsilon":601,"\\,":602,"csc":603,"eph":604,"aleph":605,"bigwedge":606,"arcsin":607,"small":608,"odot":609,"overset":610,"rbrack":611,"mit":612,"lbrace":613,"li":614,"arp":615,"arge":616,"Ġ\\#":617,"bre":618,"textsf":619,"Longrightarrow":620,"breve":621,"em":622,"yset":623,"varpi":624,"ptyset":625,"emptyset":626,"ff":627,"iff":628,"nt":629,"er":630,"lap":631,"lnot":632,"dash":633,"under":634,"slant":635,"arg":636,"underset":637,"Bo":638,"Box":639,"Ġ\"":640,"spa":641,"space":642,"deg":643,"iiint":644,"oo":645,"otnot":646,"footnot":647,"arpoo":648,"footnote":649,"rlap":650,"es":651,"imp":652,"sb":653,"te":654,"bigtriangle":655,"lies":656,"implies":657,"\\;":658,"ker":659,"footnotesize":660,"tharpoo":661,"up":662,"acu":663,"acute":664,"longleftrightarrow":665,"eil":666,"lce":667,"rceil":668,"lceil":669,"vphantom":670,"en":671,"thin":672,"ack":673,"back":674,"tt":675,"backslash":676,"xrightarrow":677,"vdash":678,"top":679,"rightharpoo":680,"varsigma":681,"Longleftrightarrow":682,"mathop":683,"large":684,"bigcap":685,"leqslant":686,"Ġ`":687,"overbrace":688,"nup":689,"rightharpoonup":690,"bigotimes":691,"triangleq":692,"Large":693,"ru":694,"null":695,"bigtriangleup":696,"varno":697,"thing":698,"varnothing":699,"doteq":700,"Ġ\\_":701,"overleftarrow":702,"hf":703,"bigstar":704,"enspace":705,"\\!":706,"stru":707,"strut":708,"ominus":709,"div":710,"ond":711,"amond":712,"ddagger":713,"Ġcm":714,"ni":715,"sk":716,"diamond":717,"rVert":718,"prot":719,"protect":720,"ip":721,"varDelta":722,"notin":723,"skip":724,"lVert":725,"Ġ\\/":726,"dotsc":727,"ill":728,"ule":729,"\\:":730,"hfill":731,"krightarrow":732,"okrightarrow":733,"hookrightarrow":734,"sharp":735,"Vdash":736,"bigvee":737,"subsetneq":738,"supseteq":739,"Ġ?":740,"ngmapsto":741,"longmapsto":742,"cdotp":743,"geqslant":744,"bigtriangledown":745,"dotsb":746,"lim":747,"fl":748,"triangleleft":749,"flat":750,"sl":751,"box":752,"Ġ---":753,"sqcup":754,"jlim":755,"ls":756,"mo":757,"dels":758,"ref":759,"models":760,"tag":761,"Pr":762,"mal":763,"ou":764,"llap":765,"thinspace":766,"enskip":767,"Vec":768,"ebox":769,"kebox":770,"nor":771,"rd":772,"squ":773,"vline":774,"¿½":775,"�":776,"Ġ�":777,"makebox":778,"surd":779,"normal":780,"are":781,"square":782,"pou":783,"mathrel":784,"varOmega":785,"nds":786,"smallsetminus":787,"pounds":788,"ns":789,"ss":790,"smi":791,"mathor":792,"rightlef":793,"textup":794,"tharpoons":795,"smile":796,"mathord":797,"rightleftharpoons":798,"cc":799,"Ġ\\-":800,"succ":801,"ftarrow":802,"rtimes":803,"det":804,"prec":805,"texttt":806,"oslash":807,"Ġ\\&":808,"arrowvert":809,"lg":810,"Ġmm":811,"inter":812,"ngleftarrow":813,"hfil":814,"intercal":815,"frow":816,"Ġ\\*":817,"frown":818,"mpe":819,"Ġpt":820,"varpro":821,"searrow":822,"bumpe":823,"varprojlim":824,"bumpeq":825,"Down":826,"SS":827,"cd":828,"ere":829,"gcd":830,"ohe":831,"tw":832,"leme":833,"there":834,"injlim":835,"tit":836,"adrightarrow":837,"varinjlim":838,"comp":839,"textit":840,"fore":841,"overleftrightarrow":842,"Downarrow":843,"oheadrightarrow":844,"twoheadrightarrow":845,"lement":846,"therefore":847,"complement":848,"ca":849,"thi":850,"longleftarrow":851,"bigm":852,"triangleright":853,"nearrow":854,"\\#":855,"nce":856,"ral":857,"cance":858,"thick":859,"cancel":860,"Uparrow":861,"nat":862,"ural":863,"mathstrut":864,"suit":865,"bigcirc":866,"smallskip":867,"diamondsuit":868,"normalsize":869,"natural":870,"gt":871,"less":872,"mathtt":873,"bigsqcup":874,"thicksim":875,"lesssim":876,"bow":877,"llde":878,"tie":879,"nullde":880,"miter":881,"limiter":882,"kern":883,"bowtie":884,"nulldelimiter":885,"nulldelimiterspace":886,"Da":887,"hphantom":888,"ro":889,"vDa":890,"barwedge":891,"beth":892,"eqno":893,"vDash":894,"AR":895,"Di":896,"GE":897,"LAR":898,"dskip":899,"ts":900,"Ġ@":901,"medskip":902,"ndown":903,"gets":904,"coprod":905,"dotsm":906,"smash":907,"rightharpoondown":908,"Diamond":909,"LARGE":910,"nrightarrow":911,"nleftrightarrow":912,"rsim":913,"rne":914,"warrow":915,"mathc":916,"corne":917,"textnormal":918,"preceq":919,"gtrsim":920,"roup":921,"corner":922,"Ġ\\[":923,"Ġ\\]":924,"mathope":925,"lefteq":926,"lose":927,"varkappa":928,"Bigm":929,"Biggm":930,"mathclose":931,"mathopen":932,"lefteqn":933,"Bar":934,"Ti":935,"lr":936,"swarrow":937,"uge":938,"vru":939,"xleftarrow":940,"mathnormal":941,"rightrightarrow":942,"rightleftarrow":943,"sqsubseteq":944,"succeq":945,"Tilde":946,"lrcorner":947,"vrule":948,"rightrightarrows":949,"rightleftarrows":950,"AA":951,"Hat":952,"ak":953,"ble":954,"dou":955,"hss":956,"min":957,"nright":958,"nleftarrow":959,"uph":960,"wbre":961,"allo":962,"side":963,"sqcap":964,"hom":965,"bigodot":966,"arpoonright":967,"blebarwedge":968,"doublebarwedge":969,"upharpoonright":970,"wbreak":971,"allowbreak":972,"sideset":973,"--":974,"Huge":975,"amal":976,"do":977,"fbox":978,"group":979,"hskip":980,"lse":981,"pprox":982,"rk":983,"rgroup":984,"rapprox":985,"Ġin":986,"arrayco":987,"sure":988,"varlim":989,"pmb":990,"cite":991,"substack":992,"leftrightarrows":993,"supsetneq":994,"Longleftarrow":995,"updownarrow":996,"ensure":997,"lgroup":998,"gtrapprox":999,"amalg":1000,"lsep":1001,"arraycolsep":1002,"ensuremath":1003,"asym":1004,"ch":1005,"dig":1006,"ddag":1007,"ew":1008,"gra":1009,"gime":1010,"jo":1011,"ltimes":1012,"nleq":1013,"tch":1014,"frame":1015,"max":1016,"thde":1017,"inrel":1018,"ver":1019,"withde":1020,"ointop":1021,"notag":1022,"smallint":1023,"skew":1024,"lims":1025,"asymp":1026,"digamma":1027,"grave":1028,"gimel":1029,"joinrel":1030,"framebox":1031,"withdelims":1032,"Ar":1033,"Rrightarrow":1034,"ae":1035,"ag":1036,"fill":1037,"hspace":1038,"huge":1039,"lq":1040,"nwarrow":1041,"wline":1042,"Ġ14":1043,"mark":1044,"led":1045,"inf":1046,"inde":1047,"Ġex":1048,"pitch":1049,"dotsi":1050,"intop":1051,"rowvert":1052,"llcorner":1053,"black":1054,"leqq":1055,"biggm":1056,"approxeq":1057,"diag":1058,"textsc":1059,"textsl":1060,"circled":1061,"fork":1062,"cur":1063,"newline":1064,"negthick":1065,"atopwithdelims":1066,"Leftarrow":1067,"footnotemark":1068,"uplus":1069,"subsetneqq":1070,"---":1071,"varlimsup":1072,"varliminf":1073,"verb":1074,"Arrowvert":1075,"pitchfork":1076,"blacksquare":1077,"diagup":1078,"negthickspace":1079,"23":1080,"25":1081,"\\-":1082,"\\/":1083,"ape":1084,"ckap":1085,"dddot":1086,"erline":1087,"ever":1088,"ij":1089,"ice":1090,"ly":1091,"md":1092,"nda":1093,"nnu":1094,"nmid":1095,"nRightarrow":1096,"nVdash":1097,"of":1098,"off":1099,"sho":1100,"spe":1101,"wr":1102,"ymath":1103,"Ġ#":1104,"Ġ\\'":1105,"Ġ\\^":1106,"Ġ10":1107,"Ġ15":1108,"mannu":1109,"igarrow":1110,"fter":1111,"meral":1112,"leftrightharpoo":1113,"rightsqu":1114,"def":1115,"arrayst":1116,"rtmid":1117,"interline":1118,"vearrow":1119,"ngeq":1120,"hoice":1121,"lax":1122,"varGamma":1123,"varpropto":1124,"vartriangle":1125,"varUpsilon":1126,"biguplus":1127,"expa":1128,"Ġ<$":1129,"mathbin":1130,"perca":1131,"textcircled":1132,"textmd":1133,"scsh":1134,"cial":1135,"retch":1136,"relax":1137,"overwithdelims":1138,"noinde":1139,"owns":1140,"veebar":1141,"underbar":1142,"underrightarrow":1143,"upperca":1144,"backsimeq":1145,"trianglelefteq":1146,"boxtimes":1147,"boxed":1148,"preccur":1149,"thickap":1150,"root":1151,"romannu":1152,"mathchoice":1153,"index":1154,"circledcirc":1155,"curvearrow":1156,"everymath":1157,"lyeq":1158,"ndafter":1159,"offinterline":1160,"shortmid":1161,"special":1162,"leftrightharpoons":1163,"rightsquigarrow":1164,"arraystretch":1165,"expandafter":1166,"scshape":1167,"noindent":1168,"uppercase":1169,"preccurlyeq":1170,"thickapprox":1171,"romannumeral":1172,"curvearrowright":1173,"offinterlineskip":1174},"merges":["Ġ }","Ġ {","Ġ \\","Ġ _","Ġ ^","Ġ 2","Ġ )","Ġ (","Ġ 1","r a","Ġ =","Ġ -","č Ċ","Ġ ,","f ra","fra c","Ġ +","m a","t a","i g","Ġ 0","a r","a l","l e","Ġ i","t h","Ġ x","f t","ig h","m e","r igh","ma th","Ġ n","Ġ .","Ġ\\ ,","i n","p h","Ġ d","le ft","Ġ a","righ t","a m","e ta","t i","Ġ m","m u","Ġ 3","Ġ k","Ġ t","Ġ e","Ġ r","Ġ &","Ġ c","Ġ p","s i","r m","d e","math rm","Ġ 4","Ġ s","p r","Ġ ~","ph a","Ġ l","al pha","d a","Ġ A","Ġ\\ ;","o t","p i","p ar","ti al","par tial","i me","pr ime","p si","d ot","Ġ j","Ġ b","Ġ f","l ta","Ġ |","am ma","b da","am bda","ph i","Ġ q","b f","Ġ g","n u","Ġ z","ra y","ar ray","Ġ M","Ġ T","Ġ y","c al","b ar","Ġ N","ig ma","Ġ R","r t","l ambda","in t","Ġ B","v e","n g","q u","Ġ L","Ġ /","l o","b eta","ng le","Ġ u","de lta","s q","sq rt","th eta","Ġ\\ \\","g amma","Ġ ]","s igma","g a","me ga","Ġ D","Ġ F","Ġ [","Ġ S","math bf","s u","Ġ P","lo n","Ġ v","su m","psi lon","Ġ V","Ġ C","c dot","e psilon","a t","h at","a d","qu ad","Ġ h","h o","r ho","h i","t o","Ġ E","l a","Ġ H","l de","ti lde","Ġ Q","Ġ 5","v ar","Ġ X","Ġ G","b e","n d","o mega","e nd","g in","be gin","ta u","Ġ 6","n a","ve c","Ġ I","Ġ o","ra ngle","Ġ *","D e","De lta","G amma","p e","ft y","in fty","Ġ K","x i","Ġ 8","o w","Ġ J","Ġ U","r ow","ta r","g e","P hi","a p","Ġ W","c o","me s","ti mes","s in","Ġ Z","la ngle","o pe","r na","ra to","ope rato","rna me","operato rname","tar row","l in","lin e","var phi","p m","r line","L ambda","Ġ\\ !","Ġ ;","dot s","co s","Ġ w","righ tarrow","b ig","c hi","o ve","Ġ\\ }","ove rline","Ġ 7","e x","p a","s t","pr o","q quad","i v","e qu","equ iv","Ġ O","l n","O mega","l l","Ġ 9","k ap","kap pa","B ig","Ġ\\ {","da g","Ġ Y","\\ {","var epsilon","cdot s","Ġ :","math cal","P si","Ġ >","b o","bo l","Ġ <","ge r","dag ger","l dots","e ll","b la","na bla","ex p","y le","st yle","z eta","S igma","w i","wi de","si m","le q","Ġ !","big g","math b","mathb b","Ġ\\ :","h bar","o times","bol d","\\ }","m i","pro x","ap prox","P i","lo g","mi d","s p","ve rt","d i","pro d","pe r","per p","y style","la ystyle","sp laystyle","di splaystyle","me q","si meq","e d","w ed","wed ge","wide tilde","s y","sy m","sym bol","bold symbol","c k","t ex","tex t","r i","T h","Th eta","ge q","s e","e q","n de","u nde","ta n","s c","a st","r c","se t","p t","wide hat","c i","ci rc","r e","ri pt","sc ript","unde rline","Ġ\\ |","re l","n eq","s ta","sta ck","stack rel","sin h","o p","u s","cos h","Big g","c e","text style","s tar","n ot","fra k","math frak","m p","bigg r","l us","op lus","var theta","bigg l","Big r","b ra","Big l","f o","s f","su b","sub set","ng rightarrow","e c","bold math","ra ll","fo rall","script style","ec t","par row","u parrow","b j","bj ect","p to","pro pto","Ġ '","lo ngrightarrow","big l","big r","o int","p s","ma ps","maps to","o m","l le","\\ |","d dot","c u","b in","bin om","v dots","a ngle","left rightarrow","ove r","o r","math sf","cu p","bra ce","n o","ar c","f lo","flo or","t ri","tri angle","X i","c ot","b ot","co ng","i t","m be","nu mbe","no numbe","nonumbe r","c ap","R igh","Righ tarrow","z e","si ze","text rm","n e","arc tan","ra lle","pa ralle","paralle l","c frac","Ġ- -","o bject","ĠO bject","bra ck","s h","ar row","ow n","var rho","subset eq","r brace","text bf","i math","o d","d own","h e","la nd","script scriptstyle","script size","c he","che ck","s la","over rightarrow","down arrow","Bigg l","g g","n to","pha nto","phanto m","e xi","h line","st s","exi sts","Bigg r","b u","r floor","d dots","i o","io ta","lle t","bu llet","co lon","in us","U psilon","l floor","l brack","unde rbrace","ne g","I m","math it","t in","tin y","j math","le f","sla sh","ve e","m inus","set minus","R e","i int","lef tarrow","V e","Ve rt","at op","su p","big cup","w p","di m","se c","sup set","L o","lo r","pm od","m od","big oplus","i l","b mod","co th","L e","ft rightarrow","Le ftrightarrow","ng leftrightarrow","s ma","u psilon","\\ ,","c sc","e ph","al eph","big wedge","arc sin","sma ll","o dot","over set","r brack","mi t","l brace","l i","ar p","ar ge","Ġ\\ #","b re","text sf","Lo ngrightarrow","bre ve","e m","y set","var pi","pt yset","em ptyset","f f","i ff","n t","e r","la p","ln ot","da sh","unde r","sla nt","ar g","under set","B o","Bo x","Ġ \"","s pa","spa ce","de g","i iint","o o","ot not","fo otnot","arp oo","footnot e","r lap","e s","i mp","s b","t e","big triangle","li es","imp lies","\\ ;","k er","footnote size","th arpoo","u p","a cu","acu te","lo ngleftrightarrow","e il","l ce","rc eil","lce il","v phantom","e n","th in","a ck","b ack","t t","back slash","x rightarrow","v dash","to p","righ tharpoo","var sigma","Lo ngleftrightarrow","math op","l arge","big cap","leq slant","Ġ `","over brace","nu p","rightharpoo nup","big otimes","triangle q","L arge","r u","nu ll","bigtriangle up","var no","thin g","varno thing","dot eq","Ġ\\ _","over leftarrow","h f","big star","en space","\\ !","st ru","stru t","om inus","d iv","o nd","am ond","d dagger","Ġc m","n i","s k","di amond","r Vert","pr ot","prot ect","i p","var Delta","not in","sk ip","l Vert","Ġ\\ /","dots c","i ll","u le","\\ :","hf ill","k rightarrow","o krightarrow","ho okrightarrow","sh arp","V dash","big vee","subset neq","supset eq","Ġ ?","ng mapsto","lo ngmapsto","cdot p","geq slant","bigtriangle down","dots b","li m","f l","triangle left","fl at","s l","bo x","Ġ-- -","sq cup","j lim","l s","m o","de ls","re f","mo dels","ta g","P r","ma l","o u","l lap","thin space","en skip","V ec","e box","k ebox","n or","r d","s qu","v line","¿ ½","ï ¿½","Ġ �","ma kebox","su rd","nor mal","ar e","squ are","p ou","math rel","var Omega","nd s","small setminus","pou nds","n s","s s","s mi","math or","right lef","text up","tharpoo ns","smi le","mathor d","rightlef tharpoons","c c","Ġ\\ -","su cc","f tarrow","r times","de t","pr ec","text tt","o slash","Ġ\\ &","arrow vert","l g","Ġm m","int er","ngle ftarrow","hf il","inter cal","f row","Ġ\\ *","frow n","m pe","Ġp t","var pro","se arrow","bu mpe","varpro jlim","bumpe q","D own","S S","c d","e re","g cd","o he","t w","le me","th ere","in jlim","ti t","ad rightarrow","var injlim","co mp","tex tit","fo re","over leftrightarrow","Down arrow","ohe adrightarrow","tw oheadrightarrow","leme nt","there fore","comp lement","c a","th i","lo ngleftarrow","big m","triangle right","ne arrow","\\ #","n ce","ra l","ca nce","thi ck","cance l","U parrow","n at","u ral","math strut","su it","big circ","small skip","diamond suit","normal size","nat ural","g t","le ss","math tt","big sqcup","thick sim","less sim","b ow","l lde","ti e","nu llde","mit er","li miter","ker n","bow tie","nullde limiter","nulldelimiter space","D a","h phantom","r o","v Da","bar wedge","be th","eq no","vDa sh","A R","D i","G E","L AR","d skip","t s","Ġ @","me dskip","nd own","ge ts","co prod","dots m","sma sh","rightharpoo ndown","Di amond","LAR GE","n rightarrow","n leftrightarrow","r sim","r ne","w arrow","math c","co rne","text normal","prec eq","gt rsim","ro up","corne r","Ġ\\ [","Ġ\\ ]","math ope","left eq","lo se","var kappa","Big m","Bigg m","mathc lose","mathope n","lefteq n","B ar","T i","l r","s warrow","u ge","v ru","x leftarrow","math normal","right rightarrow","right leftarrow","sq subseteq","succ eq","Ti lde","lr corner","vru le","rightrightarrow s","rightleftarrow s","A A","H at","a k","b le","d ou","h ss","m in","n right","n leftarrow","u ph","w bre","al lo","si de","sq cap","ho m","big odot","arpoo nright","ble barwedge","dou blebarwedge","uph arpoonright","wbre ak","allo wbreak","side set","- -","H uge","a mal","d o","f box","g roup","h skip","l se","p prox","r k","r group","ra pprox","Ġi n","array co","su re","var lim","pm b","ci te","sub stack","leftrightarrow s","supset neq","Lo ngleftarrow","up downarrow","en sure","lg roup","gt rapprox","amal g","lse p","arrayco lsep","ensure math","a sym","c h","d ig","d dag","e w","g ra","g ime","j o","l times","n leq","t ch","fra me","ma x","th de","in rel","ve r","wi thde","oint op","no tag","small int","sk ew","lim s","asym p","dig amma","gra ve","gime l","jo inrel","frame box","withde lims","A r","R rightarrow","a e","a g","f ill","h space","h uge","l q","n warrow","w line","Ġ1 4","ma rk","le d","in f","in de","Ġe x","pi tch","dot si","int op","row vert","ll corner","bla ck","leq q","bigg m","approx eq","di ag","text sc","text sl","circ led","fo rk","cu r","ne wline","neg thick","atop withdelims","Le ftarrow","footnote mark","up lus","subsetneq q","-- -","varlim sup","varlim inf","ver b","Ar rowvert","pitch fork","black square","diag up","negthick space","2 3","2 5","\\ -","\\ /","a pe","c kap","d ddot","e rline","e ver","i j","i ce","l y","m d","n da","n nu","n mid","n Rightarrow","n Vdash","o f","o ff","s ho","s pe","w r","y math","Ġ #","Ġ\\ '","Ġ\\ ^","Ġ1 0","Ġ1 5","ma nnu","ig arrow","ft er","me ral","left rightharpoo","right squ","de f","array st","rt mid","int erline","ve arrow","ng eq","ho ice","la x","var Gamma","var propto","var triangle","var Upsilon","big uplus","ex pa","Ġ< $","mathb in","per ca","text circled","text md","sc sh","ci al","re tch","re lax","over withdelims","no inde","own s","vee bar","under bar","under rightarrow","up perca","back simeq","triangleleft eq","box times","box ed","prec cur","thi ckap","ro ot","ro mannu","mathc hoice","inde x","circled circ","cur vearrow","ever ymath","ly eq","nda fter","off interline","sho rtmid","spe cial","leftrightharpoo ns","rightsqu igarrow","arrayst retch","expa ndafter","scsh ape","noinde nt","upperca se","preccur lyeq","thickap prox","romannu meral","curvearrow right","offinterline skip"]}} diff --git a/ppocr/utils/dict/latex_symbol_dict.txt b/ppocr/utils/dict/latex_symbol_dict.txt new file mode 100644 index 0000000..69b1b84 --- /dev/null +++ b/ppocr/utils/dict/latex_symbol_dict.txt @@ -0,0 +1,111 @@ +eos +sos +! +' +( +) ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +< += +> +A +B +C +E +F +G +H +I +L +M +N +P +R +S +T +V +X +Y +[ +\Delta +\alpha +\beta +\cdot +\cdots +\cos +\div +\exists +\forall +\frac +\gamma +\geq +\in +\infty +\int +\lambda +\ldots +\leq +\lim +\log +\mu +\neq +\phi +\pi +\pm +\prime +\rightarrow +\sigma +\sin +\sqrt +\sum +\tan +\theta +\times +] +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +\{ +| +\} +{ +} +^ +_ diff --git a/ppocr/utils/dict/latin_dict.txt b/ppocr/utils/dict/latin_dict.txt new file mode 100644 index 0000000..e166bf3 --- /dev/null +++ b/ppocr/utils/dict/latin_dict.txt @@ -0,0 +1,185 @@ + +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +{ +} +¡ +£ +§ +ª +« +­ +° +² +³ +´ +µ +· +º +» +¿ +À +Á + +Ä +Å +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ò +Ó +Ô +Õ +Ö +Ú +Ü +Ý +ß +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +ą +Ć +ć +Č +č +Đ +đ +ę +ı +Ł +ł +ō +Œ +œ +Š +š +Ÿ +Ž +ž +ʒ +β +δ +ε +з +Ṡ +‘ +€ +™ diff --git a/ppocr/utils/dict/layout_dict/layout_cdla_dict.txt b/ppocr/utils/dict/layout_dict/layout_cdla_dict.txt new file mode 100644 index 0000000..5380334 --- /dev/null +++ b/ppocr/utils/dict/layout_dict/layout_cdla_dict.txt @@ -0,0 +1,10 @@ +text +title +figure +figure_caption +table +table_caption +header +footer +reference +equation diff --git a/ppocr/utils/dict/layout_dict/layout_publaynet_dict.txt b/ppocr/utils/dict/layout_dict/layout_publaynet_dict.txt new file mode 100644 index 0000000..d9c92da --- /dev/null +++ b/ppocr/utils/dict/layout_dict/layout_publaynet_dict.txt @@ -0,0 +1,5 @@ +text +title +list +table +figure diff --git a/ppocr/utils/dict/layout_dict/layout_table_dict.txt b/ppocr/utils/dict/layout_dict/layout_table_dict.txt new file mode 100644 index 0000000..ecbe2e6 --- /dev/null +++ b/ppocr/utils/dict/layout_dict/layout_table_dict.txt @@ -0,0 +1 @@ +table diff --git a/ppocr/utils/dict/mr_dict.txt b/ppocr/utils/dict/mr_dict.txt new file mode 100644 index 0000000..283b150 --- /dev/null +++ b/ppocr/utils/dict/mr_dict.txt @@ -0,0 +1,153 @@ + +! +# +$ +% +& +' +( ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +É +é +ँ +ं +ः +अ +आ +इ +ई +उ +ऊ +ए +ऐ +ऑ +ओ +औ +क +ख +ग +घ +च +छ +ज +झ +ञ +ट +ठ +ड +ढ +ण +त +थ +द +ध +न +प +फ +ब +भ +म +य +र +ऱ +ल +ळ +व +श +ष +स +ह +़ +ा +ि +ी +ु +ू +ृ +ॅ +े +ै +ॉ +ो +ौ +् +० +१ +२ +३ +४ +५ +६ +७ +८ +९ diff --git a/ppocr/utils/dict/ne_dict.txt b/ppocr/utils/dict/ne_dict.txt new file mode 100644 index 0000000..5a7df95 --- /dev/null +++ b/ppocr/utils/dict/ne_dict.txt @@ -0,0 +1,153 @@ + +! +# +$ +% +& +' +( ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +É +é +ः +अ +आ +इ +ई +उ +ऊ +ऋ +ए +ऐ +ओ +औ +क +ख +ग +घ +ङ +च +छ +ज +झ +ञ +ट +ठ +ड +ढ +ण +त +थ +द +ध +न +ऩ +प +फ +ब +भ +म +य +र +ऱ +ल +व +श +ष +स +ह +़ +ा +ि +ी +ु +ू +ृ +े +ै +ो +ौ +् +॒ +ॠ +। +० +१ +२ +३ +४ +५ +६ +७ +८ +९ diff --git a/ppocr/utils/dict/oc_dict.txt b/ppocr/utils/dict/oc_dict.txt new file mode 100644 index 0000000..e88af8b --- /dev/null +++ b/ppocr/utils/dict/oc_dict.txt @@ -0,0 +1,96 @@ +o +c +_ +i +m +g +/ +2 +0 +I +L +S +V +R +C +1 +v +a +l +4 +3 +. +j +p +r +e +è +t +9 +7 +5 +8 +n +' +b +s +6 +q +u +á +d +ò +à +h +z +f +ï +í +A +ç +x +ó +é +P +O +Ò +ü +k +À +F +- +ú +­ +æ +Á +D +E +w +K +T +N +y +U +Z +G +B +J +H +M +W +Y +X +Q +% +$ +, +@ +& +! +: +( +# +? ++ +É + diff --git a/ppocr/utils/dict/parseq_dict.txt b/ppocr/utils/dict/parseq_dict.txt new file mode 100644 index 0000000..87c6d67 --- /dev/null +++ b/ppocr/utils/dict/parseq_dict.txt @@ -0,0 +1,94 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +: +; +< += +> +? +@ +[ +\ +] +^ +_ +` +{ +| +} +~ diff --git a/ppocr/utils/dict/ppocrv4_doc_dict.txt b/ppocr/utils/dict/ppocrv4_doc_dict.txt new file mode 100644 index 0000000..09e275b --- /dev/null +++ b/ppocr/utils/dict/ppocrv4_doc_dict.txt @@ -0,0 +1,15629 @@ +' +疗 +绚 +诚 +娇 +溜 +题 +贿 +者 +廖 +更 +纳 +加 +奉 +公 +一 +就 +汴 +计 +与 +路 +房 +原 +妇 +2 +0 +8 +- +7 +其 +> +: +] +, +, +骑 +刈 +全 +消 +昏 +傈 +安 +久 +钟 +嗅 +不 +影 +处 +驽 +蜿 +资 +关 +椤 +地 +瘸 +专 +问 +忖 +票 +嫉 +炎 +韵 +要 +月 +田 +节 +陂 +鄙 +捌 +备 +拳 +伺 +眼 +网 +盎 +大 +傍 +心 +东 +愉 +汇 +蹿 +科 +每 +业 +里 +航 +晏 +字 +平 +录 +先 +1 +3 +彤 +鲶 +产 +稍 +督 +腴 +有 +象 +岳 +注 +绍 +在 +泺 +文 +定 +核 +名 +水 +过 +理 +让 +偷 +率 +等 +这 +发 +” +为 +含 +肥 +酉 +相 +鄱 +七 +编 +猥 +锛 +日 +镀 +蒂 +掰 +倒 +辆 +栾 +栗 +综 +涩 +州 +雌 +滑 +馀 +了 +机 +块 +司 +宰 +甙 +兴 +矽 +抚 +保 +用 +沧 +秩 +如 +收 +息 +滥 +页 +疑 +埠 +! +! +姥 +异 +橹 +钇 +向 +下 +跄 +的 +椴 +沫 +国 +绥 +獠 +报 +开 +民 +蜇 +何 +分 +凇 +长 +讥 +藏 +掏 +施 +羽 +中 +讲 +派 +嘟 +人 +提 +浼 +间 +世 +而 +古 +多 +倪 +唇 +饯 +控 +庚 +首 +赛 +蜓 +味 +断 +制 +觉 +技 +替 +艰 +溢 +潮 +夕 +钺 +外 +摘 +枋 +动 +双 +单 +啮 +户 +枇 +确 +锦 +曜 +杜 +或 +能 +效 +霜 +盒 +然 +侗 +电 +晁 +放 +步 +鹃 +新 +杖 +蜂 +吒 +濂 +瞬 +评 +总 +隍 +对 +独 +合 +也 +是 +府 +青 +天 +诲 +墙 +组 +滴 +级 +邀 +帘 +示 +已 +时 +骸 +仄 +泅 +和 +遨 +店 +雇 +疫 +持 +巍 +踮 +境 +只 +亨 +目 +鉴 +崤 +闲 +体 +泄 +杂 +作 +般 +轰 +化 +解 +迂 +诿 +蛭 +璀 +腾 +告 +版 +服 +省 +师 +小 +规 +程 +线 +海 +办 +引 +二 +桧 +牌 +砺 +洄 +裴 +修 +图 +痫 +胡 +许 +犊 +事 +郛 +基 +柴 +呼 +食 +研 +奶 +律 +蛋 +因 +葆 +察 +戏 +褒 +戒 +再 +李 +骁 +工 +貂 +油 +鹅 +章 +啄 +休 +场 +给 +睡 +纷 +豆 +器 +捎 +说 +敏 +学 +会 +浒 +设 +诊 +格 +廓 +查 +来 +霓 +室 +溆 +¢ +诡 +寥 +焕 +舜 +柒 +狐 +回 +戟 +砾 +厄 +实 +翩 +尿 +五 +入 +径 +惭 +喹 +股 +宇 +篝 +| +; +美 +期 +云 +九 +祺 +扮 +靠 +锝 +槌 +系 +企 +酰 +阊 +暂 +蚕 +忻 +豁 +本 +羹 +执 +条 +钦 +H +獒 +限 +进 +季 +楦 +于 +芘 +玖 +铋 +茯 +未 +答 +粘 +括 +样 +精 +欠 +矢 +甥 +帷 +嵩 +扣 +令 +仔 +风 +皈 +行 +支 +部 +蓉 +刮 +站 +蜡 +救 +钊 +汗 +松 +嫌 +成 +可 +. +鹤 +院 +从 +交 +政 +怕 +活 +调 +球 +局 +验 +髌 +第 +韫 +谗 +串 +到 +圆 +年 +米 +/ +* +友 +忿 +检 +区 +看 +自 +敢 +刃 +个 +兹 +弄 +流 +留 +同 +没 +齿 +星 +聆 +轼 +湖 +什 +三 +建 +蛔 +儿 +椋 +汕 +震 +颧 +鲤 +跟 +力 +情 +璺 +铨 +陪 +务 +指 +族 +训 +滦 +鄣 +濮 +扒 +商 +箱 +十 +召 +慷 +辗 +所 +莞 +管 +护 +臭 +横 +硒 +嗓 +接 +侦 +六 +露 +党 +馋 +驾 +剖 +高 +侬 +妪 +幂 +猗 +绺 +骐 +央 +酐 +孝 +筝 +课 +徇 +缰 +门 +男 +西 +项 +句 +谙 +瞒 +秃 +篇 +教 +碲 +罚 +声 +呐 +景 +前 +富 +嘴 +鳌 +稀 +免 +朋 +啬 +睐 +去 +赈 +鱼 +住 +肩 +愕 +速 +旁 +波 +厅 +健 +茼 +厥 +鲟 +谅 +投 +攸 +炔 +数 +方 +击 +呋 +谈 +绩 +别 +愫 +僚 +躬 +鹧 +胪 +炳 +招 +喇 +膨 +泵 +蹦 +毛 +结 +5 +4 +谱 +识 +陕 +粽 +婚 +拟 +构 +且 +搜 +任 +潘 +比 +郢 +妨 +醪 +陀 +桔 +碘 +扎 +选 +哈 +骷 +楷 +亿 +明 +缆 +脯 +监 +睫 +逻 +婵 +共 +赴 +淝 +凡 +惦 +及 +达 +揖 +谩 +澹 +减 +焰 +蛹 +番 +祁 +柏 +员 +禄 +怡 +峤 +龙 +白 +叽 +生 +闯 +起 +细 +装 +谕 +竟 +聚 +钙 +上 +导 +渊 +按 +艾 +辘 +挡 +耒 +盹 +饪 +臀 +记 +邮 +蕙 +受 +各 +医 +搂 +普 +滇 +朗 +茸 +带 +翻 +酚 +( +光 +堤 +墟 +蔷 +万 +幻 +〓 +瑙 +辈 +昧 +盏 +亘 +蛀 +吉 +铰 +请 +子 +假 +闻 +税 +井 +诩 +哨 +嫂 +好 +面 +琐 +校 +馊 +鬣 +缂 +营 +访 +炖 +占 +农 +缀 +否 +经 +钚 +棵 +趟 +张 +亟 +吏 +茶 +谨 +捻 +论 +迸 +堂 +玉 +信 +吧 +瞠 +乡 +姬 +寺 +咬 +溏 +苄 +皿 +意 +赉 +宝 +尔 +钰 +艺 +特 +唳 +踉 +都 +荣 +倚 +登 +荐 +丧 +奇 +涵 +批 +炭 +近 +符 +傩 +感 +道 +着 +菊 +虹 +仲 +众 +懈 +濯 +颞 +眺 +南 +释 +北 +缝 +标 +既 +茗 +整 +撼 +迤 +贲 +挎 +耱 +拒 +某 +妍 +卫 +哇 +英 +矶 +藩 +治 +他 +元 +领 +膜 +遮 +穗 +蛾 +飞 +荒 +棺 +劫 +么 +市 +火 +温 +拈 +棚 +洼 +转 +果 +奕 +卸 +迪 +伸 +泳 +斗 +邡 +侄 +涨 +屯 +萋 +胭 +氡 +崮 +枞 +惧 +冒 +彩 +斜 +手 +豚 +随 +旭 +淑 +妞 +形 +菌 +吲 +沱 +争 +驯 +歹 +挟 +兆 +柱 +传 +至 +包 +内 +响 +临 +红 +功 +弩 +衡 +寂 +禁 +老 +棍 +耆 +渍 +织 +害 +氵 +渑 +布 +载 +靥 +嗬 +虽 +苹 +咨 +娄 +库 +雉 +榜 +帜 +嘲 +套 +瑚 +亲 +簸 +欧 +边 +6 +腿 +旮 +抛 +吹 +瞳 +得 +镓 +梗 +厨 +继 +漾 +愣 +憨 +士 +策 +窑 +抑 +躯 +襟 +脏 +参 +贸 +言 +干 +绸 +鳄 +穷 +藜 +音 +折 +详 +) +举 +悍 +甸 +癌 +黎 +谴 +死 +罩 +迁 +寒 +驷 +袖 +媒 +蒋 +掘 +模 +纠 +恣 +观 +祖 +蛆 +碍 +位 +稿 +主 +澧 +跌 +筏 +京 +锏 +帝 +贴 +证 +糠 +才 +黄 +鲸 +略 +炯 +饱 +四 +出 +园 +犀 +牧 +容 +汉 +杆 +浈 +汰 +瑷 +造 +虫 +瘩 +怪 +驴 +济 +应 +花 +沣 +谔 +夙 +旅 +价 +矿 +以 +考 +s +u +呦 +晒 +巡 +茅 +准 +肟 +瓴 +詹 +仟 +褂 +译 +桌 +混 +宁 +怦 +郑 +抿 +些 +余 +鄂 +饴 +攒 +珑 +群 +阖 +岔 +琨 +藓 +预 +环 +洮 +岌 +宀 +杲 +瀵 +最 +常 +囡 +周 +踊 +女 +鼓 +袭 +喉 +简 +范 +薯 +遐 +疏 +粱 +黜 +禧 +法 +箔 +斤 +遥 +汝 +奥 +直 +贞 +撑 +置 +绱 +集 +她 +馅 +逗 +钧 +橱 +魉 +[ +恙 +躁 +唤 +9 +旺 +膘 +待 +脾 +惫 +购 +吗 +依 +盲 +度 +瘿 +蠖 +俾 +之 +镗 +拇 +鲵 +厝 +簧 +续 +款 +展 +啃 +表 +剔 +品 +钻 +腭 +损 +清 +锶 +统 +涌 +寸 +滨 +贪 +链 +吠 +冈 +伎 +迥 +咏 +吁 +览 +防 +迅 +失 +汾 +阔 +逵 +绀 +蔑 +列 +川 +凭 +努 +熨 +揪 +利 +俱 +绉 +抢 +鸨 +我 +即 +责 +膦 +易 +毓 +鹊 +刹 +玷 +岿 +空 +嘞 +绊 +排 +术 +估 +锷 +违 +们 +苟 +铜 +播 +肘 +件 +烫 +审 +鲂 +广 +像 +铌 +惰 +铟 +巳 +胍 +鲍 +康 +憧 +色 +恢 +想 +拷 +尤 +疳 +知 +S +Y +F +D +A +峄 +裕 +帮 +握 +搔 +氐 +氘 +难 +墒 +沮 +雨 +叁 +缥 +悴 +藐 +湫 +娟 +苑 +稠 +颛 +簇 +后 +阕 +闭 +蕤 +缚 +怎 +佞 +码 +嘤 +蔡 +痊 +舱 +螯 +帕 +赫 +昵 +升 +烬 +岫 +、 +疵 +蜻 +髁 +蕨 +隶 +烛 +械 +丑 +盂 +梁 +强 +鲛 +由 +拘 +揉 +劭 +龟 +撤 +钩 +呕 +孛 +费 +妻 +漂 +求 +阑 +崖 +秤 +甘 +通 +深 +补 +赃 +坎 +床 +啪 +承 +吼 +量 +暇 +钼 +烨 +阂 +擎 +脱 +逮 +称 +P +神 +属 +矗 +华 +届 +狍 +葑 +汹 +育 +患 +窒 +蛰 +佼 +静 +槎 +运 +鳗 +庆 +逝 +曼 +疱 +克 +代 +官 +此 +麸 +耧 +蚌 +晟 +例 +础 +榛 +副 +测 +唰 +缢 +迹 +灬 +霁 +身 +岁 +赭 +扛 +又 +菡 +乜 +雾 +板 +读 +陷 +徉 +贯 +郁 +虑 +变 +钓 +菜 +圾 +现 +琢 +式 +乐 +维 +渔 +浜 +左 +吾 +脑 +钡 +警 +T +啵 +拴 +偌 +漱 +湿 +硕 +止 +骼 +魄 +积 +燥 +联 +踢 +玛 +则 +窿 +见 +振 +畿 +送 +班 +钽 +您 +赵 +刨 +印 +讨 +踝 +籍 +谡 +舌 +崧 +汽 +蔽 +沪 +酥 +绒 +怖 +财 +帖 +肱 +私 +莎 +勋 +羔 +霸 +励 +哼 +帐 +将 +帅 +渠 +纪 +婴 +娩 +岭 +厘 +滕 +吻 +伤 +坝 +冠 +戊 +隆 +瘁 +介 +涧 +物 +黍 +并 +姗 +奢 +蹑 +掣 +垸 +锴 +命 +箍 +捉 +病 +辖 +琰 +眭 +迩 +艘 +绌 +繁 +寅 +若 +毋 +思 +诉 +类 +诈 +燮 +轲 +酮 +狂 +重 +反 +职 +筱 +县 +委 +磕 +绣 +奖 +晋 +濉 +志 +徽 +肠 +呈 +獐 +坻 +口 +片 +碰 +几 +村 +柿 +劳 +料 +获 +亩 +惕 +晕 +厌 +号 +罢 +池 +正 +鏖 +煨 +家 +棕 +复 +尝 +懋 +蜥 +锅 +岛 +扰 +队 +坠 +瘾 +钬 +@ +卧 +疣 +镇 +譬 +冰 +彷 +频 +黯 +据 +垄 +采 +八 +缪 +瘫 +型 +熹 +砰 +楠 +襁 +箐 +但 +嘶 +绳 +啤 +拍 +盥 +穆 +傲 +洗 +盯 +塘 +怔 +筛 +丿 +台 +恒 +喂 +葛 +永 +¥ +烟 +酒 +桦 +书 +砂 +蚝 +缉 +态 +瀚 +袄 +圳 +轻 +蛛 +超 +榧 +遛 +姒 +奘 +铮 +右 +荽 +望 +偻 +卡 +丶 +氰 +附 +做 +革 +索 +戚 +坨 +桷 +唁 +垅 +榻 +岐 +偎 +坛 +莨 +山 +殊 +微 +骇 +陈 +爨 +推 +嗝 +驹 +澡 +藁 +呤 +卤 +嘻 +糅 +逛 +侵 +郓 +酌 +德 +摇 +※ +鬃 +被 +慨 +殡 +羸 +昌 +泡 +戛 +鞋 +河 +宪 +沿 +玲 +鲨 +翅 +哽 +源 +铅 +语 +照 +邯 +址 +荃 +佬 +顺 +鸳 +町 +霭 +睾 +瓢 +夸 +椁 +晓 +酿 +痈 +咔 +侏 +券 +噎 +湍 +签 +嚷 +离 +午 +尚 +社 +锤 +背 +孟 +使 +浪 +缦 +潍 +鞅 +军 +姹 +驶 +笑 +鳟 +鲁 +》 +孽 +钜 +绿 +洱 +礴 +焯 +椰 +颖 +囔 +乌 +孔 +巴 +互 +性 +椽 +哞 +聘 +昨 +早 +暮 +胶 +炀 +隧 +低 +彗 +昝 +铁 +呓 +氽 +藉 +喔 +癖 +瑗 +姨 +权 +胱 +韦 +堑 +蜜 +酋 +楝 +砝 +毁 +靓 +歙 +锲 +究 +屋 +喳 +骨 +辨 +碑 +武 +鸠 +宫 +辜 +烊 +适 +坡 +殃 +培 +佩 +供 +走 +蜈 +迟 +翼 +况 +姣 +凛 +浔 +吃 +飘 +债 +犟 +金 +促 +苛 +崇 +坂 +莳 +畔 +绂 +兵 +蠕 +斋 +根 +砍 +亢 +欢 +恬 +崔 +剁 +餐 +榫 +快 +扶 +‖ +濒 +缠 +鳜 +当 +彭 +驭 +浦 +篮 +昀 +锆 +秸 +钳 +弋 +娣 +瞑 +夷 +龛 +苫 +拱 +致 +% +嵊 +障 +隐 +弑 +初 +娓 +抉 +汩 +累 +蓖 +" +唬 +助 +苓 +昙 +押 +毙 +破 +城 +郧 +逢 +嚏 +獭 +瞻 +溱 +婿 +赊 +跨 +恼 +璧 +萃 +姻 +貉 +灵 +炉 +密 +氛 +陶 +砸 +谬 +衔 +点 +琛 +沛 +枳 +层 +岱 +诺 +脍 +榈 +埂 +征 +冷 +裁 +打 +蹴 +素 +瘘 +逞 +蛐 +聊 +激 +腱 +萘 +踵 +飒 +蓟 +吆 +取 +咙 +簋 +涓 +矩 +曝 +挺 +揣 +座 +你 +史 +舵 +焱 +尘 +苏 +笈 +脚 +溉 +榨 +诵 +樊 +邓 +焊 +义 +庶 +儋 +蟋 +蒲 +赦 +呷 +杞 +诠 +豪 +还 +试 +颓 +茉 +太 +除 +紫 +逃 +痴 +草 +充 +鳕 +珉 +祗 +墨 +渭 +烩 +蘸 +慕 +璇 +镶 +穴 +嵘 +恶 +骂 +险 +绋 +幕 +碉 +肺 +戳 +刘 +潞 +秣 +纾 +潜 +銮 +洛 +须 +罘 +销 +瘪 +汞 +兮 +屉 +r +林 +厕 +质 +探 +划 +狸 +殚 +善 +煊 +烹 +〒 +锈 +逯 +宸 +辍 +泱 +柚 +袍 +远 +蹋 +嶙 +绝 +峥 +娥 +缍 +雀 +徵 +认 +镱 +谷 += +贩 +勉 +撩 +鄯 +斐 +洋 +非 +祚 +泾 +诒 +饿 +撬 +威 +晷 +搭 +芍 +锥 +笺 +蓦 +候 +琊 +档 +礁 +沼 +卵 +荠 +忑 +朝 +凹 +瑞 +头 +仪 +弧 +孵 +畏 +铆 +突 +衲 +车 +浩 +气 +茂 +悖 +厢 +枕 +酝 +戴 +湾 +邹 +飚 +攘 +锂 +写 +宵 +翁 +岷 +无 +喜 +丈 +挑 +嗟 +绛 +殉 +议 +槽 +具 +醇 +淞 +笃 +郴 +阅 +饼 +底 +壕 +砚 +弈 +询 +缕 +庹 +翟 +零 +筷 +暨 +舟 +闺 +甯 +撞 +麂 +茌 +蔼 +很 +珲 +捕 +棠 +角 +阉 +媛 +娲 +诽 +剿 +尉 +爵 +睬 +韩 +诰 +匣 +危 +糍 +镯 +立 +浏 +阳 +少 +盆 +舔 +擘 +匪 +申 +尬 +铣 +旯 +抖 +赘 +瓯 +居 +ˇ +哮 +游 +锭 +茏 +歌 +坏 +甚 +秒 +舞 +沙 +仗 +劲 +潺 +阿 +燧 +郭 +嗖 +霏 +忠 +材 +奂 +耐 +跺 +砀 +输 +岖 +媳 +氟 +极 +摆 +灿 +今 +扔 +腻 +枝 +奎 +药 +熄 +吨 +话 +q +额 +慑 +嘌 +协 +喀 +壳 +埭 +视 +著 +於 +愧 +陲 +翌 +峁 +颅 +佛 +腹 +聋 +侯 +咎 +叟 +秀 +颇 +存 +较 +罪 +哄 +岗 +扫 +栏 +钾 +羌 +己 +璨 +枭 +霉 +煌 +涸 +衿 +键 +镝 +益 +岢 +奏 +连 +夯 +睿 +冥 +均 +糖 +狞 +蹊 +稻 +爸 +刿 +胥 +煜 +丽 +肿 +璃 +掸 +跚 +灾 +垂 +樾 +濑 +乎 +莲 +窄 +犹 +撮 +战 +馄 +软 +络 +显 +鸢 +胸 +宾 +妲 +恕 +埔 +蝌 +份 +遇 +巧 +瞟 +粒 +恰 +剥 +桡 +博 +讯 +凯 +堇 +阶 +滤 +卖 +斌 +骚 +彬 +兑 +磺 +樱 +舷 +两 +娱 +福 +仃 +差 +找 +桁 +÷ +净 +把 +阴 +污 +戬 +雷 +碓 +蕲 +楚 +罡 +焖 +抽 +妫 +咒 +仑 +闱 +尽 +邑 +菁 +爱 +贷 +沥 +鞑 +牡 +嗉 +崴 +骤 +塌 +嗦 +订 +拮 +滓 +捡 +锻 +次 +坪 +杩 +臃 +箬 +融 +珂 +鹗 +宗 +枚 +降 +鸬 +妯 +阄 +堰 +盐 +毅 +必 +杨 +崃 +俺 +甬 +状 +莘 +货 +耸 +菱 +腼 +铸 +唏 +痤 +孚 +澳 +懒 +溅 +翘 +疙 +杷 +淼 +缙 +骰 +喊 +悉 +砻 +坷 +艇 +赁 +界 +谤 +纣 +宴 +晃 +茹 +归 +饭 +梢 +铡 +街 +抄 +肼 +鬟 +苯 +颂 +撷 +戈 +炒 +咆 +茭 +瘙 +负 +仰 +客 +琉 +铢 +封 +卑 +珥 +椿 +镧 +窨 +鬲 +寿 +御 +袤 +铃 +萎 +砖 +餮 +脒 +裳 +肪 +孕 +嫣 +馗 +嵇 +恳 +氯 +江 +石 +褶 +冢 +祸 +阻 +狈 +羞 +银 +靳 +透 +咳 +叼 +敷 +芷 +啥 +它 +瓤 +兰 +痘 +懊 +逑 +肌 +往 +捺 +坊 +甩 +呻 +〃 +沦 +忘 +膻 +祟 +菅 +剧 +崆 +智 +坯 +臧 +霍 +墅 +攻 +眯 +倘 +拢 +骠 +铐 +庭 +岙 +瓠 +′ +缺 +泥 +迢 +捶 +? +? +郏 +喙 +掷 +沌 +纯 +秘 +种 +听 +绘 +固 +螨 +团 +香 +盗 +妒 +埚 +蓝 +拖 +旱 +荞 +铀 +血 +遏 +汲 +辰 +叩 +拽 +幅 +硬 +惶 +桀 +漠 +措 +泼 +唑 +齐 +肾 +念 +酱 +虚 +屁 +耶 +旗 +砦 +闵 +婉 +馆 +拭 +绅 +韧 +忏 +窝 +醋 +葺 +顾 +辞 +倜 +堆 +辋 +逆 +玟 +贱 +疾 +董 +惘 +倌 +锕 +淘 +嘀 +莽 +俭 +笏 +绑 +鲷 +杈 +择 +蟀 +粥 +嗯 +驰 +逾 +案 +谪 +褓 +胫 +哩 +昕 +颚 +鲢 +绠 +躺 +鹄 +崂 +儒 +俨 +丝 +尕 +泌 +啊 +萸 +彰 +幺 +吟 +骄 +苣 +弦 +脊 +瑰 +〈 +诛 +镁 +析 +闪 +剪 +侧 +哟 +框 +螃 +守 +嬗 +燕 +狭 +铈 +缮 +概 +迳 +痧 +鲲 +俯 +售 +笼 +痣 +扉 +挖 +满 +咋 +援 +邱 +扇 +歪 +便 +玑 +绦 +峡 +蛇 +叨 +〖 +泽 +胃 +斓 +喋 +怂 +坟 +猪 +该 +蚬 +炕 +弥 +赞 +棣 +晔 +娠 +挲 +狡 +创 +疖 +铕 +镭 +稷 +挫 +弭 +啾 +翔 +粉 +履 +苘 +哦 +楼 +秕 +铂 +土 +锣 +瘟 +挣 +栉 +习 +享 +桢 +袅 +磨 +桂 +谦 +延 +坚 +蔚 +噗 +署 +谟 +猬 +钎 +恐 +嬉 +雒 +倦 +衅 +亏 +璩 +睹 +刻 +殿 +王 +算 +雕 +麻 +丘 +柯 +骆 +丸 +塍 +谚 +添 +鲈 +垓 +桎 +蚯 +芥 +予 +飕 +镦 +谌 +窗 +醚 +菀 +亮 +搪 +莺 +蒿 +羁 +足 +J +真 +轶 +悬 +衷 +靛 +翊 +掩 +哒 +炅 +掐 +冼 +妮 +l +谐 +稚 +荆 +擒 +犯 +陵 +虏 +浓 +崽 +刍 +陌 +傻 +孜 +千 +靖 +演 +矜 +钕 +煽 +杰 +酗 +渗 +伞 +栋 +俗 +泫 +戍 +罕 +沾 +疽 +灏 +煦 +芬 +磴 +叱 +阱 +榉 +湃 +蜀 +叉 +醒 +彪 +租 +郡 +篷 +屎 +良 +垢 +隗 +弱 +陨 +峪 +砷 +掴 +颁 +胎 +雯 +绵 +贬 +沐 +撵 +隘 +篙 +暖 +曹 +陡 +栓 +填 +臼 +彦 +瓶 +琪 +潼 +哪 +鸡 +摩 +啦 +俟 +锋 +域 +耻 +蔫 +疯 +纹 +撇 +毒 +绶 +痛 +酯 +忍 +爪 +赳 +歆 +嘹 +辕 +烈 +册 +朴 +钱 +吮 +毯 +癜 +娃 +谀 +邵 +厮 +炽 +璞 +邃 +丐 +追 +词 +瓒 +忆 +轧 +芫 +谯 +喷 +弟 +半 +冕 +裙 +掖 +墉 +绮 +寝 +苔 +势 +顷 +褥 +切 +衮 +君 +佳 +嫒 +蚩 +霞 +佚 +洙 +逊 +镖 +暹 +唛 +& +殒 +顶 +碗 +獗 +轭 +铺 +蛊 +废 +恹 +汨 +崩 +珍 +那 +杵 +曲 +纺 +夏 +薰 +傀 +闳 +淬 +姘 +舀 +拧 +卷 +楂 +恍 +讪 +厩 +寮 +篪 +赓 +乘 +灭 +盅 +鞣 +沟 +慎 +挂 +饺 +鼾 +杳 +树 +缨 +丛 +絮 +娌 +臻 +嗳 +篡 +侩 +述 +衰 +矛 +圈 +蚜 +匕 +筹 +匿 +濞 +晨 +叶 +骋 +郝 +挚 +蚴 +滞 +增 +侍 +描 +瓣 +吖 +嫦 +蟒 +匾 +圣 +赌 +毡 +癞 +恺 +百 +曳 +需 +篓 +肮 +庖 +帏 +卿 +驿 +遗 +蹬 +鬓 +骡 +歉 +芎 +胳 +屐 +禽 +烦 +晌 +寄 +媾 +狄 +翡 +苒 +船 +廉 +终 +痞 +殇 +々 +畦 +饶 +改 +拆 +悻 +萄 +£ +瓿 +乃 +訾 +桅 +匮 +溧 +拥 +纱 +铍 +骗 +蕃 +龋 +缬 +父 +佐 +疚 +栎 +醍 +掳 +蓄 +x +惆 +颜 +鲆 +榆 +〔 +猎 +敌 +暴 +谥 +鲫 +贾 +罗 +玻 +缄 +扦 +芪 +癣 +落 +徒 +臾 +恿 +猩 +托 +邴 +肄 +牵 +春 +陛 +耀 +刊 +拓 +蓓 +邳 +堕 +寇 +枉 +淌 +啡 +湄 +兽 +酷 +萼 +碚 +濠 +萤 +夹 +旬 +戮 +梭 +琥 +椭 +昔 +勺 +蜊 +绐 +晚 +孺 +僵 +宣 +摄 +冽 +旨 +萌 +忙 +蚤 +眉 +噼 +蟑 +付 +契 +瓜 +悼 +颡 +壁 +曾 +窕 +颢 +澎 +仿 +俑 +浑 +嵌 +浣 +乍 +碌 +褪 +乱 +蔟 +隙 +玩 +剐 +葫 +箫 +纲 +围 +伐 +决 +伙 +漩 +瑟 +刑 +肓 +镳 +缓 +蹭 +氨 +皓 +典 +畲 +坍 +铑 +檐 +塑 +洞 +倬 +储 +胴 +淳 +戾 +吐 +灼 +惺 +妙 +毕 +珐 +缈 +虱 +盖 +羰 +鸿 +磅 +谓 +髅 +娴 +苴 +唷 +蚣 +霹 +抨 +贤 +唠 +犬 +誓 +逍 +庠 +逼 +麓 +籼 +釉 +呜 +碧 +秧 +氩 +摔 +霄 +穸 +纨 +辟 +妈 +映 +完 +牛 +缴 +嗷 +炊 +恩 +荔 +茆 +掉 +紊 +慌 +莓 +羟 +阙 +萁 +磐 +另 +蕹 +辱 +鳐 +湮 +吡 +吩 +唐 +睦 +垠 +舒 +圜 +冗 +瞿 +溺 +芾 +囱 +匠 +僳 +汐 +菩 +饬 +漓 +黑 +霰 +浸 +濡 +窥 +毂 +蒡 +兢 +驻 +鹉 +芮 +诙 +迫 +雳 +厂 +忐 +臆 +猴 +鸣 +蚪 +栈 +箕 +羡 +渐 +莆 +捍 +眈 +哓 +趴 +蹼 +埕 +嚣 +骛 +宏 +淄 +斑 +噜 +严 +瑛 +垃 +椎 +诱 +压 +庾 +绞 +焘 +廿 +抡 +迄 +棘 +夫 +纬 +锹 +眨 +瞌 +侠 +脐 +竞 +瀑 +孳 +骧 +遁 +姜 +颦 +荪 +滚 +萦 +伪 +逸 +粳 +爬 +锁 +矣 +役 +趣 +洒 +颔 +诏 +逐 +奸 +甭 +惠 +攀 +蹄 +泛 +尼 +拼 +阮 +鹰 +亚 +颈 +惑 +勒 +〉 +际 +肛 +爷 +刚 +钨 +丰 +养 +冶 +鲽 +辉 +蔻 +画 +覆 +皴 +妊 +麦 +返 +醉 +皂 +擀 +〗 +酶 +凑 +粹 +悟 +诀 +硖 +港 +卜 +z +杀 +涕 +± +舍 +铠 +抵 +弛 +段 +敝 +镐 +奠 +拂 +轴 +跛 +袱 +e +t +沉 +菇 +俎 +薪 +峦 +秭 +蟹 +历 +盟 +菠 +寡 +液 +肢 +喻 +染 +裱 +悱 +抱 +氙 +赤 +捅 +猛 +跑 +氮 +谣 +仁 +尺 +辊 +窍 +烙 +衍 +架 +擦 +倏 +璐 +瑁 +币 +楞 +胖 +夔 +趸 +邛 +惴 +饕 +虔 +蝎 +§ +哉 +贝 +宽 +辫 +炮 +扩 +饲 +籽 +魏 +菟 +锰 +伍 +猝 +末 +琳 +哚 +蛎 +邂 +呀 +姿 +鄞 +却 +歧 +仙 +恸 +椐 +森 +牒 +寤 +袒 +婆 +虢 +雅 +钉 +朵 +贼 +欲 +苞 +寰 +故 +龚 +坭 +嘘 +咫 +礼 +硷 +兀 +睢 +汶 +’ +铲 +烧 +绕 +诃 +浃 +钿 +哺 +柜 +讼 +颊 +璁 +腔 +洽 +咐 +脲 +簌 +筠 +镣 +玮 +鞠 +谁 +兼 +姆 +挥 +梯 +蝴 +谘 +漕 +刷 +躏 +宦 +弼 +b +垌 +劈 +麟 +莉 +揭 +笙 +渎 +仕 +嗤 +仓 +配 +怏 +抬 +错 +泯 +镊 +孰 +猿 +邪 +仍 +秋 +鼬 +壹 +歇 +吵 +炼 +< +尧 +射 +柬 +廷 +胧 +霾 +凳 +隋 +肚 +浮 +梦 +祥 +株 +堵 +退 +L +鹫 +跎 +凶 +毽 +荟 +炫 +栩 +玳 +甜 +沂 +鹿 +顽 +伯 +爹 +赔 +蛴 +徐 +匡 +欣 +狰 +缸 +雹 +蟆 +疤 +默 +沤 +啜 +痂 +衣 +禅 +w +i +h +辽 +葳 +黝 +钗 +停 +沽 +棒 +馨 +颌 +肉 +吴 +硫 +悯 +劾 +娈 +马 +啧 +吊 +悌 +镑 +峭 +帆 +瀣 +涉 +咸 +疸 +滋 +泣 +翦 +拙 +癸 +钥 +蜒 ++ +尾 +庄 +凝 +泉 +婢 +渴 +谊 +乞 +陆 +锉 +糊 +鸦 +淮 +I +B +N +晦 +弗 +乔 +庥 +葡 +尻 +席 +橡 +傣 +渣 +拿 +惩 +麋 +斛 +缃 +矮 +蛏 +岘 +鸽 +姐 +膏 +催 +奔 +镒 +喱 +蠡 +摧 +钯 +胤 +柠 +拐 +璋 +鸥 +卢 +荡 +倾 +^ +_ +珀 +逄 +萧 +塾 +掇 +贮 +笆 +聂 +圃 +冲 +嵬 +M +滔 +笕 +值 +炙 +偶 +蜱 +搐 +梆 +汪 +蔬 +腑 +鸯 +蹇 +敞 +绯 +仨 +祯 +谆 +梧 +糗 +鑫 +啸 +豺 +囹 +猾 +巢 +柄 +瀛 +筑 +踌 +沭 +暗 +苁 +鱿 +蹉 +脂 +蘖 +牢 +热 +木 +吸 +溃 +宠 +序 +泞 +偿 +拜 +檩 +厚 +朐 +毗 +螳 +吞 +媚 +朽 +担 +蝗 +橘 +畴 +祈 +糟 +盱 +隼 +郜 +惜 +珠 +裨 +铵 +焙 +琚 +唯 +咚 +噪 +骊 +丫 +滢 +勤 +棉 +呸 +咣 +淀 +隔 +蕾 +窈 +饨 +挨 +煅 +短 +匙 +粕 +镜 +赣 +撕 +墩 +酬 +馁 +豌 +颐 +抗 +酣 +氓 +佑 +搁 +哭 +递 +耷 +涡 +桃 +贻 +碣 +截 +瘦 +昭 +镌 +蔓 +氚 +甲 +猕 +蕴 +蓬 +散 +拾 +纛 +狼 +猷 +铎 +埋 +旖 +矾 +讳 +囊 +糜 +迈 +粟 +蚂 +紧 +鲳 +瘢 +栽 +稼 +羊 +锄 +斟 +睁 +桥 +瓮 +蹙 +祉 +醺 +鼻 +昱 +剃 +跳 +篱 +跷 +蒜 +翎 +宅 +晖 +嗑 +壑 +峻 +癫 +屏 +狠 +陋 +袜 +途 +憎 +祀 +莹 +滟 +佶 +溥 +臣 +约 +盛 +峰 +磁 +慵 +婪 +拦 +莅 +朕 +鹦 +粲 +裤 +哎 +疡 +嫖 +琵 +窟 +堪 +谛 +嘉 +儡 +鳝 +斩 +郾 +驸 +酊 +妄 +胜 +贺 +徙 +傅 +噌 +钢 +栅 +庇 +恋 +匝 +巯 +邈 +尸 +锚 +粗 +佟 +蛟 +薹 +纵 +蚊 +郅 +绢 +锐 +苗 +俞 +篆 +淆 +膀 +鲜 +煎 +诶 +秽 +寻 +涮 +刺 +怀 +噶 +巨 +褰 +魅 +灶 +灌 +桉 +藕 +谜 +舸 +薄 +搀 +恽 +借 +牯 +痉 +渥 +愿 +亓 +耘 +杠 +柩 +锔 +蚶 +钣 +珈 +喘 +蹒 +幽 +赐 +稗 +晤 +莱 +泔 +扯 +肯 +菪 +裆 +腩 +豉 +疆 +骜 +腐 +倭 +珏 +唔 +粮 +亡 +润 +慰 +伽 +橄 +玄 +誉 +醐 +胆 +龊 +粼 +塬 +陇 +彼 +削 +嗣 +绾 +芽 +妗 +垭 +瘴 +爽 +薏 +寨 +龈 +泠 +弹 +赢 +漪 +猫 +嘧 +涂 +恤 +圭 +茧 +烽 +屑 +痕 +巾 +赖 +荸 +凰 +腮 +畈 +亵 +蹲 +偃 +苇 +澜 +艮 +换 +骺 +烘 +苕 +梓 +颉 +肇 +哗 +悄 +氤 +涠 +葬 +屠 +鹭 +植 +竺 +佯 +诣 +鲇 +瘀 +鲅 +邦 +移 +滁 +冯 +耕 +癔 +戌 +茬 +沁 +巩 +悠 +湘 +洪 +痹 +锟 +循 +谋 +腕 +鳃 +钠 +捞 +焉 +迎 +碱 +伫 +急 +榷 +奈 +邝 +卯 +辄 +皲 +卟 +醛 +畹 +忧 +稳 +雄 +昼 +缩 +阈 +睑 +扌 +耗 +曦 +涅 +捏 +瞧 +邕 +淖 +漉 +铝 +耦 +禹 +湛 +喽 +莼 +琅 +诸 +苎 +纂 +硅 +始 +嗨 +傥 +燃 +臂 +赅 +嘈 +呆 +贵 +屹 +壮 +肋 +亍 +蚀 +卅 +豹 +腆 +邬 +迭 +浊 +} +童 +螂 +捐 +圩 +勐 +触 +寞 +汊 +壤 +荫 +膺 +渌 +芳 +懿 +遴 +螈 +泰 +蓼 +蛤 +茜 +舅 +枫 +朔 +膝 +眙 +避 +梅 +判 +鹜 +璜 +牍 +缅 +垫 +藻 +黔 +侥 +惚 +懂 +踩 +腰 +腈 +札 +丞 +唾 +慈 +顿 +摹 +荻 +琬 +~ +斧 +沈 +滂 +胁 +胀 +幄 +莜 +Z +匀 +鄄 +掌 +绰 +茎 +焚 +赋 +萱 +谑 +汁 +铒 +瞎 +夺 +蜗 +野 +娆 +冀 +弯 +篁 +懵 +灞 +隽 +芡 +脘 +俐 +辩 +芯 +掺 +喏 +膈 +蝈 +觐 +悚 +踹 +蔗 +熠 +鼠 +呵 +抓 +橼 +峨 +畜 +缔 +禾 +崭 +弃 +熊 +摒 +凸 +拗 +穹 +蒙 +抒 +祛 +劝 +闫 +扳 +阵 +醌 +踪 +喵 +侣 +搬 +仅 +荧 +赎 +蝾 +琦 +买 +婧 +瞄 +寓 +皎 +冻 +赝 +箩 +莫 +瞰 +郊 +笫 +姝 +筒 +枪 +遣 +煸 +袋 +舆 +痱 +涛 +母 +〇 +启 +践 +耙 +绲 +盘 +遂 +昊 +搞 +槿 +诬 +纰 +泓 +惨 +檬 +亻 +越 +C +o +憩 +熵 +祷 +钒 +暧 +塔 +阗 +胰 +咄 +娶 +魔 +琶 +钞 +邻 +扬 +杉 +殴 +咽 +弓 +〆 +髻 +】 +吭 +揽 +霆 +拄 +殖 +脆 +彻 +岩 +芝 +勃 +辣 +剌 +钝 +嘎 +甄 +佘 +皖 +伦 +授 +徕 +憔 +挪 +皇 +庞 +稔 +芜 +踏 +溴 +兖 +卒 +擢 +饥 +鳞 +煲 +‰ +账 +颗 +叻 +斯 +捧 +鳍 +琮 +讹 +蛙 +纽 +谭 +酸 +兔 +莒 +睇 +伟 +觑 +羲 +嗜 +宜 +褐 +旎 +辛 +卦 +诘 +筋 +鎏 +溪 +挛 +熔 +阜 +晰 +鳅 +丢 +奚 +灸 +呱 +献 +陉 +黛 +鸪 +甾 +萨 +疮 +拯 +洲 +疹 +辑 +叙 +恻 +谒 +允 +柔 +烂 +氏 +逅 +漆 +拎 +惋 +扈 +湟 +纭 +啕 +掬 +擞 +哥 +忽 +涤 +鸵 +靡 +郗 +瓷 +扁 +廊 +怨 +雏 +钮 +敦 +E +懦 +憋 +汀 +拚 +啉 +腌 +岸 +f +痼 +瞅 +尊 +咀 +眩 +飙 +忌 +仝 +迦 +熬 +毫 +胯 +篑 +茄 +腺 +凄 +舛 +碴 +锵 +诧 +羯 +後 +漏 +汤 +宓 +仞 +蚁 +壶 +谰 +皑 +铄 +棰 +罔 +辅 +晶 +苦 +牟 +闽 +\ +烃 +饮 +聿 +丙 +蛳 +朱 +煤 +涔 +鳖 +犁 +罐 +荼 +砒 +淦 +妤 +黏 +戎 +孑 +婕 +瑾 +戢 +钵 +枣 +捋 +砥 +衩 +狙 +桠 +稣 +阎 +肃 +梏 +诫 +孪 +昶 +婊 +衫 +嗔 +侃 +塞 +蜃 +樵 +峒 +貌 +屿 +欺 +缫 +阐 +栖 +诟 +珞 +荭 +吝 +萍 +嗽 +恂 +啻 +蜴 +磬 +峋 +俸 +豫 +谎 +徊 +镍 +韬 +魇 +晴 +U +囟 +猜 +蛮 +坐 +囿 +伴 +亭 +肝 +佗 +蝠 +妃 +胞 +滩 +榴 +氖 +垩 +苋 +砣 +扪 +馏 +姓 +轩 +厉 +夥 +侈 +禀 +垒 +岑 +赏 +钛 +辐 +痔 +披 +纸 +碳 +“ +坞 +蠓 +挤 +荥 +沅 +悔 +铧 +帼 +蒌 +蝇 +a +p +y +n +g +哀 +浆 +瑶 +凿 +桶 +馈 +皮 +奴 +苜 +佤 +伶 +晗 +铱 +炬 +优 +弊 +氢 +恃 +甫 +攥 +端 +锌 +灰 +稹 +炝 +曙 +邋 +亥 +眶 +碾 +拉 +萝 +绔 +捷 +浍 +腋 +姑 +菖 +凌 +涞 +麽 +锢 +桨 +潢 +绎 +镰 +殆 +锑 +渝 +铬 +困 +绽 +觎 +匈 +糙 +暑 +裹 +鸟 +盔 +肽 +迷 +綦 +『 +亳 +佝 +俘 +钴 +觇 +骥 +仆 +疝 +跪 +婶 +郯 +瀹 +唉 +脖 +踞 +针 +晾 +忒 +扼 +瞩 +叛 +椒 +疟 +嗡 +邗 +肆 +跆 +玫 +忡 +捣 +咧 +唆 +艄 +蘑 +潦 +笛 +阚 +沸 +泻 +掊 +菽 +贫 +斥 +髂 +孢 +镂 +赂 +麝 +鸾 +屡 +衬 +苷 +恪 +叠 +希 +粤 +爻 +喝 +茫 +惬 +郸 +绻 +庸 +撅 +碟 +宄 +妹 +膛 +叮 +饵 +崛 +嗲 +椅 +冤 +搅 +咕 +敛 +尹 +垦 +闷 +蝉 +霎 +勰 +败 +蓑 +泸 +肤 +鹌 +幌 +焦 +浠 +鞍 +刁 +舰 +乙 +竿 +裔 +。 +茵 +函 +伊 +兄 +丨 +娜 +匍 +謇 +莪 +宥 +似 +蝽 +翳 +酪 +翠 +粑 +薇 +祢 +骏 +赠 +叫 +Q +噤 +噻 +竖 +芗 +莠 +潭 +俊 +羿 +耜 +O +郫 +趁 +嗪 +囚 +蹶 +芒 +洁 +笋 +鹑 +敲 +硝 +啶 +堡 +渲 +揩 +』 +携 +宿 +遒 +颍 +扭 +棱 +割 +萜 +蔸 +葵 +琴 +捂 +饰 +衙 +耿 +掠 +募 +岂 +窖 +涟 +蔺 +瘤 +柞 +瞪 +怜 +匹 +距 +楔 +炜 +哆 +秦 +缎 +幼 +茁 +绪 +痨 +恨 +楸 +娅 +瓦 +桩 +雪 +嬴 +伏 +榔 +妥 +铿 +拌 +眠 +雍 +缇 +‘ +卓 +搓 +哌 +觞 +噩 +屈 +哧 +髓 +咦 +巅 +娑 +侑 +淫 +膳 +祝 +勾 +姊 +莴 +胄 +疃 +薛 +蜷 +胛 +巷 +芙 +芋 +熙 +闰 +勿 +窃 +狱 +剩 +钏 +幢 +陟 +铛 +慧 +靴 +耍 +k +浙 +浇 +飨 +惟 +绗 +祜 +澈 +啼 +咪 +磷 +摞 +诅 +郦 +抹 +跃 +壬 +吕 +肖 +琏 +颤 +尴 +剡 +抠 +凋 +赚 +泊 +津 +宕 +殷 +倔 +氲 +漫 +邺 +涎 +怠 +$ +垮 +荬 +遵 +俏 +叹 +噢 +饽 +蜘 +孙 +筵 +疼 +鞭 +羧 +牦 +箭 +潴 +c +眸 +祭 +髯 +啖 +坳 +愁 +芩 +驮 +倡 +巽 +穰 +沃 +胚 +怒 +凤 +槛 +剂 +趵 +嫁 +v +邢 +灯 +鄢 +桐 +睽 +檗 +锯 +槟 +婷 +嵋 +圻 +诗 +蕈 +颠 +遭 +痢 +芸 +怯 +馥 +竭 +锗 +徜 +恭 +遍 +籁 +剑 +嘱 +苡 +龄 +僧 +桑 +潸 +弘 +澶 +楹 +悲 +讫 +愤 +腥 +悸 +谍 +椹 +呢 +桓 +葭 +攫 +阀 +翰 +躲 +敖 +柑 +郎 +笨 +橇 +呃 +魁 +燎 +脓 +葩 +磋 +垛 +玺 +狮 +沓 +砜 +蕊 +锺 +罹 +蕉 +翱 +虐 +闾 +巫 +旦 +茱 +嬷 +枯 +鹏 +贡 +芹 +汛 +矫 +绁 +拣 +禺 +佃 +讣 +舫 +惯 +乳 +趋 +疲 +挽 +岚 +虾 +衾 +蠹 +蹂 +飓 +氦 +铖 +孩 +稞 +瑜 +壅 +掀 +勘 +妓 +畅 +髋 +W +庐 +牲 +蓿 +榕 +练 +垣 +唱 +邸 +菲 +昆 +婺 +穿 +绡 +麒 +蚱 +掂 +愚 +泷 +涪 +漳 +妩 +娉 +榄 +讷 +觅 +旧 +藤 +煮 +呛 +柳 +腓 +叭 +庵 +烷 +阡 +罂 +蜕 +擂 +猖 +咿 +媲 +脉 +【 +沏 +貅 +黠 +熏 +哲 +烁 +坦 +酵 +兜 +× +潇 +撒 +剽 +珩 +圹 +乾 +摸 +樟 +帽 +嗒 +襄 +魂 +轿 +憬 +锡 +〕 +喃 +皆 +咖 +隅 +脸 +残 +泮 +袂 +鹂 +珊 +囤 +捆 +咤 +误 +徨 +闹 +淙 +芊 +淋 +怆 +囗 +拨 +梳 +渤 +R +G +绨 +蚓 +婀 +幡 +狩 +麾 +谢 +唢 +裸 +旌 +伉 +纶 +裂 +驳 +砼 +咛 +澄 +樨 +蹈 +宙 +澍 +倍 +貔 +操 +勇 +蟠 +摈 +砧 +虬 +够 +缁 +悦 +藿 +撸 +艹 +摁 +淹 +豇 +虎 +榭 +ˉ +吱 +d +° +喧 +荀 +踱 +侮 +奋 +偕 +饷 +犍 +惮 +坑 +璎 +徘 +宛 +妆 +袈 +倩 +窦 +昂 +荏 +乖 +K +怅 +撰 +鳙 +牙 +袁 +酞 +X +痿 +琼 +闸 +雁 +趾 +荚 +虻 +涝 +《 +杏 +韭 +偈 +烤 +绫 +鞘 +卉 +症 +遢 +蓥 +诋 +杭 +荨 +匆 +竣 +簪 +辙 +敕 +虞 +丹 +缭 +咩 +黟 +m +淤 +瑕 +咂 +铉 +硼 +茨 +嶂 +痒 +畸 +敬 +涿 +粪 +窘 +熟 +叔 +嫔 +盾 +忱 +裘 +憾 +梵 +赡 +珙 +咯 +娘 +庙 +溯 +胺 +葱 +痪 +摊 +荷 +卞 +乒 +髦 +寐 +铭 +坩 +胗 +枷 +爆 +溟 +嚼 +羚 +砬 +轨 +惊 +挠 +罄 +竽 +菏 +氧 +浅 +楣 +盼 +枢 +炸 +阆 +杯 +谏 +噬 +淇 +渺 +俪 +秆 +墓 +泪 +跻 +砌 +痰 +垡 +渡 +耽 +釜 +讶 +鳎 +煞 +呗 +韶 +舶 +绷 +鹳 +缜 +旷 +铊 +皱 +龌 +檀 +霖 +奄 +槐 +艳 +蝶 +旋 +哝 +赶 +骞 +蚧 +腊 +盈 +丁 +` +蜚 +矸 +蝙 +睨 +嚓 +僻 +鬼 +醴 +夜 +彝 +磊 +笔 +拔 +栀 +糕 +厦 +邰 +纫 +逭 +纤 +眦 +膊 +馍 +躇 +烯 +蘼 +冬 +诤 +暄 +骶 +哑 +瘠 +」 +臊 +丕 +愈 +咱 +螺 +擅 +跋 +搏 +硪 +谄 +笠 +淡 +嘿 +骅 +谧 +鼎 +皋 +姚 +歼 +蠢 +驼 +耳 +胬 +挝 +涯 +狗 +蒽 +孓 +犷 +凉 +芦 +箴 +铤 +孤 +嘛 +坤 +V +茴 +朦 +挞 +尖 +橙 +诞 +搴 +碇 +洵 +浚 +帚 +蜍 +漯 +柘 +嚎 +讽 +芭 +荤 +咻 +祠 +秉 +跖 +埃 +吓 +糯 +眷 +馒 +惹 +娼 +鲑 +嫩 +讴 +轮 +瞥 +靶 +褚 +乏 +缤 +宋 +帧 +删 +驱 +碎 +扑 +俩 +俄 +偏 +涣 +竹 +噱 +皙 +佰 +渚 +唧 +斡 +# +镉 +刀 +崎 +筐 +佣 +夭 +贰 +肴 +峙 +哔 +艿 +匐 +牺 +镛 +缘 +仡 +嫡 +劣 +枸 +堀 +梨 +簿 +鸭 +蒸 +亦 +稽 +浴 +{ +衢 +束 +槲 +j +阁 +揍 +疥 +棋 +潋 +聪 +窜 +乓 +睛 +插 +冉 +阪 +苍 +搽 +「 +蟾 +螟 +幸 +仇 +樽 +撂 +慢 +跤 +幔 +俚 +淅 +覃 +觊 +溶 +妖 +帛 +侨 +曰 +妾 +泗 +· +: +瀘 +風 +Ë +( +) +∶ +紅 +紗 +瑭 +雲 +頭 +鶏 +財 +許 +• +¥ +樂 +焗 +麗 +— +; +滙 +東 +榮 +繪 +興 +… +門 +業 +π +楊 +國 +顧 +é +盤 +寳 +Λ +龍 +鳳 +島 +誌 +緣 +結 +銭 +萬 +勝 +祎 +璟 +優 +歡 +臨 +時 +購 += +★ +藍 +昇 +鐵 +觀 +勅 +農 +聲 +畫 +兿 +術 +發 +劉 +記 +專 +耑 +園 +書 +壴 +種 +Ο +● +褀 +號 +銀 +匯 +敟 +锘 +葉 +橪 +廣 +進 +蒄 +鑽 +阝 +祙 +貢 +鍋 +豊 +夬 +喆 +團 +閣 +開 +燁 +賓 +館 +酡 +沔 +順 ++ +硚 +劵 +饸 +陽 +車 +湓 +復 +萊 +氣 +軒 +華 +堃 +迮 +纟 +戶 +馬 +學 +裡 +電 +嶽 +獨 +マ +シ +サ +ジ +燘 +袪 +環 +❤ +臺 +灣 +専 +賣 +孖 +聖 +攝 +線 +▪ +α +傢 +俬 +夢 +達 +莊 +喬 +貝 +薩 +劍 +羅 +壓 +棛 +饦 +尃 +璈 +囍 +醫 +G +I +A +# +N +鷄 +髙 +嬰 +啓 +約 +隹 +潔 +賴 +藝 +~ +寶 +籣 +麺 +  +嶺 +√ +義 +網 +峩 +長 +∧ +魚 +機 +構 +② +鳯 +偉 +L +B +㙟 +畵 +鴿 +' +詩 +溝 +嚞 +屌 +藔 +佧 +玥 +蘭 +織 +1 +3 +9 +0 +7 +點 +砭 +鴨 +鋪 +銘 +廳 +弍 +‧ +創 +湯 +坶 +℃ +卩 +骝 +& +烜 +荘 +當 +潤 +扞 +係 +懷 +碶 +钅 +蚨 +讠 +☆ +叢 +爲 +埗 +涫 +塗 +→ +楽 +現 +鯨 +愛 +瑪 +鈺 +忄 +悶 +藥 +飾 +樓 +視 +孬 +ㆍ +燚 +苪 +師 +① +丼 +锽 +│ +韓 +標 +è +兒 +閏 +匋 +張 +漢 +Ü +髪 +會 +閑 +檔 +習 +裝 +の +峯 +菘 +輝 +И +雞 +釣 +億 +浐 +K +O +R +8 +H +E +P +T +W +D +S +C +M +F +姌 +饹 +» +晞 +廰 +ä +嵯 +鷹 +負 +飲 +絲 +冚 +楗 +澤 +綫 +區 +❋ +← +質 +靑 +揚 +③ +滬 +統 +産 +協 +﹑ +乸 +畐 +經 +運 +際 +洺 +岽 +為 +粵 +諾 +崋 +豐 +碁 +ɔ +V +2 +6 +齋 +誠 +訂 +´ +勑 +雙 +陳 +無 +í +泩 +媄 +夌 +刂 +i +c +t +o +r +a +嘢 +耄 +燴 +暃 +壽 +媽 +靈 +抻 +體 +唻 +É +冮 +甹 +鎮 +錦 +ʌ +蜛 +蠄 +尓 +駕 +戀 +飬 +逹 +倫 +貴 +極 +Я +Й +寬 +磚 +嶪 +郎 +職 +| +間 +n +d +剎 +伈 +課 +飛 +橋 +瘊 +№ +譜 +骓 +圗 +滘 +縣 +粿 +咅 +養 +濤 +彳 +® +% +Ⅱ +啰 +㴪 +見 +矞 +薬 +糁 +邨 +鲮 +顔 +罱 +З +選 +話 +贏 +氪 +俵 +競 +瑩 +繡 +枱 +β +綉 +á +獅 +爾 +™ +麵 +戋 +淩 +徳 +個 +劇 +場 +務 +簡 +寵 +h +實 +膠 +轱 +圖 +築 +嘣 +樹 +㸃 +營 +耵 +孫 +饃 +鄺 +飯 +麯 +遠 +輸 +坫 +孃 +乚 +閃 +鏢 +㎡ +題 +廠 +關 +↑ +爺 +將 +軍 +連 +篦 +覌 +參 +箸 +- +窠 +棽 +寕 +夀 +爰 +歐 +呙 +閥 +頡 +熱 +雎 +垟 +裟 +凬 +勁 +帑 +馕 +夆 +疌 +枼 +馮 +貨 +蒤 +樸 +彧 +旸 +靜 +龢 +暢 +㐱 +鳥 +珺 +鏡 +灡 +爭 +堷 +廚 +Ó +騰 +診 +┅ +蘇 +褔 +凱 +頂 +豕 +亞 +帥 +嘬 +⊥ +仺 +桖 +複 +饣 +絡 +穂 +顏 +棟 +納 +▏ +濟 +親 +設 +計 +攵 +埌 +烺 +ò +頤 +燦 +蓮 +撻 +節 +講 +濱 +濃 +娽 +洳 +朿 +燈 +鈴 +護 +膚 +铔 +過 +補 +Z +U +5 +4 +坋 +闿 +䖝 +餘 +缐 +铞 +貿 +铪 +桼 +趙 +鍊 +[ +㐂 +垚 +菓 +揸 +捲 +鐘 +滏 +𣇉 +爍 +輪 +燜 +鴻 +鮮 +動 +鹞 +鷗 +丄 +慶 +鉌 +翥 +飮 +腸 +⇋ +漁 +覺 +來 +熘 +昴 +翏 +鲱 +圧 +鄉 +萭 +頔 +爐 +嫚 +г +貭 +類 +聯 +幛 +輕 +訓 +鑒 +夋 +锨 +芃 +珣 +䝉 +扙 +嵐 +銷 +處 +ㄱ +語 +誘 +苝 +歸 +儀 +燒 +楿 +內 +粢 +葒 +奧 +麥 +礻 +滿 +蠔 +穵 +瞭 +態 +鱬 +榞 +硂 +鄭 +黃 +煙 +祐 +奓 +逺 +* +瑄 +獲 +聞 +薦 +讀 +這 +樣 +決 +問 +啟 +們 +執 +説 +轉 +單 +隨 +唘 +帶 +倉 +庫 +還 +贈 +尙 +皺 +■ +餅 +產 +○ +∈ +報 +狀 +楓 +賠 +琯 +嗮 +禮 +` +傳 +> +≤ +嗞 +Φ +≥ +換 +咭 +∣ +↓ +曬 +ε +応 +寫 +″ +終 +様 +純 +費 +療 +聨 +凍 +壐 +郵 +ü +黒 +∫ +製 +塊 +調 +軽 +確 +撃 +級 +馴 +Ⅲ +涇 +繹 +數 +碼 +證 +狒 +処 +劑 +< +晧 +賀 +衆 +] +櫥 +兩 +陰 +絶 +對 +鯉 +憶 +◎ +p +e +Y +蕒 +煖 +頓 +測 +試 +鼽 +僑 +碩 +妝 +帯 +≈ +鐡 +舖 +權 +喫 +倆 +ˋ +該 +悅 +ā +俫 +. +f +s +b +m +k +g +u +j +貼 +淨 +濕 +針 +適 +備 +l +/ +給 +謢 +強 +觸 +衛 +與 +⊙ +$ +緯 +變 +⑴ +⑵ +⑶ +㎏ +殺 +∩ +幚 +─ +價 +▲ +離 +ú +ó +飄 +烏 +関 +閟 +﹝ +﹞ +邏 +輯 +鍵 +驗 +訣 +導 +歷 +屆 +層 +▼ +儱 +錄 +熳 +ē +艦 +吋 +錶 +辧 +飼 +顯 +④ +禦 +販 +気 +対 +枰 +閩 +紀 +幹 +瞓 +貊 +淚 +△ +眞 +墊 +Ω +獻 +褲 +縫 +緑 +亜 +鉅 +餠 +{ +} +◆ +蘆 +薈 +█ +◇ +溫 +彈 +晳 +粧 +犸 +穩 +訊 +崬 +凖 +熥 +П +舊 +條 +紋 +圍 +Ⅳ +筆 +尷 +難 +雜 +錯 +綁 +識 +頰 +鎖 +艶 +□ +殁 +殼 +⑧ +├ +▕ +鵬 +ǐ +ō +ǒ +糝 +綱 +▎ +μ +盜 +饅 +醬 +籤 +蓋 +釀 +鹽 +據 +à +ɡ +辦 +◥ +彐 +┌ +婦 +獸 +鲩 +伱 +ī +蒟 +蒻 +齊 +袆 +腦 +寧 +凈 +妳 +煥 +詢 +偽 +謹 +啫 +鯽 +騷 +鱸 +損 +傷 +鎻 +髮 +買 +冏 +儥 +両 +﹢ +∞ +載 +喰 +z +羙 +悵 +燙 +曉 +員 +組 +徹 +艷 +痠 +鋼 +鼙 +縮 +細 +嚒 +爯 +≠ +維 +" +鱻 +壇 +厍 +帰 +浥 +犇 +薡 +軎 +² +應 +醜 +刪 +緻 +鶴 +賜 +噁 +軌 +尨 +镔 +鷺 +槗 +彌 +葚 +濛 +請 +溇 +緹 +賢 +訪 +獴 +瑅 +資 +縤 +陣 +蕟 +栢 +韻 +祼 +恁 +伢 +謝 +劃 +涑 +總 +衖 +踺 +砋 +凉 +籃 +駿 +苼 +瘋 +昽 +紡 +驊 +腎 +﹗ +響 +杋 +剛 +嚴 +禪 +歓 +槍 +傘 +檸 +檫 +炣 +勢 +鏜 +鎢 +銑 +尐 +減 +奪 +惡 +θ +僮 +婭 +臘 +ū +ì +殻 +鉄 +∑ +蛲 +焼 +緖 +續 +紹 +懮! +䰾 +䲁 +丌 +丏 +丟 +並 +乂 +乗 +乩 +乭 +乹 +亀 +亂 +亅 +亊 +亠 +亰 +亶 +亹 +仂 +仉 +仏 +仛 +仫 +仮 +仳 +仵 +仼 +伃 +伋 +伕 +伝 +伷 +伾 +佀 +佁 +佇 +佈 +佉 +佋 +佔 +併 +佹 +佺 +佾 +侁 +侅 +侊 +侖 +侘 +侚 +侞 +価 +侶 +侷 +侹 +俁 +俅 +俋 +俌 +俍 +俛 +俠 +俳 +俴 +俶 +俽 +倈 +倓 +倖 +倗 +倞 +倢 +倣 +値 +倧 +倮 +倻 +偁 +偊 +偍 +偓 +偪 +偲 +側 +偵 +偸 +傃 +傉 +傑 +傒 +傕 +傖 +傜 +傭 +債 +傾 +僅 +僉 +僊 +働 +僔 +僕 +僖 +僙 +僜 +僡 +僩 +僭 +僰 +僱 +僴 +儁 +儂 +儆 +儇 +儈 +儉 +儐 +儔 +儕 +儘 +儚 +儞 +償 +儦 +儫 +儲 +儷 +儺 +儻 +儼 +兌 +児 +兕 +兗 +兪 +冂 +円 +冇 +冊 +冑 +冖 +冧 +冨 +冪 +冫 +冴 +凃 +凜 +凞 +凪 +凵 +刄 +刎 +別 +刦 +刧 +刼 +則 +剋 +剏 +剝 +剣 +剮 +劄 +劊 +劌 +劔 +劬 +効 +劼 +勔 +勖 +勗 +勛 +勞 +勣 +勦 +勱 +勲 +勳 +勵 +勷 +勸 +勻 +匂 +匄 +匏 +匚 +匱 +匸 +卋 +卍 +卐 +卣 +卬 +卮 +卲 +卹 +卺 +卻 +卽 +厓 +厔 +厙 +厭 +厰 +厲 +厴 +厶 +叄 +収 +叕 +叡 +叵 +吔 +吥 +吳 +吶 +呂 +呉 +呎 +呾 +咁 +咑 +咗 +咘 +咟 +咥 +咲 +咼 +咾 +哂 +哏 +哐 +哖 +哱 +唃 +唄 +唫 +唭 +唵 +唸 +啁 +啍 +啚 +啞 +啣 +啯 +啱 +啲 +啷 +喈 +喚 +喢 +喦 +喪 +喲 +喼 +嗄 +嗆 +嗇 +嗊 +嗎 +嗚 +嗢 +嗩 +嗶 +嗹 +嘅 +嘆 +嘍 +嘏 +嘔 +嘗 +嘚 +嘜 +嘥 +嘩 +嘮 +嘯 +嘰 +嘸 +噍 +噏 +噓 +噝 +噠 +噥 +噦 +噯 +噰 +噲 +噴 +噸 +噹 +嚇 +嚈 +嚐 +嚕 +嚗 +嚙 +嚟 +嚤 +嚦 +嚧 +嚨 +嚩 +嚮 +嚳 +嚶 +嚿 +囀 +囂 +囃 +囉 +囑 +囒 +囓 +囝 +団 +囧 +囪 +囮 +囯 +囲 +図 +囶 +囷 +圂 +圄 +圉 +圏 +圓 +圪 +圯 +坌 +坖 +坣 +坬 +坮 +坵 +垈 +垍 +垕 +垞 +垯 +垰 +垵 +垻 +垿 +埅 +埇 +埈 +埏 +埒 +埜 +埡 +埤 +埧 +埨 +埪 +埮 +埴 +埵 +埻 +埼 +堅 +堈 +堉 +堊 +堍 +堖 +堝 +堦 +堮 +堯 +堺 +塀 +塅 +塆 +塋 +塏 +塙 +塜 +塡 +塢 +塤 +塨 +塩 +塭 +塰 +塱 +塲 +塵 +塹 +塽 +墀 +墎 +増 +墘 +墜 +墡 +墣 +墫 +墬 +墮 +墱 +墳 +墺 +墼 +墾 +壄 +壆 +壋 +壌 +壎 +壔 +壘 +壙 +壞 +壟 +壠 +壢 +壩 +壯 +壱 +壺 +変 +夊 +夠 +夤 +夾 +奀 +奐 +奣 +奩 +奫 +奭 +奮 +妀 +妁 +妏 +妑 +妠 +妧 +妭 +妸 +妺 +姀 +姁 +姃 +姈 +姉 +姍 +姦 +姪 +姫 +姮 +姵 +姶 +姸 +娋 +娍 +娎 +娖 +娛 +娫 +娳 +娸 +婁 +婑 +婯 +婻 +婼 +媃 +媊 +媐 +媓 +媖 +媗 +媜 +媞 +媧 +媭 +媯 +媺 +媼 +媿 +嫄 +嫈 +嫘 +嫪 +嫲 +嫳 +嫵 +嫺 +嫻 +嬅 +嬈 +嬋 +嬌 +嬛 +嬝 +嬡 +嬤 +嬨 +嬪 +嬬 +嬭 +嬸 +嬾 +嬿 +孀 +孆 +孋 +孌 +孮 +孻 +孿 +宍 +実 +宧 +宮 +寀 +寁 +寈 +寊 +寔 +寖 +寗 +寘 +寛 +寜 +寢 +審 +寯 +尋 +尗 +尢 +尪 +屄 +屇 +屍 +屓 +屚 +屜 +屢 +屬 +屭 +屺 +屻 +岀 +岈 +岡 +岣 +岧 +岪 +岬 +岰 +岵 +岻 +峅 +峇 +峍 +峘 +峚 +峠 +峴 +峼 +峽 +崁 +崈 +崍 +崐 +崑 +崒 +崗 +崘 +崙 +崚 +崞 +崟 +崠 +崢 +崱 +崵 +崶 +嵎 +嵒 +嵕 +嵖 +嵗 +嵙 +嵛 +嵜 +嵨 +嵮 +嵰 +嵴 +嵻 +嵿 +嶁 +嶃 +嶄 +嶇 +嶋 +嶌 +嶍 +嶒 +嶔 +嶗 +嶝 +嶠 +嶢 +嶦 +嶧 +嶬 +嶰 +嶲 +嶴 +嶷 +嶸 +嶼 +巂 +巄 +巆 +巋 +巌 +巎 +巑 +巒 +巔 +巖 +巘 +巛 +巰 +巶 +巻 +巿 +帔 +帙 +帡 +帢 +帳 +幀 +幃 +幗 +幟 +幣 +幪 +幫 +幵 +幷 +幾 +庀 +庁 +広 +庢 +庲 +庼 +廁 +廂 +廄 +廆 +廈 +廋 +廌 +廍 +廑 +廔 +廕 +廙 +廝 +廞 +廟 +廡 +廢 +廧 +廨 +廩 +廬 +廱 +廸 +廻 +廼 +弁 +弅 +弇 +弉 +弐 +弒 +弔 +弖 +弢 +弨 +弸 +弾 +彀 +彄 +彅 +彆 +彊 +彎 +彔 +彖 +彘 +彙 +彜 +彞 +彠 +彡 +彣 +彥 +彫 +彿 +徂 +徑 +從 +徠 +徧 +徫 +徬 +徭 +徴 +徸 +忉 +忝 +忞 +忬 +忯 +忳 +怍 +怙 +怛 +怵 +恆 +恊 +恥 +恵 +悆 +悛 +悝 +悞 +悧 +悪 +悰 +悳 +惇 +惔 +惣 +惱 +惲 +愃 +愆 +愍 +愐 +愒 +愔 +愜 +愨 +愭 +愴 +愷 +愼 +愾 +慄 +慘 +慚 +慜 +慟 +慣 +慥 +慮 +慳 +慾 +憂 +憊 +憍 +憐 +憑 +憓 +憕 +憙 +憚 +憤 +憫 +憲 +憺 +憻 +懃 +懇 +懌 +懍 +懐 +懣 +懮 +懲 +懶 +懸 +懺 +懼 +懽 +懾 +戇 +戔 +戕 +戙 +戡 +戥 +戦 +戩 +戰 +戱 +戲 +戸 +戻 +戽 +扆 +扥 +抃 +抇 +抦 +拋 +拏 +拝 +拡 +拺 +挙 +挵 +挹 +挻 +挾 +捒 +捜 +捦 +捨 +捩 +捫 +捭 +捱 +掃 +掄 +掙 +掛 +掞 +掟 +採 +掾 +揀 +揄 +揆 +揔 +揮 +揺 +搖 +搗 +搠 +搢 +搳 +搵 +搶 +搾 +摂 +摜 +摟 +摠 +摭 +摯 +摳 +摴 +摵 +摶 +摺 +摻 +摽 +撈 +撐 +撓 +撖 +撙 +撚 +撣 +撥 +撫 +撲 +撳 +撾 +撿 +擁 +擇 +擊 +擋 +擔 +擠 +擥 +擬 +擯 +擰 +擱 +擲 +擴 +擷 +擺 +擼 +擾 +攏 +攔 +攖 +攜 +攞 +攢 +攣 +攤 +攪 +攬 +攴 +攷 +攽 +敍 +敎 +敔 +敗 +敘 +敫 +敭 +敵 +敻 +敾 +斂 +斃 +斎 +斕 +斖 +斝 +斬 +斷 +斿 +旂 +旃 +旄 +旉 +旙 +旛 +旡 +旲 +旳 +旻 +旼 +旽 +旾 +旿 +昃 +昉 +昍 +昐 +昚 +昛 +昜 +昞 +昡 +昣 +昤 +昪 +昫 +昰 +昺 +晈 +晉 +晊 +晙 +晛 +晝 +晩 +晪 +晫 +晭 +晸 +暅 +暈 +暉 +暊 +暌 +暎 +暏 +暐 +暕 +暘 +暝 +暟 +暠 +暦 +暫 +暱 +暲 +暸 +暻 +暾 +曄 +曅 +曆 +曇 +曌 +曔 +曖 +曠 +曧 +曨 +曩 +曮 +曶 +曷 +曺 +曽 +朊 +朏 +朓 +朖 +朧 +朶 +杁 +杌 +杓 +杙 +杣 +杤 +杧 +杬 +杴 +杻 +杼 +枏 +枖 +枛 +枠 +枡 +枲 +枹 +柁 +柃 +柉 +柊 +柎 +柝 +柟 +柰 +柵 +柶 +柷 +査 +柾 +栃 +栄 +栐 +栒 +栜 +栝 +栞 +栨 +栲 +栴 +栻 +桄 +桕 +桙 +桜 +桝 +桫 +桱 +桲 +桴 +桿 +梀 +梂 +梃 +梉 +梔 +梘 +梟 +梠 +梣 +梫 +梱 +梶 +梽 +棄 +棆 +棐 +棓 +棖 +棗 +棡 +棧 +棨 +棩 +棪 +棫 +棲 +棶 +棹 +棻 +棼 +椆 +椇 +椏 +椙 +椥 +椪 +椲 +椵 +楙 +楡 +楢 +楤 +楧 +楨 +楫 +楮 +楯 +楳 +榊 +榍 +榎 +榑 +榖 +榗 +榘 +榢 +榣 +榤 +榦 +榲 +榿 +槀 +槁 +槃 +槊 +槓 +槔 +槙 +槤 +槩 +槭 +槰 +槱 +槳 +槺 +槻 +槼 +樀 +樁 +樅 +樆 +樋 +樑 +樗 +樘 +樞 +権 +樫 +樺 +樻 +橈 +橐 +橒 +橓 +橚 +橢 +橫 +橿 +檄 +檇 +檉 +檊 +檎 +檜 +檞 +檠 +檡 +檢 +檣 +檦 +檨 +檯 +檳 +檵 +檻 +檽 +櫂 +櫃 +櫆 +櫈 +櫓 +櫚 +櫛 +櫞 +櫟 +櫨 +櫪 +櫱 +櫸 +櫻 +櫾 +櫿 +欄 +欉 +欏 +欒 +欖 +欞 +欥 +欸 +欹 +欽 +歊 +歎 +歛 +歩 +歲 +歳 +歴 +歿 +殂 +殄 +殑 +殘 +殛 +殞 +殟 +殤 +殭 +殮 +殯 +殲 +殳 +毀 +毆 +毉 +毌 +毎 +毐 +毖 +毘 +毬 +毴 +毸 +毿 +氂 +氈 +氍 +氫 +氬 +氷 +氹 +氻 +氾 +汎 +汜 +汧 +汭 +沄 +沆 +沇 +沍 +沒 +沖 +沘 +沚 +沜 +沢 +沨 +沯 +沺 +況 +泂 +泆 +泇 +泐 +泖 +泚 +洌 +洎 +洢 +洣 +洤 +洨 +洩 +洸 +洹 +浄 +浛 +浞 +浟 +浡 +浤 +浯 +浵 +浹 +涙 +涼 +淍 +淎 +淏 +淓 +淛 +淠 +淥 +淪 +淯 +淰 +淵 +淶 +淸 +淺 +淽 +渃 +済 +渉 +渋 +渕 +渙 +渟 +渦 +渫 +渼 +渽 +渾 +湉 +湊 +湔 +湜 +湞 +湣 +湥 +湧 +湳 +湴 +湼 +満 +溁 +溈 +溋 +溎 +準 +溙 +溦 +溲 +溵 +溼 +滀 +滄 +滅 +滈 +滉 +滌 +滎 +滝 +滯 +滲 +滷 +滸 +滹 +滻 +滽 +滾 +漇 +漈 +漎 +漚 +漣 +漬 +漲 +漴 +漵 +漷 +漸 +漼 +漿 +潁 +潑 +潛 +潟 +潯 +潰 +潲 +潽 +潾 +潿 +澀 +澁 +澂 +澆 +澇 +澉 +澋 +澌 +澔 +澗 +澠 +澣 +澥 +澪 +澮 +澯 +澱 +澻 +濁 +濊 +濋 +濘 +濙 +濫 +濬 +濰 +濲 +濶 +濺 +濼 +濾 +瀁 +瀅 +瀆 +瀉 +瀍 +瀏 +瀔 +瀕 +瀝 +瀞 +瀟 +瀠 +瀦 +瀧 +瀨 +瀬 +瀰 +瀲 +瀴 +瀶 +瀾 +灃 +灊 +灑 +灘 +灝 +灤 +灧 +灴 +災 +炁 +炆 +炘 +炟 +炤 +炱 +炲 +炷 +炻 +烉 +烋 +烒 +烔 +烝 +烱 +烴 +焃 +焄 +焌 +焓 +焜 +焞 +焴 +焻 +焿 +煇 +煉 +煐 +煒 +煔 +煕 +煚 +煠 +煩 +煬 +煳 +煵 +煶 +熅 +熇 +熈 +熒 +熖 +熗 +熜 +熤 +熯 +熲 +熺 +熼 +熾 +熿 +燄 +燉 +燊 +燏 +燐 +燔 +燝 +燫 +燬 +燭 +燹 +燻 +燼 +燾 +燿 +爀 +爌 +爔 +爚 +爛 +爝 +爿 +牁 +牂 +牆 +牕 +牖 +牘 +牝 +牠 +牻 +牼 +牽 +犂 +犎 +犖 +犛 +犢 +犧 +犨 +犰 +犴 +犽 +狎 +狓 +狛 +狟 +狦 +狨 +狳 +狶 +狷 +狹 +狻 +猁 +猄 +猇 +猊 +猙 +猞 +猢 +猨 +猳 +猶 +猺 +猻 +獁 +獃 +獄 +獇 +獎 +獏 +獢 +獣 +獬 +獮 +獯 +獰 +獵 +獷 +獺 +獼 +獾 +玀 +玆 +玎 +玏 +玓 +玕 +玗 +玘 +玙 +玠 +玡 +玢 +玧 +玨 +玭 +玶 +玹 +玾 +珅 +珌 +珎 +珖 +珝 +珡 +珤 +珦 +珧 +珪 +珮 +珵 +珹 +珽 +琁 +琄 +琇 +琍 +琎 +琡 +琤 +琱 +琹 +琺 +琿 +瑀 +瑂 +瑆 +瑈 +瑊 +瑋 +瑑 +瑒 +瑝 +瑠 +瑢 +瑣 +瑤 +瑥 +瑧 +瑨 +瑯 +瑱 +瑳 +瑴 +瑺 +璄 +璆 +璉 +璌 +璕 +璘 +璙 +璚 +璠 +璡 +璣 +璥 +璦 +璪 +璫 +璬 +璮 +璱 +璵 +璸 +璹 +璽 +璿 +瓈 +瓊 +瓌 +瓏 +瓑 +瓔 +瓖 +瓘 +瓚 +瓛 +瓞 +甂 +甌 +甍 +甑 +甕 +甡 +甦 +甪 +畀 +畇 +畊 +畋 +畎 +畑 +畝 +畠 +畢 +畧 +畬 +畯 +異 +畳 +畷 +疇 +疊 +疋 +疍 +疒 +疕 +痍 +痙 +痟 +痩 +痲 +痺 +瘍 +瘓 +瘜 +瘞 +瘡 +瘧 +瘰 +瘺 +癀 +癆 +癇 +癒 +癘 +癟 +癡 +癢 +癤 +癥 +癩 +癬 +癭 +癮 +癯 +癰 +癱 +癲 +発 +皐 +皚 +皛 +皝 +皞 +皰 +皷 +皸 +盃 +盋 +盌 +盞 +盡 +監 +盦 +盧 +盨 +盩 +盪 +盫 +盷 +盺 +眀 +県 +眛 +眜 +眥 +眵 +眾 +睜 +睞 +睥 +睪 +睭 +睺 +瞋 +瞞 +瞢 +瞫 +瞼 +瞽 +矇 +矍 +矚 +矧 +矯 +砢 +砩 +砫 +砮 +砯 +砲 +砳 +砵 +硃 +硇 +硏 +硐 +硓 +硜 +硤 +硨 +硭 +硯 +碕 +碡 +碪 +碭 +碸 +碻 +碽 +磔 +磘 +磙 +磜 +磡 +磪 +磯 +磱 +磲 +磵 +磻 +磾 +礄 +礎 +礐 +礑 +礒 +礙 +礠 +礦 +礪 +礫 +礬 +礮 +礱 +礽 +祂 +祆 +祇 +祋 +祏 +祓 +祕 +祧 +祹 +祿 +禃 +禇 +禍 +禎 +禑 +禓 +禔 +禕 +禘 +禛 +禟 +禠 +禤 +禨 +禩 +禰 +禱 +禵 +禼 +禿 +秈 +秠 +秳 +稅 +稈 +稉 +稑 +稘 +稙 +稜 +稟 +稱 +稲 +稺 +稾 +穀 +穈 +穉 +穌 +積 +穎 +穟 +穠 +穡 +穢 +穣 +穫 +窅 +窋 +窣 +窩 +窪 +窮 +窯 +窰 +窶 +窺 +竄 +竅 +竇 +竈 +竊 +竑 +竜 +竦 +竩 +竻 +笄 +笘 +笞 +笥 +笩 +笪 +笭 +笮 +笯 +笱 +笳 +笹 +筅 +筊 +筌 +筍 +筘 +筥 +筦 +筧 +筬 +筭 +筲 +筳 +筶 +筻 +箆 +箇 +箋 +箏 +箑 +箒 +箜 +範 +篊 +篋 +篌 +篔 +篠 +篤 +篥 +篩 +篭 +篯 +篳 +簀 +簃 +簉 +簍 +簑 +簕 +簗 +簞 +簠 +簫 +簷 +簹 +簺 +簽 +簾 +籀 +籌 +籐 +籙 +籛 +籜 +籝 +籟 +籠 +籥 +籪 +籬 +籮 +籲 +籾 +粄 +粍 +粦 +粩 +糀 +糌 +糎 +糞 +糢 +糧 +糬 +糰 +糴 +糶 +糸 +糹 +糺 +糾 +紂 +紆 +紇 +紈 +紉 +紐 +紑 +紓 +紕 +紘 +紙 +紛 +紜 +紝 +紞 +紮 +紱 +紲 +紳 +紵 +紺 +紿 +絃 +絆 +経 +絎 +絕 +絛 +絜 +絞 +絢 +絨 +絪 +絳 +絵 +絹 +絺 +綃 +綈 +綎 +綏 +綖 +継 +続 +綜 +綝 +綞 +綠 +綢 +綣 +綧 +綬 +綮 +綰 +綳 +綴 +綸 +綺 +綻 +綽 +綾 +綿 +緁 +緃 +緄 +緈 +緊 +緋 +総 +緒 +緘 +緜 +緝 +緞 +締 +緡 +緤 +編 +緩 +緬 +緱 +緲 +練 +縂 +縄 +縈 +縉 +縊 +縕 +縛 +縝 +縞 +縠 +縡 +縯 +縱 +縴 +縵 +縷 +縹 +縻 +績 +繃 +繆 +繇 +繒 +繕 +繖 +繙 +繚 +繞 +繩 +繫 +繭 +繰 +繳 +繻 +繼 +繽 +繾 +纁 +纈 +纍 +纏 +纓 +纔 +纕 +纖 +纘 +纜 +缶 +缽 +罃 +罅 +罈 +罉 +罌 +罍 +罟 +罨 +罰 +罳 +罵 +罶 +罷 +罽 +羂 +羆 +羈 +羋 +羕 +羗 +羣 +羥 +羨 +羱 +翀 +翂 +翃 +翕 +翙 +翜 +翬 +翮 +翹 +耎 +耔 +耨 +耬 +聃 +聒 +聟 +聰 +聱 +聳 +聴 +聶 +聽 +聾 +肅 +肏 +肜 +肫 +肸 +肹 +胂 +胅 +胇 +胊 +胙 +胝 +胼 +脅 +脇 +脈 +脛 +脣 +脩 +脫 +脬 +脭 +脳 +脷 +脹 +腧 +腫 +腳 +膂 +膣 +膥 +膩 +膮 +膽 +膾 +膿 +臉 +臍 +臏 +臚 +臞 +臟 +臠 +臯 +舂 +舉 +舎 +舘 +舢 +舥 +舨 +舩 +舲 +舺 +艅 +艉 +艋 +艎 +艏 +艔 +艙 +艚 +艱 +艸 +艽 +芑 +芛 +芨 +芴 +芻 +苅 +苤 +苧 +苳 +苺 +苻 +苾 +茀 +茇 +茈 +茘 +茚 +茛 +茝 +茮 +茲 +茷 +茺 +荅 +荇 +荊 +荎 +荖 +荳 +莕 +莖 +莙 +莛 +莢 +莧 +莩 +莿 +菈 +菉 +菍 +菑 +菔 +菝 +菥 +菫 +菰 +菴 +菶 +菸 +菹 +菺 +菼 +菾 +萇 +萐 +萠 +萡 +萣 +萩 +萵 +萹 +葃 +葊 +葎 +葙 +葜 +葝 +葦 +葯 +葰 +葶 +葷 +蒍 +蒎 +蒐 +蒓 +蒔 +蒗 +蒞 +蒢 +蒧 +蒨 +蒭 +蒯 +蒴 +蒹 +蒺 +蒼 +蒾 +蓀 +蓁 +蓂 +蓆 +蓍 +蓘 +蓚 +蓧 +蓨 +蓪 +蓭 +蓯 +蓳 +蓽 +蔆 +蔎 +蔔 +蔕 +蔘 +蔝 +蔞 +蔣 +蔥 +蔦 +蔭 +蔴 +蔵 +蕁 +蕅 +蕎 +蕑 +蕖 +蕘 +蕚 +蕡 +蕢 +蕩 +蕪 +蕭 +蕷 +蕺 +蕻 +薀 +薆 +薊 +薌 +薐 +薑 +薔 +薗 +薘 +薙 +薜 +薞 +薟 +薨 +薫 +薲 +薷 +薸 +薺 +薾 +薿 +藎 +藟 +藦 +藨 +藪 +藶 +藸 +藹 +藺 +蘂 +蘄 +蘅 +蘊 +蘋 +蘐 +蘓 +蘗 +蘘 +蘚 +蘞 +蘢 +蘧 +蘩 +蘵 +蘶 +蘿 +虉 +虓 +虖 +虛 +虜 +虧 +虨 +虯 +虵 +虺 +蚆 +蚋 +蚍 +蚖 +蚡 +蚢 +蚵 +蚺 +蚼 +蛄 +蛉 +蛍 +蛑 +蛞 +蛯 +蛸 +蛺 +蛻 +蜆 +蜉 +蜑 +蜞 +蜢 +蜣 +蜨 +蜮 +蜯 +蜾 +蝀 +蝍 +蝓 +蝕 +蝘 +蝚 +蝟 +蝣 +蝤 +蝦 +蝨 +蝮 +蝯 +蝰 +蝲 +蝸 +螄 +螅 +螋 +螐 +螔 +螞 +螠 +螢 +螣 +螥 +螫 +螭 +螶 +螻 +螽 +螾 +蟄 +蟅 +蟊 +蟌 +蟎 +蟜 +蟥 +蟪 +蟫 +蟬 +蟯 +蟲 +蟳 +蟴 +蟶 +蟻 +蠂 +蠃 +蠅 +蠆 +蠊 +蠋 +蠍 +蠐 +蠑 +蠘 +蠙 +蠟 +蠣 +蠱 +蠲 +蠵 +蠶 +蠷 +蠻 +衂 +衎 +衕 +衚 +衜 +衝 +衞 +衽 +袓 +袛 +袞 +袴 +袾 +裊 +裎 +裒 +裖 +裬 +裵 +裾 +裿 +褌 +褍 +褎 +褘 +褙 +褞 +褧 +褫 +褭 +褸 +褻 +襌 +襖 +襞 +襠 +襤 +襦 +襪 +襯 +襲 +襴 +襶 +襻 +襾 +覇 +覈 +規 +覓 +覚 +覡 +覦 +覧 +覬 +覲 +観 +覽 +覿 +觔 +觙 +觚 +觜 +觭 +觱 +觴 +觶 +觿 +訁 +訃 +訇 +訌 +討 +訏 +訐 +訒 +訔 +訕 +訖 +託 +訛 +訝 +訟 +訥 +訴 +訶 +註 +証 +詁 +詆 +詈 +詐 +詒 +詔 +評 +詛 +詞 +詠 +詡 +詣 +詥 +詧 +詫 +詭 +詮 +詰 +詳 +詵 +詼 +誄 +誅 +誇 +認 +誒 +誕 +誡 +誣 +誤 +誥 +誦 +誨 +說 +読 +誰 +誴 +誹 +誼 +誾 +談 +諍 +諏 +諒 +論 +諗 +諜 +諟 +諠 +諡 +諤 +諦 +諧 +諪 +諫 +諭 +諮 +諱 +諲 +諳 +諴 +諶 +諷 +諸 +諺 +諼 +謀 +謁 +謂 +謄 +謊 +謌 +謎 +謏 +謐 +謔 +謖 +謗 +謙 +謚 +謜 +謠 +謤 +謨 +謩 +謫 +謬 +謳 +謾 +譏 +譓 +譔 +譙 +譚 +譞 +譫 +譭 +譯 +議 +譲 +譳 +譴 +譽 +譿 +讃 +讌 +讎 +讓 +讖 +讙 +讚 +讜 +讞 +谿 +豈 +豎 +豔 +豢 +豨 +豬 +豳 +豸 +豿 +貐 +貒 +貓 +貘 +貞 +貤 +貧 +貪 +貫 +責 +貮 +貯 +貲 +貳 +貶 +貸 +貺 +貽 +賁 +賂 +賃 +賄 +賈 +賊 +賑 +賒 +賔 +賕 +賚 +賞 +賡 +賤 +賦 +賨 +賬 +賭 +賹 +賺 +賻 +賽 +賾 +贄 +贅 +贇 +贊 +贌 +贍 +贓 +贔 +贖 +贛 +赧 +赬 +趐 +趕 +趖 +趨 +趺 +趼 +跅 +跏 +跗 +跡 +跣 +跩 +踎 +踐 +踰 +踴 +蹕 +蹟 +蹠 +蹤 +蹯 +蹺 +蹻 +躂 +躄 +躉 +躋 +躍 +躑 +躒 +躔 +躝 +躪 +躰 +軀 +軋 +軔 +軛 +軟 +転 +軫 +軲 +軸 +軹 +軺 +軻 +軼 +軾 +較 +輄 +輅 +輋 +輒 +輓 +輔 +輛 +輞 +輟 +輥 +輦 +輩 +輬 +輭 +輶 +輻 +輾 +輿 +轀 +轂 +轄 +轅 +轆 +轍 +轎 +轘 +轝 +轟 +轤 +辭 +辮 +辯 +辵 +辺 +辻 +込 +迴 +迵 +迺 +逈 +逋 +逌 +逎 +逕 +逖 +逤 +逨 +逴 +遄 +遊 +違 +遘 +遙 +遜 +遞 +遯 +遲 +遶 +遷 +遹 +遺 +遼 +邁 +邇 +邉 +邊 +邙 +邠 +邲 +邽 +邾 +郃 +郄 +郇 +郋 +郞 +郟 +郤 +郪 +郳 +郷 +郿 +鄃 +鄆 +鄋 +鄑 +鄒 +鄔 +鄖 +鄗 +鄘 +鄚 +鄜 +鄠 +鄤 +鄧 +鄩 +鄫 +鄰 +鄲 +鄳 +鄴 +酃 +酆 +酈 +酎 +酏 +酔 +酢 +酩 +酴 +酺 +酼 +醁 +醂 +醃 +醅 +醞 +醢 +醣 +醮 +醯 +醾 +醿 +釁 +釆 +釋 +釐 +釒 +釓 +釔 +釕 +釗 +釘 +釙 +釚 +釤 +釦 +釧 +釩 +釪 +釭 +釴 +釵 +釷 +釹 +釺 +鈀 +鈁 +鈄 +鈇 +鈈 +鈉 +鈊 +鈍 +鈏 +鈐 +鈑 +鈔 +鈕 +鈖 +鈞 +鈢 +鈣 +鈥 +鈦 +鈫 +鈮 +鈰 +鈳 +鈷 +鈸 +鈹 +鈾 +鈿 +鉀 +鉆 +鉈 +鉉 +鉋 +鉍 +鉏 +鉑 +鉓 +鉗 +鉚 +鉛 +鉞 +鉟 +鉤 +鉦 +鉬 +鉭 +鉲 +鉶 +鉷 +鉸 +鉻 +鉾 +鉿 +銂 +銃 +銅 +銋 +銍 +銓 +銕 +銖 +銚 +銜 +銠 +銣 +銥 +銦 +銨 +銩 +銪 +銫 +銬 +銱 +銲 +銳 +銶 +銹 +銻 +銼 +銾 +鋁 +鋅 +鋆 +鋇 +鋌 +鋏 +鋐 +鋒 +鋕 +鋗 +鋙 +鋡 +鋤 +鋥 +鋦 +鋨 +鋮 +鋯 +鋰 +鋱 +鋳 +鋶 +鋸 +鋹 +錀 +錏 +錐 +錒 +錕 +錘 +錚 +錞 +錟 +錠 +錡 +錢 +錨 +錫 +錬 +錮 +錳 +錸 +錻 +鍀 +鍇 +鍈 +鍉 +鍍 +鍏 +鍔 +鍘 +鍛 +鍝 +鍟 +鍠 +鍥 +鍩 +鍬 +鍱 +鍳 +鍶 +鍷 +鍺 +鍼 +鍾 +鎂 +鎅 +鎊 +鎌 +鎓 +鎔 +鎗 +鎘 +鎚 +鎛 +鎣 +鎦 +鎧 +鎪 +鎬 +鎭 +鎰 +鎳 +鎵 +鏃 +鏇 +鏈 +鏊 +鏌 +鏐 +鏑 +鏓 +鏗 +鏘 +鏝 +鏞 +鏟 +鏤 +鏦 +鏳 +鏴 +鏵 +鏷 +鏻 +鏽 +鐃 +鐇 +鐈 +鐓 +鐔 +鐙 +鐠 +鐤 +鐦 +鐧 +鐫 +鐬 +鐭 +鐮 +鐲 +鐳 +鐸 +鐺 +鐽 +鐿 +鑀 +鑁 +鑂 +鑄 +鑅 +鑊 +鑌 +鑑 +鑛 +鑠 +鑣 +鑨 +鑪 +鑭 +鑰 +鑲 +鑴 +鑷 +鑼 +鑾 +鑿 +閂 +閆 +閉 +閎 +閒 +閔 +閘 +閜 +閞 +閦 +閨 +閬 +閭 +閰 +閱 +閶 +閹 +閻 +閼 +閾 +閿 +闆 +闇 +闈 +闊 +闋 +闌 +闍 +闐 +闓 +闔 +闕 +闖 +闘 +闞 +闡 +闢 +闥 +阭 +阯 +陁 +陔 +陘 +陜 +陝 +陞 +陬 +陸 +険 +隄 +隈 +隊 +階 +隕 +隣 +險 +隰 +隱 +隲 +隳 +隴 +隷 +隸 +隻 +雋 +雑 +雖 +雛 +雝 +雩 +雫 +雱 +霅 +霈 +霊 +霑 +霙 +霤 +霧 +霨 +霶 +霽 +靁 +靂 +靄 +靉 +靚 +靫 +靬 +靭 +靺 +靼 +鞆 +鞏 +鞞 +鞥 +鞦 +鞨 +鞮 +鞴 +韁 +韃 +韆 +韋 +韌 +韑 +韙 +韜 +韞 +韠 +韡 +韮 +韺 +韾 +頁 +頃 +項 +須 +頊 +頌 +頍 +頎 +頏 +預 +頑 +頒 +頗 +領 +頜 +頠 +頦 +頫 +頴 +頵 +頷 +頸 +頹 +頻 +頼 +顆 +額 +顎 +顒 +顓 +顕 +顗 +願 +顙 +顛 +顥 +顫 +顰 +顱 +顳 +顴 +颮 +颯 +颱 +颶 +颺 +颼 +飆 +飈 +飠 +飡 +飢 +飥 +飩 +飪 +飫 +飭 +飴 +飽 +餃 +餄 +餉 +餌 +餎 +餒 +餓 +餗 +餚 +餛 +餞 +餡 +餵 +餺 +餾 +餿 +饋 +饌 +饑 +饒 +饗 +饞 +饟 +饢 +馘 +馛 +馦 +馭 +馯 +馱 +馳 +馼 +駁 +駄 +駅 +駆 +駐 +駑 +駒 +駔 +駘 +駙 +駛 +駝 +駟 +駢 +駭 +駰 +駱 +騁 +騂 +騄 +騅 +騋 +騎 +騏 +験 +騖 +騙 +騤 +騨 +騫 +騭 +騮 +騶 +騾 +驁 +驃 +驄 +驅 +驌 +驍 +驎 +驒 +驕 +驚 +驛 +驟 +驢 +驤 +驥 +驩 +驪 +骯 +髀 +髎 +髏 +髑 +髒 +髡 +髭 +髲 +髷 +髹 +鬄 +鬅 +鬆 +鬍 +鬚 +鬢 +鬥 +鬧 +鬨 +鬩 +鬪 +鬬 +鬮 +鬯 +鬱 +鬹 +鬻 +魃 +魈 +魋 +魍 +魎 +魕 +魘 +魛 +魞 +魟 +魣 +魨 +魩 +魮 +魯 +魴 +魷 +鮀 +鮁 +鮃 +鮄 +鮊 +鮋 +鮍 +鮐 +鮑 +鮒 +鮓 +鮗 +鮜 +鮟 +鮠 +鮡 +鮣 +鮨 +鮪 +鮫 +鮭 +鮰 +鮸 +鮹 +鮻 +鯀 +鯁 +鯃 +鯇 +鯊 +鯏 +鯒 +鯓 +鯔 +鯕 +鯖 +鯗 +鯙 +鯛 +鯡 +鯢 +鯤 +鯧 +鯪 +鯭 +鯮 +鯰 +鯶 +鯷 +鯻 +鯿 +鰂 +鰃 +鰆 +鰈 +鰉 +鰍 +鰏 +鰒 +鰓 +鰕 +鰗 +鰛 +鰜 +鰟 +鰣 +鰤 +鰧 +鰨 +鰩 +鰭 +鰮 +鰱 +鰲 +鰳 +鰶 +鰷 +鰹 +鰺 +鰻 +鰼 +鰾 +鱀 +鱂 +鱅 +鱇 +鱈 +鱉 +鱊 +鱒 +鱓 +鱔 +鱖 +鱗 +鱘 +鱚 +鱝 +鱟 +鱠 +鱣 +鱥 +鱧 +鱨 +鱮 +鱰 +鱲 +鱵 +鱷 +鱺 +鳧 +鳩 +鳰 +鳴 +鳶 +鳽 +鴆 +鴇 +鴉 +鴒 +鴓 +鴕 +鴗 +鴛 +鴝 +鴞 +鴟 +鴡 +鴣 +鴦 +鴫 +鴯 +鴰 +鴴 +鵂 +鵄 +鵎 +鵐 +鵑 +鵒 +鵓 +鵙 +鵜 +鵝 +鵞 +鵟 +鵠 +鵡 +鵪 +鵯 +鵰 +鵲 +鵵 +鵼 +鵾 +鶆 +鶇 +鶉 +鶒 +鶓 +鶘 +鶚 +鶡 +鶥 +鶩 +鶬 +鶯 +鶲 +鶹 +鶺 +鶻 +鶼 +鶿 +鷂 +鷉 +鷎 +鷓 +鷙 +鷚 +鷟 +鷥 +鷦 +鷫 +鷯 +鷲 +鷳 +鷸 +鸊 +鸌 +鸐 +鸑 +鸕 +鸘 +鸚 +鸛 +鸜 +鸝 +鸞 +鹮 +鹵 +鹹 +鹼 +麅 +麇 +麈 +麊 +麐 +麞 +麩 +麪 +麴 +麹 +麼 +麿 +黁 +黇 +黌 +黐 +黙 +黥 +黧 +黨 +黴 +黶 +黻 +黼 +黽 +黿 +鼂 +鼇 +鼈 +鼉 +鼐 +鼒 +鼕 +鼢 +鼩 +鼯 +鼱 +鼴 +鼷 +齒 +齕 +齡 +齣 +齦 +齧 +齲 +齶 +龎 +龐 +龑 +龔 +龕 +龜 +龝 +龠 +ず +梌 +叀 +晢 +媸 +錾 +鐖 +䰡 +櫬 +锱 +υ +鼗 +媪 +澴 +苈 +眴 +𝜏 +缱 +𝜶 +조 +晡 +≡ +ࠀ +н +廇 +嗛 +篚 +ώ +莰 +윤 +纚 +𢢞 +闼 +熌 +饎 +蓊 +倅 +년 +聭 +耩 +≅ + +≺ +诌 + + + +耰 +菗 +僦 +⇣ +甊 +冓 +缷 +枊 +沕 +𝐴 +❹ +형 +秾 + +щ +厹 + +˗ +疔 +䩦 +髴 +⨂ +莏 +≧ +垆 +銌 +桤 +隤 +ギ +벽 +⑸ +✘ +̣ +辶 +铼 +게 +へ +獶 +藳 +祍 +黉 +跱 +⽬ +埙 +だ +蓣 +亯 +구 + +鹎 + +⾃ +楩 +⌘ +汏 +虒 +谖 + +﹜ +劖 +じ +瑇 +㮑 +揕 +⇔ +𤔲 +薉 +𝑾 +硗 +〈 +は +盍 +狽 +ж +я +挆 +槨 +γ +阏 +襕 +𝜉 +❖ +└ +총 +시 + +ν +刲 +ด +嬲 +绤 +𝐰 +飦 +扱 +帻 +辀 +廴 +к +蔖 +– +같 +熭 +巣 + +裛 +𝑶 +蓺 +蔊 +그 +匳 +玚 +Ц +璲 +련 +𨒅 +변 +㤵 +饫 +𨚵 +X +筇 +镡 +ⅳ +𝛿 +轸 +𝑭 +鋈 +鵩 +縁 +˙ +ɿ +𝒴 +㝮 +𝜂 +栠 +橦 +緇 +肰 + +跼 +䭜 +蜅 +訸 +㻶 +𝑉 +เ + +嚢 +鼔 +𝒆 +閫 +阃 +𥞹 +杪 +誊 + +鲋 +骍 +τ +莾 +凊 +﹡ +箚 +蛱 +樯 +喾 +幞 +欕 +搡 +戉 +瘖 +᙭ +砟 +ས +∤ +ี +メ +𝝁 +穑 +渶 +𦬁 +서 +⊗ +穇 +⌊ +を +鐻 +蘤 +≫ +◐ +汙 +蒒 +⑷ +蹨 +x +裥 +嶤 +ァ +従 +침 +稂 +𪧶 +で +𝑹 +⑫ +闩 +槫 +舮 +𝑿 +戁 +간 +戯 + +ོ +æ +わ +チ +砉 +Ψ +劂 +・ +В +鬭 +钔 +盭 +黓 +⎯ +𝐏 +함 +钪 +𝑸 +澰 +래 +藒 +龃 +瞀 +伧 +♂ +¹ +ƞ +澼 +餍 +倶 +ð + +嚱 +跬 +貙 +磿 +娬 +氿 +鹘 +𝐁 +摅 +ヱ +傰 +พ +湝 +ˆ +Л +翾 +≃ +에 +滫 +С +嫕 +あ +㈣ +ⅇ +垧 +⺮ +∠ +躐 +硌 +眢 +乧 +𝑐 +泃 +轫 +↔ +㎝ +≜ +⽇ +撟 +⟹ +脿 + +㸁 +靯 +う +⁠ +懬 +搷 +瀓 +ˁ +ⅲ +훈 + +お +𝛄 +瓅 +葻 +猋 +ら +⾳ +喣 +⽿ +č +鈎 +⑤ +å +阸 +름 +て +圮 +⚫ +⻄ +胨 +琠 +戄 +箄 +𝒳 +鼍 +й +⼲ +廪 +睃 +囫 +͞ +죄 + +호 +み +饩 + +⊆ +х +欚 +瘚 +≯ +瞗 +ž +嗵 +근 +ま + +⾔ +罥 +ʹ +鼃 +д +✳ +ゃ +悊 +𝐅 +영 +@ +ɣ +𝛷 +𝜁 +ǜ +犄 +⽂ +ཆ +胒 +﹦ +谫 +є +・ +𝐻 +狺 +백 +舳 +𝑁 +ษ + +𝜓 +𝒦 +盕 +유 +𪯐 +茑 +礤 +거 +コ +肂 +鸻 +ã +⑬ +铚 +걸 +磳 +綷 +𝒚 +舭 +腚 +㈩ +榱 +𝐌 +畾 +馐 +罾 +∕ +𝔛 +𝑬 +ç +楬 +櫽 +顼 +阋 + +꺼 +諛 +̌ +้ +㮀 +乵 +沬 +⼀ +ư +鲠 +䜩 +樉 +鹈 +搧 +轾 +䟒 +등 +𝝉 +잠 +짤 +า +蘨 +愪 +ྟ +慪 +鮝 +𝛑 +び +𥞪 +𝐾 +レ +교 +ྲ +달 +𝐩 + +殹 +踇 +狥 +ベ +미 +매 +⑭ +钁 +Θ +못 +𝜇 +侂 +ę +ฟ +邶 +諣 +颃 +𡢕 +昑 +𝒖 +讱 +﹤ +緵 +骢 +朢 +骘 +ℜ + +ゞ +愬 +鹬 + +ッ +ར +급 +‚ +鸶 +蒫 +餽 +蓃 +ข +辠 +ğ +氺 +暆 +笿 +迚 +甝 +ή +徼 +旣 +ϖ +ヲ +倕 +匽 +蓱 +리 +剷 +ู +逪 + +나 +堋 +焠 +Δ +炑 +爫 +蒖 +𝒓 +悫 +𝛱 + +𝐮 +騧 +ⅴ +饾 +贠 +𝚲 +崀 +磀 +柤 +肈 +⻮ +鶄 +狲 +跫 +지 +鳇 +痖 +跂 +秫 +ʒ +합 +ไ +迨 +𝜐 + +屦 +𝐶 +; +辎 +∵ +鴁 +撏 +ς +⟶ +薮 +㟪 +犮 +ب +ビ +藡 +甏 + +眡 +訿 +鉥 +媵 + +柫 +𝒞 +ь +萏 +ค +트 +訮 +汚 +眚 +〞 +き +ほ +刖 +髄 +蘀 +や +ة +诹 +т +ན +𝒃 +掼 +䓁 +僥 +팰 +枵 +✔ +³ +ེ +鼖 +屖 +鍮 +砇 +カ +舐 +牴 +𝜎 +㡿 +攉 +⽤ +晅 +労 +蛕 +𝐽 +Ʃ +く +穽 +孥 +𝒏 + + +ɬ +玦 +檮 +ョ +∥ +중 +萯 +呲 +䰈 +새 + +釶 + +ɢ +⊂ +臮 + +梼 +デ +骖 +ス +蹩 +羼 +▽ +Π +≪ +匛 +𝐼 +稊 +่ +茠 +䢉 +秝 +茐 +齎 +そ + +芕 +噚 +癉 +蹱 +蓜 +𝐬 +ϑ +е +瀋 +ϕ +χ +镟 +霂 +隒 +▱ +ヶ +撄 +둔 +¢ +こ +跲 +莻 +𝑠 +輮 +็ +堠 +푟 +赕 +◦ +ا +런 +帒 +汘 +̱ +尥 +蘠 +𦟜 +옥 +腠 +夨 +⩾ +𝑝 +歯 +刱 +여 +け +溘 +釰 +肍 +擗 +矱 +鍌 +芧 +술 +발 +鼫 +舾 +⼯ +𝝓 +ƒ +怸 +པ +𣐼 +疎 +铷 +Η +⑺ +蒏 +림 +⃛ +゜ +褴 +𨒪 +れ +揢 +さ +櫫 +櫑 +䋎 +灋 +櫜 +诓 +❶ +𝐃 +Q +袳 +ℒ +菂 + +荙 +ℛ +⁄ +堙 +贋 +̅ +鳏 +̂ +、 +茍 +泜 +𝑈 +즉 +噔 + + +迓 +Ⅸ +❷ +이 +_ +⾊ +Ö +铥 +耹 +䶮 + +무 + +セ +饳 +อ +篾 +통 +‒ +ย +덕 +말 +艨 +Ω +𝐨 +螓 +澐 +巠 +⋅ +钶 +도 +鸱 +齍 +恑 +褛 +剟 +준 +勶 +𠟠 +ß +箅 +𝑆 +悃 +蘥 + +Ξ +𝑘 +妣 +𝑖 +𝐑 +纡 +釿 +⺌ +ヴ +𝕀 +涻 +箙 +塚 + +⼠ +墈 +∷ +疴 +ク +ㄕ +𝒂 +蒪 +蓡 + +鷇 +瘏 +𣹳 +橰 +嵚 +帀 +주 +ド +盓 +爇 +φ +觋 +𝜑 +钍 +화 +표 +Ɛ +篰 +명 +週 +с +蓛 +裢 +穜 +㱃 +玊 +鲕 +蒕 +箪 +⑯ +苽 +矦 +偰 +盝 +佊 +僨 +駉 +𝑳 +머 +ª +絅 + +锒 + +苆 +ั +𝛻 +碹 +咺 +竝 +и +づ +강 +辁 +́ +铽 +纩 +齑 +𝝎 +어 +ユ +躡 +𝒄 +ซ +畛 +鸰 +ླ + +骉 +❸ +揲 +廃 +湋 +𝑲 + + +旤 + +蹷 +钌 +국 +豙 +鬳 + +ɛ +轳 +俜 +眄 +萮 +𝐡 +颵 +箓 +魑 +𝑅 +漍 +ℤ + +逡 +학 +浖 +ょ +¬ +怴 +𝛤 +怿 +祌 +纥 +𝒑 +⃑ +棅 +笵 +낭 +栦 +⑰ +บ +𝔽 +𝑇 +埝 +⽓ +孱 +埶 +匜 +鸼 + + +벌 +ル +锸 +斫 +妟 +뽀 +昬 +댁 +ʂ +暯 +夳 +ノ +堞 +懘 +榼 +鞫 +오 +𝑡 +偑 +戗 +∴ +伥 +끝 +𬌗 +稯 +岜 +Ε +犲 +𩓞 +연 +鹚 + +ག +诜 +嗍 +倥 +鳣 +庑 +屾 +雚 + +椄 +颏 +酤 +𝒋 +欛 + +း +려 +缋 +¾ +ゴ +籑 +笤 +鞛 +鏺 +蓒 +설 +緍 +⑩ +迀 +鼋 +ɮ +위 +锪 +∨ +滆 +€ +躅 +鋓 +柀 +䐶 +啎 +𝛵 +骃 +ć +갈 +卨 +い +𝑺 +鸲 +壻 +偯 +𝑞 +譖 +곤 +溍 + +噫 +순 + +𝑽 +ы +赑 +蓸 +鸮 +稃 +っ +詗 +으 +⨀ +屮 +俦 +伛 +畱 +늬 +𝑂 +朼 +沰 +겨 +з +骀 +鸩 +𝜈 +º +苊 +诎 +皤 + +하 +̀ +砑 +凷 +翄 +𝑛 +赪 +≮ +浗 +𝐍 +û +オ +ƹ +𝜅 +묘 +曛 +鳊 +𝛩 +癹 +磒 +ば +⑨ +礆 + +乼 + +∽ +褱 +藴 +縶 +觥 +に +식 +凫 + +佥 +槷 +阍 +䰍 +졸 +전 +葢 +㝸 +も +⻔ +遽 + +蹰 +𝛺 +裏 +། +를 + +ろ +짭 + +ぐ +싶 +渰 +⊤ +浳 +൯ +∃ +옛 +蟞 +과 +芠 +飖 +⼆ +敶 +粝 +𥃩 +坿 +䩉 +𝑯 +「 +矰 + +사 +𝛶 +𝑎 +挐 +푎 +동 +ℝ +Γ +︃ +珒 +鹍 +κ +鑓 +傁 +惓 +臿 +丣 +悒 +侔 +ñ +訳 +櫭 +賛 +觏 +辂 +覅 +濓 +堿 +擪 +฀ +𝑵 +扨 +嫫 +珰 + +寃 +𝒔 +曱 +髣 +인 +≌ +莵 +踳 +ⅱ +Ø +⌋ +¯ +挢 +̇ +﹪ +哕 +𦫳 + +襛 +昳 +铙 +铫 +軱 +汔 +ネ + +躩 +옷 +ถ +엄 +皊 +臑 +𧄝 +𝑃 + +䢅 +𝐝 +𝒍 +ℱ +𝐓 +蓾 +𝑻 +䋁 +裼 +개 +ത +𝒊 +僪 +瞂 +𦞠 +요 + ̄ +荍 +𝜔 +ф +峣 +庋 +檏 +袢 +绬 +Σ +향 +钫 +え +枅 +≝ +荦 +들 +勍 +ö +𝒕 +툰 +遬 +𝐵 +擧 +咢 +钘 + +𝒢 +Ⅷ +➢ +讧 +ω +簟 +廐 +刳 +阘 +б +⊘ +髟 +臓 +루 +⎧ +诳 +у +诮 +蠪 +梹 +耤 +パ +ن +∆ + +𝑫 +น +べ +坼 + +𝑤 +褽 +憼 +심 +∇ +迖 +휆 +叚 +없 +⼿ +钖 +斠 +䪵 +胠 +𝜋 +殽 +剜 +⾝ +− +慸 +𝛽 +椔 +⟩ +皦 +筚 +奰 +Å +물 +𝒐 +嫱 +钆 +ï +∪ +⇢ +ş + +㖞 +璗 +葸 +殢 +𝜺 +夲 +骒 +ち +회 +선 +睒 +轡 +ξ + +鲧 +镞 +碜 +놈 +Å +紴 + +⇤ +ྷ +⑪ +喟 +𦼰 + +蔩 +埦 +𝜆 +耋 +˜ +한 +舣 +馓 +⑻ + +ɐ +椘 +し +莐 +辔 +憰 +碛 +⁃ +飏 +颀 +跽 +⇥ +赀 +撺 +襜 +ɒ +袧 +л +정 +꾸 +콩 + +박 +缑 +柈 + +樲 +𝑮 +詘 +µ +𝑷 +鹪 +𝛼 +차 +讬 +掯 +硎 +𝑨 +舄 +‹ +누 +バ +ก +萀 +兇 +숙 +貍 + +踈 +친 +𝜽 +摰 +甿 +坜 +遑 +삼 +배 +Μ +을 +譊 +沩 +빈 +飑 +钹 +镨 +鐉 +宭 +桗 +ə +歺 +А +⇒ +锞 +𝒪 +棊 +愓 +莶 +琲 +འ +プ +་ +𝐿 +艟 +欬 +ิ +в +ų +纻 +㎎ +婄 +Ρ +歟 +椢 +粜 +종 +خ +ね +剞 +베 +斄 +幠 +ト +疛 +よ +╳ +醳 +군 +諂 +芰 +穋 +禆 +길 +秊 +噙 +y +锓 +⁵ + +拠 +Ĥ +𝑒 +窬 +抈 +︰ +퐶 +铳 +각 +ش +錉 +ù +臝 +闶 +𝒟 +芐 +韎 +권 +萚 + +ど +羮 +镕 +欔 +瘐 +받 +𝑚 +𢦟 +髤 +腙 +⽽ +상 +铘 +장 +𤇈 +ニ +凂 +ȷ +육 +а +살 +雠 +荑 +태 +穤 +ɯ + +圬 +楑 +단 +ง +⾯ +λ +⁰ +성 +萿 +缌 + +毣 +矅 + +푚 +˘ +貣 +∂ +은 +ė +䌛 +경 +せ + +拫 +⅞ +餕 +鐨 +翭 +ื +ɵ +⺍ +Փ +▬ +ว +희 +짐 +屙 +洫 +ေ +∏ +臜 + +剸 +芓 +운 +∓ +계 +祔 +鶵 +𝝅 +柂 +訢 +禊 +扽 +恫 +𝜙 +狢 +勠 +ི +𝜒 +จ +铯 +ྱ +𝑙 +蟇 +울 +莤 +牱 +𝒗 +詇 +靃 +殓 +栍 +踟 +ي + +鲄 +㓷 +贳 +ナ +鲓 +𝒙 +薁 +Χ +侪 +恌 +㰤 +목 +̄ +丱 +― +𝛔 +𝑔 + +鸷 +﹣ +籢 +脢 +δ +窭 +‐ +阒 +석 +아 +ォ +두 +𝐦 +浬 +搰 +褃 + +ལ +乇 +腘 +眊 +偬 +Ⅻ +ℳ +畤 +芟 +曈 +飧 +堌 +═ +谶 +櫝 +嬑 +冋 +嗌 +抜 + +腜 +공 +𝜕 +ん +鲭 +郐 +酓 +𝑍 +⾏ +⼹ +㐬 +고 +𝟑 +缯 +碤 +濩 +ʰ +佻 +Υ +∗ +賅 +집 +跹 + +ɾ +蔧 +다 +栫 +庰 +欤 +洿 +捾 +𝜍 +𝑄 + +攆 +夂 +檿 +荜 +ц +柖 +唅 +ท +ɦ +讦 +습 +锿 + +纆 +檑 +殰 +歠 +鼑 +Ä +و +☑ +緦 +悁 +偞 +ส +絭 +저 +踯 +騀 +쉰 +蒷 +揗 +儵 +ρ +薅 +ャ +‗ +犒 +旟 + +승 +ང +소 +𝛴 +瀜 +锜 +𣱼 +谳 + +軑 +ポ +楁 +𝑜 +袚 +ྐ +Á +𝑑 +鲀 +牾 +鬌 +푥 +¤ +呴 +‑ +✓ +민 +⼦ +ⅰ +⽉ +擿 +ч +➝ +가 +≳ +漥 +踖 +枧 +莝 +⻘ +傧 +𝑢 +ю +赍 +q +絫 +о +ア +ℐ +髫 +齢 +湎 +甓 +揿 + +ℋ +怹 +자 +⑦ +져 +椟 +鶟 +浕 +ー +𝛂 +偾 +⃗ +喑 +鹡 +≦ +磽 +ⅆ + +葂 +鶱 +ン +貇 +褡 +▴ +것 +喿 +つ +闚 + +盳 +𝟒 +雔 +洭 +殫 +楎 +£ +^ +葲 +𝟖 +眗 +棸 +潏 +熕 +𝟐 +품 +သ +樳 +⁴ +イ +㈢ +렴 +脰 +돈 +⑮ +钲 +𝒘 +訽 +爟 +幨 +枻 +亷 +猃 +σ +黩 +嘑 +榹 +⁡ +鍧 +𝑋 +枘 +𝑥 +원 +睚 +饔 +酲 + +顸 +람 +𝐫 +脁 +½ +긴 +ʔ +Ⅰ +旆 +죽 + +궐 + +奡 +㭃 +杝 +忾 +ม +掮 +饍 +摛 +쓰 +慊 +踣 +푅 +悽 +礅 +毄 +俓 +데 +冣 +만 +驖 +𤉣 +̃ +廾 +匵 +阇 +𤸫 +戣 +刌 +剕 +杅 +ο +蒥 +ː +癃 +蒬 +â +À +嗥 +우 +ケ +」 +聩 +ë +吽 +檌 +苰 +⑹ +Ÿ + +⑥ +노 + +˄ +鸫 +廛 +㱿 +鹛 +赟 +℅ +菿 +辳 +阼 +𝒇 +哋 +♀ +氕 +砤 +† +舡 +偝 +飜 +넓 +鈜 +ầ +닝 +禚 +匲 +〉 +Ф +锊 +ϵ +∙ +惛 +箧 +𝑦 +宬 +𝑀 +臙 +𩡶 + +¡ +潀 +수 +敃 +か +أ + +苌 +饘 +咝 +𝑼 +∘ +涷 +樍 +厣 +蝼 +墻 +Ñ +秅 +︒ +∅ +↵ +葹 +ỽ +𝑗 +た +일 +蒊 +치 +竢 +¨ +佢 +潵 +櫼 +軵 +𧕿 +倨 +歱 +瘅 +𝐭 +黾 +脼 +ê +땋 +鶷 +ё +鹯 +掲 +\ +𨳡 + +Г +ι +탁 +溞 +殪 +菭 +𝛥 +擛 +録 +㥥 +∀ +锇 +锃 +편 +餬 +瘻 +ཟ +豤 +로 +ɸ +ℎ +랑 +ʃ +鼹 +臬 +ŋ +巵 +譁 +w +窳 +蓔 +䉜 +浧 +酂 +⒀ +མ +椠 +槖 + +衄 +𨐨 +̿ +ご +⺗ +顇 +𝒫 +搕 +ミ +𪪋 +« +䣛 +鹩 +鴈 +п +는 +䋰 +𝛹 +犕 +呌 +𝒮 +𝑪 +鋎 +嚻 +杚 +䕊 +蠜 +ザ +𝐂 +☐ +𥘔 + +赜 +Ν +廦 +瓾 +↦ +龉 +⽅ +棂 +𝜌 +큰 +踔 +ラ +。 +剤 +황 +⅜ +僈 +骈 +ɕ +О +м +椑 +𝑟 +纇 +𝑓 +𝐖 +Ш +⎦ +旹 +삶 +ึ +囵 + +す +ⅈ +ت +踽 +陴 +餱 +ป +막 +紟 +방 +剀 +簖 +闬 +キ +鋉 +납 +タ +谵 +詑 +족 +垔 +荋 +旰 +𥘸 +窾 + +신 +𝐎 +𝛃 + + +﹒ +縰 +猲 +郘 +파 +⊕ +镘 +𠊃 + +呔 +𝜗 +ʊ +𝛬 +辏 +陭 +𝑕 +庴 +ʐ +瀌 +倄 +蕞 +ل +絷 +러 +든 +བ +柅 +› +傚 +睠 +Ⅺ +饐 +蔮 +ɟ +莈 +𤨨 +⋯ +犭 +𝜃 +𥹳 +초 +⎞ +遌 +眇 +蓗 +綅 +토 +裰 + +⼼ +虘 +𝑌 +觯 +漶 +钤 +讒 +げ +螬 +鲹 +咷 +蓞 +僂 +𝒉 +が +桮 +포 +쟈 +柽 +ウ +綟 +缟 +䁾 +钭 +烕 +厠 + +孭 +礉 +­ +谲 + +⼤ +𝒒 +旒 +㫄 + + +鳓 +挈 +재 +ད +𧊒 +蝝 +𝐺 +懱 +芢 + +ⅼ +Ú +𝑱 +翯 +芶 +厽 +遉 +鲒 +η +𝛾 +趮 +虆 +汸 +嬖 +糈 +窸 + +추 +棬 +懔 +硁 +ぶ +抟 +胕 +𝑧 +⌦ +碫 +Ⅵ +속 +𝐚 + +Ç +행 +Ɵ +⑱ +贽 +箤 +р +敒 +衤 +풍 +⊛ +慉 +ψ +© +광 +ℕ +屣 +臌 +旵 +臁 +‡ +癎 +閡 +𡵂 +襐 +畟 + +萪 +娒 +瘼 +庳 +천 +觌 +Α +と +奁 +煃 +؟ +◯ +의 +攎 + +𝐞 +J +𢦒 +❑ +벤 +𝐒 +リ +蒉 +𝐱 +朹 +㈤ +„ +䗬 +Ι +ཀ +𡜵 +俣 +疬 + +墥 +吣 +У +榀 +絟 + +旓 +𝐛 +𝜷 +瑮 +≔ +笾 +ζ +김 +暵 +𝜹 +逶 +萙 +欇 +俧 +籴 +絰 +揶 +ǔ +宂 +伩 +Ө +菞 +梕 +エ +蹚 +제 +Š +沝 + +𦳢 +𝒱 +揠 +ℏ +𝐹 +箝 +규 +氒 +⼊ +鰌 +筮 +⼩ +대 +𝔾 +䄃 +𝐸 +﹕ +부 + +刓 +ȵ +缛 +기 +缊 +𝟎 +𨟻 +め +捃 +⽚ +鍪 +灆 +迻 +⾦ +荗 +v +𡒊 +汍 +斲 +姕 + +儴 +偒 +辤 +芀 +蝥 +ń +臥 +椀 +㪚 +š +담 +ø +䈰 +睏 +テ +﹐ + +참 +楘 +𝒌 +劓 +ɪ +醑 +绹 +諓 +𝛉 +ズ +怼 +埘 +臽 +잡 +镢 +𝜖 +진 +踬 +谠 +﹥ +髺 +腞 +현 +嘭 +ʑ +蓌 +〜 +锠 +蓶 +る + +∼ +枎 +緗 +薠 +芈 +耪 +𝒎 +謼 + +瘳 +诨 +忤 +œ +⇡ + +鲣 +ⅵ +Τ +㯮 + +㶲 +ⅹ +䙴 +坴 +馑 +缹 +椦 +ô +⼈ +フ +誏 +э +哙 +愎 + +埽 +祲 +마 +殍 +菋 +懑 + +辇 +鍤 +𝜀 +ɜ +り +𝐷 +㕞 +瑵 + +蔨 +Ⅶ +镴 +ภ +𝝀 +𢶡 +⃝ +ơ +柢 +𧴗 +ʁ +攙 + +な +𝑏 +挴 +餧 +絇 +怄 +曏 +洟 +軷 +∉ +咍 +⎪ +樛 +𝑣 +웃 +椊 +黢 +𝑩 +誩 +伓 +戠 +橥 +⟨ +豰 +懥 +涖 +綘 +詬 +ွ +˚ +刽 +ɑ +격 +稖 +𝟏 +禝 +墦 +𝑊 + +択 +檙 +∝ +颟 +诂 +𝐧 +踲 +𝜛 +𝑰 + +鲬 +⁸ +ǎ +문 + +紬 +楲 +䊭 +枨 +膑 +õ +던 +Ⅴ +秏 +馔 +拊 +缗 +隠 +牀 +淲 +鬰 +綵 +鶑 +詎 +慙 +劒 +閲 +鎡 +淒 +屨 +鉢 +扃 +鳬 +閤 +馿 +翛 +駸 +蛩 +驂 +嵲 +覩 +牋 +湲 +蓴 +賸 +遡 +翫 +嫋 +惻 +妬 +罇 +龏 +鵷 +閙 +鎸 +朅 +巉 +僞 +洶 +磧 +筯 +慇 +鷁 +斾 +斸 +酹 +幘 +羶 +閽 +薤 +泝 +覯 +怱 +缾 +氳 +躊 +檝 +擣 +虀 +誚 +甃 +艤 +櫳 +醖 +壚 +涴 +崦 +秪 +潄 +濆 +駡 +坰 +闉 +縑 +躕 +颻 +燠 +輙 +鏁 +嶮 +薖 +輈 +綆 +覷 +蹔 +攄 +鐩 +鑱 +羃 +轓 +麤 +驀 +欵 +亙 +朮 +邐 +箠 +怳 +鋩 +鷃 +篘 +蔌 +諄 +旐 +慍 +欷 +頽 +蜺 +覊 +禋 +秔 +蜩 +嚬 +櫺 +軿 +痾 +笻 +猱 +毳 +泬 +竚 +齪 +搘 +欻 +釂 +嚥 +誑 +籩 +韉 +幙 +舠 +飣 +颭 +颸 +歔 +屧 +巇 +艫 +壖 +牓 +薝 +銛 +皪 +枿 +剗 +歘 +鸂 +邅 +衒 +荄 +鴂 +嫗 +顦 +瀼 +繄 +搆 +狖 +貰 +醆 +秖 +蹀 +頳 +纒 +憇 +溽 +澦 +讐 +灩 +箎 +螿 +鰥 +飀 +澒 +矻 +枌 +擡 +鷖 +齬 +纊 +挼 +齟 +錙 +屩 +蠧 +氅 +漭 +躚 +翺 +瘵 +螘 +鵶 +㶁 +斵 +饜 +岏 +䍦 +哢 +彴 +豗 +靨 +鋋 +禳 +覘 +鞚 +擻 +涘 +溷 +沴 +嶓 +褊 +罏 +齏 +醲 +繅 +舴 +釃 +厖 +闤 +閴 +藂 +譍 +糲 +籞 +躞 +餳 +遰 +倐 +嘖 +鷀 +暍 +韝 +蘺 +齁 +醽 +醨 +憀 +䕷 +跕 +拶 +垤 +鸎 +漙 +躭 +傴 +蕕 +嘒 +晻 +糵 +閈 +嫠 +斁 +鶗 +詶 +囘 +羇 +橛 +鞬 +磈 +粃 +阨 +塿 +敧 +氊 +芼 +襆 +迍 +鬛 +憒 +釅 +蓐 +奬 +頲 +髠 +抔 +葅 +槧 +跧 +揜 +渇 +餔 +罝 +裯 +蹁 +椶 +幰 +逰 +饁 +棃 +噀 +轔 +囁 +惸 +騑 +呪 +鬐 +綌 +醤 +䆉 +艣 +鐶 +夐 +摐 +鸇 +睎 +羝 +紼 +鞿 +噉 +磑 +闗 +筩 +駮 +蹌 +縢 +闠 +鬙 +谹 +榾 +觳 +皁 +晼 +啗 +簣 +騕 +蹣 +麰 +觧 +怊 +朞 +鱍 +蟣 +畚 +蠁 +舁 +瞇 +劚 +鰐 +籯 +鬖 +柮 +飱 +帟 +酇 +崿 +霪 +緌 +踆 +欃 +縟 +搦 +琖 +搥 +倀 +觫 +遝 +嚅 +聵 +藋 +筴 +喁 +窻 +穏 +牥 +鎩 +礲 +膴 +鞵 +醵 +斚 +縲 +裀 +齷 +騃 +袠 +谺 +靦 +帬 +鯈 +曀 +灔 +崷 +趂 +徯 +闃 +洧 +獪 +稏 +煢 +歈 +呶 +壈 +襃 +旴 +檟 +簦 +谽 +箵 +謡 +慝 +餖 +稌 +朣 +禖 +嚀 +嵂 +黷 +甖 +洑 +釡 +蕝 +甆 +翣 +篸 +隮 +滃 +裌 +蔀 +籖 +秬 +鷴 +啅 +慼 +捄 +咮 +睟 +譎 +嘷 +駃 +檥 +蹐 +窊 +駞 +雘 +趯 +篲 +讋 +睍 +毰 +憗 +鳷 +嚲 +圞 +歃 +緼 +賫 +籋 +繐 +麏 +灕 +礧 +歜 +飇 +鵁 +疢 +賖 +窆 +螮 +毹 +硉 +耡 +甔 +篛 +娭 +髩 +燋 +輜 +籧 +撝 +嬙 +徃 +驦 +𡏖 +麕 +馹 +覔 +鶠 +褷 +綍 +螗 +嗈 +彯 +篨 +炰 +鄮 +噞 +尅 +鷰 +鋭 +饉 +霢 +䔩 +坱 +裓 +帨 +忺 +豅 +栱 +謦 +傯 +誵 +骭 +潩 +鬒 +嵫 +悮 +扊 +扅 +轢 +惝 +臈 +舃 +鞾 +譟 +袵 +眎 +簏 +埸 +堧 +憸 +雰 +腷 +嵓 +隩 +趄 +墐 +褦 +艑 +狴 +玿 +竪 +恧 +姱 +抆 +恓 +霣 +躓 +鞲 +晬 +簴 +唼 +曵 +褕 +罣 +縐 +衘 +巃 +攲 +輀 +貎 +哳 +醭 +鋣 +僛 +迕 +蠭 +膓 +欝 +洊 +敺 +纎 +栟 +鞓 +蛬 +灺 +痏 +恡 +踸 +霔 +濵 +昻 +鉘 +楖 +竛 +竮 +窱 +幬 +慤 +儗 +黮 +嘐 +睆 +頇 +麑 +壼 +㦸 +顋 +瘥 +苖 +韈 +盻 +袷 +矼 +塼 +鐍 +傞 +苶 +吷 +噇 +鶖 +僣 +髧 +䅉 +鯫 +襏 +縳 +蠨 +痡 +髽 +剉 +蝱 +鄽 +匼 +嚚 +襫 +缿 +鵊 +燖 +忸 +摝 +攅 +牷 +氎 +騣 +颿 +虡 +腯 +漘 +矓 +祫 +顢 +綀 +弮 +柙 +蔾 +胾 +筤 +馽 +砆 +冩 +賙 +唶 +麛 +簜 +蹏 +屼 +鞶 +煑 +踠 +愀 +蠒 +頬 +韲 +戞 +畆 +笊 +搨 +捽 +絙 +覉 +澨 +趫 +矹 +穮 +愠 +劘 +轣 +卭 +鼪 +杕 +轗 +擐 +蚿 +恚 +檛 +𩕳 +靆 +轕 +餼 +頮 +槹 +蔉 +皜 +扄 +鮆 +轑 +蹡 +嵽 +甎 +蟈 +橅 +笴 +膰 +蕣 +澘 +髿 +樕 +褵 +蜋 +窼 +櫧 +雊 +胷 +嘵 +麄 +裋 +繢 +啐 +臛 +簁 +巓 +羜 +攧 +簮 +壊 +齩 +晹 +臲 +鬵 +齠 +媮 +幮 +壍 +蠛 +槜 +羖 +窓 +隃 +嚘 +輳 +籹 +凴 +崕 +獍 +嗸 +趦 +囅 +戺 +涬 +諉 +箯 +輊 +桹 +嵷 +㲲 +愊 +蒱 +洚 +赩 +輴 +幈 +齔 +嗁 +阽 +圠 +荈 +碔 +揎 +巀 +洏 +卼 +𨁝 +痁 +黳 +嗾 +䆗 +戃 +蕆 +頋 +悤 +掎 +㯝 +吚 +猘 +鮎 +鬴 +墁 +飋 +呿 +窀 +沲 +枒 +窌 +爼 +頞 +譡 +鶋 +湩 +㦬 +僾 +斒 +醼 +鶂 +磥 +揫 +犗 +齗 +鄶 +囏 +崪 +爞 +籓 +斮 +觝 +嵸 +驔 +䨴 +頺 +萑 +珓 +牸 +闒 +凘 +悢 +蟭 +濈 +嬄 +翽 +旍 +鶢 +罋 +輠 +怩 +頖 +趍 +壝 +嫮 +蕋 +踦 +轇 +眘 +巗 +嶭 +糓 +甽 +籺 +矟 +佖 +絏 +憮 +懡 +駈 +擕 +淟 +皡 +膋 +潨 +鳲 +趠 +麨 +頩 +漻 +輗 +墄 +賮 +㴩 +莟 +縦 +岝 +醻 +曚 +䙰 +噭 +醥 +筰 +躧 +踘 +鑕 +咈 +僶 +鶊 +鬂 +聼 +騐 +壒 +磎 +歗 +淈 +隟 +狃 +縋 +媻 +趲 +惙 +呫 +聮 +羾 +尫 +佽 +髼 +繋 +鬘 +旜 +疐 +阬 +䰐 +塈 +徤 +祊 +灂 +祅 +樷 +颾 +凟 +頀 +蠏 +塒 +衹 +婬 +裩 +粞 +憯 +匭 +筈 +盬 +霮 +黕 +靮 +伻 +緺 +瘝 +羑 +醸 +樝 +僎 +絓 +噆 +愞 +痗 +愽 +岊 +黤 +湑 +搉 +綯 +焮 +疉 +楛 +玼 +喤 +粔 +飂 +贐 +緉 +覰 +胔 +鞳 +摑 +墢 +斅 +誶 +僝 +鹺 +諌 +齅 +嵼 +讟 +冦 +脝 +婣 +緐 +茰 +飶 +欎 +慁 +抝 +瘉 +𡎺 +鈯 +瘃 +麫 +匊 +窞 +羓 +氄 +嚌 +姤 +橑 +駬 +冺 +騠 +㕙 +楶 +靸 +圎 +尀 +䙀 +鄏 +軃 +竁 +㹠 +刜 +剨 +罛 +鏹 +鬉 +簨 +藭 +藷 +僇 +瀫 +袨 +忮 +冡 +齯 +楪 +囋 +蟉 +醱 +尠 +牣 +攟 +袿 +齾 +甞 +啑 +潚 +樐 +絖 +酖 +觖 +骹 +嶅 +玃 +嫜 +廹 +儤 +矂 +艓 +挱 +骳 +嵳 +洴 +礓 +厪 +﨑 +禜 +籊 +瓻 +彛 +狁 +腪 +骾 +娯 +罻 +璅 +簳 +姢 +猰 +眹 +䴥 +堘 +搯 +怤 +緫 +聫 +涊 +熛 +輤 +䡾 +譌 +髇 +攛 +稭 +媕 +鬷 +跰 +縚 +鉧 +踧 +嚭 +襮 +藞 +滮 +颷 +荂 +蓰 +怫 +閧 +臕 +稛 +怗 +闑 +抶 +薶 +嶕 +瀺 +𥫗 +墝 +埆 +皥 +惷 +鞔 +鞺 +蟛 +瀡 +鎁 +酧 +恝 +齓 +嚄 +簔 +蟺 +㔶 +胹 +憖 +惄 +鸒 +貛 +軏 +縗 +蓻 +娵 +抺 +鼛 +虩 +歕 +矑 +繂 +襚 +倂 +廥 +諝 +虗 +弜 +兟 +繿 +偘 +翶 +肻 +棙 +斆 +碨 +醎 +蟢 +銙 +躠 +櫩 +椮 +絀 +鷾 +溳 +詖 +葓 +谼 +𦩘 +韔 +翿 +呑 +馡 +騊 +堁 +蓏 +䟃 +頟 +渢 +趑 +堄 +滛 +擫 +豭 +騩 +騘 +䍧 +彍 +忭 +餙 +馺 +忩 +芣 +矴 +噂 +滍 +慫 +𨍏 +怲 +扵 +搊 +昩 +嶻 +禬 +憃 +忼 +榰 +箾 +撁 +鈆 +袗 +脤 +騞 +哤 +螀 +靧 +梲 +囦 +魖 +褠 +䭔 +煆 +挃 +宷 +熉 +朘 +憭 +䒠 +謭 +鷤 +拕 +骫 +穾 +襭 +喓 +冞 +勩 +媢 +麚 +椓 +俙 +幐 +磝 +蜎 +灙 +漦 +㛹 +䭀 +㜷 +粻 +懟 +箳 +滣 +糉 +冐 +韤 +湱 +糭 +栳 +勌 +慱 +㸌 +罫 +筞 +霿 +躶 +玞 +磉 +罦 +祴 +媟 +猒 +擭 +恠 +嵁 +屴 +慆 +庬 +蟁 +㹀 +薧 +鷕 +渻 +朂 +愯 +齚 +蝻 +胏 +饙 +鳦 +鸃 +叅 +肧 +篂 +脗 +雺 +飰 +筀 +頥 +毶 +弌 +逓 +瞍 +絁 +鏚 +嚵 +攂 +醄 +奼 +獫 +絣 +靷 +畮 +褉 +棁 +揑 +楥 +橤 +襥 +蹮 +窔 +忪 +潠 +杇 +黲 +擄 +蚻 +蘙 +虙 +袐 +陿 +帊 +醟 +髖 +㞦 +鱭 +譸 +鮞 +栧 +扺 +脽 +擉 +岨 +黈 +餻 +佪 +遻 +鋟 +瞶 +廽 +懨 +墖 +玁 +籉 +宼 +鰋 +瑽 +垖 +酕 +漰 +戹 +蝛 +瑲 +阤 +褆 +儛 +䍽 +觕 +箘 +碯 +灨 +燀 +膇 +韀 +䳏 +詿 +禂 +韣 +踡 +碏 +尵 +莭 +庻 +篿 +狤 +㘞 +艭 +殱 +鵔 +槮 +猧 +劙 +獝 +㭊 +㾕 +蠚 +帤 +蹢 +蛚 +輼 +麀 +檃 +䰒 +䪫 +悾 +濳 +杗 +揾 +駏 +撦 +耈 +蟟 +狌 +鸖 +䨥 +餫 +鍰 +耉 +毚 +袽 +䱥 +慓 +䔿 +艖 +舋 +弰 +蠺 +嫓 +蚳 +髾 +喨 +鴐 +䍥 +韍 +柹 +掁 +薋 +攕 +飺 +凢 +麌 +嫰 +鑚 +黦 +葠 +吿 +栰 +踶 +芿 +穭 +啝 +筓 +褁 +稇 +顚 +䎘 +恇 +珷 +緪 +墠 +蛣 +蛜 +讕 +疻 +惎 +袝 +霡 +罸 +鬽 +苢 +喭 +飊 +唎 +澾 +襋 +皭 +廏 +蔿 +穊 +䝟 +駊 +獹 +夣 +褾 +慴 +軥 +讁 +軰 +瞷 +𡋯 +晜 +潗 +衋 +揵 +覼 +鱐 +醡 +䏰 +侐 +亁 +桞 +驘 +鬋 +鷽 +懞 +㵳 +儳 +豝 +傺 +搒 +縧 +硾 +䏶 +覻 +薍 +憝 +榠 +湆 +皵 +鎞 +菆 +糇 +矉 +搤 +紃 +峿 +磹 +甒 +琭 +𩥇 +菢 +禡 +渹 +刅 +迒 +敂 +蹜 +磓 +傪 +縿 +㕮 +涏 +䰀 +㡛 +韛 +犠 +餦 +圝 +焫 +㝢 +潬 +馵 +澟 +鱏 +譾 +㪍 +煼 +鍜 +窽 +紾 +堨 +䕸 +穅 +戅 +穄 +駴 +偫 +煗 +媠 +酘 +矬 +貆 +茞 +骩 +扠 +岞 +潓 +炧 +陊 +栭 +釱 +㡚 +篴 +耞 +鞉 +䋏 +𤫩 +椸 +儜 +痀 +謷 +潙 +寠 +牐 +嫭 +慅 +獧 +鈒 +欿 +薳 +蟂 +郲 +軨 +斨 +訦 +𠴲 +剺 +駪 +贙 +禫 +噣 +茢 +茙 +鄼 +揷 +魌 +䫻 +嗋 +噐 +侲 +諵 +𠺕 +挍 +䑳 +㨷 +槸 +靘 +㩧 +虣 +瑿 +衱 +襹 +餭 +㗶 +枑 +悋 +纑 +嶫 +儓 +髵 +甗 +榝 +㗭 +贗 +熸 +嬃 +礌 +偭 +樠 +栮 +鷼 +鵀 +澬 +眂 +牿 +骴 +呞 +爕 +牎 +巹 +帉 +砠 +梴 +䛏 +攃 +餁 +哿 +蹝 +崺 +閌 +醝 +臡 +麖 +駼 +賵 +夘 +骻 +愡 +俔 +諐 +觩 +莂 +饈 +殣 +溠 +冱 +埓 +厫 +虥 +芄 +慽 +竃 +埿 +仭 +褼 +倛 +韸 +牗 +幖 +禈 +穧 +蜧 +諞 +脞 +蝃 +飃 +煁 +涒 +谾 +覢 +赮 +鼘 +艗 +䶉 +鴥 +轒 +睅 +傔 +惵 +唈 +懆 +磣 +膢 +堶 +囈 +瘕 +誷 +瑘 +絝 +鬈 +嘽 +鷅 +梜 +喎 +鼟 +㟧 +劻 +眑 +剴 +痎 +餟 +庌 +菷 +梐 +吺 +躘 +慞 +罼 +穨 +摏 +釄 +莋 +呺 +砅 +鴽 +㘭 +㟅 +艴 +犉 +籕 +跐 +惏 +陗 +刋 +襘 +醹 +紽 +痌 +㗀 +撋 +陼 +駷 +艼 +踼 +癏 +慠 +趒 +邍 +姞 +䂬 +堲 +苙 +椌 +嗃 +挶 +岯 +禗 +嵔 +觡 +豜 +睩 +㒿 +塠 +燂 +扤 +恟 +鬝 +鬇 +鬡 +揳 +霠 +㗫 +苐 +蒀 +圌 +戭 +䖃 +𥈭 +勮 +耝 +轞 +胮 +墯 +枮 +罿 +浺 +綪 +爓 +蘃 +襍 +轜 +閠 +畽 +鄊 +嶆 +籭 +蠯 +陑 +瘽 +迆 +賷 +䍡 +韂 +躃 +禴 +簄 +瓟 +碐 +躨 +侜 +岍 +䃸 +趚 +髐 +榅 +粣 +屝 +鴃 +圁 +蝜 +黫 +僽 +丗 +靣 +湏 +抏 +㟽 +跙 +餤 +朙 +㹞 +瞖 +繣 +㨫 +罙 +糒 +惉 +葽 +鼮 +蕳 +豏 +𥱼 +鵮 +獦 +悕 +𠴨 +闟 +惽 +慿 +隉 +椷 +𩅰 +艛 +眽 +凓 +儃 +奨 +埀 +瑫 +駚 +濇 +緶 +峉 +礨 +髢 +瞯 +壥 +姡 +㟯 +髬 +啀 +㶿 +歅 +殀 +縩 +疈 +鳸 +霳 +稬 +圊 +彚 +裠 +埳 +褋 +㔩 +矲 +剶 +硋 +聦 +峞 +浰 +窵 +嘂 +睘 +簵 +腒 +韘 +躣 +甈 +忲 +舽 +襂 +硠 +脃 +鐏 +奯 +脧 +矕 +䠞 +駹 +豶 +訑 +柸 +鰅 +瘨 +趿 +糦 +蟏 +饛 +尰 +諑 +汃 +毺 +鋃 +絚 +馧 +艬 +枍 +爊 +峗 +泙 +碖 +鵕 +尩 +閗 +𤧚 +幩 +塉 +箊 +覂 +玒 +橧 +謟 +庨 +籔 +欑 +厎 +尭 +氉 +蠈 +䓞 +矙 +梡 +瀩 +溔 +煴 +蔲 +僬 +嵢 +梩 +弝 +𣙙 +鞟 +敉 +鮚 +湠 +鐐 +爣 +裻 +䶎 +𦨴 +謿 +垾 +蝂 +睂 +癙 +韽 +㟳 +桒 +鳿 +樏 +峛 +瑉 +僄 +顣 +衺 +殗 +肦 +圑 +朒 +喌 +犦 +㰅 +疁 +氃 +吰 +陻 +盰 +娀 +魶 +㖃 +曒 +娿 +獱 +孏 +酅 +蝡 +齰 +莬 +鄀 +逥 +挿 +觵 +縆 +㟝 +繍 +碙 +㑂 +䎳 +兾 +壸 +賝 +桯 +跁 +跒 +蔍 +舼 +忀 +懭 +媌 +罭 +菵 +狔 +靿 +拪 +㲉 +䔲 +嬀 +鵽 +涳 +朾 +𡸣 +𢫫 +虈 +㜮 +顑 +櫋 +蔪 +旝 +湡 +蹛 +稆 +唽 +㟏 +熂 +龡 +煟 +韅 +韐 +慂 +剳 +掫 +兠 +摋 +羫 +璊 +鵻 +駓 +佌 +蜹 +晲 +矒 +玅 +剰 +斶 +紖 +懴 +駜 +羢 +麳 +㳷 +馞 +爥 +鍚 +鑢 +螵 +嗺 +鏨 +𠙶 +疪 +鷔 +鮧 +轊 +栘 +鼜 +睗 +蟘 +枓 +䖟 +剠 +瞤 +圛 +椳 +籸 +䪌 +鯹 +湌 +丳 +賧 +縭 +檾 +𦨻 +撆 +䩫 +磢 +惥 +譀 +罤 +鞸 +鉎 +㶏 +膁 +甋 +瓀 +懹 +槢 +硊 +弆 +琫 +嵠 +駻 +湢 +杮 +䌨 +訹 +藇 +穯 +蠉 +曭 +蹎 +詄 +毷 +𩃎 +熁 +灜 +蜫 +蜳 +昈 +帩 +鈋 +䐹 +顖 +鄹 +匶 +毾 +礜 +堭 +婞 +鷿 +㙞 +詀 +瘮 +䫜 +㾪 +捘 +屫 +誧 +䲔 +閍 +蒳 +㬋 +遟 +嶀 +葐 +蜼 +㻱 +曡 +䃜 +濴 +䦱 +霫 +譆 +霋 +蕰 +襓 +氋 +鴷 +魦 +㩻 +㡠 +灉 +贑 +燑 +峝 +輷 +烻 +耼 +螉 +跜 +豩 +㑃 +藙 +鋂 +胐 +𣔻 +紒 +瓓 +塯 +辴 +趷 +堛 +㒟 +㗲 +㬊 +䄡 +卄 +姧 +猓 +躗 +覤 +醊 +兎 +罯 +痯 +覸 +詉 +癿 +岋 +歝 +茟 +㘆 +㮰 +淜 +𥉌 +㫰 +鈌 +毵 +狉 +贜 +峬 +汻 +誖 +烓 +睋 +潎 +䲺 +㠓 +歖 +𠜱 +槵 +熚 +萷 +磤 +絸 +鷞 +聻 +屷 +㝵 +諕 +瘂 +㺷 +蚰 +柦 +䍐 +泿 +礰 +摎 +㜕 +㻞 +洓 +喍 +囌 +囐 +䙱 +腨 +妉 +鄛 +鄥 +㵝 +輧 +鱄 +騟 +鈚 +廜 +𨗨 +㶼 +膞 +崯 +硞 +萆 +眒 +譩 +揬 +藑 +匌 +㠾 +㥏 +㢮 +䕢 +帣 +酭 +枦 +孅 +鞙 +丷 +鍭 +䤴 +餂 +愗 +冘 +埛 +㒇 +郕 +蔯 +簰 +刔 +蠩 +耏 +鞹 +𧑅 +觹 +䐑 +磶 +蹵 +鵃 +耛 +蓤 +臄 +轙 +庤 +㒩 +翐 +榥 +晀 +輣 +蟚 +拲 +皠 +穱 +䃔 +䃧 +窡 +絍 +礿 +鑞 +栯 +㾓 +掿 +厞 +淂 +撶 +伹 +鹻 +軓 +岹 +蚷 +榸 +刾 +艂 +㤝 +塕 +蚔 +藾 +攓 +鏬 +珫 +黪 +蟧 +猭 +漑 +粺 +驆 +撘 +亾 +㼌 +蝑 +澓 +揞 +欱 +愶 +泲 +醷 +螴 +芚 +絻 +轃 +漮 +唪 +岉 +鬀 +䱹 +齖 +䂓 +趢 +荓 +覶 +鯾 +諿 +槥 +嚆 +爢 +瓬 +笐 +篢 +舝 +襵 +鎒 +𤝞 +肭 +瘇 +笓 +餑 +豋 +湗 +緎 +肐 +胲 +掤 +潫 +䖴 +𠎝 +𨺗 +諢 +毈 +寱 +唲 +䃭 +峮 +狘 +韊 +䬝 +呰 +㹱 +碞 +畞 +㠌 +黭 +蚘 +豵 +穥 +尯 +㳇 +隵 +灇 +壜 +楰 +彲 +甤 +綹 +旞 +𡏟 +曁 +喩 +𥲤 +郈 +塺 +訧 +絿 +掔 +蠮 +𡱰 +䃺 +宻 +灎 +羵 +𨠵 +糚 +摉 +壷 +勴 +瑃 +鎝 +𥜥 +婥 +鬺 +扢 +肣 +溰 +磩 +耇 +宎 +㔇 +霱 +敚 +汳 +鏄 +儹 +隥 +㿉 +膆 +崏 +𦭵 +郔 +扂 +垗 +㳂 +礛 +缻 +垜 +晱 +訩 +蘪 +珇 +怮 +垝 +㔢 +憛 +痝 +蟨 +鞁 +鶤 +肎 +傝 +䢆 +䰄 +𥊚 +㖀 +㠭 +壵 +墋 +㠔 +橜 +怓 +蚹 +塛 +憪 +鋝 +腶 +嶾 +翍 +溓 +齼 +蔂 +䃂 +鉺 +攑 +瓐 +泎 +眤 +邘 +崝 +稡 +愸 +髥 +輹 +詨 +髆 +麃 +虤 +洐 +婐 +挏 +峑 +嶣 +篬 +葄 +瑎 +瓉 +㳅 +葼 +姙 +䪜 +𩇕 +焭 +剚 +濪 +霵 +僒 + +羭 diff --git a/ppocr/utils/dict/ppocrv5_arabic_dict.txt b/ppocr/utils/dict/ppocrv5_arabic_dict.txt new file mode 100644 index 0000000..9c5955b --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_arabic_dict.txt @@ -0,0 +1,747 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +© +‥ +{ +} +\ +| +@ +^ +~ +÷ +∕ +∙ +⋅ +· +± +∓ +∩ +∪ +□ +← +↔ +⇒ +⇐ +⇔ +∀ +∃ +∄ +∴ +∵ +∝ +∞ +⊥ +∟ +∠ +∡ +∢ +′ +″ +∥ +⊾ +⊿ +∂ +∫ +∬ +∭ +∮ +∯ +∰ +∑ +∏ +√ +∛ +∜ +∱ +∲ +∳ +∶ +∷ +∼ +® +℉ +Ω +℧ +Å +⌀ +ℏ +⅀ +⍺ +⍵ +¢ +€ +£ +¥ +₿ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +● +▶ +𝑢 +︽ +– +﹥ +𝜓 +• +∋ +ƒ +० +⬆ +Ạ +◀ + +▫ +︾ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +Ø +Ù +Ú +Û +Ü +Ý +Þ +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +þ +ÿ +¡ +¤ +¦ +§ +¨ +ª +« +¬ +¯ +° +² +³ +´ +µ +¶ +¸ +¹ +º +» +¼ +½ +¾ +¿ +× +‐ +‑ +‒ +— +― +‖ +‗ +‘ +’ +‚ +‛ +“ +” +„ +‟ +† +‡ +‣ +․ +… +‧ +‰ +‴ +‵ +‶ +‷ +‸ +‹ +› +※ +‼ +‽ +‾ +₤ +₡ +₹ +− +∖ +∗ +≈ +≠ +≡ +≤ +≥ +⊂ +⊃ +↑ +→ +↓ +↕ +™ +Ω +℮ +∆ +✓ +✗ +✘ +▪ +◼ +✔ +✕ +☑ +☒ +№ +₽ +₴ +Α +α +Β +β +Γ +γ +Δ +δ +Ε +ε +Ζ +ζ +Η +η +Θ +θ +Ι +ι +Κ +κ +Λ +λ +Μ +μ +Ν +ν +Ξ +ξ +Ο +ο +Π +π +Ρ +ρ +Σ +σ +ς +Τ +τ +Υ +υ +Φ +φ +Χ +χ +Ψ +ψ +ω +ـ +، +؍ +؎ +؏ +ؐ +ؑ +ؒ +ؓ +ؔ +ؕ +ؖ +ؗ +ؘ +ؙ +ؚ +؛ +؞ +؟ +ء +آ +أ +ؤ +إ +ئ +ا +ب +ة +ت +ث +ج +ح +خ +د +ذ +ر +ز +س +ش +ص +ض +ط +ظ +ع +غ +ف +ق +ك +ل +م +ن +ه +و +ى +ي +ً +ٌ +ٍ +َ +ُ +ِ +ّ +ْ +ٓ +ٔ +ٕ +ٖ +ٗ +ٚ +ٛ +ٜ +ٝ +ٞ +ٟ +ٰ +ٱ +ٲ +ٳ +ٴ +ٵ +ٶ +ٷ +ٸ +ٹ +ٺ +ٻ +ټ +ٽ +پ +ٿ +ڀ +ځ +ڂ +ڃ +ڄ +څ +چ +ڇ +ڈ +ډ +ڊ +ڋ +ڌ +ڍ +ڎ +ڏ +ڐ +ڑ +ڒ +ړ +ڔ +ڕ +ږ +ڗ +ژ +ڙ +ښ +ڛ +ڜ +ڝ +ڞ +ڟ +ڠ +ڡ +ڢ +ڣ +ڤ +ڥ +ڦ +ڧ +ڨ +ک +ڪ +ګ +ڬ +ڭ +ڮ +گ +ڰ +ڱ +ڲ +ڳ +ڴ +ڵ +ڶ +ڷ +ڸ +ڹ +ں +ڻ +ڼ +ڽ +ھ +ڿ +ۀ +ہ +ۂ +ۃ +ۄ +ۅ +ۆ +ۇ +ۈ +ۉ +ۊ +ۋ +ی +ۍ +ێ +ۏ +ې +ۑ +ے +ۓ +۔ +ە +ۖ +ۗ +ۘ +ۙ +ۚ +ۛ +ۜ +۞ +ۡ +ۢ +ۣ +ۤ +ۥ +ۦ +ۧ +ۨ +۩ +ۭ +ۮ +ۯ +ݐ +ݑ +ݒ +ݓ +ݔ +ݕ +ݖ +ݗ +ݘ +ݙ +ݚ +ݛ +ݜ +ݝ +ݞ +ݟ +ݠ +ݡ +ݢ +ݣ +ݤ +ݥ +ݦ +ݧ +ݨ +ݩ +ݪ +ݫ +ݬ +ݭ +ݮ +ݯ +ݰ +ݱ +ݲ +ݳ +ݴ +ݵ +ݶ +ݷ +ݸ +ݹ +ݺ +ݻ +ݼ +ݽ +ݾ +ݿ +٠ +١ +٢ +٣ +٤ +٥ +٦ +٧ +٨ +٩ +٪ +٫ +٬ +۰ +۱ +۲ +۳ +۴ +۵ +۶ +۷ +۸ +۹ +ﭙ +ﭚ +ﭪ +ﭭ +ﭺ +ﭽ +ﷲ +﷼ +٠ +١ +٢ +٣ +٤ +٥ +٦ +٧ +٨ +٩ +٪ +٫ +٬ +۰ +۱ +۲ +۳ +۴ +۵ +۶ +۷ +۸ +۹ +ﭙ +ﭚ +ﭪ +ﭭ +ﭺ +ﭽ +ﷲ +﷼ diff --git a/ppocr/utils/dict/ppocrv5_cyrillic_dict.txt b/ppocr/utils/dict/ppocrv5_cyrillic_dict.txt new file mode 100644 index 0000000..7625a74 --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_cyrillic_dict.txt @@ -0,0 +1,850 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +© +‥ +{ +} +\ +| +@ +^ +~ +÷ +∕ +∙ +⋅ +· +± +∓ +∩ +∪ +□ +← +↔ +⇒ +⇐ +⇔ +∀ +∃ +∄ +∴ +∵ +∝ +∞ +⊥ +∟ +∠ +∡ +∢ +′ +″ +∥ +⊾ +⊿ +∂ +∫ +∬ +∭ +∮ +∯ +∰ +∑ +∏ +√ +∛ +∜ +∱ +∲ +∳ +∶ +∷ +∼ +® +℉ +Ω +℧ +Å +⌀ +ℏ +⅀ +⍺ +⍵ +¢ +€ +£ +¥ +₿ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +● +▶ +𝑢 +︽ +– +﹥ +𝜓 +• +∋ +ƒ +० +⬆ +Ạ +◀ + +▫ +︾ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +Ø +Ù +Ú +Û +Ü +Ý +Þ +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +þ +ÿ +¡ +¤ +¦ +§ +¨ +ª +« +¬ +¯ +° +² +³ +´ +µ +¶ +¸ +¹ +º +» +¼ +½ +¾ +¿ +× +‐ +‑ +‒ +— +― +‖ +‗ +‘ +’ +‚ +‛ +“ +” +„ +‟ +† +‡ +‣ +․ +… +‧ +‰ +‴ +‵ +‶ +‷ +‸ +‹ +› +※ +‼ +‽ +‾ +₤ +₡ +₹ +− +∖ +∗ +≈ +≠ +≡ +≤ +≥ +⊂ +⊃ +↑ +→ +↓ +↕ +™ +Ω +℮ +∆ +✓ +✗ +✘ +▪ +◼ +✔ +✕ +☑ +☒ +№ +₽ +₴ +Α +α +Β +β +Γ +γ +Δ +δ +Ε +ε +Ζ +ζ +Η +η +Θ +θ +Ι +ι +Κ +κ +Λ +λ +Μ +μ +Ν +ν +Ξ +ξ +Ο +ο +Π +π +Ρ +ρ +Σ +σ +ς +Τ +τ +Υ +υ +Φ +φ +Χ +χ +Ψ +ψ +ω +Ѐ +Ё +Ђ +Ѓ +Є +Ѕ +І +Ї +Ј +Љ +Њ +Ћ +Ќ +Ѝ +Ў +Џ +А +Б +В +Г +Д +Е +Ж +З +И +Й +К +Л +М +Н +О +П +Р +С +Т +У +Ф +Х +Ц +Ч +Ш +Щ +Ъ +Ы +Ь +Э +Ю +Я +а +б +в +г +д +е +ж +з +и +й +к +л +м +н +о +п +р +с +т +у +ф +х +ц +ч +ш +щ +ъ +ы +ь +э +ю +я +ѐ +ё +ђ +ѓ +є +ѕ +і +ї +ј +љ +њ +ћ +ќ +ѝ +ў +џ +Ѡ +ѡ +Ѣ +ѣ +Ѥ +ѥ +Ѧ +ѧ +Ѩ +ѩ +Ѫ +ѫ +Ѭ +ѭ +Ѯ +ѯ +Ѱ +ѱ +Ѳ +ѳ +Ѵ +ѵ +Ѷ +ѷ +Ѹ +ѹ +Ѻ +ѻ +Ѽ +ѽ +Ѿ +ѿ +Ҁ +ҁ +Ҋ +ҋ +Ҍ +ҍ +Ҏ +ҏ +Ґ +ґ +Ғ +ғ +Ҕ +ҕ +Җ +җ +Ҙ +ҙ +Қ +қ +Ҝ +ҝ +Ҟ +ҟ +Ҡ +ҡ +Ң +ң +Ҧ +ҧ +Ҩ +ҩ +Ҫ +ҫ +Ҭ +ҭ +Ү +ү +Ұ +ұ +Ҳ +ҳ +Ҷ +ҷ +Ҹ +ҹ +Һ +һ +Ҽ +ҽ +Ҿ +ҿ +Ӏ +Ӂ +ӂ +Ӄ +ӄ +Ӆ +ӆ +Ӈ +ӈ +Ӊ +ӊ +Ӌ +ӌ +Ӎ +ӎ +ӏ +Ӑ +ӑ +Ӓ +ӓ +Ӗ +ӗ +Ә +ә +Ӛ +ӛ +Ӝ +ӝ +Ӟ +ӟ +Ӡ +ӡ +Ӣ +ӣ +Ӥ +ӥ +Ӧ +ӧ +Ө +ө +Ӫ +ӫ +Ӭ +ӭ +Ӯ +ӯ +Ӱ +ӱ +Ӳ +ӳ +Ӵ +ӵ +Ӷ +ӷ +Ӹ +ӹ +Ӻ +ӻ +Ӽ +ӽ +Ӿ +ӿ +Ԁ +ԁ +Ԃ +ԃ +Ԅ +ԅ +Ԇ +ԇ +Ԉ +ԉ +Ԋ +ԋ +Ԍ +ԍ +Ԏ +ԏ +Ԑ +ԑ +Ԓ +ԓ +Ԕ +ԕ +Ԗ +ԗ +Ԙ +ԙ +Ԛ +ԛ +Ԝ +ԝ +Ԟ +ԟ +Ԡ +ԡ +Ԣ +ԣ +Ԥ +ԥ +Ԧ +ԧ +Ԩ +ԩ +Ԫ +ԫ +Ԭ +ԭ +Ԯ +ԯ +ⷠ +ⷡ +ⷢ +ⷣ +ⷤ +ⷥ +ⷦ +ⷧ +ⷨ +ⷩ +ⷪ +ⷫ +ⷬ +ⷭ +ⷮ +ⷯ +ⷰ +ⷱ +ⷲ +ⷳ +ⷴ +ⷵ +ⷶ +ⷷ +ⷸ +ⷹ +ⷺ +ⷻ +ⷼ +ⷽ +ⷾ +ⷿ +Ꙁ +ꙁ +Ꙃ +ꙃ +Ꙅ +ꙅ +Ꙇ +ꙇ +Ꙉ +ꙉ +Ꙋ +ꙋ +Ꙍ +ꙍ +Ꙏ +ꙏ +Ꙑ +ꙑ +Ꙓ +ꙓ +Ꙕ +ꙕ +Ꙗ +ꙗ +Ꙙ +ꙙ +Ꙛ +ꙛ +Ꙝ +ꙝ +Ꙟ +ꙟ +Ꙡ +ꙡ +Ꙣ +ꙣ +Ꙥ +ꙥ +Ꙧ +ꙧ +Ꙩ +ꙩ +Ꙫ +ꙫ +Ꙭ +ꙭ +ꙮ +ꙴ +ꙵ +ꙶ +ꙷ +ꙸ +ꙹ +ꙺ +ꙻ +Ꚁ +ꚁ +Ꚃ +ꚃ +Ꚅ +ꚅ +Ꚇ +ꚇ +Ꚉ +ꚉ +Ꚋ +ꚋ +Ꚍ +ꚍ +Ꚏ +ꚏ +Ꚑ +ꚑ +Ꚓ +ꚓ +Ꚕ +ꚕ +Ꚗ +ꚗ +Ꚙ +ꚙ +Ꚛ +ꚛ +ꚜ +ꚝ +ꚞ +ꚟ diff --git a/ppocr/utils/dict/ppocrv5_devanagari_dict.txt b/ppocr/utils/dict/ppocrv5_devanagari_dict.txt new file mode 100644 index 0000000..498ec2a --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_devanagari_dict.txt @@ -0,0 +1,568 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +© +‥ +{ +} +\ +| +@ +^ +~ +÷ +∕ +∙ +⋅ +· +± +∓ +∩ +∪ +□ +← +↔ +⇒ +⇐ +⇔ +∀ +∃ +∄ +∴ +∵ +∝ +∞ +⊥ +∟ +∠ +∡ +∢ +′ +″ +∥ +⊾ +⊿ +∂ +∫ +∬ +∭ +∮ +∯ +∰ +∑ +∏ +√ +∛ +∜ +∱ +∲ +∳ +∶ +∷ +∼ +® +℉ +Ω +℧ +Å +⌀ +ℏ +⅀ +⍺ +⍵ +¢ +€ +£ +¥ +₿ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +● +▶ +𝑢 +︽ +– +﹥ +𝜓 +• +∋ +ƒ +⬆ +Ạ +◀ + +▫ +︾ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +Ø +Ù +Ú +Û +Ü +Ý +Þ +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +þ +ÿ +¡ +¤ +¦ +§ +¨ +ª +« +¬ +¯ +° +² +³ +´ +µ +¶ +¸ +¹ +º +» +¼ +½ +¾ +¿ +× +‐ +‑ +‒ +— +― +‖ +‗ +‘ +’ +‚ +‛ +“ +” +„ +‟ +† +‡ +‣ +․ +… +‧ +‰ +‴ +‵ +‶ +‷ +‸ +‹ +› +※ +‼ +‽ +‾ +₤ +₡ +₹ +− +∖ +∗ +≈ +≠ +≡ +≤ +≥ +⊂ +⊃ +↑ +→ +↓ +↕ +™ +Ω +℮ +∆ +✓ +✗ +✘ +▪ +◼ +✔ +✕ +☑ +☒ +№ +₽ +₴ +Α +α +Β +β +Γ +γ +Δ +δ +Ε +ε +Ζ +ζ +Η +η +Θ +θ +Ι +ι +Κ +κ +Λ +λ +Μ +μ +Ν +ν +Ξ +ξ +Ο +ο +Π +π +Ρ +ρ +Σ +σ +ς +Τ +τ +Υ +υ +Φ +φ +Χ +χ +Ψ +ψ +ω +ऀ +ँ +ं +ः +ऄ +अ +आ +इ +ई +उ +ऊ +ऋ +ऌ +ऍ +ऎ +ए +ऐ +ऑ +ऒ +ओ +औ +क +ख +ग +घ +ङ +च +छ +ज +झ +ञ +ट +ठ +ड +ढ +ण +त +थ +द +ध +न +ऩ +प +फ +ब +भ +म +य +र +ऱ +ल +ळ +ऴ +व +श +ष +स +ह +ऺ +ऻ +़ +ऽ +ा +ि +ी +ु +ू +ृ +ॄ +ॅ +ॆ +े +ै +ॉ +ॊ +ो +ौ +् +ॎ +ॏ +ॐ +॑ +॒ +॓ +॔ +ॕ +ॖ +ॗ +क़ +ख़ +ग़ +ज़ +ड़ +ढ़ +फ़ +य़ +ॠ +ॡ +ॢ +ॣ +। +॥ +० +१ +२ +३ +४ +५ +६ +७ +८ +९ +॰ +ॱ +ॲ +ॳ +ॴ +ॵ +ॶ +ॷ +ॸ +ॹ +ॺ +ॻ +ॼ +ॽ +ॾ +ॿ diff --git a/ppocr/utils/dict/ppocrv5_dict.txt b/ppocr/utils/dict/ppocrv5_dict.txt new file mode 100644 index 0000000..567898b --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_dict.txt @@ -0,0 +1,18383 @@ +  +一 +乙 +二 +十 +丁 +厂 +七 +卜 +八 +人 +入 +儿 +匕 +几 +九 +刁 +了 +刀 +力 +乃 +又 +三 +干 +于 +亏 +工 +土 +士 +才 +下 +寸 +大 +丈 +与 +万 +上 +小 +口 +山 +巾 +千 +乞 +川 +亿 +个 +夕 +久 +么 +勺 +凡 +丸 +及 +广 +亡 +门 +丫 +义 +之 +尸 +己 +已 +巳 +弓 +子 +卫 +也 +女 +刃 +飞 +习 +叉 +马 +乡 +丰 +王 +开 +井 +天 +夫 +元 +无 +云 +专 +丐 +扎 +艺 +木 +五 +支 +厅 +不 +犬 +太 +区 +历 +歹 +友 +尤 +匹 +车 +巨 +牙 +屯 +戈 +比 +互 +切 +瓦 +止 +少 +曰 +日 +中 +贝 +冈 +内 +水 +见 +午 +牛 +手 +气 +毛 +壬 +升 +夭 +长 +仁 +什 +片 +仆 +化 +仇 +币 +仍 +仅 +斤 +爪 +反 +介 +父 +从 +仑 +今 +凶 +分 +乏 +公 +仓 +月 +氏 +勿 +欠 +风 +丹 +匀 +乌 +勾 +凤 +六 +文 +亢 +方 +火 +为 +斗 +忆 +计 +订 +户 +认 +冗 +讥 +心 +尺 +引 +丑 +巴 +孔 +队 +办 +以 +允 +予 +邓 +劝 +双 +书 +幻 +玉 +刊 +未 +末 +示 +击 +打 +巧 +正 +扑 +卉 +扒 +功 +扔 +去 +甘 +世 +艾 +古 +节 +本 +术 +可 +丙 +左 +厉 +石 +右 +布 +夯 +戊 +龙 +平 +灭 +轧 +东 +卡 +北 +占 +凸 +卢 +业 +旧 +帅 +归 +旦 +目 +且 +叶 +甲 +申 +叮 +电 +号 +田 +由 +只 +叭 +史 +央 +兄 +叽 +叼 +叫 +叩 +叨 +另 +叹 +冉 +皿 +凹 +囚 +四 +生 +矢 +失 +乍 +禾 +丘 +付 +仗 +代 +仙 +们 +仪 +白 +仔 +他 +斥 +瓜 +乎 +丛 +令 +用 +甩 +印 +尔 +乐 +句 +匆 +册 +卯 +犯 +外 +处 +冬 +鸟 +务 +包 +饥 +主 +市 +立 +冯 +玄 +闪 +兰 +半 +汁 +汇 +头 +汉 +宁 +穴 +它 +讨 +写 +让 +礼 +训 +议 +必 +讯 +记 +永 +司 +尼 +民 +弗 +弘 +出 +辽 +奶 +奴 +召 +加 +皮 +边 +孕 +发 +圣 +对 +台 +矛 +纠 +母 +幼 +丝 +邦 +式 +迂 +刑 +戎 +动 +扛 +寺 +吉 +扣 +考 +托 +老 +巩 +圾 +执 +扩 +扫 +地 +场 +扬 +耳 +芋 +共 +芒 +亚 +芝 +朽 +朴 +机 +权 +过 +臣 +吏 +再 +协 +西 +压 +厌 +戌 +在 +百 +有 +存 +而 +页 +匠 +夸 +夺 +灰 +达 +列 +死 +成 +夹 +夷 +轨 +邪 +尧 +划 +迈 +毕 +至 +此 +贞 +师 +尘 +尖 +劣 +光 +当 +早 +吁 +吐 +吓 +虫 +曲 +团 +吕 +同 +吊 +吃 +因 +吸 +吗 +吆 +屿 +屹 +岁 +帆 +回 +岂 +则 +刚 +网 +肉 +年 +朱 +先 +丢 +廷 +舌 +竹 +迁 +乔 +迄 +伟 +传 +乒 +乓 +休 +伍 +伏 +优 +臼 +伐 +延 +仲 +件 +任 +伤 +价 +伦 +份 +华 +仰 +仿 +伙 +伪 +自 +伊 +血 +向 +似 +后 +行 +舟 +全 +会 +杀 +合 +兆 +企 +众 +爷 +伞 +创 +肌 +肋 +朵 +杂 +危 +旬 +旨 +旭 +负 +匈 +名 +各 +多 +争 +色 +壮 +冲 +妆 +冰 +庄 +庆 +亦 +刘 +齐 +交 +衣 +次 +产 +决 +亥 +充 +妄 +闭 +问 +闯 +羊 +并 +关 +米 +灯 +州 +汗 +污 +江 +汛 +池 +汝 +汤 +忙 +兴 +宇 +守 +宅 +字 +安 +讲 +讳 +军 +讶 +许 +讹 +论 +讼 +农 +讽 +设 +访 +诀 +寻 +那 +迅 +尽 +导 +异 +弛 +孙 +阵 +阳 +收 +阶 +阴 +防 +奸 +如 +妇 +妃 +好 +她 +妈 +戏 +羽 +观 +欢 +买 +红 +驮 +纤 +驯 +约 +级 +纪 +驰 +纫 +巡 +寿 +弄 +麦 +玖 +玛 +形 +进 +戒 +吞 +远 +违 +韧 +运 +扶 +抚 +坛 +技 +坏 +抠 +扰 +扼 +拒 +找 +批 +址 +扯 +走 +抄 +贡 +汞 +坝 +攻 +赤 +折 +抓 +扳 +抡 +扮 +抢 +孝 +坎 +均 +抑 +抛 +投 +坟 +坑 +抗 +坊 +抖 +护 +壳 +志 +块 +扭 +声 +把 +报 +拟 +却 +抒 +劫 +芙 +芜 +苇 +芽 +花 +芹 +芥 +芬 +苍 +芳 +严 +芦 +芯 +劳 +克 +芭 +苏 +杆 +杠 +杜 +材 +村 +杖 +杏 +杉 +巫 +极 +李 +杨 +求 +甫 +匣 +更 +束 +吾 +豆 +两 +酉 +丽 +医 +辰 +励 +否 +还 +尬 +歼 +来 +连 +轩 +步 +卤 +坚 +肖 +旱 +盯 +呈 +时 +吴 +助 +县 +里 +呆 +吱 +吠 +呕 +园 +旷 +围 +呀 +吨 +足 +邮 +男 +困 +吵 +串 +员 +呐 +听 +吟 +吩 +呛 +吻 +吹 +呜 +吭 +吧 +邑 +吼 +囤 +别 +吮 +岖 +岗 +帐 +财 +针 +钉 +牡 +告 +我 +乱 +利 +秃 +秀 +私 +每 +兵 +估 +体 +何 +佐 +佑 +但 +伸 +佃 +作 +伯 +伶 +佣 +低 +你 +住 +位 +伴 +身 +皂 +伺 +佛 +囱 +近 +彻 +役 +返 +余 +希 +坐 +谷 +妥 +含 +邻 +岔 +肝 +肛 +肚 +肘 +肠 +龟 +甸 +免 +狂 +犹 +狈 +角 +删 +条 +彤 +卵 +灸 +岛 +刨 +迎 +饭 +饮 +系 +言 +冻 +状 +亩 +况 +床 +库 +庇 +疗 +吝 +应 +这 +冷 +庐 +序 +辛 +弃 +冶 +忘 +闰 +闲 +间 +闷 +判 +兑 +灶 +灿 +灼 +弟 +汪 +沐 +沛 +汰 +沥 +沙 +汽 +沃 +沦 +汹 +泛 +沧 +没 +沟 +沪 +沈 +沉 +沁 +怀 +忧 +忱 +快 +完 +宋 +宏 +牢 +究 +穷 +灾 +良 +证 +启 +评 +补 +初 +社 +祀 +识 +诈 +诉 +罕 +诊 +词 +译 +君 +灵 +即 +层 +屁 +尿 +尾 +迟 +局 +改 +张 +忌 +际 +陆 +阿 +陈 +阻 +附 +坠 +妓 +妙 +妖 +姊 +妨 +妒 +努 +忍 +劲 +矣 +鸡 +纬 +驱 +纯 +纱 +纲 +纳 +驳 +纵 +纷 +纸 +纹 +纺 +驴 +纽 +奉 +玩 +环 +武 +青 +责 +现 +玫 +表 +规 +抹 +卦 +坷 +坯 +拓 +拢 +拔 +坪 +拣 +坦 +担 +坤 +押 +抽 +拐 +拖 +者 +拍 +顶 +拆 +拎 +拥 +抵 +拘 +势 +抱 +拄 +垃 +拉 +拦 +幸 +拌 +拧 +拂 +拙 +招 +坡 +披 +拨 +择 +抬 +拇 +拗 +其 +取 +茉 +苦 +昔 +苛 +若 +茂 +苹 +苗 +英 +苟 +苑 +苞 +范 +直 +茁 +茄 +茎 +苔 +茅 +枉 +林 +枝 +杯 +枢 +柜 +枚 +析 +板 +松 +枪 +枫 +构 +杭 +杰 +述 +枕 +丧 +或 +画 +卧 +事 +刺 +枣 +雨 +卖 +郁 +矾 +矿 +码 +厕 +奈 +奔 +奇 +奋 +态 +欧 +殴 +垄 +妻 +轰 +顷 +转 +斩 +轮 +软 +到 +非 +叔 +歧 +肯 +齿 +些 +卓 +虎 +虏 +肾 +贤 +尚 +旺 +具 +味 +果 +昆 +国 +哎 +咕 +昌 +呵 +畅 +明 +易 +咙 +昂 +迪 +典 +固 +忠 +呻 +咒 +咋 +咐 +呼 +鸣 +咏 +呢 +咄 +咖 +岸 +岩 +帖 +罗 +帜 +帕 +岭 +凯 +败 +账 +贩 +贬 +购 +贮 +图 +钓 +制 +知 +迭 +氛 +垂 +牧 +物 +乖 +刮 +秆 +和 +季 +委 +秉 +佳 +侍 +岳 +供 +使 +例 +侠 +侥 +版 +侄 +侦 +侣 +侧 +凭 +侨 +佩 +货 +侈 +依 +卑 +的 +迫 +质 +欣 +征 +往 +爬 +彼 +径 +所 +舍 +金 +刹 +命 +肴 +斧 +爸 +采 +觅 +受 +乳 +贪 +念 +贫 +忿 +肤 +肺 +肢 +肿 +胀 +朋 +股 +肮 +肪 +肥 +服 +胁 +周 +昏 +鱼 +兔 +狐 +忽 +狗 +狞 +备 +饰 +饱 +饲 +变 +京 +享 +庞 +店 +夜 +庙 +府 +底 +疟 +疙 +疚 +剂 +卒 +郊 +庚 +废 +净 +盲 +放 +刻 +育 +氓 +闸 +闹 +郑 +券 +卷 +单 +炬 +炒 +炊 +炕 +炎 +炉 +沫 +浅 +法 +泄 +沽 +河 +沾 +泪 +沮 +油 +泊 +沿 +泡 +注 +泣 +泞 +泻 +泌 +泳 +泥 +沸 +沼 +波 +泼 +泽 +治 +怔 +怯 +怖 +性 +怕 +怜 +怪 +怡 +学 +宝 +宗 +定 +宠 +宜 +审 +宙 +官 +空 +帘 +宛 +实 +试 +郎 +诗 +肩 +房 +诚 +衬 +衫 +视 +祈 +话 +诞 +诡 +询 +该 +详 +建 +肃 +录 +隶 +帚 +屉 +居 +届 +刷 +屈 +弧 +弥 +弦 +承 +孟 +陋 +陌 +孤 +陕 +降 +函 +限 +妹 +姑 +姐 +姓 +妮 +始 +姆 +迢 +驾 +叁 +参 +艰 +线 +练 +组 +绅 +细 +驶 +织 +驹 +终 +驻 +绊 +驼 +绍 +绎 +经 +贯 +契 +贰 +奏 +春 +帮 +玷 +珍 +玲 +玻 +毒 +型 +拭 +挂 +封 +持 +拷 +拱 +项 +垮 +挎 +城 +挟 +挠 +政 +赴 +赵 +挡 +拽 +哉 +挺 +括 +垢 +拴 +拾 +挑 +垛 +指 +垫 +挣 +挤 +拼 +挖 +按 +挥 +挪 +拯 +某 +甚 +荆 +茸 +革 +茬 +荐 +巷 +带 +草 +茧 +茵 +茶 +荒 +茫 +荡 +荣 +荤 +荧 +故 +胡 +荫 +荔 +南 +药 +标 +栈 +柑 +枯 +柄 +栋 +相 +查 +柏 +栅 +柳 +柱 +柿 +栏 +柠 +树 +勃 +要 +柬 +咸 +威 +歪 +研 +砖 +厘 +厚 +砌 +砂 +泵 +砚 +砍 +面 +耐 +耍 +牵 +鸥 +残 +殃 +轴 +轻 +鸦 +皆 +韭 +背 +战 +点 +虐 +临 +览 +竖 +省 +削 +尝 +昧 +盹 +是 +盼 +眨 +哇 +哄 +哑 +显 +冒 +映 +星 +昨 +咧 +昭 +畏 +趴 +胃 +贵 +界 +虹 +虾 +蚁 +思 +蚂 +虽 +品 +咽 +骂 +勋 +哗 +咱 +响 +哈 +哆 +咬 +咳 +咪 +哪 +哟 +炭 +峡 +罚 +贱 +贴 +贻 +骨 +幽 +钙 +钝 +钞 +钟 +钢 +钠 +钥 +钦 +钧 +钩 +钮 +卸 +缸 +拜 +看 +矩 +毡 +氢 +怎 +牲 +选 +适 +秒 +香 +种 +秋 +科 +重 +复 +竿 +段 +便 +俩 +贷 +顺 +修 +俏 +保 +促 +俄 +俐 +侮 +俭 +俗 +俘 +信 +皇 +泉 +鬼 +侵 +禹 +侯 +追 +俊 +盾 +待 +徊 +衍 +律 +很 +须 +叙 +剑 +逃 +食 +盆 +胚 +胧 +胆 +胜 +胞 +胖 +脉 +胎 +勉 +狭 +狮 +独 +狰 +狡 +狱 +狠 +贸 +怨 +急 +饵 +饶 +蚀 +饺 +饼 +峦 +弯 +将 +奖 +哀 +亭 +亮 +度 +迹 +庭 +疮 +疯 +疫 +疤 +咨 +姿 +亲 +音 +帝 +施 +闺 +闻 +闽 +阀 +阁 +差 +养 +美 +姜 +叛 +送 +类 +迷 +籽 +娄 +前 +首 +逆 +兹 +总 +炼 +炸 +烁 +炮 +炫 +烂 +剃 +洼 +洁 +洪 +洒 +柒 +浇 +浊 +洞 +测 +洗 +活 +派 +洽 +染 +洛 +浏 +济 +洋 +洲 +浑 +浓 +津 +恃 +恒 +恢 +恍 +恬 +恤 +恰 +恼 +恨 +举 +觉 +宣 +宦 +室 +宫 +宪 +突 +穿 +窃 +客 +诫 +冠 +诬 +语 +扁 +袄 +祖 +神 +祝 +祠 +误 +诱 +诲 +说 +诵 +垦 +退 +既 +屋 +昼 +屏 +屎 +费 +陡 +逊 +眉 +孩 +陨 +除 +险 +院 +娃 +姥 +姨 +姻 +娇 +姚 +娜 +怒 +架 +贺 +盈 +勇 +怠 +癸 +蚤 +柔 +垒 +绑 +绒 +结 +绕 +骄 +绘 +给 +绚 +骆 +络 +绝 +绞 +骇 +统 +耕 +耘 +耗 +耙 +艳 +泰 +秦 +珠 +班 +素 +匿 +蚕 +顽 +盏 +匪 +捞 +栽 +捕 +埂 +捂 +振 +载 +赶 +起 +盐 +捎 +捍 +捏 +埋 +捉 +捆 +捐 +损 +袁 +捌 +都 +哲 +逝 +捡 +挫 +换 +挽 +挚 +热 +恐 +捣 +壶 +捅 +埃 +挨 +耻 +耿 +耽 +聂 +恭 +莽 +莱 +莲 +莫 +莉 +荷 +获 +晋 +恶 +莹 +莺 +真 +框 +梆 +桂 +桔 +栖 +档 +桐 +株 +桥 +桦 +栓 +桃 +格 +桩 +校 +核 +样 +根 +索 +哥 +速 +逗 +栗 +贾 +酌 +配 +翅 +辱 +唇 +夏 +砸 +砰 +砾 +础 +破 +原 +套 +逐 +烈 +殊 +殉 +顾 +轿 +较 +顿 +毙 +致 +柴 +桌 +虑 +监 +紧 +党 +逞 +晒 +眠 +晓 +哮 +唠 +鸭 +晃 +哺 +晌 +剔 +晕 +蚌 +畔 +蚣 +蚊 +蚪 +蚓 +哨 +哩 +圃 +哭 +哦 +恩 +鸯 +唤 +唁 +哼 +唧 +啊 +唉 +唆 +罢 +峭 +峨 +峰 +圆 +峻 +贼 +贿 +赂 +赃 +钱 +钳 +钻 +钾 +铁 +铃 +铅 +缺 +氧 +氨 +特 +牺 +造 +乘 +敌 +秤 +租 +积 +秧 +秩 +称 +秘 +透 +笔 +笑 +笋 +债 +借 +值 +倚 +俺 +倾 +倒 +倘 +俱 +倡 +候 +赁 +俯 +倍 +倦 +健 +臭 +射 +躬 +息 +倔 +徒 +徐 +殷 +舰 +舱 +般 +航 +途 +拿 +耸 +爹 +舀 +爱 +豺 +豹 +颁 +颂 +翁 +胰 +脆 +脂 +胸 +胳 +脏 +脐 +胶 +脑 +脓 +逛 +狸 +狼 +卿 +逢 +鸵 +留 +鸳 +皱 +饿 +馁 +凌 +凄 +恋 +桨 +浆 +衰 +衷 +高 +郭 +席 +准 +座 +症 +病 +疾 +斋 +疹 +疼 +疲 +脊 +效 +离 +紊 +唐 +瓷 +资 +凉 +站 +剖 +竞 +部 +旁 +旅 +畜 +阅 +羞 +羔 +瓶 +拳 +粉 +料 +益 +兼 +烤 +烘 +烦 +烧 +烛 +烟 +烙 +递 +涛 +浙 +涝 +浦 +酒 +涉 +消 +涡 +浩 +海 +涂 +浴 +浮 +涣 +涤 +流 +润 +涧 +涕 +浪 +浸 +涨 +烫 +涩 +涌 +悖 +悟 +悄 +悍 +悔 +悯 +悦 +害 +宽 +家 +宵 +宴 +宾 +窍 +窄 +容 +宰 +案 +请 +朗 +诸 +诺 +读 +扇 +诽 +袜 +袖 +袍 +被 +祥 +课 +冥 +谁 +调 +冤 +谅 +谆 +谈 +谊 +剥 +恳 +展 +剧 +屑 +弱 +陵 +祟 +陶 +陷 +陪 +娱 +娟 +恕 +娥 +娘 +通 +能 +难 +预 +桑 +绢 +绣 +验 +继 +骏 +球 +琐 +理 +琉 +琅 +捧 +堵 +措 +描 +域 +捺 +掩 +捷 +排 +焉 +掉 +捶 +赦 +堆 +推 +埠 +掀 +授 +捻 +教 +掏 +掐 +掠 +掂 +培 +接 +掷 +控 +探 +据 +掘 +掺 +职 +基 +聆 +勘 +聊 +娶 +著 +菱 +勒 +黄 +菲 +萌 +萝 +菌 +萎 +菜 +萄 +菊 +菩 +萍 +菠 +萤 +营 +乾 +萧 +萨 +菇 +械 +彬 +梦 +婪 +梗 +梧 +梢 +梅 +检 +梳 +梯 +桶 +梭 +救 +曹 +副 +票 +酝 +酗 +厢 +戚 +硅 +硕 +奢 +盔 +爽 +聋 +袭 +盛 +匾 +雪 +辅 +辆 +颅 +虚 +彪 +雀 +堂 +常 +眶 +匙 +晨 +睁 +眯 +眼 +悬 +野 +啪 +啦 +曼 +晦 +晚 +啄 +啡 +距 +趾 +啃 +跃 +略 +蚯 +蛀 +蛇 +唬 +累 +鄂 +唱 +患 +啰 +唾 +唯 +啤 +啥 +啸 +崖 +崎 +崭 +逻 +崔 +帷 +崩 +崇 +崛 +婴 +圈 +铐 +铛 +铝 +铜 +铭 +铲 +银 +矫 +甜 +秸 +梨 +犁 +秽 +移 +笨 +笼 +笛 +笙 +符 +第 +敏 +做 +袋 +悠 +偿 +偶 +偎 +偷 +您 +售 +停 +偏 +躯 +兜 +假 +衅 +徘 +徙 +得 +衔 +盘 +舶 +船 +舵 +斜 +盒 +鸽 +敛 +悉 +欲 +彩 +领 +脚 +脖 +脯 +豚 +脸 +脱 +象 +够 +逸 +猜 +猪 +猎 +猫 +凰 +猖 +猛 +祭 +馅 +馆 +凑 +减 +毫 +烹 +庶 +麻 +庵 +痊 +痒 +痕 +廊 +康 +庸 +鹿 +盗 +章 +竟 +商 +族 +旋 +望 +率 +阎 +阐 +着 +羚 +盖 +眷 +粘 +粗 +粒 +断 +剪 +兽 +焊 +焕 +清 +添 +鸿 +淋 +涯 +淹 +渠 +渐 +淑 +淌 +混 +淮 +淆 +渊 +淫 +渔 +淘 +淳 +液 +淤 +淡 +淀 +深 +涮 +涵 +婆 +梁 +渗 +情 +惜 +惭 +悼 +惧 +惕 +惟 +惊 +惦 +悴 +惋 +惨 +惯 +寇 +寅 +寄 +寂 +宿 +窒 +窑 +密 +谋 +谍 +谎 +谐 +袱 +祷 +祸 +谓 +谚 +谜 +逮 +敢 +尉 +屠 +弹 +隋 +堕 +随 +蛋 +隅 +隆 +隐 +婚 +婶 +婉 +颇 +颈 +绩 +绪 +续 +骑 +绰 +绳 +维 +绵 +绷 +绸 +综 +绽 +绿 +缀 +巢 +琴 +琳 +琢 +琼 +斑 +替 +揍 +款 +堪 +塔 +搭 +堰 +揩 +越 +趁 +趋 +超 +揽 +堤 +提 +博 +揭 +喜 +彭 +揣 +插 +揪 +搜 +煮 +援 +搀 +裁 +搁 +搓 +搂 +搅 +壹 +握 +搔 +揉 +斯 +期 +欺 +联 +葫 +散 +惹 +葬 +募 +葛 +董 +葡 +敬 +葱 +蒋 +蒂 +落 +韩 +朝 +辜 +葵 +棒 +棱 +棋 +椰 +植 +森 +焚 +椅 +椒 +棵 +棍 +椎 +棉 +棚 +棕 +棺 +榔 +椭 +惠 +惑 +逼 +粟 +棘 +酣 +酥 +厨 +厦 +硬 +硝 +确 +硫 +雁 +殖 +裂 +雄 +颊 +雳 +暂 +雅 +翘 +辈 +悲 +紫 +凿 +辉 +敞 +棠 +赏 +掌 +晴 +睐 +暑 +最 +晰 +量 +鼎 +喷 +喳 +晶 +喇 +遇 +喊 +遏 +晾 +景 +畴 +践 +跋 +跌 +跑 +跛 +遗 +蛙 +蛛 +蜓 +蜒 +蛤 +喝 +鹃 +喂 +喘 +喉 +喻 +啼 +喧 +嵌 +幅 +帽 +赋 +赌 +赎 +赐 +赔 +黑 +铸 +铺 +链 +销 +锁 +锄 +锅 +锈 +锋 +锌 +锐 +甥 +掰 +短 +智 +氮 +毯 +氯 +鹅 +剩 +稍 +程 +稀 +税 +筐 +等 +筑 +策 +筛 +筒 +筏 +答 +筋 +筝 +傲 +傅 +牌 +堡 +集 +焦 +傍 +储 +皓 +皖 +粤 +奥 +街 +惩 +御 +循 +艇 +舒 +逾 +番 +释 +禽 +腊 +脾 +腋 +腔 +腕 +鲁 +猩 +猬 +猾 +猴 +惫 +然 +馈 +馋 +装 +蛮 +就 +敦 +斌 +痘 +痢 +痪 +痛 +童 +竣 +阔 +善 +翔 +羡 +普 +粪 +尊 +奠 +道 +遂 +曾 +焰 +港 +滞 +湖 +湘 +渣 +渤 +渺 +湿 +温 +渴 +溃 +溅 +滑 +湃 +渝 +湾 +渡 +游 +滋 +渲 +溉 +愤 +慌 +惰 +愕 +愣 +惶 +愧 +愉 +慨 +割 +寒 +富 +寓 +窜 +窝 +窖 +窗 +窘 +遍 +雇 +裕 +裤 +裙 +禅 +禄 +谢 +谣 +谤 +谦 +犀 +属 +屡 +强 +粥 +疏 +隔 +隙 +隘 +媒 +絮 +嫂 +媚 +婿 +登 +缅 +缆 +缉 +缎 +缓 +缔 +缕 +骗 +编 +骚 +缘 +瑟 +鹉 +瑞 +瑰 +瑙 +魂 +肆 +摄 +摸 +填 +搏 +塌 +鼓 +摆 +携 +搬 +摇 +搞 +塘 +摊 +聘 +斟 +蒜 +勤 +靴 +靶 +鹊 +蓝 +墓 +幕 +蓬 +蓄 +蒲 +蓉 +蒙 +蒸 +献 +椿 +禁 +楚 +楷 +榄 +想 +槐 +榆 +楼 +概 +赖 +酪 +酬 +感 +碍 +碘 +碑 +碎 +碰 +碗 +碌 +尴 +雷 +零 +雾 +雹 +辐 +辑 +输 +督 +频 +龄 +鉴 +睛 +睹 +睦 +瞄 +睫 +睡 +睬 +嗜 +鄙 +嗦 +愚 +暖 +盟 +歇 +暗 +暇 +照 +畸 +跨 +跷 +跳 +跺 +跪 +路 +跤 +跟 +遣 +蜈 +蜗 +蛾 +蜂 +蜕 +嗅 +嗡 +嗓 +署 +置 +罪 +罩 +蜀 +幌 +错 +锚 +锡 +锣 +锤 +锥 +锦 +键 +锯 +锰 +矮 +辞 +稚 +稠 +颓 +愁 +筹 +签 +简 +筷 +毁 +舅 +鼠 +催 +傻 +像 +躲 +魁 +衙 +微 +愈 +遥 +腻 +腰 +腥 +腮 +腹 +腺 +鹏 +腾 +腿 +鲍 +猿 +颖 +触 +解 +煞 +雏 +馍 +馏 +酱 +禀 +痹 +廓 +痴 +痰 +廉 +靖 +新 +韵 +意 +誊 +粮 +数 +煎 +塑 +慈 +煤 +煌 +满 +漠 +滇 +源 +滤 +滥 +滔 +溪 +溜 +漓 +滚 +溢 +溯 +滨 +溶 +溺 +粱 +滩 +慎 +誉 +塞 +寞 +窥 +窟 +寝 +谨 +褂 +裸 +福 +谬 +群 +殿 +辟 +障 +媳 +嫉 +嫌 +嫁 +叠 +缚 +缝 +缠 +缤 +剿 +静 +碧 +璃 +赘 +熬 +墙 +墟 +嘉 +摧 +赫 +截 +誓 +境 +摘 +摔 +撇 +聚 +慕 +暮 +摹 +蔓 +蔑 +蔡 +蔗 +蔽 +蔼 +熙 +蔚 +兢 +模 +槛 +榴 +榜 +榨 +榕 +歌 +遭 +酵 +酷 +酿 +酸 +碟 +碱 +碳 +磁 +愿 +需 +辖 +辗 +雌 +裳 +颗 +瞅 +墅 +嗽 +踊 +蜻 +蜡 +蝇 +蜘 +蝉 +嘛 +嘀 +赚 +锹 +锻 +镀 +舞 +舔 +稳 +熏 +箕 +算 +箩 +管 +箫 +舆 +僚 +僧 +鼻 +魄 +魅 +貌 +膜 +膊 +膀 +鲜 +疑 +孵 +馒 +裹 +敲 +豪 +膏 +遮 +腐 +瘩 +瘟 +瘦 +辣 +彰 +竭 +端 +旗 +精 +粹 +歉 +弊 +熄 +熔 +煽 +潇 +漆 +漱 +漂 +漫 +滴 +漾 +演 +漏 +慢 +慷 +寨 +赛 +寡 +察 +蜜 +寥 +谭 +肇 +褐 +褪 +谱 +隧 +嫩 +翠 +熊 +凳 +骡 +缩 +慧 +撵 +撕 +撒 +撩 +趣 +趟 +撑 +撮 +撬 +播 +擒 +墩 +撞 +撤 +增 +撰 +聪 +鞋 +鞍 +蕉 +蕊 +蔬 +蕴 +横 +槽 +樱 +橡 +樟 +橄 +敷 +豌 +飘 +醋 +醇 +醉 +磕 +磊 +磅 +碾 +震 +霄 +霉 +瞒 +题 +暴 +瞎 +嘻 +嘶 +嘲 +嘹 +影 +踢 +踏 +踩 +踪 +蝶 +蝴 +蝠 +蝎 +蝌 +蝗 +蝙 +嘿 +嘱 +幢 +墨 +镇 +镐 +镑 +靠 +稽 +稻 +黎 +稿 +稼 +箱 +篓 +箭 +篇 +僵 +躺 +僻 +德 +艘 +膝 +膛 +鲤 +鲫 +熟 +摩 +褒 +瘪 +瘤 +瘫 +凛 +颜 +毅 +糊 +遵 +憋 +潜 +澎 +潮 +潭 +鲨 +澳 +潘 +澈 +澜 +澄 +懂 +憔 +懊 +憎 +额 +翩 +褥 +谴 +鹤 +憨 +慰 +劈 +履 +豫 +缭 +撼 +擂 +操 +擅 +燕 +蕾 +薯 +薛 +薇 +擎 +薪 +薄 +颠 +翰 +噩 +橱 +橙 +橘 +整 +融 +瓢 +醒 +霍 +霎 +辙 +冀 +餐 +嘴 +踱 +蹄 +蹂 +蟆 +螃 +器 +噪 +鹦 +赠 +默 +黔 +镜 +赞 +穆 +篮 +篡 +篷 +篱 +儒 +邀 +衡 +膨 +雕 +鲸 +磨 +瘾 +瘸 +凝 +辨 +辩 +糙 +糖 +糕 +燃 +濒 +澡 +激 +懒 +憾 +懈 +窿 +壁 +避 +缰 +缴 +戴 +擦 +藉 +鞠 +藏 +藐 +檬 +檐 +檀 +礁 +磷 +霜 +霞 +瞭 +瞧 +瞬 +瞳 +瞩 +瞪 +曙 +蹋 +蹈 +螺 +蟋 +蟀 +嚎 +赡 +穗 +魏 +簧 +簇 +繁 +徽 +爵 +朦 +臊 +鳄 +癌 +辫 +赢 +糟 +糠 +燥 +懦 +豁 +臀 +臂 +翼 +骤 +藕 +鞭 +藤 +覆 +瞻 +蹦 +嚣 +镰 +翻 +鳍 +鹰 +瀑 +襟 +璧 +戳 +孽 +警 +蘑 +藻 +攀 +曝 +蹲 +蹭 +蹬 +巅 +簸 +簿 +蟹 +颤 +靡 +癣 +瓣 +羹 +鳖 +爆 +疆 +鬓 +壤 +馨 +耀 +躁 +蠕 +嚼 +嚷 +巍 +籍 +鳞 +魔 +糯 +灌 +譬 +蠢 +霸 +露 +霹 +躏 +黯 +髓 +赣 +囊 +镶 +瓤 +罐 +矗 +乂 +乜 +兀 +弋 +孑 +孓 +幺 +亓 +韦 +廿 +丏 +卅 +仄 +厄 +仃 +仉 +仂 +兮 +刈 +爻 +卞 +闩 +讣 +尹 +夬 +爿 +毋 +邗 +邛 +艽 +艿 +札 +叵 +匝 +丕 +匜 +劢 +卟 +叱 +叻 +仨 +仕 +仟 +仡 +仫 +仞 +卮 +氐 +犰 +刍 +邝 +邙 +汀 +讦 +讧 +讪 +讫 +尻 +阡 +尕 +弁 +驭 +匡 +耒 +玎 +玑 +邢 +圩 +圬 +圭 +扦 +圪 +圳 +圹 +扪 +圮 +圯 +芊 +芍 +芄 +芨 +芑 +芎 +芗 +亘 +厍 +夼 +戍 +尥 +乩 +旯 +曳 +岌 +屺 +凼 +囡 +钇 +缶 +氘 +氖 +牝 +伎 +伛 +伢 +佤 +仵 +伥 +伧 +伉 +伫 +囟 +汆 +刖 +夙 +旮 +刎 +犷 +犸 +舛 +凫 +邬 +饧 +汕 +汔 +汐 +汲 +汜 +汊 +忖 +忏 +讴 +讵 +祁 +讷 +聿 +艮 +厾 +阱 +阮 +阪 +丞 +妁 +牟 +纡 +纣 +纥 +纨 +玕 +玙 +抟 +抔 +圻 +坂 +坍 +坞 +抃 +抉 +㧐 +芫 +邯 +芸 +芾 +苈 +苣 +芷 +芮 +苋 +芼 +苌 +苁 +芩 +芪 +芡 +芟 +苄 +苎 +苡 +杌 +杓 +杞 +杈 +忑 +孛 +邴 +邳 +矶 +奁 +豕 +忒 +欤 +轫 +迓 +邶 +忐 +卣 +邺 +旰 +呋 +呒 +呓 +呔 +呖 +呃 +旸 +吡 +町 +虬 +呗 +吽 +吣 +吲 +帏 +岐 +岈 +岘 +岑 +岚 +兕 +囵 +囫 +钊 +钋 +钌 +迕 +氙 +氚 +牤 +佞 +邱 +攸 +佚 +佝 +佟 +佗 +伽 +彷 +佘 +佥 +孚 +豸 +坌 +肟 +邸 +奂 +劬 +狄 +狁 +鸠 +邹 +饨 +饩 +饪 +饫 +饬 +亨 +庑 +庋 +疔 +疖 +肓 +闱 +闳 +闵 +羌 +炀 +沣 +沅 +沔 +沤 +沌 +沏 +沚 +汩 +汨 +沂 +汾 +沨 +汴 +汶 +沆 +沩 +泐 +怃 +怄 +忡 +忤 +忾 +怅 +忻 +忪 +怆 +忭 +忸 +诂 +诃 +诅 +诋 +诌 +诏 +诒 +孜 +陇 +陀 +陂 +陉 +妍 +妩 +妪 +妣 +妊 +妗 +妫 +妞 +姒 +妤 +邵 +劭 +刭 +甬 +邰 +纭 +纰 +纴 +纶 +纾 +玮 +玡 +玭 +玠 +玢 +玥 +玦 +盂 +忝 +匦 +坩 +抨 +拤 +坫 +拈 +垆 +抻 +劼 +拃 +拊 +坼 +坻 +㧟 +坨 +坭 +抿 +坳 +耶 +苷 +苯 +苤 +茏 +苫 +苜 +苴 +苒 +苘 +茌 +苻 +苓 +茚 +茆 +茑 +茓 +茔 +茕 +茀 +苕 +枥 +枇 +杪 +杳 +枧 +杵 +枨 +枞 +枋 +杻 +杷 +杼 +矸 +砀 +刳 +奄 +瓯 +殁 +郏 +轭 +郅 +鸢 +盱 +昊 +昙 +杲 +昃 +咂 +呸 +昕 +昀 +旻 +昉 +炅 +咔 +畀 +虮 +咀 +呷 +黾 +呱 +呤 +咚 +咆 +咛 +呶 +呣 +呦 +咝 +岢 +岿 +岬 +岫 +帙 +岣 +峁 +刿 +迥 +岷 +剀 +帔 +峄 +沓 +囹 +罔 +钍 +钎 +钏 +钒 +钕 +钗 +邾 +迮 +牦 +竺 +迤 +佶 +佬 +佰 +侑 +侉 +臾 +岱 +侗 +侃 +侏 +侩 +佻 +佾 +侪 +佼 +佯 +侬 +帛 +阜 +侔 +徂 +刽 +郄 +怂 +籴 +瓮 +戗 +肼 +䏝 +肽 +肱 +肫 +剁 +迩 +郇 +狙 +狎 +狍 +狒 +咎 +炙 +枭 +饯 +饴 +冽 +冼 +庖 +疠 +疝 +疡 +兖 +妾 +劾 +炜 +𬉼 +炖 +炘 +炝 +炔 +泔 +沭 +泷 +泸 +泱 +泅 +泗 +泠 +泺 +泖 +泫 +泮 +沱 +泯 +泓 +泾 +怙 +怵 +怦 +怛 +怏 +怍 +㤘 +怩 +怫 +怿 +宕 +穹 +宓 +诓 +诔 +诖 +诘 +戾 +诙 +戽 +郓 +衩 +祆 +祎 +祉 +祇 +诛 +诜 +诟 +诠 +诣 +诤 +诧 +诨 +诩 +戕 +孢 +亟 +陔 +妲 +妯 +姗 +帑 +弩 +孥 +驽 +虱 +迦 +迨 +绀 +绁 +绂 +驷 +驸 +绉 +绌 +驿 +骀 +甾 +珏 +珐 +珂 +珑 +玳 +珀 +顸 +珉 +珈 +拮 +垭 +挝 +垣 +挞 +垤 +赳 +贲 +垱 +垌 +郝 +垧 +垓 +挦 +垠 +茜 +荚 +荑 +贳 +荜 +莒 +茼 +茴 +茱 +莛 +荞 +茯 +荏 +荇 +荃 +荟 +荀 +茗 +荠 +茭 +茨 +垩 +荥 +荦 +荨 +荩 +剋 +荪 +茹 +荬 +荮 +柰 +栉 +柯 +柘 +栊 +柩 +枰 +栌 +柙 +枵 +柚 +枳 +柞 +柝 +栀 +柢 +栎 +枸 +柈 +柁 +枷 +柽 +剌 +酊 +郦 +甭 +砗 +砘 +砒 +斫 +砭 +砜 +奎 +耷 +虺 +殂 +殇 +殄 +殆 +轱 +轲 +轳 +轶 +轸 +虿 +毖 +觇 +尜 +哐 +眄 +眍 +𠳐 +郢 +眇 +眊 +眈 +禺 +哂 +咴 +曷 +昴 +昱 +昵 +咦 +哓 +哔 +畎 +毗 +呲 +胄 +畋 +畈 +虼 +虻 +盅 +咣 +哕 +剐 +郧 +咻 +囿 +咿 +哌 +哙 +哚 +咯 +咩 +咤 +哝 +哏 +哞 +峙 +峣 +罘 +帧 +峒 +峤 +峋 +峥 +贶 +钚 +钛 +钡 +钣 +钤 +钨 +钫 +钯 +氡 +氟 +牯 +郜 +秕 +秭 +竽 +笈 +笃 +俦 +俨 +俅 +俪 +叟 +垡 +牮 +俣 +俚 +皈 +俑 +俟 +逅 +徇 +徉 +舢 +俞 +郗 +俎 +郤 +爰 +郛 +瓴 +胨 +胪 +胛 +胂 +胙 +胍 +胗 +胝 +朐 +胫 +鸨 +匍 +狨 +狯 +飑 +狩 +狲 +訇 +逄 +昝 +饷 +饸 +饹 +胤 +孪 +娈 +弈 +奕 +庥 +疬 +疣 +疥 +疭 +庠 +竑 +彦 +飒 +闼 +闾 +闿 +阂 +羑 +迸 +籼 +酋 +炳 +炻 +炽 +炯 +烀 +炷 +烃 +洱 +洹 +洧 +洌 +浃 +洇 +洄 +洙 +涎 +洎 +洫 +浍 +洮 +洵 +浒 +浔 +浕 +洳 +恸 +恓 +恹 +恫 +恺 +恻 +恂 +恪 +恽 +宥 +扃 +衲 +衽 +衿 +袂 +祛 +祜 +祓 +祚 +诮 +祗 +祢 +诰 +诳 +鸩 +昶 +郡 +咫 +弭 +牁 +胥 +陛 +陟 +娅 +姮 +娆 +姝 +姣 +姘 +姹 +怼 +羿 +炱 +矜 +绔 +骁 +骅 +绗 +绛 +骈 +耖 +挈 +珥 +珙 +顼 +珰 +珩 +珧 +珣 +珞 +琤 +珲 +敖 +恚 +埔 +埕 +埘 +埙 +埚 +挹 +耆 +耄 +埒 +捋 +贽 +垸 +捃 +盍 +荸 +莆 +莳 +莴 +莪 +莠 +莓 +莜 +莅 +荼 +莩 +荽 +莸 +荻 +莘 +莎 +莞 +莨 +渇 +鸪 +莼 +栲 +栳 +郴 +桓 +桡 +桎 +桢 +桤 +梃 +栝 +桕 +桁 +桧 +桅 +栟 +桉 +栩 +逑 +逋 +彧 +鬲 +豇 +酐 +逦 +厝 +孬 +砝 +砹 +砺 +砧 +砷 +砟 +砼 +砥 +砣 +剞 +砻 +轼 +轾 +辂 +鸫 +趸 +龀 +鸬 +虔 +逍 +眬 +唛 +晟 +眩 +眙 +哧 +哽 +唔 +晁 +晏 +鸮 +趵 +趿 +畛 +蚨 +蚜 +蚍 +蚋 +蚬 +蚝 +蚧 +唢 +圄 +唣 +唏 +盎 +唑 +崂 +崃 +罡 +罟 +峪 +觊 +赅 +钰 +钲 +钴 +钵 +钹 +钺 +钽 +钼 +钿 +铀 +铂 +铄 +铆 +铈 +铉 +铊 +铋 +铌 +铍 +䥽 +铎 +氩 +氤 +氦 +毪 +舐 +秣 +秫 +盉 +笄 +笕 +笊 +笏 +笆 +俸 +倩 +俵 +偌 +俳 +俶 +倬 +倏 +恁 +倭 +倪 +俾 +倜 +隼 +隽 +倌 +倥 +臬 +皋 +郫 +倨 +衄 +颀 +徕 +舫 +釜 +奚 +衾 +胯 +胱 +胴 +胭 +脍 +胼 +朕 +脒 +胺 +鸱 +玺 +鸲 +狷 +猁 +狳 +猃 +狺 +逖 +桀 +袅 +饽 +凇 +栾 +挛 +亳 +疳 +疴 +疸 +疽 +痈 +疱 +痂 +痉 +衮 +凋 +颃 +恣 +旆 +旄 +旃 +阃 +阄 +訚 +阆 +恙 +粑 +朔 +郸 +烜 +烨 +烩 +烊 +剡 +郯 +烬 +涑 +浯 +涞 +涟 +娑 +涅 +涠 +浞 +涓 +浥 +涔 +浜 +浠 +浣 +浚 +悚 +悭 +悝 +悒 +悌 +悛 +宸 +窈 +剜 +诹 +冢 +诼 +袒 +袢 +祯 +诿 +谀 +谂 +谄 +谇 +屐 +屙 +陬 +勐 +奘 +牂 +蚩 +陲 +姬 +娠 +娌 +娉 +娲 +娩 +娴 +娣 +娓 +婀 +畚 +逡 +绠 +骊 +绡 +骋 +绥 +绦 +绨 +骎 +邕 +鸶 +彗 +耜 +焘 +舂 +琏 +琇 +麸 +揶 +埴 +埯 +捯 +掳 +掴 +埸 +埵 +赧 +埤 +捭 +逵 +埝 +堋 +堍 +掬 +鸷 +掖 +捽 +掊 +堉 +掸 +捩 +掮 +悫 +埭 +埽 +掇 +掼 +聃 +菁 +萁 +菘 +堇 +萘 +萋 +菽 +菖 +萜 +萸 +萑 +棻 +菔 +菟 +萏 +萃 +菏 +菹 +菪 +菅 +菀 +萦 +菰 +菡 +梵 +梿 +梏 +觋 +桴 +桷 +梓 +棁 +桫 +棂 +啬 +郾 +匮 +敕 +豉 +鄄 +酞 +酚 +戛 +硎 +硭 +硒 +硖 +硗 +硐 +硇 +硌 +鸸 +瓠 +匏 +厩 +龚 +殒 +殓 +殍 +赉 +雩 +辄 +堑 +眭 +眦 +啧 +晡 +晤 +眺 +眵 +眸 +圊 +喏 +喵 +啉 +勖 +晞 +唵 +晗 +冕 +啭 +畦 +趺 +啮 +跄 +蚶 +蛄 +蛎 +蛆 +蚰 +蛊 +圉 +蚱 +蛉 +蛏 +蚴 +啁 +啕 +唿 +啐 +唼 +唷 +啖 +啵 +啶 +啷 +唳 +唰 +啜 +帻 +崚 +崦 +帼 +崮 +崤 +崆 +赇 +赈 +赊 +铑 +铒 +铗 +铙 +铟 +铠 +铡 +铢 +铣 +铤 +铧 +铨 +铩 +铪 +铫 +铬 +铮 +铯 +铰 +铱 +铳 +铵 +铷 +氪 +牾 +鸹 +秾 +逶 +笺 +筇 +笸 +笪 +笮 +笠 +笥 +笤 +笳 +笾 +笞 +偾 +偃 +偕 +偈 +傀 +偬 +偻 +皑 +皎 +鸻 +徜 +舸 +舻 +舴 +舷 +龛 +翎 +脬 +脘 +脲 +匐 +猗 +猡 +猞 +猝 +斛 +猕 +馗 +馃 +馄 +鸾 +孰 +庹 +庾 +痔 +痍 +疵 +翊 +旌 +旎 +袤 +阇 +阈 +阉 +阊 +阋 +阍 +阏 +羟 +粝 +粕 +敝 +焐 +烯 +焓 +烽 +焖 +烷 +焗 +渍 +渚 +淇 +淅 +淞 +渎 +涿 +淖 +挲 +淠 +涸 +渑 +淦 +淝 +淬 +涪 +淙 +涫 +渌 +淄 +惬 +悻 +悱 +惝 +惘 +悸 +惆 +惚 +惇 +惮 +窕 +谌 +谏 +扈 +皲 +谑 +裆 +袷 +裉 +谒 +谔 +谕 +谖 +谗 +谙 +谛 +谝 +逯 +郿 +隈 +粜 +隍 +隗 +婧 +婊 +婕 +娼 +婢 +婵 +胬 +袈 +翌 +恿 +欸 +绫 +骐 +绮 +绯 +绱 +骒 +绲 +骓 +绶 +绺 +绻 +绾 +骖 +缁 +耠 +琫 +琵 +琶 +琪 +瑛 +琦 +琥 +琨 +靓 +琰 +琮 +琯 +琬 +琛 +琚 +辇 +鼋 +揳 +堞 +搽 +揸 +揠 +堙 +趄 +揖 +颉 +塄 +揿 +耋 +揄 +蛩 +蛰 +塆 +摒 +揆 +掾 +聒 +葑 +葚 +靰 +靸 +葳 +葺 +葸 +萼 +葆 +葩 +葶 +蒌 +萱 +戟 +葭 +楮 +棼 +椟 +棹 +椤 +棰 +赍 +椋 +椁 +椪 +棣 +椐 +鹁 +覃 +酤 +酢 +酡 +鹂 +厥 +殚 +殛 +雯 +雱 +辊 +辋 +椠 +辍 +辎 +斐 +睄 +睑 +睇 +睃 +戢 +喋 +嗒 +喃 +喱 +喹 +晷 +喈 +跖 +跗 +跞 +跚 +跎 +跏 +跆 +蛱 +蛲 +蛭 +蛳 +蛐 +蛔 +蛞 +蛴 +蛟 +蛘 +喁 +喟 +啾 +嗖 +喑 +嗟 +喽 +嗞 +喀 +喔 +喙 +嵘 +嵖 +崴 +遄 +詈 +嵎 +崽 +嵬 +嵛 +嵯 +嵝 +嵫 +幄 +嵋 +赕 +铻 +铼 +铿 +锃 +锂 +锆 +锇 +锉 +锏 +锑 +锒 +锔 +锕 +掣 +矬 +氰 +毳 +毽 +犊 +犄 +犋 +鹄 +犍 +嵇 +黍 +稃 +稂 +筚 +筵 +筌 +傣 +傈 +舄 +牍 +傥 +傧 +遑 +傩 +遁 +徨 +媭 +畲 +弑 +颌 +翕 +釉 +鹆 +舜 +貂 +腈 +腌 +腓 +腆 +腴 +腑 +腚 +腱 +鱿 +鲀 +鲂 +颍 +猢 +猹 +猥 +飓 +觞 +觚 +猱 +颎 +飧 +馇 +馊 +亵 +脔 +裒 +痣 +痨 +痦 +痞 +痤 +痫 +痧 +赓 +竦 +瓿 +啻 +颏 +鹇 +阑 +阒 +阕 +粞 +遒 +孳 +焯 +焜 +焙 +焱 +鹈 +湛 +渫 +湮 +湎 +湜 +渭 +湍 +湫 +溲 +湟 +溆 +湲 +湔 +湉 +渥 +湄 +滁 +愠 +惺 +愦 +惴 +愀 +愎 +愔 +喾 +寐 +谟 +扉 +裢 +裎 +裥 +祾 +祺 +谠 +幂 +谡 +谥 +谧 +遐 +孱 +弼 +巽 +骘 +媪 +媛 +婷 +巯 +翚 +皴 +婺 +骛 +缂 +缃 +缄 +彘 +缇 +缈 +缌 +缑 +缒 +缗 +飨 +耢 +瑚 +瑁 +瑜 +瑗 +瑄 +瑕 +遨 +骜 +韫 +髡 +塬 +鄢 +趔 +趑 +摅 +摁 +蜇 +搋 +搪 +搐 +搛 +搠 +摈 +彀 +毂 +搦 +搡 +蓁 +戡 +蓍 +鄞 +靳 +蓐 +蓦 +鹋 +蒽 +蓓 +蓖 +蓊 +蒯 +蓟 +蓑 +蒿 +蒺 +蓠 +蒟 +蒡 +蒹 +蒴 +蒗 +蓥 +颐 +楔 +楠 +楂 +楝 +楫 +楸 +椴 +槌 +楯 +皙 +榈 +槎 +榉 +楦 +楣 +楹 +椽 +裘 +剽 +甄 +酮 +酰 +酯 +酩 +蜃 +碛 +碓 +硼 +碉 +碚 +碇 +碜 +鹌 +辏 +龃 +龅 +訾 +粲 +虞 +睚 +嗪 +韪 +嗷 +嗉 +睨 +睢 +雎 +睥 +嘟 +嗑 +嗫 +嗬 +嗔 +嗝 +戥 +嗄 +煦 +暄 +遢 +暌 +跬 +跶 +跸 +跐 +跣 +跹 +跻 +蛸 +蜊 +蜍 +蜉 +蜣 +畹 +蛹 +嗣 +嗯 +嗥 +嗲 +嗳 +嗌 +嗍 +嗨 +嗐 +嗤 +嗵 +罨 +嵊 +嵩 +嵴 +骰 +锗 +锛 +锜 +锝 +锞 +锟 +锢 +锨 +锩 +锭 +锱 +雉 +氲 +犏 +歃 +稞 +稗 +稔 +筠 +筢 +筮 +筲 +筱 +牒 +煲 +敫 +徭 +愆 +艄 +觎 +毹 +貊 +貅 +貉 +颔 +腠 +腩 +腼 +腭 +腧 +塍 +媵 +詹 +鲅 +鲆 +鲇 +鲈 +稣 +鲋 +鲐 +肄 +鹐 +飕 +觥 +遛 +馐 +鹑 +亶 +瘃 +痱 +痼 +痿 +瘐 +瘁 +瘆 +麂 +裔 +歆 +旒 +雍 +阖 +阗 +阙 +羧 +豢 +粳 +猷 +煳 +煜 +煨 +煅 +煊 +煸 +煺 +滟 +溱 +溘 +漭 +滢 +溥 +溧 +溽 +裟 +溻 +溷 +滗 +滫 +溴 +滏 +滃 +滦 +溏 +滂 +滓 +溟 +滪 +愫 +慑 +慊 +鲎 +骞 +窦 +窠 +窣 +裱 +褚 +裨 +裾 +裰 +禊 +谩 +谪 +媾 +嫫 +媲 +嫒 +嫔 +媸 +缙 +缜 +缛 +辔 +骝 +缟 +缡 +缢 +缣 +骟 +耥 +璈 +瑶 +瑭 +獒 +觏 +慝 +嫠 +韬 +叆 +髦 +摽 +墁 +撂 +摞 +撄 +翥 +踅 +摭 +墉 +墒 +榖 +綦 +蔫 +蔷 +靺 +靼 +鞅 +靿 +甍 +蔸 +蔟 +蔺 +戬 +蕖 +蔻 +蓿 +斡 +鹕 +蓼 +榛 +榧 +榻 +榫 +榭 +槔 +榱 +槁 +槟 +槠 +榷 +僰 +酽 +酶 +酹 +厮 +碡 +碴 +碣 +碲 +磋 +臧 +豨 +殡 +霆 +霁 +辕 +蜚 +裴 +翡 +龇 +龈 +睿 +䁖 +睽 +嘞 +嘈 +嘌 +嘁 +嘎 +暧 +暝 +踌 +踉 +蜞 +蜥 +蜮 +蝈 +蜴 +蜱 +蜩 +蜷 +蜿 +螂 +蜢 +嘘 +嘡 +鹗 +嘣 +嘤 +嘚 +嗾 +嘧 +罴 +罱 +幔 +嶂 +幛 +赙 +罂 +骷 +骶 +鹘 +锲 +锴 +锶 +锷 +锸 +锵 +镁 +镂 +犒 +箐 +箦 +箧 +箍 +箸 +箬 +箅 +箪 +箔 +箜 +箢 +箓 +毓 +僖 +儆 +僳 +僭 +劁 +僮 +魃 +魆 +睾 +艋 +鄱 +膈 +膑 +鲑 +鲔 +鲚 +鲛 +鲟 +獐 +觫 +雒 +夤 +馑 +銮 +塾 +麽 +瘌 +瘊 +瘘 +瘙 +廖 +韶 +旖 +膂 +阚 +鄯 +鲞 +粿 +粼 +粽 +糁 +槊 +鹚 +熘 +熥 +潢 +漕 +滹 +漯 +漶 +潋 +潴 +漪 +漉 +漳 +漩 +澉 +潍 +慵 +搴 +窨 +寤 +綮 +谮 +褡 +褙 +褓 +褛 +褊 +谯 +谰 +谲 +暨 +屣 +鹛 +嫣 +嫱 +嫖 +嫦 +嫚 +嫘 +嫡 +鼐 +翟 +瞀 +鹜 +骠 +缥 +缦 +缧 +缨 +骢 +缪 +缫 +耦 +耧 +瑾 +璜 +璀 +璎 +璁 +璋 +璇 +奭 +髯 +髫 +撷 +撅 +赭 +撸 +鋆 +撙 +撺 +墀 +聩 +觐 +鞑 +蕙 +鞒 +蕈 +蕨 +蕤 +蕞 +蕺 +瞢 +蕃 +蕲 +赜 +槿 +樯 +槭 +樗 +樘 +樊 +槲 +醌 +醅 +靥 +魇 +餍 +磔 +磙 +霈 +辘 +龉 +龊 +觑 +瞌 +瞋 +瞑 +嘭 +噎 +噶 +颙 +暹 +噘 +踔 +踝 +踟 +踒 +踬 +踮 +踯 +踺 +踞 +蝽 +蝾 +蝻 +蝰 +蝮 +螋 +蝓 +蝣 +蝼 +噗 +嘬 +颚 +噍 +噢 +噙 +噜 +噌 +噔 +颛 +幞 +幡 +嶙 +嶝 +骺 +骼 +骸 +镊 +镉 +镌 +镍 +镏 +镒 +镓 +镔 +稷 +箴 +篑 +篁 +篌 +篆 +牖 +儋 +徵 +磐 +虢 +鹞 +膘 +滕 +鲠 +鲡 +鲢 +鲣 +鲥 +鲧 +鲩 +獗 +獠 +觯 +馓 +馔 +麾 +廛 +瘛 +瘼 +瘢 +瘠 +齑 +羯 +羰 +𥻗 +遴 +糌 +糍 +糅 +熜 +熵 +熠 +澍 +澌 +潸 +潦 +潲 +鋈 +潟 +潼 +潺 +憬 +憧 +寮 +窳 +谳 +褴 +褟 +褫 +谵 +熨 +屦 +嬉 +勰 +戮 +蝥 +缬 +缮 +缯 +骣 +畿 +耩 +耨 +耪 +璞 +璟 +靛 +璠 +璘 +聱 +螯 +髻 +髭 +髹 +擀 +熹 +甏 +擞 +縠 +磬 +颞 +蕻 +鞘 +颟 +薤 +薨 +檠 +薏 +薮 +薜 +薅 +樾 +橛 +橇 +樵 +檎 +橹 +樽 +樨 +橼 +墼 +橐 +翮 +醛 +醐 +醍 +醚 +磲 +赝 +飙 +殪 +霖 +霏 +霓 +錾 +辚 +臻 +遽 +氅 +瞟 +瞠 +瞰 +嚄 +嚆 +噤 +暾 +蹀 +踹 +踵 +踽 +蹉 +蹁 +螨 +蟒 +螈 +螅 +螭 +螠 +螟 +噱 +噬 +噫 +噻 +噼 +罹 +圜 +䦃 +镖 +镗 +镘 +镚 +镛 +镝 +镞 +镠 +氇 +氆 +憩 +穑 +篝 +篥 +篦 +篪 +篙 +盥 +劓 +翱 +魉 +魈 +徼 +歙 +膳 +膦 +膙 +鲮 +鲱 +鲲 +鲳 +鲴 +鲵 +鲷 +鲻 +獴 +獭 +獬 +邂 +鹧 +廨 +赟 +瘰 +廪 +瘿 +瘵 +瘴 +癃 +瘳 +斓 +麇 +麈 +嬴 +壅 +羲 +糗 +瞥 +甑 +燎 +燠 +燔 +燧 +濑 +濉 +潞 +澧 +澹 +澥 +澶 +濂 +褰 +寰 +窸 +褶 +禧 +嬖 +犟 +隰 +嬗 +颡 +缱 +缲 +缳 +璨 +璩 +璐 +璪 +螫 +擤 +壕 +觳 +罄 +擢 +薹 +鞡 +鞬 +薷 +薰 +藓 +藁 +檄 +檩 +懋 +醢 +翳 +礅 +磴 +鹩 +龋 +龌 +豳 +壑 +黻 +嚏 +嚅 +蹑 +蹒 +蹊 +蟥 +螬 +螵 +疃 +螳 +蟑 +嚓 +羁 +罽 +罾 +嶷 +黜 +黝 +髁 +髀 +镡 +镢 +镣 +镦 +镧 +镩 +镪 +镫 +罅 +黏 +簌 +篾 +篼 +簖 +簋 +鼢 +黛 +儡 +鹪 +鼾 +皤 +魍 +龠 +繇 +貘 +邈 +貔 +臌 +膻 +臆 +臃 +鲼 +鲽 +鳀 +鳃 +鳅 +鳇 +鳊 +螽 +燮 +鹫 +襄 +糜 +縻 +膺 +癍 +麋 +懑 +濡 +濮 +濞 +濠 +濯 +蹇 +謇 +邃 +襁 +檗 +擘 +孺 +隳 +嬷 +蟊 +鹬 +鍪 +鏊 +鳌 +鬈 +鬃 +瞽 +鞯 +鞨 +鞫 +鞧 +鞣 +藜 +藠 +藩 +醪 +蹙 +礓 +燹 +餮 +瞿 +曛 +颢 +曜 +躇 +蹚 +鹭 +蟛 +蟪 +蟠 +蟮 +鹮 +黠 +黟 +髅 +髂 +镬 +镭 +镯 +馥 +簟 +簪 +鼬 +雠 +艟 +鳎 +鳏 +鳐 +癞 +癔 +癜 +癖 +糨 +蹩 +鎏 +懵 +彝 +邋 +鬏 +攉 +攒 +鞲 +鞴 +藿 +蘧 +蘅 +麓 +醮 +醯 +酃 +霪 +霭 +霨 +黼 +嚯 +蹰 +蹶 +蹽 +蹼 +蹴 +蹾 +蹿 +蠖 +蠓 +蟾 +蠊 +黢 +髋 +髌 +镲 +籀 +籁 +齁 +魑 +艨 +鳓 +鳔 +鳕 +鳗 +鳙 +麒 +鏖 +羸 +㸆 +瀚 +瀣 +瀛 +襦 +谶 +襞 +骥 +缵 +瓒 +攘 +蘩 +蘖 +醴 +霰 +酆 +矍 +曦 +躅 +鼍 +巉 +黩 +黥 +黪 +镳 +镴 +黧 +纂 +璺 +鼯 +臜 +鳜 +鳝 +鳟 +獾 +孀 +骧 +瓘 +鼙 +醺 +礴 +颦 +曩 +鳢 +癫 +麝 +夔 +爝 +灏 +禳 +鐾 +羼 +蠡 +耱 +懿 +蘸 +鹳 +霾 +氍 +饕 +躐 +髑 +镵 +穰 +饔 +鬻 +鬟 +趱 +攫 +攥 +颧 +躜 +鼹 +癯 +麟 +蠲 +蠹 +躞 +衢 +鑫 +灞 +襻 +纛 +鬣 +攮 +囔 +馕 +戆 +爨 +齉 +亍 +尢 +彳 +卬 +殳 +𠙶 +毌 +邘 +戋 +圢 +氕 +伋 +仝 +冮 +氿 +汈 +氾 +忉 +宄 +讱 +扞 +圲 +圫 +芏 +芃 +朳 +朸 +𨙸 +邨 +吒 +吖 +屼 +屾 +辿 +钆 +仳 +伣 +伈 +癿 +甪 +邠 +犴 +冱 +邡 +闫 +汋 +䜣 +讻 +孖 +纩 +玒 +玓 +玘 +玚 +刬 +坜 +坉 +扽 +坋 +扺 +㧑 +毐 +芰 +芣 +苊 +苉 +芘 +芴 +芠 +芤 +杕 +杙 +杄 +杧 +杩 +尪 +尨 +轪 +坒 +芈 +旴 +旵 +呙 +㕮 +岍 +岠 +岜 +呇 +冏 +觃 +岙 +伾 +㑇 +伭 +佖 +伲 +佁 +飏 +狃 +闶 +汧 +汫 +𣲘 +𣲗 +沄 +沘 +汭 +㳇 +沇 +忮 +忳 +忺 +祃 +诇 +邲 +诎 +诐 +屃 +岊 +阽 +䢺 +阼 +妧 +妘 +𨚕 +纮 +驲 +纻 +纼 +玤 +玞 +玱 +玟 +邽 +邿 +坥 +坰 +坬 +坽 +弆 +耵 +䢼 +𦭜 +茋 +苧 +苾 +苠 +枅 +㭎 +枘 +枍 +矼 +矻 +匼 +旿 +昇 +昄 +昒 +昈 +咉 +咇 +咍 +岵 +岽 +岨 +岞 +峂 +㟃 +囷 +钐 +钔 +钖 +牥 +佴 +垈 +侁 +侹 +佸 +佺 +隹 +㑊 +侂 +佽 +侘 +郈 +舠 +郐 +郃 +攽 +肭 +肸 +肷 +狉 +狝 +饳 +忞 +於 +炌 +炆 +泙 +沺 +泂 +泜 +泃 +泇 +怊 +峃 +穸 +祋 +祊 +鸤 +弢 +弨 +陑 +陎 +卺 +乸 +妭 +姈 +迳 +叕 +驵 +䌹 +驺 +绋 +绐 +砉 +耔 +㛃 +玶 +珇 +珅 +珋 +玹 +珌 +玿 +韨 +垚 +垯 +垙 +垲 +埏 +垍 +耇 +垎 +垴 +垟 +垞 +挓 +垵 +垏 +拶 +荖 +荁 +荙 +荛 +茈 +茽 +荄 +茺 +荓 +茳 +𦰡 +茛 +荭 +㭕 +柷 +柃 +柊 +枹 +栐 +柖 +郚 +剅 +䴓 +迺 +厖 +砆 +砑 +砄 +耏 +奓 +䶮 +轵 +轷 +轹 +轺 +昺 +昽 +盷 +咡 +咺 +昳 +昣 +哒 +昤 +昫 +昡 +咥 +昪 +虷 +虸 +哃 +峘 +耑 +峛 +峗 +峧 +帡 +钘 +钜 +钪 +钬 +钭 +矧 +秬 +俫 +舁 +俜 +俙 +俍 +垕 +衎 +舣 +弇 +侴 +鸧 +䏡 +胠 +𦙶 +胈 +胩 +胣 +朏 +飐 +訄 +饻 +庤 +疢 +炣 +炟 +㶲 +洭 +洘 +洓 +洿 +㳚 +泚 +浈 +浉 +洸 +洑 +洢 +洈 +洚 +洺 +洨 +浐 +㳘 +洴 +洣 +恔 +宬 +窀 +扂 +袆 +祏 +祐 +祕 +叚 +陧 +陞 +娀 +姞 +姱 +姤 +姶 +姽 +枲 +绖 +骃 +彖 +骉 +恝 +珪 +珛 +珹 +琊 +玼 +珖 +珽 +珦 +珫 +珒 +珢 +珕 +珝 +埗 +垾 +垺 +埆 +垿 +埌 +埇 +莰 +茝 +鄀 +莶 +莝 +䓖 +莙 +栻 +桠 +桄 +梠 +栴 +梴 +栒 +酎 +酏 +砵 +砠 +砫 +砬 +硁 +恧 +翃 +郪 +𨐈 +辀 +辁 +剕 +赀 +哢 +晅 +晊 +唝 +哳 +哱 +冔 +晔 +晐 +晖 +畖 +蚄 +蚆 +帱 +崁 +峿 +崄 +帨 +崀 +赆 +钷 +眚 +甡 +笫 +倻 +倴 +脩 +倮 +倕 +倞 +倓 +倧 +衃 +虒 +舭 +舯 +舥 +瓞 +鬯 +鸰 +脎 +朓 +胲 +虓 +鱽 +狴 +峱 +狻 +眢 +勍 +痄 +疰 +痃 +竘 +羖 +羓 +桊 +敉 +烠 +烔 +烶 +烻 +涍 +浡 +浭 +浬 +涄 +涢 +涐 +浰 +浟 +浛 +浼 +浲 +涘 +悈 +悃 +悢 +宧 +窅 +窊 +窎 +扅 +扆 +袪 +袗 +袯 +祧 +隺 +堲 +疍 +𨺙 +陴 +烝 +砮 +㛚 +哿 +翀 +翂 +剟 +绤 +骍 +䂮 +琎 +珸 +珵 +琄 +琈 +琀 +珺 +掭 +堎 +堐 +埼 +掎 +埫 +堌 +晢 +掞 +埪 +壸 +㙍 +聍 +菝 +萚 +菥 +莿 +䓫 +勚 +䓬 +萆 +菂 +菍 +菼 +萣 +䓨 +菉 +䓛 +梼 +梽 +桲 +梾 +桯 +梣 +梌 +桹 +敔 +厣 +硔 +硙 +硚 +硊 +硍 +勔 +䴕 +龁 +逴 +唪 +啫 +翈 +㫰 +晙 +畤 +趼 +跂 +蛃 +蚲 +蚺 +啴 +䎃 +崧 +崟 +崞 +崒 +崌 +崡 +铏 +铕 +铖 +铘 +铚 +铞 +铥 +铴 +牻 +牿 +稆 +笱 +笯 +偰 +偡 +鸺 +偭 +偲 +偁 +㿠 +鄅 +偓 +徛 +衒 +舳 +舲 +鸼 +悆 +鄃 +瓻 +䝙 +脶 +脞 +脟 +䏲 +鱾 +猇 +猊 +猄 +觖 +𠅤 +庱 +庼 +庳 +痓 +䴔 +竫 +堃 +阌 +羝 +羕 +焆 +烺 +焌 +淏 +淟 +淜 +淴 +淯 +湴 +涴 +㥄 +惛 +惔 +悰 +惙 +寁 +逭 +袼 +裈 +祲 +谞 +艴 +弸 +弶 +隃 +婞 +娵 +婼 +媖 +婳 +婍 +婌 +婫 +婤 +婘 +婠 +绹 +骕 +絜 +珷 +琲 +琡 +琟 +琔 +琭 +堾 +堼 +揕 +㙘 +堧 +喆 +堨 +塅 +堠 +絷 +𡎚 +葜 +惎 +萳 +葙 +靬 +葴 +蒇 +蒈 +鄚 +蒉 +蓇 +萩 +蒐 +葰 +葎 +鄑 +蒎 +葖 +蒄 +萹 +棤 +棽 +棫 +椓 +椑 +鹀 +椆 +棓 +棬 +棪 +椀 +楗 +甦 +酦 +觌 +奡 +皕 +硪 +欹 +詟 +辌 +棐 +龂 +黹 +牚 +睎 +晫 +晪 +晱 +𧿹 +蛑 +畯 +斝 +喤 +崶 +嵁 +崾 +嵅 +崿 +嵚 +翙 +圌 +圐 +赑 +淼 +赒 +铹 +铽 +𨱇 +锊 +锍 +锎 +锓 +犇 +颋 +稌 +筀 +筘 +筜 +筥 +筅 +傃 +傉 +翛 +傒 +傕 +舾 +畬 +脿 +腘 +䐃 +腙 +腒 +鲃 +猰 +猯 +㺄 +馉 +鄗 +廋 +廆 +鄌 +粢 +遆 +旐 +焞 +欻 +𣸣 +溚 +溁 +湝 +渰 +湓 +㴔 +渟 +溠 +渼 +溇 +湣 +湑 +溞 +愐 +愃 +敩 +甯 +棨 +扊 +裣 +祼 +婻 +媆 +媞 +㛹 +媓 +媂 +媄 +毵 +矞 +缊 +缐 +骙 +瑃 +瑓 +瑅 +瑆 +䴖 +瑖 +瑝 +瑔 +瑀 +𤧛 +瑳 +瑂 +嶅 +瑑 +遘 +髢 +塥 +堽 +赪 +摛 +塝 +搒 +搌 +蒱 +蒨 +蓏 +蔀 +蓢 +蓂 +蒻 +蓣 +椹 +楪 +榃 +榅 +楒 +楞 +楩 +榇 +椸 +楙 +歅 +碃 +碏 +碈 +䃅 +硿 +鄠 +辒 +龆 +觜 +䣘 +暕 +鹍 +㬊 +暅 +跱 +蜐 +蜎 +嵲 +赗 +骱 +锖 +锘 +锳 +锧 +锪 +锫 +锬 +稑 +稙 +䅟 +筻 +筼 +筶 +筦 +筤 +傺 +鹎 +僇 +艅 +艉 +谼 +貆 +腽 +腨 +腯 +鲉 +鲊 +鲌 +䲟 +鲏 +雊 +猺 +飔 +觟 +𦝼 +馌 +裛 +廒 +瘀 +瘅 +鄘 +鹒 +鄜 +麀 +鄣 +阘 +煁 +煃 +煴 +煋 +煟 +煓 +滠 +溍 +溹 +滆 +滉 +溦 +溵 +漷 +滧 +滘 +滍 +愭 +慥 +慆 +塱 +裼 +禋 +禔 +禘 +禒 +谫 +鹔 +愍 +嫄 +媱 +戤 +戣 +缞 +耤 +瑧 +瑨 +瑱 +瑷 +瑢 +斠 +摏 +墕 +墈 +墐 +墘 +摴 +銎 +𡐓 +墚 +撖 +靽 +鞁 +蔌 +蔈 +蓰 +蔹 +蔊 +嘏 +榰 +榑 +槚 +𣗋 +槜 +榍 +疐 +酺 +酾 +酲 +酴 +碶 +䃎 +碨 +𥔲 +碹 +碥 +劂 +䴗 +夥 +瞍 +鹖 +㬎 +跽 +蜾 +幖 +嶍 +圙 +𨱏 +锺 +锼 +锽 +锾 +锿 +镃 +镄 +镅 +馝 +鹙 +箨 +箖 +劄 +僬 +僦 +僔 +僎 +槃 +㙦 +鲒 +鲕 +鲖 +鲗 +鲘 +鲙 +𩽾 +夐 +獍 +飗 +凘 +廑 +廙 +瘗 +瘥 +瘕 +鲝 +鄫 +熇 +漹 +漖 +潆 +漤 +潩 +漼 +漴 +㽏 +漈 +漋 +漻 +慬 +窬 +窭 +㮾 +褕 +禛 +禚 +隩 +嫕 +嫭 +嫜 +嫪 +㻬 +麹 +璆 +漦 +叇 +墣 +墦 +墡 +劐 +薁 +蕰 +蔃 +鼒 +槱 +鹝 +磏 +磉 +殣 +慭 +霅 +暵 +暲 +暶 +踦 +踣 +䗖 +蝘 +蝲 +蝤 +噇 +噂 +噀 +罶 +嶲 +嶓 +㠇 +嶟 +嶒 +镆 +镈 +镋 +镎 +镕 +稹 +儇 +皞 +皛 +䴘 +艎 +艏 +鹟 +𩾃 +鲦 +鲪 +鲬 +橥 +觭 +鹠 +鹡 +糇 +糈 +翦 +鹢 +鹣 +熛 +潖 +潵 +㵐 +澂 +澛 +瑬 +潽 +潾 +潏 +憭 +憕 +戭 +褯 +禤 +嫽 +遹 +璥 +璲 +璒 +憙 +擐 +鄹 +薳 +鞔 +黇 +蕗 +薢 +蕹 +橞 +橑 +橦 +醑 +觱 +磡 +𥕢 +磜 +豮 +鹾 +虤 +暿 +曌 +曈 +㬚 +蹅 +踶 +䗛 +螗 +疁 +㠓 +幪 +嶦 +𨱑 +馞 +穄 +篚 +篯 +簉 +鼽 +衠 +盦 +螣 +縢 +鲭 +鲯 +鲰 +鲺 +鲹 +亸 +癀 +瘭 +羱 +糒 +燋 +熻 +燊 +燚 +燏 +濩 +濋 +澪 +澽 +澴 +澭 +澼 +憷 +憺 +懔 +黉 +嬛 +鹨 +翯 +璱 +𤩽 +璬 +璮 +髽 +擿 +薿 +薸 +檑 +櫆 +檞 +醨 +繄 +磹 +磻 +瞫 +瞵 +蹐 +蟏 +㘎 +镤 +镥 +镨 +𨱔 +矰 +穙 +穜 +穟 +簕 +簃 +簏 +儦 +魋 +斶 +艚 +谿 +䲠 +鲾 +鲿 +鳁 +鳂 +鳈 +鳉 +獯 +䗪 +馘 +襕 +襚 +螱 +甓 +嬬 +嬥 +𦈡 +瓀 +釐 +鬶 +爇 +鞳 +鞮 +藟 +藦 +藨 +鹲 +檫 +黡 +礞 +礌 +𥖨 +蹢 +蹜 +蟫 +䗴 +嚚 +髃 +镮 +镱 +酂 +馧 +簠 +簝 +簰 +鼫 +鼩 +皦 +臑 +䲢 +鳑 +鳒 +鹱 +鹯 +癗 +𦒍 +旞 +翷 +冁 +䎖 +瀔 +瀍 +瀌 +襜 +䴙 +嚭 +㰀 +鬷 +醭 +蹯 +蠋 +翾 +鳘 +儳 +儴 +鼗 +𩾌 +鳚 +鳛 +麑 +麖 +蠃 +彟 +嬿 +鬒 +蘘 +欂 +醵 +颥 +甗 +𨟠 +巇 +酅 +髎 +犨 +𨭉 +㸌 +爔 +瀱 +瀹 +瀼 +瀵 +襫 +孅 +骦 +耰 +𤫉 +瓖 +鬘 +趯 +罍 +鼱 +鳠 +鳡 +鳣 +爟 +爚 +灈 +韂 +糵 +蘼 +礵 +鹴 +躔 +皭 +龢 +鳤 +亹 +籥 +鼷 +玃 +醾 +齇 +觿 +蠼 +𬣙 +𬇕 +𬣞 +𬘓 +𫭟 +𫭢 +𫇭 +𫐄 +𫵷 +𬇙 +𬣡 +𫸩 +𫘜 +𬘘 +𫘝 +𬨂 +𬀩 +𬀪 +𬬩 +𫍣 +𬣳 +𬩽 +𬮿 +𬯀 +𫰛 +𬳵 +𬳶 +𫠊 +𬍛 +鿍 +𬜬 +𪾢 +𪨰 +𫓧 +𬬮 +𬬱 +𬬭 +𬘡 +𬳽 +𬘩 +𫄧 +𪟝 +𬍤 +𫭼 +𬜯 +𬂩 +𫠆 +𬌗 +𫑡 +𪨶 +𬬸 +𬬻 +𬬹 +𬬿 +𬭁 +𫢸 +𫗧 +𬊈 +𬒈 +𬳿 +𫄨 +𬘫 +𫮃 +鿎 +𬱖 +𬟽 +𫓯 +𫟹 +𫟼 +𬇹 +𬍡 +𬤇 +𫍯 +𬤊 +𫍲 +𬯎 +𬘬 +𬘭 +𬴂 +𫘦 +𫟅 +𬘯 +𫘧 +𪣻 +𬃊 +𬷕 +𫐐 +𬹼 +𫶇 +𫖮 +鿏 +𬭊 +𫓶 +𬭎 +𫖯 +𬱟 +𫛭 +𫷷 +𬮱 +𬊤 +𬴃 +𫘨 +𬪩 +𬒔 +𬨎 +𫐓 +𫫇 +𫓹 +𬭚 +𬭛 +𬕂 +𬶋 +𬶍 +𫔶 +𫌀 +𫖳 +𫘪 +𫘬 +𫞩 +𪤗 +𬸘 +𬒗 +𫚖 +𬭤 +𫚕 +𬶐 +𬶏 +𬸚 +𬤝 +𬙂 +𬭩 +𬸣 +𫍽 +𬴊 +𬞟 +𫟦 +𬺈 +𫠜 +𪩘 +𬭬 +𬭯 +𫗴 +𬸦 +𫄷 +𬭳 +𬭶 +𫔍 +𬭸 +𬭼 +𫔎 +𬸪 +𬶟 +𬶠 +𬶨 +𫄸 +𬟁 +𬙊 +𬶭 +𬶮 +𬙋 +𬺓 +𫚭 +廠 +蔔 +兒 +幾 +幹 +虧 +纔 +與 +萬 +韆 +億 +個 +廣 +門 +義 +衛 +飛 +習 +馬 +鄉 +豐 +開 +無 +雲 +專 +藝 +廳 +區 +歷 +曆 +車 +貝 +岡 +見 +氣 +長 +僕 +幣 +僅 +從 +侖 +倉 +風 +烏 +鳳 +爲 +鬥 +憶 +計 +訂 +認 +譏 +醜 +隊 +辦 +鄧 +勸 +雙 +書 +擊 +撲 +節 +術 +厲 +龍 +滅 +軋 +東 +盧 +業 +舊 +帥 +歸 +葉 +電 +號 +衹 +隻 +嘰 +嘆 +們 +儀 +叢 +爾 +樂 +處 +鼕 +鳥 +務 +飢 +饑 +馮 +閃 +蘭 +匯 +彙 +頭 +漢 +寧 +討 +寫 +讓 +禮 +訓 +議 +訊 +記 +齣 +遼 +邊 +發 +髮 +聖 +對 +臺 +颱 +檯 +糾 +絲 +動 +鞏 +執 +擴 +掃 +場 +揚 +亞 +樸 +機 +權 +過 +協 +壓 +厭 +頁 +誇 +奪 +達 +夾 +軌 +堯 +劃 +邁 +畢 +貞 +師 +塵 +當 +噹 +籲 +嚇 +蟲 +麯 +團 +糰 +嗎 +嶼 +歲 +迴 +豈 +則 +剛 +網 +硃 +遷 +喬 +偉 +傳 +優 +傷 +價 +倫 +華 +僞 +嚮 +後 +會 +殺 +閤 +衆 +爺 +傘 +創 +雜 +負 +壯 +衝 +妝 +莊 +慶 +劉 +齊 +産 +閉 +問 +闖 +關 +燈 +湯 +興 +講 +諱 +軍 +訝 +許 +訛 +論 +訟 +農 +諷 +設 +訪 +訣 +尋 +盡 +儘 +導 +孫 +陣 +陽 +階 +陰 +婦 +媽 +戲 +觀 +歡 +買 +紅 +馱 +纖 +縴 +馴 +約 +級 +紀 +馳 +紉 +壽 +麥 +瑪 +進 +遠 +違 +韌 +運 +撫 +壇 +罎 +壞 +摳 +擾 +貢 +垻 +壩 +摺 +掄 +搶 +墳 +護 +殻 +塊 +聲 +報 +擬 +蕪 +葦 +蒼 +嚴 +蘆 +勞 +蘇 +囌 +極 +楊 +兩 +麗 +醫 +勵 +還 +殲 +來 +連 +軒 +鹵 +滷 +堅 +時 +縣 +裏 +嘔 +園 +曠 +圍 +噸 +郵 +睏 +員 +聽 +嗆 +嗚 +彆 +嶇 +崗 +帳 +財 +針 +釘 +亂 +體 +傭 +徹 +餘 +穀 +鄰 +腸 +龜 +猶 +狽 +條 +島 +飯 +飲 +係 +繫 +凍 +狀 +畝 +庫 +療 +應 +這 +廬 +閏 +閑 +間 +悶 +竈 +燦 +瀝 +淪 +滄 +溝 +滬 +瀋 +懷 +憂 +窮 +證 +啓 +評 +補 +識 +詐 +訴 +診 +詞 +譯 +靈 +層 +遲 +張 +際 +陸 +陳 +墜 +勁 +鷄 +緯 +驅 +純 +紗 +綱 +納 +駁 +縱 +紛 +紙 +紋 +紡 +驢 +紐 +環 +責 +現 +錶 +規 +攏 +揀 +擔 +頂 +擁 +勢 +攔 +擰 +撥 +擇 +蘋 +範 +莖 +樞 +櫃 +闆 +鬆 +槍 +楓 +構 +喪 +畫 +棗 +賣 +鬱 +礬 +礦 +碼 +厠 +奮 +態 +歐 +毆 +壟 +轟 +頃 +轉 +斬 +輪 +軟 +齒 +虜 +腎 +賢 +國 +暢 +嚨 +鳴 +羅 +幟 +嶺 +凱 +敗 +賬 +販 +貶 +購 +貯 +圖 +釣 +製 +颳 +俠 +僥 +偵 +側 +憑 +僑 +貨 +質 +徑 +捨 +覓 +貪 +貧 +膚 +腫 +脹 +骯 +脅 +魚 +獰 +備 +飾 +飽 +飼 +變 +龐 +廟 +瘧 +劑 +廢 +閘 +鬧 +鄭 +捲 +單 +爐 +淺 +濘 +瀉 +潑 +澤 +憐 +學 +寶 +寵 +審 +簾 +實 +試 +詩 +誠 +襯 +視 +話 +誕 +詭 +詢 +該 +詳 +肅 +録 +隸 +彌 +瀰 +陝 +駕 +參 +艱 +綫 +練 +組 +紳 +細 +駛 +織 +駒 +終 +駐 +絆 +駝 +紹 +繹 +經 +貫 +貳 +幫 +項 +挾 +撓 +趙 +擋 +墊 +擠 +揮 +薦 +帶 +繭 +蕩 +榮 +葷 +熒 +鬍 +蔭 +藥 +標 +棧 +棟 +欄 +檸 +樹 +鹹 +磚 +硯 +麵 +牽 +鷗 +殘 +軸 +輕 +鴉 +戰 +點 +臨 +覽 +竪 +嘗 +啞 +顯 +貴 +蝦 +蟻 +螞 +雖 +駡 +勛 +嘩 +響 +喲 +峽 +罰 +賤 +貼 +貽 +鈣 +鈍 +鈔 +鍾 +鐘 +鋼 +鈉 +鑰 +欽 +鈞 +鈎 +鈕 +氈 +氫 +選 +適 +種 +鞦 +復 +複 +倆 +貸 +順 +儉 +須 +鬚 +劍 +朧 +膽 +勝 +狹 +獅 +獨 +獄 +貿 +餌 +饒 +蝕 +餃 +餅 +巒 +彎 +將 +奬 +瘡 +瘋 +親 +閨 +聞 +閩 +閥 +閣 +養 +薑 +類 +婁 +總 +煉 +爍 +爛 +窪 +潔 +灑 +澆 +濁 +測 +瀏 +濟 +渾 +濃 +惱 +舉 +覺 +憲 +竊 +誡 +誣 +語 +襖 +誤 +誘 +誨 +説 +誦 +墾 +晝 +費 +遜 +隕 +險 +嬌 +賀 +壘 +綁 +絨 +結 +繞 +驕 +繪 +給 +絢 +駱 +絡 +絶 +絞 +駭 +統 +艷 +蠶 +頑 +盞 +撈 +載 +趕 +鹽 +損 +撿 +摯 +剝 +熱 +搗 +壺 +聶 +萊 +蓮 +獲 +穫 +惡 +噁 +瑩 +鶯 +檔 +橋 +樺 +樁 +樣 +賈 +礫 +礎 +顧 +轎 +較 +頓 +斃 +緻 +慮 +監 +緊 +黨 +曬 +曉 +嘮 +鴨 +暈 +鴦 +罷 +圓 +賊 +賄 +賂 +贜 +錢 +鉗 +鑽 +鉀 +鐵 +鈴 +鉛 +犧 +敵 +積 +稱 +筆 +債 +傾 +賃 +艦 +艙 +聳 +愛 +頒 +頌 +臟 +髒 +臍 +膠 +腦 +膿 +鴕 +鴛 +皺 +餓 +餒 +戀 +槳 +漿 +準 +癥 +齋 +離 +資 +競 +閲 +煩 +燒 +燭 +遞 +濤 +澇 +渦 +塗 +滌 +潤 +澗 +漲 +燙 +澀 +憫 +寬 +傢 +賓 +竅 +請 +諸 +諾 +讀 +誹 +襪 +課 +誰 +調 +諒 +諄 +談 +誼 +懇 +劇 +難 +預 +絹 +綉 +驗 +繼 +駿 +瑣 +擲 +據 +摻 +職 +蘿 +螢 +營 +蕭 +薩 +夢 +檢 +醖 +碩 +聾 +襲 +輔 +輛 +顱 +懸 +躍 +纍 +囉 +嘯 +嶄 +邏 +嬰 +銬 +鐺 +鋁 +銅 +銘 +鏟 +銀 +矯 +穢 +籠 +償 +軀 +釁 +銜 +盤 +鴿 +斂 +領 +臉 +獵 +餡 +館 +癢 +鏇 +閻 +闡 +蓋 +斷 +獸 +鴻 +漸 +淵 +漁 +澱 +滲 +慚 +懼 +驚 +慘 +慣 +謀 +諜 +謊 +諧 +禱 +禍 +謂 +諺 +謎 +彈 +墮 +隨 +隱 +嬸 +頗 +頸 +績 +緒 +續 +騎 +綽 +繩 +維 +綿 +綳 +綢 +綜 +綻 +緑 +綴 +瓊 +趨 +攬 +攙 +擱 +摟 +攪 +聯 +蔣 +韓 +橢 +確 +頰 +靂 +暫 +翹 +輩 +鑿 +輝 +賞 +睞 +噴 +疇 +踐 +遺 +鵑 +賦 +賭 +贖 +賜 +賠 +鑄 +鋪 +鏈 +銷 +鎖 +鋤 +鍋 +銹 +鋒 +鋅 +鋭 +鵝 +築 +篩 +儲 +懲 +禦 +釋 +臘 +魯 +憊 +饋 +饞 +裝 +蠻 +闊 +糞 +滯 +濕 +潰 +濺 +灣 +憤 +竄 +窩 +褲 +禪 +謝 +謡 +謗 +謙 +屬 +屢 +緬 +纜 +緝 +緞 +緩 +締 +縷 +騙 +編 +騷 +緣 +鵡 +攝 +擺 +襬 +攤 +鵲 +藍 +濛 +懞 +矇 +獻 +欖 +樓 +賴 +礙 +尷 +霧 +輻 +輯 +輸 +頻 +齡 +鑒 +蹺 +蝸 +錯 +錨 +錫 +鑼 +錘 +錐 +錦 +鍵 +鋸 +錳 +辭 +頽 +籌 +簽 +籤 +簡 +膩 +鵬 +騰 +鮑 +穎 +觸 +雛 +饃 +餾 +醬 +謄 +糧 +數 +滿 +濾 +濫 +灕 +濱 +灘 +譽 +窺 +寢 +謹 +謬 +闢 +縛 +縫 +纏 +繽 +贅 +墻 +衊 +藹 +檻 +釀 +願 +轄 +輾 +顆 +踴 +蠟 +蠅 +蟬 +賺 +鍬 +鍛 +鍍 +穩 +籮 +簫 +輿 +鮮 +饅 +瀟 +賽 +譚 +譜 +騾 +縮 +攆 +聰 +藴 +櫻 +飄 +黴 +瞞 +題 +囑 +鎮 +鎬 +鎊 +簍 +鯉 +鯽 +癟 +癱 +顔 +鯊 +瀾 +額 +譴 +鶴 +繚 +顛 +轍 +鸚 +贈 +鏡 +贊 +籃 +籬 +鯨 +癮 +辯 +瀕 +懶 +繮 +繳 +矚 +贍 +鰐 +辮 +贏 +驟 +囂 +鐮 +鰭 +鷹 +巔 +顫 +癬 +鱉 +鬢 +鱗 +躪 +贛 +鑲 +韋 +閂 +訃 +勱 +芻 +鄺 +訐 +訌 +訕 +訖 +馭 +璣 +壙 +捫 +薌 +厙 +釔 +傴 +倀 +傖 +獷 +獁 +鳬 +鄔 +餳 +懺 +謳 +詎 +訥 +紆 +紂 +紇 +紈 +璵 +摶 +塢 +㩳 +蕓 +藶 +莧 +萇 +蓯 +磯 +奩 +歟 +軔 +鄴 +嘸 +囈 +嚦 +暘 +唄 +幃 +峴 +嵐 +圇 +釗 +釙 +釕 +僉 +鳩 +鄒 +飩 +餼 +飪 +飫 +飭 +廡 +癤 +闈 +閎 +閔 +煬 +灃 +漚 +渢 +潙 +憮 +慪 +愾 +悵 +愴 +詁 +訶 +詛 +詆 +謅 +詔 +詒 +隴 +陘 +嫵 +嫗 +嬀 +剄 +紜 +紕 +紝 +綸 +紓 +瑋 +匭 +壚 +擓 +蘢 +蔦 +塋 +煢 +櫪 +梘 +棖 +樅 +碭 +甌 +郟 +軛 +鳶 +曇 +蟣 +黽 +嚀 +噝 +巋 +劌 +剴 +嶧 +釷 +釺 +釧 +釩 +釹 +釵 +儈 +儕 +儂 +劊 +慫 +糴 +戧 +膞 +邇 +梟 +餞 +飴 +癘 +瘍 +煒 +熰 +熗 +瀧 +瀘 +濼 +涇 +㥮 +懌 +誆 +誄 +詿 +詰 +詼 +鄆 +禕 +誅 +詵 +詬 +詮 +詣 +諍 +詫 +諢 +詡 +駑 +紺 +紲 +紱 +駟 +駙 +縐 +絀 +驛 +駘 +瓏 +頇 +埡 +撾 +撻 +賁 +壋 +撏 +莢 +貰 +蓽 +蕎 +薈 +薺 +堊 +滎 +犖 +蕁 +藎 +蓀 +蕒 +葤 +櫛 +櫳 +櫨 +櫟 +檉 +酈 +硨 +碸 +殤 +軲 +軻 +轤 +軼 +軫 +蠆 +覘 +瞘 +嘵 +嗶 +噦 +剮 +鄖 +噲 +噥 +嶢 +幀 +嶠 +貺 +鈈 +鈦 +鋇 +鈑 +鈐 +鎢 +鈁 +鈀 +篤 +儔 +儼 +儷 +腖 +臚 +脛 +鴇 +獪 +颮 +猻 +餉 +餄 +餎 +孿 +孌 +癧 +瘲 +颯 +闥 +閭 +闓 +閡 +熾 +烴 +浹 +澮 +滸 +潯 +濜 +慟 +懨 +愷 +惻 +惲 +誚 +禰 +誥 +誑 +鴆 +婭 +嬈 +懟 +絝 +驍 +驊 +絎 +絳 +駢 +頊 +璫 +琿 +塒 +塤 +堝 +贄 +蒔 +萵 +蕕 +鴣 +蒓 +橈 +楨 +榿 +檜 +邐 +礪 +礱 +軾 +輊 +輅 +鶇 +躉 +齔 +鸕 +矓 +嘜 +鴞 +蜆 +嗩 +嶗 +崍 +覬 +賅 +鈺 +鉦 +鈷 +鉢 +鈸 +鉞 +鉭 +鉬 +鈿 +鈾 +鉑 +鑠 +鉚 +鈰 +鉉 +鉈 +鉍 +鈮 +鈹 +鏺 +鐸 +氬 +筧 +頎 +徠 +膾 +鴟 +璽 +鴝 +獫 +裊 +餑 +欒 +攣 +癰 +痙 +頏 +閫 +鬮 +誾 +閬 +鄲 +燁 +燴 +燼 +淶 +漣 +潿 +慳 +諏 +諑 +禎 +諉 +諛 +諗 +諂 +誶 +媧 +嫻 +綆 +驪 +綃 +騁 +綏 +縧 +綈 +駸 +鷥 +燾 +璉 +麩 +擄 +摑 +鷙 +撣 +慤 +摜 +縈 +槤 +覡 +欞 +嗇 +匱 +硤 +磽 +鴯 +龔 +殞 +殮 +賚 +輒 +塹 +嘖 +囀 +嚙 +蹌 +蠣 +蠱 +蟶 +幘 +幗 +賕 +賑 +賒 +銠 +鉺 +鋏 +鐃 +銦 +鎧 +鍘 +銖 +銑 +鋌 +鏵 +銓 +鎩 +鉿 +銚 +鉻 +錚 +銫 +鉸 +銥 +銃 +銨 +銣 +鴰 +穠 +箋 +籩 +僨 +僂 +皚 +鴴 +艫 +龕 +玀 +獼 +餜 +餛 +鸞 +闍 +閾 +閹 +閶 +鬩 +閽 +閼 +羥 +糲 +燜 +漬 +瀆 +澠 +愜 +憚 +諶 +諫 +皸 +謔 +襠 +謁 +諤 +諭 +諼 +讒 +諳 +諦 +諞 +糶 +嬋 +綾 +騏 +綺 +緋 +緔 +騍 +緄 +騅 +綬 +綹 +綣 +綰 +驂 +緇 +靚 +輦 +黿 +頡 +撳 +蟄 +壪 +蔞 +櫝 +欏 +賫 +鵓 +鸝 +殫 +輥 +輞 +槧 +輟 +輜 +瞼 +躒 +蛺 +蟯 +螄 +蠐 +嘍 +嶸 +嶁 +賧 +鋙 +錸 +鏗 +鋥 +鋰 +鋯 +鋨 +銼 +鐧 +銻 +鋃 +鋦 +錒 +犢 +鵠 +篳 +牘 +儻 +儐 +儺 +嬃 +頜 +鵒 +魷 +魨 +魴 +潁 +颶 +觴 +熲 +餷 +餿 +褻 +臠 +癆 +癇 +賡 +頦 +鷳 +闌 +闃 +闋 +鵜 +憒 +嚳 +謨 +褳 +襇 +讜 +謖 +謚 +謐 +騭 +巰 +翬 +騖 +緙 +緗 +緘 +緹 +緲 +緦 +緱 +縋 +緡 +饗 +耮 +驁 +韞 +攄 +擯 +轂 +驀 +鶓 +薊 +蘺 +鎣 +頤 +櫚 +櫸 +磧 +磣 +鵪 +輳 +齟 +齙 +韙 +囁 +躂 +蹕 +躚 +躋 +噯 +鍺 +錛 +錡 +鍀 +錁 +錕 +錮 +鍁 +錈 +錠 +錙 +覦 +頷 +鮁 +鮃 +鮎 +鱸 +穌 +鮒 +鮐 +鵮 +颼 +饈 +鶉 +瘮 +闔 +闐 +闕 +灧 +瀅 +潷 +灤 +澦 +懾 +鱟 +騫 +竇 +謾 +謫 +嬡 +嬪 +縉 +縝 +縟 +轡 +騮 +縞 +縭 +縊 +縑 +騸 +覯 +韜 +靉 +攖 +薔 +藺 +鶘 +檳 +櫧 +釅 +殯 +霽 +轅 +齜 +齦 +瞜 +曖 +躊 +蟈 +鶚 +嚶 +羆 +賻 +罌 +鶻 +鍥 +鍇 +鍶 +鍔 +鍤 +鏘 +鎂 +鏤 +簀 +篋 +簞 +籙 +臏 +鮭 +鮪 +鱭 +鮫 +鱘 +饉 +鑾 +瘻 +闞 +鮝 +糝 +鷀 +瀲 +濰 +譖 +褸 +譙 +讕 +譎 +鶥 +嬙 +鶩 +驃 +縹 +縵 +縲 +纓 +驄 +繆 +繅 +耬 +瓔 +擷 +擼 +攛 +聵 +覲 +韃 +鞽 +蘄 +賾 +檣 +靨 +魘 +饜 +轆 +齬 +齪 +覷 +顒 +躓 +躑 +蠑 +螻 +顎 +嚕 +顓 +鑷 +鎘 +鎸 +鎳 +鎦 +鎰 +鎵 +鑌 +簣 +鷂 +鯁 +鱺 +鰱 +鰹 +鰣 +鯀 +鯇 +觶 +饊 +饌 +齏 +讞 +襤 +譫 +屨 +纈 +繕 +繒 +驏 +擻 +顳 +顢 +藪 +櫓 +櫞 +贋 +飆 +鏨 +轔 +蟎 +鐯 +鏢 +鏜 +鏝 +鏰 +鏞 +鏑 +鏃 +鏐 +氌 +穡 +魎 +鯪 +鯡 +鯤 +鯧 +鯝 +鯢 +鯛 +鯔 +獺 +鷓 +贇 +癭 +斕 +瀨 +顙 +繾 +繰 +繯 +蘚 +鷯 +齲 +齷 +躡 +蹣 +羈 +鐔 +鐝 +鐐 +鐓 +鑭 +鑹 +鏹 +鐙 +籪 +鷦 +鱝 +鰈 +鯷 +鰓 +鰍 +鰉 +鯿 +鷲 +懣 +鷸 +鰲 +韉 +顥 +鷺 +䴉 +髏 +鑊 +鐳 +鐲 +讎 +鰨 +鰥 +鰩 +癩 +攢 +靄 +躥 +髖 +髕 +鑔 +籟 +鰳 +鰾 +鱈 +鰻 +鱅 +讖 +驥 +纘 +瓚 +鼉 +黷 +黲 +鑣 +鑞 +臢 +鱖 +鱔 +鱒 +驤 +顰 +鱧 +癲 +灝 +鸛 +鑱 +趲 +顴 +躦 +饢 +戇 +戔 +訏 +訒 +釓 +俔 +閆 +澫 +訢 +訩 +詝 +紃 +纊 +瑒 +剗 +塸 +壢 +埨 +撝 +蔿 +榪 +軑 +軏 +咼 +㠣 +覎 +㑳 +颺 +閌 +潕 +湋 +澐 +浿 +諓 +禡 +詗 +詘 +詖 +屓 +彄 +紘 +馹 +馼 +紵 +紞 +駃 +紖 +瑲 +薴 +棡 +軝 +暐 +晛 +崬 +釴 +釤 +鍆 +鍚 +鄶 +獮 +飿 +嶨 +詷 +詪 +鄩 +鳲 +隑 +隮 +娙 +逕 +駓 +駔 +駉 +絅 +騶 +䮄 +紼 +紿 +瓅 +韍 +墶 +塏 +薘 +蕘 +蔄 +葒 +鳾 +龑 +軹 +軤 +轢 +軺 +睍 +曨 +噠 +鈃 +鈇 +鉅 +鋹 +釿 +錀 +鈧 +鈥 +鈄 +倈 +艤 +鶬 +颭 +餏 +湞 +溮 +滻 +褘 +絰 +駰 +絪 +駪 +綎 +綖 +驫 +勣 +璕 +𡑍 +䓣 +薟 +藭 +椏 +梜 +頍 +硜 +輄 +輈 +輇 +貲 +嗊 +曄 +暉 +鄳 +幬 +輋 +嶮 +贐 +鉥 +鉕 +鑪 +鉮 +鉊 +鉧 +僤 +鴒 +魛 +餗 +燖 +溳 +礐 +窵 +襏 +駼 +絺 +綌 +騂 +綄 +璡 +墠 +壼 +聹 +蘀 +勩 +罃 +檮 +棶 +厴 +䃮 +磑 +礄 +鴷 +齕 +頔 +廼 +凢 +亾 +枒 +屍 +匃 +匄 +紥 +紮 +疋 +殀 +讐 +觔 +兇 +宂 +㕥 +㠯 +栞 +佈 +佔 +呌 +敂 +冄 +坵 +僊 +怱 +悤 +冊 +夘 +戼 +牠 +妳 +嬭 +摃 +釦 +攷 +託 +衺 +衕 +弔 +喫 +囙 +㠶 +颿 +秊 +倣 +髣 +佀 +朶 +氷 +決 +併 +並 +竝 +汙 +汚 +異 +姦 +廵 +挵 +衖 +搤 +阯 +撦 +埳 +阬 +誌 +㕁 +卻 +刦 +刧 +刼 +芲 +蘤 +桿 +槓 +荳 +獃 +唫 +脗 +皁 +彿 +髴 +疘 +刪 +鉋 +鑤 +況 +牀 +恡 +棄 +洶 +汎 +災 +烖 +菑 +禩 +侷 +跼 +坿 +玅 +姉 +妬 +翫 +搨 +柺 +拕 +牴 +觝 +倖 +抝 +盃 +桮 +傑 +逩 +肎 +菓 +崐 +崑 +呪 +虖 +嘑 +謼 +詠 +㟁 +嵒 +巗 +巖 +雰 +稈 +咊 +嶽 +妷 +姪 +廹 +徃 +餚 +採 +寀 +唸 +週 +昬 +兎 +兔 +亯 +亱 +䘚 +淨 +劵 +匟 +㳒 +灋 +洩 +霑 +淚 +註 +恠 +箒 +屆 +絃 +圅 +旾 +珎 +掛 +垜 +艸 +茘 +査 +栢 +柵 +栁 +桺 +柹 +韮 +揹 +昰 +閧 +鬨 +冐 +暎 +嚥 +倃 +𠴰 +偺 +喒 +齩 +欬 +榘 +㑺 +儁 +敍 +敘 +肧 +脈 +䘑 +衇 +跡 +蹟 +砲 +礮 +薙 +鬀 +恆 +怳 +卹 +䘏 +賉 +婣 +畊 +揑 +綑 +輓 +恥 +躭 +晉 +棲 +覈 +慄 +翄 +脣 +槕 +㨪 +螡 +蟁 +㤙 +陗 +峩 +峯 +乗 +椉 +咲 +筍 +俛 +頫 +勌 +䠶 +躳 +慇 +拏 +㧱 +挐 +脃 +胷 +肐 +貍 +㽞 +畱 +淒 +悽 +蓆 +効 +傚 +涼 +缾 +菸 +煙 +淛 +湧 +誖 +猂 +醼 +讌 +㝠 +寃 +孃 +桒 +毬 +瑠 +璢 +瑯 +㨗 +搥 +搯 +蔆 +惏 +楳 +槑 +捄 +廂 +慽 +慼 +瞇 +埜 +畧 +虵 +稭 +棃 +犂 +迻 +媮 +兠 +舩 +慾 +綵 +腳 +𩓐 +夠 +豬 +貓 +湊 +減 +庻 +蔴 +菴 +朢 +睠 +觕 +麤 +釬 +銲 +痳 +殽 +婬 +滛 +湻 +㴱 +樑 +顇 +㝛 +窰 +窯 +琹 +欵 +墖 +趂 +隄 +愽 +揷 +揫 +煑 +朞 +㪚 +塟 +蔥 +蔕 +稜 +棊 +碁 +椶 +偪 +㕑 +廚 +廈 +鴈 +冣 +㝡 +晳 +鼃 +餧 +餵 +嗁 +諠 +㡌 +賸 +筴 +筞 +筩 +栰 +暠 +皜 +踰 +蝟 +㪟 +燄 +遊 +媿 +嘅 +庽 +窓 +牎 +牕 +窻 +徧 +僱 +帬 +裠 +強 +彊 +疎 +壻 +瓌 +䰟 +皷 +擕 +㩗 +㩦 +攜 +懃 +鞾 +幙 +㮣 +酧 +詶 +醻 +掽 +踫 +㼝 +盌 +磟 +覩 +倸 +㬉 +煗 +煖 +晻 +闇 +炤 +跥 +䗬 +蠭 +寘 +辠 +稺 +穉 +燬 +譭 +瘉 +癒 +顋 +骽 +猨 +蝯 +稟 +痺 +癡 +亷 +㢘 +韻 +泝 +遡 +昚 +躶 +臝 +羣 +㬪 +曡 +疊 +勦 +琍 +瓈 +𤋮 +熈 +牓 +搾 +謌 +堿 +鹻 +鹼 +矁 +燻 +髈 +𤺥 +辢 +旂 +𡚁 +潄 +砦 +詧 +嫰 +櫈 +撐 +墪 +譔 +鞵 +鞌 +蕋 +橤 +蘂 +醕 +譆 +跴 +蹤 +蜨 +蠍 +稾 +殭 +惪 +厀 +襃 +癅 +䊀 +餬 +潛 +癄 +顦 +鷰 +藷 +櫥 +螎 +蹏 +蟇 +譟 +簒 +彫 +琱 +鵰 +餹 +餻 +簷 +粦 +燐 +緐 +幑 +蹧 +粇 +穅 +臋 +籐 +繙 +飜 +孼 +蠏 +燿 +蝡 +稬 +穤 +惷 +覇 +鑵 +戹 +阨 +剳 +帀 +巵 +亙 +佇 +竚 +穽 +岅 +虯 +𦍑 +羗 +啎 +姙 +㘭 +袟 +袠 +逈 +㒺 +犛 +氂 +偘 +甕 +罋 +冺 +姍 +蝨 +琺 +瑇 +尅 +梔 +斮 +斲 +斵 +暱 +毘 +蝱 +吚 +哶 +峝 +粃 +竢 +狥 +秈 +烱 +㳄 +袵 +盇 +涖 +蒞 +碪 +蠔 +唕 +倐 +儵 +雋 +皐 +臯 +衂 +䶊 +臙 +獧 +痾 +皰 +湼 +澣 +濬 +塚 +襢 +娿 +勅 +勑 +戞 +廐 +廄 +眥 +覜 +勗 +啗 +噉 +傯 +挱 +㥫 +惥 +慂 +陻 +蕚 +萲 +蕿 +蘐 +藼 +櫂 +箠 +槨 +啑 +蹠 +蚘 +痐 +蛕 +蜖 +瘖 +遯 +醃 +飱 +冪 +簑 +枏 +柟 +檝 +楥 +矴 +椗 +嘷 +獋 +粺 +䈰 +諐 +齶 +堘 +疿 +雝 +秔 +稉 +槀 +搉 +廝 +叡 +嘠 +蜋 +筯 +篛 +麞 +糉 +緥 +璿 +髥 +臕 +餈 +剹 +橜 +罇 +蜺 +矙 +憇 +翺 +饍 +瞖 +羴 +羶 +爕 +繦 +騌 +鬉 +騣 +蔾 +䠀 +簮 +躕 +蹵 +䝔 +貛 +鼴 +麐 +塡 +あ +い +う +え +お +か +き +く +け +こ +さ +し +す +せ +そ +た +ち +つ +て +と +な +に +ぬ +ね +の +は +ひ +ふ +へ +ほ +ま +み +む +め +も +や +ゆ +よ +ら +り +る +れ +ろ +わ +を +ん +が +ぎ +ぐ +げ +ご +ざ +じ +ず +ぜ +ぞ +だ +ぢ +づ +で +ど +ば +び +ぶ +べ +ぼ +ぱ +ぴ +ぷ +ぺ +ぽ +ぁ +ぃ +ぅ +ぇ +ぉ +っ +ゃ +ゅ +ょ +ゎ +ゕ +ゖ +ア +イ +ウ +エ +オ +カ +キ +ク +ケ +コ +サ +シ +ス +セ +ソ +タ +チ +ツ +テ +ト +ナ +ニ +ヌ +ネ +ノ +ハ +ヒ +フ +ヘ +ホ +マ +ミ +ム +メ +モ +ヤ +ユ +ヨ +ラ +リ +ル +レ +ロ +ワ +ヲ +ン +ガ +ギ +グ +ゲ +ゴ +ザ +ジ +ズ +ゼ +ゾ +ダ +ヂ +ヅ +デ +ド +バ +ビ +ブ +ベ +ボ +パ +ピ +プ +ペ +ポ +ァ +ィ +ゥ +ェ +ォ +ッ +ャ +ュ +ョ +ヮ +ヵ +ヶ +ヷ +ヸ +ヹ +ヺ +・ +ー +ヽ +ヾ +ヿ +ア +イ +ウ +エ +オ +カ +キ +ク +ケ +コ +サ +シ +ス +セ +ソ +タ +チ +ツ +テ +ト +ナ +ニ +ヌ +ネ +ノ +ハ +ヒ +フ +ヘ +ホ +マ +ミ +ム +メ +モ +ヤ +ユ +ヨ +ラ +リ +ル +レ +ロ +ワ +ヲ +ン +゙ +゚ +ァ +ィ +ゥ +ェ +ォ +ッ +ャ +ュ +ョ +円 +気 +糸 +絵 +楽 +帰 +戸 +広 +黒 +図 +線 +読 +売 +歩 +毎 +亜 +悪 +圧 +扱 +囲 +為 +壱 +隠 +栄 +営 +駅 +塩 +縁 +艶 +応 +桜 +穏 +仮 +価 +箇 +ゑ +ゝ +ゞ +ヰ +ヴ +㈱ +両 +丼 +丿 +亀 +仏 +伝 +侶 +俤 +値 +倶 +倹 +偐 +偽 +働 +儛 +兌 +児 +冑 +冨 +凞 +処 +凪 +別 +剣 +剤 +剰 +劔 +労 +勧 +勲 +匁 +匂 +匲 +卍 +単 +厳 +収 +呂 +呉 +呑 +呰 +唖 +喚 +喩 +喰 +噛 +噺 +嚢 +囃 +団 +圀 +圏 +堀 +堺 +塀 +塁 +塙 +増 +墺 +壊 +壌 +壷 +変 +奨 +姫 +娯 +嫐 +嬢 +嬾 +孁 +宍 +実 +宮 +寔 +寛 +対 +専 +尭 +峠 +崋 +嶋 +巀 +巌 +巣 +巻 +帯 +幇 +庁 +廃 +廻 +弉 +弌 +弐 +弖 +弾 +従 +徳 +徴 +忯 +恵 +悩 +惣 +懐 +懽 +戦 +戯 +戻 +払 +抜 +択 +拝 +拠 +拡 +拵 +挙 +挿 +捗 +捜 +掟 +掲 +掻 +揃 +換 +揺 +摂 +撃 +撹 +斉 +斎 +旛 +旡 +晧 +晩 +暁 +暦 +曽 +杁 +杢 +杣 +杮 +枓 +枠 +枡 +柾 +栂 +栃 +桝 +桟 +桾 +梛 +梱 +梲 +梶 +椙 +検 +椥 +楕 +楡 +楢 +榊 +榎 +槇 +様 +槙 +槻 +樋 +権 +樫 +橿 +檥 +欅 +歎 +歓 +歯 +歳 +歴 +毀 +沖 +沢 +浄 +涙 +済 +渉 +渋 +渓 +渕 +満 +滝 +漑 +潅 +澁 +瀞 +瀬 +焔 +焼 +煇 +煕 +煥 +燗 +爼 +犠 +狛 +猟 +獏 +獣 +珊 +瑤 +甞 +畑 +畠 +畳 +畷 +畺 +痩 +癪 +発 +県 +眞 +砕 +碕 +礒 +禖 +禿 +稲 +穂 +穣 +竃 +竜 +竴 +笹 +筈 +筬 +筰 +箆 +箏 +箙 +篠 +篭 +簺 +籾 +粂 +粋 +粛 +粧 +糺 +紬 +絁 +経 +絖 +絣 +絽 +継 +続 +綟 +総 +縄 +縅 +縒 +縦 +繊 +繋 +繍 +繝 +繧 +纐 +纒 +罠 +罧 +罵 +羂 +羇 +羨 +聟 +聡 +聨 +聴 +脇 +脳 +膣 +膵 +臈 +臓 +臥 +舎 +舖 +舗 +舘 +芿 +苅 +茲 +荊 +荘 +莬 +莵 +菫 +萠 +蔵 +薗 +薫 +薬 +薭 +蘊 +蛍 +蝋 +蝿 +蟷 +衞 +衵 +袙 +袞 +袰 +袴 +袿 +裃 +裡 +裲 +褄 +褌 +襴 +襷 +覗 +覚 +覧 +観 +訳 +証 +諌 +諚 +諟 +諡 +諮 +譛 +譲 +讃 +豅 +豊 +豎 +賎 +賛 +贔 +躙 +躰 +転 +軽 +輌 +辥 +辺 +辻 +込 +逓 +遅 +遙 +邉 +郷 +酔 +醗 +醤 +醸 +釈 +鉄 +鉇 +鉤 +鉱 +鉾 +銈 +銕 +銭 +鋲 +鋳 +鋺 +錆 +錍 +錣 +錬 +錵 +鍑 +鍮 +鍼 +鎌 +鎗 +鎚 +鎹 +鐇 +鐚 +鐡 +鑁 +鑑 +鑚 +鑢 +閇 +関 +閦 +闘 +陥 +険 +隣 +隷 +雑 +雫 +霊 +靜 +靫 +靭 +靱 +鞄 +鞆 +頚 +頬 +頴 +頼 +顕 +顗 +餝 +饂 +駄 +駆 +駈 +騒 +験 +騨 +髄 +髙 +髪 +髷 +鯖 +鯰 +鯱 +鰒 +鰯 +鰰 +鳰 +鴎 +鴫 +鵄 +鵞 +鵺 +鶏 +鹸 +麁 +麺 +麿 +黌 +黙 +鼈 +齢 +龗 +縯 +蟅 +坖 +祂 +鼂 +鱚 +蛻 +屌 +呾 +煔 +吶 +扥 +蚖 +銂 +尃 +夋 +鵼 +徬 +寳 +彡 +舨 +湳 +麼 +鍈 +崈 +鱣 +盺 +拺 +瑥 +茷 +焻 +奀 +驎 +鱰 +砢 +痟 +廱 +僜 +瘺 +鱊 +擥 +嶰 +淓 +跅 +浵 +媗 +璦 +煠 +檊 +媃 +峅 +躄 +鉟 +塽 +蟴 +鯮 +弍 +烒 +鵵 +妑 +孋 +蚡 +恊 +輭 +廞 +產 +曅 +盜 +騤 +囪 +鱀 +茇 +葊 +逹 +狓 +崢 +趖 +凃 +羙 +鮸 +昞 +楿 +渽 +圗 +麪 +屇 +鍉 +葝 +沯 +爭 +幵 +筭 +寊 +銋 +貮 +鎭 +熺 +昜 +鍱 +墬 +愒 +磺 +嚈 +稘 +珮 +釆 +殑 +鍩 +䲁 +蕷 +鐿 +僡 +佹 +輶 +冴 +襶 +賔 +猙 +辧 +絛 +磾 +韁 +螔 +譳 +礑 +鋱 +魩 +嚗 +棆 +牆 +敟 +柶 +瓛 +魣 +巎 +轘 +襌 +枼 +鸌 +逺 +錏 +縡 +帢 +騄 +媼 +埅 +鄤 +萐 +祙 +旼 +詥 +鶲 +燉 +卲 +銱 +庲 +伱 +氽 +嵿 +挻 +煵 +窋 +鐤 +鮊 +鱬 +鰧 +嬤 +譞 +諲 +脭 +悳 +崘 +阭 +內 +袾 +冚 +壐 +咗 +礠 +孮 +痲 +埈 +肹 +鰮 +鮓 +濊 +塜 +凜 +蒢 +噰 +桼 +峍 +焴 +鶒 +鋮 +綠 +鶹 +熿 +毴 +咟 +嘥 +睺 +繡 +郎 +瘞 +鉶 +蔎 +秠 +緤 +蝀 +躝 +蟜 +繃 +囮 +墫 +乭 +胊 +濙 +瘓 +榣 +鑛 +鐫 +嶴 +甹 +坮 +銾 +蒭 +睜 +俋 +餠 +榢 +蓳 +盋 +堷 +鍏 +苝 +巛 +蚵 +暏 +熤 +嬨 +墎 +鏽 +戶 +菺 +膮 +熖 +睪 +栜 +捱 +榗 +鍷 +曧 +犽 +韑 +袓 +䖝 +焄 +喦 +髲 +疌 +㴪 +侊 +貐 +蕅 +禠 +蕑 +囯 +暊 +儞 +佋 +柎 +㐱 +鰤 +苳 +鱥 +謤 +遶 +眀 +鑀 +羋 +顏 +陜 +銩 +黶 +苼 +蒤 +棛 +儫 +咁 +抦 +衚 +棩 +焿 +脫 +麅 +玏 +埧 +淸 +黁 +淽 +彠 +鮨 +沜 +糀 +厓 +楧 +嶌 +簹 +檵 +鱇 +嶬 +廸 +卽 +樀 +贌 +酼 +籛 +沒 +晸 +諪 +蕡 +妏 +鄋 +蒍 +奧 +抇 +蓨 +薆 +鱷 +巘 +䝉 +亰 +寈 +槩 +誒 +麴 +蕟 +溎 +蘗 +榦 +斿 +暟 +炲 +拚 +娖 +繖 +橚 +寜 +爀 +饟 +悅 +鯏 +彜 +眾 +葯 +嬝 +埮 +獇 +馛 +溙 +瀦 +熼 +硓 +鈢 +樆 +輬 +鰜 +蔘 +渙 +澔 +嗮 +旉 +籜 +媊 +燘 +儚 +頹 +缽 +俽 +逨 +鱓 +郞 +歊 +杴 +珡 +杋 +醁 +鰏 +鵾 +鐽 +鮋 +巶 +荅 +薾 +囓 +蹻 +獎 +禑 +鎓 +榲 +僴 +綞 +尓 +敭 +曔 +褔 +鬅 +亊 +鏦 +蓘 +裬 +鱲 +薡 +鰗 +箑 +鬪 +縂 +璸 +甙 +茮 +辵 +岻 +覿 +滈 +鯶 +鑂 +囶 +舺 +溋 +拋 +菾 +敾 +虨 +綝 +蝍 +醂 +禨 +賹 +廧 +絕 +槗 +徫 +鎔 +曮 +蠂 +捒 +堈 +莕 +蓪 +敎 +禃 +櫱 +綧 +瀶 +逌 +浤 +碻 +刄 +逤 +剏 +氹 +菈 +娫 +蜛 +嵗 +糎 +螶 +譓 +鏳 +嵙 +瑊 +隲 +檨 +緈 +畵 +砯 +簗 +彅 +鰺 +騋 +窶 +嚒 +嵻 +尙 +頵 +槰 +虉 +醞 +巂 +彔 +偊 +畇 +鱨 +妸 +塲 +畐 +鈫 +錟 +磪 +摠 +彥 +璙 +囝 +寗 +耎 +鮡 +蘓 +弅 +焃 +飥 +戙 +塰 +儱 +槺 +噏 +魟 +禵 +佧 +咘 +盪 +瑈 +鉲 +睭 +鏌 +鼇 +郋 +魮 +朖 +滽 +渃 +滙 +熯 +醿 +鎅 +褀 +鬬 +巄 +螥 +眜 +釚 +柉 +壎 +峇 +姸 +唭 +鮜 +鈖 +嫈 +壄 +洤 +黃 +伕 +堦 +嶔 +鮰 +鞞 +漎 +鉓 +鮗 +壴 +阝 +妀 +矽 +獢 +倗 +銪 +鴓 +橒 +凈 +哖 +屚 +偍 +瑺 +媯 +淍 +驌 +椇 +赬 +薐 +糹 +碽 +濲 +釭 +晭 +纕 +寖 +閞 +歿 +呎 +鶆 +屄 +櫿 +犎 +旲 +㙟 +龎 +翜 +螾 +說 +衜 +泆 +軎 +鵂 +荎 +嚧 +硂 +桖 +褭 +筊 +鰷 +秳 +戩 +轀 +鬹 +飬 +卋 +暸 +狦 +搢 +娋 +鏴 +溫 +毉 +淰 +謩 +餺 +鵙 +鳽 +鮀 +狶 +氻 +轝 +妺 +袛 +蓭 +梂 +娛 +牼 +稅 +兿 +玾 +煚 +僩 +鶿 +鬄 +崠 +鉆 +鯓 +蚢 +庀 +鵟 +坣 +殼 +悞 +熅 +敻 +鍠 +曶 +愼 +搳 +姃 +砳 +槼 +臞 +韾 +靑 +鸊 +薲 +虛 +蠄 +啟 +鶺 +苺 +滾 +褞 +仺 +胇 +憻 +郳 +烉 +驩 +冇 +枖 +夌 +搵 +匸 +盨 +櫾 +霤 +麊 +貒 +噓 +嗢 +笩 +晈 +冂 +銳 +毿 +慜 +囧 +閜 +娸 +庢 +壆 +馯 +桱 +兗 +葃 +侅 +煐 +鐦 +藸 +鷎 +嵰 +逎 +弒 +匋 +鐭 +廔 +砩 +孆 +灴 +伷 +兪 +鴗 +澯 +幚 +旙 +勻 +礽 +婑 +鱮 +娍 +銶 +吳 +鍟 +仼 +鳧 +彞 +娽 +昛 +鰼 +剎 +佉 +鉏 +偸 +鰆 +讙 +橪 +啱 +岀 +孻 +釪 +乹 +鈳 +漇 +檦 +埻 +祿 +爌 +禇 +鱵 +㸃 +梉 +燝 +霙 +炁 +飮 +蠙 +勷 +鵎 +儥 +鐠 +唻 +廰 +嚿 +嵕 +墱 +紑 +搖 +瘜 +皝 +鸑 +瀁 +粵 +撚 +巑 +梀 +啯 +眛 +諴 +夊 +僙 +鍝 +裖 +鮣 +凬 +飡 +灊 +橓 +嫳 +筳 +咑 +粍 +瓑 +璌 +伃 +閰 +傜 +黐 +謢 +驒 +橫 +蛯 +寕 +蠵 +瞓 +旳 +翏 +硏 +寯 +韡 +楤 +鰃 +朿 +侞 +鵯 +愨 +祹 +厔 +丌 +盩 +謏 +魕 +啣 +閱 +曺 +枛 +罉 +卐 +樻 +鷉 +鯒 +鋡 +磱 +枱 +攴 +蠷 +穈 +嚟 +檽 +趐 +奐 +鋐 +檇 +薀 +峼 +咭 +訔 +韠 +鑴 +鸐 +唃 +捦 +鸜 +誴 +罳 +璄 +暃 +夀 +賨 +鞥 +鈊 +灡 +鮍 +懮 +籣 +昐 +陁 +襾 +鮠 +鈏 +囍 +婯 +艔 +貭 +䰾 +姁 +禼 +堖 +鋶 +仛 +鏷 +謜 +鑅 +忬 +蘶 +謠 +觙 +奫 +狟 +泩 +桙 +飈 +垰 +啍 +嚞 +鯕 +蒧 +榞 +徸 +璹 +揔 +欉 +魞 +菶 +玧 +鳯 +廍 +侚 +岰 +岧 +鋕 +凵 +彣 +崱 +媜 +倢 +鵐 +砋 +鷚 +鱠 +鮻 +繻 +摵 +贓 +磵 +錻 +痠 +粩 +胅 +奣 +塨 +瀠 +鸘 +啚 +娳 +霶 +壔 +峚 +甂 +廁 +覌 +鰂 +猳 +鱻 +盫 +裿 +杬 +歛 +澋 +蘞 +嵜 +尐 +旽 +鉌 +鎛 +豿 +凖 +榤 +禓 +龝 +悧 +鷟 +鮟 +吋 +喢 +岪 +吥 +漵 +頠 +豔 +巿 +鑨 +醣 +熳 +懍 +湥 +檡 +韺 +戱 +緖 +鐈 +凉 +緃 +鮹 +媐 +爯 +巆 +褍 +鐬 +昍 +扙 +鍳 +芛 +蟳 +嬅 +糬 +吔 +塭 +譿 +冧 +鏓 +嶪 +嗹 +椵 +姀 +閿 +褧 +錞 +玆 +笘 +篔 +萡 +鶡 +螐 +鮄 +鰟 +脷 +啲 +杤 +蓚 +尗 +娎 +殟 +淥 +蝚 +蓧 +彐 +嚤 +銍 +囒 +坶 +淩 +鶼 +鱂 +喼 +燫 +肏 +姵 +廌 +禟 +籝 +迵 +嵨 +堮 +蟌 +憍 +廕 +蜑 +緁 +唘 +竩 +崙 +璚 +粄 +栨 +罈 +梫 +貤 +藔 +蜯 +訁 +斖 +煶 +馦 +妠 +閟 +疕 +夆 +鎪 +膥 +澻 +嘢 +嚐 +靁 +鎻 +鰛 +穵 +烋 +縕 +褎 +疒 +壠 +溼 +圂 +咅 +鯭 +鯙 +磘 +玨 +珤 +朊 +蚼 +濶 +薞 +嚩 +丟 +嫺 +鯻 +椲 +鰕 +刂 +蠘 +踎 +瀴 +琁 +鰶 +瑴 +肜 +㐂 +欥 +媺 +竻 +讚 +𣇉 +裵 +緜 +廩 +齧 +叄 +俌 +厰 +滀 +錄 +鷫 +鯗 +攞 +姌 +蔝 +幷 +縤 +屻 +鯃 +雞 +纁 +嫲 +嵮 +屭 +嶃 +跩 +鋗 +蕢 +篊 +俬 +淎 +暻 +鏻 +憓 +玗 +溈 +笭 +糢 +勳 +閒 +沍 +咾 +鉷 +蘵 +俁 +崵 +毸 +苪 +掙 +鴡 +萭 +俴 +屜 +蒾 +艹 +剷 +慍 +朮 +枴 +氳 +猓 +甽 +箝 +譁 +贗 +迆 +鈽 +鍊 +鍰 +鏍 +靦 +餽 +丮 +丱 +仜 +仩 +伬 +伔 +仱 +伀 +伻 +佢 +佒 +侀 +侇 +佷 +佌 +佪 +侐 +侜 +俓 +侲 +俉 +侻 +侳 +俇 +倅 +倇 +倰 +倛 +倳 +倷 +俷 +倠 +偯 +偞 +偠 +偋 +偝 +偛 +偢 +偅 +偟 +偩 +偫 +傛 +傔 +傞 +傋 +傌 +傎 +傝 +偨 +傂 +傽 +傿 +僆 +傮 +僄 +僈 +傰 +僁 +傱 +僋 +僗 +僛 +僪 +僝 +僓 +僿 +儃 +儰 +僸 +僶 +僾 +儌 +僽 +儜 +儓 +儗 +儑 +儢 +儤 +儠 +儸 +儹 +儽 +冓 +冘 +冞 +凊 +凅 +凔 +刌 +刉 +刓 +刜 +刞 +刵 +刲 +剆 +刱 +剉 +剚 +剒 +剫 +剭 +剬 +剺 +剸 +剻 +剼 +劀 +劋 +劖 +劘 +劗 +劙 +劦 +勴 +匊 +匢 +匰 +匴 +匷 +匽 +卌 +卼 +厎 +厒 +厗 +厞 +厜 +厤 +厬 +厹 +吰 +吷 +吪 +呿 +咈 +呫 +呺 +呥 +呬 +呴 +茍 +咷 +咮 +咶 +哅 +咠 +咢 +唦 +唗 +唒 +哤 +唚 +唈 +哫 +唅 +唴 +啢 +唶 +啒 +啅 +唌 +唲 +喨 +喥 +喭 +噅 +喓 +喣 +啽 +喌 +嗃 +嗛 +嗋 +嗀 +喿 +喍 +嗏 +嗕 +嗈 +嘕 +嘒 +嗼 +嘐 +嘓 +嘂 +嗺 +嘝 +嘄 +嗿 +噈 +噊 +噆 +噚 +嘳 +嘽 +嘾 +噮 +噳 +噣 +噭 +噞 +嚌 +嚍 +嚃 +嚘 +嚜 +嚫 +嚪 +嚬 +嚲 +嚵 +嚽 +嚾 +囆 +囅 +囋 +囗 +圁 +圞 +圠 +坁 +坅 +坲 +坱 +垀 +坴 +垗 +垝 +垔 +垘 +垽 +垼 +埢 +埶 +堩 +堣 +塈 +堥 +塓 +塉 +塯 +塕 +塼 +墆 +塿 +塴 +墋 +塺 +墝 +墯 +壈 +墽 +壖 +壝 +壛 +壾 +壿 +夃 +夎 +夒 +夗 +奅 +奊 +奰 +奲 +奼 +妦 +妎 +妢 +妐 +妵 +姏 +姎 +㚷 +姡 +姺 +姼 +娭 +婐 +婟 +婥 +婓 +婗 +媔 +媟 +媢 +婸 +媦 +媥 +媬 +媕 +娷 +嫇 +嫋 +媰 +媻 +嫮 +嫥 +嫢 +嫛 +嫿 +嫴 +嫷 +嫶 +嬎 +嬓 +嬐 +嬲 +嬽 +孈 +屘 +孲 +孷 +宎 +宨 +寪 +寍 +寋 +寑 +寙 +寠 +寱 +尌 +尒 +尟 +尰 +尳 +屖 +屔 +屝 +屧 +屩 +屮 +屴 +岏 +岋 +岉 +岒 +岮 +岤 +岯 +岟 +岝 +峐 +峌 +峞 +峉 +峊 +峬 +峮 +峷 +崝 +崨 +崥 +崏 +崰 +崣 +崷 +嵃 +嵑 +崳 +崺 +嵂 +嵱 +嵣 +嵥 +嵞 +嶀 +嵽 +嶆 +嵺 +嵷 +嶊 +嶉 +嶈 +嵾 +嶕 +嶜 +嶡 +嶚 +嶞 +嶱 +嶩 +嶵 +嶭 +巃 +巏 +巕 +巟 +巹 +帊 +帗 +帟 +帣 +帠 +帤 +帩 +帾 +帴 +幏 +幎 +幓 +幩 +幝 +幠 +幧 +幨 +幦 +幭 +幰 +庂 +庉 +庌 +庈 +庰 +庛 +庣 +庨 +庮 +庪 +庬 +庴 +廅 +廇 +廘 +廗 +廎 +廜 +緳 +廦 +廥 +廮 +廯 +蠯 +廾 +弚 +弝 +弣 +弤 +弮 +弳 +彃 +彉 +彋 +彏 +彯 +彴 +彸 +彾 +徦 +徥 +徯 +徲 +徾 +徿 +忀 +忁 +忔 +忕 +忨 +忣 +忷 +忥 +怭 +怲 +怋 +怴 +怗 +怚 +怞 +怬 +怢 +怐 +怮 +怓 +怷 +怹 +恲 +恞 +恅 +恇 +恉 +恛 +恌 +恀 +恟 +悀 +悁 +悕 +悗 +悇 +悊 +悐 +悾 +悺 +惓 +惤 +惈 +悷 +惉 +悹 +惌 +惢 +惄 +愊 +愖 +愅 +惵 +愓 +惸 +惼 +惾 +慉 +慅 +愶 +愲 +愮 +愯 +愬 +慁 +慞 +慱 +慒 +慓 +慲 +憀 +慴 +慔 +慺 +慛 +憃 +慹 +憱 +憰 +憢 +憉 +憛 +憯 +憟 +憪 +憡 +憝 +憖 +懅 +憴 +懆 +懁 +憿 +憸 +憵 +憼 +懧 +懠 +懥 +懤 +懘 +懭 +懱 +懪 +懰 +懫 +懻 +戁 +戃 +戄 +戉 +戠 +酨 +戺 +扐 +扜 +扤 +扡 +扢 +抆 +抌 +抎 +抏 +扻 +抭 +抴 +拑 +抾 +抪 +抶 +抮 +挍 +挋 +挃 +拫 +拹 +挏 +挌 +拸 +挀 +拲 +捖 +挬 +挶 +揤 +捊 +挼 +挩 +捁 +挴 +捘 +捔 +捥 +掝 +掗 +掫 +掯 +捵 +掜 +捼 +掤 +掔 +掱 +揎 +揥 +揨 +揯 +揊 +揲 +揵 +摡 +揟 +揝 +揜 +揘 +揅 +揱 +搆 +搟 +搕 +搘 +搹 +搷 +搣 +搰 +搊 +搚 +摀 +搧 +搫 +摍 +摝 +摲 +摦 +摎 +摋 +摓 +摐 +摿 +摮 +摰 +撢 +撠 +撗 +撜 +撋 +撊 +撌 +撟 +擗 +擖 +擏 +擉 +撽 +擩 +擣 +擫 +擭 +擨 +擽 +擸 +攇 +攐 +攍 +攌 +攗 +攕 +攓 +攡 +攠 +攦 +攩 +攭 +攲 +攳 +敁 +敊 +敆 +敓 +敧 +敪 +敤 +敜 +敯 +敳 +敶 +敺 +敹 +敿 +斁 +斀 +斄 +斒 +斔 +斞 +斨 +斪 +斻 +旍 +旓 +旚 +旝 +旟 +昲 +昦 +昢 +晇 +晥 +晜 +晼 +晬 +暀 +暆 +暍 +暋 +暡 +暰 +暩 +曀 +曊 +曋 +曏 +曒 +曚 +曣 +曭 +朁 +朅 +朄 +朒 +朘 +朣 +朾 +朹 +朻 +朼 +杅 +杇 +杝 +杗 +枎 +杶 +枆 +枌 +柲 +枺 +枻 +柸 +柀 +柅 +柫 +柤 +柍 +柮 +柣 +柂 +柧 +栚 +桋 +桏 +栱 +栵 +栫 +栭 +栯 +栘 +栔 +梡 +梇 +梐 +桭 +梮 +楖 +梬 +梩 +桵 +梒 +椌 +椄 +棜 +棷 +棳 +棌 +椈 +楰 +棯 +椔 +棸 +楟 +楎 +楱 +楅 +楺 +楈 +楛 +楉 +楬 +椳 +楀 +楄 +楶 +楘 +榶 +槉 +榠 +榬 +榼 +榙 +榩 +榾 +榯 +槄 +榽 +榹 +槥 +槸 +樕 +樠 +槬 +槢 +樛 +樝 +槾 +樧 +槮 +樔 +槷 +橀 +樴 +橉 +橧 +樲 +橨 +橝 +橭 +橶 +樿 +橁 +檍 +檖 +檁 +檟 +橾 +檛 +檓 +檕 +檃 +櫅 +檹 +櫡 +櫠 +櫌 +櫑 +櫙 +櫋 +櫜 +櫐 +櫫 +櫬 +櫰 +櫹 +櫺 +櫼 +欃 +欋 +欈 +欐 +欑 +欘 +欨 +欴 +欯 +欭 +欱 +欶 +欳 +欷 +欿 +歂 +歈 +歍 +歋 +歕 +歔 +歜 +歠 +歭 +歾 +肂 +殈 +殏 +殔 +殗 +殙 +殠 +殥 +殢 +殦 +殧 +殰 +殶 +毃 +毄 +毈 +毇 +毊 +毚 +毞 +毦 +毤 +毨 +毣 +毰 +毲 +毻 +毼 +毾 +氁 +氀 +氄 +氠 +氶 +汃 +汒 +汏 +汍 +汸 +沋 +汱 +汯 +沕 +汦 +汳 +泬 +沶 +沬 +泧 +沷 +泭 +泲 +泒 +沴 +洟 +洊 +洀 +浺 +浶 +洍 +涒 +浘 +浢 +涊 +涆 +浧 +涗 +涳 +涬 +淢 +涷 +淔 +渀 +淈 +涾 +淊 +涽 +淭 +湆 +湇 +湅 +湢 +渿 +湁 +渜 +渳 +湀 +渻 +渮 +湨 +湡 +渱 +渨 +湠 +湱 +湩 +渹 +溛 +滖 +溓 +溔 +滒 +溰 +溾 +滜 +滵 +滱 +漃 +漥 +漮 +潎 +漙 +漧 +漘 +漒 +滭 +漊 +潳 +滮 +潀 +漰 +潃 +漅 +濆 +澒 +澅 +潚 +潠 +澖 +潶 +潬 +潒 +潐 +潗 +澓 +潝 +濇 +濎 +濈 +濄 +澞 +澨 +瀄 +濌 +澩 +濴 +濔 +濣 +濭 +濧 +濦 +瀇 +瀎 +濿 +瀀 +濻 +瀙 +瀖 +瀫 +瀡 +瀢 +瀩 +瀯 +瀷 +灂 +瀸 +瀿 +瀺 +灄 +灉 +灖 +灗 +灛 +灟 +灨 +灩 +灪 +炾 +炰 +烓 +烑 +缹 +焍 +烰 +焠 +焮 +焣 +煆 +煣 +煝 +熐 +熉 +熀 +熂 +熚 +燅 +燂 +熸 +燀 +燡 +爁 +爊 +爂 +爓 +爞 +爢 +爣 +牄 +牉 +牋 +牏 +牣 +牬 +牰 +牸 +牷 +犈 +犉 +犆 +犅 +犌 +犑 +犐 +犗 +犕 +犓 +犘 +犚 +犝 +犞 +犥 +犦 +犤 +犣 +犩 +犪 +犮 +犵 +犿 +狆 +狖 +狋 +狘 +狜 +狔 +狚 +狌 +狑 +狊 +狤 +狫 +狪 +狣 +猀 +狾 +猑 +猘 +猈 +狿 +猏 +猋 +猒 +猧 +猲 +猭 +猦 +猣 +猵 +猼 +獂 +獀 +獊 +獑 +獌 +獘 +獞 +獟 +獝 +獛 +獡 +獩 +獦 +獥 +獳 +獶 +獽 +獿 +玂 +玁 +玈 +玊 +玔 +珓 +珶 +琖 +瑵 +璊 +瑽 +璅 +瑿 +璗 +瓁 +瓋 +瓝 +瓟 +瓡 +瓥 +瓨 +瓬 +瓵 +瓾 +瓽 +甀 +甃 +甈 +甋 +甐 +甒 +甔 +甖 +甝 +甮 +甿 +畟 +畣 +畽 +疀 +疧 +痁 +疻 +痀 +痎 +痏 +痋 +痌 +痑 +痚 +痡 +痝 +痗 +痯 +瘏 +痷 +痸 +痻 +瘈 +瘑 +瘝 +瘣 +瘯 +瘱 +瘽 +癈 +癉 +癙 +癐 +癓 +癠 +癵 +癹 +皊 +皏 +皫 +皯 +皵 +皻 +皽 +皾 +盄 +盓 +盝 +盬 +盭 +盳 +眃 +眅 +盻 +眝 +眐 +眓 +眒 +眣 +眑 +眕 +眹 +眱 +眲 +眴 +眳 +眽 +睆 +睅 +睊 +睋 +睌 +睕 +睟 +睒 +睖 +睩 +睧 +睔 +瞁 +睼 +瞂 +睮 +睯 +瞏 +瞉 +瞚 +瞝 +瞡 +瞛 +瞲 +瞷 +瞶 +瞴 +矂 +矉 +矊 +矌 +矎 +矏 +矐 +矔 +矕 +矘 +矠 +矱 +矲 +矹 +矺 +砅 +砐 +砏 +砎 +砨 +硈 +硉 +硠 +硥 +硱 +硰 +硩 +碔 +碄 +碅 +碆 +硾 +碫 +碞 +磍 +磌 +磎 +磈 +磃 +磝 +磩 +磥 +磞 +磛 +磳 +磼 +磿 +礔 +礉 +礝 +礛 +礜 +礥 +礣 +礧 +礨 +礭 +礿 +祌 +祅 +祔 +祒 +祑 +祤 +祩 +祪 +祣 +祫 +祡 +祴 +祳 +禂 +禗 +禜 +禫 +禭 +禬 +禴 +禷 +禸 +歶 +秅 +秏 +秖 +秎 +秮 +秪 +秺 +秶 +稊 +稒 +稫 +穊 +稰 +稯 +穋 +穛 +穖 +穧 +穨 +穮 +穬 +穭 +穱 +穾 +窆 +窉 +窌 +窏 +窔 +窐 +窙 +窢 +窞 +窫 +窲 +窴 +窱 +窾 +竀 +竁 +竷 +笐 +笓 +笅 +笵 +笻 +笴 +笰 +笢 +笝 +笲 +筄 +筡 +箈 +箊 +箌 +箛 +箎 +箘 +箄 +箷 +箾 +篎 +箯 +箹 +篞 +篣 +篧 +篕 +篨 +篹 +簅 +篲 +篿 +篻 +簎 +篴 +簂 +簁 +篸 +篽 +簜 +簩 +簙 +簭 +簦 +簨 +簢 +簥 +簳 +簼 +簬 +簻 +籉 +籈 +籊 +籔 +籗 +籧 +籦 +籯 +籺 +籸 +籹 +粊 +粔 +粻 +糔 +糪 +糱 +糷 +紎 +紟 +紒 +紽 +紸 +紶 +紩 +絇 +紾 +絘 +絯 +絓 +絧 +絏 +絭 +絫 +綀 +綍 +絿 +綅 +絻 +絼 +綔 +綷 +緂 +綪 +緀 +緅 +緎 +緆 +緌 +綯 +綼 +緷 +緛 +緪 +緧 +縃 +緺 +緶 +緰 +縗 +縌 +縓 +縎 +縜 +縚 +縏 +縼 +繂 +縳 +顈 +繈 +縸 +縪 +繉 +繀 +縩 +緵 +縰 +縿 +縶 +繜 +繐 +繣 +繘 +繢 +繟 +繑 +繠 +繶 +繵 +繸 +繷 +繺 +繲 +繴 +纀 +纇 +纋 +纆 +纑 +纗 +纚 +缿 +罊 +罏 +罜 +罞 +罝 +罛 +罣 +罥 +罦 +罭 +罫 +罬 +罻 +罼 +罺 +罿 +羃 +羉 +羍 +羒 +羜 +羛 +羢 +羠 +羦 +羬 +羭 +羵 +羳 +羷 +羺 +羾 +翋 +翍 +翐 +翑 +翇 +翢 +翣 +翭 +翪 +翨 +翴 +翲 +翽 +翿 +耟 +耞 +耡 +耴 +耾 +耹 +聇 +聈 +聑 +聏 +聝 +肕 +肙 +肒 +肣 +肵 +胘 +胑 +胐 +胕 +胉 +胏 +胹 +胵 +脁 +胻 +脀 +胾 +胔 +脰 +脥 +脤 +脙 +脡 +脕 +脧 +腃 +腏 +腄 +腇 +脽 +腍 +腤 +腷 +腜 +腛 +腢 +腲 +朡 +腞 +腶 +膉 +膆 +膃 +膇 +膍 +膌 +膋 +膟 +膕 +膢 +膱 +膹 +膫 +膰 +膬 +膴 +膲 +臇 +膷 +臄 +臅 +臒 +臐 +臗 +臛 +臡 +臦 +臩 +臮 +臲 +臷 +臸 +臿 +舋 +舑 +舕 +舝 +舡 +舼 +舽 +艀 +艂 +艓 +艒 +艐 +艑 +艕 +艛 +艵 +艼 +芀 +芐 +芅 +芓 +芔 +苀 +芚 +芵 +芧 +芞 +芺 +苙 +苨 +苖 +苬 +苲 +苵 +苶 +茙 +茥 +茿 +茦 +茢 +荂 +茪 +荍 +茖 +茤 +茠 +茩 +茻 +莐 +莣 +莍 +荺 +莤 +荴 +莏 +莁 +荵 +莔 +莃 +莌 +莋 +荾 +莥 +菨 +萒 +菧 +菤 +菆 +菣 +菿 +菋 +菎 +菵 +萉 +菞 +菳 +菕 +蓱 +萿 +葹 +葥 +葀 +葧 +萰 +葍 +葽 +蔇 +葞 +萷 +萺 +萴 +葅 +菙 +葋 +萯 +葂 +葟 +葌 +蓎 +蒬 +蒮 +蒫 +蒪 +蒚 +蒝 +蓌 +蒛 +蒩 +蒘 +蒶 +蒠 +蔤 +蔏 +蔩 +蔉 +蔍 +蔧 +蔜 +蓻 +蓺 +蓴 +蔪 +蓲 +蓷 +蓫 +蔒 +蓩 +蔖 +蓾 +蔨 +蔮 +蔂 +蓶 +蔱 +蓹 +蔠 +蔰 +蕫 +蕍 +蕀 +蕆 +蕄 +蕇 +蕣 +蕛 +蕱 +蕵 +蕮 +蕧 +蕠 +蕦 +蕝 +薃 +薧 +薕 +薠 +薋 +薣 +薚 +蕼 +薉 +蕸 +薎 +薖 +薍 +薝 +薂 +藆 +藀 +藃 +藂 +薵 +薽 +藇 +藄 +藋 +藈 +藅 +薱 +薶 +藒 +藫 +藱 +藙 +藡 +藚 +藗 +藲 +藬 +藘 +藣 +藑 +藰 +蘁 +藾 +蘛 +蘉 +蘌 +蘪 +蘦 +蘟 +蘣 +蘜 +蘙 +蘮 +蘡 +蘠 +蘥 +蘴 +蘳 +蘬 +虀 +蘹 +蘱 +蘻 +蘾 +虃 +虆 +虇 +虈 +虌 +虋 +虙 +虡 +虣 +虩 +虪 +虰 +虭 +虴 +蚑 +蚞 +蚇 +蚗 +蚚 +蚅 +蚥 +蚙 +蚿 +蚷 +蛂 +蛁 +蛅 +蛈 +蚹 +蚳 +蚸 +蛌 +蚻 +蛢 +蛦 +蛓 +蛣 +蛚 +蛪 +蛝 +蛫 +蛜 +蛬 +蛗 +蜄 +蛷 +蜌 +蛖 +蛵 +蜁 +蛶 +蜳 +蝫 +蜙 +蝃 +蜬 +蝁 +蝆 +蜠 +蜲 +蜪 +蜭 +蜼 +蜵 +蝂 +蜦 +蜧 +蜸 +蜤 +蜰 +蝖 +蝷 +蟡 +蝳 +蝔 +蝛 +蝒 +蝑 +蝞 +蝭 +蝪 +蝐 +蝝 +蝬 +蝺 +蝜 +螛 +螏 +螓 +螒 +螁 +螖 +螘 +蝹 +螇 +螑 +螝 +螜 +螚 +螪 +螰 +螹 +螼 +螮 +蟉 +蟃 +蟂 +螷 +螴 +螿 +螸 +蟞 +蟧 +蟦 +蟢 +蟟 +蟤 +蟔 +蟓 +蟭 +蟘 +螤 +蟗 +蟙 +蠁 +蟨 +蠀 +蟺 +蠉 +蠌 +蟼 +蠈 +蟿 +蠗 +蠩 +蠝 +蠛 +蠠 +蠤 +蠜 +蠫 +蠬 +蠨 +蠦 +蠪 +蠥 +蠰 +蠮 +蠳 +蠸 +蠾 +蠽 +蠿 +衁 +衈 +衋 +衧 +衪 +衭 +衶 +袀 +衱 +衯 +袃 +袉 +袕 +袨 +袚 +袑 +袡 +袘 +袧 +袬 +袌 +袺 +裗 +袹 +袸 +裀 +袶 +袽 +袲 +裋 +裍 +裞 +裚 +裷 +裧 +裺 +裮 +裶 +裯 +裻 +褁 +褅 +褋 +褗 +褆 +褖 +褑 +褦 +褮 +褱 +褢 +褩 +褵 +褼 +褾 +襒 +褷 +襂 +褽 +襓 +襋 +襆 +襐 +襛 +襗 +襡 +襘 +襝 +襣 +襭 +襩 +襮 +襳 +襹 +襺 +覂 +覅 +覕 +覛 +覝 +覢 +覤 +覣 +覭 +覮 +覶 +觓 +觤 +觡 +觠 +觢 +觩 +觰 +觬 +觲 +觷 +觺 +觻 +觼 +觾 +訑 +訰 +訧 +訬 +訞 +詍 +訹 +詙 +詀 +詄 +詅 +訿 +誂 +詻 +誃 +誫 +誙 +誋 +諆 +誸 +諔 +諕 +誻 +諀 +諅 +諵 +諝 +諰 +諈 +謞 +謘 +謑 +謋 +謒 +謕 +謍 +謈 +謪 +謧 +謣 +謰 +謵 +譇 +謯 +謱 +謥 +謷 +謦 +譐 +譈 +譊 +譀 +譋 +譕 +譑 +譠 +譪 +譝 +譨 +譣 +譥 +譹 +譸 +譅 +譺 +譻 +譾 +讄 +讂 +讆 +讋 +讔 +讘 +讟 +谹 +谻 +谽 +谾 +豃 +豋 +豍 +豏 +豗 +豜 +豝 +豟 +豥 +豤 +豦 +豭 +豰 +豲 +豱 +豯 +豵 +豷 +豶 +豻 +豽 +貁 +貀 +貄 +貏 +貑 +貕 +貙 +貗 +貜 +貣 +貾 +賌 +賥 +賟 +賙 +賵 +賮 +贆 +贕 +贙 +赨 +赩 +赮 +赸 +趀 +趌 +趎 +趏 +趍 +趓 +趠 +趜 +趡 +趥 +趧 +趬 +趪 +趭 +趫 +趮 +趷 +趹 +跘 +跓 +跍 +跇 +跜 +跕 +跙 +跈 +跰 +跠 +跮 +跦 +跢 +跧 +跲 +跫 +踂 +跿 +踍 +踃 +踇 +踆 +跾 +踠 +踥 +踤 +踡 +踕 +踛 +踖 +踑 +踙 +踧 +踘 +踓 +踳 +踾 +踸 +踼 +蹎 +蹍 +蹓 +蹗 +蹖 +蹞 +蹥 +蹛 +蹡 +蹝 +蹔 +蹸 +蹳 +蹪 +躆 +躈 +躖 +躗 +躟 +躠 +躤 +躣 +躩 +躨 +躽 +軓 +軘 +軞 +軯 +軷 +軦 +軮 +軥 +軵 +軧 +軨 +軶 +軱 +軬 +輆 +軿 +輁 +輀 +輂 +輐 +輑 +輤 +輘 +輚 +輠 +輣 +輖 +輗 +輮 +輵 +輲 +輹 +輷 +輴 +轃 +轇 +轈 +轒 +轑 +轏 +轐 +轓 +轙 +轖 +轗 +轕 +轚 +轞 +轛 +轠 +辴 +迉 +迒 +迋 +迍 +迖 +迣 +迡 +迾 +迿 +逜 +逿 +遝 +遳 +遰 +遻 +邆 +邅 +遾 +邍 +邔 +邟 +邥 +邞 +邧 +郱 +郕 +郖 +郠 +郙 +郣 +郥 +郘 +郰 +郲 +郔 +鄬 +郼 +鄈 +郹 +郻 +鄁 +鄇 +郺 +鄐 +鄍 +鄏 +鄎 +鄟 +鄝 +鄡 +鄛 +鄨 +鄪 +鄦 +鄮 +鄵 +鄸 +鄻 +鄾 +酀 +酁 +酄 +酇 +酖 +酘 +酓 +酟 +酳 +醆 +醊 +醓 +醙 +醟 +醥 +醧 +醰 +醱 +醷 +醲 +醳 +醹 +醽 +釂 +釃 +釢 +釱 +釳 +釸 +鈚 +鈌 +鈒 +釽 +鈆 +鉒 +鉠 +鉯 +鈶 +鉼 +銤 +銛 +銔 +鉹 +銗 +鋄 +鋀 +鋟 +鋘 +鋩 +鋝 +鋂 +鋊 +錧 +錼 +錭 +錎 +鋋 +鎡 +鎃 +鎯 +鍖 +鍜 +鍐 +鍭 +鍌 +鎒 +鎷 +鎝 +鎉 +鎎 +鎞 +鏏 +鏂 +鏚 +鏬 +鏙 +鐋 +鐏 +鏾 +鐕 +鐨 +鐍 +鐀 +鐎 +鐖 +鐻 +鐶 +鑐 +鑋 +鑕 +鑮 +鑯 +钂 +钀 +钁 +钃 +镺 +镻 +镼 +镽 +閈 +閍 +閺 +閵 +闀 +闉 +闅 +閷 +闒 +闑 +闚 +闛 +闠 +闟 +闤 +阞 +阢 +阤 +阠 +阰 +阹 +阸 +阺 +陏 +陓 +陊 +陼 +陭 +陫 +隇 +陾 +隉 +隒 +隓 +隞 +隤 +隿 +雂 +雈 +雓 +雔 +雗 +雚 +雟 +雘 +雺 +雽 +雿 +霂 +霋 +霒 +霐 +霠 +霣 +霢 +霩 +霫 +霬 +霮 +霵 +霿 +靆 +靃 +靪 +靮 +靷 +靲 +靾 +鞃 +鞀 +鞂 +靻 +鞊 +鞎 +鞈 +鞙 +鞗 +鞚 +鞜 +鞤 +鞪 +鞷 +鞶 +鞹 +鞻 +鞿 +韄 +韅 +韇 +韎 +韐 +韏 +韕 +韔 +韗 +韝 +韟 +韣 +韥 +韰 +韱 +韹 +韽 +頄 +頖 +頞 +頝 +頩 +頨 +頯 +頲 +顁 +顄 +顊 +顉 +顅 +顐 +顑 +顜 +顝 +顠 +顣 +顟 +顤 +顪 +顩 +顲 +颬 +颲 +颸 +颽 +颻 +颾 +飁 +飂 +飉 +飋 +飌 +飣 +飶 +餂 +餀 +飺 +餔 +餖 +餕 +餤 +餟 +餥 +餫 +餪 +餲 +餯 +餭 +餱 +餰 +饁 +饇 +饐 +饎 +饙 +饘 +饛 +饡 +馣 +馲 +馰 +馵 +馻 +馺 +駂 +馽 +駜 +駍 +駏 +駎 +駖 +駮 +駬 +駥 +駤 +駣 +駩 +駺 +駴 +駷 +駹 +駶 +駻 +駽 +駾 +騃 +騉 +騑 +騊 +騇 +騚 +騕 +騥 +騝 +騛 +騢 +騠 +騧 +騞 +騜 +騵 +騲 +騴 +騱 +騬 +騪 +騩 +騹 +騽 +驆 +騺 +驓 +驔 +驈 +驉 +驖 +驞 +驠 +驦 +驨 +骭 +骫 +骹 +骿 +骴 +骾 +髇 +髊 +髆 +髍 +髐 +髟 +髧 +髬 +髳 +髶 +髺 +髾 +鬁 +髼 +鬋 +鬊 +鬎 +鬌 +鬐 +鬕 +鬗 +鬖 +鬙 +鬞 +鬠 +鬤 +鬫 +鬳 +鬵 +鬺 +鬾 +鬿 +魊 +魌 +魖 +魠 +魡 +魧 +魱 +魦 +魶 +魵 +鮅 +鮇 +魼 +魾 +魻 +鮂 +鮚 +鮞 +鮛 +鮦 +鮥 +鮤 +鮆 +鯆 +鮿 +鮵 +鯈 +鯫 +鯠 +鯞 +鯦 +鯬 +鰌 +鰋 +鰅 +鯸 +鰫 +鰝 +鰬 +鱆 +鰿 +鱄 +鱁 +鰴 +鱐 +鱍 +鱋 +鱕 +鱦 +鱢 +鱞 +鱴 +鱳 +鱹 +鳦 +鳪 +鳭 +鳱 +鳵 +鳼 +鳺 +鳿 +鳷 +鴀 +鳹 +鳻 +鴅 +鴃 +鴥 +鴠 +鴔 +鴩 +鴘 +鴢 +鴐 +鴳 +鵁 +鵧 +鴶 +鴮 +鴱 +鴸 +鵅 +鵃 +鴾 +鵀 +鴽 +鵏 +鵊 +鵛 +鵋 +鵖 +鵌 +鵗 +鵔 +鵷 +鶁 +鶊 +鶄 +鶈 +鵱 +鶀 +鵸 +鶋 +鶌 +鵽 +鵫 +鵴 +鵩 +鶅 +鵳 +鵻 +鶂 +鵹 +鶟 +鶙 +鶤 +鶝 +鶐 +鶛 +鶠 +鶔 +鶜 +鶪 +鶗 +鶢 +鶨 +鶞 +鶣 +鶖 +鶷 +鶶 +鷁 +鷇 +鷊 +鷏 +鶾 +鷅 +鷃 +鶵 +鷈 +鶱 +鶭 +鷛 +鷒 +鷞 +鷋 +鷐 +鷜 +鷑 +鷩 +鷘 +鷖 +鷵 +鷕 +鷻 +鷷 +鷣 +鷤 +鷶 +鷡 +鷮 +鷢 +鸂 +鷾 +鸇 +鸃 +鸆 +鸅 +鸀 +鸁 +鸉 +鷿 +鷽 +鸄 +鸋 +鸍 +鸏 +鸒 +鸔 +鸓 +鸗 +鸙 +鹺 +麃 +麆 +麉 +麎 +麌 +麔 +麙 +麛 +麚 +麜 +麠 +麡 +麧 +麮 +麰 +麶 +麷 +黀 +黂 +黈 +黓 +黕 +黖 +黚 +黤 +黫 +黮 +黭 +黰 +黳 +黵 +黺 +鼁 +鼀 +鼆 +鼊 +鼏 +鼖 +鼛 +鼘 +鼜 +鼤 +鼣 +鼥 +鼪 +鼨 +鼭 +鼰 +鼮 +鼵 +鼳 +鼲 +鼸 +鼶 +齀 +齂 +齃 +齌 +齍 +齎 +齖 +齗 +齘 +齛 +齠 +齞 +齝 +齥 +齤 +齫 +齱 +齰 +齮 +齯 +齴 +齵 +齸 +齻 +齺 +齹 +齾 +龒 +龤 +堔 +礂 +蒏 +蒆 +兙 +兛 +兞 +兝 +兡 +兣 +嗧 +瓩 +忼 +擡 +氊 +穇 +擧 +譌 +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +© +° +² +´ +½ +Á +Ä +Å +Ç +È +É +Í +Ó +Ö +× +Ü +ß +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +í +ð +ñ +ò +ó +ô +õ +ö +ø +ú +û +ü +ý +ā +ă +ą +ć +Č +č +đ +ē +ė +ę +ğ +ī +ı +Ł +ł +ń +ň +ō +ř +Ş +ş +Š +š +ţ +ū +ż +Ž +ž +Ș +ș +ț +Δ +α +λ +μ +φ +Г +О +а +в +л +о +р +с +т +я +ồ +— +― +’ +“ +” +… +℃ +→ +∇ +− +■ +☆ +、 +。 +々 +〆 +〈 +〉 +「 +」 +『 +』 +〔 +〕 +〜 +! +# +% +& +( +) ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; += +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +R +S +T +U +V +W +X +Z +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +~ +・ +ǎ +ǒ +ě +ǐ +ì +ǔ +ù +ǖ +ǘ +ǚ +ǜ +【 +】 +《 +》 +‥ +{ +} +\ +| +@ +^ +~ +÷ +∕ +∙ +⋅ +· +⊕ +⊖ +⊗ +⊘ +⊙ +± +∓ +∩ +∪ +□ +⊎ +⊓ +⊔ +≠ +≈ +≡ +≤ +≥ +≪ +≫ +≲ +≳ +≶ +≷ +≺ +≻ +≼ +≽ +∈ +∉ +⊂ +⊃ +⊆ +⊇ +⊄ +⊅ +∅ +∖ +∁ +∆ +∧ +∨ +¬ +⊻ +⊼ +⊽ +← +↔ +⇒ +⇐ +⇔ +∀ +∃ +∄ +∴ +∵ +∝ +∞ +⊥ +∟ +∠ +∡ +∢ +′ +″ +∥ +⊾ +⊿ +∂ +∫ +∬ +∭ +∮ +∯ +∰ +∑ +∏ +√ +∛ +∜ +∱ +∲ +∳ +∶ +∷ +∼ +® +≄ +≅ +≃ +≦ +≧ +⊈ +⊉ +⊢ +⊤ +⊨ +⊧ +℉ +Ω +℧ +Å +⌀ +ℏ +⅀ +⍺ +⍵ +¢ +€ +£ +¥ +¥ +₿ +↑ +↓ +↕ +↖ +↗ +↘ +↙ +↺ +↻ +↼ +↽ +↾ +↿ +⇀ +⇁ +⇂ +⇃ +⇋ +⇌ +ª +º +⁰ +¹ +³ +⁴ +⁵ +⁶ +⁷ +⁸ +⁹ +⁺ +⁻ +⁼ +⁽ +⁾ +ⁿ +₀ +₁ +₂ +₃ +₄ +₅ +₆ +₇ +₈ +₉ +₊ +₋ +₌ +₍ +₎ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +☰ +☱ +☲ +☳ +☴ +☵ +☶ +☷ +♀ +♂ +♳ +♴ +♵ +♶ +♷ +♸ +♹ +♺ +♩ +♪ +♫ +♬ +⚪ +⚫ +⚬ +✶ +✷ +✸ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +⏀ +⏁ +⏂ +⏃ +⏄ +⏅ +⏆ +⏇ +⏈ +⏉ +⏊ +⏋ +⏌ +⏚ +⏴ +⏵ +⏶ +⏷ +⏸ +⏹ +⏺ +⏻ +⏼ +Α +Β +Γ +Ε +Ζ +Η +Θ +Ι +Κ +Λ +Μ +Ν +Ξ +Ο +Π +Ρ +Σ +Τ +Υ +Φ +Χ +Ψ +β +γ +δ +ε +ζ +η +θ +ι +κ +ν +ξ +ο +π +ρ +σ +τ +υ +χ +ψ +ω +ϐ +ϑ +ϒ +ϕ +█ +ϖ +ϰ +ϱ +ϴ +ϵ +ϝ +Ϟ +ϟ +Ϡ +ϡ +Ϣ +ϣ +Ϥ +ϥ +Ϧ +ϧ +Ϩ +ϩ +Ϫ +ϫ +Ϭ +ϭ +Ϯ +ϯ +∸ +∹ +∺ +∻ +∽ +∾ +∿ +≀ +≁ +≂ +≆ +≇ +≉ +≊ +≋ +≌ +≍ +≎ +≏ +≐ +≑ +≒ +≓ +≔ +≕ +≖ +≗ +≘ +≙ +≚ +≛ +≜ +≝ +≞ +≟ +≢ +≣ +≨ +≩ +≬ +≭ +≮ +≯ +≰ +≱ +≴ +≵ +≸ +≹ +≾ +≿ +⊀ +⊁ +⊊ +⊋ +⊌ +⊍ +⊏ +⊐ +⊑ +⊒ +⊚ +⊛ +⊜ +⊝ +⊞ +⊟ +⊠ +⊡ +⊣ +⊦ +⊩ +⊪ +⊫ +⊬ +⊭ +⊮ +⊯ +⊰ +⊱ +⊲ +⊳ +⊴ +⊵ +⊶ +⊷ +⊸ +⊹ +⊺ +ℎ +℘ +ℜ +ℑ +ℵ +ℶ +ℷ +ℸ +⌬ +⌭ +⌮ +⌯ +⎔ +¤ +₠ +₡ +₢ +₣ +₤ +₥ +₦ +₧ +₨ +₩ +₪ +₫ +₭ +₮ +₯ +₰ +₱ +₲ +₳ +₴ +₵ +₶ +₷ +₸ +₹ +₺ +₻ +₼ +₽ +₾ +↚ +↛ +↜ +↝ +↞ +↟ +↠ +↡ +↢ +↣ +↤ +↥ +↦ +↧ +↨ +↩ +↪ +↫ +↬ +↭ +↮ +↯ +↰ +↱ +↲ +↳ +↴ +↵ +↶ +↷ +↸ +↹ +⇄ +⇅ +⇆ +⇇ +⇈ +⇉ +⇊ +⇍ +⇎ +⇏ +⇑ +⇓ +⇕ +⇖ +⇗ +⇘ +⇙ +⇚ +⇛ +⇜ +⇝ +⇞ +⇟ +⇠ +⇡ +⇢ +⇣ +⇤ +⇥ +⇦ +⇧ +⇨ +⇩ +⇪ +⇫ +⇬ +⇭ +⇮ +⇯ +⇰ +⇱ +⇲ +⇳ +⇴ +⇵ +⇶ +⇷ +⇸ +⇹ +⇺ +⇻ +⇼ +⇽ +⇾ +⇿ +ↀ +ↁ +ↂ +☀ +☁ +☂ +☃ +☄ +★ +☇ +☈ +☉ +☊ +☋ +☌ +☍ +☎ +☏ +☐ +☑ +☒ +☓ +☔ +☕ +☖ +☗ +☘ +☙ +☚ +☛ +☜ +☝ +☞ +☟ +☠ +☡ +☢ +☣ +☤ +☥ +☦ +☧ +☨ +☩ +☪ +☫ +☬ +☭ +☮ +☯ +☸ +☹ +☺ +☻ +☼ +☽ +☾ +☿ +♁ +♃ +♄ +♅ +♆ +♇ +♔ +♕ +♖ +♗ +♘ +♙ +♚ +♛ +♜ +♝ +♞ +♟ +♠ +♡ +♢ +♣ +♤ +♥ +♦ +♧ +♨ +♭ +♮ +♯ +♰ +♱ +♲ +♻ +♼ +♽ +♾ +⚀ +⚁ +⚂ +⚃ +⚄ +⚅ +⚆ +⚇ +⚈ +⚉ +⚊ +⚋ +⚌ +⚍ +⚎ +⚏ +⚐ +⚑ +⚒ +⚓ +⚔ +⚕ +⚖ +⚗ +⚘ +⚙ +⚚ +⚛ +⚜ +⚝ +⚞ +⚟ +⚠ +⚡ +⚢ +⚣ +⚤ +⚥ +⚦ +⚧ +⚨ +⚩ +⚭ +⚮ +⚯ +⚰ +⚱ +⚲ +⚳ +⚴ +⚵ +⚶ +⚷ +⚸ +⚹ +⚺ +⚻ +⚼ +⚿ +⛀ +⛁ +⛂ +⛃ +⛆ +⛇ +⛈ +⛉ +⛊ +⛋ +⛌ +⛍ +⛏ +⛐ +⛑ +⛒ +⛓ +⛕ +⛖ +⛗ +⛘ +⛙ +⛚ +⛛ +⛜ +⛝ +⛞ +⛠ +⛡ +⛢ +⛣ +⛤ +⛥ +⛦ +⛧ +⛨ +⛩ +⛪ +⛫ +⛬ +⛭ +⛮ +⛯ +⛶ +⛾ +⛿ +✆ +✇ +✈ +✉ +✌ +✍ +✎ +✏ +✐ +✑ +✒ +✓ +✔ +✕ +✙ +✚ +✛ +✜ +✝ +✞ +✟ +✠ +✡ +✢ +✣ +✤ +✥ +✦ +✧ +✩ +✪ +✫ +✬ +✭ +✮ +✯ +✰ +✱ +✲ +✳ +✴ +✵ +✹ +✺ +✻ +✼ +✽ +✾ +✿ +❀ +❁ +❂ +❃ +❄ +❅ +❆ +❇ +❈ +❉ +❊ +❋ +❍ +❏ +❐ +❑ +❒ +❖ +❘ +❙ +❚ +❛ +❜ +❝ +❞ +❡ +❢ +❣ +❤ +❥ +❦ +❧ +❨ +❩ +❪ +❫ +❬ +❭ +❮ +❯ +❰ +❱ +❲ +❳ +❴ +❵ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +➔ +➕ +➖ +➗ +➘ +➙ +➚ +➛ +➜ +➝ +➞ +➟ +➠ +➡ +➢ +➣ +➤ +➥ +➦ +➧ +➨ +➩ +➪ +➫ +➬ +➭ +➮ +➯ +➰ +➱ +➲ +➳ +➴ +➵ +➶ +➷ +➸ +➹ +➺ +➻ +➼ +➽ +➾ +➿ +⌘ +⌥ +⌃ +⎋ +⌫ +⌦ +⏏ +⌤ +⌧ +⌨ +⎆ +⎇ +⎈ +⎉ +⎊ +⎌ +⎍ +⎎ +⎏ +⎐ +⎑ +⎒ +⎓ +⎕ +⎖ +⎗ +⎘ +⎙ +⎚ +⎛ +⎜ +⎝ +⎞ +⎟ +⎠ +⎡ +⎢ +⎣ +⎤ +⎥ +⎦ +⎧ +⎨ +⎩ +⎪ +⎫ +⎬ +⎭ +⎮ +⎯ +⎰ +⎱ +⎲ +⎳ +⎴ +⎵ +⎶ +⎷ +⎸ +⎹ +⎺ +⎻ +⎼ +⎽ +⎾ +⎿ +⏍ +⏎ +⏐ +⏑ +⏒ +⏓ +⏔ +⏕ +⏖ +⏗ +⏘ +⏙ +⏛ +⏜ +⏝ +⏞ +⏟ +⏠ +⏡ +⏢ +⏣ +⏤ +⏥ +⏦ +⏧ +⏨ +⏭ +⏮ +⏯ +⏱ +⏲ +▲ +▽ +◐ +⏽ +⏾ +⏿ +ɐ +ɑ +ɒ +ɓ +ɔ +ɕ +ɖ +ɗ +ɘ +ə +ɚ +ɛ +ɜ +ɝ +ɞ +ɟ +ɠ +ɡ +ɢ +ɣ +ɤ +ɥ +ɦ +ɧ +ɨ +ɩ +ɪ +ɫ +ɬ +ɭ +ɮ +ɯ +ɰ +ɱ +ɲ +ɳ +ɴ +ɵ +ɶ +ɷ +ɸ +ɹ +ɺ +ɻ +ɼ +ɽ +ɾ +ɿ +ʀ +ʁ +ʂ +ʃ +ʄ +ʅ +ʆ +ʇ +ʈ +ʉ +ʊ +ʋ +ʌ +ʍ +ʎ +ʏ +ʐ +ʑ +ʒ +ʓ +ʔ +ʕ +ʖ +ʗ +ʘ +ʙ +ʚ +ʛ +ʜ +ʝ +ʞ +ʟ +ʠ +ʡ +ʢ +ʣ +ʤ +ʥ +ʦ +ʧ +ʨ +ʩ +ʪ +ʫ +ʬ +ʭ +ʮ +ʯ +━ +Ǝ +à +● +▶ +| +𝑢 +〖 +〗 +︽ +– +﹥ +𝜓 +• +∋ +ƒ +० +✘ +Е +◉ +〒 +𝒱 +𝜆 +⟹ +﹪ +◊ +╆ +오 +˂ +〉 +𝝎 +▪ +△ +▁ +◼ +〇 +▷ +▬ +𝒮 +† +ₒ +⼁ +〵 +⭐ +╳ +⟶ +으 +⬆ +Ạ +◀ + +▫ +丄 +︾ +◥ +‖ +𝜌 +ⅼ +▼ +⁎ +﹏ +😁 +😂 +😃 +😄 +😅 +😆 +😉 +😊 +😋 +😌 +😍 +😏 +😒 +😓 +😔 +😖 +😘 +😚 +😜 +😝 +😞 +😠 +😡 +😢 +😣 +😤 +😥 +😨 +😩 +😪 +😫 +😭 +😰 +😱 +😲 +😳 +😵 +😷 +😸 +😹 +😺 +😻 +😼 +😽 +😾 +😿 +🙀 +🙅 +🙆 +🙇 +🙈 +🙉 +🙊 +🙋 +🙌 +🙍 +🙎 +🙏 +✂ +✅ +✊ +✋ +✖ +✨ +❌ +❎ +❓ +❔ +❕ +❗ +🚀 +🚃 +🚄 +🚅 +🚇 +🚉 +🚌 +🚏 +🚑 +🚒 +🚓 +🚕 +🚗 +🚙 +🚚 +🚢 +🚤 +🚥 +🚧 +🚨 +🚩 +🚪 +🚫 +🚬 +🚭 +🚲 +🚶 +🚹 +🚺 +🚻 +🚼 +🚽 +🚾 +🛀 +Ⓜ +🅰 +🅱 +🅾 +🅿 +🆎 +🆑 +🆒 +🆓 +🆔 +🆕 +🆖 +🆗 +🆘 +🆙 +🆚 +🇩🇪 +🇬🇧 +🇨🇳 +🇯🇵 +🇫🇷 +🇰🇷 +🇪🇸 +🇮🇹 +🇷🇺 +🇺🇸 +🈁 +ℹ +⌚ +⌛ +⏩ +⏪ +⏫ +⏬ +⏰ +⏳ +◻ +◽ +◾ +♈ +♉ +♊ +♋ +♌ +♍ +♎ +♏ +♐ +♑ +♒ +♓ +♿ +⚽ +⚾ +⛄ +⛅ +⛎ +⛔ +⛲ +⛳ +⛵ +⛺ +⛽ +⤴ +⤵ +⬅ +⬇ +⬛ +⬜ +⭕ +〰 +〽 +㊗ +㊙ +🀄 +🃏 +🌀 +🌁 +🌂 +🌃 +🌄 +🌅 +🌆 +🌇 +🌈 +🌉 +🌊 +🌋 +🌌 +🌏 +🌑 +🌓 +🌔 +🌕 +🌙 +🌛 +🌟 +🌠 +🌰 +🌱 +🌴 +🌵 +🌷 +🌸 +🌹 +🌺 +🌻 +🌼 +🌽 +🌾 +🌿 +🍀 +🍁 +🍂 +🍃 +🍄 +🍅 +🍆 +🍇 +🍈 +🍉 +🍊 +🍌 +🍍 +🍎 +🍏 +🍑 +🍒 +🍓 +🍔 +🍕 +🍖 +🍗 +🍘 +🍙 +🍚 +🍛 +🍜 +🍝 +🍞 +🍟 +🍠 +🍡 +🍢 +🍣 +🍤 +🍥 +🍦 +🍧 +🍨 +🍩 +🍪 +🍫 +🍬 +🍭 +🍮 +🍯 +🍰 +🍱 +🍲 +🍳 +🍴 +🍵 +🍶 +🍷 +🍸 +🍹 +🍺 +🍻 +🎀 +🎁 +🎂 +🎃 +🎄 +🎅 +🎆 +🎇 +🎈 +🎉 +🎊 +🎋 +🎌 +🎍 +🎎 +🎏 +🎐 +🎑 +🎒 +🎓 +🎠 +🎡 +🎢 +🎣 +🎤 +🎥 +🎦 +🎧 +🎨 +🎩 +🎪 +🎫 +🎬 +🎭 +🎮 +🎯 +🎰 +🎱 +🎲 +🎳 +🎴 +🎵 +🎶 +🎷 +🎸 +🎹 +🎺 +🎻 +🎼 +🎽 +🎾 +🎿 +🏀 +🏁 +🏂 +🏃 +🏄 +🏆 +🏈 +🏊 +🏠 +🏡 +🏢 +🏣 +🏥 +🏦 +🏧 +🏨 +🏩 +🏪 +🏫 +🏬 +🏭 +🏮 +🏯 +🏰 +🐌 +🐍 +🐎 +🐑 +🐒 +🐔 +🐗 +🐘 +🐙 +🐚 +🐛 +🐜 +🐝 +🐞 +🐟 +🐠 +🐡 +🐢 +🐣 +🐤 +🐥 +🐦 +🐧 +🐨 +🐩 +🐫 +🐬 +🐭 +🐮 +🐯 +🐰 +🐱 +🐲 +🐳 +🐴 +🐵 +🐶 +🐷 +🐸 +🐹 +🐺 +🐻 +🐼 +🐽 +🐾 +👀 +👂 +👃 +👄 +👅 +👆 +👇 +👈 +👉 +👊 +👋 +👌 +👍 +👎 +👏 +👐 +👑 +👒 +👓 +👔 +👕 +👖 +👗 +👘 +👙 +👚 +👛 +👜 +👝 +👞 +👟 +👠 +👡 +👢 +👣 +👤 +👦 +👧 +👨 +👩 +👪 +👫 +👮 +👯 +👰 +👱 +👲 +👳 +👴 +👵 +👶 +👷 +👸 +👹 +👺 +👻 +👼 +👽 +👾 +👿 +💀 +💁 +💂 +💃 +💄 +💅 +💆 +💇 +💈 +💉 +💊 +💋 +💌 +💍 +💎 +💏 +💐 +💑 +💒 +💓 +💔 +💕 +💖 +💗 +💘 +💙 +💚 +💛 +💜 +💝 +💞 +💟 +💠 +💡 +💢 +💣 +💤 +💥 +💦 +💧 +💨 +💩 +💪 +💫 +💬 +💮 +💯 +💰 +💲 +💳 +💴 +💵 +💸 +💹 +💺 +💻 +💼 +💽 +💾 +💿 +📀 +📁 +📂 +📃 +📄 +📅 +📆 +📇 +📈 +📉 +📊 +📋 +📌 +📍 +📎 +📏 +📐 +📑 +📒 +📓 +📔 +📕 +📖 +📗 +📘 +📙 +📚 +📛 +📜 +📝 +📞 +📟 +📠 +📡 +📢 +📣 +📤 +📥 +📦 +📧 +📨 +📩 +📪 +📫 +📮 +📰 +📱 +📲 +📳 +📴 +📶 +📷 +📹 +📺 +📻 +📼 +🔃 +🔊 +🔋 +🔌 +🔍 +🔎 +🔏 +🔐 +🔑 +🔒 +🔓 +🔔 +🔖 +🔗 +🔘 +🔙 +🔚 +🔛 +🔜 +🔝 +🔞 +🔟 +🔠 +🔡 +🔢 +🔣 +🔤 +🔥 +🔦 +🔧 +🔨 +🔩 +🔪 +🔫 +🔮 +🔯 +🔰 +🔱 +🔲 +🔳 +🔴 +🔵 +🔶 +🔷 +🔸 +🔹 +🔺 +🔻 +🔼 +🔽 +🕐 +🕑 +🕒 +🕓 +🕔 +🕕 +🕖 +🕗 +🕘 +🕙 +🕚 +🕛 +🗻 +🗼 +🗽 +🗾 +🗿 +😀 +😇 +😈 +😎 +😐 +😑 +😕 +😗 +😙 +😛 +😟 +😦 +😧 +😬 +😮 +😯 +😴 +😶 +🚁 +🚂 +🚆 +🚈 +🚊 +🚍 +🚎 +🚐 +🚔 +🚖 +🚘 +🚛 +🚜 +🚝 +🚞 +🚟 +🚠 +🚡 +🚣 +🚦 +🚮 +🚯 +🚰 +🚱 +🚳 +🚴 +🚵 +🚷 +🚸 +🚿 +🛁 +🛂 +🛃 +🛄 +🛅 +🌍 +🌎 +🌐 +🌒 +🌖 +🌗 +🌘 +🌚 +🌜 +🌝 +🌞 +🌲 +🌳 +🍋 +🍐 +🍼 +🏇 +🏉 +🏤 +🐀 +🐁 +🐂 +🐃 +🐄 +🐅 +🐆 +🐇 +🐈 +🐉 +🐊 +🐋 +🐏 +🐐 +🐓 +🐕 +🐖 +🐪 +👥 +👬 +👭 +💭 +💶 +💷 +📬 +📭 +📯 +📵 +🔀 +🔁 +🔂 +🔄 +🔅 +🔆 +🔇 +🔉 +🔕 +🔬 +🔭 +🕜 +🕝 +🕞 +🕟 +🕠 +🕡 +🕢 +🕣 +🕤 +🕥 +🕦 +🕧 diff --git a/ppocr/utils/dict/ppocrv5_el_dict.txt b/ppocr/utils/dict/ppocrv5_el_dict.txt new file mode 100644 index 0000000..a2de2e6 --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_el_dict.txt @@ -0,0 +1,354 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +@ +[ +] +_ +` +^ +~ +© +® +™ +° +² +³ +´ +µ +¶ +· +¸ +¹ +º +» +¼ +½ +¾ +¿ +× +÷ +‖ +‗ +‘ +’ +‚ +‛ +“ +” +„ +† +‡ +… +‰ +′ +″ +‴ +‵ +‶ +‷ +‹ +› +‼ +‽ +‾ +€ +£ +¥ +¢ +₤ +₡ +₽ +₴ +₹ +₿ +± +∓ +≈ +≠ +≡ +≤ +≥ +∑ +∏ +∫ +∬ +∭ +∮ +∯ +∰ +√ +∛ +∜ +∝ +∞ +∂ +∇ +∴ +∵ +∷ +∠ +∡ +∢ +∟ +∦ +∩ +∪ +∗ +∖ +∥ +∧ +∨ +⊂ +⊃ +⊥ +∈ +∋ +∉ +∅ +↑ +→ +↓ +↔ +↕ +← +⇒ +⇐ +⇔ +∀ +∃ +∄ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +Α +Β +Γ +Δ +Ε +Ζ +Η +Θ +Ι +Κ +Λ +Μ +Ν +Ξ +Ο +Π +Ρ +Σ +Τ +Υ +Φ +Χ +Ψ +Ω +ά +έ +ή +ί +ό +ύ +ώ +Ϊ +Ϋ +Ά +Έ +Ή +Ί +Ό +Ύ +Ώ +ϊ +ΐ +ϋ +ΰ +α +β +γ +δ +ε +ζ +η +θ +ι +κ +λ +μ +ν +ξ +ο +π +ρ +ς +σ +τ +υ +φ +χ +ψ +ω +ϐ +ϑ +ϒ +ϓ +ϔ +ϕ +ϖ +Ϛ +ϛ +Ϝ +ϝ +Ϟ +ϟ +Ϡ +ϡ +Ϣ +ϣ +Ϥ +ϥ +Ϧ +ϧ +Ϩ +ϩ +Ϫ +ϫ +Ϭ +ϭ +Ϯ +ϯ +ϰ +ϱ +ϲ +ϳ +ϴ +ϵ +϶ +Ϸ +ϸ +Ϲ +Ϻ +ϻ diff --git a/ppocr/utils/dict/ppocrv5_en_dict.txt b/ppocr/utils/dict/ppocrv5_en_dict.txt new file mode 100644 index 0000000..7bf3f5f --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_en_dict.txt @@ -0,0 +1,436 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +: +; +< += +> +? +@ +[ +\ +] +_ +` +{ +| +} +^ +~ +© +® +℉ +№ +Ω +℮ +™ +∆ +✓ +✔ +✗ +✘ +✕ +☑ +☒ +● +▪ +▫ +◼ +▶ +◀ +⬆ +¤ +¦ +§ +¨ +ª +« +¬ +¯ +° +² +³ +´ +µ +¶ +¸ +¹ +º +» +¼ +½ +¾ +¿ +× +‐ +‑ +‒ +— +― +‖ +‗ +‘ +’ +‚ +‛ +“ +” +„ +‟ +† +‡ +‣ +․ +… +‧ +‰ +‴ +‵ +‶ +‷ +‸ +‹ +› +※ +‼ +‽ +‾ +− +₤ +₡ +₹ +₽ +₴ +₿ +¢ +€ +£ +¥ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +↑ +→ +↓ +↕ +← +↔ +⇒ +⇐ +⇔ +∀ +∃ +∄ +∴ +∵ +∝ +∞ +∩ +∪ +∂ +∫ +∬ +∭ +∮ +∯ +∰ +∑ +∏ +√ +∛ +∜ +∱ +∲ +∳ +∶ +∷ +∼ +∖ +∗ +≈ +≠ +≡ +≤ +≥ +⊂ +⊃ +⊥ +⊾ +⊿ +□ +∥ +∋ +ƒ +′ +″ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +Ø +Ù +Ú +Û +Ü +Ý +Þ +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +þ +ÿ +Α +Β +Γ +Δ +Ε +Ζ +Η +Θ +Ι +Κ +Λ +Μ +Ν +Ξ +Ο +Π +Ρ +Σ +Τ +Υ +Φ +Χ +Ψ +Ω +α +β +γ +δ +ε +ζ +η +θ +ι +κ +λ +μ +ν +ξ +ο +π +ρ +σ +ς +τ +υ +φ +χ +ψ +ω +Å +ℏ +⌀ +⍺ +⍵ +𝑢 +𝜓 +० +‥ +︽ +﹥ +• +÷ +∕ +∙ +⋅ +· +± +∓ +∟ +∠ +∡ +∢ +℧ +☺ diff --git a/ppocr/utils/dict/ppocrv5_eslav_dict.txt b/ppocr/utils/dict/ppocrv5_eslav_dict.txt new file mode 100644 index 0000000..d50d6e5 --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_eslav_dict.txt @@ -0,0 +1,517 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +© +‥ +{ +} +\ +| +@ +^ +~ +÷ +∕ +∙ +⋅ +· +± +∓ +∩ +∪ +□ +← +↔ +⇒ +⇐ +⇔ +∀ +∃ +∄ +∴ +∵ +∝ +∞ +⊥ +∟ +∠ +∡ +∢ +′ +″ +∥ +⊾ +⊿ +∂ +∫ +∬ +∭ +∮ +∯ +∰ +∑ +∏ +√ +∛ +∜ +∱ +∲ +∳ +∶ +∷ +∼ +® +℉ +Ω +℧ +Å +⌀ +ℏ +⅀ +⍺ +⍵ +¢ +€ +£ +¥ +₿ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +● +▶ +𝑢 +︽ +– +﹥ +𝜓 +• +∋ +ƒ +० +⬆ +Ạ +◀ + +▫ +︾ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +Ø +Ù +Ú +Û +Ü +Ý +Þ +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +þ +ÿ +¡ +¤ +¦ +§ +¨ +ª +« +¬ +¯ +° +² +³ +´ +µ +¶ +¸ +¹ +º +» +¼ +½ +¾ +¿ +× +‐ +‑ +‒ +— +― +‖ +‗ +‘ +’ +‚ +‛ +“ +” +„ +‟ +† +‡ +‣ +․ +… +‧ +‰ +‴ +‵ +‶ +‷ +‸ +‹ +› +※ +‼ +‽ +‾ +₤ +₡ +₹ +− +∖ +∗ +≈ +≠ +≡ +≤ +≥ +⊂ +⊃ +↑ +→ +↓ +↕ +™ +Ω +℮ +∆ +✓ +✗ +✘ +▪ +◼ +✔ +✕ +☑ +☒ +№ +₽ +₴ +Α +α +Β +β +Γ +γ +Δ +δ +Ε +ε +Ζ +ζ +Η +η +Θ +θ +Ι +ι +Κ +κ +Λ +λ +Μ +μ +Ν +ν +Ξ +ξ +Ο +ο +Π +π +Ρ +ρ +Σ +σ +ς +Τ +τ +Υ +υ +Φ +φ +Χ +χ +Ψ +ψ +ω +А +Б +В +Г +Ґ +Д +Е +Ё +Є +Ж +З +И +І +Ї +Й +К +Л +М +Н +О +П +Р +С +Т +У +Ў +Ф +Х +Ц +Ч +Ш +Щ +Ъ +Ы +Ь +Э +Ю +Я +а +б +в +г +ґ +д +е +ё +є +ж +з +и +і +ї +й +к +л +м +н +о +п +р +с +т +у +ў +ф +х +ц +ч +ш +щ +ъ +ы +ь +э +ю +я diff --git a/ppocr/utils/dict/ppocrv5_korean_dict.txt b/ppocr/utils/dict/ppocrv5_korean_dict.txt new file mode 100644 index 0000000..e220263 --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_korean_dict.txt @@ -0,0 +1,11945 @@ +ᄀ +ᄁ +ᄂ +ᄃ +ᄄ +ᄅ +ᄆ +ᄇ +ᄈ +ᄉ +ᄊ +ᄋ +ᄌ +ᄍ +ᄎ +ᄏ +ᄐ +ᄑ +ᄒ +ᄓ +ᄔ +ᄕ +ᄖ +ᄗ +ᄘ +ᄙ +ᄚ +ᄛ +ᄜ +ᄝ +ᄞ +ᄟ +ᄠ +ᄡ +ᄢ +ᄣ +ᄤ +ᄥ +ᄦ +ᄧ +ᄨ +ᄩ +ᄪ +ᄫ +ᄬ +ᄭ +ᄮ +ᄯ +ᄰ +ᄱ +ᄲ +ᄳ +ᄴ +ᄵ +ᄶ +ᄷ +ᄸ +ᄹ +ᄺ +ᄻ +ᄼ +ᄽ +ᄾ +ᄿ +ᅀ +ᅁ +ᅂ +ᅃ +ᅄ +ᅅ +ᅆ +ᅇ +ᅈ +ᅉ +ᅊ +ᅋ +ᅌ +ᅍ +ᅎ +ᅏ +ᅐ +ᅑ +ᅒ +ᅓ +ᅔ +ᅕ +ᅖ +ᅗ +ᅘ +ᅙ +ᅡ +ᅢ +ᅣ +ᅤ +ᅥ +ᅦ +ᅧ +ᅨ +ᅩ +ᅪ +ᅫ +ᅬ +ᅭ +ᅮ +ᅯ +ᅰ +ᅱ +ᅲ +ᅳ +ᅴ +ᅵ +ᅶ +ᅷ +ᅸ +ᅹ +ᅺ +ᅻ +ᅼ +ᅽ +ᅾ +ᅿ +ᆀ +ᆁ +ᆂ +ᆃ +ᆄ +ᆅ +ᆆ +ᆇ +ᆈ +ᆉ +ᆊ +ᆋ +ᆌ +ᆍ +ᆎ +ᆏ +ᆐ +ᆑ +ᆒ +ᆓ +ᆔ +ᆕ +ᆖ +ᆗ +ᆘ +ᆙ +ᆚ +ᆛ +ᆜ +ᆝ +ᆞ +ᆟ +ᆠ +ᆡ +ᆢ +ᆨ +ᆩ +ᆪ +ᆫ +ᆬ +ᆭ +ᆮ +ᆯ +ᆰ +ᆱ +ᆲ +ᆳ +ᆴ +ᆵ +ᆶ +ᆷ +ᆸ +ᆹ +ᆺ +ᆻ +ᆼ +ᆽ +ᆾ +ᆿ +ᇀ +ᇁ +ᇂ +ᇃ +ᇄ +ᇅ +ᇆ +ᇇ +ᇈ +ᇉ +ᇊ +ᇋ +ᇌ +ᇍ +ᇎ +ᇏ +ᇐ +ᇑ +ᇒ +ᇓ +ᇔ +ᇕ +ᇖ +ᇗ +ᇘ +ᇙ +ᇚ +ᇛ +ᇜ +ᇝ +ᇞ +ᇟ +ᇠ +ᇡ +ᇢ +ᇣ +ᇤ +ᇥ +ᇦ +ᇧ +ᇨ +ᇩ +ᇪ +ᇫ +ᇬ +ᇭ +ᇮ +ᇯ +ᇰ +ᇱ +ᇲ +ᇳ +ᇴ +ᇵ +ᇶ +ᇷ +ᇸ +ᇹ +ㄱ +ㄲ +ㄳ +ㄴ +ㄵ +ㄶ +ㄷ +ㄸ +ㄹ +ㄺ +ㄻ +ㄼ +ㄽ +ㄾ +ㄿ +ㅀ +ㅁ +ㅂ +ㅃ +ㅄ +ㅅ +ㅆ +ㅇ +ㅈ +ㅉ +ㅊ +ㅋ +ㅌ +ㅍ +ㅎ +ㅏ +ㅐ +ㅑ +ㅒ +ㅓ +ㅔ +ㅕ +ㅖ +ㅗ +ㅘ +ㅙ +ㅚ +ㅛ +ㅜ +ㅝ +ㅞ +ㅟ +ㅠ +ㅡ +ㅢ +ㅣ +ㅤ +ㅥ +ㅦ +ㅧ +ㅨ +ㅩ +ㅪ +ㅫ +ㅬ +ㅭ +ㅮ +ㅯ +ㅰ +ㅱ +ㅲ +ㅳ +ㅴ +ㅵ +ㅶ +ㅷ +ㅸ +ㅹ +ㅺ +ㅻ +ㅼ +ㅽ +ㅾ +ㅿ +ㆀ +ㆁ +ㆂ +ㆃ +ㆄ +ㆅ +ㆆ +ㆇ +ㆈ +ㆉ +ㆊ +ㆋ +ㆌ +ㆍ +ㆎ +가 +각 +갂 +갃 +간 +갅 +갆 +갇 +갈 +갉 +갊 +갋 +갌 +갍 +갎 +갏 +감 +갑 +값 +갓 +갔 +강 +갖 +갗 +갘 +같 +갚 +갛 +개 +객 +갞 +갟 +갠 +갡 +갢 +갣 +갤 +갥 +갦 +갧 +갨 +갩 +갪 +갫 +갬 +갭 +갮 +갯 +갰 +갱 +갲 +갳 +갴 +갵 +갶 +갷 +갸 +갹 +갺 +갻 +갼 +갽 +갾 +갿 +걀 +걁 +걂 +걃 +걄 +걅 +걆 +걇 +걈 +걉 +걊 +걋 +걌 +걍 +걎 +걏 +걐 +걑 +걒 +걓 +걔 +걕 +걖 +걗 +걘 +걙 +걚 +걛 +걜 +걝 +걞 +걟 +걠 +걡 +걢 +걣 +걤 +걥 +걦 +걧 +걨 +걩 +걪 +걫 +걬 +걭 +걮 +걯 +거 +걱 +걲 +걳 +건 +걵 +걶 +걷 +걸 +걹 +걺 +걻 +걼 +걽 +걾 +걿 +검 +겁 +겂 +것 +겄 +겅 +겆 +겇 +겈 +겉 +겊 +겋 +게 +겍 +겎 +겏 +겐 +겑 +겒 +겓 +겔 +겕 +겖 +겗 +겘 +겙 +겚 +겛 +겜 +겝 +겞 +겟 +겠 +겡 +겢 +겣 +겤 +겥 +겦 +겧 +겨 +격 +겪 +겫 +견 +겭 +겮 +겯 +결 +겱 +겲 +겳 +겴 +겵 +겶 +겷 +겸 +겹 +겺 +겻 +겼 +경 +겾 +겿 +곀 +곁 +곂 +곃 +계 +곅 +곆 +곇 +곈 +곉 +곊 +곋 +곌 +곍 +곎 +곏 +곐 +곑 +곒 +곓 +곔 +곕 +곖 +곗 +곘 +곙 +곚 +곛 +곜 +곝 +곞 +곟 +고 +곡 +곢 +곣 +곤 +곥 +곦 +곧 +골 +곩 +곪 +곫 +곬 +곭 +곮 +곯 +곰 +곱 +곲 +곳 +곴 +공 +곶 +곷 +곸 +곹 +곺 +곻 +과 +곽 +곾 +곿 +관 +괁 +괂 +괃 +괄 +괅 +괆 +괇 +괈 +괉 +괊 +괋 +괌 +괍 +괎 +괏 +괐 +광 +괒 +괓 +괔 +괕 +괖 +괗 +괘 +괙 +괚 +괛 +괜 +괝 +괞 +괟 +괠 +괡 +괢 +괣 +괤 +괥 +괦 +괧 +괨 +괩 +괪 +괫 +괬 +괭 +괮 +괯 +괰 +괱 +괲 +괳 +괴 +괵 +괶 +괷 +괸 +괹 +괺 +괻 +괼 +괽 +괾 +괿 +굀 +굁 +굂 +굃 +굄 +굅 +굆 +굇 +굈 +굉 +굊 +굋 +굌 +굍 +굎 +굏 +교 +굑 +굒 +굓 +굔 +굕 +굖 +굗 +굘 +굙 +굚 +굛 +굜 +굝 +굞 +굟 +굠 +굡 +굢 +굣 +굤 +굥 +굦 +굧 +굨 +굩 +굪 +굫 +구 +국 +굮 +굯 +군 +굱 +굲 +굳 +굴 +굵 +굶 +굷 +굸 +굹 +굺 +굻 +굼 +굽 +굾 +굿 +궀 +궁 +궂 +궃 +궄 +궅 +궆 +궇 +궈 +궉 +궊 +궋 +권 +궍 +궎 +궏 +궐 +궑 +궒 +궓 +궔 +궕 +궖 +궗 +궘 +궙 +궚 +궛 +궜 +궝 +궞 +궟 +궠 +궡 +궢 +궣 +궤 +궥 +궦 +궧 +궨 +궩 +궪 +궫 +궬 +궭 +궮 +궯 +궰 +궱 +궲 +궳 +궴 +궵 +궶 +궷 +궸 +궹 +궺 +궻 +궼 +궽 +궾 +궿 +귀 +귁 +귂 +귃 +귄 +귅 +귆 +귇 +귈 +귉 +귊 +귋 +귌 +귍 +귎 +귏 +귐 +귑 +귒 +귓 +귔 +귕 +귖 +귗 +귘 +귙 +귚 +귛 +규 +귝 +귞 +귟 +균 +귡 +귢 +귣 +귤 +귥 +귦 +귧 +귨 +귩 +귪 +귫 +귬 +귭 +귮 +귯 +귰 +귱 +귲 +귳 +귴 +귵 +귶 +귷 +그 +극 +귺 +귻 +근 +귽 +귾 +귿 +글 +긁 +긂 +긃 +긄 +긅 +긆 +긇 +금 +급 +긊 +긋 +긌 +긍 +긎 +긏 +긐 +긑 +긒 +긓 +긔 +긕 +긖 +긗 +긘 +긙 +긚 +긛 +긜 +긝 +긞 +긟 +긠 +긡 +긢 +긣 +긤 +긥 +긦 +긧 +긨 +긩 +긪 +긫 +긬 +긭 +긮 +긯 +기 +긱 +긲 +긳 +긴 +긵 +긶 +긷 +길 +긹 +긺 +긻 +긼 +긽 +긾 +긿 +김 +깁 +깂 +깃 +깄 +깅 +깆 +깇 +깈 +깉 +깊 +깋 +까 +깍 +깎 +깏 +깐 +깑 +깒 +깓 +깔 +깕 +깖 +깗 +깘 +깙 +깚 +깛 +깜 +깝 +깞 +깟 +깠 +깡 +깢 +깣 +깤 +깥 +깦 +깧 +깨 +깩 +깪 +깫 +깬 +깭 +깮 +깯 +깰 +깱 +깲 +깳 +깴 +깵 +깶 +깷 +깸 +깹 +깺 +깻 +깼 +깽 +깾 +깿 +꺀 +꺁 +꺂 +꺃 +꺄 +꺅 +꺆 +꺇 +꺈 +꺉 +꺊 +꺋 +꺌 +꺍 +꺎 +꺏 +꺐 +꺑 +꺒 +꺓 +꺔 +꺕 +꺖 +꺗 +꺘 +꺙 +꺚 +꺛 +꺜 +꺝 +꺞 +꺟 +꺠 +꺡 +꺢 +꺣 +꺤 +꺥 +꺦 +꺧 +꺨 +꺩 +꺪 +꺫 +꺬 +꺭 +꺮 +꺯 +꺰 +꺱 +꺲 +꺳 +꺴 +꺵 +꺶 +꺷 +꺸 +꺹 +꺺 +꺻 +꺼 +꺽 +꺾 +꺿 +껀 +껁 +껂 +껃 +껄 +껅 +껆 +껇 +껈 +껉 +껊 +껋 +껌 +껍 +껎 +껏 +껐 +껑 +껒 +껓 +껔 +껕 +껖 +껗 +께 +껙 +껚 +껛 +껜 +껝 +껞 +껟 +껠 +껡 +껢 +껣 +껤 +껥 +껦 +껧 +껨 +껩 +껪 +껫 +껬 +껭 +껮 +껯 +껰 +껱 +껲 +껳 +껴 +껵 +껶 +껷 +껸 +껹 +껺 +껻 +껼 +껽 +껾 +껿 +꼀 +꼁 +꼂 +꼃 +꼄 +꼅 +꼆 +꼇 +꼈 +꼉 +꼊 +꼋 +꼌 +꼍 +꼎 +꼏 +꼐 +꼑 +꼒 +꼓 +꼔 +꼕 +꼖 +꼗 +꼘 +꼙 +꼚 +꼛 +꼜 +꼝 +꼞 +꼟 +꼠 +꼡 +꼢 +꼣 +꼤 +꼥 +꼦 +꼧 +꼨 +꼩 +꼪 +꼫 +꼬 +꼭 +꼮 +꼯 +꼰 +꼱 +꼲 +꼳 +꼴 +꼵 +꼶 +꼷 +꼸 +꼹 +꼺 +꼻 +꼼 +꼽 +꼾 +꼿 +꽀 +꽁 +꽂 +꽃 +꽄 +꽅 +꽆 +꽇 +꽈 +꽉 +꽊 +꽋 +꽌 +꽍 +꽎 +꽏 +꽐 +꽑 +꽒 +꽓 +꽔 +꽕 +꽖 +꽗 +꽘 +꽙 +꽚 +꽛 +꽜 +꽝 +꽞 +꽟 +꽠 +꽡 +꽢 +꽣 +꽤 +꽥 +꽦 +꽧 +꽨 +꽩 +꽪 +꽫 +꽬 +꽭 +꽮 +꽯 +꽰 +꽱 +꽲 +꽳 +꽴 +꽵 +꽶 +꽷 +꽸 +꽹 +꽺 +꽻 +꽼 +꽽 +꽾 +꽿 +꾀 +꾁 +꾂 +꾃 +꾄 +꾅 +꾆 +꾇 +꾈 +꾉 +꾊 +꾋 +꾌 +꾍 +꾎 +꾏 +꾐 +꾑 +꾒 +꾓 +꾔 +꾕 +꾖 +꾗 +꾘 +꾙 +꾚 +꾛 +꾜 +꾝 +꾞 +꾟 +꾠 +꾡 +꾢 +꾣 +꾤 +꾥 +꾦 +꾧 +꾨 +꾩 +꾪 +꾫 +꾬 +꾭 +꾮 +꾯 +꾰 +꾱 +꾲 +꾳 +꾴 +꾵 +꾶 +꾷 +꾸 +꾹 +꾺 +꾻 +꾼 +꾽 +꾾 +꾿 +꿀 +꿁 +꿂 +꿃 +꿄 +꿅 +꿆 +꿇 +꿈 +꿉 +꿊 +꿋 +꿌 +꿍 +꿎 +꿏 +꿐 +꿑 +꿒 +꿓 +꿔 +꿕 +꿖 +꿗 +꿘 +꿙 +꿚 +꿛 +꿜 +꿝 +꿞 +꿟 +꿠 +꿡 +꿢 +꿣 +꿤 +꿥 +꿦 +꿧 +꿨 +꿩 +꿪 +꿫 +꿬 +꿭 +꿮 +꿯 +꿰 +꿱 +꿲 +꿳 +꿴 +꿵 +꿶 +꿷 +꿸 +꿹 +꿺 +꿻 +꿼 +꿽 +꿾 +꿿 +뀀 +뀁 +뀂 +뀃 +뀄 +뀅 +뀆 +뀇 +뀈 +뀉 +뀊 +뀋 +뀌 +뀍 +뀎 +뀏 +뀐 +뀑 +뀒 +뀓 +뀔 +뀕 +뀖 +뀗 +뀘 +뀙 +뀚 +뀛 +뀜 +뀝 +뀞 +뀟 +뀠 +뀡 +뀢 +뀣 +뀤 +뀥 +뀦 +뀧 +뀨 +뀩 +뀪 +뀫 +뀬 +뀭 +뀮 +뀯 +뀰 +뀱 +뀲 +뀳 +뀴 +뀵 +뀶 +뀷 +뀸 +뀹 +뀺 +뀻 +뀼 +뀽 +뀾 +뀿 +끀 +끁 +끂 +끃 +끄 +끅 +끆 +끇 +끈 +끉 +끊 +끋 +끌 +끍 +끎 +끏 +끐 +끑 +끒 +끓 +끔 +끕 +끖 +끗 +끘 +끙 +끚 +끛 +끜 +끝 +끞 +끟 +끠 +끡 +끢 +끣 +끤 +끥 +끦 +끧 +끨 +끩 +끪 +끫 +끬 +끭 +끮 +끯 +끰 +끱 +끲 +끳 +끴 +끵 +끶 +끷 +끸 +끹 +끺 +끻 +끼 +끽 +끾 +끿 +낀 +낁 +낂 +낃 +낄 +낅 +낆 +낇 +낈 +낉 +낊 +낋 +낌 +낍 +낎 +낏 +낐 +낑 +낒 +낓 +낔 +낕 +낖 +낗 +나 +낙 +낚 +낛 +난 +낝 +낞 +낟 +날 +낡 +낢 +낣 +낤 +낥 +낦 +낧 +남 +납 +낪 +낫 +났 +낭 +낮 +낯 +낰 +낱 +낲 +낳 +내 +낵 +낶 +낷 +낸 +낹 +낺 +낻 +낼 +낽 +낾 +낿 +냀 +냁 +냂 +냃 +냄 +냅 +냆 +냇 +냈 +냉 +냊 +냋 +냌 +냍 +냎 +냏 +냐 +냑 +냒 +냓 +냔 +냕 +냖 +냗 +냘 +냙 +냚 +냛 +냜 +냝 +냞 +냟 +냠 +냡 +냢 +냣 +냤 +냥 +냦 +냧 +냨 +냩 +냪 +냫 +냬 +냭 +냮 +냯 +냰 +냱 +냲 +냳 +냴 +냵 +냶 +냷 +냸 +냹 +냺 +냻 +냼 +냽 +냾 +냿 +넀 +넁 +넂 +넃 +넄 +넅 +넆 +넇 +너 +넉 +넊 +넋 +넌 +넍 +넎 +넏 +널 +넑 +넒 +넓 +넔 +넕 +넖 +넗 +넘 +넙 +넚 +넛 +넜 +넝 +넞 +넟 +넠 +넡 +넢 +넣 +네 +넥 +넦 +넧 +넨 +넩 +넪 +넫 +넬 +넭 +넮 +넯 +넰 +넱 +넲 +넳 +넴 +넵 +넶 +넷 +넸 +넹 +넺 +넻 +넼 +넽 +넾 +넿 +녀 +녁 +녂 +녃 +년 +녅 +녆 +녇 +녈 +녉 +녊 +녋 +녌 +녍 +녎 +녏 +념 +녑 +녒 +녓 +녔 +녕 +녖 +녗 +녘 +녙 +녚 +녛 +녜 +녝 +녞 +녟 +녠 +녡 +녢 +녣 +녤 +녥 +녦 +녧 +녨 +녩 +녪 +녫 +녬 +녭 +녮 +녯 +녰 +녱 +녲 +녳 +녴 +녵 +녶 +녷 +노 +녹 +녺 +녻 +논 +녽 +녾 +녿 +놀 +놁 +놂 +놃 +놄 +놅 +놆 +놇 +놈 +놉 +놊 +놋 +놌 +농 +놎 +놏 +놐 +놑 +높 +놓 +놔 +놕 +놖 +놗 +놘 +놙 +놚 +놛 +놜 +놝 +놞 +놟 +놠 +놡 +놢 +놣 +놤 +놥 +놦 +놧 +놨 +놩 +놪 +놫 +놬 +놭 +놮 +놯 +놰 +놱 +놲 +놳 +놴 +놵 +놶 +놷 +놸 +놹 +놺 +놻 +놼 +놽 +놾 +놿 +뇀 +뇁 +뇂 +뇃 +뇄 +뇅 +뇆 +뇇 +뇈 +뇉 +뇊 +뇋 +뇌 +뇍 +뇎 +뇏 +뇐 +뇑 +뇒 +뇓 +뇔 +뇕 +뇖 +뇗 +뇘 +뇙 +뇚 +뇛 +뇜 +뇝 +뇞 +뇟 +뇠 +뇡 +뇢 +뇣 +뇤 +뇥 +뇦 +뇧 +뇨 +뇩 +뇪 +뇫 +뇬 +뇭 +뇮 +뇯 +뇰 +뇱 +뇲 +뇳 +뇴 +뇵 +뇶 +뇷 +뇸 +뇹 +뇺 +뇻 +뇼 +뇽 +뇾 +뇿 +눀 +눁 +눂 +눃 +누 +눅 +눆 +눇 +눈 +눉 +눊 +눋 +눌 +눍 +눎 +눏 +눐 +눑 +눒 +눓 +눔 +눕 +눖 +눗 +눘 +눙 +눚 +눛 +눜 +눝 +눞 +눟 +눠 +눡 +눢 +눣 +눤 +눥 +눦 +눧 +눨 +눩 +눪 +눫 +눬 +눭 +눮 +눯 +눰 +눱 +눲 +눳 +눴 +눵 +눶 +눷 +눸 +눹 +눺 +눻 +눼 +눽 +눾 +눿 +뉀 +뉁 +뉂 +뉃 +뉄 +뉅 +뉆 +뉇 +뉈 +뉉 +뉊 +뉋 +뉌 +뉍 +뉎 +뉏 +뉐 +뉑 +뉒 +뉓 +뉔 +뉕 +뉖 +뉗 +뉘 +뉙 +뉚 +뉛 +뉜 +뉝 +뉞 +뉟 +뉠 +뉡 +뉢 +뉣 +뉤 +뉥 +뉦 +뉧 +뉨 +뉩 +뉪 +뉫 +뉬 +뉭 +뉮 +뉯 +뉰 +뉱 +뉲 +뉳 +뉴 +뉵 +뉶 +뉷 +뉸 +뉹 +뉺 +뉻 +뉼 +뉽 +뉾 +뉿 +늀 +늁 +늂 +늃 +늄 +늅 +늆 +늇 +늈 +늉 +늊 +늋 +늌 +늍 +늎 +늏 +느 +늑 +늒 +늓 +는 +늕 +늖 +늗 +늘 +늙 +늚 +늛 +늜 +늝 +늞 +늟 +늠 +늡 +늢 +늣 +늤 +능 +늦 +늧 +늨 +늩 +늪 +늫 +늬 +늭 +늮 +늯 +늰 +늱 +늲 +늳 +늴 +늵 +늶 +늷 +늸 +늹 +늺 +늻 +늼 +늽 +늾 +늿 +닀 +닁 +닂 +닃 +닄 +닅 +닆 +닇 +니 +닉 +닊 +닋 +닌 +닍 +닎 +닏 +닐 +닑 +닒 +닓 +닔 +닕 +닖 +닗 +님 +닙 +닚 +닛 +닜 +닝 +닞 +닟 +닠 +닡 +닢 +닣 +다 +닥 +닦 +닧 +단 +닩 +닪 +닫 +달 +닭 +닮 +닯 +닰 +닱 +닲 +닳 +담 +답 +닶 +닷 +닸 +당 +닺 +닻 +닼 +닽 +닾 +닿 +대 +댁 +댂 +댃 +댄 +댅 +댆 +댇 +댈 +댉 +댊 +댋 +댌 +댍 +댎 +댏 +댐 +댑 +댒 +댓 +댔 +댕 +댖 +댗 +댘 +댙 +댚 +댛 +댜 +댝 +댞 +댟 +댠 +댡 +댢 +댣 +댤 +댥 +댦 +댧 +댨 +댩 +댪 +댫 +댬 +댭 +댮 +댯 +댰 +댱 +댲 +댳 +댴 +댵 +댶 +댷 +댸 +댹 +댺 +댻 +댼 +댽 +댾 +댿 +덀 +덁 +덂 +덃 +덄 +덅 +덆 +덇 +덈 +덉 +덊 +덋 +덌 +덍 +덎 +덏 +덐 +덑 +덒 +덓 +더 +덕 +덖 +덗 +던 +덙 +덚 +덛 +덜 +덝 +덞 +덟 +덠 +덡 +덢 +덣 +덤 +덥 +덦 +덧 +덨 +덩 +덪 +덫 +덬 +덭 +덮 +덯 +데 +덱 +덲 +덳 +덴 +덵 +덶 +덷 +델 +덹 +덺 +덻 +덼 +덽 +덾 +덿 +뎀 +뎁 +뎂 +뎃 +뎄 +뎅 +뎆 +뎇 +뎈 +뎉 +뎊 +뎋 +뎌 +뎍 +뎎 +뎏 +뎐 +뎑 +뎒 +뎓 +뎔 +뎕 +뎖 +뎗 +뎘 +뎙 +뎚 +뎛 +뎜 +뎝 +뎞 +뎟 +뎠 +뎡 +뎢 +뎣 +뎤 +뎥 +뎦 +뎧 +뎨 +뎩 +뎪 +뎫 +뎬 +뎭 +뎮 +뎯 +뎰 +뎱 +뎲 +뎳 +뎴 +뎵 +뎶 +뎷 +뎸 +뎹 +뎺 +뎻 +뎼 +뎽 +뎾 +뎿 +돀 +돁 +돂 +돃 +도 +독 +돆 +돇 +돈 +돉 +돊 +돋 +돌 +돍 +돎 +돏 +돐 +돑 +돒 +돓 +돔 +돕 +돖 +돗 +돘 +동 +돚 +돛 +돜 +돝 +돞 +돟 +돠 +돡 +돢 +돣 +돤 +돥 +돦 +돧 +돨 +돩 +돪 +돫 +돬 +돭 +돮 +돯 +돰 +돱 +돲 +돳 +돴 +돵 +돶 +돷 +돸 +돹 +돺 +돻 +돼 +돽 +돾 +돿 +됀 +됁 +됂 +됃 +됄 +됅 +됆 +됇 +됈 +됉 +됊 +됋 +됌 +됍 +됎 +됏 +됐 +됑 +됒 +됓 +됔 +됕 +됖 +됗 +되 +됙 +됚 +됛 +된 +됝 +됞 +됟 +될 +됡 +됢 +됣 +됤 +됥 +됦 +됧 +됨 +됩 +됪 +됫 +됬 +됭 +됮 +됯 +됰 +됱 +됲 +됳 +됴 +됵 +됶 +됷 +됸 +됹 +됺 +됻 +됼 +됽 +됾 +됿 +둀 +둁 +둂 +둃 +둄 +둅 +둆 +둇 +둈 +둉 +둊 +둋 +둌 +둍 +둎 +둏 +두 +둑 +둒 +둓 +둔 +둕 +둖 +둗 +둘 +둙 +둚 +둛 +둜 +둝 +둞 +둟 +둠 +둡 +둢 +둣 +둤 +둥 +둦 +둧 +둨 +둩 +둪 +둫 +둬 +둭 +둮 +둯 +둰 +둱 +둲 +둳 +둴 +둵 +둶 +둷 +둸 +둹 +둺 +둻 +둼 +둽 +둾 +둿 +뒀 +뒁 +뒂 +뒃 +뒄 +뒅 +뒆 +뒇 +뒈 +뒉 +뒊 +뒋 +뒌 +뒍 +뒎 +뒏 +뒐 +뒑 +뒒 +뒓 +뒔 +뒕 +뒖 +뒗 +뒘 +뒙 +뒚 +뒛 +뒜 +뒝 +뒞 +뒟 +뒠 +뒡 +뒢 +뒣 +뒤 +뒥 +뒦 +뒧 +뒨 +뒩 +뒪 +뒫 +뒬 +뒭 +뒮 +뒯 +뒰 +뒱 +뒲 +뒳 +뒴 +뒵 +뒶 +뒷 +뒸 +뒹 +뒺 +뒻 +뒼 +뒽 +뒾 +뒿 +듀 +듁 +듂 +듃 +듄 +듅 +듆 +듇 +듈 +듉 +듊 +듋 +듌 +듍 +듎 +듏 +듐 +듑 +듒 +듓 +듔 +듕 +듖 +듗 +듘 +듙 +듚 +듛 +드 +득 +듞 +듟 +든 +듡 +듢 +듣 +들 +듥 +듦 +듧 +듨 +듩 +듪 +듫 +듬 +듭 +듮 +듯 +듰 +등 +듲 +듳 +듴 +듵 +듶 +듷 +듸 +듹 +듺 +듻 +듼 +듽 +듾 +듿 +딀 +딁 +딂 +딃 +딄 +딅 +딆 +딇 +딈 +딉 +딊 +딋 +딌 +딍 +딎 +딏 +딐 +딑 +딒 +딓 +디 +딕 +딖 +딗 +딘 +딙 +딚 +딛 +딜 +딝 +딞 +딟 +딠 +딡 +딢 +딣 +딤 +딥 +딦 +딧 +딨 +딩 +딪 +딫 +딬 +딭 +딮 +딯 +따 +딱 +딲 +딳 +딴 +딵 +딶 +딷 +딸 +딹 +딺 +딻 +딼 +딽 +딾 +딿 +땀 +땁 +땂 +땃 +땄 +땅 +땆 +땇 +땈 +땉 +땊 +땋 +때 +땍 +땎 +땏 +땐 +땑 +땒 +땓 +땔 +땕 +땖 +땗 +땘 +땙 +땚 +땛 +땜 +땝 +땞 +땟 +땠 +땡 +땢 +땣 +땤 +땥 +땦 +땧 +땨 +땩 +땪 +땫 +땬 +땭 +땮 +땯 +땰 +땱 +땲 +땳 +땴 +땵 +땶 +땷 +땸 +땹 +땺 +땻 +땼 +땽 +땾 +땿 +떀 +떁 +떂 +떃 +떄 +떅 +떆 +떇 +떈 +떉 +떊 +떋 +떌 +떍 +떎 +떏 +떐 +떑 +떒 +떓 +떔 +떕 +떖 +떗 +떘 +떙 +떚 +떛 +떜 +떝 +떞 +떟 +떠 +떡 +떢 +떣 +떤 +떥 +떦 +떧 +떨 +떩 +떪 +떫 +떬 +떭 +떮 +떯 +떰 +떱 +떲 +떳 +떴 +떵 +떶 +떷 +떸 +떹 +떺 +떻 +떼 +떽 +떾 +떿 +뗀 +뗁 +뗂 +뗃 +뗄 +뗅 +뗆 +뗇 +뗈 +뗉 +뗊 +뗋 +뗌 +뗍 +뗎 +뗏 +뗐 +뗑 +뗒 +뗓 +뗔 +뗕 +뗖 +뗗 +뗘 +뗙 +뗚 +뗛 +뗜 +뗝 +뗞 +뗟 +뗠 +뗡 +뗢 +뗣 +뗤 +뗥 +뗦 +뗧 +뗨 +뗩 +뗪 +뗫 +뗬 +뗭 +뗮 +뗯 +뗰 +뗱 +뗲 +뗳 +뗴 +뗵 +뗶 +뗷 +뗸 +뗹 +뗺 +뗻 +뗼 +뗽 +뗾 +뗿 +똀 +똁 +똂 +똃 +똄 +똅 +똆 +똇 +똈 +똉 +똊 +똋 +똌 +똍 +똎 +똏 +또 +똑 +똒 +똓 +똔 +똕 +똖 +똗 +똘 +똙 +똚 +똛 +똜 +똝 +똞 +똟 +똠 +똡 +똢 +똣 +똤 +똥 +똦 +똧 +똨 +똩 +똪 +똫 +똬 +똭 +똮 +똯 +똰 +똱 +똲 +똳 +똴 +똵 +똶 +똷 +똸 +똹 +똺 +똻 +똼 +똽 +똾 +똿 +뙀 +뙁 +뙂 +뙃 +뙄 +뙅 +뙆 +뙇 +뙈 +뙉 +뙊 +뙋 +뙌 +뙍 +뙎 +뙏 +뙐 +뙑 +뙒 +뙓 +뙔 +뙕 +뙖 +뙗 +뙘 +뙙 +뙚 +뙛 +뙜 +뙝 +뙞 +뙟 +뙠 +뙡 +뙢 +뙣 +뙤 +뙥 +뙦 +뙧 +뙨 +뙩 +뙪 +뙫 +뙬 +뙭 +뙮 +뙯 +뙰 +뙱 +뙲 +뙳 +뙴 +뙵 +뙶 +뙷 +뙸 +뙹 +뙺 +뙻 +뙼 +뙽 +뙾 +뙿 +뚀 +뚁 +뚂 +뚃 +뚄 +뚅 +뚆 +뚇 +뚈 +뚉 +뚊 +뚋 +뚌 +뚍 +뚎 +뚏 +뚐 +뚑 +뚒 +뚓 +뚔 +뚕 +뚖 +뚗 +뚘 +뚙 +뚚 +뚛 +뚜 +뚝 +뚞 +뚟 +뚠 +뚡 +뚢 +뚣 +뚤 +뚥 +뚦 +뚧 +뚨 +뚩 +뚪 +뚫 +뚬 +뚭 +뚮 +뚯 +뚰 +뚱 +뚲 +뚳 +뚴 +뚵 +뚶 +뚷 +뚸 +뚹 +뚺 +뚻 +뚼 +뚽 +뚾 +뚿 +뛀 +뛁 +뛂 +뛃 +뛄 +뛅 +뛆 +뛇 +뛈 +뛉 +뛊 +뛋 +뛌 +뛍 +뛎 +뛏 +뛐 +뛑 +뛒 +뛓 +뛔 +뛕 +뛖 +뛗 +뛘 +뛙 +뛚 +뛛 +뛜 +뛝 +뛞 +뛟 +뛠 +뛡 +뛢 +뛣 +뛤 +뛥 +뛦 +뛧 +뛨 +뛩 +뛪 +뛫 +뛬 +뛭 +뛮 +뛯 +뛰 +뛱 +뛲 +뛳 +뛴 +뛵 +뛶 +뛷 +뛸 +뛹 +뛺 +뛻 +뛼 +뛽 +뛾 +뛿 +뜀 +뜁 +뜂 +뜃 +뜄 +뜅 +뜆 +뜇 +뜈 +뜉 +뜊 +뜋 +뜌 +뜍 +뜎 +뜏 +뜐 +뜑 +뜒 +뜓 +뜔 +뜕 +뜖 +뜗 +뜘 +뜙 +뜚 +뜛 +뜜 +뜝 +뜞 +뜟 +뜠 +뜡 +뜢 +뜣 +뜤 +뜥 +뜦 +뜧 +뜨 +뜩 +뜪 +뜫 +뜬 +뜭 +뜮 +뜯 +뜰 +뜱 +뜲 +뜳 +뜴 +뜵 +뜶 +뜷 +뜸 +뜹 +뜺 +뜻 +뜼 +뜽 +뜾 +뜿 +띀 +띁 +띂 +띃 +띄 +띅 +띆 +띇 +띈 +띉 +띊 +띋 +띌 +띍 +띎 +띏 +띐 +띑 +띒 +띓 +띔 +띕 +띖 +띗 +띘 +띙 +띚 +띛 +띜 +띝 +띞 +띟 +띠 +띡 +띢 +띣 +띤 +띥 +띦 +띧 +띨 +띩 +띪 +띫 +띬 +띭 +띮 +띯 +띰 +띱 +띲 +띳 +띴 +띵 +띶 +띷 +띸 +띹 +띺 +띻 +라 +락 +띾 +띿 +란 +랁 +랂 +랃 +랄 +랅 +랆 +랇 +랈 +랉 +랊 +랋 +람 +랍 +랎 +랏 +랐 +랑 +랒 +랓 +랔 +랕 +랖 +랗 +래 +랙 +랚 +랛 +랜 +랝 +랞 +랟 +랠 +랡 +랢 +랣 +랤 +랥 +랦 +랧 +램 +랩 +랪 +랫 +랬 +랭 +랮 +랯 +랰 +랱 +랲 +랳 +랴 +략 +랶 +랷 +랸 +랹 +랺 +랻 +랼 +랽 +랾 +랿 +럀 +럁 +럂 +럃 +럄 +럅 +럆 +럇 +럈 +량 +럊 +럋 +럌 +럍 +럎 +럏 +럐 +럑 +럒 +럓 +럔 +럕 +럖 +럗 +럘 +럙 +럚 +럛 +럜 +럝 +럞 +럟 +럠 +럡 +럢 +럣 +럤 +럥 +럦 +럧 +럨 +럩 +럪 +럫 +러 +럭 +럮 +럯 +런 +럱 +럲 +럳 +럴 +럵 +럶 +럷 +럸 +럹 +럺 +럻 +럼 +럽 +럾 +럿 +렀 +렁 +렂 +렃 +렄 +렅 +렆 +렇 +레 +렉 +렊 +렋 +렌 +렍 +렎 +렏 +렐 +렑 +렒 +렓 +렔 +렕 +렖 +렗 +렘 +렙 +렚 +렛 +렜 +렝 +렞 +렟 +렠 +렡 +렢 +렣 +려 +력 +렦 +렧 +련 +렩 +렪 +렫 +렬 +렭 +렮 +렯 +렰 +렱 +렲 +렳 +렴 +렵 +렶 +렷 +렸 +령 +렺 +렻 +렼 +렽 +렾 +렿 +례 +롁 +롂 +롃 +롄 +롅 +롆 +롇 +롈 +롉 +롊 +롋 +롌 +롍 +롎 +롏 +롐 +롑 +롒 +롓 +롔 +롕 +롖 +롗 +롘 +롙 +롚 +롛 +로 +록 +롞 +롟 +론 +롡 +롢 +롣 +롤 +롥 +롦 +롧 +롨 +롩 +롪 +롫 +롬 +롭 +롮 +롯 +롰 +롱 +롲 +롳 +롴 +롵 +롶 +롷 +롸 +롹 +롺 +롻 +롼 +롽 +롾 +롿 +뢀 +뢁 +뢂 +뢃 +뢄 +뢅 +뢆 +뢇 +뢈 +뢉 +뢊 +뢋 +뢌 +뢍 +뢎 +뢏 +뢐 +뢑 +뢒 +뢓 +뢔 +뢕 +뢖 +뢗 +뢘 +뢙 +뢚 +뢛 +뢜 +뢝 +뢞 +뢟 +뢠 +뢡 +뢢 +뢣 +뢤 +뢥 +뢦 +뢧 +뢨 +뢩 +뢪 +뢫 +뢬 +뢭 +뢮 +뢯 +뢰 +뢱 +뢲 +뢳 +뢴 +뢵 +뢶 +뢷 +뢸 +뢹 +뢺 +뢻 +뢼 +뢽 +뢾 +뢿 +룀 +룁 +룂 +룃 +룄 +룅 +룆 +룇 +룈 +룉 +룊 +룋 +료 +룍 +룎 +룏 +룐 +룑 +룒 +룓 +룔 +룕 +룖 +룗 +룘 +룙 +룚 +룛 +룜 +룝 +룞 +룟 +룠 +룡 +룢 +룣 +룤 +룥 +룦 +룧 +루 +룩 +룪 +룫 +룬 +룭 +룮 +룯 +룰 +룱 +룲 +룳 +룴 +룵 +룶 +룷 +룸 +룹 +룺 +룻 +룼 +룽 +룾 +룿 +뤀 +뤁 +뤂 +뤃 +뤄 +뤅 +뤆 +뤇 +뤈 +뤉 +뤊 +뤋 +뤌 +뤍 +뤎 +뤏 +뤐 +뤑 +뤒 +뤓 +뤔 +뤕 +뤖 +뤗 +뤘 +뤙 +뤚 +뤛 +뤜 +뤝 +뤞 +뤟 +뤠 +뤡 +뤢 +뤣 +뤤 +뤥 +뤦 +뤧 +뤨 +뤩 +뤪 +뤫 +뤬 +뤭 +뤮 +뤯 +뤰 +뤱 +뤲 +뤳 +뤴 +뤵 +뤶 +뤷 +뤸 +뤹 +뤺 +뤻 +뤼 +뤽 +뤾 +뤿 +륀 +륁 +륂 +륃 +륄 +륅 +륆 +륇 +륈 +륉 +륊 +륋 +륌 +륍 +륎 +륏 +륐 +륑 +륒 +륓 +륔 +륕 +륖 +륗 +류 +륙 +륚 +륛 +륜 +륝 +륞 +륟 +률 +륡 +륢 +륣 +륤 +륥 +륦 +륧 +륨 +륩 +륪 +륫 +륬 +륭 +륮 +륯 +륰 +륱 +륲 +륳 +르 +륵 +륶 +륷 +른 +륹 +륺 +륻 +를 +륽 +륾 +륿 +릀 +릁 +릂 +릃 +름 +릅 +릆 +릇 +릈 +릉 +릊 +릋 +릌 +릍 +릎 +릏 +릐 +릑 +릒 +릓 +릔 +릕 +릖 +릗 +릘 +릙 +릚 +릛 +릜 +릝 +릞 +릟 +릠 +릡 +릢 +릣 +릤 +릥 +릦 +릧 +릨 +릩 +릪 +릫 +리 +릭 +릮 +릯 +린 +릱 +릲 +릳 +릴 +릵 +릶 +릷 +릸 +릹 +릺 +릻 +림 +립 +릾 +릿 +맀 +링 +맂 +맃 +맄 +맅 +맆 +맇 +마 +막 +맊 +맋 +만 +맍 +많 +맏 +말 +맑 +맒 +맓 +맔 +맕 +맖 +맗 +맘 +맙 +맚 +맛 +맜 +망 +맞 +맟 +맠 +맡 +맢 +맣 +매 +맥 +맦 +맧 +맨 +맩 +맪 +맫 +맬 +맭 +맮 +맯 +맰 +맱 +맲 +맳 +맴 +맵 +맶 +맷 +맸 +맹 +맺 +맻 +맼 +맽 +맾 +맿 +먀 +먁 +먂 +먃 +먄 +먅 +먆 +먇 +먈 +먉 +먊 +먋 +먌 +먍 +먎 +먏 +먐 +먑 +먒 +먓 +먔 +먕 +먖 +먗 +먘 +먙 +먚 +먛 +먜 +먝 +먞 +먟 +먠 +먡 +먢 +먣 +먤 +먥 +먦 +먧 +먨 +먩 +먪 +먫 +먬 +먭 +먮 +먯 +먰 +먱 +먲 +먳 +먴 +먵 +먶 +먷 +머 +먹 +먺 +먻 +먼 +먽 +먾 +먿 +멀 +멁 +멂 +멃 +멄 +멅 +멆 +멇 +멈 +멉 +멊 +멋 +멌 +멍 +멎 +멏 +멐 +멑 +멒 +멓 +메 +멕 +멖 +멗 +멘 +멙 +멚 +멛 +멜 +멝 +멞 +멟 +멠 +멡 +멢 +멣 +멤 +멥 +멦 +멧 +멨 +멩 +멪 +멫 +멬 +멭 +멮 +멯 +며 +멱 +멲 +멳 +면 +멵 +멶 +멷 +멸 +멹 +멺 +멻 +멼 +멽 +멾 +멿 +몀 +몁 +몂 +몃 +몄 +명 +몆 +몇 +몈 +몉 +몊 +몋 +몌 +몍 +몎 +몏 +몐 +몑 +몒 +몓 +몔 +몕 +몖 +몗 +몘 +몙 +몚 +몛 +몜 +몝 +몞 +몟 +몠 +몡 +몢 +몣 +몤 +몥 +몦 +몧 +모 +목 +몪 +몫 +몬 +몭 +몮 +몯 +몰 +몱 +몲 +몳 +몴 +몵 +몶 +몷 +몸 +몹 +몺 +못 +몼 +몽 +몾 +몿 +뫀 +뫁 +뫂 +뫃 +뫄 +뫅 +뫆 +뫇 +뫈 +뫉 +뫊 +뫋 +뫌 +뫍 +뫎 +뫏 +뫐 +뫑 +뫒 +뫓 +뫔 +뫕 +뫖 +뫗 +뫘 +뫙 +뫚 +뫛 +뫜 +뫝 +뫞 +뫟 +뫠 +뫡 +뫢 +뫣 +뫤 +뫥 +뫦 +뫧 +뫨 +뫩 +뫪 +뫫 +뫬 +뫭 +뫮 +뫯 +뫰 +뫱 +뫲 +뫳 +뫴 +뫵 +뫶 +뫷 +뫸 +뫹 +뫺 +뫻 +뫼 +뫽 +뫾 +뫿 +묀 +묁 +묂 +묃 +묄 +묅 +묆 +묇 +묈 +묉 +묊 +묋 +묌 +묍 +묎 +묏 +묐 +묑 +묒 +묓 +묔 +묕 +묖 +묗 +묘 +묙 +묚 +묛 +묜 +묝 +묞 +묟 +묠 +묡 +묢 +묣 +묤 +묥 +묦 +묧 +묨 +묩 +묪 +묫 +묬 +묭 +묮 +묯 +묰 +묱 +묲 +묳 +무 +묵 +묶 +묷 +문 +묹 +묺 +묻 +물 +묽 +묾 +묿 +뭀 +뭁 +뭂 +뭃 +뭄 +뭅 +뭆 +뭇 +뭈 +뭉 +뭊 +뭋 +뭌 +뭍 +뭎 +뭏 +뭐 +뭑 +뭒 +뭓 +뭔 +뭕 +뭖 +뭗 +뭘 +뭙 +뭚 +뭛 +뭜 +뭝 +뭞 +뭟 +뭠 +뭡 +뭢 +뭣 +뭤 +뭥 +뭦 +뭧 +뭨 +뭩 +뭪 +뭫 +뭬 +뭭 +뭮 +뭯 +뭰 +뭱 +뭲 +뭳 +뭴 +뭵 +뭶 +뭷 +뭸 +뭹 +뭺 +뭻 +뭼 +뭽 +뭾 +뭿 +뮀 +뮁 +뮂 +뮃 +뮄 +뮅 +뮆 +뮇 +뮈 +뮉 +뮊 +뮋 +뮌 +뮍 +뮎 +뮏 +뮐 +뮑 +뮒 +뮓 +뮔 +뮕 +뮖 +뮗 +뮘 +뮙 +뮚 +뮛 +뮜 +뮝 +뮞 +뮟 +뮠 +뮡 +뮢 +뮣 +뮤 +뮥 +뮦 +뮧 +뮨 +뮩 +뮪 +뮫 +뮬 +뮭 +뮮 +뮯 +뮰 +뮱 +뮲 +뮳 +뮴 +뮵 +뮶 +뮷 +뮸 +뮹 +뮺 +뮻 +뮼 +뮽 +뮾 +뮿 +므 +믁 +믂 +믃 +믄 +믅 +믆 +믇 +믈 +믉 +믊 +믋 +믌 +믍 +믎 +믏 +믐 +믑 +믒 +믓 +믔 +믕 +믖 +믗 +믘 +믙 +믚 +믛 +믜 +믝 +믞 +믟 +믠 +믡 +믢 +믣 +믤 +믥 +믦 +믧 +믨 +믩 +믪 +믫 +믬 +믭 +믮 +믯 +믰 +믱 +믲 +믳 +믴 +믵 +믶 +믷 +미 +믹 +믺 +믻 +민 +믽 +믾 +믿 +밀 +밁 +밂 +밃 +밄 +밅 +밆 +밇 +밈 +밉 +밊 +밋 +밌 +밍 +밎 +및 +밐 +밑 +밒 +밓 +바 +박 +밖 +밗 +반 +밙 +밚 +받 +발 +밝 +밞 +밟 +밠 +밡 +밢 +밣 +밤 +밥 +밦 +밧 +밨 +방 +밪 +밫 +밬 +밭 +밮 +밯 +배 +백 +밲 +밳 +밴 +밵 +밶 +밷 +밸 +밹 +밺 +밻 +밼 +밽 +밾 +밿 +뱀 +뱁 +뱂 +뱃 +뱄 +뱅 +뱆 +뱇 +뱈 +뱉 +뱊 +뱋 +뱌 +뱍 +뱎 +뱏 +뱐 +뱑 +뱒 +뱓 +뱔 +뱕 +뱖 +뱗 +뱘 +뱙 +뱚 +뱛 +뱜 +뱝 +뱞 +뱟 +뱠 +뱡 +뱢 +뱣 +뱤 +뱥 +뱦 +뱧 +뱨 +뱩 +뱪 +뱫 +뱬 +뱭 +뱮 +뱯 +뱰 +뱱 +뱲 +뱳 +뱴 +뱵 +뱶 +뱷 +뱸 +뱹 +뱺 +뱻 +뱼 +뱽 +뱾 +뱿 +벀 +벁 +벂 +벃 +버 +벅 +벆 +벇 +번 +벉 +벊 +벋 +벌 +벍 +벎 +벏 +벐 +벑 +벒 +벓 +범 +법 +벖 +벗 +벘 +벙 +벚 +벛 +벜 +벝 +벞 +벟 +베 +벡 +벢 +벣 +벤 +벥 +벦 +벧 +벨 +벩 +벪 +벫 +벬 +벭 +벮 +벯 +벰 +벱 +벲 +벳 +벴 +벵 +벶 +벷 +벸 +벹 +벺 +벻 +벼 +벽 +벾 +벿 +변 +볁 +볂 +볃 +별 +볅 +볆 +볇 +볈 +볉 +볊 +볋 +볌 +볍 +볎 +볏 +볐 +병 +볒 +볓 +볔 +볕 +볖 +볗 +볘 +볙 +볚 +볛 +볜 +볝 +볞 +볟 +볠 +볡 +볢 +볣 +볤 +볥 +볦 +볧 +볨 +볩 +볪 +볫 +볬 +볭 +볮 +볯 +볰 +볱 +볲 +볳 +보 +복 +볶 +볷 +본 +볹 +볺 +볻 +볼 +볽 +볾 +볿 +봀 +봁 +봂 +봃 +봄 +봅 +봆 +봇 +봈 +봉 +봊 +봋 +봌 +봍 +봎 +봏 +봐 +봑 +봒 +봓 +봔 +봕 +봖 +봗 +봘 +봙 +봚 +봛 +봜 +봝 +봞 +봟 +봠 +봡 +봢 +봣 +봤 +봥 +봦 +봧 +봨 +봩 +봪 +봫 +봬 +봭 +봮 +봯 +봰 +봱 +봲 +봳 +봴 +봵 +봶 +봷 +봸 +봹 +봺 +봻 +봼 +봽 +봾 +봿 +뵀 +뵁 +뵂 +뵃 +뵄 +뵅 +뵆 +뵇 +뵈 +뵉 +뵊 +뵋 +뵌 +뵍 +뵎 +뵏 +뵐 +뵑 +뵒 +뵓 +뵔 +뵕 +뵖 +뵗 +뵘 +뵙 +뵚 +뵛 +뵜 +뵝 +뵞 +뵟 +뵠 +뵡 +뵢 +뵣 +뵤 +뵥 +뵦 +뵧 +뵨 +뵩 +뵪 +뵫 +뵬 +뵭 +뵮 +뵯 +뵰 +뵱 +뵲 +뵳 +뵴 +뵵 +뵶 +뵷 +뵸 +뵹 +뵺 +뵻 +뵼 +뵽 +뵾 +뵿 +부 +북 +붂 +붃 +분 +붅 +붆 +붇 +불 +붉 +붊 +붋 +붌 +붍 +붎 +붏 +붐 +붑 +붒 +붓 +붔 +붕 +붖 +붗 +붘 +붙 +붚 +붛 +붜 +붝 +붞 +붟 +붠 +붡 +붢 +붣 +붤 +붥 +붦 +붧 +붨 +붩 +붪 +붫 +붬 +붭 +붮 +붯 +붰 +붱 +붲 +붳 +붴 +붵 +붶 +붷 +붸 +붹 +붺 +붻 +붼 +붽 +붾 +붿 +뷀 +뷁 +뷂 +뷃 +뷄 +뷅 +뷆 +뷇 +뷈 +뷉 +뷊 +뷋 +뷌 +뷍 +뷎 +뷏 +뷐 +뷑 +뷒 +뷓 +뷔 +뷕 +뷖 +뷗 +뷘 +뷙 +뷚 +뷛 +뷜 +뷝 +뷞 +뷟 +뷠 +뷡 +뷢 +뷣 +뷤 +뷥 +뷦 +뷧 +뷨 +뷩 +뷪 +뷫 +뷬 +뷭 +뷮 +뷯 +뷰 +뷱 +뷲 +뷳 +뷴 +뷵 +뷶 +뷷 +뷸 +뷹 +뷺 +뷻 +뷼 +뷽 +뷾 +뷿 +븀 +븁 +븂 +븃 +븄 +븅 +븆 +븇 +븈 +븉 +븊 +븋 +브 +븍 +븎 +븏 +븐 +븑 +븒 +븓 +블 +븕 +븖 +븗 +븘 +븙 +븚 +븛 +븜 +븝 +븞 +븟 +븠 +븡 +븢 +븣 +븤 +븥 +븦 +븧 +븨 +븩 +븪 +븫 +븬 +븭 +븮 +븯 +븰 +븱 +븲 +븳 +븴 +븵 +븶 +븷 +븸 +븹 +븺 +븻 +븼 +븽 +븾 +븿 +빀 +빁 +빂 +빃 +비 +빅 +빆 +빇 +빈 +빉 +빊 +빋 +빌 +빍 +빎 +빏 +빐 +빑 +빒 +빓 +빔 +빕 +빖 +빗 +빘 +빙 +빚 +빛 +빜 +빝 +빞 +빟 +빠 +빡 +빢 +빣 +빤 +빥 +빦 +빧 +빨 +빩 +빪 +빫 +빬 +빭 +빮 +빯 +빰 +빱 +빲 +빳 +빴 +빵 +빶 +빷 +빸 +빹 +빺 +빻 +빼 +빽 +빾 +빿 +뺀 +뺁 +뺂 +뺃 +뺄 +뺅 +뺆 +뺇 +뺈 +뺉 +뺊 +뺋 +뺌 +뺍 +뺎 +뺏 +뺐 +뺑 +뺒 +뺓 +뺔 +뺕 +뺖 +뺗 +뺘 +뺙 +뺚 +뺛 +뺜 +뺝 +뺞 +뺟 +뺠 +뺡 +뺢 +뺣 +뺤 +뺥 +뺦 +뺧 +뺨 +뺩 +뺪 +뺫 +뺬 +뺭 +뺮 +뺯 +뺰 +뺱 +뺲 +뺳 +뺴 +뺵 +뺶 +뺷 +뺸 +뺹 +뺺 +뺻 +뺼 +뺽 +뺾 +뺿 +뻀 +뻁 +뻂 +뻃 +뻄 +뻅 +뻆 +뻇 +뻈 +뻉 +뻊 +뻋 +뻌 +뻍 +뻎 +뻏 +뻐 +뻑 +뻒 +뻓 +뻔 +뻕 +뻖 +뻗 +뻘 +뻙 +뻚 +뻛 +뻜 +뻝 +뻞 +뻟 +뻠 +뻡 +뻢 +뻣 +뻤 +뻥 +뻦 +뻧 +뻨 +뻩 +뻪 +뻫 +뻬 +뻭 +뻮 +뻯 +뻰 +뻱 +뻲 +뻳 +뻴 +뻵 +뻶 +뻷 +뻸 +뻹 +뻺 +뻻 +뻼 +뻽 +뻾 +뻿 +뼀 +뼁 +뼂 +뼃 +뼄 +뼅 +뼆 +뼇 +뼈 +뼉 +뼊 +뼋 +뼌 +뼍 +뼎 +뼏 +뼐 +뼑 +뼒 +뼓 +뼔 +뼕 +뼖 +뼗 +뼘 +뼙 +뼚 +뼛 +뼜 +뼝 +뼞 +뼟 +뼠 +뼡 +뼢 +뼣 +뼤 +뼥 +뼦 +뼧 +뼨 +뼩 +뼪 +뼫 +뼬 +뼭 +뼮 +뼯 +뼰 +뼱 +뼲 +뼳 +뼴 +뼵 +뼶 +뼷 +뼸 +뼹 +뼺 +뼻 +뼼 +뼽 +뼾 +뼿 +뽀 +뽁 +뽂 +뽃 +뽄 +뽅 +뽆 +뽇 +뽈 +뽉 +뽊 +뽋 +뽌 +뽍 +뽎 +뽏 +뽐 +뽑 +뽒 +뽓 +뽔 +뽕 +뽖 +뽗 +뽘 +뽙 +뽚 +뽛 +뽜 +뽝 +뽞 +뽟 +뽠 +뽡 +뽢 +뽣 +뽤 +뽥 +뽦 +뽧 +뽨 +뽩 +뽪 +뽫 +뽬 +뽭 +뽮 +뽯 +뽰 +뽱 +뽲 +뽳 +뽴 +뽵 +뽶 +뽷 +뽸 +뽹 +뽺 +뽻 +뽼 +뽽 +뽾 +뽿 +뾀 +뾁 +뾂 +뾃 +뾄 +뾅 +뾆 +뾇 +뾈 +뾉 +뾊 +뾋 +뾌 +뾍 +뾎 +뾏 +뾐 +뾑 +뾒 +뾓 +뾔 +뾕 +뾖 +뾗 +뾘 +뾙 +뾚 +뾛 +뾜 +뾝 +뾞 +뾟 +뾠 +뾡 +뾢 +뾣 +뾤 +뾥 +뾦 +뾧 +뾨 +뾩 +뾪 +뾫 +뾬 +뾭 +뾮 +뾯 +뾰 +뾱 +뾲 +뾳 +뾴 +뾵 +뾶 +뾷 +뾸 +뾹 +뾺 +뾻 +뾼 +뾽 +뾾 +뾿 +뿀 +뿁 +뿂 +뿃 +뿄 +뿅 +뿆 +뿇 +뿈 +뿉 +뿊 +뿋 +뿌 +뿍 +뿎 +뿏 +뿐 +뿑 +뿒 +뿓 +뿔 +뿕 +뿖 +뿗 +뿘 +뿙 +뿚 +뿛 +뿜 +뿝 +뿞 +뿟 +뿠 +뿡 +뿢 +뿣 +뿤 +뿥 +뿦 +뿧 +뿨 +뿩 +뿪 +뿫 +뿬 +뿭 +뿮 +뿯 +뿰 +뿱 +뿲 +뿳 +뿴 +뿵 +뿶 +뿷 +뿸 +뿹 +뿺 +뿻 +뿼 +뿽 +뿾 +뿿 +쀀 +쀁 +쀂 +쀃 +쀄 +쀅 +쀆 +쀇 +쀈 +쀉 +쀊 +쀋 +쀌 +쀍 +쀎 +쀏 +쀐 +쀑 +쀒 +쀓 +쀔 +쀕 +쀖 +쀗 +쀘 +쀙 +쀚 +쀛 +쀜 +쀝 +쀞 +쀟 +쀠 +쀡 +쀢 +쀣 +쀤 +쀥 +쀦 +쀧 +쀨 +쀩 +쀪 +쀫 +쀬 +쀭 +쀮 +쀯 +쀰 +쀱 +쀲 +쀳 +쀴 +쀵 +쀶 +쀷 +쀸 +쀹 +쀺 +쀻 +쀼 +쀽 +쀾 +쀿 +쁀 +쁁 +쁂 +쁃 +쁄 +쁅 +쁆 +쁇 +쁈 +쁉 +쁊 +쁋 +쁌 +쁍 +쁎 +쁏 +쁐 +쁑 +쁒 +쁓 +쁔 +쁕 +쁖 +쁗 +쁘 +쁙 +쁚 +쁛 +쁜 +쁝 +쁞 +쁟 +쁠 +쁡 +쁢 +쁣 +쁤 +쁥 +쁦 +쁧 +쁨 +쁩 +쁪 +쁫 +쁬 +쁭 +쁮 +쁯 +쁰 +쁱 +쁲 +쁳 +쁴 +쁵 +쁶 +쁷 +쁸 +쁹 +쁺 +쁻 +쁼 +쁽 +쁾 +쁿 +삀 +삁 +삂 +삃 +삄 +삅 +삆 +삇 +삈 +삉 +삊 +삋 +삌 +삍 +삎 +삏 +삐 +삑 +삒 +삓 +삔 +삕 +삖 +삗 +삘 +삙 +삚 +삛 +삜 +삝 +삞 +삟 +삠 +삡 +삢 +삣 +삤 +삥 +삦 +삧 +삨 +삩 +삪 +삫 +사 +삭 +삮 +삯 +산 +삱 +삲 +삳 +살 +삵 +삶 +삷 +삸 +삹 +삺 +삻 +삼 +삽 +삾 +삿 +샀 +상 +샂 +샃 +샄 +샅 +샆 +샇 +새 +색 +샊 +샋 +샌 +샍 +샎 +샏 +샐 +샑 +샒 +샓 +샔 +샕 +샖 +샗 +샘 +샙 +샚 +샛 +샜 +생 +샞 +샟 +샠 +샡 +샢 +샣 +샤 +샥 +샦 +샧 +샨 +샩 +샪 +샫 +샬 +샭 +샮 +샯 +샰 +샱 +샲 +샳 +샴 +샵 +샶 +샷 +샸 +샹 +샺 +샻 +샼 +샽 +샾 +샿 +섀 +섁 +섂 +섃 +섄 +섅 +섆 +섇 +섈 +섉 +섊 +섋 +섌 +섍 +섎 +섏 +섐 +섑 +섒 +섓 +섔 +섕 +섖 +섗 +섘 +섙 +섚 +섛 +서 +석 +섞 +섟 +선 +섡 +섢 +섣 +설 +섥 +섦 +섧 +섨 +섩 +섪 +섫 +섬 +섭 +섮 +섯 +섰 +성 +섲 +섳 +섴 +섵 +섶 +섷 +세 +섹 +섺 +섻 +센 +섽 +섾 +섿 +셀 +셁 +셂 +셃 +셄 +셅 +셆 +셇 +셈 +셉 +셊 +셋 +셌 +셍 +셎 +셏 +셐 +셑 +셒 +셓 +셔 +셕 +셖 +셗 +션 +셙 +셚 +셛 +셜 +셝 +셞 +셟 +셠 +셡 +셢 +셣 +셤 +셥 +셦 +셧 +셨 +셩 +셪 +셫 +셬 +셭 +셮 +셯 +셰 +셱 +셲 +셳 +셴 +셵 +셶 +셷 +셸 +셹 +셺 +셻 +셼 +셽 +셾 +셿 +솀 +솁 +솂 +솃 +솄 +솅 +솆 +솇 +솈 +솉 +솊 +솋 +소 +속 +솎 +솏 +손 +솑 +솒 +솓 +솔 +솕 +솖 +솗 +솘 +솙 +솚 +솛 +솜 +솝 +솞 +솟 +솠 +송 +솢 +솣 +솤 +솥 +솦 +솧 +솨 +솩 +솪 +솫 +솬 +솭 +솮 +솯 +솰 +솱 +솲 +솳 +솴 +솵 +솶 +솷 +솸 +솹 +솺 +솻 +솼 +솽 +솾 +솿 +쇀 +쇁 +쇂 +쇃 +쇄 +쇅 +쇆 +쇇 +쇈 +쇉 +쇊 +쇋 +쇌 +쇍 +쇎 +쇏 +쇐 +쇑 +쇒 +쇓 +쇔 +쇕 +쇖 +쇗 +쇘 +쇙 +쇚 +쇛 +쇜 +쇝 +쇞 +쇟 +쇠 +쇡 +쇢 +쇣 +쇤 +쇥 +쇦 +쇧 +쇨 +쇩 +쇪 +쇫 +쇬 +쇭 +쇮 +쇯 +쇰 +쇱 +쇲 +쇳 +쇴 +쇵 +쇶 +쇷 +쇸 +쇹 +쇺 +쇻 +쇼 +쇽 +쇾 +쇿 +숀 +숁 +숂 +숃 +숄 +숅 +숆 +숇 +숈 +숉 +숊 +숋 +숌 +숍 +숎 +숏 +숐 +숑 +숒 +숓 +숔 +숕 +숖 +숗 +수 +숙 +숚 +숛 +순 +숝 +숞 +숟 +술 +숡 +숢 +숣 +숤 +숥 +숦 +숧 +숨 +숩 +숪 +숫 +숬 +숭 +숮 +숯 +숰 +숱 +숲 +숳 +숴 +숵 +숶 +숷 +숸 +숹 +숺 +숻 +숼 +숽 +숾 +숿 +쉀 +쉁 +쉂 +쉃 +쉄 +쉅 +쉆 +쉇 +쉈 +쉉 +쉊 +쉋 +쉌 +쉍 +쉎 +쉏 +쉐 +쉑 +쉒 +쉓 +쉔 +쉕 +쉖 +쉗 +쉘 +쉙 +쉚 +쉛 +쉜 +쉝 +쉞 +쉟 +쉠 +쉡 +쉢 +쉣 +쉤 +쉥 +쉦 +쉧 +쉨 +쉩 +쉪 +쉫 +쉬 +쉭 +쉮 +쉯 +쉰 +쉱 +쉲 +쉳 +쉴 +쉵 +쉶 +쉷 +쉸 +쉹 +쉺 +쉻 +쉼 +쉽 +쉾 +쉿 +슀 +슁 +슂 +슃 +슄 +슅 +슆 +슇 +슈 +슉 +슊 +슋 +슌 +슍 +슎 +슏 +슐 +슑 +슒 +슓 +슔 +슕 +슖 +슗 +슘 +슙 +슚 +슛 +슜 +슝 +슞 +슟 +슠 +슡 +슢 +슣 +스 +슥 +슦 +슧 +슨 +슩 +슪 +슫 +슬 +슭 +슮 +슯 +슰 +슱 +슲 +슳 +슴 +습 +슶 +슷 +슸 +승 +슺 +슻 +슼 +슽 +슾 +슿 +싀 +싁 +싂 +싃 +싄 +싅 +싆 +싇 +싈 +싉 +싊 +싋 +싌 +싍 +싎 +싏 +싐 +싑 +싒 +싓 +싔 +싕 +싖 +싗 +싘 +싙 +싚 +싛 +시 +식 +싞 +싟 +신 +싡 +싢 +싣 +실 +싥 +싦 +싧 +싨 +싩 +싪 +싫 +심 +십 +싮 +싯 +싰 +싱 +싲 +싳 +싴 +싵 +싶 +싷 +싸 +싹 +싺 +싻 +싼 +싽 +싾 +싿 +쌀 +쌁 +쌂 +쌃 +쌄 +쌅 +쌆 +쌇 +쌈 +쌉 +쌊 +쌋 +쌌 +쌍 +쌎 +쌏 +쌐 +쌑 +쌒 +쌓 +쌔 +쌕 +쌖 +쌗 +쌘 +쌙 +쌚 +쌛 +쌜 +쌝 +쌞 +쌟 +쌠 +쌡 +쌢 +쌣 +쌤 +쌥 +쌦 +쌧 +쌨 +쌩 +쌪 +쌫 +쌬 +쌭 +쌮 +쌯 +쌰 +쌱 +쌲 +쌳 +쌴 +쌵 +쌶 +쌷 +쌸 +쌹 +쌺 +쌻 +쌼 +쌽 +쌾 +쌿 +썀 +썁 +썂 +썃 +썄 +썅 +썆 +썇 +썈 +썉 +썊 +썋 +썌 +썍 +썎 +썏 +썐 +썑 +썒 +썓 +썔 +썕 +썖 +썗 +썘 +썙 +썚 +썛 +썜 +썝 +썞 +썟 +썠 +썡 +썢 +썣 +썤 +썥 +썦 +썧 +써 +썩 +썪 +썫 +썬 +썭 +썮 +썯 +썰 +썱 +썲 +썳 +썴 +썵 +썶 +썷 +썸 +썹 +썺 +썻 +썼 +썽 +썾 +썿 +쎀 +쎁 +쎂 +쎃 +쎄 +쎅 +쎆 +쎇 +쎈 +쎉 +쎊 +쎋 +쎌 +쎍 +쎎 +쎏 +쎐 +쎑 +쎒 +쎓 +쎔 +쎕 +쎖 +쎗 +쎘 +쎙 +쎚 +쎛 +쎜 +쎝 +쎞 +쎟 +쎠 +쎡 +쎢 +쎣 +쎤 +쎥 +쎦 +쎧 +쎨 +쎩 +쎪 +쎫 +쎬 +쎭 +쎮 +쎯 +쎰 +쎱 +쎲 +쎳 +쎴 +쎵 +쎶 +쎷 +쎸 +쎹 +쎺 +쎻 +쎼 +쎽 +쎾 +쎿 +쏀 +쏁 +쏂 +쏃 +쏄 +쏅 +쏆 +쏇 +쏈 +쏉 +쏊 +쏋 +쏌 +쏍 +쏎 +쏏 +쏐 +쏑 +쏒 +쏓 +쏔 +쏕 +쏖 +쏗 +쏘 +쏙 +쏚 +쏛 +쏜 +쏝 +쏞 +쏟 +쏠 +쏡 +쏢 +쏣 +쏤 +쏥 +쏦 +쏧 +쏨 +쏩 +쏪 +쏫 +쏬 +쏭 +쏮 +쏯 +쏰 +쏱 +쏲 +쏳 +쏴 +쏵 +쏶 +쏷 +쏸 +쏹 +쏺 +쏻 +쏼 +쏽 +쏾 +쏿 +쐀 +쐁 +쐂 +쐃 +쐄 +쐅 +쐆 +쐇 +쐈 +쐉 +쐊 +쐋 +쐌 +쐍 +쐎 +쐏 +쐐 +쐑 +쐒 +쐓 +쐔 +쐕 +쐖 +쐗 +쐘 +쐙 +쐚 +쐛 +쐜 +쐝 +쐞 +쐟 +쐠 +쐡 +쐢 +쐣 +쐤 +쐥 +쐦 +쐧 +쐨 +쐩 +쐪 +쐫 +쐬 +쐭 +쐮 +쐯 +쐰 +쐱 +쐲 +쐳 +쐴 +쐵 +쐶 +쐷 +쐸 +쐹 +쐺 +쐻 +쐼 +쐽 +쐾 +쐿 +쑀 +쑁 +쑂 +쑃 +쑄 +쑅 +쑆 +쑇 +쑈 +쑉 +쑊 +쑋 +쑌 +쑍 +쑎 +쑏 +쑐 +쑑 +쑒 +쑓 +쑔 +쑕 +쑖 +쑗 +쑘 +쑙 +쑚 +쑛 +쑜 +쑝 +쑞 +쑟 +쑠 +쑡 +쑢 +쑣 +쑤 +쑥 +쑦 +쑧 +쑨 +쑩 +쑪 +쑫 +쑬 +쑭 +쑮 +쑯 +쑰 +쑱 +쑲 +쑳 +쑴 +쑵 +쑶 +쑷 +쑸 +쑹 +쑺 +쑻 +쑼 +쑽 +쑾 +쑿 +쒀 +쒁 +쒂 +쒃 +쒄 +쒅 +쒆 +쒇 +쒈 +쒉 +쒊 +쒋 +쒌 +쒍 +쒎 +쒏 +쒐 +쒑 +쒒 +쒓 +쒔 +쒕 +쒖 +쒗 +쒘 +쒙 +쒚 +쒛 +쒜 +쒝 +쒞 +쒟 +쒠 +쒡 +쒢 +쒣 +쒤 +쒥 +쒦 +쒧 +쒨 +쒩 +쒪 +쒫 +쒬 +쒭 +쒮 +쒯 +쒰 +쒱 +쒲 +쒳 +쒴 +쒵 +쒶 +쒷 +쒸 +쒹 +쒺 +쒻 +쒼 +쒽 +쒾 +쒿 +쓀 +쓁 +쓂 +쓃 +쓄 +쓅 +쓆 +쓇 +쓈 +쓉 +쓊 +쓋 +쓌 +쓍 +쓎 +쓏 +쓐 +쓑 +쓒 +쓓 +쓔 +쓕 +쓖 +쓗 +쓘 +쓙 +쓚 +쓛 +쓜 +쓝 +쓞 +쓟 +쓠 +쓡 +쓢 +쓣 +쓤 +쓥 +쓦 +쓧 +쓨 +쓩 +쓪 +쓫 +쓬 +쓭 +쓮 +쓯 +쓰 +쓱 +쓲 +쓳 +쓴 +쓵 +쓶 +쓷 +쓸 +쓹 +쓺 +쓻 +쓼 +쓽 +쓾 +쓿 +씀 +씁 +씂 +씃 +씄 +씅 +씆 +씇 +씈 +씉 +씊 +씋 +씌 +씍 +씎 +씏 +씐 +씑 +씒 +씓 +씔 +씕 +씖 +씗 +씘 +씙 +씚 +씛 +씜 +씝 +씞 +씟 +씠 +씡 +씢 +씣 +씤 +씥 +씦 +씧 +씨 +씩 +씪 +씫 +씬 +씭 +씮 +씯 +씰 +씱 +씲 +씳 +씴 +씵 +씶 +씷 +씸 +씹 +씺 +씻 +씼 +씽 +씾 +씿 +앀 +앁 +앂 +앃 +아 +악 +앆 +앇 +안 +앉 +않 +앋 +알 +앍 +앎 +앏 +앐 +앑 +앒 +앓 +암 +압 +앖 +앗 +았 +앙 +앚 +앛 +앜 +앝 +앞 +앟 +애 +액 +앢 +앣 +앤 +앥 +앦 +앧 +앨 +앩 +앪 +앫 +앬 +앭 +앮 +앯 +앰 +앱 +앲 +앳 +앴 +앵 +앶 +앷 +앸 +앹 +앺 +앻 +야 +약 +앾 +앿 +얀 +얁 +얂 +얃 +얄 +얅 +얆 +얇 +얈 +얉 +얊 +얋 +얌 +얍 +얎 +얏 +얐 +양 +얒 +얓 +얔 +얕 +얖 +얗 +얘 +얙 +얚 +얛 +얜 +얝 +얞 +얟 +얠 +얡 +얢 +얣 +얤 +얥 +얦 +얧 +얨 +얩 +얪 +얫 +얬 +얭 +얮 +얯 +얰 +얱 +얲 +얳 +어 +억 +얶 +얷 +언 +얹 +얺 +얻 +얼 +얽 +얾 +얿 +엀 +엁 +엂 +엃 +엄 +업 +없 +엇 +었 +엉 +엊 +엋 +엌 +엍 +엎 +엏 +에 +엑 +엒 +엓 +엔 +엕 +엖 +엗 +엘 +엙 +엚 +엛 +엜 +엝 +엞 +엟 +엠 +엡 +엢 +엣 +엤 +엥 +엦 +엧 +엨 +엩 +엪 +엫 +여 +역 +엮 +엯 +연 +엱 +엲 +엳 +열 +엵 +엶 +엷 +엸 +엹 +엺 +엻 +염 +엽 +엾 +엿 +였 +영 +옂 +옃 +옄 +옅 +옆 +옇 +예 +옉 +옊 +옋 +옌 +옍 +옎 +옏 +옐 +옑 +옒 +옓 +옔 +옕 +옖 +옗 +옘 +옙 +옚 +옛 +옜 +옝 +옞 +옟 +옠 +옡 +옢 +옣 +오 +옥 +옦 +옧 +온 +옩 +옪 +옫 +올 +옭 +옮 +옯 +옰 +옱 +옲 +옳 +옴 +옵 +옶 +옷 +옸 +옹 +옺 +옻 +옼 +옽 +옾 +옿 +와 +왁 +왂 +왃 +완 +왅 +왆 +왇 +왈 +왉 +왊 +왋 +왌 +왍 +왎 +왏 +왐 +왑 +왒 +왓 +왔 +왕 +왖 +왗 +왘 +왙 +왚 +왛 +왜 +왝 +왞 +왟 +왠 +왡 +왢 +왣 +왤 +왥 +왦 +왧 +왨 +왩 +왪 +왫 +왬 +왭 +왮 +왯 +왰 +왱 +왲 +왳 +왴 +왵 +왶 +왷 +외 +왹 +왺 +왻 +왼 +왽 +왾 +왿 +욀 +욁 +욂 +욃 +욄 +욅 +욆 +욇 +욈 +욉 +욊 +욋 +욌 +욍 +욎 +욏 +욐 +욑 +욒 +욓 +요 +욕 +욖 +욗 +욘 +욙 +욚 +욛 +욜 +욝 +욞 +욟 +욠 +욡 +욢 +욣 +욤 +욥 +욦 +욧 +욨 +용 +욪 +욫 +욬 +욭 +욮 +욯 +우 +욱 +욲 +욳 +운 +욵 +욶 +욷 +울 +욹 +욺 +욻 +욼 +욽 +욾 +욿 +움 +웁 +웂 +웃 +웄 +웅 +웆 +웇 +웈 +웉 +웊 +웋 +워 +웍 +웎 +웏 +원 +웑 +웒 +웓 +월 +웕 +웖 +웗 +웘 +웙 +웚 +웛 +웜 +웝 +웞 +웟 +웠 +웡 +웢 +웣 +웤 +웥 +웦 +웧 +웨 +웩 +웪 +웫 +웬 +웭 +웮 +웯 +웰 +웱 +웲 +웳 +웴 +웵 +웶 +웷 +웸 +웹 +웺 +웻 +웼 +웽 +웾 +웿 +윀 +윁 +윂 +윃 +위 +윅 +윆 +윇 +윈 +윉 +윊 +윋 +윌 +윍 +윎 +윏 +윐 +윑 +윒 +윓 +윔 +윕 +윖 +윗 +윘 +윙 +윚 +윛 +윜 +윝 +윞 +윟 +유 +육 +윢 +윣 +윤 +윥 +윦 +윧 +율 +윩 +윪 +윫 +윬 +윭 +윮 +윯 +윰 +윱 +윲 +윳 +윴 +융 +윶 +윷 +윸 +윹 +윺 +윻 +으 +윽 +윾 +윿 +은 +읁 +읂 +읃 +을 +읅 +읆 +읇 +읈 +읉 +읊 +읋 +음 +읍 +읎 +읏 +읐 +응 +읒 +읓 +읔 +읕 +읖 +읗 +의 +읙 +읚 +읛 +읜 +읝 +읞 +읟 +읠 +읡 +읢 +읣 +읤 +읥 +읦 +읧 +읨 +읩 +읪 +읫 +읬 +읭 +읮 +읯 +읰 +읱 +읲 +읳 +이 +익 +읶 +읷 +인 +읹 +읺 +읻 +일 +읽 +읾 +읿 +잀 +잁 +잂 +잃 +임 +입 +잆 +잇 +있 +잉 +잊 +잋 +잌 +잍 +잎 +잏 +자 +작 +잒 +잓 +잔 +잕 +잖 +잗 +잘 +잙 +잚 +잛 +잜 +잝 +잞 +잟 +잠 +잡 +잢 +잣 +잤 +장 +잦 +잧 +잨 +잩 +잪 +잫 +재 +잭 +잮 +잯 +잰 +잱 +잲 +잳 +잴 +잵 +잶 +잷 +잸 +잹 +잺 +잻 +잼 +잽 +잾 +잿 +쟀 +쟁 +쟂 +쟃 +쟄 +쟅 +쟆 +쟇 +쟈 +쟉 +쟊 +쟋 +쟌 +쟍 +쟎 +쟏 +쟐 +쟑 +쟒 +쟓 +쟔 +쟕 +쟖 +쟗 +쟘 +쟙 +쟚 +쟛 +쟜 +쟝 +쟞 +쟟 +쟠 +쟡 +쟢 +쟣 +쟤 +쟥 +쟦 +쟧 +쟨 +쟩 +쟪 +쟫 +쟬 +쟭 +쟮 +쟯 +쟰 +쟱 +쟲 +쟳 +쟴 +쟵 +쟶 +쟷 +쟸 +쟹 +쟺 +쟻 +쟼 +쟽 +쟾 +쟿 +저 +적 +젂 +젃 +전 +젅 +젆 +젇 +절 +젉 +젊 +젋 +젌 +젍 +젎 +젏 +점 +접 +젒 +젓 +젔 +정 +젖 +젗 +젘 +젙 +젚 +젛 +제 +젝 +젞 +젟 +젠 +젡 +젢 +젣 +젤 +젥 +젦 +젧 +젨 +젩 +젪 +젫 +젬 +젭 +젮 +젯 +젰 +젱 +젲 +젳 +젴 +젵 +젶 +젷 +져 +젹 +젺 +젻 +젼 +젽 +젾 +젿 +졀 +졁 +졂 +졃 +졄 +졅 +졆 +졇 +졈 +졉 +졊 +졋 +졌 +졍 +졎 +졏 +졐 +졑 +졒 +졓 +졔 +졕 +졖 +졗 +졘 +졙 +졚 +졛 +졜 +졝 +졞 +졟 +졠 +졡 +졢 +졣 +졤 +졥 +졦 +졧 +졨 +졩 +졪 +졫 +졬 +졭 +졮 +졯 +조 +족 +졲 +졳 +존 +졵 +졶 +졷 +졸 +졹 +졺 +졻 +졼 +졽 +졾 +졿 +좀 +좁 +좂 +좃 +좄 +종 +좆 +좇 +좈 +좉 +좊 +좋 +좌 +좍 +좎 +좏 +좐 +좑 +좒 +좓 +좔 +좕 +좖 +좗 +좘 +좙 +좚 +좛 +좜 +좝 +좞 +좟 +좠 +좡 +좢 +좣 +좤 +좥 +좦 +좧 +좨 +좩 +좪 +좫 +좬 +좭 +좮 +좯 +좰 +좱 +좲 +좳 +좴 +좵 +좶 +좷 +좸 +좹 +좺 +좻 +좼 +좽 +좾 +좿 +죀 +죁 +죂 +죃 +죄 +죅 +죆 +죇 +죈 +죉 +죊 +죋 +죌 +죍 +죎 +죏 +죐 +죑 +죒 +죓 +죔 +죕 +죖 +죗 +죘 +죙 +죚 +죛 +죜 +죝 +죞 +죟 +죠 +죡 +죢 +죣 +죤 +죥 +죦 +죧 +죨 +죩 +죪 +죫 +죬 +죭 +죮 +죯 +죰 +죱 +죲 +죳 +죴 +죵 +죶 +죷 +죸 +죹 +죺 +죻 +주 +죽 +죾 +죿 +준 +줁 +줂 +줃 +줄 +줅 +줆 +줇 +줈 +줉 +줊 +줋 +줌 +줍 +줎 +줏 +줐 +중 +줒 +줓 +줔 +줕 +줖 +줗 +줘 +줙 +줚 +줛 +줜 +줝 +줞 +줟 +줠 +줡 +줢 +줣 +줤 +줥 +줦 +줧 +줨 +줩 +줪 +줫 +줬 +줭 +줮 +줯 +줰 +줱 +줲 +줳 +줴 +줵 +줶 +줷 +줸 +줹 +줺 +줻 +줼 +줽 +줾 +줿 +쥀 +쥁 +쥂 +쥃 +쥄 +쥅 +쥆 +쥇 +쥈 +쥉 +쥊 +쥋 +쥌 +쥍 +쥎 +쥏 +쥐 +쥑 +쥒 +쥓 +쥔 +쥕 +쥖 +쥗 +쥘 +쥙 +쥚 +쥛 +쥜 +쥝 +쥞 +쥟 +쥠 +쥡 +쥢 +쥣 +쥤 +쥥 +쥦 +쥧 +쥨 +쥩 +쥪 +쥫 +쥬 +쥭 +쥮 +쥯 +쥰 +쥱 +쥲 +쥳 +쥴 +쥵 +쥶 +쥷 +쥸 +쥹 +쥺 +쥻 +쥼 +쥽 +쥾 +쥿 +즀 +즁 +즂 +즃 +즄 +즅 +즆 +즇 +즈 +즉 +즊 +즋 +즌 +즍 +즎 +즏 +즐 +즑 +즒 +즓 +즔 +즕 +즖 +즗 +즘 +즙 +즚 +즛 +즜 +증 +즞 +즟 +즠 +즡 +즢 +즣 +즤 +즥 +즦 +즧 +즨 +즩 +즪 +즫 +즬 +즭 +즮 +즯 +즰 +즱 +즲 +즳 +즴 +즵 +즶 +즷 +즸 +즹 +즺 +즻 +즼 +즽 +즾 +즿 +지 +직 +짂 +짃 +진 +짅 +짆 +짇 +질 +짉 +짊 +짋 +짌 +짍 +짎 +짏 +짐 +집 +짒 +짓 +짔 +징 +짖 +짗 +짘 +짙 +짚 +짛 +짜 +짝 +짞 +짟 +짠 +짡 +짢 +짣 +짤 +짥 +짦 +짧 +짨 +짩 +짪 +짫 +짬 +짭 +짮 +짯 +짰 +짱 +짲 +짳 +짴 +짵 +짶 +짷 +째 +짹 +짺 +짻 +짼 +짽 +짾 +짿 +쨀 +쨁 +쨂 +쨃 +쨄 +쨅 +쨆 +쨇 +쨈 +쨉 +쨊 +쨋 +쨌 +쨍 +쨎 +쨏 +쨐 +쨑 +쨒 +쨓 +쨔 +쨕 +쨖 +쨗 +쨘 +쨙 +쨚 +쨛 +쨜 +쨝 +쨞 +쨟 +쨠 +쨡 +쨢 +쨣 +쨤 +쨥 +쨦 +쨧 +쨨 +쨩 +쨪 +쨫 +쨬 +쨭 +쨮 +쨯 +쨰 +쨱 +쨲 +쨳 +쨴 +쨵 +쨶 +쨷 +쨸 +쨹 +쨺 +쨻 +쨼 +쨽 +쨾 +쨿 +쩀 +쩁 +쩂 +쩃 +쩄 +쩅 +쩆 +쩇 +쩈 +쩉 +쩊 +쩋 +쩌 +쩍 +쩎 +쩏 +쩐 +쩑 +쩒 +쩓 +쩔 +쩕 +쩖 +쩗 +쩘 +쩙 +쩚 +쩛 +쩜 +쩝 +쩞 +쩟 +쩠 +쩡 +쩢 +쩣 +쩤 +쩥 +쩦 +쩧 +쩨 +쩩 +쩪 +쩫 +쩬 +쩭 +쩮 +쩯 +쩰 +쩱 +쩲 +쩳 +쩴 +쩵 +쩶 +쩷 +쩸 +쩹 +쩺 +쩻 +쩼 +쩽 +쩾 +쩿 +쪀 +쪁 +쪂 +쪃 +쪄 +쪅 +쪆 +쪇 +쪈 +쪉 +쪊 +쪋 +쪌 +쪍 +쪎 +쪏 +쪐 +쪑 +쪒 +쪓 +쪔 +쪕 +쪖 +쪗 +쪘 +쪙 +쪚 +쪛 +쪜 +쪝 +쪞 +쪟 +쪠 +쪡 +쪢 +쪣 +쪤 +쪥 +쪦 +쪧 +쪨 +쪩 +쪪 +쪫 +쪬 +쪭 +쪮 +쪯 +쪰 +쪱 +쪲 +쪳 +쪴 +쪵 +쪶 +쪷 +쪸 +쪹 +쪺 +쪻 +쪼 +쪽 +쪾 +쪿 +쫀 +쫁 +쫂 +쫃 +쫄 +쫅 +쫆 +쫇 +쫈 +쫉 +쫊 +쫋 +쫌 +쫍 +쫎 +쫏 +쫐 +쫑 +쫒 +쫓 +쫔 +쫕 +쫖 +쫗 +쫘 +쫙 +쫚 +쫛 +쫜 +쫝 +쫞 +쫟 +쫠 +쫡 +쫢 +쫣 +쫤 +쫥 +쫦 +쫧 +쫨 +쫩 +쫪 +쫫 +쫬 +쫭 +쫮 +쫯 +쫰 +쫱 +쫲 +쫳 +쫴 +쫵 +쫶 +쫷 +쫸 +쫹 +쫺 +쫻 +쫼 +쫽 +쫾 +쫿 +쬀 +쬁 +쬂 +쬃 +쬄 +쬅 +쬆 +쬇 +쬈 +쬉 +쬊 +쬋 +쬌 +쬍 +쬎 +쬏 +쬐 +쬑 +쬒 +쬓 +쬔 +쬕 +쬖 +쬗 +쬘 +쬙 +쬚 +쬛 +쬜 +쬝 +쬞 +쬟 +쬠 +쬡 +쬢 +쬣 +쬤 +쬥 +쬦 +쬧 +쬨 +쬩 +쬪 +쬫 +쬬 +쬭 +쬮 +쬯 +쬰 +쬱 +쬲 +쬳 +쬴 +쬵 +쬶 +쬷 +쬸 +쬹 +쬺 +쬻 +쬼 +쬽 +쬾 +쬿 +쭀 +쭁 +쭂 +쭃 +쭄 +쭅 +쭆 +쭇 +쭈 +쭉 +쭊 +쭋 +쭌 +쭍 +쭎 +쭏 +쭐 +쭑 +쭒 +쭓 +쭔 +쭕 +쭖 +쭗 +쭘 +쭙 +쭚 +쭛 +쭜 +쭝 +쭞 +쭟 +쭠 +쭡 +쭢 +쭣 +쭤 +쭥 +쭦 +쭧 +쭨 +쭩 +쭪 +쭫 +쭬 +쭭 +쭮 +쭯 +쭰 +쭱 +쭲 +쭳 +쭴 +쭵 +쭶 +쭷 +쭸 +쭹 +쭺 +쭻 +쭼 +쭽 +쭾 +쭿 +쮀 +쮁 +쮂 +쮃 +쮄 +쮅 +쮆 +쮇 +쮈 +쮉 +쮊 +쮋 +쮌 +쮍 +쮎 +쮏 +쮐 +쮑 +쮒 +쮓 +쮔 +쮕 +쮖 +쮗 +쮘 +쮙 +쮚 +쮛 +쮜 +쮝 +쮞 +쮟 +쮠 +쮡 +쮢 +쮣 +쮤 +쮥 +쮦 +쮧 +쮨 +쮩 +쮪 +쮫 +쮬 +쮭 +쮮 +쮯 +쮰 +쮱 +쮲 +쮳 +쮴 +쮵 +쮶 +쮷 +쮸 +쮹 +쮺 +쮻 +쮼 +쮽 +쮾 +쮿 +쯀 +쯁 +쯂 +쯃 +쯄 +쯅 +쯆 +쯇 +쯈 +쯉 +쯊 +쯋 +쯌 +쯍 +쯎 +쯏 +쯐 +쯑 +쯒 +쯓 +쯔 +쯕 +쯖 +쯗 +쯘 +쯙 +쯚 +쯛 +쯜 +쯝 +쯞 +쯟 +쯠 +쯡 +쯢 +쯣 +쯤 +쯥 +쯦 +쯧 +쯨 +쯩 +쯪 +쯫 +쯬 +쯭 +쯮 +쯯 +쯰 +쯱 +쯲 +쯳 +쯴 +쯵 +쯶 +쯷 +쯸 +쯹 +쯺 +쯻 +쯼 +쯽 +쯾 +쯿 +찀 +찁 +찂 +찃 +찄 +찅 +찆 +찇 +찈 +찉 +찊 +찋 +찌 +찍 +찎 +찏 +찐 +찑 +찒 +찓 +찔 +찕 +찖 +찗 +찘 +찙 +찚 +찛 +찜 +찝 +찞 +찟 +찠 +찡 +찢 +찣 +찤 +찥 +찦 +찧 +차 +착 +찪 +찫 +찬 +찭 +찮 +찯 +찰 +찱 +찲 +찳 +찴 +찵 +찶 +찷 +참 +찹 +찺 +찻 +찼 +창 +찾 +찿 +챀 +챁 +챂 +챃 +채 +책 +챆 +챇 +챈 +챉 +챊 +챋 +챌 +챍 +챎 +챏 +챐 +챑 +챒 +챓 +챔 +챕 +챖 +챗 +챘 +챙 +챚 +챛 +챜 +챝 +챞 +챟 +챠 +챡 +챢 +챣 +챤 +챥 +챦 +챧 +챨 +챩 +챪 +챫 +챬 +챭 +챮 +챯 +챰 +챱 +챲 +챳 +챴 +챵 +챶 +챷 +챸 +챹 +챺 +챻 +챼 +챽 +챾 +챿 +첀 +첁 +첂 +첃 +첄 +첅 +첆 +첇 +첈 +첉 +첊 +첋 +첌 +첍 +첎 +첏 +첐 +첑 +첒 +첓 +첔 +첕 +첖 +첗 +처 +척 +첚 +첛 +천 +첝 +첞 +첟 +철 +첡 +첢 +첣 +첤 +첥 +첦 +첧 +첨 +첩 +첪 +첫 +첬 +청 +첮 +첯 +첰 +첱 +첲 +첳 +체 +첵 +첶 +첷 +첸 +첹 +첺 +첻 +첼 +첽 +첾 +첿 +쳀 +쳁 +쳂 +쳃 +쳄 +쳅 +쳆 +쳇 +쳈 +쳉 +쳊 +쳋 +쳌 +쳍 +쳎 +쳏 +쳐 +쳑 +쳒 +쳓 +쳔 +쳕 +쳖 +쳗 +쳘 +쳙 +쳚 +쳛 +쳜 +쳝 +쳞 +쳟 +쳠 +쳡 +쳢 +쳣 +쳤 +쳥 +쳦 +쳧 +쳨 +쳩 +쳪 +쳫 +쳬 +쳭 +쳮 +쳯 +쳰 +쳱 +쳲 +쳳 +쳴 +쳵 +쳶 +쳷 +쳸 +쳹 +쳺 +쳻 +쳼 +쳽 +쳾 +쳿 +촀 +촁 +촂 +촃 +촄 +촅 +촆 +촇 +초 +촉 +촊 +촋 +촌 +촍 +촎 +촏 +촐 +촑 +촒 +촓 +촔 +촕 +촖 +촗 +촘 +촙 +촚 +촛 +촜 +총 +촞 +촟 +촠 +촡 +촢 +촣 +촤 +촥 +촦 +촧 +촨 +촩 +촪 +촫 +촬 +촭 +촮 +촯 +촰 +촱 +촲 +촳 +촴 +촵 +촶 +촷 +촸 +촹 +촺 +촻 +촼 +촽 +촾 +촿 +쵀 +쵁 +쵂 +쵃 +쵄 +쵅 +쵆 +쵇 +쵈 +쵉 +쵊 +쵋 +쵌 +쵍 +쵎 +쵏 +쵐 +쵑 +쵒 +쵓 +쵔 +쵕 +쵖 +쵗 +쵘 +쵙 +쵚 +쵛 +최 +쵝 +쵞 +쵟 +쵠 +쵡 +쵢 +쵣 +쵤 +쵥 +쵦 +쵧 +쵨 +쵩 +쵪 +쵫 +쵬 +쵭 +쵮 +쵯 +쵰 +쵱 +쵲 +쵳 +쵴 +쵵 +쵶 +쵷 +쵸 +쵹 +쵺 +쵻 +쵼 +쵽 +쵾 +쵿 +춀 +춁 +춂 +춃 +춄 +춅 +춆 +춇 +춈 +춉 +춊 +춋 +춌 +춍 +춎 +춏 +춐 +춑 +춒 +춓 +추 +축 +춖 +춗 +춘 +춙 +춚 +춛 +출 +춝 +춞 +춟 +춠 +춡 +춢 +춣 +춤 +춥 +춦 +춧 +춨 +충 +춪 +춫 +춬 +춭 +춮 +춯 +춰 +춱 +춲 +춳 +춴 +춵 +춶 +춷 +춸 +춹 +춺 +춻 +춼 +춽 +춾 +춿 +췀 +췁 +췂 +췃 +췄 +췅 +췆 +췇 +췈 +췉 +췊 +췋 +췌 +췍 +췎 +췏 +췐 +췑 +췒 +췓 +췔 +췕 +췖 +췗 +췘 +췙 +췚 +췛 +췜 +췝 +췞 +췟 +췠 +췡 +췢 +췣 +췤 +췥 +췦 +췧 +취 +췩 +췪 +췫 +췬 +췭 +췮 +췯 +췰 +췱 +췲 +췳 +췴 +췵 +췶 +췷 +췸 +췹 +췺 +췻 +췼 +췽 +췾 +췿 +츀 +츁 +츂 +츃 +츄 +츅 +츆 +츇 +츈 +츉 +츊 +츋 +츌 +츍 +츎 +츏 +츐 +츑 +츒 +츓 +츔 +츕 +츖 +츗 +츘 +츙 +츚 +츛 +츜 +츝 +츞 +츟 +츠 +측 +츢 +츣 +츤 +츥 +츦 +츧 +츨 +츩 +츪 +츫 +츬 +츭 +츮 +츯 +츰 +츱 +츲 +츳 +츴 +층 +츶 +츷 +츸 +츹 +츺 +츻 +츼 +츽 +츾 +츿 +칀 +칁 +칂 +칃 +칄 +칅 +칆 +칇 +칈 +칉 +칊 +칋 +칌 +칍 +칎 +칏 +칐 +칑 +칒 +칓 +칔 +칕 +칖 +칗 +치 +칙 +칚 +칛 +친 +칝 +칞 +칟 +칠 +칡 +칢 +칣 +칤 +칥 +칦 +칧 +침 +칩 +칪 +칫 +칬 +칭 +칮 +칯 +칰 +칱 +칲 +칳 +카 +칵 +칶 +칷 +칸 +칹 +칺 +칻 +칼 +칽 +칾 +칿 +캀 +캁 +캂 +캃 +캄 +캅 +캆 +캇 +캈 +캉 +캊 +캋 +캌 +캍 +캎 +캏 +캐 +캑 +캒 +캓 +캔 +캕 +캖 +캗 +캘 +캙 +캚 +캛 +캜 +캝 +캞 +캟 +캠 +캡 +캢 +캣 +캤 +캥 +캦 +캧 +캨 +캩 +캪 +캫 +캬 +캭 +캮 +캯 +캰 +캱 +캲 +캳 +캴 +캵 +캶 +캷 +캸 +캹 +캺 +캻 +캼 +캽 +캾 +캿 +컀 +컁 +컂 +컃 +컄 +컅 +컆 +컇 +컈 +컉 +컊 +컋 +컌 +컍 +컎 +컏 +컐 +컑 +컒 +컓 +컔 +컕 +컖 +컗 +컘 +컙 +컚 +컛 +컜 +컝 +컞 +컟 +컠 +컡 +컢 +컣 +커 +컥 +컦 +컧 +컨 +컩 +컪 +컫 +컬 +컭 +컮 +컯 +컰 +컱 +컲 +컳 +컴 +컵 +컶 +컷 +컸 +컹 +컺 +컻 +컼 +컽 +컾 +컿 +케 +켁 +켂 +켃 +켄 +켅 +켆 +켇 +켈 +켉 +켊 +켋 +켌 +켍 +켎 +켏 +켐 +켑 +켒 +켓 +켔 +켕 +켖 +켗 +켘 +켙 +켚 +켛 +켜 +켝 +켞 +켟 +켠 +켡 +켢 +켣 +켤 +켥 +켦 +켧 +켨 +켩 +켪 +켫 +켬 +켭 +켮 +켯 +켰 +켱 +켲 +켳 +켴 +켵 +켶 +켷 +켸 +켹 +켺 +켻 +켼 +켽 +켾 +켿 +콀 +콁 +콂 +콃 +콄 +콅 +콆 +콇 +콈 +콉 +콊 +콋 +콌 +콍 +콎 +콏 +콐 +콑 +콒 +콓 +코 +콕 +콖 +콗 +콘 +콙 +콚 +콛 +콜 +콝 +콞 +콟 +콠 +콡 +콢 +콣 +콤 +콥 +콦 +콧 +콨 +콩 +콪 +콫 +콬 +콭 +콮 +콯 +콰 +콱 +콲 +콳 +콴 +콵 +콶 +콷 +콸 +콹 +콺 +콻 +콼 +콽 +콾 +콿 +쾀 +쾁 +쾂 +쾃 +쾄 +쾅 +쾆 +쾇 +쾈 +쾉 +쾊 +쾋 +쾌 +쾍 +쾎 +쾏 +쾐 +쾑 +쾒 +쾓 +쾔 +쾕 +쾖 +쾗 +쾘 +쾙 +쾚 +쾛 +쾜 +쾝 +쾞 +쾟 +쾠 +쾡 +쾢 +쾣 +쾤 +쾥 +쾦 +쾧 +쾨 +쾩 +쾪 +쾫 +쾬 +쾭 +쾮 +쾯 +쾰 +쾱 +쾲 +쾳 +쾴 +쾵 +쾶 +쾷 +쾸 +쾹 +쾺 +쾻 +쾼 +쾽 +쾾 +쾿 +쿀 +쿁 +쿂 +쿃 +쿄 +쿅 +쿆 +쿇 +쿈 +쿉 +쿊 +쿋 +쿌 +쿍 +쿎 +쿏 +쿐 +쿑 +쿒 +쿓 +쿔 +쿕 +쿖 +쿗 +쿘 +쿙 +쿚 +쿛 +쿜 +쿝 +쿞 +쿟 +쿠 +쿡 +쿢 +쿣 +쿤 +쿥 +쿦 +쿧 +쿨 +쿩 +쿪 +쿫 +쿬 +쿭 +쿮 +쿯 +쿰 +쿱 +쿲 +쿳 +쿴 +쿵 +쿶 +쿷 +쿸 +쿹 +쿺 +쿻 +쿼 +쿽 +쿾 +쿿 +퀀 +퀁 +퀂 +퀃 +퀄 +퀅 +퀆 +퀇 +퀈 +퀉 +퀊 +퀋 +퀌 +퀍 +퀎 +퀏 +퀐 +퀑 +퀒 +퀓 +퀔 +퀕 +퀖 +퀗 +퀘 +퀙 +퀚 +퀛 +퀜 +퀝 +퀞 +퀟 +퀠 +퀡 +퀢 +퀣 +퀤 +퀥 +퀦 +퀧 +퀨 +퀩 +퀪 +퀫 +퀬 +퀭 +퀮 +퀯 +퀰 +퀱 +퀲 +퀳 +퀴 +퀵 +퀶 +퀷 +퀸 +퀹 +퀺 +퀻 +퀼 +퀽 +퀾 +퀿 +큀 +큁 +큂 +큃 +큄 +큅 +큆 +큇 +큈 +큉 +큊 +큋 +큌 +큍 +큎 +큏 +큐 +큑 +큒 +큓 +큔 +큕 +큖 +큗 +큘 +큙 +큚 +큛 +큜 +큝 +큞 +큟 +큠 +큡 +큢 +큣 +큤 +큥 +큦 +큧 +큨 +큩 +큪 +큫 +크 +큭 +큮 +큯 +큰 +큱 +큲 +큳 +클 +큵 +큶 +큷 +큸 +큹 +큺 +큻 +큼 +큽 +큾 +큿 +킀 +킁 +킂 +킃 +킄 +킅 +킆 +킇 +킈 +킉 +킊 +킋 +킌 +킍 +킎 +킏 +킐 +킑 +킒 +킓 +킔 +킕 +킖 +킗 +킘 +킙 +킚 +킛 +킜 +킝 +킞 +킟 +킠 +킡 +킢 +킣 +키 +킥 +킦 +킧 +킨 +킩 +킪 +킫 +킬 +킭 +킮 +킯 +킰 +킱 +킲 +킳 +킴 +킵 +킶 +킷 +킸 +킹 +킺 +킻 +킼 +킽 +킾 +킿 +타 +탁 +탂 +탃 +탄 +탅 +탆 +탇 +탈 +탉 +탊 +탋 +탌 +탍 +탎 +탏 +탐 +탑 +탒 +탓 +탔 +탕 +탖 +탗 +탘 +탙 +탚 +탛 +태 +택 +탞 +탟 +탠 +탡 +탢 +탣 +탤 +탥 +탦 +탧 +탨 +탩 +탪 +탫 +탬 +탭 +탮 +탯 +탰 +탱 +탲 +탳 +탴 +탵 +탶 +탷 +탸 +탹 +탺 +탻 +탼 +탽 +탾 +탿 +턀 +턁 +턂 +턃 +턄 +턅 +턆 +턇 +턈 +턉 +턊 +턋 +턌 +턍 +턎 +턏 +턐 +턑 +턒 +턓 +턔 +턕 +턖 +턗 +턘 +턙 +턚 +턛 +턜 +턝 +턞 +턟 +턠 +턡 +턢 +턣 +턤 +턥 +턦 +턧 +턨 +턩 +턪 +턫 +턬 +턭 +턮 +턯 +터 +턱 +턲 +턳 +턴 +턵 +턶 +턷 +털 +턹 +턺 +턻 +턼 +턽 +턾 +턿 +텀 +텁 +텂 +텃 +텄 +텅 +텆 +텇 +텈 +텉 +텊 +텋 +테 +텍 +텎 +텏 +텐 +텑 +텒 +텓 +텔 +텕 +텖 +텗 +텘 +텙 +텚 +텛 +템 +텝 +텞 +텟 +텠 +텡 +텢 +텣 +텤 +텥 +텦 +텧 +텨 +텩 +텪 +텫 +텬 +텭 +텮 +텯 +텰 +텱 +텲 +텳 +텴 +텵 +텶 +텷 +텸 +텹 +텺 +텻 +텼 +텽 +텾 +텿 +톀 +톁 +톂 +톃 +톄 +톅 +톆 +톇 +톈 +톉 +톊 +톋 +톌 +톍 +톎 +톏 +톐 +톑 +톒 +톓 +톔 +톕 +톖 +톗 +톘 +톙 +톚 +톛 +톜 +톝 +톞 +톟 +토 +톡 +톢 +톣 +톤 +톥 +톦 +톧 +톨 +톩 +톪 +톫 +톬 +톭 +톮 +톯 +톰 +톱 +톲 +톳 +톴 +통 +톶 +톷 +톸 +톹 +톺 +톻 +톼 +톽 +톾 +톿 +퇀 +퇁 +퇂 +퇃 +퇄 +퇅 +퇆 +퇇 +퇈 +퇉 +퇊 +퇋 +퇌 +퇍 +퇎 +퇏 +퇐 +퇑 +퇒 +퇓 +퇔 +퇕 +퇖 +퇗 +퇘 +퇙 +퇚 +퇛 +퇜 +퇝 +퇞 +퇟 +퇠 +퇡 +퇢 +퇣 +퇤 +퇥 +퇦 +퇧 +퇨 +퇩 +퇪 +퇫 +퇬 +퇭 +퇮 +퇯 +퇰 +퇱 +퇲 +퇳 +퇴 +퇵 +퇶 +퇷 +퇸 +퇹 +퇺 +퇻 +퇼 +퇽 +퇾 +퇿 +툀 +툁 +툂 +툃 +툄 +툅 +툆 +툇 +툈 +툉 +툊 +툋 +툌 +툍 +툎 +툏 +툐 +툑 +툒 +툓 +툔 +툕 +툖 +툗 +툘 +툙 +툚 +툛 +툜 +툝 +툞 +툟 +툠 +툡 +툢 +툣 +툤 +툥 +툦 +툧 +툨 +툩 +툪 +툫 +투 +툭 +툮 +툯 +툰 +툱 +툲 +툳 +툴 +툵 +툶 +툷 +툸 +툹 +툺 +툻 +툼 +툽 +툾 +툿 +퉀 +퉁 +퉂 +퉃 +퉄 +퉅 +퉆 +퉇 +퉈 +퉉 +퉊 +퉋 +퉌 +퉍 +퉎 +퉏 +퉐 +퉑 +퉒 +퉓 +퉔 +퉕 +퉖 +퉗 +퉘 +퉙 +퉚 +퉛 +퉜 +퉝 +퉞 +퉟 +퉠 +퉡 +퉢 +퉣 +퉤 +퉥 +퉦 +퉧 +퉨 +퉩 +퉪 +퉫 +퉬 +퉭 +퉮 +퉯 +퉰 +퉱 +퉲 +퉳 +퉴 +퉵 +퉶 +퉷 +퉸 +퉹 +퉺 +퉻 +퉼 +퉽 +퉾 +퉿 +튀 +튁 +튂 +튃 +튄 +튅 +튆 +튇 +튈 +튉 +튊 +튋 +튌 +튍 +튎 +튏 +튐 +튑 +튒 +튓 +튔 +튕 +튖 +튗 +튘 +튙 +튚 +튛 +튜 +튝 +튞 +튟 +튠 +튡 +튢 +튣 +튤 +튥 +튦 +튧 +튨 +튩 +튪 +튫 +튬 +튭 +튮 +튯 +튰 +튱 +튲 +튳 +튴 +튵 +튶 +튷 +트 +특 +튺 +튻 +튼 +튽 +튾 +튿 +틀 +틁 +틂 +틃 +틄 +틅 +틆 +틇 +틈 +틉 +틊 +틋 +틌 +틍 +틎 +틏 +틐 +틑 +틒 +틓 +틔 +틕 +틖 +틗 +틘 +틙 +틚 +틛 +틜 +틝 +틞 +틟 +틠 +틡 +틢 +틣 +틤 +틥 +틦 +틧 +틨 +틩 +틪 +틫 +틬 +틭 +틮 +틯 +티 +틱 +틲 +틳 +틴 +틵 +틶 +틷 +틸 +틹 +틺 +틻 +틼 +틽 +틾 +틿 +팀 +팁 +팂 +팃 +팄 +팅 +팆 +팇 +팈 +팉 +팊 +팋 +파 +팍 +팎 +팏 +판 +팑 +팒 +팓 +팔 +팕 +팖 +팗 +팘 +팙 +팚 +팛 +팜 +팝 +팞 +팟 +팠 +팡 +팢 +팣 +팤 +팥 +팦 +팧 +패 +팩 +팪 +팫 +팬 +팭 +팮 +팯 +팰 +팱 +팲 +팳 +팴 +팵 +팶 +팷 +팸 +팹 +팺 +팻 +팼 +팽 +팾 +팿 +퍀 +퍁 +퍂 +퍃 +퍄 +퍅 +퍆 +퍇 +퍈 +퍉 +퍊 +퍋 +퍌 +퍍 +퍎 +퍏 +퍐 +퍑 +퍒 +퍓 +퍔 +퍕 +퍖 +퍗 +퍘 +퍙 +퍚 +퍛 +퍜 +퍝 +퍞 +퍟 +퍠 +퍡 +퍢 +퍣 +퍤 +퍥 +퍦 +퍧 +퍨 +퍩 +퍪 +퍫 +퍬 +퍭 +퍮 +퍯 +퍰 +퍱 +퍲 +퍳 +퍴 +퍵 +퍶 +퍷 +퍸 +퍹 +퍺 +퍻 +퍼 +퍽 +퍾 +퍿 +펀 +펁 +펂 +펃 +펄 +펅 +펆 +펇 +펈 +펉 +펊 +펋 +펌 +펍 +펎 +펏 +펐 +펑 +펒 +펓 +펔 +펕 +펖 +펗 +페 +펙 +펚 +펛 +펜 +펝 +펞 +펟 +펠 +펡 +펢 +펣 +펤 +펥 +펦 +펧 +펨 +펩 +펪 +펫 +펬 +펭 +펮 +펯 +펰 +펱 +펲 +펳 +펴 +펵 +펶 +펷 +편 +펹 +펺 +펻 +펼 +펽 +펾 +펿 +폀 +폁 +폂 +폃 +폄 +폅 +폆 +폇 +폈 +평 +폊 +폋 +폌 +폍 +폎 +폏 +폐 +폑 +폒 +폓 +폔 +폕 +폖 +폗 +폘 +폙 +폚 +폛 +폜 +폝 +폞 +폟 +폠 +폡 +폢 +폣 +폤 +폥 +폦 +폧 +폨 +폩 +폪 +폫 +포 +폭 +폮 +폯 +폰 +폱 +폲 +폳 +폴 +폵 +폶 +폷 +폸 +폹 +폺 +폻 +폼 +폽 +폾 +폿 +퐀 +퐁 +퐂 +퐃 +퐄 +퐅 +퐆 +퐇 +퐈 +퐉 +퐊 +퐋 +퐌 +퐍 +퐎 +퐏 +퐐 +퐑 +퐒 +퐓 +퐔 +퐕 +퐖 +퐗 +퐘 +퐙 +퐚 +퐛 +퐜 +퐝 +퐞 +퐟 +퐠 +퐡 +퐢 +퐣 +퐤 +퐥 +퐦 +퐧 +퐨 +퐩 +퐪 +퐫 +퐬 +퐭 +퐮 +퐯 +퐰 +퐱 +퐲 +퐳 +퐴 +퐵 +퐶 +퐷 +퐸 +퐹 +퐺 +퐻 +퐼 +퐽 +퐾 +퐿 +푀 +푁 +푂 +푃 +푄 +푅 +푆 +푇 +푈 +푉 +푊 +푋 +푌 +푍 +푎 +푏 +푐 +푑 +푒 +푓 +푔 +푕 +푖 +푗 +푘 +푙 +푚 +푛 +표 +푝 +푞 +푟 +푠 +푡 +푢 +푣 +푤 +푥 +푦 +푧 +푨 +푩 +푪 +푫 +푬 +푭 +푮 +푯 +푰 +푱 +푲 +푳 +푴 +푵 +푶 +푷 +푸 +푹 +푺 +푻 +푼 +푽 +푾 +푿 +풀 +풁 +풂 +풃 +풄 +풅 +풆 +풇 +품 +풉 +풊 +풋 +풌 +풍 +풎 +풏 +풐 +풑 +풒 +풓 +풔 +풕 +풖 +풗 +풘 +풙 +풚 +풛 +풜 +풝 +풞 +풟 +풠 +풡 +풢 +풣 +풤 +풥 +풦 +풧 +풨 +풩 +풪 +풫 +풬 +풭 +풮 +풯 +풰 +풱 +풲 +풳 +풴 +풵 +풶 +풷 +풸 +풹 +풺 +풻 +풼 +풽 +풾 +풿 +퓀 +퓁 +퓂 +퓃 +퓄 +퓅 +퓆 +퓇 +퓈 +퓉 +퓊 +퓋 +퓌 +퓍 +퓎 +퓏 +퓐 +퓑 +퓒 +퓓 +퓔 +퓕 +퓖 +퓗 +퓘 +퓙 +퓚 +퓛 +퓜 +퓝 +퓞 +퓟 +퓠 +퓡 +퓢 +퓣 +퓤 +퓥 +퓦 +퓧 +퓨 +퓩 +퓪 +퓫 +퓬 +퓭 +퓮 +퓯 +퓰 +퓱 +퓲 +퓳 +퓴 +퓵 +퓶 +퓷 +퓸 +퓹 +퓺 +퓻 +퓼 +퓽 +퓾 +퓿 +픀 +픁 +픂 +픃 +프 +픅 +픆 +픇 +픈 +픉 +픊 +픋 +플 +픍 +픎 +픏 +픐 +픑 +픒 +픓 +픔 +픕 +픖 +픗 +픘 +픙 +픚 +픛 +픜 +픝 +픞 +픟 +픠 +픡 +픢 +픣 +픤 +픥 +픦 +픧 +픨 +픩 +픪 +픫 +픬 +픭 +픮 +픯 +픰 +픱 +픲 +픳 +픴 +픵 +픶 +픷 +픸 +픹 +픺 +픻 +피 +픽 +픾 +픿 +핀 +핁 +핂 +핃 +필 +핅 +핆 +핇 +핈 +핉 +핊 +핋 +핌 +핍 +핎 +핏 +핐 +핑 +핒 +핓 +핔 +핕 +핖 +핗 +하 +학 +핚 +핛 +한 +핝 +핞 +핟 +할 +핡 +핢 +핣 +핤 +핥 +핦 +핧 +함 +합 +핪 +핫 +핬 +항 +핮 +핯 +핰 +핱 +핲 +핳 +해 +핵 +핶 +핷 +핸 +핹 +핺 +핻 +핼 +핽 +핾 +핿 +햀 +햁 +햂 +햃 +햄 +햅 +햆 +햇 +했 +행 +햊 +햋 +햌 +햍 +햎 +햏 +햐 +햑 +햒 +햓 +햔 +햕 +햖 +햗 +햘 +햙 +햚 +햛 +햜 +햝 +햞 +햟 +햠 +햡 +햢 +햣 +햤 +향 +햦 +햧 +햨 +햩 +햪 +햫 +햬 +햭 +햮 +햯 +햰 +햱 +햲 +햳 +햴 +햵 +햶 +햷 +햸 +햹 +햺 +햻 +햼 +햽 +햾 +햿 +헀 +헁 +헂 +헃 +헄 +헅 +헆 +헇 +허 +헉 +헊 +헋 +헌 +헍 +헎 +헏 +헐 +헑 +헒 +헓 +헔 +헕 +헖 +헗 +험 +헙 +헚 +헛 +헜 +헝 +헞 +헟 +헠 +헡 +헢 +헣 +헤 +헥 +헦 +헧 +헨 +헩 +헪 +헫 +헬 +헭 +헮 +헯 +헰 +헱 +헲 +헳 +헴 +헵 +헶 +헷 +헸 +헹 +헺 +헻 +헼 +헽 +헾 +헿 +혀 +혁 +혂 +혃 +현 +혅 +혆 +혇 +혈 +혉 +혊 +혋 +혌 +혍 +혎 +혏 +혐 +협 +혒 +혓 +혔 +형 +혖 +혗 +혘 +혙 +혚 +혛 +혜 +혝 +혞 +혟 +혠 +혡 +혢 +혣 +혤 +혥 +혦 +혧 +혨 +혩 +혪 +혫 +혬 +혭 +혮 +혯 +혰 +혱 +혲 +혳 +혴 +혵 +혶 +혷 +호 +혹 +혺 +혻 +혼 +혽 +혾 +혿 +홀 +홁 +홂 +홃 +홄 +홅 +홆 +홇 +홈 +홉 +홊 +홋 +홌 +홍 +홎 +홏 +홐 +홑 +홒 +홓 +화 +확 +홖 +홗 +환 +홙 +홚 +홛 +활 +홝 +홞 +홟 +홠 +홡 +홢 +홣 +홤 +홥 +홦 +홧 +홨 +황 +홪 +홫 +홬 +홭 +홮 +홯 +홰 +홱 +홲 +홳 +홴 +홵 +홶 +홷 +홸 +홹 +홺 +홻 +홼 +홽 +홾 +홿 +횀 +횁 +횂 +횃 +횄 +횅 +횆 +횇 +횈 +횉 +횊 +횋 +회 +획 +횎 +횏 +횐 +횑 +횒 +횓 +횔 +횕 +횖 +횗 +횘 +횙 +횚 +횛 +횜 +횝 +횞 +횟 +횠 +횡 +횢 +횣 +횤 +횥 +횦 +횧 +효 +횩 +횪 +횫 +횬 +횭 +횮 +횯 +횰 +횱 +횲 +횳 +횴 +횵 +횶 +횷 +횸 +횹 +횺 +횻 +횼 +횽 +횾 +횿 +훀 +훁 +훂 +훃 +후 +훅 +훆 +훇 +훈 +훉 +훊 +훋 +훌 +훍 +훎 +훏 +훐 +훑 +훒 +훓 +훔 +훕 +훖 +훗 +훘 +훙 +훚 +훛 +훜 +훝 +훞 +훟 +훠 +훡 +훢 +훣 +훤 +훥 +훦 +훧 +훨 +훩 +훪 +훫 +훬 +훭 +훮 +훯 +훰 +훱 +훲 +훳 +훴 +훵 +훶 +훷 +훸 +훹 +훺 +훻 +훼 +훽 +훾 +훿 +휀 +휁 +휂 +휃 +휄 +휅 +휆 +휇 +휈 +휉 +휊 +휋 +휌 +휍 +휎 +휏 +휐 +휑 +휒 +휓 +휔 +휕 +휖 +휗 +휘 +휙 +휚 +휛 +휜 +휝 +휞 +휟 +휠 +휡 +휢 +휣 +휤 +휥 +휦 +휧 +휨 +휩 +휪 +휫 +휬 +휭 +휮 +휯 +휰 +휱 +휲 +휳 +휴 +휵 +휶 +휷 +휸 +휹 +휺 +휻 +휼 +휽 +휾 +휿 +흀 +흁 +흂 +흃 +흄 +흅 +흆 +흇 +흈 +흉 +흊 +흋 +흌 +흍 +흎 +흏 +흐 +흑 +흒 +흓 +흔 +흕 +흖 +흗 +흘 +흙 +흚 +흛 +흜 +흝 +흞 +흟 +흠 +흡 +흢 +흣 +흤 +흥 +흦 +흧 +흨 +흩 +흪 +흫 +희 +흭 +흮 +흯 +흰 +흱 +흲 +흳 +흴 +흵 +흶 +흷 +흸 +흹 +흺 +흻 +흼 +흽 +흾 +흿 +힀 +힁 +힂 +힃 +힄 +힅 +힆 +힇 +히 +힉 +힊 +힋 +힌 +힍 +힎 +힏 +힐 +힑 +힒 +힓 +힔 +힕 +힖 +힗 +힘 +힙 +힚 +힛 +힜 +힝 +힞 +힟 +힠 +힡 +힢 +힣 +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +© +‥ +{ +} +\ +| +@ +^ +~ +÷ +∕ +∙ +⋅ +· +± +∓ +∩ +∪ +□ +← +↔ +⇒ +⇐ +⇔ +∀ +∃ +∄ +∴ +∵ +∝ +∞ +⊥ +∟ +∠ +∡ +∢ +′ +″ +∥ +⊾ +⊿ +∂ +∫ +∬ +∭ +∮ +∯ +∰ +∑ +∏ +√ +∛ +∜ +∱ +∲ +∳ +∶ +∷ +∼ +® +℉ +Ω +℧ +Å +⌀ +ℏ +⅀ +⍺ +⍵ +¢ +€ +£ +¥ +₿ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +● +▶ +𝑢 +︽ +– +﹥ +𝜓 +• +∋ +ƒ +० +⬆ +Ạ +◀ + +▫ +︾ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +Ø +Ù +Ú +Û +Ü +Ý +Þ +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +þ +ÿ +¡ +¤ +¦ +§ +¨ +ª +« +¬ +¯ +° +² +³ +´ +µ +¶ +¸ +¹ +º +» +¼ +½ +¾ +¿ +× +‐ +‑ +‒ +— +― +‖ +‗ +‘ +’ +‚ +‛ +“ +” +„ +‟ +† +‡ +‣ +․ +… +‧ +‰ +‴ +‵ +‶ +‷ +‸ +‹ +› +※ +‼ +‽ +‾ +₤ +₡ +₹ +− +∖ +∗ +≈ +≠ +≡ +≤ +≥ +⊂ +⊃ +↑ +→ +↓ +↕ +™ +Ω +℮ +∆ +✓ +✗ +✘ +▪ +◼ +✔ +✕ +☑ +☒ +№ +₽ +₴ +Α +α +Β +β +Γ +γ +Δ +δ +Ε +ε +Ζ +ζ +Η +η +Θ +θ +Ι +ι +Κ +κ +Λ +λ +Μ +μ +Ν +ν +Ξ +ξ +Ο +ο +Π +π +Ρ +ρ +Σ +σ +ς +Τ +τ +Υ +υ +Φ +φ +Χ +χ +Ψ +ψ +ω diff --git a/ppocr/utils/dict/ppocrv5_latin_dict.txt b/ppocr/utils/dict/ppocrv5_latin_dict.txt new file mode 100644 index 0000000..c618257 --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_latin_dict.txt @@ -0,0 +1,836 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +× +Ø +Ù +Ú +Û +Ü +Ý +Þ +ß +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +÷ +ø +ù +ú +û +ü +ý +þ +ÿ +Ā +ā +Ă +ă +Ą +ą +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ď +ď +Đ +đ +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ĥ +ĥ +Ħ +ħ +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +ı +IJ +ij +Ĵ +ĵ +Ķ +ķ +ĸ +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +Ń +ń +Ņ +ņ +Ň +ň +ʼn +Ŋ +ŋ +Ō +ō +Ŏ +ŏ +Ő +ő +Œ +œ +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ţ +ţ +Ť +ť +Ŧ +ŧ +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ŵ +ŵ +Ŷ +ŷ +Ÿ +Ź +ź +Ż +ż +Ž +ž +ſ +ƀ +Ɓ +Ƃ +ƃ +Ƅ +ƅ +Ɔ +Ƈ +ƈ +Ɖ +Ɗ +Ƌ +ƌ +ƍ +Ǝ +Ə +Ɛ +Ƒ +ƒ +Ɠ +Ɣ +ƕ +Ɩ +Ɨ +Ƙ +ƙ +ƚ +ƛ +Ɯ +Ɲ +ƞ +Ɵ +Ơ +ơ +Ƣ +ƣ +Ƥ +ƥ +Ʀ +Ƨ +ƨ +Ʃ +ƪ +ƫ +Ƭ +ƭ +Ʈ +Ư +ư +Ʊ +Ʋ +Ƴ +ƴ +Ƶ +ƶ +Ʒ +Ƹ +ƹ +ƺ +ƻ +Ƽ +ƽ +ƾ +ƿ +ǀ +ǁ +ǂ +ǃ +DŽ +Dž +dž +LJ +Lj +lj +NJ +Nj +nj +Ǎ +ǎ +Ǐ +ǐ +Ǒ +ǒ +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +ǝ +Ǟ +ǟ +Ǡ +ǡ +Ǣ +ǣ +Ǥ +ǥ +Ǧ +ǧ +Ǩ +ǩ +Ǫ +ǫ +Ǭ +ǭ +Ǯ +ǯ +ǰ +DZ +Dz +dz +Ǵ +ǵ +Ƕ +Ƿ +Ǹ +ǹ +Ǻ +ǻ +Ǽ +ǽ +Ǿ +ǿ +Ȁ +ȁ +Ȃ +ȃ +Ȅ +ȅ +Ȇ +ȇ +Ȉ +ȉ +Ȋ +ȋ +Ȍ +ȍ +Ȏ +ȏ +Ȑ +ȑ +Ȓ +ȓ +Ȕ +ȕ +Ȗ +ȗ +Ș +ș +Ț +ț +Ȝ +ȝ +Ȟ +ȟ +Ƞ +ȡ +Ȣ +ȣ +Ȥ +ȥ +Ȧ +ȧ +Ȩ +ȩ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ +Ȳ +ȳ +ȴ +ȵ +ȶ +ȷ +ȸ +ȹ +Ⱥ +Ȼ +ȼ +Ƚ +Ⱦ +ȿ +ɀ +Ɂ +ɂ +Ƀ +Ʉ +Ʌ +Ɇ +ɇ +Ɉ +ɉ +Ɋ +ɋ +Ɍ +ɍ +Ɏ +ɏ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +: +; +< += +> +? +@ +[ +\ +] +_ +` +{ +| +} +^ +~ +© +® +℉ +№ +Ω +℮ +™ +∆ +✓ +✔ +✗ +✘ +✕ +☑ +☒ +● +▪ +▫ +◼ +▶ +◀ +⬆ +¤ +¦ +§ +¨ +ª +« +¬ +¯ +° +² +³ +´ +µ +¶ +¸ +¹ +º +» +¼ +½ +¾ +¿ +× +‐ +‑ +‒ +— +― +‖ +‗ +‘ +’ +‚ +‛ +“ +” +„ +‟ +† +‡ +‣ +․ +… +‧ +‰ +‴ +‵ +‶ +‷ +‸ +‹ +› +※ +‼ +‽ +‾ +− +₤ +₡ +₹ +₽ +₴ +₿ +¢ +€ +£ +¥ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +↑ +→ +↓ +↕ +← +↔ +⇒ +⇐ +⇔ +∀ +∃ +∄ +∴ +∵ +∝ +∞ +∩ +∪ +∂ +∫ +∬ +∭ +∮ +∯ +∰ +∑ +∏ +√ +∛ +∜ +∱ +∲ +∳ +∶ +∷ +∼ +∖ +∗ +≈ +≠ +≡ +≤ +≥ +⊂ +⊃ +⊥ +⊾ +⊿ +□ +∥ +∋ +ƒ +′ +″ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +Ø +Ù +Ú +Û +Ü +Ý +Þ +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +þ +ÿ +Α +Β +Γ +Δ +Ε +Ζ +Η +Θ +Ι +Κ +Λ +Μ +Ν +Ξ +Ο +Π +Ρ +Σ +Τ +Υ +Φ +Χ +Ψ +Ω +α +β +γ +δ +ε +ζ +η +θ +ι +κ +λ +μ +ν +ξ +ο +π +ρ +σ +ς +τ +υ +φ +χ +ψ +ω +Å +ℏ +⌀ +⍺ +⍵ +𝑢 +𝜓 +० +‥ +︽ +﹥ +• +÷ +∕ +∙ +⋅ +· +± +∓ +∟ +∠ +∡ +∢ +℧ +☺ diff --git a/ppocr/utils/dict/ppocrv5_ta_dict.txt b/ppocr/utils/dict/ppocrv5_ta_dict.txt new file mode 100644 index 0000000..1290f65 --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_ta_dict.txt @@ -0,0 +1,513 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +© +‥ +{ +} +\ +| +@ +^ +~ +÷ +∕ +∙ +⋅ +· +± +∓ +∩ +∪ +□ +← +↔ +⇒ +⇐ +⇔ +∀ +∃ +∄ +∴ +∵ +∝ +∞ +⊥ +∟ +∠ +∡ +∢ +′ +″ +∥ +⊾ +⊿ +∂ +∫ +∬ +∭ +∮ +∯ +∰ +∑ +∏ +√ +∛ +∜ +∱ +∲ +∳ +∶ +∷ +∼ +® +℉ +Ω +℧ +Å +⌀ +ℏ +⅀ +⍺ +⍵ +¢ +€ +£ +¥ +₿ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +● +▶ +𝑢 +︽ +– +﹥ +𝜓 +• +∋ +ƒ +० +⬆ +Ạ +◀ + +▫ +︾ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +Ø +Ù +Ú +Û +Ü +Ý +Þ +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +þ +ÿ +¡ +¤ +¦ +§ +¨ +ª +« +¬ +¯ +° +² +³ +´ +µ +¶ +¸ +¹ +º +» +¼ +½ +¾ +¿ +× +‐ +‑ +‒ +— +― +‖ +‗ +‘ +’ +‚ +‛ +“ +” +„ +‟ +† +‡ +‣ +․ +… +‧ +‰ +‴ +‵ +‶ +‷ +‸ +‹ +› +※ +‼ +‽ +‾ +₤ +₡ +₹ +− +∖ +∗ +≈ +≠ +≡ +≤ +≥ +⊂ +⊃ +↑ +→ +↓ +↕ +™ +Ω +℮ +∆ +✓ +✗ +✘ +▪ +◼ +✔ +✕ +☑ +☒ +№ +₽ +₴ +Α +α +Β +β +Γ +γ +Δ +δ +Ε +ε +Ζ +ζ +Η +η +Θ +θ +Ι +ι +Κ +κ +Λ +λ +Μ +μ +Ν +ν +Ξ +ξ +Ο +ο +Π +π +Ρ +ρ +Σ +σ +ς +Τ +τ +Υ +υ +Φ +φ +Χ +χ +Ψ +ψ +ω +ஂ +ஃ +அ +ஆ +இ +ஈ +உ +ஊ +எ +ஏ +ஐ +ஒ +ஓ +ஔ +க +ங +ச +ஜ +ஞ +ட +ண +த +ந +ன +ப +ம +ய +ர +ற +ல +ள +ழ +வ +ஶ +ஷ +ஸ +ஹ +ா +ி +ீ +ு +ூ +ெ +ே +ை +ொ +ோ +ௌ +் +ௐ +ௗ +௦ +௧ +௨ +௩ +௪ +௫ +௬ +௭ +௮ +௯ +௰ +௱ +௲ +௳ +௴ +௵ +௶ +௷ +௸ +௹ +௺ diff --git a/ppocr/utils/dict/ppocrv5_te_dict.txt b/ppocr/utils/dict/ppocrv5_te_dict.txt new file mode 100644 index 0000000..878f639 --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_te_dict.txt @@ -0,0 +1,540 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +© +‥ +{ +} +\ +| +@ +^ +~ +÷ +∕ +∙ +⋅ +· +± +∓ +∩ +∪ +□ +← +↔ +⇒ +⇐ +⇔ +∀ +∃ +∄ +∴ +∵ +∝ +∞ +⊥ +∟ +∠ +∡ +∢ +′ +″ +∥ +⊾ +⊿ +∂ +∫ +∬ +∭ +∮ +∯ +∰ +∑ +∏ +√ +∛ +∜ +∱ +∲ +∳ +∶ +∷ +∼ +® +℉ +Ω +℧ +Å +⌀ +ℏ +⅀ +⍺ +⍵ +¢ +€ +£ +¥ +₿ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +● +▶ +𝑢 +︽ +– +﹥ +𝜓 +• +∋ +ƒ +⬆ +Ạ +◀ + +▫ +︾ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +Ø +Ù +Ú +Û +Ü +Ý +Þ +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +þ +ÿ +¡ +¤ +¦ +§ +¨ +ª +« +¬ +¯ +° +² +³ +´ +µ +¶ +¸ +¹ +º +» +¼ +½ +¾ +¿ +× +‐ +‑ +‒ +— +― +‖ +‗ +‘ +’ +‚ +‛ +“ +” +„ +‟ +† +‡ +‣ +․ +… +‧ +‰ +‴ +‵ +‶ +‷ +‸ +‹ +› +※ +‼ +‽ +‾ +₤ +₡ +₹ +− +∖ +∗ +≈ +≠ +≡ +≤ +≥ +⊂ +⊃ +↑ +→ +↓ +↕ +™ +Ω +℮ +∆ +✓ +✗ +✘ +▪ +◼ +✔ +✕ +☑ +☒ +№ +₽ +₴ +Α +α +Β +β +Γ +γ +Δ +δ +Ε +ε +Ζ +ζ +Η +η +Θ +θ +Ι +ι +Κ +κ +Λ +λ +Μ +μ +Ν +ν +Ξ +ξ +Ο +ο +Π +π +Ρ +ρ +Σ +σ +ς +Τ +τ +Υ +υ +Φ +φ +Χ +χ +Ψ +ψ +ω +ఀ +ఁ +ం +ః +ఄ +అ +ఆ +ఇ +ఈ +ఉ +ఊ +ఋ +ఌ +ఎ +ఏ +ఐ +ఒ +ఓ +ఔ +క +ఖ +గ +ఘ +ఙ +చ +ఛ +జ +ఝ +ఞ +ట +ఠ +డ +ఢ +ణ +త +థ +ద +ధ +న +ప +ఫ +బ +భ +మ +య +ర +ఱ +ల +ళ +ఴ +వ +శ +ష +స +హ +఼ +ఽ +ా +ి +ీ +ు +ూ +ృ +ౄ +ె +ే +ై +ొ +ో +ౌ +్ +ౕ +ౖ +ౘ +ౙ +ౚ +ౝ +ౠ +ౡ +ౢ +ౣ +౦ +౧ +౨ +౩ +౪ +౫ +౬ +౭ +౮ +౯ +౷ +౸ +౹ +౺ +౻ +౼ +౽ +౾ +౿ diff --git a/ppocr/utils/dict/ppocrv5_th_dict.txt b/ppocr/utils/dict/ppocrv5_th_dict.txt new file mode 100644 index 0000000..afab9d5 --- /dev/null +++ b/ppocr/utils/dict/ppocrv5_th_dict.txt @@ -0,0 +1,524 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +] +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +© +‥ +{ +} +\ +| +@ +^ +~ +÷ +∕ +∙ +⋅ +· +± +∓ +∩ +∪ +□ +← +↔ +⇒ +⇐ +⇔ +∀ +∃ +∄ +∴ +∵ +∝ +∞ +⊥ +∟ +∠ +∡ +∢ +′ +″ +∥ +⊾ +⊿ +∂ +∫ +∬ +∭ +∮ +∯ +∰ +∑ +∏ +√ +∛ +∜ +∱ +∲ +∳ +∶ +∷ +∼ +® +℉ +Ω +℧ +Å +⌀ +ℏ +⅀ +⍺ +⍵ +¢ +€ +£ +¥ +₿ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +● +▶ +𝑢 +︽ +– +﹥ +𝜓 +• +∋ +ƒ +० +⬆ +Ạ +◀ + +▫ +︾ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +Ø +Ù +Ú +Û +Ü +Ý +Þ +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +ø +ù +ú +û +ü +ý +þ +ÿ +¡ +¤ +¦ +§ +¨ +ª +« +¬ +¯ +° +² +³ +´ +µ +¶ +¸ +¹ +º +» +¼ +½ +¾ +¿ +× +‐ +‑ +‒ +— +― +‖ +‗ +‘ +’ +‚ +‛ +“ +” +„ +‟ +† +‡ +‣ +․ +… +‧ +‰ +‴ +‵ +‶ +‷ +‸ +‹ +› +※ +‼ +‽ +‾ +₤ +₡ +₹ +− +∖ +∗ +≈ +≠ +≡ +≤ +≥ +⊂ +⊃ +↑ +→ +↓ +↕ +™ +Ω +℮ +∆ +✓ +✗ +✘ +▪ +◼ +✔ +✕ +☑ +☒ +№ +₽ +₴ +Α +α +Β +β +Γ +γ +Δ +δ +Ε +ε +Ζ +ζ +Η +η +Θ +θ +Ι +ι +Κ +κ +Λ +λ +Μ +μ +Ν +ν +Ξ +ξ +Ο +ο +Π +π +Ρ +ρ +Σ +σ +ς +Τ +τ +Υ +υ +Φ +φ +Χ +χ +Ψ +ψ +ω +ก +ข +ค +ฅ +ฆ +ง +จ +ฉ +ช +ซ +ฌ +ญ +ฎ +ฏ +ฐ +ฑ +ฒ +ณ +ด +ต +ถ +ท +ธ +น +บ +ป +ผ +ฝ +พ +ฟ +ภ +ม +ย +ร +ฤ +ล +ฦ +ว +ศ +ษ +ส +ห +ฬ +อ +ฮ +ฯ +ะ +ั +า +ำ +ิ +ี +ึ +ื +ุ +ู +ฺ +฿ +เ +แ +โ +ใ +ไ +ๅ +ๆ +็ +่ +้ +๊ +๋ +์ +ํ +๐ +๑ +๒ +๓ +๔ +๕ +๖ +๗ +๘ +๙ +๚ diff --git a/ppocr/utils/dict/ppocrv6_dict.txt b/ppocr/utils/dict/ppocrv6_dict.txt new file mode 100644 index 0000000..270082a --- /dev/null +++ b/ppocr/utils/dict/ppocrv6_dict.txt @@ -0,0 +1,18708 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +: +; +< += +> +? +@ +[ +\ +] +^ +_ +` +{ +| +} +~ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +¢ +£ +¤ +¥ +¦ +§ +¨ +© +ª +« +¬ +® +¯ +° +± +² +³ +´ +µ +¶ +· +¸ +¹ +º +» +¼ +½ +¾ +¿ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +× +Ø +Ù +Ú +Û +Ü +Ý +Þ +ß +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +÷ +ø +ù +ú +û +ü +ý +þ +ÿ +Ā +ā +Ă +ă +Ą +ą +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ď +ď +Đ +đ +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ĥ +ĥ +Ħ +ħ +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +ı +IJ +ij +Ĵ +ĵ +Ķ +ķ +ĸ +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +Ń +ń +Ņ +ņ +Ň +ň +ʼn +Ŋ +ŋ +Ō +ō +Ŏ +ŏ +Ő +ő +Œ +œ +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ţ +ţ +Ť +ť +Ŧ +ŧ +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ŵ +ŵ +Ŷ +ŷ +Ÿ +Ź +ź +Ż +ż +Ž +ž +ſ +ƀ +Ɓ +Ƃ +ƃ +Ƅ +ƅ +Ɔ +Ƈ +ƈ +Ɖ +Ɗ +Ƌ +ƌ +ƍ +Ǝ +Ə +Ɛ +Ƒ +ƒ +Ɠ +Ɣ +ƕ +Ɩ +Ɨ +Ƙ +ƙ +ƚ +ƛ +Ɯ +Ɲ +ƞ +Ɵ +Ơ +ơ +Ƣ +ƣ +Ƥ +ƥ +Ʀ +Ƨ +ƨ +Ʃ +ƪ +ƫ +Ƭ +ƭ +Ʈ +Ư +ư +Ʊ +Ʋ +Ƴ +ƴ +Ƶ +ƶ +Ʒ +Ƹ +ƹ +ƺ +ƻ +Ƽ +ƽ +ƾ +ƿ +ǀ +ǁ +ǂ +ǃ +DŽ +Dž +dž +LJ +Lj +lj +NJ +Nj +nj +Ǎ +ǎ +Ǐ +ǐ +Ǒ +ǒ +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +ǝ +Ǟ +ǟ +Ǡ +ǡ +Ǣ +ǣ +Ǥ +ǥ +Ǧ +ǧ +Ǩ +ǩ +Ǫ +ǫ +Ǭ +ǭ +Ǯ +ǯ +ǰ +DZ +Dz +dz +Ǵ +ǵ +Ƕ +Ƿ +Ǹ +ǹ +Ǻ +ǻ +Ǽ +ǽ +Ǿ +ǿ +Ȁ +ȁ +Ȃ +ȃ +Ȅ +ȅ +Ȇ +ȇ +Ȉ +ȉ +Ȋ +ȋ +Ȍ +ȍ +Ȏ +ȏ +Ȑ +ȑ +Ȓ +ȓ +Ȕ +ȕ +Ȗ +ȗ +Ș +ș +Ț +ț +Ȝ +ȝ +Ȟ +ȟ +Ƞ +ȡ +Ȣ +ȣ +Ȥ +ȥ +Ȧ +ȧ +Ȩ +ȩ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ +Ȳ +ȳ +ȴ +ȵ +ȶ +ȷ +ȸ +ȹ +Ⱥ +Ȼ +ȼ +Ƚ +Ⱦ +ȿ +ɀ +Ɂ +ɂ +Ƀ +Ʉ +Ʌ +Ɇ +ɇ +Ɉ +ɉ +Ɋ +ɋ +Ɍ +ɍ +Ɏ +ɏ +ɐ +ɑ +ɒ +ɓ +ɔ +ɕ +ɖ +ɗ +ɘ +ə +ɚ +ɛ +ɜ +ɝ +ɞ +ɟ +ɠ +ɡ +ɢ +ɣ +ɤ +ɥ +ɦ +ɧ +ɨ +ɩ +ɪ +ɫ +ɬ +ɭ +ɮ +ɯ +ɰ +ɱ +ɲ +ɳ +ɴ +ɵ +ɶ +ɷ +ɸ +ɹ +ɺ +ɻ +ɼ +ɽ +ɾ +ɿ +ʀ +ʁ +ʂ +ʃ +ʄ +ʅ +ʆ +ʇ +ʈ +ʉ +ʊ +ʋ +ʌ +ʍ +ʎ +ʏ +ʐ +ʑ +ʒ +ʓ +ʔ +ʕ +ʖ +ʗ +ʘ +ʙ +ʚ +ʛ +ʜ +ʝ +ʞ +ʟ +ʠ +ʡ +ʢ +ʣ +ʤ +ʥ +ʦ +ʧ +ʨ +ʩ +ʪ +ʫ +ʬ +ʭ +ʮ +ʯ +Α +Β +Γ +Δ +Ε +Ζ +Η +Θ +Ι +Κ +Λ +Μ +Ν +Ξ +Ο +Π +Ρ +Σ +Τ +Υ +Φ +Χ +Ψ +Ω +α +β +γ +δ +ε +ζ +η +θ +ι +κ +λ +μ +ν +ξ +ο +π +ρ +σ +τ +υ +φ +χ +ψ +ω +ϐ +ϑ +ϒ +ϕ +ϖ +ϝ +Ϟ +ϟ +Ϡ +ϡ +Ϣ +ϣ +Ϥ +ϥ +Ϧ +ϧ +Ϩ +ϩ +Ϫ +ϫ +Ϭ +ϭ +Ϯ +ϯ +ϰ +ϱ +ϴ +ϵ +Ạ +ồ +– +— +― +‖ +‘ +’ +“ +” +† +‡ +• +‥ +… +‰ +′ +″ +※ +⁎ +⁰ +⁴ +⁵ +⁶ +⁷ +⁸ +⁹ +⁺ +⁻ +⁼ +⁽ +⁾ +ⁿ +₀ +₁ +₂ +₃ +₄ +₅ +₆ +₇ +₈ +₉ +₊ +₋ +₌ +₍ +₎ +ₒ +₠ +₡ +₢ +₣ +₤ +₥ +₦ +₧ +₨ +₩ +₪ +₫ +€ +₭ +₮ +₯ +₰ +₱ +₲ +₳ +₴ +₵ +₶ +₷ +₸ +₹ +₺ +₻ +₼ +₽ +₾ +₿ +℃ +℉ +ℎ +ℏ +ℑ +℘ +ℜ +™ +℧ +Å +ℵ +ℶ +ℷ +ℸ +ℹ +⅀ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +ⅼ +ↀ +ↁ +ↂ +← +↑ +→ +↓ +↔ +↕ +↖ +↗ +↘ +↙ +↚ +↛ +↜ +↝ +↞ +↟ +↠ +↡ +↢ +↣ +↤ +↥ +↦ +↧ +↨ +↩ +↪ +↫ +↬ +↭ +↮ +↯ +↰ +↱ +↲ +↳ +↴ +↵ +↶ +↷ +↸ +↹ +↺ +↻ +↼ +↽ +↾ +↿ +⇀ +⇁ +⇂ +⇃ +⇄ +⇅ +⇆ +⇇ +⇈ +⇉ +⇊ +⇋ +⇌ +⇍ +⇎ +⇏ +⇐ +⇑ +⇒ +⇓ +⇔ +⇕ +⇖ +⇗ +⇘ +⇙ +⇚ +⇛ +⇜ +⇝ +⇞ +⇟ +⇠ +⇡ +⇢ +⇣ +⇤ +⇥ +⇦ +⇧ +⇨ +⇩ +⇪ +⇫ +⇬ +⇭ +⇮ +⇯ +⇰ +⇱ +⇲ +⇳ +⇴ +⇵ +⇶ +⇷ +⇸ +⇹ +⇺ +⇻ +⇼ +⇽ +⇾ +⇿ +∀ +∁ +∂ +∃ +∄ +∅ +∆ +∇ +∈ +∉ +∋ +∏ +∑ +− +∓ +∕ +∖ +∙ +√ +∛ +∜ +∝ +∞ +∟ +∠ +∡ +∢ +∥ +∧ +∨ +∩ +∪ +∫ +∬ +∭ +∮ +∯ +∰ +∱ +∲ +∳ +∴ +∵ +∶ +∷ +∸ +∹ +∺ +∻ +∼ +∽ +∾ +∿ +≀ +≁ +≂ +≃ +≄ +≅ +≆ +≇ +≈ +≉ +≊ +≋ +≌ +≍ +≎ +≏ +≐ +≑ +≒ +≓ +≔ +≕ +≖ +≗ +≘ +≙ +≚ +≛ +≜ +≝ +≞ +≟ +≠ +≡ +≢ +≣ +≤ +≥ +≦ +≧ +≨ +≩ +≪ +≫ +≬ +≭ +≮ +≯ +≰ +≱ +≲ +≳ +≴ +≵ +≶ +≷ +≸ +≹ +≺ +≻ +≼ +≽ +≾ +≿ +⊀ +⊁ +⊂ +⊃ +⊄ +⊅ +⊆ +⊇ +⊈ +⊉ +⊊ +⊋ +⊌ +⊍ +⊎ +⊏ +⊐ +⊑ +⊒ +⊓ +⊔ +⊕ +⊖ +⊗ +⊘ +⊙ +⊚ +⊛ +⊜ +⊝ +⊞ +⊟ +⊠ +⊡ +⊢ +⊣ +⊤ +⊥ +⊦ +⊧ +⊨ +⊩ +⊪ +⊫ +⊬ +⊭ +⊮ +⊯ +⊰ +⊱ +⊲ +⊳ +⊴ +⊵ +⊶ +⊷ +⊸ +⊹ +⊺ +⊻ +⊼ +⊽ +⊾ +⊿ +⋅ +⌀ +⌃ +⌘ +⌚ +⌛ +⌤ +⌥ +⌦ +⌧ +⌨ +〉 +⌫ +⌬ +⌭ +⌮ +⌯ +⍵ +⍺ +⎆ +⎇ +⎈ +⎉ +⎊ +⎋ +⎌ +⎍ +⎎ +⎏ +⎐ +⎑ +⎒ +⎓ +⎔ +⎕ +⎖ +⎗ +⎘ +⎙ +⎚ +⎛ +⎜ +⎝ +⎞ +⎟ +⎠ +⎡ +⎢ +⎣ +⎤ +⎥ +⎦ +⎧ +⎨ +⎩ +⎪ +⎫ +⎬ +⎭ +⎮ +⎯ +⎰ +⎱ +⎲ +⎳ +⎴ +⎵ +⎶ +⎷ +⎸ +⎹ +⎺ +⎻ +⎼ +⎽ +⎾ +⎿ +⏀ +⏁ +⏂ +⏃ +⏄ +⏅ +⏆ +⏇ +⏈ +⏉ +⏊ +⏋ +⏌ +⏍ +⏎ +⏏ +⏐ +⏑ +⏒ +⏓ +⏔ +⏕ +⏖ +⏗ +⏘ +⏙ +⏚ +⏛ +⏜ +⏝ +⏞ +⏟ +⏠ +⏡ +⏢ +⏣ +⏤ +⏥ +⏦ +⏧ +⏨ +⏩ +⏪ +⏫ +⏬ +⏭ +⏮ +⏯ +⏰ +⏱ +⏲ +⏳ +⏴ +⏵ +⏶ +⏷ +⏸ +⏹ +⏺ +⏻ +⏼ +⏽ +⏾ +⏿ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +⑪ +⑫ +⑬ +⑭ +⑮ +⑯ +⑰ +⑱ +⑲ +⑳ +Ⓜ +━ +╆ +╳ +▁ +█ +■ +□ +▪ +▫ +▬ +▲ +△ +▶ +▷ +▼ +▽ +◀ +◆ +◇ +◉ +◊ +○ +◎ +● +◐ +◥ +◻ +◼ +◽ +◾ +☀ +☁ +☂ +☃ +☄ +★ +☆ +☇ +☈ +☉ +☊ +☋ +☌ +☍ +☎ +☏ +☐ +☑ +☒ +☓ +☔ +☕ +☖ +☗ +☘ +☙ +☚ +☛ +☜ +☝ +☞ +☟ +☠ +☡ +☢ +☣ +☤ +☥ +☦ +☧ +☨ +☩ +☪ +☫ +☬ +☭ +☮ +☯ +☰ +☱ +☲ +☳ +☴ +☵ +☶ +☷ +☸ +☹ +☺ +☻ +☼ +☽ +☾ +☿ +♀ +♁ +♂ +♃ +♄ +♅ +♆ +♇ +♈ +♉ +♊ +♋ +♌ +♍ +♎ +♏ +♐ +♑ +♒ +♓ +♔ +♕ +♖ +♗ +♘ +♙ +♚ +♛ +♜ +♝ +♞ +♟ +♠ +♡ +♢ +♣ +♤ +♥ +♦ +♧ +♨ +♩ +♪ +♫ +♬ +♭ +♮ +♯ +♰ +♱ +♲ +♳ +♴ +♵ +♶ +♷ +♸ +♹ +♺ +♻ +♼ +♽ +♾ +♿ +⚀ +⚁ +⚂ +⚃ +⚄ +⚅ +⚆ +⚇ +⚈ +⚉ +⚊ +⚋ +⚌ +⚍ +⚎ +⚏ +⚐ +⚑ +⚒ +⚓ +⚔ +⚕ +⚖ +⚗ +⚘ +⚙ +⚚ +⚛ +⚜ +⚝ +⚞ +⚟ +⚠ +⚡ +⚢ +⚣ +⚤ +⚥ +⚦ +⚧ +⚨ +⚩ +⚪ +⚫ +⚬ +⚭ +⚮ +⚯ +⚰ +⚱ +⚲ +⚳ +⚴ +⚵ +⚶ +⚷ +⚸ +⚹ +⚺ +⚻ +⚼ +⚽ +⚾ +⚿ +⛀ +⛁ +⛂ +⛃ +⛄ +⛅ +⛆ +⛇ +⛈ +⛉ +⛊ +⛋ +⛌ +⛍ +⛎ +⛏ +⛐ +⛑ +⛒ +⛓ +⛔ +⛕ +⛖ +⛗ +⛘ +⛙ +⛚ +⛛ +⛜ +⛝ +⛞ +⛠ +⛡ +⛢ +⛣ +⛤ +⛥ +⛦ +⛧ +⛨ +⛩ +⛪ +⛫ +⛬ +⛭ +⛮ +⛯ +⛲ +⛳ +⛵ +⛶ +⛺ +⛽ +⛾ +⛿ +✂ +✅ +✆ +✇ +✈ +✉ +✊ +✋ +✌ +✍ +✎ +✏ +✐ +✑ +✒ +✓ +✔ +✕ +✖ +✘ +✙ +✚ +✛ +✜ +✝ +✞ +✟ +✠ +✡ +✢ +✣ +✤ +✥ +✦ +✧ +✨ +✩ +✪ +✫ +✬ +✭ +✮ +✯ +✰ +✱ +✲ +✳ +✴ +✵ +✶ +✷ +✸ +✹ +✺ +✻ +✼ +✽ +✾ +✿ +❀ +❁ +❂ +❃ +❄ +❅ +❆ +❇ +❈ +❉ +❊ +❋ +❌ +❍ +❎ +❏ +❐ +❑ +❒ +❓ +❔ +❕ +❖ +❗ +❘ +❙ +❚ +❛ +❜ +❝ +❞ +❡ +❢ +❣ +❤ +❥ +❦ +❧ +❨ +❩ +❪ +❫ +❬ +❭ +❮ +❯ +❰ +❱ +❲ +❳ +❴ +❵ +❶ +❷ +❸ +❹ +❺ +❻ +❼ +❽ +❾ +❿ +➀ +➁ +➂ +➃ +➄ +➅ +➆ +➇ +➈ +➉ +➊ +➋ +➌ +➍ +➎ +➏ +➐ +➑ +➒ +➓ +➔ +➕ +➖ +➗ +➘ +➙ +➚ +➛ +➜ +➝ +➞ +➟ +➠ +➡ +➢ +➣ +➤ +➥ +➦ +➧ +➨ +➩ +➪ +➫ +➬ +➭ +➮ +➯ +➰ +➱ +➲ +➳ +➴ +➵ +➶ +➷ +➸ +➹ +➺ +➻ +➼ +➽ +➾ +➿ +⟶ +⟹ +⤴ +⤵ +⬅ +⬆ +⬇ +⬛ +⬜ +⭐ +⭕ +  +、 +。 +々 +〆 +〇 +〈 +〉 +《 +》 +「 +」 +『 +』 +【 +】 +〒 +〔 +〕 +〖 +〗 +〜 +〰 +〵 +〽 +ぁ +あ +ぃ +い +ぅ +う +ぇ +え +ぉ +お +か +が +き +ぎ +く +ぐ +け +げ +こ +ご +さ +ざ +し +じ +す +ず +せ +ぜ +そ +ぞ +た +だ +ち +ぢ +っ +つ +づ +て +で +と +ど +な +に +ぬ +ね +の +は +ば +ぱ +ひ +び +ぴ +ふ +ぶ +ぷ +へ +べ +ぺ +ほ +ぼ +ぽ +ま +み +む +め +も +ゃ +や +ゅ +ゆ +ょ +よ +ら +り +る +れ +ろ +ゎ +わ +ゑ +を +ん +ゕ +ゖ +ゝ +ゞ +ァ +ア +ィ +イ +ゥ +ウ +ェ +エ +ォ +オ +カ +ガ +キ +ギ +ク +グ +ケ +ゲ +コ +ゴ +サ +ザ +シ +ジ +ス +ズ +セ +ゼ +ソ +ゾ +タ +ダ +チ +ヂ +ッ +ツ +ヅ +テ +デ +ト +ド +ナ +ニ +ヌ +ネ +ノ +ハ +バ +パ +ヒ +ビ +ピ +フ +ブ +プ +ヘ +ベ +ペ +ホ +ボ +ポ +マ +ミ +ム +メ +モ +ャ +ヤ +ュ +ユ +ョ +ヨ +ラ +リ +ル +レ +ロ +ヮ +ワ +ヰ +ヲ +ン +ヴ +ヵ +ヶ +ヷ +ヸ +ヹ +ヺ +・ +ー +ヽ +ヾ +ヿ +㈱ +㊗ +㊙ +㐂 +㐱 +㑇 +㑊 +㑳 +㑺 +㒺 +㕁 +㕑 +㕥 +㕮 +㘎 +㘭 +㙍 +㙘 +㙟 +㙦 +㚷 +㛃 +㛚 +㛹 +㝛 +㝠 +㝡 +㟁 +㟃 +㠇 +㠓 +㠣 +㠯 +㠶 +㡌 +㢘 +㤘 +㤙 +㥄 +㥫 +㥮 +㧐 +㧑 +㧟 +㧱 +㨗 +㨪 +㩗 +㩦 +㩳 +㪚 +㪟 +㫰 +㬉 +㬊 +㬎 +㬚 +㬪 +㭎 +㭕 +㮣 +㮾 +㰀 +㳄 +㳇 +㳒 +㳘 +㳚 +㴔 +㴪 +㴱 +㵐 +㶲 +㸃 +㸆 +㸌 +㺄 +㻬 +㼝 +㽏 +㽞 +㿠 +䁖 +䂮 +䃅 +䃎 +䃮 +䅟 +䈰 +䊀 +䌹 +䎃 +䎖 +䏝 +䏡 +䏲 +䐃 +䓖 +䓛 +䓣 +䓨 +䓫 +䓬 +䖝 +䗖 +䗛 +䗪 +䗬 +䗴 +䘏 +䘑 +䘚 +䜣 +䝉 +䝔 +䝙 +䠀 +䠶 +䢺 +䢼 +䣘 +䥽 +䦃 +䮄 +䰟 +䰾 +䲁 +䲟 +䲠 +䲢 +䴉 +䴓 +䴔 +䴕 +䴖 +䴗 +䴘 +䴙 +䶊 +䶮 +一 +丁 +七 +丄 +万 +丈 +三 +上 +下 +丌 +不 +与 +丏 +丐 +丑 +专 +且 +丕 +世 +丘 +丙 +业 +丛 +东 +丝 +丞 +丟 +両 +丢 +两 +严 +並 +丧 +个 +丫 +中 +丮 +丰 +丱 +串 +临 +丸 +丹 +为 +主 +丼 +丽 +举 +丿 +乂 +乃 +久 +么 +义 +之 +乌 +乍 +乎 +乏 +乐 +乒 +乓 +乔 +乖 +乗 +乘 +乙 +乜 +九 +乞 +也 +习 +乡 +书 +乩 +乭 +买 +乱 +乳 +乸 +乹 +乾 +亀 +亂 +了 +予 +争 +亊 +事 +二 +亍 +于 +亏 +云 +互 +亓 +五 +井 +亘 +亙 +亚 +些 +亜 +亞 +亟 +亡 +亢 +交 +亥 +亦 +产 +亨 +亩 +享 +京 +亭 +亮 +亯 +亰 +亱 +亲 +亳 +亵 +亶 +亷 +亸 +亹 +人 +亾 +亿 +什 +仁 +仂 +仃 +仄 +仅 +仆 +仇 +仉 +今 +介 +仍 +从 +仏 +仑 +仓 +仔 +仕 +他 +仗 +付 +仙 +仛 +仜 +仝 +仞 +仟 +仡 +代 +令 +以 +仨 +仩 +仪 +仫 +们 +仮 +仰 +仱 +仲 +仳 +仵 +件 +价 +仺 +任 +仼 +份 +仿 +伀 +企 +伃 +伈 +伉 +伊 +伋 +伍 +伎 +伏 +伐 +休 +伔 +伕 +众 +优 +伙 +会 +伛 +伝 +伞 +伟 +传 +伢 +伣 +伤 +伥 +伦 +伧 +伪 +伫 +伬 +伭 +伯 +估 +伱 +伲 +伴 +伶 +伷 +伸 +伺 +伻 +似 +伽 +伾 +佀 +佁 +佃 +但 +佇 +佈 +佉 +佋 +佌 +位 +低 +住 +佐 +佑 +佒 +体 +佔 +何 +佖 +佗 +佘 +余 +佚 +佛 +作 +佝 +佞 +佟 +你 +佢 +佣 +佤 +佥 +佧 +佩 +佪 +佬 +佯 +佰 +佳 +佴 +併 +佶 +佷 +佸 +佹 +佺 +佻 +佼 +佽 +佾 +使 +侀 +侁 +侂 +侃 +侄 +侅 +來 +侇 +侈 +侉 +侊 +例 +侍 +侏 +侐 +侑 +侔 +侖 +侗 +侘 +侚 +供 +侜 +依 +侞 +侠 +価 +侣 +侥 +侦 +侧 +侨 +侩 +侪 +侬 +侮 +侯 +侲 +侳 +侴 +侵 +侶 +侷 +侹 +侻 +便 +俁 +係 +促 +俄 +俅 +俇 +俉 +俊 +俋 +俌 +俍 +俎 +俏 +俐 +俑 +俓 +俔 +俗 +俘 +俙 +俚 +俛 +俜 +保 +俞 +俟 +俠 +信 +俣 +俤 +俦 +俨 +俩 +俪 +俫 +俬 +俭 +修 +俯 +俱 +俳 +俴 +俵 +俶 +俷 +俸 +俺 +俽 +俾 +倀 +倃 +倅 +倆 +倇 +倈 +倉 +個 +倌 +倍 +倏 +倐 +們 +倒 +倓 +倔 +倕 +倖 +倗 +倘 +候 +倚 +倛 +倜 +倞 +借 +倠 +倡 +倢 +倣 +値 +倥 +倦 +倧 +倨 +倩 +倪 +倫 +倬 +倭 +倮 +倰 +倳 +倴 +倶 +倷 +倸 +倹 +债 +倻 +值 +倾 +偁 +偃 +偅 +假 +偈 +偉 +偊 +偋 +偌 +偍 +偎 +偏 +偐 +偓 +偕 +偘 +做 +偛 +停 +偝 +偞 +偟 +偠 +偡 +偢 +健 +偨 +偩 +偪 +偫 +偬 +偭 +偯 +偰 +偲 +側 +偵 +偶 +偷 +偸 +偺 +偻 +偽 +偾 +偿 +傀 +傂 +傃 +傅 +傈 +傉 +傋 +傌 +傍 +傎 +傑 +傒 +傔 +傕 +傖 +傘 +備 +傚 +傛 +傜 +傝 +傞 +傢 +傣 +傥 +傧 +储 +傩 +催 +傭 +傮 +傯 +傰 +傱 +傲 +傳 +傴 +債 +傷 +傺 +傻 +傽 +傾 +傿 +僁 +僂 +僄 +僅 +僆 +僇 +僈 +僉 +僊 +僋 +働 +僎 +像 +僑 +僓 +僔 +僕 +僖 +僗 +僙 +僚 +僛 +僜 +僝 +僞 +僡 +僤 +僥 +僦 +僧 +僨 +僩 +僪 +僬 +僭 +僮 +僰 +僱 +僳 +僴 +僵 +僶 +僸 +價 +僻 +僽 +僾 +僿 +儀 +儁 +儂 +儃 +億 +儆 +儇 +儈 +儉 +儋 +儌 +儐 +儑 +儒 +儓 +儔 +儕 +儗 +儘 +儚 +儛 +儜 +儞 +償 +儠 +儡 +儢 +儤 +儥 +儦 +優 +儫 +儰 +儱 +儲 +儳 +儴 +儵 +儷 +儸 +儹 +儺 +儻 +儼 +儽 +儿 +兀 +允 +元 +兄 +充 +兆 +兇 +先 +光 +克 +兌 +免 +兎 +児 +兑 +兒 +兔 +兕 +兖 +兗 +兙 +党 +兛 +兜 +兝 +兞 +兠 +兡 +兢 +兣 +入 +內 +全 +兩 +兪 +八 +公 +六 +兮 +兰 +共 +关 +兴 +兵 +其 +具 +典 +兹 +养 +兼 +兽 +兿 +冀 +冁 +冂 +冄 +内 +円 +冇 +冈 +冉 +冊 +册 +再 +冏 +冐 +冑 +冒 +冓 +冔 +冕 +冗 +冘 +写 +冚 +军 +农 +冞 +冠 +冢 +冣 +冤 +冥 +冧 +冨 +冪 +冬 +冮 +冯 +冰 +冱 +冲 +决 +冴 +况 +冶 +冷 +冺 +冻 +冼 +冽 +净 +凃 +凄 +凅 +准 +凇 +凈 +凉 +凊 +凋 +凌 +凍 +减 +凑 +凔 +凖 +凘 +凛 +凜 +凝 +凞 +几 +凡 +凢 +凤 +処 +凪 +凫 +凬 +凭 +凯 +凰 +凱 +凳 +凵 +凶 +凸 +凹 +出 +击 +凼 +函 +凿 +刀 +刁 +刂 +刃 +刄 +分 +切 +刈 +刉 +刊 +刌 +刍 +刎 +刑 +划 +刓 +刖 +列 +刘 +则 +刚 +创 +刜 +初 +刞 +删 +判 +別 +刦 +刧 +刨 +利 +刪 +别 +刬 +刭 +刮 +到 +刱 +刲 +刳 +刵 +制 +刷 +券 +刹 +刺 +刻 +刼 +刽 +刿 +剀 +剁 +剂 +剃 +剄 +剅 +剆 +則 +剉 +削 +剋 +剌 +前 +剎 +剏 +剐 +剑 +剒 +剔 +剕 +剖 +剗 +剚 +剛 +剜 +剝 +剞 +剟 +剡 +剣 +剤 +剥 +剧 +剩 +剪 +剫 +剬 +剭 +剮 +副 +剰 +割 +剳 +剴 +創 +剷 +剸 +剹 +剺 +剻 +剼 +剽 +剿 +劀 +劁 +劂 +劃 +劄 +劇 +劈 +劉 +劊 +劋 +劌 +劍 +劐 +劑 +劓 +劔 +劖 +劗 +劘 +劙 +力 +劝 +办 +功 +加 +务 +劢 +劣 +劦 +动 +助 +努 +劫 +劬 +劭 +励 +劲 +劳 +労 +劵 +効 +劼 +劾 +势 +勁 +勃 +勅 +勇 +勉 +勋 +勌 +勍 +勐 +勑 +勒 +勔 +動 +勖 +勗 +勘 +務 +勚 +勛 +勝 +勞 +募 +勢 +勣 +勤 +勦 +勧 +勩 +勰 +勱 +勲 +勳 +勴 +勵 +勷 +勸 +勺 +勻 +勾 +勿 +匀 +匁 +匂 +匃 +匄 +包 +匆 +匈 +匊 +匋 +匍 +匏 +匐 +匕 +化 +北 +匙 +匜 +匝 +匟 +匠 +匡 +匢 +匣 +匦 +匪 +匭 +匮 +匯 +匰 +匱 +匲 +匴 +匷 +匸 +匹 +区 +医 +匼 +匽 +匾 +匿 +區 +十 +千 +卅 +升 +午 +卉 +半 +卋 +卌 +卍 +华 +协 +卐 +卑 +卒 +卓 +協 +单 +卖 +南 +単 +博 +卜 +卞 +卟 +占 +卡 +卢 +卣 +卤 +卦 +卧 +卫 +卬 +卮 +卯 +印 +危 +卲 +即 +却 +卵 +卷 +卸 +卹 +卺 +卻 +卼 +卽 +卿 +厀 +厂 +厄 +厅 +历 +厉 +压 +厌 +厍 +厎 +厒 +厓 +厔 +厕 +厖 +厗 +厘 +厙 +厚 +厜 +厝 +厞 +原 +厠 +厢 +厣 +厤 +厥 +厦 +厨 +厩 +厬 +厭 +厮 +厰 +厲 +厳 +厴 +厹 +去 +厾 +县 +叁 +参 +參 +叄 +叆 +叇 +又 +叉 +及 +友 +双 +反 +収 +发 +叔 +叕 +取 +受 +变 +叙 +叚 +叛 +叟 +叠 +叡 +叢 +口 +古 +句 +另 +叨 +叩 +只 +叫 +召 +叭 +叮 +可 +台 +叱 +史 +右 +叵 +叶 +号 +司 +叹 +叻 +叼 +叽 +吁 +吃 +各 +吆 +合 +吉 +吊 +吋 +同 +名 +后 +吏 +吐 +向 +吒 +吓 +吔 +吕 +吖 +吗 +吚 +君 +吝 +吞 +吟 +吠 +吡 +吣 +吥 +否 +吧 +吨 +吩 +吪 +含 +听 +吭 +吮 +启 +吰 +吱 +吲 +吳 +吴 +吵 +吶 +吷 +吸 +吹 +吻 +吼 +吽 +吾 +呀 +呂 +呃 +呆 +呇 +呈 +呉 +告 +呋 +呌 +呎 +呐 +呑 +呒 +呓 +呔 +呕 +呖 +呗 +员 +呙 +呛 +呜 +呢 +呣 +呤 +呥 +呦 +周 +呪 +呫 +呬 +呰 +呱 +呲 +味 +呴 +呵 +呶 +呷 +呸 +呺 +呻 +呼 +命 +呾 +呿 +咀 +咁 +咂 +咄 +咅 +咆 +咇 +咈 +咉 +咊 +咋 +和 +咍 +咎 +咏 +咐 +咑 +咒 +咔 +咕 +咖 +咗 +咘 +咙 +咚 +咛 +咝 +咟 +咠 +咡 +咢 +咣 +咤 +咥 +咦 +咧 +咨 +咩 +咪 +咫 +咬 +咭 +咮 +咯 +咱 +咲 +咳 +咴 +咶 +咷 +咸 +咺 +咻 +咼 +咽 +咾 +咿 +哀 +品 +哂 +哃 +哄 +哅 +哆 +哇 +哈 +哉 +哌 +响 +哎 +哏 +哐 +哑 +哒 +哓 +哔 +哕 +哖 +哗 +哙 +哚 +哝 +哞 +哟 +員 +哢 +哤 +哥 +哦 +哧 +哨 +哩 +哪 +哫 +哭 +哮 +哱 +哲 +哳 +哶 +哺 +哼 +哽 +哿 +唁 +唃 +唄 +唅 +唆 +唇 +唈 +唉 +唌 +唏 +唐 +唑 +唒 +唔 +唕 +唖 +唗 +唘 +唚 +唛 +唝 +唠 +唢 +唣 +唤 +唦 +唧 +唪 +唫 +唬 +唭 +售 +唯 +唰 +唱 +唲 +唳 +唴 +唵 +唶 +唷 +唸 +唻 +唼 +唾 +唿 +啁 +啃 +啄 +啅 +商 +啉 +啊 +啍 +啎 +問 +啐 +啑 +啒 +啓 +啕 +啖 +啗 +啚 +啜 +啞 +啟 +啡 +啢 +啣 +啤 +啥 +啦 +啧 +啪 +啫 +啬 +啭 +啮 +啯 +啰 +啱 +啲 +啴 +啵 +啶 +啷 +啸 +啻 +啼 +啽 +啾 +喀 +喁 +喂 +喃 +善 +喆 +喇 +喈 +喉 +喊 +喋 +喌 +喍 +喏 +喑 +喒 +喓 +喔 +喘 +喙 +喚 +喜 +喝 +喟 +喢 +喣 +喤 +喥 +喦 +喧 +喨 +喩 +喪 +喫 +喬 +喭 +單 +喰 +喱 +喲 +喳 +喵 +営 +喷 +喹 +喻 +喼 +喽 +喾 +喿 +嗀 +嗁 +嗃 +嗄 +嗅 +嗆 +嗇 +嗈 +嗉 +嗊 +嗋 +嗌 +嗍 +嗎 +嗏 +嗐 +嗑 +嗒 +嗓 +嗔 +嗕 +嗖 +嗚 +嗛 +嗜 +嗝 +嗞 +嗟 +嗡 +嗢 +嗣 +嗤 +嗥 +嗦 +嗧 +嗨 +嗩 +嗪 +嗫 +嗬 +嗮 +嗯 +嗲 +嗳 +嗵 +嗶 +嗷 +嗹 +嗺 +嗼 +嗽 +嗾 +嗿 +嘀 +嘁 +嘂 +嘄 +嘅 +嘆 +嘈 +嘉 +嘌 +嘍 +嘎 +嘏 +嘐 +嘑 +嘒 +嘓 +嘔 +嘕 +嘖 +嘗 +嘘 +嘚 +嘛 +嘜 +嘝 +嘞 +嘟 +嘠 +嘡 +嘢 +嘣 +嘤 +嘥 +嘧 +嘩 +嘬 +嘭 +嘮 +嘯 +嘰 +嘱 +嘲 +嘳 +嘴 +嘵 +嘶 +嘷 +嘸 +嘹 +嘻 +嘽 +嘾 +嘿 +噀 +噁 +噂 +噅 +噆 +噇 +噈 +噉 +噊 +噌 +噍 +噎 +噏 +噓 +噔 +噗 +噘 +噙 +噚 +噛 +噜 +噝 +噞 +噠 +噢 +噣 +噤 +噥 +噦 +器 +噩 +噪 +噫 +噬 +噭 +噮 +噯 +噰 +噱 +噲 +噳 +噴 +噶 +噸 +噹 +噺 +噻 +噼 +嚀 +嚃 +嚄 +嚅 +嚆 +嚇 +嚈 +嚌 +嚍 +嚎 +嚏 +嚐 +嚒 +嚓 +嚕 +嚗 +嚘 +嚙 +嚚 +嚜 +嚞 +嚟 +嚢 +嚣 +嚤 +嚥 +嚦 +嚧 +嚨 +嚩 +嚪 +嚫 +嚬 +嚭 +嚮 +嚯 +嚲 +嚳 +嚴 +嚵 +嚶 +嚷 +嚼 +嚽 +嚾 +嚿 +囀 +囁 +囂 +囃 +囅 +囆 +囈 +囉 +囊 +囋 +囌 +囍 +囑 +囒 +囓 +囔 +囗 +囙 +囚 +四 +囝 +回 +囟 +因 +囡 +团 +団 +囤 +囧 +囪 +囫 +园 +囮 +囯 +困 +囱 +囲 +図 +围 +囵 +囶 +囷 +囹 +固 +国 +图 +囿 +圀 +圁 +圂 +圃 +圄 +圅 +圆 +圇 +圈 +圉 +圊 +國 +圌 +圍 +圏 +圐 +園 +圓 +圖 +圗 +團 +圙 +圜 +圞 +土 +圠 +圢 +圣 +圧 +在 +圩 +圪 +圫 +圬 +圭 +圮 +圯 +地 +圲 +圳 +圹 +场 +圻 +圾 +址 +坁 +坂 +坅 +均 +坉 +坊 +坋 +坌 +坍 +坎 +坏 +坐 +坑 +坒 +坖 +块 +坚 +坛 +坜 +坝 +坞 +坟 +坠 +坡 +坣 +坤 +坥 +坦 +坨 +坩 +坪 +坫 +坬 +坭 +坮 +坯 +坰 +坱 +坲 +坳 +坴 +坵 +坶 +坷 +坻 +坼 +坽 +坿 +垀 +垂 +垃 +垄 +垆 +垈 +型 +垌 +垍 +垎 +垏 +垒 +垓 +垔 +垕 +垗 +垘 +垙 +垚 +垛 +垜 +垝 +垞 +垟 +垠 +垡 +垢 +垣 +垤 +垦 +垧 +垩 +垫 +垭 +垮 +垯 +垰 +垱 +垲 +垴 +垵 +垸 +垺 +垻 +垼 +垽 +垾 +垿 +埂 +埃 +埅 +埆 +埇 +埈 +埋 +埌 +城 +埏 +埒 +埔 +埕 +埗 +埘 +埙 +埚 +埜 +埝 +域 +埠 +埡 +埢 +埤 +埧 +埨 +埪 +埫 +埭 +埮 +埯 +埳 +埴 +埵 +埶 +執 +埸 +培 +基 +埻 +埼 +埽 +堀 +堂 +堃 +堅 +堆 +堇 +堈 +堉 +堊 +堋 +堌 +堍 +堎 +堐 +堑 +堔 +堕 +堖 +堘 +堙 +堝 +堞 +堠 +堡 +堣 +堤 +堥 +堦 +堧 +堨 +堩 +堪 +堮 +堯 +堰 +報 +堲 +場 +堵 +堷 +堺 +堼 +堽 +堾 +堿 +塀 +塁 +塄 +塅 +塆 +塈 +塉 +塊 +塋 +塌 +塍 +塏 +塑 +塒 +塓 +塔 +塕 +塗 +塘 +塙 +塚 +塜 +塝 +塞 +塟 +塡 +塢 +塤 +塥 +塨 +塩 +填 +塬 +塭 +塯 +塰 +塱 +塲 +塴 +塵 +塸 +塹 +塺 +塼 +塽 +塾 +塿 +墀 +墁 +境 +墅 +墆 +墈 +墉 +墊 +墋 +墎 +墐 +墒 +墓 +墕 +墖 +増 +墘 +墙 +墚 +墜 +墝 +增 +墟 +墠 +墡 +墣 +墦 +墨 +墩 +墪 +墫 +墬 +墮 +墯 +墱 +墳 +墶 +墺 +墻 +墼 +墽 +墾 +壁 +壄 +壅 +壆 +壇 +壈 +壊 +壋 +壌 +壎 +壐 +壑 +壓 +壔 +壕 +壖 +壘 +壙 +壚 +壛 +壝 +壞 +壟 +壠 +壢 +壤 +壩 +壪 +士 +壬 +壮 +壯 +声 +壱 +売 +壳 +壴 +壶 +壷 +壸 +壹 +壺 +壻 +壼 +壽 +壾 +壿 +夀 +夃 +处 +夆 +备 +変 +夊 +夋 +夌 +复 +夎 +夏 +夐 +夒 +夔 +夕 +外 +夗 +夘 +夙 +多 +夜 +够 +夠 +夢 +夤 +夥 +大 +天 +太 +夫 +夬 +夭 +央 +夯 +失 +头 +夷 +夸 +夹 +夺 +夼 +夾 +奀 +奁 +奂 +奄 +奅 +奇 +奈 +奉 +奊 +奋 +奎 +奏 +奐 +契 +奓 +奔 +奕 +奖 +套 +奘 +奚 +奠 +奡 +奢 +奣 +奥 +奧 +奨 +奩 +奪 +奫 +奬 +奭 +奮 +奰 +奲 +女 +奴 +奶 +奸 +她 +奼 +好 +妀 +妁 +如 +妃 +妄 +妆 +妇 +妈 +妊 +妍 +妎 +妏 +妐 +妑 +妒 +妓 +妖 +妗 +妘 +妙 +妝 +妞 +妠 +妢 +妣 +妤 +妥 +妦 +妧 +妨 +妩 +妪 +妫 +妬 +妭 +妮 +妯 +妲 +妳 +妵 +妷 +妸 +妹 +妺 +妻 +妾 +姀 +姁 +姃 +姆 +姈 +姉 +姊 +始 +姌 +姍 +姎 +姏 +姐 +姑 +姒 +姓 +委 +姗 +姘 +姙 +姚 +姜 +姝 +姞 +姡 +姣 +姤 +姥 +姦 +姨 +姪 +姫 +姬 +姮 +姱 +姵 +姶 +姸 +姹 +姺 +姻 +姼 +姽 +姿 +娀 +威 +娃 +娄 +娅 +娆 +娇 +娈 +娉 +娋 +娌 +娍 +娎 +娑 +娓 +娖 +娘 +娙 +娛 +娜 +娟 +娠 +娣 +娥 +娩 +娫 +娭 +娯 +娱 +娲 +娳 +娴 +娵 +娶 +娷 +娸 +娼 +娽 +娿 +婀 +婁 +婆 +婉 +婊 +婌 +婍 +婐 +婑 +婓 +婕 +婗 +婘 +婚 +婞 +婟 +婠 +婢 +婣 +婤 +婥 +婦 +婧 +婪 +婫 +婬 +婭 +婯 +婳 +婴 +婵 +婶 +婷 +婸 +婺 +婻 +婼 +婿 +媂 +媃 +媄 +媆 +媊 +媐 +媒 +媓 +媔 +媕 +媖 +媗 +媚 +媛 +媜 +媞 +媟 +媢 +媥 +媦 +媧 +媪 +媬 +媭 +媮 +媯 +媰 +媱 +媲 +媳 +媵 +媸 +媺 +媻 +媼 +媽 +媾 +媿 +嫁 +嫂 +嫄 +嫇 +嫈 +嫉 +嫋 +嫌 +嫐 +嫒 +嫔 +嫕 +嫖 +嫗 +嫘 +嫚 +嫛 +嫜 +嫠 +嫡 +嫢 +嫣 +嫥 +嫦 +嫩 +嫪 +嫫 +嫭 +嫮 +嫰 +嫱 +嫲 +嫳 +嫴 +嫵 +嫶 +嫷 +嫺 +嫻 +嫽 +嫿 +嬀 +嬃 +嬅 +嬈 +嬉 +嬋 +嬌 +嬎 +嬐 +嬓 +嬖 +嬗 +嬙 +嬛 +嬝 +嬡 +嬢 +嬤 +嬥 +嬨 +嬪 +嬬 +嬭 +嬰 +嬲 +嬴 +嬷 +嬸 +嬽 +嬾 +嬿 +孀 +孁 +孃 +孅 +孆 +孈 +孋 +孌 +子 +孑 +孓 +孔 +孕 +孖 +字 +存 +孙 +孚 +孛 +孜 +孝 +孟 +孢 +季 +孤 +孥 +学 +孩 +孪 +孫 +孬 +孮 +孰 +孱 +孲 +孳 +孵 +孷 +學 +孺 +孻 +孼 +孽 +孿 +宁 +宂 +它 +宄 +宅 +宇 +守 +安 +宋 +完 +宍 +宎 +宏 +宓 +宕 +宗 +官 +宙 +定 +宛 +宜 +宝 +实 +実 +宠 +审 +客 +宣 +室 +宥 +宦 +宧 +宨 +宪 +宫 +宬 +宮 +宰 +害 +宴 +宵 +家 +宸 +容 +宽 +宾 +宿 +寀 +寁 +寂 +寃 +寄 +寅 +密 +寇 +寈 +寊 +寋 +富 +寍 +寐 +寑 +寒 +寓 +寔 +寕 +寖 +寗 +寘 +寙 +寛 +寜 +寝 +寞 +察 +寠 +寡 +寢 +寤 +寥 +實 +寧 +寨 +審 +寪 +寫 +寬 +寮 +寯 +寰 +寱 +寳 +寵 +寶 +寸 +对 +寺 +寻 +导 +対 +寿 +封 +専 +尃 +射 +尅 +将 +將 +專 +尉 +尊 +尋 +尌 +對 +導 +小 +尐 +少 +尒 +尓 +尔 +尕 +尖 +尗 +尘 +尙 +尚 +尜 +尝 +尟 +尢 +尤 +尥 +尧 +尨 +尪 +尬 +尭 +尰 +就 +尳 +尴 +尷 +尸 +尹 +尺 +尻 +尼 +尽 +尾 +尿 +局 +屁 +层 +屃 +屄 +居 +屆 +屇 +屈 +屉 +届 +屋 +屌 +屍 +屎 +屏 +屐 +屑 +屓 +屔 +展 +屖 +屘 +屙 +屚 +屜 +屝 +属 +屠 +屡 +屢 +屣 +層 +履 +屦 +屧 +屨 +屩 +屬 +屭 +屮 +屯 +山 +屴 +屹 +屺 +屻 +屼 +屾 +屿 +岀 +岁 +岂 +岅 +岈 +岉 +岊 +岋 +岌 +岍 +岏 +岐 +岑 +岒 +岔 +岖 +岗 +岘 +岙 +岚 +岛 +岜 +岝 +岞 +岟 +岠 +岡 +岢 +岣 +岤 +岧 +岨 +岩 +岪 +岫 +岬 +岭 +岮 +岯 +岰 +岱 +岳 +岵 +岷 +岸 +岻 +岽 +岿 +峁 +峂 +峃 +峄 +峅 +峇 +峉 +峊 +峋 +峌 +峍 +峐 +峒 +峗 +峘 +峙 +峚 +峛 +峝 +峞 +峠 +峡 +峣 +峤 +峥 +峦 +峧 +峨 +峩 +峪 +峬 +峭 +峮 +峯 +峰 +峱 +峴 +島 +峷 +峻 +峼 +峽 +峿 +崀 +崁 +崂 +崃 +崄 +崆 +崇 +崈 +崋 +崌 +崍 +崎 +崏 +崐 +崑 +崒 +崔 +崖 +崗 +崘 +崙 +崚 +崛 +崝 +崞 +崟 +崠 +崡 +崢 +崣 +崤 +崥 +崦 +崧 +崨 +崩 +崬 +崭 +崮 +崰 +崱 +崳 +崴 +崵 +崶 +崷 +崺 +崽 +崾 +崿 +嵁 +嵂 +嵃 +嵅 +嵇 +嵊 +嵋 +嵌 +嵎 +嵐 +嵑 +嵒 +嵕 +嵖 +嵗 +嵘 +嵙 +嵚 +嵛 +嵜 +嵝 +嵞 +嵣 +嵥 +嵨 +嵩 +嵫 +嵬 +嵮 +嵯 +嵰 +嵱 +嵲 +嵴 +嵷 +嵺 +嵻 +嵽 +嵾 +嵿 +嶀 +嶁 +嶂 +嶃 +嶄 +嶅 +嶆 +嶇 +嶈 +嶉 +嶊 +嶋 +嶌 +嶍 +嶒 +嶓 +嶔 +嶕 +嶗 +嶙 +嶚 +嶜 +嶝 +嶞 +嶟 +嶠 +嶡 +嶢 +嶦 +嶧 +嶨 +嶩 +嶪 +嶬 +嶭 +嶮 +嶰 +嶱 +嶲 +嶴 +嶵 +嶷 +嶸 +嶺 +嶼 +嶽 +巀 +巂 +巃 +巄 +巅 +巆 +巇 +巉 +巋 +巌 +巍 +巎 +巏 +巑 +巒 +巔 +巕 +巖 +巗 +巘 +巛 +川 +州 +巟 +巡 +巢 +巣 +工 +左 +巧 +巨 +巩 +巫 +差 +巯 +巰 +己 +已 +巳 +巴 +巵 +巶 +巷 +巹 +巻 +巽 +巾 +巿 +帀 +币 +市 +布 +帅 +帆 +师 +帊 +希 +帏 +帐 +帑 +帔 +帕 +帖 +帗 +帘 +帙 +帚 +帛 +帜 +帝 +帟 +帠 +帡 +帢 +帣 +帤 +帥 +带 +帧 +帨 +帩 +師 +帬 +席 +帮 +帯 +帰 +帱 +帳 +帴 +帶 +帷 +常 +帻 +帼 +帽 +帾 +幀 +幂 +幃 +幄 +幅 +幇 +幌 +幎 +幏 +幑 +幓 +幔 +幕 +幖 +幗 +幘 +幙 +幚 +幛 +幝 +幞 +幟 +幠 +幡 +幢 +幣 +幦 +幧 +幨 +幩 +幪 +幫 +幬 +幭 +幰 +干 +平 +年 +幵 +并 +幷 +幸 +幹 +幺 +幻 +幼 +幽 +幾 +广 +庀 +庁 +庂 +広 +庄 +庆 +庇 +庈 +庉 +床 +庋 +庌 +序 +庐 +庑 +库 +应 +底 +庖 +店 +庙 +庚 +庛 +府 +庞 +废 +庠 +庢 +庣 +庤 +庥 +度 +座 +庨 +庪 +庫 +庬 +庭 +庮 +庰 +庱 +庲 +庳 +庴 +庵 +庶 +康 +庸 +庹 +庻 +庼 +庽 +庾 +廁 +廂 +廃 +廄 +廅 +廆 +廇 +廈 +廉 +廊 +廋 +廌 +廍 +廎 +廐 +廑 +廒 +廓 +廔 +廕 +廖 +廗 +廘 +廙 +廚 +廛 +廜 +廝 +廞 +廟 +廠 +廡 +廢 +廣 +廥 +廦 +廧 +廨 +廩 +廪 +廬 +廮 +廯 +廰 +廱 +廳 +廵 +延 +廷 +廸 +廹 +建 +廻 +廼 +廾 +廿 +开 +弁 +异 +弃 +弄 +弅 +弆 +弇 +弈 +弉 +弊 +弋 +弌 +弍 +式 +弐 +弑 +弒 +弓 +弔 +引 +弖 +弗 +弘 +弚 +弛 +弝 +弟 +张 +弢 +弣 +弤 +弥 +弦 +弧 +弨 +弩 +弭 +弮 +弯 +弱 +弳 +張 +弶 +強 +弸 +弹 +强 +弼 +弾 +彀 +彃 +彄 +彅 +彆 +彈 +彉 +彊 +彋 +彌 +彎 +彏 +彐 +归 +当 +彔 +录 +彖 +彗 +彘 +彙 +彜 +彝 +彞 +彟 +彠 +彡 +形 +彣 +彤 +彥 +彦 +彧 +彩 +彪 +彫 +彬 +彭 +彯 +彰 +影 +彳 +彴 +彷 +彸 +役 +彻 +彼 +彾 +彿 +往 +征 +徂 +徃 +径 +待 +徇 +很 +徉 +徊 +律 +後 +徐 +徑 +徒 +従 +徕 +得 +徘 +徙 +徛 +徜 +從 +徠 +御 +徥 +徦 +徧 +徨 +復 +循 +徫 +徬 +徭 +微 +徯 +徲 +徳 +徴 +徵 +德 +徸 +徹 +徼 +徽 +徾 +徿 +忀 +忁 +心 +必 +忆 +忉 +忌 +忍 +忏 +忐 +忑 +忒 +忔 +忕 +忖 +志 +忘 +忙 +応 +忝 +忞 +忠 +忡 +忣 +忤 +忥 +忧 +忨 +忪 +快 +忬 +忭 +忮 +忯 +忱 +忳 +念 +忷 +忸 +忺 +忻 +忼 +忽 +忾 +忿 +怀 +态 +怂 +怃 +怄 +怅 +怆 +怊 +怋 +怍 +怎 +怏 +怐 +怒 +怓 +怔 +怕 +怖 +怗 +怙 +怚 +怛 +怜 +思 +怞 +怠 +怡 +怢 +急 +怦 +性 +怨 +怩 +怪 +怫 +怬 +怭 +怮 +怯 +怱 +怲 +怳 +怴 +怵 +怷 +怹 +总 +怼 +怿 +恀 +恁 +恂 +恃 +恅 +恆 +恇 +恉 +恊 +恋 +恌 +恍 +恐 +恒 +恓 +恔 +恕 +恙 +恚 +恛 +恝 +恞 +恟 +恠 +恡 +恢 +恣 +恤 +恥 +恧 +恨 +恩 +恪 +恫 +恬 +恭 +息 +恰 +恲 +恳 +恵 +恶 +恸 +恹 +恺 +恻 +恼 +恽 +恿 +悀 +悁 +悃 +悄 +悅 +悆 +悇 +悈 +悉 +悊 +悌 +悍 +悐 +悒 +悔 +悕 +悖 +悗 +悚 +悛 +悝 +悞 +悟 +悠 +悢 +患 +悤 +悦 +悧 +您 +悩 +悪 +悫 +悬 +悭 +悯 +悰 +悱 +悲 +悳 +悴 +悵 +悶 +悷 +悸 +悹 +悺 +悻 +悼 +悽 +悾 +惄 +情 +惆 +惇 +惈 +惉 +惊 +惋 +惌 +惎 +惏 +惑 +惓 +惔 +惕 +惘 +惙 +惚 +惛 +惜 +惝 +惟 +惠 +惡 +惢 +惣 +惤 +惥 +惦 +惧 +惨 +惩 +惪 +惫 +惬 +惭 +惮 +惯 +惰 +惱 +惲 +想 +惴 +惵 +惶 +惷 +惸 +惹 +惺 +惻 +惼 +惾 +愀 +愁 +愃 +愅 +愆 +愈 +愉 +愊 +愍 +愎 +意 +愐 +愒 +愓 +愔 +愕 +愖 +愚 +愛 +愜 +感 +愠 +愣 +愤 +愦 +愧 +愨 +愫 +愬 +愭 +愮 +愯 +愲 +愴 +愶 +愷 +愼 +愽 +愾 +愿 +慁 +慂 +慄 +慅 +慆 +慇 +慈 +慉 +慊 +態 +慌 +慍 +慎 +慑 +慒 +慓 +慔 +慕 +慘 +慚 +慛 +慜 +慝 +慞 +慟 +慢 +慣 +慤 +慥 +慧 +慨 +慪 +慫 +慬 +慭 +慮 +慰 +慱 +慲 +慳 +慴 +慵 +慶 +慷 +慹 +慺 +慼 +慽 +慾 +憀 +憂 +憃 +憇 +憉 +憊 +憋 +憍 +憎 +憐 +憑 +憒 +憓 +憔 +憕 +憖 +憙 +憚 +憛 +憝 +憟 +憡 +憢 +憤 +憧 +憨 +憩 +憪 +憫 +憬 +憭 +憮 +憯 +憰 +憱 +憲 +憴 +憵 +憶 +憷 +憸 +憺 +憻 +憼 +憾 +憿 +懁 +懂 +懃 +懅 +懆 +懇 +懈 +應 +懊 +懋 +懌 +懍 +懐 +懑 +懒 +懔 +懘 +懞 +懟 +懠 +懣 +懤 +懥 +懦 +懧 +懨 +懪 +懫 +懭 +懮 +懰 +懱 +懲 +懵 +懶 +懷 +懸 +懺 +懻 +懼 +懽 +懾 +懿 +戀 +戁 +戃 +戄 +戆 +戇 +戈 +戉 +戊 +戋 +戌 +戍 +戎 +戏 +成 +我 +戒 +戔 +戕 +或 +戗 +战 +戙 +戚 +戛 +戞 +戟 +戠 +戡 +戢 +戣 +戤 +戥 +戦 +戧 +戩 +截 +戬 +戭 +戮 +戯 +戰 +戱 +戲 +戳 +戴 +戶 +户 +戸 +戹 +戺 +戻 +戼 +戽 +戾 +房 +所 +扁 +扂 +扃 +扅 +扆 +扇 +扈 +扉 +扊 +手 +才 +扎 +扐 +扑 +扒 +打 +扔 +払 +托 +扙 +扛 +扜 +扞 +扡 +扢 +扣 +扤 +扥 +扦 +执 +扩 +扪 +扫 +扬 +扭 +扮 +扯 +扰 +扱 +扳 +扶 +批 +扺 +扻 +扼 +扽 +找 +承 +技 +抃 +抄 +抆 +抇 +抉 +把 +抌 +抎 +抏 +抑 +抒 +抓 +抔 +投 +抖 +抗 +折 +抚 +抛 +抜 +抝 +択 +抟 +抠 +抡 +抢 +护 +报 +抦 +抨 +抪 +披 +抬 +抭 +抮 +抱 +抴 +抵 +抶 +抹 +抻 +押 +抽 +抾 +抿 +拂 +拃 +拄 +担 +拆 +拇 +拈 +拉 +拊 +拋 +拌 +拍 +拎 +拏 +拐 +拑 +拒 +拓 +拔 +拕 +拖 +拗 +拘 +拙 +拚 +招 +拜 +拝 +拟 +拠 +拡 +拢 +拣 +拤 +拥 +拦 +拧 +拨 +择 +拫 +括 +拭 +拮 +拯 +拱 +拲 +拳 +拴 +拵 +拶 +拷 +拸 +拹 +拺 +拼 +拽 +拾 +拿 +挀 +持 +挂 +挃 +指 +挈 +按 +挋 +挌 +挍 +挎 +挏 +挐 +挑 +挓 +挖 +挙 +挚 +挛 +挝 +挞 +挟 +挠 +挡 +挣 +挤 +挥 +挦 +挨 +挩 +挪 +挫 +挬 +振 +挱 +挲 +挴 +挵 +挶 +挹 +挺 +挻 +挼 +挽 +挾 +挿 +捁 +捂 +捃 +捄 +捅 +捆 +捉 +捊 +捋 +捌 +捍 +捎 +捏 +捐 +捒 +捔 +捕 +捖 +捗 +捘 +捜 +捞 +损 +捡 +换 +捣 +捥 +捦 +捧 +捨 +捩 +捫 +捭 +据 +捯 +捱 +捲 +捵 +捶 +捷 +捺 +捻 +捼 +捽 +掀 +掂 +掃 +掄 +掇 +授 +掉 +掊 +掌 +掎 +掏 +掐 +排 +掔 +掖 +掗 +掘 +掙 +掛 +掜 +掝 +掞 +掟 +掠 +採 +探 +掣 +掤 +接 +控 +推 +掩 +措 +掫 +掬 +掭 +掮 +掯 +掰 +掱 +掲 +掳 +掴 +掷 +掸 +掺 +掻 +掼 +掽 +掾 +揀 +揃 +揄 +揅 +揆 +揉 +揊 +揍 +揎 +描 +提 +揑 +插 +揔 +揕 +揖 +揘 +揚 +換 +揜 +揝 +揟 +揠 +握 +揣 +揤 +揥 +揨 +揩 +揪 +揫 +揭 +揮 +揯 +揱 +揲 +揳 +援 +揵 +揶 +揷 +揸 +揹 +揺 +揽 +揿 +搀 +搁 +搂 +搅 +搆 +搉 +搊 +搋 +搌 +損 +搏 +搐 +搒 +搓 +搔 +搕 +搖 +搗 +搘 +搚 +搛 +搜 +搞 +搟 +搠 +搡 +搢 +搣 +搤 +搥 +搦 +搧 +搨 +搪 +搫 +搬 +搭 +搯 +搰 +搳 +搴 +搵 +搶 +搷 +搹 +携 +搽 +搾 +摀 +摁 +摂 +摃 +摄 +摅 +摆 +摇 +摈 +摊 +摋 +摍 +摎 +摏 +摐 +摑 +摒 +摓 +摔 +摘 +摛 +摜 +摝 +摞 +摟 +摠 +摡 +摦 +摧 +摩 +摭 +摮 +摯 +摰 +摲 +摳 +摴 +摵 +摶 +摸 +摹 +摺 +摻 +摽 +摿 +撂 +撃 +撄 +撅 +撇 +撈 +撊 +撋 +撌 +撏 +撐 +撑 +撒 +撓 +撕 +撖 +撗 +撙 +撚 +撜 +撝 +撞 +撟 +撠 +撢 +撣 +撤 +撥 +撦 +撩 +撫 +撬 +播 +撮 +撰 +撲 +撳 +撵 +撷 +撸 +撹 +撺 +撻 +撼 +撽 +撾 +撿 +擀 +擁 +擂 +擄 +擅 +擇 +擉 +擊 +擋 +操 +擎 +擏 +擐 +擒 +擓 +擔 +擕 +擖 +擗 +擘 +據 +擞 +擠 +擡 +擢 +擣 +擤 +擥 +擦 +擧 +擨 +擩 +擫 +擬 +擭 +擯 +擰 +擱 +擲 +擴 +擷 +擸 +擺 +擻 +擼 +擽 +擾 +擿 +攀 +攄 +攆 +攇 +攉 +攌 +攍 +攏 +攐 +攒 +攓 +攔 +攕 +攖 +攗 +攘 +攙 +攛 +攜 +攝 +攞 +攠 +攡 +攢 +攣 +攤 +攥 +攦 +攩 +攪 +攫 +攬 +攭 +攮 +支 +攲 +攳 +攴 +收 +攷 +攸 +改 +攻 +攽 +放 +政 +敁 +敂 +故 +敆 +效 +敉 +敊 +敌 +敍 +敎 +敏 +救 +敓 +敔 +敕 +敖 +敗 +敘 +教 +敛 +敜 +敝 +敞 +敟 +敢 +散 +敤 +敦 +敧 +敩 +敪 +敫 +敬 +敭 +敯 +数 +敲 +敳 +整 +敵 +敶 +敷 +數 +敹 +敺 +敻 +敾 +敿 +斀 +斁 +斂 +斃 +斄 +文 +斉 +斋 +斌 +斎 +斐 +斑 +斒 +斓 +斔 +斕 +斖 +斗 +料 +斛 +斜 +斝 +斞 +斟 +斠 +斡 +斤 +斥 +斧 +斨 +斩 +斪 +斫 +斬 +断 +斮 +斯 +新 +斲 +斵 +斶 +斷 +方 +斻 +於 +施 +斿 +旁 +旂 +旃 +旄 +旅 +旆 +旉 +旋 +旌 +旍 +旎 +族 +旐 +旒 +旓 +旖 +旗 +旙 +旚 +旛 +旝 +旞 +旟 +无 +旡 +既 +日 +旦 +旧 +旨 +早 +旬 +旭 +旮 +旯 +旰 +旱 +旲 +旳 +旴 +旵 +时 +旷 +旸 +旺 +旻 +旼 +旽 +旾 +旿 +昀 +昂 +昃 +昄 +昆 +昇 +昈 +昉 +昊 +昌 +昍 +明 +昏 +昐 +昒 +易 +昔 +昕 +昙 +昚 +昛 +昜 +昝 +昞 +星 +映 +昡 +昢 +昣 +昤 +春 +昦 +昧 +昨 +昪 +昫 +昬 +昭 +是 +昰 +昱 +昲 +昳 +昴 +昵 +昶 +昺 +昼 +昽 +显 +晁 +時 +晃 +晅 +晇 +晈 +晉 +晊 +晋 +晌 +晏 +晐 +晒 +晓 +晔 +晕 +晖 +晗 +晙 +晚 +晛 +晜 +晝 +晞 +晟 +晡 +晢 +晤 +晥 +晦 +晧 +晨 +晩 +晪 +晫 +晬 +晭 +普 +景 +晰 +晱 +晳 +晴 +晶 +晷 +晸 +智 +晻 +晼 +晾 +暀 +暁 +暂 +暃 +暄 +暅 +暆 +暇 +暈 +暉 +暊 +暋 +暌 +暍 +暎 +暏 +暐 +暑 +暕 +暖 +暗 +暘 +暝 +暟 +暠 +暡 +暢 +暦 +暧 +暨 +暩 +暫 +暮 +暰 +暱 +暲 +暴 +暵 +暶 +暸 +暹 +暻 +暾 +暿 +曀 +曄 +曅 +曆 +曇 +曈 +曉 +曊 +曋 +曌 +曏 +曒 +曔 +曖 +曙 +曚 +曛 +曜 +曝 +曠 +曡 +曣 +曦 +曧 +曨 +曩 +曬 +曭 +曮 +曰 +曲 +曳 +更 +曶 +曷 +書 +曹 +曺 +曼 +曽 +曾 +替 +最 +朁 +會 +朄 +朅 +月 +有 +朊 +朋 +服 +朏 +朐 +朒 +朓 +朔 +朕 +朖 +朗 +朘 +望 +朝 +朞 +期 +朡 +朢 +朣 +朦 +朧 +木 +未 +末 +本 +札 +朮 +术 +朱 +朳 +朴 +朵 +朶 +朸 +朹 +机 +朻 +朼 +朽 +朾 +朿 +杀 +杁 +杂 +权 +杄 +杅 +杆 +杇 +杈 +杉 +杋 +杌 +李 +杏 +材 +村 +杓 +杕 +杖 +杗 +杙 +杜 +杝 +杞 +束 +杠 +条 +杢 +杣 +杤 +来 +杧 +杨 +杩 +杪 +杬 +杭 +杮 +杯 +杰 +東 +杲 +杳 +杴 +杵 +杶 +杷 +杻 +杼 +松 +板 +极 +构 +枅 +枆 +枇 +枉 +枋 +枌 +枍 +枎 +枏 +析 +枒 +枓 +枕 +枖 +林 +枘 +枚 +枛 +果 +枝 +枞 +枠 +枡 +枢 +枣 +枥 +枧 +枨 +枪 +枫 +枭 +枯 +枰 +枱 +枲 +枳 +枴 +枵 +架 +枷 +枸 +枹 +枺 +枻 +枼 +柀 +柁 +柂 +柃 +柄 +柅 +柈 +柉 +柊 +柍 +柎 +柏 +某 +柑 +柒 +染 +柔 +柖 +柘 +柙 +柚 +柜 +柝 +柞 +柟 +柠 +柢 +柣 +柤 +查 +柧 +柩 +柫 +柬 +柮 +柯 +柰 +柱 +柲 +柳 +柴 +柵 +柶 +柷 +柸 +柹 +柺 +査 +柽 +柾 +柿 +栀 +栁 +栂 +栃 +栄 +栅 +标 +栈 +栉 +栊 +栋 +栌 +栎 +栏 +栐 +树 +栒 +栓 +栔 +栖 +栗 +栘 +栚 +栜 +栝 +栞 +栟 +校 +栢 +栨 +栩 +株 +栫 +栭 +栯 +栰 +栱 +栲 +栳 +栴 +栵 +样 +核 +根 +栻 +格 +栽 +栾 +桀 +桁 +桂 +桃 +桄 +桅 +框 +案 +桉 +桊 +桋 +桌 +桎 +桏 +桐 +桑 +桒 +桓 +桔 +桕 +桖 +桙 +桜 +桝 +桟 +桠 +桡 +桢 +档 +桤 +桥 +桦 +桧 +桨 +桩 +桫 +桭 +桮 +桯 +桱 +桲 +桴 +桵 +桶 +桷 +桹 +桺 +桼 +桾 +桿 +梀 +梁 +梂 +梃 +梅 +梆 +梇 +梉 +梌 +梏 +梐 +梒 +梓 +梔 +梗 +梘 +梛 +梜 +條 +梟 +梠 +梡 +梢 +梣 +梦 +梧 +梨 +梩 +梫 +梬 +梭 +梮 +梯 +械 +梱 +梲 +梳 +梴 +梵 +梶 +梼 +梽 +梾 +梿 +检 +棁 +棂 +棃 +棄 +棆 +棉 +棊 +棋 +棌 +棍 +棐 +棒 +棓 +棕 +棖 +棗 +棘 +棚 +棛 +棜 +棟 +棠 +棡 +棣 +棤 +棧 +棨 +棩 +棪 +棫 +棬 +森 +棯 +棰 +棱 +棲 +棳 +棵 +棶 +棷 +棸 +棹 +棺 +棻 +棼 +棽 +椀 +椁 +椄 +椅 +椆 +椇 +椈 +椉 +椋 +椌 +植 +椎 +椏 +椐 +椑 +椒 +椓 +椔 +椗 +椙 +検 +椟 +椠 +椤 +椥 +椪 +椭 +椰 +椲 +椳 +椴 +椵 +椶 +椸 +椹 +椽 +椿 +楀 +楂 +楄 +楅 +楈 +楉 +楊 +楎 +楒 +楓 +楔 +楕 +楖 +楗 +楘 +楙 +楚 +楛 +楝 +楞 +楟 +楠 +楡 +楢 +楣 +楤 +楥 +楦 +楧 +楨 +楩 +楪 +楫 +楬 +業 +楮 +楯 +楰 +楱 +楳 +極 +楶 +楷 +楸 +楹 +楺 +楼 +楽 +楿 +概 +榃 +榄 +榅 +榆 +榇 +榈 +榉 +榊 +榍 +榎 +榑 +榔 +榕 +榖 +榗 +榘 +榙 +榛 +榜 +榞 +榠 +榢 +榣 +榤 +榦 +榧 +榨 +榩 +榪 +榫 +榬 +榭 +榮 +榯 +榰 +榱 +榲 +榴 +榶 +榷 +榹 +榻 +榼 +榽 +榾 +榿 +槀 +槁 +槃 +槄 +槇 +槉 +槊 +構 +槌 +槍 +槎 +槐 +槑 +槓 +槔 +槕 +槗 +様 +槙 +槚 +槛 +槜 +槟 +槠 +槢 +槤 +槥 +槧 +槨 +槩 +槬 +槭 +槮 +槰 +槱 +槲 +槳 +槷 +槸 +槺 +槻 +槼 +槽 +槾 +槿 +樀 +樁 +樂 +樅 +樆 +樊 +樋 +樑 +樓 +樔 +樕 +樗 +樘 +標 +樛 +樝 +樞 +樟 +樠 +模 +樣 +樧 +樨 +権 +横 +樫 +樯 +樱 +樲 +樴 +樵 +樸 +樹 +樺 +樻 +樽 +樾 +樿 +橀 +橁 +橄 +橇 +橈 +橉 +橋 +橐 +橑 +橒 +橓 +橘 +橙 +橚 +橛 +橜 +橝 +橞 +機 +橡 +橢 +橤 +橥 +橦 +橧 +橨 +橪 +橫 +橭 +橱 +橶 +橹 +橼 +橾 +橿 +檀 +檁 +檃 +檄 +檇 +檉 +檊 +檍 +檎 +檐 +檑 +檓 +檔 +檕 +檖 +檗 +檛 +檜 +檝 +檞 +檟 +檠 +檡 +檢 +檣 +檥 +檦 +檨 +檩 +檫 +檬 +檮 +檯 +檳 +檵 +檸 +檹 +檻 +檽 +櫂 +櫃 +櫅 +櫆 +櫈 +櫋 +櫌 +櫐 +櫑 +櫓 +櫙 +櫚 +櫛 +櫜 +櫝 +櫞 +櫟 +櫠 +櫡 +櫥 +櫧 +櫨 +櫪 +櫫 +櫬 +櫰 +櫱 +櫳 +櫸 +櫹 +櫺 +櫻 +櫼 +櫾 +櫿 +欂 +欃 +欄 +欅 +欈 +欉 +權 +欋 +欏 +欐 +欑 +欒 +欖 +欘 +欞 +欠 +次 +欢 +欣 +欤 +欥 +欧 +欨 +欬 +欭 +欯 +欱 +欲 +欳 +欴 +欵 +欶 +欷 +欸 +欹 +欺 +欻 +欽 +款 +欿 +歂 +歃 +歅 +歆 +歇 +歈 +歉 +歊 +歋 +歌 +歍 +歎 +歐 +歓 +歔 +歕 +歙 +歛 +歜 +歟 +歠 +歡 +止 +正 +此 +步 +武 +歧 +歩 +歪 +歭 +歯 +歲 +歳 +歴 +歶 +歷 +歸 +歹 +死 +歼 +歾 +歿 +殀 +殁 +殂 +殃 +殄 +殆 +殇 +殈 +殉 +殊 +残 +殍 +殏 +殑 +殒 +殓 +殔 +殖 +殗 +殘 +殙 +殚 +殛 +殞 +殟 +殠 +殡 +殢 +殣 +殤 +殥 +殦 +殧 +殪 +殫 +殭 +殮 +殯 +殰 +殲 +殳 +殴 +段 +殶 +殷 +殺 +殻 +殼 +殽 +殿 +毀 +毁 +毂 +毃 +毄 +毅 +毆 +毇 +毈 +毉 +毊 +毋 +毌 +母 +毎 +每 +毐 +毒 +毓 +比 +毕 +毖 +毗 +毘 +毙 +毚 +毛 +毞 +毡 +毣 +毤 +毦 +毨 +毪 +毫 +毬 +毯 +毰 +毲 +毳 +毴 +毵 +毸 +毹 +毻 +毼 +毽 +毾 +毿 +氀 +氁 +氂 +氄 +氅 +氆 +氇 +氈 +氊 +氌 +氍 +氏 +氐 +民 +氓 +气 +氕 +氖 +気 +氘 +氙 +氚 +氛 +氟 +氠 +氡 +氢 +氣 +氤 +氦 +氧 +氨 +氩 +氪 +氫 +氬 +氮 +氯 +氰 +氲 +氳 +水 +氶 +氷 +永 +氹 +氻 +氽 +氾 +氿 +汀 +汁 +求 +汃 +汆 +汇 +汈 +汉 +汊 +汋 +汍 +汎 +汏 +汐 +汒 +汔 +汕 +汗 +汙 +汚 +汛 +汜 +汝 +汞 +江 +池 +污 +汤 +汦 +汧 +汨 +汩 +汪 +汫 +汭 +汯 +汰 +汱 +汲 +汳 +汴 +汶 +汸 +汹 +決 +汽 +汾 +沁 +沂 +沃 +沄 +沅 +沆 +沇 +沈 +沉 +沋 +沌 +沍 +沏 +沐 +沒 +沓 +沔 +沕 +沖 +沘 +沙 +沚 +沛 +沜 +沟 +没 +沢 +沣 +沤 +沥 +沦 +沧 +沨 +沩 +沪 +沫 +沬 +沭 +沮 +沯 +沱 +河 +沴 +沶 +沷 +沸 +油 +沺 +治 +沼 +沽 +沾 +沿 +況 +泂 +泃 +泄 +泅 +泆 +泇 +泉 +泊 +泌 +泐 +泒 +泓 +泔 +法 +泖 +泗 +泙 +泚 +泛 +泜 +泝 +泞 +泠 +泡 +波 +泣 +泥 +泧 +注 +泩 +泪 +泫 +泬 +泭 +泮 +泯 +泰 +泱 +泲 +泳 +泵 +泷 +泸 +泺 +泻 +泼 +泽 +泾 +洀 +洁 +洄 +洇 +洈 +洊 +洋 +洌 +洍 +洎 +洑 +洒 +洓 +洗 +洘 +洙 +洚 +洛 +洞 +洟 +洢 +洣 +洤 +津 +洧 +洨 +洩 +洪 +洫 +洭 +洮 +洱 +洲 +洳 +洴 +洵 +洶 +洸 +洹 +洺 +活 +洼 +洽 +派 +洿 +流 +浃 +浄 +浅 +浆 +浇 +浈 +浉 +浊 +测 +浍 +济 +浏 +浐 +浑 +浒 +浓 +浔 +浕 +浘 +浙 +浚 +浛 +浜 +浞 +浟 +浠 +浡 +浢 +浣 +浤 +浥 +浦 +浧 +浩 +浪 +浬 +浭 +浮 +浯 +浰 +浲 +浴 +浵 +浶 +海 +浸 +浹 +浺 +浼 +浿 +涂 +涄 +涅 +涆 +涇 +消 +涉 +涊 +涌 +涍 +涎 +涐 +涑 +涒 +涓 +涔 +涕 +涖 +涗 +涘 +涙 +涛 +涝 +涞 +涟 +涠 +涡 +涢 +涣 +涤 +润 +涧 +涨 +涩 +涪 +涫 +涬 +涮 +涯 +液 +涳 +涴 +涵 +涷 +涸 +涼 +涽 +涾 +涿 +淀 +淄 +淅 +淆 +淇 +淈 +淊 +淋 +淌 +淍 +淎 +淏 +淑 +淒 +淓 +淔 +淖 +淘 +淙 +淚 +淛 +淜 +淝 +淞 +淟 +淠 +淡 +淢 +淤 +淥 +淦 +淨 +淩 +淪 +淫 +淬 +淭 +淮 +淯 +淰 +深 +淳 +淴 +淵 +淶 +混 +淸 +淹 +淺 +添 +淼 +淽 +渀 +渃 +清 +渇 +済 +渉 +渊 +渋 +渌 +渍 +渎 +渐 +渑 +渓 +渔 +渕 +渗 +渙 +渚 +減 +渜 +渝 +渟 +渠 +渡 +渢 +渣 +渤 +渥 +渦 +渨 +温 +渫 +測 +渭 +渮 +港 +渰 +渱 +渲 +渳 +渴 +游 +渹 +渺 +渻 +渼 +渽 +渾 +渿 +湀 +湁 +湃 +湄 +湅 +湆 +湇 +湉 +湊 +湋 +湍 +湎 +湑 +湓 +湔 +湖 +湘 +湛 +湜 +湝 +湞 +湟 +湠 +湡 +湢 +湣 +湥 +湧 +湨 +湩 +湫 +湮 +湯 +湱 +湲 +湳 +湴 +湻 +湼 +湾 +湿 +満 +溁 +溃 +溅 +溆 +溇 +溈 +溉 +溋 +溍 +溎 +溏 +源 +溓 +溔 +準 +溘 +溙 +溚 +溛 +溜 +溝 +溞 +溟 +溠 +溢 +溥 +溦 +溧 +溪 +溫 +溮 +溯 +溰 +溱 +溲 +溳 +溴 +溵 +溶 +溷 +溹 +溺 +溻 +溼 +溽 +溾 +滀 +滁 +滂 +滃 +滄 +滅 +滆 +滇 +滈 +滉 +滋 +滌 +滍 +滎 +滏 +滑 +滒 +滓 +滔 +滕 +滖 +滗 +滘 +滙 +滚 +滛 +滜 +滝 +滞 +滟 +滠 +满 +滢 +滤 +滥 +滦 +滧 +滨 +滩 +滪 +滫 +滬 +滭 +滮 +滯 +滱 +滲 +滴 +滵 +滷 +滸 +滹 +滻 +滽 +滾 +滿 +漁 +漂 +漃 +漅 +漆 +漇 +漈 +漉 +漊 +漋 +漎 +漏 +漑 +漒 +漓 +演 +漕 +漖 +漘 +漙 +漚 +漠 +漢 +漣 +漤 +漥 +漦 +漧 +漩 +漪 +漫 +漬 +漭 +漮 +漯 +漰 +漱 +漲 +漳 +漴 +漵 +漶 +漷 +漸 +漹 +漻 +漼 +漾 +漿 +潀 +潁 +潃 +潄 +潅 +潆 +潇 +潋 +潍 +潎 +潏 +潐 +潑 +潒 +潔 +潕 +潖 +潗 +潘 +潙 +潚 +潛 +潜 +潝 +潞 +潟 +潠 +潢 +潤 +潦 +潩 +潬 +潭 +潮 +潯 +潰 +潲 +潳 +潴 +潵 +潶 +潷 +潸 +潺 +潼 +潽 +潾 +潿 +澀 +澁 +澂 +澄 +澅 +澆 +澇 +澈 +澉 +澋 +澌 +澍 +澎 +澐 +澒 +澓 +澔 +澖 +澗 +澛 +澜 +澞 +澠 +澡 +澣 +澤 +澥 +澦 +澧 +澨 +澩 +澪 +澫 +澭 +澮 +澯 +澱 +澳 +澴 +澶 +澹 +澻 +澼 +澽 +激 +濁 +濂 +濃 +濄 +濆 +濇 +濈 +濉 +濊 +濋 +濌 +濎 +濑 +濒 +濔 +濕 +濘 +濙 +濛 +濜 +濞 +濟 +濠 +濡 +濣 +濤 +濦 +濧 +濩 +濫 +濬 +濭 +濮 +濯 +濰 +濱 +濲 +濴 +濶 +濺 +濻 +濼 +濾 +濿 +瀀 +瀁 +瀄 +瀅 +瀆 +瀇 +瀉 +瀋 +瀌 +瀍 +瀎 +瀏 +瀑 +瀔 +瀕 +瀖 +瀘 +瀙 +瀚 +瀛 +瀝 +瀞 +瀟 +瀠 +瀡 +瀢 +瀣 +瀦 +瀧 +瀨 +瀩 +瀫 +瀬 +瀯 +瀰 +瀱 +瀲 +瀴 +瀵 +瀶 +瀷 +瀸 +瀹 +瀺 +瀼 +瀾 +瀿 +灂 +灃 +灄 +灈 +灉 +灊 +灋 +灌 +灏 +灑 +灕 +灖 +灗 +灘 +灛 +灝 +灞 +灟 +灡 +灣 +灤 +灧 +灨 +灩 +灪 +火 +灭 +灯 +灰 +灴 +灵 +灶 +灸 +灼 +災 +灾 +灿 +炀 +炁 +炅 +炆 +炉 +炊 +炌 +炎 +炒 +炔 +炕 +炖 +炘 +炙 +炜 +炝 +炟 +炣 +炤 +炫 +炬 +炭 +炮 +炯 +炰 +炱 +炲 +炳 +炷 +炸 +点 +為 +炻 +炼 +炽 +炾 +烀 +烁 +烂 +烃 +烈 +烉 +烊 +烋 +烏 +烑 +烒 +烓 +烔 +烖 +烘 +烙 +烛 +烜 +烝 +烟 +烠 +烤 +烦 +烧 +烨 +烩 +烫 +烬 +热 +烯 +烰 +烱 +烴 +烶 +烷 +烹 +烺 +烻 +烽 +焃 +焄 +焆 +焉 +焊 +焌 +焍 +焐 +焓 +焔 +焕 +焖 +焗 +焘 +焙 +焚 +焜 +焞 +焠 +無 +焣 +焦 +焮 +焯 +焰 +焱 +焴 +然 +焻 +焼 +焿 +煁 +煃 +煅 +煆 +煇 +煉 +煊 +煋 +煌 +煎 +煐 +煑 +煒 +煓 +煔 +煕 +煖 +煗 +煙 +煚 +煜 +煝 +煞 +煟 +煠 +煢 +煣 +煤 +煥 +煦 +照 +煨 +煩 +煬 +煮 +煲 +煳 +煴 +煵 +煶 +煸 +煺 +煽 +熀 +熂 +熄 +熅 +熇 +熈 +熉 +熊 +熏 +熐 +熒 +熔 +熖 +熗 +熘 +熙 +熚 +熛 +熜 +熟 +熠 +熤 +熥 +熨 +熬 +熯 +熰 +熱 +熲 +熳 +熵 +熸 +熹 +熺 +熻 +熼 +熾 +熿 +燀 +燁 +燂 +燃 +燄 +燅 +燈 +燉 +燊 +燋 +燎 +燏 +燐 +燒 +燔 +燕 +燖 +燗 +燘 +燙 +燚 +燜 +燝 +營 +燠 +燡 +燥 +燦 +燧 +燫 +燬 +燭 +燮 +燴 +燹 +燻 +燼 +燾 +燿 +爀 +爁 +爂 +爆 +爇 +爊 +爌 +爍 +爐 +爓 +爔 +爕 +爚 +爛 +爝 +爞 +爟 +爢 +爣 +爨 +爪 +爬 +爭 +爯 +爰 +爱 +爲 +爵 +父 +爷 +爸 +爹 +爺 +爻 +爼 +爽 +爾 +爿 +牀 +牁 +牂 +牄 +牆 +片 +版 +牉 +牋 +牌 +牍 +牎 +牏 +牒 +牓 +牕 +牖 +牘 +牙 +牚 +牛 +牝 +牟 +牠 +牡 +牢 +牣 +牤 +牥 +牦 +牧 +物 +牬 +牮 +牯 +牰 +牲 +牴 +牵 +牷 +牸 +特 +牺 +牻 +牼 +牽 +牾 +牿 +犀 +犁 +犂 +犄 +犅 +犆 +犇 +犈 +犉 +犊 +犋 +犌 +犍 +犎 +犏 +犐 +犑 +犒 +犓 +犕 +犖 +犗 +犘 +犚 +犛 +犝 +犞 +犟 +犠 +犢 +犣 +犤 +犥 +犦 +犧 +犨 +犩 +犪 +犬 +犮 +犯 +犰 +犴 +犵 +状 +犷 +犸 +犹 +犽 +犿 +狀 +狁 +狂 +狃 +狄 +狆 +狈 +狉 +狊 +狋 +狌 +狍 +狎 +狐 +狑 +狒 +狓 +狔 +狖 +狗 +狘 +狙 +狚 +狛 +狜 +狝 +狞 +狟 +狠 +狡 +狣 +狤 +狥 +狦 +狨 +狩 +狪 +狫 +独 +狭 +狮 +狯 +狰 +狱 +狲 +狳 +狴 +狶 +狷 +狸 +狹 +狺 +狻 +狼 +狽 +狾 +狿 +猀 +猁 +猂 +猃 +猄 +猇 +猈 +猊 +猋 +猎 +猏 +猑 +猒 +猓 +猕 +猖 +猗 +猘 +猙 +猛 +猜 +猝 +猞 +猟 +猡 +猢 +猣 +猥 +猦 +猧 +猨 +猩 +猪 +猫 +猬 +猭 +献 +猯 +猰 +猱 +猲 +猳 +猴 +猵 +猶 +猷 +猹 +猺 +猻 +猼 +猾 +猿 +獀 +獁 +獂 +獃 +獄 +獅 +獇 +獊 +獋 +獌 +獍 +獎 +獏 +獐 +獑 +獒 +獗 +獘 +獛 +獝 +獞 +獟 +獠 +獡 +獢 +獣 +獥 +獦 +獧 +獨 +獩 +獪 +獫 +獬 +獭 +獮 +獯 +獰 +獲 +獳 +獴 +獵 +獶 +獷 +獸 +獺 +獻 +獼 +獽 +獾 +獿 +玀 +玁 +玂 +玃 +玄 +玅 +玆 +率 +玈 +玉 +玊 +王 +玎 +玏 +玑 +玒 +玓 +玔 +玕 +玖 +玗 +玘 +玙 +玚 +玛 +玞 +玟 +玠 +玡 +玢 +玤 +玥 +玦 +玧 +玨 +玩 +玫 +玭 +玮 +环 +现 +玱 +玲 +玳 +玶 +玷 +玹 +玺 +玻 +玼 +玾 +玿 +珀 +珂 +珅 +珇 +珈 +珉 +珊 +珋 +珌 +珍 +珎 +珏 +珐 +珑 +珒 +珓 +珕 +珖 +珙 +珛 +珝 +珞 +珠 +珡 +珢 +珣 +珤 +珥 +珦 +珧 +珩 +珪 +珫 +班 +珮 +珰 +珲 +珵 +珶 +珷 +珸 +珹 +珺 +珽 +現 +琀 +琁 +球 +琄 +琅 +理 +琇 +琈 +琉 +琊 +琍 +琎 +琏 +琐 +琔 +琖 +琚 +琛 +琟 +琡 +琢 +琤 +琥 +琦 +琨 +琪 +琫 +琬 +琭 +琮 +琯 +琰 +琱 +琲 +琳 +琴 +琵 +琶 +琹 +琺 +琼 +琿 +瑀 +瑁 +瑂 +瑃 +瑄 +瑅 +瑆 +瑇 +瑈 +瑊 +瑋 +瑑 +瑒 +瑓 +瑔 +瑕 +瑖 +瑗 +瑙 +瑚 +瑛 +瑜 +瑝 +瑞 +瑟 +瑠 +瑢 +瑣 +瑤 +瑥 +瑧 +瑨 +瑩 +瑪 +瑬 +瑭 +瑯 +瑰 +瑱 +瑲 +瑳 +瑴 +瑵 +瑶 +瑷 +瑺 +瑽 +瑾 +瑿 +璀 +璁 +璃 +璄 +璅 +璆 +璇 +璈 +璉 +璊 +璋 +璌 +璎 +璐 +璒 +璕 +璗 +璘 +璙 +璚 +璜 +璞 +璟 +璠 +璡 +璢 +璣 +璥 +璦 +璧 +璨 +璩 +璪 +璫 +璬 +璮 +環 +璱 +璲 +璵 +璸 +璹 +璺 +璽 +璿 +瓀 +瓁 +瓅 +瓈 +瓊 +瓋 +瓌 +瓏 +瓑 +瓒 +瓔 +瓖 +瓘 +瓚 +瓛 +瓜 +瓝 +瓞 +瓟 +瓠 +瓡 +瓢 +瓣 +瓤 +瓥 +瓦 +瓨 +瓩 +瓬 +瓮 +瓯 +瓴 +瓵 +瓶 +瓷 +瓻 +瓽 +瓾 +瓿 +甀 +甂 +甃 +甄 +甈 +甋 +甌 +甍 +甏 +甐 +甑 +甒 +甓 +甔 +甕 +甖 +甗 +甘 +甙 +甚 +甜 +甝 +甞 +生 +甡 +產 +産 +甥 +甦 +用 +甩 +甪 +甫 +甬 +甭 +甮 +甯 +田 +由 +甲 +申 +电 +男 +甸 +甹 +町 +画 +甽 +甾 +甿 +畀 +畅 +畇 +畈 +畊 +畋 +界 +畎 +畏 +畐 +畑 +畔 +畖 +留 +畚 +畛 +畜 +畝 +畟 +畠 +畢 +畣 +畤 +略 +畦 +畧 +番 +畫 +畬 +畯 +異 +畱 +畲 +畳 +畴 +畵 +當 +畷 +畸 +畹 +畺 +畽 +畿 +疀 +疁 +疃 +疆 +疇 +疊 +疋 +疌 +疍 +疎 +疏 +疐 +疑 +疒 +疔 +疕 +疖 +疗 +疘 +疙 +疚 +疝 +疟 +疠 +疡 +疢 +疣 +疤 +疥 +疧 +疫 +疬 +疭 +疮 +疯 +疰 +疱 +疲 +疳 +疴 +疵 +疸 +疹 +疻 +疼 +疽 +疾 +疿 +痀 +痁 +痂 +痃 +痄 +病 +症 +痈 +痉 +痊 +痋 +痌 +痍 +痎 +痏 +痐 +痑 +痒 +痓 +痔 +痕 +痗 +痘 +痙 +痚 +痛 +痝 +痞 +痟 +痠 +痡 +痢 +痣 +痤 +痦 +痧 +痨 +痩 +痪 +痫 +痯 +痰 +痱 +痲 +痳 +痴 +痷 +痸 +痹 +痺 +痻 +痼 +痾 +痿 +瘀 +瘁 +瘃 +瘅 +瘆 +瘈 +瘉 +瘊 +瘋 +瘌 +瘍 +瘏 +瘐 +瘑 +瘓 +瘕 +瘖 +瘗 +瘘 +瘙 +瘛 +瘜 +瘝 +瘞 +瘟 +瘠 +瘡 +瘢 +瘣 +瘤 +瘥 +瘦 +瘧 +瘩 +瘪 +瘫 +瘭 +瘮 +瘯 +瘰 +瘱 +瘲 +瘳 +瘴 +瘵 +瘸 +瘺 +瘻 +瘼 +瘽 +瘾 +瘿 +癀 +療 +癃 +癄 +癅 +癆 +癇 +癈 +癉 +癌 +癍 +癐 +癒 +癓 +癔 +癖 +癗 +癘 +癙 +癜 +癞 +癟 +癠 +癡 +癢 +癣 +癤 +癥 +癧 +癩 +癪 +癫 +癬 +癭 +癮 +癯 +癰 +癱 +癲 +癵 +癸 +癹 +発 +登 +發 +白 +百 +癿 +皁 +皂 +的 +皆 +皇 +皈 +皊 +皋 +皎 +皏 +皐 +皑 +皓 +皕 +皖 +皙 +皚 +皛 +皜 +皝 +皞 +皤 +皦 +皫 +皭 +皮 +皯 +皰 +皱 +皲 +皴 +皵 +皷 +皸 +皺 +皻 +皽 +皾 +皿 +盂 +盃 +盄 +盅 +盆 +盇 +盈 +盉 +益 +盋 +盌 +盍 +盎 +盏 +盐 +监 +盒 +盓 +盔 +盖 +盗 +盘 +盛 +盜 +盝 +盞 +盟 +盡 +監 +盤 +盥 +盦 +盧 +盨 +盩 +盪 +盫 +盬 +盭 +目 +盯 +盱 +盲 +盳 +直 +盷 +相 +盹 +盺 +盻 +盼 +盾 +眀 +省 +眃 +眄 +眅 +眇 +眈 +眉 +眊 +看 +県 +眍 +眐 +眑 +眒 +眓 +眕 +眙 +眚 +眛 +眜 +眝 +眞 +真 +眠 +眢 +眣 +眥 +眦 +眨 +眩 +眬 +眭 +眯 +眱 +眲 +眳 +眴 +眵 +眶 +眷 +眸 +眹 +眺 +眼 +眽 +眾 +着 +睁 +睃 +睄 +睅 +睆 +睇 +睊 +睋 +睌 +睍 +睎 +睏 +睐 +睑 +睒 +睔 +睕 +睖 +睚 +睛 +睜 +睞 +睟 +睠 +睡 +睢 +督 +睥 +睦 +睧 +睨 +睩 +睪 +睫 +睬 +睭 +睮 +睯 +睹 +睺 +睼 +睽 +睾 +睿 +瞀 +瞁 +瞂 +瞄 +瞅 +瞇 +瞉 +瞋 +瞌 +瞍 +瞎 +瞏 +瞑 +瞒 +瞓 +瞖 +瞘 +瞚 +瞛 +瞜 +瞝 +瞞 +瞟 +瞠 +瞡 +瞢 +瞥 +瞧 +瞩 +瞪 +瞫 +瞬 +瞭 +瞰 +瞲 +瞳 +瞴 +瞵 +瞶 +瞷 +瞻 +瞼 +瞽 +瞿 +矁 +矂 +矇 +矉 +矊 +矌 +矍 +矎 +矏 +矐 +矓 +矔 +矕 +矗 +矘 +矙 +矚 +矛 +矜 +矞 +矠 +矢 +矣 +知 +矧 +矩 +矫 +矬 +短 +矮 +矯 +矰 +矱 +矲 +石 +矴 +矶 +矸 +矹 +矺 +矻 +矼 +矽 +矾 +矿 +砀 +码 +砂 +砄 +砅 +砆 +砉 +砋 +砌 +砍 +砎 +砏 +砐 +砑 +砒 +研 +砕 +砖 +砗 +砘 +砚 +砜 +砝 +砟 +砠 +砢 +砣 +砥 +砦 +砧 +砨 +砩 +砫 +砬 +砭 +砮 +砯 +砰 +砲 +砳 +破 +砵 +砷 +砸 +砹 +砺 +砻 +砼 +砾 +础 +硁 +硂 +硃 +硅 +硇 +硈 +硉 +硊 +硌 +硍 +硎 +硏 +硐 +硒 +硓 +硔 +硕 +硖 +硗 +硙 +硚 +硜 +硝 +硠 +硤 +硥 +硨 +硩 +硪 +硫 +硬 +硭 +确 +硯 +硰 +硱 +硼 +硾 +硿 +碁 +碃 +碄 +碅 +碆 +碇 +碈 +碉 +碌 +碍 +碎 +碏 +碑 +碓 +碔 +碕 +碗 +碘 +碚 +碛 +碜 +碞 +碟 +碡 +碣 +碥 +碧 +碨 +碩 +碪 +碫 +碭 +碰 +碱 +碲 +碳 +碴 +碶 +碸 +碹 +確 +碻 +碼 +碽 +碾 +磁 +磃 +磅 +磈 +磉 +磊 +磋 +磌 +磍 +磎 +磏 +磐 +磑 +磔 +磕 +磘 +磙 +磚 +磛 +磜 +磝 +磞 +磟 +磡 +磣 +磥 +磧 +磨 +磩 +磪 +磬 +磯 +磱 +磲 +磳 +磴 +磵 +磷 +磹 +磺 +磻 +磼 +磽 +磾 +磿 +礁 +礂 +礄 +礅 +礉 +礌 +礎 +礐 +礑 +礒 +礓 +礔 +礙 +礛 +礜 +礝 +礞 +礠 +礣 +礥 +礦 +礧 +礨 +礪 +礫 +礬 +礭 +礮 +礱 +礴 +礵 +示 +礼 +礽 +社 +礿 +祀 +祁 +祂 +祃 +祅 +祆 +祇 +祈 +祉 +祊 +祋 +祌 +祎 +祏 +祐 +祑 +祒 +祓 +祔 +祕 +祖 +祗 +祙 +祚 +祛 +祜 +祝 +神 +祟 +祠 +祡 +祢 +祣 +祤 +祥 +祧 +票 +祩 +祪 +祫 +祭 +祯 +祲 +祳 +祴 +祷 +祸 +祹 +祺 +祼 +祾 +祿 +禀 +禁 +禂 +禃 +禄 +禅 +禇 +禊 +禋 +禍 +禎 +福 +禑 +禒 +禓 +禔 +禕 +禖 +禗 +禘 +禚 +禛 +禜 +禟 +禠 +禡 +禤 +禦 +禧 +禨 +禩 +禪 +禫 +禬 +禭 +禮 +禰 +禱 +禳 +禴 +禵 +禷 +禸 +禹 +禺 +离 +禼 +禽 +禾 +禿 +秀 +私 +秃 +秅 +秆 +秈 +秉 +秊 +秋 +种 +秎 +秏 +科 +秒 +秔 +秕 +秖 +秘 +租 +秠 +秣 +秤 +秦 +秧 +秩 +秪 +秫 +秬 +秭 +秮 +积 +称 +秳 +秶 +秸 +秺 +移 +秽 +秾 +稀 +稂 +稃 +稅 +稆 +稈 +稉 +稊 +程 +稌 +稍 +税 +稑 +稒 +稔 +稗 +稘 +稙 +稚 +稜 +稞 +稟 +稠 +稣 +稫 +稬 +稭 +種 +稯 +稰 +稱 +稲 +稳 +稷 +稹 +稺 +稻 +稼 +稽 +稾 +稿 +穀 +穂 +穄 +穅 +穆 +穇 +穈 +穉 +穊 +穋 +穌 +積 +穎 +穏 +穑 +穖 +穗 +穙 +穛 +穜 +穟 +穠 +穡 +穢 +穣 +穤 +穧 +穨 +穩 +穫 +穬 +穭 +穮 +穰 +穱 +穴 +穵 +究 +穷 +穸 +穹 +空 +穽 +穾 +穿 +窀 +突 +窃 +窄 +窅 +窆 +窈 +窉 +窊 +窋 +窌 +窍 +窎 +窏 +窐 +窑 +窒 +窓 +窔 +窕 +窖 +窗 +窘 +窙 +窜 +窝 +窞 +窟 +窠 +窢 +窣 +窥 +窦 +窨 +窩 +窪 +窫 +窬 +窭 +窮 +窯 +窰 +窱 +窲 +窳 +窴 +窵 +窶 +窸 +窺 +窻 +窾 +窿 +竀 +竁 +竃 +竄 +竅 +竇 +竈 +竊 +立 +竑 +竖 +竘 +站 +竚 +竜 +竝 +竞 +竟 +章 +竢 +竣 +童 +竦 +竩 +竪 +竫 +竭 +端 +竴 +競 +竷 +竹 +竺 +竻 +竽 +竿 +笃 +笄 +笅 +笆 +笈 +笊 +笋 +笏 +笐 +笑 +笓 +笔 +笕 +笘 +笙 +笛 +笝 +笞 +笠 +笢 +笤 +笥 +符 +笨 +笩 +笪 +笫 +第 +笭 +笮 +笯 +笰 +笱 +笲 +笳 +笴 +笵 +笸 +笹 +笺 +笻 +笼 +笾 +筀 +筄 +筅 +筆 +筇 +筈 +等 +筊 +筋 +筌 +筍 +筏 +筐 +筑 +筒 +答 +策 +筘 +筚 +筛 +筜 +筝 +筞 +筠 +筡 +筢 +筤 +筥 +筦 +筧 +筩 +筬 +筭 +筮 +筯 +筰 +筱 +筲 +筳 +筴 +筵 +筶 +筷 +筹 +筻 +筼 +签 +简 +箄 +箅 +箆 +箇 +箈 +箊 +箋 +箌 +箍 +箎 +箏 +箐 +箑 +箒 +箓 +箔 +箕 +箖 +算 +箘 +箙 +箛 +箜 +箝 +箠 +管 +箢 +箦 +箧 +箨 +箩 +箪 +箫 +箬 +箭 +箯 +箱 +箴 +箷 +箸 +箹 +箾 +節 +篁 +範 +篆 +篇 +築 +篊 +篋 +篌 +篎 +篑 +篓 +篔 +篕 +篙 +篚 +篛 +篝 +篞 +篠 +篡 +篣 +篤 +篥 +篦 +篧 +篨 +篩 +篪 +篭 +篮 +篯 +篱 +篲 +篳 +篴 +篷 +篸 +篹 +篻 +篼 +篽 +篾 +篿 +簀 +簁 +簂 +簃 +簅 +簇 +簉 +簋 +簌 +簍 +簎 +簏 +簑 +簒 +簕 +簖 +簗 +簙 +簜 +簝 +簞 +簟 +簠 +簡 +簢 +簣 +簥 +簦 +簧 +簨 +簩 +簪 +簫 +簬 +簭 +簮 +簰 +簳 +簷 +簸 +簹 +簺 +簻 +簼 +簽 +簾 +簿 +籀 +籁 +籃 +籈 +籉 +籊 +籌 +籍 +籐 +籔 +籗 +籙 +籛 +籜 +籝 +籟 +籠 +籣 +籤 +籥 +籦 +籧 +籩 +籪 +籬 +籮 +籯 +籲 +米 +籴 +籸 +籹 +籺 +类 +籼 +籽 +籾 +粂 +粃 +粄 +粇 +粉 +粊 +粋 +粍 +粑 +粒 +粔 +粕 +粗 +粘 +粛 +粜 +粝 +粞 +粟 +粢 +粤 +粥 +粦 +粧 +粩 +粪 +粮 +粱 +粲 +粳 +粵 +粹 +粺 +粻 +粼 +粽 +精 +粿 +糀 +糁 +糅 +糇 +糈 +糉 +糊 +糌 +糍 +糎 +糒 +糔 +糕 +糖 +糗 +糙 +糜 +糝 +糞 +糟 +糠 +糢 +糧 +糨 +糪 +糬 +糯 +糰 +糱 +糲 +糴 +糵 +糶 +糷 +糸 +糹 +糺 +系 +糾 +紀 +紂 +紃 +約 +紅 +紆 +紇 +紈 +紉 +紊 +紋 +納 +紎 +紐 +紑 +紒 +紓 +純 +紕 +紖 +紗 +紘 +紙 +級 +紛 +紜 +紝 +紞 +紟 +素 +紡 +索 +紥 +紧 +紩 +紫 +紬 +紮 +累 +細 +紱 +紲 +紳 +紵 +紶 +紸 +紹 +紺 +紼 +紽 +紾 +紿 +絀 +絁 +終 +絃 +組 +絅 +絆 +絇 +経 +絎 +絏 +結 +絓 +絕 +絖 +絘 +絛 +絜 +絝 +絞 +絡 +絢 +絣 +給 +絧 +絨 +絪 +絫 +絭 +絮 +絯 +絰 +統 +絲 +絳 +絵 +絶 +絷 +絹 +絺 +絻 +絼 +絽 +絿 +綀 +綁 +綃 +綄 +綅 +綆 +綈 +綉 +綌 +綍 +綎 +綏 +綑 +經 +綔 +綖 +継 +続 +綜 +綝 +綞 +綟 +綠 +綢 +綣 +綦 +綧 +綪 +綫 +綬 +維 +綮 +綯 +綰 +綱 +網 +綳 +綴 +綵 +綷 +綸 +綹 +綺 +綻 +綼 +綽 +綾 +綿 +緀 +緁 +緂 +緃 +緄 +緅 +緆 +緇 +緈 +緊 +緋 +緌 +緎 +総 +緐 +緑 +緒 +緔 +緖 +緗 +緘 +緙 +線 +緛 +緜 +緝 +緞 +締 +緡 +緣 +緤 +緥 +緦 +緧 +編 +緩 +緪 +緬 +緯 +緰 +緱 +緲 +緳 +練 +緵 +緶 +緷 +緹 +緺 +緻 +縁 +縂 +縃 +縄 +縅 +縈 +縉 +縊 +縋 +縌 +縎 +縏 +縐 +縑 +縒 +縓 +縕 +縗 +縚 +縛 +縜 +縝 +縞 +縟 +縠 +縡 +縢 +縣 +縤 +縦 +縧 +縩 +縪 +縫 +縭 +縮 +縯 +縰 +縱 +縲 +縳 +縴 +縵 +縶 +縷 +縸 +縹 +縻 +縼 +總 +績 +縿 +繀 +繁 +繂 +繃 +繄 +繅 +繆 +繇 +繈 +繉 +繊 +繋 +繍 +繐 +繑 +繒 +織 +繕 +繖 +繘 +繙 +繚 +繜 +繝 +繞 +繟 +繠 +繡 +繢 +繣 +繦 +繧 +繩 +繪 +繫 +繭 +繮 +繯 +繰 +繲 +繳 +繴 +繵 +繶 +繷 +繸 +繹 +繺 +繻 +繼 +繽 +繾 +纀 +纁 +纂 +纆 +纇 +纈 +纊 +纋 +續 +纍 +纏 +纐 +纑 +纒 +纓 +纔 +纕 +纖 +纗 +纘 +纚 +纛 +纜 +纠 +纡 +红 +纣 +纤 +纥 +约 +级 +纨 +纩 +纪 +纫 +纬 +纭 +纮 +纯 +纰 +纱 +纲 +纳 +纴 +纵 +纶 +纷 +纸 +纹 +纺 +纻 +纼 +纽 +纾 +线 +绀 +绁 +绂 +练 +组 +绅 +细 +织 +终 +绉 +绊 +绋 +绌 +绍 +绎 +经 +绐 +绑 +绒 +结 +绔 +绕 +绖 +绗 +绘 +给 +绚 +绛 +络 +绝 +绞 +统 +绠 +绡 +绢 +绣 +绤 +绥 +绦 +继 +绨 +绩 +绪 +绫 +续 +绮 +绯 +绰 +绱 +绲 +绳 +维 +绵 +绶 +绷 +绸 +绹 +绺 +绻 +综 +绽 +绾 +绿 +缀 +缁 +缂 +缃 +缄 +缅 +缆 +缇 +缈 +缉 +缊 +缌 +缎 +缐 +缑 +缒 +缓 +缔 +缕 +编 +缗 +缘 +缙 +缚 +缛 +缜 +缝 +缞 +缟 +缠 +缡 +缢 +缣 +缤 +缥 +缦 +缧 +缨 +缩 +缪 +缫 +缬 +缭 +缮 +缯 +缰 +缱 +缲 +缳 +缴 +缵 +缶 +缸 +缹 +缺 +缽 +缾 +缿 +罂 +罃 +罄 +罅 +罇 +罈 +罉 +罊 +罋 +罌 +罍 +罎 +罏 +罐 +网 +罔 +罕 +罗 +罘 +罚 +罛 +罜 +罝 +罞 +罟 +罠 +罡 +罢 +罣 +罥 +罦 +罧 +罨 +罩 +罪 +罫 +罬 +罭 +置 +罰 +罱 +署 +罳 +罴 +罵 +罶 +罷 +罹 +罺 +罻 +罼 +罽 +罾 +罿 +羁 +羂 +羃 +羅 +羆 +羇 +羈 +羉 +羊 +羋 +羌 +羍 +美 +羑 +羒 +羓 +羔 +羕 +羖 +羗 +羙 +羚 +羛 +羜 +羝 +羞 +羟 +羠 +羡 +羢 +羣 +群 +羥 +羦 +羧 +羨 +義 +羬 +羭 +羯 +羰 +羱 +羲 +羳 +羴 +羵 +羶 +羷 +羸 +羹 +羺 +羼 +羽 +羾 +羿 +翀 +翁 +翂 +翃 +翄 +翅 +翇 +翈 +翊 +翋 +翌 +翍 +翎 +翏 +翐 +翑 +習 +翔 +翕 +翘 +翙 +翚 +翛 +翜 +翟 +翠 +翡 +翢 +翣 +翥 +翦 +翨 +翩 +翪 +翫 +翬 +翭 +翮 +翯 +翰 +翱 +翲 +翳 +翴 +翷 +翹 +翺 +翻 +翼 +翽 +翾 +翿 +耀 +老 +考 +耄 +者 +耆 +耇 +耋 +而 +耍 +耎 +耏 +耐 +耑 +耒 +耔 +耕 +耖 +耗 +耘 +耙 +耜 +耞 +耟 +耠 +耡 +耢 +耤 +耥 +耦 +耧 +耨 +耩 +耪 +耬 +耮 +耰 +耱 +耳 +耴 +耵 +耶 +耷 +耸 +耹 +耻 +耽 +耾 +耿 +聂 +聃 +聆 +聇 +聈 +聊 +聋 +职 +聍 +聏 +聑 +聒 +联 +聖 +聘 +聚 +聝 +聞 +聟 +聡 +聨 +聩 +聪 +聯 +聰 +聱 +聲 +聳 +聴 +聵 +聶 +職 +聹 +聽 +聾 +聿 +肂 +肃 +肄 +肅 +肆 +肇 +肉 +肋 +肌 +肎 +肏 +肐 +肒 +肓 +肕 +肖 +肘 +肙 +肚 +肛 +肜 +肝 +肟 +肠 +股 +肢 +肣 +肤 +肥 +肧 +肩 +肪 +肫 +肭 +肮 +肯 +肱 +育 +肴 +肵 +肷 +肸 +肹 +肺 +肼 +肽 +肾 +肿 +胀 +胁 +胂 +胃 +胄 +胅 +胆 +胇 +胈 +胉 +胊 +背 +胍 +胎 +胏 +胐 +胑 +胔 +胕 +胖 +胗 +胘 +胙 +胚 +胛 +胜 +胝 +胞 +胠 +胡 +胣 +胤 +胥 +胧 +胨 +胩 +胪 +胫 +胬 +胭 +胯 +胰 +胱 +胲 +胳 +胴 +胵 +胶 +胷 +胸 +胹 +胺 +胻 +胼 +能 +胾 +脀 +脁 +脂 +脃 +脅 +脆 +脇 +脈 +脉 +脊 +脍 +脎 +脏 +脐 +脑 +脒 +脓 +脔 +脕 +脖 +脗 +脘 +脙 +脚 +脛 +脞 +脟 +脡 +脣 +脤 +脥 +脧 +脩 +脫 +脬 +脭 +脯 +脰 +脱 +脲 +脳 +脶 +脷 +脸 +脹 +脽 +脾 +脿 +腃 +腄 +腆 +腇 +腈 +腊 +腋 +腌 +腍 +腎 +腏 +腐 +腑 +腒 +腓 +腔 +腕 +腖 +腘 +腙 +腚 +腛 +腜 +腞 +腠 +腢 +腤 +腥 +腦 +腧 +腨 +腩 +腫 +腭 +腮 +腯 +腰 +腱 +腲 +腳 +腴 +腶 +腷 +腸 +腹 +腺 +腻 +腼 +腽 +腾 +腿 +膀 +膂 +膃 +膆 +膇 +膈 +膉 +膊 +膋 +膌 +膍 +膏 +膑 +膕 +膘 +膙 +膚 +膛 +膜 +膝 +膞 +膟 +膠 +膢 +膣 +膥 +膦 +膨 +膩 +膫 +膬 +膮 +膰 +膱 +膲 +膳 +膴 +膵 +膷 +膹 +膺 +膻 +膽 +膾 +膿 +臀 +臂 +臃 +臄 +臅 +臆 +臇 +臈 +臉 +臊 +臋 +臌 +臍 +臏 +臐 +臑 +臒 +臓 +臕 +臗 +臘 +臙 +臚 +臛 +臜 +臝 +臞 +臟 +臠 +臡 +臢 +臣 +臥 +臦 +臧 +臨 +臩 +自 +臬 +臭 +臮 +臯 +臲 +至 +致 +臷 +臸 +臺 +臻 +臼 +臾 +臿 +舀 +舁 +舂 +舄 +舅 +舆 +與 +興 +舉 +舊 +舋 +舌 +舍 +舎 +舐 +舑 +舒 +舔 +舕 +舖 +舗 +舘 +舛 +舜 +舝 +舞 +舟 +舠 +舡 +舢 +舣 +舥 +舨 +舩 +航 +舫 +般 +舭 +舯 +舰 +舱 +舲 +舳 +舴 +舵 +舶 +舷 +舸 +船 +舺 +舻 +舼 +舽 +舾 +艀 +艂 +艄 +艅 +艇 +艉 +艋 +艎 +艏 +艐 +艑 +艒 +艓 +艔 +艕 +艘 +艙 +艚 +艛 +艟 +艤 +艦 +艨 +艫 +艮 +良 +艰 +艱 +色 +艳 +艴 +艵 +艶 +艷 +艸 +艹 +艺 +艼 +艽 +艾 +艿 +芀 +节 +芃 +芄 +芅 +芈 +芊 +芋 +芍 +芎 +芏 +芐 +芑 +芒 +芓 +芔 +芗 +芘 +芙 +芚 +芛 +芜 +芝 +芞 +芟 +芠 +芡 +芣 +芤 +芥 +芦 +芧 +芨 +芩 +芪 +芫 +芬 +芭 +芮 +芯 +芰 +花 +芲 +芳 +芴 +芵 +芷 +芸 +芹 +芺 +芻 +芼 +芽 +芾 +芿 +苀 +苁 +苄 +苅 +苇 +苈 +苉 +苊 +苋 +苌 +苍 +苎 +苏 +苑 +苒 +苓 +苔 +苕 +苖 +苗 +苘 +苙 +苛 +苜 +苝 +苞 +苟 +苠 +苡 +苣 +苤 +若 +苦 +苧 +苨 +苪 +苫 +苬 +苯 +英 +苲 +苳 +苴 +苵 +苶 +苷 +苹 +苺 +苻 +苼 +苾 +茀 +茁 +茂 +范 +茄 +茅 +茆 +茇 +茈 +茉 +茋 +茌 +茍 +茎 +茏 +茑 +茓 +茔 +茕 +茖 +茗 +茘 +茙 +茚 +茛 +茜 +茝 +茠 +茢 +茤 +茥 +茦 +茧 +茨 +茩 +茪 +茫 +茬 +茭 +茮 +茯 +茱 +茲 +茳 +茴 +茵 +茶 +茷 +茸 +茹 +茺 +茻 +茼 +茽 +茿 +荀 +荁 +荂 +荃 +荄 +荅 +荆 +荇 +草 +荊 +荍 +荎 +荏 +荐 +荑 +荒 +荓 +荔 +荖 +荘 +荙 +荚 +荛 +荜 +荞 +荟 +荠 +荡 +荣 +荤 +荥 +荦 +荧 +荨 +荩 +荪 +荫 +荬 +荭 +荮 +药 +荳 +荴 +荵 +荷 +荸 +荺 +荻 +荼 +荽 +荾 +莁 +莃 +莅 +莆 +莉 +莊 +莋 +莌 +莍 +莎 +莏 +莐 +莒 +莓 +莔 +莕 +莖 +莘 +莙 +莛 +莜 +莝 +莞 +莠 +莢 +莣 +莤 +莥 +莧 +莨 +莩 +莪 +莫 +莬 +莰 +莱 +莲 +莳 +莴 +莵 +莶 +获 +莸 +莹 +莺 +莼 +莽 +莿 +菀 +菁 +菂 +菅 +菆 +菇 +菈 +菉 +菊 +菋 +菌 +菍 +菎 +菏 +菑 +菓 +菔 +菕 +菖 +菘 +菙 +菜 +菝 +菞 +菟 +菠 +菡 +菣 +菤 +菥 +菧 +菨 +菩 +菪 +菫 +華 +菰 +菱 +菲 +菳 +菴 +菵 +菶 +菸 +菹 +菺 +菼 +菽 +菾 +菿 +萁 +萃 +萄 +萆 +萇 +萉 +萊 +萋 +萌 +萍 +萎 +萏 +萐 +萑 +萒 +萘 +萚 +萜 +萝 +萠 +萡 +萣 +萤 +营 +萦 +萧 +萨 +萩 +萬 +萭 +萯 +萰 +萱 +萲 +萳 +萴 +萵 +萷 +萸 +萹 +萺 +萼 +落 +萿 +葀 +葂 +葃 +葅 +葆 +葉 +葊 +葋 +葌 +葍 +葎 +葑 +葒 +葖 +著 +葙 +葚 +葛 +葜 +葝 +葞 +葟 +葡 +董 +葤 +葥 +葦 +葧 +葩 +葫 +葬 +葭 +葯 +葰 +葱 +葳 +葴 +葵 +葶 +葷 +葸 +葹 +葺 +葽 +蒂 +蒄 +蒆 +蒇 +蒈 +蒉 +蒋 +蒌 +蒍 +蒎 +蒏 +蒐 +蒓 +蒔 +蒗 +蒘 +蒙 +蒚 +蒛 +蒜 +蒝 +蒞 +蒟 +蒠 +蒡 +蒢 +蒤 +蒧 +蒨 +蒩 +蒪 +蒫 +蒬 +蒭 +蒮 +蒯 +蒱 +蒲 +蒴 +蒶 +蒸 +蒹 +蒺 +蒻 +蒼 +蒽 +蒾 +蒿 +蓀 +蓁 +蓂 +蓄 +蓆 +蓇 +蓉 +蓊 +蓋 +蓌 +蓍 +蓎 +蓏 +蓐 +蓑 +蓓 +蓖 +蓘 +蓚 +蓝 +蓟 +蓠 +蓢 +蓣 +蓥 +蓦 +蓧 +蓨 +蓩 +蓪 +蓫 +蓬 +蓭 +蓮 +蓯 +蓰 +蓱 +蓲 +蓳 +蓴 +蓶 +蓷 +蓹 +蓺 +蓻 +蓼 +蓽 +蓾 +蓿 +蔀 +蔂 +蔃 +蔄 +蔆 +蔇 +蔈 +蔉 +蔊 +蔌 +蔍 +蔎 +蔏 +蔑 +蔒 +蔓 +蔔 +蔕 +蔖 +蔗 +蔘 +蔚 +蔜 +蔝 +蔞 +蔟 +蔠 +蔡 +蔣 +蔤 +蔥 +蔦 +蔧 +蔨 +蔩 +蔪 +蔫 +蔬 +蔭 +蔮 +蔰 +蔱 +蔴 +蔵 +蔷 +蔸 +蔹 +蔺 +蔻 +蔼 +蔽 +蔾 +蔿 +蕀 +蕁 +蕃 +蕄 +蕅 +蕆 +蕇 +蕈 +蕉 +蕊 +蕋 +蕍 +蕎 +蕑 +蕒 +蕓 +蕕 +蕖 +蕗 +蕘 +蕙 +蕚 +蕛 +蕝 +蕞 +蕟 +蕠 +蕡 +蕢 +蕣 +蕤 +蕦 +蕧 +蕨 +蕩 +蕪 +蕫 +蕭 +蕮 +蕰 +蕱 +蕲 +蕴 +蕵 +蕷 +蕸 +蕹 +蕺 +蕻 +蕼 +蕾 +蕿 +薀 +薁 +薂 +薃 +薄 +薅 +薆 +薇 +薈 +薉 +薊 +薋 +薌 +薍 +薎 +薏 +薐 +薑 +薔 +薕 +薖 +薗 +薘 +薙 +薚 +薛 +薜 +薝 +薞 +薟 +薠 +薡 +薢 +薣 +薤 +薦 +薧 +薨 +薩 +薪 +薫 +薬 +薭 +薮 +薯 +薰 +薱 +薲 +薳 +薴 +薵 +薶 +薷 +薸 +薹 +薺 +薽 +薾 +薿 +藀 +藁 +藂 +藃 +藄 +藅 +藆 +藇 +藈 +藉 +藋 +藍 +藎 +藏 +藐 +藑 +藒 +藓 +藔 +藕 +藗 +藘 +藙 +藚 +藜 +藝 +藟 +藠 +藡 +藣 +藤 +藥 +藦 +藨 +藩 +藪 +藫 +藬 +藭 +藰 +藱 +藲 +藴 +藶 +藷 +藸 +藹 +藺 +藻 +藼 +藾 +藿 +蘀 +蘁 +蘂 +蘄 +蘅 +蘆 +蘇 +蘉 +蘊 +蘋 +蘌 +蘐 +蘑 +蘓 +蘖 +蘗 +蘘 +蘙 +蘚 +蘛 +蘜 +蘞 +蘟 +蘠 +蘡 +蘢 +蘣 +蘤 +蘥 +蘦 +蘧 +蘩 +蘪 +蘬 +蘭 +蘮 +蘱 +蘳 +蘴 +蘵 +蘶 +蘸 +蘹 +蘺 +蘻 +蘼 +蘾 +蘿 +虀 +虃 +虆 +虇 +虈 +虉 +虋 +虌 +虎 +虏 +虐 +虑 +虒 +虓 +虔 +處 +虖 +虙 +虚 +虛 +虜 +虞 +號 +虡 +虢 +虣 +虤 +虧 +虨 +虩 +虪 +虫 +虬 +虭 +虮 +虯 +虰 +虱 +虴 +虵 +虷 +虸 +虹 +虺 +虻 +虼 +虽 +虾 +虿 +蚀 +蚁 +蚂 +蚄 +蚅 +蚆 +蚇 +蚊 +蚋 +蚌 +蚍 +蚑 +蚓 +蚕 +蚖 +蚗 +蚘 +蚙 +蚚 +蚜 +蚝 +蚞 +蚡 +蚢 +蚣 +蚤 +蚥 +蚧 +蚨 +蚩 +蚪 +蚬 +蚯 +蚰 +蚱 +蚲 +蚳 +蚴 +蚵 +蚶 +蚷 +蚸 +蚹 +蚺 +蚻 +蚼 +蚿 +蛀 +蛁 +蛂 +蛃 +蛄 +蛅 +蛆 +蛇 +蛈 +蛉 +蛊 +蛋 +蛌 +蛍 +蛎 +蛏 +蛐 +蛑 +蛓 +蛔 +蛕 +蛖 +蛗 +蛘 +蛙 +蛚 +蛛 +蛜 +蛝 +蛞 +蛟 +蛢 +蛣 +蛤 +蛦 +蛩 +蛪 +蛫 +蛬 +蛭 +蛮 +蛯 +蛰 +蛱 +蛲 +蛳 +蛴 +蛵 +蛶 +蛷 +蛸 +蛹 +蛺 +蛻 +蛾 +蜀 +蜁 +蜂 +蜃 +蜄 +蜆 +蜇 +蜈 +蜉 +蜊 +蜋 +蜌 +蜍 +蜎 +蜐 +蜑 +蜒 +蜓 +蜕 +蜖 +蜗 +蜘 +蜙 +蜚 +蜛 +蜜 +蜞 +蜠 +蜡 +蜢 +蜣 +蜤 +蜥 +蜦 +蜧 +蜨 +蜩 +蜪 +蜬 +蜭 +蜮 +蜯 +蜰 +蜱 +蜲 +蜳 +蜴 +蜵 +蜷 +蜸 +蜺 +蜻 +蜼 +蜾 +蜿 +蝀 +蝁 +蝂 +蝃 +蝆 +蝇 +蝈 +蝉 +蝋 +蝌 +蝍 +蝎 +蝐 +蝑 +蝒 +蝓 +蝔 +蝕 +蝖 +蝗 +蝘 +蝙 +蝚 +蝛 +蝜 +蝝 +蝞 +蝟 +蝠 +蝡 +蝣 +蝤 +蝥 +蝦 +蝨 +蝪 +蝫 +蝬 +蝭 +蝮 +蝯 +蝰 +蝱 +蝲 +蝳 +蝴 +蝶 +蝷 +蝸 +蝹 +蝺 +蝻 +蝼 +蝽 +蝾 +蝿 +螁 +螂 +螃 +螄 +螅 +螇 +螈 +螋 +融 +螎 +螏 +螐 +螑 +螒 +螓 +螔 +螖 +螗 +螘 +螚 +螛 +螜 +螝 +螞 +螟 +螠 +螡 +螢 +螣 +螤 +螥 +螨 +螪 +螫 +螬 +螭 +螮 +螯 +螰 +螱 +螳 +螴 +螵 +螶 +螷 +螸 +螹 +螺 +螻 +螼 +螽 +螾 +螿 +蟀 +蟁 +蟂 +蟃 +蟄 +蟅 +蟆 +蟇 +蟈 +蟉 +蟊 +蟋 +蟌 +蟎 +蟏 +蟑 +蟒 +蟓 +蟔 +蟗 +蟘 +蟙 +蟛 +蟜 +蟞 +蟟 +蟠 +蟡 +蟢 +蟣 +蟤 +蟥 +蟦 +蟧 +蟨 +蟪 +蟫 +蟬 +蟭 +蟮 +蟯 +蟲 +蟳 +蟴 +蟶 +蟷 +蟹 +蟺 +蟻 +蟼 +蟾 +蟿 +蠀 +蠁 +蠂 +蠃 +蠄 +蠅 +蠆 +蠈 +蠉 +蠊 +蠋 +蠌 +蠍 +蠏 +蠐 +蠑 +蠓 +蠔 +蠕 +蠖 +蠗 +蠘 +蠙 +蠛 +蠜 +蠝 +蠟 +蠠 +蠡 +蠢 +蠣 +蠤 +蠥 +蠦 +蠨 +蠩 +蠪 +蠫 +蠬 +蠭 +蠮 +蠯 +蠰 +蠱 +蠲 +蠳 +蠵 +蠶 +蠷 +蠸 +蠹 +蠻 +蠼 +蠽 +蠾 +蠿 +血 +衁 +衂 +衃 +衄 +衅 +衆 +衇 +衈 +衊 +衋 +行 +衍 +衎 +衒 +術 +衔 +衕 +衖 +街 +衙 +衚 +衛 +衜 +衝 +衞 +衠 +衡 +衢 +衣 +补 +衧 +表 +衩 +衪 +衫 +衬 +衭 +衮 +衯 +衰 +衱 +衲 +衵 +衶 +衷 +衹 +衺 +衽 +衾 +衿 +袀 +袁 +袂 +袃 +袄 +袅 +袆 +袈 +袉 +袋 +袌 +袍 +袑 +袒 +袓 +袕 +袖 +袗 +袘 +袙 +袚 +袛 +袜 +袞 +袟 +袠 +袡 +袢 +袤 +袧 +袨 +袪 +被 +袬 +袭 +袯 +袰 +袱 +袲 +袴 +袵 +袶 +袷 +袸 +袹 +袺 +袼 +袽 +袾 +袿 +裀 +裁 +裂 +裃 +装 +裆 +裈 +裉 +裊 +裋 +裍 +裎 +裏 +裒 +裔 +裕 +裖 +裗 +裘 +裙 +裚 +裛 +補 +裝 +裞 +裟 +裠 +裡 +裢 +裣 +裤 +裥 +裧 +裨 +裬 +裮 +裯 +裰 +裱 +裲 +裳 +裴 +裵 +裶 +裷 +裸 +裹 +裺 +裻 +裼 +製 +裾 +裿 +褀 +褁 +褂 +褄 +褅 +褆 +複 +褊 +褋 +褌 +褍 +褎 +褐 +褑 +褒 +褓 +褔 +褕 +褖 +褗 +褘 +褙 +褚 +褛 +褞 +褟 +褡 +褢 +褥 +褦 +褧 +褩 +褪 +褫 +褭 +褮 +褯 +褰 +褱 +褲 +褳 +褴 +褵 +褶 +褷 +褸 +褻 +褼 +褽 +褾 +襁 +襂 +襃 +襄 +襆 +襇 +襋 +襌 +襏 +襐 +襒 +襓 +襕 +襖 +襗 +襘 +襚 +襛 +襜 +襝 +襞 +襟 +襠 +襡 +襢 +襣 +襤 +襦 +襩 +襪 +襫 +襬 +襭 +襮 +襯 +襲 +襳 +襴 +襶 +襷 +襹 +襺 +襻 +襾 +西 +要 +覂 +覃 +覅 +覆 +覇 +覈 +見 +覌 +覎 +規 +覓 +覕 +視 +覗 +覘 +覚 +覛 +覜 +覝 +覡 +覢 +覣 +覤 +覦 +覧 +覩 +親 +覬 +覭 +覮 +覯 +覲 +観 +覶 +覷 +覺 +覽 +覿 +觀 +见 +观 +觃 +规 +觅 +视 +觇 +览 +觉 +觊 +觋 +觌 +觎 +觏 +觐 +觑 +角 +觓 +觔 +觕 +觖 +觙 +觚 +觜 +觝 +觞 +觟 +觠 +觡 +觢 +解 +觤 +觥 +触 +觩 +觫 +觬 +觭 +觯 +觰 +觱 +觲 +觳 +觴 +觶 +觷 +觸 +觺 +觻 +觼 +觾 +觿 +言 +訁 +訂 +訃 +訄 +訇 +計 +訊 +訌 +討 +訏 +訐 +訑 +訒 +訓 +訔 +訕 +訖 +託 +記 +訚 +訛 +訝 +訞 +訟 +訢 +訣 +訥 +訧 +訩 +訪 +訬 +設 +訰 +許 +訳 +訴 +訶 +訹 +診 +註 +証 +訾 +訿 +詀 +詁 +詄 +詅 +詆 +詈 +詍 +詎 +詐 +詒 +詔 +評 +詖 +詗 +詘 +詙 +詛 +詝 +詞 +詟 +詠 +詡 +詢 +詣 +詥 +試 +詧 +詩 +詪 +詫 +詬 +詭 +詮 +詰 +話 +該 +詳 +詵 +詶 +詷 +詹 +詻 +詼 +詿 +誂 +誃 +誄 +誅 +誆 +誇 +誉 +誊 +誋 +誌 +認 +誑 +誒 +誓 +誕 +誖 +誘 +誙 +誚 +語 +誠 +誡 +誣 +誤 +誥 +誦 +誨 +說 +誫 +説 +読 +誰 +課 +誴 +誶 +誸 +誹 +誻 +誼 +誾 +調 +諀 +諂 +諄 +諅 +諆 +談 +諈 +諉 +請 +諌 +諍 +諏 +諐 +諑 +諒 +諓 +諔 +諕 +論 +諗 +諚 +諛 +諜 +諝 +諞 +諟 +諠 +諡 +諢 +諤 +諦 +諧 +諪 +諫 +諭 +諮 +諰 +諱 +諲 +諳 +諴 +諵 +諶 +諷 +諸 +諺 +諼 +諾 +謀 +謁 +謂 +謄 +謅 +謇 +謈 +謊 +謋 +謌 +謍 +謎 +謏 +謐 +謑 +謒 +謔 +謕 +謖 +謗 +謘 +謙 +謚 +講 +謜 +謝 +謞 +謠 +謡 +謢 +謣 +謤 +謥 +謦 +謧 +謨 +謩 +謪 +謫 +謬 +謯 +謰 +謱 +謳 +謵 +謷 +謹 +謼 +謾 +譀 +譁 +譅 +譆 +譇 +譈 +證 +譊 +譋 +譌 +譎 +譏 +譐 +譑 +譓 +譔 +譕 +譖 +識 +譙 +譚 +譛 +譜 +譝 +譞 +譟 +譠 +譣 +譥 +警 +譨 +譪 +譫 +譬 +譭 +譯 +議 +譲 +譳 +譴 +護 +譸 +譹 +譺 +譻 +譽 +譾 +譿 +讀 +讂 +讃 +讄 +讆 +變 +讋 +讌 +讎 +讐 +讒 +讓 +讔 +讕 +讖 +讘 +讙 +讚 +讜 +讞 +讟 +计 +订 +讣 +认 +讥 +讦 +讧 +讨 +让 +讪 +讫 +训 +议 +讯 +记 +讱 +讲 +讳 +讴 +讵 +讶 +讷 +许 +讹 +论 +讻 +讼 +讽 +设 +访 +诀 +证 +诂 +诃 +评 +诅 +识 +诇 +诈 +诉 +诊 +诋 +诌 +词 +诎 +诏 +诐 +译 +诒 +诓 +诔 +试 +诖 +诗 +诘 +诙 +诚 +诛 +诜 +话 +诞 +诟 +诠 +诡 +询 +诣 +诤 +该 +详 +诧 +诨 +诩 +诫 +诬 +语 +诮 +误 +诰 +诱 +诲 +诳 +说 +诵 +请 +诸 +诹 +诺 +读 +诼 +诽 +课 +诿 +谀 +谁 +谂 +调 +谄 +谅 +谆 +谇 +谈 +谊 +谋 +谌 +谍 +谎 +谏 +谐 +谑 +谒 +谓 +谔 +谕 +谖 +谗 +谙 +谚 +谛 +谜 +谝 +谞 +谟 +谠 +谡 +谢 +谣 +谤 +谥 +谦 +谧 +谨 +谩 +谪 +谫 +谬 +谭 +谮 +谯 +谰 +谱 +谲 +谳 +谴 +谵 +谶 +谷 +谹 +谻 +谼 +谽 +谾 +谿 +豁 +豃 +豅 +豆 +豇 +豈 +豉 +豊 +豋 +豌 +豍 +豎 +豏 +豐 +豔 +豕 +豗 +豚 +豜 +豝 +豟 +象 +豢 +豤 +豥 +豦 +豨 +豪 +豫 +豬 +豭 +豮 +豯 +豰 +豱 +豲 +豳 +豵 +豶 +豷 +豸 +豹 +豺 +豻 +豽 +豿 +貀 +貁 +貂 +貄 +貅 +貆 +貉 +貊 +貌 +貍 +貏 +貐 +貑 +貒 +貓 +貔 +貕 +貗 +貘 +貙 +貛 +貜 +貝 +貞 +負 +財 +貢 +貣 +貤 +貧 +貨 +販 +貪 +貫 +責 +貭 +貮 +貯 +貰 +貲 +貳 +貴 +貶 +買 +貸 +貺 +費 +貼 +貽 +貾 +貿 +賀 +賁 +賂 +賃 +賄 +賅 +資 +賈 +賉 +賊 +賌 +賎 +賑 +賒 +賓 +賔 +賕 +賙 +賚 +賛 +賜 +賞 +賟 +賠 +賡 +賢 +賣 +賤 +賥 +賦 +賧 +賨 +質 +賫 +賬 +賭 +賮 +賴 +賵 +賸 +賹 +賺 +賻 +購 +賽 +賾 +贄 +贅 +贆 +贇 +贈 +贊 +贋 +贌 +贍 +贏 +贐 +贓 +贔 +贕 +贖 +贗 +贙 +贛 +贜 +贝 +贞 +负 +贡 +财 +责 +贤 +败 +账 +货 +质 +贩 +贪 +贫 +贬 +购 +贮 +贯 +贰 +贱 +贲 +贳 +贴 +贵 +贶 +贷 +贸 +费 +贺 +贻 +贼 +贽 +贾 +贿 +赀 +赁 +赂 +赃 +资 +赅 +赆 +赇 +赈 +赉 +赊 +赋 +赌 +赍 +赎 +赏 +赐 +赑 +赒 +赓 +赔 +赕 +赖 +赗 +赘 +赙 +赚 +赛 +赜 +赝 +赞 +赟 +赠 +赡 +赢 +赣 +赤 +赦 +赧 +赨 +赩 +赪 +赫 +赬 +赭 +赮 +走 +赳 +赴 +赵 +赶 +起 +赸 +趀 +趁 +趂 +趄 +超 +越 +趋 +趌 +趍 +趎 +趏 +趐 +趑 +趓 +趔 +趕 +趖 +趙 +趜 +趟 +趠 +趡 +趣 +趥 +趧 +趨 +趪 +趫 +趬 +趭 +趮 +趯 +趱 +趲 +足 +趴 +趵 +趷 +趸 +趹 +趺 +趼 +趾 +趿 +跂 +跃 +跄 +跅 +跆 +跇 +跈 +跋 +跌 +跍 +跎 +跏 +跐 +跑 +跓 +跕 +跖 +跗 +跘 +跙 +跚 +跛 +跜 +距 +跞 +跟 +跠 +跡 +跢 +跣 +跤 +跥 +跦 +跧 +跨 +跩 +跪 +跫 +跬 +跮 +路 +跰 +跱 +跲 +跳 +跴 +践 +跶 +跷 +跸 +跹 +跺 +跻 +跼 +跽 +跾 +跿 +踂 +踃 +踅 +踆 +踇 +踉 +踊 +踌 +踍 +踎 +踏 +踐 +踑 +踒 +踓 +踔 +踕 +踖 +踘 +踙 +踛 +踝 +踞 +踟 +踠 +踡 +踢 +踣 +踤 +踥 +踦 +踧 +踩 +踪 +踫 +踬 +踮 +踯 +踰 +踱 +踳 +踴 +踵 +踶 +踸 +踹 +踺 +踼 +踽 +踾 +蹀 +蹁 +蹂 +蹄 +蹅 +蹇 +蹈 +蹉 +蹊 +蹋 +蹌 +蹍 +蹎 +蹏 +蹐 +蹑 +蹒 +蹓 +蹔 +蹕 +蹖 +蹗 +蹙 +蹚 +蹛 +蹜 +蹝 +蹞 +蹟 +蹠 +蹡 +蹢 +蹣 +蹤 +蹥 +蹦 +蹧 +蹩 +蹪 +蹬 +蹭 +蹯 +蹰 +蹲 +蹳 +蹴 +蹵 +蹶 +蹸 +蹺 +蹻 +蹼 +蹽 +蹾 +蹿 +躁 +躂 +躄 +躅 +躆 +躇 +躈 +躉 +躊 +躋 +躍 +躏 +躐 +躑 +躒 +躓 +躔 +躕 +躖 +躗 +躙 +躚 +躜 +躝 +躞 +躟 +躠 +躡 +躣 +躤 +躥 +躦 +躨 +躩 +躪 +身 +躬 +躭 +躯 +躰 +躲 +躳 +躶 +躺 +躽 +軀 +車 +軋 +軌 +軍 +軎 +軏 +軑 +軒 +軓 +軔 +軘 +軛 +軝 +軞 +軟 +転 +軤 +軥 +軦 +軧 +軨 +軫 +軬 +軮 +軯 +軱 +軲 +軵 +軶 +軷 +軸 +軹 +軺 +軻 +軼 +軽 +軾 +軿 +輀 +輁 +輂 +較 +輄 +輅 +輆 +輇 +輈 +載 +輊 +輋 +輌 +輐 +輑 +輒 +輓 +輔 +輕 +輖 +輗 +輘 +輚 +輛 +輜 +輝 +輞 +輟 +輠 +輣 +輤 +輥 +輦 +輩 +輪 +輬 +輭 +輮 +輯 +輲 +輳 +輴 +輵 +輶 +輷 +輸 +輹 +輻 +輾 +輿 +轀 +轂 +轃 +轄 +轅 +轆 +轇 +轈 +轉 +轍 +轎 +轏 +轐 +轑 +轒 +轓 +轔 +轕 +轖 +轗 +轘 +轙 +轚 +轛 +轝 +轞 +轟 +轠 +轡 +轢 +轤 +车 +轧 +轨 +轩 +轪 +轫 +转 +轭 +轮 +软 +轰 +轱 +轲 +轳 +轴 +轵 +轶 +轷 +轸 +轹 +轺 +轻 +轼 +载 +轾 +轿 +辀 +辁 +辂 +较 +辄 +辅 +辆 +辇 +辈 +辉 +辊 +辋 +辌 +辍 +辎 +辏 +辐 +辑 +辒 +输 +辔 +辕 +辖 +辗 +辘 +辙 +辚 +辛 +辜 +辞 +辟 +辠 +辢 +辣 +辥 +辦 +辧 +辨 +辩 +辫 +辭 +辮 +辯 +辰 +辱 +農 +辴 +辵 +边 +辺 +辻 +込 +辽 +达 +辿 +迁 +迂 +迄 +迅 +迆 +过 +迈 +迉 +迋 +迍 +迎 +运 +近 +迒 +迓 +返 +迕 +迖 +还 +这 +进 +远 +违 +连 +迟 +迡 +迢 +迣 +迤 +迥 +迦 +迨 +迩 +迪 +迫 +迭 +迮 +述 +迳 +迴 +迵 +迷 +迸 +迹 +迺 +迻 +追 +迾 +迿 +退 +送 +适 +逃 +逄 +逅 +逆 +逈 +选 +逊 +逋 +逌 +逍 +逎 +透 +逐 +逑 +递 +逓 +途 +逕 +逖 +逗 +這 +通 +逛 +逜 +逝 +逞 +速 +造 +逡 +逢 +連 +逤 +逦 +逨 +逩 +逭 +逮 +逯 +週 +進 +逴 +逵 +逶 +逸 +逹 +逺 +逻 +逼 +逾 +逿 +遁 +遂 +遄 +遅 +遆 +遇 +遊 +運 +遍 +過 +遏 +遐 +遑 +遒 +道 +達 +違 +遗 +遘 +遙 +遛 +遜 +遝 +遞 +遠 +遡 +遢 +遣 +遥 +遨 +適 +遭 +遮 +遯 +遰 +遲 +遳 +遴 +遵 +遶 +遷 +選 +遹 +遺 +遻 +遼 +遽 +遾 +避 +邀 +邁 +邂 +邃 +還 +邅 +邆 +邇 +邈 +邉 +邊 +邋 +邍 +邏 +邐 +邑 +邓 +邔 +邕 +邗 +邘 +邙 +邛 +邝 +邞 +邟 +邠 +邡 +邢 +那 +邥 +邦 +邧 +邨 +邪 +邬 +邮 +邯 +邰 +邱 +邲 +邳 +邴 +邵 +邶 +邸 +邹 +邺 +邻 +邽 +邾 +邿 +郁 +郃 +郄 +郅 +郇 +郈 +郊 +郋 +郎 +郏 +郐 +郑 +郓 +郔 +郕 +郖 +郗 +郘 +郙 +郚 +郛 +郜 +郝 +郞 +郟 +郠 +郡 +郢 +郣 +郤 +郥 +郦 +郧 +部 +郪 +郫 +郭 +郯 +郰 +郱 +郲 +郳 +郴 +郵 +郷 +郸 +郹 +郺 +郻 +郼 +都 +郾 +郿 +鄀 +鄁 +鄂 +鄃 +鄄 +鄅 +鄆 +鄇 +鄈 +鄉 +鄋 +鄌 +鄍 +鄎 +鄏 +鄐 +鄑 +鄒 +鄔 +鄖 +鄗 +鄘 +鄙 +鄚 +鄛 +鄜 +鄝 +鄞 +鄟 +鄠 +鄡 +鄢 +鄣 +鄤 +鄦 +鄧 +鄨 +鄩 +鄪 +鄫 +鄬 +鄭 +鄮 +鄯 +鄰 +鄱 +鄲 +鄳 +鄴 +鄵 +鄶 +鄸 +鄹 +鄺 +鄻 +鄾 +酀 +酁 +酂 +酃 +酄 +酅 +酆 +酇 +酈 +酉 +酊 +酋 +酌 +配 +酎 +酏 +酐 +酒 +酓 +酔 +酖 +酗 +酘 +酚 +酝 +酞 +酟 +酡 +酢 +酣 +酤 +酥 +酦 +酧 +酨 +酩 +酪 +酬 +酮 +酯 +酰 +酱 +酲 +酳 +酴 +酵 +酶 +酷 +酸 +酹 +酺 +酼 +酽 +酾 +酿 +醁 +醂 +醃 +醅 +醆 +醇 +醉 +醊 +醋 +醌 +醍 +醐 +醑 +醒 +醓 +醕 +醖 +醗 +醙 +醚 +醛 +醜 +醞 +醟 +醢 +醣 +醤 +醥 +醧 +醨 +醪 +醫 +醬 +醭 +醮 +醯 +醰 +醱 +醲 +醳 +醴 +醵 +醷 +醸 +醹 +醺 +醻 +醼 +醽 +醾 +醿 +釀 +釁 +釂 +釃 +釅 +釆 +采 +釈 +釉 +释 +釋 +里 +重 +野 +量 +釐 +金 +釓 +釔 +釕 +釗 +釘 +釙 +釚 +釜 +針 +釢 +釣 +釤 +釦 +釧 +釩 +釪 +釬 +釭 +釱 +釳 +釴 +釵 +釷 +釸 +釹 +釺 +釽 +釿 +鈀 +鈁 +鈃 +鈄 +鈆 +鈇 +鈈 +鈉 +鈊 +鈌 +鈍 +鈎 +鈏 +鈐 +鈑 +鈒 +鈔 +鈕 +鈖 +鈚 +鈞 +鈢 +鈣 +鈥 +鈦 +鈧 +鈫 +鈮 +鈰 +鈳 +鈴 +鈶 +鈷 +鈸 +鈹 +鈺 +鈽 +鈾 +鈿 +鉀 +鉄 +鉅 +鉆 +鉇 +鉈 +鉉 +鉊 +鉋 +鉌 +鉍 +鉏 +鉑 +鉒 +鉓 +鉕 +鉗 +鉚 +鉛 +鉞 +鉟 +鉠 +鉢 +鉤 +鉥 +鉦 +鉧 +鉬 +鉭 +鉮 +鉯 +鉱 +鉲 +鉴 +鉶 +鉷 +鉸 +鉹 +鉺 +鉻 +鉼 +鉾 +鉿 +銀 +銂 +銃 +銅 +銈 +銋 +銍 +銎 +銑 +銓 +銔 +銕 +銖 +銗 +銘 +銚 +銛 +銜 +銠 +銣 +銤 +銥 +銦 +銨 +銩 +銪 +銫 +銬 +銭 +銮 +銱 +銲 +銳 +銶 +銷 +銹 +銻 +銼 +銾 +鋀 +鋁 +鋂 +鋃 +鋄 +鋅 +鋆 +鋇 +鋈 +鋊 +鋋 +鋌 +鋏 +鋐 +鋒 +鋕 +鋗 +鋘 +鋙 +鋝 +鋟 +鋡 +鋤 +鋥 +鋦 +鋨 +鋩 +鋪 +鋭 +鋮 +鋯 +鋰 +鋱 +鋲 +鋳 +鋶 +鋸 +鋹 +鋺 +鋼 +錀 +錁 +錄 +錆 +錈 +錍 +錎 +錏 +錐 +錒 +錕 +錘 +錙 +錚 +錛 +錞 +錟 +錠 +錡 +錢 +錣 +錦 +錧 +錨 +錫 +錬 +錭 +錮 +錯 +録 +錳 +錵 +錶 +錸 +錻 +錼 +錾 +鍀 +鍁 +鍆 +鍇 +鍈 +鍉 +鍊 +鍋 +鍌 +鍍 +鍏 +鍐 +鍑 +鍔 +鍖 +鍘 +鍚 +鍛 +鍜 +鍝 +鍟 +鍠 +鍤 +鍥 +鍩 +鍪 +鍬 +鍭 +鍮 +鍰 +鍱 +鍳 +鍵 +鍶 +鍷 +鍺 +鍼 +鍾 +鎂 +鎃 +鎅 +鎉 +鎊 +鎌 +鎎 +鎏 +鎒 +鎓 +鎔 +鎖 +鎗 +鎘 +鎚 +鎛 +鎝 +鎞 +鎡 +鎢 +鎣 +鎦 +鎧 +鎩 +鎪 +鎬 +鎭 +鎮 +鎯 +鎰 +鎳 +鎵 +鎷 +鎸 +鎹 +鎻 +鏂 +鏃 +鏇 +鏈 +鏊 +鏌 +鏍 +鏏 +鏐 +鏑 +鏓 +鏖 +鏗 +鏘 +鏙 +鏚 +鏜 +鏝 +鏞 +鏟 +鏡 +鏢 +鏤 +鏦 +鏨 +鏬 +鏰 +鏳 +鏴 +鏵 +鏷 +鏹 +鏺 +鏻 +鏽 +鏾 +鐀 +鐃 +鐇 +鐈 +鐋 +鐍 +鐎 +鐏 +鐐 +鐓 +鐔 +鐕 +鐖 +鐘 +鐙 +鐚 +鐝 +鐠 +鐡 +鐤 +鐦 +鐧 +鐨 +鐫 +鐬 +鐭 +鐮 +鐯 +鐲 +鐳 +鐵 +鐶 +鐸 +鐺 +鐻 +鐽 +鐾 +鐿 +鑀 +鑁 +鑂 +鑄 +鑅 +鑊 +鑋 +鑌 +鑐 +鑑 +鑒 +鑔 +鑕 +鑚 +鑛 +鑞 +鑠 +鑢 +鑣 +鑤 +鑨 +鑪 +鑫 +鑭 +鑮 +鑯 +鑰 +鑱 +鑲 +鑴 +鑵 +鑷 +鑹 +鑼 +鑽 +鑾 +鑿 +钀 +钁 +钂 +钃 +钆 +钇 +针 +钉 +钊 +钋 +钌 +钍 +钎 +钏 +钐 +钒 +钓 +钔 +钕 +钖 +钗 +钘 +钙 +钚 +钛 +钜 +钝 +钞 +钟 +钠 +钡 +钢 +钣 +钤 +钥 +钦 +钧 +钨 +钩 +钪 +钫 +钬 +钭 +钮 +钯 +钰 +钱 +钲 +钳 +钴 +钵 +钷 +钹 +钺 +钻 +钼 +钽 +钾 +钿 +铀 +铁 +铂 +铃 +铄 +铅 +铆 +铈 +铉 +铊 +铋 +铌 +铍 +铎 +铏 +铐 +铑 +铒 +铕 +铖 +铗 +铘 +铙 +铚 +铛 +铜 +铝 +铞 +铟 +铠 +铡 +铢 +铣 +铤 +铥 +铧 +铨 +铩 +铪 +铫 +铬 +铭 +铮 +铯 +铰 +铱 +铲 +铳 +铴 +铵 +银 +铷 +铸 +铹 +铺 +铻 +铼 +铽 +链 +铿 +销 +锁 +锂 +锃 +锄 +锅 +锆 +锇 +锈 +锉 +锊 +锋 +锌 +锍 +锎 +锏 +锐 +锑 +锒 +锓 +锔 +锕 +锖 +锗 +锘 +错 +锚 +锛 +锜 +锝 +锞 +锟 +锡 +锢 +锣 +锤 +锥 +锦 +锧 +锨 +锩 +锪 +锫 +锬 +锭 +键 +锯 +锰 +锱 +锲 +锳 +锴 +锵 +锶 +锷 +锸 +锹 +锺 +锻 +锼 +锽 +锾 +锿 +镀 +镁 +镂 +镃 +镄 +镅 +镆 +镇 +镈 +镉 +镊 +镋 +镌 +镍 +镎 +镏 +镐 +镑 +镒 +镓 +镔 +镕 +镖 +镗 +镘 +镚 +镛 +镜 +镝 +镞 +镠 +镡 +镢 +镣 +镤 +镥 +镦 +镧 +镨 +镩 +镪 +镫 +镬 +镭 +镮 +镯 +镰 +镱 +镲 +镳 +镴 +镵 +镶 +長 +镺 +镻 +镼 +镽 +长 +門 +閂 +閃 +閆 +閇 +閈 +閉 +開 +閌 +閍 +閎 +閏 +閑 +閒 +間 +閔 +閘 +閜 +閞 +閟 +閡 +関 +閣 +閤 +閥 +閦 +閧 +閨 +閩 +閫 +閬 +閭 +閰 +閱 +閲 +閵 +閶 +閷 +閹 +閺 +閻 +閼 +閽 +閾 +閿 +闀 +闃 +闅 +闆 +闇 +闈 +闉 +闊 +闋 +闌 +闍 +闐 +闑 +闒 +闓 +闔 +闕 +闖 +闘 +闚 +闛 +關 +闞 +闟 +闠 +闡 +闢 +闤 +闥 +门 +闩 +闪 +闫 +闭 +问 +闯 +闰 +闱 +闲 +闳 +间 +闵 +闶 +闷 +闸 +闹 +闺 +闻 +闼 +闽 +闾 +闿 +阀 +阁 +阂 +阃 +阄 +阅 +阆 +阇 +阈 +阉 +阊 +阋 +阌 +阍 +阎 +阏 +阐 +阑 +阒 +阔 +阕 +阖 +阗 +阘 +阙 +阚 +阜 +阝 +阞 +队 +阠 +阡 +阢 +阤 +阨 +阪 +阬 +阭 +阮 +阯 +阰 +阱 +防 +阳 +阴 +阵 +阶 +阸 +阹 +阺 +阻 +阼 +阽 +阿 +陀 +陁 +陂 +附 +际 +陆 +陇 +陈 +陉 +陊 +陋 +陌 +降 +陎 +陏 +限 +陑 +陓 +陔 +陕 +陗 +陘 +陛 +陜 +陝 +陞 +陟 +陡 +院 +陣 +除 +陥 +陧 +陨 +险 +陪 +陫 +陬 +陭 +陰 +陲 +陳 +陴 +陵 +陶 +陷 +陸 +険 +陻 +陼 +陽 +陾 +隃 +隄 +隅 +隆 +隇 +隈 +隉 +隊 +隋 +隍 +階 +随 +隐 +隑 +隒 +隓 +隔 +隕 +隗 +隘 +隙 +際 +障 +隞 +隠 +隣 +隤 +隧 +隨 +隩 +險 +隮 +隰 +隱 +隲 +隳 +隴 +隶 +隷 +隸 +隹 +隺 +隻 +隼 +隽 +难 +隿 +雀 +雁 +雂 +雄 +雅 +集 +雇 +雈 +雉 +雊 +雋 +雌 +雍 +雎 +雏 +雑 +雒 +雓 +雔 +雕 +雖 +雗 +雘 +雙 +雚 +雛 +雜 +雝 +雞 +雟 +雠 +離 +難 +雨 +雩 +雪 +雫 +雯 +雰 +雱 +雲 +雳 +零 +雷 +雹 +雺 +電 +雽 +雾 +雿 +需 +霁 +霂 +霄 +霅 +霆 +震 +霈 +霉 +霊 +霋 +霍 +霎 +霏 +霐 +霑 +霒 +霓 +霖 +霙 +霜 +霞 +霠 +霢 +霣 +霤 +霧 +霨 +霩 +霪 +霫 +霬 +霭 +霮 +霰 +露 +霵 +霶 +霸 +霹 +霽 +霾 +霿 +靁 +靂 +靃 +靄 +靆 +靈 +靉 +靑 +青 +靓 +靖 +静 +靚 +靛 +靜 +非 +靠 +靡 +面 +靥 +靦 +靨 +革 +靪 +靫 +靬 +靭 +靮 +靰 +靱 +靲 +靳 +靴 +靶 +靷 +靸 +靺 +靻 +靼 +靽 +靾 +靿 +鞀 +鞁 +鞂 +鞃 +鞄 +鞅 +鞆 +鞈 +鞊 +鞋 +鞌 +鞍 +鞎 +鞏 +鞑 +鞒 +鞔 +鞗 +鞘 +鞙 +鞚 +鞜 +鞞 +鞠 +鞡 +鞣 +鞤 +鞥 +鞦 +鞧 +鞨 +鞪 +鞫 +鞬 +鞭 +鞮 +鞯 +鞲 +鞳 +鞴 +鞵 +鞶 +鞷 +鞹 +鞻 +鞽 +鞾 +鞿 +韁 +韂 +韃 +韄 +韅 +韆 +韇 +韉 +韋 +韌 +韍 +韎 +韏 +韐 +韑 +韓 +韔 +韕 +韗 +韙 +韜 +韝 +韞 +韟 +韠 +韡 +韣 +韥 +韦 +韧 +韨 +韩 +韪 +韫 +韬 +韭 +韮 +韰 +韱 +音 +韵 +韶 +韹 +韺 +韻 +韽 +韾 +響 +頁 +頂 +頃 +頄 +項 +順 +頇 +須 +頊 +頌 +頍 +頎 +頏 +預 +頑 +頒 +頓 +頔 +頖 +頗 +領 +頚 +頜 +頝 +頞 +頠 +頡 +頤 +頦 +頨 +頩 +頫 +頬 +頭 +頯 +頰 +頲 +頴 +頵 +頷 +頸 +頹 +頻 +頼 +頽 +顁 +顄 +顅 +顆 +顇 +顈 +顉 +顊 +顋 +題 +額 +顎 +顏 +顐 +顑 +顒 +顓 +顔 +顕 +顗 +願 +顙 +顛 +顜 +顝 +類 +顟 +顠 +顢 +顣 +顤 +顥 +顦 +顧 +顩 +顪 +顫 +顯 +顰 +顱 +顲 +顳 +顴 +页 +顶 +顷 +顸 +项 +顺 +须 +顼 +顽 +顾 +顿 +颀 +颁 +颂 +颃 +预 +颅 +领 +颇 +颈 +颉 +颊 +颋 +颌 +颍 +颎 +颏 +颐 +频 +颓 +颔 +颖 +颗 +题 +颙 +颚 +颛 +颜 +额 +颞 +颟 +颠 +颡 +颢 +颤 +颥 +颦 +颧 +風 +颬 +颭 +颮 +颯 +颱 +颲 +颳 +颶 +颸 +颺 +颻 +颼 +颽 +颾 +颿 +飁 +飂 +飄 +飆 +飈 +飉 +飋 +飌 +风 +飏 +飐 +飑 +飒 +飓 +飔 +飕 +飗 +飘 +飙 +飛 +飜 +飞 +食 +飡 +飢 +飣 +飥 +飧 +飨 +飩 +飪 +飫 +飬 +飭 +飮 +飯 +飱 +飲 +飴 +飶 +飺 +飼 +飽 +飾 +飿 +餀 +餂 +餃 +餄 +餅 +餈 +餉 +養 +餌 +餍 +餎 +餏 +餐 +餑 +餒 +餓 +餔 +餕 +餖 +餗 +餘 +餚 +餛 +餜 +餝 +餞 +餟 +餠 +餡 +餤 +餥 +餧 +館 +餪 +餫 +餬 +餭 +餮 +餯 +餰 +餱 +餲 +餳 +餵 +餷 +餹 +餺 +餻 +餼 +餽 +餾 +餿 +饁 +饂 +饃 +饅 +饇 +饈 +饉 +饊 +饋 +饌 +饍 +饎 +饐 +饑 +饒 +饔 +饕 +饗 +饘 +饙 +饛 +饜 +饞 +饟 +饡 +饢 +饥 +饧 +饨 +饩 +饪 +饫 +饬 +饭 +饮 +饯 +饰 +饱 +饲 +饳 +饴 +饵 +饶 +饷 +饸 +饹 +饺 +饻 +饼 +饽 +饿 +馁 +馃 +馄 +馅 +馆 +馇 +馈 +馉 +馊 +馋 +馌 +馍 +馏 +馐 +馑 +馒 +馓 +馔 +馕 +首 +馗 +馘 +香 +馛 +馝 +馞 +馣 +馥 +馦 +馧 +馨 +馬 +馭 +馮 +馯 +馰 +馱 +馲 +馳 +馴 +馵 +馹 +馺 +馻 +馼 +馽 +駁 +駂 +駃 +駄 +駅 +駆 +駈 +駉 +駍 +駎 +駏 +駐 +駑 +駒 +駓 +駔 +駕 +駖 +駘 +駙 +駛 +駜 +駝 +駟 +駡 +駢 +駣 +駤 +駥 +駩 +駪 +駬 +駭 +駮 +駰 +駱 +駴 +駶 +駷 +駸 +駹 +駺 +駻 +駼 +駽 +駾 +駿 +騁 +騂 +騃 +騄 +騅 +騇 +騉 +騊 +騋 +騌 +騍 +騎 +騏 +騑 +騒 +験 +騕 +騖 +騙 +騚 +騛 +騜 +騝 +騞 +騠 +騢 +騣 +騤 +騥 +騧 +騨 +騩 +騪 +騫 +騬 +騭 +騮 +騰 +騱 +騲 +騴 +騵 +騶 +騷 +騸 +騹 +騺 +騽 +騾 +驀 +驁 +驂 +驃 +驄 +驅 +驆 +驈 +驉 +驊 +驌 +驍 +驎 +驏 +驒 +驓 +驔 +驕 +驖 +驗 +驚 +驛 +驞 +驟 +驠 +驢 +驤 +驥 +驦 +驨 +驩 +驪 +驫 +马 +驭 +驮 +驯 +驰 +驱 +驲 +驳 +驴 +驵 +驶 +驷 +驸 +驹 +驺 +驻 +驼 +驽 +驾 +驿 +骀 +骁 +骂 +骃 +骄 +骅 +骆 +骇 +骈 +骉 +骊 +骋 +验 +骍 +骎 +骏 +骐 +骑 +骒 +骓 +骕 +骖 +骗 +骘 +骙 +骚 +骛 +骜 +骝 +骞 +骟 +骠 +骡 +骢 +骣 +骤 +骥 +骦 +骧 +骨 +骫 +骭 +骯 +骰 +骱 +骴 +骶 +骷 +骸 +骹 +骺 +骼 +骽 +骾 +骿 +髀 +髁 +髂 +髃 +髄 +髅 +髆 +髇 +髈 +髊 +髋 +髌 +髍 +髎 +髏 +髐 +髑 +髒 +髓 +體 +髕 +髖 +高 +髙 +髟 +髡 +髢 +髣 +髥 +髦 +髧 +髪 +髫 +髬 +髭 +髮 +髯 +髲 +髳 +髴 +髶 +髷 +髹 +髺 +髻 +髼 +髽 +髾 +鬀 +鬁 +鬃 +鬄 +鬅 +鬆 +鬈 +鬉 +鬊 +鬋 +鬌 +鬍 +鬎 +鬏 +鬐 +鬒 +鬓 +鬕 +鬖 +鬗 +鬘 +鬙 +鬚 +鬞 +鬟 +鬠 +鬢 +鬣 +鬤 +鬥 +鬧 +鬨 +鬩 +鬪 +鬫 +鬬 +鬮 +鬯 +鬱 +鬲 +鬳 +鬵 +鬶 +鬷 +鬹 +鬺 +鬻 +鬼 +鬾 +鬿 +魁 +魂 +魃 +魄 +魅 +魆 +魇 +魈 +魉 +魊 +魋 +魌 +魍 +魎 +魏 +魑 +魔 +魕 +魖 +魘 +魚 +魛 +魞 +魟 +魠 +魡 +魣 +魦 +魧 +魨 +魩 +魮 +魯 +魱 +魴 +魵 +魶 +魷 +魻 +魼 +魾 +鮀 +鮁 +鮂 +鮃 +鮄 +鮅 +鮆 +鮇 +鮊 +鮋 +鮍 +鮎 +鮐 +鮑 +鮒 +鮓 +鮗 +鮚 +鮛 +鮜 +鮝 +鮞 +鮟 +鮠 +鮡 +鮣 +鮤 +鮥 +鮦 +鮨 +鮪 +鮫 +鮭 +鮮 +鮰 +鮵 +鮸 +鮹 +鮻 +鮿 +鯀 +鯁 +鯃 +鯆 +鯇 +鯈 +鯉 +鯊 +鯏 +鯒 +鯓 +鯔 +鯕 +鯖 +鯗 +鯙 +鯛 +鯝 +鯞 +鯠 +鯡 +鯢 +鯤 +鯦 +鯧 +鯨 +鯪 +鯫 +鯬 +鯭 +鯮 +鯰 +鯱 +鯶 +鯷 +鯸 +鯻 +鯽 +鯿 +鰂 +鰃 +鰅 +鰆 +鰈 +鰉 +鰋 +鰌 +鰍 +鰏 +鰐 +鰒 +鰓 +鰕 +鰗 +鰛 +鰜 +鰝 +鰟 +鰣 +鰤 +鰥 +鰧 +鰨 +鰩 +鰫 +鰬 +鰭 +鰮 +鰯 +鰰 +鰱 +鰲 +鰳 +鰴 +鰶 +鰷 +鰹 +鰺 +鰻 +鰼 +鰾 +鰿 +鱀 +鱁 +鱂 +鱄 +鱅 +鱆 +鱇 +鱈 +鱉 +鱊 +鱋 +鱍 +鱐 +鱒 +鱓 +鱔 +鱕 +鱖 +鱗 +鱘 +鱚 +鱝 +鱞 +鱟 +鱠 +鱢 +鱣 +鱥 +鱦 +鱧 +鱨 +鱬 +鱭 +鱮 +鱰 +鱲 +鱳 +鱴 +鱵 +鱷 +鱸 +鱹 +鱺 +鱻 +鱼 +鱽 +鱾 +鱿 +鲀 +鲁 +鲂 +鲃 +鲅 +鲆 +鲇 +鲈 +鲉 +鲊 +鲋 +鲌 +鲍 +鲎 +鲏 +鲐 +鲑 +鲒 +鲔 +鲕 +鲖 +鲗 +鲘 +鲙 +鲚 +鲛 +鲜 +鲝 +鲞 +鲟 +鲠 +鲡 +鲢 +鲣 +鲤 +鲥 +鲦 +鲧 +鲨 +鲩 +鲪 +鲫 +鲬 +鲭 +鲮 +鲯 +鲰 +鲱 +鲲 +鲳 +鲴 +鲵 +鲷 +鲸 +鲹 +鲺 +鲻 +鲼 +鲽 +鲾 +鲿 +鳀 +鳁 +鳂 +鳃 +鳄 +鳅 +鳇 +鳈 +鳉 +鳊 +鳌 +鳍 +鳎 +鳏 +鳐 +鳑 +鳒 +鳓 +鳔 +鳕 +鳖 +鳗 +鳘 +鳙 +鳚 +鳛 +鳜 +鳝 +鳞 +鳟 +鳠 +鳡 +鳢 +鳣 +鳤 +鳥 +鳦 +鳧 +鳩 +鳪 +鳬 +鳭 +鳯 +鳰 +鳱 +鳲 +鳳 +鳴 +鳵 +鳶 +鳷 +鳹 +鳺 +鳻 +鳼 +鳽 +鳾 +鳿 +鴀 +鴃 +鴅 +鴆 +鴇 +鴈 +鴉 +鴎 +鴐 +鴒 +鴓 +鴔 +鴕 +鴗 +鴘 +鴛 +鴝 +鴞 +鴟 +鴠 +鴡 +鴢 +鴣 +鴥 +鴦 +鴨 +鴩 +鴫 +鴮 +鴯 +鴰 +鴱 +鴳 +鴴 +鴶 +鴷 +鴸 +鴻 +鴽 +鴾 +鴿 +鵀 +鵁 +鵂 +鵃 +鵄 +鵅 +鵊 +鵋 +鵌 +鵎 +鵏 +鵐 +鵑 +鵒 +鵓 +鵔 +鵖 +鵗 +鵙 +鵛 +鵜 +鵝 +鵞 +鵟 +鵠 +鵡 +鵧 +鵩 +鵪 +鵫 +鵬 +鵮 +鵯 +鵰 +鵱 +鵲 +鵳 +鵴 +鵵 +鵷 +鵸 +鵹 +鵺 +鵻 +鵼 +鵽 +鵾 +鶀 +鶁 +鶂 +鶄 +鶅 +鶆 +鶇 +鶈 +鶉 +鶊 +鶋 +鶌 +鶏 +鶐 +鶒 +鶓 +鶔 +鶖 +鶗 +鶘 +鶙 +鶚 +鶛 +鶜 +鶝 +鶞 +鶟 +鶠 +鶡 +鶢 +鶣 +鶤 +鶥 +鶨 +鶩 +鶪 +鶬 +鶭 +鶯 +鶱 +鶲 +鶴 +鶵 +鶶 +鶷 +鶹 +鶺 +鶻 +鶼 +鶾 +鶿 +鷀 +鷁 +鷂 +鷃 +鷄 +鷅 +鷇 +鷈 +鷉 +鷊 +鷋 +鷎 +鷏 +鷐 +鷑 +鷒 +鷓 +鷕 +鷖 +鷗 +鷘 +鷙 +鷚 +鷛 +鷜 +鷞 +鷟 +鷡 +鷢 +鷣 +鷤 +鷥 +鷦 +鷩 +鷫 +鷮 +鷯 +鷰 +鷲 +鷳 +鷵 +鷶 +鷷 +鷸 +鷹 +鷺 +鷻 +鷽 +鷾 +鷿 +鸀 +鸁 +鸂 +鸃 +鸄 +鸅 +鸆 +鸇 +鸉 +鸊 +鸋 +鸌 +鸍 +鸏 +鸐 +鸑 +鸒 +鸓 +鸔 +鸕 +鸗 +鸘 +鸙 +鸚 +鸛 +鸜 +鸝 +鸞 +鸟 +鸠 +鸡 +鸢 +鸣 +鸤 +鸥 +鸦 +鸧 +鸨 +鸩 +鸪 +鸫 +鸬 +鸭 +鸮 +鸯 +鸰 +鸱 +鸲 +鸳 +鸵 +鸶 +鸷 +鸸 +鸹 +鸺 +鸻 +鸼 +鸽 +鸾 +鸿 +鹀 +鹁 +鹂 +鹃 +鹄 +鹅 +鹆 +鹇 +鹈 +鹉 +鹊 +鹋 +鹌 +鹍 +鹎 +鹏 +鹐 +鹑 +鹒 +鹔 +鹕 +鹖 +鹗 +鹘 +鹙 +鹚 +鹛 +鹜 +鹝 +鹞 +鹟 +鹠 +鹡 +鹢 +鹣 +鹤 +鹦 +鹧 +鹨 +鹩 +鹪 +鹫 +鹬 +鹭 +鹮 +鹯 +鹰 +鹱 +鹲 +鹳 +鹴 +鹵 +鹸 +鹹 +鹺 +鹻 +鹼 +鹽 +鹾 +鹿 +麀 +麁 +麂 +麃 +麅 +麆 +麇 +麈 +麉 +麊 +麋 +麌 +麎 +麐 +麑 +麒 +麓 +麔 +麖 +麗 +麙 +麚 +麛 +麜 +麝 +麞 +麟 +麠 +麡 +麤 +麥 +麦 +麧 +麩 +麪 +麮 +麯 +麰 +麴 +麵 +麶 +麷 +麸 +麹 +麺 +麻 +麼 +麽 +麾 +麿 +黀 +黁 +黂 +黃 +黄 +黇 +黈 +黉 +黌 +黍 +黎 +黏 +黐 +黑 +黒 +黓 +黔 +黕 +黖 +默 +黙 +黚 +黛 +黜 +黝 +點 +黟 +黠 +黡 +黢 +黤 +黥 +黧 +黨 +黩 +黪 +黫 +黭 +黮 +黯 +黰 +黲 +黳 +黴 +黵 +黶 +黷 +黹 +黺 +黻 +黼 +黽 +黾 +黿 +鼀 +鼁 +鼂 +鼃 +鼆 +鼇 +鼈 +鼉 +鼊 +鼋 +鼍 +鼎 +鼏 +鼐 +鼒 +鼓 +鼕 +鼖 +鼗 +鼘 +鼙 +鼛 +鼜 +鼠 +鼢 +鼣 +鼤 +鼥 +鼨 +鼩 +鼪 +鼫 +鼬 +鼭 +鼮 +鼯 +鼰 +鼱 +鼲 +鼳 +鼴 +鼵 +鼶 +鼷 +鼸 +鼹 +鼻 +鼽 +鼾 +齀 +齁 +齂 +齃 +齇 +齉 +齊 +齋 +齌 +齍 +齎 +齏 +齐 +齑 +齒 +齔 +齕 +齖 +齗 +齘 +齙 +齛 +齜 +齝 +齞 +齟 +齠 +齡 +齢 +齣 +齤 +齥 +齦 +齧 +齩 +齪 +齫 +齬 +齮 +齯 +齰 +齱 +齲 +齴 +齵 +齶 +齷 +齸 +齹 +齺 +齻 +齾 +齿 +龀 +龁 +龂 +龃 +龄 +龅 +龆 +龇 +龈 +龉 +龊 +龋 +龌 +龍 +龎 +龐 +龑 +龒 +龔 +龕 +龗 +龙 +龚 +龛 +龜 +龝 +龟 +龠 +龢 +龤 +鿍 +鿎 +鿏 +郎 +凉 +︽ +︾ +﹏ +﹥ +﹪ +! +# +% +& +( +) ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; += +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +| +~ +・ +ヲ +ァ +ィ +ゥ +ェ +ォ +ャ +ュ +ョ +ッ +ア +イ +ウ +エ +オ +カ +キ +ク +ケ +コ +サ +シ +ス +セ +ソ +タ +チ +ツ +テ +ト +ナ +ニ +ヌ +ネ +ノ +ハ +ヒ +フ +ヘ +ホ +マ +ミ +ム +メ +モ +ヤ +ユ +ヨ +ラ +リ +ル +レ +ロ +ワ +ン +゙ +゚ +¥ +𠅤 +𠙶 +𠳐 +𠴰 +𡎚 +𡐓 +𡑍 +𡚁 +𣇉 +𣗋 +𣲗 +𣲘 +𣸣 +𤋮 +𤧛 +𤩽 +𤫉 +𤺥 +𥔲 +𥕢 +𥖨 +𥻗 +𦈡 +𦍑 +𦒍 +𦙶 +𦝼 +𦭜 +𦰡 +𧿹 +𨐈 +𨙸 +𨚕 +𨟠 +𨭉 +𨱇 +𨱏 +𨱑 +𨱔 +𨺙 +𩓐 +𩽾 +𩾃 +𩾌 +𪟝 +𪣻 +𪤗 +𪨰 +𪨶 +𪩘 +𪾢 +𫄧 +𫄨 +𫄷 +𫄸 +𫇭 +𫌀 +𫍣 +𫍯 +𫍲 +𫍽 +𫐄 +𫐐 +𫐓 +𫑡 +𫓧 +𫓯 +𫓶 +𫓹 +𫔍 +𫔎 +𫔶 +𫖮 +𫖯 +𫖳 +𫗧 +𫗴 +𫘜 +𫘝 +𫘦 +𫘧 +𫘨 +𫘪 +𫘬 +𫚕 +𫚖 +𫚭 +𫛭 +𫞩 +𫟅 +𫟦 +𫟹 +𫟼 +𫠆 +𫠊 +𫠜 +𫢸 +𫫇 +𫭟 +𫭢 +𫭼 +𫮃 +𫰛 +𫵷 +𫶇 +𫷷 +𫸩 +𬀩 +𬀪 +𬂩 +𬃊 +𬇕 +𬇙 +𬇹 +𬉼 +𬊈 +𬊤 +𬌗 +𬍛 +𬍡 +𬍤 +𬒈 +𬒔 +𬒗 +𬕂 +𬘓 +𬘘 +𬘡 +𬘩 +𬘫 +𬘬 +𬘭 +𬘯 +𬙂 +𬙊 +𬙋 +𬜬 +𬜯 +𬞟 +𬟁 +𬟽 +𬣙 +𬣞 +𬣡 +𬣳 +𬤇 +𬤊 +𬤝 +𬨂 +𬨎 +𬩽 +𬪩 +𬬩 +𬬭 +𬬮 +𬬱 +𬬸 +𬬹 +𬬻 +𬬿 +𬭁 +𬭊 +𬭎 +𬭚 +𬭛 +𬭤 +𬭩 +𬭬 +𬭯 +𬭳 +𬭶 +𬭸 +𬭼 +𬮱 +𬮿 +𬯀 +𬯎 +𬱖 +𬱟 +𬳵 +𬳶 +𬳽 +𬳿 +𬴂 +𬴃 +𬴊 +𬶋 +𬶍 +𬶏 +𬶐 +𬶟 +𬶠 +𬶨 +𬶭 +𬶮 +𬷕 +𬸘 +𬸚 +𬸣 +𬸦 +𬸪 +𬹼 +𬺈 +𬺓 +🀄 +🃏 +🅰 +🅱 +🅾 +🅿 +🆎 +🆑 +🆒 +🆓 +🆔 +🆕 +🆖 +🆗 +🆘 +🆙 +🆚 +🇨 +🇩 +🇪 +🇫 +🇬 +🇮 +🇯 +🇰 +🇷 +🇺 +🈁 +🌀 +🌁 +🌂 +🌃 +🌄 +🌅 +🌆 +🌇 +🌈 +🌉 +🌊 +🌋 +🌌 +🌍 +🌎 +🌏 +🌐 +🌑 +🌒 +🌓 +🌔 +🌕 +🌖 +🌗 +🌘 +🌙 +🌚 +🌛 +🌜 +🌝 +🌞 +🌟 +🌠 +🌰 +🌱 +🌲 +🌳 +🌴 +🌵 +🌷 +🌸 +🌹 +🌺 +🌻 +🌼 +🌽 +🌾 +🌿 +🍀 +🍁 +🍂 +🍃 +🍄 +🍅 +🍆 +🍇 +🍈 +🍉 +🍊 +🍋 +🍌 +🍍 +🍎 +🍏 +🍐 +🍑 +🍒 +🍓 +🍔 +🍕 +🍖 +🍗 +🍘 +🍙 +🍚 +🍛 +🍜 +🍝 +🍞 +🍟 +🍠 +🍡 +🍢 +🍣 +🍤 +🍥 +🍦 +🍧 +🍨 +🍩 +🍪 +🍫 +🍬 +🍭 +🍮 +🍯 +🍰 +🍱 +🍲 +🍳 +🍴 +🍵 +🍶 +🍷 +🍸 +🍹 +🍺 +🍻 +🍼 +🎀 +🎁 +🎂 +🎃 +🎄 +🎅 +🎆 +🎇 +🎈 +🎉 +🎊 +🎋 +🎌 +🎍 +🎎 +🎏 +🎐 +🎑 +🎒 +🎓 +🎠 +🎡 +🎢 +🎣 +🎤 +🎥 +🎦 +🎧 +🎨 +🎩 +🎪 +🎫 +🎬 +🎭 +🎮 +🎯 +🎰 +🎱 +🎲 +🎳 +🎴 +🎵 +🎶 +🎷 +🎸 +🎹 +🎺 +🎻 +🎼 +🎽 +🎾 +🎿 +🏀 +🏁 +🏂 +🏃 +🏄 +🏆 +🏇 +🏈 +🏉 +🏊 +🏠 +🏡 +🏢 +🏣 +🏤 +🏥 +🏦 +🏧 +🏨 +🏩 +🏪 +🏫 +🏬 +🏭 +🏮 +🏯 +🏰 +🐀 +🐁 +🐂 +🐃 +🐄 +🐅 +🐆 +🐇 +🐈 +🐉 +🐊 +🐋 +🐌 +🐍 +🐎 +🐏 +🐐 +🐑 +🐒 +🐓 +🐔 +🐕 +🐖 +🐗 +🐘 +🐙 +🐚 +🐛 +🐜 +🐝 +🐞 +🐟 +🐠 +🐡 +🐢 +🐣 +🐤 +🐥 +🐦 +🐧 +🐨 +🐩 +🐪 +🐫 +🐬 +🐭 +🐮 +🐯 +🐰 +🐱 +🐲 +🐳 +🐴 +🐵 +🐶 +🐷 +🐸 +🐹 +🐺 +🐻 +🐼 +🐽 +🐾 +👀 +👂 +👃 +👄 +👅 +👆 +👇 +👈 +👉 +👊 +👋 +👌 +👍 +👎 +👏 +👐 +👑 +👒 +👓 +👔 +👕 +👖 +👗 +👘 +👙 +👚 +👛 +👜 +👝 +👞 +👟 +👠 +👡 +👢 +👣 +👤 +👥 +👦 +👧 +👨 +👩 +👪 +👫 +👬 +👭 +👮 +👯 +👰 +👱 +👲 +👳 +👴 +👵 +👶 +👷 +👸 +👹 +👺 +👻 +👼 +👽 +👾 +👿 +💀 +💁 +💂 +💃 +💄 +💅 +💆 +💇 +💈 +💉 +💊 +💋 +💌 +💍 +💎 +💏 +💐 +💑 +💒 +💓 +💔 +💕 +💖 +💗 +💘 +💙 +💚 +💛 +💜 +💝 +💞 +💟 +💠 +💡 +💢 +💣 +💤 +💥 +💦 +💧 +💨 +💩 +💪 +💫 +💬 +💭 +💮 +💯 +💰 +💲 +💳 +💴 +💵 +💶 +💷 +💸 +💹 +💺 +💻 +💼 +💽 +💾 +💿 +📀 +📁 +📂 +📃 +📄 +📅 +📆 +📇 +📈 +📉 +📊 +📋 +📌 +📍 +📎 +📏 +📐 +📑 +📒 +📓 +📔 +📕 +📖 +📗 +📘 +📙 +📚 +📛 +📜 +📝 +📞 +📟 +📠 +📡 +📢 +📣 +📤 +📥 +📦 +📧 +📨 +📩 +📪 +📫 +📬 +📭 +📮 +📯 +📰 +📱 +📲 +📳 +📴 +📵 +📶 +📷 +📹 +📺 +📻 +📼 +🔀 +🔁 +🔂 +🔃 +🔄 +🔅 +🔆 +🔇 +🔉 +🔊 +🔋 +🔌 +🔍 +🔎 +🔏 +🔐 +🔑 +🔒 +🔓 +🔔 +🔕 +🔖 +🔗 +🔘 +🔙 +🔚 +🔛 +🔜 +🔝 +🔞 +🔟 +🔠 +🔡 +🔢 +🔣 +🔤 +🔥 +🔦 +🔧 +🔨 +🔩 +🔪 +🔫 +🔬 +🔭 +🔮 +🔯 +🔰 +🔱 +🔲 +🔳 +🔴 +🔵 +🔶 +🔷 +🔸 +🔹 +🔺 +🔻 +🔼 +🔽 +🕐 +🕑 +🕒 +🕓 +🕔 +🕕 +🕖 +🕗 +🕘 +🕙 +🕚 +🕛 +🕜 +🕝 +🕞 +🕟 +🕠 +🕡 +🕢 +🕣 +🕤 +🕥 +🕦 +🕧 +🗻 +🗼 +🗽 +🗾 +🗿 +😀 +😁 +😂 +😃 +😄 +😅 +😆 +😇 +😈 +😉 +😊 +😋 +😌 +😍 +😎 +😏 +😐 +😑 +😒 +😓 +😔 +😕 +😖 +😗 +😘 +😙 +😚 +😛 +😜 +😝 +😞 +😟 +😠 +😡 +😢 +😣 +😤 +😥 +😦 +😧 +😨 +😩 +😪 +😫 +😬 +😭 +😮 +😯 +😰 +😱 +😲 +😳 +😴 +😵 +😶 +😷 +😸 +😹 +😺 +😻 +😼 +😽 +😾 +😿 +🙀 +🙅 +🙆 +🙇 +🙈 +🙉 +🙊 +🙋 +🙌 +🙍 +🙎 +🙏 +🚀 +🚁 +🚂 +🚃 +🚄 +🚅 +🚆 +🚇 +🚈 +🚉 +🚊 +🚌 +🚍 +🚎 +🚏 +🚐 +🚑 +🚒 +🚓 +🚔 +🚕 +🚖 +🚗 +🚘 +🚙 +🚚 +🚛 +🚜 +🚝 +🚞 +🚟 +🚠 +🚡 +🚢 +🚣 +🚤 +🚥 +🚦 +🚧 +🚨 +🚩 +🚪 +🚫 +🚬 +🚭 +🚮 +🚯 +🚰 +🚱 +🚲 +🚳 +🚴 +🚵 +🚶 +🚷 +🚸 +🚹 +🚺 +🚻 +🚼 +🚽 +🚾 +🚿 +🛀 +🛁 +🛂 +🛃 +🛄 +🛅 diff --git a/ppocr/utils/dict/ppocrv6_tiny_dict.txt b/ppocr/utils/dict/ppocrv6_tiny_dict.txt new file mode 100644 index 0000000..3757d18 --- /dev/null +++ b/ppocr/utils/dict/ppocrv6_tiny_dict.txt @@ -0,0 +1,6904 @@ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +: +; +< += +> +? +@ +[ +\ +] +^ +_ +` +{ +| +} +~ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +¢ +£ +¤ +¥ +¦ +§ +¨ +© +ª +« +¬ +® +¯ +° +± +² +³ +´ +µ +¶ +· +¸ +¹ +º +» +¼ +½ +¾ +¿ +À +Á + +à +Ä +Å +Æ +Ç +È +É +Ê +Ë +Ì +Í +Î +Ï +Ð +Ñ +Ò +Ó +Ô +Õ +Ö +× +Ø +Ù +Ú +Û +Ü +Ý +Þ +ß +à +á +â +ã +ä +å +æ +ç +è +é +ê +ë +ì +í +î +ï +ð +ñ +ò +ó +ô +õ +ö +÷ +ø +ù +ú +û +ü +ý +þ +ÿ +Ā +ā +Ă +ă +Ą +ą +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ď +ď +Đ +đ +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ĥ +ĥ +Ħ +ħ +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +ı +IJ +ij +Ĵ +ĵ +Ķ +ķ +ĸ +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +Ń +ń +Ņ +ņ +Ň +ň +ʼn +Ŋ +ŋ +Ō +ō +Ŏ +ŏ +Ő +ő +Œ +œ +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ţ +ţ +Ť +ť +Ŧ +ŧ +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ŵ +ŵ +Ŷ +ŷ +Ÿ +Ź +ź +Ż +ż +Ž +ž +ſ +ƒ +Α +Β +Γ +Δ +Ε +Ζ +Η +Θ +Ι +Κ +Λ +Μ +Ν +Ξ +Ο +Π +Ρ +Σ +Τ +Υ +Φ +Χ +Ψ +Ω +α +β +γ +δ +ε +ζ +η +θ +ι +κ +λ +μ +ν +ξ +ο +π +ρ +σ +τ +υ +φ +χ +ψ +ω +ϕ +ϵ +Ạ +ồ +– +— +― +‖ +‘ +’ +“ +” +† +‡ +• +‥ +… +‰ +′ +″ +※ +⁎ +⁰ +⁴ +⁵ +⁶ +⁷ +⁸ +⁹ +⁺ +⁻ +⁼ +⁽ +⁾ +ⁿ +₀ +₁ +₂ +₃ +₄ +₅ +₆ +₇ +₈ +₉ +₊ +₋ +₌ +₍ +₎ +ₒ +₠ +₡ +₤ +₧ +₩ +₪ +₫ +€ +₭ +₮ +₱ +₹ +₺ +₽ +₿ +℃ +℉ +ℎ +ℏ +ℑ +℘ +ℜ +™ +℧ +Å +ℵ +ℶ +ℷ +ℸ +ℹ +⅀ +Ⅰ +Ⅱ +Ⅲ +Ⅳ +Ⅴ +Ⅵ +Ⅶ +Ⅷ +Ⅸ +Ⅹ +Ⅺ +Ⅻ +ⅰ +ⅱ +ⅲ +ⅳ +ⅴ +ⅵ +ⅶ +ⅷ +ⅸ +ⅹ +ⅺ +ⅻ +← +↑ +→ +↓ +↔ +↕ +↖ +↗ +↘ +↙ +⇐ +⇑ +⇒ +⇓ +⇔ +⇕ +∀ +∂ +∃ +∅ +∆ +∇ +∈ +∉ +∏ +∑ +− +∓ +∕ +∙ +√ +∝ +∞ +∠ +∥ +∧ +∨ +∩ +∪ +∫ +∬ +∮ +∴ +∵ +∶ +∷ +∼ +∽ +≈ +≌ +≒ +≠ +≡ +≤ +≥ +≦ +≧ +≪ +≫ +≮ +≯ +⊂ +⊃ +⊆ +⊇ +⊊ +⊋ +⊕ +⊗ +⊙ +⊥ +⋅ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +⑪ +⑫ +⑬ +⑭ +⑮ +⑯ +⑰ +⑱ +⑲ +⑳ +■ +□ +▲ +△ +▶ +▼ +▽ +◀ +◆ +◇ +○ +◎ +● +☀ +☁ +☂ +★ +☆ +☎ +☐ +☑ +☒ +☔ +☕ +☠ +♀ +♂ +♠ +♡ +♣ +♤ +♥ +♦ +♻ +⚓ +⚠ +⚡ +✓ +✔ +✕ +✖ +✘ +✚ +✠ +✨ +✳ +✴ +✶ +✽ +❄ +❌ +❎ +❓ +❤ +  +、 +。 +々 +〆 +〇 +〈 +〉 +《 +》 +「 +」 +『 +』 +【 +】 +〒 +〔 +〕 +〖 +〗 +〜 +〰 +〵 +〽 +㈱ +㊗ +㊙ +一 +丁 +七 +丄 +万 +丈 +三 +上 +下 +不 +与 +丐 +丑 +专 +且 +丕 +世 +丘 +丙 +业 +丛 +东 +丝 +丞 +両 +丢 +两 +严 +丧 +个 +丫 +中 +丰 +串 +临 +丸 +丹 +为 +主 +丼 +丽 +举 +丿 +乂 +乃 +久 +么 +义 +之 +乌 +乍 +乎 +乏 +乐 +乒 +乓 +乔 +乖 +乘 +乙 +乜 +九 +乞 +也 +习 +乡 +书 +买 +乱 +乳 +乸 +乾 +了 +予 +争 +事 +二 +亍 +于 +亏 +云 +互 +亓 +五 +井 +亘 +亚 +些 +亜 +亞 +亟 +亡 +亢 +交 +亥 +亦 +产 +亨 +亩 +享 +京 +亭 +亮 +亲 +亳 +亵 +人 +亿 +什 +仁 +仃 +仄 +仅 +仆 +仇 +今 +介 +仍 +从 +仑 +仓 +仔 +仕 +他 +仗 +付 +仙 +仝 +仞 +仟 +仡 +代 +令 +以 +仨 +仪 +们 +仰 +仲 +件 +价 +仺 +任 +份 +仿 +企 +伈 +伉 +伊 +伍 +伎 +伏 +伐 +休 +众 +优 +伙 +会 +伞 +伟 +传 +伤 +伦 +伪 +伫 +伯 +估 +伱 +伴 +伶 +伸 +伺 +似 +伽 +佃 +但 +位 +低 +住 +佐 +佑 +体 +何 +佗 +佘 +余 +佚 +佛 +作 +佝 +佞 +佟 +你 +佣 +佤 +佧 +佩 +佬 +佯 +佰 +佳 +佶 +佼 +使 +侃 +侄 +來 +侈 +例 +侍 +侏 +侑 +侗 +供 +依 +侠 +侣 +侥 +侦 +侧 +侨 +侩 +侬 +侮 +侯 +侵 +便 +係 +促 +俄 +俊 +俎 +俏 +俐 +俑 +俗 +俘 +俚 +保 +俞 +俟 +信 +俨 +俩 +俪 +俫 +俬 +俭 +修 +俯 +俱 +俵 +俸 +俺 +俾 +倆 +倉 +個 +倌 +倍 +倏 +們 +倒 +倔 +倘 +候 +倚 +倜 +借 +倡 +倦 +倩 +倪 +倫 +倬 +倭 +债 +值 +倾 +偃 +假 +偈 +偉 +偌 +偎 +偏 +偕 +做 +停 +健 +偶 +偷 +偻 +偽 +偿 +傀 +傅 +傈 +傍 +備 +傢 +傣 +傥 +储 +傩 +催 +傲 +傳 +傷 +傻 +像 +僑 +僚 +僧 +僳 +僵 +價 +僻 +儀 +億 +儋 +儒 +儡 +儥 +優 +儱 +儿 +兀 +允 +元 +兄 +充 +兆 +先 +光 +克 +免 +兑 +兒 +兔 +兖 +党 +兜 +兢 +入 +內 +全 +兩 +八 +公 +六 +兮 +兰 +共 +关 +兴 +兵 +其 +具 +典 +兹 +养 +兼 +兽 +兿 +冀 +内 +冈 +冉 +册 +再 +冏 +冒 +冕 +冗 +写 +冚 +军 +农 +冠 +冢 +冤 +冥 +冬 +冮 +冯 +冰 +冲 +决 +况 +冶 +冷 +冻 +冼 +冽 +净 +凄 +准 +凇 +凈 +凉 +凋 +凌 +凍 +减 +凑 +凖 +凛 +凝 +几 +凡 +凤 +処 +凬 +凭 +凯 +凰 +凱 +凳 +凶 +凸 +凹 +出 +击 +函 +凿 +刀 +刁 +刂 +刃 +分 +切 +刈 +刊 +刍 +刑 +划 +列 +刘 +则 +刚 +创 +初 +删 +判 +刨 +利 +刪 +别 +刮 +到 +制 +刷 +券 +刹 +刺 +刻 +刿 +剁 +剂 +剃 +削 +剌 +前 +剎 +剐 +剑 +剔 +剖 +剡 +剥 +剧 +剩 +剪 +副 +割 +創 +剽 +剿 +劇 +劈 +劉 +劍 +劑 +力 +劝 +办 +功 +加 +务 +劣 +动 +助 +努 +劫 +劭 +励 +劲 +劳 +劵 +劾 +势 +勁 +勃 +勅 +勇 +勉 +勋 +勐 +勑 +勒 +動 +勘 +務 +勝 +募 +勤 +勰 +勺 +勾 +勿 +匀 +包 +匆 +匈 +匋 +匍 +匐 +匕 +化 +北 +匙 +匝 +匠 +匡 +匣 +匪 +匮 +匯 +匹 +区 +医 +匾 +匿 +區 +十 +千 +卅 +升 +午 +卉 +半 +华 +协 +卑 +卒 +卓 +協 +单 +卖 +南 +博 +卜 +卞 +卟 +占 +卡 +卢 +卤 +卦 +卧 +卫 +卯 +印 +危 +即 +却 +卵 +卷 +卸 +卿 +厂 +厄 +厅 +历 +厉 +压 +厌 +厍 +厕 +厘 +厚 +厝 +原 +厢 +厥 +厦 +厨 +厩 +厮 +去 +县 +叁 +参 +參 +又 +叉 +及 +友 +双 +反 +发 +叔 +取 +受 +变 +叙 +叛 +叟 +叠 +叢 +口 +古 +句 +另 +叨 +叩 +只 +叫 +召 +叭 +叮 +可 +台 +叱 +史 +右 +叶 +号 +司 +叹 +叻 +叼 +叽 +吁 +吃 +各 +吆 +合 +吉 +吊 +吋 +同 +名 +后 +吏 +吐 +向 +吒 +吓 +吕 +吖 +吗 +君 +吝 +吞 +吟 +吠 +吡 +否 +吧 +吨 +吩 +含 +听 +吭 +吮 +启 +吱 +吲 +吴 +吵 +吸 +吹 +吻 +吼 +吾 +呀 +呃 +呆 +呈 +告 +呋 +呐 +呓 +呕 +呗 +员 +呙 +呛 +呜 +呢 +呤 +呦 +周 +呱 +味 +呵 +呷 +呸 +呻 +呼 +命 +咀 +咂 +咄 +咅 +咆 +咋 +和 +咎 +咏 +咐 +咒 +咔 +咕 +咖 +咙 +咚 +咛 +咣 +咤 +咦 +咧 +咨 +咩 +咪 +咫 +咬 +咭 +咯 +咱 +咳 +咸 +咻 +咽 +咿 +哀 +品 +哄 +哆 +哇 +哈 +哉 +哌 +响 +哎 +哑 +哒 +哓 +哔 +哗 +哚 +哝 +哞 +哟 +員 +哥 +哦 +哧 +哨 +哩 +哪 +哭 +哮 +哲 +哺 +哼 +哽 +唁 +唆 +唇 +唉 +唏 +唐 +唑 +唔 +唘 +唛 +唠 +唢 +唤 +唧 +唬 +售 +唯 +唰 +唱 +唳 +唷 +唻 +唾 +啃 +啄 +商 +啉 +啊 +問 +啓 +啕 +啖 +啜 +啟 +啡 +啤 +啥 +啦 +啧 +啪 +啫 +啬 +啮 +啰 +啵 +啶 +啸 +啻 +啼 +啾 +喀 +喂 +喃 +善 +喆 +喇 +喉 +喊 +喋 +喏 +喔 +喘 +喙 +喜 +喝 +喧 +喫 +喬 +單 +喰 +喱 +喳 +喵 +喷 +喹 +喻 +喽 +嗅 +嗉 +嗑 +嗒 +嗓 +嗔 +嗖 +嗜 +嗝 +嗞 +嗟 +嗡 +嗣 +嗤 +嗦 +嗨 +嗪 +嗬 +嗮 +嗯 +嗲 +嗳 +嗷 +嗽 +嘀 +嘈 +嘉 +嘌 +嘎 +嘘 +嘛 +嘞 +嘟 +嘢 +嘣 +嘤 +嘧 +嘬 +嘱 +嘲 +嘴 +嘶 +嘹 +嘻 +嘿 +噌 +噎 +噗 +噜 +噢 +噤 +器 +噩 +噪 +噬 +噱 +噶 +噻 +噼 +嚎 +嚏 +嚒 +嚓 +嚞 +嚣 +嚷 +嚼 +囊 +囍 +囔 +囗 +囚 +四 +回 +囟 +因 +囡 +团 +囤 +园 +困 +囱 +围 +囹 +固 +国 +图 +囿 +圃 +圆 +圈 +國 +圍 +園 +圖 +圗 +團 +圜 +土 +圣 +圧 +在 +圩 +圭 +地 +圳 +圹 +场 +圻 +圾 +址 +坂 +均 +坊 +坋 +坍 +坎 +坏 +坐 +坑 +块 +坚 +坛 +坝 +坞 +坟 +坠 +坡 +坤 +坦 +坨 +坩 +坪 +坫 +坭 +坯 +坳 +坶 +坷 +坻 +垂 +垃 +垄 +型 +垌 +垒 +垓 +垚 +垛 +垟 +垠 +垡 +垢 +垣 +垦 +垩 +垫 +垭 +垮 +垸 +埂 +埃 +埋 +埌 +城 +埔 +埕 +埗 +埚 +域 +埠 +埭 +執 +培 +基 +堀 +堂 +堃 +堆 +堇 +堑 +堕 +堡 +堤 +堪 +堰 +報 +場 +堵 +堷 +塊 +塌 +塍 +塑 +塔 +塗 +塘 +塞 +填 +塬 +塾 +境 +墅 +墉 +墊 +墒 +墓 +墙 +增 +墟 +墨 +墩 +壁 +壅 +壇 +壐 +壑 +壓 +壕 +壤 +士 +壬 +壮 +声 +壳 +壴 +壶 +壹 +壽 +夀 +处 +夆 +备 +夋 +夌 +复 +夏 +夔 +夕 +外 +夙 +多 +夜 +够 +夢 +夥 +大 +天 +太 +夫 +夬 +夭 +央 +夯 +失 +头 +夷 +夸 +夹 +夺 +奂 +奄 +奇 +奈 +奉 +奋 +奎 +奏 +契 +奓 +奔 +奕 +奖 +套 +奘 +奚 +奠 +奢 +奥 +奧 +女 +奴 +奶 +奸 +她 +好 +如 +妃 +妄 +妆 +妇 +妈 +妊 +妍 +妒 +妓 +妖 +妗 +妙 +妝 +妞 +妤 +妥 +妨 +妩 +妪 +妫 +妮 +妯 +妲 +妳 +妹 +妻 +妾 +姆 +姊 +始 +姌 +姐 +姑 +姒 +姓 +委 +姗 +姘 +姚 +姜 +姝 +姣 +姥 +姨 +姬 +姹 +姻 +姿 +威 +娃 +娄 +娅 +娆 +娇 +娈 +娉 +娌 +娑 +娓 +娘 +娜 +娟 +娠 +娣 +娥 +娩 +娱 +娲 +娴 +娶 +娼 +娽 +婀 +婆 +婉 +婊 +婕 +婚 +婢 +婦 +婧 +婪 +婴 +婵 +婶 +婷 +婺 +婿 +媄 +媒 +媚 +媛 +媲 +媳 +媽 +媾 +嫁 +嫂 +嫉 +嫌 +嫒 +嫔 +嫖 +嫚 +嫡 +嫣 +嫦 +嫩 +嬉 +嬗 +嬰 +嬴 +嬷 +孃 +子 +孑 +孓 +孔 +孕 +孖 +字 +存 +孙 +孚 +孛 +孜 +孝 +孟 +孢 +季 +孤 +学 +孩 +孪 +孫 +孬 +孰 +孳 +孵 +學 +孺 +孽 +宁 +它 +宄 +宅 +宇 +守 +安 +宋 +完 +宏 +宓 +宕 +宗 +官 +宙 +定 +宛 +宜 +宝 +实 +宠 +审 +客 +宣 +室 +宥 +宦 +宪 +宫 +宰 +害 +宴 +宵 +家 +宸 +容 +宽 +宾 +宿 +寂 +寄 +寅 +密 +寇 +富 +寐 +寒 +寓 +寕 +寝 +寞 +察 +寡 +寤 +寥 +實 +寧 +寨 +寫 +寬 +寮 +寰 +寳 +寵 +寶 +寸 +对 +寺 +寻 +导 +対 +寿 +封 +専 +尃 +射 +将 +將 +專 +尉 +尊 +對 +導 +小 +少 +尓 +尔 +尕 +尖 +尘 +尙 +尚 +尝 +尤 +尧 +尬 +就 +尴 +尷 +尸 +尹 +尺 +尻 +尼 +尽 +尾 +尿 +局 +屁 +层 +居 +屆 +屈 +屉 +届 +屋 +屌 +屎 +屏 +屐 +屑 +展 +属 +屠 +屡 +層 +履 +屯 +山 +屹 +屿 +岁 +岂 +岌 +岐 +岑 +岔 +岖 +岗 +岘 +岙 +岚 +岛 +岢 +岩 +岫 +岭 +岱 +岳 +岷 +岸 +岽 +岿 +峁 +峄 +峋 +峒 +峙 +峡 +峤 +峥 +峦 +峨 +峩 +峪 +峭 +峯 +峰 +島 +峻 +崂 +崃 +崆 +崇 +崋 +崎 +崔 +崖 +崛 +崤 +崧 +崩 +崬 +崭 +崮 +崴 +崽 +嵇 +嵊 +嵋 +嵌 +嵐 +嵘 +嵩 +嵬 +嵯 +嶂 +嶙 +嶪 +嶺 +嶽 +巅 +巍 +川 +州 +巡 +巢 +工 +左 +巧 +巨 +巩 +巫 +差 +巯 +己 +已 +巳 +巴 +巷 +巽 +巾 +币 +市 +布 +帅 +帆 +师 +希 +帏 +帐 +帑 +帕 +帖 +帘 +帚 +帛 +帜 +帝 +帥 +带 +帧 +師 +席 +帮 +帯 +帰 +帶 +帷 +常 +帼 +帽 +幂 +幄 +幅 +幌 +幔 +幕 +幚 +幛 +幡 +幢 +干 +平 +年 +并 +幸 +幹 +幺 +幻 +幼 +幽 +广 +庄 +庆 +庇 +床 +序 +庐 +库 +应 +底 +庖 +店 +庙 +庚 +府 +庞 +废 +庠 +庥 +度 +座 +庫 +庭 +庵 +庶 +康 +庸 +庹 +庾 +廉 +廊 +廓 +廖 +廚 +廠 +廣 +廰 +廳 +延 +廷 +建 +廿 +开 +异 +弃 +弄 +弈 +弊 +弋 +弍 +式 +弑 +弓 +引 +弗 +弘 +弛 +弟 +张 +弥 +弦 +弧 +弩 +弭 +弯 +弱 +張 +強 +弹 +强 +弼 +彈 +彐 +归 +当 +录 +彗 +彝 +形 +彤 +彦 +彧 +彩 +彪 +彬 +彭 +彰 +影 +彳 +彷 +役 +彻 +彼 +往 +征 +径 +待 +徇 +很 +徉 +徊 +律 +後 +徐 +徒 +徕 +得 +徘 +徙 +徜 +御 +徨 +復 +循 +微 +徳 +徵 +德 +徹 +徽 +心 +必 +忆 +忌 +忍 +忏 +忐 +忑 +忒 +忖 +志 +忘 +忙 +応 +忠 +忡 +忧 +快 +忱 +念 +忻 +忽 +忿 +怀 +态 +怂 +怅 +怆 +怎 +怏 +怒 +怔 +怕 +怖 +怜 +思 +怠 +怡 +急 +怦 +性 +怨 +怪 +怯 +总 +恂 +恃 +恋 +恍 +恐 +恒 +恕 +恙 +恢 +恣 +恤 +恨 +恩 +恪 +恬 +恭 +息 +恰 +恳 +恶 +恸 +恹 +恺 +恻 +恼 +恽 +恿 +悄 +悅 +悉 +悌 +悍 +悔 +悖 +悚 +悟 +悠 +患 +悦 +您 +悬 +悯 +悱 +悲 +悴 +悵 +悶 +悸 +悻 +悼 +情 +惆 +惊 +惋 +惑 +惕 +惘 +惚 +惜 +惟 +惠 +惦 +惧 +惨 +惩 +惫 +惬 +惭 +惮 +惯 +惰 +想 +惴 +惶 +惹 +惺 +愁 +愈 +愉 +意 +愕 +愚 +愛 +感 +愣 +愤 +愧 +愫 +愿 +慈 +態 +慌 +慎 +慑 +慕 +慢 +慧 +慨 +慰 +慵 +慶 +慷 +憋 +憎 +憔 +憧 +憨 +憩 +憬 +憶 +憾 +懂 +懈 +應 +懊 +懋 +懒 +懦 +懵 +懷 +懿 +戀 +戈 +戊 +戋 +戌 +戍 +戎 +戏 +成 +我 +戒 +或 +战 +戚 +戛 +戟 +戢 +截 +戬 +戮 +戳 +戴 +戶 +户 +戾 +房 +所 +扁 +扇 +扈 +扉 +手 +才 +扎 +扑 +扒 +打 +扔 +托 +扙 +扛 +扞 +扣 +扦 +执 +扩 +扪 +扫 +扬 +扭 +扮 +扯 +扰 +扳 +扶 +批 +扼 +找 +承 +技 +抄 +抉 +把 +抑 +抒 +抓 +投 +抖 +抗 +折 +抚 +抛 +抠 +抡 +抢 +护 +报 +抨 +披 +抬 +抱 +抵 +抹 +抻 +押 +抽 +抿 +拂 +拄 +担 +拆 +拇 +拈 +拉 +拌 +拍 +拎 +拐 +拒 +拓 +拔 +拖 +拗 +拘 +拙 +拚 +招 +拜 +拟 +拢 +拣 +拥 +拦 +拧 +拨 +择 +括 +拭 +拮 +拯 +拱 +拳 +拴 +拷 +拼 +拽 +拾 +拿 +持 +挂 +指 +按 +挎 +挑 +挖 +挚 +挛 +挝 +挞 +挟 +挠 +挡 +挣 +挤 +挥 +挨 +挪 +挫 +振 +挲 +挺 +挽 +捂 +捅 +捆 +捉 +捋 +捌 +捍 +捎 +捏 +捐 +捕 +捞 +损 +捡 +换 +捣 +捧 +据 +捲 +捶 +捷 +捺 +捻 +掀 +掂 +掇 +授 +掉 +掊 +掌 +掏 +掐 +排 +掖 +掘 +掠 +探 +掣 +接 +控 +推 +掩 +措 +掬 +掰 +掳 +掴 +掷 +掸 +掺 +揉 +揍 +描 +提 +插 +揖 +揚 +換 +握 +揣 +揩 +揪 +揭 +援 +揸 +揽 +搀 +搁 +搂 +搅 +損 +搏 +搐 +搓 +搔 +搜 +搞 +搪 +搬 +搭 +搴 +携 +搽 +摁 +摄 +摆 +摇 +摈 +摊 +摒 +摔 +摘 +摞 +摧 +摩 +摸 +摹 +撂 +撃 +撅 +撇 +撑 +撒 +撕 +撞 +撤 +撩 +撬 +播 +撮 +撰 +撵 +撷 +撸 +撻 +撼 +擀 +擂 +擅 +操 +擎 +擒 +擘 +據 +擞 +擢 +擦 +攀 +攒 +攘 +攝 +攥 +攫 +支 +收 +攸 +改 +攻 +放 +政 +故 +效 +敌 +敏 +救 +敕 +敖 +教 +敛 +敝 +敞 +敟 +敢 +散 +敦 +敬 +数 +敲 +整 +敷 +數 +文 +斋 +斌 +斐 +斑 +斓 +斗 +料 +斛 +斜 +斟 +斡 +斤 +斥 +斧 +斩 +断 +斯 +新 +方 +於 +施 +旁 +旅 +旋 +旌 +旎 +族 +旖 +旗 +无 +既 +日 +旦 +旧 +旨 +早 +旬 +旭 +旮 +旯 +旱 +时 +旷 +旸 +旺 +昀 +昂 +昆 +昇 +昊 +昌 +明 +昏 +易 +昔 +昕 +昙 +昝 +星 +映 +春 +昧 +昨 +昭 +是 +昱 +昴 +昵 +昶 +昼 +显 +晁 +時 +晃 +晋 +晌 +晏 +晒 +晓 +晔 +晕 +晖 +晗 +晚 +晞 +晟 +晤 +晦 +晧 +晨 +普 +景 +晰 +晳 +晴 +晶 +晷 +智 +晾 +暂 +暃 +暄 +暇 +暑 +暖 +暗 +暢 +暧 +暨 +暮 +暴 +暹 +曉 +曙 +曜 +曝 +曦 +曬 +曰 +曲 +曳 +更 +書 +曹 +曼 +曾 +替 +最 +會 +月 +有 +朋 +服 +朐 +朔 +朕 +朗 +望 +朝 +期 +朦 +木 +未 +末 +本 +札 +术 +朱 +朴 +朵 +机 +朽 +朿 +杀 +杂 +权 +杆 +杈 +杉 +李 +杏 +材 +村 +杖 +杜 +杞 +束 +杠 +条 +来 +杨 +杩 +杭 +杯 +杰 +東 +杲 +杳 +杵 +杷 +松 +板 +极 +构 +枇 +枉 +枋 +析 +枕 +林 +枚 +果 +枝 +枞 +枢 +枣 +枪 +枫 +枭 +枯 +枰 +枱 +枳 +架 +枷 +枸 +枼 +柄 +柏 +某 +柑 +柒 +染 +柔 +柘 +柚 +柜 +柞 +柠 +查 +柩 +柬 +柯 +柱 +柳 +柴 +柿 +栀 +栅 +标 +栈 +栉 +栋 +栎 +栏 +树 +栓 +栖 +栗 +校 +栩 +株 +样 +核 +根 +格 +栽 +栾 +桀 +桁 +桂 +桃 +桅 +框 +案 +桉 +桌 +桎 +桐 +桑 +桓 +桔 +桖 +桠 +桡 +桢 +档 +桥 +桦 +桧 +桨 +桩 +桶 +桷 +桼 +梁 +梅 +梆 +梏 +梓 +梗 +條 +梢 +梦 +梧 +梨 +梭 +梯 +械 +梳 +梵 +检 +棉 +棋 +棍 +棒 +棕 +棘 +棚 +棛 +棟 +棠 +棣 +森 +棰 +棱 +棵 +棺 +棽 +椁 +椅 +椋 +植 +椎 +椐 +椒 +椤 +椭 +椰 +椴 +椹 +椽 +椿 +楂 +楊 +楓 +楔 +楗 +楚 +楝 +楞 +楠 +楣 +楦 +業 +極 +楷 +楸 +楹 +楼 +楽 +楿 +概 +榄 +榆 +榈 +榉 +榔 +榕 +榛 +榜 +榞 +榧 +榨 +榫 +榭 +榮 +榴 +榷 +榻 +構 +槌 +槎 +槐 +様 +槛 +槟 +槲 +槽 +槿 +樂 +樊 +樓 +標 +樟 +模 +樣 +樨 +横 +樱 +樵 +樸 +樹 +樽 +樾 +橄 +橇 +橋 +橘 +橙 +機 +橡 +橪 +橱 +橹 +橼 +檀 +檐 +檔 +檗 +檩 +檬 +櫥 +權 +欠 +次 +欢 +欣 +欧 +欲 +欺 +款 +歆 +歇 +歉 +歌 +歐 +歙 +歡 +止 +正 +此 +步 +武 +歧 +歪 +歷 +歸 +歹 +死 +歼 +殁 +殃 +殆 +殇 +殉 +殊 +残 +殒 +殖 +殚 +殡 +殴 +段 +殷 +殺 +殼 +殿 +毁 +毂 +毅 +毋 +母 +每 +毒 +毓 +比 +毕 +毗 +毙 +毛 +毡 +毫 +毯 +毽 +氏 +氐 +民 +氓 +气 +氖 +気 +氘 +氙 +氚 +氛 +氟 +氡 +氢 +氣 +氤 +氦 +氧 +氨 +氩 +氪 +氮 +氯 +氰 +氲 +水 +永 +氽 +汀 +汁 +求 +汇 +汉 +汊 +汐 +汕 +汗 +汛 +汝 +汞 +江 +池 +污 +汤 +汨 +汩 +汪 +汰 +汲 +汴 +汶 +汹 +決 +汽 +汾 +沁 +沂 +沃 +沅 +沈 +沉 +沌 +沏 +沐 +沓 +沔 +沙 +沛 +沟 +没 +沣 +沤 +沥 +沦 +沧 +沪 +沫 +沭 +沮 +沱 +河 +沸 +油 +治 +沼 +沽 +沾 +沿 +泄 +泅 +泉 +泊 +泌 +泓 +泔 +法 +泗 +泛 +泞 +泠 +泡 +波 +泣 +泥 +注 +泩 +泪 +泫 +泮 +泯 +泰 +泱 +泳 +泵 +泷 +泸 +泺 +泻 +泼 +泽 +泾 +洁 +洄 +洋 +洒 +洗 +洙 +洛 +洞 +津 +洪 +洮 +洱 +洲 +洳 +洵 +洺 +活 +洼 +洽 +派 +流 +浃 +浅 +浆 +浇 +浈 +浊 +测 +浍 +济 +浏 +浐 +浑 +浒 +浓 +浔 +浙 +浚 +浜 +浠 +浣 +浥 +浦 +浩 +浪 +浮 +浴 +海 +浸 +浼 +涂 +涅 +涇 +消 +涉 +涌 +涎 +涓 +涔 +涕 +涛 +涝 +涞 +涟 +涠 +涡 +涣 +涤 +润 +涧 +涨 +涩 +涪 +涫 +涮 +涯 +液 +涵 +涸 +涿 +淀 +淄 +淅 +淆 +淇 +淋 +淌 +淑 +淖 +淘 +淙 +淚 +淝 +淞 +淡 +淤 +淦 +淨 +淩 +淫 +淬 +淮 +深 +淳 +混 +淹 +添 +淼 +清 +渊 +渌 +渍 +渎 +渐 +渑 +渔 +渗 +渚 +渝 +渠 +渡 +渣 +渤 +渥 +温 +測 +渭 +港 +渲 +渴 +游 +渺 +湃 +湄 +湍 +湓 +湖 +湘 +湛 +湟 +湫 +湮 +湯 +湾 +湿 +溃 +溅 +溆 +溉 +溏 +源 +溜 +溝 +溟 +溢 +溥 +溧 +溪 +溫 +溯 +溱 +溴 +溶 +溺 +滁 +滂 +滇 +滋 +滏 +滑 +滓 +滔 +滕 +滘 +滙 +滚 +滞 +滟 +满 +滢 +滤 +滥 +滦 +滨 +滩 +滬 +滴 +滿 +漁 +漂 +漆 +漉 +漏 +漓 +演 +漕 +漠 +漢 +漩 +漪 +漫 +漯 +漱 +漳 +漾 +潇 +潋 +潍 +潔 +潘 +潜 +潞 +潢 +潤 +潦 +潭 +潮 +潴 +潸 +潺 +潼 +澄 +澈 +澍 +澎 +澜 +澡 +澤 +澧 +澳 +澶 +澹 +激 +濂 +濃 +濉 +濑 +濒 +濕 +濞 +濟 +濠 +濡 +濤 +濮 +濯 +濱 +瀑 +瀘 +瀚 +瀛 +瀣 +瀵 +瀹 +灌 +灏 +灞 +灡 +灣 +火 +灭 +灯 +灰 +灵 +灶 +灸 +灼 +灾 +灿 +炀 +炅 +炉 +炊 +炎 +炒 +炔 +炕 +炖 +炙 +炜 +炝 +炫 +炬 +炭 +炮 +炯 +炳 +炸 +点 +為 +炼 +炽 +烁 +烂 +烃 +烈 +烊 +烏 +烘 +烙 +烛 +烜 +烟 +烤 +烦 +烧 +烨 +烩 +烫 +烬 +热 +烯 +烷 +烹 +烺 +烽 +焉 +焊 +焕 +焖 +焗 +焘 +焙 +焚 +無 +焦 +焯 +焰 +焱 +然 +煅 +煊 +煌 +煎 +煖 +煙 +煜 +煞 +煤 +煥 +煦 +照 +煨 +煮 +煲 +煸 +煽 +熄 +熊 +熏 +熔 +熘 +熙 +熟 +熠 +熥 +熨 +熬 +熱 +熳 +熵 +熹 +燁 +燃 +燈 +燎 +燒 +燕 +燘 +燙 +燚 +燜 +營 +燥 +燦 +燧 +燮 +燴 +爆 +爍 +爐 +爨 +爪 +爬 +爭 +爯 +爰 +爱 +爲 +爵 +父 +爷 +爸 +爹 +爺 +爻 +爽 +爾 +片 +版 +牌 +牍 +牒 +牙 +牛 +牟 +牡 +牢 +牦 +牧 +物 +牯 +牲 +牵 +特 +牺 +犀 +犁 +犇 +犊 +犍 +犟 +犬 +犯 +状 +犷 +犸 +犹 +狀 +狂 +狄 +狈 +狍 +狐 +狒 +狗 +狙 +狞 +狠 +狡 +狩 +独 +狭 +狮 +狰 +狱 +狸 +狼 +猎 +猕 +猖 +猗 +猛 +猜 +猝 +猥 +猩 +猪 +猫 +猬 +献 +猴 +猷 +猾 +猿 +獅 +獐 +獒 +獗 +獠 +獨 +獭 +獲 +獸 +獻 +玄 +率 +玉 +王 +玑 +玖 +玛 +玟 +玥 +玩 +玫 +玮 +环 +现 +玲 +玳 +玷 +玺 +玻 +珀 +珂 +珈 +珉 +珊 +珍 +珏 +珐 +珑 +珙 +珞 +珠 +珣 +珥 +珩 +班 +珲 +珺 +現 +球 +琅 +理 +琉 +琊 +琏 +琐 +琚 +琛 +琢 +琥 +琦 +琨 +琪 +琬 +琮 +琯 +琰 +琳 +琴 +琵 +琶 +琼 +瑁 +瑄 +瑕 +瑗 +瑙 +瑚 +瑛 +瑜 +瑞 +瑟 +瑩 +瑪 +瑭 +瑰 +瑶 +瑷 +瑾 +璀 +璁 +璃 +璇 +璈 +璋 +璎 +璐 +璜 +璞 +璟 +璧 +璨 +璩 +環 +璺 +瓒 +瓜 +瓠 +瓢 +瓣 +瓤 +瓦 +瓮 +瓯 +瓴 +瓶 +瓷 +瓿 +甄 +甘 +甙 +甚 +甜 +生 +產 +産 +甥 +用 +甩 +甫 +甬 +甭 +甯 +田 +由 +甲 +申 +电 +男 +甸 +甹 +町 +画 +甾 +畅 +畈 +界 +畏 +畐 +畔 +留 +畜 +略 +畦 +番 +畫 +畲 +畴 +畵 +當 +畸 +畹 +畿 +疃 +疆 +疌 +疏 +疑 +疖 +疗 +疙 +疚 +疝 +疟 +疡 +疣 +疤 +疥 +疫 +疮 +疯 +疱 +疲 +疳 +疵 +疸 +疹 +疼 +疽 +疾 +痂 +病 +症 +痈 +痉 +痊 +痒 +痔 +痕 +痘 +痛 +痞 +痠 +痢 +痣 +痤 +痧 +痨 +痪 +痫 +痰 +痱 +痴 +痹 +痼 +痿 +瘀 +瘁 +瘊 +瘘 +瘙 +瘟 +瘠 +瘢 +瘤 +瘦 +瘩 +瘪 +瘫 +瘴 +瘸 +瘾 +瘿 +療 +癌 +癔 +癖 +癜 +癞 +癣 +癫 +癸 +登 +發 +白 +百 +皂 +的 +皆 +皇 +皈 +皋 +皎 +皑 +皓 +皖 +皙 +皮 +皱 +皲 +皴 +皺 +皿 +盂 +盅 +盆 +盈 +益 +盎 +盏 +盐 +监 +盒 +盔 +盖 +盗 +盘 +盛 +盜 +盟 +盤 +盥 +目 +盯 +盱 +盲 +直 +相 +盹 +盼 +盾 +省 +眈 +眉 +看 +眙 +眞 +真 +眠 +眦 +眨 +眩 +眭 +眯 +眶 +眷 +眸 +眺 +眼 +着 +睁 +睇 +睐 +睑 +睛 +睡 +睢 +督 +睦 +睨 +睫 +睬 +睹 +睽 +睾 +睿 +瞄 +瞅 +瞌 +瞎 +瞑 +瞒 +瞓 +瞟 +瞠 +瞥 +瞧 +瞩 +瞪 +瞬 +瞭 +瞰 +瞳 +瞻 +瞿 +矗 +矛 +矜 +矞 +矢 +矣 +知 +矩 +矫 +短 +矮 +石 +矶 +矸 +矽 +矾 +矿 +砀 +码 +砂 +砌 +砍 +砒 +研 +砖 +砚 +砜 +砝 +砣 +砥 +砦 +砧 +砬 +砭 +砰 +破 +砷 +砸 +砺 +砻 +砼 +砾 +础 +硂 +硅 +硒 +硕 +硖 +硚 +硝 +硪 +硫 +硬 +确 +硼 +碁 +碇 +碉 +碌 +碍 +碎 +碑 +碓 +碗 +碘 +碚 +碟 +碣 +碧 +碩 +碰 +碱 +碲 +碳 +碴 +碶 +確 +碼 +碾 +磁 +磅 +磊 +磋 +磐 +磕 +磚 +磨 +磬 +磴 +磷 +磺 +礁 +礴 +示 +礼 +社 +祀 +祁 +祈 +祉 +祎 +祐 +祖 +祗 +祙 +祚 +祛 +祜 +祝 +神 +祟 +祠 +祢 +祥 +票 +祭 +祯 +祷 +祸 +祺 +禀 +禁 +禄 +禅 +福 +禦 +禧 +禮 +禹 +禺 +离 +禽 +禾 +秀 +私 +秃 +秆 +秉 +秋 +种 +科 +秒 +秕 +秘 +租 +秣 +秤 +秦 +秧 +秩 +秭 +积 +称 +秸 +移 +秽 +稀 +程 +稍 +税 +稔 +稗 +稚 +稞 +稠 +稣 +種 +稳 +稷 +稹 +稻 +稼 +稽 +稿 +穂 +穆 +穗 +穩 +穰 +穴 +穵 +究 +穷 +穸 +穹 +空 +穿 +突 +窃 +窄 +窈 +窍 +窑 +窒 +窕 +窖 +窗 +窘 +窜 +窝 +窟 +窠 +窥 +窦 +窨 +窿 +立 +竖 +站 +竞 +竟 +章 +竣 +童 +竭 +端 +競 +竹 +竺 +竽 +竿 +笃 +笆 +笈 +笋 +笏 +笑 +笔 +笕 +笙 +笛 +笠 +符 +笨 +笫 +第 +笺 +笼 +筆 +等 +筋 +筏 +筐 +筑 +筒 +答 +策 +筛 +筝 +筠 +筱 +筵 +筷 +筹 +签 +简 +箍 +箐 +箔 +箕 +算 +管 +箩 +箫 +箬 +箭 +箱 +箴 +箸 +節 +篁 +篆 +篇 +築 +篑 +篓 +篙 +篝 +篡 +篦 +篪 +篮 +篱 +篷 +簇 +簋 +簌 +簡 +簧 +簪 +簸 +簿 +籁 +籍 +籣 +籤 +米 +类 +籼 +籽 +粉 +粑 +粒 +粕 +粗 +粘 +粟 +粢 +粤 +粥 +粧 +粪 +粮 +粱 +粲 +粳 +粵 +粹 +粼 +粽 +精 +粿 +糁 +糅 +糊 +糍 +糕 +糖 +糗 +糙 +糜 +糝 +糟 +糠 +糯 +系 +紀 +約 +紅 +紊 +紋 +納 +純 +紗 +級 +素 +索 +紧 +紫 +累 +細 +終 +組 +結 +絡 +給 +絮 +統 +絲 +絶 +綁 +綉 +經 +綦 +綫 +維 +綱 +網 +緑 +線 +緣 +緯 +緻 +縣 +縫 +縮 +繁 +織 +繡 +繪 +繹 +纂 +纛 +纠 +红 +纣 +纤 +约 +级 +纨 +纪 +纫 +纬 +纭 +纯 +纰 +纱 +纲 +纳 +纵 +纶 +纷 +纸 +纹 +纺 +纽 +纾 +线 +绀 +绁 +绂 +练 +组 +绅 +细 +织 +终 +绉 +绊 +绋 +绌 +绍 +绎 +经 +绐 +绑 +绒 +结 +绔 +绕 +绗 +绘 +给 +绚 +绛 +络 +绝 +绞 +统 +绠 +绡 +绢 +绣 +绥 +绦 +继 +绨 +绩 +绪 +绫 +续 +绮 +绯 +绰 +绱 +绲 +绳 +维 +绵 +绶 +绷 +绸 +绺 +绻 +综 +绽 +绾 +绿 +缀 +缁 +缂 +缃 +缄 +缅 +缆 +缇 +缈 +缉 +缎 +缐 +缓 +缔 +缕 +编 +缘 +缙 +缚 +缜 +缝 +缠 +缢 +缤 +缥 +缦 +缨 +缩 +缪 +缫 +缬 +缭 +缮 +缰 +缴 +缸 +缺 +罂 +罄 +罐 +网 +罔 +罕 +罗 +罘 +罚 +罡 +罢 +罩 +罪 +置 +罱 +署 +罹 +羁 +羅 +羊 +羌 +美 +羔 +羙 +羚 +羞 +羟 +羡 +群 +羧 +義 +羯 +羰 +羲 +羸 +羹 +羽 +羿 +翁 +翅 +翊 +翌 +翎 +翏 +習 +翔 +翘 +翟 +翠 +翡 +翥 +翦 +翩 +翰 +翱 +翳 +翻 +翼 +耀 +老 +考 +耄 +者 +耆 +而 +耍 +耐 +耑 +耒 +耕 +耗 +耘 +耙 +耜 +耦 +耧 +耱 +耳 +耵 +耶 +耷 +耸 +耻 +耽 +耿 +聂 +聆 +聊 +聋 +职 +联 +聖 +聘 +聚 +聞 +聨 +聪 +聯 +聲 +職 +聿 +肃 +肄 +肆 +肇 +肉 +肋 +肌 +肓 +肖 +肘 +肚 +肛 +肝 +肟 +肠 +股 +肢 +肤 +肥 +肩 +肪 +肮 +肯 +肱 +育 +肴 +肺 +肼 +肽 +肾 +肿 +胀 +胁 +胃 +胄 +胆 +背 +胍 +胎 +胖 +胗 +胚 +胛 +胜 +胞 +胡 +胤 +胥 +胧 +胪 +胫 +胬 +胭 +胯 +胰 +胱 +胳 +胴 +胶 +胸 +胺 +能 +脂 +脆 +脉 +脊 +脍 +脏 +脐 +脑 +脒 +脓 +脖 +脘 +脚 +脯 +脱 +脲 +脸 +脾 +腆 +腈 +腊 +腋 +腌 +腐 +腑 +腓 +腔 +腕 +腥 +腦 +腩 +腭 +腮 +腰 +腱 +腴 +腸 +腹 +腺 +腻 +腼 +腾 +腿 +膀 +膈 +膊 +膏 +膘 +膚 +膛 +膜 +膝 +膠 +膦 +膨 +膳 +膺 +膻 +臀 +臂 +臃 +臆 +臊 +臣 +臧 +臨 +自 +臭 +至 +致 +臺 +臻 +臼 +臾 +舀 +舅 +舆 +與 +興 +舊 +舌 +舍 +舒 +舔 +舖 +舛 +舜 +舞 +舟 +航 +舫 +般 +舰 +舱 +舵 +舶 +舷 +舸 +船 +艄 +艇 +艘 +艦 +艮 +良 +艰 +色 +艳 +艶 +艷 +艹 +艺 +艾 +艿 +节 +芃 +芊 +芋 +芍 +芎 +芒 +芗 +芘 +芙 +芜 +芝 +芡 +芥 +芦 +芩 +芪 +芫 +芬 +芭 +芮 +芯 +花 +芳 +芷 +芸 +芹 +芽 +芾 +苁 +苄 +苇 +苋 +苍 +苎 +苏 +苑 +苒 +苓 +苔 +苕 +苗 +苘 +苛 +苜 +苝 +苞 +苟 +苡 +苣 +若 +苦 +苪 +苫 +苯 +英 +苴 +苷 +苹 +茁 +茂 +范 +茄 +茅 +茆 +茉 +茌 +茎 +茏 +茗 +茜 +茧 +茨 +茫 +茬 +茭 +茯 +茱 +茴 +茵 +茶 +茸 +茹 +茼 +荀 +荃 +荆 +草 +荏 +荐 +荒 +荔 +荘 +荚 +荞 +荟 +荠 +荡 +荣 +荤 +荥 +荧 +荨 +荪 +荫 +荬 +荭 +药 +荷 +荸 +荻 +荼 +荽 +莅 +莆 +莉 +莊 +莎 +莒 +莓 +莘 +莜 +莞 +莠 +莨 +莪 +莫 +莱 +莲 +莳 +莴 +获 +莹 +莺 +莼 +莽 +菀 +菁 +菅 +菇 +菊 +菌 +菏 +菓 +菖 +菘 +菜 +菟 +菠 +菡 +菩 +菪 +華 +菱 +菲 +菽 +萁 +萃 +萄 +萊 +萋 +萌 +萍 +萎 +萘 +萜 +萝 +萤 +营 +萦 +萧 +萨 +萬 +萭 +萱 +萸 +萼 +落 +葆 +葉 +葑 +葒 +著 +葛 +葡 +董 +葩 +葫 +葬 +葭 +葱 +葳 +葵 +葺 +蒂 +蒄 +蒋 +蒌 +蒙 +蒜 +蒟 +蒡 +蒤 +蒲 +蒸 +蒻 +蒽 +蒿 +蓄 +蓉 +蓋 +蓑 +蓓 +蓖 +蓝 +蓟 +蓥 +蓦 +蓬 +蓮 +蓼 +蓿 +蔑 +蔓 +蔗 +蔚 +蔟 +蔡 +蔫 +蔬 +蔷 +蔸 +蔺 +蔻 +蔼 +蔽 +蕃 +蕈 +蕉 +蕊 +蕒 +蕙 +蕤 +蕨 +蕲 +蕴 +蕹 +蕾 +薄 +薇 +薈 +薏 +薛 +薡 +薦 +薩 +薪 +薬 +薯 +薰 +薹 +藁 +藉 +藍 +藏 +藐 +藓 +藔 +藕 +藜 +藝 +藤 +藥 +藩 +藻 +藿 +蘆 +蘇 +蘑 +蘖 +蘭 +蘸 +蘼 +虎 +虏 +虐 +虑 +虔 +處 +虚 +虞 +號 +虢 +虫 +虬 +虱 +虹 +虻 +虽 +虾 +蚀 +蚁 +蚂 +蚊 +蚌 +蚓 +蚕 +蚜 +蚝 +蚣 +蚤 +蚧 +蚨 +蚩 +蚪 +蚬 +蚯 +蚱 +蚴 +蚶 +蛀 +蛆 +蛇 +蛊 +蛋 +蛎 +蛏 +蛐 +蛔 +蛙 +蛛 +蛟 +蛤 +蛭 +蛮 +蛰 +蛳 +蛴 +蛹 +蛾 +蜀 +蜂 +蜃 +蜇 +蜈 +蜊 +蜍 +蜒 +蜓 +蜕 +蜗 +蜘 +蜚 +蜛 +蜜 +蜡 +蜥 +蜱 +蜴 +蜷 +蜻 +蜿 +蝇 +蝈 +蝉 +蝌 +蝎 +蝗 +蝙 +蝠 +蝴 +蝶 +蝽 +蝾 +螂 +螃 +螈 +融 +螟 +螨 +螯 +螳 +螺 +蟀 +蟆 +蟋 +蟑 +蟒 +蟠 +蟹 +蟾 +蠄 +蠓 +蠔 +蠕 +蠖 +蠡 +蠢 +蠹 +血 +衅 +衆 +行 +衍 +術 +衔 +街 +衙 +衛 +衡 +衢 +衣 +补 +表 +衩 +衫 +衬 +衮 +衰 +衲 +衷 +衾 +衿 +袁 +袂 +袄 +袅 +袆 +袈 +袋 +袍 +袒 +袖 +袜 +袤 +袪 +被 +袭 +袱 +裁 +裂 +装 +裆 +裔 +裕 +裘 +裙 +補 +裝 +裟 +裡 +裤 +裨 +裱 +裳 +裴 +裸 +裹 +製 +褀 +褂 +複 +褐 +褒 +褓 +褔 +褚 +褥 +褪 +褰 +褲 +褶 +襁 +襄 +襟 +西 +要 +覃 +覆 +見 +覌 +視 +親 +覺 +觀 +见 +观 +规 +觅 +视 +觇 +览 +觉 +觊 +觎 +觐 +觑 +角 +觞 +解 +触 +觸 +言 +訂 +計 +訊 +訓 +記 +訣 +設 +許 +診 +訾 +詢 +試 +詩 +話 +該 +詹 +誉 +誊 +誌 +誓 +誘 +語 +誠 +説 +課 +調 +諾 +謇 +講 +謢 +謹 +證 +識 +譜 +警 +譬 +護 +讀 +變 +计 +订 +讣 +认 +讥 +讨 +让 +讪 +讫 +训 +议 +讯 +记 +讲 +讳 +讴 +讶 +讷 +许 +讹 +论 +讼 +讽 +设 +访 +诀 +证 +诃 +评 +诅 +识 +诈 +诉 +诊 +诋 +词 +诏 +译 +诒 +试 +诗 +诘 +诙 +诚 +诛 +话 +诞 +诟 +诠 +诡 +询 +诣 +诤 +该 +详 +诧 +诩 +诫 +诬 +语 +误 +诰 +诱 +诲 +说 +诵 +请 +诸 +诺 +读 +诽 +课 +诿 +谀 +谁 +调 +谄 +谅 +谆 +谈 +谊 +谋 +谌 +谍 +谎 +谏 +谐 +谑 +谒 +谓 +谔 +谕 +谗 +谙 +谚 +谛 +谜 +谟 +谡 +谢 +谣 +谤 +谥 +谦 +谧 +谨 +谩 +谪 +谬 +谭 +谯 +谰 +谱 +谴 +谷 +豁 +豆 +豇 +豉 +豊 +豌 +豐 +豕 +豚 +象 +豪 +豫 +豹 +豺 +貂 +貅 +貉 +貊 +貌 +貔 +貝 +負 +財 +貢 +貨 +販 +貭 +貴 +買 +費 +貼 +貿 +賀 +賓 +賠 +賣 +質 +賴 +購 +贈 +贏 +贝 +贞 +负 +贡 +财 +责 +贤 +败 +账 +货 +质 +贩 +贪 +贫 +贬 +购 +贮 +贯 +贰 +贱 +贲 +贴 +贵 +贷 +贸 +费 +贺 +贻 +贼 +贾 +贿 +赁 +赂 +赃 +资 +赅 +赈 +赉 +赊 +赋 +赌 +赎 +赏 +赐 +赓 +赔 +赖 +赘 +赚 +赛 +赝 +赞 +赠 +赡 +赢 +赣 +赤 +赦 +赫 +赭 +走 +赳 +赴 +赵 +赶 +起 +趁 +超 +越 +趋 +趙 +趟 +趣 +足 +趴 +趵 +趸 +趾 +跃 +跄 +跆 +跋 +跌 +跎 +跑 +跖 +跚 +跛 +距 +跟 +跤 +跨 +跪 +路 +跳 +践 +跷 +跺 +跻 +踉 +踊 +踌 +踏 +踝 +踞 +踢 +踩 +踪 +踮 +踱 +踵 +踹 +蹂 +蹄 +蹇 +蹈 +蹉 +蹊 +蹋 +蹑 +蹒 +蹙 +蹦 +蹬 +蹭 +蹲 +蹴 +蹶 +蹼 +蹿 +躁 +躇 +躏 +身 +躬 +躯 +躲 +躺 +車 +軍 +軎 +軒 +軽 +載 +輕 +輝 +輪 +輯 +輸 +轉 +车 +轧 +轨 +轩 +转 +轭 +轮 +软 +轰 +轱 +轲 +轴 +轶 +轻 +轼 +载 +轿 +较 +辄 +辅 +辆 +辈 +辉 +辊 +辋 +辍 +辐 +辑 +输 +辕 +辖 +辗 +辘 +辙 +辛 +辜 +辞 +辟 +辣 +辦 +辧 +辨 +辩 +辫 +辰 +辱 +農 +边 +辽 +达 +迁 +迂 +迄 +迅 +过 +迈 +迎 +运 +近 +返 +还 +这 +进 +远 +违 +连 +迟 +迢 +迤 +迥 +迦 +迩 +迪 +迫 +迭 +迮 +述 +迳 +迷 +迸 +迹 +追 +退 +送 +适 +逃 +逄 +逅 +逆 +选 +逊 +逍 +透 +逐 +逑 +递 +途 +逗 +這 +通 +逛 +逝 +逞 +速 +造 +逢 +連 +逭 +逮 +逯 +進 +逵 +逸 +逹 +逺 +逻 +逼 +逾 +遁 +遂 +遇 +運 +遍 +過 +遏 +遐 +遒 +道 +達 +遗 +遛 +遠 +遢 +遣 +遥 +遨 +適 +遭 +遮 +遴 +遵 +選 +避 +邀 +邂 +邃 +還 +邈 +邋 +邏 +邑 +邓 +邕 +邗 +邛 +邝 +邡 +邢 +那 +邦 +邨 +邪 +邬 +邮 +邯 +邰 +邱 +邳 +邴 +邵 +邸 +邹 +邺 +邻 +郁 +郅 +郊 +郎 +郏 +郑 +郓 +郗 +郛 +郜 +郝 +郡 +郢 +郦 +郧 +部 +郫 +郭 +郯 +郴 +郵 +郸 +都 +郾 +鄂 +鄄 +鄉 +鄙 +鄞 +鄢 +鄣 +鄭 +鄯 +鄱 +鄺 +酉 +酊 +酋 +酌 +配 +酐 +酒 +酗 +酚 +酝 +酞 +酡 +酣 +酥 +酪 +酬 +酮 +酯 +酰 +酱 +酵 +酶 +酷 +酸 +酿 +醇 +醉 +醋 +醌 +醍 +醐 +醒 +醚 +醛 +醜 +醪 +醫 +醬 +醴 +醺 +釀 +采 +釉 +释 +里 +重 +野 +量 +金 +釜 +針 +釣 +鈴 +鈺 +鉅 +鉌 +鉴 +銀 +銘 +銭 +銮 +銷 +鋪 +鋼 +錄 +錦 +錯 +錶 +鍊 +鍋 +鍵 +鎏 +鎖 +鎮 +鎻 +鏖 +鏡 +鏢 +鐘 +鐡 +鐵 +鑒 +鑫 +鑽 +钇 +针 +钉 +钊 +钎 +钏 +钒 +钓 +钕 +钗 +钙 +钚 +钛 +钜 +钝 +钞 +钟 +钠 +钡 +钢 +钣 +钥 +钦 +钧 +钨 +钩 +钬 +钮 +钯 +钰 +钱 +钳 +钴 +钵 +钺 +钻 +钼 +钽 +钾 +钿 +铀 +铁 +铂 +铃 +铄 +铅 +铆 +铈 +铉 +铊 +铋 +铌 +铍 +铎 +铐 +铑 +铒 +铕 +铖 +铛 +铜 +铝 +铞 +铟 +铠 +铡 +铢 +铣 +铤 +铧 +铨 +铪 +铬 +铭 +铮 +铰 +铱 +铲 +铵 +银 +铸 +铺 +链 +铿 +销 +锁 +锂 +锄 +锅 +锆 +锈 +锉 +锋 +锌 +锏 +锐 +锑 +锔 +锕 +锗 +锘 +错 +锚 +锛 +锝 +锟 +锡 +锢 +锣 +锤 +锥 +锦 +锨 +锭 +键 +锯 +锰 +锲 +锴 +锵 +锶 +锷 +锹 +锺 +锻 +锽 +镀 +镁 +镂 +镇 +镉 +镊 +镌 +镍 +镐 +镑 +镒 +镓 +镖 +镗 +镛 +镜 +镝 +镣 +镦 +镧 +镭 +镯 +镰 +镱 +镳 +镶 +長 +长 +門 +閃 +開 +閏 +閑 +間 +閟 +関 +閣 +閥 +閩 +關 +门 +闪 +闫 +闭 +问 +闯 +闰 +闱 +闲 +闳 +间 +闵 +闷 +闸 +闹 +闺 +闻 +闽 +闾 +闿 +阀 +阁 +阂 +阄 +阅 +阆 +阈 +阉 +阊 +阎 +阐 +阑 +阔 +阕 +阖 +阗 +阙 +阚 +阜 +阝 +队 +阡 +阪 +阮 +阱 +防 +阳 +阴 +阵 +阶 +阻 +阿 +陀 +陂 +附 +际 +陆 +陇 +陈 +陉 +陋 +陌 +降 +限 +陕 +陛 +陟 +陡 +院 +除 +陨 +险 +陪 +陰 +陲 +陳 +陵 +陶 +陷 +陽 +隅 +隆 +隋 +隍 +随 +隐 +隔 +隗 +隘 +隙 +際 +障 +隧 +隨 +隶 +隹 +隼 +隽 +难 +雀 +雁 +雄 +雅 +集 +雇 +雉 +雌 +雍 +雎 +雏 +雒 +雕 +雙 +雜 +雞 +離 +難 +雨 +雪 +雯 +雲 +雳 +零 +雷 +雹 +電 +雾 +需 +霁 +霄 +霆 +震 +霉 +霍 +霎 +霏 +霓 +霖 +霜 +霞 +霭 +霰 +露 +霸 +霹 +霾 +靈 +靑 +青 +靓 +靖 +静 +靛 +靜 +非 +靠 +靡 +面 +靥 +革 +靳 +靴 +靶 +鞅 +鞋 +鞍 +鞑 +鞘 +鞠 +鞣 +鞭 +韓 +韦 +韧 +韩 +韫 +韬 +韭 +音 +韵 +韶 +頂 +順 +頓 +頔 +頡 +頤 +頭 +頰 +題 +顏 +顔 +類 +顧 +顯 +页 +顶 +顷 +项 +顺 +须 +顽 +顾 +顿 +颁 +颂 +预 +颅 +领 +颇 +颈 +颉 +颊 +颌 +颍 +颐 +频 +颓 +颔 +颖 +颗 +题 +颚 +颛 +颜 +额 +颞 +颠 +颡 +颢 +颤 +颦 +颧 +風 +飄 +风 +飒 +飓 +飕 +飘 +飙 +飛 +飞 +食 +飨 +飬 +飮 +飯 +飲 +飼 +飾 +餅 +養 +餐 +餘 +餠 +館 +餮 +饃 +饅 +饕 +饥 +饨 +饪 +饬 +饭 +饮 +饯 +饰 +饱 +饲 +饴 +饵 +饶 +饷 +饸 +饹 +饺 +饼 +饽 +饿 +馁 +馄 +馅 +馆 +馈 +馊 +馋 +馍 +馏 +馒 +馕 +首 +馗 +香 +馥 +馨 +馬 +馮 +馴 +駕 +騰 +騷 +驗 +马 +驭 +驮 +驯 +驰 +驱 +驳 +驴 +驶 +驷 +驸 +驹 +驻 +驼 +驽 +驾 +驿 +骁 +骂 +骄 +骅 +骆 +骇 +骊 +骋 +验 +骏 +骐 +骑 +骓 +骗 +骚 +骛 +骜 +骝 +骞 +骠 +骡 +骤 +骥 +骧 +骨 +骰 +骶 +骷 +骸 +骺 +骼 +髁 +髂 +髅 +髋 +髌 +髓 +體 +高 +髙 +髦 +髪 +髮 +髯 +髻 +鬃 +鬓 +鬟 +鬣 +鬲 +鬼 +魁 +魂 +魄 +魅 +魇 +魉 +魏 +魔 +魚 +鮮 +鯉 +鯨 +鯽 +鱬 +鱸 +鱻 +鱼 +鱿 +鲁 +鲂 +鲅 +鲆 +鲇 +鲈 +鲍 +鲑 +鲛 +鲜 +鲟 +鲢 +鲤 +鲨 +鲩 +鲫 +鲮 +鲱 +鲲 +鲳 +鲵 +鲷 +鲸 +鲽 +鳃 +鳄 +鳅 +鳌 +鳍 +鳎 +鳐 +鳕 +鳖 +鳗 +鳙 +鳜 +鳝 +鳞 +鳟 +鳥 +鳯 +鳳 +鴨 +鴻 +鴿 +鵬 +鶏 +鷄 +鷗 +鷹 +鸟 +鸠 +鸡 +鸢 +鸣 +鸥 +鸦 +鸨 +鸪 +鸬 +鸭 +鸯 +鸳 +鸵 +鸽 +鸾 +鸿 +鹂 +鹃 +鹄 +鹅 +鹉 +鹊 +鹌 +鹏 +鹑 +鹗 +鹜 +鹞 +鹤 +鹦 +鹧 +鹫 +鹭 +鹰 +鹳 +鹽 +鹿 +麂 +麋 +麒 +麓 +麗 +麝 +麟 +麥 +麦 +麯 +麵 +麸 +麺 +麻 +麽 +麾 +黃 +黄 +黍 +黎 +黏 +黑 +黒 +黔 +默 +黛 +黜 +黝 +點 +黟 +黠 +黯 +鼎 +鼓 +鼙 +鼠 +鼬 +鼻 +鼽 +鼾 +齊 +齋 +齐 +齿 +龄 +龈 +龊 +龋 +龌 +龍 +龙 +龚 +龛 +龟 +龢 +︽ +︾ +﹏ +﹥ +﹪ +! +# +% +& +( +) ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; += +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +| +~ +¥ diff --git a/ppocr/utils/dict/pu_dict.txt b/ppocr/utils/dict/pu_dict.txt new file mode 100644 index 0000000..9500fae --- /dev/null +++ b/ppocr/utils/dict/pu_dict.txt @@ -0,0 +1,130 @@ +p +u +_ +i +m +g +/ +8 +I +L +S +V +R +C +2 +0 +1 +v +a +l +6 +7 +4 +5 +. +j + +q +e +s +t +ã +o +x +9 +c +n +r +z +ç +õ +3 +A +U +d +º +ô +­ +, +E +; +ó +á +b +D +? +ú +ê +- +h +P +f +à +N +í +O +M +G +É +é +â +F +: +T +Á +" +Q +) +W +J +B +H +( +ö +% +Ö +« +w +K +y +! +k +] +' +Z ++ +Ç +Õ +Y +À +X +µ +» +ª +Í +ü +ä +´ +è +ñ +ß +ï +Ú +ë +Ô +Ï +Ó +[ +Ì +< + +ò +§ +³ +ø +å +# +$ +& +@ diff --git a/ppocr/utils/dict/rs_dict.txt b/ppocr/utils/dict/rs_dict.txt new file mode 100644 index 0000000..d1ce46d --- /dev/null +++ b/ppocr/utils/dict/rs_dict.txt @@ -0,0 +1,91 @@ +r +s +_ +i +m +g +/ +1 +I +L +S +V +R +C +2 +0 +v +a +l +7 +5 +8 +6 +. +j +p + +t +d +9 +3 +e +š +4 +k +u +ć +c +n +đ +o +z +č +b +ž +f +Z +T +h +M +F +O +Š +B +H +A +E +Đ +Ž +D +P +G +Č +K +U +N +J +Ć +w +y +W +x +Y +X +q +Q +# +& +$ +, +- +% +' +@ +! +: +? +( +É +é ++ diff --git a/ppocr/utils/dict/rsc_dict.txt b/ppocr/utils/dict/rsc_dict.txt new file mode 100644 index 0000000..95dd463 --- /dev/null +++ b/ppocr/utils/dict/rsc_dict.txt @@ -0,0 +1,134 @@ +r +s +c +_ +i +m +g +/ +5 +I +L +S +V +R +C +2 +0 +1 +v +a +l +9 +7 +8 +. +j +p +м +а +с +и +р +ћ +е +ш +3 +4 +о +г +н +з +в +л +6 +т +ж +у +к +п +њ +д +ч +С +ј +ф +ц +љ +х +О +И +А +б +Ш +К +ђ +џ +М +В +З +Д +Р +У +Н +Т +Б +? +П +Х +Ј +Ц +Г +Љ +Л +Ф +e +n +w +E +F +A +N +f +o +b +M +G +t +y +W +k +P +u +H +B +T +z +h +O +Y +d +U +K +D +x +X +J +Z +Q +q +' +- +@ +é +# +! +, +% +$ +: +& ++ +( +É + diff --git a/ppocr/utils/dict/ru_dict.txt b/ppocr/utils/dict/ru_dict.txt new file mode 100644 index 0000000..3b0cf3a --- /dev/null +++ b/ppocr/utils/dict/ru_dict.txt @@ -0,0 +1,125 @@ +к +в +а +з +и +у +р +о +н +я +х +п +л +ы +г +е +т +м +д +ж +ш +ь +с +ё +б +й +ч +ю +ц +щ +М +э +ф +А +ъ +С +Ф +Ю +В +К +Т +Н +О +Э +У +И +Г +Л +Р +Д +Б +Ш +П +З +Х +Е +Ж +Я +Ц +Ч +Й +Щ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z + diff --git a/ppocr/utils/dict/samaritan_dict.txt b/ppocr/utils/dict/samaritan_dict.txt new file mode 100644 index 0000000..1fe9187 --- /dev/null +++ b/ppocr/utils/dict/samaritan_dict.txt @@ -0,0 +1,222 @@ +! +# +$ +% +& +' +( ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +É +é +ء +آ +أ +ؤ +إ +ئ +ا +ب +ة +ت +ث +ج +ح +خ +د +ذ +ر +ز +س +ش +ص +ض +ط +ظ +ع +غ +ف +ق +ك +ل +م +ن +ه +و +ى +ي +ً +ٌ +ٍ +َ +ُ +ِ +ّ +ْ +ٓ +ٔ +ٰ +ٱ +ٹ +پ +چ +ڈ +ڑ +ژ +ک +ڭ +گ +ں +ھ +ۀ +ہ +ۂ +ۃ +ۆ +ۇ +ۈ +ۋ +ی +ې +ے +ۓ +ە +١ +٢ +٣ +٤ +٥ +٦ +٧ +٨ +٩ +ࠀ +ࠁ +ࠂ +ࠃ +ࠄ +ࠅ +ࠆ +ࠇ +ࠈ +ࠉ +ࠊ +ࠋ +ࠌ +ࠍ +ࠎ +ࠏ +ࠐ +ࠑ +ࠒ +ࠓ +ࠔ +ࠕ +ࠖ +ࠗ +࠘ +࠙ +ࠚ +ࠛ +ࠜ +ࠝ +ࠞ +ࠟ +ࠠ +ࠡ +ࠢ +ࠣ +ࠤ +ࠥ +ࠦ +ࠧ +ࠨ +ࠩ +ࠪ +ࠫ +ࠬ +࠭ +࠰ +࠱ +࠲ +࠳ +࠴ +࠵ +࠶ +࠷ +࠸ +࠹ +࠺ +࠻ +࠼ +࠽ +࠾ diff --git a/ppocr/utils/dict/spin_dict.txt b/ppocr/utils/dict/spin_dict.txt new file mode 100644 index 0000000..b6c16c8 --- /dev/null +++ b/ppocr/utils/dict/spin_dict.txt @@ -0,0 +1,68 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +: +( +' +- +, +% +> +. +[ +? +) +" += +_ +* +] +; +& ++ +$ +@ +/ +| +! +< +# +` +{ +~ +\ +} +^ diff --git a/ppocr/utils/dict/syriac_dict.txt b/ppocr/utils/dict/syriac_dict.txt new file mode 100644 index 0000000..4417f4a --- /dev/null +++ b/ppocr/utils/dict/syriac_dict.txt @@ -0,0 +1,157 @@ +! +# +$ +% +& +' +( ++ +, +- +. +/ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +_ +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +É +é +܀ +܁ +܂ +܃ +܄ +܅ +܆ +܇ +܈ +܉ +܊ +܋ +܌ +܍ +܏ +ܐ +ܑ +ܒ +ܓ +ܔ +ܕ +ܖ +ܗ +ܘ +ܙ +ܚ +ܛ +ܜ +ܝ +ܞ +ܟ +ܠ +ܡ +ܢ +ܣ +ܤ +ܥ +ܦ +ܧ +ܨ +ܩ +ܪ +ܫ +ܬ +ܭ +ܮ +ܯ +ܰ +ܱ +ܲ +ܳ +ܴ +ܵ +ܶ +ܷ +ܸ +ܹ +ܺ +ܻ +ܼ +ܽ +ܾ +ܿ +݀ +݁ +݂ +݃ +݄ +݅ +݆ +݇ +݈ +݉ +݊ +ݍ +ݎ +ݏ diff --git a/ppocr/utils/dict/ta_dict.txt b/ppocr/utils/dict/ta_dict.txt new file mode 100644 index 0000000..19d8189 --- /dev/null +++ b/ppocr/utils/dict/ta_dict.txt @@ -0,0 +1,128 @@ +t +a +_ +i +m +g +/ +3 +I +L +S +V +R +C +2 +0 +1 +v +l +9 +7 +8 +. +j +p +ப +ூ +த +ம +ி +வ +ர +் +ந +ோ +ன +6 +ஆ +ற +ல +5 +ள +ா +ொ +ழ +ு +4 +ெ +ண +க +ட +ை +ே +ச +ய +ஒ +இ +அ +ங +உ +ீ +ஞ +எ +ஓ +ஃ +ஜ +ஷ +ஸ +ஏ +ஊ +ஹ +ஈ +ஐ +ௌ +ஔ +s +c +e +n +w +F +T +O +P +K +A +N +G +Y +E +M +H +U +B +o +b +D +d +r +W +u +y +f +X +k +q +h +J +z +Z +Q +x +- +' +$ +, +% +@ +é +! +# ++ +É +& +: +( +? + diff --git a/ppocr/utils/dict/table_dict.txt b/ppocr/utils/dict/table_dict.txt new file mode 100644 index 0000000..2ef028c --- /dev/null +++ b/ppocr/utils/dict/table_dict.txt @@ -0,0 +1,277 @@ +← + +☆ +─ +α + + +⋅ +$ +ω +ψ +χ +( +υ +≥ +σ +, +ρ +ε +0 +■ +4 +8 +✗ +b +< +✓ +Ψ +Ω +€ +D +3 +Π +H +║ + +L +Φ +Χ +θ +P +κ +λ +μ +T +ξ +X +β +γ +δ +\ +ζ +η +` +d + +h +f +l +Θ +p +√ +t + +x +Β +Γ +Δ +| +ǂ +ɛ +j +̧ +➢ +⁡ +̌ +′ +« +△ +▲ +# + +' +Ι ++ +¶ +/ +▼ +⇑ +□ +· +7 +▪ +; +? +➔ +∩ +C +÷ +G +⇒ +K + +O +S +С +W +Α +[ +○ +_ +● +‡ +c +z +g + +o + +〈 +〉 +s +⩽ +w +φ +ʹ +{ +» +∣ +̆ +e +ˆ +∈ +τ +◆ +ι +∅ +∆ +∙ +∘ +Ø +ß +✔ +∞ +∑ +− +× +◊ +∗ +∖ +˃ +˂ +∫ +" +i +& +π +↔ +* +∥ +æ +∧ +. +⁄ +ø +Q +∼ +6 +⁎ +: +★ +> +a +B +≈ +F +J +̄ +N +♯ +R +V + +― +Z +♣ +^ +¤ +¥ +§ + +¢ +£ +≦ +­ +≤ +‖ +Λ +© +n +↓ +→ +↑ +r +° +± +v + +♂ +k +♀ +~ +ᅟ +̇ +@ +” +♦ +ł +® +⊕ +„ +! + +% +⇓ +) +- +1 +5 +9 += +А +A +‰ +⋆ +Σ +E +◦ +I +※ +M +m +̨ +⩾ +† + +• +U +Y +
 +] +̸ +2 +‐ +– +‒ +̂ +— +̀ +́ +’ +‘ +⋮ +⋯ +̊ +“ +̈ +≧ +q +u +ı +y + +​ +̃ +} +ν diff --git a/ppocr/utils/dict/table_master_structure_dict.txt b/ppocr/utils/dict/table_master_structure_dict.txt new file mode 100644 index 0000000..95ab253 --- /dev/null +++ b/ppocr/utils/dict/table_master_structure_dict.txt @@ -0,0 +1,39 @@ + + + + + + + + + + + colspan="2" + colspan="3" + + + rowspan="2" + colspan="4" + colspan="6" + rowspan="3" + colspan="9" + colspan="10" + colspan="7" + rowspan="4" + rowspan="5" + rowspan="9" + colspan="8" + rowspan="8" + rowspan="6" + rowspan="7" + rowspan="10" + + + + + + + + diff --git a/ppocr/utils/dict/table_structure_dict.txt b/ppocr/utils/dict/table_structure_dict.txt new file mode 100644 index 0000000..fec6f7d --- /dev/null +++ b/ppocr/utils/dict/table_structure_dict.txt @@ -0,0 +1,28 @@ + + + + + + + + + + colspan="2" + colspan="3" + rowspan="2" + colspan="4" + colspan="6" + rowspan="3" + colspan="9" + colspan="10" + colspan="7" + rowspan="4" + rowspan="5" + rowspan="9" + colspan="8" + rowspan="8" + rowspan="6" + rowspan="7" + rowspan="10" diff --git a/ppocr/utils/dict/table_structure_dict_ch.txt b/ppocr/utils/dict/table_structure_dict_ch.txt new file mode 100644 index 0000000..0c59c0e --- /dev/null +++ b/ppocr/utils/dict/table_structure_dict_ch.txt @@ -0,0 +1,48 @@ + + + + + + + + + + colspan="2" + colspan="3" + colspan="4" + colspan="5" + colspan="6" + colspan="7" + colspan="8" + colspan="9" + colspan="10" + colspan="11" + colspan="12" + colspan="13" + colspan="14" + colspan="15" + colspan="16" + colspan="17" + colspan="18" + colspan="19" + colspan="20" + rowspan="2" + rowspan="3" + rowspan="4" + rowspan="5" + rowspan="6" + rowspan="7" + rowspan="8" + rowspan="9" + rowspan="10" + rowspan="11" + rowspan="12" + rowspan="13" + rowspan="14" + rowspan="15" + rowspan="16" + rowspan="17" + rowspan="18" + rowspan="19" + rowspan="20" diff --git a/ppocr/utils/dict/te_dict.txt b/ppocr/utils/dict/te_dict.txt new file mode 100644 index 0000000..83d74cc --- /dev/null +++ b/ppocr/utils/dict/te_dict.txt @@ -0,0 +1,151 @@ +t +e +_ +i +m +g +/ +5 +I +L +S +V +R +C +2 +0 +1 +v +a +l +3 +4 +8 +9 +. +j +p +త +ె +ర +క +్ +ి +ం +చ +ే +ద +ు +7 +6 +ఉ +ా +మ +ట +ో +వ +ప +ల +శ +ఆ +య +ై +భ +' +ీ +గ +ూ +డ +ధ +హ +న +జ +స +[ +‌ +ష +అ +ణ +ఫ +బ +ఎ +; +ళ +థ +ొ +ఠ +ృ +ఒ +ఇ +ః +ఊ +ఖ +- +ఐ +ఘ +ౌ +ఏ +ఈ +ఛ +, +ఓ +ఞ +| +? +: +ఢ +" +( +” +! ++ +) +* += +& +“ +€ +] +£ +$ +s +c +n +w +k +J +G +u +d +r +E +o +h +y +b +f +B +M +O +T +N +D +P +A +F +x +W +Y +U +H +K +X +z +Z +Q +q +É +% +# +@ +é diff --git a/ppocr/utils/dict/th_dict.txt b/ppocr/utils/dict/th_dict.txt new file mode 100644 index 0000000..83a5b54 --- /dev/null +++ b/ppocr/utils/dict/th_dict.txt @@ -0,0 +1,81 @@ +ก +ข +ฃ +ค +ฅ +ฆ +ง +จ +ฉ +ช +ซ +ฌ +ญ +ฎ +ฏ +ฐ +ฑ +ฒ +ณ +ด +ต +ถ +ท +ธ +น +บ +ป +ผ +ฝ +พ +ฟ +ภ +ม +ย +ร +ล +ว +ศ +ษ +ส +ห +ฬ +อ +ฮ +ะ +ั +า +ำ +ิ +ี +ึ +ื +ุ +ู +เ +แ +โ +ใ +ไ +็ +่ +้ +๊ +๋ +์ +๐ +๑ +๒ +๓ +๔ +๕ +๖ +๗ +๘ +๙ +ฯ +ๆ +ฤ +ฤา +ฦ +ฦา diff --git a/ppocr/utils/dict/ug_dict.txt b/ppocr/utils/dict/ug_dict.txt new file mode 100644 index 0000000..8fc0db7 --- /dev/null +++ b/ppocr/utils/dict/ug_dict.txt @@ -0,0 +1,131 @@ +` +~ += +| + +_ +- +, +; +: +! +? +/ +. +' +" +( +) +[ +] +{ +} +@ +$ +* +\ +& +# +% ++ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +A +b +B +c +C +d +D +e +E +f +F +g +G +h +H +i +I +j +J +k +K +l +L +m +M +n +N +o +O +p +P +q +Q +r +R +s +S +t +T +u +U +v +V +w +W +x +X +y +Y +z +Z +Ö +ö +Ü +ü +Ë +ë +ا +ە +ب +پ +ت +ج +چ +خ +د +ر +ز +ژ +س +ش +غ +ف +ق +ك +گ +ڭ +ل +م +ن +ھ +و +ۇ +ۆ +ۈ +ۋ +ې +ى +ي +ئ diff --git a/ppocr/utils/dict/uk_dict.txt b/ppocr/utils/dict/uk_dict.txt new file mode 100644 index 0000000..c5ffc0a --- /dev/null +++ b/ppocr/utils/dict/uk_dict.txt @@ -0,0 +1,142 @@ +u +k +_ +i +m +g +/ +1 +6 +I +L +S +V +R +C +2 +0 +v +a +l +7 +9 +. +j +p +в +і +д +п +о +н +с +т +ю +4 +5 +3 +а +и +м +е +р +ч +у +Б +з +л +к +8 +А +В +г +є +б +ь +х +ґ +ш +ц +ф +я +щ +ж +Г +Х +У +Т +Е +І +Н +П +З +Л +Ю +С +Д +М +К +Р +Ф +О +Ц +И +Я +Ч +Ш +Ж +Є +Ґ +Ь +s +c +e +n +w +A +P +r +E +t +o +h +d +y +M +G +N +F +B +T +D +U +O +W +Z +f +H +Y +b +K +z +x +Q +X +q +J +$ +- +' +# +& +% +? +: +! +, ++ +@ +( +é +É + diff --git a/ppocr/utils/dict/unimernet_tokenizer/tokenizer.json b/ppocr/utils/dict/unimernet_tokenizer/tokenizer.json new file mode 100644 index 0000000..55a7760 --- /dev/null +++ b/ppocr/utils/dict/unimernet_tokenizer/tokenizer.json @@ -0,0 +1,100067 @@ +{ + "version": "1.0", + "truncation": { + "direction": "Right", + "max_length": 4096, + "strategy": "LongestFirst", + "stride": 0 + }, + "padding": { + "strategy": { + "Fixed": 4096 + }, + "direction": "Right", + "pad_to_multiple_of": null, + "pad_id": 1, + "pad_type_id": 0, + "pad_token": "" + }, + "added_tokens": [ + { + "id": 0, + "content": "", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 1, + "content": "", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 2, + "content": "", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 3, + "content": "", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 4, + "content": "[START_REF]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 5, + "content": "[END_REF]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 6, + "content": "[IMAGE]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 7, + "content": "", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 8, + "content": "", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 9, + "content": "", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 10, + "content": "", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 11, + "content": "[START_SUP]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 12, + "content": "[END_SUP]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 13, + "content": "[START_SUB]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 14, + "content": "[END_SUB]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 15, + "content": "[START_DNA]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 16, + "content": "[END_DNA]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 17, + "content": "[START_AMINO]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 18, + "content": "[END_AMINO]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 19, + "content": "[START_SMILES]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 20, + "content": "[END_SMILES]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 21, + "content": "[START_I_SMILES]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + }, + { + "id": 22, + "content": "[END_I_SMILES]", + "single_word": false, + "lstrip": false, + "rstrip": false, + "normalized": false, + "special": true + } + ], + "normalizer": { + "type": "NFKC" + }, + "pre_tokenizer": { + "type": "Sequence", + "pretokenizers": [ + { + "type": "Split", + "pattern": { + "String": "SPL1T-TH1S-Pl3A5E" + }, + "behavior": "Removed", + "invert": false + }, + { + "type": "Digits", + "individual_digits": true + }, + { + "type": "Split", + "pattern": { + "Regex": "[\\(\\)\\[\\]\\{\\}]|([!\"\\#\\$%\\&'\\*\\+,\\-\\./:;<=>\\?\\\\\\^_`\\|\\~])\\1*" + }, + "behavior": "Isolated", + "invert": false + }, + { + "type": "Split", + "pattern": { + "String": "\n" + }, + "behavior": "Isolated", + "invert": false + }, + { + "type": "ByteLevel", + "add_prefix_space": false, + "trim_offsets": true, + "use_regex": true + } + ] + }, + "post_processor": { + "type": "TemplateProcessing", + "single": [ + { + "SpecialToken": { + "id": "", + "type_id": 0 + } + }, + { + "Sequence": { + "id": "A", + "type_id": 0 + } + }, + { + "SpecialToken": { + "id": "", + "type_id": 0 + } + } + ], + "pair": [ + { + "Sequence": { + "id": "A", + "type_id": 0 + } + }, + { + "Sequence": { + "id": "B", + "type_id": 1 + } + } + ], + "special_tokens": { + "": { + "id": "", + "ids": [ + 2 + ], + "tokens": [ + "" + ] + }, + "": { + "id": "", + "ids": [ + 0 + ], + "tokens": [ + "" + ] + } + } + }, + "decoder": { + "type": "ByteLevel", + "add_prefix_space": true, + "trim_offsets": true, + "use_regex": true + }, + "model": { + "type": "BPE", + "dropout": null, + "unk_token": null, + "continuing_subword_prefix": null, + "end_of_word_suffix": null, + "fuse_unk": false, + "byte_fallback": false, + "vocab": { + "": 0, + "": 1, + "": 2, + "": 3, + "[START_REF]": 4, + "[END_REF]": 5, + "[IMAGE]": 6, + "": 7, + "": 8, + "": 9, + "": 10, + "[START_SUP]": 11, + "[END_SUP]": 12, + "[START_SUB]": 13, + "[END_SUB]": 14, + "[START_DNA]": 15, + "[END_DNA]": 16, + "[START_AMINO]": 17, + "[END_AMINO]": 18, + "[START_SMILES]": 19, + "[END_SMILES]": 20, + "[START_I_SMILES]": 21, + "[END_I_SMILES]": 22, + "!": 23, + "\"": 24, + "#": 25, + "$": 26, + "%": 27, + "&": 28, + "'": 29, + "(": 30, + ")": 31, + "*": 32, + "+": 33, + ",": 34, + "-": 35, + ".": 36, + "/": 37, + "0": 38, + "1": 39, + "2": 40, + "3": 41, + "4": 42, + "5": 43, + "6": 44, + "7": 45, + "8": 46, + "9": 47, + ":": 48, + ";": 49, + "<": 50, + "=": 51, + ">": 52, + "?": 53, + "@": 54, + "A": 55, + "B": 56, + "C": 57, + "D": 58, + "E": 59, + "F": 60, + "G": 61, + "H": 62, + "I": 63, + "J": 64, + "K": 65, + "L": 66, + "M": 67, + "N": 68, + "O": 69, + "P": 70, + "Q": 71, + "R": 72, + "S": 73, + "T": 74, + "U": 75, + "V": 76, + "W": 77, + "X": 78, + "Y": 79, + "Z": 80, + "[": 81, + "\\": 82, + "]": 83, + "^": 84, + "_": 85, + "`": 86, + "a": 87, + "b": 88, + "c": 89, + "d": 90, + "e": 91, + "f": 92, + "g": 93, + "h": 94, + "i": 95, + "j": 96, + "k": 97, + "l": 98, + "m": 99, + "n": 100, + "o": 101, + "p": 102, + "q": 103, + "r": 104, + "s": 105, + "t": 106, + "u": 107, + "v": 108, + "w": 109, + "x": 110, + "y": 111, + "z": 112, + "{": 113, + "|": 114, + "}": 115, + "~": 116, + "¡": 117, + "¢": 118, + "£": 119, + "¤": 120, + "¥": 121, + "¦": 122, + "§": 123, + "¨": 124, + "©": 125, + "ª": 126, + "«": 127, + "¬": 128, + "®": 129, + "¯": 130, + "°": 131, + "±": 132, + "²": 133, + "³": 134, + "´": 135, + "µ": 136, + "¶": 137, + "·": 138, + "¸": 139, + "¹": 140, + "º": 141, + "»": 142, + "¼": 143, + "½": 144, + "¾": 145, + "¿": 146, + "À": 147, + "Á": 148, + "Â": 149, + "Ã": 150, + "Ä": 151, + "Å": 152, + "Æ": 153, + "Ç": 154, + "È": 155, + "É": 156, + "Ê": 157, + "Ë": 158, + "Ì": 159, + "Í": 160, + "Î": 161, + "Ï": 162, + "Ð": 163, + "Ñ": 164, + "Ò": 165, + "Ó": 166, + "Ô": 167, + "Õ": 168, + "Ö": 169, + "×": 170, + "Ø": 171, + "Ù": 172, + "Ú": 173, + "Û": 174, + "Ü": 175, + "Ý": 176, + "Þ": 177, + "ß": 178, + "à": 179, + "á": 180, + "â": 181, + "ã": 182, + "ä": 183, + "å": 184, + "æ": 185, + "ç": 186, + "è": 187, + "é": 188, + "ê": 189, + "ë": 190, + "ì": 191, + "í": 192, + "î": 193, + "ï": 194, + "ð": 195, + "ñ": 196, + "ò": 197, + "ó": 198, + "ô": 199, + "õ": 200, + "ö": 201, + "÷": 202, + "ø": 203, + "ù": 204, + "ú": 205, + "û": 206, + "ü": 207, + "ý": 208, + "þ": 209, + "ÿ": 210, + "Ā": 211, + "ā": 212, + "Ă": 213, + "ă": 214, + "Ą": 215, + "ą": 216, + "Ć": 217, + "ć": 218, + "Ĉ": 219, + "ĉ": 220, + "Ċ": 221, + "ċ": 222, + "Č": 223, + "č": 224, + "Ď": 225, + "ď": 226, + "Đ": 227, + "đ": 228, + "Ē": 229, + "ē": 230, + "Ĕ": 231, + "ĕ": 232, + "Ė": 233, + "ė": 234, + "Ę": 235, + "ę": 236, + "Ě": 237, + "ě": 238, + "Ĝ": 239, + "ĝ": 240, + "Ğ": 241, + "ğ": 242, + "Ġ": 243, + "ġ": 244, + "Ģ": 245, + "ģ": 246, + "Ĥ": 247, + "ĥ": 248, + "Ħ": 249, + "ħ": 250, + "Ĩ": 251, + "ĩ": 252, + "Ī": 253, + "ī": 254, + "Ĭ": 255, + "ĭ": 256, + "Į": 257, + "į": 258, + "İ": 259, + "ı": 260, + "IJ": 261, + "ij": 262, + "Ĵ": 263, + "ĵ": 264, + "Ķ": 265, + "ķ": 266, + "ĸ": 267, + "Ĺ": 268, + "ĺ": 269, + "Ļ": 270, + "ļ": 271, + "Ľ": 272, + "ľ": 273, + "Ŀ": 274, + "ŀ": 275, + "Ł": 276, + "ł": 277, + "Ń": 278, + "Ġt": 279, + "in": 280, + "Ġa": 281, + "he": 282, + "on": 283, + "re": 284, + "at": 285, + "Ġthe": 286, + "er": 287, + "Ġs": 288, + "Ġo": 289, + "en": 290, + "al": 291, + "Ġc": 292, + "ti": 293, + "or": 294, + "ed": 295, + "es": 296, + "is": 297, + "Ġp": 298, + "Ġof": 299, + "nd": 300, + "Ġin": 301, + "Ġf": 302, + "Ġw": 303, + "ĠĠ": 304, + "it": 305, + "an": 306, + "ro": 307, + "ar": 308, + "Ġd": 309, + "Ġm": 310, + "Ġb": 311, + "Ġand": 312, + "ic": 313, + "le": 314, + "ing": 315, + "ion": 316, + "as": 317, + "Ġe": 318, + "Ġre": 319, + "ation": 320, + "Ġto": 321, + "el": 322, + "ent": 323, + "ac": 324, + "et": 325, + "ec": 326, + "tion": 327, + "om": 328, + "st": 329, + "ĠT": 330, + "Ġn": 331, + "Ġth": 332, + "ol": 333, + "ul": 334, + "im": 335, + "RE": 336, + "ig": 337, + "us": 338, + "REF": 339, + "Ġl": 340, + "Ġh": 341, + "ur": 342, + "Ġis": 343, + "ĠĠĠĠ": 344, + "Ġfor": 345, + "id": 346, + "am": 347, + "ĠS": 348, + "ve": 349, + "il": 350, + "ĠA": 351, + "ĠC": 352, + "Ġg": 353, + "ot": 354, + "ith": 355, + "ly": 356, + "ce": 357, + "Ġcon": 358, + "ow": 359, + "Ġst": 360, + "ut": 361, + "os": 362, + "Ġwith": 363, + "od": 364, + "ra": 365, + "Ġv": 366, + "Ġpro": 367, + "um": 368, + "ĠI": 369, + "if": 370, + "uc": 371, + "ter": 372, + "un": 373, + "AR": 374, + "ST": 375, + "res": 376, + "Ġon": 377, + "EN": 378, + "ere": 379, + "ĠP": 380, + "ĠThe": 381, + "ĠM": 382, + "Ġas": 383, + "ART": 384, + "Ġan": 385, + "END": 386, + "START": 387, + "Ġthat": 388, + "qu": 389, + "em": 390, + "Ġbe": 391, + "Ġex": 392, + "ri": 393, + "ab": 394, + "ity": 395, + "tic": 396, + "ver": 397, + "Ġal": 398, + "pl": 399, + "ts": 400, + "ĠF": 401, + "Ġâ": 402, + "ure": 403, + "Ġby": 404, + "ate": 405, + "ag": 406, + "ir": 407, + "oc": 408, + "per": 409, + "ĠB": 410, + "ay": 411, + "ĠD": 412, + "Ġcom": 413, + "ĠH": 414, + "ated": 415, + "ĠR": 416, + "Ġare": 417, + "rom": 418, + "ĠE": 419, + "op": 420, + "ad": 421, + "se": 422, + "ĠL": 423, + "igh": 424, + "ĠN": 425, + "ment": 426, + "her": 427, + "og": 428, + "ain": 429, + "ect": 430, + "ud": 431, + "Ġde": 432, + "Ġr": 433, + "Ġat": 434, + "Ġwas": 435, + "Ġus": 436, + "Ġres": 437, + "ell": 438, + "iz": 439, + "ine": 440, + "ph": 441, + "Ġac": 442, + "ess": 443, + "ore": 444, + "ical": 445, + "th": 446, + "und": 447, + "rac": 448, + "Ġwe": 449, + "ath": 450, + "ĠG": 451, + "Ġfrom": 452, + "ati": 453, + "up": 454, + "ist": 455, + "ant": 456, + "Ġor": 457, + "ff": 458, + "Ġcomp": 459, + "Ġwh": 460, + "ĠW": 461, + "ch": 462, + "ers": 463, + "Ġsp": 464, + "orm": 465, + "Ġch": 466, + "ations": 467, + "ran": 468, + "ub": 469, + "te": 470, + "di": 471, + "Ġsh": 472, + "ge": 473, + "ase": 474, + "Ġwere": 475, + "ĠĠĠĠĠĠĠĠ": 476, + "ĠÎ": 477, + "ap": 478, + "ĠIn": 479, + "and": 480, + "Ġse": 481, + "vel": 482, + "Ġim": 483, + "ĠâĪ": 484, + "ens": 485, + "ies": 486, + "ich": 487, + "ight": 488, + "duc": 489, + "ĠO": 490, + "Ġit": 491, + "tions": 492, + "end": 493, + "Ġco": 494, + "Ġthis": 495, + "Ġcan": 496, + "Ġk": 497, + "âĢ": 498, + "lec": 499, + "ted": 500, + "Ġmod": 501, + "math": 502, + "Ġcont": 503, + "Ġne": 504, + "Ġpar": 505, + "ib": 506, + "ĠĠĠ": 507, + "Ġle": 508, + "iv": 509, + "ug": 510, + "ence": 511, + "ign": 512, + "ous": 513, + "ents": 514, + "ys": 515, + "ave": 516, + "red": 517, + "ress": 518, + "able": 519, + "por": 520, + "all": 521, + "iff": 522, + "est": 523, + "Ġap": 524, + "Ġinc": 525, + "nt": 526, + "ary": 527, + "iti": 528, + "Ġwhich": 529, + "Ġnot": 530, + "form": 531, + "Ġsy": 532, + "Ġad": 533, + "low": 534, + "ak": 535, + "Ġper": 536, + "Ġhe": 537, + "pro": 538, + "ance": 539, + "ial": 540, + "ue": 541, + "Ġen": 542, + "Ġcl": 543, + "ass": 544, + "ip": 545, + "rans": 546, + "Ġob": 547, + "Ġgen": 548, + "tim": 549, + "Ġdis": 550, + "unc": 551, + "Ġint": 552, + "ep": 553, + "etw": 554, + "Ġdiff": 555, + "ach": 556, + "ther": 557, + "ime": 558, + "age": 559, + "ple": 560, + "ill": 561, + "yp": 562, + "ĠK": 563, + "act": 564, + "ari": 565, + "Ġmet": 566, + "ors": 567, + "Ġhave": 568, + "Ġstud": 569, + "ong": 570, + "ĠU": 571, + "Ġpl": 572, + "ide": 573, + "ma": 574, + "hen": 575, + "ific": 576, + "ome": 577, + "Ġi": 578, + "ular": 579, + "ĠV": 580, + "ally": 581, + "Ġshow": 582, + "rib": 583, + "ia": 584, + "enti": 585, + "Ġass": 586, + "ond": 587, + "ft": 588, + "Ġab": 589, + "Ġinter": 590, + "ĠTh": 591, + "The": 592, + "str": 593, + "Ġcell": 594, + "cal": 595, + "Ġmodel": 596, + "ata": 597, + "ast": 598, + "Ġeff": 599, + "Ġtrans": 600, + "ates": 601, + "ased": 602, + "ost": 603, + "vi": 604, + "ang": 605, + "our": 606, + "Ġme": 607, + "ard": 608, + "Ġdiffere": 609, + "Ġpre": 610, + "Ġdi": 611, + "ĠâĪĴ": 612, + "olog": 613, + "ution": 614, + "ound": 615, + "ace": 616, + "Ġresul": 617, + "erm": 618, + "pos": 619, + "here": 620, + "tive": 621, + "ord": 622, + "so": 623, + "stem": 624, + "yl": 625, + "Ġph": 626, + "Ġy": 627, + "ame": 628, + "ork": 629, + "ative": 630, + "Ġqu": 631, + "ric": 632, + "SU": 633, + "wo": 634, + "Ġun": 635, + "Ġev": 636, + "are": 637, + "##": 638, + "de": 639, + "een": 640, + "tiv": 641, + "Ġgro": 642, + "ory": 643, + "Ġcons": 644, + "Ġsub": 645, + "ta": 646, + "--": 647, + "Ġstr": 648, + "ber": 649, + "erv": 650, + "etween": 651, + "enc": 652, + "Ġanal": 653, + "int": 654, + "Ġhas": 655, + "uch": 656, + "Ġreg": 657, + "Ġbetween": 658, + "Ġdet": 659, + "Ġall": 660, + "cess": 661, + "Ġexp": 662, + "ection": 663, + "ĠâĢ": 664, + "ind": 665, + "ater": 666, + "Ġsign": 667, + "pt": 668, + "ugh": 669, + "ite": 670, + "ility": 671, + "Ġusing": 672, + "Ġval": 673, + "Ġro": 674, + "ree": 675, + "Ġrel": 676, + "out": 677, + "Ġfunc": 678, + "ition": 679, + "Ġcor": 680, + "Ġalso": 681, + "Ġtwo": 682, + "ne": 683, + "ĠJ": 684, + "Ġsystem": 685, + "cl": 686, + "uct": 687, + "Ġsim": 688, + "tain": 689, + "ust": 690, + "ied": 691, + "port": 692, + "Ġrec": 693, + "Ġresp": 694, + "Ġdata": 695, + "rm": 696, + "resent": 697, + "uld": 698, + "xt": 699, + "Ġj": 700, + "ry": 701, + "ack": 702, + "Ġra": 703, + "par": 704, + "Ġform": 705, + "Ġsc": 706, + "frac": 707, + "ĠWe": 708, + "ating": 709, + "ech": 710, + "hod": 711, + "Ġfol": 712, + "ined": 713, + "ĠSt": 714, + "ual": 715, + "Ġused": 716, + "Ġone": 717, + "Ġdes": 718, + "ĠÏ": 719, + "Ġvari": 720, + "Ġdist": 721, + "Ġnum": 722, + "ym": 723, + "ew": 724, + "rec": 725, + "ob": 726, + "Ġinf": 727, + "Ġar": 728, + "lect": 729, + "ll": 730, + "ons": 731, + "ĠThis": 732, + "ose": 733, + "ile": 734, + "play": 735, + "ear": 736, + "ox": 737, + "ures": 738, + "one": 739, + "Ġstudy": 740, + "ysis": 741, + "Ġfollow": 742, + "yle": 743, + "ract": 744, + "dis": 745, + "Ġpos": 746, + "right": 747, + "Ġthan": 748, + "ros": 749, + "av": 750, + "Fig": 751, + "Ġtime": 752, + "ization": 753, + "ulation": 754, + "ized": 755, + "Ġsur": 756, + "oth": 757, + "Ġout": 758, + "Ġcol": 759, + "ature": 760, + "ive": 761, + "Ġsol": 762, + "Ġx": 763, + "eld": 764, + "Ġother": 765, + "plic": 766, + "Ġdef": 767, + "erg": 768, + "Ġgener": 769, + "ely": 770, + "Ġbeen": 771, + "Ġincre": 772, + "Ġthese": 773, + "Ġno": 774, + "ax": 775, + "style": 776, + "arg": 777, + "ian": 778, + "Ġind": 779, + "Ġsuch": 780, + "Ġfunction": 781, + "ting": 782, + "Ġequ": 783, + "aus": 784, + "Ġund": 785, + "mathb": 786, + "tical": 787, + "Ġhigh": 788, + "rain": 789, + "Ġam": 790, + "ield": 791, + "oun": 792, + "ression": 793, + "Ġspec": 794, + "Ġop": 795, + "Ġdec": 796, + "Ġover": 797, + "Ġmethod": 798, + "Ġset": 799, + "âĪ": 800, + "Ġif": 801, + "dition": 802, + "ues": 803, + "ects": 804, + "display": 805, + "hem": 806, + "Ġpati": 807, + "Ġresults": 808, + "old": 809, + "anc": 810, + "displaystyle": 811, + "Ġeach": 812, + "Ġmore": 813, + "les": 814, + "pr": 815, + "acter": 816, + "Ġtheir": 817, + "Ġacc": 818, + "Ġappro": 819, + "iss": 820, + "ize": 821, + "Ġinv": 822, + "ases": 823, + "Ġcells": 824, + "irst": 825, + "lu": 826, + "ail": 827, + "Ġmeas": 828, + "Ġlow": 829, + "ov": 830, + "the": 831, + "ik": 832, + "**": 833, + "ef": 834, + "Ġbut": 835, + "hes": 836, + "fter": 837, + "Ġdifferent": 838, + "vely": 839, + "Ġext": 840, + "Ġthere": 841, + "oci": 842, + "Ġprob": 843, + "Ġits": 844, + "ron": 845, + "ments": 846, + "Ġag": 847, + "NA": 848, + "Ġpo": 849, + "ice": 850, + "ype": 851, + "Ġgroup": 852, + "âĢĵ": 853, + "ever": 854, + "ult": 855, + "ism": 856, + "tern": 857, + "ability": 858, + "ions": 859, + "ark": 860, + "Ġnon": 861, + "to": 862, + "ĠĠĠĠĠĠĠ": 863, + "Ġobs": 864, + "Ġtre": 865, + "als": 866, + "left": 867, + "ĠPro": 868, + "Ġonly": 869, + "Ġman": 870, + "der": 871, + "Ġpol": 872, + "uring": 873, + "amet": 874, + "rol": 875, + "In": 876, + "yn": 877, + "Ġunder": 878, + "ĠCh": 879, + "Ġwhere": 880, + "ood": 881, + "ĠX": 882, + "nce": 883, + "Ġpartic": 884, + "ected": 885, + "ĠFig": 886, + "Ġem": 887, + "Ġfact": 888, + "ĠAn": 889, + "Ġperform": 890, + "Ġso": 891, + "Ġanalysis": 892, + "stract": 893, + "hed": 894, + "Ġmay": 895, + "atic": 896, + "Ġrep": 897, + "tein": 898, + "duced": 899, + "Ġup": 900, + "Ġinto": 901, + "Ġnumber": 902, + "Ġour": 903, + "Ġet": 904, + "eg": 905, + "itle": 906, + "over": 907, + "ix": 908, + "ator": 909, + "ulti": 910, + "Ġincl": 911, + "ould": 912, + "ici": 913, + "bstract": 914, + "Ġcomple": 915, + "Ġpatients": 916, + "Ġdo": 917, + "Ġexper": 918, + "vid": 919, + "ange": 920, + "Ġlevel": 921, + "Ġprocess": 922, + "mathcal": 923, + "ps": 924, + "Ġsignific": 925, + "Ġsam": 926, + "Title": 927, + "Ġbl": 928, + "Ġstruct": 929, + "eta": 930, + "Ġobserv": 931, + "raph": 932, + "gr": 933, + "Ġactiv": 934, + "Ġfirst": 935, + "velop": 936, + "gen": 937, + "ible": 938, + "Ġsm": 939, + "Ġwill": 940, + "ĠQ": 941, + "Ġmeasure": 942, + "put": 943, + "Ġloc": 944, + "Ġmo": 945, + "vers": 946, + "of": 947, + "tal": 948, + "ered": 949, + "own": 950, + "Ġmat": 951, + "ities": 952, + "til": 953, + "inal": 954, + "Ġcar": 955, + "pha": 956, + "Ġboth": 957, + "Ġcur": 958, + "SUB": 959, + "its": 960, + "rel": 961, + "Ġwhen": 962, + "Ġz": 963, + "Ġchar": 964, + "Ġbi": 965, + "cent": 966, + "Ġthen": 967, + "ise": 968, + "owever": 969, + "Ġmin": 970, + "ĠFor": 971, + "ĠY": 972, + "ption": 973, + "Ġes": 974, + "mun": 975, + "Ġinclud": 976, + "istic": 977, + "con": 978, + "Ġobtain": 979, + "ared": 980, + "duction": 981, + "Ġsignificant": 982, + "ĠZ": 983, + "Ġpresent": 984, + "ann": 985, + "Ġid": 986, + "ency": 987, + "Ġver": 988, + "val": 989, + "yd": 990, + "rough": 991, + "SUP": 992, + "fore": 993, + "Ġsome": 994, + "ĠAs": 995, + "Ġsup": 996, + "Ġafter": 997, + "ological": 998, + "entif": 999, + "Ġcase": 1000, + "Ġsec": 1001, + "elf": 1002, + "Ġdep": 1003, + "ks": 1004, + "Ġcal": 1005, + "ved": 1006, + "Ġtem": 1007, + "Ġuse": 1008, + "ĠCom": 1009, + "lam": 1010, + "ines": 1011, + "ays": 1012, + "Ġgiv": 1013, + "Ġconsid": 1014, + "Ġelect": 1015, + "ational": 1016, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 1017, + "iqu": 1018, + "ties": 1019, + "Ġline": 1020, + "Ġsu": 1021, + "Abstract": 1022, + "ount": 1023, + "Ġdevelop": 1024, + "ĠCon": 1025, + "ology": 1026, + "alpha": 1027, + "ans": 1028, + "prime": 1029, + "cc": 1030, + "ogen": 1031, + "Ġwork": 1032, + "ven": 1033, + "ium": 1034, + "ective": 1035, + "Ġpa": 1036, + "ten": 1037, + "ĠAl": 1038, + "Ġï": 1039, + "Ġfe": 1040, + "âĢĻ": 1041, + "ential": 1042, + "line": 1043, + "Ġparamet": 1044, + "Ġprotein": 1045, + "Ġdisc": 1046, + "face": 1047, + "ces": 1048, + "Ġwell": 1049, + "ural": 1050, + "eng": 1051, + "Ġduring": 1052, + "row": 1053, + "ants": 1054, + "Ġrem": 1055, + "formation": 1056, + "Ġexam": 1057, + "Ġmic": 1058, + "âĪĴ": 1059, + "lem": 1060, + "ergy": 1061, + "Ġassoci": 1062, + "ĠÃ": 1063, + "rop": 1064, + "Ġfield": 1065, + "ty": 1066, + "Ġclass": 1067, + "Ġu": 1068, + "ie": 1069, + "Ġbec": 1070, + "Ġexperim": 1071, + "sp": 1072, + "Ġpr": 1073, + "ilar": 1074, + "tial": 1075, + "Ġconst": 1076, + "ĠIt": 1077, + "Ġcontrol": 1078, + "da": 1079, + "Ġmulti": 1080, + "itive": 1081, + "ics": 1082, + "urn": 1083, + "Ġindic": 1084, + "Ġfound": 1085, + "text": 1086, + "Ġnew": 1087, + "Ġref": 1088, + "gor": 1089, + "rap": 1090, + "Ġdesc": 1091, + "Ġsame": 1092, + "Ġfollowing": 1093, + "Ġdistrib": 1094, + "Figure": 1095, + "ild": 1096, + "Ġanti": 1097, + "etwork": 1098, + "ove": 1099, + "Ġthrough": 1100, + "Ġmost": 1101, + "cer": 1102, + "Ġdeterm": 1103, + "ha": 1104, + "elta": 1105, + "arge": 1106, + "Ġshown": 1107, + "ince": 1108, + "Ġany": 1109, + "ren": 1110, + "dot": 1111, + "ral": 1112, + "ration": 1113, + "amma": 1114, + "oid": 1115, + "Ġmed": 1116, + "ension": 1117, + "art": 1118, + "Ġpred": 1119, + "met": 1120, + "mathbb": 1121, + "ake": 1122, + "Ġcalc": 1123, + "Ġhig": 1124, + "Ġthree": 1125, + "Ġbased": 1126, + "mon": 1127, + "arch": 1128, + "----": 1129, + "ples": 1130, + "ages": 1131, + "ause": 1132, + "ish": 1133, + "tively": 1134, + "qui": 1135, + "resp": 1136, + "Ġcharacter": 1137, + "ock": 1138, + "Ġtreat": 1139, + "Ġproper": 1140, + "ex": 1141, + "Ġsmall": 1142, + "Ġterm": 1143, + "bda": 1144, + "Ġkn": 1145, + "ode": 1146, + "ings": 1147, + "Ġexpression": 1148, + "Ġmon": 1149, + "emb": 1150, + "ute": 1151, + "echn": 1152, + "hib": 1153, + "Ġdirec": 1154, + "ination": 1155, + "ithm": 1156, + "ulated": 1157, + "Ġcy": 1158, + "Ġpot": 1159, + "Ġorder": 1160, + "ote": 1161, + "ically": 1162, + "Ġvalues": 1163, + "ort": 1164, + "urther": 1165, + "cept": 1166, + "ynam": 1167, + "ough": 1168, + "echan": 1169, + "Ġâī": 1170, + "ok": 1171, + "ement": 1172, + "Ġμ": 1173, + "Ġestim": 1174, + "Ġeffect": 1175, + "Ġpath": 1176, + "Ġconf": 1177, + "Ġapp": 1178, + "Ġgiven": 1179, + "Ġend": 1180, + "set": 1181, + "Ġgl": 1182, + "Ġthey": 1183, + "ning": 1184, + "Ġtest": 1185, + "Ġtemper": 1186, + "ves": 1187, + "Ġvalue": 1188, + "ited": 1189, + "ality": 1190, + "Ġlim": 1191, + "Ġspect": 1192, + "ently": 1193, + "tit": 1194, + "Ġsequ": 1195, + "Ġidentif": 1196, + "//": 1197, + "igma": 1198, + "Ġenergy": 1199, + "inc": 1200, + "ness": 1201, + "ensity": 1202, + "Ġproblem": 1203, + "ydro": 1204, + "agn": 1205, + "ane": 1206, + "rent": 1207, + "com": 1208, + "ject": 1209, + "Ġimport": 1210, + "ĉĉ": 1211, + "Ġoper": 1212, + "olution": 1213, + "Ġaut": 1214, + "ectively": 1215, + "ĠHowever": 1216, + "ho": 1217, + "ental": 1218, + "Ġsing": 1219, + "ey": 1220, + "mu": 1221, + "ross": 1222, + "action": 1223, + "epend": 1224, + "ĠEx": 1225, + "vious": 1226, + "Ġstudies": 1227, + "sc": 1228, + "ormal": 1229, + "Ġhad": 1230, + "Ġmain": 1231, + "alth": 1232, + "gorithm": 1233, + "Ġfl": 1234, + "omet": 1235, + "ĠÂ": 1236, + "..": 1237, + "err": 1238, + "Ġposs": 1239, + "Ġdifferen": 1240, + "Ġobserved": 1241, + "ray": 1242, + "Ġpredic": 1243, + "Ġgene": 1244, + "Ġstate": 1245, + "We": 1246, + "Ġstructure": 1247, + "Ġret": 1248, + "respond": 1249, + "requ": 1250, + "ily": 1251, + "ĠâĪĪ": 1252, + "Ġser": 1253, + "Ġbound": 1254, + "Ġrepresent": 1255, + "phi": 1256, + "Ġtreatment": 1257, + "hat": 1258, + "Ġrequi": 1259, + "app": 1260, + "uman": 1261, + "Ġhigher": 1262, + "Ġlarge": 1263, + "Ġtra": 1264, + "ward": 1265, + "Ġobtained": 1266, + "Ġcould": 1267, + "tig": 1268, + "ĠUn": 1269, + "Ġdescrib": 1270, + "Ġsimilar": 1271, + "ported": 1272, + "ins": 1273, + "Ġaddition": 1274, + "osis": 1275, + "Ġnetwork": 1276, + "Ġele": 1277, + "pi": 1278, + "rix": 1279, + "Ġrate": 1280, + "gan": 1281, + "ugg": 1282, + "uss": 1283, + "Ġmechan": 1284, + "Ġdise": 1285, + "Ġeffects": 1286, + "Ġmodels": 1287, + "orph": 1288, + "ike": 1289, + "Ġsecond": 1290, + "mathbf": 1291, + "Ġdue": 1292, + "Ġq": 1293, + "Ġpres": 1294, + "Ġtechn": 1295, + "els": 1296, + "Ġcorrespond": 1297, + "Ġassociated": 1298, + "posed": 1299, + "Ġmass": 1300, + "round": 1301, + "view": 1302, + "Ġins": 1303, + "ĠâĢ¢": 1304, + "ditions": 1305, + "Ġwhile": 1306, + "ole": 1307, + "Ġlong": 1308, + "alu": 1309, + "Ġcap": 1310, + "Ġsurface": 1311, + "Ġcomplex": 1312, + "Ġcent": 1313, + "Ġcompared": 1314, + "Ġfind": 1315, + "arget": 1316, + "atory": 1317, + "fer": 1318, + "Ġsize": 1319, + "Ġcontain": 1320, + "usion": 1321, + "utions": 1322, + "Ġdem": 1323, + "ES": 1324, + "Ġdepend": 1325, + "atis": 1326, + "sum": 1327, + "ffici": 1328, + "Ġbas": 1329, + "lambda": 1330, + "ier": 1331, + "AT": 1332, + "Ġmax": 1333, + "Ġimp": 1334, + "Ġevalu": 1335, + "Ġtemperature": 1336, + "ink": 1337, + "ector": 1338, + "Ġscal": 1339, + "Ġgrow": 1340, + "ower": 1341, + "Ġrespectively": 1342, + "lear": 1343, + "sh": 1344, + "ick": 1345, + "Ġfil": 1346, + "irc": 1347, + "ilon": 1348, + "ram": 1349, + "Ġα": 1350, + "ification": 1351, + "Ġocc": 1352, + "Ġyear": 1353, + "Ġsugg": 1354, + "Ġradi": 1355, + "ified": 1356, + "havi": 1357, + "Ġwithin": 1358, + "Ġsens": 1359, + "Ġinte": 1360, + "Ġwould": 1361, + "Ġconcent": 1362, + "Ġmicro": 1363, + "Ġsingle": 1364, + "ĠSp": 1365, + "ou": 1366, + "Ġatt": 1367, + "Ġself": 1368, + "Ġabout": 1369, + "ength": 1370, + "Ġel": 1371, + "ĠRe": 1372, + "xim": 1373, + "Ġconditions": 1374, + "ude": 1375, + "ĠAt": 1376, + "where": 1377, + "med": 1378, + "Ġneed": 1379, + "iron": 1380, + "Ġpop": 1381, + "Ġresult": 1382, + "Ġpoint": 1383, + "Ġlo": 1384, + "Ġalgorithm": 1385, + "Ġactivity": 1386, + "leq": 1387, + "plement": 1388, + "ĠRes": 1389, + "Ġsym": 1390, + "onstr": 1391, + "atures": 1392, + "Ġimpro": 1393, + "for": 1394, + "Ġgeneral": 1395, + "iter": 1396, + "Ġexpl": 1397, + "###": 1398, + "Ġdom": 1399, + "Ġtri": 1400, + "min": 1401, + "Ġdistribution": 1402, + "Ġtr": 1403, + "ĠThere": 1404, + "oss": 1405, + "uce": 1406, + "mathrm": 1407, + "ull": 1408, + "ER": 1409, + "reg": 1410, + "Ġpe": 1411, + "Ġtotal": 1412, + "Ġlead": 1413, + "==": 1414, + "iod": 1415, + "Ġassum": 1416, + "Ġchang": 1417, + "Ġgra": 1418, + "MI": 1419, + "Ġcomput": 1420, + "Ġcomb": 1421, + "Ġinformation": 1422, + "Ġdesign": 1423, + "Ġiniti": 1424, + "Ġfrequ": 1425, + "imension": 1426, + "cop": 1427, + "Ġproperties": 1428, + "Ġconsider": 1429, + "Ġlevels": 1430, + "ene": 1431, + "Ġtype": 1432, + "ived": 1433, + "ĠHe": 1434, + "ependent": 1435, + "Ġapplic": 1436, + "Ġinves": 1437, + "Ġprevious": 1438, + "aw": 1439, + "Ġspace": 1440, + "Ġprovid": 1441, + "hyl": 1442, + "Ġinvestig": 1443, + "Ġapproach": 1444, + "aterial": 1445, + "onse": 1446, + "lecular": 1447, + "Ġparameters": 1448, + "Ġphase": 1449, + "ulations": 1450, + "ubl": 1451, + "beta": 1452, + "Ġav": 1453, + "Ġflu": 1454, + "Ġpotential": 1455, + "ĠThese": 1456, + "sigma": 1457, + "lo": 1458, + "times": 1459, + "Ġoptim": 1460, + "ision": 1461, + "Ġaff": 1462, + "Ġmean": 1463, + "Ġbehavi": 1464, + "Ġvol": 1465, + "orem": 1466, + "agne": 1467, + "Ġdecre": 1468, + "tional": 1469, + "Ġsolution": 1470, + "Ġhuman": 1471, + "ger": 1472, + "Ġpaper": 1473, + "Ġcompar": 1474, + "Ġlower": 1475, + "andard": 1476, + "Ġcorrel": 1477, + "cri": 1478, + "Ġcurrent": 1479, + "Ġder": 1480, + "ission": 1481, + "ĠFigure": 1482, + "Ġproduc": 1483, + "Ġwater": 1484, + "ĠTo": 1485, + "Ġthose": 1486, + "Ġacid": 1487, + "Ġcancer": 1488, + "Ġlocal": 1489, + "ton": 1490, + "Ġflow": 1491, + "Ġregion": 1492, + "Ġhealth": 1493, + "Ġimportant": 1494, + "ograph": 1495, + "abl": 1496, + "Ġselec": 1497, + "Ġgre": 1498, + "Ġindi": 1499, + "ade": 1500, + "rid": 1501, + "Ġshould": 1502, + "based": 1503, + "Ġabove": 1504, + "ld": 1505, + "Ġsystems": 1506, + "ication": 1507, + "Ġed": 1508, + "Ġtyp": 1509, + "Ġphys": 1510, + "oper": 1511, + "Ġcompon": 1512, + "ON": 1513, + "Ġsuper": 1514, + "ga": 1515, + "hemical": 1516, + "isk": 1517, + "oph": 1518, + "Ġhy": 1519, + "Ġanaly": 1520, + "inu": 1521, + "Ġtarget": 1522, + "ĠAd": 1523, + "Ġpat": 1524, + "gamma": 1525, + "Ġsamples": 1526, + "Ġsl": 1527, + "Ġpart": 1528, + "olds": 1529, + "Ġbel": 1530, + "imum": 1531, + "ĠIm": 1532, + "Ġdisease": 1533, + "II": 1534, + "ists": 1535, + "iver": 1536, + "Ġperformance": 1537, + "ĠĠĠĠĠĠĠĠĠĠĠ": 1538, + "gle": 1539, + "Ġox": 1540, + "ndom": 1541, + "ĠĠĠĠĠ": 1542, + "Ġbecause": 1543, + "ayer": 1544, + "Ġrange": 1545, + "Ġcoun": 1546, + "Ġincreased": 1547, + "och": 1548, + "onal": 1549, + "Ġvery": 1550, + "Ġdynam": 1551, + "anti": 1552, + "Ġadd": 1553, + "Ġinhib": 1554, + "Ġmethods": 1555, + "idence": 1556, + "inical": 1557, + "erence": 1558, + "ival": 1559, + "ule": 1560, + "Ġfactor": 1561, + "Ġfin": 1562, + "ints": 1563, + "viron": 1564, + "Ġsour": 1565, + "verage": 1566, + "equ": 1567, + "Ġear": 1568, + "Ġshowed": 1569, + "ites": 1570, + "Ġperformed": 1571, + "Ġrese": 1572, + "ĠEn": 1573, + "Ġspecies": 1574, + "AC": 1575, + "ĠCl": 1576, + "hip": 1577, + "tilde": 1578, + "io": 1579, + "ately": 1580, + "Th": 1581, + "ody": 1582, + "Ġincrease": 1583, + "ĠPh": 1584, + "âĢĿ": 1585, + "Ġshows": 1586, + "ĠAc": 1587, + "Ġpost": 1588, + "ording": 1589, + "ences": 1590, + "oy": 1591, + "ner": 1592, + "Ġresponse": 1593, + "Ġoccur": 1594, + "rho": 1595, + "Ġperiod": 1596, + "ars": 1597, + "Ġred": 1598, + "ĠOn": 1599, + "Ġdensity": 1600, + "Ġexample": 1601, + "get": 1602, + "Ġreal": 1603, + "ĠCount": 1604, + "acy": 1605, + "Ġpower": 1606, + "Ġabs": 1607, + "ital": 1608, + "Ġprim": 1609, + "âĢIJ": 1610, + "Ġdefined": 1611, + "Ġnormal": 1612, + "aj": 1613, + "Ġinst": 1614, + "Ġallow": 1615, + "Ġpossible": 1616, + "Ġvis": 1617, + "Ġreported": 1618, + "Ġsignal": 1619, + "theta": 1620, + "Ġden": 1621, + "ables": 1622, + "Ġdeg": 1623, + "Ġindivid": 1624, + "agnetic": 1625, + "Ġgroups": 1626, + "ae": 1627, + "arrow": 1628, + "Ġstat": 1629, + "Ġmechanism": 1630, + "osp": 1631, + "mer": 1632, + "other": 1633, + "Ġprot": 1634, + "Ġcases": 1635, + "Ġcr": 1636, + "Ġte": 1637, + "Ġintegr": 1638, + "ets": 1639, + "Ġdevelopment": 1640, + "Ġrandom": 1641, + "Ġinvol": 1642, + "Ġincluding": 1643, + "Ġerr": 1644, + "gram": 1645, + "Ġparticular": 1646, + "eps": 1647, + "Ġstandard": 1648, + "position": 1649, + "Ġcontrib": 1650, + "sequ": 1651, + "Ġmany": 1652, + "Ġfurther": 1653, + "Ġsignificantly": 1654, + "ators": 1655, + "urb": 1656, + "Ġagain": 1657, + "bar": 1658, + "Ġwithout": 1659, + "Ġsever": 1660, + "Ġtop": 1661, + "ret": 1662, + "led": 1663, + "Ġmatrix": 1664, + "Ġspecific": 1665, + "ateg": 1666, + "ĨĴ": 1667, + "Ġdirect": 1668, + "Ġsample": 1669, + "Ġthem": 1670, + "SA": 1671, + "oint": 1672, + "Ġrole": 1673, + "Ġchanges": 1674, + "raction": 1675, + "Ġsum": 1676, + "Ġindividual": 1677, + "IN": 1678, + "Ġimmun": 1679, + "ced": 1680, + "oh": 1681, + "Ġstrong": 1682, + "Ġep": 1683, + "Ġlinear": 1684, + "ually": 1685, + "delta": 1686, + "way": 1687, + "asing": 1688, + "Ġtim": 1689, + "Ġvi": 1690, + "ison": 1691, + "Ġfunctions": 1692, + "Ġamong": 1693, + "Ġsee": 1694, + "erest": 1695, + "Ġgrowth": 1696, + "Ġrati": 1697, + "ĠSc": 1698, + "ixed": 1699, + "RNA": 1700, + "eed": 1701, + "tau": 1702, + "Ġent": 1703, + "Ġdr": 1704, + "ores": 1705, + "Ġapproxim": 1706, + "ful": 1707, + "Ġrele": 1708, + "Ġfactors": 1709, + "Ġdiscuss": 1710, + "Ġphot": 1711, + "Ġproposed": 1712, + "ero": 1713, + "omega": 1714, + "Ġfour": 1715, + "astic": 1716, + "Ġyears": 1717, + "hesis": 1718, + "ique": 1719, + "Ġmaterial": 1720, + "Ġbre": 1721, + "Ġprof": 1722, + "ĠAp": 1723, + "Ġneg": 1724, + "Ġbu": 1725, + "Ġassess": 1726, + "ĠâĢľ": 1727, + "Ġvir": 1728, + "atter": 1729, + "Ġdescribed": 1730, + "istics": 1731, + "Ġcompos": 1732, + "az": 1733, + "struc": 1734, + "Ġtum": 1735, + "partial": 1736, + "af": 1737, + "Ġwho": 1738, + "atal": 1739, + "Ġdemonstr": 1740, + "ances": 1741, + "yt": 1742, + "Ġremain": 1743, + "Ġless": 1744, + "Ġpositive": 1745, + "omic": 1746, + "Ġsince": 1747, + "ogn": 1748, + "Ġcondition": 1749, + "::": 1750, + "Ġdoes": 1751, + "tice": 1752, + "osph": 1753, + "Ġprov": 1754, + "ĠCO": 1755, + "Ġrat": 1756, + "Ġterms": 1757, + "box": 1758, + "Ġtak": 1759, + "Ġpattern": 1760, + "ale": 1761, + "Ġnan": 1762, + "ules": 1763, + "Ġmut": 1764, + "ished": 1765, + "Ġrelated": 1766, + "Ġtheory": 1767, + "bol": 1768, + "cdot": 1769, + "vironment": 1770, + "air": 1771, + "ivers": 1772, + "ĠAr": 1773, + "Ġï£": 1774, + "ressed": 1775, + "Ġâī¤": 1776, + "ĠMet": 1777, + "ID": 1778, + "ults": 1779, + "Ġβ": 1780, + "Ġdat": 1781, + "pose": 1782, + "Ġorig": 1783, + "Ġreturn": 1784, + "Ġchange": 1785, + "Ġlarg": 1786, + "au": 1787, + "aces": 1788, + "Ġarea": 1789, + "Ġgenes": 1790, + "AS": 1791, + "Ġhydro": 1792, + "Ġconsist": 1793, + "man": 1794, + "Ġresearch": 1795, + "ĠDe": 1796, + "Ġorgan": 1797, + "ask": 1798, + "Ġback": 1799, + "Ġfollows": 1800, + "ung": 1801, + "roll": 1802, + "Ġequation": 1803, + "plied": 1804, + "tr": 1805, + "Ġcorresponding": 1806, + "odes": 1807, + "ested": 1808, + "Ġrelations": 1809, + "nal": 1810, + "Ġfr": 1811, + "Ġlimit": 1812, + "mit": 1813, + "Ġoff": 1814, + "uted": 1815, + "Ġrisk": 1816, + "read": 1817, + "Ġknown": 1818, + "plit": 1819, + "tivity": 1820, + "Ġsequence": 1821, + "Ġconsidered": 1822, + "xi": 1823, + "ĠMod": 1824, + "vity": 1825, + "Ġnuc": 1826, + "cle": 1827, + "ices": 1828, + "Ġlength": 1829, + "Ġseveral": 1830, + "sing": 1831, + "oot": 1832, + "not": 1833, + "Ġstress": 1834, + "ĠIf": 1835, + "CT": 1836, + "roph": 1837, + "Ġcommun": 1838, + "Ġclust": 1839, + "ĠLe": 1840, + "me": 1841, + "antum": 1842, + "Ġmemb": 1843, + "Ġlab": 1844, + "Ġeven": 1845, + "Ġinflu": 1846, + "ck": 1847, + "ĠÃĹ": 1848, + "Ġlog": 1849, + "ving": 1850, + "ests": 1851, + "Ġhis": 1852, + "ank": 1853, + "ĠInd": 1854, + "actions": 1855, + "fty": 1856, + "mod": 1857, + "Ġreview": 1858, + "though": 1859, + "Ġeffici": 1860, + "Ġmap": 1861, + "infty": 1862, + "Ġbeing": 1863, + "land": 1864, + "Ġclinical": 1865, + "Ġmeasured": 1866, + "ering": 1867, + "ĠTable": 1868, + "Ġshe": 1869, + "see": 1870, + "Ġsection": 1871, + "Ġavail": 1872, + "omen": 1873, + "Ġvers": 1874, + "Ġdel": 1875, + "ither": 1876, + "eration": 1877, + "Ġhand": 1878, + "Ġcontinu": 1879, + "Ġconn": 1880, + "hors": 1881, + "rad": 1882, + "Ġfam": 1883, + "Ġlear": 1884, + "Ġinitial": 1885, + "ystem": 1886, + "Ġge": 1887, + "Ġâ̲": 1888, + "Ġcirc": 1889, + "Ġpubl": 1890, + "ĠIs": 1891, + "Ġvia": 1892, + "Ġcommon": 1893, + "ife": 1894, + "Ġmark": 1895, + "Ġever": 1896, + "arc": 1897, + "big": 1898, + "ertain": 1899, + "\\\\": 1900, + "var": 1901, + "As": 1902, + "roscop": 1903, + "Ġage": 1904, + "Ġhow": 1905, + "ĠLet": 1906, + "struct": 1907, + "Ġaverage": 1908, + "vant": 1909, + "ĠSh": 1910, + "imensional": 1911, + "SC": 1912, + "ape": 1913, + "nu": 1914, + "Ġloss": 1915, + "ason": 1916, + "ides": 1917, + "Ġpopulation": 1918, + "Ġdomain": 1919, + "inding": 1920, + "we": 1921, + "AL": 1922, + "Ġaccur": 1923, + "ety": 1924, + "Ġcaus": 1925, + "Delta": 1926, + "rapy": 1927, + "Ġprom": 1928, + "time": 1929, + "Ġintro": 1930, + "Ġmultiple": 1931, + "Ġconstant": 1932, + "pling": 1933, + "ino": 1934, + "ajor": 1935, + "ior": 1936, + "abol": 1937, + "def": 1938, + "Ġpoints": 1939, + "verse": 1940, + "name": 1941, + "ĠSe": 1942, + "itor": 1943, + "Pro": 1944, + "arm": 1945, + "Ġtiss": 1946, + "Ġfib": 1947, + "Ġgraph": 1948, + "Ġcall": 1949, + "atisf": 1950, + "Ġconduc": 1951, + "dex": 1952, + "ĠNe": 1953, + "Ġpers": 1954, + "ern": 1955, + "CR": 1956, + "angle": 1957, + "Ġfrequency": 1958, + "AP": 1959, + "Ġpresented": 1960, + "amp": 1961, + "Ġbefore": 1962, + "ords": 1963, + "Ġinput": 1964, + "ĠâĨĴ": 1965, + "Ġparticip": 1966, + "OR": 1967, + "Ġchild": 1968, + "Ġcre": 1969, + "fficient": 1970, + "Ġsepar": 1971, + "uration": 1972, + "α": 1973, + "Ġexist": 1974, + "ised": 1975, + "Ġlight": 1976, + "imal": 1977, + "****": 1978, + "ĠDNA": 1979, + "hel": 1980, + "Ġinterest": 1981, + "bf": 1982, + "ke": 1983, + "Ġcollec": 1984, + "Ġtrain": 1985, + "ai": 1986, + "ĠPl": 1987, + "Ġλ": 1988, + "ĠCo": 1989, + "Ġimage": 1990, + "Ġhyp": 1991, + "oma": 1992, + "Ġweight": 1993, + "Ġcross": 1994, + "rt": 1995, + "Ġdifference": 1996, + "Ġfeatures": 1997, + "medi": 1998, + "type": 1999, + "Ġpress": 2000, + "IC": 2001, + "Ġtherm": 2002, + "Ġstates": 2003, + "ustr": 2004, + "till": 2005, + "Ġhist": 2006, + "Ġratio": 2007, + "aging": 2008, + "ĠAll": 2009, + "Ġhel": 2010, + "bon": 2011, + "Ġbehavior": 2012, + "Ġpri": 2013, + "Ġsynt": 2014, + "ended": 2015, + "ĠInt": 2016, + "tt": 2017, + "Ġvarious": 2018, + "rect": 2019, + "Ġprec": 2020, + "Ġtimes": 2021, + "MS": 2022, + "Ġanalyz": 2023, + "Ġcare": 2024, + "mat": 2025, + "Ġalong": 2026, + "Ġpur": 2027, + "atively": 2028, + "Ġstar": 2029, + "jects": 2030, + "ii": 2031, + "istance": 2032, + "ĠThen": 2033, + "AN": 2034, + "Ġparameter": 2035, + "ulate": 2036, + "Ġevery": 2037, + "Ġsatisf": 2038, + "Ġdetermined": 2039, + "ina": 2040, + "rane": 2041, + "Ġpair": 2042, + "ool": 2043, + "Table": 2044, + "Ġthus": 2045, + "ogene": 2046, + "ĠÏĨ": 2047, + "Ġprogram": 2048, + "asc": 2049, + "Ġenvironment": 2050, + "MP": 2051, + "Ġread": 2052, + "Ġach": 2053, + "Ġpresence": 2054, + "Ġmice": 2055, + "For": 2056, + "Ġproduction": 2057, + "Ġdifferences": 2058, + "Ġprovide": 2059, + "ste": 2060, + "ames": 2061, + "ĉĠ": 2062, + "Ġ±": 2063, + "roup": 2064, + "Ġelectron": 2065, + "Ġhyper": 2066, + "bit": 2067, + "ĠRec": 2068, + "Ġvector": 2069, + "uble": 2070, + "rangle": 2071, + "Ġwr": 2072, + "wide": 2073, + "ĠâĬ": 2074, + "rack": 2075, + "ryst": 2076, + "Ġinj": 2077, + "ega": 2078, + "Ġwhe": 2079, + "psilon": 2080, + "Ġagainst": 2081, + "Ġdiagn": 2082, + "Ġhom": 2083, + "Ġachie": 2084, + "ns": 2085, + "Ġrece": 2086, + "--------": 2087, + "Ġavailable": 2088, + "inf": 2089, + "Ġsuc": 2090, + "Ġgu": 2091, + "Ġmajor": 2092, + "ĠThus": 2093, + "ware": 2094, + "Ġsupport": 2095, + "lor": 2096, + "Ġexperimental": 2097, + "ĠMo": 2098, + "Ġconcentration": 2099, + "tics": 2100, + "Ġnec": 2101, + "Ġphen": 2102, + "sq": 2103, + "Ġclos": 2104, + "sub": 2105, + "Ġknow": 2106, + "Ġformation": 2107, + "Ġdid": 2108, + "ouse": 2109, + "inary": 2110, + "ict": 2111, + "ĠCD": 2112, + "This": 2113, + "less": 2114, + "Ġnear": 2115, + "Ġimprove": 2116, + "abil": 2117, + "Ġreve": 2118, + "Ġexperiments": 2119, + "ience": 2120, + "ula": 2121, + "ored": 2122, + "Ġunc": 2123, + "__": 2124, + "Ġapplied": 2125, + "Ġreduced": 2126, + "Ġdetail": 2127, + "stand": 2128, + "Ġcho": 2129, + "omy": 2130, + "Ġcalculated": 2131, + "Ġenh": 2132, + "LES": 2133, + "itro": 2134, + "Ġrespons": 2135, + "Ġest": 2136, + "Ġmi": 2137, + "Ġcoe": 2138, + "ĠTherefore": 2139, + "ĠMore": 2140, + "bl": 2141, + "anced": 2142, + "ume": 2143, + "Ġband": 2144, + "Ġact": 2145, + "Ġeither": 2146, + "omes": 2147, + "ĠGen": 2148, + "vare": 2149, + "ET": 2150, + "reen": 2151, + "ĠPar": 2152, + "ĠSim": 2153, + "Ġidentified": 2154, + "Ġinteraction": 2155, + "Ġmade": 2156, + "Ġsource": 2157, + "tis": 2158, + "ots": 2159, + "mega": 2160, + "Ġserv": 2161, + "ms": 2162, + "alysis": 2163, + "vent": 2164, + "ense": 2165, + "gl": 2166, + "Ġlines": 2167, + "Ġappear": 2168, + "tif": 2169, + "Ġfree": 2170, + "oms": 2171, + "ining": 2172, + "eren": 2173, + "Ġchann": 2174, + "varepsilon": 2175, + "sim": 2176, + "Ġcou": 2177, + "°": 2178, + "Ġerror": 2179, + "Ġquanti": 2180, + "ĠEq": 2181, + "by": 2182, + "ĠII": 2183, + "tex": 2184, + "ĠSch": 2185, + "sqrt": 2186, + "ocus": 2187, + "Ġdev": 2188, + "quad": 2189, + "ters": 2190, + "Ġrelationship": 2191, + "oll": 2192, + "Ġgo": 2193, + "Ġwave": 2194, + "Ġleft": 2195, + "ways": 2196, + "hi": 2197, + "Ġright": 2198, + "obal": 2199, + "Ġdown": 2200, + "uk": 2201, + "Ġcoll": 2202, + "Ġmagnetic": 2203, + "Ġprog": 2204, + "dots": 2205, + "Ġstrateg": 2206, + "bs": 2207, + "unction": 2208, + "Ġenc": 2209, + "Ġclear": 2210, + "Ġcost": 2211, + "geb": 2212, + "etter": 2213, + "MILES": 2214, + "lamm": 2215, + "Ġmust": 2216, + "Ġeffective": 2217, + "Ġexc": 2218, + "Ġplas": 2219, + "Ġsuggest": 2220, + "itions": 2221, + "Ġleast": 2222, + "ying": 2223, + "lying": 2224, + "Ġlik": 2225, + "Omega": 2226, + "aking": 2227, + "Ġmaximum": 2228, + "Ġrelative": 2229, + "é": 2230, + "Ġaccording": 2231, + "ient": 2232, + "Ġway": 2233, + "Ġsem": 2234, + "atural": 2235, + "like": 2236, + "resh": 2237, + "ĠMe": 2238, + "Ps": 2239, + "ĠTrans": 2240, + "isc": 2241, + "Ġprac": 2242, + "Ġrun": 2243, + "Ġconver": 2244, + "Ġsk": 2245, + "Ġyield": 2246, + "geq": 2247, + "ably": 2248, + "Ġantib": 2249, + "izing": 2250, + "β": 2251, + "mission": 2252, + "Ġnow": 2253, + "Ġdetection": 2254, + "eloc": 2255, + "Ġget": 2256, + "ert": 2257, + "Ġvariables": 2258, + "Ġopen": 2259, + "Ġpressure": 2260, + "Ġstrain": 2261, + "ument": 2262, + "ĠFurther": 2263, + "Ġquantum": 2264, + "Ġimplement": 2265, + "Ġearly": 2266, + "Ġframe": 2267, + "Ġshort": 2268, + "Ġdrug": 2269, + "Ġrequired": 2270, + "PS": 2271, + "Ġmy": 2272, + "Ġmuch": 2273, + "Ġmem": 2274, + "CC": 2275, + "Ġquality": 2276, + "Ġproteins": 2277, + "Ġlayer": 2278, + "Ġques": 2279, + "Ġrecept": 2280, + "Ġhere": 2281, + "Ġproced": 2282, + "ured": 2283, + "Ġdeveloped": 2284, + "Ġposition": 2285, + "rum": 2286, + "Ġlat": 2287, + "Ġincreasing": 2288, + "EM": 2289, + "Ġmeasurements": 2290, + "Ġben": 2291, + "Ġisol": 2292, + "wh": 2293, + "To": 2294, + "Ġvalid": 2295, + "Ġfunctional": 2296, + "emma": 2297, + "...": 2298, + "orld": 2299, + "ries": 2300, + "Ġprobability": 2301, + "ĠNew": 2302, + "Ġmm": 2303, + "OS": 2304, + "AD": 2305, + "Ġδ": 2306, + "Ġscale": 2307, + "ĠFe": 2308, + "ĠTheorem": 2309, + "ĠQu": 2310, + "Ġcomponents": 2311, + "Ġblood": 2312, + "ĠÏĥ": 2313, + "acc": 2314, + "Ġbetter": 2315, + "Ġstep": 2316, + "Ġγ": 2317, + "Ġfac": 2318, + "aneous": 2319, + "Ġload": 2320, + "Ġmetabol": 2321, + "Ġevolution": 2322, + "son": 2323, + "ream": 2324, + "Ġeas": 2325, + "ird": 2326, + "dimensional": 2327, + "bor": 2328, + "Ġmus": 2329, + "Ġequations": 2330, + "psi": 2331, + "order": 2332, + "olar": 2333, + "Ġnumer": 2334, + "Ġkey": 2335, + "orth": 2336, + "Ġsimple": 2337, + "ift": 2338, + "cale": 2339, + "Ġindex": 2340, + "ĠâĢĵ": 2341, + "Ġconcentr": 2342, + "ges": 2343, + "Ġnegative": 2344, + "Ġveloc": 2345, + "Ġax": 2346, + "ĠEff": 2347, + "Ġfinite": 2348, + "Ġill": 2349, + "ching": 2350, + "Ġpatient": 2351, + "epsilon": 2352, + "Ġmen": 2353, + "Ġcri": 2354, + "IS": 2355, + "Cl": 2356, + "Ġconcl": 2357, + "Ġθ": 2358, + "ibility": 2359, + "Ġsymmet": 2360, + "enter": 2361, + "Ġdistance": 2362, + "Ġpolym": 2363, + "ights": 2364, + "Ġcult": 2365, + "Ġpeak": 2366, + "Ġacross": 2367, + "inition": 2368, + "Ġlet": 2369, + "Ġconstruc": 2370, + "Ġincluded": 2371, + "Ġhowever": 2372, + "Ġregions": 2373, + "Ġlearning": 2374, + "Ġevidence": 2375, + "inally": 2376, + "Ġneut": 2377, + "itation": 2378, + "Ġwhether": 2379, + "Ġoutput": 2380, + "ĠSection": 2381, + "Ġgood": 2382, + "IT": 2383, + "uation": 2384, + "Ġtypes": 2385, + "bm": 2386, + "cos": 2387, + "with": 2388, + "lim": 2389, + "otic": 2390, + "Ġstill": 2391, + "Ġdays": 2392, + "Ġstudied": 2393, + "Ġimages": 2394, + "ble": 2395, + "Ġarg": 2396, + "linear": 2397, + "Ġprocesses": 2398, + "Ġwid": 2399, + "Ġtraining": 2400, + "Ġindependent": 2401, + "plac": 2402, + "Ġresid": 2403, + "Ġsuccess": 2404, + "Ġnucle": 2405, + "GF": 2406, + "let": 2407, + "ploy": 2408, + "Ġtumor": 2409, + "Gamma": 2410, + "Ġtherefore": 2411, + "rast": 2412, + "Ġfocus": 2413, + "ash": 2414, + "Ġbelow": 2415, + "ially": 2416, + "Ġcomparison": 2417, + "Ġadj": 2418, + "Ġlike": 2419, + "Ġmolecular": 2420, + "ried": 2421, + "Ġfit": 2422, + "ĠDi": 2423, + "log": 2424, + "Ġplay": 2425, + "work": 2426, + "ections": 2427, + "Ġelectro": 2428, + "uit": 2429, + "more": 2430, + "Ġmight": 2431, + "Ġanalys": 2432, + "Ġmeans": 2433, + "Ġcorrelation": 2434, + "kn": 2435, + "Ġcontroll": 2436, + "IV": 2437, + "Ch": 2438, + "pec": 2439, + "rag": 2440, + "Ġmagn": 2441, + "Ġphysical": 2442, + "ION": 2443, + "Ġreveal": 2444, + "Ġphosph": 2445, + "Ġrates": 2446, + "Ġlarger": 2447, + "Ġstim": 2448, + "Ġsoft": 2449, + "Ġcompound": 2450, + "be": 2451, + "chi": 2452, + "ĠNo": 2453, + "Ġimpact": 2454, + "tor": 2455, + "Ġprimary": 2456, + "ocial": 2457, + "Ġapplication": 2458, + "Ġsolutions": 2459, + "duce": 2460, + "Ġcharacteristics": 2461, + "Ġelements": 2462, + "Ġview": 2463, + "Ġlater": 2464, + "uture": 2465, + "Ġfamily": 2466, + "rial": 2467, + "Ġtranscri": 2468, + "orption": 2469, + "Ġsw": 2470, + "CD": 2471, + "ED": 2472, + "Ġemb": 2473, + "Ġzero": 2474, + "ols": 2475, + "Ġlife": 2476, + "cep": 2477, + "ĠLi": 2478, + "ths": 2479, + "Ġseries": 2480, + "Ġaround": 2481, + "Ġtransition": 2482, + "ĠCor": 2483, + "ĠâĪĤ": 2484, + "Ġdatas": 2485, + "Ġher": 2486, + "ĠBy": 2487, + "AM": 2488, + "spec": 2489, + "oles": 2490, + "ography": 2491, + "tle": 2492, + "ĠCar": 2493, + "alle": 2494, + "Ġestabl": 2495, + "agement": 2496, + "Ġschem": 2497, + "ground": 2498, + "Ġfail": 2499, + "Ġexpected": 2500, + "Ġrequire": 2501, + "array": 2502, + "Ġexperiment": 2503, + "Ġelement": 2504, + "Ġneu": 2505, + "Ġgenerated": 2506, + "Ġsite": 2507, + "ĠCont": 2508, + "ĠRNA": 2509, + "eral": 2510, + "Ġcontent": 2511, + "Ġbacter": 2512, + "ler": 2513, + "Ġtransfer": 2514, + "ulf": 2515, + "rightarrow": 2516, + "any": 2517, + "ĠSince": 2518, + "induced": 2519, + "Ġreaction": 2520, + "heck": 2521, + "Ġstructures": 2522, + "Ġcount": 2523, + "Ġdetermine": 2524, + "zym": 2525, + "ĠBl": 2526, + "Ġunderstand": 2527, + "ocal": 2528, + "Ġsyn": 2529, + "Ġpoly": 2530, + "ury": 2531, + "Ġbest": 2532, + "Ġfixed": 2533, + "reng": 2534, + "Ġchemical": 2535, + "Ġtissue": 2536, + "Ġpul": 2537, + "Ġboundary": 2538, + "ising": 2539, + "Ġbro": 2540, + "atistical": 2541, + "icity": 2542, + "sk": 2543, + "ring": 2544, + "Ġlast": 2545, + "Ġchildren": 2546, + "rim": 2547, + "Ġreduction": 2548, + "Ġspin": 2549, + "Ġbody": 2550, + "operator": 2551, + "vari": 2552, + "Ġdiv": 2553, + "ymbol": 2554, + "Ġmal": 2555, + "Ġspati": 2556, + "ah": 2557, + "ĠBi": 2558, + "back": 2559, + "sy": 2560, + "Ġseen": 2561, + "ĠWith": 2562, + "ids": 2563, + "plications": 2564, + "Ġnecess": 2565, + "Ġside": 2566, + "Ġbrain": 2567, + "Ġfew": 2568, + "Ġapplications": 2569, + "utes": 2570, + "aches": 2571, + "Ġactive": 2572, + "varphi": 2573, + "term": 2574, + "Ġmom": 2575, + "iversity": 2576, + "Ġfinal": 2577, + "ledge": 2578, + "Ġdynamics": 2579, + "aving": 2580, + "erc": 2581, + "orphism": 2582, + "ones": 2583, + "off": 2584, + "pm": 2585, + "Ġaction": 2586, + "Ġnatural": 2587, + "ĠGe": 2588, + "Ġyou": 2589, + "lex": 2590, + "ĠĠĠĠĠĠ": 2591, + "stit": 2592, + "Ġgas": 2593, + "Ġmake": 2594, + "Ġinduced": 2595, + "ĠAfter": 2596, + "ĠWh": 2597, + "Ġcomponent": 2598, + "Ġinfection": 2599, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 2600, + "Ġconfir": 2601, + "igen": 2602, + "ĠSystem": 2603, + "ticle": 2604, + "Ġprovided": 2605, + "ternal": 2606, + "bers": 2607, + "OD": 2608, + "ĠInter": 2609, + "ott": 2610, + "aves": 2611, + "ĠStud": 2612, + "py": 2613, + "Ġresistance": 2614, + "ĠSur": 2615, + "atch": 2616, + "Ġdim": 2617, + "Ġinterp": 2618, + "Ġcycl": 2619, + "ont": 2620, + "iting": 2621, + "AG": 2622, + "Ġequival": 2623, + "otype": 2624, + "Ġpreviously": 2625, + "Ġadditional": 2626, + "outh": 2627, + "Ġimpl": 2628, + "Ġion": 2629, + "Ġir": 2630, + "Ġcop": 2631, + "Ġhal": 2632, + "Ġactivation": 2633, + "langle": 2634, + "Ġfull": 2635, + "SS": 2636, + "ĠOp": 2637, + "idd": 2638, + "Ġproof": 2639, + "Ġproblems": 2640, + "Ġtransform": 2641, + "Ġinteractions": 2642, + "Ġsupp": 2643, + "des": 2644, + "ĠReg": 2645, + "operatorname": 2646, + "egin": 2647, + "Ġcryst": 2648, + "Ġincreases": 2649, + "ronic": 2650, + "Ġadap": 2651, + "inant": 2652, + "Ġvelocity": 2653, + "ĠAss": 2654, + "iques": 2655, + "Ġcontinuous": 2656, + "ĠComp": 2657, + "ĠProper": 2658, + "Ġprior": 2659, + "orb": 2660, + "Ġnovel": 2661, + "Ġblock": 2662, + "Ġvolume": 2663, + "Ġregard": 2664, + "ometry": 2665, + "EC": 2666, + "Ġresulting": 2667, + "ĠOr": 2668, + "Ġcarbon": 2669, + "arent": 2670, + "Ġbinding": 2671, + "ij": 2672, + "Ġaccess": 2673, + "Ġweak": 2674, + "Ġunit": 2675, + "Ġide": 2676, + "\"\"": 2677, + "Ġcm": 2678, + "Ġcritical": 2679, + "Ġrespect": 2680, + "trans": 2681, + "Ġâī¥": 2682, + "Ġsal": 2683, + "ead": 2684, + "Ġsimulation": 2685, + "Ġcapac": 2686, + "itivity": 2687, + "Ġrecord": 2688, + "rak": 2689, + "Ġneur": 2690, + "onic": 2691, + "ople": 2692, + "Ġmg": 2693, + "Ġstreng": 2694, + "erve": 2695, + "Ġreduc": 2696, + "Ġpass": 2697, + "ordin": 2698, + "exp": 2699, + "jective": 2700, + "ensor": 2701, + "Ġparticles": 2702, + "Ġair": 2703, + "Ġlink": 2704, + "ĠÏĦ": 2705, + "Ġlist": 2706, + "cin": 2707, + "ĠOur": 2708, + "pri": 2709, + "vere": 2710, + "ibr": 2711, + "iform": 2712, + "Ġexplain": 2713, + "Ġfem": 2714, + "Ġutil": 2715, + "St": 2716, + "overline": 2717, + "Ġoften": 2718, + "ery": 2719, + "ope": 2720, + "ĠUsing": 2721, + "begin": 2722, + "Ġdifferenti": 2723, + "pers": 2724, + "self": 2725, + "izes": 2726, + "Ġconcentrations": 2727, + "IR": 2728, + "ĠSup": 2729, + "Ġbasis": 2730, + "Ġinclude": 2731, + "ĠBond": 2732, + "Ġextrac": 2733, + "ĠMethod": 2734, + "ĠData": 2735, + "ĠDef": 2736, + "wn": 2737, + "Ġnetworks": 2738, + "igned": 2739, + "âĢ¢": 2740, + "Ġexpressed": 2741, + "Ġcontrast": 2742, + "esis": 2743, + "col": 2744, + "inter": 2745, + "pid": 2746, + "Ġdri": 2747, + "Ġdefine": 2748, + "Ġinfluence": 2749, + "Ġselected": 2750, + "EL": 2751, + "Ġcontaining": 2752, + "Ġsil": 2753, + "gebra": 2754, + "reat": 2755, + "bolds": 2756, + "Ġinvestigated": 2757, + "ĠCol": 2758, + "ymmet": 2759, + "ytes": 2760, + "Ġmolec": 2761, + "Ġinvolved": 2762, + "Ġday": 2763, + "Ġchain": 2764, + "ĠMoreover": 2765, + "Ġdiag": 2766, + "Ġang": 2767, + "Ġlikely": 2768, + "Ġspectrum": 2769, + "Ġderiv": 2770, + "boldsymbol": 2771, + "Ġhelp": 2772, + "ĠAm": 2773, + "Ġtreated": 2774, + "Ġvariable": 2775, + "ellular": 2776, + "ĠDes": 2777, + "aps": 2778, + "Ġnm": 2779, + "ĠÏģ": 2780, + "ĠWhen": 2781, + "Ġhighly": 2782, + "amin": 2783, + "Ġwhat": 2784, + "related": 2785, + "Ġchrom": 2786, + "Ġsurv": 2787, + "ĠAnalysis": 2788, + "Ġsit": 2789, + "fact": 2790, + "oding": 2791, + "Ġproduct": 2792, + "Ġevents": 2793, + "ras": 2794, + "ĠPer": 2795, + "max": 2796, + "ĠAg": 2797, + "cont": 2798, + "icro": 2799, + "Ġadv": 2800, + "Ġcalled": 2801, + "Ġdegree": 2802, + "AB": 2803, + "TR": 2804, + "Ġseg": 2805, + "ĠCan": 2806, + "Ġdemonstrated": 2807, + "wise": 2808, + "Ġve": 2809, + "ĠCa": 2810, + "Ġdetected": 2811, + "co": 2812, + "Ġderived": 2813, + "Ġexhib": 2814, + "Ġglobal": 2815, + "alax": 2816, + "ulating": 2817, + "Al": 2818, + "angu": 2819, + "bo": 2820, + "Ġrecom": 2821, + "Ġfeature": 2822, + "dependent": 2823, + "Ġrot": 2824, + "vention": 2825, + "Ġremov": 2826, + "Ġwind": 2827, + "Ġaccuracy": 2828, + "size": 2829, + "Ġsumm": 2830, + "Ġmeasurement": 2831, + "Ġfields": 2832, + "wards": 2833, + "Ġliter": 2834, + "ataly": 2835, + "ĠStr": 2836, + "Ġreport": 2837, + "Ġcentral": 2838, + "Ġsqu": 2839, + "Ġtherapy": 2840, + "hest": 2841, + "Ġfeed": 2842, + "SMILES": 2843, + "ĠAN": 2844, + "Ġsites": 2845, + "â̲": 2846, + "ours": 2847, + "omal": 2848, + "Ġlip": 2849, + "Ġanalyzed": 2850, + "Ġ°": 2851, + "Ġwee": 2852, + "tem": 2853, + "Ġanother": 2854, + "iles": 2855, + "Ġcomplete": 2856, + "Ġnext": 2857, + "ĠOne": 2858, + "bi": 2859, + "rip": 2860, + "state": 2861, + "ĠModel": 2862, + "Ġfindings": 2863, + "ĠPre": 2864, + "Ġrecent": 2865, + "ascular": 2866, + "Ġestimate": 2867, + "Ġmechanisms": 2868, + "ĠResults": 2869, + "Ġparticipants": 2870, + "Ġeng": 2871, + "most": 2872, + "ometric": 2873, + "Ġequal": 2874, + "Ġrob": 2875, + "Ġpolar": 2876, + "Ġgenetic": 2877, + "Ġbo": 2878, + "Ġrest": 2879, + "ĠÏĢ": 2880, + "Ġrelation": 2881, + "Ġquestion": 2882, + "epti": 2883, + "Ġdiffic": 2884, + "ems": 2885, + "Ġfuture": 2886, + "ify": 2887, + "Ġmode": 2888, + "Ġmembrane": 2889, + "Ġheat": 2890, + "Aut": 2891, + "ding": 2892, + "Ġoxid": 2893, + "Ġconfig": 2894, + "plication": 2895, + "ĠMon": 2896, + "allel": 2897, + "ided": 2898, + "Ġdirection": 2899, + "pled": 2900, + "Ġprovides": 2901, + "Ġindicate": 2902, + "Ġsets": 2903, + "Ġtechnique": 2904, + "Ġmac": 2905, + "Ġhypot": 2906, + "Ġatten": 2907, + "Ġevent": 2908, + "Ġstage": 2909, + "Ġnode": 2910, + "Ġreference": 2911, + "Ġupper": 2912, + "Ġtechniques": 2913, + "Ġgreater": 2914, + "Ġdirectly": 2915, + "Ġareas": 2916, + "Ġdiss": 2917, + "hor": 2918, + "ĠPol": 2919, + "Ġevaluation": 2920, + "Ġpatterns": 2921, + "ĠAbstract": 2922, + "Ġvirus": 2923, + "vey": 2924, + "PC": 2925, + "Ġwomen": 2926, + "rient": 2927, + "Ġplasma": 2928, + "Ġproduced": 2929, + "Ġε": 2930, + "Ġanalyses": 2931, + "ĠSub": 2932, + "Ġsetting": 2933, + "Ġmoment": 2934, + "Ġthermal": 2935, + "Ġoptimal": 2936, + "Ġtaken": 2937, + "Ġrecogn": 2938, + "Ġvariation": 2939, + "ĠLemma": 2940, + "Ġsus": 2941, + "frak": 2942, + "ĠIL": 2943, + "Ġprocedure": 2944, + "hood": 2945, + "Ġaim": 2946, + "aries": 2947, + "mathfrak": 2948, + "Ġplant": 2949, + "brid": 2950, + "elect": 2951, + "Ġvisual": 2952, + "urs": 2953, + "cence": 2954, + "Ġfive": 2955, + "Ġspatial": 2956, + "Ġreceptor": 2957, + "Ġindicated": 2958, + "Ġess": 2959, + "Ġconsistent": 2960, + "Ġturn": 2961, + "tices": 2962, + "Ġexists": 2963, + "ectors": 2964, + "Ġenzym": 2965, + "meric": 2966, + "Ġnoise": 2967, + "Ġground": 2968, + "Ġestimated": 2969, + "eline": 2970, + "Ġchannel": 2971, + "tition": 2972, + "Ġdiscussed": 2973, + "omer": 2974, + "otes": 2975, + "Ġexact": 2976, + "ĠSec": 2977, + "Ġtake": 2978, + "Ġknowledge": 2979, + "Ġprop": 2980, + "Ġinflamm": 2981, + "Ġdouble": 2982, + "It": 2983, + "Ġcontext": 2984, + "ĠMed": 2985, + "MA": 2986, + "Ġfat": 2987, + "ams": 2988, + "data": 2989, + "ands": 2990, + "Ġcardi": 2991, + "ĠFurthermore": 2992, + "ocy": 2993, + "Ġobservations": 2994, + "apping": 2995, + "ĠInf": 2996, + "omial": 2997, + "Ġpublic": 2998, + "Ġemploy": 2999, + "Ġreason": 3000, + "ygen": 3001, + "Ġfollowed": 3002, + "Ġamount": 3003, + "Ġcertain": 3004, + "which": 3005, + "otyp": 3006, + "ĠCell": 3007, + "Ġchall": 3008, + "Ġparticle": 3009, + "ambda": 3010, + "Ġens": 3011, + "Ġpeople": 3012, + "ault": 3013, + "ĠUnd": 3014, + "ĠBe": 3015, + "umin": 3016, + "roscopy": 3017, + "MR": 3018, + "lation": 3019, + "Ġrepe": 3020, + "Ġable": 3021, + "ĠSo": 3022, + "ĠâĪŀ": 3023, + "Ġenti": 3024, + "Ġmove": 3025, + "Ġtrac": 3026, + "CO": 3027, + "Ġheter": 3028, + "Ġspeed": 3029, + "Ġefficiency": 3030, + "Ġoptical": 3031, + "Ġcombination": 3032, + "eness": 3033, + "Ġchem": 3034, + "LE": 3035, + "appa": 3036, + "Ġdecrease": 3037, + "μ": 3038, + "ped": 3039, + "note": 3040, + "ĠMulti": 3041, + "Ġaltern": 3042, + "Ġassume": 3043, + "ĠForm": 3044, + "stric": 3045, + "que": 3046, + "Ġiss": 3047, + "urrent": 3048, + "Ġprinc": 3049, + "Ġtask": 3050, + "ops": 3051, + "Ġwhereas": 3052, + "CH": 3053, + "Ġrevealed": 3054, + "Ġcannot": 3055, + "active": 3056, + "enz": 3057, + "Ġfore": 3058, + "Ġoperator": 3059, + "Ġcolum": 3060, + "atin": 3061, + "Ġoriginal": 3062, + "Ġsmaller": 3063, + "Ġmaterials": 3064, + "hydro": 3065, + "Ġcurve": 3066, + "Ġselection": 3067, + "akes": 3068, + "Ġexpos": 3069, + "ats": 3070, + "ĠÏī": 3071, + "Ġpack": 3072, + "Ġstability": 3073, + "Ġoverall": 3074, + "Ġmorph": 3075, + "Ġmetric": 3076, + "Ġol": 3077, + "Ġbar": 3078, + "ĠIN": 3079, + "IM": 3080, + "cy": 3081, + "ethyl": 3082, + "SP": 3083, + "Ġresponses": 3084, + "ancy": 3085, + "Ġlay": 3086, + "specific": 3087, + "Ġvs": 3088, + "aged": 3089, + "Ġsocial": 3090, + "Ġcut": 3091, + "IP": 3092, + "Ġlimited": 3093, + "encies": 3094, + "Ġprotoc": 3095, + "Ġcomposition": 3096, + "ĠThey": 3097, + "Ġnumbers": 3098, + "mbox": 3099, + "Ġdecreased": 3100, + "vec": 3101, + "RO": 3102, + "Authors": 3103, + "Ġthick": 3104, + "Ġcoordin": 3105, + "Ġmes": 3106, + "Ġaffect": 3107, + "Ġclose": 3108, + "Ġtransport": 3109, + "CA": 3110, + "rete": 3111, + "come": 3112, + "Ġcollected": 3113, + "ĠFrom": 3114, + "Ġcontains": 3115, + "chit": 3116, + "ĠDet": 3117, + "Ġflux": 3118, + "overy": 3119, + "eu": 3120, + "aff": 3121, + "Ġconducted": 3122, + "Ġcriter": 3123, + "Ġliterature": 3124, + "Ġmemory": 3125, + "Ġsequences": 3126, + "Ġpan": 3127, + "plicit": 3128, + "Ġtrue": 3129, + "Ġmedium": 3130, + "Ġdam": 3131, + "ire": 3132, + "cell": 3133, + "Let": 3134, + "eful": 3135, + "ĠAmeric": 3136, + "Ġnodes": 3137, + "gether": 3138, + "Ġtogether": 3139, + "TP": 3140, + "Ġrather": 3141, + "Ġauthors": 3142, + "Ġsch": 3143, + "Ġprocessing": 3144, + "Ġspectra": 3145, + "Ġevaluated": 3146, + "alk": 3147, + "Ġreduce": 3148, + "ĠHigh": 3149, + "ĠCons": 3150, + "Ġcycle": 3151, + "orn": 3152, + "iers": 3153, + "Ġpropor": 3154, + "ories": 3155, + "rate": 3156, + "Ġhost": 3157, + "ooth": 3158, + "ynt": 3159, + "Ġsources": 3160, + "Ġindividuals": 3161, + "Ġaccount": 3162, + "ĠAlthough": 3163, + "Ġcorrec": 3164, + "Ġplan": 3165, + "entially": 3166, + "Ġdistinc": 3167, + "Ġsoil": 3168, + "Ġsearch": 3169, + "Ġmanagement": 3170, + "Ġversion": 3171, + "âĢĶ": 3172, + "Ġfig": 3173, + "ĠNote": 3174, + "Ġhead": 3175, + "ditional": 3176, + "Ġbuild": 3177, + "ĠGl": 3178, + "asis": 3179, + "group": 3180, + "Ġdisplay": 3181, + "ĠUniversity": 3182, + "ootnote": 3183, + "ameter": 3184, + "minist": 3185, + "opl": 3186, + "ymph": 3187, + "Lambda": 3188, + "Ġidentify": 3189, + "ĠStere": 3190, + "ĠïĢ": 3191, + "Ġprol": 3192, + "ource": 3193, + "icial": 3194, + "Ġsimulations": 3195, + "Ġthresh": 3196, + "point": 3197, + "earch": 3198, + "elling": 3199, + "ĠAcc": 3200, + "Ġframework": 3201, + "Ġstrength": 3202, + "ĠAb": 3203, + "ticles": 3204, + "Ġcos": 3205, + "Footnote": 3206, + "ru": 3207, + "ospital": 3208, + "Ġstable": 3209, + "Ġmotion": 3210, + "Ġtested": 3211, + "Ġtests": 3212, + "aster": 3213, + "ldots": 3214, + "CL": 3215, + "inite": 3216, + "Ġspecial": 3217, + "====": 3218, + "Ġapproaches": 3219, + "ping": 3220, + "Ġconsum": 3221, + "SD": 3222, + "Ġjust": 3223, + "kappa": 3224, + "Ġthough": 3225, + "faces": 3226, + "Ġrapid": 3227, + "ensive": 3228, + "Ġnecessary": 3229, + "Ġtub": 3230, + "Ġforce": 3231, + "Ġblack": 3232, + "volution": 3233, + "ĠAtom": 3234, + "ĠHere": 3235, + "itude": 3236, + "ensions": 3237, + "ffer": 3238, + "rich": 3239, + "Ġgives": 3240, + "Ġshape": 3241, + "Ġhard": 3242, + "omp": 3243, + "Ġrepresentation": 3244, + "ling": 3245, + "ĠDec": 3246, + "Ġnumerical": 3247, + "Ġplace": 3248, + "Ġleading": 3249, + "Ġbenef": 3250, + "Ġregular": 3251, + "Ġcluster": 3252, + "Ġrelatively": 3253, + "Ġpercent": 3254, + "Ġautom": 3255, + "Ġsympt": 3256, + "ibri": 3257, + "ches": 3258, + "henyl": 3259, + "car": 3260, + "Ġillustr": 3261, + "ports": 3262, + "emic": 3263, + "Ġgive": 3264, + "Ġconven": 3265, + "lection": 3266, + "ĠĠĠĠĠĠĠĠĠĠĠĠ": 3267, + "ĠAnd": 3268, + "Ġfood": 3269, + "mic": 3270, + "ographic": 3271, + "Ġcheck": 3272, + "Ġability": 3273, + "iquid": 3274, + "Ġsubstr": 3275, + "ĠâĪĨ": 3276, + "Ġedge": 3277, + "ĠPD": 3278, + "Ġclassification": 3279, + "Ġsurvival": 3280, + "ĠCal": 3281, + "erate": 3282, + "Ġuseful": 3283, + "Ġcarried": 3284, + "Ġintensity": 3285, + "HE": 3286, + "ocenter": 3287, + "Ġpathway": 3288, + "Ġdefinition": 3289, + "Ġscheme": 3290, + "Ġsubsequ": 3291, + "ĠFirst": 3292, + "Ġconsequ": 3293, + "ĠDiff": 3294, + "Ġinhibit": 3295, + "Ġamplit": 3296, + "aser": 3297, + "ĠNetwork": 3298, + "normal": 3299, + "ĠST": 3300, + "Ġsolid": 3301, + "perim": 3302, + "comes": 3303, + "Ġcyt": 3304, + "odies": 3305, + "IF": 3306, + "radi": 3307, + "Ġmor": 3308, + "Ġcore": 3309, + "BS": 3310, + "********": 3311, + "Ġsoftware": 3312, + "ĠGu": 3313, + "ired": 3314, + "ident": 3315, + "Ġdifficult": 3316, + "use": 3317, + "Ġadded": 3318, + "ley": 3319, + "Ġcaused": 3320, + "gence": 3321, + "Ġbase": 3322, + "####": 3323, + "ogenic": 3324, + "from": 3325, + "Ġstatus": 3326, + "Ġassociation": 3327, + "ĠStereocenter": 3328, + "Ġgalax": 3329, + "NO": 3330, + "anguage": 3331, + "Ġdimension": 3332, + "ogenesis": 3333, + "Ġemission": 3334, + "Ġdeath": 3335, + "ulin": 3336, + "Ġagre": 3337, + "turb": 3338, + "nabl": 3339, + "poral": 3340, + "Ġpor": 3341, + "Ġcombined": 3342, + "Ġalgorithms": 3343, + "Cs": 3344, + "Ġsensitivity": 3345, + "Ġallows": 3346, + "Ġcapacity": 3347, + "version": 3348, + "Ġrestric": 3349, + "rome": 3350, + "Ġexposure": 3351, + "hy": 3352, + "anning": 3353, + "Ġobject": 3354, + "Ġcode": 3355, + "fl": 3356, + "roduction": 3357, + "resents": 3358, + "rup": 3359, + "Ġtext": 3360, + "ĠMat": 3361, + "Ġleads": 3362, + "Ġreson": 3363, + "Ġproducts": 3364, + "Ġwhole": 3365, + "Ġmatter": 3366, + "Phi": 3367, + "opt": 3368, + "encing": 3369, + "fficients": 3370, + "na": 3371, + "pecially": 3372, + "Ġhaving": 3373, + "ropy": 3374, + "Ġuncertain": 3375, + "enari": 3376, + "rical": 3377, + "Ġminim": 3378, + "Ġorigin": 3379, + "uper": 3380, + "ĠNon": 3381, + "Ġevaluate": 3382, + "Proof": 3383, + "cap": 3384, + "Ġsignaling": 3385, + "Ġpolymer": 3386, + "tically": 3387, + "itten": 3388, + "antit": 3389, + "Ġuser": 3390, + "level": 3391, + "Ġmeasures": 3392, + "Ġdynamic": 3393, + "Ġmonths": 3394, + "oti": 3395, + "rand": 3396, + "Ġuntil": 3397, + "Ġdenote": 3398, + "Ġnote": 3399, + "Ġmaintain": 3400, + "Ġkin": 3401, + "scill": 3402, + "Ġimaging": 3403, + "Ġpain": 3404, + "avy": 3405, + "Ġmit": 3406, + "othe": 3407, + "Ġregul": 3408, + "known": 3409, + "Ġplot": 3410, + "nabla": 3411, + "Ġfraction": 3412, + "wer": 3413, + "Ġstrategy": 3414, + "Ġgreat": 3415, + "Ġdataset": 3416, + "Ġunique": 3417, + "CM": 3418, + "Ġtw": 3419, + "han": 3420, + "ĠEu": 3421, + "andid": 3422, + "Ġbackground": 3423, + "Ġbroad": 3424, + "ilt": 3425, + "Ġimproved": 3426, + "Ġdiagnosis": 3427, + "ious": 3428, + "Ġdig": 3429, + "rem": 3430, + "era": 3431, + "Ġexcl": 3432, + "Ġmetal": 3433, + "Ġsix": 3434, + "Ġminimum": 3435, + "usions": 3436, + "ee": 3437, + "Ġcompounds": 3438, + "Ġasp": 3439, + "Ġeth": 3440, + "Ġdetect": 3441, + "ference": 3442, + "Ġη": 3443, + "Ġstatistical": 3444, + "atives": 3445, + "ris": 3446, + "Ġtheorem": 3447, + "ĠOF": 3448, + "ww": 3449, + "arily": 3450, + "ception": 3451, + "iving": 3452, + "Ġtesting": 3453, + "Ġdiagnos": 3454, + "Ġrepresents": 3455, + "Sigma": 3456, + "onical": 3457, + "Ġequivalent": 3458, + "Ġbiom": 3459, + "Ġsubst": 3460, + "raints": 3461, + "ĠRef": 3462, + "Ġscore": 3463, + "Ġdoc": 3464, + "Ġimplies": 3465, + "eter": 3466, + "Ġsynthesis": 3467, + "ilibri": 3468, + "attering": 3469, + "CS": 3470, + "alse": 3471, + "Ġneuro": 3472, + "Ġalthough": 3473, + "irus": 3474, + "methyl": 3475, + "Ġtranscription": 3476, + "ÏĢ": 3477, + "ĠMolecular": 3478, + "Ġcause": 3479, + "mut": 3480, + "ĠId": 3481, + "λ": 3482, + "add": 3483, + "Ġplac": 3484, + "Ġagg": 3485, + "ture": 3486, + "Ġlack": 3487, + "Ġprediction": 3488, + "raw": 3489, + "An": 3490, + "Ġult": 3491, + "ynomial": 3492, + "Ġimmune": 3493, + "ili": 3494, + "Ġprep": 3495, + "γ": 3496, + "class": 3497, + "Ġmach": 3498, + "ample": 3499, + "Ġresolution": 3500, + "Ġcoupling": 3501, + "seud": 3502, + "Ġindicates": 3503, + "Ġgeneration": 3504, + "Ġhar": 3505, + "Ġfund": 3506, + "scale": 3507, + "Ġeigen": 3508, + "ĠRel": 3509, + "abor": 3510, + "ĠCH": 3511, + "ext": 3512, + "amm": 3513, + "Ġcorrect": 3514, + "Ġscreen": 3515, + "Ġstructural": 3516, + "ĠpH": 3517, + "Ġrelevant": 3518, + "Ġangle": 3519, + "IG": 3520, + "Ġalgebra": 3521, + "helial": 3522, + "Ġworld": 3523, + "Ġcurves": 3524, + "ĠIntroduction": 3525, + "Ġthird": 3526, + "Ġintroduced": 3527, + "Big": 3528, + "no": 3529, + "auss": 3530, + "subset": 3531, + "Ġtransmission": 3532, + "Ġprofile": 3533, + "Ġν": 3534, + "Ġespecially": 3535, + "Ġattrib": 3536, + "uction": 3537, + "Ġcoefficients": 3538, + "Ġremains": 3539, + "Ġneigh": 3540, + "osen": 3541, + "Ġreli": 3542, + "Ġhighest": 3543, + "Ġuniform": 3544, + "Ġfar": 3545, + "chitect": 3546, + "||": 3547, + "Ġappropri": 3548, + "plex": 3549, + "ĠMass": 3550, + "ogeneous": 3551, + "ales": 3552, + "Ġrefer": 3553, + "Ġneeded": 3554, + "Ġdifferential": 3555, + "ceed": 3556, + "$$": 3557, + "ynamic": 3558, + "Ġsex": 3559, + "Ġspectral": 3560, + "char": 3561, + "PE": 3562, + "TS": 3563, + "Ġapproximately": 3564, + "value": 3565, + "Ġhalf": 3566, + "ending": 3567, + "Ġgradi": 3568, + "Ġcoefficient": 3569, + "ĠPhys": 3570, + "Ġconcer": 3571, + "Ġlabel": 3572, + "iral": 3573, + "Ġcharge": 3574, + "Ġoxygen": 3575, + "Ġdevi": 3576, + "Ġinternal": 3577, + "Ġexpans": 3578, + "load": 3579, + "ĠSm": 3580, + "rang": 3581, + "Con": 3582, + "ĠNa": 3583, + "Ġke": 3584, + "Ġdiab": 3585, + "ached": 3586, + "Ġlocation": 3587, + "Ġvolt": 3588, + "ĠDisc": 3589, + "---": 3590, + "ocytes": 3591, + "oretical": 3592, + "Ġgain": 3593, + "Ġmedi": 3594, + "ympt": 3595, + "oted": 3596, + "ĠVal": 3597, + "Ġcommunity": 3598, + "plementary": 3599, + "Ġtree": 3600, + "ĠTwo": 3601, + "Ġwhose": 3602, + "Ġdone": 3603, + "amine": 3604, + "Ġbiological": 3605, + "inks": 3606, + "Ġalmost": 3607, + "Ġslight": 3608, + "Ġrepro": 3609, + "ģĦ": 3610, + "Ġtherap": 3611, + "ocation": 3612, + "Ġgly": 3613, + "ĠEqu": 3614, + "Ġcolor": 3615, + "Ġnam": 3616, + "section": 3617, + "ĠEm": 3618, + "ready": 3619, + "Hz": 3620, + "PD": 3621, + "function": 3622, + "change": 3623, + "Ġprincip": 3624, + "Ġbecome": 3625, + "ĠâĢĺ": 3626, + "Ġcour": 3627, + "Ġlocated": 3628, + "Ġrang": 3629, + "inity": 3630, + "Ġinterval": 3631, + "gin": 3632, + "Ġinvestigate": 3633, + "free": 3634, + "Ġvitro": 3635, + "Ġsubset": 3636, + "Ġmov": 3637, + "Ġprove": 3638, + "Ġliver": 3639, + "ategor": 3640, + "etes": 3641, + "Ġlymph": 3642, + "dom": 3643, + "ĠElect": 3644, + "Ġserum": 3645, + "Ġscenari": 3646, + "ends": 3647, + "ĠFinally": 3648, + "Ġfilter": 3649, + "IL": 3650, + "Ġabund": 3651, + "mentation": 3652, + "imals": 3653, + "num": 3654, + "enced": 3655, + "Ġproperty": 3656, + "matrix": 3657, + "ĠCompar": 3658, + "Ġland": 3659, + "ĠChar": 3660, + "ressive": 3661, + "ulus": 3662, + "Ġbone": 3663, + "Ex": 3664, + "Ġradiation": 3665, + "Ġsuggested": 3666, + "ĠComput": 3667, + "Ġthreshold": 3668, + "ĠAD": 3669, + "Ġhor": 3670, + "Ġinduc": 3671, + "Ġapproximation": 3672, + "Ġadminist": 3673, + "Ġord": 3674, + "Ġlung": 3675, + "Ġreceived": 3676, + "Ġnorm": 3677, + "Ġestimates": 3678, + "Ġlaw": 3679, + "Ġoutcomes": 3680, + "ĠPr": 3681, + "Ġdepth": 3682, + "Ġelse": 3683, + "Ġcontribution": 3684, + "hetic": 3685, + "Ġconserv": 3686, + "Ġupon": 3687, + "Ġdeep": 3688, + "MD": 3689, + "Ġmel": 3690, + "Ġfilm": 3691, + "ilibrium": 3692, + "Ġoscill": 3693, + "olved": 3694, + "Ġbreast": 3695, + "CP": 3696, + "ĠDist": 3697, + "rices": 3698, + "inated": 3699, + "Ġoptimization": 3700, + "Ġpredicted": 3701, + "sf": 3702, + "dim": 3703, + "ĠSN": 3704, + "Ġavoid": 3705, + "Ġneural": 3706, + "Ġwa": 3707, + "rope": 3708, + "Ġdistributions": 3709, + "oxid": 3710, + "Ġsmooth": 3711, + "path": 3712, + "Ġfluid": 3713, + "Ġsaf": 3714, + "Ġchoice": 3715, + "AA": 3716, + "Ġmolecules": 3717, + "US": 3718, + "Ġalways": 3719, + "ivo": 3720, + "Ġregression": 3721, + "Ġsuccessful": 3722, + "Ġwall": 3723, + "oung": 3724, + "Ġactivities": 3725, + "Ġdependence": 3726, + "Ġrequires": 3727, + "Ġplane": 3728, + "Ġdesigned": 3729, + "PI": 3730, + "down": 3731, + "Ġpopulations": 3732, + "cor": 3733, + "mediate": 3734, + "Ġdose": 3735, + "Ġbond": 3736, + "Co": 3737, + "ĠMan": 3738, + "Ġdiagram": 3739, + "gs": 3740, + "Ġtool": 3741, + "Ġisolated": 3742, + "Ġversus": 3743, + "ney": 3744, + "Ġemerg": 3745, + "ĠAut": 3746, + "aim": 3747, + "field": 3748, + "Ġexamined": 3749, + "Ġsat": 3750, + "SM": 3751, + "ĠSpec": 3752, + "Ġparallel": 3753, + "isation": 3754, + "Ġdistinct": 3755, + "Ġpredict": 3756, + "Ġfer": 3757, + "Ġunderstanding": 3758, + "ĠSimilar": 3759, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 3760, + "udes": 3761, + "Ġorient": 3762, + "hic": 3763, + "uz": 3764, + "Ġmodified": 3765, + "Ġâμ": 3766, + "FF": 3767, + "There": 3768, + "Ġtrial": 3769, + "xy": 3770, + "gery": 3771, + "Ġalready": 3772, + "define": 3773, + "ming": 3774, + "ĠSD": 3775, + "Ġmonitor": 3776, + "Ġpsy": 3777, + "Ġbecomes": 3778, + "istry": 3779, + "ĠÎĵ": 3780, + "Ġhum": 3781, + "rier": 3782, + "ession": 3783, + "Ġhistory": 3784, + "ö": 3785, + "Ġξ": 3786, + "Ġestablished": 3787, + "Ġachieved": 3788, + "estern": 3789, + "ÏĨ": 3790, + "ĠHence": 3791, + "Ġassessment": 3792, + "otor": 3793, + "Ġdescribe": 3794, + "ochond": 3795, + "ylation": 3796, + "sts": 3797, + "space": 3798, + "Ġdiseases": 3799, + "jection": 3800, + "Ġslow": 3801, + "Ġnonlinear": 3802, + "ply": 3803, + "ml": 3804, + "Ġembed": 3805, + "comp": 3806, + "Ġefficient": 3807, + "Ġoperation": 3808, + "Ġcontact": 3809, + "oz": 3810, + "Ġinvari": 3811, + "Ġcenter": 3812, + "Ġconc": 3813, + "widetilde": 3814, + "Ġbeam": 3815, + "Ġclosed": 3816, + "ĠMethods": 3817, + "Ġchronic": 3818, + "aling": 3819, + "Ġsevere": 3820, + "Ġforms": 3821, + "ilit": 3822, + "side": 3823, + "pen": 3824, + "Ġbran": 3825, + "oud": 3826, + "tality": 3827, + "Ġmaps": 3828, + "acts": 3829, + "OL": 3830, + "PR": 3831, + "ĠÍ": 3832, + "sl": 3833, + "Ġinstance": 3834, + "ully": 3835, + "Ġestimation": 3836, + "Ġplate": 3837, + "Ġdevice": 3838, + "ĠIII": 3839, + "sin": 3840, + "Ġplants": 3841, + "ittle": 3842, + "Ġproduce": 3843, + "Ġhence": 3844, + "Ġnature": 3845, + "Ġrelease": 3846, + "ĠMin": 3847, + "rict": 3848, + "Ġconnected": 3849, + "ottom": 3850, + "ellar": 3851, + "Ġformed": 3852, + "Ġmob": 3853, + "Ġcomputed": 3854, + "ĠRE": 3855, + "Ġpolynomial": 3856, + "Ġliquid": 3857, + "gn": 3858, + "Ġassay": 3859, + "Ġmanif": 3860, + "ĠSi": 3861, + "rence": 3862, + "Ġaxis": 3863, + "VID": 3864, + "Ġsignals": 3865, + "θ": 3866, + "tok": 3867, + "ds": 3868, + "Ġrats": 3869, + "Ġtor": 3870, + "olecular": 3871, + "ched": 3872, + "Ġdescri": 3873, + "Ġexpon": 3874, + "Ġperturb": 3875, + "Ġgluc": 3876, + "Ġcolumn": 3877, + "UL": 3878, + "Ġmainly": 3879, + "Ġmul": 3880, + "ider": 3881, + "ĠCR": 3882, + "Ġcataly": 3883, + "Ġlaser": 3884, + "tioned": 3885, + "den": 3886, + "Ġsuggests": 3887, + "fig": 3888, + "Ġpropag": 3889, + "org": 3890, + "rep": 3891, + "Ġcharacterized": 3892, + "ologies": 3893, + "Ġaccum": 3894, + "Ġvary": 3895, + "Ġcontrolled": 3896, + "Ġupd": 3897, + "ĠBr": 3898, + "Ġentire": 3899, + "Ġ@": 3900, + "âģĦ": 3901, + "ĠÌ": 3902, + "Ġdatab": 3903, + "ano": 3904, + "amil": 3905, + "Ġadjust": 3906, + "ye": 3907, + "pression": 3908, + "erences": 3909, + "Ġessential": 3910, + "ĠHydro": 3911, + "ĠTr": 3912, + "Ġappropriate": 3913, + "Ġformula": 3914, + "Ġlattice": 3915, + "Ġacute": 3916, + "Ġusually": 3917, + "itable": 3918, + "Ġmar": 3919, + "Ġμm": 3920, + "ĠUSA": 3921, + "Ġincub": 3922, + "ocks": 3923, + "Ġpepti": 3924, + "iddle": 3925, + "Ġdecom": 3926, + "Ġdamage": 3927, + "Ġgenome": 3928, + "Ġmouse": 3929, + "circ": 3930, + "Ġlayers": 3931, + "Ġtrack": 3932, + "Ġtox": 3933, + "Ġreplac": 3934, + "Ġadvant": 3935, + "izon": 3936, + "Ġrecorded": 3937, + "Ġstart": 3938, + "Ġrank": 3939, + "ser": 3940, + "ĠGene": 3941, + "aussian": 3942, + "ingu": 3943, + "Ġconstraints": 3944, + "flow": 3945, + "Ġmig": 3946, + "PL": 3947, + "Ġincor": 3948, + "appro": 3949, + "Ġfast": 3950, + "Ġmuscle": 3951, + "Ġhome": 3952, + "eq": 3953, + "ĠÏĪ": 3954, + "Ġstrongly": 3955, + "ĠEurope": 3956, + "Ġsubjects": 3957, + "Ġobjects": 3958, + "test": 3959, + "tered": 3960, + "ĠWhile": 3961, + "Ġsymmetry": 3962, + "Ġquantif": 3963, + "``": 3964, + "Ġbreak": 3965, + "ĠExperim": 3966, + "Ġmixt": 3967, + "<<": 3968, + "ĠChina": 3969, + "ĠIdentif": 3970, + "Ġaffected": 3971, + "Ġsecondary": 3972, + "Ġinequ": 3973, + "incl": 3974, + "EG": 3975, + "FT": 3976, + "Ġfailure": 3977, + "ectiv": 3978, + "Ġkm": 3979, + "Ġsampling": 3980, + "Ġexpansion": 3981, + "Ġpractice": 3982, + "uations": 3983, + "ognitive": 3984, + "Ġdiet": 3985, + "Ġtemperatures": 3986, + "Ġcontrols": 3987, + "Ġchosen": 3988, + "Ġgenerally": 3989, + "ancer": 3990, + "Ġdegrad": 3991, + "uli": 3992, + "sm": 3993, + "otherapy": 3994, + "Ġtowards": 3995, + "ĠProperties": 3996, + "Ġclusters": 3997, + "Ġdelay": 3998, + "Ġhep": 3999, + "PA": 4000, + "ĠStudy": 4001, + "antitative": 4002, + "Ġclassical": 4003, + "ĠZh": 4004, + "ĠΩ": 4005, + "ĠBo": 4006, + "Ġseed": 4007, + "ĠStruct": 4008, + "Ġtrend": 4009, + "iological": 4010, + "Ġconfirmed": 4011, + "Ġdistributed": 4012, + "bial": 4013, + "Ġname": 4014, + "CN": 4015, + "valence": 4016, + "erior": 4017, + "iven": 4018, + "ned": 4019, + "Ġbehaviour": 4020, + "asks": 4021, + "gra": 4022, + "mark": 4023, + "Ġerrors": 4024, + "ĠRep": 4025, + "light": 4026, + "cript": 4027, + "If": 4028, + "Ġcandid": 4029, + "Ġdepends": 4030, + "ĠNational": 4031, + "Ġholds": 4032, + "Ġprotocol": 4033, + "ĠUnited": 4034, + "Ġinterface": 4035, + "Ġexpect": 4036, + "Ġïģ": 4037, + "ĠHIV": 4038, + "Ġroot": 4039, + "Ġscattering": 4040, + "words": 4041, + "Ġobservation": 4042, + "otop": 4043, + "Ġoccurs": 4044, + "ources": 4045, + "pite": 4046, + "ĠSte": 4047, + "Ġorth": 4048, + "Ġstain": 4049, + "Ġsteps": 4050, + "Ġcompare": 4051, + "Ġbasic": 4052, + "Ġinhibition": 4053, + "Ġsymptoms": 4054, + "ĠHealth": 4055, + "Ġpublished": 4056, + "fold": 4057, + "Ġtun": 4058, + "Ġvivo": 4059, + "Ġreconstr": 4060, + "ĠmRNA": 4061, + "icy": 4062, + "Ġhybrid": 4063, + "yr": 4064, + "Ġmixed": 4065, + "vis": 4066, + "ChI": 4067, + "Ġmedical": 4068, + "Ġfrag": 4069, + "Ġanimals": 4070, + "Ġimportance": 4071, + "Ġengine": 4072, + "ĠCT": 4073, + "Ġpairs": 4074, + "Ġbal": 4075, + "ĠEar": 4076, + "hers": 4077, + "Ġsynd": 4078, + "Ġarchitect": 4079, + "Ġidentification": 4080, + "Ġstrategies": 4081, + "Ġregulation": 4082, + "ĠLa": 4083, + "ror": 4084, + "Ġfluores": 4085, + "urity": 4086, + "Ġconcept": 4087, + "Ġattention": 4088, + "Ġtransformation": 4089, + "ucle": 4090, + "ĠResearch": 4091, + "Ġsimpl": 4092, + "Ġculture": 4093, + "aring": 4094, + "ifically": 4095, + "pir": 4096, + "ze": 4097, + "PT": 4098, + "mosp": 4099, + "Ġswit": 4100, + "Ġnor": 4101, + "Ġenhance": 4102, + "Ġenvironmental": 4103, + "rary": 4104, + "ĠMicro": 4105, + "Ġwide": 4106, + "opath": 4107, + "auge": 4108, + "zeta": 4109, + "Ġste": 4110, + "ĠEl": 4111, + "Ġwords": 4112, + "Ġnuclear": 4113, + "Ġlanguage": 4114, + "Ġdetails": 4115, + "opar": 4116, + "ĠRed": 4117, + "water": 4118, + "Ġcategor": 4119, + "Ġfile": 4120, + "Ġcover": 4121, + "Ġachieve": 4122, + "á": 4123, + "umm": 4124, + "Ġlig": 4125, + "Ġsurvey": 4126, + "Ġextended": 4127, + "lab": 4128, + "ĠInc": 4129, + "Ġdispers": 4130, + "Ġrecomm": 4131, + "ĠBased": 4132, + "Ġabsence": 4133, + "Ġconstruction": 4134, + "Ġpoor": 4135, + "Ġvoltage": 4136, + "Ġcellular": 4137, + "Ġmortality": 4138, + "Ġshowing": 4139, + "Ġprolif": 4140, + "mp": 4141, + "Ġneurons": 4142, + "Ġsupported": 4143, + "Ġprevent": 4144, + "eli": 4145, + "oxy": 4146, + "ica": 4147, + "Ġfully": 4148, + "Ġenough": 4149, + "otimes": 4150, + "ĠMR": 4151, + "Ġbul": 4152, + "Ġphenomen": 4153, + "FA": 4154, + "Ġdecision": 4155, + "Ġdual": 4156, + "Ġdecay": 4157, + "Ġown": 4158, + "Ġuses": 4159, + "Ġchalleng": 4160, + "Ġaddress": 4161, + "OC": 4162, + "tivation": 4163, + "Ġmill": 4164, + "Ġmodes": 4165, + "atus": 4166, + "iction": 4167, + "Ġabsorption": 4168, + "Ġepit": 4169, + "Ġconstra": 4170, + "Ġagreement": 4171, + "ĠAf": 4172, + "Ġbias": 4173, + "uded": 4174, + "Ġparts": 4175, + "Ġvan": 4176, + "Ġcolon": 4177, + "Ġexternal": 4178, + "Ġtheoretical": 4179, + "asi": 4180, + "Ġles": 4181, + "abilities": 4182, + "LA": 4183, + "ttps": 4184, + "Ġinstead": 4185, + "Ġmembers": 4186, + "++": 4187, + "Ġrecently": 4188, + "Ġprepared": 4189, + "Ġarticle": 4190, + "day": 4191, + "Ġextract": 4192, + "Ġâİ": 4193, + "Ġpathways": 4194, + "ÏĦ": 4195, + "mid": 4196, + "orage": 4197, + "Ġcommunication": 4198, + "Ġaccel": 4199, + "Ġunits": 4200, + "itis": 4201, + "ynthesis": 4202, + "Ġamplitude": 4203, + "rie": 4204, + "ultaneous": 4205, + "ĠLear": 4206, + "ecause": 4207, + "do": 4208, + "eff": 4209, + "Ġexplicit": 4210, + "Ġcriteria": 4211, + "bre": 4212, + "Ġexec": 4213, + "Ġmechanical": 4214, + "eros": 4215, + "ĠConcl": 4216, + "ĠExt": 4217, + "Ġclasses": 4218, + "Ġlonger": 4219, + "Ġcalculations": 4220, + "eutic": 4221, + "ociated": 4222, + "ardi": 4223, + "Ġcourse": 4224, + "Ġpartial": 4225, + "Ġsensor": 4226, + "Ïĥ": 4227, + "Ġoperators": 4228, + "ĠAmerican": 4229, + "ĠmM": 4230, + "Ġvacc": 4231, + "occ": 4232, + "icon": 4233, + "Ġoutcome": 4234, + "Ġanalog": 4235, + "Ġthickness": 4236, + "Ġreach": 4237, + "Ġassumed": 4238, + "ender": 4239, + "Ġmale": 4240, + "SE": 4241, + "Ġintra": 4242, + "Ġimplementation": 4243, + "emia": 4244, + "Ġenhanced": 4245, + "bility": 4246, + "Ġeasily": 4247, + "ump": 4248, + "Ġcarcin": 4249, + "osa": 4250, + "Ġcorresponds": 4251, + "neg": 4252, + "Ġmagnitude": 4253, + "const": 4254, + "Ġlatter": 4255, + "Ġrepresented": 4256, + "Ġsed": 4257, + "Ġparticularly": 4258, + "Ġwritten": 4259, + "part": 4260, + "Ġoil": 4261, + "berg": 4262, + "ĠBar": 4263, + "Ġdys": 4264, + "ĠSome": 4265, + "ĠMar": 4266, + "Ġalternative": 4267, + "ĠGerm": 4268, + "Ġgenerate": 4269, + "Ġconstruct": 4270, + "ians": 4271, + "stream": 4272, + "Ġec": 4273, + "ochemical": 4274, + "ibration": 4275, + "operative": 4276, + "ister": 4277, + "Ġrobust": 4278, + "tre": 4279, + "Ġmodeling": 4280, + "oring": 4281, + "ese": 4282, + "ded": 4283, + "ideo": 4284, + "Ġhydrogen": 4285, + "uments": 4286, + "Ġdemonstrate": 4287, + "Ġcorrelated": 4288, + "Ġsystematic": 4289, + "Ġsurgery": 4290, + "Ġindicating": 4291, + "Ġhypothesis": 4292, + "year": 4293, + "mitted": 4294, + "Ġstars": 4295, + "Ġprofiles": 4296, + "Ġconsists": 4297, + "tri": 4298, + "Ġdependent": 4299, + "ishing": 4300, + "top": 4301, + "Ġheart": 4302, + "atically": 4303, + "Ġinjury": 4304, + "Ġquad": 4305, + "Ġweeks": 4306, + "uting": 4307, + "ĠTe": 4308, + "Ġidenti": 4309, + "Ġgradient": 4310, + "Ġcalculation": 4311, + "Ġur": 4312, + "RT": 4313, + "zation": 4314, + "Ġeduc": 4315, + "ening": 4316, + "PP": 4317, + "zed": 4318, + "ush": 4319, + "Ġcharacteristic": 4320, + "Ġstrains": 4321, + "eth": 4322, + "Ġdivers": 4323, + "âĪĪ": 4324, + "oids": 4325, + "olic": 4326, + "Ġinterpret": 4327, + "Key": 4328, + "Ġattack": 4329, + "pective": 4330, + "Ġlabor": 4331, + "Ġmetast": 4332, + "NF": 4333, + "Ġtissues": 4334, + "Ġradius": 4335, + "ĠEach": 4336, + "Ġcat": 4337, + "Ġdon": 4338, + "Ġelev": 4339, + "Ġassemb": 4340, + "rons": 4341, + "Ġarbit": 4342, + "Ġpanel": 4343, + "Ġgrid": 4344, + "Ġtable": 4345, + "roscopic": 4346, + "Ġcle": 4347, + "ĠIntern": 4348, + "obacter": 4349, + "Ġassumption": 4350, + "ĠCOVID": 4351, + "Ġbounded": 4352, + "Ġothers": 4353, + "Ġschool": 4354, + "Ġhospital": 4355, + "lected": 4356, + "ĠCu": 4357, + "ÃĹ": 4358, + "Ġcomplet": 4359, + "Ġwidth": 4360, + "Ġlinks": 4361, + "po": 4362, + "ollow": 4363, + "Ġnut": 4364, + "Ġappears": 4365, + "rown": 4366, + "aro": 4367, + "Ġusers": 4368, + "Ġclim": 4369, + "Ġslightly": 4370, + "Ġblue": 4371, + "rab": 4372, + "ĠSer": 4373, + "Ġfigure": 4374, + "ĠRad": 4375, + "Ġelectric": 4376, + "mm": 4377, + "ochastic": 4378, + "rief": 4379, + "Ġcollection": 4380, + "Ġstem": 4381, + "Ġgover": 4382, + "Ġbur": 4383, + "Ġtypical": 4384, + "sup": 4385, + "Ġaggreg": 4386, + "raz": 4387, + "ĉĉĉ": 4388, + "Ġstation": 4389, + "Ġarter": 4390, + "ively": 4391, + "itrogen": 4392, + "Ġconstit": 4393, + "empt": 4394, + "ĠEffect": 4395, + "Ġdescription": 4396, + "Ġscores": 4397, + "Ġmethyl": 4398, + "ĠOb": 4399, + "ĠStates": 4400, + "Ġsplit": 4401, + "ĠVari": 4402, + "ĠWang": 4403, + "Ġcere": 4404, + "ĠFran": 4405, + "Ġneeds": 4406, + "ĠFour": 4407, + "Ġproject": 4408, + "Ġdevices": 4409, + "Ġintegral": 4410, + "ĠEs": 4411, + "ymmetric": 4412, + "Ġmess": 4413, + "Ġplays": 4414, + "ĠLearning": 4415, + "Ġoverl": 4416, + "Here": 4417, + "ignment": 4418, + "Ġdeliver": 4419, + "apan": 4420, + "CE": 4421, + "Ġgauge": 4422, + "ĠJoh": 4423, + "----------------": 4424, + "Ġunderlying": 4425, + "Ġthin": 4426, + "Ġassessed": 4427, + "Ġdiffusion": 4428, + "Ġheight": 4429, + "ĠSw": 4430, + "Ġdark": 4431, + "print": 4432, + "range": 4433, + "ĠCI": 4434, + "ises": 4435, + "lier": 4436, + "rant": 4437, + "omorphism": 4438, + "Ġcompact": 4439, + "ips": 4440, + "ĠName": 4441, + "Ġtechnology": 4442, + "agen": 4443, + "Ġconfiguration": 4444, + "Ġduration": 4445, + "ĠClass": 4446, + "Ġput": 4447, + "Ġmaking": 4448, + "Ġasympt": 4449, + "aid": 4450, + "Ġcoh": 4451, + "Ġcomplexity": 4452, + "Ġsections": 4453, + "ĠMD": 4454, + "ĠĠĠĠĠĠĠĠĠ": 4455, + "Ġrad": 4456, + "Ġsubstrate": 4457, + "dd": 4458, + "Ġann": 4459, + "Ġorganic": 4460, + "Ġtaking": 4461, + "Ġincludes": 4462, + "Ġkine": 4463, + "ares": 4464, + "Ġrow": 4465, + "ategory": 4466, + "Ġmitochond": 4467, + "UT": 4468, + "Ġsyndrome": 4469, + "ĠProb": 4470, + "retion": 4471, + "Ġfluct": 4472, + "ĠDis": 4473, + "Ġtransl": 4474, + "plas": 4475, + "Ġpsych": 4476, + "Ġsurfaces": 4477, + "Ġdetailed": 4478, + "amilton": 4479, + "Ġhold": 4480, + "ĠâĬĹ": 4481, + "ĠCN": 4482, + "Ġdil": 4483, + "ĠOver": 4484, + "atform": 4485, + "Ġvertical": 4486, + "Ġcomputation": 4487, + "Ġpure": 4488, + "Ġmakes": 4489, + "Ġexisting": 4490, + "Ġexamples": 4491, + "SO": 4492, + "orders": 4493, + "Ġmix": 4494, + "Ġincorpor": 4495, + "Ġrequ": 4496, + "antic": 4497, + "DNA": 4498, + "δ": 4499, + "Ġcloud": 4500, + "ĠTechn": 4501, + "Ġïĥ": 4502, + "ements": 4503, + "Ġbaseline": 4504, + "stein": 4505, + "Ġbelong": 4506, + "Ġtrials": 4507, + "Ġhorizon": 4508, + "Ġphosphor": 4509, + "Ġans": 4510, + "dix": 4511, + "roid": 4512, + "Ġapply": 4513, + "ued": 4514, + "ernel": 4515, + "Ġfemale": 4516, + "icacy": 4517, + "Ġvectors": 4518, + "Ġmatrices": 4519, + "atric": 4520, + "ĠMc": 4521, + "Ġpy": 4522, + "Ġchlor": 4523, + "len": 4524, + "Ġclearly": 4525, + "static": 4526, + "ref": 4527, + "ĠSouth": 4528, + "Ġmedia": 4529, + "ĠShe": 4530, + "ĠBay": 4531, + "Ġagents": 4532, + "By": 4533, + "Ġdifferentiation": 4534, + "istant": 4535, + "orphic": 4536, + "Ġvariety": 4537, + "Ġservice": 4538, + "Ġmapping": 4539, + "velength": 4540, + "Ġchannels": 4541, + "Ġcompute": 4542, + "Ġstream": 4543, + "uls": 4544, + "amide": 4545, + "oking": 4546, + "vit": 4547, + "Ġyields": 4548, + "omb": 4549, + "ĠGaussian": 4550, + "Ġpen": 4551, + "une": 4552, + "Ġexperience": 4553, + "band": 4554, + "ĠDo": 4555, + "mathsf": 4556, + "Ġallowed": 4557, + "Ar": 4558, + "RA": 4559, + "Ġbacterial": 4560, + "Ġmiss": 4561, + "Ġbacteria": 4562, + "Ġmomentum": 4563, + "Ġhours": 4564, + "uck": 4565, + "ĠProposition": 4566, + "bert": 4567, + "otrop": 4568, + "Ġvariance": 4569, + "Ġtrig": 4570, + "Ġshift": 4571, + "Ġequilibrium": 4572, + "bu": 4573, + "ING": 4574, + "Ġwhite": 4575, + "Ġkind": 4576, + "Ġjoint": 4577, + "Ġtemporal": 4578, + "ĠIV": 4579, + "ĠAfric": 4580, + "Ġsubject": 4581, + "ĠPo": 4582, + "head": 4583, + "idel": 4584, + "Ġantibody": 4585, + "ĠEffects": 4586, + "Ġspe": 4587, + "Ġsufficient": 4588, + "jected": 4589, + "rees": 4590, + "ĠTop": 4591, + "Ġmutations": 4592, + "isions": 4593, + "BC": 4594, + "Ġinduction": 4595, + "Ġinteresting": 4596, + "ella": 4597, + "can": 4598, + "Ġsusp": 4599, + "ĠGroup": 4600, + "Ġextracted": 4601, + "istically": 4602, + "coh": 4603, + "map": 4604, + "Ġaccurate": 4605, + "Ġtoo": 4606, + "Ġdimensions": 4607, + "tegr": 4608, + "Ġgreen": 4609, + "ĠRo": 4610, + "Ġwild": 4611, + "Ġloop": 4612, + "Ġmeta": 4613, + "Ġsubstit": 4614, + "osome": 4615, + "Ġsuggesting": 4616, + "Ġspecim": 4617, + "amental": 4618, + "iment": 4619, + "Ġij": 4620, + "Ġclaim": 4621, + "Ġauthor": 4622, + "Ġfilms": 4623, + "Ġcounter": 4624, + "Ġconventional": 4625, + "rin": 4626, + "otypes": 4627, + "Ġpast": 4628, + "Since": 4629, + "mediated": 4630, + "reatment": 4631, + "Ġextension": 4632, + "Ġbio": 4633, + "Ġsent": 4634, + "hal": 4635, + "Ġobjective": 4636, + "Ġarray": 4637, + "Ġsuitable": 4638, + "ĠBut": 4639, + "ĠHuman": 4640, + "organ": 4641, + "but": 4642, + "model": 4643, + "SI": 4644, + "Ġhealthy": 4645, + "Ġvac": 4646, + "Ġlate": 4647, + "Ġring": 4648, + "Ġlittle": 4649, + "MT": 4650, + "Ġsquare": 4651, + "Ġgeometry": 4652, + "ĠTHE": 4653, + "ĠSing": 4654, + "jug": 4655, + "Ġstudents": 4656, + ",,": 4657, + "Ġadult": 4658, + "Ġcharacterization": 4659, + "Ġatmosp": 4660, + "Ġmonitoring": 4661, + "ani": 4662, + "net": 4663, + "ĠPa": 4664, + "optosis": 4665, + "Ġcontin": 4666, + "ĠSol": 4667, + "Ġdatabase": 4668, + "import": 4669, + "mann": 4670, + "ĠProcess": 4671, + "ĠChen": 4672, + "Ġgap": 4673, + "Ġenzyme": 4674, + "OT": 4675, + "Ġsimultaneous": 4676, + "Ġexistence": 4677, + "BP": 4678, + "ĠJapan": 4679, + "ounts": 4680, + "Ġturb": 4681, + "Ġspaces": 4682, + "ĠWeight": 4683, + "ophil": 4684, + "Ġast": 4685, + "Ġwrite": 4686, + "Ġdiabetes": 4687, + "ĠCA": 4688, + "Ġneutral": 4689, + "Ġvariations": 4690, + "axon": 4691, + "Ġbegin": 4692, + "under": 4693, + "Ġextraction": 4694, + "ĠPati": 4695, + "Ġfron": 4696, + "efined": 4697, + "Ġacids": 4698, + "Ġservices": 4699, + "Ġsense": 4700, + "Ġagent": 4701, + "hens": 4702, + "electric": 4703, + "values": 4704, + "Ġimprovement": 4705, + "herent": 4706, + "actic": 4707, + "Ġacet": 4708, + "cdots": 4709, + "Ġamino": 4710, + "Ġroom": 4711, + "Ġexpress": 4712, + "Ġexcept": 4713, + "Ġold": 4714, + "plant": 4715, + "cepti": 4716, + "ĠPCR": 4717, + "ĠER": 4718, + "ĠBoth": 4719, + "vex": 4720, + "Ġadults": 4721, + "Ġpseud": 4722, + "Ġalle": 4723, + "Ġworks": 4724, + "Ġconsumption": 4725, + "ipher": 4726, + "cm": 4727, + "cast": 4728, + "Ġnanopar": 4729, + "Ïī": 4730, + "Ġecon": 4731, + "ynamics": 4732, + "Ġalter": 4733, + "Ġskin": 4734, + "Ġdiameter": 4735, + "GC": 4736, + "ĠSign": 4737, + "vial": 4738, + "Ġglucose": 4739, + "ĠNorth": 4740, + "otox": 4741, + "Ġprote": 4742, + "dx": 4743, + "ĠCr": 4744, + "Ġfract": 4745, + "Ġinside": 4746, + "Ġstatic": 4747, + "wid": 4748, + "Ġstorage": 4749, + "ĠAL": 4750, + "ĠMark": 4751, + "ĠAT": 4752, + "Ġsensitive": 4753, + "Ġads": 4754, + "Ġedges": 4755, + "ana": 4756, + "Re": 4757, + "Ġsummar": 4758, + "ĠAND": 4759, + "Ġremaining": 4760, + "ditionally": 4761, + "Ġmid": 4762, + "ĠTheory": 4763, + "MC": 4764, + "Ġflex": 4765, + "oly": 4766, + "Ġdegradation": 4767, + "Ġintr": 4768, + "ota": 4769, + "isms": 4770, + "Ġampl": 4771, + "ĠAre": 4772, + "Ġworking": 4773, + "Ġdiversity": 4774, + "Ġtensor": 4775, + "Ġbinary": 4776, + "\"\"\"": 4777, + "vals": 4778, + "Ġhem": 4779, + "ML": 4780, + "Ġμg": 4781, + "neq": 4782, + "ensities": 4783, + "Ġtakes": 4784, + "Ġcharg": 4785, + "Ġintervention": 4786, + "Ġalb": 4787, + "Ġqual": 4788, + "Ġmentioned": 4789, + "Ġones": 4790, + "ĠAccording": 4791, + "illed": 4792, + "OH": 4793, + "Sup": 4794, + "Ġgalaxies": 4795, + "aily": 4796, + "Ġrule": 4797, + "Ġcognitive": 4798, + "hern": 4799, + "Ġrecognition": 4800, + "Ġbuffer": 4801, + "Ġmarg": 4802, + "ĠNi": 4803, + "ĠâĪļ": 4804, + "Ġclin": 4805, + "Ġintegration": 4806, + "Ġsin": 4807, + "ĠAlso": 4808, + "Ġmachine": 4809, + "wr": 4810, + "idity": 4811, + "Ġsubsequent": 4812, + "Fe": 4813, + "Ġnames": 4814, + "ather": 4815, + "ĠCy": 4816, + "Ġmetabolism": 4817, + "Ġreactions": 4818, + "Ġiter": 4819, + "Ġnoted": 4820, + "Ġcauses": 4821, + "ĠHamilton": 4822, + "go": 4823, + "Ġrare": 4824, + "VA": 4825, + "ĠMy": 4826, + "vol": 4827, + "asure": 4828, + "Ġsignificance": 4829, + "ĠNone": 4830, + "Ġvehic": 4831, + "SR": 4832, + "Ġvariability": 4833, + "ĠDevelop": 4834, + "aren": 4835, + "Ġpromot": 4836, + "ards": 4837, + "Ġcomputational": 4838, + "Ġshall": 4839, + "izations": 4840, + "ĠHydrogen": 4841, + "Ġproliferation": 4842, + "Ġcoupled": 4843, + "chron": 4844, + "Ġconvergence": 4845, + "Ġgast": 4846, + "Ġcalculate": 4847, + "raft": 4848, + "paration": 4849, + "heric": 4850, + "ĠPC": 4851, + "plate": 4852, + "ptions": 4853, + "ĠAlgorithm": 4854, + "Ġresulted": 4855, + "DE": 4856, + "Ġinvestigation": 4857, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 4858, + "olation": 4859, + "Ġtasks": 4860, + "Ġleg": 4861, + "iness": 4862, + "Ġemployed": 4863, + "On": 4864, + "Ġexperi": 4865, + "Ġtraject": 4866, + "GA": 4867, + "Ġpurpose": 4868, + "ĠNum": 4869, + "Ġcompletely": 4870, + "that": 4871, + "ĠOptim": 4872, + "Ġformal": 4873, + "eck": 4874, + "ĠProtein": 4875, + "Ġgoal": 4876, + "Ġthroughout": 4877, + "Ġconsidering": 4878, + "Ġreflect": 4879, + "treated": 4880, + "oration": 4881, + "ribution": 4882, + "Ġtherapeutic": 4883, + "Ġfinding": 4884, + "UN": 4885, + "Then": 4886, + "ilities": 4887, + "Ġunknown": 4888, + "overed": 4889, + "Ġvertex": 4890, + "Ġexchange": 4891, + "Ġdrugs": 4892, + "ĠCP": 4893, + "Ġinstr": 4894, + "Ġsymmetric": 4895, + "ĠDep": 4896, + "Ġconstructed": 4897, + "Ġprevalence": 4898, + "Ġdecreases": 4899, + "ĠmiR": 4900, + "Ġyet": 4901, + "Ġbox": 4902, + "graph": 4903, + "widehat": 4904, + "alian": 4905, + "ufact": 4906, + "LR": 4907, + "cription": 4908, + "Ġnp": 4909, + "ĠCharacter": 4910, + "Ġepid": 4911, + "ν": 4912, + "Ġstages": 4913, + "Ġsay": 4914, + "ĠDuring": 4915, + "atur": 4916, + "ientif": 4917, + "abric": 4918, + "ü": 4919, + "ament": 4920, + "inations": 4921, + "Ġsolar": 4922, + "Ġdiscrete": 4923, + "ĠEr": 4924, + "ĠGeneral": 4925, + "bal": 4926, + "ĠCent": 4927, + "uel": 4928, + "Ġmixture": 4929, + "Ġwidely": 4930, + "ĠSecond": 4931, + "Ġresources": 4932, + "ĠAppro": 4933, + "ĠIR": 4934, + "Ġstring": 4935, + "opro": 4936, + "Ġinner": 4937, + "ĠComplex": 4938, + "OP": 4939, + "Ġatoms": 4940, + "Ġphases": 4941, + "Ġdomains": 4942, + "ada": 4943, + "Ġcountries": 4944, + "acet": 4945, + "ociation": 4946, + "izer": 4947, + "Ġitself": 4948, + "Ġminimal": 4949, + "ĠControl": 4950, + "ttp": 4951, + "Ġbottom": 4952, + "ball": 4953, + "ĠMay": 4954, + "dev": 4955, + "now": 4956, + "ember": 4957, + "Ġpercentage": 4958, + "ĠOther": 4959, + "omas": 4960, + "Ġled": 4961, + "Res": 4962, + "ĠEng": 4963, + "kg": 4964, + "Ġfrequencies": 4965, + "kin": 4966, + "Ġincidence": 4967, + "Ġanimal": 4968, + "Ġadop": 4969, + "Ġidentity": 4970, + "ĠRT": 4971, + "Ġyoung": 4972, + "istent": 4973, + "weight": 4974, + "gu": 4975, + "Ġseason": 4976, + "Ġexplained": 4977, + "ĠUnder": 4978, + "iotic": 4979, + "well": 4980, + "Ġmetabolic": 4981, + "gical": 4982, + "±": 4983, + "Theorem": 4984, + "ades": 4985, + "plicated": 4986, + "Ġcontained": 4987, + "Ġsulf": 4988, + "Ġcool": 4989, + "Ġperson": 4990, + "Ïģ": 4991, + "Ġpix": 4992, + "ĠSal": 4993, + "link": 4994, + "ini": 4995, + "tual": 4996, + "SH": 4997, + "ged": 4998, + "ky": 4999, + "asts": 5000, + "ercise": 5001, + "ĠHar": 5002, + "Ġrelax": 5003, + "equiv": 5004, + "Ġyour": 5005, + "Ġunderg": 5006, + "Ġrecovery": 5007, + "Ġcomm": 5008, + "Ġdenotes": 5009, + "formed": 5010, + "aria": 5011, + "etic": 5012, + "Ġtumors": 5013, + "ĠHy": 5014, + "Ġmarkers": 5015, + "Ġplaced": 5016, + "olute": 5017, + "Ġwaves": 5018, + "Ġuncertainty": 5019, + "Ġcontribute": 5020, + "ĠHist": 5021, + "Ġaver": 5022, + "Ġfav": 5023, + "Ġpow": 5024, + "ĠSee": 5025, + "Ġteam": 5026, + "Ġscales": 5027, + "ientific": 5028, + "ierarch": 5029, + "Ġearlier": 5030, + "Ġsatisfies": 5031, + "Ġcrystal": 5032, + "Ġpregn": 5033, + "Ġobserve": 5034, + "Ġonline": 5035, + "Ġcontributions": 5036, + "ogram": 5037, + "ĠMa": 5038, + "Ġfrac": 5039, + "Ġspread": 5040, + "Ġonce": 5041, + "det": 5042, + "Ġrespond": 5043, + "Ġplatform": 5044, + "Ġinflammatory": 5045, + "utive": 5046, + "ĠSumm": 5047, + "place": 5048, + "Ġions": 5049, + "Ġwindow": 5050, + "axis": 5051, + "estinal": 5052, + "Ġdepending": 5053, + "Ġseparation": 5054, + "Ġforward": 5055, + "ĠTi": 5056, + "Ġglass": 5057, + "Ġaccept": 5058, + "Ġfeedback": 5059, + "Ġonto": 5060, + "ME": 5061, + "merc": 5062, + "unctional": 5063, + "Ġapoptosis": 5064, + "ĠProperty": 5065, + "Ġintegrated": 5066, + "Ġorb": 5067, + "Ġdeviation": 5068, + "Ġantibodies": 5069, + "Ġremoved": 5070, + "Ġlipid": 5071, + "armac": 5072, + "Ġarbitrary": 5073, + "agger": 5074, + "Ġembry": 5075, + "Ġgrain": 5076, + "Ġdrop": 5077, + "Ġstarting": 5078, + "Ġrelationships": 5079, + "ĠÏĩ": 5080, + "SF": 5081, + "Ġsimply": 5082, + "Ġfacilit": 5083, + "Ġzone": 5084, + "ils": 5085, + "Psi": 5086, + "Ġinequality": 5087, + "Keywords": 5088, + "Ġtoler": 5089, + "edge": 5090, + "Ġeasy": 5091, + "Ġalpha": 5092, + "Ġperf": 5093, + "width": 5094, + "init": 5095, + "Ġimplemented": 5096, + "CF": 5097, + "osity": 5098, + "ocyte": 5099, + "Ġproportion": 5100, + "rest": 5101, + "ĠSuper": 5102, + "Ġpref": 5103, + "Ġword": 5104, + "ev": 5105, + "Ġextent": 5106, + "Ġinjection": 5107, + "alled": 5108, + "ĠAnti": 5109, + "Ġbeta": 5110, + "ĠJan": 5111, + "ĠGa": 5112, + "ĠZhang": 5113, + "Ġiron": 5114, + "Ġquantitative": 5115, + "roc": 5116, + "Ġfall": 5117, + "Ġregarding": 5118, + "Ġfix": 5119, + "Ġdatasets": 5120, + "Ġtend": 5121, + "Ġscalar": 5122, + "Ġresidual": 5123, + "Ġratios": 5124, + "ĠΦ": 5125, + "king": 5126, + "Ġinflammation": 5127, + "Ġsingular": 5128, + "ĠPark": 5129, + "omatic": 5130, + "unctions": 5131, + "Ġwar": 5132, + "ÍĴ": 5133, + "hemat": 5134, + "Ġface": 5135, + "ĠHu": 5136, + "Ġfundamental": 5137, + "Ġwavelength": 5138, + "eling": 5139, + "ĠSuch": 5140, + "RNAs": 5141, + "ct": 5142, + "Ġiden": 5143, + "cean": 5144, + "new": 5145, + "Type": 5146, + "ĠFormula": 5147, + "Ġmedic": 5148, + "ussion": 5149, + "Ġdistingu": 5150, + "Ġresonance": 5151, + "ATION": 5152, + "inear": 5153, + "Ġhyd": 5154, + "ln": 5155, + "âĨĴ": 5156, + "ĠUp": 5157, + "Ġactual": 5158, + "Ġadapt": 5159, + "hene": 5160, + "Ġmotor": 5161, + "list": 5162, + "abit": 5163, + "Ind": 5164, + "otal": 5165, + "Ġneighbor": 5166, + "ĠPT": 5167, + "gener": 5168, + "Ġpossibility": 5169, + "ergies": 5170, + "Ġseems": 5171, + "ĠUS": 5172, + "Ġimm": 5173, + "Ġtypically": 5174, + "Ġsimulated": 5175, + "ĠSystems": 5176, + "ectiveness": 5177, + "rying": 5178, + "Ġkinase": 5179, + "Ġdecomposition": 5180, + "ateral": 5181, + "Ġrotation": 5182, + "pendix": 5183, + "enn": 5184, + "att": 5185, + "vate": 5186, + "Ġtargets": 5187, + "Ġsituation": 5188, + "Ġinvolve": 5189, + "Ġcreated": 5190, + "hesized": 5191, + "Ġalone": 5192, + "ci": 5193, + "ĠmL": 5194, + "Ġdivided": 5195, + "Ġbulk": 5196, + "oin": 5197, + "HC": 5198, + "Ġarm": 5199, + "LO": 5200, + "ills": 5201, + "Ġmedian": 5202, + "ham": 5203, + "imer": 5204, + "flu": 5205, + "Ġfiber": 5206, + "ĠSU": 5207, + "file": 5208, + "tivated": 5209, + "Ġradio": 5210, + "ĠNames": 5211, + "pe": 5212, + "Ġoste": 5213, + "Ġelim": 5214, + "Ġsuscepti": 5215, + "rehens": 5216, + "Ġdiscussion": 5217, + "ĠSep": 5218, + "Ġarchitecture": 5219, + "Ġdest": 5220, + "typ": 5221, + "rame": 5222, + "Ġpartition": 5223, + "Ġoccurred": 5224, + "Ġsizes": 5225, + "cles": 5226, + "Ġsched": 5227, + "Molecular": 5228, + "Ġκ": 5229, + "Ġinvas": 5230, + "cup": 5231, + "PCR": 5232, + "ĠSMILES": 5233, + "tially": 5234, + "oxide": 5235, + "ĠEd": 5236, + "Ġmanufact": 5237, + "ĠMaterial": 5238, + "Ġflat": 5239, + "Ġmutation": 5240, + "Ġintroduce": 5241, + "bound": 5242, + "Ġdisorders": 5243, + "regulated": 5244, + "ĠMor": 5245, + "Ġfalse": 5246, + "inger": 5247, + "ĠTR": 5248, + "Ġextrem": 5249, + "war": 5250, + "Ġsymbol": 5251, + "Ġanomal": 5252, + "ĠAR": 5253, + "Ġissues": 5254, + "Ġcoordinates": 5255, + "Ġreceptors": 5256, + "Ġprogression": 5257, + "ĠFl": 5258, + "ublic": 5259, + "Ġelectronic": 5260, + "Ġaspects": 5261, + "Ġdocument": 5262, + "flo": 5263, + "ĠPred": 5264, + "Ġgraphs": 5265, + "Ġtraditional": 5266, + "DM": 5267, + "Ġsafety": 5268, + "ĠDr": 5269, + "ĠSequ": 5270, + "Ġcomposite": 5271, + "ĠÎĽ": 5272, + "Ġresponsible": 5273, + "Ġgran": 5274, + "Ġintermediate": 5275, + "odium": 5276, + "posite": 5277, + "phase": 5278, + "dt": 5279, + "Ġweek": 5280, + "Ġdos": 5281, + "Ġstabil": 5282, + "LC": 5283, + "ĠKey": 5284, + "Ġvertices": 5285, + "Ġcomputer": 5286, + "ĠCanonical": 5287, + "Ġinvariant": 5288, + "emark": 5289, + "benz": 5290, + "Ġice": 5291, + "tile": 5292, + "zy": 5293, + "ĠOut": 5294, + "Ġmovement": 5295, + "Ġshif": 5296, + "leep": 5297, + "Ġdaily": 5298, + "Ġpositions": 5299, + "Ġhim": 5300, + "Ġcreate": 5301, + "Our": 5302, + "Ġresearc": 5303, + "Ġprogn": 5304, + "duct": 5305, + "Ġscreening": 5306, + "Ġchoose": 5307, + "process": 5308, + "mal": 5309, + "Ġlaboratory": 5310, + "Ġoperations": 5311, + "Ġtools": 5312, + "ologic": 5313, + "qquad": 5314, + "Ġcommonly": 5315, + "Ġvoid": 5316, + "Ġoccup": 5317, + "associated": 5318, + "Ġcorrelations": 5319, + "Ġcarcinoma": 5320, + "lin": 5321, + "Ġvideo": 5322, + "Ġheavy": 5323, + "Ġlargest": 5324, + "Ġmiddle": 5325, + "ĉĉĉĉ": 5326, + "ĠBas": 5327, + "asons": 5328, + "iding": 5329, + "Ġetc": 5330, + "ache": 5331, + "ĠEval": 5332, + "ira": 5333, + "romagnetic": 5334, + "Ġcovari": 5335, + "LI": 5336, + "Ġdele": 5337, + "Ġstra": 5338, + "amples": 5339, + "oder": 5340, + "Ġcategory": 5341, + "ĠInstit": 5342, + "Ġpolicy": 5343, + "Based": 5344, + "ibly": 5345, + "Ġdetermination": 5346, + "Ġrespir": 5347, + "otropic": 5348, + "Ġolder": 5349, + "ĠMal": 5350, + "Ġcytok": 5351, + "Ġdegrees": 5352, + "aut": 5353, + "illing": 5354, + "eting": 5355, + "Ġreduces": 5356, + "Ġideal": 5357, + "binding": 5358, + "ĠSpect": 5359, + "unit": 5360, + "Ġdiver": 5361, + "ĠWorld": 5362, + "Ġmarked": 5363, + "aly": 5364, + "Ġcomplexes": 5365, + "ĠSummary": 5366, + "Ġpropose": 5367, + "ĠAustr": 5368, + "Ġmaxim": 5369, + "Ġround": 5370, + "Ġinhibitor": 5371, + "Ġefficacy": 5372, + "actor": 5373, + "bur": 5374, + "Ġtransf": 5375, + "ĠGal": 5376, + "Ġproved": 5377, + "ĠDefined": 5378, + "At": 5379, + "Ġselect": 5380, + "Ġnanoparticles": 5381, + "Wh": 5382, + "ken": 5383, + "ĠSP": 5384, + "enge": 5385, + "Ġdelivery": 5386, + "Ġdisorder": 5387, + "ĠInChI": 5388, + "ĠComparison": 5389, + "ifying": 5390, + "ĠMechan": 5391, + "Ġconclude": 5392, + "Ġrepeated": 5393, + "ellow": 5394, + "ĠÃĢ": 5395, + "CI": 5396, + "ĠHz": 5397, + "analysis": 5398, + "Tr": 5399, + "ÃŃ": 5400, + "elihood": 5401, + "Ġexpand": 5402, + "ĠDevelopment": 5403, + "ĠState": 5404, + "Ġtet": 5405, + "ffic": 5406, + "Ġparent": 5407, + "Ġscenario": 5408, + "rs": 5409, + "ĠWhat": 5410, + "âī": 5411, + "Ġstimulation": 5412, + "ĠObs": 5413, + "zero": 5414, + "Ġmanner": 5415, + "ashed": 5416, + "ĠLog": 5417, + "Ġoxide": 5418, + "phosph": 5419, + "Ġmigration": 5420, + "Ġsubgroup": 5421, + "rosis": 5422, + "ipp": 5423, + "DR": 5424, + "dec": 5425, + "osomal": 5426, + "Ġsegment": 5427, + "ogenous": 5428, + "FP": 5429, + "hand": 5430, + "ĠSurface": 5431, + "itz": 5432, + "Ġcrystall": 5433, + "this": 5434, + "Ġbuilding": 5435, + "tag": 5436, + "Ġreducing": 5437, + "Ġuns": 5438, + "Ġrecomb": 5439, + "Ġcam": 5440, + "Ġlimits": 5441, + "ocardi": 5442, + "&&": 5443, + "Ġseparate": 5444, + "Ġsupplement": 5445, + "kele": 5446, + "Ġgrad": 5447, + "Ġissue": 5448, + "ĠQuantum": 5449, + "Ġcurrently": 5450, + "Ġquite": 5451, + "EP": 5452, + "Ġrules": 5453, + "Ġweights": 5454, + "uary": 5455, + "illi": 5456, + "Ġbecame": 5457, + "ó": 5458, + "Ġnormalized": 5459, + "ĠNetworks": 5460, + "erved": 5461, + "Ġstatistics": 5462, + "ĠTime": 5463, + "ĠUV": 5464, + "Ġcav": 5465, + "used": 5466, + "Ġfish": 5467, + "Ġmajority": 5468, + "ĠPe": 5469, + "Ġcohort": 5470, + "Ġsemi": 5471, + "Ġgame": 5472, + "monary": 5473, + "MM": 5474, + "oded": 5475, + "Ġvent": 5476, + "Ġauto": 5477, + "Ġabundance": 5478, + "nov": 5479, + "Ġasymptotic": 5480, + "Ġtreatments": 5481, + "uly": 5482, + "Ġconstraint": 5483, + "Ġbey": 5484, + "ĠSO": 5485, + "Ġstd": 5486, + "Ġdeveloping": 5487, + "ĠNot": 5488, + "Lemma": 5489, + "Ġapparent": 5490, + "Ġcircuit": 5491, + "From": 5492, + "ĠEuropean": 5493, + "Ġsolve": 5494, + "ĠÍij": 5495, + "ux": 5496, + "Ġbeyond": 5497, + "ept": 5498, + "Ġappe": 5499, + "requency": 5500, + "Ġvacu": 5501, + "ĠIndeed": 5502, + "ĠChemical": 5503, + "ĠUndefined": 5504, + "Note": 5505, + "Ġnull": 5506, + "Ġinverse": 5507, + "Ġnamely": 5508, + "Ġshear": 5509, + "mL": 5510, + "All": 5511, + "Rec": 5512, + "Ġgeneralized": 5513, + "ranes": 5514, + "ĠTest": 5515, + "iling": 5516, + "Ġfluorescence": 5517, + "ĠΣ": 5518, + "Ġindepend": 5519, + "diff": 5520, + "Ġproviding": 5521, + "phenyl": 5522, + "hing": 5523, + "Ġviral": 5524, + "ĠBecause": 5525, + "Ġintrac": 5526, + "ĠHig": 5527, + "Ġwant": 5528, + "Ġprinciple": 5529, + "anol": 5530, + "Ġha": 5531, + "ovascular": 5532, + "Ġformer": 5533, + "Ġestablish": 5534, + "Ġadvantage": 5535, + "III": 5536, + "Ġsequencing": 5537, + "Ġprocedures": 5538, + "tra": 5539, + "index": 5540, + "fe": 5541, + "Ġpi": 5542, + "Ġobvious": 5543, + "Ġregime": 5544, + "sur": 5545, + "Ġpresents": 5546, + "Ġdisplac": 5547, + "Ġdecl": 5548, + "ĠAppendix": 5549, + "Ġinteract": 5550, + "lands": 5551, + "inate": 5552, + "omorphic": 5553, + "Ġlowest": 5554, + "Ġartif": 5555, + "Ġinvolving": 5556, + "Ġcommerc": 5557, + "Ġdop": 5558, + "Ġconform": 5559, + "ĠIg": 5560, + "rolog": 5561, + "vised": 5562, + "Ġflo": 5563, + "Ġcardiac": 5564, + "pts": 5565, + "rig": 5566, + "Ġensure": 5567, + "Ġaccumulation": 5568, + "Ġentropy": 5569, + "Ġidea": 5570, + "perature": 5571, + "Ġquestions": 5572, + "ĠPR": 5573, + "Ġstatistically": 5574, + "dagger": 5575, + "Ġnitrogen": 5576, + "scr": 5577, + "ĠDiscussion": 5578, + "Ġreports": 5579, + "Ġpulse": 5580, + "Ġrequirements": 5581, + "Ġcomparing": 5582, + "quired": 5583, + "layer": 5584, + "Ġspectroscopy": 5585, + "vironments": 5586, + "Ġscaling": 5587, + "Ġexposed": 5588, + "MB": 5589, + "ξ": 5590, + "Ġhole": 5591, + "Ġá": 5592, + "Ġsimilarity": 5593, + "Ġvariants": 5594, + "body": 5595, + "Ġkeep": 5596, + "ĠCancer": 5597, + "edi": 5598, + "osomes": 5599, + "Ç«": 5600, + "Ad": 5601, + "âĪŀ": 5602, + "monic": 5603, + "ging": 5604, + "split": 5605, + "know": 5606, + "Ġrough": 5607, + "hematical": 5608, + "vision": 5609, + "Ġded": 5610, + "Ġcycles": 5611, + "Ġfamil": 5612, + "Ġadministration": 5613, + "etal": 5614, + "Ġcoron": 5615, + "Ġinfections": 5616, + "Ġmacroph": 5617, + "atics": 5618, + "Ġpredictions": 5619, + "isher": 5620, + "erent": 5621, + "reted": 5622, + "include": 5623, + "Ġclimate": 5624, + "sec": 5625, + "========": 5626, + "ĠMS": 5627, + "Ġcompe": 5628, + "ratic": 5629, + "lig": 5630, + "poses": 5631, + "Ġpolarization": 5632, + "llip": 5633, + "derived": 5634, + "Ġreleased": 5635, + "Ġconnection": 5636, + "lic": 5637, + "Ġcoli": 5638, + "Ġoutside": 5639, + "Ġabsolute": 5640, + "esian": 5641, + "ĠEnd": 5642, + "ĠOf": 5643, + "Ġidentical": 5644, + "Ġmodule": 5645, + "Ġmitochondrial": 5646, + "Ġadvanced": 5647, + "ingly": 5648, + "formance": 5649, + "Ġtoward": 5650, + "uding": 5651, + "ek": 5652, + "Ġmeaning": 5653, + "crib": 5654, + "ulator": 5655, + "FN": 5656, + "key": 5657, + "cons": 5658, + "Ġapplying": 5659, + "ishes": 5660, + "Ġmamm": 5661, + "Ġderivatives": 5662, + "Ġorientation": 5663, + "Ġstochastic": 5664, + "ĠAug": 5665, + "Ġrenal": 5666, + "ĠGreen": 5667, + "Ġcomplement": 5668, + "obl": 5669, + "pirical": 5670, + "orts": 5671, + "BM": 5672, + "Ġexcess": 5673, + "Ġmorphology": 5674, + "Ġsound": 5675, + "ifier": 5676, + "Ġimplications": 5677, + "ĠDesign": 5678, + "approx": 5679, + "prop": 5680, + "Ġcandidate": 5681, + "Ġdepos": 5682, + "Ġequip": 5683, + "ustain": 5684, + "inese": 5685, + "etry": 5686, + "Ġpotentially": 5687, + "Ġstraight": 5688, + "Ġcruc": 5689, + "iology": 5690, + "Ġkernel": 5691, + "Ġalcoh": 5692, + "idden": 5693, + "return": 5694, + "Ġcorrection": 5695, + "rot": 5696, + "Ġmicroscopy": 5697, + "Ġfoot": 5698, + "GL": 5699, + "ĠCells": 5700, + "irth": 5701, + "yg": 5702, + "ĠPath": 5703, + "outhern": 5704, + "ĠLong": 5705, + "Ġrevers": 5706, + "ε": 5707, + "arse": 5708, + "Ġcereb": 5709, + "isted": 5710, + "Ġpuls": 5711, + "Ġdisk": 5712, + "itud": 5713, + "Ġdu": 5714, + "Ġangular": 5715, + "chem": 5716, + "length": 5717, + "Ġexactly": 5718, + "roke": 5719, + "uth": 5720, + "Ġcond": 5721, + "insic": 5722, + "Ġrise": 5723, + "take": 5724, + "Ġtopological": 5725, + "Ġremark": 5726, + "ollary": 5727, + "Ġcer": 5728, + "TE": 5729, + "nment": 5730, + "Ġbuilt": 5731, + "Ġfre": 5732, + "Ġenergies": 5733, + "ecting": 5734, + "ĠTem": 5735, + "rared": 5736, + "ĠNow": 5737, + "charge": 5738, + "Ġlocations": 5739, + "Ġbalance": 5740, + "Ġla": 5741, + "Ġreached": 5742, + "lammatory": 5743, + "Ġfabric": 5744, + "ifications": 5745, + "Ġdiagnostic": 5746, + "Ġmutant": 5747, + "ĠNO": 5748, + "HD": 5749, + "ĠAB": 5750, + "Ġdiscrim": 5751, + "Ġprecip": 5752, + "ĠThree": 5753, + "Ġinser": 5754, + "Ġinfected": 5755, + "Ġconstants": 5756, + "Ω": 5757, + "negative": 5758, + "Ġconfidence": 5759, + "ĠPatients": 5760, + "ollowing": 5761, + "ads": 5762, + "Ġhypert": 5763, + "ĠInternational": 5764, + "Def": 5765, + "ariate": 5766, + "Ġintervals": 5767, + "Ġexercise": 5768, + "Ġeducation": 5769, + "Ġremoval": 5770, + "thern": 5771, + "ster": 5772, + "Ġinteger": 5773, + "ĠPA": 5774, + "Ġkid": 5775, + "Ġcategories": 5776, + "ĠGiven": 5777, + "Ġvascular": 5778, + "herence": 5779, + "mathscr": 5780, + "ĠRet": 5781, + "Ġinsulin": 5782, + "ticip": 5783, + "ĠCF": 5784, + "Ġlook": 5785, + "ymmetry": 5786, + "Ġforces": 5787, + "ĠPhysical": 5788, + "LS": 5789, + "care": 5790, + "Ġhouse": 5791, + "Ġinduce": 5792, + "Ġbelie": 5793, + "ria": 5794, + "ĠAssum": 5795, + "Ġcomputing": 5796, + "Ġbus": 5797, + "âĪİ": 5798, + "Ġpractical": 5799, + "train": 5800, + "TT": 5801, + "Ġplastic": 5802, + "ĠNor": 5803, + "Ġfeas": 5804, + "ĠHamiltonian": 5805, + "Ġtail": 5806, + "ĠZn": 5807, + "Ġinterpretation": 5808, + "ducing": 5809, + "Is": 5810, + "Ġexamine": 5811, + "ulates": 5812, + "Ġmatch": 5813, + "ĠÄ": 5814, + "ives": 5815, + "ameters": 5816, + "ĠμM": 5817, + "Ġexhibit": 5818, + "Ġnit": 5819, + "oto": 5820, + "ĠClinical": 5821, + "ervation": 5822, + "ĠAdditionally": 5823, + "arant": 5824, + "Ġelastic": 5825, + "DA": 5826, + "otopic": 5827, + "Ġactivated": 5828, + "Ġter": 5829, + "Ġconsequence": 5830, + "Ġendot": 5831, + "ophag": 5832, + "Ġcomparable": 5833, + "Ġdominant": 5834, + "η": 5835, + "Ġvalidation": 5836, + "Im": 5837, + "ĠÅ": 5838, + "Ġleaf": 5839, + "Ġfung": 5840, + "taining": 5841, + "Ġunivers": 5842, + "Ġphyl": 5843, + "Ġlibr": 5844, + "Ġextra": 5845, + "Ġprint": 5846, + "mediately": 5847, + "Ġmaximal": 5848, + "idae": 5849, + "Ġoral": 5850, + "bin": 5851, + "Ġpeptide": 5852, + "ĠMax": 5853, + "arp": 5854, + "Ġconclusion": 5855, + "Ġsatisfy": 5856, + "Ġanalyze": 5857, + "ois": 5858, + "Ġinfer": 5859, + "Ġdraw": 5860, + "Ġdepression": 5861, + "Ġmetall": 5862, + "Ġposterior": 5863, + "Ġpeaks": 5864, + "sol": 5865, + "Ġhorizontal": 5866, + "Ġlateral": 5867, + "ĠOR": 5868, + "NN": 5869, + "Ġemo": 5870, + "PV": 5871, + "TA": 5872, + "Ġincubated": 5873, + "Ġretrie": 5874, + "Ġhumans": 5875, + "Ġri": 5876, + "Ġsoci": 5877, + "onia": 5878, + "Ġinterven": 5879, + "Ġvarying": 5880, + "Ġsti": 5881, + "ĠImmun": 5882, + "Ġonset": 5883, + "Ġleaves": 5884, + "Ġotherwise": 5885, + "Ġblocks": 5886, + "Ġassigned": 5887, + "SCs": 5888, + "Ġbios": 5889, + "Ġmixing": 5890, + "ara": 5891, + "li": 5892, + "Ġdeformation": 5893, + "Ġcosts": 5894, + "Ġperipher": 5895, + "ĠTra": 5896, + "Ġatomic": 5897, + "Ġrandomly": 5898, + "Ġargument": 5899, + "Ġitems": 5900, + "Ġsuff": 5901, + "Ġprobably": 5902, + "ners": 5903, + "Ġinhibitors": 5904, + "Ġbeh": 5905, + "ĠDeep": 5906, + "Ġpig": 5907, + "ĠType": 5908, + "ĠMost": 5909, + "ura": 5910, + "itudinal": 5911, + "Ġderivative": 5912, + "Ġexplore": 5913, + "ĠInformation": 5914, + "Ġgrap": 5915, + "ĠÎĶ": 5916, + "Ġprogress": 5917, + "****************": 5918, + "Ġul": 5919, + "ARS": 5920, + "oral": 5921, + "ostic": 5922, + "Com": 5923, + "ĠExternal": 5924, + "ĠStatistical": 5925, + "ĠRam": 5926, + "ĠLo": 5927, + "Ġelectrical": 5928, + "long": 5929, + "Net": 5930, + "ENT": 5931, + "va": 5932, + "ä": 5933, + "urations": 5934, + "Ġdesired": 5935, + "iring": 5936, + "Ġphysics": 5937, + "Ġmasses": 5938, + "ki": 5939, + "Ġbands": 5940, + "Ġalk": 5941, + "ĠSimilarly": 5942, + "Ġsurround": 5943, + "Ġconvex": 5944, + "oster": 5945, + "Ġlinked": 5946, + "Ġfocused": 5947, + "Ġhot": 5948, + "Ġmatching": 5949, + "Ġoxidation": 5950, + "Ġanten": 5951, + "miss": 5952, + "Ġmental": 5953, + "ille": 5954, + "iciency": 5955, + "ĠLiu": 5956, + "Ġprobe": 5957, + "ĠEstim": 5958, + "Ġindices": 5959, + "che": 5960, + "ĠRob": 5961, + "Ġconv": 5962, + "ĠVer": 5963, + "apse": 5964, + "Si": 5965, + "phal": 5966, + "Ġlesions": 5967, + "Ġmolecule": 5968, + "Ġadi": 5969, + "Ġdate": 5970, + "Ġcomposed": 5971, + "Ġaud": 5972, + "structure": 5973, + "oton": 5974, + "infor": 5975, + "Ġclustering": 5976, + "acent": 5977, + "star": 5978, + "PO": 5979, + "ĠChinese": 5980, + "Ġspecifically": 5981, + "erential": 5982, + "Ġcapture": 5983, + "ĠLow": 5984, + "Ġfine": 5985, + "Ġfemales": 5986, + "ĠHow": 5987, + "Ġaer": 5988, + "vector": 5989, + "portun": 5990, + "forms": 5991, + "zo": 5992, + "Ġprecision": 5993, + "ypt": 5994, + "Ġminutes": 5995, + "κ": 5996, + "Ġoxidative": 5997, + "conn": 5998, + "ensus": 5999, + "Ġtrace": 6000, + "Ġconjug": 6001, + "Ġhighlight": 6002, + "ss": 6003, + "ĠExperimental": 6004, + "ĠThat": 6005, + "artment": 6006, + "MO": 6007, + "''": 6008, + "ometer": 6009, + "Ġstop": 6010, + "Ġrib": 6011, + "Ġouter": 6012, + "rh": 6013, + "ript": 6014, + "Ġfluctuations": 6015, + "obs": 6016, + "non": 6017, + "Ġquark": 6018, + "Ġð": 6019, + "ĠMac": 6020, + "Ġperiods": 6021, + "rolled": 6022, + "AV": 6023, + "ĠOc": 6024, + "ĠImage": 6025, + "ĠBel": 6026, + "Ġpropagation": 6027, + "ĠDon": 6028, + "www": 6029, + "glish": 6030, + "Ġexhibited": 6031, + "ogeneity": 6032, + "ĠBack": 6033, + "Ġactions": 6034, + "ski": 6035, + "ĠAmong": 6036, + "Ġbrief": 6037, + "riers": 6038, + "ĠNF": 6039, + "positive": 6040, + "sequently": 6041, + "ulence": 6042, + "Ġenvironments": 6043, + "Ġcurv": 6044, + "omics": 6045, + "Ġbit": 6046, + "Ġgel": 6047, + "Ġrepresentations": 6048, + "Ġaway": 6049, + "ĠField": 6050, + "obic": 6051, + "CG": 6052, + "Ġcomprehens": 6053, + "Ġhierarch": 6054, + "Ġinduces": 6055, + "BD": 6056, + "Ġhapp": 6057, + "Ġeight": 6058, + "Ġgravity": 6059, + "Ġadaptive": 6060, + "BL": 6061, + "genic": 6062, + "Ġinstruc": 6063, + "Ġanalytical": 6064, + "ĠOx": 6065, + "ĠCON": 6066, + "Ġsurgical": 6067, + "Ġdip": 6068, + "ato": 6069, + "Ġrandomized": 6070, + "Ġroles": 6071, + "dep": 6072, + "ĠâĪĩ": 6073, + "chang": 6074, + "Ġdispersion": 6075, + "Ġseparated": 6076, + "ĠOrgan": 6077, + "ĠVi": 6078, + "ĠJohn": 6079, + "Ġannot": 6080, + "Ġresource": 6081, + "energy": 6082, + "relation": 6083, + "mean": 6084, + "ĠBen": 6085, + "Ġconfirm": 6086, + "With": 6087, + "Ġinfinite": 6088, + "ĠScience": 6089, + "Ġsuccessfully": 6090, + "Ġlocalization": 6091, + "mode": 6092, + "https": 6093, + "gebras": 6094, + "idelines": 6095, + "Ġeffectiveness": 6096, + "hyd": 6097, + "Ġsaid": 6098, + "ico": 6099, + "Ġtransitions": 6100, + "eding": 6101, + "Ġprograms": 6102, + "Ġmobile": 6103, + "Ġimmediately": 6104, + "ectivity": 6105, + "ĠTherm": 6106, + "ogenetic": 6107, + "Ġseven": 6108, + "Ġemph": 6109, + "GE": 6110, + "neum": 6111, + "Ġfusion": 6112, + "limits": 6113, + "Ġcalcium": 6114, + "raf": 6115, + "minus": 6116, + "Ġtrap": 6117, + "Ġspecimens": 6118, + "ancing": 6119, + "ĠMarch": 6120, + "Ġten": 6121, + "Ġfamilies": 6122, + "ĠHD": 6123, + "isons": 6124, + "Ġpreparation": 6125, + "hold": 6126, + "ether": 6127, + "ĠVol": 6128, + "ĠDise": 6129, + "Ġrunning": 6130, + "Ġqualit": 6131, + "Ġeffectively": 6132, + "fficiently": 6133, + "BI": 6134, + "Ġdenoted": 6135, + "ĠEquation": 6136, + "Ġdemand": 6137, + "itory": 6138, + "aching": 6139, + "Ġsodium": 6140, + "Ġreproduc": 6141, + "cho": 6142, + "Ġbil": 6143, + "Pi": 6144, + "umb": 6145, + "Ġreconstruction": 6146, + "forward": 6147, + "One": 6148, + "Ġconversion": 6149, + "Ġformulation": 6150, + "Ġnearly": 6151, + "ĠLag": 6152, + "Str": 6153, + "terior": 6154, + "Ġoperating": 6155, + "andom": 6156, + "Ġmoving": 6157, + "ĠReview": 6158, + "////": 6159, + "nai": 6160, + "pp": 6161, + "otide": 6162, + "label": 6163, + "ococc": 6164, + "Ġnever": 6165, + "aker": 6166, + "Ġdigital": 6167, + "Bl": 6168, + "Un": 6169, + "Ġmember": 6170, + "sel": 6171, + "Ġpotenti": 6172, + "Ġcopy": 6173, + "Ġelectrons": 6174, + "chlor": 6175, + "annel": 6176, + "ylind": 6177, + "Ġmis": 6178, + "ĠSet": 6179, + "Ġnutri": 6180, + "Ġdescribes": 6181, + "Ġassumptions": 6182, + "Ġvirtual": 6183, + "Ġcoordinate": 6184, + "Ġvor": 6185, + "ĠArab": 6186, + "ĠImp": 6187, + "Ġdeposition": 6188, + "Ġinstit": 6189, + "Ġrepresentative": 6190, + "everal": 6191, + "Ġmillion": 6192, + "ĠMA": 6193, + "Ġmales": 6194, + "Ġcrucial": 6195, + "Ġcold": 6196, + "Ġloading": 6197, + "Ġtranslation": 6198, + "Ġstead": 6199, + "rays": 6200, + "Ġchallenge": 6201, + "activity": 6202, + "idal": 6203, + "uff": 6204, + "Ġseem": 6205, + "Ġnational": 6206, + "Ġfa": 6207, + "Ġminor": 6208, + "Ġundergo": 6209, + "cr": 6210, + "Ġcapt": 6211, + "ele": 6212, + "uple": 6213, + "ĠMg": 6214, + "lege": 6215, + "GR": 6216, + "Ġrig": 6217, + "Ġarri": 6218, + "Ġdetector": 6219, + "Ġstrict": 6220, + "Ġadhes": 6221, + "Ġsea": 6222, + "theless": 6223, + "Ġsleep": 6224, + "ĠCommun": 6225, + "Ġantioxid": 6226, + "Ġmarker": 6227, + "Ġflows": 6228, + "ancre": 6229, + "ĠJanuary": 6230, + "input": 6231, + "UP": 6232, + "Ġstored": 6233, + "ading": 6234, + "itively": 6235, + "Ġslope": 6236, + "Ġshell": 6237, + "Ġelevated": 6238, + "ilk": 6239, + "Ġfrequently": 6240, + "Ġball": 6241, + "urban": 6242, + "Ġml": 6243, + "usive": 6244, + "ĠAnt": 6245, + "amino": 6246, + "Sim": 6247, + "Ġphysiological": 6248, + "regulation": 6249, + "esity": 6250, + "Ġexplan": 6251, + "Ġaden": 6252, + "reme": 6253, + "Ġdiffer": 6254, + "Ġmodification": 6255, + "Ġirradi": 6256, + "He": 6257, + "acial": 6258, + "Ġsuppress": 6259, + "quis": 6260, + "Ġdry": 6261, + "erated": 6262, + "Ġprojection": 6263, + "Ġpool": 6264, + "plete": 6265, + "Ġdirections": 6266, + "Ġchanged": 6267, + "ĠIts": 6268, + "Ġster": 6269, + "Ġradial": 6270, + "Ġgr": 6271, + "Ġperiodic": 6272, + "Ġbin": 6273, + "Ġpip": 6274, + "men": 6275, + "then": 6276, + "pc": 6277, + "amily": 6278, + "ĠDM": 6279, + "Ġsediment": 6280, + "mi": 6281, + "Ġclosely": 6282, + "Ġrepair": 6283, + "Ġrespiratory": 6284, + "Ġhorm": 6285, + "Ans": 6286, + "dr": 6287, + "ls": 6288, + "Ġhomogeneous": 6289, + "etric": 6290, + "DS": 6291, + "Ġresidues": 6292, + "ĠValue": 6293, + "Fs": 6294, + "Ġwhy": 6295, + "Sp": 6296, + "Ġca": 6297, + "Ġnarrow": 6298, + "gent": 6299, + "Ġbr": 6300, + "Ġquasi": 6301, + "Ġpict": 6302, + "mo": 6303, + "Ġatom": 6304, + "Ġhabit": 6305, + "Ġlimitations": 6306, + "conduc": 6307, + "Ġshock": 6308, + "ceptor": 6309, + "ĠDetection": 6310, + "Sh": 6311, + "ube": 6312, + "Ġellip": 6313, + "UR": 6314, + "Ġstaining": 6315, + "Ġrapidly": 6316, + "ĠBur": 6317, + "ĠBro": 6318, + "Ġuptake": 6319, + "Ġchallenges": 6320, + "SN": 6321, + "Ġanis": 6322, + "Ġbounds": 6323, + "step": 6324, + "omeric": 6325, + "tention": 6326, + "ĠEvaluation": 6327, + "Ġrecommend": 6328, + "Me": 6329, + "Ġmoderate": 6330, + "elled": 6331, + "Ġtit": 6332, + "ĠYang": 6333, + "Ġpharmac": 6334, + "inflammatory": 6335, + "ĠJune": 6336, + "Ġsensors": 6337, + "aired": 6338, + "Ġapproximate": 6339, + "SV": 6340, + "Ġbund": 6341, + "rc": 6342, + "oman": 6343, + "Ġvisible": 6344, + "Ġmeasuring": 6345, + "ogonal": 6346, + "ĠFourier": 6347, + "Ġtheories": 6348, + "Ġprofession": 6349, + "tained": 6350, + "atas": 6351, + "ĠInterest": 6352, + "param": 6353, + "ĠStructure": 6354, + "Ġliving": 6355, + "Data": 6356, + "ĠSM": 6357, + "Ġnet": 6358, + "Ġsimultaneously": 6359, + "continu": 6360, + "Ġshor": 6361, + "########": 6362, + "Ġdecreasing": 6363, + "Ġreferred": 6364, + "gg": 6365, + "Thus": 6366, + "Ġdro": 6367, + "pril": 6368, + "ĠPers": 6369, + "Ġencoding": 6370, + "Ġarc": 6371, + "Ġregulatory": 6372, + "Ġtrained": 6373, + "cepts": 6374, + "Ġrout": 6375, + "lys": 6376, + "Par": 6377, + "ĠUl": 6378, + "ĠGraph": 6379, + "âĪĤ": 6380, + "Ġirre": 6381, + "oidal": 6382, + "Ġexceed": 6383, + "Ġmostly": 6384, + "ĠPat": 6385, + "aternal": 6386, + "Ġer": 6387, + "Ġcoverage": 6388, + "FS": 6389, + "ĠRot": 6390, + "Ġclassified": 6391, + "Ġexcitation": 6392, + "Ġconductivity": 6393, + "Ġcommercial": 6394, + "ĠDel": 6395, + "ĠPolar": 6396, + "HR": 6397, + "Ġtraffic": 6398, + "zing": 6399, + "Ġsettings": 6400, + "Ġinclusion": 6401, + "Answer": 6402, + "Ġvit": 6403, + "vitational": 6404, + "Ġbind": 6405, + "Ġoc": 6406, + "ĠWestern": 6407, + "Ġprosp": 6408, + "Ġnorth": 6409, + "itch": 6410, + "ĠRiver": 6411, + "Ġvehicle": 6412, + "Ġlikelihood": 6413, + "LD": 6414, + "Ġinsp": 6415, + "âĪĨ": 6416, + "Ġleuk": 6417, + "ĠBre": 6418, + "Ġsynthetic": 6419, + "ĠGermany": 6420, + "ĠTheir": 6421, + "target": 6422, + "ĠEnglish": 6423, + "Ġnotation": 6424, + "ĠATP": 6425, + "ĠModels": 6426, + "Ġabnormal": 6427, + "ĠConclusions": 6428, + "Ġoccurrence": 6429, + "Ġmicrobi": 6430, + "ĠWar": 6431, + "tember": 6432, + "Ġlocally": 6433, + "born": 6434, + "Ġbarrier": 6435, + "Ġexpressions": 6436, + "oval": 6437, + "Ġflav": 6438, + "emble": 6439, + "Ġdynamical": 6440, + "Ġphoton": 6441, + "apped": 6442, + "Ġglut": 6443, + "Ġkinetic": 6444, + "Ġalcohol": 6445, + "Ġtransplant": 6446, + "LP": 6447, + "Ġdefault": 6448, + "Ġopportun": 6449, + "args": 6450, + "ĠDav": 6451, + "Ġfront": 6452, + "hom": 6453, + "Ġways": 6454, + "ĠAssociation": 6455, + "Ġkidney": 6456, + "Ġproportional": 6457, + "When": 6458, + "Ġepithelial": 6459, + "Ġfresh": 6460, + "Ġrecall": 6461, + "Ġenzymes": 6462, + "br": 6463, + "Ġmalign": 6464, + "textrm": 6465, + "ĠUse": 6466, + "Now": 6467, + "ĠLie": 6468, + "Ġimpair": 6469, + "Ġguarant": 6470, + "Ġinver": 6471, + "Ġtranscript": 6472, + "Ġsustain": 6473, + "Ġactually": 6474, + "alities": 6475, + "ĠMic": 6476, + "ĠIC": 6477, + "ĠMeasure": 6478, + "Ġ": 6479, + "Ġdensities": 6480, + "Ġgalaxy": 6481, + "Ġsufficiently": 6482, + "Ġorbit": 6483, + "ford": 6484, + "Ġpartially": 6485, + "ĠPy": 6486, + "Ġreverse": 6487, + "Ġsurve": 6488, + "ĠWork": 6489, + "Ġask": 6490, + "However": 6491, + "Ġsitu": 6492, + "Ġvacuum": 6493, + "tober": 6494, + "Ġspac": 6495, + "anth": 6496, + "Or": 6497, + "ags": 6498, + "Ġbig": 6499, + "herical": 6500, + "erge": 6501, + "ellite": 6502, + "Ġinvolves": 6503, + "ĠVis": 6504, + "Ġsummary": 6505, + "ĠSupplementary": 6506, + "ĠColl": 6507, + "Ġadjacent": 6508, + "ontaneous": 6509, + "abs": 6510, + "Ġresearchers": 6511, + "ka": 6512, + "Ġintern": 6513, + "Ġmonth": 6514, + "ĠNeural": 6515, + "apor": 6516, + "ĠNan": 6517, + "Ġstri": 6518, + "EE": 6519, + "Ġconsisting": 6520, + "Ġupdate": 6521, + "Ġphoto": 6522, + "Val": 6523, + "sens": 6524, + "Ġveget": 6525, + "BR": 6526, + "Ġcoinc": 6527, + "ĠJuly": 6528, + "tility": 6529, + "ĠExpression": 6530, + "Ġtopology": 6531, + "Ġgrowing": 6532, + "aptic": 6533, + "uced": 6534, + "Ġperipheral": 6535, + "enes": 6536, + "Ġplots": 6537, + "Ġexplo": 6538, + "Ġwor": 6539, + "ba": 6540, + "atitis": 6541, + "ief": 6542, + "wave": 6543, + "Ġprotection": 6544, + "Ġdefects": 6545, + "Ġadsorption": 6546, + "Ġshared": 6547, + "Ġstellar": 6548, + "ĠBa": 6549, + "ĠEnergy": 6550, + "queous": 6551, + "ĠAugust": 6552, + "Ġlys": 6553, + "Ġplus": 6554, + "irel": 6555, + "ĠGP": 6556, + "ĠNeu": 6557, + "dist": 6558, + "gers": 6559, + "ifer": 6560, + "isp": 6561, + "Ġstrat": 6562, + "ione": 6563, + "ĠMaterials": 6564, + "Ġln": 6565, + "Ġpulmonary": 6566, + "ened": 6567, + "plan": 6568, + "Mod": 6569, + "Ġorganization": 6570, + "Ġrelaxation": 6571, + "Ġcortex": 6572, + "Ġmodulation": 6573, + "ogl": 6574, + "shift": 6575, + "Ġsecurity": 6576, + "Ġfatty": 6577, + "Ġms": 6578, + "local": 6579, + "ergic": 6580, + "Ġinterference": 6581, + "inson": 6582, + "cf": 6583, + "Ġreasons": 6584, + "pred": 6585, + "Ġinterventions": 6586, + "Ġjo": 6587, + "ĠID": 6588, + "ĠArea": 6589, + "ĠHa": 6590, + "uits": 6591, + "output": 6592, + "Le": 6593, + "ycl": 6594, + "inted": 6595, + "Ġnano": 6596, + "NC": 6597, + "ĠCap": 6598, + "Ġchanging": 6599, + "Ġcust": 6600, + "Ġappeared": 6601, + "Ġgrown": 6602, + "ĠUK": 6603, + "Ġradical": 6604, + "ĠPot": 6605, + "ĠProgram": 6606, + "ĠSR": 6607, + "Ġshap": 6608, + "oscop": 6609, + "ĠChang": 6610, + "Ġquantity": 6611, + "ĠTaxon": 6612, + "idation": 6613, + "Ġadding": 6614, + "ĠLee": 6615, + "Ġamounts": 6616, + "Ġdespite": 6617, + "Ġremained": 6618, + "Ġscenarios": 6619, + "lets": 6620, + "oming": 6621, + "Ġcurvature": 6622, + "Ġdimensional": 6623, + "Ġpromising": 6624, + "ĠFil": 6625, + "string": 6626, + "Ġattributed": 6627, + "ymer": 6628, + "Ġneighb": 6629, + "Ġinputs": 6630, + "Ġmagnet": 6631, + "Ġtrees": 6632, + "Ġenter": 6633, + "ruit": 6634, + "stable": 6635, + "toplas": 6636, + "Ġmessage": 6637, + "rophic": 6638, + "Ġisolates": 6639, + "tz": 6640, + "Ġdisplayed": 6641, + "HA": 6642, + "ocl": 6643, + "Ġderive": 6644, + "Ġsynchron": 6645, + "QU": 6646, + "Ãŀ": 6647, + "Ġexamination": 6648, + "Ġdeb": 6649, + "Ġdefin": 6650, + "Ġfault": 6651, + "Ġsteady": 6652, + "Ġphenotype": 6653, + "Ġperspective": 6654, + "Ġstatement": 6655, + "df": 6656, + "void": 6657, + "Ġpromote": 6658, + "illary": 6659, + "ĠEth": 6660, + "Ġwalk": 6661, + "Ġrepresenting": 6662, + "Ġgenomic": 6663, + "ĠGr": 6664, + "shape": 6665, + "ĠPet": 6666, + "ĠLocal": 6667, + "plicity": 6668, + "ĠProblem": 6669, + "GS": 6670, + "Ġcompleted": 6671, + "inking": 6672, + "Ġreads": 6673, + "Ġinde": 6674, + "ceived": 6675, + "ĠPL": 6676, + "ĠMean": 6677, + "ĠSchool": 6678, + "Ġbiomark": 6679, + "ireless": 6680, + "cut": 6681, + "osing": 6682, + "nel": 6683, + "ĠApril": 6684, + "ĠBal": 6685, + "Ġadopted": 6686, + "Ġcomplications": 6687, + "Ġassembly": 6688, + "fort": 6689, + "har": 6690, + "Ġadoles": 6691, + "Ġanswer": 6692, + "Ġcommunities": 6693, + "ĠInstitute": 6694, + "Ġvariant": 6695, + "Finally": 6696, + "mitte": 6697, + "Ġrestricted": 6698, + "Ġmanip": 6699, + "aters": 6700, + "EX": 6701, + "Ġdust": 6702, + "Ġsupply": 6703, + "Ġperme": 6704, + "Ġreliable": 6705, + "ĠResp": 6706, + "Ġsubt": 6707, + "oks": 6708, + "Ġpoll": 6709, + "Ġcanc": 6710, + "ĠUnit": 6711, + "Ġendothelial": 6712, + "dy": 6713, + "ĠBlack": 6714, + "Ġempirical": 6715, + "Ġport": 6716, + "opy": 6717, + "Ġinitially": 6718, + "Ġcondens": 6719, + "Ġeye": 6720, + "Ġlisted": 6721, + "urrence": 6722, + "Ġreplaced": 6723, + "Ġselective": 6724, + "Ġdistances": 6725, + "Ġparas": 6726, + "ĠPost": 6727, + "ĠSeptember": 6728, + "Ġmissing": 6729, + "verex": 6730, + "Er": 6731, + "Ġthought": 6732, + "thal": 6733, + "Ġchromat": 6734, + "Ġbenefit": 6735, + "rames": 6736, + "ĠSuppose": 6737, + "Ġsubs": 6738, + "Ġangi": 6739, + "ori": 6740, + "Ġreplic": 6741, + "Ġschemes": 6742, + "pre": 6743, + "plane": 6744, + "Ġsouth": 6745, + "ager": 6746, + "Ġbeginning": 6747, + "vents": 6748, + "onent": 6749, + "iples": 6750, + "ĠHer": 6751, + "Ġspectrom": 6752, + "Ġdense": 6753, + "Ġtook": 6754, + "iverse": 6755, + "Ġdisturb": 6756, + "pass": 6757, + "Ġillustrated": 6758, + "Ġreveals": 6759, + "ama": 6760, + "Ġreflec": 6761, + "Ġallowing": 6762, + "Ġexponential": 6763, + "oustic": 6764, + "subseteq": 6765, + "Ġsn": 6766, + "Ġurban": 6767, + "Ġextend": 6768, + "Ġassays": 6769, + "rice": 6770, + "CoV": 6771, + "quisition": 6772, + "rine": 6773, + "ĠIntegr": 6774, + "fil": 6775, + "VD": 6776, + "Ġfibro": 6777, + "Ġcompens": 6778, + "ĠImpro": 6779, + "ĠĠĠĠĠĠĠĠĠĠ": 6780, + "ĠGR": 6781, + "ÏĪ": 6782, + "Ġbasal": 6783, + "Ġolig": 6784, + "HT": 6785, + "Ġvess": 6786, + "uzzy": 6787, + "Ġpossibly": 6788, + "Ġtolerance": 6789, + "Theta": 6790, + "Ġviol": 6791, + "uclear": 6792, + "ĠLim": 6793, + "gel": 6794, + "Ġmetrics": 6795, + "ĠMus": 6796, + "amination": 6797, + "Ġelectrode": 6798, + "Ġpersonal": 6799, + "Ġcooling": 6800, + "Ġacquired": 6801, + "ĠFunction": 6802, + "ows": 6803, + "olester": 6804, + "DP": 6805, + "Ġreliability": 6806, + "Ġmuc": 6807, + "ĠOctober": 6808, + "Ġgold": 6809, + "ca": 6810, + "Ġcul": 6811, + "fit": 6812, + "Ġlem": 6813, + "Ġexcit": 6814, + "Ġnucleus": 6815, + "iation": 6816, + "Ġpregnancy": 6817, + "Ġsynthesized": 6818, + "hemistry": 6819, + "Ġmembranes": 6820, + "vert": 6821, + "ĠKim": 6822, + "tenance": 6823, + "Ġquantities": 6824, + "Ġeconomic": 6825, + "Ġbenefits": 6826, + "Ġcylind": 6827, + "pler": 6828, + "ĠLarge": 6829, + "Ġengineering": 6830, + "ĠEp": 6831, + "Ġcoating": 6832, + "ativ": 6833, + "Ġconduct": 6834, + "Ġabsorb": 6835, + "ĠDecember": 6836, + "Ġopposite": 6837, + "ĠGlobal": 6838, + "Ġlif": 6839, + "ĠDue": 6840, + "Ġintake": 6841, + "odynamic": 6842, + "TM": 6843, + "Ġfed": 6844, + "Ġspecified": 6845, + "Ġgeometric": 6846, + "Ġrespective": 6847, + "Ġbirth": 6848, + "ĠCompound": 6849, + "Ġstarted": 6850, + "Ġmother": 6851, + "arr": 6852, + "Ġprimarily": 6853, + "Ġparen": 6854, + "Ġtube": 6855, + "Ġinters": 6856, + "Ġgraphene": 6857, + "itial": 6858, + "ously": 6859, + "Ġcardiovascular": 6860, + "ĠeV": 6861, + "Ġheating": 6862, + "Ġmathematical": 6863, + "Ġindependently": 6864, + "BA": 6865, + "Ġaffects": 6866, + "umor": 6867, + "ĠMP": 6868, + "ĠDem": 6869, + "ĠWest": 6870, + "ĠDom": 6871, + "itter": 6872, + "Ġdisrup": 6873, + "oped": 6874, + "Ġphenomenon": 6875, + "Ġlumin": 6876, + "Ac": 6877, + "Ġprefer": 6878, + "omers": 6879, + "Ġgender": 6880, + "ĠGL": 6881, + "FC": 6882, + "Ġindeed": 6883, + "Ġrational": 6884, + "ĠSC": 6885, + "Ġprincipal": 6886, + "Ġperfect": 6887, + "Ġintroduction": 6888, + "tes": 6889, + "Ġpiec": 6890, + "Ġcity": 6891, + "Ġpopular": 6892, + "Ġcoding": 6893, + "cler": 6894, + "ague": 6895, + "ĠHR": 6896, + "Ġtracking": 6897, + "ker": 6898, + "Ġphosphorylation": 6899, + "Ġpaths": 6900, + "Ġsolving": 6901, + "Ġdy": 6902, + "Ġplayed": 6903, + "Ġprecise": 6904, + "ĠSl": 6905, + "ĠSem": 6906, + "Ġgenerating": 6907, + "ĠSun": 6908, + "Ġcriterion": 6909, + "Ġbranch": 6910, + "Ġζ": 6911, + "tish": 6912, + "Se": 6913, + "Ġantigen": 6914, + "Ġcalibration": 6915, + "Es": 6916, + "ĠItal": 6917, + "Ġmassive": 6918, + "En": 6919, + "No": 6920, + "YP": 6921, + "ya": 6922, + "Ġsatisfying": 6923, + "Ġquick": 6924, + "HO": 6925, + "Ġbehaviors": 6926, + "icrobial": 6927, + "Ġamb": 6928, + "Ġproton": 6929, + "SL": 6930, + "Ġusual": 6931, + "rows": 6932, + "ench": 6933, + "UC": 6934, + "Ġweighted": 6935, + "Ġrecords": 6936, + "ĠAC": 6937, + "GT": 6938, + "inn": 6939, + "Ġeq": 6940, + "ĠWil": 6941, + "yroid": 6942, + "Ġsetup": 6943, + "IA": 6944, + "press": 6945, + "isely": 6946, + "Ġentry": 6947, + "%%": 6948, + "ĠSil": 6949, + "east": 6950, + "ĠEvolution": 6951, + "ĠRandom": 6952, + "Ġcavity": 6953, + "Ġnamed": 6954, + "knowled": 6955, + "mber": 6956, + "uestion": 6957, + "ĠâĪ©": 6958, + "gi": 6959, + "Ġdetermining": 6960, + "tin": 6961, + "Ġgenus": 6962, + "Ġtoxicity": 6963, + "ocyt": 6964, + "Ġperturbation": 6965, + "rought": 6966, + "ĠBri": 6967, + "Ġcarb": 6968, + "ĠGra": 6969, + "ĠFlu": 6970, + "uns": 6971, + "Ġdriven": 6972, + "Ġbatch": 6973, + "rif": 6974, + "Pl": 6975, + "Ġdisplacement": 6976, + "ĠCL": 6977, + "Ġdepic": 6978, + "Ġpredictive": 6979, + "Int": 6980, + "hydroxy": 6981, + "tid": 6982, + "dri": 6983, + "Ġpancre": 6984, + "Ġdiagonal": 6985, + "Ġseverity": 6986, + "Ġlongitudinal": 6987, + "ĠED": 6988, + "atible": 6989, + "dir": 6990, + "ĠAnother": 6991, + "ĠHel": 6992, + "van": 6993, + "Ġpneum": 6994, + "Ġspecificity": 6995, + "squ": 6996, + "Ġign": 6997, + "Ġbed": 6998, + "ĠWT": 6999, + "awa": 7000, + "ester": 7001, + "Ġkg": 7002, + "Ġcompression": 7003, + "evertheless": 7004, + "Ġmask": 7005, + "-----------": 7006, + "Ġtens": 7007, + "rowth": 7008, + "ĠGo": 7009, + "Ġfaster": 7010, + "Ġcanonical": 7011, + "Ġdetermin": 7012, + "ustrial": 7013, + "ĠEarth": 7014, + "while": 7015, + "ournal": 7016, + "Ġcountry": 7017, + "Ġferm": 7018, + "rist": 7019, + "Ġproxim": 7020, + "Ġmicrobial": 7021, + "Ġextensive": 7022, + "Ġcham": 7023, + "Ġ§": 7024, + "such": 7025, + "went": 7026, + "Ġlar": 7027, + "Using": 7028, + "ĠPM": 7029, + "Ġoffset": 7030, + "ĠPI": 7031, + "ĠBayesian": 7032, + "HS": 7033, + "ĠAfrica": 7034, + "Ġsusceptibility": 7035, + "ĠâĬĤ": 7036, + "ococcus": 7037, + "ĠDir": 7038, + "Ġbos": 7039, + "Ġdysfunction": 7040, + "ovember": 7041, + "Ġunderst": 7042, + "Ġlargely": 7043, + "ĠCM": 7044, + "Ġmaintained": 7045, + "Ġpossess": 7046, + "Ġexcluded": 7047, + "ensis": 7048, + "ĠDC": 7049, + "opsis": 7050, + "Ġtorch": 7051, + "idine": 7052, + "Ġforest": 7053, + "ĠExact": 7054, + "ĠStudies": 7055, + "iffiff": 7056, + "ĠCam": 7057, + "angular": 7058, + "Ġremove": 7059, + "oir": 7060, + "ava": 7061, + "ida": 7062, + "Ġmant": 7063, + "Log": 7064, + "Ġranging": 7065, + "rog": 7066, + "Ġchains": 7067, + "ĠÇ«": 7068, + "ĠCase": 7069, + "ĠAP": 7070, + "points": 7071, + "Ġtargeting": 7072, + "Ġscience": 7073, + "Ġepis": 7074, + "ĠSoci": 7075, + "Ġphysic": 7076, + "Ġpromoter": 7077, + "ĠEarly": 7078, + "estic": 7079, + "tives": 7080, + "Ġassuming": 7081, + "ĠMi": 7082, + "Ġlemma": 7083, + "Ġconfigurations": 7084, + "alia": 7085, + "Ġpay": 7086, + "rino": 7087, + "eb": 7088, + "Ġvaried": 7089, + "ounted": 7090, + "Ġinterview": 7091, + "ĠGeV": 7092, + "OM": 7093, + "ognition": 7094, + "Ġenhancement": 7095, + "ĠMach": 7096, + "plies": 7097, + "Ob": 7098, + "setminus": 7099, + "Ġintrinsic": 7100, + "Ġcomparisons": 7101, + "bold": 7102, + "xiety": 7103, + "Ġstroke": 7104, + "GB": 7105, + "ancial": 7106, + "stead": 7107, + "Ġrock": 7108, + "thon": 7109, + "ĠCurrent": 7110, + "cat": 7111, + "Ġguidelines": 7112, + "cycl": 7113, + "Ġintracellular": 7114, + "oney": 7115, + "ko": 7116, + "Ġdirected": 7117, + "ripts": 7118, + "Ġtravel": 7119, + "Ġlens": 7120, + "idi": 7121, + "ĠAssess": 7122, + "Ġdx": 7123, + "ĠPos": 7124, + "Ġmethodology": 7125, + "Ġpredom": 7126, + "defined": 7127, + "ĠPop": 7128, + "Ġgovernment": 7129, + "ellig": 7130, + "phyl": 7131, + "oli": 7132, + "ropical": 7133, + "Ġembedded": 7134, + "edom": 7135, + "cribed": 7136, + "ĠDisease": 7137, + "Ġmediated": 7138, + "Ġcircular": 7139, + "ĠTopological": 7140, + "Ġearth": 7141, + "ritis": 7142, + "gal": 7143, + "mass": 7144, + "Ġcomprehensive": 7145, + "ĠAir": 7146, + "Ġnerve": 7147, + "Ġimplant": 7148, + "Ġextremely": 7149, + "ĠSE": 7150, + "Ġmarket": 7151, + "Ġconserved": 7152, + "embrane": 7153, + "Ġschedul": 7154, + "Ġruns": 7155, + "Ph": 7156, + "Ġtechnical": 7157, + "TL": 7158, + "Ġregional": 7159, + "Ġgerm": 7160, + "ĠProt": 7161, + "Ġbright": 7162, + "Ġartery": 7163, + "Ġmacrophages": 7164, + "mittee": 7165, + "ĠSingle": 7166, + "Ġcome": 7167, + "wa": 7168, + "acchar": 7169, + "plet": 7170, + "Ġsensing": 7171, + "rosp": 7172, + "atom": 7173, + "Ġcompr": 7174, + "ĠLu": 7175, + "Ġavailability": 7176, + "prot": 7177, + "Ġfitting": 7178, + "selves": 7179, + "ĠPrim": 7180, + "rew": 7181, + "Ġwaste": 7182, + "ĠKing": 7183, + "pot": 7184, + "Ġinstrument": 7185, + "ĠYork": 7186, + "AF": 7187, + "antial": 7188, + "standing": 7189, + "Ġplanning": 7190, + "uster": 7191, + "ĠâĨ": 7192, + "NT": 7193, + "icular": 7194, + "Ġmelan": 7195, + "Ġexcell": 7196, + "iller": 7197, + "ĠLD": 7198, + "info": 7199, + "Ġshare": 7200, + "vas": 7201, + "Ġlum": 7202, + "Ġaqueous": 7203, + "Ġquery": 7204, + "Ġmag": 7205, + "ulture": 7206, + "ĠBer": 7207, + "Ġoffer": 7208, + "ĠNMR": 7209, + "aceae": 7210, + "Ġmodern": 7211, + "Ġcircum": 7212, + "Ġcultures": 7213, + "Ġdog": 7214, + "Ġcir": 7215, + "Ġpoli": 7216, + "Ġchemotherapy": 7217, + "Ġplates": 7218, + "Ġrestriction": 7219, + "stack": 7220, + "ĠFlow": 7221, + "ĠBu": 7222, + "ĠCenter": 7223, + "Ġproceed": 7224, + "timicrobial": 7225, + "she": 7226, + "Ġthereby": 7227, + "Ġknock": 7228, + "Ġdiverse": 7229, + "ustry": 7230, + "Ġstated": 7231, + "ĠHol": 7232, + "More": 7233, + "Ġconservation": 7234, + "Ġprevention": 7235, + "norm": 7236, + "Ġpal": 7237, + "ĠCalc": 7238, + "Ġclean": 7239, + "ĠPlas": 7240, + "```": 7241, + "perp": 7242, + "prod": 7243, + "Ġâī¡": 7244, + "porter": 7245, + "Ġtransient": 7246, + "asp": 7247, + "Ġtargeted": 7248, + "ĠPri": 7249, + "Supplementary": 7250, + "ĠTreatment": 7251, + "zen": 7252, + "ĠMart": 7253, + "ĠFerm": 7254, + "uscript": 7255, + "ĠSynthesis": 7256, + "Ġcombinations": 7257, + "ULL": 7258, + "Ġweb": 7259, + "Ġthrom": 7260, + "Ġexplicitly": 7261, + "anks": 7262, + "Ġadaptation": 7263, + "ĠSequence": 7264, + "Ġacts": 7265, + "Ġranges": 7266, + "fs": 7267, + "bru": 7268, + "Ġsystemic": 7269, + "Ġsteel": 7270, + "Ġprivate": 7271, + "Ġobesity": 7272, + "ĠPart": 7273, + "mented": 7274, + "break": 7275, + "ERT": 7276, + "Ġfibers": 7277, + "Ġiso": 7278, + "Ġtransverse": 7279, + "CTION": 7280, + "ĠRi": 7281, + "itin": 7282, + "ĠRepresent": 7283, + "ophys": 7284, + "Ġcoast": 7285, + "Ġalignment": 7286, + "ACT": 7287, + "esides": 7288, + "open": 7289, + "gly": 7290, + "Ġsalt": 7291, + "unced": 7292, + "iaz": 7293, + "Ġcosm": 7294, + "Ġangles": 7295, + "ĠâĢł": 7296, + "ĠIdentification": 7297, + "hex": 7298, + "ĠHall": 7299, + "Ġhepat": 7300, + "Ġsegments": 7301, + "ĠPhase": 7302, + "ĠLand": 7303, + "forming": 7304, + "hbox": 7305, + "ications": 7306, + "Ġsubsequently": 7307, + "ĠCur": 7308, + "Ġlabels": 7309, + "vidence": 7310, + "uality": 7311, + "Ġheld": 7312, + "emann": 7313, + "Ġcamera": 7314, + "cing": 7315, + "ubic": 7316, + "ĠSARS": 7317, + "ulatory": 7318, + "keletal": 7319, + "ĠInflu": 7320, + "ĠIndia": 7321, + "amic": 7322, + "Ġsand": 7323, + "Ġcomes": 7324, + "Ġassociations": 7325, + "Ġcharged": 7326, + "Ġsper": 7327, + "oprotein": 7328, + "iii": 7329, + "odal": 7330, + "Ġboundaries": 7331, + "tization": 7332, + "ĠHeavy": 7333, + "ĠReal": 7334, + "ĠAF": 7335, + "Ġcontroller": 7336, + "Ġantioxidant": 7337, + "Ġbars": 7338, + "Ġwet": 7339, + "ener": 7340, + "ĠComplexity": 7341, + "Ġstack": 7342, + "Therefore": 7343, + "Ġreplication": 7344, + "Ġappearance": 7345, + "Ġtrajectory": 7346, + "Ġunderstood": 7347, + "Ġdot": 7348, + "Ġimag": 7349, + "Ġscanning": 7350, + "Ti": 7351, + "ruct": 7352, + "ĠLy": 7353, + "Ġspontaneous": 7354, + "lat": 7355, + "omon": 7356, + "Ġroots": 7357, + "Ġlive": 7358, + "Ġfinally": 7359, + "¿½": 7360, + "Ġapproved": 7361, + "ĠApplications": 7362, + "ĠPan": 7363, + "Ġlost": 7364, + "Ġsatisfied": 7365, + "Ġgamma": 7366, + "ional": 7367, + "Ġimproving": 7368, + "Ġmanifold": 7369, + "Ġcodes": 7370, + "bb": 7371, + "ĠNovember": 7372, + "Ġrich": 7373, + "NP": 7374, + "ĠEle": 7375, + "SB": 7376, + "Ġdeal": 7377, + "Ġoptions": 7378, + "Ġcultured": 7379, + "Ġvul": 7380, + ">>": 7381, + "arithm": 7382, + "oys": 7383, + "These": 7384, + "ĠDeterm": 7385, + "Ġquadratic": 7386, + "ĠComb": 7387, + "isson": 7388, + "ĠPerformance": 7389, + "Ġexception": 7390, + "Ġnuclei": 7391, + "Ġadverse": 7392, + "ket": 7393, + "ĠPal": 7394, + "ĠMany": 7395, + "Ġdiffraction": 7396, + "Ġtransmit": 7397, + "Ġphosphate": 7398, + "olesterol": 7399, + "Ġquestionnai": 7400, + "ĠSea": 7401, + "bruary": 7402, + "Ġmodelling": 7403, + "ĠDR": 7404, + "olin": 7405, + "chmark": 7406, + "Ġprecisely": 7407, + "gans": 7408, + "vin": 7409, + "ridge": 7410, + "ĠIncre": 7411, + "Ġneuronal": 7412, + "ĠâīĪ": 7413, + "Ġexcellent": 7414, + "etary": 7415, + "Ġoverlap": 7416, + "Ġstronger": 7417, + "Ġfracture": 7418, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 7419, + "Ġclinic": 7420, + "ĠList": 7421, + "Ġhistor": 7422, + "generation": 7423, + "riched": 7424, + "illus": 7425, + "ĠÃħ": 7426, + "ĠRole": 7427, + "Ġlabeled": 7428, + "Ġorthogonal": 7429, + "Ġischem": 7430, + "Ġinstability": 7431, + "loop": 7432, + "Ġplotted": 7433, + "ĠProcessing": 7434, + "ĠTa": 7435, + "ĠConclusion": 7436, + "Ġmagne": 7437, + "Ġuniversal": 7438, + "Ġjet": 7439, + "Ġregim": 7440, + "float": 7441, + "Ġcod": 7442, + "adj": 7443, + "boldmath": 7444, + "Ġarrang": 7445, + "Ġtrends": 7446, + "Ġprecipitation": 7447, + "frequency": 7448, + "Ġcontrad": 7449, + "Ġtransferred": 7450, + "Ġmaintenance": 7451, + "ÎĶ": 7452, + "np": 7453, + "istence": 7454, + "heres": 7455, + "lective": 7456, + "ĠSurvey": 7457, + "ĠÐ": 7458, + "Ġstand": 7459, + "Ġdiscovery": 7460, + "ains": 7461, + "versely": 7462, + "Ġnumerous": 7463, + "ylated": 7464, + "Ġembedding": 7465, + "Ġcollabor": 7466, + "ename": 7467, + "immun": 7468, + "Ġadjusted": 7469, + "ires": 7470, + "cur": 7471, + "Ġvaccine": 7472, + "Ġtraits": 7473, + "Ġmorphological": 7474, + "Ġprecurs": 7475, + "roscope": 7476, + "adi": 7477, + "ecutive": 7478, + "uan": 7479, + "Ġtract": 7480, + "ĠPres": 7481, + "Ġmyel": 7482, + "Ġadequ": 7483, + "Ġethanol": 7484, + "ih": 7485, + "Ġmeth": 7486, + "Ġcounts": 7487, + "Ġqualitative": 7488, + "Ġmusic": 7489, + "Ġreinfor": 7490, + "After": 7491, + "Ġacquisition": 7492, + "Ġhttps": 7493, + "alling": 7494, + "ita": 7495, + "icate": 7496, + "script": 7497, + "Ġoptimized": 7498, + "ĠHo": 7499, + "Ġmild": 7500, + "oplas": 7501, + "Ġoverex": 7502, + "ĠâΧ": 7503, + "Ġcollect": 7504, + "ĠMain": 7505, + "Ġextracellular": 7506, + "Ġanc": 7507, + "rawn": 7508, + "Ġexplored": 7509, + "Ġreserv": 7510, + "ĠApplication": 7511, + "case": 7512, + "Ġmarine": 7513, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠ": 7514, + "iled": 7515, + "Ġmesh": 7516, + "ĠMonte": 7517, + "clos": 7518, + "Ġperforming": 7519, + "Ag": 7520, + "regular": 7521, + "Ġcatal": 7522, + "Ġpotentials": 7523, + "antly": 7524, + "URE": 7525, + "Ġaccomp": 7526, + "Ġreasonable": 7527, + "Ġpresentation": 7528, + "abolic": 7529, + "ĠOnly": 7530, + "anned": 7531, + "Ġsubstantial": 7532, + "Ġdietary": 7533, + "Ġsubstrates": 7534, + "apter": 7535, + "Ġintestinal": 7536, + "Ġproduces": 7537, + "Proposition": 7538, + "rogen": 7539, + "ĠStat": 7540, + "burg": 7541, + "rench": 7542, + "textbf": 7543, + "ystems": 7544, + "atable": 7545, + "ĠVir": 7546, + "Ġsolved": 7547, + "icense": 7548, + "Ġsong": 7549, + "Ġextreme": 7550, + "pty": 7551, + "ĠCity": 7552, + "vered": 7553, + "ĠMRI": 7554, + "Ġtwice": 7555, + "ĠMn": 7556, + "Ġmerg": 7557, + "activation": 7558, + "Ġng": 7559, + "Ġodd": 7560, + "Ġattrac": 7561, + "Ġattempt": 7562, + "Ġseparately": 7563, + "Ġrobot": 7564, + "ĠMultiple": 7565, + "Ġscientific": 7566, + "ĠPP": 7567, + "Ġmineral": 7568, + "Ġprotocols": 7569, + "Ġsuperior": 7570, + "ocamp": 7571, + "boxyl": 7572, + "Ġuniformly": 7573, + "ĠSeveral": 7574, + "Ġmol": 7575, + "Cor": 7576, + "underline": 7577, + "Ġinfluenced": 7578, + "Ġcurren": 7579, + "using": 7580, + "race": 7581, + "ĠNevertheless": 7582, + "Ġaccom": 7583, + "Ġgravitational": 7584, + "Ġindirect": 7585, + "Ġcapable": 7586, + "Ġanalysed": 7587, + "Ġdischarge": 7588, + "Ġves": 7589, + "Ġligand": 7590, + "lik": 7591, + "Ġsi": 7592, + "Ġaged": 7593, + "Ġcrystals": 7594, + "Ġspeech": 7595, + "Ġcopper": 7596, + "ĠSan": 7597, + "ĠArm": 7598, + "Ġmanuscript": 7599, + "Ġsecretion": 7600, + "wedge": 7601, + "·": 7602, + "Ġraw": 7603, + "Ġaimed": 7604, + "Ġevolutionary": 7605, + "Ġconsequences": 7606, + "Ġitem": 7607, + "Ġwestern": 7608, + "Ġsolvent": 7609, + "Ġstimuli": 7610, + "Ġrequirement": 7611, + "http": 7612, + "efore": 7613, + "ĠAtl": 7614, + "Ġatmospheric": 7615, + "Ġpackage": 7616, + "Ġmyocardi": 7617, + "Ġdashed": 7618, + "Ġverify": 7619, + "ativistic": 7620, + "Ġtom": 7621, + "avirus": 7622, + "aken": 7623, + "ĠNumer": 7624, + "Ġadvantages": 7625, + "FR": 7626, + "ĠSelf": 7627, + "rected": 7628, + "config": 7629, + "Ġiteration": 7630, + "Ġeigenvalues": 7631, + "Ġprobabilities": 7632, + "FIG": 7633, + "ĠWater": 7634, + "ĠAu": 7635, + "Ġgave": 7636, + "Ġvar": 7637, + "ricular": 7638, + "opathy": 7639, + "Ġrh": 7640, + "ordance": 7641, + "Ġwin": 7642, + "ĠScale": 7643, + "Ġannual": 7644, + "ataset": 7645, + "Ġpel": 7646, + "ĠâĪª": 7647, + "ĠCC": 7648, + "itors": 7649, + "Ġlith": 7650, + "Ġchromosome": 7651, + "Ġfuel": 7652, + "Ġmultiv": 7653, + "Ġmanufacture": 7654, + "la": 7655, + "ĠSa": 7656, + "umes": 7657, + "igm": 7658, + "Ġnanoc": 7659, + "EGF": 7660, + "Ġsignature": 7661, + "NS": 7662, + "Ġmeet": 7663, + "Ġfair": 7664, + "meth": 7665, + "Ġlocalized": 7666, + "ĠCentral": 7667, + "deg": 7668, + "Ġsurrounding": 7669, + "Ġnone": 7670, + "ĠMO": 7671, + "ĠInterestingly": 7672, + "Ġmultic": 7673, + "ĠKe": 7674, + "Ġinhibited": 7675, + "ĠCare": 7676, + "ĠOpen": 7677, + "Ġglob": 7678, + "EA": 7679, + "ĠFound": 7680, + "Ġpixel": 7681, + "oke": 7682, + "RD": 7683, + "loc": 7684, + "tious": 7685, + "Ġdistinguish": 7686, + "Ġanterior": 7687, + "urch": 7688, + "Ġjud": 7689, + "ĠPower": 7690, + "Ġswitch": 7691, + "ĠSyn": 7692, + "Ġinvolvement": 7693, + "ucl": 7694, + "Ġlibrary": 7695, + "ĠConst": 7696, + "Ġspherical": 7697, + "ĠTNF": 7698, + "Ġaltered": 7699, + "vance": 7700, + "transfer": 7701, + "Ms": 7702, + "ĠOper": 7703, + "inement": 7704, + "seq": 7705, + "Cons": 7706, + "hole": 7707, + "ĠPhot": 7708, + "Ġgut": 7709, + "acterial": 7710, + "ĠIP": 7711, + "unt": 7712, + "Ġnom": 7713, + "has": 7714, + "ĠFebruary": 7715, + "Ġprostate": 7716, + "ĠML": 7717, + "high": 7718, + "ĠBackground": 7719, + "ulent": 7720, + "Ġocean": 7721, + "after": 7722, + "ĠOff": 7723, + "loss": 7724, + "Ġfavor": 7725, + "Ġworkers": 7726, + "Ġhidden": 7727, + "Ġextracts": 7728, + "razil": 7729, + "sign": 7730, + "None": 7731, + "Ġcolumns": 7732, + "Ġfractions": 7733, + "Ġcovered": 7734, + "ĠServ": 7735, + "Ġinform": 7736, + "bed": 7737, + "Ġattem": 7738, + "raining": 7739, + "Ġneutron": 7740, + "Ġrice": 7741, + "Ġmotif": 7742, + "Ġartificial": 7743, + "Ġinhibitory": 7744, + "Ġdt": 7745, + "AGE": 7746, + "Ġsampled": 7747, + "Ġbatter": 7748, + "Ġsubjected": 7749, + "Ġgeneric": 7750, + "ĠNH": 7751, + "Ġcontinue": 7752, + "utional": 7753, + "Ġaug": 7754, + "ius": 7755, + "Ġexecution": 7756, + "ĠWilli": 7757, + "ĠDespite": 7758, + "AMI": 7759, + "Ġcontents": 7760, + "ĠSens": 7761, + "ogens": 7762, + "Col": 7763, + "Ġfo": 7764, + "Ġaddi": 7765, + "uated": 7766, + "Ġrecommended": 7767, + "ĠSW": 7768, + "Ġarch": 7769, + "ĠYes": 7770, + "Ġhol": 7771, + "aturally": 7772, + "titive": 7773, + "Ġche": 7774, + "Ġsector": 7775, + "ĠDefinition": 7776, + "Ġconcepts": 7777, + "orous": 7778, + "small": 7779, + "erson": 7780, + "inator": 7781, + "ĠMT": 7782, + "Ġhypertension": 7783, + "cks": 7784, + "Ġnative": 7785, + "Ġtax": 7786, + "ryl": 7787, + "Ġreactive": 7788, + "rb": 7789, + "ducible": 7790, + "omm": 7791, + "Ġdiagnosed": 7792, + "Ġdriving": 7793, + "Ġbiomass": 7794, + "uate": 7795, + "Ġpil": 7796, + "called": 7797, + "Ġserve": 7798, + "Ġinterfer": 7799, + "ippocamp": 7800, + "Ġalgebraic": 7801, + "Ġbegan": 7802, + "Ġpicture": 7803, + "independent": 7804, + "Ġutilized": 7805, + "going": 7806, + "ora": 7807, + "nm": 7808, + "Ġdownstream": 7809, + "Ġorbital": 7810, + "ountain": 7811, + "ĠHis": 7812, + "Ġresol": 7813, + "Ġcorrections": 7814, + "onym": 7815, + "scripts": 7816, + "Ġsilicon": 7817, + "Ġcum": 7818, + "ĠTri": 7819, + "Ġpeptides": 7820, + "Ġreceiving": 7821, + "Ġstationary": 7822, + "ĠμL": 7823, + "clerosis": 7824, + "Ġmodules": 7825, + "ema": 7826, + "ĠAfrican": 7827, + "struction": 7828, + "Ġfarm": 7829, + "Ġlearn": 7830, + "node": 7831, + "®": 7832, + "Ġsuperconduc": 7833, + "ĠLinear": 7834, + "Ġtechnologies": 7835, + "Ġnecessarily": 7836, + "Ġcoronary": 7837, + "ĠEast": 7838, + "Ġframes": 7839, + "Ġsegmentation": 7840, + "Vs": 7841, + "Ġbehavioral": 7842, + "Îĵ": 7843, + "Ġlogic": 7844, + "Ġaccompan": 7845, + "tified": 7846, + "hanol": 7847, + "ĠInhib": 7848, + "ilation": 7849, + "ander": 7850, + "Ġeffort": 7851, + "ĠDen": 7852, + "DI": 7853, + "optim": 7854, + "terminal": 7855, + "Ġmobility": 7856, + "Ġconsideration": 7857, + "OVA": 7858, + "Ġparad": 7859, + "oxo": 7860, + "Ġdeficiency": 7861, + "ultural": 7862, + "Ġvalidity": 7863, + "Ġorders": 7864, + "Ġlocus": 7865, + "Ġarth": 7866, + "emat": 7867, + "Ġfeeding": 7868, + "Ġprogramming": 7869, + "Ġtemplate": 7870, + "elian": 7871, + "Ġoption": 7872, + "ĠFollowing": 7873, + "Ġenable": 7874, + "Ġassign": 7875, + "Ġformul": 7876, + "pu": 7877, + "Ġatmosphere": 7878, + "slant": 7879, + "ĠRuss": 7880, + "ĠEvidence": 7881, + "Ġsimilarly": 7882, + "Ġcamp": 7883, + "Ġwound": 7884, + "ĠCharacterization": 7885, + "ĠPBS": 7886, + "ees": 7887, + "ĠDirect": 7888, + "ĠSL": 7889, + "Ġfruit": 7890, + "Ġgate": 7891, + "ito": 7892, + "Chem": 7893, + "Ġcollision": 7894, + "ortic": 7895, + "Ġpolymorphism": 7896, + "enza": 7897, + "what": 7898, + "Ġexperimentally": 7899, + "Ġultra": 7900, + "ez": 7901, + "Ġnerv": 7902, + "Ġessentially": 7903, + "ĠAustralia": 7904, + "ĠStandard": 7905, + "Ġmedicine": 7906, + "adian": 7907, + "ĠHiggs": 7908, + "uge": 7909, + "Ġsupports": 7910, + "uma": 7911, + "Ġcomplicated": 7912, + "date": 7913, + "ophagy": 7914, + "ĠMarkov": 7915, + "Ġoccurring": 7916, + "oplus": 7917, + "Pub": 7918, + "prob": 7919, + "urable": 7920, + "Ġkept": 7921, + "Ġisolation": 7922, + "Ġevol": 7923, + "iliary": 7924, + "Ġregist": 7925, + "Ġholes": 7926, + "Ġclar": 7927, + "ipar": 7928, + "Ġenrich": 7929, + "Ġroute": 7930, + "ayers": 7931, + "ediatric": 7932, + "Ġpolynomials": 7933, + "Ġtrivial": 7934, + "ĠSam": 7935, + "variant": 7936, + "Ġfreedom": 7937, + "poss": 7938, + "Ġinference": 7939, + "ola": 7940, + "Ġinterpreted": 7941, + "Ca": 7942, + "emory": 7943, + "Ġcentury": 7944, + "ĠRem": 7945, + "ĠWu": 7946, + "Ġsuppression": 7947, + "Ġgenerator": 7948, + "ĠHom": 7949, + "Ġviscos": 7950, + "Ġpseudo": 7951, + "ĠChild": 7952, + "ĠSA": 7953, + "iber": 7954, + "Ġequivalence": 7955, + "ifies": 7956, + "ĠConsider": 7957, + "oline": 7958, + "âī¤": 7959, + "Ġdeple": 7960, + "Ġaveraged": 7961, + "Ġsouthern": 7962, + "Ġordered": 7963, + "ĠBrown": 7964, + "Ġmethylation": 7965, + "ĠAdap": 7966, + "Ġmaternal": 7967, + "onded": 7968, + "ĠBehavi": 7969, + "Ġidentifiers": 7970, + "Ġprocessed": 7971, + "GG": 7972, + "VI": 7973, + "Ġcha": 7974, + "unk": 7975, + "ĠFunctional": 7976, + "Ġhydroph": 7977, + "Ġfinancial": 7978, + "econd": 7979, + "ĠΨ": 7980, + "Ġemphas": 7981, + "Ġdefect": 7982, + "mar": 7983, + "Ġnorthern": 7984, + "core": 7985, + "Ġadhesion": 7986, + "Ġtele": 7987, + "Ġwarm": 7988, + "rifug": 7989, + "rangian": 7990, + "resolution": 7991, + "Ġhex": 7992, + "hbar": 7993, + "Ġharmonic": 7994, + "Ġcontrac": 7995, + "Ġreading": 7996, + "Ġefforts": 7997, + "ĠOl": 7998, + "Ġanxiety": 7999, + "bul": 8000, + "TC": 8001, + "ipid": 8002, + "Remark": 8003, + "Ġforming": 8004, + "ilbert": 8005, + "amond": 8006, + "Ġanalytic": 8007, + "orec": 8008, + "cha": 8009, + "ĠConsequently": 8010, + "ĠSu": 8011, + "forall": 8012, + "ĠÃŀ": 8013, + "Ġaspect": 8014, + "Ġinsights": 8015, + "ativity": 8016, + "iotics": 8017, + "heimer": 8018, + "ĠLabor": 8019, + "Ġaware": 8020, + "ĠBritish": 8021, + "chemical": 8022, + "Ġâĭ": 8023, + "clusion": 8024, + "ĠMich": 8025, + "Ġgrade": 8026, + "ĠSEM": 8027, + "ĠCirc": 8028, + "heses": 8029, + "WL": 8030, + "Ġenabl": 8031, + "Ġdend": 8032, + "Ġindustry": 8033, + "Ġimproves": 8034, + "tet": 8035, + "Ġtel": 8036, + "Ġwashed": 8037, + "Ġshorter": 8038, + "Ġincident": 8039, + "ĠActivity": 8040, + "Ġdoses": 8041, + "ĠBrazil": 8042, + "Ġtransformations": 8043, + "Ġformat": 8044, + "ĠProof": 8045, + "Ġlen": 8046, + "ulative": 8047, + "Ġcyclic": 8048, + "Ġrecruit": 8049, + "ptr": 8050, + "TH": 8051, + "Ġreceive": 8052, + "ĠNext": 8053, + "ĠExp": 8054, + "iant": 8055, + "instein": 8056, + "Set": 8057, + "rene": 8058, + "Ġgeomet": 8059, + "Ġconsiderable": 8060, + "So": 8061, + "ught": 8062, + "Ġpapers": 8063, + "ĠCS": 8064, + "za": 8065, + "Ġisomorphism": 8066, + "hou": 8067, + "Ġmutants": 8068, + "Ġportion": 8069, + "Ġþ": 8070, + "Ġcontinuum": 8071, + "Cu": 8072, + "ĠComputed": 8073, + "Ġcombining": 8074, + "ova": 8075, + "ĠNP": 8076, + "Ġcrack": 8077, + "Ġsometimes": 8078, + "Ġcontinued": 8079, + "Definition": 8080, + "arcin": 8081, + "ĠCd": 8082, + "ĠMedical": 8083, + "iences": 8084, + "ĠCross": 8085, + "Ġtranscriptional": 8086, + "ĠZe": 8087, + "std": 8088, + "iforn": 8089, + "Ġfailed": 8090, + "Ġidentifying": 8091, + "Ġmir": 8092, + "Ġmetastasis": 8093, + "OF": 8094, + "nn": 8095, + "ĠCID": 8096, + "Ġoscillations": 8097, + "ancies": 8098, + "write": 8099, + "Ġbandwidth": 8100, + "Ġtrade": 8101, + "Ġaging": 8102, + "ĠModeling": 8103, + "Ġassert": 8104, + "Ġcurrents": 8105, + "Ġfire": 8106, + "ubiqu": 8107, + "Ġalbum": 8108, + "Ġfrequent": 8109, + "Name": 8110, + "Ġpurch": 8111, + "Ġplayer": 8112, + "ĠEsc": 8113, + "Ġnotion": 8114, + "Ġinternational": 8115, + "ulum": 8116, + "oic": 8117, + "Ġincubation": 8118, + "Ġphenomena": 8119, + "Ġserver": 8120, + "uter": 8121, + "Ġven": 8122, + "quin": 8123, + "Ġhypox": 8124, + "ĠRF": 8125, + "iton": 8126, + "Error": 8127, + "Ġhemat": 8128, + "Ġthemselves": 8129, + "Ġperp": 8130, + "idual": 8131, + "Ġpurposes": 8132, + "mes": 8133, + "wing": 8134, + "rov": 8135, + "Ġemiss": 8136, + "Ġexperienced": 8137, + "ques": 8138, + "ĠLC": 8139, + "ĠRecent": 8140, + "book": 8141, + "Ġalkal": 8142, + "idx": 8143, + "hyth": 8144, + "Ġconcrete": 8145, + "Ġswitching": 8146, + "Ġexplanation": 8147, + "irds": 8148, + "Ġsigns": 8149, + "Ġobj": 8150, + "Ġcytokines": 8151, + "ubble": 8152, + "adder": 8153, + "Ġuncertainties": 8154, + "Ġpromotes": 8155, + "Ġcompl": 8156, + "Ġscan": 8157, + "Ġprime": 8158, + "PH": 8159, + "Ġheterogeneous": 8160, + "ĠYou": 8161, + "Although": 8162, + "Ġserious": 8163, + "Ġdrive": 8164, + "Ġheterogeneity": 8165, + "rystall": 8166, + "Ġod": 8167, + "Ġconvolution": 8168, + "ĠâĬĨ": 8169, + "ĠSpace": 8170, + "Ġgastric": 8171, + "ĠStre": 8172, + "ĠPV": 8173, + "base": 8174, + "Met": 8175, + "Ġlosses": 8176, + "Ġcytotox": 8177, + "Ġcontrolling": 8178, + "lease": 8179, + "Ġregulated": 8180, + "ĠEngine": 8181, + "ĠHospital": 8182, + "Br": 8183, + "onom": 8184, + "hyde": 8185, + "stage": 8186, + "Ġgiving": 8187, + "ĠPen": 8188, + "ĠSociety": 8189, + "driven": 8190, + "iang": 8191, + "Ġmodifications": 8192, + "BV": 8193, + "Ġacceleration": 8194, + "Ġmilk": 8195, + "onomic": 8196, + "Ġthink": 8197, + "oglob": 8198, + "Ġfeasible": 8199, + "nam": 8200, + "Ġreflection": 8201, + "ĠPoly": 8202, + "Ġsummarized": 8203, + "FL": 8204, + "Ġrect": 8205, + "Ġpredominant": 8206, + "Ġblot": 8207, + "dehyde": 8208, + "Ġtransformed": 8209, + "Ġfacilitate": 8210, + "ĠCarlo": 8211, + "Ġgreatly": 8212, + "ĠSocial": 8213, + "Ġparents": 8214, + "bigg": 8215, + "rospective": 8216, + "Ġprognosis": 8217, + "Ġcharacterize": 8218, + "Ġconnectivity": 8219, + "Ġtrajectories": 8220, + "ĠSH": 8221, + "Ġlies": 8222, + "Ġcandidates": 8223, + "romy": 8224, + "Ġsor": 8225, + "ĠIns": 8226, + "Ġthor": 8227, + "Ġmetals": 8228, + "ĠSV": 8229, + "Ġtiming": 8230, + "Ġutility": 8231, + "Ġnewly": 8232, + "ĠIFN": 8233, + "Ġaffecting": 8234, + "cement": 8235, + "ĠMel": 8236, + "ĠÌģ": 8237, + "types": 8238, + "lysis": 8239, + "ercul": 8240, + "Ġdistor": 8241, + "actors": 8242, + "psy": 8243, + "Ġbook": 8244, + "ĠEven": 8245, + "temperature": 8246, + "Ġinvasion": 8247, + "Ġrecognized": 8248, + "factor": 8249, + "Ne": 8250, + "Ġintersection": 8251, + "Ġcortical": 8252, + "ng": 8253, + "Ġdeploy": 8254, + "Ġamplitudes": 8255, + "Ġda": 8256, + "ĠGC": 8257, + "Ġchallenging": 8258, + "Ġprelim": 8259, + "GM": 8260, + "Acc": 8261, + "Ġfourth": 8262, + "alc": 8263, + "ĠPS": 8264, + "ĠGenetic": 8265, + "lock": 8266, + "error": 8267, + "skip": 8268, + "sime": 8269, + "Ġana": 8270, + "simeq": 8271, + "Ġcerebral": 8272, + "ĠEX": 8273, + "aved": 8274, + "rophy": 8275, + "idopsis": 8276, + "Ġbehind": 8277, + "Ġenables": 8278, + "Ġindustrial": 8279, + "ĠPac": 8280, + "Ġdefinitions": 8281, + "Ġcatalytic": 8282, + "Ġdissip": 8283, + "ervical": 8284, + "Ġcommut": 8285, + "Ġrepeat": 8286, + "Ġchiral": 8287, + "Ġpron": 8288, + "pol": 8289, + "Ġgoing": 8290, + "Ġmicroscope": 8291, + "Ġhealthcare": 8292, + "ĠClassification": 8293, + "titude": 8294, + "ĠFermi": 8295, + "Ġhttp": 8296, + "arest": 8297, + "Ġsupporting": 8298, + "Ġwood": 8299, + "night": 8300, + "Ġkinetics": 8301, + "Ġsubsets": 8302, + "Ġsubunit": 8303, + "ĠCanada": 8304, + "aton": 8305, + "Ġaccurately": 8306, + "Ġresistant": 8307, + "Ġï̽": 8308, + "riction": 8309, + "Ġchamber": 8310, + "igue": 8311, + "ĠPhil": 8312, + "Ġrecover": 8313, + "cs": 8314, + "Ġsphere": 8315, + "ĠSpecifically": 8316, + "Ġanne": 8317, + "Ġinitiation": 8318, + "ĠTH": 8319, + "Ġbud": 8320, + "ordered": 8321, + "Ġdielectric": 8322, + "ĠCollege": 8323, + "Ġproducing": 8324, + "Ġantenna": 8325, + "Bs": 8326, + "ĠFrench": 8327, + "OX": 8328, + "ĠAmerica": 8329, + "ĠâĢĶ": 8330, + "ounting": 8331, + "fully": 8332, + "Ġserved": 8333, + "Ġresidue": 8334, + "Ġarguments": 8335, + "Ġpand": 8336, + "Ġcompany": 8337, + "Ġconditional": 8338, + "mia": 8339, + "ĠQCD": 8340, + "Ġviscosity": 8341, + "Ġprospective": 8342, + "asonal": 8343, + "Ġdominated": 8344, + "Ġpenet": 8345, + "opo": 8346, + "Ġnine": 8347, + "ĠIll": 8348, + "ĠVisual": 8349, + "Ġfiles": 8350, + "Ġyeast": 8351, + "Ġthank": 8352, + "GN": 8353, + "real": 8354, + "Ġverified": 8355, + "ĠIndian": 8356, + "Ġstiff": 8357, + "rological": 8358, + "Ġdram": 8359, + "Ġtight": 8360, + "ĠGerman": 8361, + "ĠTechnology": 8362, + "ĠApproach": 8363, + "romatic": 8364, + "Ġacoustic": 8365, + "tian": 8366, + "osin": 8367, + "ĠDepartment": 8368, + "otropy": 8369, + "Ġempty": 8370, + "trivial": 8371, + "ofil": 8372, + "Ġalgebras": 8373, + "texts": 8374, + "Ġwebs": 8375, + "Ġpore": 8376, + "Ġpacket": 8377, + "Time": 8378, + "img": 8379, + "ony": 8380, + "ritic": 8381, + "Ġvelocities": 8382, + "ĠDynamics": 8383, + "Ġcancers": 8384, + "Ġtrunc": 8385, + "ĠFormation": 8386, + "ĠDonor": 8387, + "ĠMit": 8388, + "IST": 8389, + "Ġconcluded": 8390, + "Ġantag": 8391, + "ĠSoft": 8392, + "append": 8393, + "Ġfragments": 8394, + "ĠProf": 8395, + "Ġfluor": 8396, + "ĠJac": 8397, + "ĠSn": 8398, + "Ġlept": 8399, + "Ġsplitting": 8400, + "Ġsexual": 8401, + "ĠFore": 8402, + "ĠGener": 8403, + "Ġneighborhood": 8404, + "Ġbenchmark": 8405, + "ĠRA": 8406, + "Ġdivision": 8407, + "ifornia": 8408, + "True": 8409, + "Ġfuzzy": 8410, + "Ġtro": 8411, + "cents": 8412, + "Ġconstitu": 8413, + "atial": 8414, + "astern": 8415, + "ĠTim": 8416, + "Ġperception": 8417, + "Ġsubstanti": 8418, + "Ġmacro": 8419, + "Ġoutl": 8420, + "ĠObserv": 8421, + "prising": 8422, + "oked": 8423, + "orectal": 8424, + "ĠCho": 8425, + "ĠDifferent": 8426, + "Ġinvestigations": 8427, + "Ġconsistency": 8428, + "ients": 8429, + "ĠFOR": 8430, + "ASS": 8431, + "ĠVan": 8432, + "Ġsituations": 8433, + "ĠBR": 8434, + "Ġinfrared": 8435, + "ymal": 8436, + "Ġpixels": 8437, + "Ġcarrier": 8438, + "sen": 8439, + "INT": 8440, + "Ġefficiently": 8441, + "DT": 8442, + "ĠExpl": 8443, + "ionic": 8444, + "Ġnaturally": 8445, + "Ġpropos": 8446, + "Ġguide": 8447, + "Ġconclusions": 8448, + "oon": 8449, + "Ġgrant": 8450, + "Ġinstances": 8451, + "Ġreviewed": 8452, + "Ġelectromagnetic": 8453, + "Ġthreat": 8454, + "edia": 8455, + "ĠOptimization": 8456, + "ĠBio": 8457, + "Ġtrigger": 8458, + "icient": 8459, + "otypic": 8460, + "Ġstret": 8461, + "Ġantic": 8462, + "Ġtoxic": 8463, + "Ġspinal": 8464, + "UPAC": 8465, + "Ġoverview": 8466, + "otion": 8467, + "Ġstraightforward": 8468, + "Ġpositively": 8469, + "aste": 8470, + "Ġreferences": 8471, + "ulose": 8472, + "ĠGre": 8473, + "Ġantagon": 8474, + "Ġshifts": 8475, + "Ġdrawn": 8476, + "ĠWhite": 8477, + "Ġfractional": 8478, + "Ġbundle": 8479, + "Ġexhibits": 8480, + "Ġreservoir": 8481, + "ĠAlex": 8482, + "Ġaggregation": 8483, + "Ġcircle": 8484, + "Ġpractices": 8485, + "ĠCoval": 8486, + "ĠDistribution": 8487, + "Ġtang": 8488, + "ĠMut": 8489, + "Ġregulate": 8490, + "osphere": 8491, + "iro": 8492, + "AMINO": 8493, + "vest": 8494, + "Ġphotos": 8495, + "Ġevident": 8496, + "Ġbusiness": 8497, + "control": 8498, + "Ġworth": 8499, + "ĠPoisson": 8500, + "ĠArabidopsis": 8501, + "ĠTarget": 8502, + "Ġregulates": 8503, + "ĠIr": 8504, + "ĠAdv": 8505, + "Ġensemble": 8506, + "pring": 8507, + "Ġprice": 8508, + "ĠFL": 8509, + "ĠImpact": 8510, + "Ġeventually": 8511, + "inating": 8512, + "Ġcentrifug": 8513, + "frame": 8514, + "Ġdiagrams": 8515, + "Ġtag": 8516, + "Ġtry": 8517, + "surface": 8518, + "ĠIdentifiers": 8519, + "rained": 8520, + "Ġsides": 8521, + "Ġinn": 8522, + "Ġflexible": 8523, + "Ġsatellite": 8524, + "Ġaffinity": 8525, + "Ġsummer": 8526, + "GP": 8527, + "amb": 8528, + "Ġaqu": 8529, + "String": 8530, + "treatment": 8531, + "ĠDynamic": 8532, + "mathop": 8533, + "Ġnotice": 8534, + "nes": 8535, + "rowave": 8536, + "vestig": 8537, + "Ġoutputs": 8538, + "Ġcoherent": 8539, + "Ġillustrate": 8540, + "Ġvalidated": 8541, + "ĠSchem": 8542, + "Ġasked": 8543, + "batch": 8544, + "Ġpurified": 8545, + "Ġminimize": 8546, + "ĠDE": 8547, + "UM": 8548, + "check": 8549, + "varian": 8550, + "ĠGold": 8551, + "ylene": 8552, + "IO": 8553, + "Ġcholesterol": 8554, + "PubChem": 8555, + "ĠKore": 8556, + "ĠCounty": 8557, + "Ġii": 8558, + "ĠMAP": 8559, + "ectomy": 8560, + "Ġsemantic": 8561, + "Ġcollagen": 8562, + "Ġperceived": 8563, + "ichia": 8564, + "Ġadministered": 8565, + "containing": 8566, + "rank": 8567, + "InChI": 8568, + "Ġirradiation": 8569, + "Ġlogarithm": 8570, + "Ġgames": 8571, + "Ġinjected": 8572, + "ĠMHz": 8573, + "Ġdors": 8574, + "Ġevaluating": 8575, + "ĠHyper": 8576, + "Ġchromatography": 8577, + "phen": 8578, + "ĠKar": 8579, + "Ġantimicrobial": 8580, + "riend": 8581, + "Ġdescribing": 8582, + "Ġwt": 8583, + "Ġhormone": 8584, + "AK": 8585, + "ĠIUPAC": 8586, + "Ga": 8587, + "Ġvitamin": 8588, + "Ġconnections": 8589, + "uous": 8590, + "ĠLine": 8591, + "Ġbeneficial": 8592, + "cases": 8593, + "icated": 8594, + "isks": 8595, + "parent": 8596, + "Id": 8597, + "eries": 8598, + "run": 8599, + "Ġmind": 8600, + "itt": 8601, + "sulf": 8602, + "zheimer": 8603, + "Ġinterf": 8604, + "Vert": 8605, + "Ġanth": 8606, + "ologous": 8607, + "ĠLife": 8608, + "Ġmur": 8609, + "Ġpermut": 8610, + "oting": 8611, + "Ġneutrino": 8612, + "Ġborn": 8613, + "pmatrix": 8614, + "ĠCalifornia": 8615, + "agent": 8616, + "Ġcollisions": 8617, + "ĠNS": 8618, + "Ġhippocamp": 8619, + "Ġpowder": 8620, + "Ġvaries": 8621, + "Ġepidem": 8622, + "ĠWeb": 8623, + "uler": 8624, + "Ġinterested": 8625, + "Ġdevelopmental": 8626, + "Ġlengths": 8627, + "Ġcolour": 8628, + "Ġquas": 8629, + "ĠRich": 8630, + "Eq": 8631, + "Ġinfants": 8632, + "ĠPH": 8633, + "ophila": 8634, + "Ġcausing": 8635, + "Ge": 8636, + "module": 8637, + "IB": 8638, + "Ġcontributed": 8639, + "rose": 8640, + "Ġcytoplas": 8641, + "--------------------------------": 8642, + "Ġroad": 8643, + "symmetric": 8644, + "Us": 8645, + "Ġweakly": 8646, + "tite": 8647, + "Ġdefines": 8648, + "ĠPE": 8649, + "Ġmetabolites": 8650, + "Ġlob": 8651, + "Ġterminal": 8652, + "Ġdemonstrates": 8653, + "ĠAcceptor": 8654, + "ĠClo": 8655, + "Ġinferred": 8656, + "Ġvill": 8657, + "First": 8658, + "Ġneglig": 8659, + "Ġwireless": 8660, + "Ab": 8661, + "particle": 8662, + "oisotopic": 8663, + "Ġexcited": 8664, + "PM": 8665, + "Ġconsecutive": 8666, + "ĠIsotype": 8667, + "Ġstimulus": 8668, + "ĠMC": 8669, + "timate": 8670, + "ĠCovalently": 8671, + "Bonded": 8672, + "Ġyellow": 8673, + "Ġalloy": 8674, + "density": 8675, + "Ġfilters": 8676, + "Ġamplification": 8677, + "Ġwon": 8678, + "ht": 8679, + "Ġimpacts": 8680, + "Ġstaff": 8681, + "ĠâĪĢ": 8682, + "ĠIsomeric": 8683, + "Ġsmoking": 8684, + "Qu": 8685, + "Ġcaptured": 8686, + "haps": 8687, + "ĠNULL": 8688, + "Ġriver": 8689, + "count": 8690, + "Ġmanifest": 8691, + "Ġdiabetic": 8692, + "Ġalterations": 8693, + "ĠRotatable": 8694, + "ĠPRO": 8695, + "ĠMonoisotopic": 8696, + "ĠïĤ": 8697, + "spect": 8698, + "Ġcatalyst": 8699, + "Ġmodeled": 8700, + "Ġpage": 8701, + "ĠROS": 8702, + "ĠCanonicalized": 8703, + "ĠTw": 8704, + "Ġaux": 8705, + "avage": 8706, + "ĠRaman": 8707, + "sto": 8708, + "perf": 8709, + "Ġreplacement": 8710, + "ĠEnvironment": 8711, + "Ġacting": 8712, + "pati": 8713, + "ificant": 8714, + "through": 8715, + "Ġsaturation": 8716, + "Ġtip": 8717, + "Ġrecurrence": 8718, + "ĠHistory": 8719, + "Ġprotective": 8720, + "Ġburden": 8721, + "ado": 8722, + "yes": 8723, + "inst": 8724, + "Ap": 8725, + "ĠSy": 8726, + "Ġphon": 8727, + "ĠâĪij": 8728, + "Ġgenotype": 8729, + "Ġcovariance": 8730, + "Ġquickly": 8731, + "ĠDu": 8732, + "Ġsug": 8733, + "Ġdecline": 8734, + "ĠTB": 8735, + "Ġstrictly": 8736, + "Ġmoist": 8737, + "undred": 8738, + "ĠCB": 8739, + "atile": 8740, + "ĠHF": 8741, + "Ġarticles": 8742, + "Ġps": 8743, + "ĠEnh": 8744, + "isting": 8745, + "Ġbiology": 8746, + "Ġbodies": 8747, + "ĠAk": 8748, + "ĠNumerical": 8749, + "ĠLagrangian": 8750, + "Ġdiscovered": 8751, + "Ġvic": 8752, + "opes": 8753, + "Ġfragment": 8754, + "Ġty": 8755, + "ismic": 8756, + "Ġhepatic": 8757, + "Ġenriched": 8758, + "pan": 8759, + "Ġinfluences": 8760, + "ĠLake": 8761, + "color": 8762, + "Ġenrichment": 8763, + "ochemistry": 8764, + "Ġunstable": 8765, + "ĠIgG": 8766, + "derly": 8767, + "Ġecos": 8768, + "Ġconcerning": 8769, + "ĠRisk": 8770, + "Ġmargin": 8771, + "Ġpathogenesis": 8772, + "Ġpump": 8773, + "Ġpreliminary": 8774, + "Ġtumour": 8775, + "Further": 8776, + "azole": 8777, + "Ġelectrodes": 8778, + "Ġdial": 8779, + "ubes": 8780, + "ĠNatural": 8781, + "ĠMul": 8782, + "ĠïĢŃ": 8783, + "Ġnic": 8784, + "Ġimped": 8785, + "only": 8786, + "Ġcomparative": 8787, + "rection": 8788, + "aki": 8789, + "Ġrend": 8790, + "Ġsparse": 8791, + "Ġindicator": 8792, + "location": 8793, + "tism": 8794, + "activated": 8795, + "ĠPb": 8796, + "eptide": 8797, + "Ġendogenous": 8798, + "Ġcenters": 8799, + "ao": 8800, + "sw": 8801, + "Ġconsensus": 8802, + "Ġattributes": 8803, + "Ġsafe": 8804, + "Ġbelieve": 8805, + "ovirus": 8806, + "Ġimmunity": 8807, + "Ġfitted": 8808, + "Ġcontributes": 8809, + "iable": 8810, + "Ġviruses": 8811, + "Ġinsight": 8812, + "ĠNovel": 8813, + "ĠAlzheimer": 8814, + "cepted": 8815, + "ĠPt": 8816, + "Ġcentre": 8817, + "nat": 8818, + "Ġbiosynthesis": 8819, + "mits": 8820, + "Ġchemistry": 8821, + "Ġjus": 8822, + "anish": 8823, + "Ġrefrac": 8824, + "ĠTor": 8825, + "Ġpanels": 8826, + "Ġimply": 8827, + "Ġmatched": 8828, + "usc": 8829, + "word": 8830, + "vae": 8831, + "ĠStar": 8832, + "syn": 8833, + "Mat": 8834, + "Ġapplicable": 8835, + "ĠPseud": 8836, + "ampions": 8837, + "ĠRen": 8838, + "Ġusage": 8839, + "ĠLight": 8840, + "prec": 8841, + "Ġfibrosis": 8842, + "Ġreconstruc": 8843, + "ĠON": 8844, + "ĠGHz": 8845, + "GD": 8846, + "algebra": 8847, + "iger": 8848, + "Ġdecisions": 8849, + "infected": 8850, + "knowledg": 8851, + "Ġexpressing": 8852, + "Ġmyocardial": 8853, + "ordination": 8854, + "Ġprognostic": 8855, + "Ġfibrobl": 8856, + "Ġacceler": 8857, + "ĠAssessment": 8858, + "Ġconstrained": 8859, + "Ġallele": 8860, + "ride": 8861, + "Ġrequest": 8862, + "abilistic": 8863, + "teb": 8864, + "Ġga": 8865, + "Ġrecovered": 8866, + "Ġpromin": 8867, + "urses": 8868, + "ĠHC": 8869, + "ĠMur": 8870, + "ĠEqs": 8871, + "Ġdefining": 8872, + "Ġmer": 8873, + "image": 8874, + "Ġorganisms": 8875, + "grad": 8876, + "Ġreflected": 8877, + "elastic": 8878, + "eties": 8879, + "dimethyl": 8880, + "ELO": 8881, + "random": 8882, + "ĠDiagn": 8883, + "erculosis": 8884, + "rob": 8885, + "Ġmoments": 8886, + "ĠEC": 8887, + "Ġexperiences": 8888, + "erving": 8889, + "ĠNC": 8890, + "Ġvortex": 8891, + "gre": 8892, + "structures": 8893, + "elt": 8894, + "Ġcarry": 8895, + "ĠThrough": 8896, + "Ġpreced": 8897, + "rastruct": 8898, + "itus": 8899, + "Ġpsychological": 8900, + "Ġlimiting": 8901, + "two": 8902, + "ĠBound": 8903, + "ĠCre": 8904, + "ĠSmith": 8905, + "Ġcast": 8906, + "Ġcompetition": 8907, + "sch": 8908, + "Ġcapability": 8909, + "achment": 8910, + "Ġinhibits": 8911, + "ð": 8912, + "ĠDifferential": 8913, + "Ġautomatically": 8914, + "Ġgest": 8915, + "Ġwaters": 8916, + "Ġuniqu": 8917, + "zer": 8918, + "Equ": 8919, + "Ġstudying": 8920, + "Ġdied": 8921, + "Ġos": 8922, + "Ġrecombination": 8923, + "uncil": 8924, + "Ġpathogen": 8925, + "GFR": 8926, + "UV": 8927, + "eneration": 8928, + "ĠSta": 8929, + "Ġinstant": 8930, + "Ġproven": 8931, + "Ġds": 8932, + "Ġdamp": 8933, + "Next": 8934, + "ĠYoung": 8935, + "Ġpowerful": 8936, + "Ġwriting": 8937, + "kl": 8938, + "Ġcareer": 8939, + "ĠCorollary": 8940, + "Ns": 8941, + "Ġ�": 8942, + "ĠMil": 8943, + "Ġburn": 8944, + "ticular": 8945, + "ondon": 8946, + "Pr": 8947, + "ĠLin": 8948, + "ĠJapanese": 8949, + "ĠLab": 8950, + "Ġstrip": 8951, + "protein": 8952, + "Ġhour": 8953, + "anglement": 8954, + "anguages": 8955, + "rd": 8956, + "parse": 8957, + "Ġemissions": 8958, + "Hence": 8959, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 8960, + "Ġjob": 8961, + "ĠAS": 8962, + "Ġaxial": 8963, + "ĠTur": 8964, + "carbon": 8965, + "MF": 8966, + "ĠNE": 8967, + "Ġarise": 8968, + "Ġlinearly": 8969, + "Ġprolong": 8970, + "Ġleak": 8971, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 8972, + "Ġmoved": 8973, + "orbidity": 8974, + "Ġprofessional": 8975, + "code": 8976, + "osine": 8977, + "Ġpolic": 8978, + "Ġbonds": 8979, + "mask": 8980, + "Ġconverted": 8981, + "ville": 8982, + "ectious": 8983, + "parallel": 8984, + "ĠHal": 8985, + "ĠTGF": 8986, + "mental": 8987, + "Ġreader": 8988, + "Ġstandards": 8989, + "ago": 8990, + "ĠEN": 8991, + "Ġstations": 8992, + "Ġnormalization": 8993, + "ĠÎĺ": 8994, + "chain": 8995, + "What": 8996, + "Ġtomography": 8997, + "Ġentries": 8998, + "blue": 8999, + "ĠPrevious": 9000, + "ias": 9001, + "Ġquestionnaire": 9002, + "Ġhaz": 9003, + "Ġhomology": 9004, + "very": 9005, + "Ġnucleotide": 9006, + "ĠGenome": 9007, + "Ġμl": 9008, + "Ġutilization": 9009, + "Ġpolymers": 9010, + "rote": 9011, + "Ġsmallest": 9012, + "calc": 9013, + "Ġspl": 9014, + "Ġtension": 9015, + "Ġdiscontinu": 9016, + "ala": 9017, + "hol": 9018, + "Ġdetermines": 9019, + "Ġproj": 9020, + "ĠOverall": 9021, + "Ġble": 9022, + "fo": 9023, + "Ġprinciples": 9024, + "Ġinteracting": 9025, + "Ġhardware": 9026, + "life": 9027, + "ails": 9028, + "Ġdifficulty": 9029, + "Ġchoices": 9030, + "Ġcard": 9031, + "Ġlact": 9032, + "Ġroll": 9033, + "Ġquantified": 9034, + "ĠScientific": 9035, + "Ġlandsc": 9036, + "aligned": 9037, + "Ġcomposites": 9038, + "herichia": 9039, + "Ġenvelop": 9040, + "itig": 9041, + "Ste": 9042, + "Ġcompet": 9043, + "Ġimpairment": 9044, + "Ġclosure": 9045, + "Ġreturned": 9046, + "Ġreceiver": 9047, + "Ġpeer": 9048, + "Ġconsent": 9049, + "Ġultras": 9050, + "Ġphotons": 9051, + "Ġsuppose": 9052, + "Ġpredicting": 9053, + "ĠâĬķ": 9054, + "Ġcompan": 9055, + "Ġnegligible": 9056, + "current": 9057, + "umber": 9058, + "Ġcompatible": 9059, + "iop": 9060, + "ĠStructural": 9061, + "Ref": 9062, + "Ġson": 9063, + "Ġequality": 9064, + "Ġconsisted": 9065, + "Ġvibr": 9066, + "oupling": 9067, + "vation": 9068, + "Ġovercome": 9069, + "super": 9070, + "lict": 9071, + "Ġpancreatic": 9072, + "Gs": 9073, + "aped": 9074, + "asal": 9075, + "wan": 9076, + "Ġlatent": 9077, + "Ġcovering": 9078, + "Ġlesion": 9079, + "iance": 9080, + "ĠFT": 9081, + "wood": 9082, + "jecture": 9083, + "ĠBC": 9084, + "linked": 9085, + "ĠLaw": 9086, + "Ġemit": 9087, + "Ġunclear": 9088, + "Ġprem": 9089, + "acted": 9090, + "polar": 9091, + "cre": 9092, + "Ġmodulus": 9093, + "ropath": 9094, + "Sub": 9095, + "ami": 9096, + "Ġpick": 9097, + "ERR": 9098, + "Ġmovements": 9099, + "Ni": 9100, + "Ġmechanics": 9101, + "odic": 9102, + "Ġgal": 9103, + "ĠManagement": 9104, + "host": 9105, + "ewise": 9106, + "ĠTotal": 9107, + "ĠInfluence": 9108, + "Ġubiqu": 9109, + "rophys": 9110, + "Ġcaps": 9111, + "Ġparticipant": 9112, + "Ġpolyp": 9113, + "td": 9114, + "Ġiterations": 9115, + "dominal": 9116, + "BB": 9117, + "Ġcharacters": 9118, + "Ġdeviations": 9119, + "resistant": 9120, + "Ġmalaria": 9121, + "Ġremote": 9122, + "hskip": 9123, + "Ġunderwent": 9124, + "util": 9125, + "block": 9126, + "uclide": 9127, + "Φ": 9128, + "electron": 9129, + "Ġsensory": 9130, + "ĠSimulation": 9131, + "Ġreward": 9132, + "Ġpandemic": 9133, + "Ġbor": 9134, + "ynthetic": 9135, + "Ġinvasive": 9136, + "RF": 9137, + "ĠSmall": 9138, + "ĠFisher": 9139, + "valent": 9140, + "ĠMI": 9141, + "rocytes": 9142, + "ĠTE": 9143, + "Ġstre": 9144, + "Ġperturbations": 9145, + "Ġsimplicity": 9146, + "ĠGrowth": 9147, + "ĠÎł": 9148, + "Ġinoc": 9149, + "arding": 9150, + "atum": 9151, + "multi": 9152, + "ĠDiv": 9153, + "anes": 9154, + "acillus": 9155, + "Ġlifetime": 9156, + "ĠHep": 9157, + "Ġaz": 9158, + "usp": 9159, + "ĠAssume": 9160, + "Ġbreaking": 9161, + "ĠAtt": 9162, + "ticipants": 9163, + "Ġluminosity": 9164, + "Ġdonor": 9165, + "params": 9166, + "ohyd": 9167, + "Ġprogen": 9168, + "ĠPO": 9169, + "GO": 9170, + "ĠLeg": 9171, + "Ġbiomarkers": 9172, + "Ġrural": 9173, + "Ġneon": 9174, + "gluc": 9175, + "ĠPB": 9176, + "Ġguid": 9177, + "Ġcervical": 9178, + "pace": 9179, + "Ġcord": 9180, + "umn": 9181, + "Ġsubspace": 9182, + "Ġattached": 9183, + "Ġdeposited": 9184, + "Ġindicators": 9185, + "ĠSF": 9186, + "quire": 9187, + "Ġdissolved": 9188, + "rite": 9189, + "ĠNA": 9190, + "Ġju": 9191, + "Ġaddressed": 9192, + "Ġsuppressed": 9193, + "Ġpneumonia": 9194, + "Ġsession": 9195, + "ĠChe": 9196, + "ĠFer": 9197, + "Ġaccordance": 9198, + "Des": 9199, + "Ġquar": 9200, + "Ġfitness": 9201, + "Ġviability": 9202, + "osh": 9203, + "Ġphylogenetic": 9204, + "ectin": 9205, + "pat": 9206, + "ĠFrance": 9207, + "Ġmessages": 9208, + "Ġloci": 9209, + "Ġconflict": 9210, + "Ġrelevance": 9211, + "Ġinstructions": 9212, + "Ġsomewhat": 9213, + "changed": 9214, + "Ġcorrectly": 9215, + "ozyg": 9216, + "avig": 9217, + "ĠLat": 9218, + "Ġovarian": 9219, + "ĠRemark": 9220, + "joint": 9221, + "aint": 9222, + "west": 9223, + "sample": 9224, + "Ġdivergence": 9225, + "Ġhair": 9226, + "agonal": 9227, + "Ġmim": 9228, + "Ġimmediate": 9229, + "ĠPort": 9230, + "Ġoffers": 9231, + "Ġdepicted": 9232, + "Ġhydrox": 9233, + "ĠTow": 9234, + "Ġemerging": 9235, + "oupled": 9236, + "Ġhundred": 9237, + "Ġadapted": 9238, + "eller": 9239, + "ĠRelations": 9240, + "ette": 9241, + "Ġgastro": 9242, + "Ġmorphism": 9243, + "Ġequipment": 9244, + "pop": 9245, + "unately": 9246, + "Ġtransplantation": 9247, + "ifiers": 9248, + "Ġelderly": 9249, + "onucle": 9250, + "Ġrefers": 9251, + "arial": 9252, + "ĠCommittee": 9253, + "Ġmalignant": 9254, + "omonas": 9255, + "Ġallocation": 9256, + "ogether": 9257, + "Ġnanot": 9258, + "plot": 9259, + "ĠMes": 9260, + "Ġplanar": 9261, + "ells": 9262, + "source": 9263, + "owski": 9264, + "Ġna": 9265, + "Ġclock": 9266, + "Ġambient": 9267, + "ocene": 9268, + "Ġfluorescent": 9269, + "Ġvalu": 9270, + "ĠMagnetic": 9271, + "Ġdepart": 9272, + "phosphate": 9273, + "Ġroughly": 9274, + "Ġneither": 9275, + "ĠAltern": 9276, + "Ġstay": 9277, + "Ġspot": 9278, + "ĠEnt": 9279, + "Ġseconds": 9280, + "hard": 9281, + "Ġrecurrent": 9282, + "Ġpatch": 9283, + "Ġlimitation": 9284, + "ĠDer": 9285, + "Ġsharp": 9286, + "Ġexpectation": 9287, + "ĠLore": 9288, + "dict": 9289, + "Reg": 9290, + "Ġneutroph": 9291, + "Ġnur": 9292, + "Ġstarts": 9293, + "ostasis": 9294, + "Ġorganized": 9295, + "ĠcDNA": 9296, + "orient": 9297, + "ĠExample": 9298, + "ĠFund": 9299, + "aylor": 9300, + "idering": 9301, + "Ġtriple": 9302, + "nic": 9303, + "Ġattacks": 9304, + "ĠDros": 9305, + "è": 9306, + "ĠEM": 9307, + "Ġoptimum": 9308, + "Ġpull": 9309, + "Ġce": 9310, + "eryth": 9311, + "Ġrating": 9312, + "Ġreproductive": 9313, + "Ġdecades": 9314, + "Ġreplace": 9315, + "List": 9316, + "ĠFast": 9317, + "Ġredshift": 9318, + "opsy": 9319, + "illa": 9320, + "double": 9321, + "tera": 9322, + "Ġgoals": 9323, + "ĠSk": 9324, + "INE": 9325, + "Ġbiochemical": 9326, + "uint": 9327, + "Ġfetal": 9328, + "ĠRiemann": 9329, + "uries": 9330, + "Ġpp": 9331, + "Ġsymbols": 9332, + "ĠKa": 9333, + "Di": 9334, + "ĠGalax": 9335, + "ĠCompared": 9336, + "Ġcasc": 9337, + "Ġbits": 9338, + "Ġscaff": 9339, + "Ġestimator": 9340, + "ĠAdditional": 9341, + "Ġimprovements": 9342, + "ectives": 9343, + "Ġhous": 9344, + "ĠMagn": 9345, + "Ġmultivariate": 9346, + "Ġagric": 9347, + "vo": 9348, + "utter": 9349, + "ĠAcknowledg": 9350, + "su": 9351, + "Ġammon": 9352, + "Ġaims": 9353, + "Ġzinc": 9354, + "Ġelong": 9355, + "ĠGO": 9356, + "Question": 9357, + "including": 9358, + "LogP": 9359, + "Ġintellig": 9360, + "Ġcone": 9361, + "ĠFoundation": 9362, + "Ġimpaired": 9363, + "Ġillness": 9364, + "ĠEscherichia": 9365, + "Ġabundant": 9366, + "scal": 9367, + "ensively": 9368, + "Ġnegatively": 9369, + "parameter": 9370, + "Ġpermeability": 9371, + "domain": 9372, + "rated": 9373, + "Ġepoch": 9374, + "Ġadolescents": 9375, + "Ġdefic": 9376, + "ĠEstimation": 9377, + "Ġroutine": 9378, + "Per": 9379, + "tol": 9380, + "Ġelliptic": 9381, + "ĠHE": 9382, + "oblast": 9383, + "Ġreaches": 9384, + "Ġfluxes": 9385, + "Ġsun": 9386, + "ĠAnaly": 9387, + "âĢľ": 9388, + "ĠXLogP": 9389, + "Ġfiltering": 9390, + "rian": 9391, + "ĠScal": 9392, + "Ġpin": 9393, + "ĠTiO": 9394, + "iments": 9395, + "Ġmarginal": 9396, + "Ġrecombinant": 9397, + "Ġencour": 9398, + "Ġalumin": 9399, + "Ġtf": 9400, + "atalytic": 9401, + "Ġobservational": 9402, + "Ġgeneralization": 9403, + "Ġ": 9404, + "Ġantibiotic": 9405, + "Ġgenerates": 9406, + "ĠdB": 9407, + "Spec": 9408, + "rically": 9409, + "Ġvaluable": 9410, + "Ġtopic": 9411, + "Ġtermin": 9412, + "Ġsemicon": 9413, + "Ġquantification": 9414, + "ubb": 9415, + "Ġkinem": 9416, + "erring": 9417, + "Ġaeros": 9418, + "pack": 9419, + "Ġfewer": 9420, + "Ġfatigue": 9421, + "Ġgoes": 9422, + "Ġnight": 9423, + "ĠUs": 9424, + "â̬": 9425, + "ĠPrinc": 9426, + "Ġspring": 9427, + "Ġconcerns": 9428, + "Ġsmart": 9429, + "Ġsecret": 9430, + "Ġmmol": 9431, + "Ġbelief": 9432, + "DC": 9433, + "Ġsubstantially": 9434, + "âĪĩ": 9435, + "Ġsubstitution": 9436, + "mapsto": 9437, + "sky": 9438, + "illance": 9439, + "Ġstudent": 9440, + "okine": 9441, + "Ġinterior": 9442, + "Ġeigenvalue": 9443, + "my": 9444, + "Ġcloser": 9445, + "erenti": 9446, + "Ġecological": 9447, + "ĠFigures": 9448, + "olytic": 9449, + "Ġarrays": 9450, + "ĠCas": 9451, + "Ġloops": 9452, + "Ġcorrected": 9453, + "Ġrhe": 9454, + "Ġinversion": 9455, + "Ġpreferred": 9456, + "umab": 9457, + "ĠDI": 9458, + "Ġadequate": 9459, + "irm": 9460, + "Ġimplicit": 9461, + "ship": 9462, + "Ġplayers": 9463, + "Ġdelayed": 9464, + "Ġwinter": 9465, + "Ġvulner": 9466, + "Ġshapes": 9467, + "Ġstained": 9468, + "ĠMajor": 9469, + "Ġhierarchical": 9470, + "ĠDig": 9471, + "ersion": 9472, + "ĠEfficient": 9473, + "Ġwalls": 9474, + "dfrac": 9475, + "Ġclassifier": 9476, + "Ġmonol": 9477, + "Ġupdated": 9478, + "Ġmature": 9479, + "ĠLI": 9480, + "earing": 9481, + "Ġfinger": 9482, + "ounter": 9483, + "ankton": 9484, + "While": 9485, + "Ġrealistic": 9486, + "ĠCamp": 9487, + "Ġfilled": 9488, + "Ġdead": 9489, + "ĠPacific": 9490, + "Ïĩ": 9491, + "ĠDavid": 9492, + "Ġadditive": 9493, + "enchymal": 9494, + "Ġobser": 9495, + "Ġstere": 9496, + "Ġultrasound": 9497, + "ĠPredic": 9498, + "Ġends": 9499, + "sectional": 9500, + "mas": 9501, + "omat": 9502, + "ivity": 9503, + "Ġhandle": 9504, + "Ġmetastatic": 9505, + "olet": 9506, + "ryp": 9507, + "ACE": 9508, + "Ġporous": 9509, + "Ġconcern": 9510, + "itored": 9511, + "Ġcircles": 9512, + "Ġemotional": 9513, + "gered": 9514, + "Ġfriction": 9515, + "first": 9516, + "ophy": 9517, + "escop": 9518, + "aded": 9519, + "Ġresolved": 9520, + "ERS": 9521, + "Ġpathogens": 9522, + "Ġgradually": 9523, + "ĠBrain": 9524, + "xf": 9525, + "anium": 9526, + "ael": 9527, + "New": 9528, + "Ġcytokine": 9529, + "ĠBP": 9530, + "Ġspecimen": 9531, + "olean": 9532, + "Ġtaxon": 9533, + "Ġsequential": 9534, + "κB": 9535, + "ademic": 9536, + "plings": 9537, + "~~": 9538, + "ermal": 9539, + "tree": 9540, + "Ġcausal": 9541, + "arian": 9542, + "Ġcrop": 9543, + "opol": 9544, + "channel": 9545, + "ĠMex": 9546, + "Ġclon": 9547, + "ĠRecently": 9548, + "ĠInvestig": 9549, + "Ġrecommendations": 9550, + "format": 9551, + "ĠMET": 9552, + "Ġsentence": 9553, + "Ġbp": 9554, + "ĠGW": 9555, + "Ġrecording": 9556, + "Ġple": 9557, + "totic": 9558, + "Ġ": 9559, + "Ġranged": 9560, + "ention": 9561, + "obacteria": 9562, + "ceptions": 9563, + "ĠImport": 9564, + "dynamic": 9565, + "porary": 9566, + "Given": 9567, + "Ġturbulence": 9568, + "Ġgram": 9569, + "Ġequally": 9570, + "cd": 9571, + "ĠOs": 9572, + "Ġturns": 9573, + "Ġdetecting": 9574, + "atio": 9575, + "generate": 9576, + "grade": 9577, + "Ġcirculation": 9578, + "Ġmanufacturer": 9579, + "La": 9580, + "ĠHilbert": 9581, + "Ts": 9582, + "integr": 9583, + "Ġbelongs": 9584, + "ĠInternet": 9585, + "angl": 9586, + "ĠâĬ¥": 9587, + "ĠDrosophila": 9588, + "uclidean": 9589, + "tan": 9590, + "Ġextends": 9591, + "Ġexpanded": 9592, + "illin": 9593, + "square": 9594, + "ysacchar": 9595, + "Ġquantify": 9596, + "Ġpulses": 9597, + "Ġvesic": 9598, + "ĠNK": 9599, + "orescence": 9600, + "ĠPhosph": 9601, + "Ġvision": 9602, + "ĠHuang": 9603, + "ĠResponse": 9604, + "house": 9605, + "ears": 9606, + "Ġeg": 9607, + "Ġaccepted": 9608, + "ĠTM": 9609, + "ametric": 9610, + "Ġpathological": 9611, + "Ġrecruitment": 9612, + "ATA": 9613, + "Ġfigures": 9614, + "ĠPress": 9615, + "Ġaligned": 9616, + "Ġpostoperative": 9617, + "ĠMeV": 9618, + "Ġconsiderably": 9619, + "Ġconformal": 9620, + "ĠIsland": 9621, + "number": 9622, + "Ġautomatic": 9623, + "Ġsplic": 9624, + "Ġcytos": 9625, + "Ġdescrip": 9626, + "ĠSant": 9627, + "lies": 9628, + "uity": 9629, + "itone": 9630, + "ECT": 9631, + "ĠBon": 9632, + "Ġdisapp": 9633, + "board": 9634, + "orrh": 9635, + "Ġcalculating": 9636, + "nee": 9637, + "ĠMeas": 9638, + "Ġgenomes": 9639, + "Ġphotoc": 9640, + "Ġreadily": 9641, + "ovine": 9642, + "ĠDev": 9643, + "Ġsatur": 9644, + "Ġkinds": 9645, + "ĠPK": 9646, + "Ġrod": 9647, + "Ġjunction": 9648, + "ĠHA": 9649, + "Ġdesigns": 9650, + "hn": 9651, + "Ġordering": 9652, + "Ġcosmological": 9653, + "Ġpilot": 9654, + "Ġcolorectal": 9655, + "ĠLondon": 9656, + "ĠDirac": 9657, + "Cont": 9658, + "ĠWind": 9659, + "ĠTre": 9660, + "idin": 9661, + "ĠïĢ«": 9662, + "iltration": 9663, + "Moreover": 9664, + "Ġretention": 9665, + "timately": 9666, + "hydrogen": 9667, + "del": 9668, + "bolic": 9669, + "ĠQuanti": 9670, + "period": 9671, + "Ġretrieval": 9672, + "atase": 9673, + "endicular": 9674, + "ulties": 9675, + "RS": 9676, + "NH": 9677, + "Ġinformed": 9678, + "Ġfiltered": 9679, + "membrane": 9680, + "Ġstiffness": 9681, + "ĠOcean": 9682, + "ĠSY": 9683, + "Ġlot": 9684, + "ĠFigs": 9685, + "Ġansw": 9686, + "ĠEngland": 9687, + "ĠAtlantic": 9688, + "processing": 9689, + "Ġdogs": 9690, + "Ġlie": 9691, + "Ġunion": 9692, + "ĠTan": 9693, + "Ġhalo": 9694, + "Ġcontinuously": 9695, + "Bu": 9696, + "AMP": 9697, + "ĠApp": 9698, + "Ġmoisture": 9699, + "Ġthyroid": 9700, + "Ġaccompanied": 9701, + "Ġfold": 9702, + "Ġoriginally": 9703, + "Ġspan": 9704, + "ĠFA": 9705, + "connected": 9706, + "Ġrecurs": 9707, + "vian": 9708, + "ĠEquations": 9709, + "ena": 9710, + "arcinoma": 9711, + "....": 9712, + "Ġdiscrep": 9713, + "UH": 9714, + "о": 9715, + "anger": 9716, + "Ġmonitored": 9717, + "Ġinfluenza": 9718, + "Ġsure": 9719, + "black": 9720, + "oe": 9721, + "Ġalloc": 9722, + "Ġhabitat": 9723, + "ophenyl": 9724, + "Ġventricular": 9725, + "Ġpolicies": 9726, + "amate": 9727, + "Ġreporting": 9728, + "Ġsoluble": 9729, + "================": 9730, + "Ġdipole": 9731, + "Ġirreducible": 9732, + "ĠPrec": 9733, + "acetyl": 9734, + "Ġthread": 9735, + "ĠApproxim": 9736, + "Ġmapped": 9737, + "ipro": 9738, + "Ġtropical": 9739, + "Sch": 9740, + "ĠANOVA": 9741, + "Ġlanguages": 9742, + "icine": 9743, + "ĠFamily": 9744, + "functions": 9745, + "EF": 9746, + "Ġnutrient": 9747, + "Ġanalyzing": 9748, + "inescence": 9749, + "Ġthromb": 9750, + "Ġkit": 9751, + "Ġmammalian": 9752, + "optotic": 9753, + "Ġequipped": 9754, + "ona": 9755, + "Ġque": 9756, + "Ġcame": 9757, + "Ġsimplified": 9758, + "Ġdecays": 9759, + "Ġpassive": 9760, + "Ġdeletion": 9761, + "Ġobtaining": 9762, + "Ġmixtures": 9763, + "Ġprimers": 9764, + "ĠPsy": 9765, + "osc": 9766, + "oment": 9767, + "Ġchloride": 9768, + "ĠPaul": 9769, + "start": 9770, + "intestinal": 9771, + "helium": 9772, + "arth": 9773, + "odot": 9774, + "Ġfits": 9775, + "Ġsquares": 9776, + "ĠCardi": 9777, + "aka": 9778, + "ributed": 9779, + "Ġinequalities": 9780, + "omething": 9781, + "hedral": 9782, + "ĠFuture": 9783, + "Ġgli": 9784, + "Ġmetallic": 9785, + "Ġfacilities": 9786, + "Ġobst": 9787, + "possible": 9788, + "Ġzones": 9789, + "ucid": 9790, + "Ġdrift": 9791, + "depend": 9792, + "valued": 9793, + "Ġnons": 9794, + "Ġworldwide": 9795, + "Ġtrust": 9796, + "Ġsole": 9797, + "ĠLevel": 9798, + "ĠSha": 9799, + "Ġregardless": 9800, + "Ġspectrometry": 9801, + "ductor": 9802, + "leuk": 9803, + "Ġskills": 9804, + "Ġincorporated": 9805, + "Ġlearned": 9806, + "Ġure": 9807, + "Ġextinc": 9808, + "ODU": 9809, + "Ġgrains": 9810, + "atern": 9811, + "ĠIndex": 9812, + "comput": 9813, + "ua": 9814, + "Ġcontamination": 9815, + "ĠAff": 9816, + "uning": 9817, + "Ġasymmetric": 9818, + "Ġopening": 9819, + "Ġbat": 9820, + "Ġagree": 9821, + "ITY": 9822, + "ĠChanges": 9823, + "organic": 9824, + "ĠRay": 9825, + "ĠHand": 9826, + "ni": 9827, + "inic": 9828, + "Ġrisks": 9829, + "Ġstock": 9830, + "Ġneck": 9831, + "Ġvolumes": 9832, + "ĠPrac": 9833, + "Ġincreasingly": 9834, + "Sc": 9835, + "oses": 9836, + "GFP": 9837, + "Ġassignment": 9838, + "ĠFed": 9839, + "ospit": 9840, + "Ġoverexpression": 9841, + "Ġmaster": 9842, + "Ġopt": 9843, + "iler": 9844, + "invariant": 9845, + "Ġconverges": 9846, + "Similar": 9847, + "ny": 9848, + "Ġstore": 9849, + "Ġelevation": 9850, + "Ġcoal": 9851, + "het": 9852, + "item": 9853, + "PLC": 9854, + "ohist": 9855, + "Gen": 9856, + "ĠChem": 9857, + "ĠCost": 9858, + "pair": 9859, + "Ġnumerically": 9860, + "Ġpreference": 9861, + "ĠNucle": 9862, + "ĠBD": 9863, + "TI": 9864, + "ĠHyp": 9865, + "roy": 9866, + "Te": 9867, + "ĠFin": 9868, + "Ġclaims": 9869, + "ibilities": 9870, + "Ġlarvae": 9871, + "ima": 9872, + "embly": 9873, + "Ġcit": 9874, + "LL": 9875, + "Ġsilica": 9876, + "ĠVI": 9877, + "Ġreaching": 9878, + "Of": 9879, + "ĠAustralian": 9880, + "tub": 9881, + "world": 9882, + "oni": 9883, + "ĠFP": 9884, + "Ġbriefly": 9885, + "ĠDescription": 9886, + "ζ": 9887, + "charg": 9888, + "Ġcis": 9889, + "ĠCat": 9890, + "Ġrecip": 9891, + "Ġemergency": 9892, + "Ġstrand": 9893, + "Ġrealized": 9894, + "posing": 9895, + "otope": 9896, + "Ġmaintaining": 9897, + "ĠChrist": 9898, + "Ġcreating": 9899, + "Ġembryos": 9900, + "Ġskeletal": 9901, + "Ġages": 9902, + "represent": 9903, + "Cr": 9904, + "Ġestimating": 9905, + "Ġrear": 9906, + "ĠYu": 9907, + "ĠPi": 9908, + "mg": 9909, + "Ġfloat": 9910, + "ĠRoy": 9911, + "pus": 9912, + "Ġchick": 9913, + "Ġmicrobiota": 9914, + "vasive": 9915, + "ĠBern": 9916, + "ĠPattern": 9917, + "lines": 9918, + "Ġflood": 9919, + "ĠLou": 9920, + "ilitary": 9921, + "rosion": 9922, + "Ġsurveys": 9923, + "FI": 9924, + "iae": 9925, + "Ġsearc": 9926, + "mol": 9927, + "Ġtitle": 9928, + "ĠMachine": 9929, + "Ġcircuits": 9930, + "ĠNumber": 9931, + "zi": 9932, + "ĠBMI": 9933, + "Ġautomated": 9934, + "plicate": 9935, + "ĠLPS": 9936, + "Ġelectrochemical": 9937, + "Ġwebsite": 9938, + "Ġanisotropy": 9939, + "Ġrings": 9940, + "Ġinnov": 9941, + "bits": 9942, + "win": 9943, + "ĠNAD": 9944, + "According": 9945, + "ĠConn": 9946, + "ureus": 9947, + "ĠFeature": 9948, + "ĠInstead": 9949, + "Comp": 9950, + "itudes": 9951, + "Mo": 9952, + "Ġscope": 9953, + "tification": 9954, + "ĠIS": 9955, + "ĠNeut": 9956, + "Ġregulating": 9957, + "coding": 9958, + "Ġrows": 9959, + "hl": 9960, + "ĠKn": 9961, + "istor": 9962, + "ampionship": 9963, + "Ġprominent": 9964, + "Ġrs": 9965, + "umatic": 9966, + "Am": 9967, + "Ġdifferentially": 9968, + "ugin": 9969, + "Ġadvance": 9970, + "phys": 9971, + "Ġsharing": 9972, + "Ġart": 9973, + "vacy": 9974, + "titions": 9975, + "Ġstyle": 9976, + "Figures": 9977, + "Ġglu": 9978, + "Ġvaccination": 9979, + "ĠOptical": 9980, + "fluid": 9981, + "ĠFre": 9982, + "Ġgradients": 9983, + "ophyl": 9984, + "ĠPubl": 9985, + "Ġaccretion": 9986, + "Ġâ̲â̲": 9987, + "ressing": 9988, + "Ġtransmitted": 9989, + "Ġnervous": 9990, + "umar": 9991, + "Ġreviews": 9992, + "Ġgenotypes": 9993, + "lower": 9994, + "ĠEV": 9995, + "Ġcontract": 9996, + "atibility": 9997, + "Ġchildhood": 9998, + "Ġonc": 9999, + "Ġbiofil": 10000, + "Ġautophagy": 10001, + "Ġadsorb": 10002, + "ĠSupport": 10003, + "Ġligands": 10004, + "power": 10005, + "rectional": 10006, + "ĠRap": 10007, + "similar": 10008, + "Ġinfarc": 10009, + "Ġelectroly": 10010, + "Ġincome": 10011, + "arity": 10012, + "ĠAv": 10013, + "eric": 10014, + "Ġclinically": 10015, + "unch": 10016, + "Ġattribute": 10017, + "Ġcommand": 10018, + "ributions": 10019, + "Ġglyc": 10020, + "Ġtranscripts": 10021, + "ograms": 10022, + "Ġassessing": 10023, + "FO": 10024, + "scriptstyle": 10025, + "ji": 10026, + "rick": 10027, + "environment": 10028, + "Ġlaws": 10029, + "Ġnormally": 10030, + "Ġdepletion": 10031, + "ĠRO": 10032, + "Ġencoded": 10033, + "hma": 10034, + "Ġbranches": 10035, + "Ġargs": 10036, + "ounger": 10037, + "orge": 10038, + "umps": 10039, + "Ġviewed": 10040, + "Ġultr": 10041, + "RR": 10042, + "ulsion": 10043, + "ĠHor": 10044, + "Ġfro": 10045, + "ĠMeasurement": 10046, + "xx": 10047, + "erman": 10048, + "ĠOnce": 10049, + "Ġoriented": 10050, + "ĠPoint": 10051, + "Ġtown": 10052, + "Ġformulas": 10053, + "SY": 10054, + "ĠAM": 10055, + "Ġconsiderations": 10056, + "ĠTC": 10057, + "ĠKit": 10058, + "Ġactin": 10059, + "Ġplasmid": 10060, + "Ġhistorical": 10061, + "Ġdye": 10062, + "Ġheur": 10063, + "ĠLeague": 10064, + "ĠMad": 10065, + "Ġgraft": 10066, + "Ġsilver": 10067, + "Over": 10068, + "ĠCos": 10069, + "ographical": 10070, + "Ġprecursor": 10071, + "rus": 10072, + "Ġregarded": 10073, + "ĠHam": 10074, + "functional": 10075, + "iveness": 10076, + "fficiency": 10077, + "igene": 10078, + "ocol": 10079, + "Ġcumulative": 10080, + "Ġseasonal": 10081, + "Ġmu": 10082, + "ĠBan": 10083, + "omycin": 10084, + "Ġbool": 10085, + "ĠMag": 10086, + "ĠAnal": 10087, + "entia": 10088, + "aign": 10089, + "Ġfootball": 10090, + "acting": 10091, + "Ġreturns": 10092, + "ĠTom": 10093, + "shaped": 10094, + "itance": 10095, + "ĠExperiment": 10096, + "ĠOS": 10097, + "Ġabsent": 10098, + "ranial": 10099, + "Ġtherapies": 10100, + "Op": 10101, + "ounced": 10102, + "ATE": 10103, + "Value": 10104, + "green": 10105, + "Ġvegetation": 10106, + "Ds": 10107, + "Ġincom": 10108, + "ç": 10109, + "Ġmarrow": 10110, + "ĠCouncil": 10111, + "Ġinvest": 10112, + "Ġclub": 10113, + "Trans": 10114, + "device": 10115, + "Ġvibration": 10116, + "ĠXu": 10117, + "////////": 10118, + "ĠHen": 10119, + "vier": 10120, + "Ġanalogous": 10121, + "Ġdelta": 10122, + "Ġsaline": 10123, + "Ġrequiring": 10124, + "Ġneuron": 10125, + "oo": 10126, + "ĠQuality": 10127, + "Ġteac": 10128, + "ĠEc": 10129, + "Li": 10130, + "Ġpublication": 10131, + "ĠPhysics": 10132, + "Ġppm": 10133, + "thase": 10134, + "Ġcreation": 10135, + "ĠAge": 10136, + "Ġbelonging": 10137, + "Ġionic": 10138, + "ĠSI": 10139, + "uating": 10140, + "endif": 10141, + "ĠCour": 10142, + "а": 10143, + "Ġdots": 10144, + "Ġeast": 10145, + "arcom": 10146, + "Ġâĩ": 10147, + "Ġrights": 10148, + "essions": 10149, + "Ġversions": 10150, + "ĠFree": 10151, + "ĠStress": 10152, + "Ġsediments": 10153, + "Ġmitig": 10154, + "Ġbow": 10155, + "ĠAct": 10156, + "ĠCarbon": 10157, + "there": 10158, + "teen": 10159, + "Ġphenotypes": 10160, + "Ġnearest": 10161, + "ĠPotential": 10162, + "Ġdeform": 10163, + "Ġreflects": 10164, + "Ġpartners": 10165, + "Ġanest": 10166, + "Ġadvers": 10167, + "ĠFactor": 10168, + "Ġconvenient": 10169, + "ulos": 10170, + "ĠPur": 10171, + "ĠMer": 10172, + "Ġflag": 10173, + "Ġtriang": 10174, + "Ġseeds": 10175, + "Ġfif": 10176, + "obil": 10177, + "ĠCK": 10178, + "mentioned": 10179, + "Ġvapor": 10180, + "ogue": 10181, + "Ġpredictor": 10182, + "Out": 10183, + "Ġcompletion": 10184, + "ĠSeg": 10185, + "Ġdiffuse": 10186, + "Ġraised": 10187, + "Ġcoordination": 10188, + "Ġsynaptic": 10189, + "ĠBor": 10190, + "ĠBol": 10191, + "Ġpolymerase": 10192, + "Ġwheat": 10193, + "Ġinsertion": 10194, + "Ġesc": 10195, + "ĠWal": 10196, + "Ġdistal": 10197, + "transferase": 10198, + "Ġinterfaces": 10199, + "Ġinsu": 10200, + "Ġpoorly": 10201, + "Ġaureus": 10202, + "Ġbenz": 10203, + "Ġuniverse": 10204, + "ĠInteraction": 10205, + "ĠFrame": 10206, + "ĠImaging": 10207, + "Ġexploration": 10208, + "ĠEngineering": 10209, + "ĠBesides": 10210, + "tia": 10211, + "Ġenum": 10212, + "anine": 10213, + "Ġtot": 10214, + "ĠEduc": 10215, + "Ġderivation": 10216, + "Array": 10217, + "yloid": 10218, + "ĠArch": 10219, + "isen": 10220, + "acity": 10221, + "akers": 10222, + "Ġsheet": 10223, + "ĠEst": 10224, + "Ġwear": 10225, + "Ġeryth": 10226, + "ECK": 10227, + "hematics": 10228, + "Ġarterial": 10229, + "criptstyle": 10230, + "scriptscriptstyle": 10231, + "echanical": 10232, + "Ġparticipation": 10233, + "cher": 10234, + "urance": 10235, + "ĠFR": 10236, + "ĠCV": 10237, + "Ġcomplementary": 10238, + "aine": 10239, + "empty": 10240, + "Ġdiges": 10241, + "Ġexponent": 10242, + "Ġsimulate": 10243, + "UE": 10244, + "Ġantibiotics": 10245, + "ĠUnivers": 10246, + "Ġpathology": 10247, + "thermal": 10248, + "pa": 10249, + "Ġstresses": 10250, + "ĠLaboratory": 10251, + "Node": 10252, + "Ġleave": 10253, + "ashing": 10254, + "Ġdiscre": 10255, + "Ġsuspension": 10256, + "reek": 10257, + "Ġscheduling": 10258, + "ĠDA": 10259, + "aryn": 10260, + "ĠNaCl": 10261, + "strain": 10262, + "STR": 10263, + "ĠCong": 10264, + "olf": 10265, + "Ġcalibr": 10266, + "ĠOptimal": 10267, + "Ġó": 10268, + "Gl": 10269, + "ĠRh": 10270, + "Ġdifficulties": 10271, + "Ġvessels": 10272, + "Ġasymmetry": 10273, + "Ġcoherence": 10274, + "ĠTaxonomy": 10275, + "Ġped": 10276, + "ĠHouse": 10277, + "titudes": 10278, + "ĠFar": 10279, + "OY": 10280, + "Ġconcentrated": 10281, + "Ġsignalling": 10282, + "Ġfungal": 10283, + "Ġconsistently": 10284, + "Ġenhances": 10285, + "Ġforecast": 10286, + "Ġcubic": 10287, + "ĠEP": 10288, + "Ġparticipate": 10289, + "ĠPlant": 10290, + "risk": 10291, + "And": 10292, + "adic": 10293, + "oflu": 10294, + "Ġsperm": 10295, + "ĠChris": 10296, + "ND": 10297, + "colon": 10298, + "Ġfaces": 10299, + "Ġtuberculosis": 10300, + "rystal": 10301, + "floor": 10302, + "ups": 10303, + "Ġgray": 10304, + "ĠPublic": 10305, + "tensor": 10306, + "Ġrigid": 10307, + "Ġeastern": 10308, + "ĠItaly": 10309, + "Ġsignatures": 10310, + "Ġshallow": 10311, + "ón": 10312, + "ĠCe": 10313, + "Ġprojects": 10314, + "Ġrouting": 10315, + "Ġpredicts": 10316, + "ĠFeatures": 10317, + "ĠDistrict": 10318, + "Ġcarrying": 10319, + "ĉĠĠĠĠ": 10320, + "ĠTO": 10321, + "HM": 10322, + "dings": 10323, + "Ġrenormal": 10324, + "Ġbring": 10325, + "pin": 10326, + "aled": 10327, + "Ġclouds": 10328, + "names": 10329, + "oxin": 10330, + "Ġperpendicular": 10331, + "WT": 10332, + "ership": 10333, + "Ġrecon": 10334, + "Ġworked": 10335, + "ĠâĢ«": 10336, + "rastructure": 10337, + "Ġpointed": 10338, + "EV": 10339, + "ĠTaylor": 10340, + "Ġhepatitis": 10341, + "Ġorbits": 10342, + "ĠFactors": 10343, + "cellular": 10344, + "Ġfocal": 10345, + "Ġboost": 10346, + "Ġmicrowave": 10347, + "ĠProject": 10348, + "BF": 10349, + "Ġpolitical": 10350, + "Ġsupplemented": 10351, + "Ġillustrates": 10352, + "Ġideas": 10353, + "ĠDrug": 10354, + "obile": 10355, + "ĠHO": 10356, + "Ġrobustness": 10357, + "rosine": 10358, + "ĠNormal": 10359, + "Ġstimulated": 10360, + "Ġimpedance": 10361, + "fortunately": 10362, + "zyme": 10363, + "Ġbarriers": 10364, + "actory": 10365, + "learly": 10366, + "Ġpreprint": 10367, + "sensitive": 10368, + "Ġturbulent": 10369, + "thing": 10370, + "Ġboard": 10371, + "Ġpit": 10372, + "Ġintegrity": 10373, + "Ġrotating": 10374, + "uda": 10375, + "Ġventi": 10376, + "ĠSNPs": 10377, + "Ġcorrespondence": 10378, + "Ġvisualization": 10379, + "avail": 10380, + "Ġbeams": 10381, + "ĠContinu": 10382, + "Ġpersistent": 10383, + "Ġbath": 10384, + "ĠmiRNAs": 10385, + "Ġcustom": 10386, + "Ġordinary": 10387, + "Ġgenerators": 10388, + "Ġbridge": 10389, + "Ġdomin": 10390, + "amy": 10391, + "Ġlooking": 10392, + "table": 10393, + "False": 10394, + "Ġsoils": 10395, + "Ġmatches": 10396, + "Ġprogressive": 10397, + "states": 10398, + "ĠShort": 10399, + "Ġcores": 10400, + "Ġintroducing": 10401, + "Ġarrest": 10402, + "Ġtexture": 10403, + "Ġdorsal": 10404, + "Ġdrain": 10405, + "izoph": 10406, + "ĠQue": 10407, + "ñ": 10408, + "disc": 10409, + "Index": 10410, + "Ġextensively": 10411, + "Ġplasticity": 10412, + "Ġreally": 10413, + "ĠError": 10414, + "Ġsugges": 10415, + "Ġconsequently": 10416, + "Ġperforms": 10417, + "likely": 10418, + "ivered": 10419, + "Ġthermodynamic": 10420, + "Ġker": 10421, + "Ġacetate": 10422, + "Ġgets": 10423, + "leqslant": 10424, + "Ġpredictors": 10425, + "ĠSwed": 10426, + "nan": 10427, + "heter": 10428, + "Ġanomaly": 10429, + "Ġoperational": 10430, + "Ġretrospective": 10431, + "Ġtends": 10432, + "aden": 10433, + "Ġborder": 10434, + "Ġmethanol": 10435, + "ĠEnter": 10436, + "Ġcollapse": 10437, + "Ġpurchased": 10438, + "Da": 10439, + "ĠHT": 10440, + "Ġfulf": 10441, + "Ġcrust": 10442, + "stone": 10443, + "Ġpenal": 10444, + "Ġtunn": 10445, + "ĠTemperature": 10446, + "Ġpotent": 10447, + "lecule": 10448, + "Ġcovers": 10449, + "Ġbattery": 10450, + "Ġbeg": 10451, + "Ġorgans": 10452, + "ĠThomas": 10453, + "Ġsolub": 10454, + "ocrine": 10455, + "ĠSpin": 10456, + "Ġinterests": 10457, + "doc": 10458, + "Ġundergoing": 10459, + "ui": 10460, + "Ġinherent": 10461, + "Ġintegrals": 10462, + "irable": 10463, + "ashi": 10464, + "Ġregeneration": 10465, + "Ġinflation": 10466, + "manif": 10467, + "ĠRecognition": 10468, + "Ġdisplays": 10469, + "Another": 10470, + "Ġcontamin": 10471, + "junction": 10472, + "Ġcopies": 10473, + "MRI": 10474, + "Ġvehicles": 10475, + "Get": 10476, + "Ġperhaps": 10477, + "Ġwest": 10478, + "Ġintensive": 10479, + "Ġsomething": 10480, + "Ġhypoxia": 10481, + "Ġcouplings": 10482, + "Ġfeasibility": 10483, + "azine": 10484, + "unic": 10485, + "iner": 10486, + "ĠIT": 10487, + "Ġdistrict": 10488, + "ĠJames": 10489, + "eval": 10490, + "Ġplacebo": 10491, + "aque": 10492, + "Ġelucid": 10493, + "ĠJacob": 10494, + "Ġcounting": 10495, + "Ġflexibility": 10496, + "Ġperman": 10497, + "Ġadvances": 10498, + "ulph": 10499, + "Ġentanglement": 10500, + "Ġintegers": 10501, + "Ġfocusing": 10502, + "kov": 10503, + "Ġhospit": 10504, + "Ġapplies": 10505, + "Ġcot": 10506, + "Sm": 10507, + "assium": 10508, + "Ġdocumented": 10509, + "Ġloaded": 10510, + "Ġrely": 10511, + "Ġinfectious": 10512, + "Ġprobes": 10513, + "Ġhighlighted": 10514, + "Ġpediatric": 10515, + "Ġweather": 10516, + "Ġmanual": 10517, + "Ġcation": 10518, + "Ġinterpolation": 10519, + "ĠStep": 10520, + "ĠKal": 10521, + "DH": 10522, + "db": 10523, + "izophren": 10524, + "ader": 10525, + "carb": 10526, + "Ġagon": 10527, + "orphous": 10528, + "tors": 10529, + "atz": 10530, + "Ġbif": 10531, + "Ġcharges": 10532, + "ĠAgain": 10533, + "Ġbron": 10534, + "ĠGover": 10535, + "Ġmining": 10536, + "aver": 10537, + "Ġearthqu": 10538, + "Ġviews": 10539, + "Ġscene": 10540, + "parameters": 10541, + "Ġbroken": 10542, + "Test": 10543, + "ĠSum": 10544, + "ĠProm": 10545, + "ÎĽ": 10546, + "Ġcutoff": 10547, + "Ġbirds": 10548, + "Ġarising": 10549, + "ĠAI": 10550, + "ĠCE": 10551, + "Ġpronounced": 10552, + "aspase": 10553, + "Ġintended": 10554, + "Ġaffine": 10555, + "Ġurine": 10556, + "Ġbelieved": 10557, + "ĠPrimary": 10558, + "ĠConf": 10559, + "Ġabdominal": 10560, + "spin": 10561, + "uniform": 10562, + "ĠStochastic": 10563, + "ĠProv": 10564, + "ĠmiRNA": 10565, + "ĠBell": 10566, + "BO": 10567, + "ĠSoftware": 10568, + "ĠTs": 10569, + "utri": 10570, + "icking": 10571, + "ien": 10572, + "Ġmicros": 10573, + "ĠNR": 10574, + "Ġleukemia": 10575, + "Ġsupernat": 10576, + "family": 10577, + "Ġalloys": 10578, + "ĠPET": 10579, + "ĠAbs": 10580, + "ĠGA": 10581, + "ĠQuantitative": 10582, + "Lo": 10583, + "Ġisland": 10584, + "second": 10585, + "pectives": 10586, + "Ġlatency": 10587, + "angi": 10588, + "Ġflight": 10589, + "ĠEuclidean": 10590, + "emy": 10591, + "ĠBlood": 10592, + "leukin": 10593, + "LT": 10594, + "enh": 10595, + "Ġswe": 10596, + "Ġunitary": 10597, + "ĠRepublic": 10598, + "Ġstructured": 10599, + "ĠSen": 10600, + "Mn": 10601, + "centric": 10602, + "Ġtransgenic": 10603, + "Ġhelpful": 10604, + "pyx": 10605, + "Ġhomeostasis": 10606, + "Na": 10607, + "Ġpassed": 10608, + "Ġeyes": 10609, + "Ġabstract": 10610, + "ulse": 10611, + "Ġmirror": 10612, + "Ġregulator": 10613, + "Ġmurine": 10614, + "loaded": 10615, + "Ġmodular": 10616, + "Ġlandscape": 10617, + "icks": 10618, + "Ġsnow": 10619, + "Ġbovine": 10620, + "elli": 10621, + "Ġdatabases": 10622, + "Ġoutbreak": 10623, + "larg": 10624, + "ĠRun": 10625, + "BE": 10626, + "Ġsurprising": 10627, + "Ġacceptable": 10628, + "Ġrotational": 10629, + "pg": 10630, + "FE": 10631, + "wik": 10632, + "Ġyounger": 10633, + "ashion": 10634, + "Ġmicroscopic": 10635, + "regation": 10636, + "Ġfibr": 10637, + "ĠPlan": 10638, + "Ġhapl": 10639, + "Ġmanifolds": 10640, + "Ġoutper": 10641, + "Ġchoosing": 10642, + "eper": 10643, + "ĠkeV": 10644, + "ĠTyp": 10645, + "pread": 10646, + "ntz": 10647, + "ĠReport": 10648, + "ĠMatrix": 10649, + "Ġintu": 10650, + "Ġproperly": 10651, + "ogly": 10652, + "oscopic": 10653, + "ĠAMP": 10654, + "ĠBM": 10655, + "Ġelementary": 10656, + "keleton": 10657, + "Ġsynthase": 10658, + "Ġionization": 10659, + "bes": 10660, + "ophage": 10661, + "duces": 10662, + "acco": 10663, + "Ġprotect": 10664, + "ĠCoul": 10665, + "Ġspent": 10666, + "Ġmand": 10667, + "Ġhind": 10668, + "fluor": 10669, + "ĠGood": 10670, + "Ġdoing": 10671, + "Object": 10672, + "ducts": 10673, + "oyl": 10674, + "chiatric": 10675, + "Ġov": 10676, + "cel": 10677, + "Ġbases": 10678, + "Ġmitochondria": 10679, + "pted": 10680, + "artz": 10681, + "Ġbrown": 10682, + "Ġequals": 10683, + "tible": 10684, + "Ġopportunity": 10685, + "azol": 10686, + "Ġofficial": 10687, + "ailed": 10688, + "Ġurinary": 10689, + "ĠHan": 10690, + "Be": 10691, + "result": 10692, + "units": 10693, + "Ġbad": 10694, + "ĠString": 10695, + "izable": 10696, + "condition": 10697, + "ĠElectron": 10698, + "immune": 10699, + "ĠME": 10700, + "hao": 10701, + "Σ": 10702, + "ĠMAT": 10703, + "Ġadopt": 10704, + "Ġelic": 10705, + "Ġshr": 10706, + "Ġproximal": 10707, + "FD": 10708, + "ĠSS": 10709, + "Ġentirely": 10710, + "esium": 10711, + "ĠEEG": 10712, + "Ġpaired": 10713, + "ĠTP": 10714, + "ĠDO": 10715, + "NAL": 10716, + "idespread": 10717, + "Ġmoves": 10718, + "site": 10719, + "Ġrain": 10720, + "Ġlap": 10721, + "ĠFu": 10722, + "ĠMeta": 10723, + "ircraft": 10724, + "Ġmagnetization": 10725, + "operation": 10726, + "Ġprost": 10727, + "Step": 10728, + "Ġsubgroups": 10729, + "ĠSouthern": 10730, + "Ġathe": 10731, + "luor": 10732, + "ĠTaxonomic": 10733, + "ĠEinstein": 10734, + "Ġrace": 10735, + "ĠKen": 10736, + "Ġattempts": 10737, + "Ġcosmic": 10738, + "ĠDop": 10739, + "Ġfixation": 10740, + "Ġremoving": 10741, + "BT": 10742, + "Ġlimb": 10743, + "Ġalign": 10744, + "Ġdried": 10745, + "du": 10746, + "Ġputative": 10747, + "uccess": 10748, + "pert": 10749, + "Ġslowly": 10750, + "also": 10751, + "olip": 10752, + "Ġclient": 10753, + "Ġbasin": 10754, + "Ġsusceptible": 10755, + "Ġcoming": 10756, + "nson": 10757, + "ĠNGC": 10758, + "assert": 10759, + "Ġtensile": 10760, + "Ġarises": 10761, + "cutaneous": 10762, + "Ġcaro": 10763, + "Bi": 10764, + "Ġdiscussions": 10765, + "Ġabnormalities": 10766, + "Ġpollution": 10767, + "ĠAx": 10768, + "Ġloads": 10769, + "Do": 10770, + "iao": 10771, + "Ġmedication": 10772, + "Ġintact": 10773, + "ĠCX": 10774, + "Ġbreeding": 10775, + "ĠUnion": 10776, + "ĠBat": 10777, + "ĠParticipants": 10778, + "ĠRegulation": 10779, + "Ġcontradiction": 10780, + "Ġintensities": 10781, + "encephal": 10782, + "rile": 10783, + "ĠTLR": 10784, + "Ġredund": 10785, + "Ġpersons": 10786, + "ĠArc": 10787, + "solid": 10788, + "law": 10789, + "Results": 10790, + "ilic": 10791, + "zone": 10792, + "ocytosis": 10793, + "Ġtriangle": 10794, + "STM": 10795, + "ĠVirus": 10796, + "Ġaid": 10797, + "soft": 10798, + "Ġsoon": 10799, + "expected": 10800, + "Ġanch": 10801, + "ĠMu": 10802, + "ĠSr": 10803, + "ĠLO": 10804, + "Ġcry": 10805, + "Ġupstream": 10806, + "oxic": 10807, + "mathit": 10808, + "ĠKle": 10809, + "Ġisotropic": 10810, + "Ġspatially": 10811, + "ĠHard": 10812, + "Ġextr": 10813, + "bas": 10814, + "eor": 10815, + "ivil": 10816, + "yan": 10817, + "Ġshifted": 10818, + "Ġbiopsy": 10819, + "Ġfeel": 10820, + "glut": 10821, + "Size": 10822, + "Ġerg": 10823, + "ĠTer": 10824, + "Ġdeaths": 10825, + "borne": 10826, + "Ġrelativistic": 10827, + "ĠVEGF": 10828, + "atab": 10829, + "spring": 10830, + "restim": 10831, + "ĠSearch": 10832, + "yphenyl": 10833, + "ecal": 10834, + "urc": 10835, + "Ġlamin": 10836, + "Ġserial": 10837, + "las": 10838, + "ĠProduction": 10839, + "Ġsocio": 10840, + "Ġmodify": 10841, + "ĠService": 10842, + "Ġbary": 10843, + "Ġradiative": 10844, + "bigl": 10845, + "Ġparadigm": 10846, + "patient": 10847, + "Ġspp": 10848, + "phone": 10849, + "Ġî": 10850, + "Ġrocks": 10851, + "ĠMartin": 10852, + "mn": 10853, + "Ġfluids": 10854, + "ĠINTR": 10855, + "ods": 10856, + "Ġdivis": 10857, + "Consider": 10858, + "component": 10859, + "Ġanomalies": 10860, + "Ġknee": 10861, + "ĠRelationship": 10862, + "aud": 10863, + "Ġovernight": 10864, + "Ġrainf": 10865, + "Ġannealing": 10866, + "Ġtreating": 10867, + "Ġcoarse": 10868, + "Model": 10869, + "Ġpose": 10870, + "Ġoccas": 10871, + "ĠWilliam": 10872, + "oor": 10873, + "Ġadjustment": 10874, + "ĠFunctions": 10875, + "imeter": 10876, + "Ġdetectors": 10877, + "Ġinstitutional": 10878, + "Ġthroughput": 10879, + "ividual": 10880, + "Ġentities": 10881, + "Ġprolonged": 10882, + "Ġship": 10883, + "Ġpreserved": 10884, + "ODUCTION": 10885, + "Ġlogistic": 10886, + "ĠPrediction": 10887, + "tized": 10888, + "ĠOrig": 10889, + "ĠHem": 10890, + "onomous": 10891, + "################": 10892, + "ĠGeneration": 10893, + "bottom": 10894, + "ĠKnow": 10895, + "clinical": 10896, + "Ġtrauma": 10897, + "Ġiterative": 10898, + "Ġfacility": 10899, + "ront": 10900, + "ĠBus": 10901, + "Ġretinal": 10902, + "Ġconduction": 10903, + "Ġchecked": 10904, + "Ġcalls": 10905, + "ologists": 10906, + "CON": 10907, + "ĠSciences": 10908, + "Ġnonzero": 10909, + "Ġbrack": 10910, + "Ġmelting": 10911, + "Ġasc": 10912, + "Ġmention": 10913, + "ĠBL": 10914, + "Ġverification": 10915, + "ukary": 10916, + "ĠSpatial": 10917, + "ĠGram": 10918, + "Ġplaces": 10919, + "Ġnecrosis": 10920, + "ĠChildren": 10921, + "Ġdelivered": 10922, + "Ġresection": 10923, + "Ġdeterministic": 10924, + "Section": 10925, + "Ġmultim": 10926, + "DF": 10927, + "Ġhypotheses": 10928, + "Ġraise": 10929, + "Ġseismic": 10930, + "Ġlam": 10931, + "ĠHCC": 10932, + "bigr": 10933, + "Ġhealing": 10934, + "isy": 10935, + "Ġoptimize": 10936, + "obacterium": 10937, + "edy": 10938, + "Ġtruth": 10939, + "Ġspacetime": 10940, + "Ġchromatin": 10941, + "Ġdomestic": 10942, + "Ġrecru": 10943, + "ĠJose": 10944, + "ĠThermal": 10945, + "Ġenvelope": 10946, + "vable": 10947, + "Ġincons": 10948, + "Ġnod": 10949, + "и": 10950, + "Ġcontributing": 10951, + "Ġguarantee": 10952, + "ĠPhen": 10953, + "Ġrab": 10954, + "Man": 10955, + "Ġsurveillance": 10956, + "Ġthings": 10957, + "Ġprev": 10958, + "ĠNonlinear": 10959, + "Ġgaps": 10960, + "aya": 10961, + "ĠCri": 10962, + "Ġcrystalline": 10963, + "strict": 10964, + "Ġcomputations": 10965, + "Ġunable": 10966, + "habil": 10967, + "umina": 10968, + "Ġpromoting": 10969, + "egrad": 10970, + "Ġregister": 10971, + "Ġcrossing": 10972, + "ulators": 10973, + "ĠLanguage": 10974, + "ĠAA": 10975, + "Ġiner": 10976, + "ĠLV": 10977, + "osan": 10978, + "Ġcoastal": 10979, + "Ġbiod": 10980, + "ĠMOD": 10981, + "Ġneighbour": 10982, + "Ġpredominantly": 10983, + "ĠNewton": 10984, + "ĠStrateg": 10985, + "being": 10986, + "Ġì": 10987, + "Ġcapabilities": 10988, + "Ġunless": 10989, + "formal": 10990, + "Ġvessel": 10991, + "bmatrix": 10992, + "ESS": 10993, + "Ġrainfall": 10994, + "ã": 10995, + "Ġprepar": 10996, + "axial": 10997, + "Ġdental": 10998, + "ĠProte": 10999, + "Ġworse": 11000, + "doped": 11001, + "hentic": 11002, + "Ġvalidate": 11003, + "Zn": 11004, + "Ġspecification": 11005, + "si": 11006, + "ĠAng": 11007, + "Ġtubes": 11008, + "ulic": 11009, + "ĠAny": 11010, + "ĠMap": 11011, + "Ġfabricated": 11012, + "Ġforced": 11013, + "ĠWilson": 11014, + "olysis": 11015, + "ĠWave": 11016, + "ĠCast": 11017, + "Ġasthma": 11018, + "Ġperi": 11019, + "ĠCyt": 11020, + "asty": 11021, + "Ġsky": 11022, + "rupt": 11023, + "Dec": 11024, + "Ġmelanoma": 11025, + "PER": 11026, + "Ġcontinuity": 11027, + "Box": 11028, + "system": 11029, + "Ġnavig": 11030, + "Ġcirculating": 11031, + "Ġcolony": 11032, + "lesssim": 11033, + "adium": 11034, + "Ġtetra": 11035, + "Ġaccounts": 11036, + "Ġpresenting": 11037, + "ĠLik": 11038, + "Ġresis": 11039, + "Ġdamping": 11040, + "ĠGly": 11041, + "ĠNeuro": 11042, + "user": 11043, + "Ġcapital": 11044, + "urate": 11045, + "ĠMW": 11046, + "Ġcorrelates": 11047, + "ĠGib": 11048, + "Ġhappens": 11049, + "Ġgall": 11050, + "ĠWithin": 11051, + "Ġcombine": 11052, + "Ġsinus": 11053, + "ĠKin": 11054, + "********************************": 11055, + "Map": 11056, + "Ġmaturation": 11057, + "Ġblocking": 11058, + "ĠCloud": 11059, + "Ġcontacts": 11060, + "Ġsac": 11061, + "ALL": 11062, + "ĠRab": 11063, + "zz": 11064, + "utch": 11065, + "Ġcarriers": 11066, + "ĠSNR": 11067, + "erb": 11068, + "Ġprotected": 11069, + "racking": 11070, + "radient": 11071, + "Ġattractive": 11072, + "Ġlag": 11073, + "Ġopin": 11074, + "ĠGi": 11075, + "Ġdefense": 11076, + "Ġtuning": 11077, + "Ġelectroph": 11078, + "Ġgreatest": 11079, + "Ġreconstructed": 11080, + "ĠPopulation": 11081, + "MAP": 11082, + "Ġwrote": 11083, + "AND": 11084, + "economic": 11085, + "ĠMichael": 11086, + "ĠBlock": 11087, + "Ġvo": 11088, + "oprop": 11089, + "Ġprofiling": 11090, + "ootst": 11091, + "ĠAsian": 11092, + "Ġoscillation": 11093, + "ĠâĨIJ": 11094, + "UD": 11095, + "Ġsigned": 11096, + "ĠEuler": 11097, + "ĠComparative": 11098, + "ĠWhere": 11099, + "ĠJack": 11100, + "Ġpassing": 11101, + "Ġvillage": 11102, + "Ġau": 11103, + "ĠNorthern": 11104, + "essage": 11105, + "matic": 11106, + "Ġaffili": 11107, + "ĠFac": 11108, + "Ġoverlapping": 11109, + "shell": 11110, + "Ġobstac": 11111, + "Ġbecoming": 11112, + "entive": 11113, + "Ġeasier": 11114, + "initely": 11115, + "Ġcentered": 11116, + "Ġacademic": 11117, + "annels": 11118, + "Ġirregular": 11119, + "Ġprojections": 11120, + "Ġproposition": 11121, + "Ġdiscrimination": 11122, + "Ġremod": 11123, + "Ġshoot": 11124, + "month": 11125, + "essor": 11126, + "Ġdiffers": 11127, + "ĠTV": 11128, + "ĠZhou": 11129, + "Ġinher": 11130, + "Ġmachines": 11131, + "Ġmell": 11132, + "Ġconjugate": 11133, + "Ġcoc": 11134, + "una": 11135, + "anyl": 11136, + "Ġoffic": 11137, + "Ġopportunities": 11138, + "Ġvein": 11139, + "ĠCharacteristics": 11140, + "Ġpathogenic": 11141, + "OYSA": 11142, + "ĠParkinson": 11143, + "ĠGalactic": 11144, + "FFFA": 11145, + "yses": 11146, + "UHFFFA": 11147, + "UHFFFAOYSA": 11148, + "actin": 11149, + "Ġunus": 11150, + "hesia": 11151, + "aceu": 11152, + "adow": 11153, + "oside": 11154, + "Ġglycos": 11155, + "Ġdiluted": 11156, + "ĠSource": 11157, + "olated": 11158, + "armaceu": 11159, + "antom": 11160, + "Ġmusc": 11161, + "Ġaveraging": 11162, + "Ġvisit": 11163, + "Ġcatch": 11164, + "Ġsatisfaction": 11165, + "Ġvon": 11166, + "valid": 11167, + "Ġyielded": 11168, + "Ġpackets": 11169, + "Ġresonant": 11170, + "pret": 11171, + "ĠGFP": 11172, + "Ġcutting": 11173, + "Ġreplacing": 11174, + "aze": 11175, + "Pa": 11176, + "Ġtoday": 11177, + "Ġdecided": 11178, + "ilateral": 11179, + "imate": 11180, + "lings": 11181, + "ĠRobust": 11182, + "ĠAst": 11183, + "odynamics": 11184, + "Ġlacking": 11185, + "izophrenia": 11186, + "Ġcontraction": 11187, + "umann": 11188, + "ĠSample": 11189, + "Ġdiamond": 11190, + "method": 11191, + "TOR": 11192, + "Ġcomments": 11193, + "sey": 11194, + "Ġmanufacturing": 11195, + "ĠDa": 11196, + "NR": 11197, + "Ġoperated": 11198, + "rates": 11199, + "Ġextinction": 11200, + "uvant": 11201, + "ĠFinite": 11202, + "Ġlymphocytes": 11203, + "bro": 11204, + "omology": 11205, + "Ġinstruments": 11206, + "bec": 11207, + "ogle": 11208, + "Ġquoti": 11209, + "Ġhyperbolic": 11210, + "Ġtrim": 11211, + "Ġpap": 11212, + "aturated": 11213, + "haus": 11214, + "Ġsessions": 11215, + "Ġcampaign": 11216, + "Ġvarieties": 11217, + "Ġprojected": 11218, + "Ġrid": 11219, + "bone": 11220, + "Ġancest": 11221, + "ĠET": 11222, + "mail": 11223, + "ĠTransport": 11224, + "///": 11225, + "ĠAnn": 11226, + "Ġcompositions": 11227, + "ĠINTRODUCTION": 11228, + "ĠâĪĴâĨĴ": 11229, + "Ġwhenever": 11230, + "ĠLip": 11231, + "parts": 11232, + "Ġisomorphic": 11233, + "Ġsulfate": 11234, + "Ġhop": 11235, + "Ġgon": 11236, + "ĠObject": 11237, + "Ġpipeline": 11238, + "Ġma": 11239, + "ĠGas": 11240, + "Ġtendency": 11241, + "object": 11242, + "Ġparametric": 11243, + "ĠReturn": 11244, + "Ġdwar": 11245, + "Ġpressures": 11246, + "ĠBios": 11247, + "Ġmultiplication": 11248, + "Ġdimin": 11249, + "Ġcolors": 11250, + "ĠTrue": 11251, + "Max": 11252, + "ĠDepend": 11253, + "Ġpairwise": 11254, + "Ġlake": 11255, + "Ġhierarchy": 11256, + "Ġthresholds": 11257, + "ĠAdaptive": 11258, + "making": 11259, + "Ġcatalysts": 11260, + "ipal": 11261, + "Ġeggs": 11262, + "Ġwire": 11263, + "ophyll": 11264, + "ictor": 11265, + "labeled": 11266, + "Ġmuscles": 11267, + "ĠUnderstanding": 11268, + "Ġfibre": 11269, + "controlled": 11270, + "Ġinvariance": 11271, + "Ġcache": 11272, + "Ġboson": 11273, + "Ġnearby": 11274, + "ĠWomen": 11275, + "ĠInitial": 11276, + "Ġprobabilistic": 11277, + "Ġembryonic": 11278, + "ĠBetween": 11279, + "Ġconjecture": 11280, + "ienti": 11281, + "tx": 11282, + "gens": 11283, + "anck": 11284, + "Ġgir": 11285, + "ĠLower": 11286, + "Ġhospitals": 11287, + "bridge": 11288, + "Method": 11289, + "Ġtheta": 11290, + "ja": 11291, + "Ġconceptual": 11292, + "Ġcolle": 11293, + "ĠSaf": 11294, + "dic": 11295, + "Ġpet": 11296, + "Ġprimer": 11297, + "ĠOh": 11298, + "Ġuntreated": 11299, + "longrightarrow": 11300, + "Ġlicense": 11301, + "Ġhelps": 11302, + "Ġcleavage": 11303, + "Ġamplified": 11304, + "е": 11305, + "Ġaccessible": 11306, + "ĠSelection": 11307, + "ĠLorentz": 11308, + "Py": 11309, + "Ġpolarized": 11310, + "ĠSTAT": 11311, + "mitt": 11312, + "Up": 11313, + "Ġongoing": 11314, + "Ġneph": 11315, + "efficient": 11316, + "activ": 11317, + "ĠRR": 11318, + "Ġfunctioning": 11319, + "otin": 11320, + "Ġlists": 11321, + "Ġformalism": 11322, + "Ġoscillator": 11323, + "Ġgastrointestinal": 11324, + "ootstrap": 11325, + "ĠAsia": 11326, + "ĠDay": 11327, + "Ġcompeting": 11328, + "ivalent": 11329, + "Ġbladder": 11330, + "Ġhit": 11331, + "Ġapproximations": 11332, + "ĠEg": 11333, + "ĠClust": 11334, + "Ġrelies": 11335, + "NE": 11336, + "copro": 11337, + "Ġbank": 11338, + "Ġintegrating": 11339, + "ĠHear": 11340, + "Ġinitiated": 11341, + "acryl": 11342, + "ĠBH": 11343, + "racted": 11344, + "yc": 11345, + "ĠRa": 11346, + "Ġremarkable": 11347, + "ĠË": 11348, + "teness": 11349, + "Ġemploying": 11350, + "steine": 11351, + "Ġï£Ń": 11352, + "Ġtransfected": 11353, + "Ġinjuries": 11354, + "ĠBrief": 11355, + "Ġwidespread": 11356, + "ĠAK": 11357, + "IVE": 11358, + "Ġharm": 11359, + "Ġpole": 11360, + "Ġanisotropic": 11361, + "aten": 11362, + "gene": 11363, + "ivariate": 11364, + "Inter": 11365, + "ductors": 11366, + "Ġaccompl": 11367, + "oglobin": 11368, + "cong": 11369, + "Ġqueries": 11370, + "escope": 11371, + "ĠHop": 11372, + "Ġentity": 11373, + "Ġoffered": 11374, + "State": 11375, + "ĠExperiments": 11376, + "anner": 11377, + "ĠWood": 11378, + "arded": 11379, + "agon": 11380, + "Ġfibroblasts": 11381, + "Ġnanos": 11382, + "Ġperoxid": 11383, + "Ġevid": 11384, + "Ġ": 11385, + "Ġretained": 11386, + "osqu": 11387, + "Ġleaving": 11388, + "Ġfashion": 11389, + "ĠnM": 11390, + "Ġmutual": 11391, + "approxim": 11392, + "Ġwalking": 11393, + "Ġimpossible": 11394, + "Ġdemonstrating": 11395, + "Ġdegener": 11396, + "ĠAV": 11397, + "Ġcontrary": 11398, + "ustion": 11399, + "oclonal": 11400, + "Anal": 11401, + "Ġperformances": 11402, + "Ġcomprom": 11403, + "orms": 11404, + "Ġbudget": 11405, + "ĠHaw": 11406, + "Ġarthritis": 11407, + "obj": 11408, + "noise": 11409, + "TiO": 11410, + "ochrome": 11411, + "Ġgeodes": 11412, + "bean": 11413, + "Ġselectivity": 11414, + "ĠFood": 11415, + "ughter": 11416, + "Ġpermutation": 11417, + "ĠRP": 11418, + "osal": 11419, + "Ġadip": 11420, + "armaceutical": 11421, + "when": 11422, + "ĠText": 11423, + "week": 11424, + "Ġbonding": 11425, + "arb": 11426, + "ocor": 11427, + "Ġvoc": 11428, + "Ġupregulated": 11429, + "Ġneighbors": 11430, + "Ġtrait": 11431, + "Ġtheore": 11432, + "Ġcf": 11433, + "ĠBerg": 11434, + "ĠLA": 11435, + "Ġlas": 11436, + "unte": 11437, + "ceptual": 11438, + "ASE": 11439, + "Ġischemic": 11440, + "Ġbending": 11441, + "dataset": 11442, + "Ġkeeping": 11443, + "Ġarrows": 11444, + "Ġsubstances": 11445, + "Ġns": 11446, + "Ġextending": 11447, + "ĠRu": 11448, + "Ġsupplementation": 11449, + "critical": 11450, + "ĠTraining": 11451, + "bullet": 11452, + "Ġpara": 11453, + "tail": 11454, + "ĠReference": 11455, + "Ġ": 11456, + "Ġdissipation": 11457, + "Ġauxiliary": 11458, + "ĠCycl": 11459, + "stim": 11460, + "Ġdilution": 11461, + "buf": 11462, + "ĠMiss": 11463, + "Ġultimately": 11464, + "Ġpowers": 11465, + "Ġstands": 11466, + "usted": 11467, + "ĠOH": 11468, + "habilitation": 11469, + "analy": 11470, + "ĠBra": 11471, + "adding": 11472, + "Corollary": 11473, + "Ġdrought": 11474, + "quality": 11475, + "Ġstandardized": 11476, + "ĠJe": 11477, + "ĠAcid": 11478, + "Ġmism": 11479, + "ĠChrom": 11480, + "draw": 11481, + "ĠBiom": 11482, + "ĠStability": 11483, + "Furthermore": 11484, + "last": 11485, + "vic": 11486, + "Ġabst": 11487, + "Ġbis": 11488, + "Ġemergence": 11489, + "Ġgiant": 11490, + "De": 11491, + "ĠSamples": 11492, + "ABA": 11493, + "nas": 11494, + "Ġont": 11495, + "Ġevap": 11496, + "levant": 11497, + "main": 11498, + "ĠRod": 11499, + "Ġcros": 11500, + "itary": 11501, + "Ġdoub": 11502, + "rö": 11503, + "igenetic": 11504, + "Ġincomplete": 11505, + "depth": 11506, + "ïģ": 11507, + "Ġsaturated": 11508, + "Ġaerosol": 11509, + "Assum": 11510, + "Ġimmunos": 11511, + "Ġlipids": 11512, + "itoneal": 11513, + "Ġbearing": 11514, + "ĠImplications": 11515, + "Ġsustained": 11516, + "Ġcompetitive": 11517, + "Ġmotivation": 11518, + "Ġdisturbance": 11519, + "rystalline": 11520, + "Ġtaxa": 11521, + "Ġdementia": 11522, + "Ġconcerned": 11523, + "PIO": 11524, + "homogeneous": 11525, + "ĠEv": 11526, + "ĠGeorge": 11527, + "ĠAlgorithms": 11528, + "ickel": 11529, + "usively": 11530, + "Ġcorner": 11531, + "ĠRest": 11532, + "Ġinfinity": 11533, + "ĠTransform": 11534, + "heng": 11535, + "Ġneurode": 11536, + "olim": 11537, + "Íij": 11538, + "Ġskew": 11539, + "ĠBS": 11540, + "score": 11541, + "YPE": 11542, + "eman": 11543, + "elle": 11544, + "ĠCorrelation": 11545, + "Ġcultural": 11546, + "ophosph": 11547, + "Ġattenuation": 11548, + "Ġaggregate": 11549, + "Ġambig": 11550, + "Ġanomalous": 11551, + "Ġtors": 11552, + "Ġplanet": 11553, + "ĠNPs": 11554, + "hr": 11555, + "ĠDivision": 11556, + "ĠEducation": 11557, + "lectic": 11558, + "Ġbrought": 11559, + "ĠMorph": 11560, + "Ġplanes": 11561, + "Ġsugar": 11562, + "Ġdendritic": 11563, + "Ġcontour": 11564, + "Ġcylinder": 11565, + "post": 11566, + "Ġwent": 11567, + "RL": 11568, + "Ġadmission": 11569, + "MSE": 11570, + "IX": 11571, + "Ġdisjoint": 11572, + "Ġannotation": 11573, + "Ġisotope": 11574, + "Ġμν": 11575, + "Ġeliminate": 11576, + "Ġreactor": 11577, + "onents": 11578, + "Ġreasoning": 11579, + "Ġmorbidity": 11580, + "Ġcorrosion": 11581, + "othermal": 11582, + "arctic": 11583, + "ĠMB": 11584, + "ĠZhao": 11585, + "Ġhistological": 11586, + "Ġsuperconducting": 11587, + "attered": 11588, + "Ġhousehold": 11589, + "ĠProp": 11590, + "Ġasser": 11591, + "hered": 11592, + "Ġteams": 11593, + "Ġvanishes": 11594, + "Pre": 11595, + "aments": 11596, + "Ġamorphous": 11597, + "ĠDetermination": 11598, + "missions": 11599, + "Ġoverhead": 11600, + "determ": 11601, + "Ġutilizing": 11602, + "fa": 11603, + "ipolar": 11604, + "Ġformulated": 11605, + "Ġextrap": 11606, + "grid": 11607, + "Ġhumidity": 11608, + "uber": 11609, + "tumor": 11610, + "rous": 11611, + "Ġdistortion": 11612, + "dynamics": 11613, + "ĠLoss": 11614, + "Ġscaled": 11615, + "Ġischemia": 11616, + "Ġaxes": 11617, + "Ġquantit": 11618, + "nit": 11619, + "ĠRegion": 11620, + "ained": 11621, + "Ġfill": 11622, + "Ġbranching": 11623, + "ĠTiss": 11624, + "cross": 11625, + "Ġplatelet": 11626, + "iffiffiffiff": 11627, + "rops": 11628, + "lux": 11629, + "join": 11630, + "uracy": 11631, + "icide": 11632, + "ĠLouis": 11633, + "Ġ": 11634, + "Ġstrings": 11635, + "yset": 11636, + "Ġfacial": 11637, + "ĠMMP": 11638, + "RES": 11639, + "Ġhydrolysis": 11640, + "ĠCanadian": 11641, + "Ġprojective": 11642, + "Ġscatter": 11643, + "uron": 11644, + "ĠPsych": 11645, + "complex": 11646, + "ĠNam": 11647, + "Ġconcurrent": 11648, + "IONS": 11649, + "Ġthous": 11650, + "Ġchance": 11651, + "Ġplacement": 11652, + "Ġawareness": 11653, + "Ġtrib": 11654, + "ĠTex": 11655, + "ĠThird": 11656, + "Ġlabeling": 11657, + "cerol": 11658, + "Ġsaw": 11659, + "ĠBand": 11660, + "ĠPear": 11661, + "Ġpregnant": 11662, + "ĠDown": 11663, + "platin": 11664, + "Seq": 11665, + "xe": 11666, + "ethylene": 11667, + "ĠHigher": 11668, + "Ġreality": 11669, + "uris": 11670, + "ĠPAR": 11671, + "lb": 11672, + "dose": 11673, + "shif": 11674, + "iliar": 11675, + "total": 11676, + "SW": 11677, + "Ġvalve": 11678, + "nder": 11679, + "н": 11680, + "amous": 11681, + "Ġendomet": 11682, + "LISA": 11683, + "Ġfractures": 11684, + "Ġfilt": 11685, + "role": 11686, + "Ġmicrostructure": 11687, + "ĠSNP": 11688, + "TER": 11689, + "ĠZnO": 11690, + "oving": 11691, + "ali": 11692, + "ĠGM": 11693, + "unct": 11694, + "Ġextensions": 11695, + "expression": 11696, + "Ġescape": 11697, + "ĠMas": 11698, + "ĠSpanish": 11699, + "Ġfloor": 11700, + "ĠCommon": 11701, + "otopy": 11702, + "plementation": 11703, + "Ġrhyth": 11704, + "Ġserves": 11705, + "yto": 11706, + "Ġwavelengths": 11707, + "emptyset": 11708, + "ĠHill": 11709, + "nor": 11710, + "ĠElectro": 11711, + "Ġdehydrogen": 11712, + "Ġwhom": 11713, + "imetric": 11714, + "ĠRoman": 11715, + "ĠVe": 11716, + "âī¥": 11717, + "ĠKu": 11718, + "ĠTransfer": 11719, + "Äĩ": 11720, + "ĠTF": 11721, + "brain": 11722, + "coprotein": 11723, + "ĠGreat": 11724, + "aven": 11725, + "ĠIndividual": 11726, + "uri": 11727, + "Ġfungi": 11728, + "Ġparam": 11729, + "pton": 11730, + "symmetry": 11731, + "Ġlock": 11732, + "meas": 11733, + "Ġhaem": 11734, + "Ġhip": 11735, + "Ass": 11736, + "enger": 11737, + "Ġpotassium": 11738, + "anal": 11739, + "ibrary": 11740, + "Ġschools": 11741, + "natal": 11742, + "Ġalleles": 11743, + "ĠHLA": 11744, + "oxygen": 11745, + "ĠCup": 11746, + "Ġpurely": 11747, + "DO": 11748, + "Ġchip": 11749, + "ôı": 11750, + "Car": 11751, + "sil": 11752, + "Ġunlikely": 11753, + "correspond": 11754, + "ĠDP": 11755, + "Ġintense": 11756, + "Ġforcing": 11757, + "ĠJournal": 11758, + "Ġarrow": 11759, + "ocyan": 11760, + "Ġcultiv": 11761, + "Ġblind": 11762, + "Ġselecting": 11763, + "ocarcinoma": 11764, + "rance": 11765, + "Ġhydrophobic": 11766, + "closed": 11767, + "Ġensures": 11768, + "Ġpromoted": 11769, + "Ġdetectable": 11770, + "ranean": 11771, + "Ġschedule": 11772, + "Ġpartly": 11773, + "Ġgland": 11774, + "Ġcouple": 11775, + "ĠEmerg": 11776, + "Ġtraces": 11777, + "poly": 11778, + "Ġprotease": 11779, + "ystic": 11780, + "Ġdocuments": 11781, + "positions": 11782, + "Ġdriver": 11783, + "tium": 11784, + "ĠCYP": 11785, + "close": 11786, + "ĠRecep": 11787, + "Ġpermit": 11788, + "Ġblocked": 11789, + "Ġinvestigating": 11790, + "ĠTumor": 11791, + "ĠBig": 11792, + "Ġwavegu": 11793, + "Ġsubstance": 11794, + "Ġweaker": 11795, + "ĠMont": 11796, + "rovers": 11797, + "ĠMexico": 11798, + "pres": 11799, + "ĠAcute": 11800, + "Ġmicrogl": 11801, + "ĠES": 11802, + "itoring": 11803, + "ĠSeries": 11804, + "lights": 11805, + "Ġhypothesized": 11806, + "Ġconstructs": 11807, + "Ġfiltration": 11808, + "Black": 11809, + "Ġunchanged": 11810, + "Ġobservable": 11811, + "Ġray": 11812, + "between": 11813, + "Ġ": 11814, + "ĠPosition": 11815, + "Ġthi": 11816, + "ĠSystematic": 11817, + "Class": 11818, + "km": 11819, + "ĠTak": 11820, + "Ġrespondents": 11821, + "Ġinnate": 11822, + "Ġant": 11823, + "Ġconnecting": 11824, + "Rel": 11825, + "Ġmanipulation": 11826, + "ĠNeg": 11827, + "NPs": 11828, + "ĠDiab": 11829, + "ĠActive": 11830, + "ĠGall": 11831, + "ĠCoulomb": 11832, + "Ġspacing": 11833, + "ĠFlor": 11834, + "Ġconductance": 11835, + "Ġtracks": 11836, + "ĠZhu": 11837, + "weighted": 11838, + "rocy": 11839, + "Ġfather": 11840, + "idium": 11841, + "structured": 11842, + "ĠTel": 11843, + "Ġstrom": 11844, + "ithub": 11845, + "certain": 11846, + "But": 11847, + "ĠAccess": 11848, + "Ġpreventing": 11849, + "restrial": 11850, + "ĠConsidering": 11851, + "true": 11852, + "Ġhosts": 11853, + "Ġworst": 11854, + "ĠPd": 11855, + "gredi": 11856, + "Ġglycol": 11857, + "Ġstory": 11858, + "osquito": 11859, + "paratus": 11860, + "Ġmeeting": 11861, + "Ġepisode": 11862, + "nc": 11863, + "ĠSand": 11864, + "Ġuint": 11865, + "ynamical": 11866, + "urt": 11867, + "Ġeducational": 11868, + "Ġfocuses": 11869, + "gt": 11870, + "ĠHS": 11871, + "Ġdeterminant": 11872, + "Ġlithium": 11873, + "ĠDigital": 11874, + "Ġguidance": 11875, + "Ġpriority": 11876, + "Ġparty": 11877, + "orial": 11878, + "Two": 11879, + "ĠProblems": 11880, + "Ġseman": 11881, + "ĠCNN": 11882, + "ĠEpid": 11883, + "Ġplaying": 11884, + "Ġelimination": 11885, + "ĠSat": 11886, + "Ġobjectives": 11887, + "plectic": 11888, + "Ġcircumst": 11889, + "ĠGS": 11890, + "ocellular": 11891, + "otrans": 11892, + "Ġfinds": 11893, + "Ġaromatic": 11894, + "izers": 11895, + "Ġfavorable": 11896, + "standard": 11897, + "ichlor": 11898, + "models": 11899, + "otyping": 11900, + "Ġstabilization": 11901, + "Ġhandling": 11902, + "Ġcoated": 11903, + "even": 11904, + "Ġletter": 11905, + "ZE": 11906, + "Ġultrason": 11907, + "Ġfriend": 11908, + "Ġsensiti": 11909, + "Ġattachment": 11910, + "Ġapart": 11911, + "Ġgrey": 11912, + "Ġaircraft": 11913, + "ĠrRNA": 11914, + "Ġenabled": 11915, + "Ġbuff": 11916, + "Ġredox": 11917, + "assisted": 11918, + "Ġgenerality": 11919, + "PSS": 11920, + "Ġelection": 11921, + "response": 11922, + "Ġdedicated": 11923, + "Ġdemographic": 11924, + "Ġimposed": 11925, + "ĠKir": 11926, + "ĠRadio": 11927, + "ĠELISA": 11928, + "gae": 11929, + "Ġresc": 11930, + "ĠRic": 11931, + "raphic": 11932, + "Ġrail": 11933, + "Ġjournal": 11934, + "oler": 11935, + "WS": 11936, + "Ġincorporation": 11937, + "wind": 11938, + "Ġauditory": 11939, + "AE": 11940, + "task": 11941, + "Ġpc": 11942, + "wall": 11943, + "Ġapprec": 11944, + "aterials": 11945, + "Ġpartner": 11946, + "Ġcollective": 11947, + "Ġscoring": 11948, + "ĠFrank": 11949, + "Ġpermanent": 11950, + "ĠIran": 11951, + "umination": 11952, + "Med": 11953, + "ĠHybrid": 11954, + "Ġphenotypic": 11955, + "Ġdisruption": 11956, + "violet": 11957, + "ospheric": 11958, + "Ġregimes": 11959, + "ĠColor": 11960, + "ĠPatient": 11961, + "Ġfever": 11962, + "Ġnn": 11963, + "Ġvariational": 11964, + "keys": 11965, + "Ġdistill": 11966, + "Ġspectroscopic": 11967, + "ĠArchitect": 11968, + "acing": 11969, + "Ġproves": 11970, + "Ġverteb": 11971, + "ĠComputer": 11972, + "Ġexpensive": 11973, + "Ġfrozen": 11974, + "arcoma": 11975, + "NK": 11976, + "Ġhistone": 11977, + "Ġpolymerization": 11978, + "Ġtob": 11979, + "Ġturned": 11980, + "effective": 11981, + "ĠAuthor": 11982, + "API": 11983, + "Ġdecade": 11984, + "ĠRobert": 11985, + "Example": 11986, + "overset": 11987, + "ABLE": 11988, + "ĠBehavior": 11989, + "feed": 11990, + "ĠTai": 11991, + "Ġ": 11992, + "Ġegg": 11993, + "Ġcath": 11994, + "aux": 11995, + "ĠJohnson": 11996, + "Ġtorque": 11997, + "Ġpurification": 11998, + "White": 11999, + "cious": 12000, + "ĠSong": 12001, + "Ġprecipit": 12002, + "reshold": 12003, + "Ġmilitary": 12004, + "Ġconvection": 12005, + "ĠMiddle": 12006, + "ĠWhe": 12007, + "Ġôı": 12008, + "aland": 12009, + "aration": 12010, + "figure": 12011, + "Ġdeduce": 12012, + "chloro": 12013, + "cost": 12014, + "ithmetic": 12015, + "ĠItalian": 12016, + "missible": 12017, + "ĠCommunity": 12018, + "ĠNature": 12019, + "Ġdioxide": 12020, + "Ġbalanced": 12021, + "ett": 12022, + "STAT": 12023, + "ilding": 12024, + "Ġevolved": 12025, + "Ġmonot": 12026, + "pur": 12027, + "Ġpreferences": 12028, + "dinger": 12029, + "Ġargue": 12030, + "Ġmotions": 12031, + "Ġinfant": 12032, + "Ġaccelerated": 12033, + "Ġobserver": 12034, + "Ġfabrication": 12035, + "ĠMechanisms": 12036, + "Ġfunctor": 12037, + "Ġharves": 12038, + "rase": 12039, + "ĠSpecial": 12040, + "Ġdeposits": 12041, + "Ġrub": 12042, + "à¸": 12043, + "ĠCPU": 12044, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 12045, + "atomical": 12046, + "Ġfinit": 12047, + "Ġsecure": 12048, + "Ġnutritional": 12049, + "renal": 12050, + "ĠFalse": 12051, + "Ġshel": 12052, + "Ġrecruited": 12053, + "ambig": 12054, + "ĠSignaling": 12055, + "KO": 12056, + "organisms": 12057, + "ĠLT": 12058, + "elen": 12059, + "ĠMarc": 12060, + "abatic": 12061, + "Ġtables": 12062, + "Ġconfined": 12063, + "ĠAz": 12064, + "Ġproductivity": 12065, + "Ġadherence": 12066, + "Ġreplicates": 12067, + "Ġvirt": 12068, + "fin": 12069, + "Ġagricultural": 12070, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 12071, + "ĠChampionship": 12072, + "anda": 12073, + "ĠChurch": 12074, + "During": 12075, + "Ġinserted": 12076, + "ighter": 12077, + "Ġxen": 12078, + "Ġsave": 12079, + "Ġtangent": 12080, + "venous": 12081, + "Ġconverge": 12082, + "Ġdistinguished": 12083, + "Ġexplos": 12084, + "Ġaortic": 12085, + "Ġjump": 12086, + "Ġneonatal": 12087, + "udden": 12088, + "Ġslower": 12089, + "Ġinfarction": 12090, + "Ġprevents": 12091, + "uer": 12092, + "Ġeros": 12093, + "RP": 12094, + "Ġcontinues": 12095, + "ORT": 12096, + "Ġconsiders": 12097, + "ĠNuclear": 12098, + "lymp": 12099, + "Ġaccounted": 12100, + "oresis": 12101, + "Ġneighboring": 12102, + "ĠRichard": 12103, + "Ġenfor": 12104, + "ĠChronic": 12105, + "Ġdiscover": 12106, + "ĠHong": 12107, + "cells": 12108, + "ĠChall": 12109, + "Ġhomogen": 12110, + "Ġatheros": 12111, + "Ġisolate": 12112, + "ĠPlasma": 12113, + "ĠDL": 12114, + "parametric": 12115, + "ĠUpper": 12116, + "HP": 12117, + "Ġintroduces": 12118, + "Ġmothers": 12119, + "Ġattract": 12120, + "Ġexclusion": 12121, + "gravity": 12122, + "ĠKr": 12123, + "Ġspike": 12124, + "ĠHeat": 12125, + "vival": 12126, + "ĠRNAs": 12127, + "bach": 12128, + "atorial": 12129, + "ĠLtd": 12130, + "onomy": 12131, + "invasive": 12132, + "lass": 12133, + "Ġwells": 12134, + "Ġimaginary": 12135, + "Ġcarbohyd": 12136, + "oda": 12137, + "Ġactivate": 12138, + "µĦ": 12139, + "Ġenzymatic": 12140, + "pes": 12141, + "Ġstatements": 12142, + "Ġapproximated": 12143, + "ĠSalmon": 12144, + "ophageal": 12145, + "ĠHPV": 12146, + "conf": 12147, + "umat": 12148, + "Ġsulfur": 12149, + "ĠRecall": 12150, + "Ġchond": 12151, + "Ġviable": 12152, + "poration": 12153, + "Ġcarefully": 12154, + "tetra": 12155, + "Ġlymphoma": 12156, + "stat": 12157, + "Ġconservative": 12158, + "atabase": 12159, + "mand": 12160, + "Ġscored": 12161, + "Ġvas": 12162, + "Ġprivacy": 12163, + "onymous": 12164, + "Ġlogarithmic": 12165, + "ĠEcon": 12166, + "Ġachieves": 12167, + "Ġabundances": 12168, + "cam": 12169, + "Ġcyan": 12170, + "ĠEL": 12171, + "idelity": 12172, + "jo": 12173, + "Ġanticip": 12174, + "reported": 12175, + "Ġarrangement": 12176, + "iterranean": 12177, + "psis": 12178, + "ichi": 12179, + "Ġta": 12180, + "umping": 12181, + "ĠActivation": 12182, + "Ġmelt": 12183, + "Ġanno": 12184, + "oge": 12185, + "ĠDam": 12186, + "optimal": 12187, + "Ġneurological": 12188, + "sa": 12189, + "ĠParameters": 12190, + "offset": 12191, + "Ġcement": 12192, + "Ġinhibiting": 12193, + "Ġchose": 12194, + "itzer": 12195, + "attr": 12196, + "Ġmoder": 12197, + "atories": 12198, + "Ġteaching": 12199, + "ĠCore": 12200, + "phthal": 12201, + "ĠLuc": 12202, + "Ġingredi": 12203, + "Ġclearance": 12204, + "Ġachieving": 12205, + "tage": 12206, + "Ġburst": 12207, + "vie": 12208, + "ĠSpain": 12209, + "pto": 12210, + "Ġtransmembrane": 12211, + "Ġsupplementary": 12212, + "Ġtoken": 12213, + "Ġobviously": 12214, + "ĠVector": 12215, + "Ġdestr": 12216, + "HOD": 12217, + "Ġassumes": 12218, + "Ġpenetration": 12219, + "Ġsubjective": 12220, + "holds": 12221, + "ão": 12222, + "Ġmotiv": 12223, + "Ġproviders": 12224, + "vascular": 12225, + "Ġdepartment": 12226, + "ocket": 12227, + "File": 12228, + "Ġbreath": 12229, + "ĠBest": 12230, + "grable": 12231, + "Ġliqu": 12232, + "ĠArg": 12233, + "ĠBob": 12234, + "Ġfragmentation": 12235, + "ectic": 12236, + "Ġvital": 12237, + "since": 12238, + "alloc": 12239, + "oxyphenyl": 12240, + "Ġradiotherapy": 12241, + "ĠSDS": 12242, + "Ġcytometry": 12243, + "nucle": 12244, + "ĠIM": 12245, + "ĠTeV": 12246, + "rafish": 12247, + "ĠKorea": 12248, + "Ġstrengthen": 12249, + "Ġbare": 12250, + "Ġwoman": 12251, + "Ġradar": 12252, + "Ġplatforms": 12253, + "ozygous": 12254, + "ĠAh": 12255, + "Ġsubtypes": 12256, + "pyrid": 12257, + "ĠTranscription": 12258, + "Ġáº": 12259, + "ĠMeasurements": 12260, + "Ġsurviv": 12261, + "ĠNear": 12262, + "Ġcascade": 12263, + "outhe": 12264, + "BU": 12265, + "Ġexponentially": 12266, + "Ġhazard": 12267, + "ĠsiRNA": 12268, + "Ġcellulose": 12269, + "Figs": 12270, + "Ġdifferentiated": 12271, + "Ġimplicated": 12272, + "metric": 12273, + "Ġcorrelate": 12274, + "Ġmission": 12275, + "Ġmantle": 12276, + "ĠPhyl": 12277, + "ĠHart": 12278, + "Ġgases": 12279, + "Ġunity": 12280, + "Ġexpert": 12281, + "Ġchart": 12282, + "Ġdict": 12283, + "Ġepile": 12284, + "Ġoffspring": 12285, + "Ġemerged": 12286, + "Ġdemands": 12287, + "Ġpresum": 12288, + "orbid": 12289, + "ĠMedicine": 12290, + "Ġstreams": 12291, + "ticed": 12292, + "ĠNic": 12293, + "Ġfilling": 12294, + "ĠCro": 12295, + "Ġrestrictions": 12296, + "See": 12297, + "ĠMill": 12298, + "Ġparental": 12299, + "Ġdeterminants": 12300, + "Ġecosystem": 12301, + "ĠWall": 12302, + "ĠMemory": 12303, + "plets": 12304, + "Ġaggregates": 12305, + "perturb": 12306, + "Ġresidents": 12307, + "ACK": 12308, + "vectors": 12309, + "Ġmanually": 12310, + "Ġïĺ": 12311, + "ĠFramework": 12312, + "Ġvag": 12313, + "ebrafish": 12314, + "lib": 12315, + "ĠHeart": 12316, + "ĠAnimal": 12317, + "Ġwider": 12318, + "Gene": 12319, + "ĠRos": 12320, + "Ġoperate": 12321, + "Ġpossibilities": 12322, + "ĠStrong": 12323, + "Ġpyro": 12324, + "respectively": 12325, + "Ġhybridization": 12326, + "ipedia": 12327, + "xin": 12328, + "Ġstom": 12329, + "fish": 12330, + "ĠForce": 12331, + "Ġdimer": 12332, + "SUL": 12333, + "else": 12334, + "Ġunde": 12335, + "gar": 12336, + "conv": 12337, + "Ġarrival": 12338, + "Ġmonoclonal": 12339, + "IAL": 12340, + "Ġly": 12341, + "Ġsymmetries": 12342, + "Ġnursing": 12343, + "rach": 12344, + "ĠóµĦ": 12345, + "Ġbiased": 12346, + "Ġcues": 12347, + "Ġbiomarker": 12348, + "ders": 12349, + "Ġcrow": 12350, + "ernels": 12351, + "Ġbilateral": 12352, + "Ġphysically": 12353, + "Ġpatches": 12354, + "Ġuncon": 12355, + "ĠBefore": 12356, + "default": 12357, + "estyle": 12358, + "tfrac": 12359, + "ĠCox": 12360, + "Ġinfiltration": 12361, + "Ġconvert": 12362, + "Ġstrengths": 12363, + "ĠSar": 12364, + "igible": 12365, + "ocomp": 12366, + "Ġstir": 12367, + "Ġschizophrenia": 12368, + "was": 12369, + "Ġow": 12370, + "eterm": 12371, + "ĠOrder": 12372, + "Ġfoss": 12373, + "Ġlineage": 12374, + "Ġrabbit": 12375, + "Ġregularization": 12376, + "ranch": 12377, + "oplastic": 12378, + "TO": 12379, + "Ġmeasurable": 12380, + "Ġmang": 12381, + "initial": 12382, + "Ġbuildings": 12383, + "Ġsystematically": 12384, + "Ġfermions": 12385, + "Ġlibraries": 12386, + "Ġablation": 12387, + "ideos": 12388, + "ĠWi": 12389, + "photon": 12390, + "ĠTesting": 12391, + "ĠComputing": 12392, + "tier": 12393, + "inet": 12394, + "Ġprimitive": 12395, + "Ġcapillary": 12396, + "Ġslip": 12397, + "vergence": 12398, + "rapeutic": 12399, + "ĠBlue": 12400, + "ĠAcad": 12401, + "hai": 12402, + "ĠLew": 12403, + "Ġtriangular": 12404, + "MSO": 12405, + "Ġsalinity": 12406, + "Ġnanocom": 12407, + "oa": 12408, + "Ġhomomorphism": 12409, + "ĠMM": 12410, + "Ġresin": 12411, + "DB": 12412, + "uminescence": 12413, + "dashed": 12414, + "ĠKh": 12415, + "quark": 12416, + "embles": 12417, + "Ġidentifies": 12418, + "Ġfollic": 12419, + "Ġmetam": 12420, + "ĠHerm": 12421, + "Ġtobacco": 12422, + "Ġrealization": 12423, + "hydrox": 12424, + "ĠBet": 12425, + "Because": 12426, + "Ġpieces": 12427, + "Ġtalk": 12428, + "Ġopened": 12429, + "asome": 12430, + "Ġsurge": 12431, + "Ġfluctuation": 12432, + "github": 12433, + "ĠBacter": 12434, + "Ġbinds": 12435, + "ĠRapid": 12436, + "auer": 12437, + "pH": 12438, + "embed": 12439, + "ĠDoc": 12440, + "uchi": 12441, + "ĠCandid": 12442, + "Ġrarely": 12443, + "Ġmountain": 12444, + "ĠFat": 12445, + "Ġsend": 12446, + "ovsk": 12447, + "ĠOrganization": 12448, + "ĠFranc": 12449, + "ĠOP": 12450, + "âμ": 12451, + "okes": 12452, + "ece": 12453, + "deficient": 12454, + "Ġlinkage": 12455, + "odon": 12456, + "Ġfly": 12457, + "Ġtidal": 12458, + "ĠExamples": 12459, + "ĠRout": 12460, + "Ġaccommod": 12461, + "Suppose": 12462, + "adap": 12463, + "Ġdie": 12464, + "root": 12465, + "Ġhon": 12466, + "Ġminimizing": 12467, + "Ġroughness": 12468, + "Ġgrass": 12469, + "enta": 12470, + "ĠLang": 12471, + "edu": 12472, + "ĠSimple": 12473, + "enic": 12474, + "Ġinducing": 12475, + "tf": 12476, + "Ġcontexts": 12477, + "ĠGeneralized": 12478, + "ĠWnt": 12479, + "Pb": 12480, + "atomic": 12481, + "dem": 12482, + "ĠPreparation": 12483, + "Ġinsufficient": 12484, + "sam": 12485, + "ĠSpecies": 12486, + "ĠSolar": 12487, + "Ġunsigned": 12488, + "ĠHER": 12489, + "âĬ": 12490, + "Ġparity": 12491, + "Ġnitrate": 12492, + "ĠCer": 12493, + "ptic": 12494, + "identif": 12495, + "geal": 12496, + "Ġemotion": 12497, + "ĠLP": 12498, + "Ġenhancing": 12499, + "Ġmeaningful": 12500, + "station": 12501, + "Ġrelig": 12502, + "yo": 12503, + "Ġperspectives": 12504, + "Ġscans": 12505, + "uginosa": 12506, + "Ġsummarize": 12507, + "relations": 12508, + "Ġdistant": 12509, + "Ġfunctionality": 12510, + "Ġdeeper": 12511, + "olate": 12512, + "ĠPor": 12513, + "graphs": 12514, + "ĠWa": 12515, + "ophilic": 12516, + "CLUS": 12517, + "ropathy": 12518, + "Ġcred": 12519, + "Ġuniversity": 12520, + "seg": 12521, + "vee": 12522, + "OG": 12523, + "ĠMen": 12524, + "ĠCritical": 12525, + "ãģ": 12526, + "Ġexit": 12527, + "vartheta": 12528, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 12529, + "Ġunf": 12530, + "Ġproposal": 12531, + "Ġtyrosine": 12532, + "otides": 12533, + "Ġproximity": 12534, + "Ġboxes": 12535, + "caten": 12536, + "ĠEnvironmental": 12537, + "bounded": 12538, + "downarrow": 12539, + "Ġfalls": 12540, + "Ġfertil": 12541, + "Ġcomprised": 12542, + "Ġmellitus": 12543, + "Ġleakage": 12544, + "uty": 12545, + "Ġchromosomes": 12546, + "ĠStatistics": 12547, + "%%%%": 12548, + "Ġcombinator": 12549, + "Ġket": 12550, + "advant": 12551, + "Ther": 12552, + "Ġtopics": 12553, + "flat": 12554, + "nia": 12555, + "ĠSpectral": 12556, + "Ġsynchronization": 12557, + "varrho": 12558, + "Ġcolonies": 12559, + "ĠFive": 12560, + "agues": 12561, + "ĠFC": 12562, + "IDS": 12563, + "Ġaward": 12564, + "Ġyielding": 12565, + "Ġarchitectures": 12566, + "ashington": 12567, + "chitz": 12568, + "perty": 12569, + "Ġmoduli": 12570, + "moment": 12571, + "speed": 12572, + "Ġmesenchymal": 12573, + "optera": 12574, + "Ġincomp": 12575, + "Cell": 12576, + "ĠMice": 12577, + "Ġgot": 12578, + "teger": 12579, + "Ġtau": 12580, + "ĠAdS": 12581, + "Ġbill": 12582, + "Ġdrinking": 12583, + "ulsive": 12584, + "Ġknockdown": 12585, + "Ġarms": 12586, + "ĠAutom": 12587, + "ĠIncreased": 12588, + "HF": 12589, + "Ġglobally": 12590, + "Ġdoping": 12591, + "Ġath": 12592, + "ĠCop": 12593, + "Ġsuccessive": 12594, + "ULT": 12595, + "eless": 12596, + "Ġbleeding": 12597, + "Ġfoods": 12598, + "Ġimmunohist": 12599, + "Ġdefinite": 12600, + "ĠJones": 12601, + "ĠTS": 12602, + "Ġjoined": 12603, + "ĠTowards": 12604, + "ĠCs": 12605, + "Ġunlike": 12606, + "Ġvalence": 12607, + "dor": 12608, + "oS": 12609, + "Ġpush": 12610, + "Ġoffice": 12611, + "Ġaluminum": 12612, + "idyl": 12613, + "idirectional": 12614, + "written": 12615, + "Ġbubble": 12616, + "HI": 12617, + "Ġmarkedly": 12618, + "ĠTok": 12619, + "Ġvesicles": 12620, + "Ġquotient": 12621, + "Ġreproduce": 12622, + "Ġelsewhere": 12623, + "ĠMyc": 12624, + "Ġinfrastructure": 12625, + "Ġgained": 12626, + "abel": 12627, + "ĠSex": 12628, + "ĠTables": 12629, + "etin": 12630, + "Ġhomolog": 12631, + "Ġlegal": 12632, + "hea": 12633, + "Ġsociety": 12634, + "Ġmanaged": 12635, + "idase": 12636, + "ĠInhibition": 12637, + "Ġparasite": 12638, + "Ġvolunte": 12639, + "ATP": 12640, + "ios": 12641, + "Ġsepsis": 12642, + "Ġribosomal": 12643, + "Ġconfound": 12644, + "ĠStaphyl": 12645, + "aryngeal": 12646, + "ïĢ": 12647, + "comb": 12648, + "ĠObjective": 12649, + "SULTS": 12650, + "Ġthorough": 12651, + "mt": 12652, + "Ġchest": 12653, + "Vector": 12654, + "element": 12655, + "Ġvirulence": 12656, + "Ġhemisp": 12657, + "Ġsought": 12658, + "ĠKo": 12659, + "Ġnutrition": 12660, + "uling": 12661, + "iana": 12662, + "Ġprototype": 12663, + "ĠOnt": 12664, + "cine": 12665, + "Ġdotted": 12666, + "Ġobese": 12667, + "ountered": 12668, + "Ġphysicians": 12669, + "Ġmini": 12670, + "Ľľ": 12671, + "spaces": 12672, + "Ġexclusively": 12673, + "ĠConvolution": 12674, + "Ġcaspase": 12675, + "ĠLink": 12676, + "div": 12677, + "ĠRoyal": 12678, + "hist": 12679, + "itness": 12680, + "Ġester": 12681, + "Ġconducting": 12682, + "Ġparticipated": 12683, + "Ġairway": 12684, + "Ġaeruginosa": 12685, + "Ext": 12686, + "argument": 12687, + "ocking": 12688, + "Ġintegrate": 12689, + "Ġcontrovers": 12690, + "apes": 12691, + "training": 12692, + "ĠPrevalence": 12693, + "temp": 12694, + "both": 12695, + "Ġreactivity": 12696, + "Ġranking": 12697, + "Ġtunneling": 12698, + "ODE": 12699, + "ĠMediterranean": 12700, + "Ġresonances": 12701, + "Mg": 12702, + "Ġlib": 12703, + "ĠHeter": 12704, + "Ġnothing": 12705, + "Ġindication": 12706, + "ĠHM": 12707, + "ocytic": 12708, + "strand": 12709, + "Ġcollaboration": 12710, + "Ġelectrostatic": 12711, + "Ġindependence": 12712, + "hab": 12713, + "Ġconflic": 12714, + "Ġiod": 12715, + "inus": 12716, + "Ġdependency": 12717, + "ĠLam": 12718, + "Ġexamining": 12719, + "Ġoccupied": 12720, + "Ġqueue": 12721, + "ĠBul": 12722, + "Ġregistered": 12723, + "Ġindividually": 12724, + "Rx": 12725, + "ausal": 12726, + "VE": 12727, + "Ġbrightness": 12728, + "respons": 12729, + "balance": 12730, + "Ġcytotoxic": 12731, + "fall": 12732, + "commut": 12733, + "ICAL": 12734, + "uran": 12735, + "aining": 12736, + "raulic": 12737, + "results": 12738, + "Ġepisodes": 12739, + "YS": 12740, + "ĠGar": 12741, + "Ġsurfact": 12742, + "drug": 12743, + "Ġcities": 12744, + "ĠChange": 12745, + "osition": 12746, + "Ġtriggered": 12747, + "Ġcytoplasmic": 12748, + "erves": 12749, + "Ġlex": 12750, + "Ġasymptotically": 12751, + "phy": 12752, + "Ġfrontal": 12753, + "ĠDensity": 12754, + "Ġsynerg": 12755, + "cycle": 12756, + "ĠImproved": 12757, + "ø": 12758, + "Ġmono": 12759, + "Ġaccumulated": 12760, + "oriented": 12761, + "bour": 12762, + "Ġtunnel": 12763, + "coming": 12764, + "Ġapparatus": 12765, + "Ġencountered": 12766, + "Cre": 12767, + "Ġletters": 12768, + "etch": 12769, + "Ġexcessive": 12770, + "Ġbiofilm": 12771, + "Ġrearrang": 12772, + "Ġpolymorphisms": 12773, + "erobic": 12774, + "Ġconnect": 12775, + "resolved": 12776, + "ĠNN": 12777, + "Ġretro": 12778, + "ĠIniti": 12779, + "ĠQuantif": 12780, + "Ġpup": 12781, + "Tensor": 12782, + "Ġsentences": 12783, + "lay": 12784, + "rants": 12785, + "ploid": 12786, + "ĠAnderson": 12787, + "Ġdesirable": 12788, + "stud": 12789, + "iability": 12790, + "Ġdrying": 12791, + "ecess": 12792, + "Ġdens": 12793, + "Ġdescript": 12794, + "ĠËĨ": 12795, + "Ġclones": 12796, + "Ġjuven": 12797, + "bp": 12798, + "Ġkil": 12799, + "HL": 12800, + "Ġhemorrh": 12801, + "ĠKi": 12802, + "How": 12803, + "Ġenerge": 12804, + "Ġsubsection": 12805, + "ĠSac": 12806, + "dial": 12807, + "Ġcardiomy": 12808, + "Ġtouch": 12809, + "dm": 12810, + "Ġscienti": 12811, + "oides": 12812, + "ĠÃĤ": 12813, + "ysaccharide": 12814, + "Ġsclerosis": 12815, + "ĠZealand": 12816, + "inine": 12817, + "Ġunusual": 12818, + "ĠBA": 12819, + "ipschitz": 12820, + "gap": 12821, + "ĠDifferences": 12822, + "Ġduality": 12823, + "edical": 12824, + "Ġlign": 12825, + "Ġfails": 12826, + "Ġlect": 12827, + "Ġrelate": 12828, + "Ġincorrect": 12829, + "Ġspecify": 12830, + "Ġcylindrical": 12831, + "ĠPF": 12832, + "ĠLind": 12833, + "Ġdeterior": 12834, + "Ġherb": 12835, + "dz": 12836, + "Ġweld": 12837, + "Ġnominal": 12838, + "copy": 12839, + "Ġacetyl": 12840, + "html": 12841, + "Ġrecognize": 12842, + "***": 12843, + "itian": 12844, + "WA": 12845, + "ĠMN": 12846, + "ĠFind": 12847, + "Ġauthentic": 12848, + "perture": 12849, + "Ġcytotoxicity": 12850, + "ofl": 12851, + "ĠGet": 12852, + "Ġcohomology": 12853, + "Ġremainder": 12854, + "Ġexpanding": 12855, + "Ġheav": 12856, + "osterone": 12857, + "Right": 12858, + "Ġcopol": 12859, + "Ġshed": 12860, + "Ġcompliance": 12861, + "Ġacidic": 12862, + "oric": 12863, + "Ġamyloid": 12864, + "Ġevaporation": 12865, + "dl": 12866, + "Ġdelays": 12867, + "Po": 12868, + "ĠCHECK": 12869, + "tains": 12870, + "Ġreversed": 12871, + "ĠMPa": 12872, + "Ġprocessor": 12873, + "Ġhall": 12874, + "ĠLast": 12875, + "Ġplasm": 12876, + "ĠAssociated": 12877, + "ĠBasic": 12878, + "inos": 12879, + "Ġsymptom": 12880, + "ãĢ": 12881, + "Ġanthrop": 12882, + "Ġjudg": 12883, + "Ġeti": 12884, + "kle": 12885, + "Ġwrong": 12886, + "room": 12887, + "Ġdevelopments": 12888, + "ĠMaximum": 12889, + "Ġcoatings": 12890, + "Ġheuristic": 12891, + "rontal": 12892, + "Some": 12893, + "Ġutilize": 12894, + "ĠâĪħ": 12895, + "coll": 12896, + "ĠRelated": 12897, + "Ġdegeneration": 12898, + "template": 12899, + "Ġmodulated": 12900, + "Ġparametri": 12901, + "Ġsaliv": 12902, + "ĠPseudomonas": 12903, + "Ġantigens": 12904, + "Ġharmon": 12905, + "ĠLHC": 12906, + "doi": 12907, + "ensitive": 12908, + "ĠNotice": 12909, + "ĠMoh": 12910, + "tilage": 12911, + "ACS": 12912, + "Ġdiscrepancy": 12913, + "Ġspik": 12914, + "Ġrestrict": 12915, + "itrile": 12916, + "leg": 12917, + "ĠBase": 12918, + "Ġconvolutional": 12919, + "ĠResistance": 12920, + "Ġappearing": 12921, + "ĠImages": 12922, + "ĠMann": 12923, + "Ġreact": 12924, + "Ġmacrophage": 12925, + "Ġwavelet": 12926, + "ochrom": 12927, + "Ġfairly": 12928, + "Ġpreceding": 12929, + "Ġspir": 12930, + "network": 12931, + "ĠNak": 12932, + "IFT": 12933, + "Ġago": 12934, + "Ġencryp": 12935, + "ald": 12936, + "ensin": 12937, + "Ġsulph": 12938, + "ĠPolymer": 12939, + "ĠArt": 12940, + "Ġsubunits": 12941, + "shot": 12942, + "Ġbegins": 12943, + "Ġexer": 12944, + "propto": 12945, + "Ġnurses": 12946, + "Ġsuffices": 12947, + "Ġgraded": 12948, + "ĠRock": 12949, + "Ġuniquely": 12950, + "itol": 12951, + "Ġspiral": 12952, + "Ġthanks": 12953, + "character": 12954, + "ĠDistributed": 12955, + "ĠCart": 12956, + "Form": 12957, + "Ġformulations": 12958, + "ictionary": 12959, + "Ġspreading": 12960, + "Ġsingularity": 12961, + "Ġpigs": 12962, + "itu": 12963, + "otrophic": 12964, + "ÑĢ": 12965, + "Ġsemiconductor": 12966, + "Ġdrag": 12967, + "next": 12968, + "maxim": 12969, + "unn": 12970, + "Ġargued": 12971, + "plastic": 12972, + "Ġdehydrogenase": 12973, + "Ġreinforcement": 12974, + "entral": 12975, + "ĠDS": 12976, + "Ġcompanies": 12977, + "Ġquantization": 12978, + "ĠDri": 12979, + "Ġsimpler": 12980, + "Ġradii": 12981, + "ĠEthics": 12982, + "ĠElectronic": 12983, + "taken": 12984, + "Ġpharmacological": 12985, + "pson": 12986, + "Ġpairing": 12987, + "Ġnest": 12988, + "ĠRS": 12989, + "Ġlic": 12990, + "ocon": 12991, + "Ġobserving": 12992, + "ĠFM": 12993, + "IES": 12994, + "Ġsubmitted": 12995, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 12996, + "Ġnoisy": 12997, + "Ġvanishing": 12998, + "ĠTechnologies": 12999, + "ilst": 13000, + "agic": 13001, + "Ġembeddings": 13002, + "Ġplans": 13003, + "reak": 13004, + "oct": 13005, + "Ġepithelium": 13006, + "Ġreversible": 13007, + "Ġrequests": 13008, + "Vi": 13009, + "ĠProg": 13010, + "methoxy": 13011, + "uria": 13012, + "Ġslice": 13013, + "Ġmetastases": 13014, + "ĠMary": 13015, + "Ġpriori": 13016, + "Ġexplains": 13017, + "ĠSigma": 13018, + "ĠArmy": 13019, + "Ġprey": 13020, + "KL": 13021, + "ĠPass": 13022, + "Ġreproduction": 13023, + "Ġfermentation": 13024, + "ulo": 13025, + "Ġproofs": 13026, + "ĠAccordingly": 13027, + "tist": 13028, + "ĠïĢ©": 13029, + "Ġmeat": 13030, + "Ġplanned": 13031, + "Ġangiogenesis": 13032, + "WR": 13033, + "ĠAust": 13034, + "Similarly": 13035, + "ĠWashington": 13036, + "Ġrefinement": 13037, + "Ġembryo": 13038, + "Ġdissociation": 13039, + "án": 13040, + "plasia": 13041, + "ĠGro": 13042, + "Ġsimilarities": 13043, + "Ġsolubility": 13044, + "Ġimmobil": 13045, + "ĠScot": 13046, + "ĠSubsequently": 13047, + "divid": 13048, + "Ġclosest": 13049, + "ĠWat": 13050, + "ĠâĮ": 13051, + "ĠAGN": 13052, + "Ġprescribed": 13053, + "Ġmosquito": 13054, + "Ġfirm": 13055, + "Ġdegenerate": 13056, + "Ġethyl": 13057, + "Ġharvest": 13058, + "ĠSpecific": 13059, + "Ġcompartment": 13060, + "public": 13061, + "ĠBiological": 13062, + "Ġpiece": 13063, + "Ġattitudes": 13064, + "Ġspray": 13065, + "ĠSix": 13066, + "Ġprofessionals": 13067, + "Ġslot": 13068, + "Ġretrieved": 13069, + "vement": 13070, + "Ġexecuted": 13071, + "seed": 13072, + "Ġoutflow": 13073, + "distance": 13074, + "ĠTerm": 13075, + "ady": 13076, + "ĠProvince": 13077, + "ĠCentre": 13078, + "ĠDFT": 13079, + "Ġsudden": 13080, + "Ġseiz": 13081, + "rat": 13082, + "romo": 13083, + "otechn": 13084, + "Ġhighlights": 13085, + "Ġelectrolyte": 13086, + "ĠAdvanced": 13087, + "allow": 13088, + "px": 13089, + "osed": 13090, + "subarray": 13091, + "racks": 13092, + "PRO": 13093, + "ogeny": 13094, + "Ġpooled": 13095, + "Ġdtype": 13096, + "Ġopposed": 13097, + "ĠGrand": 13098, + "Ġdesigning": 13099, + "bel": 13100, + "itability": 13101, + "Ġminimization": 13102, + "Ġdramatically": 13103, + "Ġsoy": 13104, + "agents": 13105, + "ĠMetal": 13106, + "ĠMV": 13107, + "ribute": 13108, + "DD": 13109, + "itan": 13110, + "Ġspeeds": 13111, + "Ġmarried": 13112, + "Ġevaluations": 13113, + "ĠKingdom": 13114, + "Ġclay": 13115, + "ĠTissue": 13116, + "leftarrow": 13117, + "Ġcompensation": 13118, + "child": 13119, + "pool": 13120, + "uparrow": 13121, + "ĠDomain": 13122, + "species": 13123, + "Ġmethane": 13124, + "ĠEGFR": 13125, + "Ġparser": 13126, + "have": 13127, + "Ġneglected": 13128, + "func": 13129, + "apsed": 13130, + "Ġsays": 13131, + "adata": 13132, + "binom": 13133, + "Case": 13134, + "Ġreporter": 13135, + "Sn": 13136, + "Ġmaximize": 13137, + "Ġbifurc": 13138, + "ĠCNS": 13139, + "ĠOlymp": 13140, + "Ġdeclare": 13141, + "Ġencoder": 13142, + "Ġabelian": 13143, + "Ġsingularities": 13144, + "Ġech": 13145, + "Ψ": 13146, + "Ġproto": 13147, + "Ġphag": 13148, + "Ġpolyg": 13149, + "Ġbott": 13150, + "Ġadipose": 13151, + "uing": 13152, + "jk": 13153, + "uchy": 13154, + "ĠStudent": 13155, + "Ġnanow": 13156, + "Ġthym": 13157, + "Ed": 13158, + "End": 13159, + "Ġtransforms": 13160, + "ĠPCA": 13161, + "kern": 13162, + "regn": 13163, + "Ġcomment": 13164, + "ĠLL": 13165, + "elles": 13166, + "Ġengagement": 13167, + "ĠPeter": 13168, + "ISPR": 13169, + "ĠChannel": 13170, + "iny": 13171, + "Ġbundles": 13172, + "Ald": 13173, + "Ġpublications": 13174, + "TG": 13175, + "stra": 13176, + "Ġfear": 13177, + "Ġretic": 13178, + "plements": 13179, + "Ġcorpus": 13180, + "ĠCluster": 13181, + "ĠRate": 13182, + "Ġsimplest": 13183, + "acic": 13184, + "rbrack": 13185, + "Ġblow": 13186, + "Ġcompress": 13187, + "ĠDark": 13188, + "Ġpsychiatric": 13189, + "ĠConversely": 13190, + "Ġowing": 13191, + "Ġabsor": 13192, + "ĠHP": 13193, + "Ġcrude": 13194, + "equal": 13195, + "ĠArray": 13196, + "ĠRelative": 13197, + "Ġcombustion": 13198, + "Red": 13199, + "kt": 13200, + "ĠmA": 13201, + "Ġtex": 13202, + "porters": 13203, + "Ġdiffered": 13204, + "Ġaudio": 13205, + "zon": 13206, + "odi": 13207, + "Ġmacroscopic": 13208, + "acin": 13209, + "Ġzeros": 13210, + "Ġforeign": 13211, + "Ġduct": 13212, + "bow": 13213, + "worth": 13214, + "ĠRoad": 13215, + "rey": 13216, + "aceous": 13217, + "Ġblast": 13218, + "Ġgranul": 13219, + "Ġwing": 13220, + "Ġannotated": 13221, + "ĠFull": 13222, + "Ġinfluencing": 13223, + "vy": 13224, + "iazol": 13225, + "Ġpitch": 13226, + "Ġrehabilitation": 13227, + "ĠPrior": 13228, + "comit": 13229, + "mathtt": 13230, + "dia": 13231, + "ĠIon": 13232, + "Ġabuse": 13233, + "Ġharvested": 13234, + "Ġepidemic": 13235, + "Ġfilament": 13236, + "Ġnucleation": 13237, + "ĠKnowledge": 13238, + "rinos": 13239, + "Ġbent": 13240, + "Ġsquared": 13241, + "Ġhippocampal": 13242, + "ĠTG": 13243, + "ANT": 13244, + "modified": 13245, + "ario": 13246, + "ĠFace": 13247, + "Ġgrows": 13248, + "Ġfaults": 13249, + "virus": 13250, + "Ġpartitioning": 13251, + "airs": 13252, + "Ġhearing": 13253, + "Ġcongen": 13254, + "Ġrip": 13255, + "ĠCollabor": 13256, + "Ġinterviews": 13257, + "Ġhuge": 13258, + "Ġbreakdown": 13259, + "Ġmonthly": 13260, + "ĠCONCLUS": 13261, + "Each": 13262, + "Diff": 13263, + "Ġrelay": 13264, + "ĠMuse": 13265, + "oscopy": 13266, + "Ġrenew": 13267, + "gb": 13268, + "Ġbrid": 13269, + "Ġoutlined": 13270, + "orig": 13271, + "eat": 13272, + "ĠWithout": 13273, + "Ġspor": 13274, + "ĠTN": 13275, + "ĠJo": 13276, + "ĠAU": 13277, + "Not": 13278, + "Ġretin": 13279, + "ĠAngel": 13280, + "Ġtried": 13281, + "eyond": 13282, + "je": 13283, + "ĠRussian": 13284, + "ĠUnfortunately": 13285, + "ĠMeanwhile": 13286, + "ographs": 13287, + "Ġaccounting": 13288, + "ĠAβ": 13289, + "mb": 13290, + "Ġdopamine": 13291, + "ĠBriefly": 13292, + "ĠFrequency": 13293, + "Matrix": 13294, + "ĠJoseph": 13295, + "Ġexperts": 13296, + "Ġdrops": 13297, + "ĠRESULTS": 13298, + "Ġrectangular": 13299, + "athione": 13300, + "center": 13301, + "ĠLeft": 13302, + "inform": 13303, + "kins": 13304, + "Ġmil": 13305, + "ĠMah": 13306, + "Ġmedial": 13307, + "ĠCompany": 13308, + "Ġpassage": 13309, + "Ġleader": 13310, + "Ġscreened": 13311, + "eri": 13312, + "posites": 13313, + "rarily": 13314, + "Ġphone": 13315, + "ietic": 13316, + "Ġexpectations": 13317, + "ĠParticle": 13318, + "ĠMountain": 13319, + "Ġinterleukin": 13320, + "Ġfifth": 13321, + "Ġvast": 13322, + "Ġlogical": 13323, + "Ġterr": 13324, + "Ġcreates": 13325, + "Ġfinitely": 13326, + "Ġswim": 13327, + "Ġsupernatant": 13328, + "opathological": 13329, + "ĠUltra": 13330, + "ĠTy": 13331, + "Ġgrand": 13332, + "Ġconstitute": 13333, + "ologist": 13334, + "ĠBroad": 13335, + "aware": 13336, + "Ġvicinity": 13337, + "agulation": 13338, + "unsigned": 13339, + "ĠSize": 13340, + "ĠCognitive": 13341, + "Ġsuspected": 13342, + "Ġupl": 13343, + "Ġautoimmune": 13344, + "ĠSK": 13345, + "CB": 13346, + "Ġslices": 13347, + "ĠChi": 13348, + "Ġobservables": 13349, + "Ġhippocampus": 13350, + "sover": 13351, + "Ġfunding": 13352, + "Ġconformation": 13353, + "ĠQuestion": 13354, + "ĠSqu": 13355, + "ĠWill": 13356, + "Ġscattered": 13357, + "irty": 13358, + "Ġplaus": 13359, + "correlation": 13360, + "Ġventilation": 13361, + "ĠGenes": 13362, + "Ġbenign": 13363, + "Ġhetero": 13364, + "Status": 13365, + "angled": 13366, + "Ġbootstrap": 13367, + "Ġvaccines": 13368, + "Ġmicroorganisms": 13369, + "Ġvisits": 13370, + "Ġtheorems": 13371, + "drop": 13372, + "ĠTA": 13373, + "Ġcycling": 13374, + "Ġspectrometer": 13375, + "Ġgroundwater": 13376, + "Ġnanotubes": 13377, + "Ġjoints": 13378, + "ĠEll": 13379, + "Ġconsult": 13380, + "Ġwindows": 13381, + "Ġdisability": 13382, + "Ġgains": 13383, + "Ġdischarg": 13384, + "Ġheated": 13385, + "Ġafore": 13386, + "arying": 13387, + "incre": 13388, + "Ġaggressive": 13389, + "Ġhemod": 13390, + "arium": 13391, + "ĠInst": 13392, + "vm": 13393, + "Ġdroplet": 13394, + "ptive": 13395, + "viously": 13396, + "Ġstarch": 13397, + "Ġdf": 13398, + "osyl": 13399, + "Ġdonors": 13400, + "ĠUnlike": 13401, + "Ġalkaline": 13402, + "Ġintelligence": 13403, + "aa": 13404, + "Ġacceptance": 13405, + "Ġsliding": 13406, + "apses": 13407, + "ĠDiss": 13408, + "istan": 13409, + "auc": 13410, + "Ġbins": 13411, + "Ġmodulate": 13412, + "Ġmanage": 13413, + "outs": 13414, + "Ġsenes": 13415, + "Ġdifferentiate": 13416, + "Ġcounted": 13417, + "ASK": 13418, + "Ġantibacterial": 13419, + "Ġentered": 13420, + "Ġdisadvant": 13421, + "ĠSalmonella": 13422, + "Ġisotopic": 13423, + "Ġannounced": 13424, + "ĠBoard": 13425, + "Ġrestoration": 13426, + "Ġallevi": 13427, + "Ġprogramme": 13428, + "Ġalbumin": 13429, + "Ġcatalog": 13430, + "estine": 13431, + "Ġdifferently": 13432, + "Ġmolar": 13433, + "rödinger": 13434, + "ĠEvent": 13435, + "ministration": 13436, + "ĠSerum": 13437, + "ROM": 13438, + "kw": 13439, + "bot": 13440, + "Ġjets": 13441, + "ĠDouble": 13442, + "eler": 13443, + "Ġinfusion": 13444, + "Ġconsumed": 13445, + "ĠIron": 13446, + "ĠProcesses": 13447, + "Ġadmits": 13448, + "Ġjuris": 13449, + "ĠPeriod": 13450, + "Ġremodeling": 13451, + "alley": 13452, + "Ġenabling": 13453, + "Ġbackward": 13454, + "ĠMid": 13455, + "brevi": 13456, + "Ġclassify": 13457, + "Ġcrypt": 13458, + "Ġhelix": 13459, + "ĠJiang": 13460, + "Ġhoney": 13461, + "gestion": 13462, + "xc": 13463, + "Ġcoincides": 13464, + "ĠDN": 13465, + "Ġapoptotic": 13466, + "Ġinstall": 13467, + "ĠRever": 13468, + "ĠDoppler": 13469, + "icago": 13470, + "erals": 13471, + "Ġpie": 13472, + "ĠMars": 13473, + "ĠStaphylococcus": 13474, + "Ġnoting": 13475, + "Ġgenera": 13476, + "ĠIo": 13477, + "Ġhope": 13478, + "Ġpreserve": 13479, + "MAX": 13480, + "ynchron": 13481, + "Ġrup": 13482, + "Ġcomprising": 13483, + "ĠWay": 13484, + "Ġviolation": 13485, + "QR": 13486, + "Ġreflecting": 13487, + "Ġregularity": 13488, + "ĠSiO": 13489, + "ĠJun": 13490, + "Ġcommunications": 13491, + "rating": 13492, + "Ġfamiliar": 13493, + "Ġinstantaneous": 13494, + "Ġcortic": 13495, + "Ġapparently": 13496, + "XX": 13497, + "Ġexcitations": 13498, + "ĠAward": 13499, + "Num": 13500, + "ĠUN": 13501, + "Ġqubit": 13502, + "ĠAction": 13503, + "ĠFried": 13504, + "Ġeliminated": 13505, + "Ġaspir": 13506, + "hler": 13507, + "Ġdecoding": 13508, + "unov": 13509, + "Ġanalogue": 13510, + "ulmonary": 13511, + "Ġgeographic": 13512, + "Ġsort": 13513, + "ĠCRC": 13514, + "Aldrich": 13515, + "ĠkDa": 13516, + "ĠND": 13517, + "Ġsettle": 13518, + "exists": 13519, + "Ġstatistic": 13520, + "ĠBow": 13521, + "ĠCG": 13522, + "Ġorganizations": 13523, + "ĠMobile": 13524, + "Ġinvent": 13525, + "Ġincorporate": 13526, + "ĠFib": 13527, + "ordan": 13528, + "Ġcolleagues": 13529, + "ĠStation": 13530, + "Ġsen": 13531, + "Ġencaps": 13532, + "ĠRH": 13533, + "relim": 13534, + "Ġcarbonate": 13535, + "ĠNether": 13536, + "mem": 13537, + "EEE": 13538, + "Ġaforementioned": 13539, + "Ġpent": 13540, + "ĠSignal": 13541, + "Ġsuspended": 13542, + "Color": 13543, + "Ġspins": 13544, + "Ġproportions": 13545, + "ulty": 13546, + "Ġenrolled": 13547, + "ĠTEM": 13548, + "ĠReceptor": 13549, + "Ġprevalent": 13550, + "large": 13551, + "vs": 13552, + "Ġtruncated": 13553, + "Ġâĭħ": 13554, + "lm": 13555, + "anil": 13556, + "Ġannih": 13557, + "ĠGalaxy": 13558, + "eras": 13559, + "Ġepigenetic": 13560, + "Ġtooth": 13561, + "Ġcondensation": 13562, + "ĠTensor": 13563, + "Ġinorganic": 13564, + "ymers": 13565, + "uf": 13566, + "anese": 13567, + "aret": 13568, + "Ġarithmetic": 13569, + "âĨ": 13570, + "Ġtrying": 13571, + "Ġimplementing": 13572, + "xd": 13573, + "Ġillumination": 13574, + "ela": 13575, + "Ġdeficits": 13576, + "Ġspots": 13577, + "Ġdoesn": 13578, + "Ġresting": 13579, + "trained": 13580, + "Ġerosion": 13581, + "Ġgranular": 13582, + "Ġscar": 13583, + "Ġpollen": 13584, + "lie": 13585, + "Ġconvers": 13586, + "Ġdisturbances": 13587, + "ĠGod": 13588, + "Ġenlarg": 13589, + "ĠLate": 13590, + "ylase": 13591, + "Ġfacts": 13592, + "enty": 13593, + "ĠStreet": 13594, + "sequence": 13595, + "Ġvenous": 13596, + "ĠCheck": 13597, + "agg": 13598, + "Ġabsorbed": 13599, + "Ġcommit": 13600, + "sets": 13601, + "Ġdestroy": 13602, + "Ġbowel": 13603, + "Ġfinished": 13604, + "ĠFeed": 13605, + "Ġdoped": 13606, + "ĠAlb": 13607, + "ĠMitochond": 13608, + "Ġtheoretically": 13609, + "RI": 13610, + "Ġmeteor": 13611, + "ĠMG": 13612, + "Ġnation": 13613, + "ĠBasin": 13614, + "nik": 13615, + "Ġdepths": 13616, + "ĠMechanism": 13617, + "Ġmotifs": 13618, + "ĠHay": 13619, + "Ġmotivated": 13620, + "ĠCopy": 13621, + "ĠEastern": 13622, + "Ġpersistence": 13623, + "Ġrays": 13624, + "FB": 13625, + "andem": 13626, + "layers": 13627, + "eyer": 13628, + "ĠStrept": 13629, + "Ġregistration": 13630, + "ĠAntarctic": 13631, + "CV": 13632, + "ĠPap": 13633, + "ĠSpe": 13634, + "Ġsplicing": 13635, + "performance": 13636, + "Ġsemantics": 13637, + "Ġlocom": 13638, + "oblastoma": 13639, + "Ġmoney": 13640, + "Ġtransparent": 13641, + "Ġhr": 13642, + "ĠInteractions": 13643, + "Ġsap": 13644, + "Ġbiases": 13645, + "Ġteeth": 13646, + "ynolds": 13647, + "omethyl": 13648, + "ĠmV": 13649, + "Ġsolely": 13650, + "Ġorange": 13651, + "blast": 13652, + "ATIONS": 13653, + "call": 13654, + "opoietic": 13655, + "sided": 13656, + "ĠFox": 13657, + "ĠVideo": 13658, + "Ġinspection": 13659, + "Ġbuck": 13660, + "hesize": 13661, + "present": 13662, + "ĠAntib": 13663, + "Ġham": 13664, + "alam": 13665, + "ĠPG": 13666, + "ĠAE": 13667, + "Ġjoin": 13668, + "Ġmonocytes": 13669, + "estiv": 13670, + "Ġrandomised": 13671, + "Ġtranslocation": 13672, + "Ġincorporating": 13673, + "Ġprolifer": 13674, + "Ġodds": 13675, + "ITH": 13676, + "Ġran": 13677, + "Ġinstruction": 13678, + "Ġresolve": 13679, + "Ġft": 13680, + "ĠHead": 13681, + "Ġreagent": 13682, + "Ġadmitted": 13683, + "human": 13684, + "posure": 13685, + "ĠCha": 13686, + "ĠFr": 13687, + "Ġbroadcast": 13688, + "Ġnutrients": 13689, + "nob": 13690, + "Ġnotable": 13691, + "ĠIGF": 13692, + "ĠClearly": 13693, + "Ġquarks": 13694, + "Ġeukary": 13695, + "ĠAdd": 13696, + "itosan": 13697, + "Ġinteractive": 13698, + "itting": 13699, + "ĠComputational": 13700, + "Ġdissolution": 13701, + "istribution": 13702, + "product": 13703, + "ĠABC": 13704, + "olimits": 13705, + "biased": 13706, + "Ġtrapped": 13707, + "PK": 13708, + "ĠHPLC": 13709, + "rophot": 13710, + "zes": 13711, + "ourse": 13712, + "ĠHot": 13713, + "Ġrecipro": 13714, + "nolimits": 13715, + "ello": 13716, + "Ġassessments": 13717, + "ENTS": 13718, + "Ġalteration": 13719, + "tw": 13720, + "Ġchaotic": 13721, + "ĠLoc": 13722, + "Ġcattle": 13723, + "Ray": 13724, + "Ġformally": 13725, + "leave": 13726, + "textstyle": 13727, + "Ġventral": 13728, + "ĠWilliams": 13729, + "ĠPeople": 13730, + "ixing": 13731, + "ĠTherapy": 13732, + "Ġiii": 13733, + "ĠDT": 13734, + "Ġbic": 13735, + "Ġspheres": 13736, + "Ġvisc": 13737, + "Ġestablishment": 13738, + "Ġdescriptions": 13739, + "ĠAverage": 13740, + "Ġtour": 13741, + "ĠInfection": 13742, + "ĠLicense": 13743, + "Ġprepare": 13744, + "Hs": 13745, + "finite": 13746, + "rium": 13747, + "oreg": 13748, + "entry": 13749, + "Ġdisks": 13750, + "Ġelongation": 13751, + "cpu": 13752, + "ĠCharles": 13753, + "FIGURE": 13754, + "ston": 13755, + "ĠObservations": 13756, + "Add": 13757, + "ĠTask": 13758, + "atomy": 13759, + "igration": 13760, + "ĠDatabase": 13761, + "ĠTexas": 13762, + "Ġphyt": 13763, + "ller": 13764, + "conjug": 13765, + "onald": 13766, + "Ġheavily": 13767, + "Ġsple": 13768, + "Ġassist": 13769, + "ĠCp": 13770, + "Ġhappen": 13771, + "uv": 13772, + "ĠUniverse": 13773, + "ĠGPS": 13774, + "WE": 13775, + "Xi": 13776, + "Ġadministr": 13777, + "strong": 13778, + "Ġmagnitudes": 13779, + "Ġsimplify": 13780, + "Ġelegans": 13781, + "esh": 13782, + "ĠBody": 13783, + "ĠNetherlands": 13784, + "ï": 13785, + "ometers": 13786, + "Bo": 13787, + "FM": 13788, + "ĠNiger": 13789, + "plus": 13790, + "instance": 13791, + "Ġdistress": 13792, + "Organ": 13793, + "Cas": 13794, + "Ġsymplectic": 13795, + "Ġbreaks": 13796, + "ÑĤ": 13797, + "Ġfermion": 13798, + "emporal": 13799, + "Ġsomatic": 13800, + "event": 13801, + "neut": 13802, + "lammation": 13803, + "ĠLibrary": 13804, + "Ġmultiplic": 13805, + "ĠInstr": 13806, + "ethel": 13807, + "urys": 13808, + "Ġhelped": 13809, + "Ġcollege": 13810, + "Ġcartilage": 13811, + "Ġrpm": 13812, + "western": 13813, + "resis": 13814, + "Ġlobe": 13815, + "QL": 13816, + "Input": 13817, + "Ġemphasis": 13818, + "best": 13819, + "Ġtotally": 13820, + "ĠMETHOD": 13821, + "ĠFa": 13822, + "ĠReduction": 13823, + "icious": 13824, + "Ġimplantation": 13825, + "potential": 13826, + "problem": 13827, + "Ġobtains": 13828, + "urons": 13829, + "Ġconstructing": 13830, + "ĠMusic": 13831, + "Ġcancell": 13832, + "Ġnews": 13833, + "ĠChapter": 13834, + "Ġlabelled": 13835, + "Ġzebrafish": 13836, + "ĠSolid": 13837, + "Ġglutamate": 13838, + "ĉĉĉĉĉ": 13839, + "Ġchapter": 13840, + "ĠPresident": 13841, + "Min": 13842, + "Ġatrial": 13843, + "cp": 13844, + "fi": 13845, + "final": 13846, + "Ġtok": 13847, + "Ġeffector": 13848, + "Ġspine": 13849, + "Ġidentities": 13850, + "isco": 13851, + "olis": 13852, + "ĠCle": 13853, + "Ġinvariants": 13854, + "Path": 13855, + "ĠGon": 13856, + "factory": 13857, + "Ġexogenous": 13858, + "ĠMAPK": 13859, + "Ġanswers": 13860, + "Ġgetting": 13861, + "Rs": 13862, + "IH": 13863, + "ĠDefine": 13864, + "ĠConvolutional": 13865, + "Ġgeometrical": 13866, + "ĠInput": 13867, + "Ġà": 13868, + "Ġattenuated": 13869, + "Ġradicals": 13870, + "ĠAcademy": 13871, + "ãĥ": 13872, + "ichlet": 13873, + "Ġtorus": 13874, + "ĠTheoretical": 13875, + "ĠTD": 13876, + "Ġantiv": 13877, + "onge": 13878, + "Ġintravenous": 13879, + "Ġhypoth": 13880, + "Ġwastewater": 13881, + "ĠFlo": 13882, + "Ġporosity": 13883, + "Ġpall": 13884, + "aci": 13885, + "Ġrecordings": 13886, + "Ġeating": 13887, + "ĠDW": 13888, + "unting": 13889, + "ĠDim": 13890, + "Ġemitted": 13891, + "ĠJoint": 13892, + "ofib": 13893, + "Ġearthquake": 13894, + "Ġmunic": 13895, + "Ġreductions": 13896, + "Ġconjunction": 13897, + "ĠLocation": 13898, + "Ġestablishing": 13899, + "ĠMathematical": 13900, + "ĠSolution": 13901, + "buffer": 13902, + "arin": 13903, + "iley": 13904, + "ĠCommission": 13905, + "ĠGABA": 13906, + "ĠMuseum": 13907, + "Ġverb": 13908, + "lecules": 13909, + "infection": 13910, + "Ġinsect": 13911, + "iser": 13912, + "Ġprovision": 13913, + "Ġagreed": 13914, + "Ġafford": 13915, + "theory": 13916, + "knowledge": 13917, + "Protein": 13918, + "Ġkernels": 13919, + "Ġderm": 13920, + "Ġwish": 13921, + "Ġvox": 13922, + "Scale": 13923, + "hu": 13924, + "Ġcounterparts": 13925, + "ĠRoss": 13926, + "Ġunp": 13927, + "ĠOnline": 13928, + "Ġtransporter": 13929, + "Graph": 13930, + "Ġuter": 13931, + "Ġminute": 13932, + "Ġautomorphism": 13933, + "iltr": 13934, + "ĠRespons": 13935, + "ĠSym": 13936, + "Ġfactorization": 13937, + "sem": 13938, + "Ġmediates": 13939, + "Ġunexpected": 13940, + "Ġorganism": 13941, + "Ġattempted": 13942, + "aran": 13943, + "venue": 13944, + "etheless": 13945, + "Ġnoticed": 13946, + "ĠInvestigation": 13947, + "Ġcareg": 13948, + "Ġgrouped": 13949, + "orbit": 13950, + "Ġshortest": 13951, + "Ġbroader": 13952, + "ĠMIM": 13953, + "rises": 13954, + "veloper": 13955, + "ĠHi": 13956, + "ĠkHz": 13957, + "Ġbeads": 13958, + "Ġphyto": 13959, + "ĠDoes": 13960, + "Ġmammals": 13961, + "Ġrefined": 13962, + "volume": 13963, + "Ser": 13964, + "Ġresistivity": 13965, + "Ġterrestrial": 13966, + "Ġaxi": 13967, + "ifluor": 13968, + "Ġ£": 13969, + "Ġvice": 13970, + "ĠKel": 13971, + "VM": 13972, + "ĠTown": 13973, + "adm": 13974, + "plates": 13975, + "Ġholomorphic": 13976, + "ĠRib": 13977, + "ĠSB": 13978, + "ĠTemporal": 13979, + "src": 13980, + "Ġupdates": 13981, + "Ġseek": 13982, + "endix": 13983, + "oretic": 13984, + "warz": 13985, + "Ġroutes": 13986, + "Ġstanding": 13987, + "ĠÃģ": 13988, + "Ġclassic": 13989, + "Ġpale": 13990, + "lections": 13991, + "Ġclassifiers": 13992, + "Ġpathophys": 13993, + "Ġmounted": 13994, + "Ġdesignated": 13995, + "Ġvideos": 13996, + "Ġincoming": 13997, + "Ġguarantees": 13998, + "Ġparasites": 13999, + "ĠBacillus": 14000, + "four": 14001, + "Ġâ΍": 14002, + "Ġcommutative": 14003, + "stackrel": 14004, + "ĠBanach": 14005, + "Ġdealing": 14006, + "emporary": 14007, + "Multi": 14008, + "otomy": 14009, + "reting": 14010, + "Ġnond": 14011, + "ĠConference": 14012, + "tzmann": 14013, + "Ġphosphorus": 14014, + "Ġchemicals": 14015, + "Ġdispar": 14016, + "degree": 14017, + "Ġarbitrarily": 14018, + "rocyte": 14019, + "Ġparabolic": 14020, + "Ġdimensionless": 14021, + "Ġosm": 14022, + "Ġphonon": 14023, + "tiary": 14024, + "ĠSect": 14025, + "ophysical": 14026, + "ĠMapping": 14027, + "bis": 14028, + "ĠCommunication": 14029, + "Ġmimic": 14030, + "Ġregulators": 14031, + "Ġneutrophils": 14032, + "fn": 14033, + "ĠImportantly": 14034, + "Ġmere": 14035, + "Ġconfirms": 14036, + "agram": 14037, + "Ġattend": 14038, + "ungal": 14039, + "ĠGroups": 14040, + "Ġzo": 14041, + "Ġmouth": 14042, + "Ġsteep": 14043, + "Ġprevented": 14044, + "Ġdepressive": 14045, + "acies": 14046, + "ĠLS": 14047, + "Ġnitric": 14048, + "Ġvisualized": 14049, + "Ġtranscriptome": 14050, + "Ġgait": 14051, + "ercury": 14052, + "Ġshot": 14053, + "ĠVen": 14054, + "Ġexchang": 14055, + "Ġintention": 14056, + "ĠTang": 14057, + "Ġfavour": 14058, + "veolar": 14059, + "Ġpermission": 14060, + "Ġhabitats": 14061, + "Ġmaize": 14062, + "inct": 14063, + "Ġtelevision": 14064, + "rystals": 14065, + "ĠRadi": 14066, + "Ġflavon": 14067, + "Ġcann": 14068, + "iota": 14069, + "ĠOT": 14070, + "pic": 14071, + "Rad": 14072, + "titial": 14073, + "ĠOrth": 14074, + "stellar": 14075, + "ĠKine": 14076, + "Ġnavigation": 14077, + "fast": 14078, + "ĠCRISPR": 14079, + "Ġkinematic": 14080, + "Ġsearching": 14081, + "Ġmicrom": 14082, + "Ġinstalled": 14083, + "ĠTaiwan": 14084, + "ila": 14085, + "rf": 14086, + "riage": 14087, + "plinary": 14088, + "Ġecho": 14089, + "rav": 14090, + "ĠLes": 14091, + "create": 14092, + "Ġubiquit": 14093, + "Ġprecursors": 14094, + "KE": 14095, + "Ġdivide": 14096, + "Ġlnc": 14097, + "ĠConstruction": 14098, + "anic": 14099, + "estim": 14100, + "isters": 14101, + "Ġfeet": 14102, + "ariant": 14103, + "ĠSchw": 14104, + "Ġexclude": 14105, + "Ġvolcan": 14106, + "ĠOverview": 14107, + "Ġyr": 14108, + "olk": 14109, + "Ġ©": 14110, + "ĠFE": 14111, + "Ġspermat": 14112, + "Ġcapacitance": 14113, + "ĠSchrödinger": 14114, + "ĠGE": 14115, + "Ġcalibrated": 14116, + "SEM": 14117, + "Ġlattices": 14118, + "plier": 14119, + "Arg": 14120, + "ĠNT": 14121, + "ĠEnhanced": 14122, + "Ġbrom": 14123, + "Ġmultip": 14124, + "Ġcertified": 14125, + "Ġislands": 14126, + "Ġcyst": 14127, + "Ġaltitude": 14128, + "edef": 14129, + "Ġconstrain": 14130, + "Ġsatisfactory": 14131, + "Ġspecialized": 14132, + "Ġjunctions": 14133, + "Ġcoronavirus": 14134, + "udge": 14135, + "exc": 14136, + "Ġalt": 14137, + "ĠBacterial": 14138, + "Ġseasons": 14139, + "ĠLM": 14140, + "Ġhistogram": 14141, + "Ġsolvents": 14142, + "average": 14143, + "Ġcardinal": 14144, + "chrom": 14145, + "python": 14146, + "dered": 14147, + "enia": 14148, + "ĠGH": 14149, + "ĠEss": 14150, + "____": 14151, + "ĠPak": 14152, + "sized": 14153, + "ĠHg": 14154, + "Ġelif": 14155, + "ĠSchematic": 14156, + "Ġcytoplasm": 14157, + "ĠFort": 14158, + "ania": 14159, + "Ġcareful": 14160, + "ĠDual": 14161, + "Ġtranslated": 14162, + "Ġnasal": 14163, + "Inv": 14164, + "Ġdaughter": 14165, + "Ġemphasize": 14166, + "modules": 14167, + "Ġlives": 14168, + "Ġhomotopy": 14169, + "Ġbot": 14170, + "Ġdisordered": 14171, + "mato": 14172, + "Second": 14173, + "Ġclaimed": 14174, + "addle": 14175, + "Ġinterfacial": 14176, + "Ġviscous": 14177, + "Ġdestination": 14178, + "ĠPlanck": 14179, + "Ġabsorbance": 14180, + "Ġvolatile": 14181, + "Ġstorm": 14182, + "Ġcarboxyl": 14183, + "ĠBank": 14184, + "ĠPack": 14185, + "Ġscaffold": 14186, + "tebr": 14187, + "ipot": 14188, + "Ġtumours": 14189, + "ĠGol": 14190, + "Ġelectrophoresis": 14191, + "Ġrealize": 14192, + "Ġconstituents": 14193, + "Sol": 14194, + "ĠEvery": 14195, + "Ġmediate": 14196, + "Ġcoincide": 14197, + "Ġexploit": 14198, + "Ġmonoton": 14199, + "measure": 14200, + "Ġsupplied": 14201, + "racellular": 14202, + "Ġferro": 14203, + "Ġpurs": 14204, + "erentially": 14205, + "trast": 14206, + "ĠRB": 14207, + "Ġdissem": 14208, + "asy": 14209, + "Ġrelating": 14210, + "null": 14211, + "uates": 14212, + "constant": 14213, + "ĠContinuous": 14214, + "Ġgeometries": 14215, + "rust": 14216, + "ĠSTR": 14217, + "cluster": 14218, + "Ġprogenitor": 14219, + "ĠCSF": 14220, + "ĠYam": 14221, + "ĠReynolds": 14222, + "ĠMY": 14223, + "ĠKO": 14224, + "ĠWalk": 14225, + "ariable": 14226, + "inder": 14227, + "ĠRight": 14228, + "ĠAlgebra": 14229, + "ĠWik": 14230, + "Ġinactivation": 14231, + "tmp": 14232, + "access": 14233, + "ĠLater": 14234, + "Ġmicrobiome": 14235, + "Ġgeodesic": 14236, + "Ġrejection": 14237, + "uses": 14238, + "Ġhardness": 14239, + "Ġhydrodynamic": 14240, + "Ġvanish": 14241, + "Ġpollut": 14242, + "amycin": 14243, + "ĠÏŃ": 14244, + "ipitation": 14245, + "Ġaugmented": 14246, + "ĠTT": 14247, + "aval": 14248, + "Ġencode": 14249, + "Ġtoxin": 14250, + "eto": 14251, + "ighbor": 14252, + "addr": 14253, + "Ġdamaged": 14254, + "oi": 14255, + "Ġtransduction": 14256, + "Ġinteracts": 14257, + "ÃŃa": 14258, + "ĠCall": 14259, + "riends": 14260, + "ĠMonitoring": 14261, + "ĠVariation": 14262, + "Ġôı¼": 14263, + "Ġdich": 14264, + "Ġspars": 14265, + "align": 14266, + "Ġanatomical": 14267, + "Ġcentrifuged": 14268, + "urally": 14269, + "ĠZr": 14270, + "ĠCarl": 14271, + "Recall": 14272, + "Ġopinion": 14273, + "Ġera": 14274, + "Ġdrainage": 14275, + "Ġmicroarray": 14276, + "status": 14277, + "umental": 14278, + "Ġcomprises": 14279, + "pressure": 14280, + "Ġpractition": 14281, + "mac": 14282, + "Ġcongr": 14283, + "urnal": 14284, + "ĠAPI": 14285, + "ĠLR": 14286, + "Ġtransfection": 14287, + "Ġslopes": 14288, + "ĠCode": 14289, + "Ġphil": 14290, + "bool": 14291, + "Ws": 14292, + "ĠâĻ": 14293, + "Ġassociate": 14294, + "otoxicity": 14295, + "rade": 14296, + "ĠMiller": 14297, + "ĠϪ": 14298, + "Ġshorten": 14299, + "Ġadditionally": 14300, + "ĠEffective": 14301, + "Ġsupervised": 14302, + "Ġelabor": 14303, + "ĠCellular": 14304, + "Ġtell": 14305, + "ĠRC": 14306, + "save": 14307, + "imid": 14308, + "Ġratings": 14309, + "ĠTaking": 14310, + "Ġapproval": 14311, + "Ġpenalty": 14312, + "KK": 14313, + "context": 14314, + "aks": 14315, + "pecific": 14316, + "Ġtempor": 14317, + "Ġupregulation": 14318, + "VAL": 14319, + "Ġencodes": 14320, + "inin": 14321, + "Ġnotes": 14322, + "ĠForest": 14323, + "Ġcombinatorial": 14324, + "ymptotic": 14325, + "Ġsquamous": 14326, + "ĠAsh": 14327, + "ourn": 14328, + "Ġmyeloid": 14329, + "elines": 14330, + "Bio": 14331, + "Ġbreed": 14332, + "ĠRub": 14333, + "uzz": 14334, + "Ġsinglet": 14335, + "enna": 14336, + "Ġcritically": 14337, + "dig": 14338, + "disci": 14339, + "Ġdropped": 14340, + "Ġlipoprotein": 14341, + "ĠEt": 14342, + "Ġnov": 14343, + "ophen": 14344, + "Ġancient": 14345, + "Base": 14346, + "Ġsmoothing": 14347, + "itives": 14348, + "pine": 14349, + "Ġsolver": 14350, + "perm": 14351, + "ĠHome": 14352, + "Ġazim": 14353, + "lVert": 14354, + "Ġtransportation": 14355, + "Ġdex": 14356, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 14357, + "opathic": 14358, + "experim": 14359, + "âĢ¢âĢ¢": 14360, + "perfusion": 14361, + "Ġdoi": 14362, + "ĠLact": 14363, + "Ġhepatocellular": 14364, + "Ġmismatch": 14365, + "Ġadenocarcinoma": 14366, + "ĠPain": 14367, + "Ġspr": 14368, + "Ġconfinement": 14369, + "Ġexceeds": 14370, + "Ġhash": 14371, + "ĠComparing": 14372, + "ĠSensor": 14373, + "Ġfiring": 14374, + "kes": 14375, + "vir": 14376, + "inea": 14377, + "affected": 14378, + "Ġmodelled": 14379, + "Ġether": 14380, + "Ġsuffer": 14381, + "â̲â̲": 14382, + "оÐ": 14383, + "ĠBir": 14384, + "Äģ": 14385, + "Ġsecreted": 14386, + "Ġcatheter": 14387, + "Ġyouth": 14388, + "expl": 14389, + "ĠDar": 14390, + "ĠWHO": 14391, + "Ġfoundation": 14392, + "Ġhydraulic": 14393, + "ĠCarol": 14394, + "SSION": 14395, + "Ġá¹": 14396, + "feld": 14397, + "avor": 14398, + "Ġpasses": 14399, + "visiae": 14400, + "Ġapplicability": 14401, + "Ġnested": 14402, + "Fl": 14403, + "ĠCatal": 14404, + "Ġmicroenvironment": 14405, + "labels": 14406, + "Ġcrystallization": 14407, + "Info": 14408, + "Ġpositioning": 14409, + "Ġtriangles": 14410, + "Ġtryp": 14411, + "ĠTransition": 14412, + "Ġsett": 14413, + "Ġneurot": 14414, + "Mon": 14415, + "Ġdroplets": 14416, + "ĠART": 14417, + "Ġcorne": 14418, + "Ġmultiplicity": 14419, + "Ġeccentric": 14420, + "Ġiv": 14421, + "ĠMatter": 14422, + "learning": 14423, + "electro": 14424, + "ĠWeyl": 14425, + "Ġdecide": 14426, + "ĠWr": 14427, + "ĠHierarch": 14428, + "Ġapical": 14429, + "Ġfailures": 14430, + "Ġdigestion": 14431, + "MIC": 14432, + "Ġgeographical": 14433, + "ĠElement": 14434, + "ĠThough": 14435, + "Ġchron": 14436, + "limited": 14437, + "ĠDISC": 14438, + "ĠArchitecture": 14439, + "Ġvibrational": 14440, + "ĠVarious": 14441, + "Ġdynamically": 14442, + "aked": 14443, + "Ġconvenience": 14444, + "ĠIsra": 14445, + "ĠMDA": 14446, + "itic": 14447, + "Au": 14448, + "Ġassistance": 14449, + "ventional": 14450, + "midt": 14451, + "ospor": 14452, + "Following": 14453, + "Ġinferior": 14454, + "Ġnickel": 14455, + "raine": 14456, + "paren": 14457, + "Ġtitanium": 14458, + "Field": 14459, + "Ġhoc": 14460, + "ĠCauchy": 14461, + "ĠMcC": 14462, + "ĠScreen": 14463, + "Ġneglect": 14464, + "classes": 14465, + "ĠIF": 14466, + "Ġstratified": 14467, + "enses": 14468, + "ĠPlate": 14469, + "ozoic": 14470, + "Ġinstitutions": 14471, + "ĠThose": 14472, + "Ġgenerations": 14473, + "transform": 14474, + "Ġpartitions": 14475, + "Rxiv": 14476, + "enth": 14477, + "Ġstic": 14478, + "olith": 14479, + "ĠFem": 14480, + "Ġagar": 14481, + "beam": 14482, + "Ġprotons": 14483, + "LU": 14484, + "Ġworkload": 14485, + "Ġminerals": 14486, + "Ġmt": 14487, + "lla": 14488, + "ĠPharmac": 14489, + "Ġconverter": 14490, + "ĠMechanical": 14491, + "Ġflavor": 14492, + "Ġphosphatase": 14493, + "Ġsums": 14494, + "PCs": 14495, + "Ġisoforms": 14496, + "igroup": 14497, + "pyr": 14498, + "features": 14499, + "Ġperc": 14500, + "Ġcompleteness": 14501, + "Ġforests": 14502, + "Ġdividing": 14503, + "ĠLipschitz": 14504, + "periodic": 14505, + "Ġrecycl": 14506, + "ĠNag": 14507, + "Ġtwin": 14508, + "eptides": 14509, + "Ġcohor": 14510, + "Ġsearches": 14511, + "eated": 14512, + "Hg": 14513, + "ĠPU": 14514, + "ĠTree": 14515, + "allic": 14516, + "PF": 14517, + "Ġappendix": 14518, + "ĠCov": 14519, + "Ġchecking": 14520, + "Ġbackbone": 14521, + "Thermo": 14522, + "Ġactivating": 14523, + "ĠVictor": 14524, + "Ġcritic": 14525, + "ĠLem": 14526, + "groups": 14527, + "REG": 14528, + "ĠOcc": 14529, + "SCC": 14530, + "ĠXRD": 14531, + "ĠValues": 14532, + "Ġsubtype": 14533, + "Ġstretching": 14534, + "ORM": 14535, + "some": 14536, + "Ġflip": 14537, + "Ġphenolic": 14538, + "Ġkilled": 14539, + "Ġsequenced": 14540, + "uscular": 14541, + "abin": 14542, + "Ġquadr": 14543, + "Ġtranslational": 14544, + "Ġsolids": 14545, + "direct": 14546, + "Ġpromotion": 14547, + "Ġcohorts": 14548, + "ĠClimate": 14549, + "ĠOld": 14550, + "ĠSir": 14551, + "gue": 14552, + "strate": 14553, + "ĠPoss": 14554, + "Ġreceives": 14555, + "ĠValidation": 14556, + "uctive": 14557, + "Ġcerevisiae": 14558, + "Gu": 14559, + "isis": 14560, + "ceil": 14561, + "ĠPearson": 14562, + "ĠPrelim": 14563, + "ĠGran": 14564, + "CSF": 14565, + "Ġsterile": 14566, + "ofluorescence": 14567, + "bad": 14568, + "Ġcolored": 14569, + "compass": 14570, + "equation": 14571, + "jan": 14572, + "Ġconditioning": 14573, + "Ġvoice": 14574, + "Ġmening": 14575, + "Ġgranted": 14576, + "Ġrenormalization": 14577, + "ĠLimit": 14578, + "thi": 14579, + "Ġaperture": 14580, + "Ġdosage": 14581, + "directed": 14582, + "ĠBreast": 14583, + "ocular": 14584, + "bearing": 14585, + "sal": 14586, + "ascul": 14587, + "upervised": 14588, + "Ġmonolayer": 14589, + "Ġmembership": 14590, + "ĠWireless": 14591, + "show": 14592, + "ĠMedia": 14593, + "ĠVL": 14594, + "essel": 14595, + "Ġdecoder": 14596, + "ĠMF": 14597, + "ĠComposition": 14598, + "ĠClark": 14599, + "Point": 14600, + "ĠNano": 14601, + "ĠDeg": 14602, + "NL": 14603, + "ĠBox": 14604, + "Ġexploring": 14605, + "molecular": 14606, + "Other": 14607, + "ĠDiabetes": 14608, + "height": 14609, + "Ġkinases": 14610, + "Ġadjusting": 14611, + "Ġsports": 14612, + "offs": 14613, + "ĠIEEE": 14614, + "Ġtil": 14615, + "ĠIntra": 14616, + "Ġplanets": 14617, + "ĠEpidem": 14618, + "Ġtomato": 14619, + "Ġscaffolds": 14620, + "ĠMetabol": 14621, + "ĠGeometry": 14622, + "imetry": 14623, + "ĠTen": 14624, + "thread": 14625, + "ohex": 14626, + "Ġproposes": 14627, + "prim": 14628, + "ĠParty": 14629, + "Ġquarter": 14630, + "ĠShi": 14631, + "Ġaberr": 14632, + "ĠIntr": 14633, + "Ġdirector": 14634, + "affe": 14635, + "ĠSus": 14636, + "ensors": 14637, + "Ele": 14638, + "Ġpoles": 14639, + "Additional": 14640, + "Ġbypass": 14641, + "catenin": 14642, + "Ġundertaken": 14643, + "imation": 14644, + "opor": 14645, + "Ġpreserving": 14646, + "Ġmultiplex": 14647, + "ĠRepresentative": 14648, + "sis": 14649, + "ĠAG": 14650, + "achy": 14651, + "Ġfruits": 14652, + "Ġreconstruct": 14653, + "ensen": 14654, + "Ġstrongest": 14655, + "Ġscav": 14656, + "ĠCheng": 14657, + "ĠCoron": 14658, + "ĠObservation": 14659, + "ĠAch": 14660, + "ĠGeorg": 14661, + "ĠSVM": 14662, + "ĠChern": 14663, + "Ġreversal": 14664, + "via": 14665, + "imp": 14666, + "Ġdeployment": 14667, + "ĠHad": 14668, + "Ġcircumstances": 14669, + "obi": 14670, + "Ġcurved": 14671, + "Induced": 14672, + "ĠPositive": 14673, + "imb": 14674, + "ĠParis": 14675, + "ĠStein": 14676, + "icz": 14677, + "ĠCath": 14678, + "Ġdrawing": 14679, + "tory": 14680, + "Ġcontinental": 14681, + "Ġquantitatively": 14682, + "acerb": 14683, + "Ġnorms": 14684, + "ĠBE": 14685, + "Several": 14686, + "door": 14687, + "Ġplateau": 14688, + "Gal": 14689, + "Ġcivil": 14690, + "ĠFix": 14691, + "LAB": 14692, + "occal": 14693, + "Ġsorted": 14694, + "ĠâĢĿ": 14695, + "Ġediting": 14696, + "ĠChristian": 14697, + "Ġclarify": 14698, + "Ġwaveguide": 14699, + "bell": 14700, + "Ġdeduced": 14701, + "odec": 14702, + "utrition": 14703, + "Ġcompressive": 14704, + "ĠEU": 14705, + "ĠRegression": 14706, + "Ġranked": 14707, + "Ġestimators": 14708, + "Ġabilities": 14709, + "Ġbeliefs": 14710, + "three": 14711, + "ĠâĩĴ": 14712, + "rology": 14713, + "Ġautonomous": 14714, + "ĠSz": 14715, + "schem": 14716, + "ĠALT": 14717, + "ĠPatterns": 14718, + "Ġexon": 14719, + "Ġlifestyle": 14720, + "fill": 14721, + "ĠCAR": 14722, + "ĠDomains": 14723, + "Ġpaid": 14724, + "Ġtab": 14725, + "ĠCohen": 14726, + "airy": 14727, + "Ġsheep": 14728, + "Ġseaw": 14729, + "ĠKong": 14730, + "gas": 14731, + "Ġreserved": 14732, + "Ġresil": 14733, + "Ġobl": 14734, + "carbox": 14735, + "ĠGovernment": 14736, + "upper": 14737, + "racting": 14738, + "Ġgangl": 14739, + "ĠRV": 14740, + "Ġbronch": 14741, + "Methods": 14742, + "ĠLiver": 14743, + "Ġguess": 14744, + "charomy": 14745, + "ICE": 14746, + "Ġcongenital": 14747, + "Ġka": 14748, + "Ġspanning": 14749, + "ĠRecomm": 14750, + "ea": 14751, + "Ġconvention": 14752, + "Ġsheets": 14753, + "Ġthermo": 14754, + "Ġqualitatively": 14755, + "Ġoxides": 14756, + "Ġcongru": 14757, + "ĠJer": 14758, + "Ġpreservation": 14759, + "ĠBT": 14760, + "ĠDMSO": 14761, + "Ġcomplication": 14762, + "Ġsurvivors": 14763, + "Ġreduct": 14764, + "Ġdescent": 14765, + "Ġsucrose": 14766, + "ĠCourt": 14767, + "Ġmetabolite": 14768, + "ĠMath": 14769, + "ĠSecurity": 14770, + "ĠNotably": 14771, + "ĠStem": 14772, + "Ġdwarf": 14773, + "bc": 14774, + "Ġrevis": 14775, + "ĠKl": 14776, + "ĠGh": 14777, + "Ġmanager": 14778, + "Ġinvestment": 14779, + "Ġmotility": 14780, + "Em": 14781, + "ĠMr": 14782, + "asic": 14783, + "ĠBos": 14784, + "Ġinspired": 14785, + "placian": 14786, + "Ġease": 14787, + "Ġtorsion": 14788, + "ĠDirichlet": 14789, + "Ġspleen": 14790, + "agation": 14791, + "onate": 14792, + "ĠTrial": 14793, + "Ġturnover": 14794, + "Ġselectively": 14795, + "ĠÍĴ": 14796, + "iano": 14797, + "Ġnontrivial": 14798, + "iasis": 14799, + "Ñģ": 14800, + "ĠGuo": 14801, + "Ġaddresses": 14802, + "Ġuniqueness": 14803, + "Ġwithdraw": 14804, + "riz": 14805, + "Ġcomputationally": 14806, + "Ġpersonality": 14807, + "AX": 14808, + "wenty": 14809, + "Ġgovern": 14810, + "berts": 14811, + "Ġrobots": 14812, + "Ġready": 14813, + "Ġdiets": 14814, + "lit": 14815, + "My": 14816, + "ĠReve": 14817, + "ĠLos": 14818, + "infrared": 14819, + "Ġintram": 14820, + "lated": 14821, + "plankton": 14822, + "ĠGrant": 14823, + "piper": 14824, + "Ġantennas": 14825, + "Ġbol": 14826, + "fp": 14827, + "ĠVit": 14828, + "Compar": 14829, + "oken": 14830, + "Ġkeys": 14831, + "ĠClub": 14832, + "inery": 14833, + "ĠFoot": 14834, + "Ġwarming": 14835, + "mond": 14836, + "Ġmiles": 14837, + "Ġspeaking": 14838, + "ĠIv": 14839, + "Ġconformational": 14840, + "ĠOk": 14841, + "Ġunified": 14842, + "Ġassembled": 14843, + "Ġinverted": 14844, + "Ġfelt": 14845, + "corresponding": 14846, + "ĠECM": 14847, + "ĠNSC": 14848, + "Ġindoor": 14849, + "gov": 14850, + "Ġantagonist": 14851, + "unched": 14852, + "ĠJava": 14853, + "ĠCombined": 14854, + "tivities": 14855, + "Ġalternating": 14856, + "ãĤ": 14857, + "ĠDiagnosis": 14858, + "Ġdistinction": 14859, + "leigh": 14860, + "ĠTogether": 14861, + "Ġparticipating": 14862, + "Ġglomer": 14863, + "oche": 14864, + "Ġcopyright": 14865, + "ĠGTP": 14866, + "ĠVar": 14867, + "Ġammonium": 14868, + "Ġfacilitates": 14869, + "Ġperfusion": 14870, + "ĠLB": 14871, + "full": 14872, + "Ġreti": 14873, + "iferase": 14874, + "Ġimmunosup": 14875, + "ĠImplementation": 14876, + "Ġpores": 14877, + "ĠBB": 14878, + "ĠBud": 14879, + "ĠVO": 14880, + "ĠVo": 14881, + "Ġphysician": 14882, + "ĠAUC": 14883, + "Ġcertainly": 14884, + "μm": 14885, + "ĠKol": 14886, + "Ġwrap": 14887, + "middle": 14888, + "Ġsilencing": 14889, + "Ġfreshwater": 14890, + "igan": 14891, + "area": 14892, + "AI": 14893, + "Ġmicrotub": 14894, + "Ġarranged": 14895, + "structive": 14896, + "ĠRegular": 14897, + "ĠFile": 14898, + "alks": 14899, + "Ġplain": 14900, + "Ġintegrable": 14901, + "ĠMembrane": 14902, + "istors": 14903, + "Ġaquatic": 14904, + "Ġworkflow": 14905, + "ĠGer": 14906, + "ulant": 14907, + "Ġactivates": 14908, + "Term": 14909, + "ĠUpon": 14910, + "ĠPut": 14911, + "Var": 14912, + "ĠOD": 14913, + "half": 14914, + "Ġulcer": 14915, + "ĠBO": 14916, + "ĠGy": 14917, + "rences": 14918, + "Ġpurity": 14919, + "Ġarrive": 14920, + "ĠSignificant": 14921, + "ĠMAC": 14922, + "ĠOtherwise": 14923, + "oured": 14924, + "Ġtan": 14925, + "ĠRL": 14926, + "ĠQTL": 14927, + "Ġammonia": 14928, + "vmode": 14929, + "Ġmagnesium": 14930, + "Ġacknowled": 14931, + "Ġalternatives": 14932, + "idents": 14933, + "rVert": 14934, + "ĠComplete": 14935, + "ĠBone": 14936, + "yer": 14937, + "ĠBab": 14938, + "Ġeut": 14939, + "Ġnovo": 14940, + "disciplinary": 14941, + "Ġseverely": 14942, + "uki": 14943, + "ĠPN": 14944, + "leavevmode": 14945, + "clip": 14946, + "ĠSynd": 14947, + "ĠMIMO": 14948, + "adequ": 14949, + "ĠArctic": 14950, + "lycer": 14951, + "RET": 14952, + "ensed": 14953, + "coated": 14954, + "VP": 14955, + "Ġlakes": 14956, + "Ġchurch": 14957, + "Ġhomologous": 14958, + "Ġoxidase": 14959, + "ĠAud": 14960, + "Ġincrement": 14961, + "Ġneutrinos": 14962, + "arbon": 14963, + "TYPE": 14964, + "izumab": 14965, + "utable": 14966, + "Ġimplying": 14967, + "ĠMotion": 14968, + "Ġâīĥ": 14969, + "Ġpages": 14970, + "Ġplausible": 14971, + "ĠNL": 14972, + "Ġisotop": 14973, + "ĠHyd": 14974, + "Att": 14975, + "lattice": 14976, + "shore": 14977, + "Ġsucceed": 14978, + "Ġsupposed": 14979, + "ĠTransmission": 14980, + "Dimensional": 14981, + "inguistic": 14982, + "Ġcontours": 14983, + "Ġconcomit": 14984, + "Ġagrees": 14985, + "ĠDani": 14986, + "quar": 14987, + "Ġshield": 14988, + "Ġozone": 14989, + "ĠTet": 14990, + "lbrack": 14991, + "Ġwat": 14992, + "Ġcytochrome": 14993, + "tailed": 14994, + "pix": 14995, + "Ġcoex": 14996, + "ĠView": 14997, + "odef": 14998, + "ĠWild": 14999, + "ĠLE": 15000, + "hop": 15001, + "Ġpointing": 15002, + "uncture": 15003, + "Ġecology": 15004, + "Ġbab": 15005, + "rea": 15006, + "ego": 15007, + "Ġviolence": 15008, + "ĠtRNA": 15009, + "ĠRN": 15010, + "pent": 15011, + "orel": 15012, + "ĠParallel": 15013, + "Ġdrives": 15014, + "nobreak": 15015, + "Ġholog": 15016, + "Ġprobable": 15017, + "Ġentering": 15018, + "Ġsink": 15019, + "Ġswelling": 15020, + "producing": 15021, + "âĨĴâĪŀ": 15022, + "ĠSafety": 15023, + "Ġanalyse": 15024, + "series": 15025, + "Ġdrivers": 15026, + "KS": 15027, + "ĠRMS": 15028, + "Ġgenetics": 15029, + "ĠFred": 15030, + "Ġsubm": 15031, + "Ġscientists": 15032, + "ĠFD": 15033, + "ĠSolutions": 15034, + "ĠFab": 15035, + "Ġencompass": 15036, + "commutative": 15037, + "Ġadiabatic": 15038, + "butyl": 15039, + "PEG": 15040, + "Ġαβ": 15041, + "ĠStan": 15042, + "Ġclustered": 15043, + "Ġholding": 15044, + "ĠBeck": 15045, + "ĠYan": 15046, + "Ġaster": 15047, + "Ġeconom": 15048, + "Ġignored": 15049, + "uro": 15050, + "yles": 15051, + "ubbles": 15052, + "Ġfate": 15053, + "Ġperceptions": 15054, + "Ġlin": 15055, + "én": 15056, + "Ġactu": 15057, + "Ġarsen": 15058, + "Ġba": 15059, + "epoch": 15060, + "ĠStim": 15061, + "Ġmedications": 15062, + "ECs": 15063, + "ĠMinistry": 15064, + "ĠPublisher": 15065, + "Ġdepri": 15066, + "Ġobstruction": 15067, + "ĠmRNAs": 15068, + "Ġbrother": 15069, + "Ġcrossover": 15070, + "ĠTurb": 15071, + "tation": 15072, + "Ġtank": 15073, + "ĠMem": 15074, + "Ġintestine": 15075, + "Ġmicroglia": 15076, + "ĠMaxwell": 15077, + "Ġjurisdic": 15078, + "Ġphenyl": 15079, + "hyper": 15080, + "ums": 15081, + "ĠHIF": 15082, + "ĠShen": 15083, + "Ġcheckpoint": 15084, + "ĠBrownian": 15085, + "ĠâĭĨ": 15086, + "ĠStrain": 15087, + "ĠExtraction": 15088, + "Ġbatteries": 15089, + "ĠPle": 15090, + "ĠConditions": 15091, + "Ġinconsistent": 15092, + "ĠHost": 15093, + "ypical": 15094, + "Ġcrops": 15095, + "alg": 15096, + "ĠFI": 15097, + "anta": 15098, + "Ġfounded": 15099, + "Ġmarks": 15100, + "distribution": 15101, + "Ġι": 15102, + "Ġhors": 15103, + "Ġsnap": 15104, + "WM": 15105, + "Ġmanifestations": 15106, + "empl": 15107, + "Ġproving": 15108, + "leading": 15109, + "ĠACE": 15110, + "ĠLED": 15111, + "channels": 15112, + "Ġlift": 15113, + "Function": 15114, + "inase": 15115, + "supervised": 15116, + "ĠUser": 15117, + "Ġphysiology": 15118, + "Ġlinking": 15119, + "pressed": 15120, + "Ġiff": 15121, + "ĠJim": 15122, + "Ġglutathione": 15123, + "ĠTI": 15124, + "Ġane": 15125, + "enosis": 15126, + "Ġcollections": 15127, + "Ġgenetically": 15128, + "ĠFilter": 15129, + "ĠChicago": 15130, + "ĠServices": 15131, + "Ġsupersymmetric": 15132, + "Ġstriking": 15133, + "Ġirrig": 15134, + "ococcal": 15135, + "Ġfibres": 15136, + "Ġecosystems": 15137, + "uming": 15138, + "fly": 15139, + "Ġlungs": 15140, + "Ġcovariates": 15141, + "Ġlayout": 15142, + "ĠRaj": 15143, + "Ġsummation": 15144, + "abled": 15145, + "Ġfreely": 15146, + "Ġrevised": 15147, + "Ġcuts": 15148, + "ĠIntegrated": 15149, + "Ġpharmaceutical": 15150, + "Ġrespiration": 15151, + "ĠBill": 15152, + "Ġestrogen": 15153, + "raint": 15154, + "Ġpercentages": 15155, + "ĠPf": 15156, + "ĠGF": 15157, + "methylene": 15158, + "Ġorigins": 15159, + "trim": 15160, + "match": 15161, + "itney": 15162, + "ĠYe": 15163, + "Ġallocated": 15164, + "manifold": 15165, + "ĠTris": 15166, + "ĠLys": 15167, + "Ġcompressed": 15168, + "orer": 15169, + "Ġhimself": 15170, + "Ġquin": 15171, + "ĠAssembly": 15172, + "single": 15173, + "temporal": 15174, + "Ġsoph": 15175, + "Ġepidemiological": 15176, + "Ġknockout": 15177, + "Ġcompares": 15178, + "ĠSensitivity": 15179, + "Ġgirls": 15180, + "ĠValley": 15181, + "alid": 15182, + "ĠScheme": 15183, + "ĠCOMP": 15184, + "Ġrefractive": 15185, + "ĠOffice": 15186, + "Ġlatest": 15187, + "Ġprices": 15188, + "carboxyl": 15189, + "Ġeconomy": 15190, + "Ġbooks": 15191, + "ĠDD": 15192, + "Ġneoplas": 15193, + "appings": 15194, + "Ġfolding": 15195, + "momentum": 15196, + "potent": 15197, + "Ġprefix": 15198, + "ĠRiemannian": 15199, + "ĠERK": 15200, + "ĠPathway": 15201, + "Ġlarval": 15202, + "olor": 15203, + "Ġattitude": 15204, + "geqslant": 15205, + "Ġgates": 15206, + "Ġagonist": 15207, + "Ġï̍": 15208, + "ĠMCF": 15209, + "ostatic": 15210, + "micro": 15211, + "Ġdoubl": 15212, + "ĠParameter": 15213, + "Ġequivalently": 15214, + "Ġsrc": 15215, + "Most": 15216, + "ĉĠĠĠ": 15217, + "Ġrheumat": 15218, + "ĠHum": 15219, + "region": 15220, + "Ġwinds": 15221, + "Ġquadrup": 15222, + "cales": 15223, + "ulfide": 15224, + "balanced": 15225, + "Under": 15226, + "generated": 15227, + "oplasmic": 15228, + "Ġweighting": 15229, + "ĠNov": 15230, + "veloc": 15231, + "utils": 15232, + "ĠACT": 15233, + "Ġvulnerable": 15234, + "dc": 15235, + "Ġstromal": 15236, + "Ġexacerb": 15237, + "HV": 15238, + "Ġperfectly": 15239, + "txt": 15240, + "direction": 15241, + "ogon": 15242, + "Ġbim": 15243, + "ĠMarg": 15244, + "itons": 15245, + "Ġtermination": 15246, + "eda": 15247, + "Ġpretreatment": 15248, + "Ġimportantly": 15249, + "Ġduc": 15250, + "Ġartifacts": 15251, + "Stud": 15252, + "otensin": 15253, + "reland": 15254, + "ahn": 15255, + "Ġdeployed": 15256, + "ĠEF": 15257, + "ensing": 15258, + "ĠCard": 15259, + "ĠJordan": 15260, + "apunov": 15261, + "Ġanesthesia": 15262, + "Ġatherosclerosis": 15263, + "inner": 15264, + "structural": 15265, + "ĠAsp": 15266, + "throughput": 15267, + "urities": 15268, + "Ġinset": 15269, + "without": 15270, + "Ġacquire": 15271, + "Ġcombines": 15272, + "ĠShar": 15273, + "MASK": 15274, + "ĠLiter": 15275, + "Ġconscious": 15276, + "iscell": 15277, + "consistent": 15278, + "yst": 15279, + "Ġfilaments": 15280, + "ĠAlice": 15281, + "ĠGround": 15282, + "ĠmTOR": 15283, + "versal": 15284, + "Ġlineages": 15285, + "particles": 15286, + "aroscopic": 15287, + "ĠProced": 15288, + "Ġorientations": 15289, + "ĠMouse": 15290, + "Ġaccordingly": 15291, + "Ġsuppressor": 15292, + "Ġdestruction": 15293, + "OV": 15294, + "ĠProteins": 15295, + "PECT": 15296, + "Ġcup": 15297, + "Ġmonomer": 15298, + "plemental": 15299, + "Ġneutrophil": 15300, + "Ġerup": 15301, + "Ġtac": 15302, + "Ġasymptomatic": 15303, + "ĠEmbed": 15304, + "ĠRadiation": 15305, + "ĠGame": 15306, + "Ġneedle": 15307, + "Ġreuse": 15308, + "ĠDutch": 15309, + "Ġjuvenile": 15310, + "Ġmomenta": 15311, + "ĠBose": 15312, + "Ġdeveloper": 15313, + "Ġresiduals": 15314, + "Å¡": 15315, + "Ġcognition": 15316, + "ĠRegional": 15317, + "You": 15318, + "ĠConcent": 15319, + "ocin": 15320, + "ĠPartial": 15321, + "Ġcompletes": 15322, + "ĠSingh": 15323, + "ĠExc": 15324, + "ĠIsolation": 15325, + "ĠStructures": 15326, + "Ġintermitt": 15327, + "Exception": 15328, + "Ġanalytically": 15329, + "Ġelectricity": 15330, + "âĭ": 15331, + "Äį": 15332, + "Ġproteome": 15333, + "Ġic": 15334, + "kal": 15335, + "inux": 15336, + "ĠBeyond": 15337, + "Ġimplied": 15338, + "ASH": 15339, + "Ġclone": 15340, + "ĠRussia": 15341, + "ĠHod": 15342, + "tebrates": 15343, + "Ġproxy": 15344, + "holder": 15345, + "elve": 15346, + "Ġvalley": 15347, + "utely": 15348, + "Ġjobs": 15349, + "ruption": 15350, + "roids": 15351, + "ĠWhy": 15352, + "eping": 15353, + "ĠYet": 15354, + "Ġpyl": 15355, + "Ġbra": 15356, + "ilization": 15357, + "eters": 15358, + "Ġadver": 15359, + "Ġove": 15360, + "kernel": 15361, + "samples": 15362, + "ordinate": 15363, + "ĠAssuming": 15364, + "Ġcontaminated": 15365, + "Ġbipolar": 15366, + "Ġlac": 15367, + "Ġluc": 15368, + "Ġcentrifugation": 15369, + "Both": 15370, + "Ġnd": 15371, + "Ġtib": 15372, + "Before": 15373, + "ĠImmune": 15374, + "Ġash": 15375, + "Ġconditioned": 15376, + "ĠRank": 15377, + "NOS": 15378, + "Ġnanoparticle": 15379, + "Ġdependencies": 15380, + "Ġhouseholds": 15381, + "agers": 15382, + "Ġspectrophot": 15383, + "Ġbile": 15384, + "ĠHans": 15385, + "ĠAcknowledgements": 15386, + "ratio": 15387, + "ĠSecondary": 15388, + "Ġdownregulated": 15389, + "fixed": 15390, + "Obs": 15391, + "ĠHL": 15392, + "Ġsends": 15393, + "tings": 15394, + "Ġfi": 15395, + "ĠPaper": 15396, + "Ġultraviolet": 15397, + "ĠBall": 15398, + "Ġdrastic": 15399, + "ailure": 15400, + "oil": 15401, + "exchange": 15402, + "ĠDan": 15403, + "ĠAuto": 15404, + "Ġarchae": 15405, + "ĠCollection": 15406, + "Ġantiviral": 15407, + "ĠChemistry": 15408, + "Ġferr": 15409, + "choice": 15410, + "vac": 15411, + "olipid": 15412, + "Ġdanger": 15413, + "ĠLittle": 15414, + "Ġdehyd": 15415, + "Ġoccasion": 15416, + "opropyl": 15417, + "abe": 15418, + "Ġinterferon": 15419, + "Ġexport": 15420, + "onitrile": 15421, + "pd": 15422, + "ĠContext": 15423, + "ruz": 15424, + "ĠDys": 15425, + "Ġassembl": 15426, + "Ġoils": 15427, + "Image": 15428, + "rowing": 15429, + "Ġaneurys": 15430, + "Ġliquids": 15431, + "Ġactively": 15432, + "Ġevapor": 15433, + "ĠPresent": 15434, + "Ġconstitutive": 15435, + "ĠSite": 15436, + "Ġscript": 15437, + "Ġrepeats": 15438, + "ĠSIR": 15439, + "ĠFilm": 15440, + "ĠSanta": 15441, + "ĠRepresentation": 15442, + "ĠAma": 15443, + "ordon": 15444, + "ĠMolecule": 15445, + "Ġgoverning": 15446, + "ĠSoil": 15447, + "Ver": 15448, + "Ġphotonic": 15449, + "tify": 15450, + "ĠLewis": 15451, + "athered": 15452, + "Ġcategorical": 15453, + "iscellaneous": 15454, + "update": 15455, + "Ġdeficit": 15456, + "Ġadjuvant": 15457, + "ĠHenry": 15458, + "Group": 15459, + "istency": 15460, + "agraph": 15461, + "ĠImproving": 15462, + "El": 15463, + "Ġflame": 15464, + "rogate": 15465, + "omorph": 15466, + "Ġqubits": 15467, + "Ġillustration": 15468, + "ĠFlorida": 15469, + "ĠDG": 15470, + "bigcup": 15471, + "Ġprovince": 15472, + "egradation": 15473, + "ĠLandau": 15474, + "Ġgrating": 15475, + "Ġinsects": 15476, + "Ġdraft": 15477, + "ĠHb": 15478, + "Ġss": 15479, + "ĠRas": 15480, + "Ġmucosa": 15481, + "Ġhydroxyl": 15482, + "Ġmodest": 15483, + "Ġconfirming": 15484, + "ĠGalaxies": 15485, + "Gaussian": 15486, + "ĠRetrie": 15487, + "Ġrestored": 15488, + "memory": 15489, + "Ġreinforced": 15490, + "rific": 15491, + "Ġassisted": 15492, + "Ġaffiliations": 15493, + "RC": 15494, + "ducer": 15495, + "ĠIntellig": 15496, + "ĠASD": 15497, + "modium": 15498, + "Ġomitted": 15499, + "okers": 15500, + "Ġguided": 15501, + "Ġgraphical": 15502, + "ĠQual": 15503, + "Due": 15504, + "Ġnemat": 15505, + "variable": 15506, + "Ġsenescence": 15507, + "Ġpipe": 15508, + "Ġsustainable": 15509, + "Ġteacher": 15510, + "Ġthing": 15511, + "ĠGPU": 15512, + "TB": 15513, + "Ġreform": 15514, + "Ġreflex": 15515, + "Ġindicative": 15516, + "about": 15517, + "Ġopi": 15518, + "effect": 15519, + "Ġdispersed": 15520, + "kh": 15521, + "ithelial": 15522, + "ĠTreg": 15523, + "ipl": 15524, + "ĠAutomatic": 15525, + "Ġnitro": 15526, + "complete": 15527, + "Ġbosons": 15528, + "Ġpac": 15529, + "Ġavoiding": 15530, + "isl": 15531, + "plasty": 15532, + "responsive": 15533, + "dest": 15534, + "ĠBrad": 15535, + "ĠDecision": 15536, + "ĠDiscovery": 15537, + "Ġchicken": 15538, + "mus": 15539, + "ĠWITH": 15540, + "Ġtric": 15541, + "Ġquartz": 15542, + "onstruction": 15543, + "ĠFields": 15544, + "Ġassim": 15545, + "oprot": 15546, + "Ġguaranteed": 15547, + "fat": 15548, + "icts": 15549, + "Ġchol": 15550, + "ido": 15551, + "ĠKL": 15552, + "Ġchitosan": 15553, + "ĠNd": 15554, + "ĠOscill": 15555, + "Ġevolve": 15556, + "cu": 15557, + "Ġmast": 15558, + "Ġamph": 15559, + "torch": 15560, + "Vis": 15561, + "entity": 15562, + "ĠAdam": 15563, + "Ġdevoted": 15564, + "Ġethical": 15565, + "Ġpremature": 15566, + "Ġconsumer": 15567, + "Ġrecursive": 15568, + "Ġgluon": 15569, + "Ġmoderately": 15570, + "Ġmodalities": 15571, + "Ġcanal": 15572, + "force": 15573, + "ĠChlor": 15574, + "slash": 15575, + "sten": 15576, + "Ġcommercially": 15577, + "ongs": 15578, + "Ġstimulate": 15579, + "atinum": 15580, + "ĠRail": 15581, + "Ġconvective": 15582, + "Ġarteries": 15583, + "inv": 15584, + "ĠWol": 15585, + "ĠLung": 15586, + "letes": 15587, + "raphy": 15588, + "ĠHI": 15589, + "Ġgraphite": 15590, + "Ġhousing": 15591, + "each": 15592, + "Ġcalor": 15593, + "acetamide": 15594, + "rochemical": 15595, + "Ġhands": 15596, + "Ġelucidate": 15597, + "ĠChand": 15598, + "road": 15599, + "nova": 15600, + "ĠLineage": 15601, + "Ġram": 15602, + "Ġfight": 15603, + "Ġrecommendation": 15604, + "Ġamongst": 15605, + "Ġswitches": 15606, + "berry": 15607, + "Ġtherein": 15608, + "algebras": 15609, + "ĠTaken": 15610, + "azz": 15611, + "Ġfurn": 15612, + "Ġamel": 15613, + "Ġteachers": 15614, + "arn": 15615, + "Ġavoided": 15616, + "Ġaverages": 15617, + "amer": 15618, + "ĠCondition": 15619, + "Ġdislocation": 15620, + "ircon": 15621, + "Ġadolescent": 15622, + "Ġtur": 15623, + "env": 15624, + "Ġze": 15625, + "DL": 15626, + "loading": 15627, + "icidal": 15628, + "category": 15629, + "ĠDB": 15630, + "Ġmucosal": 15631, + "ĠRG": 15632, + "Ġtaxonomic": 15633, + "Ġmutagen": 15634, + "ĠStage": 15635, + "necess": 15636, + "ĠPerm": 15637, + "Ġocclusion": 15638, + "Ġexploited": 15639, + "Ġanaerobic": 15640, + "uled": 15641, + "Ġwanted": 15642, + "ĠCombining": 15643, + "Ġsubcutaneous": 15644, + "Recomm": 15645, + "Ġdiscusses": 15646, + "Ġcounterpart": 15647, + "ĠFB": 15648, + "Ġadsorbed": 15649, + "don": 15650, + "Many": 15651, + "ĠSweden": 15652, + "ĠAndrew": 15653, + "enhanced": 15654, + "Ġdoctor": 15655, + "ĠKorean": 15656, + "ĠSAR": 15657, + "Ġmating": 15658, + "aturation": 15659, + "ĠLatin": 15660, + "Ġsorting": 15661, + "Ġskip": 15662, + "Os": 15663, + "Ġwife": 15664, + "Ġcommittee": 15665, + "lvert": 15666, + "ĠACC": 15667, + "ĠComm": 15668, + "Ġsubtle": 15669, + "ĠSurvival": 15670, + "because": 15671, + "Ġfeat": 15672, + "ĠPortug": 15673, + "ARY": 15674, + "ĠISB": 15675, + "itron": 15676, + "Ġsectors": 15677, + "Ġadjoint": 15678, + "ĠAlexander": 15679, + "Ġimpurity": 15680, + "ĠMarine": 15681, + "lact": 15682, + "Ġtrapping": 15683, + "Ġgeneralize": 15684, + "filter": 15685, + "Ġpolarity": 15686, + "Also": 15687, + "Ġstabilized": 15688, + "ĠVirgin": 15689, + "Ġstores": 15690, + "PAGE": 15691, + "Ġdrawback": 15692, + "Ġâݪ": 15693, + "jet": 15694, + "Ġsubstituted": 15695, + "LINE": 15696, + "Ġoutperforms": 15697, + "Ġtermed": 15698, + "Ġweekly": 15699, + "Ġpolyc": 15700, + "Ġfused": 15701, + "Ġferromagnetic": 15702, + "lr": 15703, + "ellites": 15704, + "ĠTurn": 15705, + "ĠCulture": 15706, + "prise": 15707, + "ÅĤ": 15708, + "omposition": 15709, + "elfare": 15710, + "ĠGoogle": 15711, + "oarth": 15712, + "Ġë": 15713, + "Ġmist": 15714, + "ĠMathematics": 15715, + "SET": 15716, + "Ġepochs": 15717, + "Ġcontras": 15718, + "ishment": 15719, + "ĠFirstly": 15720, + "Ġdeclared": 15721, + "aur": 15722, + "ĠPed": 15723, + "Ġreplicate": 15724, + "Ġeligible": 15725, + "Ġconcaten": 15726, + "Ġcig": 15727, + "Ġtriplet": 15728, + "found": 15729, + "ĠCz": 15730, + "Ġaccomplished": 15731, + "Ġgoverned": 15732, + "onuclear": 15733, + "ĠNY": 15734, + "ĠEthiop": 15735, + "Ġinject": 15736, + "Ġeosin": 15737, + "annon": 15738, + "olo": 15739, + "ĠMHC": 15740, + "Ġpreoperative": 15741, + "Ġdates": 15742, + "Ġsigma": 15743, + "Long": 15744, + "ĠReson": 15745, + "Ġsymptomatic": 15746, + "Ġvolunteers": 15747, + "Ġcooperation": 15748, + "Ġarr": 15749, + "Ġcloned": 15750, + "Ġdent": 15751, + "ĠSob": 15752, + "Ġcathode": 15753, + "ctx": 15754, + "Ġencephal": 15755, + "Ġpiv": 15756, + "vive": 15757, + "umetric": 15758, + "ĠFF": 15759, + "Ġunderestim": 15760, + "Ġcoded": 15761, + "Ġanalges": 15762, + "spectral": 15763, + "Ġattracted": 15764, + "Ġtwenty": 15765, + "Ġinactive": 15766, + "Ġvictim": 15767, + "Ġholder": 15768, + "ogenes": 15769, + "Ġsuffering": 15770, + "rex": 15771, + "Ġprophyl": 15772, + "ĠUniversal": 15773, + "Ġdenom": 15774, + "stolic": 15775, + "ansion": 15776, + "SIZE": 15777, + "ĠHCV": 15778, + "Ġtechnological": 15779, + "CNN": 15780, + "enching": 15781, + "Ġdebris": 15782, + "ĠBoundary": 15783, + "linking": 15784, + "Ġstopped": 15785, + "ĠDie": 15786, + "ĠCosm": 15787, + "Ġturning": 15788, + "Ġglycoprotein": 15789, + "ĠKumar": 15790, + "Ġpg": 15791, + "ĠBY": 15792, + "Ġrising": 15793, + "ĠROC": 15794, + "Despite": 15795, + "ĠBoolean": 15796, + "ilder": 15797, + "Ġexponents": 15798, + "inters": 15799, + "printf": 15800, + "Ġlit": 15801, + "track": 15802, + "Ġfidelity": 15803, + "Ġsmoke": 15804, + "otemporal": 15805, + "Ġadmissible": 15806, + "ĠBoltzmann": 15807, + "TF": 15808, + "olite": 15809, + "liament": 15810, + "Ġcalculus": 15811, + "itized": 15812, + "Ġdivergent": 15813, + "Ġcolonization": 15814, + "Ġconvergent": 15815, + "ĠHas": 15816, + "Ġconsumers": 15817, + "Ġmyc": 15818, + "Ġcontig": 15819, + "Ġepidemiology": 15820, + "és": 15821, + "ĠAssoci": 15822, + "given": 15823, + "Ġwhilst": 15824, + "ĠKur": 15825, + "Ġreasonably": 15826, + "Ġaerobic": 15827, + "separ": 15828, + "Ġchecks": 15829, + "ĠSemantic": 15830, + "Ġserving": 15831, + "ĠAtmosp": 15832, + "Ġoxidized": 15833, + "coupled": 15834, + "ĠbioRxiv": 15835, + "Ġtuned": 15836, + "uspended": 15837, + "Ġindirectly": 15838, + "ĠCAD": 15839, + "ĠCurrently": 15840, + "Ġbehaviours": 15841, + "ĠPPAR": 15842, + "rors": 15843, + "ereb": 15844, + "Ġwidths": 15845, + "diagonal": 15846, + "ervice": 15847, + "Ġole": 15848, + "means": 15849, + "IME": 15850, + "ĠTracking": 15851, + "Ġacknowledge": 15852, + "ĠHon": 15853, + "ĠTechniques": 15854, + "ĠOxid": 15855, + "blind": 15856, + "Ġdiast": 15857, + "named": 15858, + "asitic": 15859, + "Ġpreparations": 15860, + "ĠArth": 15861, + "Ġpreserves": 15862, + "Ġfasc": 15863, + "Ġwaveform": 15864, + "ĠCrystal": 15865, + "Ġuncom": 15866, + "Ġelast": 15867, + "Ġfunctionally": 15868, + "Hom": 15869, + "ĠCoast": 15870, + "Ġoptic": 15871, + "ĠAlternatively": 15872, + "onyl": 15873, + "ĠLig": 15874, + "aldehyde": 15875, + "Ġsimulator": 15876, + "Ġdramatic": 15877, + "ifera": 15878, + "Ġexhibiting": 15879, + "Ġbehavioural": 15880, + "thick": 15881, + "xture": 15882, + "Ġexecutive": 15883, + "Ġcondensate": 15884, + "ĠOutcomes": 15885, + "Text": 15886, + "ointed": 15887, + "ĠCopyright": 15888, + "Ġdc": 15889, + "odd": 15890, + "ĠDiversity": 15891, + "chip": 15892, + "ĠBuilding": 15893, + "Ġpulsed": 15894, + "harmonic": 15895, + "Ġclinicians": 15896, + "dp": 15897, + "ĠqPCR": 15898, + "marks": 15899, + "Ġappreci": 15900, + "ĠLaser": 15901, + "Ġsizeof": 15902, + "yrene": 15903, + "Ġcooperative": 15904, + "generative": 15905, + "ĠLib": 15906, + "Ġdispersal": 15907, + "Ġevolving": 15908, + "ĠStatus": 15909, + "Ġsupercon": 15910, + "ĠMamm": 15911, + "Ġinterstitial": 15912, + "isenberg": 15913, + "Ġâľ": 15914, + "Ġconfocal": 15915, + "Ġmodulates": 15916, + "hour": 15917, + "Ġperoxide": 15918, + "dependence": 15919, + "Ġperturbed": 15920, + "illation": 15921, + "Ġplaque": 15922, + "ĠNeumann": 15923, + "Ġtriggers": 15924, + "omain": 15925, + "ĠAdministration": 15926, + "olia": 15927, + "ĠMIC": 15928, + "osaic": 15929, + "ĠGB": 15930, + "textnormal": 15931, + "Ġdominance": 15932, + "ĠExper": 15933, + "CAM": 15934, + "ĠAbout": 15935, + "ĠGarc": 15936, + "Ġsummarizes": 15937, + "App": 15938, + "charomyces": 15939, + "tificial": 15940, + "Ġglycerol": 15941, + "ĠAssumption": 15942, + "Ġtect": 15943, + "ĠFW": 15944, + "Ġcotton": 15945, + "general": 15946, + "ĠFern": 15947, + "Pt": 15948, + "Ġworker": 15949, + "Ġanion": 15950, + "grams": 15951, + "req": 15952, + "Ġlooks": 15953, + "Ġimplementations": 15954, + "ĠColumb": 15955, + "agi": 15956, + "ĠAttention": 15957, + "ĠTeam": 15958, + "oning": 15959, + "onential": 15960, + "tiny": 15961, + "ĠHighly": 15962, + "textup": 15963, + "Ġinvertible": 15964, + "ocortic": 15965, + "Inf": 15966, + "ĠOfficial": 15967, + "ĠModelling": 15968, + "Ġinclusions": 15969, + "Ġblank": 15970, + "Ġsight": 15971, + "ĠGamma": 15972, + "Ġlepton": 15973, + "Ġpneumoniae": 15974, + "Ġrotor": 15975, + "Ġethnic": 15976, + "Ġretain": 15977, + "varying": 15978, + "ĠEB": 15979, + "Ġastrocytes": 15980, + "ĠNorm": 15981, + "Ġnanom": 15982, + "classical": 15983, + "Ġshadow": 15984, + "ĠReferences": 15985, + "ĠFS": 15986, + "Ġnonnegative": 15987, + "bond": 15988, + "ĠCoh": 15989, + "Ġnumpy": 15990, + "Ġoct": 15991, + "span": 15992, + "racts": 15993, + "Ġnotably": 15994, + "Ġsophistic": 15995, + "PAR": 15996, + "Ġhormones": 15997, + "Ġtensors": 15998, + "ĠÌĦ": 15999, + "ĠConstraints": 16000, + "ĠâIJ": 16001, + "Ġtransit": 16002, + "Ġruntime": 16003, + "author": 16004, + "Ġprompt": 16005, + "ĠSG": 16006, + "Ġgrate": 16007, + "cemia": 16008, + "ĠLyapunov": 16009, + "convex": 16010, + "Ġforecasting": 16011, + "push": 16012, + "Ġjurisdictional": 16013, + "ÃĢ": 16014, + "Ġbiomedical": 16015, + "Ġepilepsy": 16016, + "feature": 16017, + "wiki": 16018, + "View": 16019, + "Ġlesser": 16020, + "Ġconjugated": 16021, + "Ġwaiting": 16022, + "ĠWord": 16023, + "IZ": 16024, + "Ġhydroxy": 16025, + "Ġdisp": 16026, + "Ġseeded": 16027, + "fitting": 16028, + "Ġstratification": 16029, + "Ġendpoint": 16030, + "Ġmediators": 16031, + "ductive": 16032, + "Ġinjections": 16033, + "ĠMicrobi": 16034, + "Ġinsert": 16035, + "ĠEmb": 16036, + "Ġstopping": 16037, + "welling": 16038, + "Ġirradiated": 16039, + "Ġmetallicity": 16040, + "vinyl": 16041, + "Ġplasmids": 16042, + "Rep": 16043, + "ĠDifferenti": 16044, + "ĠSmart": 16045, + "ĠIdentifier": 16046, + "ĠBF": 16047, + "ropic": 16048, + "Ġkinematics": 16049, + "Ġinoculated": 16050, + "CK": 16051, + "auses": 16052, + "ĠReturns": 16053, + "reement": 16054, + "Ġanticancer": 16055, + "Ġspecifications": 16056, + "Ġadds": 16057, + "Ġstake": 16058, + "Ġwheel": 16059, + "üller": 16060, + "ĠSon": 16061, + "Ġrupture": 16062, + "Ġsold": 16063, + "than": 16064, + "Ġintermedi": 16065, + "ĠNik": 16066, + "Ġtuple": 16067, + "establ": 16068, + "Ġnorthe": 16069, + "Ġsuppresses": 16070, + "Ġfet": 16071, + "Ġwashing": 16072, + "Ġinterplay": 16073, + "Ġregularly": 16074, + "EXT": 16075, + "Ġemployees": 16076, + "yz": 16077, + "rupted": 16078, + "etts": 16079, + "ĠUAV": 16080, + "Ġdifferentiable": 16081, + "inge": 16082, + "MDA": 16083, + "Ġho": 16084, + "Ġtags": 16085, + "Ġcompatibility": 16086, + "ĠÃĥ": 16087, + "bus": 16088, + "ĠUC": 16089, + "Ġtokens": 16090, + "Ġclients": 16091, + "Ġprescription": 16092, + "ĠÌĪ": 16093, + "ĠReaction": 16094, + "velocity": 16095, + "ĠNLR": 16096, + "ĠGast": 16097, + "ĠPlasmodium": 16098, + "ĠCut": 16099, + "Ġnas": 16100, + "grained": 16101, + "Ġchromosomal": 16102, + "Ġpossesses": 16103, + "Ġmath": 16104, + "Ġelected": 16105, + "placement": 16106, + "Ġcollecting": 16107, + "Ġgels": 16108, + "aire": 16109, + "Ġdeformations": 16110, + "raise": 16111, + "Ġflank": 16112, + "sulfanyl": 16113, + "zens": 16114, + "priate": 16115, + "Ġchlorophyll": 16116, + "abi": 16117, + "available": 16118, + "ا": 16119, + "Ġtack": 16120, + "fields": 16121, + "Ġrichness": 16122, + "Ġimplants": 16123, + "obenz": 16124, + "idential": 16125, + "Ġbillion": 16126, + "utor": 16127, + "ĠISBN": 16128, + "Ġinsurance": 16129, + "NET": 16130, + "Ġinadequ": 16131, + "Ġmerged": 16132, + "ĠRange": 16133, + "Ġavoidance": 16134, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 16135, + "rics": 16136, + "Ġexclusive": 16137, + "LV": 16138, + "Ġ": 16139, + "Ġcategorized": 16140, + "Ġultrasonic": 16141, + "ipe": 16142, + "icans": 16143, + "ĠAPP": 16144, + "Ġtraumatic": 16145, + "Ba": 16146, + "ĠAssay": 16147, + "ĠGrid": 16148, + "ĠClassical": 16149, + "ĠDES": 16150, + "Ġsoybean": 16151, + "Ġtopography": 16152, + "ĠControll": 16153, + "Ġemotions": 16154, + "Ġcarbohydrate": 16155, + "Ġconsol": 16156, + "oxyl": 16157, + "Ġbifurcation": 16158, + "Ġcoil": 16159, + "find": 16160, + "Ġwitness": 16161, + "ĠLF": 16162, + "threshold": 16163, + "Ġaddressing": 16164, + "Ġscrew": 16165, + "Ġactor": 16166, + "ĠWell": 16167, + "Ġ": 16168, + "ïĺ": 16169, + "ĠDF": 16170, + "ĠCorporation": 16171, + "ĠMitochondrial": 16172, + "Ġkpc": 16173, + "unders": 16174, + "Ġfibrin": 16175, + "axel": 16176, + "Ġpolyt": 16177, + "Ġshaped": 16178, + "rez": 16179, + "steresis": 16180, + "ĠComprehens": 16181, + "Ġ": 16182, + "dh": 16183, + "Ġsemic": 16184, + "Ġmot": 16185, + "ĠDavis": 16186, + "ska": 16187, + "ĠLH": 16188, + "Ġexpansions": 16189, + "acks": 16190, + "Ġoptimizing": 16191, + "eak": 16192, + "ĠQi": 16193, + "mul": 16194, + "ograft": 16195, + "Ġsuicide": 16196, + "calar": 16197, + "ĠScott": 16198, + "Ġthinking": 16199, + "Ġdirectional": 16200, + "Ġsurfactant": 16201, + "Ġdegraded": 16202, + "Ġregimen": 16203, + "itative": 16204, + "ĠVersion": 16205, + "ĠMaster": 16206, + "ĠSimulations": 16207, + "NCBI": 16208, + "lip": 16209, + "Ġreagents": 16210, + "Ġposted": 16211, + "osus": 16212, + "Ġlayered": 16213, + "ĠSpectrum": 16214, + "ĠGraphs": 16215, + "burst": 16216, + "Ġlived": 16217, + "Ġelemental": 16218, + "Ġ": 16219, + "ĠDiscrete": 16220, + "Ġexcluding": 16221, + "Ġoriginating": 16222, + "ĠGames": 16223, + "continuous": 16224, + "ATED": 16225, + "Ġpyram": 16226, + "luent": 16227, + "Ġtwisted": 16228, + "ĠNb": 16229, + "oxicity": 16230, + "Ġscr": 16231, + "Ġfun": 16232, + "ĠSegmentation": 16233, + "Ġphenol": 16234, + "Ġmeters": 16235, + "ĠEigen": 16236, + "ĠWeak": 16237, + "Ġschematic": 16238, + "rone": 16239, + "Ġphilos": 16240, + "titis": 16241, + "ĠIreland": 16242, + "Ġgy": 16243, + "ĠPTM": 16244, + "Ġpacking": 16245, + "ilinear": 16246, + "zeros": 16247, + "Ġubiquitin": 16248, + "ĠPressure": 16249, + "Ġinfiltr": 16250, + "ENS": 16251, + "validation": 16252, + "Ġprone": 16253, + "Ġoutline": 16254, + "hs": 16255, + "rength": 16256, + "Ġattain": 16257, + "Ġtwe": 16258, + "Ġtandem": 16259, + "Can": 16260, + "Ġlatitude": 16261, + "uitary": 16262, + "Ġvoltages": 16263, + "ĠGao": 16264, + "Ġpharmacokine": 16265, + "Ġcontextual": 16266, + "Ġxyl": 16267, + "elson": 16268, + "ĠMetabolic": 16269, + "oden": 16270, + "tiles": 16271, + "fficking": 16272, + "Ġdistilled": 16273, + "Ġalph": 16274, + "Ġpiezo": 16275, + "growth": 16276, + "Ġbore": 16277, + "Ġredundant": 16278, + "Ġdemonstration": 16279, + "Ġik": 16280, + "Ġrounds": 16281, + "ĠSri": 16282, + "figuration": 16283, + "ĠRayleigh": 16284, + "Line": 16285, + "ovol": 16286, + "Ġobstacle": 16287, + "cn": 16288, + "Ġbioactive": 16289, + "ĠOA": 16290, + "physical": 16291, + "atidyl": 16292, + "ACC": 16293, + "how": 16294, + "Ġresultant": 16295, + "ĠHubble": 16296, + "ĠVor": 16297, + "Ġensuring": 16298, + "Ġannotations": 16299, + "acyl": 16300, + "stituted": 16301, + "ĠAmb": 16302, + "feeding": 16303, + "Ġpresumably": 16304, + "Ġblockade": 16305, + "Ġsoc": 16306, + "ĠUrb": 16307, + "Ġmultiplied": 16308, + "Ġdiffe": 16309, + "Ġreflectance": 16310, + "ĠKeywords": 16311, + "ĠBayes": 16312, + "odeficiency": 16313, + "ĠBinding": 16314, + "inely": 16315, + "except": 16316, + "ĠUltr": 16317, + "ĠBrazilian": 16318, + "Number": 16319, + "Ġmassless": 16320, + "ĠConsistent": 16321, + "Ġcrisis": 16322, + "ogs": 16323, + "Ġresidence": 16324, + "Ġimper": 16325, + "fts": 16326, + "Ġcaptures": 16327, + "ĠSyndrome": 16328, + "Ġdimensionality": 16329, + "jun": 16330, + "Ġexhaus": 16331, + "ĠModern": 16332, + "Ġpercenti": 16333, + "Level": 16334, + "ĠResponses": 16335, + "Ġlaunched": 16336, + "Ġrepos": 16337, + "ĠKam": 16338, + "atility": 16339, + "Ġcarotid": 16340, + "rotic": 16341, + "ĠMand": 16342, + "UB": 16343, + "ĠMixed": 16344, + "Ġindexes": 16345, + "Ġcisplatin": 16346, + "ican": 16347, + "ionine": 16348, + "Ġhab": 16349, + "ĠIce": 16350, + "ĠGT": 16351, + "ĠAgg": 16352, + "ĠLDL": 16353, + "Ġvolcanic": 16354, + "dB": 16355, + "ĠElectric": 16356, + "Ġtmp": 16357, + "Ġgrids": 16358, + "liquid": 16359, + "prom": 16360, + "ĠGAL": 16361, + "Ġpestic": 16362, + "Ġhelium": 16363, + "Ġ": 16364, + "ĠDong": 16365, + "Ġmagnification": 16366, + "kip": 16367, + "ĠGrad": 16368, + "ĠWei": 16369, + "ĠPDF": 16370, + "ĠGluc": 16371, + "Pol": 16372, + "Ġtumorigen": 16373, + "yrin": 16374, + "Ġshelf": 16375, + "adher": 16376, + "entials": 16377, + "sn": 16378, + "Ġcultivars": 16379, + "Ġorbitals": 16380, + "ĠPEG": 16381, + "ĠAnne": 16382, + "eno": 16383, + "Ġattended": 16384, + "ophore": 16385, + "ishop": 16386, + "Ġfriends": 16387, + "posable": 16388, + "Ġimpose": 16389, + "Ġendemic": 16390, + "Ġsick": 16391, + "shifts": 16392, + "ĠOutput": 16393, + "LM": 16394, + "ĠMiscellaneous": 16395, + "Ġthousands": 16396, + "ĠDataset": 16397, + "Ġperturbative": 16398, + "oprec": 16399, + "Ġbene": 16400, + "Ġreef": 16401, + "Ġfossil": 16402, + "Ġcited": 16403, + "plicates": 16404, + "Ġrelates": 16405, + "ĠVII": 16406, + "Ġantifer": 16407, + "Ġglasses": 16408, + "closure": 16409, + "Ġrubber": 16410, + "Ġbird": 16411, + "Ġsupersymmetry": 16412, + "Ġmeson": 16413, + "hell": 16414, + "Ġparties": 16415, + "kar": 16416, + "ĠHur": 16417, + "ĠEA": 16418, + "ĠStars": 16419, + "othing": 16420, + "hot": 16421, + "illar": 16422, + "ASP": 16423, + "hev": 16424, + "ïĥ": 16425, + "aques": 16426, + "Ġcoordinated": 16427, + "ĠIslands": 16428, + "enable": 16429, + "SiO": 16430, + "Ġexceptional": 16431, + "Comb": 16432, + "ĠLike": 16433, + "Ġbroadly": 16434, + "ĠBac": 16435, + "Ġnil": 16436, + "ipartite": 16437, + "rations": 16438, + "Ġrewrite": 16439, + "Ġsalts": 16440, + "dimension": 16441, + "ĠVehic": 16442, + "Ġhundreds": 16443, + "ĠUr": 16444, + "Ġendpoints": 16445, + "ĠMODEL": 16446, + "ĠHBV": 16447, + "ĠVirtual": 16448, + "ĠConfl": 16449, + "ĠPractice": 16450, + "ĠAFM": 16451, + "Ġadversarial": 16452, + "Ġdiameters": 16453, + "Ġtransported": 16454, + "REM": 16455, + "ĠBart": 16456, + "Ġedition": 16457, + "Ġturbine": 16458, + "Ġminus": 16459, + "otechnology": 16460, + "Ig": 16461, + "Ġbigger": 16462, + "abul": 16463, + "Ġperoxidase": 16464, + "white": 16465, + "ĠSed": 16466, + "dihydro": 16467, + "Ġsegregation": 16468, + "Ġreductase": 16469, + "Ġhoriz": 16470, + "Ġinfinitely": 16471, + "availability": 16472, + "Ġactivator": 16473, + "Ġcensus": 16474, + "pressing": 16475, + "Ġspirit": 16476, + "conver": 16477, + "ĠQuantification": 16478, + "omerase": 16479, + "Ġrelapse": 16480, + "ĠFinal": 16481, + "Ġoverweight": 16482, + "aper": 16483, + "Ġformulae": 16484, + "rr": 16485, + "Ġfemoral": 16486, + "Ġfoam": 16487, + "otics": 16488, + "Ġprovider": 16489, + "Ġinstrumental": 16490, + "Ġadvice": 16491, + "Ġoccupation": 16492, + "assembly": 16493, + "bias": 16494, + "ĠNOT": 16495, + "restric": 16496, + "ĠProtocol": 16497, + "ĠCandida": 16498, + "ĠRhod": 16499, + "arden": 16500, + "funder": 16501, + "osens": 16502, + "Ġparams": 16503, + "front": 16504, + "Ġexerc": 16505, + "Ġgalactic": 16506, + "rvert": 16507, + "Ġimbalance": 16508, + "Ġkilling": 16509, + "ĠGenomic": 16510, + "Ġip": 16511, + "Ġcave": 16512, + "Ġfalc": 16513, + "ĠRM": 16514, + "Ġcarries": 16515, + "global": 16516, + "Ġcube": 16517, + "Ġrigorous": 16518, + "Ġcomputes": 16519, + "QP": 16520, + "Ġexposures": 16521, + "cover": 16522, + "ologically": 16523, + "Oper": 16524, + "Ġpec": 16525, + "Ġinhomogeneous": 16526, + "Ġservers": 16527, + "aliana": 16528, + "nb": 16529, + "Ġexplaining": 16530, + "Ġshrink": 16531, + "Ġcomorbid": 16532, + "ethoxy": 16533, + "outheast": 16534, + "Ġcourses": 16535, + "ĠNM": 16536, + "ĠShape": 16537, + "Ġflies": 16538, + "ĠMir": 16539, + "Ġpublicly": 16540, + "Ġphotometric": 16541, + "versible": 16542, + "olev": 16543, + "Ġvulnerability": 16544, + "Ġcations": 16545, + "Ġseeking": 16546, + "UTR": 16547, + "Ġdecomposed": 16548, + "Ġhus": 16549, + "Ġdisappear": 16550, + "Ġencounter": 16551, + "Ġtransforming": 16552, + "Ġpolymeric": 16553, + "Ġdiscretization": 16554, + "otoxic": 16555, + "ĠIter": 16556, + "ĠMari": 16557, + "Ġunfold": 16558, + "ĠAdult": 16559, + "obacillus": 16560, + "metal": 16561, + "berger": 16562, + "raphene": 16563, + "respective": 16564, + "Ġsurvive": 16565, + "ovich": 16566, + "Ġprotects": 16567, + "ĠRog": 16568, + "Ġimmunotherapy": 16569, + "ĠDSM": 16570, + "Ġanalogy": 16571, + "ĠPER": 16572, + "ĠPython": 16573, + "hum": 16574, + "ĠAdj": 16575, + "ĠLikewise": 16576, + "Ġ": 16577, + "Ġstomach": 16578, + "Ġinit": 16579, + "Ġwires": 16580, + "Ġingredients": 16581, + "Ġperceptual": 16582, + "Hand": 16583, + "Back": 16584, + "Ġmood": 16585, + "Ġdeformed": 16586, + "ĠRead": 16587, + "Ġrhiz": 16588, + "ĠOrganism": 16589, + "ĠIndones": 16590, + "annot": 16591, + "ictory": 16592, + "Ġtended": 16593, + "ĠSound": 16594, + "iax": 16595, + "Sr": 16596, + "ĠTab": 16597, + "ĠLaplacian": 16598, + "oluminescence": 16599, + "backslash": 16600, + "iologic": 16601, + "Ġtypename": 16602, + "ĠYear": 16603, + "Dependent": 16604, + "Ġslides": 16605, + "Ġsacrific": 16606, + "Ġconcomitant": 16607, + "opsies": 16608, + "Bigg": 16609, + "peak": 16610, + "ĠApplying": 16611, + "Ġcodon": 16612, + "ĠSimultaneous": 16613, + "tise": 16614, + "Ġtertiary": 16615, + "ĠPoll": 16616, + "Ġrevision": 16617, + "RAF": 16618, + "xmm": 16619, + "Ġsuited": 16620, + "ĠRecommend": 16621, + "ĠRy": 16622, + "Ġsake": 16623, + "Ġstretch": 16624, + "ĠSampling": 16625, + "Ġtubular": 16626, + "Ġpark": 16627, + "Ġultimate": 16628, + "Ġlands": 16629, + "ĠCriter": 16630, + "assay": 16631, + "mor": 16632, + "Ġdocking": 16633, + "Ġgradual": 16634, + "Ġeditor": 16635, + "Ġpolice": 16636, + "affin": 16637, + "ĠDeath": 16638, + "Ġpromoters": 16639, + "assic": 16640, + "Ġwriter": 16641, + "ĠVolume": 16642, + "iso": 16643, + "Ġdisag": 16644, + "token": 16645, + "Ġsteroid": 16646, + "Non": 16647, + "ĠMethyl": 16648, + "Americ": 16649, + "due": 16650, + "ĠLess": 16651, + "Ġdyst": 16652, + "ĠStatement": 16653, + "ĠTwenty": 16654, + "Ġaccessed": 16655, + "Ġblotting": 16656, + "ĠCOPD": 16657, + "Ġsteam": 16658, + "Ġdescriptive": 16659, + "ĠVery": 16660, + "Ġcapacities": 16661, + "ĠPersonal": 16662, + "acid": 16663, + "ähler": 16664, + "estival": 16665, + "Context": 16666, + "Ġastr": 16667, + "Analysis": 16668, + "Ġsept": 16669, + "Ġprinted": 16670, + "dual": 16671, + "aman": 16672, + "erer": 16673, + "Ġweakness": 16674, + "ìĿ": 16675, + "ĠTranslation": 16676, + "Ġpropagating": 16677, + "ĠSections": 16678, + "aca": 16679, + "Ġconfusion": 16680, + "IK": 16681, + "Ġframeworks": 16682, + "Ġsituated": 16683, + "Ġstays": 16684, + "nodes": 16685, + "chen": 16686, + "artments": 16687, + "Ġfreezing": 16688, + "ws": 16689, + "nett": 16690, + "Ġcontrollers": 16691, + "Ġsilic": 16692, + "LAST": 16693, + "foot": 16694, + "ĠDISCU": 16695, + "RH": 16696, + "ridine": 16697, + "ĠRev": 16698, + "perg": 16699, + "pyrim": 16700, + "flags": 16701, + "ĠGuide": 16702, + "Ġspeaker": 16703, + "tisol": 16704, + "rell": 16705, + "ĠDEG": 16706, + "Ġfu": 16707, + "ĠGut": 16708, + "Ġshar": 16709, + "Ġgross": 16710, + "Ġcrosses": 16711, + "wavelength": 16712, + "ĠApplied": 16713, + "ïve": 16714, + "ĠHB": 16715, + "ĠEdge": 16716, + "Ġinertial": 16717, + "Ġvocal": 16718, + "production": 16719, + "pathetic": 16720, + "Ġplanetary": 16721, + "Ġsister": 16722, + "Ġminima": 16723, + "Ġlongest": 16724, + "Ġflash": 16725, + "Ġperiodon": 16726, + "Ġepidermal": 16727, + "Ġfloating": 16728, + "GET": 16729, + "ĠTake": 16730, + "pdf": 16731, + "ĠLiquid": 16732, + "Ġremarkably": 16733, + "Sign": 16734, + "Ġshells": 16735, + "oglobulin": 16736, + "quilibrium": 16737, + "ĠMoore": 16738, + "ĠAdvers": 16739, + "ĠMycobacterium": 16740, + "Invitrogen": 16741, + "Ġthaliana": 16742, + "BY": 16743, + "ĠBit": 16744, + "Ġts": 16745, + "Ġsynchronous": 16746, + "yx": 16747, + "Ġpropagator": 16748, + "ĠIncreasing": 16749, + "iparum": 16750, + "Ġfreeze": 16751, + "ĠSelective": 16752, + "afe": 16753, + "Ġstrept": 16754, + "phantom": 16755, + "ĠGenerally": 16756, + "Ġalternate": 16757, + "ĠConvergence": 16758, + "////////////////": 16759, + "enging": 16760, + "ĠRandomized": 16761, + "develop": 16762, + "predict": 16763, + "ressor": 16764, + "Ġmathematics": 16765, + "fr": 16766, + "ĠComputation": 16767, + "ĠMalays": 16768, + "Ġbreathing": 16769, + "Through": 16770, + "ĠSIM": 16771, + "Ġanode": 16772, + "oad": 16773, + "ĠATCC": 16774, + "Ġconstituent": 16775, + "ĠMeasuring": 16776, + "ĠfMRI": 16777, + "Ġanemia": 16778, + "liest": 16779, + "Ġhemisphere": 16780, + "Ġmaxima": 16781, + "Ġtemporary": 16782, + "Ġdz": 16783, + "otoxin": 16784, + "Count": 16785, + "oned": 16786, + "ú": 16787, + "Ġcollaborative": 16788, + "Ġkb": 16789, + "Ġversa": 16790, + "ĠSwedish": 16791, + "ika": 16792, + "Ġdialysis": 16793, + "Ġperovsk": 16794, + "Ġwilling": 16795, + "ĠGreek": 16796, + "Output": 16797, + "Ġsemigroup": 16798, + "Ġbottlen": 16799, + "ĠGibbs": 16800, + "dark": 16801, + "Ġrheumatoid": 16802, + "urring": 16803, + "matched": 16804, + "Ġsophisticated": 16805, + "Ġcustomer": 16806, + "tetrahydro": 16807, + "XY": 16808, + "bug": 16809, + "Ġmorning": 16810, + "ĠCVD": 16811, + "Ġmappings": 16812, + "ĠMSCs": 16813, + "ĠDH": 16814, + "Ġquatern": 16815, + "health": 16816, + "ı": 16817, + "Ġtemp": 16818, + "ĠJew": 16819, + "ĠIl": 16820, + "Ġvortices": 16821, + "Ġserine": 16822, + "ĠOxygen": 16823, + "weg": 16824, + "Ġexplanations": 16825, + "PG": 16826, + "Ġciti": 16827, + "Ġlocality": 16828, + "===": 16829, + "ĠThom": 16830, + "Ġdairy": 16831, + "Block": 16832, + "ordial": 16833, + "akov": 16834, + "Ġglioma": 16835, + "Ġtransaction": 16836, + "Ġincremental": 16837, + "anche": 16838, + "Ret": 16839, + "magnetic": 16840, + "pyrrol": 16841, + "ĠPic": 16842, + "Ġamelior": 16843, + "oxidant": 16844, + "roviral": 16845, + "oratory": 16846, + "Ġsav": 16847, + "ĠStream": 16848, + "Ġsuperf": 16849, + "ĠICU": 16850, + "Ġevidenced": 16851, + "Ġrepeatedly": 16852, + "Ġrated": 16853, + "ĠPit": 16854, + "FAULT": 16855, + "Ġhat": 16856, + "ĠContent": 16857, + "Ġisoform": 16858, + "VER": 16859, + "Ġnodal": 16860, + "Ġscheduled": 16861, + "Ġshoulder": 16862, + "Ġtap": 16863, + "Ġportal": 16864, + "Ġtraps": 16865, + "aev": 16866, + "ĠSOD": 16867, + "ematic": 16868, + "Ġenj": 16869, + "Ġreticulum": 16870, + "ĠMinister": 16871, + "ĠSel": 16872, + "Ġfalling": 16873, + "rost": 16874, + "NG": 16875, + "fd": 16876, + "nitro": 16877, + "ĠMove": 16878, + "relativistic": 16879, + "enges": 16880, + "ĠSST": 16881, + "ĠInv": 16882, + "Ġfinish": 16883, + "ĠPoland": 16884, + "osecond": 16885, + "ĠBAL": 16886, + "oarthritis": 16887, + "Ġoptics": 16888, + "ĠSky": 16889, + "Ġadvoc": 16890, + "Ġhemorrhage": 16891, + "Ġmodulating": 16892, + "nis": 16893, + "Ġmachinery": 16894, + "Ġupdating": 16895, + "Ġcharacterizing": 16896, + "ishman": 16897, + "Ġtemplates": 16898, + "ĠLaplace": 16899, + "ĠEns": 16900, + "Recently": 16901, + "orus": 16902, + "arts": 16903, + "diffusion": 16904, + "ĠLevels": 16905, + "aga": 16906, + "ĠInj": 16907, + "ĠLayer": 16908, + "Ġremn": 16909, + "Ġelasticity": 16910, + "Ġmerely": 16911, + "Ġfission": 16912, + "engue": 16913, + "make": 16914, + "Ġmonop": 16915, + "Ġurea": 16916, + "ĠSimon": 16917, + "miR": 16918, + "ĠSecondly": 16919, + "uric": 16920, + "ĠVariable": 16921, + "ilis": 16922, + "Ġmultiplicative": 16923, + "ĠNoise": 16924, + "Ġswitched": 16925, + "Ġnicot": 16926, + "Ġefficiencies": 16927, + "hema": 16928, + "Ġappointed": 16929, + "guided": 16930, + "Ġwinning": 16931, + "ĠMechanics": 16932, + "Ġneo": 16933, + "ĠBRCA": 16934, + "udi": 16935, + "Ġcontainer": 16936, + "shop": 16937, + "Ġsuggestions": 16938, + "KB": 16939, + "Ġsubstitute": 16940, + "Ox": 16941, + "VC": 16942, + "Ġstone": 16943, + "anna": 16944, + "ĠDepression": 16945, + "Ġcontemporary": 16946, + "Ġoutliers": 16947, + "quet": 16948, + "ĠZheng": 16949, + "Ġoccl": 16950, + "Ġalveolar": 16951, + "expressing": 16952, + "Ġcomfort": 16953, + "Ġignore": 16954, + "Among": 16955, + "ĠKlein": 16956, + "Ġrhythm": 16957, + "Ġimmers": 16958, + "Ġfaith": 16959, + "bling": 16960, + "Ġaugmentation": 16961, + "ĠPrevention": 16962, + "Ġhepar": 16963, + "Ġnotations": 16964, + "Ġhematopoietic": 16965, + "perfect": 16966, + "Ġshares": 16967, + "notin": 16968, + "Ġpictures": 16969, + "ĠAcknowledgments": 16970, + "Ġtick": 16971, + "Ġunrelated": 16972, + "ĠTool": 16973, + "Ġmas": 16974, + "osocial": 16975, + "gest": 16976, + "ushed": 16977, + "Ġphosphorylated": 16978, + "Ġceramic": 16979, + "cool": 16980, + "orylation": 16981, + "Ġdeficient": 16982, + "Ġrelaxed": 16983, + "ĠAnalyses": 16984, + "ecraft": 16985, + "Ġretina": 16986, + "ĠInternal": 16987, + "Ġspite": 16988, + "Ġrecipients": 16989, + "Ġshut": 16990, + "Ġethylene": 16991, + "ĠGulf": 16992, + "Ġunaffected": 16993, + "ĠResource": 16994, + "ĠNet": 16995, + "Ġperpet": 16996, + "Ġslab": 16997, + "report": 16998, + "Ġμmol": 16999, + "Ġidx": 17000, + "Ġskill": 17001, + "ĠInduction": 17002, + "Ġmalignancy": 17003, + "Ġcv": 17004, + "Ġdiffering": 17005, + "Ġappropriately": 17006, + "ijing": 17007, + "Ġwarrant": 17008, + "rally": 17009, + "Ġalgae": 17010, + "weights": 17011, + "casts": 17012, + "Ġocular": 17013, + "racycl": 17014, + "Ġdominates": 17015, + "Ġleuc": 17016, + "Where": 17017, + "phon": 17018, + "Ġsocioeconomic": 17019, + "itzerland": 17020, + "Ġresilience": 17021, + "Ġneighbourhood": 17022, + "Ġtone": 17023, + "psych": 17024, + "ĠOrganic": 17025, + "Ġgather": 17026, + "Ġfalciparum": 17027, + "Ġengineered": 17028, + "ĠAvail": 17029, + "intering": 17030, + "Ġclimatic": 17031, + "ĠEvolutionary": 17032, + "NMR": 17033, + "Ġrev": 17034, + "central": 17035, + "ĠSin": 17036, + "Ġdeclined": 17037, + "opausal": 17038, + "Ġalarm": 17039, + "Rightarrow": 17040, + "sex": 17041, + "Ġenergetic": 17042, + "ïĤ": 17043, + "Ġdiscs": 17044, + "Ġolfactory": 17045, + "uripot": 17046, + "spectrum": 17047, + "spot": 17048, + "Ġhemoglobin": 17049, + "Mark": 17050, + "cov": 17051, + "arboxyl": 17052, + "Ġindications": 17053, + "Ġsalmon": 17054, + "Ġsearched": 17055, + "Ġended": 17056, + "rologic": 17057, + "rfloor": 17058, + "Ġautism": 17059, + "Ġselen": 17060, + "ĠHung": 17061, + "ĠInference": 17062, + "Ġmammary": 17063, + "lfloor": 17064, + "Ġseroton": 17065, + "Ġfunded": 17066, + "ĠViet": 17067, + "Ġrivers": 17068, + "ĠReinfor": 17069, + "urg": 17070, + "Ġalbicans": 17071, + "ĠThermo": 17072, + "ERROR": 17073, + "Ġmutually": 17074, + "Ġirr": 17075, + "ĠRat": 17076, + "Ġimg": 17077, + "Ġlymphocyte": 17078, + "ĠRefs": 17079, + "ĠSparse": 17080, + "holders": 17081, + "Free": 17082, + "RED": 17083, + "ĠGauss": 17084, + "Ġcircadian": 17085, + "ĠJin": 17086, + "Ġconstitutes": 17087, + "Ġwors": 17088, + "Ġfeatured": 17089, + "ocent": 17090, + "lete": 17091, + "Ġontology": 17092, + "Ġbilayer": 17093, + "ĠCambridge": 17094, + "Ġencryption": 17095, + "rotron": 17096, + "etti": 17097, + "ĠAer": 17098, + "Ġcouples": 17099, + "rail": 17100, + "Ġtwist": 17101, + "Ġridge": 17102, + "GAN": 17103, + "iders": 17104, + "SHIFT": 17105, + "Ġdiffus": 17106, + "Ġmeant": 17107, + "ĠSchwarz": 17108, + "Sb": 17109, + "Ġarcs": 17110, + "Notice": 17111, + "iy": 17112, + "Ġemerge": 17113, + "kwargs": 17114, + "Eff": 17115, + "Ent": 17116, + "ionization": 17117, + "choline": 17118, + "ustries": 17119, + "acher": 17120, + "spl": 17121, + "population": 17122, + "fol": 17123, + "Ġquestionnaires": 17124, + "Ġallergic": 17125, + "wich": 17126, + "ĠVacc": 17127, + "Ġattained": 17128, + "ĠAnimals": 17129, + "amics": 17130, + "ĠRegarding": 17131, + "ĠSemi": 17132, + "Ġglac": 17133, + "ĠEfficacy": 17134, + "Ġsynergistic": 17135, + "ISH": 17136, + "Ġmaintains": 17137, + "Ġsongs": 17138, + "ĠNegative": 17139, + "amoto": 17140, + "ĠModified": 17141, + "Ġseparable": 17142, + "Ġbinaries": 17143, + "Ġaccessibility": 17144, + "Iter": 17145, + "din": 17146, + "ĠBinary": 17147, + "equilibrium": 17148, + "Ġcue": 17149, + "magn": 17150, + "Ġedema": 17151, + "�": 17152, + "Ġpositioned": 17153, + "Ġcharging": 17154, + "Ġunivariate": 17155, + "hep": 17156, + "Ġclade": 17157, + "Ġcysteine": 17158, + "racle": 17159, + "Ġrescue": 17160, + "habit": 17161, + "ĠDISCUSSION": 17162, + "Ġdepicts": 17163, + "pole": 17164, + "Ġstenosis": 17165, + "Ġveter": 17166, + "pringer": 17167, + "ĠPow": 17168, + "Ġcovariant": 17169, + "Ġmodifying": 17170, + "Algorithm": 17171, + "averaged": 17172, + "alo": 17173, + "reson": 17174, + "Ġcharacterised": 17175, + "Ġni": 17176, + "Ġseemed": 17177, + "ĠRom": 17178, + "short": 17179, + "NV": 17180, + "Ġfertility": 17181, + "ĠMemb": 17182, + "Ġlying": 17183, + "Ġinstitution": 17184, + "images": 17185, + "ĠBorel": 17186, + "fsys": 17187, + "cataly": 17188, + "Ġseparating": 17189, + "biotic": 17190, + "mel": 17191, + "pgfsys": 17192, + "ĠJackson": 17193, + "Ġbag": 17194, + "ograp": 17195, + "propyl": 17196, + "ĠProgramming": 17197, + "ocratic": 17198, + "Ġpion": 17199, + "ĠGradient": 17200, + "Ġsphe": 17201, + "Ġinline": 17202, + "Ġdominate": 17203, + "Ġsuffered": 17204, + "ĠDiseases": 17205, + "igenous": 17206, + "will": 17207, + "Ġamin": 17208, + "adherin": 17209, + "ĠTro": 17210, + "adjusted": 17211, + "EW": 17212, + "Ġdebut": 17213, + "nea": 17214, + "ĠDun": 17215, + "Ġdictionary": 17216, + "operatively": 17217, + "KA": 17218, + "beit": 17219, + "Ġpersonnel": 17220, + "ĠŽ": 17221, + "review": 17222, + "into": 17223, + "ĠTokyo": 17224, + "Ġtrop": 17225, + "Ġventric": 17226, + "ĠMETHODS": 17227, + "Ġimplication": 17228, + "akis": 17229, + "ĠCMB": 17230, + "Ġtransmitter": 17231, + "oichi": 17232, + "ĠNigeria": 17233, + "ĠKon": 17234, + "Ġbear": 17235, + "ĠKan": 17236, + "ĠPlot": 17237, + "ĠSPSS": 17238, + "ĠBiology": 17239, + "Ġbaryon": 17240, + "ĠmicroRNA": 17241, + "Ġreproducibility": 17242, + "Ġlactate": 17243, + "Ġpolyphen": 17244, + "ĠMt": 17245, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 17246, + "endit": 17247, + "Ġhydrothermal": 17248, + "Ġwealth": 17249, + "Ġhadron": 17250, + "Ġwhereby": 17251, + "ellum": 17252, + "ĠDiffusion": 17253, + "ĠOrigin": 17254, + "Ġnonlinearity": 17255, + "Ġinformative": 17256, + "Ġvisited": 17257, + "Ġvirtually": 17258, + "ĠTun": 17259, + "Ġreset": 17260, + "ĠElectrical": 17261, + "ĠGlu": 17262, + "ĠSAM": 17263, + "ĠIsing": 17264, + "ĠStra": 17265, + "onder": 17266, + "Ġdies": 17267, + "Ġreciprocal": 17268, + "Check": 17269, + "ĠGuidelines": 17270, + "hester": 17271, + "Ġproblematic": 17272, + "ĠAtomic": 17273, + "Ġconcentrate": 17274, + "steps": 17275, + "json": 17276, + "Recommended": 17277, + "ĠScreening": 17278, + "Ġnaive": 17279, + "Ġpractitioners": 17280, + "Ġfasting": 17281, + "Ġmechanistic": 17282, + "options": 17283, + "Ptr": 17284, + "ITE": 17285, + "Work": 17286, + "âĢĺ": 17287, + "rafts": 17288, + "Ġunw": 17289, + "Ġannihilation": 17290, + "objective": 17291, + "ĠDynamical": 17292, + "adec": 17293, + "ĠLith": 17294, + "Ġextracting": 17295, + "Ġcoral": 17296, + "ĠStable": 17297, + "Ġbackgrounds": 17298, + "omorphisms": 17299, + "ĠâĪ«": 17300, + "Ġgrew": 17301, + "Inst": 17302, + "gels": 17303, + "Ġinhal": 17304, + "dam": 17305, + "heim": 17306, + "benzyl": 17307, + "Ġpelvic": 17308, + "Ġdiarr": 17309, + "Ġdiode": 17310, + "Ġempir": 17311, + "ĠAlf": 17312, + "ĠUncertain": 17313, + "ĠHCl": 17314, + "Ġjointly": 17315, + "Ġdepar": 17316, + "Ġmerging": 17317, + "Ġchi": 17318, + "apt": 17319, + "Ġplt": 17320, + "Ġidi": 17321, + "Ġperfor": 17322, + "stituting": 17323, + "page": 17324, + "aré": 17325, + "indices": 17326, + "putation": 17327, + "different": 17328, + "burn": 17329, + "Ġsurrounded": 17330, + "ĠTL": 17331, + "untary": 17332, + "strip": 17333, + "lan": 17334, + "Ġcow": 17335, + "ĠSab": 17336, + "ĠGaAs": 17337, + "pf": 17338, + "Ġesophageal": 17339, + "ĠAlt": 17340, + "Ġhospitalization": 17341, + "ĠApproximation": 17342, + "Organism": 17343, + "ĠFair": 17344, + "Ġtracing": 17345, + "Ġpreferentially": 17346, + "Ġlowering": 17347, + "uliar": 17348, + "ĠDeriv": 17349, + "Ġphytoplankton": 17350, + "omyc": 17351, + "That": 17352, + "ĠIsrael": 17353, + "Ġminimized": 17354, + "Ġanything": 17355, + "rule": 17356, + "pow": 17357, + "Ġfamous": 17358, + "ĠAccuracy": 17359, + "Ġphotocatalytic": 17360, + "ĠNonetheless": 17361, + "Ġdivisor": 17362, + "vb": 17363, + "Ġcameras": 17364, + "ĠWales": 17365, + "ĠContributions": 17366, + "Ġdisplacements": 17367, + "ĠTam": 17368, + "Ġvolumetric": 17369, + "essional": 17370, + "Ġcompensate": 17371, + "Ġace": 17372, + "triangle": 17373, + "buff": 17374, + "Ġnamespace": 17375, + "Ġbounding": 17376, + "ynchronous": 17377, + "md": 17378, + "Ġimagery": 17379, + "itated": 17380, + "Ġoriginated": 17381, + "ĠBelg": 17382, + "ĠECG": 17383, + "existing": 17384, + "ĠStokes": 17385, + "sensitivity": 17386, + "tidine": 17387, + "ĠWM": 17388, + "Ġmonotone": 17389, + "Ġproceeds": 17390, + "ĠClustering": 17391, + "ĠIoT": 17392, + "ernary": 17393, + "alamic": 17394, + "ĠCollaboration": 17395, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 17396, + "OLD": 17397, + "Îĺ": 17398, + "ĠNanopar": 17399, + "ĠMultiv": 17400, + "Ġcystic": 17401, + "pire": 17402, + "Ġoperates": 17403, + "Ġmediating": 17404, + "Ġbeneath": 17405, + "obe": 17406, + "gate": 17407, + "Ġoocytes": 17408, + "Ġmargins": 17409, + "ymmetries": 17410, + "Ġreligious": 17411, + "ĠNit": 17412, + "Ġcutaneous": 17413, + "ANS": 17414, + "Ġdevelops": 17415, + "asia": 17416, + "ĠRoberts": 17417, + "avier": 17418, + "Ġsimplic": 17419, + "Ġrevealing": 17420, + "UND": 17421, + "Ġtea": 17422, + "Ġlysis": 17423, + "Ġaggregated": 17424, + "ĠRGB": 17425, + "Ġcorro": 17426, + "Ġbir": 17427, + "inae": 17428, + "vd": 17429, + "Ġcourt": 17430, + "Ġcontroversial": 17431, + "Ġtow": 17432, + "Ġhysteresis": 17433, + "enberg": 17434, + "Ġenters": 17435, + "png": 17436, + "ĠFlex": 17437, + "Assume": 17438, + "ĠBad": 17439, + "ĠSimilarities": 17440, + "Experim": 17441, + "ATH": 17442, + "Ġut": 17443, + "terms": 17444, + "ĠMol": 17445, + "Ġvisually": 17446, + "Ġadoption": 17447, + "Ġprinting": 17448, + "Ġequiv": 17449, + "ĠPert": 17450, + "Ġpercol": 17451, + "Ġsomeone": 17452, + "abulary": 17453, + "Ġlever": 17454, + "ĠHaus": 17455, + "icillin": 17456, + "itar": 17457, + "Ġtourn": 17458, + "Altern": 17459, + "Exp": 17460, + "~~~~": 17461, + "ĠFo": 17462, + "Ġabol": 17463, + "median": 17464, + "Ġrolling": 17465, + "hm": 17466, + "Ġtelescope": 17467, + "ĠCav": 17468, + "Ġseedlings": 17469, + "inhib": 17470, + "Ġdin": 17471, + "Ġimpurities": 17472, + "Ġamplifier": 17473, + "ĠKer": 17474, + "Ġdiminished": 17475, + "PB": 17476, + "fib": 17477, + "rock": 17478, + "ĠBin": 17479, + "Ġphotosynthetic": 17480, + "ĠCrypt": 17481, + "Ġpreterm": 17482, + "Ġhits": 17483, + "Ġfractal": 17484, + "Ġdiscarded": 17485, + "Ġendocrine": 17486, + "oshi": 17487, + "Ġmodulo": 17488, + "wt": 17489, + "Ġquenching": 17490, + "Ġsounds": 17491, + "ĠEDTA": 17492, + "reactive": 17493, + "Ġresist": 17494, + "anghai": 17495, + "Ġnarr": 17496, + "Ġinitiate": 17497, + "ĠSaint": 17498, + "XR": 17499, + "GeV": 17500, + "ĠIndependent": 17501, + "Ġinjective": 17502, + "upus": 17503, + "Ġlinguistic": 17504, + "Ġanalogues": 17505, + "Ġdissection": 17506, + "Ġlasers": 17507, + "diab": 17508, + "ĠTele": 17509, + "Ġcracks": 17510, + "Ġbrane": 17511, + "VO": 17512, + "ĠExtended": 17513, + "Ġtells": 17514, + "Ġremarks": 17515, + "ulting": 17516, + "ĠBurn": 17517, + "dL": 17518, + "ressible": 17519, + "ĠChap": 17520, + "Ġsq": 17521, + "Ġreproduced": 17522, + "ĠBcl": 17523, + "Ġswarm": 17524, + "opathology": 17525, + "chrotron": 17526, + "Ġmine": 17527, + "Ġhadronic": 17528, + "ĠLocalization": 17529, + "ĠMotor": 17530, + "Ġvisualize": 17531, + "Ġcats": 17532, + "Ġbalancing": 17533, + "ĠSched": 17534, + "CoA": 17535, + "Ġthermodynamics": 17536, + "ĠDiagnostic": 17537, + "Ġrelief": 17538, + "Ġpositivity": 17539, + "Ġhub": 17540, + "ĠInfrared": 17541, + "Sur": 17542, + "omed": 17543, + "Ġoptically": 17544, + "Ġvascul": 17545, + "isations": 17546, + "encoder": 17547, + "Ġcopolymer": 17548, + "Ġrestore": 17549, + "Ġinertia": 17550, + "ubicin": 17551, + "Ġetiology": 17552, + "ĠSecret": 17553, + "ĠCW": 17554, + "Const": 17555, + "ĠBrit": 17556, + "ĠConstant": 17557, + "ĠDIS": 17558, + "Ġdiscipl": 17559, + "bra": 17560, + "ĠOral": 17561, + "ĠUL": 17562, + "Ġdeline": 17563, + "Ġnucleon": 17564, + "Ġemployment": 17565, + "ĠRD": 17566, + "qq": 17567, + "ĠCarolina": 17568, + "ĠGab": 17569, + "Ġassertion": 17570, + "CMC": 17571, + "rgb": 17572, + "Frame": 17573, + "ĠJust": 17574, + "Ġinoculation": 17575, + "cluding": 17576, + "Ġoscillatory": 17577, + "Ġcancel": 17578, + "ĠPoinc": 17579, + "pora": 17580, + "ĠJul": 17581, + "ruvate": 17582, + "Ġpolitic": 17583, + "urus": 17584, + "ĠAdvances": 17585, + "ĠRoot": 17586, + "thood": 17587, + "oxygenase": 17588, + "msg": 17589, + "ĠkV": 17590, + "Ġadmit": 17591, + "Ġrefractory": 17592, + "Ġcloning": 17593, + "Ġfatal": 17594, + "plantation": 17595, + "ĠGir": 17596, + "Ġtes": 17597, + "ĠRho": 17598, + "ohn": 17599, + "Ġinnovation": 17600, + "Ġsending": 17601, + "Ġcable": 17602, + "Ġniche": 17603, + "Ġreserve": 17604, + "Ġatrophy": 17605, + "athan": 17606, + "ĠÃij": 17607, + "itization": 17608, + "Ġfan": 17609, + "Ġbubbles": 17610, + "ĠTheorems": 17611, + "ĠSwitzerland": 17612, + "ĠHeisenberg": 17613, + "ĠReduced": 17614, + "Ra": 17615, + "Zr": 17616, + "ĠPossible": 17617, + "Upsilon": 17618, + "ĠAgric": 17619, + "ellect": 17620, + "nds": 17621, + "mathds": 17622, + "atre": 17623, + "Ġforaging": 17624, + "Ġupward": 17625, + "idene": 17626, + "Ġglands": 17627, + "fed": 17628, + "uccessful": 17629, + "ĠWolf": 17630, + "Ġusefulness": 17631, + "oporous": 17632, + "Ġpunct": 17633, + "ardo": 17634, + "Ġsystolic": 17635, + "ĠTargeting": 17636, + "Ġillumin": 17637, + "Ġpigment": 17638, + "Ġsimulating": 17639, + "Ġportions": 17640, + "ĠPrinciples": 17641, + "ĠHopf": 17642, + "lipid": 17643, + "ĠLU": 17644, + "ubation": 17645, + "ĠArtificial": 17646, + "Ġprison": 17647, + "aning": 17648, + "ĠGN": 17649, + "ĠStrategies": 17650, + "ĠPas": 17651, + "Ta": 17652, + "ĠProbability": 17653, + "orum": 17654, + "Ġskeleton": 17655, + "Ġcompartments": 17656, + "Read": 17657, + "Ġcoach": 17658, + "Ġmodality": 17659, + "ĠRegister": 17660, + "Ġje": 17661, + "Ġheights": 17662, + "inyl": 17663, + "Ġsubspaces": 17664, + "tip": 17665, + "Ġá¸": 17666, + "ĠGI": 17667, + "Char": 17668, + "rogenic": 17669, + "rett": 17670, + "eutics": 17671, + "Ġadhesive": 17672, + "ĠPier": 17673, + "Left": 17674, + "idental": 17675, + "NAc": 17676, + "Ġconjugation": 17677, + "orov": 17678, + "idge": 17679, + "imaging": 17680, + "ĠTW": 17681, + "Ġpresident": 17682, + "ĠOste": 17683, + "assemb": 17684, + "Ġinternet": 17685, + "Ġdeals": 17686, + "ĠGAP": 17687, + "Ġformulate": 17688, + "ĠUpdate": 17689, + "ĠRNAi": 17690, + "clero": 17691, + "Ġpermutations": 17692, + "Ġisotopes": 17693, + "opic": 17694, + "ĠQU": 17695, + "romes": 17696, + "ĠPolicy": 17697, + "ĠCreek": 17698, + "ĠWindows": 17699, + "Ġmerge": 17700, + "Ġaccident": 17701, + "Ġsuperposition": 17702, + "Ġdebate": 17703, + "Ġdocumentation": 17704, + "Ġeigenvectors": 17705, + "sor": 17706, + "ĠPhoto": 17707, + "Ġdeposit": 17708, + "Ġgermination": 17709, + "Ġsubgraph": 17710, + "ĠRecords": 17711, + "Ġchemically": 17712, + "ĠPredicting": 17713, + "ĠKy": 17714, + "selective": 17715, + "ynman": 17716, + "dispers": 17717, + "Ġlumbar": 17718, + "Ġmusical": 17719, + "inates": 17720, + "Ġinherited": 17721, + "ju": 17722, + "Ġtracer": 17723, + "Ġending": 17724, + "Ġengaged": 17725, + "handed": 17726, + "Ġproducer": 17727, + "Ġentangled": 17728, + "ĠDelta": 17729, + "Ġpiecewise": 17730, + "NAME": 17731, + "stop": 17732, + "Ġmutated": 17733, + "Ġrecess": 17734, + "Ġimmuno": 17735, + "cancer": 17736, + "ĠAkt": 17737, + "iters": 17738, + "ĠBMP": 17739, + "Ġcompanion": 17740, + "Ġcommunicate": 17741, + "Ġhollow": 17742, + "Ġpad": 17743, + "Ġsph": 17744, + "omod": 17745, + "Ġparton": 17746, + "Ġspontaneously": 17747, + "eared": 17748, + "Ġrotations": 17749, + "Ġcosmology": 17750, + "Ġmoreover": 17751, + "princ": 17752, + "Ġeverywhere": 17753, + "brane": 17754, + "lational": 17755, + "eme": 17756, + "Ġbehave": 17757, + "umen": 17758, + "oston": 17759, + "oves": 17760, + "Ġgar": 17761, + "Ġadrenal": 17762, + "ĠEstimating": 17763, + "Nb": 17764, + "Ġechocardi": 17765, + "Ġemphasized": 17766, + "Ġengines": 17767, + "Ġbrackets": 17768, + "Ġleaders": 17769, + "Ġdistinctive": 17770, + "ĠLymph": 17771, + "Ġexert": 17772, + "Ġinnovative": 17773, + "coupling": 17774, + "ĠSignific": 17775, + "sheet": 17776, + "ĠCover": 17777, + "ĠCCD": 17778, + "ĠFall": 17779, + "stimulated": 17780, + "Ġsuperoxide": 17781, + "Ġpollutants": 17782, + "Ġbytes": 17783, + "ĠLipid": 17784, + "Ġtrafficking": 17785, + "Ġleadership": 17786, + "informatics": 17787, + "Ġbiodiversity": 17788, + "ador": 17789, + "Ġinterconn": 17790, + "Ġharmonics": 17791, + "Ġseawater": 17792, + "ĠIllumina": 17793, + "necessary": 17794, + "ĠAnton": 17795, + "Ġprocessors": 17796, + "typename": 17797, + "Det": 17798, + "proton": 17799, + "Ġsubtraction": 17800, + "Ġshifting": 17801, + "Ġcustomers": 17802, + "Ke": 17803, + "ĠOB": 17804, + "atonin": 17805, + "atellite": 17806, + "ĠSUS": 17807, + "ĠColon": 17808, + "ĠTimes": 17809, + "TV": 17810, + "ĠMink": 17811, + "ĠIntegration": 17812, + "Ġprofound": 17813, + "ITC": 17814, + "Ġgras": 17815, + "ĠNASA": 17816, + "ĠACK": 17817, + "radiol": 17818, + "ĠMale": 17819, + "ĠWorking": 17820, + "ticity": 17821, + "ilibria": 17822, + "boundary": 17823, + "ĠRI": 17824, + "ĠAli": 17825, + "cardi": 17826, + "ĠFGF": 17827, + "branes": 17828, + "Ġbeet": 17829, + "Ġmissed": 17830, + "Source": 17831, + "ĠBot": 17832, + "ieve": 17833, + "Ġisother": 17834, + "neys": 17835, + "nl": 17836, + "ortion": 17837, + "Ġcooled": 17838, + "MV": 17839, + "Ġomit": 17840, + "Ġverbal": 17841, + "arette": 17842, + "Ġconference": 17843, + "Ġtransformer": 17844, + "Ġrejected": 17845, + "Ġprogressively": 17846, + "ĠTurkey": 17847, + "Ġathletes": 17848, + "Ġanatomy": 17849, + "EQ": 17850, + "Ġdeterioration": 17851, + "ĠDietary": 17852, + "Ġcorn": 17853, + "Ġcapsule": 17854, + "Ġvibrations": 17855, + "Ġoccupational": 17856, + "Ġexosomes": 17857, + "Ġrewritten": 17858, + "Ġlignin": 17859, + "Ġbiopsies": 17860, + "ĠAdversarial": 17861, + "Ġmercury": 17862, + "Ġplatinum": 17863, + "Ġirrelevant": 17864, + "Ġkeratin": 17865, + "ĠEmission": 17866, + "Ġeukaryotic": 17867, + "Ġinteg": 17868, + "Ġknot": 17869, + "Ġsera": 17870, + "Ġcavities": 17871, + "ĠMedi": 17872, + "Indeed": 17873, + "Eu": 17874, + "ĠâŁ": 17875, + "Ġscenes": 17876, + "Ġlaparoscopic": 17877, + "Ġsenior": 17878, + "ĠDistance": 17879, + "predic": 17880, + "Ġearliest": 17881, + "Ġorg": 17882, + "ĠThor": 17883, + "bury": 17884, + "oblasts": 17885, + "Ġpumping": 17886, + "targeted": 17887, + "Ġrap": 17888, + "ĠPil": 17889, + "Îł": 17890, + "Ġneurom": 17891, + "oft": 17892, + "ostat": 17893, + "Ġpadding": 17894, + "Ġconflicts": 17895, + "Ġstems": 17896, + "ĠSaccharomyces": 17897, + "engine": 17898, + "Ġalkyl": 17899, + "Ġtill": 17900, + "ĠQuad": 17901, + "good": 17902, + "rox": 17903, + "ĠFuzzy": 17904, + "Ġrobotic": 17905, + "ĠDenote": 17906, + "ĠNIR": 17907, + "ĠYuk": 17908, + "parency": 17909, + "Ġlegs": 17910, + "ylvan": 17911, + "Ġtightly": 17912, + "Ġdecor": 17913, + "ĠVP": 17914, + "ĠMun": 17915, + "atoms": 17916, + "ĠSilver": 17917, + "Ġneurodegenerative": 17918, + "Ġresponded": 17919, + "Ġrecons": 17920, + "GEN": 17921, + "ĠFine": 17922, + "fc": 17923, + "Ġparagraph": 17924, + "Ġintens": 17925, + "Ġalongside": 17926, + "Ġbrand": 17927, + "monium": 17928, + "Ġpm": 17929, + "Ġsimplex": 17930, + "ĠPreliminary": 17931, + "Ġdownregulation": 17932, + "Ġxy": 17933, + "ĠMak": 17934, + "opter": 17935, + "ushing": 17936, + "ĠBog": 17937, + "oxia": 17938, + "================================": 17939, + "common": 17940, + "ĠASS": 17941, + "ĠHDL": 17942, + "alamus": 17943, + "Ġirrigation": 17944, + "NM": 17945, + "Ġfading": 17946, + "Ġpreventive": 17947, + "Ġreliably": 17948, + "ĠEthiopia": 17949, + "othesis": 17950, + "izability": 17951, + "OB": 17952, + "Ġtriglycer": 17953, + "Ġgestational": 17954, + "Ġbesides": 17955, + "ĠIii": 17956, + "ĠZone": 17957, + "Ġcoping": 17958, + "Ġminority": 17959, + "Ġdeprivation": 17960, + "Ġhexagonal": 17961, + "chlorophenyl": 17962, + "ĠóµĦ¨": 17963, + "Ġgyr": 17964, + "Ġviewing": 17965, + "Newton": 17966, + "ĠHierarchical": 17967, + "oL": 17968, + "eces": 17969, + "Ġconcludes": 17970, + "Ġfungus": 17971, + "Ġpylori": 17972, + "Ġobstacles": 17973, + "thiazol": 17974, + "conjugated": 17975, + "rass": 17976, + "Ġlose": 17977, + "Ġforth": 17978, + "ĠAllen": 17979, + "oplast": 17980, + "ĠProtection": 17981, + "Ġintermittent": 17982, + "Ġluciferase": 17983, + "ĠMK": 17984, + "Ġgaug": 17985, + "ĠFan": 17986, + "Ġmodal": 17987, + "ĠExercise": 17988, + "scattering": 17989, + "ĠShim": 17990, + "Ġexcretion": 17991, + "Ġatypical": 17992, + "Ġmalignancies": 17993, + "anglades": 17994, + "ĠSpectroscopy": 17995, + "Ġadenosine": 17996, + "lif": 17997, + "Ġnucleic": 17998, + "Ġinclination": 17999, + "ĠCass": 18000, + "Ġethn": 18001, + "Ġexempl": 18002, + "ĠDy": 18003, + "Ġlambda": 18004, + "Ġjac": 18005, + "ĠPRE": 18006, + "Ġrailway": 18007, + "Ġfle": 18008, + "Ġreflections": 18009, + "Ġnanostructures": 18010, + "tists": 18011, + "prints": 18012, + "ĠCAT": 18013, + "Ġsib": 18014, + "Ġchloro": 18015, + "Ġrecipient": 18016, + "optic": 18017, + "Ġcounty": 18018, + "Ġnucleotides": 18019, + "Ġzircon": 18020, + "Ġhorses": 18021, + "ĠMental": 18022, + "inline": 18023, + "ĠNorway": 18024, + "They": 18025, + "Ġmuscular": 18026, + "acetic": 18027, + "ĠJu": 18028, + "Ġcommunic": 18029, + "files": 18030, + "filled": 18031, + "HB": 18032, + "Ġregulations": 18033, + "Ġaccumulate": 18034, + "ĠPanel": 18035, + "Cy": 18036, + "öl": 18037, + "ĠPakistan": 18038, + "Ġthoracic": 18039, + "ĠMPI": 18040, + "portion": 18041, + "Ġinductive": 18042, + "ĠCongress": 18043, + "Ġfibroblast": 18044, + "clust": 18045, + "Ġcentres": 18046, + "adel": 18047, + "Ġsubstitutions": 18048, + "Ġtruncation": 18049, + "rification": 18050, + "oka": 18051, + "Flow": 18052, + "ĠReduc": 18053, + "polarized": 18054, + "ibular": 18055, + "Pe": 18056, + "ĠAML": 18057, + "ĠAgency": 18058, + "Ġtilt": 18059, + "ublished": 18060, + "Ġdepolar": 18061, + "Ġbelt": 18062, + "Ġoptimizer": 18063, + "ELL": 18064, + "ĠHandbook": 18065, + "ĠVirginia": 18066, + "sense": 18067, + "ĠDur": 18068, + "Ġpiezoelectric": 18069, + "Ġawarded": 18070, + "ailing": 18071, + "Pos": 18072, + "pref": 18073, + "ĠSummer": 18074, + "edo": 18075, + "ĠIde": 18076, + "ĠBSA": 18077, + "Ġmonomers": 18078, + "Ġcoagulation": 18079, + "Ġgam": 18080, + "Ġhomes": 18081, + "Ġheads": 18082, + "admium": 18083, + "ĠOC": 18084, + "Ġoccupancy": 18085, + "ĠEmpirical": 18086, + "ĠIi": 18087, + "Ġchir": 18088, + "Ġdegeneracy": 18089, + "Ġflowers": 18090, + "Ġsuperconductivity": 18091, + "Ġinversely": 18092, + "optical": 18093, + "were": 18094, + "ĠAsymptotic": 18095, + "Sec": 18096, + "title": 18097, + "posal": 18098, + "ĠProgn": 18099, + "Ġposes": 18100, + "ĠBorn": 18101, + "Ġcontinuation": 18102, + "Ġcultivated": 18103, + "entiment": 18104, + "Ġmanaging": 18105, + "Ġthrombosis": 18106, + "aug": 18107, + "CNT": 18108, + "urea": 18109, + "Ġspind": 18110, + "ĠWhereas": 18111, + "ĠPerson": 18112, + "Ġbipartite": 18113, + "Ġrescal": 18114, + "Ġmarkets": 18115, + "phan": 18116, + "perties": 18117, + "Ġfermionic": 18118, + "Ġmunicip": 18119, + "Ġachievable": 18120, + "tab": 18121, + "Åį": 18122, + "ĠRelation": 18123, + "Total": 18124, + "xia": 18125, + "Ġintelligent": 18126, + "ĠUT": 18127, + "ĠDal": 18128, + "Ġmedicinal": 18129, + "Ġinadequate": 18130, + "iently": 18131, + "ersen": 18132, + "Ġprecondition": 18133, + "Ġmethodological": 18134, + "Ġcanopy": 18135, + "Ġbacterium": 18136, + "column": 18137, + "Cal": 18138, + "ĠDiego": 18139, + "ĠSak": 18140, + "ĠComprehensive": 18141, + "Ġantitumor": 18142, + "Ġflower": 18143, + "ĠKhan": 18144, + "Ġmetadata": 18145, + "Ġphotore": 18146, + "ogenicity": 18147, + "Ġleague": 18148, + "olating": 18149, + "Ġpromise": 18150, + "ĠPere": 18151, + "Ġpermits": 18152, + "Ġthreads": 18153, + "ĠDCs": 18154, + "ĠCham": 18155, + "razol": 18156, + "Bank": 18157, + "Ġwithdrawal": 18158, + "Ġappend": 18159, + "othelial": 18160, + "ĠMeasures": 18161, + "Ġguideline": 18162, + "Ġmitigate": 18163, + "adjoint": 18164, + "Ġbracket": 18165, + "Pad": 18166, + "Mills": 18167, + "Buffer": 18168, + "Ġcass": 18169, + "hoc": 18170, + "manifolds": 18171, + "herry": 18172, + "Ġfacilitated": 18173, + "Event": 18174, + "ĠÈ": 18175, + "ĠCruz": 18176, + "ĠBrand": 18177, + "Ġnecessity": 18178, + "burgh": 18179, + "ĠmeV": 18180, + "ĠcAMP": 18181, + "Off": 18182, + "selected": 18183, + "Ġengage": 18184, + "Ġredundancy": 18185, + "Ġnanocomposites": 18186, + "solution": 18187, + "onset": 18188, + "ĠExposure": 18189, + "Ġrepetitive": 18190, + "Ãł": 18191, + "ĠRAD": 18192, + "ĠTurk": 18193, + "Ġcorneal": 18194, + "Ġexploiting": 18195, + "Ġobstructive": 18196, + "gramming": 18197, + "ĠMED": 18198, + "Ġmathem": 18199, + "Ġconductive": 18200, + "Ġphotosynthesis": 18201, + "Einstein": 18202, + "ĠPeng": 18203, + "MW": 18204, + "ĠSchmidt": 18205, + "Ġrepetition": 18206, + "identified": 18207, + "Ġinjured": 18208, + "Ġdefective": 18209, + "ĠPel": 18210, + "Ġcultivation": 18211, + "Ġfirstly": 18212, + "Ġanalyzer": 18213, + "Ġstainless": 18214, + "Ġjoining": 18215, + "ĠOxidative": 18216, + "Ġphage": 18217, + "Ġexpendit": 18218, + "Ġhomogeneity": 18219, + "iple": 18220, + "ovic": 18221, + "Ġcrossed": 18222, + "ĠTrust": 18223, + "ĠFract": 18224, + "rophysiological": 18225, + "Ġbasically": 18226, + "Ġcoales": 18227, + "Ġgravit": 18228, + "fulness": 18229, + "cano": 18230, + "Ġcolitis": 18231, + "Ġchaos": 18232, + "carbons": 18233, + "Once": 18234, + "ĠToward": 18235, + "orf": 18236, + "topic": 18237, + "ĠPlay": 18238, + "ĠCorrespond": 18239, + "ĠSleep": 18240, + "ticularly": 18241, + "cumin": 18242, + "vdots": 18243, + "ĠRhe": 18244, + "Ġultraf": 18245, + "Ġtimescale": 18246, + "ĠDetails": 18247, + "angles": 18248, + "Ġsurrogate": 18249, + "ĠFluid": 18250, + "cz": 18251, + "Ġinitialization": 18252, + "ĠTelescope": 18253, + "rases": 18254, + "ĠStock": 18255, + "ĠCond": 18256, + "Ġimmunodeficiency": 18257, + "Bel": 18258, + "oser": 18259, + "shown": 18260, + "Ġkcal": 18261, + "Equation": 18262, + "protective": 18263, + "Ġcalling": 18264, + "Ġanticipated": 18265, + "Ġambiguity": 18266, + "ĠNode": 18267, + "ĠGD": 18268, + "Ġinlet": 18269, + "Ġbread": 18270, + "Ġexceeded": 18271, + "Ġimmunization": 18272, + "Ġprohib": 18273, + "ytic": 18274, + "Ġboys": 18275, + "tu": 18276, + "Ġtower": 18277, + "Like": 18278, + "ĠAnomal": 18279, + "âĮ": 18280, + "ĠShow": 18281, + "Ġimaged": 18282, + "Ġequil": 18283, + "Ġrendering": 18284, + "obility": 18285, + "Ġgeological": 18286, + "friend": 18287, + "ör": 18288, + "carboxamide": 18289, + "ovolta": 18290, + "Current": 18291, + "ĠSti": 18292, + "ĠMU": 18293, + "Ġvalued": 18294, + "Ġpoison": 18295, + "Ġpractically": 18296, + "Ġrequested": 18297, + "Code": 18298, + "Ġbrings": 18299, + "Ġdimethyl": 18300, + "hyp": 18301, + "cemic": 18302, + "Vol": 18303, + "quanti": 18304, + "Ġexha": 18305, + "Ġresponsibility": 18306, + "ĠControlled": 18307, + "Ġfur": 18308, + "Ġresemb": 18309, + "ĠKaw": 18310, + "Ġevoked": 18311, + "Ġuterine": 18312, + "л": 18313, + "Ġanonymous": 18314, + "ĠChallenges": 18315, + "Ġanchor": 18316, + "ĠAbd": 18317, + "Der": 18318, + "Ġthermally": 18319, + "ĠCAP": 18320, + "oblot": 18321, + "ĠFire": 18322, + "Ġdiagnostics": 18323, + "Ġexecute": 18324, + "alis": 18325, + "roni": 18326, + "ĠHarris": 18327, + "ĠGonz": 18328, + "Ġvig": 18329, + "ĠProfessor": 18330, + "Ġinventory": 18331, + "intensity": 18332, + "ĠNSCLC": 18333, + "Ġinterfere": 18334, + "ysaccharides": 18335, + "Ġregener": 18336, + "ĠAuthors": 18337, + "Ġtranslate": 18338, + "ĠTests": 18339, + "ĠLove": 18340, + "ĠInduced": 18341, + "ennis": 18342, + "ĠGEN": 18343, + "Ġoligonucle": 18344, + "Ġmeter": 18345, + "satisf": 18346, + "hesion": 18347, + "Ġtransporters": 18348, + "BIT": 18349, + "ĠConc": 18350, + "Ġglauc": 18351, + "scores": 18352, + "Ġmerger": 18353, + "GH": 18354, + "Ġstoichi": 18355, + "ĠXia": 18356, + "effects": 18357, + "ĠExploring": 18358, + "dorff": 18359, + "Ġcardinality": 18360, + "ĠKaz": 18361, + "false": 18362, + "ĠHSP": 18363, + "Ġunsupervised": 18364, + "inguish": 18365, + "ischer": 18366, + "Ġrelativity": 18367, + "onormal": 18368, + "oothed": 18369, + "edges": 18370, + "ĠIMP": 18371, + "Ġimpulse": 18372, + "ĠColumbia": 18373, + "Ġparticulate": 18374, + "ĠSupporting": 18375, + "ĠSDSS": 18376, + "voltage": 18377, + "ĠAmazon": 18378, + "Ġepoxy": 18379, + "Call": 18380, + "Bigl": 18381, + "Ġmeets": 18382, + "Ġequatorial": 18383, + "Ġneuros": 18384, + "Ġperitoneal": 18385, + "desc": 18386, + "inputs": 18387, + "Ġexterior": 18388, + "aco": 18389, + "Ġmeal": 18390, + "ĠDaniel": 18391, + "Ġintuitive": 18392, + "Ġcouns": 18393, + "depress": 18394, + "inis": 18395, + "phot": 18396, + "ĠAmin": 18397, + "Ġreservoirs": 18398, + "ĠWhole": 18399, + "Ġcaud": 18400, + "Ġbosonic": 18401, + "Ġreaders": 18402, + "Ġcrim": 18403, + "Ġpathophysiology": 18404, + "argo": 18405, + "these": 18406, + "income": 18407, + "Ġissued": 18408, + "Ġhepatocytes": 18409, + "ĠCi": 18410, + "deriv": 18411, + "upta": 18412, + "tuple": 18413, + "ĠChan": 18414, + "Ġauthentication": 18415, + "ygd": 18416, + "Ġinfin": 18417, + "Ġaccelerate": 18418, + "eptive": 18419, + "Ġhydrogel": 18420, + "aska": 18421, + "ONE": 18422, + "Ġfederal": 18423, + "ographics": 18424, + "Ġmuon": 18425, + "Ġslide": 18426, + "Ġelliptical": 18427, + "atite": 18428, + "Ġcc": 18429, + "ETs": 18430, + "Ġclarity": 18431, + "ocycl": 18432, + "isal": 18433, + "rections": 18434, + "ayan": 18435, + "roweak": 18436, + "ĠSOC": 18437, + "oderm": 18438, + "tun": 18439, + "asm": 18440, + "ĠHir": 18441, + "likelihood": 18442, + "Ġadul": 18443, + "tl": 18444, + "High": 18445, + "Ġalters": 18446, + "plitude": 18447, + "ĠRelease": 18448, + "Ġharmful": 18449, + "late": 18450, + "ounds": 18451, + "ĠFederal": 18452, + "ĠEconomic": 18453, + "Ġrabb": 18454, + "Ġaccommodate": 18455, + "emission": 18456, + "ĠBah": 18457, + "cox": 18458, + "ĠModulation": 18459, + "Ġconstructions": 18460, + "igner": 18461, + "ĠUrban": 18462, + "Ġwake": 18463, + "Ġadversary": 18464, + "wikipedia": 18465, + "Ġsuite": 18466, + "wick": 18467, + "expressed": 18468, + "rod": 18469, + "KD": 18470, + "Ġcomputers": 18471, + "ĠBanglades": 18472, + "Ġpersist": 18473, + "Ġburning": 18474, + "Ġadministrative": 18475, + "Ġplug": 18476, + "ĠRepresentations": 18477, + "ĠScattering": 18478, + "Ġendometrial": 18479, + "Ġdescriptors": 18480, + "Ġcommission": 18481, + "Bar": 18482, + "ighth": 18483, + "ĠMarsh": 18484, + "sampling": 18485, + "Ġhull": 18486, + "icin": 18487, + "Prob": 18488, + "Ġnurse": 18489, + "Ġsham": 18490, + "ĠKerr": 18491, + "Ġprefrontal": 18492, + "Ġfixing": 18493, + "OK": 18494, + "Ġbold": 18495, + "Ġcorollary": 18496, + "cfg": 18497, + "ĠOxford": 18498, + "Ġboron": 18499, + "RB": 18500, + "ĠCab": 18501, + "Bigr": 18502, + "ĠPredict": 18503, + "Ġpeculiar": 18504, + "hidden": 18505, + "isa": 18506, + "iden": 18507, + "appropriate": 18508, + "orh": 18509, + "ellectual": 18510, + "Ġseizures": 18511, + "asser": 18512, + "tilis": 18513, + "handle": 18514, + "iaxial": 18515, + "sym": 18516, + "Ġcarcinomas": 18517, + "sea": 18518, + "spired": 18519, + "Ġabrupt": 18520, + "tests": 18521, + "Ġwelfare": 18522, + "ĠOil": 18523, + "ĠLoad": 18524, + "FLAG": 18525, + "uthal": 18526, + "Ġfacing": 18527, + "American": 18528, + "LAS": 18529, + "Ġirrespective": 18530, + "Ġroutinely": 18531, + "wal": 18532, + "Ġsettlement": 18533, + "ĠAqu": 18534, + "Ġelectronics": 18535, + "Ġhandled": 18536, + "Ġbiologically": 18537, + "smooth": 18538, + "ĠBelongs": 18539, + "tib": 18540, + "Ġtrav": 18541, + "pressive": 18542, + "ournals": 18543, + "к": 18544, + "filename": 18545, + "Ġhelical": 18546, + "Ġbacteri": 18547, + "Ġsatellites": 18548, + "BH": 18549, + "ented": 18550, + "ĠFootball": 18551, + "Ġ": 18552, + "ĠHV": 18553, + "Ġtrip": 18554, + "ĠCKD": 18555, + "rani": 18556, + "Ġcleaning": 18557, + "limit": 18558, + "ĠTCP": 18559, + "Ġscin": 18560, + "Ġsludge": 18561, + "Ġsymbolic": 18562, + "ĠSequencing": 18563, + "adal": 18564, + "ĠPhilipp": 18565, + "ICS": 18566, + "Ġvaginal": 18567, + "Ġcommitment": 18568, + "ĠAwards": 18569, + "trig": 18570, + "Ġguitar": 18571, + "acetate": 18572, + "Ġbet": 18573, + "ClN": 18574, + "Ġagriculture": 18575, + "Ġchief": 18576, + "Ġembol": 18577, + "build": 18578, + "Ġtexts": 18579, + "ĠCooper": 18580, + "lived": 18581, + "ĠDelay": 18582, + "ĠMode": 18583, + "yal": 18584, + "BN": 18585, + "Ġindexed": 18586, + "expr": 18587, + "ERN": 18588, + "vens": 18589, + "Ġpointer": 18590, + "cv": 18591, + "acon": 18592, + "tance": 18593, + "ĠâĪĿ": 18594, + "Ġlowered": 18595, + "Ġmitotic": 18596, + "rhosis": 18597, + "ĠPage": 18598, + "ür": 18599, + "imm": 18600, + "ĠTherapeutic": 18601, + "Ġosteopor": 18602, + "Ġbilinear": 18603, + "ĠCatholic": 18604, + "ĠAlternative": 18605, + "oxidation": 18606, + "Ġinitio": 18607, + "benzo": 18608, + "ĠAdi": 18609, + "person": 18610, + "peritoneal": 18611, + "ĉĉĠ": 18612, + "Ġattraction": 18613, + "Ġdiarrhea": 18614, + "Ġren": 18615, + "ĠISO": 18616, + "imir": 18617, + "Ġterminology": 18618, + "ukey": 18619, + "Ġresonator": 18620, + "Ġsubstituting": 18621, + "Ġharbor": 18622, + "provid": 18623, + "decay": 18624, + "ĠHDAC": 18625, + "ĠAnalytical": 18626, + "Ġpostnatal": 18627, + "Ġundes": 18628, + "Specific": 18629, + "dichlor": 18630, + "ARI": 18631, + "tot": 18632, + "Ġdigit": 18633, + "oping": 18634, + "ĠZinc": 18635, + "Ġlethal": 18636, + "Whitney": 18637, + "Fi": 18638, + "quantum": 18639, + "ĠFailure": 18640, + "Ġsolves": 18641, + "ĠSpaces": 18642, + "earman": 18643, + "Ġgoat": 18644, + "Ġsynapses": 18645, + "Ġresuspended": 18646, + "Ġresident": 18647, + "Ġcompac": 18648, + "Ġcortisol": 18649, + "Ġphotometry": 18650, + "WP": 18651, + "select": 18652, + "Ġcele": 18653, + "orubicin": 18654, + "ĠMultic": 18655, + "ĠJean": 18656, + "Ġclip": 18657, + "Ġsa": 18658, + "oco": 18659, + "geometric": 18660, + "Ġhelic": 18661, + "Ġempirically": 18662, + "Ġmicrofluid": 18663, + "idis": 18664, + "Ġautocor": 18665, + "WF": 18666, + "ĠRespir": 18667, + "radiation": 18668, + "Ġthemes": 18669, + "Ġtaste": 18670, + "ricing": 18671, + "Ġexaminations": 18672, + "ĠSensing": 18673, + "same": 18674, + "DEFAULT": 18675, + "Ġphylogeny": 18676, + "hig": 18677, + "Ġplatelets": 18678, + "ĠHistor": 18679, + "aba": 18680, + "Ġresidential": 18681, + "Ġunbounded": 18682, + "anding": 18683, + "hedron": 18684, + "rys": 18685, + "ĠCCR": 18686, + "Ġconce": 18687, + "Ġparasitic": 18688, + "cb": 18689, + "ĠFeynman": 18690, + "ĠKepler": 18691, + "ô": 18692, + "ĠGil": 18693, + "ĠMATLAB": 18694, + "ben": 18695, + "scope": 18696, + "Ġdiscrimin": 18697, + "Ġjustified": 18698, + "plasma": 18699, + "ĠChoi": 18700, + "Ġroof": 18701, + "PCA": 18702, + "ĠTCR": 18703, + "Ġvoxel": 18704, + "ĠWard": 18705, + "Ġuncor": 18706, + "Stok": 18707, + "Ġspur": 18708, + "TRA": 18709, + "Ġdiagnoses": 18710, + "rophysical": 18711, + "ategories": 18712, + "Ġoverestim": 18713, + "Ġstreaming": 18714, + "ĠRecovery": 18715, + "Ġeverything": 18716, + "LOW": 18717, + "Gener": 18718, + "Ġunbiased": 18719, + "Ġvariances": 18720, + "compact": 18721, + "espan": 18722, + "inj": 18723, + "Ġendoscopic": 18724, + "Ġideals": 18725, + "ĠRice": 18726, + "ĠKaplan": 18727, + "Ġfecal": 18728, + "ferred": 18729, + "ĠCycle": 18730, + "Ġimplanted": 18731, + "Ġwine": 18732, + "PET": 18733, + "Ġassignments": 18734, + "Ġabsol": 18735, + "XT": 18736, + "Ġswimming": 18737, + "MN": 18738, + "ĠGeometric": 18739, + "ĠHealthcare": 18740, + "Ġpowders": 18741, + "ĠGel": 18742, + "Ġdownward": 18743, + "Ġexceeding": 18744, + "ĠHEK": 18745, + "lym": 18746, + "ĠBV": 18747, + "Ġvisco": 18748, + "iet": 18749, + "ĠCOX": 18750, + "ployment": 18751, + "inski": 18752, + "Ġoutdoor": 18753, + "ĠLiterature": 18754, + "anted": 18755, + "methoxyphenyl": 18756, + "ĠMedium": 18757, + "Ġdia": 18758, + "ailand": 18759, + "variance": 18760, + "ĠEvaluating": 18761, + "oxacin": 18762, + "Ġantif": 18763, + "Ġpulp": 18764, + "Ġcorrobor": 18765, + "ĠOt": 18766, + "Ġrabbits": 18767, + "Ru": 18768, + "Ġfunctionals": 18769, + "âĩ": 18770, + "Ġimmersion": 18771, + "Ġcreatin": 18772, + "ĠqRT": 18773, + "Ġcondensed": 18774, + "nr": 18775, + "ĠVA": 18776, + "had": 18777, + "Ġking": 18778, + "oble": 18779, + "Ġexisted": 18780, + "Ġthesis": 18781, + "ubbard": 18782, + "apoptotic": 18783, + "Ġflowering": 18784, + "ĠAdaptation": 18785, + "ĠKalman": 18786, + "trl": 18787, + "Ġment": 18788, + "utation": 18789, + "ĠConv": 18790, + "Ġhistories": 18791, + "Ġenanti": 18792, + "nell": 18793, + "onian": 18794, + "ĠFabric": 18795, + "Ġxx": 18796, + "Ġfell": 18797, + "Ġcytosolic": 18798, + "Ġmud": 18799, + "Ġsuspensions": 18800, + "ĠMicrobial": 18801, + "measured": 18802, + "Ġdownload": 18803, + "Ġinvalid": 18804, + "Ġcapturing": 18805, + "ĠHH": 18806, + "ĠGray": 18807, + "ĠAZ": 18808, + "ĠNash": 18809, + "viation": 18810, + "naire": 18811, + "ortium": 18812, + "ynch": 18813, + "aminergic": 18814, + "Ġwait": 18815, + "Schem": 18816, + "trace": 18817, + "ĠVill": 18818, + "Ġpools": 18819, + "Ġhypoxic": 18820, + "xp": 18821, + "Ġshaded": 18822, + "ORY": 18823, + "turn": 18824, + "interacting": 18825, + "Ġdestroyed": 18826, + "akh": 18827, + "ĠCpG": 18828, + "dotted": 18829, + "ĠTranscript": 18830, + "planar": 18831, + "Ġpreclinical": 18832, + "ĠRepro": 18833, + "ĠSurgery": 18834, + "Stokes": 18835, + "ifdef": 18836, + "Ġdiscriminate": 18837, + "ĠGross": 18838, + "Ġflags": 18839, + "iety": 18840, + "ummy": 18841, + "Ġtransfers": 18842, + "SG": 18843, + "ĠSci": 18844, + "Ġheader": 18845, + "ĠFunding": 18846, + "Ġdetrim": 18847, + "Ġinstabilities": 18848, + "ĠPhylogenetic": 18849, + "ymethyl": 18850, + "ĠAssessing": 18851, + "ROC": 18852, + "elsen": 18853, + "Equal": 18854, + "Ġcas": 18855, + "Ġvertically": 18856, + "Ġvisibility": 18857, + "ĠFTIR": 18858, + "scrib": 18859, + "Ġbursts": 18860, + "ĠDoug": 18861, + "ĠFrancisco": 18862, + "ĠMSC": 18863, + "Ġpredis": 18864, + "established": 18865, + "Ġfaced": 18866, + "ĠWI": 18867, + "Sl": 18868, + "Ġcharts": 18869, + "orthy": 18870, + "izontal": 18871, + "ialysis": 18872, + "Ġtunable": 18873, + "Ġexplosion": 18874, + "Sw": 18875, + "TNF": 18876, + "Ġdiscontinuous": 18877, + "ecture": 18878, + "ciences": 18879, + "mathbbm": 18880, + "look": 18881, + "Ġtachy": 18882, + "Ġbrow": 18883, + "observed": 18884, + "Ġanaest": 18885, + "Sal": 18886, + "qPCR": 18887, + "Ġsees": 18888, + "Ġspacecraft": 18889, + "Ġsales": 18890, + "ĠTrac": 18891, + "Tem": 18892, + "ivest": 18893, + "ĠFc": 18894, + "ĠNews": 18895, + "Ġharvesting": 18896, + "ĠEG": 18897, + "pad": 18898, + "Ġnanowires": 18899, + "Ġpotato": 18900, + "pliers": 18901, + "onin": 18902, + "Ġworm": 18903, + "sue": 18904, + "tie": 18905, + "Ġmasks": 18906, + "Ġthrow": 18907, + "!!": 18908, + "behavi": 18909, + "Ġpine": 18910, + "ogy": 18911, + "TEST": 18912, + "onto": 18913, + "Ġcreatinine": 18914, + "ĠBoston": 18915, + "Ġchair": 18916, + "ploys": 18917, + "oven": 18918, + "Ġentrance": 18919, + "Ġcoch": 18920, + "Ġdyes": 18921, + "Tor": 18922, + "ĠPDE": 18923, + "underset": 18924, + "atasets": 18925, + "Ġternary": 18926, + "choose": 18927, + "five": 18928, + "chloride": 18929, + "onium": 18930, + "Property": 18931, + "Ġtu": 18932, + "Ġadequately": 18933, + "romycin": 18934, + "Ġcooper": 18935, + "ïĽľ": 18936, + "Ġpapill": 18937, + "ĠStreptococcus": 18938, + "ĠCY": 18939, + "Ġgrouping": 18940, + "Ġbioc": 18941, + "ĠCardiac": 18942, + "ĠBook": 18943, + "reference": 18944, + "Ġconfirmation": 18945, + "ivery": 18946, + "Ġwarning": 18947, + "pretation": 18948, + "Ġlove": 18949, + "Ġoscillators": 18950, + "sed": 18951, + "ĠTX": 18952, + "ilent": 18953, + "ĠVas": 18954, + "Ġclamp": 18955, + "Ġahead": 18956, + "acs": 18957, + "Ġdepleted": 18958, + "Ġmethodologies": 18959, + "may": 18960, + "Ġcaffe": 18961, + "Ġsequentially": 18962, + "osacchar": 18963, + "Ġcomprise": 18964, + "Ġchel": 18965, + "Ġinacc": 18966, + "Ġtendon": 18967, + "Sequ": 18968, + "ought": 18969, + "server": 18970, + "ĠPerturb": 18971, + "Ġterrain": 18972, + "curve": 18973, + "ĠArgent": 18974, + "TABLE": 18975, + "Ġimplicitly": 18976, + "Ġenjoy": 18977, + "ĠSitter": 18978, + "Ġmicron": 18979, + "ĠEvans": 18980, + "nsylvan": 18981, + "Ġlooked": 18982, + "spe": 18983, + "volving": 18984, + "ĠLSTM": 18985, + "agnetism": 18986, + "ĠNotch": 18987, + "ĠTal": 18988, + "ĠDEGs": 18989, + "leman": 18990, + "Ġboolean": 18991, + "Ġobey": 18992, + "organization": 18993, + "seen": 18994, + "ĠEnc": 18995, + "schild": 18996, + "ĠOntario": 18997, + "Element": 18998, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 18999, + "mouse": 19000, + "Ġpolyethylene": 19001, + "Ġacetic": 19002, + "sections": 19003, + "uronal": 19004, + "ĠDick": 19005, + "Ġkill": 19006, + "Ġbroadening": 19007, + "Ġfluoride": 19008, + "Ġsaved": 19009, + "Ġdeem": 19010, + "Stream": 19011, + "aced": 19012, + "ĠJeff": 19013, + "QA": 19014, + "Ġscalable": 19015, + "ĠFif": 19016, + "ĠMini": 19017, + "Ġsupergravity": 19018, + "Ġcolloidal": 19019, + "LY": 19020, + "OA": 19021, + "Ġperic": 19022, + "Ġshortly": 19023, + "Ġvap": 19024, + "Ġsplits": 19025, + "move": 19026, + "Ġstimulating": 19027, + "ĠBeijing": 19028, + "Ġpyr": 19029, + "ÏŃ": 19030, + "Ġlexical": 19031, + "âĢł": 19032, + "ÅĦ": 19033, + "itories": 19034, + "olerance": 19035, + "Ġinsulator": 19036, + "ĠLeon": 19037, + "Ġpropagate": 19038, + "ĠElements": 19039, + "yen": 19040, + "Module": 19041, + "ĠWhether": 19042, + "Ġaph": 19043, + "ĠLaure": 19044, + "ĠMutations": 19045, + "Ġhypertrophy": 19046, + "Ġoceanic": 19047, + "ographically": 19048, + "patients": 19049, + "ĠAngeles": 19050, + "Ġphe": 19051, + "Ġsquee": 19052, + "Ġcaroten": 19053, + "fine": 19054, + "Ġsketch": 19055, + "Ġansatz": 19056, + "titution": 19057, + "ĠFus": 19058, + "ĠSug": 19059, + "obacterial": 19060, + "Ħĥ": 19061, + "Related": 19062, + "Ġartist": 19063, + "Ġacryl": 19064, + "lined": 19065, + "rafted": 19066, + "ĠQoS": 19067, + "ĠFeng": 19068, + "search": 19069, + "Ġnanotube": 19070, + "ĠVM": 19071, + "ahl": 19072, + "Ġstride": 19073, + "ĠTag": 19074, + "ĠLar": 19075, + "Ġdesorption": 19076, + "dtype": 19077, + "Ġbug": 19078, + "Ġcaregivers": 19079, + "ĠHun": 19080, + "ĠPractical": 19081, + "Ġoblig": 19082, + "rer": 19083, + "ĠKang": 19084, + "ĠProducts": 19085, + "ometh": 19086, + "ĠHeLa": 19087, + "Ġlaboratories": 19088, + "natural": 19089, + "Ġful": 19090, + "Ġmold": 19091, + "abine": 19092, + "ĠSpring": 19093, + "Ġcobal": 19094, + "Ġhighlighting": 19095, + "ĠPref": 19096, + "cyclic": 19097, + "ĠCONCLUSION": 19098, + "ĠSources": 19099, + "Ġapex": 19100, + "parser": 19101, + "ĠLogic": 19102, + "Ġpond": 19103, + "Ġtold": 19104, + "ĠShap": 19105, + "pergillus": 19106, + "Ġsaying": 19107, + "Ġmutagenesis": 19108, + "ĠmmHg": 19109, + "ĠPAN": 19110, + "Ġsmokers": 19111, + "oday": 19112, + "Ġherein": 19113, + "CMV": 19114, + "ĠPW": 19115, + "Ġredshifts": 19116, + "ĠMinim": 19117, + "yman": 19118, + "ulli": 19119, + "dense": 19120, + "Ġarsenic": 19121, + "ĠEMT": 19122, + "ogaster": 19123, + "carboxylate": 19124, + "sys": 19125, + "Ro": 19126, + "anch": 19127, + "ĠAlpha": 19128, + "ĠTechnical": 19129, + "sv": 19130, + "Ġbones": 19131, + "Ġacceptor": 19132, + "Ġnewborn": 19133, + "private": 19134, + "Ġnanor": 19135, + "ĠSwiss": 19136, + "around": 19137, + "Ġsyntax": 19138, + "ĠKähler": 19139, + "Ġaerial": 19140, + "ĠPale": 19141, + "typedef": 19142, + "namespace": 19143, + "Ġconfounding": 19144, + "viÄĩ": 19145, + "Ġretard": 19146, + "Ġzeta": 19147, + "ĠTum": 19148, + "isch": 19149, + "Ġsulfide": 19150, + "ĠTian": 19151, + "uy": 19152, + "Ġintuition": 19153, + "Ġphospholip": 19154, + "ĠSher": 19155, + "ricts": 19156, + "----------------------------------------------------------------": 19157, + "okines": 19158, + "glucose": 19159, + "toler": 19160, + "iferative": 19161, + "ĠFluor": 19162, + "Ġencourage": 19163, + "Ġresponsive": 19164, + "perturbative": 19165, + "Ġsaddle": 19166, + "lers": 19167, + "ndez": 19168, + "ĠZero": 19169, + "ĠDiet": 19170, + "Ġdevelopers": 19171, + "Syn": 19172, + "Ġconfer": 19173, + "Ġoriginate": 19174, + "ropol": 19175, + "haw": 19176, + "letion": 19177, + "mskip": 19178, + "Ġber": 19179, + "Ġpeat": 19180, + "vially": 19181, + "Ġgranules": 19182, + "ĠÌĥ": 19183, + "Ġpluripot": 19184, + "Ġassimilation": 19185, + "Ġdenominator": 19186, + "abilization": 19187, + "ĠEpidemiology": 19188, + "MIN": 19189, + "eeds": 19190, + "ĠVR": 19191, + "Eval": 19192, + "store": 19193, + "ĠBaseline": 19194, + "Ġcu": 19195, + "ĠSpectra": 19196, + "Ġfractionation": 19197, + "Ġplacing": 19198, + "Ġburied": 19199, + "eleration": 19200, + "Ġalkali": 19201, + "ĠIU": 19202, + "Calc": 19203, + "weak": 19204, + "Ġmorphisms": 19205, + "Ġligase": 19206, + "Ġfs": 19207, + "Ġutilizes": 19208, + "Comput": 19209, + "â": 19210, + "Ġstig": 19211, + "relative": 19212, + "Ġimmature": 19213, + "ĠFrac": 19214, + "api": 19215, + "Ġoutpatient": 19216, + "Ġachievement": 19217, + "Ġstacking": 19218, + "Ġnodules": 19219, + "IND": 19220, + "ĠGPa": 19221, + "Ġpercolation": 19222, + "mspace": 19223, + "Ġbrains": 19224, + "uffle": 19225, + "entropy": 19226, + "Lab": 19227, + "Ġstabilize": 19228, + "ĠRicci": 19229, + "ĠAntimicrobial": 19230, + "personal": 19231, + "Ġfarms": 19232, + "ĠPin": 19233, + "Ġporcine": 19234, + "Ġoccasionally": 19235, + "whe": 19236, + "Ġundergoes": 19237, + "Ġregimens": 19238, + "Ġblade": 19239, + "Ġlinearized": 19240, + "Ġdecon": 19241, + "Ġpacked": 19242, + "Ġfishes": 19243, + "ĠMend": 19244, + "Ġapproaching": 19245, + "Ġballs": 19246, + "Ġproinflammatory": 19247, + "imeric": 19248, + "ĠDirector": 19249, + "Ġsoliton": 19250, + "Ġmosaic": 19251, + "viet": 19252, + "Mean": 19253, + "ĠPad": 19254, + "Ġtriplicate": 19255, + "supported": 19256, + "Ġcart": 19257, + "<<<<": 19258, + "Ġremission": 19259, + "aseous": 19260, + "asticity": 19261, + "ĠMik": 19262, + "ĠStrategy": 19263, + "ramer": 19264, + "ĠPolish": 19265, + "Ġenthal": 19266, + "Ġheterozygous": 19267, + "ĠGravity": 19268, + "Ax": 19269, + "Ġorganizational": 19270, + "Ġmovie": 19271, + "Ġexploratory": 19272, + "WLED": 19273, + "Ġmoiety": 19274, + "decre": 19275, + "ĠStill": 19276, + "Ġ¡": 19277, + "Ġgreenhouse": 19278, + "Ġsuperconductors": 19279, + "enum": 19280, + "elin": 19281, + "Ġoffering": 19282, + "stad": 19283, + "ĠTrich": 19284, + "Ġrepl": 19285, + "Ġrecycling": 19286, + "phor": 19287, + "Ġinelastic": 19288, + "ockey": 19289, + "ĠâĢĻ": 19290, + "Ġsequel": 19291, + "EB": 19292, + "ĠChile": 19293, + "Ġfibrillation": 19294, + "Ġdisulfide": 19295, + "obtained": 19296, + "ubin": 19297, + "Ĥ¬": 19298, + "Ġfacilitating": 19299, + "Ġhopping": 19300, + "Ġmediator": 19301, + "Ġhydration": 19302, + "Ġsparsity": 19303, + "Ġsati": 19304, + "Ġisothermal": 19305, + "Ġreturning": 19306, + "Ġtraveling": 19307, + "Ġing": 19308, + "Ġstent": 19309, + "Ġcapacitor": 19310, + "Ġcompromise": 19311, + "ĠSud": 19312, + "ĠVision": 19313, + "Ġtopologies": 19314, + "opolysaccharide": 19315, + "ĠProfile": 19316, + "ĠRing": 19317, + "Ġdiscrepancies": 19318, + "Dis": 19319, + "ARD": 19320, + "cccc": 19321, + "Ġdirectory": 19322, + "ĠCMOS": 19323, + "owed": 19324, + "illo": 19325, + "ĠInsights": 19326, + "ĠTib": 19327, + "Ġaband": 19328, + "arose": 19329, + "Order": 19330, + "Ġ¬": 19331, + "Ġintracranial": 19332, + "Ġintermediates": 19333, + "Ġhabits": 19334, + "Ġcarp": 19335, + "property": 19336, + "IMAGE": 19337, + "ĠUk": 19338, + "Ġhydrophilic": 19339, + "Wid": 19340, + "Ġabiotic": 19341, + "Ġobservers": 19342, + "Ġchor": 19343, + "ĠConservation": 19344, + "ĠEnhance": 19345, + "ĠAutomated": 19346, + "ĠGlut": 19347, + "iratory": 19348, + "Ġspaw": 19349, + "ĠEfficiency": 19350, + "vast": 19351, + "initi": 19352, + "Ġoptional": 19353, + "ĠScaling": 19354, + "ifold": 19355, + "ĠmtDNA": 19356, + "ĠReconstruction": 19357, + "Ġcountable": 19358, + "ĠGrass": 19359, + "Den": 19360, + "ĠChain": 19361, + "enzyme": 19362, + "Ġwaveforms": 19363, + "Ġpancreas": 19364, + "ĠDetailed": 19365, + "cmd": 19366, + "Ġâİľ": 19367, + "Ġmagneto": 19368, + "ĠFPGA": 19369, + "Ġabsolutely": 19370, + "Ġstimulates": 19371, + "achus": 19372, + "ĠArn": 19373, + "message": 19374, + "ocompatibility": 19375, + "HCl": 19376, + "ĠFish": 19377, + "Ġphenomenological": 19378, + "Ġsalivary": 19379, + "ondo": 19380, + "Ġnotions": 19381, + "fur": 19382, + "UCT": 19383, + "Ġwww": 19384, + "abet": 19385, + "ĠSulf": 19386, + "Fil": 19387, + "dominated": 19388, + "arser": 19389, + "Ġpackages": 19390, + "Ġsplice": 19391, + "Flo": 19392, + "NOWLED": 19393, + "xa": 19394, + "ĠYuan": 19395, + "Ġacetone": 19396, + "ĠVitamin": 19397, + "ĠÎŀ": 19398, + "Ġobsc": 19399, + "Ġchaper": 19400, + "Ġmort": 19401, + "MAN": 19402, + "Ġsubtilis": 19403, + "Ġoptimality": 19404, + "Ġcontinuing": 19405, + "Ġduplication": 19406, + "Ġmultiplying": 19407, + "Ġimmunological": 19408, + "Ġcirrhosis": 19409, + "hospital": 19410, + "ĠProbabilistic": 19411, + "Ġdeletions": 19412, + "Ġcaution": 19413, + "Ġowner": 19414, + "oxorubicin": 19415, + "Ġlaunch": 19416, + "Ġcure": 19417, + "thus": 19418, + "ĠHermitian": 19419, + "canonical": 19420, + "Ġimmunore": 19421, + "formin": 19422, + "Ġbroadband": 19423, + "partum": 19424, + "ophe": 19425, + "ĠBeta": 19426, + "ĠBI": 19427, + "Ġïĺº": 19428, + "Ġjumps": 19429, + "Ġparadox": 19430, + "umped": 19431, + "Ġdoctors": 19432, + "Ġhospitalized": 19433, + "Ġwash": 19434, + "precision": 19435, + "Ġruled": 19436, + "Ġduplicate": 19437, + "ante": 19438, + "Ġneurotrans": 19439, + "Ġïĥ§": 19440, + "Ġtheme": 19441, + "Taking": 19442, + "ĠPlants": 19443, + "following": 19444, + "Ġageing": 19445, + "Ġcongestion": 19446, + "osarcoma": 19447, + "Ġrepository": 19448, + "ĠHess": 19449, + "ĠCatalytic": 19450, + "ĠDV": 19451, + "INK": 19452, + "priv": 19453, + "ĠAna": 19454, + "ĠSLE": 19455, + "ĠThailand": 19456, + "íķ": 19457, + "Ġduty": 19458, + "locations": 19459, + "oter": 19460, + "Ġlysine": 19461, + "Ġindist": 19462, + "Ġagonists": 19463, + "Ack": 19464, + "Ġminimally": 19465, + "Ġetching": 19466, + "ugging": 19467, + "cuda": 19468, + "ndef": 19469, + "Ġreferring": 19470, + "Ġlysates": 19471, + "Ġserotonin": 19472, + "cribing": 19473, + "ĠInterface": 19474, + "dV": 19475, + "Ġdurations": 19476, + "Ġphotod": 19477, + "Ġdating": 19478, + "Ġirreversible": 19479, + "osidase": 19480, + "ĠFROM": 19481, + "within": 19482, + "SNR": 19483, + "Ġarrhyth": 19484, + "ĠRatio": 19485, + "ĠThin": 19486, + "centered": 19487, + "Ġshocks": 19488, + "ĠVers": 19489, + "Ġnoticeable": 19490, + "Ġfoci": 19491, + "Ġorthonormal": 19492, + "ĠâİŁ": 19493, + "Ġluminescence": 19494, + "ĠSUSY": 19495, + "internal": 19496, + "ĠTour": 19497, + "Ġabbrevi": 19498, + "ĠMAL": 19499, + "vertex": 19500, + "Ġemploys": 19501, + "INS": 19502, + "Ġimmunohistochemistry": 19503, + "Ġheparin": 19504, + "Ġidiopathic": 19505, + "Ġimmobilized": 19506, + "ishe": 19507, + "phth": 19508, + "thin": 19509, + "ĠStorage": 19510, + "Ġperovskite": 19511, + "Prot": 19512, + "ĠDepending": 19513, + "Ġblends": 19514, + "Ġpredator": 19515, + "Ġdisplaying": 19516, + "Ġvesicle": 19517, + "ĠKra": 19518, + "Ġlane": 19519, + "Ġmultilayer": 19520, + "Ġhomozygous": 19521, + "cosh": 19522, + "Ġsuperficial": 19523, + "Ġil": 19524, + "ĠKR": 19525, + "ĠBrun": 19526, + "ĠEW": 19527, + "opa": 19528, + "ĠCartesian": 19529, + "ĠCytoplas": 19530, + "ĠPennsylvan": 19531, + "bands": 19532, + "Ġangiotensin": 19533, + "ĠLattice": 19534, + "GI": 19535, + "jee": 19536, + "Ġenlarged": 19537, + "enius": 19538, + "ĠIa": 19539, + "oux": 19540, + "Ġgent": 19541, + "Ġcarbonyl": 19542, + "chers": 19543, + "Ġhypothe": 19544, + "Ġmicrosp": 19545, + "Ġaffective": 19546, + "Ġaxons": 19547, + "ei": 19548, + "yptoph": 19549, + "ĠJon": 19550, + "queue": 19551, + "ĠGauge": 19552, + "menopausal": 19553, + "ĠDas": 19554, + "ĠEssential": 19555, + "ĠFault": 19556, + "ĠBil": 19557, + "Ġtestosterone": 19558, + "Ġchambers": 19559, + "dione": 19560, + "Ġelicited": 19561, + "IGN": 19562, + "Ġantioxidants": 19563, + "populations": 19564, + "Ġovary": 19565, + "Ġâĸ": 19566, + "Ġabstraction": 19567, + "Ġhydrocarbons": 19568, + "Ġrectal": 19569, + "Ġtriggering": 19570, + "Ġthoroughly": 19571, + "Run": 19572, + "acteria": 19573, + "information": 19574, + "ĠBed": 19575, + "Ġquenc": 19576, + "Ġunders": 19577, + "ĠScotland": 19578, + "Ġrevolution": 19579, + "Ġpituitary": 19580, + "Ġanthropogenic": 19581, + "focus": 19582, + "Ġmethan": 19583, + "Ġinflow": 19584, + "Ġdeflection": 19585, + "ĠCape": 19586, + "Ġmultidimensional": 19587, + "Ġarrived": 19588, + "ĠSpar": 19589, + "dv": 19590, + "Ġcows": 19591, + "ĠBh": 19592, + "Ġjk": 19593, + "tolyl": 19594, + "Ġeigenstates": 19595, + "Ġpreprocessing": 19596, + "ĠRain": 19597, + "ä¸": 19598, + "inz": 19599, + "Ġmn": 19600, + "REE": 19601, + "atrick": 19602, + "Dev": 19603, + "Ġfulfilled": 19604, + "Ġartic": 19605, + "Ġrealizations": 19606, + "ĠComponent": 19607, + "ĠWS": 19608, + "Ġinfo": 19609, + "printed": 19610, + "atosis": 19611, + "cache": 19612, + "anov": 19613, + "ĠTg": 19614, + "content": 19615, + "junc": 19616, + "ĠCDK": 19617, + "Ġbehaves": 19618, + "ĠKid": 19619, + "difference": 19620, + "ĠPs": 19621, + "ĠUg": 19622, + "Ġstructurally": 19623, + "erebral": 19624, + "ĠSurve": 19625, + "heal": 19626, + "onite": 19627, + "Ġdeleted": 19628, + "itim": 19629, + "Star": 19630, + "ĠSpeech": 19631, + "ĠAstr": 19632, + "gradient": 19633, + "Ġfellow": 19634, + "Ġsyring": 19635, + "NB": 19636, + "ĠNB": 19637, + "Ġcreep": 19638, + "Ġlogging": 19639, + "Ġinten": 19640, + "scalar": 19641, + "ĠAtmospheric": 19642, + "Ġlupus": 19643, + "Ġidentically": 19644, + "processed": 19645, + "signal": 19646, + "ĠClostr": 19647, + "ancers": 19648, + "Ġdb": 19649, + "Ġsubsystem": 19650, + "situ": 19651, + "Ġferroelectric": 19652, + "ĠïĽľ": 19653, + "Ġore": 19654, + "ĠRb": 19655, + "ĠMicrosoft": 19656, + "ĠCoch": 19657, + "ĠActin": 19658, + "Ġnerves": 19659, + "Ġexpertise": 19660, + "otive": 19661, + "ĠPoincaré": 19662, + "ĠRig": 19663, + "Ġpsychosocial": 19664, + "Ġprogenitors": 19665, + "ĠMyr": 19666, + "ĠHug": 19667, + "Ġbiogenesis": 19668, + "Ġincorporates": 19669, + "Ġnevertheless": 19670, + "ĠDecl": 19671, + "observ": 19672, + "Ġmultiplier": 19673, + "Ġresponding": 19674, + "hoff": 19675, + "Ġimpacted": 19676, + "Ġsyndromes": 19677, + "kel": 19678, + "ĠSynt": 19679, + "ĠConcer": 19680, + "ĠAmericans": 19681, + "Ġspaced": 19682, + "umption": 19683, + "ĠThompson": 19684, + "ĠJacobian": 19685, + "Tra": 19686, + "evolution": 19687, + "Ġdidn": 19688, + "Ġpercentile": 19689, + "Ġlid": 19690, + "equivalent": 19691, + "Ġantico": 19692, + "Ġmultiply": 19693, + "Ġpenicillin": 19694, + "Ġresponsiveness": 19695, + "Ġrunoff": 19696, + "alanine": 19697, + "squares": 19698, + "ĠInsulin": 19699, + "rele": 19700, + "ĠLif": 19701, + "ĠMinkowski": 19702, + "Ġblend": 19703, + "ĠPand": 19704, + "Ġtwelve": 19705, + "Ġhybrids": 19706, + "Ġbass": 19707, + "interaction": 19708, + "ĠBangladesh": 19709, + "Ġopens": 19710, + "ĠArts": 19711, + "Ġconcave": 19712, + "Ġpedest": 19713, + "Ġfist": 19714, + "ĠAdults": 19715, + "openia": 19716, + "ENCE": 19717, + "ĠFusion": 19718, + "Ġmicroc": 19719, + "ĠSurgical": 19720, + "ylate": 19721, + "Ġpackaging": 19722, + "OCK": 19723, + "QC": 19724, + "Tri": 19725, + "scan": 19726, + "Ġregards": 19727, + "Ġdiscriminant": 19728, + "Ġindustries": 19729, + "icus": 19730, + "ĠWalker": 19731, + "Ġpeers": 19732, + "synt": 19733, + "Ġhorse": 19734, + "Ġflowing": 19735, + "urred": 19736, + "ĠCRP": 19737, + "ĠCareer": 19738, + "iffiffiffiffiffiffiffiff": 19739, + "ĠMSE": 19740, + "hana": 19741, + "ĠMortality": 19742, + "Ġtumorigenesis": 19743, + "ĠIslam": 19744, + "Ġazimuthal": 19745, + "wen": 19746, + "Ġsys": 19747, + "azin": 19748, + "neighbor": 19749, + "Config": 19750, + "they": 19751, + "Ġsorption": 19752, + "Ġspanned": 19753, + "Ġviewpoint": 19754, + "MOD": 19755, + "Ġthrust": 19756, + "uplex": 19757, + "Ġhistograms": 19758, + "Ġprogrammed": 19759, + "Ġethics": 19760, + "ectable": 19761, + "representation": 19762, + "umns": 19763, + "Ġstreet": 19764, + "ĠSobolev": 19765, + "Ġexcision": 19766, + "ĠRud": 19767, + "quires": 19768, + "Ġowned": 19769, + "Ġthousand": 19770, + "Ġantagonists": 19771, + "UST": 19772, + "Ġdrastically": 19773, + "ĠóµĦ©": 19774, + "ĠDor": 19775, + "ĠMOS": 19776, + "pn": 19777, + "ĠDecre": 19778, + "Dep": 19779, + "Ġsintering": 19780, + "Ġpurple": 19781, + "ethanol": 19782, + "Ġhydrocarbon": 19783, + "ĠFO": 19784, + "leftrightarrow": 19785, + "Ġimmunofluorescence": 19786, + "ĠOM": 19787, + "Ġmaturity": 19788, + "Ġearthquakes": 19789, + "Ġaxon": 19790, + "Ġprobed": 19791, + "ORD": 19792, + "ĠADP": 19793, + "sg": 19794, + "omere": 19795, + "Ġtranscribed": 19796, + "Mar": 19797, + "ĠUtil": 19798, + "ĠIA": 19799, + "Ġcompiled": 19800, + "Ġsupervision": 19801, + "ĠXen": 19802, + "ĠJur": 19803, + "compar": 19804, + "Ġhypertensive": 19805, + "ilized": 19806, + "rae": 19807, + "Conclusion": 19808, + "'''": 19809, + "Double": 19810, + "ĠFas": 19811, + "Ġinsectic": 19812, + "ĠPrem": 19813, + "Pri": 19814, + "ĠCao": 19815, + "ĠQuestionnaire": 19816, + "Ġgathered": 19817, + "GW": 19818, + "ĠNV": 19819, + "ĠLactobacillus": 19820, + "Ġcyclin": 19821, + "Ġreject": 19822, + "Ġskull": 19823, + "Ġaw": 19824, + "ĠCold": 19825, + "Ġmesons": 19826, + "bd": 19827, + "Ġdetrimental": 19828, + "apore": 19829, + "nowled": 19830, + "ĠCXCL": 19831, + "Ġspikes": 19832, + "Ġtent": 19833, + "ĠLength": 19834, + "Ġdoor": 19835, + "Ġflour": 19836, + "ustration": 19837, + "Health": 19838, + "Ġtransparency": 19839, + "Ġdisrupted": 19840, + "Hy": 19841, + "overl": 19842, + "ĠReinforcement": 19843, + "ceptors": 19844, + "ĠKos": 19845, + "retroviral": 19846, + "ĠINT": 19847, + "ĠSor": 19848, + "Ġadopting": 19849, + "Ġendoplasmic": 19850, + "Ġsuit": 19851, + "Ġopioid": 19852, + "Ġintegrin": 19853, + "away": 19854, + "Ġtailored": 19855, + "ĠSoc": 19856, + "Ġquies": 19857, + "Ġhusband": 19858, + "Ġumb": 19859, + "ĠCai": 19860, + "ĠAspergillus": 19861, + "ĠGaN": 19862, + "Ġdistinguishing": 19863, + "Ġextrapolation": 19864, + "Ġcage": 19865, + "Ġscavenging": 19866, + "KF": 19867, + "Tree": 19868, + "ĠConflict": 19869, + "UNC": 19870, + "Ġmanganese": 19871, + "days": 19872, + "ÃŁ": 19873, + "ĠLive": 19874, + "sd": 19875, + "ractor": 19876, + "Ġlute": 19877, + "Ġdissimilar": 19878, + "Ġib": 19879, + "ĠVeg": 19880, + "Ġoccurrences": 19881, + "Ġbinomial": 19882, + "Scheme": 19883, + "Ġtape": 19884, + "ĠCant": 19885, + "Ġelectrosp": 19886, + "Cd": 19887, + "made": 19888, + "Ġsevent": 19889, + "shared": 19890, + "Ġaccession": 19891, + "orp": 19892, + "DATA": 19893, + "leted": 19894, + "Vari": 19895, + "Ġrose": 19896, + "tagged": 19897, + "ĠAth": 19898, + "Ġeddy": 19899, + "estone": 19900, + "Ġesters": 19901, + "Ġtyping": 19902, + "ĠStudents": 19903, + "yi": 19904, + "oresistance": 19905, + "inois": 19906, + "Ġglucocortic": 19907, + "iosis": 19908, + "Ġcoronal": 19909, + "Ġsheath": 19910, + "ĠTrack": 19911, + "Ġequilibria": 19912, + "amming": 19913, + "Ġpione": 19914, + "Ġsciences": 19915, + "Ġsuppressing": 19916, + "Ġdeco": 19917, + "ifndef": 19918, + "His": 19919, + "Ġpellet": 19920, + "Linear": 19921, + "orbent": 19922, + "Ġflatten": 19923, + "Ġstraw": 19924, + "Ġalbeit": 19925, + "ĠPredictive": 19926, + "Ġgaze": 19927, + "Ġhydroly": 19928, + "uther": 19929, + "oders": 19930, + "Ġflap": 19931, + "Ġsimplicial": 19932, + "System": 19933, + "Ġstressed": 19934, + "Ġimmunoglobulin": 19935, + "ilia": 19936, + "Ġconsuming": 19937, + "Ġé": 19938, + "galact": 19939, + "Ġadulthood": 19940, + "Ġvorticity": 19941, + "yclic": 19942, + "ovoltaic": 19943, + "ivestock": 19944, + "Ġbeds": 19945, + "ĠPlanning": 19946, + "Ġparameterized": 19947, + "Ġghost": 19948, + "maximum": 19949, + "Ġsuperim": 19950, + "Ġphysicochemical": 19951, + "gp": 19952, + "ongue": 19953, + "Ġprimordial": 19954, + "xff": 19955, + "insula": 19956, + "Mc": 19957, + "Ġminimizes": 19958, + "ĠGravitational": 19959, + "osoma": 19960, + "ignificant": 19961, + "Ġelucidated": 19962, + "Ġsubsurface": 19963, + "significant": 19964, + "Ġrelatives": 19965, + "ferroni": 19966, + "transf": 19967, + "Ġtails": 19968, + "beck": 19969, + "omagnetic": 19970, + "Ġunnecessary": 19971, + "Ġmonomial": 19972, + "delay": 19973, + "Ġsta": 19974, + "ĠSuz": 19975, + "Ġaltering": 19976, + "LOG": 19977, + "ĠLac": 19978, + "Ġranks": 19979, + "hw": 19980, + "ĠNep": 19981, + "Ġneuropath": 19982, + "ĠCompe": 19983, + "Gr": 19984, + "Pati": 19985, + "reduce": 19986, + "ĠMalaysia": 19987, + "ceral": 19988, + "Ġmicrobes": 19989, + "Ġlensing": 19990, + "ĠCalcium": 19991, + "ĠDetermin": 19992, + "ĠCosta": 19993, + "Ġkeeps": 19994, + "printing": 19995, + "ĉĉĉĉĉĉ": 19996, + "chin": 19997, + "exposed": 19998, + "Ġperiodically": 19999, + "Ġrender": 20000, + "ĠCardiovascular": 20001, + "entin": 20002, + "Ġbioavailability": 20003, + "Ġinterpretations": 20004, + "ĠCU": 20005, + "Ġnegoti": 20006, + "Ġantim": 20007, + "Ġdeemed": 20008, + "Ġae": 20009, + "Ġhalos": 20010, + "ĠMichigan": 20011, + "Ġosteoarthritis": 20012, + "diag": 20013, + "ĠBeng": 20014, + "Ġmetagen": 20015, + "Ġparameterization": 20016, + "diagn": 20017, + "ĠMatching": 20018, + "Ġcatalysis": 20019, + "uts": 20020, + "Ġdissemination": 20021, + "Ġoutlet": 20022, + "ĠMoon": 20023, + "ĠGST": 20024, + "sphere": 20025, + "Ġresearcher": 20026, + "ambiguation": 20027, + "Ġraises": 20028, + "Ġflavonoids": 20029, + "ĠMultivariate": 20030, + "Ġaccl": 20031, + "WI": 20032, + "Ġnu": 20033, + "Ġergodic": 20034, + "unique": 20035, + "atinib": 20036, + "Ġresolutions": 20037, + "Ġhouses": 20038, + "DEC": 20039, + "ighed": 20040, + "Ġsixth": 20041, + "Ġpolitician": 20042, + "apache": 20043, + "Ġsolute": 20044, + "Ġaugment": 20045, + "stress": 20046, + "HIV": 20047, + "ĠSets": 20048, + "Ġtransistors": 20049, + "qubit": 20050, + "amines": 20051, + "Ġfarmers": 20052, + "Ġnt": 20053, + "ĠLagrange": 20054, + "Ġvegetables": 20055, + "Ġpret": 20056, + "ĠSynthetic": 20057, + "Ġcones": 20058, + "Ġmedicines": 20059, + "Ġgenomics": 20060, + "Ġexperiencing": 20061, + "agland": 20062, + "Ġgenital": 20063, + "ĠObservatory": 20064, + "ĠSkin": 20065, + "ĠRosen": 20066, + "ĠBritain": 20067, + "genome": 20068, + "ĠEntropy": 20069, + "Ġrac": 20070, + "Go": 20071, + "Ġwalks": 20072, + "criptor": 20073, + "ĠBaker": 20074, + "oker": 20075, + "Ġpropensity": 20076, + "Ġpopularity": 20077, + "restricted": 20078, + "ĠBert": 20079, + "before": 20080, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 20081, + "auto": 20082, + "Rank": 20083, + "ĠRCT": 20084, + "Ġpocket": 20085, + "obut": 20086, + "Ġbenzene": 20087, + "ĠCNT": 20088, + "yptophan": 20089, + "allis": 20090, + "ĠResources": 20091, + "ĠBerlin": 20092, + "Ġscholar": 20093, + "glob": 20094, + "ĠSpeed": 20095, + "ĠXiao": 20096, + "biggl": 20097, + "ANCE": 20098, + "ĠPrime": 20099, + "Phys": 20100, + "idia": 20101, + "Ġmonoc": 20102, + "ĠCommunications": 20103, + "ĠPrecision": 20104, + "ĠPauli": 20105, + "Ġinvestigators": 20106, + "ĠLiang": 20107, + "Ġmeteorological": 20108, + "mog": 20109, + "reens": 20110, + "ubric": 20111, + "Ġrearrangement": 20112, + "orta": 20113, + "Elect": 20114, + "ĠTukey": 20115, + "ĠMis": 20116, + "Ġepiderm": 20117, + "ĠACKNOWLED": 20118, + "wart": 20119, + "Ġexciton": 20120, + "Ġassociative": 20121, + "styrene": 20122, + "Ġlosing": 20123, + "ĠOd": 20124, + "prep": 20125, + "essation": 20126, + "Ġattributable": 20127, + "ĠNavier": 20128, + "anz": 20129, + "Ġcorrectness": 20130, + "oints": 20131, + "ĠRather": 20132, + "Ġassemblies": 20133, + "Ġbridges": 20134, + "OSS": 20135, + "MET": 20136, + "Ġperm": 20137, + "Ġauthorities": 20138, + "Ġiodine": 20139, + "shire": 20140, + "interval": 20141, + "eptid": 20142, + "Ġpotency": 20143, + "Ġrenewable": 20144, + "vard": 20145, + "Ġsurjective": 20146, + "Ġsubsequence": 20147, + "ĠEVs": 20148, + "itching": 20149, + "Ġgenotyping": 20150, + "ĠAccurate": 20151, + "iophene": 20152, + "Gly": 20153, + "plified": 20154, + "ĠDistinct": 20155, + "ACH": 20156, + "Ġspeakers": 20157, + "holm": 20158, + "Ġpros": 20159, + "ĠDevice": 20160, + "mc": 20161, + "ĠDense": 20162, + "ĠVa": 20163, + "rison": 20164, + "Ġacyl": 20165, + "ĠPrincipal": 20166, + "ĠViral": 20167, + "Ġcosine": 20168, + "ĠResidual": 20169, + "Ġefflux": 20170, + "ĠSubjects": 20171, + "Ġrectangle": 20172, + "workers": 20173, + "Ġrotated": 20174, + "Ġbomb": 20175, + "ĠResolution": 20176, + "near": 20177, + "Ġ®": 20178, + "Ġestablishes": 20179, + "amed": 20180, + "Ġcompetence": 20181, + "Glu": 20182, + "ĠDend": 20183, + "ĠHsp": 20184, + "ensation": 20185, + "ĠLead": 20186, + "Ġlogger": 20187, + "sinh": 20188, + "Ġintellectual": 20189, + "former": 20190, + "Ce": 20191, + "Ġmonocyte": 20192, + "hores": 20193, + "Ġdiastolic": 20194, + "Ġlifespan": 20195, + "ĠSilva": 20196, + "arum": 20197, + "Ġtransducer": 20198, + "Ġoutgoing": 20199, + "entation": 20200, + "Ġabsorbing": 20201, + "itage": 20202, + "Ġsynthesize": 20203, + "Ġfeeling": 20204, + "asian": 20205, + "Ġceramics": 20206, + "iph": 20207, + "Ġnonlocal": 20208, + "Part": 20209, + "Ġimmersed": 20210, + "stationary": 20211, + "lecting": 20212, + "Ġwelding": 20213, + "Ġresembles": 20214, + "ĠKat": 20215, + "master": 20216, + "Ġintersect": 20217, + "ĠOlig": 20218, + "ĠTrends": 20219, + "agh": 20220, + "ĠNav": 20221, + "ĠTu": 20222, + "Ġepist": 20223, + "Ġclinics": 20224, + "Ġrepresentatives": 20225, + "Ġgrateful": 20226, + "GPIO": 20227, + "HH": 20228, + "Ġunambig": 20229, + "tuning": 20230, + "Ġnewsp": 20231, + "cohol": 20232, + "################################": 20233, + "%%%%%%%%": 20234, + "represented": 20235, + "ocic": 20236, + "ĠFuk": 20237, + "ĠSund": 20238, + "hasone": 20239, + "Mode": 20240, + "olone": 20241, + "ĠSb": 20242, + "Three": 20243, + "Link": 20244, + "cephal": 20245, + "ĠKap": 20246, + "Ġeliminating": 20247, + "Ġmelanogaster": 20248, + "âŁ": 20249, + "ĠBMD": 20250, + "ISE": 20251, + "ĠBattle": 20252, + "Ġshrinkage": 20253, + "ĠSeven": 20254, + "ĠGlass": 20255, + "romagn": 20256, + "Ġkl": 20257, + "ĠObviously": 20258, + "preserving": 20259, + "ĠPlatform": 20260, + "ĠÌĩ": 20261, + "omavirus": 20262, + "ĠEight": 20263, + "Ġallerg": 20264, + "ĠNanoparticles": 20265, + "aryl": 20266, + "Ġpriors": 20267, + "pattern": 20268, + "Ġlinearity": 20269, + "Ġtruly": 20270, + "Process": 20271, + "Ġdescending": 20272, + "ĠVictoria": 20273, + "cond": 20274, + "ĠICP": 20275, + "orescent": 20276, + "Ġauthority": 20277, + "Ġmock": 20278, + "igmoid": 20279, + "Ġcomorbidities": 20280, + "simple": 20281, + "Ġblo": 20282, + "ĠCompute": 20283, + "Ġgestation": 20284, + "achusetts": 20285, + "Ġphantom": 20286, + "ĠEdward": 20287, + "ĠFBS": 20288, + "factors": 20289, + "ĠEstimates": 20290, + "clear": 20291, + "WB": 20292, + "products": 20293, + "numpy": 20294, + "brief": 20295, + "Ġshop": 20296, + "ĠPoli": 20297, + "ĠRespiratory": 20298, + "Ġsurprisingly": 20299, + "Ġnanocomposite": 20300, + "dividual": 20301, + "Ġholographic": 20302, + "ygdala": 20303, + "roplasty": 20304, + "otactic": 20305, + "ĠPennsylvania": 20306, + "ĠScore": 20307, + "Obj": 20308, + "Ġstories": 20309, + "Ġmaximizing": 20310, + "Ġgelatin": 20311, + "rites": 20312, + "ĠTau": 20313, + "Ġtrypsin": 20314, + "Ġith": 20315, + "Ġfaint": 20316, + "Ġpriming": 20317, + "eworthy": 20318, + "ĠInverse": 20319, + "Ġknots": 20320, + "sharp": 20321, + "Ġtrains": 20322, + "Ġcredit": 20323, + "ĠBelow": 20324, + "pixel": 20325, + "Ġspindle": 20326, + "ĠPast": 20327, + "Ġenumerate": 20328, + "olateral": 20329, + "Ġattending": 20330, + "Ġquantized": 20331, + "Ġhaplotypes": 20332, + "encl": 20333, + "Ġwaven": 20334, + "Ġfurthermore": 20335, + "Ġchallenged": 20336, + "Ġmanufactured": 20337, + "ipheral": 20338, + "Ġinfinites": 20339, + "ĠRand": 20340, + "Ġstaging": 20341, + "agan": 20342, + "Ġperox": 20343, + "trifluor": 20344, + "ĠMcK": 20345, + "ĠFOX": 20346, + "ĠLank": 20347, + "ĠLuo": 20348, + "ĠAnth": 20349, + "ibrio": 20350, + "yel": 20351, + "ĠJi": 20352, + "ĠIO": 20353, + "ĠBridge": 20354, + "ĠRow": 20355, + "Ġcompensated": 20356, + "atsu": 20357, + "Ġhypothetical": 20358, + "Ġterminals": 20359, + "Ġcobalt": 20360, + "mers": 20361, + "ĠMang": 20362, + "NI": 20363, + "ĠRac": 20364, + "ALS": 20365, + "fen": 20366, + "ĠUb": 20367, + "Ġpredation": 20368, + "cadherin": 20369, + "ĠShanghai": 20370, + "Ġtries": 20371, + "Ġsport": 20372, + "acrylate": 20373, + "ĠAlgebraic": 20374, + "aints": 20375, + "Expr": 20376, + "Ġandrogen": 20377, + "Ġwedge": 20378, + "disp": 20379, + "Ġstirred": 20380, + "ĠAle": 20381, + "Ġcock": 20382, + "Four": 20383, + "Ġscanner": 20384, + "Ġplasmon": 20385, + "ĠGender": 20386, + "ĠRecord": 20387, + "ĠInjury": 20388, + "oblastic": 20389, + "ĠFluorescence": 20390, + "Ġantidepress": 20391, + "Ġdefinitive": 20392, + "Ġrepression": 20393, + "ordinates": 20394, + "Ġangiography": 20395, + "ĠHelical": 20396, + "Ġcancellation": 20397, + "release": 20398, + "Ġrelational": 20399, + "ĠAndre": 20400, + "molecule": 20401, + "Ġshaping": 20402, + "ĠDenmark": 20403, + "ĠALS": 20404, + "ĠNW": 20405, + "overrightarrow": 20406, + "Ġcombat": 20407, + "boxes": 20408, + "subject": 20409, + "Ġnanoscale": 20410, + "Ġcanine": 20411, + "Ġsaving": 20412, + "Ġstrategic": 20413, + "Stat": 20414, + "ĠDub": 20415, + "Ġpermitted": 20416, + "ĠTwitter": 20417, + "âĶ": 20418, + "Ġmemories": 20419, + "ĠBusiness": 20420, + "adays": 20421, + "Ġpooling": 20422, + "ĠClusters": 20423, + "imide": 20424, + "ounters": 20425, + "fraction": 20426, + "ĠCliff": 20427, + "Cam": 20428, + "Even": 20429, + "KY": 20430, + "kit": 20431, + "ibrated": 20432, + "Ġaccompanying": 20433, + "anus": 20434, + "Ġbuoy": 20435, + "Ġproliferative": 20436, + "Ġproc": 20437, + "Ġstabilizing": 20438, + "ĠNamely": 20439, + "posp": 20440, + "soon": 20441, + "Ġaberrant": 20442, + "Ġinterstellar": 20443, + "Overall": 20444, + "ĠGn": 20445, + "ĠFeedback": 20446, + "Ġoracle": 20447, + "Ġprenatal": 20448, + "commun": 20449, + "Ġoutbreaks": 20450, + "Ġfertilization": 20451, + "ĠMAG": 20452, + "Ġsinger": 20453, + "ĠMicrowave": 20454, + "ĠParliament": 20455, + "casting": 20456, + "General": 20457, + "algorithm": 20458, + "Ġphrase": 20459, + "Ġavian": 20460, + "ĠPLA": 20461, + "Ġhardly": 20462, + "approximately": 20463, + "ARCH": 20464, + "Ġtransc": 20465, + "Ġdecomp": 20466, + "contin": 20467, + "ĠMilky": 20468, + "Ġherpes": 20469, + "Range": 20470, + "OFF": 20471, + "prisingly": 20472, + "lx": 20473, + "ĠABA": 20474, + "Ġshore": 20475, + "Ġderiving": 20476, + "Ġpellets": 20477, + "nowledg": 20478, + "Item": 20479, + "stranded": 20480, + "built": 20481, + "Glc": 20482, + "quist": 20483, + "ĠSubstrate": 20484, + "Ġtraditionally": 20485, + "ĠMount": 20486, + "ivalence": 20487, + "axation": 20488, + "Ġlocate": 20489, + "Ġgun": 20490, + "Ġvocabulary": 20491, + "ĠPolym": 20492, + "Ġect": 20493, + "Ġmult": 20494, + "Ġsedimentary": 20495, + "Ġautocorrelation": 20496, + "ĠSympt": 20497, + "Ġterritory": 20498, + "Ġexcitatory": 20499, + "Ġvote": 20500, + "Ġhered": 20501, + "acea": 20502, + "ĠFocus": 20503, + "ampling": 20504, + "ffee": 20505, + "Ġprimes": 20506, + "ĠMaking": 20507, + "irs": 20508, + "MPs": 20509, + "Ġlitter": 20510, + "amethasone": 20511, + "ĠkJ": 20512, + "Ġsecretory": 20513, + "Ġcostly": 20514, + "Ġpartnership": 20515, + "ĠBacteria": 20516, + "Ġperoxidation": 20517, + "stroke": 20518, + "ĠSav": 20519, + "ĠBW": 20520, + "Ġconnects": 20521, + "Ġamine": 20522, + "ril": 20523, + "Ġbattle": 20524, + "ĠNotes": 20525, + "ĠProvid": 20526, + "ĠInstitutional": 20527, + "Ġpropri": 20528, + "fan": 20529, + "Ġpun": 20530, + "romb": 20531, + "vities": 20532, + "ĠCAM": 20533, + "ĠIsh": 20534, + "ĠHN": 20535, + "ĠRecomb": 20536, + "sche": 20537, + "Ġsynchrotron": 20538, + "rik": 20539, + "synaptic": 20540, + "ĠGeorgia": 20541, + "??": 20542, + "CY": 20543, + "Ġcorresponded": 20544, + "kinase": 20545, + "ĠITS": 20546, + "Ġproposals": 20547, + "Ġbioge": 20548, + "ĠESR": 20549, + "ĠWen": 20550, + "ĠJa": 20551, + "ĠSevere": 20552, + "ĠAden": 20553, + "ĠCCL": 20554, + "Ġseat": 20555, + "ĠKre": 20556, + "Ġhelping": 20557, + "Ġnets": 20558, + "ĠLep": 20559, + "hedra": 20560, + "opoulos": 20561, + "ĠBak": 20562, + "ansas": 20563, + "Ġrefrig": 20564, + "Ġubiquitous": 20565, + "Ġmatters": 20566, + "Ġsilicate": 20567, + "ĠLastly": 20568, + "ĠTheories": 20569, + "Ġagarose": 20570, + "biggr": 20571, + "transition": 20572, + "ĠDecomposition": 20573, + "bromo": 20574, + "Ġstakeholders": 20575, + "ĠEE": 20576, + "Only": 20577, + "ĠKenya": 20578, + "Ġargon": 20579, + "ĠIdentifying": 20580, + "Ġtournament": 20581, + "clock": 20582, + "ĠCFU": 20583, + "ĠBehavioral": 20584, + "Ġpod": 20585, + "Ġtaxonomy": 20586, + "ĠProduct": 20587, + "ĠAlong": 20588, + "Ġfamilial": 20589, + "Ġdescriptor": 20590, + "vated": 20591, + "ĠVariables": 20592, + "tp": 20593, + "Ġgoods": 20594, + "ĠAST": 20595, + "ĠAnis": 20596, + "Ġspinor": 20597, + "attention": 20598, + "Ġbasket": 20599, + "Struct": 20600, + "Ġimmunohistochemical": 20601, + "engers": 20602, + "CAT": 20603, + "Ġtangential": 20604, + "Cap": 20605, + "ĠPair": 20606, + "Ġviscoelastic": 20607, + "ĠAds": 20608, + "Ġglycosylation": 20609, + "Ġdur": 20610, + "ĠMinimum": 20611, + "Ġrigidity": 20612, + "stats": 20613, + "tillation": 20614, + "ĠDiscrim": 20615, + "ĠLegend": 20616, + "Previous": 20617, + "film": 20618, + "Ġaluminium": 20619, + "Micro": 20620, + "inia": 20621, + "egel": 20622, + "ĠSubcellular": 20623, + "Ġbottleneck": 20624, + "Ġsyll": 20625, + "icle": 20626, + "Ġsheaf": 20627, + "chell": 20628, + "example": 20629, + "ĠSelected": 20630, + "Ġpredators": 20631, + "Ġreper": 20632, + "Ġstrugg": 20633, + "ĠMaria": 20634, + "lyl": 20635, + "LF": 20636, + "Ġexercises": 20637, + "obium": 20638, + "ILITY": 20639, + "corrected": 20640, + "Ġbenchmarks": 20641, + "ĠTol": 20642, + "Ġintercept": 20643, + "ĠCalculation": 20644, + "ĠIndonesia": 20645, + "Ġglioblastoma": 20646, + "KM": 20647, + "ĠSupplemental": 20648, + "Ġcitizens": 20649, + "adren": 20650, + "Ġmultimodal": 20651, + "Ġmosquitoes": 20652, + "iva": 20653, + "ĠFindings": 20654, + "ĠPub": 20655, + "ĠMacroph": 20656, + "Acknowledg": 20657, + "Ġbasins": 20658, + "exact": 20659, + "Ġgrades": 20660, + "Ġfir": 20661, + "iga": 20662, + "ĠPolynomial": 20663, + "ĠLongitudinal": 20664, + "Ġsemiconductors": 20665, + "Top": 20666, + "iptera": 20667, + "Ġlacks": 20668, + "rograph": 20669, + "Ġselects": 20670, + "Ġsweet": 20671, + "Ġbac": 20672, + "Ġdownloaded": 20673, + "aponic": 20674, + "ijk": 20675, + "otonic": 20676, + "normalized": 20677, + "ĠVariability": 20678, + "division": 20679, + "ĠSupers": 20680, + "ilab": 20681, + "Human": 20682, + "Ġleptin": 20683, + "Ġosmotic": 20684, + "Ġhur": 20685, + "ĠSingapore": 20686, + "ĠOPT": 20687, + "ĠSoviet": 20688, + "litaxel": 20689, + "retaceous": 20690, + "ĠOnc": 20691, + "ĠIX": 20692, + "ulas": 20693, + "uent": 20694, + "Ġlymphoid": 20695, + "Tc": 20696, + "Ġrationale": 20697, + "Layer": 20698, + "osities": 20699, + "Ġdesire": 20700, + "ĠAnnual": 20701, + "uba": 20702, + "ĠCompounds": 20703, + "Ġantifungal": 20704, + "Ġcationic": 20705, + "items": 20706, + "acterium": 20707, + "amilies": 20708, + "Ġelongated": 20709, + "ĠMassachusetts": 20710, + "ĠIrish": 20711, + "asso": 20712, + "azo": 20713, + "ĠBurk": 20714, + "robenius": 20715, + "Ġisinstance": 20716, + "bion": 20717, + "Ġgreedy": 20718, + "Ġnicotine": 20719, + "Ġretrieve": 20720, + "Ġsympathetic": 20721, + "quee": 20722, + "Ġfoli": 20723, + "Ġsputter": 20724, + "Ġgrading": 20725, + "determined": 20726, + "Ġabnorm": 20727, + "Ġmanagers": 20728, + "Ġtopical": 20729, + "Ġimmig": 20730, + "ĠDNN": 20731, + "gtr": 20732, + "Ġdetections": 20733, + "ĠObesity": 20734, + "suc": 20735, + "ĠSche": 20736, + "Ġtrunk": 20737, + "Ġtough": 20738, + "ĠBN": 20739, + "Ġru": 20740, + "oxif": 20741, + "Ġaiming": 20742, + "ĠExtracellular": 20743, + "Ġhaplotype": 20744, + "Du": 20745, + "ĠDing": 20746, + "ĠDol": 20747, + "Ġhumid": 20748, + "brom": 20749, + "Ġoffline": 20750, + "Combining": 20751, + "Ġpulsar": 20752, + "Ġpari": 20753, + "partate": 20754, + "imated": 20755, + "Ġwatershed": 20756, + "acrylamide": 20757, + "exec": 20758, + "ĠComposite": 20759, + "Ġdispersive": 20760, + "Ġtons": 20761, + "rometry": 20762, + "ĠJud": 20763, + "aza": 20764, + "Ġchickens": 20765, + "register": 20766, + "nz": 20767, + "Util": 20768, + "ĠVes": 20769, + "eV": 20770, + "ĠRule": 20771, + "substituted": 20772, + "Conv": 20773, + "query": 20774, + "Mac": 20775, + "ĠTar": 20776, + "implies": 20777, + "ĠRates": 20778, + "Ġrins": 20779, + "Ġtimescales": 20780, + "ĠCzech": 20781, + "Such": 20782, + "restimate": 20783, + "ĠMb": 20784, + "ĠFuj": 20785, + "ĠIMD": 20786, + "cit": 20787, + "Ġraising": 20788, + "........": 20789, + "home": 20790, + "asted": 20791, + "Ġocta": 20792, + "Ġcadmium": 20793, + "Ġpsori": 20794, + "roleum": 20795, + "ĠStellar": 20796, + "ĠKinase": 20797, + "ĠGard": 20798, + "ieu": 20799, + "ĠMoS": 20800, + "MG": 20801, + "ĠGSH": 20802, + "Ġhazards": 20803, + "Ġnice": 20804, + "heating": 20805, + "Ġreproducible": 20806, + "genesis": 20807, + "ĠIgM": 20808, + "Ġbeat": 20809, + "onuclease": 20810, + "entralized": 20811, + "ĠLé": 20812, + "Ġdol": 20813, + "Ġdeeply": 20814, + "ractive": 20815, + "Ġglial": 20816, + "iella": 20817, + "Ġinitialized": 20818, + "ĠMethodology": 20819, + "Ġbenthic": 20820, + "omi": 20821, + "ĠAlter": 20822, + "Ordered": 20823, + "ĠLIN": 20824, + "Ġunilateral": 20825, + "Ġcorticoster": 20826, + "LEN": 20827, + "Ġdilute": 20828, + "Ġmetalloprotein": 20829, + "abeth": 20830, + "ampion": 20831, + "Ġmoral": 20832, + "ĠSiC": 20833, + "Ġquadrature": 20834, + "Ġsedimentation": 20835, + "ete": 20836, + "ĠFrag": 20837, + "Ġpeaked": 20838, + "Ġmitigation": 20839, + "Ġsoldi": 20840, + "Ġdoubly": 20841, + "Ġellipso": 20842, + "ĠlncRNAs": 20843, + "Ġâİ¢": 20844, + "ĠSame": 20845, + "ĠSustain": 20846, + "ĠCapacity": 20847, + "Ġsomat": 20848, + "Ġtransistor": 20849, + "Ġassayed": 20850, + "ĠNur": 20851, + "tools": 20852, + "Sing": 20853, + "Ġligament": 20854, + "atever": 20855, + "Ġperce": 20856, + "hence": 20857, + "UX": 20858, + "sent": 20859, + "EGG": 20860, + "third": 20861, + "enders": 20862, + "theoretic": 20863, + "Ġrewards": 20864, + "uto": 20865, + "Ġinstallation": 20866, + "ĠKinetic": 20867, + "ĠInnov": 20868, + "ĠSolving": 20869, + "ĠSymmetry": 20870, + "Ġramp": 20871, + "Ġneuropathy": 20872, + "omerization": 20873, + "Ġcatech": 20874, + "Pred": 20875, + "ĠBoh": 20876, + "EMENT": 20877, + "Ġarmy": 20878, + "ĠYukawa": 20879, + "Ġalignments": 20880, + "ĠDependence": 20881, + "Ġenv": 20882, + "ean": 20883, + "sr": 20884, + "Ġinterpreting": 20885, + "elocity": 20886, + "Ġpsychology": 20887, + "Ġbiofilms": 20888, + "Ġeccentricity": 20889, + "lot": 20890, + "analytic": 20891, + "Ġperiodicity": 20892, + "nings": 20893, + "ĠKent": 20894, + "flag": 20895, + "Ġmp": 20896, + "ĠNich": 20897, + "hire": 20898, + "Ġflare": 20899, + "Ġcitrate": 20900, + "Ġpaste": 20901, + "Ġdelete": 20902, + "zymes": 20903, + "orientation": 20904, + "ĠHY": 20905, + "Ġcommands": 20906, + "Ġstrike": 20907, + "symbol": 20908, + "ĠMind": 20909, + "Ġoptimisation": 20910, + "Ġosteoporosis": 20911, + "ĠInflammation": 20912, + "ĠIntelligence": 20913, + "eh": 20914, + "utum": 20915, + "Ġvec": 20916, + "ellation": 20917, + "ĠBloch": 20918, + "ĠMajorana": 20919, + "enor": 20920, + "ĠNgu": 20921, + "Ġdeuter": 20922, + "opedia": 20923, + "Ġutter": 20924, + "Ġribosome": 20925, + "Ġactors": 20926, + "electronic": 20927, + "ée": 20928, + "Ġfeaturing": 20929, + "agle": 20930, + "Ġperin": 20931, + "ĠCivil": 20932, + "Ġpredefined": 20933, + "lag": 20934, + "ĠJAK": 20935, + "jamin": 20936, + "individual": 20937, + "onc": 20938, + "Ġfishing": 20939, + "ditive": 20940, + "Norm": 20941, + "ĠScanning": 20942, + "vanishing": 20943, + "Ġcessation": 20944, + "ĠHole": 20945, + "ributes": 20946, + "IE": 20947, + "ĠMpc": 20948, + "wegian": 20949, + "Ma": 20950, + "Ġrevisited": 20951, + "ĠPlus": 20952, + "abilized": 20953, + "Ġscanned": 20954, + "ĠExchange": 20955, + "Ġbromide": 20956, + "Life": 20957, + "otroph": 20958, + "ADS": 20959, + "âĭħ": 20960, + "Ġoperative": 20961, + "ĠBERT": 20962, + "Ġplume": 20963, + "Ġpoorer": 20964, + "Ġtrout": 20965, + "Ġmicrotubule": 20966, + "Ġphosphatidyl": 20967, + "radius": 20968, + "ĠMuscle": 20969, + "Ġcarcinogenesis": 20970, + "Ġseeing": 20971, + "uclein": 20972, + "follow": 20973, + "Ġsupplements": 20974, + "olars": 20975, + "specially": 20976, + "Ġcompleting": 20977, + "Ġnaïve": 20978, + "ĠÏ©": 20979, + "clerotic": 20980, + "Disc": 20981, + "ĠFestival": 20982, + "Ġclick": 20983, + "clusive": 20984, + "Ġcatalogue": 20985, + "Ġapps": 20986, + "ĠSED": 20987, + "Ġstacked": 20988, + "Ġtune": 20989, + "ĠDMEM": 20990, + "Ġaerosols": 20991, + "Ġgear": 20992, + "antine": 20993, + "ĠStone": 20994, + "Ġpositives": 20995, + "triang": 20996, + "probability": 20997, + "Ġdecoupling": 20998, + "ĠÍĵ": 20999, + "ĠVin": 21000, + "Ġsurvived": 21001, + "Ġreplicated": 21002, + "utrient": 21003, + "Ġtemperate": 21004, + "Ġensembles": 21005, + "Ġmulticenter": 21006, + "Ġgaseous": 21007, + "idea": 21008, + "classification": 21009, + "ĠOutcome": 21010, + "clonal": 21011, + "Ġdiscontinuity": 21012, + "Ġadvantageous": 21013, + "Ġdistricts": 21014, + "ĠIBM": 21015, + "inguishable": 21016, + "Ġcars": 21017, + "cult": 21018, + "enriched": 21019, + "argin": 21020, + "novae": 21021, + "steady": 21022, + "Ġbuy": 21023, + "piration": 21024, + "Ġpartitioned": 21025, + "Ġinability": 21026, + "pq": 21027, + "Ġbull": 21028, + "odend": 21029, + "Ġassistant": 21030, + "Ġlumen": 21031, + "Ġconverting": 21032, + "PY": 21033, + "zol": 21034, + "utors": 21035, + "ĠNLRP": 21036, + "apply": 21037, + "ĠBonferroni": 21038, + "Ls": 21039, + "Ġtips": 21040, + "ĠLN": 21041, + "rolase": 21042, + "Ġadvis": 21043, + "ĠMetast": 21044, + "Ġsaliva": 21045, + "Ġinhabit": 21046, + "Ġrim": 21047, + "debug": 21048, + "Any": 21049, + "Ġforb": 21050, + "Ġversatile": 21051, + "ĠCompact": 21052, + "voc": 21053, + "ĠIso": 21054, + "ĠJus": 21055, + "bodies": 21056, + "ARM": 21057, + "ĠGWAS": 21058, + "hetized": 21059, + "Ġmicrofluidic": 21060, + "Ġacetonitrile": 21061, + "Ġinhom": 21062, + "Ġparench": 21063, + "Ġinsensitive": 21064, + "Ġagency": 21065, + "poor": 21066, + "ĠAngi": 21067, + "Ġapproached": 21068, + "Ġemulsion": 21069, + "Ġvoluntary": 21070, + "utt": 21071, + "ĠRecurrent": 21072, + "riculum": 21073, + "ê": 21074, + "Ġtall": 21075, + "ĠDepth": 21076, + "Ġff": 21077, + "ĠIncidence": 21078, + "Ġmanifestation": 21079, + "Ġcompromised": 21080, + "iaceae": 21081, + "ĠMIT": 21082, + "otransfer": 21083, + "ĠWD": 21084, + "mov": 21085, + "ĠManual": 21086, + "Medi": 21087, + "Ġinterfering": 21088, + "ĠJacobi": 21089, + "KT": 21090, + "Ġsarcoma": 21091, + "Ġkidneys": 21092, + "Ġodor": 21093, + "Ġti": 21094, + "yday": 21095, + "although": 21096, + "visible": 21097, + "Ġdengue": 21098, + "ĠCAL": 21099, + "strat": 21100, + "ĠVariations": 21101, + "inib": 21102, + "components": 21103, + "ĠTob": 21104, + "ĠAntioxidant": 21105, + "ÍĶ": 21106, + "Ġkiller": 21107, + "Ġsubtracted": 21108, + "ĠEvents": 21109, + "Ġimplements": 21110, + "ĠGAN": 21111, + "Ġprophylaxis": 21112, + "Ġnozz": 21113, + "Ġsmoothed": 21114, + "Ġdecaying": 21115, + "ĠInitially": 21116, + "Ġuncommon": 21117, + "Ġconductor": 21118, + "ĠWOR": 21119, + "avity": 21120, + "ĠXie": 21121, + "ĠAcet": 21122, + "Ġine": 21123, + "ĠBeam": 21124, + "opolymer": 21125, + "ĠXML": 21126, + "ĠWide": 21127, + "Ñĥ": 21128, + "Ġejection": 21129, + "BMI": 21130, + "tc": 21131, + "uez": 21132, + "Ġcerebellar": 21133, + "Ġcatchment": 21134, + "coxon": 21135, + "ĠShannon": 21136, + "Ġcentrality": 21137, + "Ġsafely": 21138, + "probe": 21139, + "ĠLaboratories": 21140, + "Ġnc": 21141, + "Ġspher": 21142, + "Ġprobing": 21143, + "ĠLev": 21144, + "Ġaf": 21145, + "ĠMig": 21146, + "ĠVascular": 21147, + "Ġprogrammes": 21148, + "Ġcontaminants": 21149, + "sequent": 21150, + "Ġbonded": 21151, + "integration": 21152, + "bos": 21153, + "ĠFew": 21154, + "ĠIllinois": 21155, + "She": 21156, + "WC": 21157, + "ĠGPIO": 21158, + "oC": 21159, + "ĠMaternal": 21160, + "ercetin": 21161, + "ĠMassive": 21162, + "Ġenorm": 21163, + "imgur": 21164, + "Ġbidirectional": 21165, + "ĠGraphene": 21166, + "insky": 21167, + "ĠObserve": 21168, + "Ġstops": 21169, + "bio": 21170, + "ĠLines": 21171, + "ĠGill": 21172, + "Ġeigenvector": 21173, + "Space": 21174, + "ĠMining": 21175, + "Ġmelatonin": 21176, + "ĠSET": 21177, + "onsequ": 21178, + "oscale": 21179, + "ĠRaw": 21180, + "Ġreviewers": 21181, + "Ġnanofib": 21182, + "taking": 21183, + "ammad": 21184, + "Ġrecursion": 21185, + "usal": 21186, + "Ġpositron": 21187, + "ĠNIH": 21188, + "ĠINTER": 21189, + "ĠDocument": 21190, + "Ġconstantly": 21191, + "Ġundergone": 21192, + "Ġelectroweak": 21193, + "Ġiteratively": 21194, + "folio": 21195, + "Ġsubfamily": 21196, + "Ġâİ¥": 21197, + "Page": 21198, + "ferm": 21199, + "avir": 21200, + "Ġagencies": 21201, + "Ġpolys": 21202, + "ĠSquare": 21203, + "ymm": 21204, + "Ġhydrogels": 21205, + "almost": 21206, + "arter": 21207, + "Ġankle": 21208, + "Ġrises": 21209, + "Ġmedull": 21210, + "gated": 21211, + "Ġmononuclear": 21212, + "Ġdiscussing": 21213, + "Ġprofessor": 21214, + "transformed": 21215, + "Ġcolours": 21216, + "ragg": 21217, + "emicon": 21218, + "Ġsymmetrical": 21219, + "Ġplacental": 21220, + "Ġli": 21221, + "Ġstudio": 21222, + "sequences": 21223, + "Ġtam": 21224, + "ĠLap": 21225, + "ĠCriteria": 21226, + "Ġhappened": 21227, + "Ġantiferromagnetic": 21228, + "ĠHausdorff": 21229, + "ĠCONCLUSIONS": 21230, + "HER": 21231, + "VR": 21232, + "ĠKor": 21233, + "ĠAPO": 21234, + "Ġprotecting": 21235, + "ĠSOL": 21236, + "ĠBuck": 21237, + "phia": 21238, + "ĠMultim": 21239, + "onine": 21240, + "ulsions": 21241, + "Ġgp": 21242, + "benzamide": 21243, + "ĠNADPH": 21244, + "ĠOhio": 21245, + "ĠMEG": 21246, + "COVID": 21247, + "Ġdisplaced": 21248, + "ĠAbb": 21249, + "Ġbranched": 21250, + "ĠNavy": 21251, + "ĠNrf": 21252, + "ĠODE": 21253, + "achi": 21254, + "ĠTransient": 21255, + "Ġcircumference": 21256, + "Ġbees": 21257, + "iration": 21258, + "Ġfaculty": 21259, + "IGHT": 21260, + "ĠMetabolism": 21261, + "MK": 21262, + "ĠInequ": 21263, + "ĠQualitative": 21264, + "PBS": 21265, + "terminus": 21266, + "kary": 21267, + "ovian": 21268, + "ĠTHz": 21269, + "ĠReliability": 21270, + "furan": 21271, + "Ġcorners": 21272, + "Ġattacker": 21273, + "Ġmarriage": 21274, + "oprecipitation": 21275, + "ĠCry": 21276, + "ĠâĬĻ": 21277, + "Ġevolves": 21278, + "Ġban": 21279, + "Ġdiurnal": 21280, + "ounce": 21281, + "Ġoverw": 21282, + "ĠHoff": 21283, + "Ġextrinsic": 21284, + "amps": 21285, + "ULAR": 21286, + "opher": 21287, + "Ġlighting": 21288, + "Ġarchitectural": 21289, + "hesive": 21290, + "Ġsavings": 21291, + "Ġglaucoma": 21292, + "ozoa": 21293, + "ĠOption": 21294, + "controll": 21295, + "ecker": 21296, + "Ġosteocl": 21297, + "Ġglycine": 21298, + "analyses": 21299, + "ĠAld": 21300, + "ĠSyd": 21301, + "ĠCx": 21302, + "Ġscalars": 21303, + "Ġknowing": 21304, + "Ġremember": 21305, + "ĠEmbry": 21306, + "TEM": 21307, + "ĠBran": 21308, + "FORM": 21309, + "Ġsurviving": 21310, + "Ġglobular": 21311, + "Ġinclusive": 21312, + "sched": 21313, + "UTION": 21314, + "Ġquadrupole": 21315, + "ĠHubbard": 21316, + "Ġaxonal": 21317, + "ĠCosmic": 21318, + "Ġslots": 21319, + "ĠProcedure": 21320, + "agin": 21321, + "ĠLoop": 21322, + "arer": 21323, + "Ġbutter": 21324, + "Ġhistopathological": 21325, + "fusion": 21326, + "ANOVA": 21327, + "Ġclosing": 21328, + "ĠLord": 21329, + "ĠBis": 21330, + "ĠRAM": 21331, + "IDE": 21332, + "Ġjournals": 21333, + "Ġmonkeys": 21334, + "Ġattenuates": 21335, + "Ġsegmented": 21336, + "TOF": 21337, + "otional": 21338, + "polymer": 21339, + "ĠShah": 21340, + "Akt": 21341, + "Wr": 21342, + "lov": 21343, + "Ġpolymorphic": 21344, + "Ġarrangements": 21345, + "UF": 21346, + "lon": 21347, + "Ġdepressed": 21348, + "NAT": 21349, + "ĠOperation": 21350, + "ι": 21351, + "ĠRan": 21352, + "âIJ": 21353, + "Ġthereafter": 21354, + "Ġmyeloma": 21355, + "jor": 21356, + "Ã¥": 21357, + "ĠWinter": 21358, + "ptosis": 21359, + "Dir": 21360, + "verty": 21361, + "ĠFinn": 21362, + "Ġortholog": 21363, + "Ġmonotonically": 21364, + "Ġtectonic": 21365, + "ĠGBM": 21366, + "ĠAO": 21367, + "Ġgenerative": 21368, + "Clearly": 21369, + "Ġtile": 21370, + "ĠRNN": 21371, + "Ġgrounds": 21372, + "GaAs": 21373, + "Ġbee": 21374, + "ĠBoy": 21375, + "ĠTranscriptional": 21376, + "urin": 21377, + "otom": 21378, + "Ġsinusoidal": 21379, + "ĠAy": 21380, + "ĠClinic": 21381, + "utorial": 21382, + "ĠADC": 21383, + "ERIAL": 21384, + "cation": 21385, + "ĠADHD": 21386, + "cyclohex": 21387, + "ĠHawai": 21388, + "astom": 21389, + "Ġmorphologies": 21390, + "Ġrodents": 21391, + "Ġscalability": 21392, + "ROS": 21393, + "aemia": 21394, + "Ġdecompose": 21395, + "Ġpivotal": 21396, + "Ġdiffusivity": 21397, + "Ġcovalent": 21398, + "ĠKD": 21399, + "atalyst": 21400, + "Ġoldest": 21401, + "Ġsuitability": 21402, + "Ġwants": 21403, + "ifts": 21404, + "ĠDistributions": 21405, + "ĠQueen": 21406, + "lich": 21407, + "Ġparse": 21408, + "ĠMHD": 21409, + "Ġrecre": 21410, + "Ġhydroxide": 21411, + "eum": 21412, + "Ġlev": 21413, + "Ġreferral": 21414, + "planes": 21415, + "ĠEgypt": 21416, + "Ġlenti": 21417, + "Ġtransactions": 21418, + "Ġexpense": 21419, + "Ġcysts": 21420, + "Ġabscess": 21421, + "ĠmicroRNAs": 21422, + "effectiveness": 21423, + "ĠDifferentiation": 21424, + "Ġcertif": 21425, + "cience": 21426, + "ĠREL": 21427, + "Ġreadout": 21428, + "ĠQuasi": 21429, + "Ġrounded": 21430, + "otti": 21431, + "efficients": 21432, + "Ġsynchronized": 21433, + "Ġsilico": 21434, + "Ġforecasts": 21435, + "Ġdμ": 21436, + "Ġexotic": 21437, + "ĠOCT": 21438, + "xb": 21439, + "Ġasynchronous": 21440, + "nez": 21441, + "chiat": 21442, + "Ġhaemat": 21443, + "Ġfulfill": 21444, + "ĠMix": 21445, + "ibli": 21446, + "fm": 21447, + "Ġjava": 21448, + "soluble": 21449, + "Ġincompressible": 21450, + "âĨij": 21451, + "CDM": 21452, + "Ġdilation": 21453, + "LYP": 21454, + "ashes": 21455, + "ĠSports": 21456, + "Ġfundament": 21457, + "ĠSaudi": 21458, + "Ġenroll": 21459, + "ĠNaOH": 21460, + "Ġcrustal": 21461, + "ĠInstruments": 21462, + "Ġïģ¡": 21463, + "Result": 21464, + "Ġpreferential": 21465, + "Ġsugars": 21466, + "Ġdimers": 21467, + "ĠEmerging": 21468, + "ère": 21469, + "diabetic": 21470, + "Ġstrengthening": 21471, + "epi": 21472, + "ĠMeg": 21473, + "ĠYour": 21474, + "ĠSetting": 21475, + "lez": 21476, + "ĠBou": 21477, + "Ġhistology": 21478, + "Ġolive": 21479, + "ĠDisorders": 21480, + "Ġdistorted": 21481, + "Ġcompete": 21482, + "cens": 21483, + "ĠAe": 21484, + "ĠGG": 21485, + "Ġquantifying": 21486, + "Ġaur": 21487, + "ĠWright": 21488, + "Ġsuperconductor": 21489, + "eds": 21490, + "stalk": 21491, + "concent": 21492, + "ĠLimited": 21493, + "Ġstyles": 21494, + "design": 21495, + "ĠEllip": 21496, + "PLA": 21497, + "mogorov": 21498, + "ĠRidge": 21499, + "Ġrandomization": 21500, + "aft": 21501, + "icially": 21502, + "ĠBiotechnology": 21503, + "Ġseizure": 21504, + "KI": 21505, + "AVE": 21506, + "receptor": 21507, + "Ġgrammar": 21508, + "Ġcrime": 21509, + "nection": 21510, + "inces": 21511, + "ĠCompton": 21512, + "Ġventricle": 21513, + "Ġredistribution": 21514, + "ynaptic": 21515, + "Parameter": 21516, + "Normal": 21517, + "Pack": 21518, + "ermann": 21519, + "ulants": 21520, + "degenerate": 21521, + "ĠNewtonian": 21522, + "Ġancestral": 21523, + "phrag": 21524, + "Ġimpression": 21525, + "Ġnormalize": 21526, + "Ġambiguous": 21527, + "Ġingredient": 21528, + "ĠClaim": 21529, + "Ġcleaved": 21530, + "ĠApproaches": 21531, + "ĠSPECT": 21532, + "csv": 21533, + "ĠReveals": 21534, + "ĠWaves": 21535, + "Ġdwarfs": 21536, + "ĠProgress": 21537, + "Ġaorta": 21538, + "Ġnig": 21539, + "ĠAdams": 21540, + "ĠMüller": 21541, + "ĠYellow": 21542, + "ĠCord": 21543, + "ĠPhill": 21544, + "ĠFormal": 21545, + "besgue": 21546, + "termin": 21547, + "rn": 21548, + "bn": 21549, + "kine": 21550, + "rit": 21551, + "qi": 21552, + "ĠRoute": 21553, + "enol": 21554, + "ĠASC": 21555, + "ĠPu": 21556, + "mill": 21557, + "umer": 21558, + "Ġsupernova": 21559, + "iative": 21560, + "differenti": 21561, + "Ġtolu": 21562, + "opus": 21563, + "RM": 21564, + "Ġpoverty": 21565, + "ĠXX": 21566, + "ĠïĤ¶": 21567, + "ultry": 21568, + "Optim": 21569, + "Ġglacial": 21570, + "ĠDispers": 21571, + "Ġdifferentiating": 21572, + "ández": 21573, + "project": 21574, + "ĠEliz": 21575, + "scaling": 21576, + "ĠToll": 21577, + "Ġnonempty": 21578, + "Ġpredicate": 21579, + "Ġgyrus": 21580, + "minute": 21581, + "âĸ": 21582, + "ĠHind": 21583, + "ĠLiving": 21584, + "VS": 21585, + "prior": 21586, + "ĠVertical": 21587, + "arks": 21588, + "ĠSFR": 21589, + "ĠVietnam": 21590, + "compare": 21591, + ">>>": 21592, + "Ġbanks": 21593, + "Ġseptic": 21594, + "ĠBif": 21595, + "ĠEPS": 21596, + "ĠIntel": 21597, + "ĠDisorder": 21598, + "PN": 21599, + "ĠNord": 21600, + "tiveness": 21601, + "Ġdrilling": 21602, + "ĠSubject": 21603, + "enario": 21604, + "Ġrms": 21605, + "phones": 21606, + "hang": 21607, + "ĠTechnique": 21608, + "Ġclot": 21609, + "Ġintersections": 21610, + "Ġanions": 21611, + "above": 21612, + "Ġclause": 21613, + "Ġgenu": 21614, + "ozo": 21615, + "rhiz": 21616, + "Ġlobes": 21617, + "ĠBian": 21618, + "Ġexerted": 21619, + "ureth": 21620, + "roma": 21621, + "ĠCharge": 21622, + "ĠSynchron": 21623, + "Ġconting": 21624, + "otherapeutic": 21625, + "gtrsim": 21626, + "ĠResonance": 21627, + "ĠFal": 21628, + "undle": 21629, + "Ġdropout": 21630, + "ĠPerspective": 21631, + "OLOG": 21632, + "atches": 21633, + "ĠSequences": 21634, + "Considering": 21635, + "Ġprospects": 21636, + "Ġaliqu": 21637, + "Ġstrata": 21638, + "Ġanalogs": 21639, + "Ġencouraged": 21640, + "ĠPulmonary": 21641, + "Ġchim": 21642, + "ĠCFT": 21643, + "unar": 21644, + "izz": 21645, + "endocrine": 21646, + "ĠCRE": 21647, + "ĠStroke": 21648, + "âĩĴ": 21649, + "upuncture": 21650, + "translational": 21651, + "ĠGriff": 21652, + "ĠSter": 21653, + "erged": 21654, + "phrine": 21655, + "Ġlivestock": 21656, + "ĠHash": 21657, + "Ġdosing": 21658, + "Ġplasmas": 21659, + "ĠComparisons": 21660, + "Ġencouraging": 21661, + "Ġcomparatively": 21662, + "Ġcharacterisation": 21663, + "Ġascending": 21664, + "ĠFixed": 21665, + "Ġvegetable": 21666, + "especially": 21667, + "ĠLange": 21668, + "ĠCoding": 21669, + "Ġvertebrate": 21670, + "FW": 21671, + "ĠORF": 21672, + "ĠTub": 21673, + "lee": 21674, + "Ġtimely": 21675, + "Ep": 21676, + "ĠâĪĴâĪŀ": 21677, + "Ġliposomes": 21678, + "Ġextremal": 21679, + "ropolitan": 21680, + "ĠCay": 21681, + "ĠBiod": 21682, + "oulli": 21683, + "Dri": 21684, + "ĠRats": 21685, + "Ġcentroid": 21686, + "ospin": 21687, + "rospinal": 21688, + "Ġsolitons": 21689, + "portive": 21690, + "ĠMcG": 21691, + "Bbb": 21692, + "Ġparaffin": 21693, + "lectively": 21694, + "Ġmetastable": 21695, + "Ġdissipative": 21696, + "Ġassemblages": 21697, + "Ġcolonic": 21698, + "Ġsized": 21699, + "Ġcryp": 21700, + "processor": 21701, + "ção": 21702, + "Ġacknowledged": 21703, + "ĠUncertainty": 21704, + "ĠIndustrial": 21705, + "Ġuncont": 21706, + "Ġrefere": 21707, + "ĠNitrogen": 21708, + "Ġlifting": 21709, + "Ġforget": 21710, + "Ġfeelings": 21711, + "Ġdigits": 21712, + "Ġstratig": 21713, + "ypes": 21714, + "Ġcompositional": 21715, + "Ġsupernatants": 21716, + "Ġconflicting": 21717, + "Ġdisadvantage": 21718, + "adelphia": 21719, + "Pd": 21720, + "ĠCoupling": 21721, + "Ġexpenditure": 21722, + "iki": 21723, + "described": 21724, + "ĠRNase": 21725, + "ĠConvex": 21726, + "ĠBax": 21727, + "ungsten": 21728, + "Ġboiling": 21729, + "Ġbasement": 21730, + "ocardial": 21731, + "history": 21732, + "inton": 21733, + "trimethyl": 21734, + "Ġgrafting": 21735, + "ĠHardy": 21736, + "ĠFemale": 21737, + "ĠFollow": 21738, + "ĠEST": 21739, + "tistic": 21740, + "Open": 21741, + "Ġreflux": 21742, + "elements": 21743, + "Ġpolysaccharide": 21744, + "dims": 21745, + "acency": 21746, + "Ġbiore": 21747, + "capac": 21748, + "Ġoverexpressed": 21749, + "either": 21750, + "Ġlaid": 21751, + "Ġincision": 21752, + "Ġassets": 21753, + "inflammation": 21754, + "Ġreconstructions": 21755, + "Ġglomerular": 21756, + "Ġconvey": 21757, + "ĠCXCR": 21758, + "oro": 21759, + "Ġclassifying": 21760, + "Ġcope": 21761, + "Ġpd": 21762, + "linic": 21763, + "Ġchord": 21764, + "ĠAging": 21765, + "Ġpalm": 21766, + "Ġpermittivity": 21767, + "ĠReverse": 21768, + "Ġoffshore": 21769, + "Ġdoubt": 21770, + "imoto": 21771, + "ĠColomb": 21772, + "Ġrodent": 21773, + "ĠElectrochemical": 21774, + "ĠImprovement": 21775, + "inescent": 21776, + "ĠTriton": 21777, + "Ġtransfusion": 21778, + "Ġlocomotion": 21779, + "Ġdangerous": 21780, + "Ġweighed": 21781, + "ĠHSV": 21782, + "techn": 21783, + "ĠDiagram": 21784, + "Ġparietal": 21785, + "six": 21786, + "Ġtitles": 21787, + "ylon": 21788, + "Ġheuristics": 21789, + "Ġjaponic": 21790, + "Ġtranslations": 21791, + "Ġtiters": 21792, + "Ġworms": 21793, + "ĠDPP": 21794, + "Ġcytoskeleton": 21795, + "Mediated": 21796, + "ariance": 21797, + "thel": 21798, + "Ãħ": 21799, + "ĠInflammatory": 21800, + "Ġoscillating": 21801, + "Ġavoids": 21802, + "Define": 21803, + "ĠOlympics": 21804, + "ogel": 21805, + "Ġheme": 21806, + "Ġmicrop": 21807, + "Ġthreats": 21808, + "QCD": 21809, + "XRD": 21810, + "ĠCoupled": 21811, + "Ġlm": 21812, + "ĠHelic": 21813, + "Ġdischarged": 21814, + "Ġrooted": 21815, + "Ġalleviate": 21816, + "Ġcausality": 21817, + "ĠCrow": 21818, + "ĠMack": 21819, + "ĠAirport": 21820, + "Ġchemokine": 21821, + "Ġll": 21822, + "ĠNar": 21823, + "omyces": 21824, + "ethoxyphenyl": 21825, + "ĠDaily": 21826, + "ĠFinland": 21827, + "Ġhorn": 21828, + "ĠOrient": 21829, + "Ġionized": 21830, + "ĠYears": 21831, + "Ġquasipar": 21832, + "Ġpercutaneous": 21833, + "Phase": 21834, + "Ġforeground": 21835, + "ĠANAL": 21836, + "Ġincrements": 21837, + "stan": 21838, + "Ġspeculate": 21839, + "TX": 21840, + "Ġpile": 21841, + "Ġdic": 21842, + "ipy": 21843, + "window": 21844, + "neutral": 21845, + "ĠAtlas": 21846, + "ĠMTT": 21847, + "ĠNy": 21848, + "ĠVIII": 21849, + "ĠFilms": 21850, + "singular": 21851, + "remove": 21852, + "Length": 21853, + "ĠRece": 21854, + "wait": 21855, + "Ġpurchase": 21856, + "ĠWikipedia": 21857, + "ĠLars": 21858, + "Ġsyntactic": 21859, + "Ġactuator": 21860, + "ĠAKT": 21861, + "ĠBry": 21862, + "ĠResult": 21863, + "ĠVariational": 21864, + "Ġjudgment": 21865, + "JECT": 21866, + "ximab": 21867, + "Ġtraced": 21868, + "Ġcardiomyopathy": 21869, + "WN": 21870, + "ĠRodrig": 21871, + "bt": 21872, + "Ġbid": 21873, + "acle": 21874, + "amura": 21875, + "Ġepic": 21876, + "Ġpuzz": 21877, + "ĠSox": 21878, + "Ġinflux": 21879, + "ÃŃn": 21880, + "uloskeletal": 21881, + "Dim": 21882, + "ĠSCC": 21883, + "ĠRAS": 21884, + "mr": 21885, + "UI": 21886, + "Ġjun": 21887, + "ĠSpearman": 21888, + "Ġfairness": 21889, + "etz": 21890, + "ĠPPI": 21891, + "inance": 21892, + "enko": 21893, + "Ġgalact": 21894, + "öm": 21895, + "Ġexceptions": 21896, + "ĠCretaceous": 21897, + "MY": 21898, + "Resp": 21899, + "Ġpep": 21900, + "ĠOrd": 21901, + "STE": 21902, + "Ġhelicity": 21903, + "Ġofficer": 21904, + "Target": 21905, + "ĠNorwegian": 21906, + "Ġdehydration": 21907, + "ĠSIRT": 21908, + "ĠRobinson": 21909, + "ĠBenchmark": 21910, + "viral": 21911, + "Real": 21912, + "Ġdoxorubicin": 21913, + "Ġestimations": 21914, + "ĠCauc": 21915, + "Ġadditives": 21916, + "modes": 21917, + "ĠHend": 21918, + "Ġaccelerating": 21919, + "ĠGordon": 21920, + "ĠMagnet": 21921, + "Ġgonad": 21922, + "Ġpyrolysis": 21923, + "coholic": 21924, + "ĠPKC": 21925, + "SAR": 21926, + "Ġwinding": 21927, + "terious": 21928, + "ĠMountains": 21929, + "ĠSymbol": 21930, + "ĠMatthe": 21931, + "ĠShin": 21932, + "Script": 21933, + "rug": 21934, + "ĠmW": 21935, + "ĠISM": 21936, + "ĠNg": 21937, + "Ġappoint": 21938, + "ĠAIDS": 21939, + "Ġports": 21940, + "differential": 21941, + "ĠJes": 21942, + "ĠNeed": 21943, + "Ġlenses": 21944, + "ĠAMPK": 21945, + "à¤": 21946, + "leaf": 21947, + "ĠBron": 21948, + "Ġprofit": 21949, + "Local": 21950, + "ĠExamination": 21951, + "ĠChief": 21952, + "Ġopinions": 21953, + "ĠRound": 21954, + "formations": 21955, + "Ġcollinear": 21956, + "Ġdigested": 21957, + "lassical": 21958, + "ervative": 21959, + "Ġcephal": 21960, + "Ġdisadvantages": 21961, + "Ġïĥ·": 21962, + "Ġsubtracting": 21963, + "Ġweigh": 21964, + "Bound": 21965, + "DG": 21966, + "Ġinfluential": 21967, + "Ġtoxins": 21968, + "ĠBenjamin": 21969, + "ĠNumbers": 21970, + "crystal": 21971, + "Ġstocks": 21972, + "ĠBour": 21973, + "ĠCompeting": 21974, + "Ġacqu": 21975, + "tRNA": 21976, + "ĠSeparation": 21977, + "Ġtagged": 21978, + "Ġconject": 21979, + "ĠPrince": 21980, + "Ġgrazing": 21981, + "Ġreleases": 21982, + "ĠChallenge": 21983, + "ATPase": 21984, + "Ġemail": 21985, + "insically": 21986, + "ĠRegulatory": 21987, + "Message": 21988, + "Ġslit": 21989, + "Ġpolygon": 21990, + "Ġdoubling": 21991, + "Ġreceivers": 21992, + "Ġtracked": 21993, + "Ġengineer": 21994, + "stained": 21995, + "ĠDanish": 21996, + "shock": 21997, + "ĠMaz": 21998, + "Ġcough": 21999, + "ĠImmunohist": 22000, + "Consequ": 22001, + "armacy": 22002, + "Ġchemo": 22003, + "ĠMH": 22004, + "Ġemerges": 22005, + "Ġannealed": 22006, + "Ġhypothesize": 22007, + "ĠTypically": 22008, + "ĠBang": 22009, + "ĠPuls": 22010, + "Ġgirl": 22011, + "Ġherbiv": 22012, + "ĠANN": 22013, + "Ġseism": 22014, + "ĠCytok": 22015, + "ĠThroughout": 22016, + "Ġadaptations": 22017, + "lang": 22018, + "Ġclonal": 22019, + "umulation": 22020, + "ĠUniform": 22021, + "Ġhi": 22022, + "opent": 22023, + "Ġbutton": 22024, + "tene": 22025, + "Ġproteasome": 22026, + "bred": 22027, + "ĠNelson": 22028, + "racycline": 22029, + "ĠDY": 22030, + "Ġimmunoblot": 22031, + "prol": 22032, + "Ġpic": 22033, + "Ġcompilation": 22034, + "ĠDevices": 22035, + "etermined": 22036, + "ĠFrancis": 22037, + "notation": 22038, + "writing": 22039, + "terase": 22040, + "ĠStephen": 22041, + "amel": 22042, + "ĠChu": 22043, + "alone": 22044, + "Ġexhaust": 22045, + "relevant": 22046, + "ĠStrat": 22047, + "Ġparametrization": 22048, + "ĠBull": 22049, + "ĠRemote": 22050, + "increasing": 22051, + "Ġdd": 22052, + "â̰": 22053, + "yroidism": 22054, + "ilin": 22055, + "ĠHip": 22056, + "ICA": 22057, + "ĠApoptosis": 22058, + "Ġmachining": 22059, + "LDL": 22060, + "Ġgem": 22061, + "ĠFFT": 22062, + "ĠGuang": 22063, + "Ġoriginates": 22064, + "dat": 22065, + "cone": 22066, + "ĠAdoles": 22067, + "ucci": 22068, + "avoid": 22069, + "ulpt": 22070, + "urium": 22071, + "Ġliteracy": 22072, + "Recent": 22073, + "avg": 22074, + "Ġinvited": 22075, + "ĠPeak": 22076, + "ĠEnterobacter": 22077, + "Ġaneurysm": 22078, + "ĠMorris": 22079, + "tida": 22080, + "ĠSER": 22081, + "ĠMichel": 22082, + "ĠIBD": 22083, + "ĠNG": 22084, + "Ġscarce": 22085, + "web": 22086, + "Ġexpresses": 22087, + "Ġschema": 22088, + "Ġlessons": 22089, + "Ġarginine": 22090, + "Ġphotographs": 22091, + "ĠNeurons": 22092, + "ĠATPase": 22093, + "Ġfiller": 22094, + "rapped": 22095, + "Ġrandomness": 22096, + "Ġveins": 22097, + "Ġwounds": 22098, + "ĠApart": 22099, + "Ġracial": 22100, + "Ġnoteworthy": 22101, + "Ġremoves": 22102, + "Ġganglion": 22103, + "Ġlaminar": 22104, + "ĠSSR": 22105, + "Ġpolysaccharides": 22106, + "Ġbuf": 22107, + "Ġendothelium": 22108, + "ĠCAS": 22109, + "ĠGolgi": 22110, + "Ġinheritance": 22111, + "isite": 22112, + "COMP": 22113, + "Ġpt": 22114, + "Ġmeshes": 22115, + "Ġtherapeutics": 22116, + "OST": 22117, + "olinergic": 22118, + "UG": 22119, + "squared": 22120, + "Ġdegrade": 22121, + "uum": 22122, + "Ġretrosp": 22123, + "Loc": 22124, + "ĠJNK": 22125, + "Options": 22126, + "Ġinsulating": 22127, + "Ġspecifies": 22128, + "Ġoven": 22129, + "yy": 22130, + "ĠConver": 22131, + "Ġdisappeared": 22132, + "ĠPrognostic": 22133, + "ĠNguyen": 22134, + "Ġperiphery": 22135, + "bank": 22136, + "Ġimid": 22137, + "Ġassigning": 22138, + "ĠMess": 22139, + "propan": 22140, + "ioma": 22141, + "olyb": 22142, + "Ġepitope": 22143, + "Ġemitting": 22144, + "DIR": 22145, + "ync": 22146, + "Ġimpairments": 22147, + "ĠMicroscopy": 22148, + "ĠFWHM": 22149, + "gray": 22150, + "Ġfing": 22151, + "ucial": 22152, + "plemented": 22153, + "eas": 22154, + "estamp": 22155, + "Ġcrest": 22156, + "ĠMos": 22157, + "Ġneutrons": 22158, + "Ġbroth": 22159, + "Ġheadache": 22160, + "ongevity": 22161, + "Ġreass": 22162, + "ĠPSF": 22163, + "ĠBuch": 22164, + "visor": 22165, + "Ġdenoting": 22166, + "integer": 22167, + "ouin": 22168, + "efficacy": 22169, + "Ġglutamine": 22170, + "Ġpicked": 22171, + "ĠCampbell": 22172, + "ĠKernel": 22173, + "Ġships": 22174, + "lt": 22175, + "ondyl": 22176, + "Ġcredi": 22177, + "Ġpeptid": 22178, + "ĠEstabl": 22179, + "bons": 22180, + "Ġaggl": 22181, + "USE": 22182, + "supp": 22183, + "upsilon": 22184, + "characterized": 22185, + "isheries": 22186, + "May": 22187, + "ARC": 22188, + "Ġroads": 22189, + "Ġdeparture": 22190, + "ĠMAX": 22191, + "ĠTRA": 22192, + "imod": 22193, + "ĠAlber": 22194, + "Ġterminated": 22195, + "ölder": 22196, + "Scalar": 22197, + "hash": 22198, + "ĠMSS": 22199, + "Ġsmoothness": 22200, + "Ġresemble": 22201, + "ĠEffectiveness": 22202, + "rx": 22203, + "ĠEye": 22204, + "Ġfaecal": 22205, + "þ": 22206, + "ĠClostridium": 22207, + "achine": 22208, + "ĠBDNF": 22209, + "Ġcab": 22210, + "ĠWong": 22211, + "ĠDouglas": 22212, + "Ġreperfusion": 22213, + "ĠXi": 22214, + "Ġconfused": 22215, + "ĠPhiladelphia": 22216, + "Ġapple": 22217, + "Ġile": 22218, + "Ġfavored": 22219, + "Ġplaques": 22220, + "Ġtrivially": 22221, + "ĠTypical": 22222, + "Ġcentralized": 22223, + "ĠFacebook": 22224, + "Ġnortheast": 22225, + "Ġnormality": 22226, + "ĠTb": 22227, + "Ġapt": 22228, + "Ġfacet": 22229, + "ĠRenal": 22230, + "clk": 22231, + "Ġligation": 22232, + "ifferenti": 22233, + "Ġputting": 22234, + "Ġintrig": 22235, + "walled": 22236, + "Et": 22237, + "ĠCow": 22238, + "ĠNations": 22239, + "Ġcampus": 22240, + "ĠKinetics": 22241, + "ĠMexican": 22242, + "ERK": 22243, + "Ġlatitudes": 22244, + "ĠRoll": 22245, + "ĠQD": 22246, + "adaptive": 22247, + "Ġquenched": 22248, + "Ġfram": 22249, + "Qi": 22250, + "Ġtongue": 22251, + "edes": 22252, + "Ġascorb": 22253, + "ĠGlucose": 22254, + "ouri": 22255, + "Ġdefeated": 22256, + "ophilus": 22257, + "ralateral": 22258, + "xrightarrow": 22259, + "ĠJup": 22260, + "axes": 22261, + "eger": 22262, + "MIT": 22263, + "ĠMember": 22264, + "ĠNu": 22265, + "Ġtransloc": 22266, + "ĠFlux": 22267, + "ĠColorado": 22268, + "Ġrelying": 22269, + "atrol": 22270, + "Ġcontrasts": 22271, + "centage": 22272, + "Ġleukocyte": 22273, + "Ġcoincidence": 22274, + "Ġcontractions": 22275, + "oga": 22276, + "ANN": 22277, + "ĠAbsorption": 22278, + "Return": 22279, + "reprene": 22280, + "baum": 22281, + "traumatic": 22282, + "incial": 22283, + "Ġautophag": 22284, + "Ġalgorithmic": 22285, + "rimp": 22286, + "Ġdivides": 22287, + "ĠRose": 22288, + "ĠEric": 22289, + "Ġaddiction": 22290, + "plification": 22291, + "Ġdiffusive": 22292, + "ĠVehicle": 22293, + "enerate": 22294, + "tising": 22295, + "Ġstarvation": 22296, + "absorption": 22297, + "ĠAra": 22298, + "Ġgrav": 22299, + "ĠSubunit": 22300, + "Ġamide": 22301, + "Ġenhancer": 22302, + "Ġmerid": 22303, + "ermost": 22304, + "Ġalgal": 22305, + "ĠQueens": 22306, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 22307, + "Ġjudge": 22308, + "ĠGreenland": 22309, + "brace": 22310, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 22311, + "Ġhypergly": 22312, + "Ġemergent": 22313, + "Fisher": 22314, + "ĠLas": 22315, + "Ġsexes": 22316, + "Sep": 22317, + "Ġphrases": 22318, + "ĠSequential": 22319, + "inki": 22320, + "Ġaxioms": 22321, + "study": 22322, + "Ġtiny": 22323, + "Ġcd": 22324, + "catalyzed": 22325, + "asaki": 22326, + "ĠWR": 22327, + "ĠMinimal": 22328, + "Ġsubcellular": 22329, + "Ġphospho": 22330, + "ESI": 22331, + "Ġvow": 22332, + "Ġsupplies": 22333, + "operand": 22334, + "Fix": 22335, + "anian": 22336, + "writer": 22337, + "âζ": 22338, + "Ġwinner": 22339, + "ĠPID": 22340, + "ĠLebesgue": 22341, + "Ġsimplification": 22342, + "ĠRelationships": 22343, + "Ġautomata": 22344, + "ĠContribution": 22345, + "Ġhereditary": 22346, + "errin": 22347, + "ĠBLAST": 22348, + "aea": 22349, + "yleth": 22350, + "ĠTc": 22351, + "adeh": 22352, + "adjuvant": 22353, + "Wave": 22354, + "counter": 22355, + "ĠGupta": 22356, + "ĠGhana": 22357, + "Cho": 22358, + "Ġourselves": 22359, + "Ġevenly": 22360, + "lymph": 22361, + "Ġcerebellum": 22362, + "Ġcopolymers": 22363, + "modular": 22364, + "Ġharder": 22365, + "Ġplease": 22366, + "ĠPSD": 22367, + "Ġlimbs": 22368, + "Ġexploitation": 22369, + "iry": 22370, + "Ġperiodontal": 22371, + "ATCH": 22372, + "Ġmalicious": 22373, + "ĠSlov": 22374, + "HY": 22375, + "Consequently": 22376, + "oren": 22377, + "ĠPare": 22378, + "agine": 22379, + "ĠROI": 22380, + "ĠWhich": 22381, + "ĠNative": 22382, + "amen": 22383, + "reshape": 22384, + "oplankton": 22385, + "Ġartifact": 22386, + "Ġrhin": 22387, + "gpu": 22388, + "Ġundet": 22389, + "Ġsporadic": 22390, + "Ġorally": 22391, + "Ġstepwise": 22392, + "ĠCohort": 22393, + "Ġrhod": 22394, + "cyt": 22395, + "Ġierr": 22396, + "Ġmotors": 22397, + "ĠIgE": 22398, + "calculated": 22399, + "ĠChampionships": 22400, + "pel": 22401, + "ĠFerr": 22402, + "Ġisometric": 22403, + "nutrition": 22404, + "Ġunsaturated": 22405, + "Ġdoll": 22406, + "ĠRMSE": 22407, + "Ġsolitary": 22408, + "approximation": 22409, + "Ġreperto": 22410, + "sight": 22411, + "Ġcranial": 22412, + "ilical": 22413, + "ĠOst": 22414, + "oul": 22415, + "Ġdg": 22416, + "ĠProceed": 22417, + "Ġmilling": 22418, + "sz": 22419, + "Ġmineralization": 22420, + "Ġcigarette": 22421, + "Ġporph": 22422, + "Ġspons": 22423, + "ĠGreece": 22424, + "ipore": 22425, + "accept": 22426, + "ĠPTSD": 22427, + "Å«": 22428, + "Ġcipher": 22429, + "Ġfunctionalized": 22430, + "Poly": 22431, + "Ġabd": 22432, + "flight": 22433, + "ĠSydney": 22434, + "Ġdisaster": 22435, + "ĠHaving": 22436, + "Ġdiesel": 22437, + "ĠGreg": 22438, + "Ġspans": 22439, + "ĠSeasonal": 22440, + "STEM": 22441, + "ierr": 22442, + "ĠIB": 22443, + "Ġlemm": 22444, + "anum": 22445, + "ĠBottom": 22446, + "Ġseal": 22447, + "boost": 22448, + "Ġlegend": 22449, + "bing": 22450, + "abis": 22451, + "Ġchitin": 22452, + "Ġmaximally": 22453, + "Ġimmunosuppressive": 22454, + "âĪĴâĪĴ": 22455, + "Ġabolished": 22456, + "ige": 22457, + "Ġesophag": 22458, + "Ġlasted": 22459, + "Ġcarbohydrates": 22460, + "Ġchips": 22461, + "ĠFernand": 22462, + "far": 22463, + "ĠPoints": 22464, + "calation": 22465, + "ĠRegions": 22466, + "CHK": 22467, + "veratrol": 22468, + "truth": 22469, + "Ġstrange": 22470, + "Interest": 22471, + "sho": 22472, + "ĠInduc": 22473, + "Ġmigraine": 22474, + "ĠVac": 22475, + "ophores": 22476, + "Ġerrone": 22477, + "scriptsize": 22478, + "ĠNeutron": 22479, + "Ġindistinguishable": 22480, + "istine": 22481, + "Ġhelper": 22482, + "specified": 22483, + "Ġjuice": 22484, + "oxal": 22485, + "ĠJung": 22486, + "Ġmagazine": 22487, + "Ġtelephone": 22488, + "ĠPhyt": 22489, + "Ġum": 22490, + "ĠAvailability": 22491, + "ĠTropical": 22492, + "ĠCases": 22493, + "Ġdescend": 22494, + "Har": 22495, + "âĪĹ": 22496, + "ĠâĨĵ": 22497, + "Ks": 22498, + "Ġê": 22499, + "oluble": 22500, + "Ġchampionship": 22501, + "ĠMovement": 22502, + "ĠXY": 22503, + "kappaB": 22504, + "years": 22505, + "memb": 22506, + "quine": 22507, + "Ġletting": 22508, + "Ġbiggest": 22509, + "Ġcards": 22510, + "Ġbiotin": 22511, + "ĠAur": 22512, + "modal": 22513, + "Ġvaccinated": 22514, + "Ġtranslates": 22515, + "ĠPAC": 22516, + "lli": 22517, + "reonine": 22518, + "Ġcurcumin": 22519, + "ĠConstruct": 22520, + "Ġconvinc": 22521, + "ĠNat": 22522, + "Ġamygdala": 22523, + "Ġprotr": 22524, + "ĠSingular": 22525, + "ĠContact": 22526, + "kind": 22527, + "ĠDaw": 22528, + "ogroup": 22529, + "ĠKCl": 22530, + "Ġhygi": 22531, + "erenced": 22532, + "Ġsurveyed": 22533, + "ĠMull": 22534, + "esthetic": 22535, + "Ġmsg": 22536, + "ĠRequire": 22537, + "Ġdistortions": 22538, + "Control": 22539, + "BERT": 22540, + "Ġautonomic": 22541, + "Ġhormonal": 22542, + "Ġstrips": 22543, + "Ġtrophic": 22544, + "ifting": 22545, + "opod": 22546, + "ĠSpontaneous": 22547, + "Ġlogs": 22548, + "OPT": 22549, + "ĠMot": 22550, + "ĠGmb": 22551, + "aharan": 22552, + "ĠPOL": 22553, + "Ġvisceral": 22554, + "blocks": 22555, + "Ġsitting": 22556, + "Ġsine": 22557, + "Ġoncogenic": 22558, + "ERRQ": 22559, + "quinone": 22560, + "Ġsmartphone": 22561, + "ĠTanz": 22562, + "lactam": 22563, + "ĠSignificance": 22564, + "Ġeu": 22565, + "ĠISS": 22566, + "ĠTrig": 22567, + "ĠMaj": 22568, + "tingale": 22569, + "Ġdilat": 22570, + "ennes": 22571, + "ĠBelgium": 22572, + "lev": 22573, + "ĠContr": 22574, + "ĠGalois": 22575, + "ĠCombination": 22576, + "ĠThi": 22577, + "ĠAustria": 22578, + "Prom": 22579, + "Ġelicit": 22580, + "biosis": 22581, + "Ġlymphatic": 22582, + "ĠMurray": 22583, + "ĠXPS": 22584, + "Ġcong": 22585, + "screen": 22586, + "tide": 22587, + "amoyl": 22588, + "ĠMcD": 22589, + "Ġretired": 22590, + "mixed": 22591, + "ELD": 22592, + "ĠMaps": 22593, + "ĠVE": 22594, + "cession": 22595, + "numer": 22596, + "idated": 22597, + "ĠBishop": 22598, + "Ġneonates": 22599, + "Ġlandsl": 22600, + "ĠFractional": 22601, + "Ġspecifying": 22602, + "ĠJr": 22603, + "Ġnanowire": 22604, + "Ġconsultation": 22605, + "language": 22606, + "Ġpricing": 22607, + "ĠLimitations": 22608, + "ĠPediatric": 22609, + "ĠDimension": 22610, + "Ġpreparing": 22611, + "Lag": 22612, + "segment": 22613, + "Ġspend": 22614, + "athe": 22615, + "Ġweap": 22616, + "ĠJos": 22617, + "textit": 22618, + "outputs": 22619, + "ordering": 22620, + "Ġplacenta": 22621, + "ationally": 22622, + "ĠKun": 22623, + "Ġoutstanding": 22624, + "Ġthicknesses": 22625, + "ĠChIP": 22626, + "deoxy": 22627, + "ĠZo": 22628, + "ĠDeveloping": 22629, + "Ġstringent": 22630, + "iency": 22631, + "perse": 22632, + "Ġpend": 22633, + "ĠDevelopmental": 22634, + "Ġextern": 22635, + "Ġinverter": 22636, + "ĠDAPI": 22637, + "lectivity": 22638, + "Ġtablets": 22639, + "Ġprogester": 22640, + "ĠïģŃ": 22641, + "Ġanswered": 22642, + "entary": 22643, + "ORS": 22644, + "Ġdir": 22645, + "Ġdeleterious": 22646, + "Ġdopaminergic": 22647, + "Random": 22648, + "diss": 22649, + "Ġmonolayers": 22650, + "Ġintegrand": 22651, + "ĠComponents": 22652, + "ĠPerc": 22653, + "agit": 22654, + "ARN": 22655, + "esophageal": 22656, + "ivan": 22657, + "neider": 22658, + "ĠStarting": 22659, + "PORT": 22660, + "yellow": 22661, + "Ġregisters": 22662, + "pairs": 22663, + "Ġethnicity": 22664, + "Ġboy": 22665, + "auti": 22666, + "Ġchromium": 22667, + "POS": 22668, + "vature": 22669, + "ayashi": 22670, + "Ġinappropriate": 22671, + "ĠSNA": 22672, + "Domain": 22673, + "ĠPrice": 22674, + "Ġmacular": 22675, + "Ġoverload": 22676, + "ĠUnified": 22677, + "Ġattach": 22678, + "ĠScottish": 22679, + "maps": 22680, + "agl": 22681, + "emi": 22682, + "Ġseam": 22683, + "ĠAnalog": 22684, + "dated": 22685, + "uo": 22686, + "Ġplated": 22687, + "Ġasset": 22688, + "Ġscreens": 22689, + "Ġspurious": 22690, + "Besides": 22691, + "Ġbaselines": 22692, + "heads": 22693, + "Ġcoat": 22694, + "ĠRemoval": 22695, + "Ġinfinitesimal": 22696, + "ĠTransformation": 22697, + "Ġcommens": 22698, + "Float": 22699, + "AUC": 22700, + "ĠLay": 22701, + "Ġintron": 22702, + "ĠDetecting": 22703, + "ĠHerein": 22704, + "ĠAssociations": 22705, + "Ġprogesterone": 22706, + "Bacteria": 22707, + "Ġsentiment": 22708, + "ĠPhenomen": 22709, + "matter": 22710, + "Ġcylinders": 22711, + "Ġtoluene": 22712, + "Ġspatiotemporal": 22713, + "Ġlanding": 22714, + "ĠCoronavirus": 22715, + "ĠBerry": 22716, + "ĠBragg": 22717, + "Ġregistry": 22718, + "Ġenthalpy": 22719, + "tica": 22720, + "razine": 22721, + "Ġcargo": 22722, + "otation": 22723, + "Ġcontradicts": 22724, + "Ġpesticides": 22725, + "ĠFischer": 22726, + "Ġmechanically": 22727, + "ĠInterfer": 22728, + "ĠCyp": 22729, + "ĠKas": 22730, + "Ġmetres": 22731, + "Ġantiretroviral": 22732, + "Ġtravers": 22733, + "selection": 22734, + "ĠWA": 22735, + "Ġdoublet": 22736, + "meta": 22737, + "ENTR": 22738, + "sonic": 22739, + "Ġmarking": 22740, + "ĠOverex": 22741, + "Ġpyruvate": 22742, + "Ġextrusion": 22743, + "Ġingestion": 22744, + "Ġcocaine": 22745, + "ĠFellow": 22746, + "CNTs": 22747, + "BG": 22748, + "ĠMorphological": 22749, + "Ġdefence": 22750, + "ĠYosh": 22751, + "mitter": 22752, + "rystallization": 22753, + "STRACT": 22754, + "Ġinflammasome": 22755, + "ĠGd": 22756, + "Ġshaft": 22757, + "Ġeruption": 22758, + "ĠOxide": 22759, + "ifolds": 22760, + "ĠGam": 22761, + "ĠGap": 22762, + "command": 22763, + "ĠIgA": 22764, + "Ġshortening": 22765, + "assembled": 22766, + "isopropyl": 22767, + "Ġalumina": 22768, + "ĠATM": 22769, + "Ġct": 22770, + "Ġspinning": 22771, + "ĠPetsc": 22772, + "prefix": 22773, + "Ġperpetuity": 22774, + "PRE": 22775, + "Ġfruct": 22776, + "GHz": 22777, + "elike": 22778, + "enyl": 22779, + "Ġwherein": 22780, + "UK": 22781, + "visual": 22782, + "lipidemia": 22783, + "reduction": 22784, + "anin": 22785, + "olas": 22786, + "Ġamplic": 22787, + "ĠSAT": 22788, + "Ġmodulator": 22789, + "forth": 22790, + "rl": 22791, + "Ġcrew": 22792, + "ĠiP": 22793, + "Ġxi": 22794, + "ADD": 22795, + "ĠAlexand": 22796, + "constrained": 22797, + "ratory": 22798, + "ĠkW": 22799, + "ĠMDR": 22800, + "ĠlncRNA": 22801, + "Mill": 22802, + "ĠMgO": 22803, + "circuit": 22804, + "Ġpersonalized": 22805, + "ĠOperator": 22806, + "stock": 22807, + "ĠPSA": 22808, + "ensable": 22809, + "Ġlean": 22810, + "yield": 22811, + "Ġopacity": 22812, + "ĠCommons": 22813, + "Ġsummed": 22814, + "ucker": 22815, + "ecke": 22816, + "epithelial": 22817, + "Ġasking": 22818, + "uese": 22819, + "ĠFlav": 22820, + "Ġlactic": 22821, + "Ġlubric": 22822, + "Ġisn": 22823, + "regions": 22824, + "support": 22825, + "Below": 22826, + "ĠNom": 22827, + "Ġhyal": 22828, + "ikh": 22829, + "ban": 22830, + "ĠBG": 22831, + "rometer": 22832, + "indic": 22833, + "opharyngeal": 22834, + "ITION": 22835, + "ĠPropagation": 22836, + "ĠPlace": 22837, + "ĠCircuit": 22838, + "ĠCOL": 22839, + "Green": 22840, + "Ir": 22841, + "lav": 22842, + "ĠdS": 22843, + "ĠMoment": 22844, + "Ġinducible": 22845, + "Ġdischarges": 22846, + "habdi": 22847, + "ĠExperience": 22848, + "Ġsg": 22849, + "Ġoutward": 22850, + "Ġportable": 22851, + "ĠOperators": 22852, + "Av": 22853, + "ĠDQ": 22854, + "ostatin": 22855, + "Ġeosinophil": 22856, + "Ġstriatum": 22857, + "ĠConsensus": 22858, + "Ġimperfect": 22859, + "NOT": 22860, + "ĠDemocratic": 22861, + ";;": 22862, + "Body": 22863, + "dii": 22864, + "Ho": 22865, + "ĠRailway": 22866, + "ĠUganda": 22867, + "Ġunpaired": 22868, + "friendly": 22869, + "Ġreprogramming": 22870, + "Alternative": 22871, + "RG": 22872, + "imet": 22873, + "enez": 22874, + "ĠHypothesis": 22875, + "Ġton": 22876, + "ĠCombin": 22877, + "ĠDelivery": 22878, + "Last": 22879, + "Ġowners": 22880, + "razole": 22881, + "ĠKob": 22882, + "Ġformats": 22883, + "Ġpolyclonal": 22884, + "Ġidentifier": 22885, + "ILL": 22886, + "Ġsurgeon": 22887, + "Ġpostp": 22888, + "ĠGenerative": 22889, + "ĠMall": 22890, + "abc": 22891, + "ĠHaz": 22892, + "Ġsmoothly": 22893, + "Ġcrystallographic": 22894, + "ĠFDA": 22895, + "Ġcoexistence": 22896, + "ionized": 22897, + "Ġcompiler": 22898, + "ĠArter": 22899, + "Ġappearances": 22900, + "amiltonian": 22901, + "Ġencapsulated": 22902, + "atia": 22903, + "wi": 22904, + "reb": 22905, + "Ġwafer": 22906, + "ubs": 22907, + "ĠUE": 22908, + "ĠGSK": 22909, + "Ġviv": 22910, + "Ġflooding": 22911, + "ĠGyr": 22912, + "Ġstably": 22913, + "Ġdislocations": 22914, + "Ġescap": 22915, + "ĠPhysiological": 22916, + "tidal": 22917, + "yme": 22918, + "ĠMaxim": 22919, + "iterator": 22920, + "ordant": 22921, + "Ġattentional": 22922, + "Ġcatalyzed": 22923, + "ĠTryp": 22924, + "PIN": 22925, + "ĠCorrelations": 22926, + "Ġhydrological": 22927, + "Ġnose": 22928, + "export": 22929, + "Ġdext": 22930, + "ĠBenef": 22931, + "ĠBiosystems": 22932, + "ĠPars": 22933, + "Ġreadings": 22934, + "Ġinstrumentation": 22935, + "ĠIQ": 22936, + "RIC": 22937, + "Ġgrafts": 22938, + "overs": 22939, + "ĠMedic": 22940, + "Ġmonod": 22941, + "Ġuniformity": 22942, + "ĠATLAS": 22943, + "Ġmasked": 22944, + "Ri": 22945, + "ĠPhysic": 22946, + "Ġimposing": 22947, + "ĠParad": 22948, + "imetic": 22949, + "Ġdemanding": 22950, + "unks": 22951, + "Ġfolds": 22952, + "ĠAnc": 22953, + "Ġvolatility": 22954, + "Ġbringing": 22955, + "acil": 22956, + "ĠNMDA": 22957, + "reduced": 22958, + "tii": 22959, + "Ġnorthwest": 22960, + "ĠBessel": 22961, + "ventions": 22962, + "Ġconsolidation": 22963, + "Meier": 22964, + "Ġmicrof": 22965, + "Ġqualified": 22966, + "Ġinsignificant": 22967, + "ĠMorphology": 22968, + "Ġpointwise": 22969, + "Ġlearns": 22970, + "Ġguard": 22971, + "CHECK": 22972, + "phonon": 22973, + "ĠEnhancement": 22974, + "Ġzonal": 22975, + "ERG": 22976, + "Start": 22977, + "Ġhistoric": 22978, + "ĠPure": 22979, + "ĠGmbH": 22980, + "glu": 22981, + "Ġpatterning": 22982, + "Ġstick": 22983, + "uminosity": 22984, + "Dataset": 22985, + "Ġoverride": 22986, + "ĠSteel": 22987, + "Ġfuels": 22988, + "mechanical": 22989, + "Ġautologous": 22990, + "Ġdepartments": 22991, + "ĠBlo": 22992, + "Ġimported": 22993, + "Ġrestrictive": 22994, + "eigen": 22995, + "ĠRome": 22996, + "ĠÌĬ": 22997, + "Ġepitopes": 22998, + "Ġlabelling": 22999, + "Ġownership": 23000, + "ĠEspecially": 23001, + "Ġcoffee": 23002, + "ĠGRB": 23003, + "Head": 23004, + "ĠVent": 23005, + "esare": 23006, + "ĠParticles": 23007, + "UNCTION": 23008, + "jj": 23009, + "uents": 23010, + "elic": 23011, + "ĠTat": 23012, + "ĠFle": 23013, + "Ġgating": 23014, + "Ġrefuge": 23015, + "Additionally": 23016, + "Ġrhs": 23017, + "Ġmaybe": 23018, + "ĠFang": 23019, + "Ġadvent": 23020, + "otransferase": 23021, + "should": 23022, + "Ġproteomic": 23023, + "Ġlegitim": 23024, + "PERIM": 23025, + "ĠGiant": 23026, + "Ġgraphics": 23027, + "onomical": 23028, + "scatter": 23029, + "Ġsuggestive": 23030, + "plots": 23031, + "Ġmultidrug": 23032, + "Ġabsorber": 23033, + "XS": 23034, + "consuming": 23035, + "Ġsustainability": 23036, + "opre": 23037, + "fix": 23038, + "Ġvolcano": 23039, + "ĠTypes": 23040, + "ĠCreate": 23041, + "Ġchooses": 23042, + "Ġstirring": 23043, + "Ġsurgeons": 23044, + "dS": 23045, + "Ġcharacterizes": 23046, + "Ġadjustments": 23047, + "texttt": 23048, + "etra": 23049, + "Ġclassifications": 23050, + "spots": 23051, + "Ġâϝ": 23052, + "erex": 23053, + "dehyd": 23054, + "ĠBrig": 23055, + "ĠSuperconduc": 23056, + "Ġgrants": 23057, + "ĠCen": 23058, + "ĠYin": 23059, + "ĠReactions": 23060, + "description": 23061, + "transcription": 23062, + "important": 23063, + "Ġhemodynamic": 23064, + "ĠYi": 23065, + "ĠGolden": 23066, + "kk": 23067, + "alb": 23068, + "Ġrooms": 23069, + "Ġsegreg": 23070, + "Ġsumming": 23071, + "Ġsuccession": 23072, + "Ġfollicular": 23073, + "Ġtackle": 23074, + "Down": 23075, + "Ġevaluates": 23076, + "atica": 23077, + "annual": 23078, + "ĠAlbert": 23079, + "Ġtal": 23080, + "orbital": 23081, + "fted": 23082, + "variables": 23083, + "Ġwetland": 23084, + "outheastern": 23085, + "MEM": 23086, + "ĠBrill": 23087, + "ĠSodium": 23088, + "ĠAlexa": 23089, + "umed": 23090, + "BUG": 23091, + "arine": 23092, + "Ġrevenue": 23093, + "habditis": 23094, + "Ġdissol": 23095, + "amplitude": 23096, + "Ġartists": 23097, + "Ġnormalised": 23098, + "Ġfluctuating": 23099, + "Ġaspar": 23100, + "ĠFi": 23101, + "olates": 23102, + "ispanic": 23103, + "Ġacetylation": 23104, + "ĠConcentration": 23105, + "Ġthro": 23106, + "shots": 23107, + "Ġnarrative": 23108, + "ĠWaals": 23109, + "ammonium": 23110, + "ureau": 23111, + "------------": 23112, + "Ġresearches": 23113, + "Ġbaby": 23114, + "Ġsharply": 23115, + "ÙĦ": 23116, + "ĠCel": 23117, + "CX": 23118, + "uminal": 23119, + "Ġgermline": 23120, + "ĠTransformer": 23121, + "pseud": 23122, + "HG": 23123, + "Ka": 23124, + "ĠSMC": 23125, + "ĠNutrition": 23126, + "Ġbarc": 23127, + "ĠWrite": 23128, + "Ġproteases": 23129, + "Ġsweep": 23130, + "ĠKolmogorov": 23131, + "morph": 23132, + "inducible": 23133, + "Ġexciting": 23134, + "lein": 23135, + "ĠHass": 23136, + "Ġproductive": 23137, + "mesh": 23138, + "ĠCMS": 23139, + "Ġheavier": 23140, + "Ġmeetings": 23141, + "ĠCopper": 23142, + "Ġvirtue": 23143, + "asant": 23144, + "ĠDEN": 23145, + "Ġinherently": 23146, + "rio": 23147, + "Ġhoused": 23148, + "Ġintraoperative": 23149, + "Ġcrown": 23150, + "conditions": 23151, + "ANG": 23152, + "YSIS": 23153, + "iman": 23154, + "Ġnmol": 23155, + "ĠRetrieval": 23156, + "algae": 23157, + "Ġkappa": 23158, + "deep": 23159, + "inence": 23160, + "ĠCarcinoma": 23161, + "Ġchromatographic": 23162, + "Ġascribed": 23163, + "Ġleverage": 23164, + "ĠKK": 23165, + "omyel": 23166, + "pet": 23167, + "ĠNJ": 23168, + "comm": 23169, + "Ġannually": 23170, + "gran": 23171, + "Ġaval": 23172, + "ĠNish": 23173, + "Ġevac": 23174, + "Ġmultif": 23175, + "Ġfunds": 23176, + "enny": 23177, + "ĠMong": 23178, + "ĠException": 23179, + "paths": 23180, + "ymen": 23181, + "hpp": 23182, + "Ġrestricting": 23183, + "saturated": 23184, + "âĻ": 23185, + "Ġlearners": 23186, + "ĠLanka": 23187, + "inities": 23188, + "ĠGDP": 23189, + "Ġspeciation": 23190, + "Ġensured": 23191, + "Ġneutralizing": 23192, + "Ġballoon": 23193, + "Comparison": 23194, + "ĠCalibration": 23195, + "ĠInfluenza": 23196, + "Ġvapour": 23197, + "XA": 23198, + "tracking": 23199, + "ĠICD": 23200, + "fluoro": 23201, + "ĠDamage": 23202, + "Ġpra": 23203, + "Ġconceived": 23204, + "ĠCosmological": 23205, + "Ġloose": 23206, + "inositol": 23207, + "ĠClifford": 23208, + "owa": 23209, + "Ġoffsets": 23210, + "document": 23211, + "Ġenormous": 23212, + "Ġphotoelectron": 23213, + "record": 23214, + "esticular": 23215, + "Ġvocals": 23216, + "Ġconsciousness": 23217, + "Ġtrem": 23218, + "Ġlandscapes": 23219, + "ĠFundamental": 23220, + "tebrate": 23221, + "Ġvertebral": 23222, + "Ġregenerative": 23223, + "Ġtroposp": 23224, + "Integr": 23225, + "Ġassociates": 23226, + "oved": 23227, + "ussed": 23228, + "aws": 23229, + "ĠSide": 23230, + "Ġinterconnected": 23231, + "Ġsuperfamily": 23232, + "ĠCook": 23233, + "loader": 23234, + "Ġpython": 23235, + "ĠCounter": 23236, + "books": 23237, + "Ġïģ²": 23238, + "breaking": 23239, + "gy": 23240, + "Ġcarbox": 23241, + "Ġedited": 23242, + "otyped": 23243, + "Ġduoden": 23244, + "anne": 23245, + "Ġanastom": 23246, + "ginate": 23247, + "ĠBiosciences": 23248, + "rage": 23249, + "ĠChiral": 23250, + "Ġsimplifies": 23251, + "Ġtestis": 23252, + "ström": 23253, + "ials": 23254, + "Ġmicelles": 23255, + "correct": 23256, + "ĠGenetics": 23257, + "along": 23258, + "Rem": 23259, + "resistance": 23260, + "Ġdrink": 23261, + "orbed": 23262, + "ĠTreat": 23263, + "ĠSho": 23264, + "shows": 23265, + "ér": 23266, + "Ġmimics": 23267, + "occup": 23268, + "eclam": 23269, + "ONG": 23270, + "Ġmarketing": 23271, + "ĠFinding": 23272, + "Ġendometri": 23273, + "âĶĢ": 23274, + "strained": 23275, + "ĠMuch": 23276, + "Ġexons": 23277, + "ĠHil": 23278, + "TD": 23279, + "ĠWW": 23280, + "ĠVic": 23281, + "enda": 23282, + "Ġfactory": 23283, + "ĠHepG": 23284, + "ĠStatic": 23285, + "blastoma": 23286, + "wd": 23287, + "raisal": 23288, + "ĠBasis": 23289, + "Ins": 23290, + "ĠUnsupervised": 23291, + "elo": 23292, + "oselective": 23293, + "Ġaccomplish": 23294, + "ĠProspective": 23295, + "Ġuncorrelated": 23296, + "ĠGate": 23297, + "icycl": 23298, + "Ġurgent": 23299, + "ĠPathways": 23300, + "Ġoblique": 23301, + "ĠIndividuals": 23302, + "Ġinitiative": 23303, + "Ġcatast": 23304, + "jections": 23305, + "Ġautosomal": 23306, + "ĠPhilip": 23307, + "Ġcomprehension": 23308, + "mM": 23309, + "pain": 23310, + "ĠmicroM": 23311, + "Ġencounters": 23312, + "goto": 23313, + "Ġladder": 23314, + "Ġoccupy": 23315, + "ĠSurfaces": 23316, + "Doc": 23317, + "ugby": 23318, + "Ġexamines": 23319, + "osynthesis": 23320, + "ĠKEGG": 23321, + "glass": 23322, + "slice": 23323, + "propagation": 23324, + "stry": 23325, + "Ġillustrating": 23326, + "imi": 23327, + "Ġspores": 23328, + "Ġastrophysical": 23329, + "Ġenclosed": 23330, + "Ġinferences": 23331, + "Ġbijection": 23332, + "Ġeveryday": 23333, + "Ġalternatively": 23334, + "reaction": 23335, + "iants": 23336, + "contact": 23337, + "Ġging": 23338, + "ĠBias": 23339, + "Ġautomaton": 23340, + "background": 23341, + "Ġneighbouring": 23342, + "Ġdetects": 23343, + "porate": 23344, + "ĠSharma": 23345, + "Hydro": 23346, + "Ġsacc": 23347, + "ĠFiber": 23348, + "ĠChlam": 23349, + "Ġbuffers": 23350, + "Applying": 23351, + "lceil": 23352, + "emph": 23353, + "ĠGSE": 23354, + "metry": 23355, + "Ġimmunost": 23356, + "ĠHistorical": 23357, + "ĠDrag": 23358, + "Ġtransplanted": 23359, + "Ġfrail": 23360, + "Ġanthocyan": 23361, + "inte": 23362, + "ĠBhat": 23363, + "ĠOg": 23364, + "Ġsteering": 23365, + "benzene": 23366, + "****************************************************************": 23367, + "Ġsynthet": 23368, + "Act": 23369, + "Ġcin": 23370, + "Ġherbal": 23371, + "Ġdyn": 23372, + "Ġhyperplasia": 23373, + "header": 23374, + "Ġcalculates": 23375, + "ĠDifference": 23376, + "Ġbats": 23377, + "ductivity": 23378, + "Ġconformations": 23379, + "city": 23380, + "Ġseparates": 23381, + "ĠCDC": 23382, + "ĠPrism": 23383, + "ĠBehaviour": 23384, + "ĠKelly": 23385, + "ĠSey": 23386, + "ĠÃł": 23387, + "LEX": 23388, + "gkin": 23389, + "strom": 23390, + "Ġvom": 23391, + "ĠWin": 23392, + "ĠWigner": 23393, + "Ġcontralateral": 23394, + "ĠMinor": 23395, + "Ġstereo": 23396, + "ĠApproximately": 23397, + "LED": 23398, + "say": 23399, + "ĠJS": 23400, + "Ġalcohols": 23401, + "Ġsan": 23402, + "Ġhardening": 23403, + "IFN": 23404, + "Ġretrospectively": 23405, + "Ġgeneralised": 23406, + "Ġtibial": 23407, + "ĠWeek": 23408, + "Ġaryl": 23409, + "ĠPeninsula": 23410, + "Ġdeterminations": 23411, + "Ġphotovoltaic": 23412, + "Ġsuggestion": 23413, + "Jac": 23414, + "ĠVitro": 23415, + "Ġcyclo": 23416, + "Ġfibrous": 23417, + "disambiguation": 23418, + "program": 23419, + "Ġguest": 23420, + "ĠDust": 23421, + "rceil": 23422, + "Ġpowered": 23423, + "Ġcardiomyocytes": 23424, + "heat": 23425, + "ylic": 23426, + "Ġpresentations": 23427, + "Ġtransmitting": 23428, + "WD": 23429, + "added": 23430, + "Initial": 23431, + "Del": 23432, + "ĠVelocity": 23433, + "Ġmole": 23434, + "Ġoval": 23435, + "Ġplankton": 23436, + "their": 23437, + "ĠQED": 23438, + "volutions": 23439, + "Ġmandatory": 23440, + "Ġrepulsive": 23441, + "ĉĠĠ": 23442, + "Ġpostulated": 23443, + "ĠCortex": 23444, + "ĠCarb": 23445, + "CHKERRQ": 23446, + "Ġoverlay": 23447, + "ĠFarm": 23448, + "enorhabditis": 23449, + "Ġposed": 23450, + "Ġinstanti": 23451, + "ZT": 23452, + "ĠVisualization": 23453, + "ĠGAPDH": 23454, + "lecom": 23455, + "ochron": 23456, + "ĠBj": 23457, + "ĠTrib": 23458, + "Ġbyte": 23459, + "Ġsuperimposed": 23460, + "Ġundi": 23461, + "Ġaccelerator": 23462, + "criptions": 23463, + "ĠSmooth": 23464, + "Ġzip": 23465, + "nesota": 23466, + "ĠEFF": 23467, + "ĠCole": 23468, + "ĠBru": 23469, + "rend": 23470, + "utz": 23471, + "Ġdiagnose": 23472, + "basis": 23473, + "diamond": 23474, + "ĠInn": 23475, + "ĠMedian": 23476, + "Ġmarginally": 23477, + "Ġlemmas": 23478, + "rectomy": 23479, + "Ġdialogue": 23480, + "ĠBrid": 23481, + "Ġå": 23482, + "oxane": 23483, + "aris": 23484, + "Ġmunicipality": 23485, + "Ġproducers": 23486, + "Regarding": 23487, + "ĠFV": 23488, + "ideal": 23489, + "exponential": 23490, + "Label": 23491, + "ĠFrobenius": 23492, + "Ġell": 23493, + "ĠLTE": 23494, + "Ġlipase": 23495, + "rp": 23496, + "Ġdm": 23497, + "otri": 23498, + "cloud": 23499, + "ĠAgent": 23500, + "MSCs": 23501, + "osom": 23502, + "hydropy": 23503, + "neurons": 23504, + "Ġsolvable": 23505, + "ducting": 23506, + "Ġrendered": 23507, + "Ġattractor": 23508, + "Ġbrac": 23509, + "Ãģ": 23510, + "Ġhosted": 23511, + "ĠOct": 23512, + "Ġguiding": 23513, + "Ġdigestive": 23514, + "js": 23515, + "Ġintent": 23516, + "flux": 23517, + "Ġbiosynthetic": 23518, + "Ġelections": 23519, + "ĠWilcoxon": 23520, + "Ġspectrophotometer": 23521, + "Ġimpairs": 23522, + "Ġabdomen": 23523, + "kb": 23524, + "ĠWho": 23525, + "ASSERT": 23526, + "Ġeluted": 23527, + "Ġmaximization": 23528, + "Ġcollector": 23529, + "ĠPreviously": 23530, + "aq": 23531, + "ambo": 23532, + "ĠOz": 23533, + "Cur": 23534, + "Ġcaffeine": 23535, + "Mass": 23536, + "pal": 23537, + "piece": 23538, + "ouville": 23539, + "ĠMeyer": 23540, + "uta": 23541, + "chan": 23542, + "ĠKS": 23543, + "omotor": 23544, + "ĠGPR": 23545, + "Ġeval": 23546, + "ĠCooperative": 23547, + "oglycan": 23548, + "Ġnozzle": 23549, + "ĠShel": 23550, + "Ġinterchange": 23551, + "Ġundergrad": 23552, + "Ġexplanatory": 23553, + "Ġphagocytosis": 23554, + "Ġctx": 23555, + "hess": 23556, + "Ġuniversality": 23557, + "ĠKilling": 23558, + "onsin": 23559, + "Ġlasting": 23560, + "ĠImm": 23561, + "Ġconcordance": 23562, + "yma": 23563, + "Ġautumn": 23564, + "Ġbarley": 23565, + "Ġconsequent": 23566, + "isi": 23567, + "Ġconjugates": 23568, + "Ġtaught": 23569, + "Ġcovariate": 23570, + "Ġadolescence": 23571, + "Ġvillages": 23572, + "Ġeigenfunctions": 23573, + "Ġtemporally": 23574, + "ĠMinnesota": 23575, + "yrate": 23576, + "iesis": 23577, + "definite": 23578, + "Ġalphabet": 23579, + "ĠYun": 23580, + "ĠMAR": 23581, + "Ġsealed": 23582, + "ronectin": 23583, + "ĠSepar": 23584, + "nx": 23585, + "CAA": 23586, + "Ġreception": 23587, + "ucky": 23588, + "ĠPTEN": 23589, + "ĠMorgan": 23590, + "Ġdiodes": 23591, + "Ġmetformin": 23592, + "Ġsynthes": 23593, + "ĠParticip": 23594, + "ĠJersey": 23595, + "Ġamphib": 23596, + "chel": 23597, + "Ġlamp": 23598, + "ĠHels": 23599, + "ĠFN": 23600, + "Ġexcav": 23601, + "isecond": 23602, + "intro": 23603, + "Ġnoncommutative": 23604, + "Ġsubsystems": 23605, + "summ": 23606, + "Ġcontrasting": 23607, + "ĠSilicon": 23608, + "ĠPartition": 23609, + "GlcNAc": 23610, + "Ġdiscern": 23611, + "ĠBounds": 23612, + "ĠRah": 23613, + "Ġapproximating": 23614, + "ĠHypert": 23615, + "ĠDil": 23616, + "Ġcompactness": 23617, + "Ġcaught": 23618, + "ĠImprove": 23619, + "ĠToronto": 23620, + "ĠBiomark": 23621, + "ĠBag": 23622, + "ĠInvent": 23623, + "Ġelaborate": 23624, + "ĠMott": 23625, + "ABC": 23626, + "ĠGraham": 23627, + "Ġpoultry": 23628, + "ĠConjecture": 23629, + "ĠAlgebras": 23630, + "ĠNLO": 23631, + "apsing": 23632, + "pathy": 23633, + "ĠElizabeth": 23634, + "ĠTit": 23635, + "ĠSCI": 23636, + "anton": 23637, + "Ġvoting": 23638, + "mathrel": 23639, + "ĠFord": 23640, + "igibility": 23641, + "Ġallergy": 23642, + "acoustic": 23643, + "ĠDyn": 23644, + "ĠDSC": 23645, + "ĠGRO": 23646, + "ĠThirty": 23647, + "Ġanalysing": 23648, + "ĠEmpire": 23649, + "fire": 23650, + "Ġpathologic": 23651, + "Ġpatent": 23652, + "Ġheard": 23653, + "ĠFront": 23654, + "isconsin": 23655, + "hypert": 23656, + "uzumab": 23657, + "ĠMutation": 23658, + "Ġbiliary": 23659, + "Ġsuperfluid": 23660, + "ĠWC": 23661, + "ustom": 23662, + "ĠActivities": 23663, + "Ġpolypeptide": 23664, + "heets": 23665, + "Ġborders": 23666, + "early": 23667, + "Ġorthogon": 23668, + "Ġbulge": 23669, + "ï£": 23670, + "Ġconical": 23671, + "ĠLept": 23672, + "Ġelectrolytes": 23673, + "Ġ«": 23674, + "regulating": 23675, + "Ġviolated": 23676, + "âĺ": 23677, + "ALT": 23678, + "ĠWorks": 23679, + "ĠHepat": 23680, + "urgical": 23681, + "obar": 23682, + "ĠReactive": 23683, + "possibly": 23684, + "ĠAdsorption": 23685, + "ĠRio": 23686, + "anoic": 23687, + "ĠâĨij": 23688, + "Ġintriguing": 23689, + "Ġom": 23690, + "hertz": 23691, + "ĠApproximate": 23692, + "ĠParent": 23693, + "Ġcoin": 23694, + "expand": 23695, + "в": 23696, + "Ġnonparametric": 23697, + "extern": 23698, + "aeus": 23699, + "glycerol": 23700, + "Ġcp": 23701, + "Ġbatches": 23702, + "Ġnanomaterials": 23703, + "Use": 23704, + "ĠVivo": 23705, + "Rh": 23706, + "Ġtiles": 23707, + "Ġdepict": 23708, + "Ġsouthwest": 23709, + "ĠCasimir": 23710, + "layered": 23711, + "ĠLeaf": 23712, + "fem": 23713, + "bered": 23714, + "Ġsubalgebra": 23715, + "Ġdetachment": 23716, + "ĠLeuk": 23717, + "olus": 23718, + "ĠRick": 23719, + "Ġabortion": 23720, + "Ġclarified": 23721, + "Ġganglia": 23722, + "QS": 23723, + "oising": 23724, + "ĠForward": 23725, + "ĠPeripheral": 23726, + "shifted": 23727, + "bula": 23728, + "ramolecular": 23729, + "ĠFEM": 23730, + "ĠProton": 23731, + "AME": 23732, + "Ġschedules": 23733, + "Ġaa": 23734, + "ĠUDP": 23735, + "stere": 23736, + "Ġmorphine": 23737, + "Ġspecialist": 23738, + "ĠAndroid": 23739, + "Identif": 23740, + "Ġunexpl": 23741, + "Ġheterozyg": 23742, + "Ġfid": 23743, + "pyridyl": 23744, + "ĠWy": 23745, + "phosphor": 23746, + "Ġfriendly": 23747, + "Ġmicrol": 23748, + "ĠSplit": 23749, + "agner": 23750, + "cribe": 23751, + "Ġmoth": 23752, + "ĠEuro": 23753, + "igs": 23754, + "ĠConditional": 23755, + "ĠStewart": 23756, + "properties": 23757, + "ASC": 23758, + "ĠTraditional": 23759, + "ĠPortugal": 23760, + "Ġearned": 23761, + "Ġcathe": 23762, + "Create": 23763, + "iciencies": 23764, + "Ġsphing": 23765, + "xml": 23766, + "Ġimmunomod": 23767, + "Ġcommute": 23768, + "Ġselenium": 23769, + "anges": 23770, + "hook": 23771, + "denoted": 23772, + "Ġjustify": 23773, + "ĠPool": 23774, + "Ġguinea": 23775, + "Ġcontra": 23776, + "Ġfolded": 23777, + "Ġlisting": 23778, + "ĠLG": 23779, + "ĠLane": 23780, + "Ġsurely": 23781, + "vet": 23782, + "fluorophenyl": 23783, + "Ġcorona": 23784, + "ĠAbund": 23785, + "ĠObjects": 23786, + "Ġtrough": 23787, + "cht": 23788, + "Ġdish": 23789, + "ithi": 23790, + "ĠMatlab": 23791, + "worm": 23792, + "Ġproteomics": 23793, + "Ġintermolecular": 23794, + "ĠPeters": 23795, + "Ġmirrors": 23796, + "quinoline": 23797, + "artens": 23798, + "ĠJewish": 23799, + "kB": 23800, + "ĠDegradation": 23801, + "Ġreleasing": 23802, + "VEGF": 23803, + "Ġsubpopulations": 23804, + "ĠTraffic": 23805, + "Ġproline": 23806, + "ĠHf": 23807, + "Ġadren": 23808, + "birth": 23809, + "Ġsender": 23810, + "Ġatlas": 23811, + "Ġworkplace": 23812, + "Ġreflectivity": 23813, + "ĠExistence": 23814, + "cls": 23815, + "Ġfiner": 23816, + "Ġbreastfeeding": 23817, + "onectin": 23818, + "Ġcogn": 23819, + "ellate": 23820, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 23821, + "byte": 23822, + "Ġsket": 23823, + "NULL": 23824, + "systems": 23825, + "ĠBranch": 23826, + "ĠProposed": 23827, + "learn": 23828, + "Ġtolerant": 23829, + "Ġvertebrates": 23830, + "Ġmultilevel": 23831, + "ĠPAH": 23832, + "Ġaudience": 23833, + "ĠWL": 23834, + "nitrop": 23835, + "ĠCt": 23836, + "Ġsativa": 23837, + "eight": 23838, + "Ġmeg": 23839, + "ocell": 23840, + "Ġstating": 23841, + "dominant": 23842, + "bytes": 23843, + "Ġpu": 23844, + "ĠBatter": 23845, + "otaxis": 23846, + "ĠEBV": 23847, + "Ġnanocrystals": 23848, + "Ġmonopole": 23849, + "Ġdiaphrag": 23850, + "ĠVel": 23851, + "Appendix": 23852, + "atten": 23853, + "impl": 23854, + "Ġlandmark": 23855, + "enclature": 23856, + "ĠSTAR": 23857, + "Ġprostagland": 23858, + "oprotective": 23859, + "Ġloadings": 23860, + "ĠPresence": 23861, + "ĠNSF": 23862, + "resses": 23863, + "FU": 23864, + "ilers": 23865, + "Ġerythrocytes": 23866, + "trac": 23867, + "islation": 23868, + "ĠNight": 23869, + "Ġsteroids": 23870, + "tiz": 23871, + "ĠDMA": 23872, + "Ġric": 23873, + "Ġsalient": 23874, + "ĠFur": 23875, + "special": 23876, + "Ġbioinformatics": 23877, + "ignant": 23878, + "ĠEXPERIM": 23879, + "avorable": 23880, + "disk": 23881, + "Ġcurriculum": 23882, + "imidazol": 23883, + "higher": 23884, + "Ġdesigner": 23885, + "ĠStrength": 23886, + "Ġcytosol": 23887, + "ĠChannels": 23888, + "Land": 23889, + "spar": 23890, + "Expression": 23891, + "Ġdaytime": 23892, + "mercial": 23893, + "vbox": 23894, + "inar": 23895, + "ieving": 23896, + "cein": 23897, + "ĠNCBI": 23898, + "RAN": 23899, + "¸Ģ": 23900, + "Hig": 23901, + "ĠDHA": 23902, + "Ġsubscript": 23903, + "Ġ¢": 23904, + "orange": 23905, + "Ġknows": 23906, + "ĠNAF": 23907, + "produced": 23908, + "epid": 23909, + "Ġdexamethasone": 23910, + "Ġformaldehyde": 23911, + "yll": 23912, + "Ġectopic": 23913, + "ĠVerification": 23914, + "activating": 23915, + "ĠIG": 23916, + "ĠPav": 23917, + "Ġtrading": 23918, + "Ġgraduate": 23919, + "ĠFIR": 23920, + "encil": 23921, + "every": 23922, + "Ġradiological": 23923, + "ĠMammalian": 23924, + "MES": 23925, + "inium": 23926, + "ĠSAS": 23927, + "ĠWH": 23928, + "Override": 23929, + "ĠScheduling": 23930, + "ĠBes": 23931, + "ĠYao": 23932, + "Ġglad": 23933, + "ĠStandards": 23934, + "Ġprovinces": 23935, + "eners": 23936, + "Ġnr": 23937, + "Ġtranspos": 23938, + "ĠCarib": 23939, + "Ġfauna": 23940, + "umi": 23941, + "reset": 23942, + "Ġsupra": 23943, + "Ġdivisions": 23944, + "Ġbiodegrad": 23945, + "metrics": 23946, + "ografts": 23947, + "Ġfunctors": 23948, + "Ġsupportive": 23949, + "Ġcaudal": 23950, + "Ġexerts": 23951, + "Ġcub": 23952, + "odimer": 23953, + "Ġairborne": 23954, + "Ġdelivering": 23955, + "Ġmultivariable": 23956, + "Ġfurnace": 23957, + "Ġremnant": 23958, + "Ġinco": 23959, + "ĠElectromagnetic": 23960, + "mapping": 23961, + "Ġdeclines": 23962, + "cold": 23963, + "ĠSeed": 23964, + "conversion": 23965, + "Ġglycogen": 23966, + "dT": 23967, + "awi": 23968, + "APP": 23969, + "Hol": 23970, + "atalysts": 23971, + "ĠSatellite": 23972, + "garis": 23973, + "card": 23974, + "ĠBreak": 23975, + "ĠAgainst": 23976, + "ddot": 23977, + "Ġpruning": 23978, + "ĠCaenorhabditis": 23979, + "Ġsucceeded": 23980, + "ubert": 23981, + "ĠÏħ": 23982, + "IDs": 23983, + "Ġasymptotics": 23984, + "Ġautoanti": 23985, + "ĠScalar": 23986, + "Ġnematode": 23987, + "hd": 23988, + "Ġgyn": 23989, + "istocene": 23990, + "Ġunderground": 23991, + "ĠEthical": 23992, + "Ġsial": 23993, + "ĠMigration": 23994, + "cope": 23995, + "Ġstigma": 23996, + "Ġeleven": 23997, + "Ġcoloring": 23998, + "initions": 23999, + "ĠJay": 24000, + "oba": 24001, + "ĠLDA": 24002, + "Ġbuilds": 24003, + "gences": 24004, + "ĠEcology": 24005, + "scheme": 24006, + "ĠUltras": 24007, + "Ġmediation": 24008, + "ĠTaq": 24009, + "Ġflying": 24010, + "ĠEquilibrium": 24011, + "ophosphate": 24012, + "ĠArgentina": 24013, + "psia": 24014, + "ttes": 24015, + "Ġdisparity": 24016, + "Ġadvertis": 24017, + "aggreg": 24018, + "ISA": 24019, + "odem": 24020, + "ĠRational": 24021, + "Ġsilent": 24022, + "divided": 24023, + "Pan": 24024, + "JA": 24025, + "claim": 24026, + "Ġradioactive": 24027, + "Ġpink": 24028, + "Ġconverse": 24029, + "ĠMell": 24030, + "enib": 24031, + "ruskal": 24032, + "slope": 24033, + "henol": 24034, + "ĠPon": 24035, + "partition": 24036, + "SMGR": 24037, + "titled": 24038, + "ĠInterference": 24039, + "tosecond": 24040, + "Ġseq": 24041, + "Ġtransitive": 24042, + "ĠWid": 24043, + "reviewed": 24044, + "×¥": 24045, + "ĠVC": 24046, + "recall": 24047, + "ogeneic": 24048, + "ĠOverexpression": 24049, + "Ġcommitted": 24050, + "Ġsynapse": 24051, + "Short": 24052, + "ĠNeutral": 24053, + "icles": 24054, + "ISM": 24055, + "Ġintrinsically": 24056, + "Ġmicrosatellite": 24057, + "RN": 24058, + "ĠâĪĥ": 24059, + "detection": 24060, + "Ġcodimension": 24061, + "Ġdrawbacks": 24062, + "ĠTurner": 24063, + "Ġsputtering": 24064, + "Ġdismut": 24065, + "Ġhypogly": 24066, + "Ġspeak": 24067, + "JD": 24068, + "Ġsul": 24069, + "Ġperinatal": 24070, + "Ġink": 24071, + "iest": 24072, + "Ġofficers": 24073, + "tick": 24074, + "Ġretaining": 24075, + "ĠNET": 24076, + "Ġexchanges": 24077, + "Ġanyone": 24078, + "ĠEndothelial": 24079, + "send": 24080, + "injection": 24081, + "ĠPeru": 24082, + "Ġclades": 24083, + "uctuations": 24084, + "Ġsulphate": 24085, + "pio": 24086, + "Ġphysi": 24087, + "ĠMiy": 24088, + "ĠBAS": 24089, + "arius": 24090, + "Ġlipopolysaccharide": 24091, + "Ġneurodegeneration": 24092, + "ĠTurkish": 24093, + "Ġophthal": 24094, + "Ġacted": 24095, + "entre": 24096, + "Ġshaking": 24097, + "Ġchloroplast": 24098, + "ĠSid": 24099, + "regnancy": 24100, + "asion": 24101, + "ĠHs": 24102, + "Ġinitiating": 24103, + "Ġflexural": 24104, + "Ϫ": 24105, + "Ġparac": 24106, + "Ġinterlayer": 24107, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 24108, + "cause": 24109, + "ractions": 24110, + "Ġvaluation": 24111, + "SYSMGR": 24112, + "ĠGarcia": 24113, + "arrays": 24114, + "Ġcasting": 24115, + "ĠPFN": 24116, + "ĠLanc": 24117, + "ĠGlob": 24118, + "Ġdenti": 24119, + "Ġportfolio": 24120, + "ĠHolocene": 24121, + "ĠMATERIAL": 24122, + "Ġsarc": 24123, + "Lear": 24124, + "Ġtin": 24125, + "ĠClear": 24126, + "below": 24127, + "Ġadvection": 24128, + "Ġoverlaps": 24129, + "Ġarthroplasty": 24130, + "compute": 24131, + "Ġglycolysis": 24132, + "hept": 24133, + "lora": 24134, + "frames": 24135, + "ĠHern": 24136, + "proto": 24137, + "Ġswine": 24138, + "Ġjejun": 24139, + "Ġrepeating": 24140, + "ancreatic": 24141, + "ĠCollins": 24142, + "ĠPrinciple": 24143, + "Ġnanof": 24144, + "Ġadjacency": 24145, + "Ġsynov": 24146, + "chet": 24147, + "ĠAlmost": 24148, + "Ġintrusion": 24149, + "Ġechocardiography": 24150, + "liferation": 24151, + "Ġquiescent": 24152, + "ĠMuk": 24153, + "Ġlifetimes": 24154, + "graded": 24155, + "Ġoverwhel": 24156, + "zel": 24157, + "Ġnitride": 24158, + "Ġdisturbed": 24159, + "Ġfastest": 24160, + "grability": 24161, + "Ġtolerated": 24162, + "frag": 24163, + "ĠExtension": 24164, + "anoate": 24165, + "iferous": 24166, + "Ġhydrodynamics": 24167, + "IONAL": 24168, + "ĠToday": 24169, + "ĠExpansion": 24170, + "Ġvenom": 24171, + "ĠHepatitis": 24172, + "ño": 24173, + "onation": 24174, + "synuclein": 24175, + "Ġbasketball": 24176, + "clusions": 24177, + "Ġsettled": 24178, + "IQR": 24179, + "ĠCra": 24180, + "Ġautomation": 24181, + "ĠHealthy": 24182, + "ĠPortuguese": 24183, + "ĠAbelian": 24184, + "Ġgad": 24185, + "ĠHG": 24186, + "ĠRoth": 24187, + "Ġconsume": 24188, + "FG": 24189, + "inals": 24190, + "ĠMCMC": 24191, + "Ġpregnancies": 24192, + "DES": 24193, + "portional": 24194, + "ĠBiochemical": 24195, + "Ġmissions": 24196, + "ĠAntibody": 24197, + "ĠBCG": 24198, + "ĠLAS": 24199, + "marine": 24200, + "DMA": 24201, + "Ġlongevity": 24202, + "ĠDry": 24203, + "ĠRao": 24204, + "Ġinterferometer": 24205, + "Ġdiscretized": 24206, + "osensory": 24207, + "sit": 24208, + "etta": 24209, + "tainer": 24210, + "otherwise": 24211, + "AKT": 24212, + "ĠFaculty": 24213, + "Ġascertain": 24214, + "ĠSimulated": 24215, + "Ġpayload": 24216, + "OUT": 24217, + "Ġsuffers": 24218, + "Ġtungsten": 24219, + "ĠAnxiety": 24220, + "ĠHeterogeneous": 24221, + "lingual": 24222, + "Ġpherom": 24223, + "bors": 24224, + "linux": 24225, + "Ġmonkey": 24226, + "£": 24227, + "url": 24228, + "ĠAcross": 24229, + "ĠAKI": 24230, + "Ġopp": 24231, + "ocalization": 24232, + "Ġmorphogenesis": 24233, + "gic": 24234, + "ĠPCM": 24235, + "Ġoligomers": 24236, + "Ġexhaustive": 24237, + "ĠGIS": 24238, + "Ġpristine": 24239, + "ĠActiv": 24240, + "ĠScilab": 24241, + "ĠAcoustic": 24242, + "ĠPick": 24243, + "integral": 24244, + "Ġphilosophy": 24245, + "ĠDeng": 24246, + "ĠHab": 24247, + "scape": 24248, + "ĠEmergency": 24249, + "Ġepi": 24250, + "ĠBET": 24251, + "ricket": 24252, + "Ġannulus": 24253, + "Ġlysosomal": 24254, + "Ġstrands": 24255, + "CAP": 24256, + "ĠAmino": 24257, + "ĠStri": 24258, + "ependence": 24259, + "Ġfootprint": 24260, + "ĠFatty": 24261, + "ĠNaz": 24262, + "nest": 24263, + "ĠExplicit": 24264, + "planetary": 24265, + "lead": 24266, + "Ġgrip": 24267, + "need": 24268, + "ATT": 24269, + "ERV": 24270, + "ĠTargeted": 24271, + "CRP": 24272, + "Ġparamagnetic": 24273, + "ĠTyr": 24274, + "ĠMicroRNA": 24275, + "hline": 24276, + "gh": 24277, + "pit": 24278, + "ĠIsolated": 24279, + "jectory": 24280, + "Ġcleaned": 24281, + "oste": 24282, + "Ġpathologies": 24283, + "propylene": 24284, + "ĠReason": 24285, + "ĠINFO": 24286, + "RAY": 24287, + "Values": 24288, + "Ġalive": 24289, + "Ġbiof": 24290, + "ewicz": 24291, + "Ġcracking": 24292, + "google": 24293, + "locked": 24294, + "crop": 24295, + "eca": 24296, + "urane": 24297, + "SVM": 24298, + "utta": 24299, + "ĠMetric": 24300, + "ĠEncycl": 24301, + "ĠModule": 24302, + "Ġwarranted": 24303, + "Ġmultidisciplinary": 24304, + "ĠElastic": 24305, + "labelled": 24306, + "ĠSchwarzschild": 24307, + "ĠPCC": 24308, + "major": 24309, + "video": 24310, + "Ġstoring": 24311, + "ĠMake": 24312, + "ako": 24313, + "ĠJia": 24314, + "Ġtoroidal": 24315, + "ĠHMM": 24316, + "Ġmasking": 24317, + "Again": 24318, + "Ġnephropathy": 24319, + "gf": 24320, + "Ġdominating": 24321, + "erkin": 24322, + "ĠFabrication": 24323, + "ĠFel": 24324, + "DEF": 24325, + "culture": 24326, + "ĠIra": 24327, + "ĠREG": 24328, + "ilingual": 24329, + "Ġmuss": 24330, + "plain": 24331, + "zh": 24332, + "iston": 24333, + "ĠÎ¥": 24334, + "minimal": 24335, + "cmp": 24336, + "GaN": 24337, + "Ġmonotonic": 24338, + "Ġinvolution": 24339, + "Ġwhatever": 24340, + "ĠInstrument": 24341, + "imple": 24342, + "ĠPCI": 24343, + "ĠNeuronal": 24344, + "Ġfacets": 24345, + "Ġhemodialysis": 24346, + "apatite": 24347, + "ĠKil": 24348, + "ontally": 24349, + "Ġinserting": 24350, + "ĠRIP": 24351, + "Ġconnective": 24352, + "ĠFederation": 24353, + "nut": 24354, + "ĠGun": 24355, + "inuous": 24356, + "Mor": 24357, + "ĠWisconsin": 24358, + "Ġmush": 24359, + "ITS": 24360, + "Ġeject": 24361, + "ĠBPS": 24362, + "ĠHorn": 24363, + "ĠEmbedding": 24364, + "Ġraces": 24365, + "ĠJam": 24366, + "Ġposture": 24367, + "ĠInvol": 24368, + "ĠIMDb": 24369, + "ĠPlease": 24370, + "proportion": 24371, + "ĠInterleukin": 24372, + "Ġarte": 24373, + "Ġsubsp": 24374, + "oderma": 24375, + "Find": 24376, + "imit": 24377, + "ĠClin": 24378, + "Hel": 24379, + "FILE": 24380, + "original": 24381, + "ervoir": 24382, + "Ġpleural": 24383, + "clipse": 24384, + "encer": 24385, + "inaries": 24386, + "Ġvictory": 24387, + "Ġinvestigates": 24388, + "ĠImportance": 24389, + "ĠMIN": 24390, + "Ġphonons": 24391, + "integrated": 24392, + "Ġexchanged": 24393, + "ystis": 24394, + "Ġmigrate": 24395, + "Rob": 24396, + "eland": 24397, + "proof": 24398, + "ĠIntegral": 24399, + "Ġmergers": 24400, + "Ġpolyphenols": 24401, + "ĠFully": 24402, + "Ġuro": 24403, + "Ġhomogenous": 24404, + "Ġrecognizing": 24405, + "ĠSignals": 24406, + "vat": 24407, + "igms": 24408, + "Ġaccuracies": 24409, + "Substituting": 24410, + "Ġpoisoning": 24411, + "Ġshrimp": 24412, + "ĠHölder": 24413, + "ĠTanzania": 24414, + "JS": 24415, + "MENT": 24416, + "ĠTopology": 24417, + "Ġinvers": 24418, + "ĠDU": 24419, + "Ġuniaxial": 24420, + "ĠSEC": 24421, + "party": 24422, + "Ġcontrollable": 24423, + "Ġfum": 24424, + "ostics": 24425, + "Ġmanifested": 24426, + "Ġpropagated": 24427, + "Ġsuffix": 24428, + "ĠCAN": 24429, + "ĠPret": 24430, + "keeping": 24431, + "Assuming": 24432, + "Ġsuture": 24433, + "Ġpest": 24434, + "Ġgamet": 24435, + "ĠAlignment": 24436, + "esarean": 24437, + "tum": 24438, + "Ġrefine": 24439, + "Ġpopulated": 24440, + "Ġestu": 24441, + "ĠDefense": 24442, + "ĠPrivacy": 24443, + "ĠWein": 24444, + "ĠSenate": 24445, + "Ġazimuth": 24446, + "ĠProfessional": 24447, + "Ġlabour": 24448, + "Ġseminal": 24449, + "ĠIntervention": 24450, + "ĠOlder": 24451, + "AU": 24452, + "Wind": 24453, + "dynamical": 24454, + "ĠVeter": 24455, + "ación": 24456, + "Ġcooking": 24457, + "Ġâīª": 24458, + "Ġbead": 24459, + "Ġdensely": 24460, + "Ġpalliative": 24461, + "mort": 24462, + "ĠAAV": 24463, + "ĠRyan": 24464, + "Prim": 24465, + "galax": 24466, + "muir": 24467, + "sters": 24468, + "ĠSalt": 24469, + "queeze": 24470, + "ĠPlateau": 24471, + "Ġí": 24472, + "Ġlighter": 24473, + "ordinary": 24474, + "formaldehyde": 24475, + "ĠWer": 24476, + "Ġbark": 24477, + "Ġhomogenized": 24478, + "Ġpyramidal": 24479, + "Ġinert": 24480, + "ĠAPC": 24481, + "ĠMicros": 24482, + "ĠProteobacteria": 24483, + "ĠPurification": 24484, + "Ġparametrized": 24485, + "Ġille": 24486, + "accuracy": 24487, + "embedding": 24488, + "Ġtoughness": 24489, + "Ġisometry": 24490, + "backs": 24491, + "ĠFIG": 24492, + "ĠRon": 24493, + "ĠESP": 24494, + "Ġmicroglial": 24495, + "interp": 24496, + "ĠIntegrating": 24497, + "ĠReducing": 24498, + "Ġhearts": 24499, + "Ġseriously": 24500, + "Ġspecially": 24501, + "CTRL": 24502, + "ĠSurprisingly": 24503, + "Ġhyperplane": 24504, + "polynomial": 24505, + "Ġreconc": 24506, + "Ġpharmacokinetic": 24507, + "Mart": 24508, + "ĠBright": 24509, + "mable": 24510, + "Ġionizing": 24511, + "Ġtrich": 24512, + "zymatic": 24513, + "Ġleptons": 24514, + "etting": 24515, + "ĠHex": 24516, + "Ġneurop": 24517, + "Ġadipocytes": 24518, + "Ġrods": 24519, + "Ġsupercritical": 24520, + "Ġsuccin": 24521, + "Ġanter": 24522, + "ĠNAC": 24523, + "ĠSubsequent": 24524, + "IGH": 24525, + "Ġsoutheast": 24526, + "Ġendowed": 24527, + "Ġconverging": 24528, + "Ġspatio": 24529, + "Ġcelebr": 24530, + "helix": 24531, + "Ġaccessions": 24532, + "Ġimmobilization": 24533, + "ĠEQ": 24534, + "spatial": 24535, + "Ġinformal": 24536, + "Ġdere": 24537, + "ĠEnzyme": 24538, + "ĠBBC": 24539, + "ĠEPR": 24540, + "Ġelectrically": 24541, + "Ġleukocytes": 24542, + "Ġalanine": 24543, + "Ġmitogen": 24544, + "Ġintramolecular": 24545, + "ĠNI": 24546, + "Ġprokary": 24547, + "ISO": 24548, + "Ġdodec": 24549, + "ĠTrade": 24550, + "ĠDai": 24551, + "ccc": 24552, + "ĠWalter": 24553, + "ĠNeither": 24554, + "Ġvulgaris": 24555, + "Ġlongitude": 24556, + "ĠIntro": 24557, + "option": 24558, + "ĠQC": 24559, + "ĠâĿ": 24560, + "protection": 24561, + "ĠIMF": 24562, + "aprote": 24563, + "Ġlinker": 24564, + "Ġfounder": 24565, + "Ġaspiration": 24566, + "clusters": 24567, + "ĠPay": 24568, + "ĠRoles": 24569, + "Ġacyclic": 24570, + "overing": 24571, + "Ġremind": 24572, + "ĠTong": 24573, + "ĠAtten": 24574, + "Ġengineers": 24575, + "Ġdysregulation": 24576, + "ĠFourth": 24577, + "Ġfilename": 24578, + "ĠCool": 24579, + "protected": 24580, + "Ġnilpotent": 24581, + "ĠHK": 24582, + "clone": 24583, + "ĠStadium": 24584, + "ais": 24585, + "osamine": 24586, + "ABILITY": 24587, + "rovascular": 24588, + "ĠAH": 24589, + "ĠConcept": 24590, + "Ġcerebrospinal": 24591, + "owitz": 24592, + "Ġresolving": 24593, + "Ġwings": 24594, + "ĠEGF": 24595, + "ĠCommand": 24596, + "iazep": 24597, + "Ġbeef": 24598, + "Ġspines": 24599, + "Ġpriorities": 24600, + "Ġattempting": 24601, + "Ġtelomere": 24602, + "BQU": 24603, + "Ġviolations": 24604, + "LB": 24605, + "omnia": 24606, + "osm": 24607, + "irq": 24608, + "Ġdiversification": 24609, + "alt": 24610, + "ĠBRAF": 24611, + "Ġorganisation": 24612, + "die": 24613, + "Ġautoreg": 24614, + "icked": 24615, + "ĠEcological": 24616, + "ĠTrain": 24617, + "ĠPY": 24618, + "Ġmusculoskeletal": 24619, + "Ġhorizons": 24620, + "Ġomega": 24621, + "Ġquasars": 24622, + "eption": 24623, + "Ġerad": 24624, + "Ġluminal": 24625, + "Interestingly": 24626, + "Ġpayment": 24627, + "cnt": 24628, + "Ġdipl": 24629, + "Ġrecognised": 24630, + "Cat": 24631, + "ĠChl": 24632, + "Ġmillions": 24633, + "Ġdisappearance": 24634, + "GAP": 24635, + "Ġradiographic": 24636, + "Ġpostpartum": 24637, + "developed": 24638, + "xual": 24639, + "Ġhed": 24640, + "idered": 24641, + "ĠCertain": 24642, + "Ġdysplasia": 24643, + "________": 24644, + "ĠHalf": 24645, + "Ġasymmetries": 24646, + "ĠAlcohol": 24647, + "Sum": 24648, + "Ġfm": 24649, + "Ġchap": 24650, + "Ġpretreated": 24651, + "ĠGallery": 24652, + "Ġoutperform": 24653, + "Ġbreeds": 24654, + "Ġtied": 24655, + "Ġdiffeomorphism": 24656, + "Ġcausative": 24657, + "Ġcollectively": 24658, + "Ġsuboptimal": 24659, + "Ġinsulation": 24660, + "Ġmanipulate": 24661, + "Ġkilomet": 24662, + "Ġrepulsion": 24663, + "Ġchloroform": 24664, + "Ġbean": 24665, + "Ġhero": 24666, + "rophysics": 24667, + "ĠPeptide": 24668, + "Ġoutlier": 24669, + "Derived": 24670, + "isser": 24671, + "ĠInfant": 24672, + "sulfonyl": 24673, + "Ġrecursively": 24674, + "Hu": 24675, + "ĠKoh": 24676, + "pyridine": 24677, + "Ġsquad": 24678, + "Ġthirty": 24679, + "Ġspoken": 24680, + "ĠZar": 24681, + "othermic": 24682, + "Ġcalcification": 24683, + "ĠHelsinki": 24684, + "Ġbeach": 24685, + "ĠFDR": 24686, + "Ġprobiotic": 24687, + "Ġfinishing": 24688, + "ymmetrical": 24689, + "Ġvacancy": 24690, + "Ġthrombo": 24691, + "Compared": 24692, + "AST": 24693, + "sted": 24694, + "otherap": 24695, + "Ġiodide": 24696, + "Ġtt": 24697, + "alignment": 24698, + "Ġmicrovascular": 24699, + "Ġinitialize": 24700, + "ĠANALYSIS": 24701, + "Ġtopographic": 24702, + "ĠReporting": 24703, + "Ġunderestimated": 24704, + "puted": 24705, + "Ġatherosclerotic": 24706, + "Qiagen": 24707, + "gut": 24708, + "ĠCortical": 24709, + "Ġdisrupt": 24710, + "este": 24711, + "Ġglue": 24712, + "Ġnarrower": 24713, + "Ġinpatient": 24714, + "Ġscholars": 24715, + "Ġbc": 24716, + "ĠPsychological": 24717, + "ĠHamiltonians": 24718, + "Ġhonor": 24719, + "tibular": 24720, + "Ġinsertions": 24721, + "oscope": 24722, + "Ġpharmacokinetics": 24723, + "Ġmathematically": 24724, + "Ġfork": 24725, + "ipital": 24726, + "ĠArgs": 24727, + "abolism": 24728, + "Ġâİł": 24729, + "ĠRobot": 24730, + "ĠCasc": 24731, + "Ġleaching": 24732, + "ĠLack": 24733, + "Ġendocytosis": 24734, + "Ġtris": 24735, + "Ġsensitivities": 24736, + "Ġlicensed": 24737, + "Ġsponge": 24738, + "carbonyl": 24739, + "feat": 24740, + "Ġprecl": 24741, + "Ġwaist": 24742, + "tifications": 24743, + "Ġoliv": 24744, + "binary": 24745, + "atri": 24746, + "ĠBiot": 24747, + "TZ": 24748, + "Ġfake": 24749, + "ĠMosc": 24750, + "ĠHPS": 24751, + "ĠVoltage": 24752, + "ĠâİĿ": 24753, + "ĠAhmed": 24754, + "ĠSexual": 24755, + "dehydes": 24756, + "ĠCot": 24757, + "Ġmagma": 24758, + "oxylin": 24759, + "ÐĪ": 24760, + "amethyl": 24761, + "ĠLOS": 24762, + "diphenyl": 24763, + "experimental": 24764, + "Ġpluripotent": 24765, + "agittal": 24766, + "walk": 24767, + "Ġplasmonic": 24768, + "Ġcontracts": 24769, + "Ġexped": 24770, + "ĠArabia": 24771, + "Ġshoots": 24772, + "ĠRAN": 24773, + "ustrated": 24774, + "Ġconvexity": 24775, + "ĠmJ": 24776, + "ĠAbsolute": 24777, + "ĠSEL": 24778, + "MIP": 24779, + "ĠActually": 24780, + "sole": 24781, + "QI": 24782, + "ĠTGFβ": 24783, + "Ġâİŀ": 24784, + "Ġrearrangements": 24785, + "Ġcuring": 24786, + "expensive": 24787, + "ceptibility": 24788, + "Ġours": 24789, + "ĠKidney": 24790, + "Ġassigns": 24791, + "Ġvoxels": 24792, + "oreal": 24793, + "Ġevening": 24794, + "hus": 24795, + "ĠãĢ": 24796, + "oradi": 24797, + "ĠCorrection": 24798, + "Ġnanofibers": 24799, + "Ġcantile": 24800, + "bigoplus": 24801, + "uminous": 24802, + "eclampsia": 24803, + "ĠCult": 24804, + "ECH": 24805, + "atology": 24806, + "Ġji": 24807, + "cryp": 24808, + "ĠAspects": 24809, + "eni": 24810, + "Ġsemis": 24811, + "IRS": 24812, + "ĠPho": 24813, + "encoding": 24814, + "ĠJustice": 24815, + "ococci": 24816, + "Ġhypothalamic": 24817, + "ractable": 24818, + "ĠOrb": 24819, + "Simons": 24820, + "Ġmanipulated": 24821, + "attribute": 24822, + "onov": 24823, + "orously": 24824, + "endar": 24825, + "uder": 24826, + "insert": 24827, + "Ġlysed": 24828, + "ĠHodge": 24829, + "Ġfootballer": 24830, + "Device": 24831, + "ĠLeast": 24832, + "Ġstratum": 24833, + "Ġmitral": 24834, + "Ġsell": 24835, + "ĠMuc": 24836, + "glycer": 24837, + "oj": 24838, + "Ġpathogenicity": 24839, + "ĠDeclaration": 24840, + "opause": 24841, + "ĠArticle": 24842, + "Ġrinsed": 24843, + "ĠLévy": 24844, + "rement": 24845, + "Ġants": 24846, + "ĠDic": 24847, + "ĠkPa": 24848, + "urry": 24849, + "motion": 24850, + "client": 24851, + "Ġaccessory": 24852, + "Ġdepolarization": 24853, + "namely": 24854, + "Ġdisparities": 24855, + "Ġfavourable": 24856, + "ĠTibet": 24857, + "Ġoocyte": 24858, + "istration": 24859, + "Ġunresolved": 24860, + "criptive": 24861, + "physics": 24862, + "Ġbenzo": 24863, + "Ġcrystallinity": 24864, + "Ġpayoff": 24865, + "Ġumbilical": 24866, + "osil": 24867, + "ĠSystemic": 24868, + "ĠSTM": 24869, + "Ġstabilizer": 24870, + "USA": 24871, + "ĠJensen": 24872, + "Aug": 24873, + "ĠHat": 24874, + "AGG": 24875, + "underbrace": 24876, + "Ġmanipulations": 24877, + "ĠManc": 24878, + "nedy": 24879, + "Ġscratch": 24880, + "Cherry": 24881, + "osaccharides": 24882, + "Ġprecipitate": 24883, + "quarters": 24884, + "icul": 24885, + "Ġoptimally": 24886, + "many": 24887, + "Ġneoplasms": 24888, + "Ġinward": 24889, + "aryng": 24890, + "Ġmoll": 24891, + "ĠWel": 24892, + "ĠWiley": 24893, + "Ġnewspaper": 24894, + "Ġinhabitants": 24895, + "ĠSuccess": 24896, + "Ġbridging": 24897, + "Ġdisconnected": 24898, + "Ġhygiene": 24899, + "Dist": 24900, + "Ġscripts": 24901, + "Ġmesoporous": 24902, + "Ġrestricts": 24903, + "actone": 24904, + "Ġaquifer": 24905, + "ĠïĤ·": 24906, + "Ġplex": 24907, + "Ġpresumed": 24908, + "Ġips": 24909, + "ĠMilitary": 24910, + "Ġjudged": 24911, + "Ġald": 24912, + "Ġsequest": 24913, + "compared": 24914, + "ULATION": 24915, + "adapted": 24916, + "Ġinstructed": 24917, + "pulse": 24918, + "Ġcusp": 24919, + "matching": 24920, + "carrier": 24921, + "Ġenforce": 24922, + "ĠInterview": 24923, + "ometrics": 24924, + "Ġnullptr": 24925, + "Ġflavour": 24926, + "ĠPareto": 24927, + "ĠBER": 24928, + "Ġuv": 24929, + "Ġcrash": 24930, + "ĠCann": 24931, + "ĠMineral": 24932, + "ĠOlympic": 24933, + "Ġpolycrystalline": 24934, + "lett": 24935, + "Tables": 24936, + "requent": 24937, + "Ġsedentary": 24938, + "unsaturated": 24939, + "ĠBernoulli": 24940, + "Ġadmissions": 24941, + "itorial": 24942, + "acute": 24943, + "Ġadditions": 24944, + "weet": 24945, + "ALE": 24946, + "ĠManip": 24947, + "tokens": 24948, + "preced": 24949, + "dk": 24950, + "consider": 24951, + "Ġïĺ¹": 24952, + "Ġwrites": 24953, + "cardia": 24954, + "ctomy": 24955, + "omatous": 24956, + "Symbol": 24957, + "usten": 24958, + "Ġproteolytic": 24959, + "categories": 24960, + "Ġfic": 24961, + "Ġswing": 24962, + "Ġpassenger": 24963, + "Ġoverlapped": 24964, + "ifi": 24965, + "Ġmutational": 24966, + "ĠJosephson": 24967, + "Ġregret": 24968, + "ĠArk": 24969, + "ĠCFD": 24970, + "Ġmaneu": 24971, + "encoded": 24972, + "textsc": 24973, + "Ġdecompositions": 24974, + "ĠDeb": 24975, + "Ġmandibular": 24976, + "dU": 24977, + "ĠPIC": 24978, + "Ġtranscriptomic": 24979, + "Ġtelescop": 24980, + "ĠSantos": 24981, + "oE": 24982, + "ĠMCP": 24983, + "Ġindigenous": 24984, + "Ġmicrospheres": 24985, + "Ġcodew": 24986, + "zip": 24987, + "Ġfingers": 24988, + "Ġcampaigns": 24989, + "¸Ģł": 24990, + "Ġaccidents": 24991, + "ĠTools": 24992, + "Planck": 24993, + "»": 24994, + "eder": 24995, + "ingham": 24996, + "oxidase": 24997, + "Ġancestor": 24998, + "whose": 24999, + "Ġphospholipid": 25000, + "Ġconversation": 25001, + "ĠHof": 25002, + "cortical": 25003, + "glycos": 25004, + "Ġmanufacturers": 25005, + "opulmonary": 25006, + "Ġinclined": 25007, + "ĠBethe": 25008, + "Ġspending": 25009, + "ĠFusarium": 25010, + "uitively": 25011, + "Ġfemur": 25012, + "ĠLinks": 25013, + "Ġnitrite": 25014, + "Main": 25015, + "Ġflora": 25016, + "ĠPhD": 25017, + "ĠWriting": 25018, + "ĠHessian": 25019, + "Ġμs": 25020, + "ools": 25021, + "Ġvictims": 25022, + "ĠRew": 25023, + "ansen": 25024, + "Ear": 25025, + "Ġorn": 25026, + "Ġthermoelectric": 25027, + "ENSE": 25028, + "ĠWeighted": 25029, + "holes": 25030, + "Ġcen": 25031, + "Ġacuity": 25032, + "Ġvacancies": 25033, + "ĠDuke": 25034, + "Ġpaclitaxel": 25035, + "Ġconverts": 25036, + "bourne": 25037, + "ĠACS": 25038, + "osi": 25039, + "Ġcriminal": 25040, + "ĠIb": 25041, + "unes": 25042, + "ĠNanoc": 25043, + "Post": 25044, + "ĠMDS": 25045, + "Ġeconomics": 25046, + "Ġthoughts": 25047, + "Ġneuroprotective": 25048, + "Ġintersects": 25049, + "cers": 25050, + "atid": 25051, + "usa": 25052, + "ĠAns": 25053, + "Ġafterwards": 25054, + "ĠOFDM": 25055, + "ĠCMV": 25056, + "ĠCum": 25057, + "ATG": 25058, + "ĠImageNet": 25059, + "ĠAttack": 25060, + "ogeneities": 25061, + "Ġcounseling": 25062, + "ĠCONTR": 25063, + "ález": 25064, + "ĠDh": 25065, + "ĠGV": 25066, + "Ġpositional": 25067, + "Ġgang": 25068, + "ĠInteractive": 25069, + "wig": 25070, + "ĠTrace": 25071, + "ĠDSS": 25072, + "Ġsynthetase": 25073, + "ĠGalile": 25074, + "usually": 25075, + "ĠBass": 25076, + "ardless": 25077, + "Ġexecuting": 25078, + "KP": 25079, + "ĠNepal": 25080, + "READ": 25081, + "ĠLock": 25082, + "ohydro": 25083, + "rotation": 25084, + "dil": 25085, + "roscopically": 25086, + "reperfusion": 25087, + "Ġdishes": 25088, + "ĠProceedings": 25089, + "ĠNPC": 25090, + "Ġmonsoon": 25091, + "ĠLemmas": 25092, + "ĠChandra": 25093, + "Ġreactors": 25094, + "Ġtryptophan": 25095, + "ĠVT": 25096, + "ĠDEM": 25097, + "Ġlegislation": 25098, + "mk": 25099, + "Ġtoric": 25100, + "ĠPrograms": 25101, + "ĠPubMed": 25102, + "ĠrDNA": 25103, + "Ġposts": 25104, + "ĠâİĽ": 25105, + "Ġshedding": 25106, + "tolerant": 25107, + "Ġvoids": 25108, + "ĠCaribbean": 25109, + "CODE": 25110, + "Tube": 25111, + "ALSE": 25112, + "Ġchlorine": 25113, + "Ġcoerc": 25114, + "ĠRhiz": 25115, + "ĠKirk": 25116, + "ĠÃĸ": 25117, + "rout": 25118, + "icides": 25119, + "agu": 25120, + "ĠKw": 25121, + "Ġcru": 25122, + "Observe": 25123, + "ĠRevis": 25124, + "Ġanonym": 25125, + "Ġprerequ": 25126, + "ocortical": 25127, + "Ġrestaur": 25128, + "ĠPopulations": 25129, + "dst": 25130, + "Ġfort": 25131, + "regs": 25132, + "ĠPolarization": 25133, + "Ġpancreatitis": 25134, + "aph": 25135, + "threat": 25136, + "ften": 25137, + "ĠAlaska": 25138, + "ĠFlexible": 25139, + "Ġrepertoire": 25140, + "kan": 25141, + "mathchoice": 25142, + "Ġmitosis": 25143, + "Ġeat": 25144, + "utin": 25145, + "Ġrt": 25146, + "Ġdummy": 25147, + "ĠCys": 25148, + "ĠGor": 25149, + "earchers": 25150, + "HPLC": 25151, + "Ġbay": 25152, + "ĠNielsen": 25153, + "ĠRoc": 25154, + "iani": 25155, + "icit": 25156, + "rague": 25157, + "Ġcourts": 25158, + "testing": 25159, + "Ġamplify": 25160, + "Ġtuples": 25161, + "proliferative": 25162, + "ĠParas": 25163, + "Ġmagnets": 25164, + "Ġchemokines": 25165, + "ĠMitchell": 25166, + "ĠPetri": 25167, + "holtz": 25168, + "ych": 25169, + "matrices": 25170, + "Ġcorrecting": 25171, + "ĠPCa": 25172, + "ynamically": 25173, + "ĠNAFLD": 25174, + "Ġeffluent": 25175, + "itum": 25176, + "Ġthrows": 25177, + "ĠGuid": 25178, + "ochromatic": 25179, + "ĠFro": 25180, + "idad": 25181, + "romagnetism": 25182, + "Herm": 25183, + "ĠSpi": 25184, + "ĠQuas": 25185, + "domains": 25186, + "Ġquadrant": 25187, + "ĠSOX": 25188, + "ĠGovernor": 25189, + "Ġamenable": 25190, + "held": 25191, + "ĠCul": 25192, + "Ġunderwater": 25193, + "ĠKron": 25194, + "ĠSpati": 25195, + "anoyl": 25196, + "CU": 25197, + "ovir": 25198, + "Ġdemographics": 25199, + "Within": 25200, + "ĠMé": 25201, + "textsf": 25202, + "ĠLabel": 25203, + "Ġgenuine": 25204, + "Ġhill": 25205, + "ĠLaz": 25206, + "Ġtesticular": 25207, + "ĠBrow": 25208, + "ICATION": 25209, + "¡": 25210, + "ĠAIC": 25211, + "ancomycin": 25212, + "strual": 25213, + "Ġarrested": 25214, + "ĠSom": 25215, + "ĠIHC": 25216, + "ĠPose": 25217, + "ĠMö": 25218, + "istar": 25219, + "ĠPAM": 25220, + "ĠHCT": 25221, + "Ġtypedef": 25222, + "ĠMorse": 25223, + "ĠLeishman": 25224, + "limb": 25225, + "Ġspheroid": 25226, + "osely": 25227, + "ĠGuinea": 25228, + "renew": 25229, + "Ġpsoriasis": 25230, + "ista": 25231, + "ĠChung": 25232, + "orthogonal": 25233, + "ĠShear": 25234, + "ĠMuslim": 25235, + "ĠPict": 25236, + "Integer": 25237, + "Ġspacer": 25238, + "Ly": 25239, + "Ġdermal": 25240, + "Ġoncology": 25241, + "Ġdp": 25242, + "Ġphotoluminescence": 25243, + "regon": 25244, + "aminase": 25245, + "Ġáºĭ": 25246, + "Instance": 25247, + "verb": 25248, + "Ġmethylated": 25249, + "ĠGem": 25250, + "istently": 25251, + "ĠMgCl": 25252, + "ĠElevated": 25253, + "⣩": 25254, + "onstruct": 25255, + "Ġsnapshot": 25256, + "enem": 25257, + "ĠDisk": 25258, + "Ġhydrostatic": 25259, + "Ġïĥª": 25260, + "vor": 25261, + "ĠIE": 25262, + "ĠLY": 25263, + "ORF": 25264, + "Ġfoil": 25265, + "male": 25266, + "Ġdepended": 25267, + "sparse": 25268, + "Ġmetas": 25269, + "Ġtextures": 25270, + "Ġstacks": 25271, + "MHz": 25272, + "Ġfn": 25273, + "Ġultrac": 25274, + "ĠShould": 25275, + "Vec": 25276, + "nine": 25277, + "infinite": 25278, + "ĠLawrence": 25279, + "ĠInventory": 25280, + "ĠProstate": 25281, + "Ġgesture": 25282, + "ĠSuzuki": 25283, + "Abs": 25284, + "ricane": 25285, + "ĠPeriodic": 25286, + "Myc": 25287, + "ifiable": 25288, + "Ġinefficient": 25289, + "Ġcollapsed": 25290, + "Ġtopologically": 25291, + "Ġpreferable": 25292, + "Ġbronchial": 25293, + "uston": 25294, + "Ġflexion": 25295, + "ourney": 25296, + "translation": 25297, + "Ġepitaxial": 25298, + "Ġirradiance": 25299, + "Ġneighbours": 25300, + "switch": 25301, + "Ġactuators": 25302, + "SOD": 25303, + "mir": 25304, + "dies": 25305, + "ikawa": 25306, + "ĠALL": 25307, + "ĠRSV": 25308, + "ĠHEP": 25309, + "Ġendurance": 25310, + "connection": 25311, + "Ġgestures": 25312, + "odontic": 25313, + "ĠUnc": 25314, + "Ġdismutase": 25315, + "Having": 25316, + "mix": 25317, + "Ġneurogenesis": 25318, + "Ġmyocardium": 25319, + "ĠRussell": 25320, + "Hist": 25321, + "ĠSPI": 25322, + "triazol": 25323, + "agulant": 25324, + "ĠRequired": 25325, + "ĠshRNA": 25326, + "ĠArthur": 25327, + "Ġspawning": 25328, + "dried": 25329, + "Ġrectif": 25330, + "ĠÃī": 25331, + "Ġosteogenic": 25332, + "replace": 25333, + "Ġgaining": 25334, + "Ġneutralization": 25335, + "ĠHartree": 25336, + "Ġfollicles": 25337, + "Ġreligion": 25338, + "Ġduplex": 25339, + "Ġtransients": 25340, + "amped": 25341, + "Ġmicrotubules": 25342, + "interest": 25343, + "Ġsteels": 25344, + "Batch": 25345, + "Ġdenaturation": 25346, + "ĠPhillips": 25347, + "Ġquiet": 25348, + "ĠBureau": 25349, + "ĠRare": 25350, + "Ġquercetin": 25351, + "aults": 25352, + "Ġelution": 25353, + "uka": 25354, + "ĠInterpretation": 25355, + "RV": 25356, + "ĠESC": 25357, + "ĠKom": 25358, + "arettes": 25359, + "ĠïģĦ": 25360, + "Ġtradition": 25361, + "Ġdissected": 25362, + "Neigh": 25363, + "Ġsheaves": 25364, + "Ġbelonged": 25365, + "ĠHistoric": 25366, + "ĠOE": 25367, + "Ġjson": 25368, + "lemma": 25369, + "ĠYAP": 25370, + "odext": 25371, + "interface": 25372, + "Ġextremity": 25373, + "crossing": 25374, + "precedented": 25375, + "according": 25376, + "Ġconstructive": 25377, + "ĠStimulation": 25378, + "ĠHFD": 25379, + "Ġwavenumber": 25380, + "Ġhrs": 25381, + "Ġpapillomavirus": 25382, + "Ġvomiting": 25383, + "Ġreactivation": 25384, + "ometrically": 25385, + "ĠDimensions": 25386, + "objects": 25387, + "orton": 25388, + "ĠMathem": 25389, + "ĠOlive": 25390, + "Ġcrosstalk": 25391, + "partite": 25392, + "opathies": 25393, + "ĠCNTs": 25394, + "rousal": 25395, + "Ġcrowd": 25396, + "ĠLangmuir": 25397, + "ĠTox": 25398, + "echanics": 25399, + "imus": 25400, + "ĠShock": 25401, + "tanh": 25402, + "ĠBrillouin": 25403, + "Ġtransferring": 25404, + "Ġellipse": 25405, + "ĠAddition": 25406, + "ĠRural": 25407, + "Ġgeodesics": 25408, + "GEM": 25409, + "ĠPOS": 25410, + "ĠMission": 25411, + "ocarp": 25412, + "ĠJane": 25413, + "Lie": 25414, + "freq": 25415, + "opot": 25416, + "ĠVibrio": 25417, + "ĠObj": 25418, + "erts": 25419, + "ĠTrials": 25420, + "CFT": 25421, + "ĠCodes": 25422, + "μg": 25423, + "Reference": 25424, + "ĠFung": 25425, + "ĠSuppression": 25426, + "hog": 25427, + "Ġresistive": 25428, + "Chi": 25429, + "intered": 25430, + "Ġpostmenopausal": 25431, + "Statistical": 25432, + "ĠEdwards": 25433, + "Ġses": 25434, + "Ġfarming": 25435, + "quartile": 25436, + "cooled": 25437, + "Ġnanop": 25438, + "ĠProbing": 25439, + "ĠBernard": 25440, + "uni": 25441, + "ieties": 25442, + "ĠMarket": 25443, + "osum": 25444, + "ĠMessage": 25445, + "Ġaxiom": 25446, + "cg": 25447, + "ĠMoving": 25448, + "Resolution": 25449, + "Ġadsorbent": 25450, + "Ġmultin": 25451, + "Ġineffective": 25452, + "propag": 25453, + "hardt": 25454, + "Saharan": 25455, + "Wil": 25456, + "ĠIvan": 25457, + "irubin": 25458, + "Ġtrabec": 25459, + "alli": 25460, + "ĠCDCl": 25461, + "Ġsew": 25462, + "ĠIss": 25463, + "Ġaggression": 25464, + "ĠJuan": 25465, + "Ġdispersions": 25466, + "Ġauxin": 25467, + "FET": 25468, + "lp": 25469, + "reach": 25470, + "ĠPGE": 25471, + "chestr": 25472, + "Ġlecture": 25473, + "ĠDonald": 25474, + "slip": 25475, + "ĠHbA": 25476, + "ĠSecure": 25477, + "ĠBeh": 25478, + "Ġdamages": 25479, + "WH": 25480, + "alkyl": 25481, + "Ha": 25482, + "ĠThanks": 25483, + "Ġsensitization": 25484, + "Ġwaterm": 25485, + "Ġtwins": 25486, + "Ġcultivar": 25487, + "Ġzeolite": 25488, + "Variable": 25489, + "ĠBent": 25490, + "Ġantisense": 25491, + "ĠHansen": 25492, + "repreneur": 25493, + "ĠSNe": 25494, + "ĠEMG": 25495, + "Ġreacted": 25496, + "Ġoverflow": 25497, + "Ġformalin": 25498, + "ĠUsually": 25499, + "olybden": 25500, + "Ġacad": 25501, + "ATURE": 25502, + "Ġwaveguides": 25503, + "Ġchunk": 25504, + "Ġmodifies": 25505, + "Ġeryt": 25506, + "ĠZhong": 25507, + "Ġgranule": 25508, + "Ġcs": 25509, + "ĠGrade": 25510, + "Ġlandmarks": 25511, + "uristic": 25512, + "Ġamines": 25513, + "ĠIntrinsic": 25514, + "Ġerroneous": 25515, + "Ġlockdown": 25516, + "ypti": 25517, + "Child": 25518, + "Ġuniversities": 25519, + "Ġparasit": 25520, + "Ġignition": 25521, + "Tim": 25522, + "araj": 25523, + "ravel": 25524, + "ĠLands": 25525, + "ĠCircular": 25526, + "Ġrotate": 25527, + "Patients": 25528, + "ĠWB": 25529, + "Ġmyelin": 25530, + "ĠWeiss": 25531, + "Ġdipolar": 25532, + "Ġfollicle": 25533, + "ĠWatson": 25534, + "ĠIncor": 25535, + "Ġfoundations": 25536, + "ĠPip": 25537, + "Ġpressing": 25538, + "Ġforbidden": 25539, + "avan": 25540, + "ĠmAb": 25541, + "union": 25542, + "ĠFresh": 25543, + "ĠCorp": 25544, + "floxacin": 25545, + "coordinate": 25546, + "Ġshunt": 25547, + "Ġconstituted": 25548, + "aniline": 25549, + "Ġtweets": 25550, + "ĠChow": 25551, + "Ġmobilization": 25552, + "zyk": 25553, + "EST": 25554, + "neigh": 25555, + "ĠMeng": 25556, + "ĠResNet": 25557, + "ĠJet": 25558, + "Ġluminous": 25559, + "Ġstressors": 25560, + "does": 25561, + "trifluoromethyl": 25562, + "Ġconcert": 25563, + "ĠChoice": 25564, + "phim": 25565, + "alcoholic": 25566, + "ochem": 25567, + "iltered": 25568, + "Ġpredictable": 25569, + "Ġtran": 25570, + "ĠPra": 25571, + "Ġvalves": 25572, + "Ġautonomy": 25573, + "regulate": 25574, + "ĠBeach": 25575, + "ĠOntology": 25576, + "Ġisofl": 25577, + "Ġquoted": 25578, + "ĠLex": 25579, + "thy": 25580, + "Ġcomplaints": 25581, + "ĠTrees": 25582, + "Ġopposing": 25583, + "ĠAcceler": 25584, + "contrast": 25585, + "Ġcompeted": 25586, + "OE": 25587, + "ĠRoche": 25588, + "issance": 25589, + "Ġpeace": 25590, + "ĠAim": 25591, + "Ġinfertility": 25592, + "ĠAntarctica": 25593, + "thien": 25594, + "Summ": 25595, + "Ġjudgments": 25596, + "amides": 25597, + "Ġspill": 25598, + "Ġhereafter": 25599, + "ĠConstit": 25600, + "computer": 25601, + "Ġbegun": 25602, + "ocentric": 25603, + "Ġpumps": 25604, + "medium": 25605, + "chol": 25606, + "metallic": 25607, + "Ġflares": 25608, + "Ġpetroleum": 25609, + "Ġwithd": 25610, + "ĠTheatre": 25611, + "Ġunlabeled": 25612, + "Ġregularized": 25613, + "osteric": 25614, + "ĠPFS": 25615, + "Ġunem": 25616, + "Ġpresently": 25617, + "Ġbuffered": 25618, + "affinity": 25619, + "ĠDemographic": 25620, + "ĠKondo": 25621, + "Ġcenturies": 25622, + "Ġmigratory": 25623, + "arynx": 25624, + "Associated": 25625, + "anilino": 25626, + "grown": 25627, + "ĠExecutive": 25628, + "ĠEk": 25629, + "ĠHemat": 25630, + "ĠPlayer": 25631, + "ĠCHD": 25632, + "flex": 25633, + "ĠSever": 25634, + "altham": 25635, + "impro": 25636, + "anet": 25637, + "ocyst": 25638, + "ĠAster": 25639, + "COL": 25640, + "ĠSimilarity": 25641, + "ĠHoward": 25642, + "Ġmulticast": 25643, + "ĠEnsemble": 25644, + "ìĹ": 25645, + "olys": 25646, + "ĠGenomics": 25647, + "Ġresonators": 25648, + "Ġfistula": 25649, + "onen": 25650, + "users": 25651, + "Ġhypo": 25652, + "rogens": 25653, + "Ġmedal": 25654, + "ĠMIP": 25655, + "Ġvoltam": 25656, + "Ġappreciated": 25657, + "ĠPé": 25658, + "ĠGaia": 25659, + "Ġbuckling": 25660, + "Ġcongruence": 25661, + "furyl": 25662, + "ĠEpstein": 25663, + "Ġcascades": 25664, + "gold": 25665, + "Ġanhyd": 25666, + "Ġgraduated": 25667, + "Memory": 25668, + "ĠIndustry": 25669, + "ĠSchneider": 25670, + "Ġemployee": 25671, + "ĠCorn": 25672, + "MAC": 25673, + "rove": 25674, + "ropod": 25675, + "service": 25676, + "ĠOxidation": 25677, + "Ġenumeration": 25678, + "mad": 25679, + "ĠClose": 25680, + "ĠModular": 25681, + "Ġprogeny": 25682, + "Ġgt": 25683, + "reading": 25684, + "ĠIndic": 25685, + "opathologic": 25686, + "ĠPFNGL": 25687, + "XL": 25688, + "cis": 25689, + "ĠMike": 25690, + "ĠBBB": 25691, + "ĠExtreme": 25692, + "ĠChoose": 25693, + "Ġhorizontally": 25694, + "ĠASSERT": 25695, + "Ġglucocorticoid": 25696, + "Bay": 25697, + "Ġpdf": 25698, + "Ġcontainers": 25699, + "ĠLOC": 25700, + "ĠYield": 25701, + "oprote": 25702, + "Ġfructose": 25703, + "ĠICC": 25704, + "Ġdecid": 25705, + "rimidine": 25706, + "Ġfragmented": 25707, + "Ġisomorphisms": 25708, + "м": 25709, + "Ġintegrates": 25710, + "Ġfibration": 25711, + "ĠâĬ¤": 25712, + "Ġxenograft": 25713, + "nucleon": 25714, + "ĠCSP": 25715, + "Ġsut": 25716, + "ĠSpir": 25717, + "Ġdissoci": 25718, + "ĠTBI": 25719, + "ĠForces": 25720, + "Ġhypersurface": 25721, + "Ġmyosin": 25722, + "ĠQueensland": 25723, + "Neg": 25724, + "ĠURL": 25725, + "bind": 25726, + "Applied": 25727, + "ĠDob": 25728, + "ĠKE": 25729, + "Ġmemor": 25730, + "ĠArabic": 25731, + "ĠLateral": 25732, + "ĠStart": 25733, + "nose": 25734, + "tibility": 25735, + "asters": 25736, + "Ġusability": 25737, + "Ġincenti": 25738, + "ymn": 25739, + "ĠAnalytic": 25740, + "Pet": 25741, + "ĠMask": 25742, + "World": 25743, + "brand": 25744, + "Ġeliminates": 25745, + "Ġmerit": 25746, + "ĠPhilippines": 25747, + "ĠBCL": 25748, + "ĠOri": 25749, + "Ġparadigms": 25750, + "ĠInters": 25751, + "rizona": 25752, + "Ġconception": 25753, + "Ġrelied": 25754, + "ĠJoe": 25755, + "ĠApple": 25756, + "Ġlightweight": 25757, + "mortem": 25758, + "olig": 25759, + "Ġviz": 25760, + "Ġstones": 25761, + "Ġkeywords": 25762, + "ĠSecretary": 25763, + "TN": 25764, + "older": 25765, + "ĠIntestinal": 25766, + "Ġpossessed": 25767, + "Ġmonotonicity": 25768, + "emitting": 25769, + "ĠDefining": 25770, + "ĠParticularly": 25771, + "Ġautomorphisms": 25772, + "Ġerythemat": 25773, + "ĠWaters": 25774, + "ĠCyclic": 25775, + "maximal": 25776, + "xty": 25777, + "ĠSad": 25778, + "Ġuranium": 25779, + "Ġhypothalamus": 25780, + "ĠSUMO": 25781, + "Ġdealt": 25782, + "Ġkits": 25783, + "Ġpainting": 25784, + "ĠSier": 25785, + "chool": 25786, + "ODO": 25787, + "surfaces": 25788, + "ĠPneum": 25789, + "organized": 25790, + "ĠCPT": 25791, + "Ġinsoluble": 25792, + "ĠCoherent": 25793, + "Ġrecessive": 25794, + "Ġbivariate": 25795, + "Ġedit": 25796, + "Ġnationwide": 25797, + "MODE": 25798, + "chest": 25799, + "ĠSLC": 25800, + "Ġintraperitoneal": 25801, + "ĠDisordered": 25802, + "Ġinsufficiency": 25803, + "iev": 25804, + "iazole": 25805, + "Write": 25806, + "ĠDATA": 25807, + "toral": 25808, + "Ġqualities": 25809, + "Ġpossessing": 25810, + "ĠMats": 25811, + "Ġretinopathy": 25812, + "ĠBK": 25813, + "Ġnovelty": 25814, + "ceans": 25815, + "Ġreserves": 25816, + "ĠNADH": 25817, + "Ġisotherm": 25818, + "Ġsoldiers": 25819, + "pb": 25820, + "iterpen": 25821, + "ĠAgents": 25822, + "zu": 25823, + "Ġunwanted": 25824, + "Ġhyperparameters": 25825, + "ecan": 25826, + "ĠSES": 25827, + "ĠFG": 25828, + "ĠNavig": 25829, + "Ġtriangulation": 25830, + "Ġnetworking": 25831, + "Ġpolystyrene": 25832, + "Ġinductively": 25833, + "breviations": 25834, + "Ġneuromuscular": 25835, + "ĠLinux": 25836, + "studied": 25837, + "ĠBeing": 25838, + "Ġdeficiencies": 25839, + "ĠMatrices": 25840, + "Ġwearing": 25841, + "Ġhadrons": 25842, + "amyl": 25843, + "Ġdiscourse": 25844, + "ochlor": 25845, + "ĠMelan": 25846, + "ĠLan": 25847, + "VL": 25848, + "Ġmunicipal": 25849, + "Ġenrollment": 25850, + "ĠSymmetric": 25851, + "Ġdisciplines": 25852, + "ĠBaron": 25853, + "Research": 25854, + "Ġmagnetite": 25855, + "omide": 25856, + "polarization": 25857, + "leys": 25858, + "Ġseemingly": 25859, + "hepatic": 25860, + "Ġclo": 25861, + "ĠQuatern": 25862, + "Ġcompetit": 25863, + "Requ": 25864, + "gauge": 25865, + "Ġhydrochloride": 25866, + "dropout": 25867, + "panel": 25868, + "Ġaspirin": 25869, + "ĠRUN": 25870, + "Ġribbon": 25871, + "Ġinaccurate": 25872, + "ĠPall": 25873, + "ducers": 25874, + "Throughout": 25875, + "Ġcellul": 25876, + "Ġsuspect": 25877, + "Ġallelic": 25878, + "Ġsnake": 25879, + "ordinated": 25880, + "ĠAutophagy": 25881, + "Ġeig": 25882, + "Ġrif": 25883, + "ĠKennedy": 25884, + "Ġbottle": 25885, + "ĠYouth": 25886, + "awed": 25887, + "linearity": 25888, + "uker": 25889, + "ĠOX": 25890, + "extension": 25891, + "Ġward": 25892, + "ĠComplexes": 25893, + "Ġbiosensor": 25894, + "ĠCartan": 25895, + "dn": 25896, + "Ġsonic": 25897, + "Ġindexing": 25898, + "Ġdv": 25899, + "reliable": 25900, + "pk": 25901, + "RENT": 25902, + "Ġtanks": 25903, + "ĠHet": 25904, + "ĠWing": 25905, + "ĠCuO": 25906, + "Ġprintf": 25907, + "Ġluminosities": 25908, + "course": 25909, + "Ġscram": 25910, + "Ġsampler": 25911, + "Ġmultipliers": 25912, + "Default": 25913, + "odil": 25914, + "intr": 25915, + "sequencing": 25916, + "Ġtransmissions": 25917, + "ĠWhit": 25918, + "ĠOpportun": 25919, + "Ġinternally": 25920, + "Ġacknowledges": 25921, + "ĠEdition": 25922, + "Ġarteri": 25923, + "Ġalbedo": 25924, + "ĠNucleotide": 25925, + "Ġyes": 25926, + "ĠRelativistic": 25927, + "Ġvotes": 25928, + "ĠFormulation": 25929, + "uscitation": 25930, + "Ġconcurrently": 25931, + "uin": 25932, + "Ġnoninvasive": 25933, + "Ġprimates": 25934, + "μl": 25935, + "Ġsubtropical": 25936, + "gun": 25937, + "ĠSoutheast": 25938, + "ön": 25939, + "Ġequator": 25940, + "Ġworkshop": 25941, + "Ġschist": 25942, + "undant": 25943, + "ĠMODIS": 25944, + "tar": 25945, + "Ġaeg": 25946, + "Ġplotting": 25947, + "ĠDET": 25948, + "Manager": 25949, + "uned": 25950, + "oxifen": 25951, + "ĠInver": 25952, + "Ġxanth": 25953, + "ĠServer": 25954, + "Ġstretched": 25955, + "Global": 25956, + "Core": 25957, + "ĠWeber": 25958, + "yard": 25959, + "Ġexplores": 25960, + "ĠBiography": 25961, + "SNP": 25962, + "ĠNeutrino": 25963, + "Ġkilometres": 25964, + "Ġcommutes": 25965, + "Ġacceptability": 25966, + "ĠAntibodies": 25967, + "icol": 25968, + "Ġmuseum": 25969, + "Ġdenit": 25970, + "Ġextrapolated": 25971, + "Ġacetylcholine": 25972, + "Token": 25973, + "ĠFock": 25974, + "onde": 25975, + "Ġdiscriminative": 25976, + "ĠMant": 25977, + "Ġessence": 25978, + "celand": 25979, + "ĠChair": 25980, + "Ġintegrative": 25981, + "ĠSPD": 25982, + "henium": 25983, + "arbonate": 25984, + "BASE": 25985, + "regulates": 25986, + "patch": 25987, + "Ġdib": 25988, + "Ġantisymmetric": 25989, + "Ġwearable": 25990, + "Edge": 25991, + "rets": 25992, + "Ġperceive": 25993, + "ĠMagnesium": 25994, + "adows": 25995, + "Ġdisposal": 25996, + "Ġairport": 25997, + "ausea": 25998, + "fits": 25999, + "Ġnecro": 26000, + "ĠSIN": 26001, + "ĠDuc": 26002, + "ĠReading": 26003, + "bys": 26004, + "Ġreflective": 26005, + "his": 26006, + "ometries": 26007, + "Ġvirial": 26008, + "Ġartificially": 26009, + "children": 26010, + "ĠUltrasound": 26011, + "VIEW": 26012, + "Ġsculpt": 26013, + "Ġsurf": 26014, + "Ġsexually": 26015, + "Ġgeometrically": 26016, + "Ġdivisors": 26017, + "Ġinitiatives": 26018, + "acci": 26019, + "Ġkeratinocytes": 26020, + "aR": 26021, + "arot": 26022, + "Ġïĥ¨": 26023, + "computed": 26024, + "ĠTCGA": 26025, + "psychological": 26026, + "ĠMAN": 26027, + "ĠMPC": 26028, + "ticing": 26029, + "limiting": 26030, + "amins": 26031, + "Ġsurfactants": 26032, + "ĠSerb": 26033, + "Ġrhythms": 26034, + "ĠRouting": 26035, + "wang": 26036, + "Ġmicrostructures": 26037, + "ophytes": 26038, + "Ġanalgesic": 26039, + "FOR": 26040, + "qual": 26041, + "Ġpublish": 26042, + "ĠTiming": 26043, + "porous": 26044, + "ranging": 26045, + "eron": 26046, + "ĠZi": 26047, + "ĠMarshall": 26048, + "Width": 26049, + "Ġisomers": 26050, + "Ġ·": 26051, + "phenoxy": 26052, + "Ġureth": 26053, + "robl": 26054, + "Ġmentioning": 26055, + "ozyme": 26056, + "ĠLud": 26057, + "Ġopposition": 26058, + "Ġabandoned": 26059, + "Ġroutines": 26060, + "ĠHST": 26061, + "mutex": 26062, + "coded": 26063, + "eating": 26064, + "tert": 26065, + "emiconductor": 26066, + "dw": 26067, + "Ġbaryons": 26068, + "Ġleucine": 26069, + "otron": 26070, + "Ġendos": 26071, + "Ġreproduces": 26072, + "Ġanalgesia": 26073, + "Ġimmunoreactivity": 26074, + "ĠPrep": 26075, + "ĠGarcÃŃa": 26076, + "Ġincoherent": 26077, + "aned": 26078, + "lepton": 26079, + "andra": 26080, + "ulae": 26081, + "ĠHidden": 26082, + "FV": 26083, + "Ġgeneralizes": 26084, + "ĠStevens": 26085, + "ĠFoster": 26086, + "Ġfreshly": 26087, + "Ġhf": 26088, + "Denote": 26089, + "oes": 26090, + "ĠDin": 26091, + "Ġdetox": 26092, + "Ġdecoupled": 26093, + "Ġseparations": 26094, + "ucleotide": 26095, + "Ġelectrophysiological": 26096, + "ĠBALB": 26097, + "QTL": 26098, + "ĠACh": 26099, + "ĠRele": 26100, + "quez": 26101, + "MnO": 26102, + "ectures": 26103, + "Ġischa": 26104, + "Ġinsulators": 26105, + "cellulose": 26106, + "ĠFLAG": 26107, + "ombic": 26108, + "ĠUsed": 26109, + "jiang": 26110, + "expansion": 26111, + "ĠRepeat": 26112, + "ĠReserve": 26113, + "abelian": 26114, + "ĠHunting": 26115, + "GRO": 26116, + "lyte": 26117, + "ĠBark": 26118, + "Ġcreative": 26119, + "Ġbend": 26120, + "elerated": 26121, + "dish": 26122, + "Ġhighway": 26123, + "Ġcrossings": 26124, + "just": 26125, + "ono": 26126, + "ullivan": 26127, + "ĠDead": 26128, + "Ġtradeoff": 26129, + "eon": 26130, + "ogical": 26131, + "experiment": 26132, + "Ġconfers": 26133, + "ĠDot": 26134, + "Ġcoils": 26135, + "Ġaxion": 26136, + "ĠIRS": 26137, + "ĠÅ©": 26138, + "Ġglacier": 26139, + "ĠMoscow": 26140, + "ĠSpringer": 26141, + "Ġinvis": 26142, + "ĠArnold": 26143, + "University": 26144, + "attern": 26145, + "peror": 26146, + "ĠLimits": 26147, + "Ġincompatible": 26148, + "rather": 26149, + "ĠTes": 26150, + "Ġfailing": 26151, + "Ġthickening": 26152, + "Ġestradiol": 26153, + "asse": 26154, + "Ġnecessit": 26155, + "Ġsacrificed": 26156, + "ĠSear": 26157, + "ĠNorthe": 26158, + "raisebox": 26159, + "ĠSlow": 26160, + "ĠMunic": 26161, + "Ġlearner": 26162, + "igenic": 26163, + "Ġdermatitis": 26164, + "uten": 26165, + "Ġdeer": 26166, + "Ġhistamine": 26167, + "Lat": 26168, + "Mal": 26169, + "illy": 26170, + "Ġgeochemical": 26171, + "Ġspermatozoa": 26172, + "Ġvinyl": 26173, + "emet": 26174, + "Ġeffectors": 26175, + "ĠEncyclopedia": 26176, + "Ġordinal": 26177, + "Ġcontroversy": 26178, + "ĠPerspectives": 26179, + "oviruses": 26180, + "marked": 26181, + "ĠSPE": 26182, + "ĠNutri": 26183, + "Ġadhere": 26184, + "ĠHighway": 26185, + "Ġdistillation": 26186, + "MRT": 26187, + "pletion": 26188, + "Ġannihil": 26189, + "Ġwavefunction": 26190, + "Ġconfigured": 26191, + "Ġmethionine": 26192, + "Low": 26193, + "sensor": 26194, + "ĠSnow": 26195, + "Sample": 26196, + "Ġdefinitely": 26197, + "ĠMeth": 26198, + "rypt": 26199, + "Ġprompted": 26200, + "Ġmonolith": 26201, + "ĠEnvironments": 26202, + "tm": 26203, + "ĠCOD": 26204, + "oris": 26205, + "equations": 26206, + "âĺĨ": 26207, + "ĠNeighbor": 26208, + "Ġimagine": 26209, + "ĠUsers": 26210, + "ĠCamera": 26211, + "ĠModification": 26212, + "ĠAttacks": 26213, + "Ġinhalation": 26214, + "áº": 26215, + "Ġventil": 26216, + "ĠNU": 26217, + "ĠContrast": 26218, + "Ġconfining": 26219, + "Service": 26220, + "Wallis": 26221, + "ĠATR": 26222, + "Ġsubduction": 26223, + "Ġïģ¢": 26224, + "Ġtitration": 26225, + "Roche": 26226, + "viv": 26227, + "Ġbears": 26228, + "bola": 26229, + "Ġblinded": 26230, + "measures": 26231, + "ĠStack": 26232, + "occurrence": 26233, + "Ġpermeation": 26234, + "lar": 26235, + "eptors": 26236, + "ĠDIF": 26237, + "corrhiz": 26238, + "ĠVisc": 26239, + "figurable": 26240, + "Ġscheduler": 26241, + "Ġoccasions": 26242, + "amboo": 26243, + "Ġamp": 26244, + "gain": 26245, + "ĠCit": 26246, + "Ġpreceded": 26247, + "Ġtactile": 26248, + "Ġïĥ¦": 26249, + "generic": 26250, + "Ġretrograde": 26251, + "Ġfans": 26252, + "Ġfisher": 26253, + "Ġlights": 26254, + "eeper": 26255, + "Ġundesirable": 26256, + "wald": 26257, + "embol": 26258, + "Ġwrist": 26259, + "Ġauthorized": 26260, + "Ġchondrocytes": 26261, + "ĠEPA": 26262, + "neu": 26263, + "ĠOperations": 26264, + "Ġcheap": 26265, + "Ġanionic": 26266, + "ĠOregon": 26267, + "cot": 26268, + "reason": 26269, + "existence": 26270, + "ĠFinancial": 26271, + "olybdenum": 26272, + "cus": 26273, + "ĠNON": 26274, + "Ġlocked": 26275, + "Bit": 26276, + "Sil": 26277, + "mixing": 26278, + "ĠSites": 26279, + "aproteobacteria": 26280, + "ĠInner": 26281, + "Ġcarc": 26282, + "Ġbiotic": 26283, + "ĠFlag": 26284, + "Ġmagic": 26285, + "kinetic": 26286, + "icted": 26287, + "Ġbulb": 26288, + "supset": 26289, + "pez": 26290, + "derivative": 26291, + "ĠeIF": 26292, + "ĠRough": 26293, + "directional": 26294, + "exit": 26295, + "axy": 26296, + "xtures": 26297, + "phimurium": 26298, + "ĠTFs": 26299, + "athin": 26300, + "Ġorch": 26301, + "Ġspectro": 26302, + "ductase": 26303, + "quinolin": 26304, + "Ġgrasp": 26305, + "Ġparsing": 26306, + "Ġdifficile": 26307, + "ĠLDH": 26308, + "ĠJupiter": 26309, + "ĠFIF": 26310, + "ĠPrize": 26311, + "Ġintentions": 26312, + "session": 26313, + "powered": 26314, + "ĠBam": 26315, + "phasic": 26316, + "Ġignoring": 26317, + "ĠRichardson": 26318, + "principles": 26319, + "Ġofficially": 26320, + "Ct": 26321, + "Ġincon": 26322, + "ĠRegulates": 26323, + "Ġmisc": 26324, + "ĠEZ": 26325, + "Ġsynonym": 26326, + "Ġunfolding": 26327, + "ĠDEC": 26328, + "ĠRX": 26329, + "PDF": 26330, + "Ġbranes": 26331, + "typically": 26332, + "Ġcages": 26333, + "ifolia": 26334, + "ugu": 26335, + "ollen": 26336, + "Ġtablet": 26337, + "ĠSah": 26338, + "ĠPVD": 26339, + "Ġalert": 26340, + "Ġformerly": 26341, + "ĠKRAS": 26342, + "sun": 26343, + "Ġdeacetyl": 26344, + "Mer": 26345, + "Ġskewed": 26346, + "ĠPleistocene": 26347, + "ĠBetter": 26348, + "ĠHud": 26349, + "ĠBrook": 26350, + "Ġpts": 26351, + "ĠHU": 26352, + "omo": 26353, + "agrass": 26354, + "Ġenvironmentally": 26355, + "Ġhonest": 26356, + "ĠNine": 26357, + "Ġpigments": 26358, + "links": 26359, + "ĠTOP": 26360, + "ĠCytoplasm": 26361, + "Gib": 26362, + "Ġaccessing": 26363, + "mias": 26364, + "Ġexplosive": 26365, + "Ġreside": 26366, + "artan": 26367, + "Ġtransitional": 26368, + "Ġunprecedented": 26369, + "Ġrom": 26370, + "ĠTNFα": 26371, + "Ġprecipitated": 26372, + "Ġtie": 26373, + "ISS": 26374, + "Ġthicker": 26375, + "ĠLatent": 26376, + "ĠValueError": 26377, + "dq": 26378, + "dma": 26379, + "Ġchromatic": 26380, + "ĠSubsection": 26381, + "ĠFACS": 26382, + "Ġrenormalized": 26383, + "Prop": 26384, + "mTOR": 26385, + "ĠHCO": 26386, + "Ġoverlo": 26387, + "bsiella": 26388, + "ylobacter": 26389, + "Ġneuroimaging": 26390, + "Ġassemblage": 26391, + "Ġexpands": 26392, + "ĠîĪ": 26393, + "ĠFun": 26394, + "Ġcitation": 26395, + "IKV": 26396, + "Ġtroops": 26397, + "inistic": 26398, + "Ġcubes": 26399, + "Ġfont": 26400, + "ĠHos": 26401, + "geries": 26402, + "Ġsuccessively": 26403, + "Ġdecoherence": 26404, + "Springer": 26405, + "hin": 26406, + "atine": 26407, + "ĠâĪ¥": 26408, + "SAS": 26409, + "ét": 26410, + "ĠSediment": 26411, + "uously": 26412, + "ĠWars": 26413, + "indicated": 26414, + "Ġflask": 26415, + "AIDS": 26416, + "Ġcra": 26417, + "ĠLot": 26418, + "Ġprimal": 26419, + "Ġjustice": 26420, + "zag": 26421, + "Ġmaxillary": 26422, + "Ġgeneralizations": 26423, + "uela": 26424, + "Ġtagging": 26425, + "Ġpupil": 26426, + "Ġinexpensive": 26427, + "Ġwatch": 26428, + "ĠAMD": 26429, + "ĠFir": 26430, + "Ġneuroblastoma": 26431, + "Ġmaximizes": 26432, + "ĠObserved": 26433, + "mixture": 26434, + "Ġopportunistic": 26435, + "trial": 26436, + "ahan": 26437, + "Ġïģ¬": 26438, + "Ġcatar": 26439, + "ĠControls": 26440, + "ĠNewman": 26441, + "Ġmicrostructural": 26442, + "borns": 26443, + "Ġoxygenation": 26444, + "ĠMacro": 26445, + "ĠJak": 26446, + "plicating": 26447, + "Ġoligodend": 26448, + "Ġresorption": 26449, + "Ġdorm": 26450, + "Ġsolvers": 26451, + "ĠKruskal": 26452, + "ĠRevolution": 26453, + "ĠGastro": 26454, + "Driven": 26455, + "Ġtiter": 26456, + "Ġori": 26457, + "ĠPCL": 26458, + "Ġwetlands": 26459, + "Ġarticular": 26460, + "CCA": 26461, + "enoic": 26462, + "Ġtrick": 26463, + "operiod": 26464, + "ĠCochrane": 26465, + "aday": 26466, + "ĠCerebral": 26467, + "Ġmodulators": 26468, + "ĠSSC": 26469, + "Ġactivations": 26470, + "Ġadapting": 26471, + "ĠScalable": 26472, + "none": 26473, + "pip": 26474, + "Ġprivi": 26475, + "ĠPseudo": 26476, + "Ġdisappears": 26477, + "ĠEur": 26478, + "Ġunconstrained": 26479, + "Ġsubmit": 26480, + "Ġreputation": 26481, + "atar": 26482, + "ĠBai": 26483, + "arians": 26484, + "ĠIntracellular": 26485, + "trees": 26486, + "Ġwetting": 26487, + "ĠFrances": 26488, + "Ġeligibility": 26489, + "folder": 26490, + "ĠStaff": 26491, + "oki": 26492, + "Ġstrengthened": 26493, + "ĠCob": 26494, + "teral": 26495, + "ĠYeast": 26496, + "bye": 26497, + "decoder": 26498, + "Ġrainbow": 26499, + "perturbed": 26500, + "vc": 26501, + "Ġsupplemental": 26502, + "Ġbirths": 26503, + "WO": 26504, + "conc": 26505, + "stitution": 26506, + "hybrid": 26507, + "Ġki": 26508, + "Ġhypere": 26509, + "ĠSMA": 26510, + "formula": 26511, + "Ġundefined": 26512, + "naphth": 26513, + "Ġdeclining": 26514, + "Ġshielding": 26515, + "Yau": 26516, + "Ġrever": 26517, + "ĠWilk": 26518, + "Ġdecimal": 26519, + "HCO": 26520, + "angered": 26521, + "Ġerythrocyte": 26522, + "ĉĉĠĠĠ": 26523, + "nuclear": 26524, + "Ġabnormality": 26525, + "Pres": 26526, + "Participants": 26527, + "ĠWagner": 26528, + "Ġfibrils": 26529, + "Ġfetus": 26530, + "ĠExpress": 26531, + "request": 26532, + "minimum": 26533, + "ĠBooks": 26534, + "hetamine": 26535, + "ushes": 26536, + "ĠBach": 26537, + "ĠDOS": 26538, + "lectric": 26539, + "ĠTween": 26540, + "ĠHughes": 26541, + "Ġmartens": 26542, + "Ġnematic": 26543, + "Ġexperimentation": 26544, + "ĠParker": 26545, + "Ġepisodic": 26546, + "Ġtelem": 26547, + "ADE": 26548, + "columns": 26549, + "Ġfundamentally": 26550, + "enet": 26551, + "ĠVl": 26552, + "earth": 26553, + "Ġquantile": 26554, + "ĠReplication": 26555, + "Ġcleared": 26556, + "Energy": 26557, + "Smith": 26558, + "Ġantidepressant": 26559, + "mx": 26560, + "pmod": 26561, + "amid": 26562, + "Ġserotype": 26563, + "Ġundergraduate": 26564, + "ĠArizona": 26565, + "Ġpushed": 26566, + "ulu": 26567, + "ĠNIC": 26568, + "Ġrheological": 26569, + "omegal": 26570, + "ĠQing": 26571, + "orch": 26572, + "irmed": 26573, + "ĠQuery": 26574, + "Ġsandwich": 26575, + "Ġclinician": 26576, + "ĠElliptic": 26577, + "ĠMeh": 26578, + "DEV": 26579, + "ĠDetermining": 26580, + "alcogen": 26581, + "bench": 26582, + "azep": 26583, + "ĠMississ": 26584, + "tizing": 26585, + "ĠRBC": 26586, + "Ġofficials": 26587, + "Tag": 26588, + "kT": 26589, + "luence": 26590, + "ĠRoom": 26591, + "Ġlectin": 26592, + "bara": 26593, + "kyl": 26594, + "OND": 26595, + "ĠDose": 26596, + "Ġprism": 26597, + "Ġreductive": 26598, + "ĠSpectroscopic": 26599, + "odied": 26600, + "colone": 26601, + "ĠCONFIG": 26602, + "Ġbrittle": 26603, + "inverse": 26604, + "ĠBuff": 26605, + "ytocin": 26606, + "Ġformations": 26607, + "ĠConventional": 26608, + "prev": 26609, + "Ġferrite": 26610, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 26611, + "Ġadopts": 26612, + "ĠMiocene": 26613, + "management": 26614, + "ĠCRF": 26615, + "ĠHelm": 26616, + "Ġdoubled": 26617, + "ĠEFFECT": 26618, + "Ġdance": 26619, + "structions": 26620, + "rait": 26621, + "ifers": 26622, + "ellip": 26623, + "utting": 26624, + "prof": 26625, + "ĠQin": 26626, + "Ġabsc": 26627, + "Ġexploits": 26628, + "Ġcyber": 26629, + "definition": 26630, + "ĠCoronary": 26631, + "Ġdeterg": 26632, + "ĠPerception": 26633, + "ĠCurves": 26634, + "Ġnematodes": 26635, + "Ġlistening": 26636, + "Ġcatalase": 26637, + "Coll": 26638, + "ré": 26639, + "islative": 26640, + "Ġarriving": 26641, + "Ġviolating": 26642, + "д": 26643, + "hetics": 26644, + "ĠJar": 26645, + "concept": 26646, + "Ġbrush": 26647, + "immunity": 26648, + "Ġfingerprint": 26649, + "resid": 26650, + "Ġelevations": 26651, + "ockets": 26652, + "Ġcatechol": 26653, + "иÑ": 26654, + "Ġprecipitates": 26655, + "Ġsoccer": 26656, + "insulin": 26657, + "Ġpursue": 26658, + "ĠICA": 26659, + "ĠPolice": 26660, + "ĠMurphy": 26661, + "Task": 26662, + "ĠCoc": 26663, + "ĠHabit": 26664, + "ĠKP": 26665, + "Ġfloral": 26666, + "Ġhun": 26667, + "Ġhydrogenation": 26668, + "Ġspong": 26669, + "Ġchimeric": 26670, + "ĠKoch": 26671, + "gon": 26672, + "ĠSchur": 26673, + "ĠGreater": 26674, + "RX": 26675, + "Ġcing": 26676, + "ĠWaltham": 26677, + "angling": 26678, + "Ġcounties": 26679, + "Ġlamina": 26680, + "Ġcouncil": 26681, + "sort": 26682, + "ĠBarc": 26683, + "ĠDow": 26684, + "ĠZeng": 26685, + "Ġdevised": 26686, + "uitable": 26687, + "Ġmethylene": 26688, + "Ġsuperiority": 26689, + "Ġepidermis": 26690, + "Ġprag": 26691, + "ĠPED": 26692, + "threatening": 26693, + "ishi": 26694, + "Ġepsilon": 26695, + "address": 26696, + "ENTAL": 26697, + "ĠBle": 26698, + "ĠAntonio": 26699, + "oother": 26700, + "ĠAgar": 26701, + "Ġneighborhoods": 26702, + "Ġshortened": 26703, + "STATE": 26704, + "ĠSerial": 26705, + "MAR": 26706, + "OU": 26707, + "Ġencapsulation": 26708, + "ĠConsortium": 26709, + "Dr": 26710, + "profile": 26711, + "Ġemitter": 26712, + "Ġnecrotic": 26713, + "ĠAutonomous": 26714, + "ĠPhosphorylation": 26715, + "minim": 26716, + "anthin": 26717, + "ĠSph": 26718, + "ĠGur": 26719, + "dihydroxy": 26720, + "distributed": 26721, + "ĠRPMI": 26722, + "stones": 26723, + "Ġhyperfine": 26724, + "Ġislet": 26725, + "ĠSlo": 26726, + "pletely": 26727, + "Ġinactivated": 26728, + "ĠAgriculture": 26729, + "Ġtremend": 26730, + "Ġeveryone": 26731, + "omponent": 26732, + "ZnO": 26733, + "MPI": 26734, + "ĠDiamond": 26735, + "Ġ⣨": 26736, + "Cost": 26737, + "Ġdisabilities": 26738, + "inver": 26739, + "ĠCensus": 26740, + "echo": 26741, + "Ġvegetative": 26742, + "Ġwillingness": 26743, + "Ġrecap": 26744, + "ĠConstraint": 26745, + "ĠPatrick": 26746, + "Ġovert": 26747, + "Ġmoieties": 26748, + "orax": 26749, + "ippi": 26750, + "Direct": 26751, + "Ġcaries": 26752, + "Ġlocalities": 26753, + "lattices": 26754, + "ĠExploration": 26755, + "ĠAW": 26756, + "Ġlocking": 26757, + "Ġcoincident": 26758, + "Ġmultimedia": 26759, + "Ġtemporarily": 26760, + "ĠCaus": 26761, + "encia": 26762, + "Ġweathering": 26763, + "ĠHelicobacter": 26764, + "ĠThings": 26765, + "hips": 26766, + "moving": 26767, + "Ġsigmoid": 26768, + "isin": 26769, + "ĠBec": 26770, + "Ġmicrograms": 26771, + "bounds": 26772, + "ĠColumn": 26773, + "Ġcommuting": 26774, + "ĠJen": 26775, + "Ġhourly": 26776, + "MSC": 26777, + "Ġattendance": 26778, + "ĠâIJ£": 26779, + "ĠEO": 26780, + "prog": 26781, + "Ġrapamycin": 26782, + "ĠPredictors": 26783, + "ĠRetrieved": 26784, + "Ġsubspecies": 26785, + "Ġderives": 26786, + "ĠĤ": 26787, + "ĠGenerating": 26788, + "anners": 26789, + "Ġvolat": 26790, + "Ġvisiting": 26791, + "ĠCalculations": 26792, + "ña": 26793, + "Ġdesert": 26794, + "Ġexpectancy": 26795, + "BMCs": 26796, + "ĠExplo": 26797, + "Ġtravelling": 26798, + "icum": 26799, + "Ġsubdivision": 26800, + "Ġcrosslinking": 26801, + "benzoth": 26802, + "ĠTon": 26803, + "REN": 26804, + "Ġleth": 26805, + "rabbit": 26806, + "ĠAbove": 26807, + "ulted": 26808, + "Ġconstric": 26809, + "Jones": 26810, + "zhou": 26811, + "vern": 26812, + "ĠLady": 26813, + "ĠBuffer": 26814, + "ĠControlling": 26815, + "Ġmultiscale": 26816, + "nikov": 26817, + "acycl": 26818, + "Ġprosthesis": 26819, + "Af": 26820, + "ĠCorps": 26821, + "structed": 26822, + "Grid": 26823, + "inning": 26824, + "olding": 26825, + "Ġthiol": 26826, + "ikov": 26827, + "âĢ¢âĢ¢âĢ¢": 26828, + "Ġgovernments": 26829, + "rapping": 26830, + "Ġthrombocyt": 26831, + "Leg": 26832, + "RY": 26833, + "ĠIceland": 26834, + "ocycle": 26835, + "ĠMemorial": 26836, + "got": 26837, + "Ġidem": 26838, + "ĠBuild": 26839, + "olipoprotein": 26840, + "DV": 26841, + "Ġphthal": 26842, + "richment": 26843, + "ĠHaem": 26844, + "Ġanswering": 26845, + "ĠIJ": 26846, + "Ġtransgene": 26847, + "Ġrenamed": 26848, + "ĠImageJ": 26849, + "Ġcassette": 26850, + "Ġcoalescence": 26851, + "Ġcompaction": 26852, + "Ġwildlife": 26853, + "Ġwins": 26854, + "Ġsupernovae": 26855, + "enteric": 26856, + "isphere": 26857, + "Ġtracker": 26858, + "Ġevidences": 26859, + "Ġcomorbidity": 26860, + "ĠRules": 26861, + "phasing": 26862, + "ĠLangevin": 26863, + "ĠFit": 26864, + "Ġpsychiat": 26865, + "Ġbreakthrough": 26866, + "Ġcholinergic": 26867, + "ĠMetall": 26868, + "breeding": 26869, + "itinib": 26870, + "Ġsolo": 26871, + "abling": 26872, + "elief": 26873, + "oscill": 26874, + "rev": 26875, + "arya": 26876, + "Ġgoodness": 26877, + "ĠPBE": 26878, + "Ġawards": 26879, + "Ġcrani": 26880, + "Ġphotograp": 26881, + "arents": 26882, + "Ġfixes": 26883, + "rÃŃ": 26884, + "assuming": 26885, + "Ġcongruent": 26886, + "ĠMother": 26887, + "ĠNap": 26888, + "ĠProc": 26889, + "Ġcategorization": 26890, + "inch": 26891, + "ĠHorm": 26892, + "ĠInterventions": 26893, + "Ġnonequilibrium": 26894, + "Ġencrypted": 26895, + "primary": 26896, + "iens": 26897, + "lac": 26898, + "rams": 26899, + "Ġboards": 26900, + "ĠHell": 26901, + "charged": 26902, + "Ġperioperative": 26903, + "emp": 26904, + "ĠInvolvement": 26905, + "Russ": 26906, + "univers": 26907, + "ĠDJ": 26908, + "Ġdisagreement": 26909, + "Ġpert": 26910, + "Ġstroma": 26911, + "Ġcalcite": 26912, + "Ġrotary": 26913, + "Ġmethyltransferase": 26914, + "Ġancestry": 26915, + "ĠWitten": 26916, + "CRC": 26917, + "uretic": 26918, + "ophyta": 26919, + "provided": 26920, + "Ġcorrespondingly": 26921, + "bigcap": 26922, + "ĠAgilent": 26923, + "ë": 26924, + "rooms": 26925, + "Ġdisent": 26926, + "Ġdilutions": 26927, + "ĠMyel": 26928, + "Ġquasar": 26929, + "Ġtilted": 26930, + "Ġinternalization": 26931, + "ĠPrivate": 26932, + "ĠFriedman": 26933, + "Ġseventh": 26934, + "ĠClosed": 26935, + "CTC": 26936, + "gren": 26937, + "ĠColombia": 26938, + "odel": 26939, + "Ġpolitics": 26940, + "ĠMSSM": 26941, + "Ġmate": 26942, + "Ġcommod": 26943, + "ĠRus": 26944, + "Ġanesthetized": 26945, + "together": 26946, + "ĠBCS": 26947, + "ewski": 26948, + "romagnet": 26949, + "ĠCun": 26950, + "Ġcurative": 26951, + "Ġimputation": 26952, + "Ġcarbide": 26953, + "DFT": 26954, + "nsic": 26955, + "bee": 26956, + "Ġsplen": 26957, + "ĠMaryland": 26958, + "Ġoligonucleotide": 26959, + "ĠVeget": 26960, + "buffered": 26961, + "National": 26962, + "letic": 26963, + "ĠSyl": 26964, + "Ġseab": 26965, + "ardial": 26966, + "Ġportray": 26967, + "Ġaberrations": 26968, + "Ġstorms": 26969, + "ĠShan": 26970, + "ĠGenBank": 26971, + "issa": 26972, + "Ġcet": 26973, + "Ġbench": 26974, + "ĠRecommendations": 26975, + "Ġtriples": 26976, + "Ġïĥ¥": 26977, + "ĠNeuros": 26978, + "Ġdiscom": 26979, + "season": 26980, + "ĠExec": 26981, + "changing": 26982, + "Ġarrives": 26983, + "Hash": 26984, + "mRNA": 26985, + "Ġfric": 26986, + "asa": 26987, + "obia": 26988, + "Ġpostsynaptic": 26989, + "optimizer": 26990, + "ĠClouds": 26991, + "Ġhypersensitivity": 26992, + "vacc": 26993, + "ĠSig": 26994, + "philic": 26995, + "Ġgrounded": 26996, + "ĠWan": 26997, + "ĠCalabi": 26998, + "ĠMachines": 26999, + "Ġaxisymmetric": 27000, + "ĠSteve": 27001, + "Ġpulled": 27002, + "ĠExcel": 27003, + "Ġdiamonds": 27004, + "KR": 27005, + "West": 27006, + "ĠDest": 27007, + "Ġannular": 27008, + "Ġarchive": 27009, + "Ġparenchyma": 27010, + "ĠEH": 27011, + "ópez": 27012, + "Ġunpublished": 27013, + "Ġsoutheastern": 27014, + "Ġnests": 27015, + "dimensions": 27016, + "latitude": 27017, + "Orig": 27018, + "eced": 27019, + "ĠDraw": 27020, + "redshift": 27021, + "Ġamyl": 27022, + "omyelitis": 27023, + "Why": 27024, + "caro": 27025, + "iq": 27026, + "assess": 27027, + "ĠContin": 27028, + "Ġchirality": 27029, + "matical": 27030, + "Ġchaperone": 27031, + "Ġendometriosis": 27032, + "relu": 27033, + "Ġconverged": 27034, + "broad": 27035, + "ĠIterative": 27036, + "Ġvasculature": 27037, + "fund": 27038, + "ĠFly": 27039, + "Ġantigenic": 27040, + "Ġmeningitis": 27041, + "Ġentails": 27042, + "horn": 27043, + "Ġlocomotor": 27044, + "izard": 27045, + "Ġuneven": 27046, + "parity": 27047, + "packet": 27048, + "tubulin": 27049, + "Ġsewage": 27050, + "Ġdecentralized": 27051, + "Ġgrafted": 27052, + "Ġsep": 27053, + "ĠExtensive": 27054, + "Ġspline": 27055, + "quer": 27056, + "archit": 27057, + "Ġprimate": 27058, + "Ġïģ±": 27059, + "pyrimidin": 27060, + "ĠSAP": 27061, + "Ġunderlie": 27062, + "Ġanalyzes": 27063, + "ĠCCA": 27064, + "recogn": 27065, + "IPT": 27066, + "Different": 27067, + "ĠTEST": 27068, + "Ġunfavorable": 27069, + "edic": 27070, + "ĠAbnormal": 27071, + "pyrimidine": 27072, + "urine": 27073, + "embedded": 27074, + "varies": 27075, + "otropin": 27076, + "Ġsemen": 27077, + "Ġtransmittance": 27078, + "Ġabras": 27079, + "Ġó¸Ģł": 27080, + "Ġtriglyceride": 27081, + "bundle": 27082, + "ĠYb": 27083, + "ĠCarr": 27084, + "Ġnaming": 27085, + "Weight": 27086, + "Ġcondensates": 27087, + "Ġnos": 27088, + "amard": 27089, + "vertices": 27090, + "ELS": 27091, + "idone": 27092, + "Ġcontest": 27093, + "Ġheading": 27094, + "ĠGalerkin": 27095, + "GV": 27096, + "ĠGli": 27097, + "Ġfermented": 27098, + "Ġbilingual": 27099, + "Ġticks": 27100, + "Ġkary": 27101, + "ragal": 27102, + "ĠAber": 27103, + "ĠYouTube": 27104, + "UCTURE": 27105, + "branch": 27106, + "ر": 27107, + "ĠFH": 27108, + "onoi": 27109, + "imotor": 27110, + "Ġverifying": 27111, + "ĠConceptual": 27112, + "ĠDeterminants": 27113, + "urm": 27114, + "uronic": 27115, + "ĠKau": 27116, + "ĠConformal": 27117, + "Ġdropping": 27118, + "ĠFlows": 27119, + "gluon": 27120, + "again": 27121, + "ĠMRSA": 27122, + "warf": 27123, + "Ġemphasizes": 27124, + "Entry": 27125, + "ĠASP": 27126, + "resol": 27127, + "ventricular": 27128, + "ĠâĨĶ": 27129, + "Ġoverexpressing": 27130, + "omegalovirus": 27131, + "inoc": 27132, + "SCO": 27133, + "ĠPARP": 27134, + "ĠSchul": 27135, + "ĠCamb": 27136, + "ĠPod": 27137, + "ĠPun": 27138, + "ĠCompetition": 27139, + "ĠGATA": 27140, + "Ġmoon": 27141, + "Ġputs": 27142, + "angiogenic": 27143, + "ĠRepublican": 27144, + "ĠUbiqu": 27145, + "eys": 27146, + "ĠGong": 27147, + "arger": 27148, + "ĠIntermediate": 27149, + "Ġinterpolated": 27150, + "Ġenlargement": 27151, + "Ġinstruct": 27152, + "Ġrc": 27153, + "dioxo": 27154, + "eye": 27155, + "ĠCarls": 27156, + "ĠMeasured": 27157, + "ircles": 27158, + "ĠRaf": 27159, + "Ġarb": 27160, + "examples": 27161, + "Mi": 27162, + "ĠStern": 27163, + "ĠFK": 27164, + "Ġmillisecond": 27165, + "ĠIRF": 27166, + "ĠEpithelial": 27167, + "edicine": 27168, + "eles": 27169, + "sig": 27170, + "âĪĢ": 27171, + "ĠWiener": 27172, + "bauer": 27173, + "ouses": 27174, + "Ġcoloured": 27175, + "ĠIncrease": 27176, + "Ġtriglycerides": 27177, + "Ġaegypti": 27178, + "ĠNumerous": 27179, + "Ġretardation": 27180, + "Ġintercellular": 27181, + "ĠKlebsiella": 27182, + "ĠDra": 27183, + "ĠDIC": 27184, + "ĠThreshold": 27185, + "rainment": 27186, + "Ġreproducing": 27187, + "Ġulcers": 27188, + "Ġarousal": 27189, + "ĠHills": 27190, + "Ġcalves": 27191, + "ĠReservoir": 27192, + "ĠRadar": 27193, + "Ġpsychosis": 27194, + "ĠFORM": 27195, + "duration": 27196, + "ĠAcademic": 27197, + "catal": 27198, + "olla": 27199, + "olol": 27200, + "ĠCron": 27201, + "iko": 27202, + "Ġextremes": 27203, + "ĠTrypan": 27204, + "Ġbip": 27205, + "Ġalginate": 27206, + "ĠHoch": 27207, + "ĠBennett": 27208, + "ĠHippocamp": 27209, + "ĠGeological": 27210, + "Nevertheless": 27211, + "ĠHes": 27212, + "ĠAdding": 27213, + "Ġexternally": 27214, + "Ġslag": 27215, + "Ġteach": 27216, + "ĠStanley": 27217, + "controller": 27218, + "ĠUnits": 27219, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 27220, + "Ġaerodynamic": 27221, + "ovalent": 27222, + "cube": 27223, + "ÅŁ": 27224, + "require": 27225, + "romolecules": 27226, + "irteen": 27227, + "Ġclauses": 27228, + "Ġdefeat": 27229, + "policy": 27230, + "Ġfaithful": 27231, + "Ġpq": 27232, + "ĠTanaka": 27233, + "ĠEver": 27234, + "Ġunpredict": 27235, + "auty": 27236, + "ĠGALAX": 27237, + "Ġtide": 27238, + "ĠFiltering": 27239, + "Ġeuthan": 27240, + "merce": 27241, + "DEX": 27242, + "Ġnesting": 27243, + "DN": 27244, + "IRT": 27245, + "ĠThr": 27246, + "tissue": 27247, + "Ġpalae": 27248, + "Ï©": 27249, + "Ġdilated": 27250, + "Ġpinning": 27251, + "Rb": 27252, + "ĠSap": 27253, + "ragonal": 27254, + "ĠSPR": 27255, + "ĠDial": 27256, + "Ġacupuncture": 27257, + "diameter": 27258, + "ĠPCB": 27259, + "Parameters": 27260, + "ĠProfiles": 27261, + "transfected": 27262, + "liter": 27263, + "ĠRights": 27264, + "Ġcontributor": 27265, + "ĠCorrel": 27266, + "Ġregressions": 27267, + "Ġsegmental": 27268, + "Shape": 27269, + "IAN": 27270, + "ecom": 27271, + "comings": 27272, + "Ġhemorrhagic": 27273, + "opos": 27274, + "Ġrefraction": 27275, + "PFC": 27276, + "proj": 27277, + "ovo": 27278, + "ĠDerived": 27279, + "Ġundirected": 27280, + "Ġlos": 27281, + "Ġengaging": 27282, + "cans": 27283, + "Ġdestructive": 27284, + "Pop": 27285, + "Ġmakers": 27286, + "ĠWor": 27287, + "ĠAreas": 27288, + "vasion": 27289, + "Ġparaformaldehyde": 27290, + "abinoid": 27291, + "cpy": 27292, + "proxim": 27293, + "Ġenamel": 27294, + "Ġpaediatric": 27295, + "ĠChildhood": 27296, + "Ġpectin": 27297, + "ofilm": 27298, + "Ġcarboxylic": 27299, + "Ġausten": 27300, + "Ġunequal": 27301, + "ĠCountry": 27302, + "Ġiterated": 27303, + "Ġflanking": 27304, + "Ġtraction": 27305, + "anson": 27306, + "iscus": 27307, + "ĠDavies": 27308, + "raham": 27309, + "terozoic": 27310, + "ĠBrass": 27311, + "Oc": 27312, + "Ġunification": 27313, + "meter": 27314, + "ĠNeon": 27315, + "building": 27316, + "icting": 27317, + "Ġjustification": 27318, + "Prior": 27319, + "Ġfirms": 27320, + "Ġeducated": 27321, + "Ġintersecting": 27322, + "Ġboosting": 27323, + "Pass": 27324, + "member": 27325, + "contains": 27326, + "rano": 27327, + "relax": 27328, + "ĠCollaborative": 27329, + "Ġpx": 27330, + "Ġseeding": 27331, + "cripts": 27332, + "inez": 27333, + "omeres": 27334, + "Ġsiblings": 27335, + "anging": 27336, + "fert": 27337, + "Ġrecovering": 27338, + "pure": 27339, + "Ġsd": 27340, + "ĠVul": 27341, + "pedance": 27342, + "Ġfighting": 27343, + "Super": 27344, + "ĠIto": 27345, + "Ġperimeter": 27346, + "ĠInhibitors": 27347, + "electrode": 27348, + "enabled": 27349, + "fb": 27350, + "ĠPCs": 27351, + "Ġnausea": 27352, + "ĠConversion": 27353, + "Ġsla": 27354, + "Ġinvertebrates": 27355, + "ĠBrian": 27356, + "Ġcontiguous": 27357, + "ĠACKNOWLEDGM": 27358, + "urface": 27359, + "Ġcoars": 27360, + "ĠLeh": 27361, + "ĠCompression": 27362, + "cycles": 27363, + "Ġsinh": 27364, + "ĠOccup": 27365, + "strength": 27366, + "Ġconstr": 27367, + "Ġpesticide": 27368, + "Ġbisp": 27369, + "ĠTn": 27370, + "Ġparentheses": 27371, + "degrad": 27372, + "Ġhyperglycemia": 27373, + "PW": 27374, + "kj": 27375, + "ecological": 27376, + "Ġthy": 27377, + "Ġeleg": 27378, + "ĠSynaptic": 27379, + "scaled": 27380, + "tity": 27381, + "Ġequity": 27382, + "Ġblockchain": 27383, + "ĠLithium": 27384, + "Ġspark": 27385, + "Ġentitled": 27386, + "Ġconventions": 27387, + "Argument": 27388, + "Ġretail": 27389, + "Ġneoplastic": 27390, + "Ġdamped": 27391, + "ĠSurveillance": 27392, + "ĠAnna": 27393, + "Ġspacetimes": 27394, + "inges": 27395, + "ahashi": 27396, + "ĠInfections": 27397, + "Ġneglecting": 27398, + "Ġevaporated": 27399, + "vastatin": 27400, + "Ġgh": 27401, + "ĠNLP": 27402, + "Ġphones": 27403, + "Ġlifted": 27404, + "Ġdivisible": 27405, + "Ġdurability": 27406, + "osited": 27407, + "Ġexcitability": 27408, + "Ġbuoyancy": 27409, + "Ġuncontrolled": 27410, + "bran": 27411, + "ĠPhe": 27412, + "Ġimmunocomp": 27413, + "Ġeventual": 27414, + "Ġclassroom": 27415, + "Ġmicrographs": 27416, + "Ġrecharge": 27417, + "ettes": 27418, + "ĠDiver": 27419, + "ĠDall": 27420, + "Ġmetac": 27421, + "Ġneuroendocrine": 27422, + "topology": 27423, + "ĠHawking": 27424, + "omson": 27425, + "ĠHarry": 27426, + "mouth": 27427, + "Ġdeciding": 27428, + "Ġuncovered": 27429, + "Ġgolden": 27430, + "ĠCastle": 27431, + "Ġfiducial": 27432, + "Aware": 27433, + "ĠGan": 27434, + "erahertz": 27435, + "ĠSaturn": 27436, + "LN": 27437, + "Unit": 27438, + "ĥĹ": 27439, + "Ġbinder": 27440, + "INFO": 27441, + "ĠTemper": 27442, + "ipel": 27443, + "Ġnumerator": 27444, + "Ġwebsites": 27445, + "Ġthreatened": 27446, + "Ġremnants": 27447, + "ĠFinnish": 27448, + "hof": 27449, + "media": 27450, + "concentration": 27451, + "ĠReed": 27452, + "ĠLeishmania": 27453, + "Ġmultifunctional": 27454, + "racy": 27455, + "Ġdistribute": 27456, + "ĠDecay": 27457, + "Ġgrinding": 27458, + "Loss": 27459, + "MPL": 27460, + "ĠLakes": 27461, + "ĠQR": 27462, + "ĠStructured": 27463, + "ĠMalaria": 27464, + "Ġflavonoid": 27465, + "Ġtowns": 27466, + "opia": 27467, + "ĠVec": 27468, + "othy": 27469, + "Ġsingles": 27470, + "Ġpenetrate": 27471, + "ĠPig": 27472, + "ieved": 27473, + "Ġderivations": 27474, + "Ġdiscomfort": 27475, + "afenib": 27476, + "ĠLegendre": 27477, + "ĠPax": 27478, + "ĠMX": 27479, + "ĠExtrem": 27480, + "ĠForeign": 27481, + "ĠCourse": 27482, + "ĠHit": 27483, + "vage": 27484, + "Ġclique": 27485, + "Ġcompensatory": 27486, + "User": 27487, + "Ġdraws": 27488, + "ĠProtective": 27489, + "Ġallocate": 27490, + "ĠPant": 27491, + "Ġdash": 27492, + "Ġparal": 27493, + "ĠCirculating": 27494, + "ĠHistone": 27495, + "ĠÅ«": 27496, + "Ġprojec": 27497, + "ĠAAA": 27498, + "ĠPMS": 27499, + "glacial": 27500, + "ĠMeeting": 27501, + "ĠAntibiotic": 27502, + "ategorical": 27503, + "Ġattenuate": 27504, + "Power": 27505, + "owicz": 27506, + "ĠDefault": 27507, + "Ġmarsh": 27508, + "plasm": 27509, + "ĠPathology": 27510, + "ĠEf": 27511, + "Lys": 27512, + "flies": 27513, + "Ġinterviewed": 27514, + "ĠQA": 27515, + "Ġimpuls": 27516, + "Ġpapillary": 27517, + "dR": 27518, + "uh": 27519, + "ĠJing": 27520, + "Ġrescaled": 27521, + "efficiency": 27522, + "Ġef": 27523, + "ĠEisen": 27524, + "Ġattacked": 27525, + "Ġopto": 27526, + "Ġspeculated": 27527, + "haz": 27528, + "Ġideally": 27529, + "ymenoptera": 27530, + "Ġlr": 27531, + "ĠIz": 27532, + "resource": 27533, + "ĠFacility": 27534, + "ĠAcquisition": 27535, + "Ġpostural": 27536, + "autiful": 27537, + "Ġgingival": 27538, + "Ġpertaining": 27539, + "ĠExtra": 27540, + "ĠProgramme": 27541, + "hesus": 27542, + "fermion": 27543, + "Ġsteadily": 27544, + "Ġterminus": 27545, + "Parser": 27546, + "ĠInclusion": 27547, + "ĠWuhan": 27548, + "Ġrepetitions": 27549, + "done": 27550, + "ĠCep": 27551, + "Ġunstructured": 27552, + "ĠCollectively": 27553, + "Ġsettling": 27554, + "Ġjaw": 27555, + "ĠUni": 27556, + "Ġrestoring": 27557, + "urtles": 27558, + "Full": 27559, + "Ġdynamo": 27560, + "IGO": 27561, + "ĠBAT": 27562, + "ová": 27563, + "venues": 27564, + "ĠPerhaps": 27565, + "sensing": 27566, + "ĠIschem": 27567, + "odemographic": 27568, + "Ss": 27569, + "ĠLund": 27570, + "Ġelite": 27571, + "protocol": 27572, + "ĠChristopher": 27573, + "basic": 27574, + "Ġpuber": 27575, + "Ġmagnetism": 27576, + "vars": 27577, + "inducing": 27578, + "Ġdated": 27579, + "Ġenemy": 27580, + "ĠStop": 27581, + "social": 27582, + "ĠdÏĦ": 27583, + "ĠBun": 27584, + "Small": 27585, + "purpose": 27586, + "Ġhunting": 27587, + "CPU": 27588, + "ĠJunior": 27589, + "REL": 27590, + "Ġcontractile": 27591, + "Ġsilicone": 27592, + "adrenergic": 27593, + "bz": 27594, + "Ġfus": 27595, + "ifted": 27596, + "sep": 27597, + "âĪĴâĪŀ": 27598, + "Ġdrum": 27599, + "----------": 27600, + "ĠTregs": 27601, + "itarian": 27602, + "century": 27603, + "âĬ¥": 27604, + "Numer": 27605, + "ĠBenz": 27606, + "Ġcommunicating": 27607, + "Ġpaternal": 27608, + "ĠFGFR": 27609, + "ĠâĤ¬": 27610, + "Ġdeviate": 27611, + "fre": 27612, + "Ġmolten": 27613, + "Ġstandardization": 27614, + "Ġfunctionalities": 27615, + "ĠPaulo": 27616, + "Ġbucket": 27617, + "ĠConcentrations": 27618, + "ĠKum": 27619, + "Ġmimicking": 27620, + "Drop": 27621, + "zoa": 27622, + "ĠNuclei": 27623, + "brack": 27624, + "ecolor": 27625, + "Ġcarn": 27626, + "Ġveterinary": 27627, + "Ġchemotherapeutic": 27628, + "Ġferment": 27629, + "lasting": 27630, + "ĠRogers": 27631, + "ieri": 27632, + "Ġconverters": 27633, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 27634, + "ĠRepair": 27635, + "Europe": 27636, + "TIME": 27637, + "Ġties": 27638, + "ĠPIN": 27639, + "Ġtribut": 27640, + "Ġhomogenization": 27641, + "excitation": 27642, + "atization": 27643, + "ĠRash": 27644, + "Ġprecession": 27645, + "ás": 27646, + "Ġspiking": 27647, + "ĠGrassmann": 27648, + "minister": 27649, + "Ġfactorial": 27650, + "ĠDeut": 27651, + "sampled": 27652, + "Ġeukaryotes": 27653, + "overlapping": 27654, + "agglut": 27655, + "Ġprescribing": 27656, + "Ġcro": 27657, + "omechanical": 27658, + "iza": 27659, + "ĠManufact": 27660, + "native": 27661, + "ursive": 27662, + "ĠIssues": 27663, + "Ġstreptomycin": 27664, + "endi": 27665, + "ĠSpr": 27666, + "ceq": 27667, + "arginine": 27668, + "ixon": 27669, + "ĠFoundations": 27670, + "Single": 27671, + "Ġoxal": 27672, + "Ġhydrate": 27673, + "Iterator": 27674, + "kii": 27675, + "aminated": 27676, + "Ġsprings": 27677, + "oln": 27678, + "ĠSetup": 27679, + "Ġripening": 27680, + "Ġtheoretic": 27681, + "Ġcfg": 27682, + "μL": 27683, + "Gordon": 27684, + "SK": 27685, + "Ġnations": 27686, + "Query": 27687, + "Ùħ": 27688, + "Ġfores": 27689, + "requencies": 27690, + "ĠPharmaceutical": 27691, + "ĠAllocation": 27692, + "otypical": 27693, + "ĠPilot": 27694, + "thora": 27695, + "ĠVand": 27696, + "Ġsyringe": 27697, + "ĠRAP": 27698, + "rometric": 27699, + "Ġïģ´": 27700, + "Ġcitations": 27701, + "would": 27702, + "Ġnortheastern": 27703, + "comparison": 27704, + "locus": 27705, + "ethe": 27706, + "ĠKB": 27707, + "Ġhomologs": 27708, + "Ġencephalitis": 27709, + "Ġzig": 27710, + "Ġincentive": 27711, + "Ġconfidential": 27712, + "Ġvestibular": 27713, + "ĠOTUs": 27714, + "Ġsynovial": 27715, + "ĠRelativity": 27716, + "Ġsubdivided": 27717, + "chez": 27718, + "Ġlikewise": 27719, + "ĠPDMS": 27720, + "ĠÅł": 27721, + "Ġsocieties": 27722, + "ocyanate": 27723, + "gia": 27724, + "Ġlocalize": 27725, + "Ġlactation": 27726, + "Ġnodule": 27727, + "ĠCOR": 27728, + "Ġharboring": 27729, + "ĠEQU": 27730, + "harvest": 27731, + "Ġbandgap": 27732, + "rk": 27733, + "Ġresistor": 27734, + "Ġye": 27735, + "ĠAsymmetric": 27736, + "Ġpropagators": 27737, + "Ġdiagnosing": 27738, + "ĠAffairs": 27739, + "Ġejecta": 27740, + "Ġisomer": 27741, + "Ġix": 27742, + "Ġfoliation": 27743, + "Ġcapacitors": 27744, + "Ġcad": 27745, + "ĠNeutroph": 27746, + "pliance": 27747, + "Ġcompressible": 27748, + "ĠHunter": 27749, + "ĠMZ": 27750, + "ĠWeib": 27751, + "Ġnoncoding": 27752, + "Ġmountains": 27753, + "Ġadvertising": 27754, + "alez": 27755, + "bright": 27756, + "limsup": 27757, + "Ci": 27758, + "ĠNev": 27759, + "ĠStrains": 27760, + "ostomy": 27761, + "opal": 27762, + "Ġconcatenated": 27763, + "ĠPerf": 27764, + "CHO": 27765, + "Ġturtles": 27766, + "ĠFra": 27767, + "Ġallogeneic": 27768, + "Ġunsuccessful": 27769, + "YM": 27770, + "erver": 27771, + "Ġcuc": 27772, + "Ġfires": 27773, + "chart": 27774, + "Ġinterrupted": 27775, + "Ġdecides": 27776, + "Ġauction": 27777, + "ĠUntil": 27778, + "ĠATG": 27779, + "Ġdiam": 27780, + "magnitude": 27781, + "Ġdl": 27782, + "Vertex": 27783, + "mont": 27784, + "Ġfemtosecond": 27785, + "Params": 27786, + "Ġlysate": 27787, + "ishers": 27788, + "ĠPAT": 27789, + "ĠKev": 27790, + "ĠKnock": 27791, + "Ġgroove": 27792, + "Lu": 27793, + "ĠJohann": 27794, + "Ġreplica": 27795, + "ĠMATERIALS": 27796, + "Ġlots": 27797, + "Ġgenerically": 27798, + "ĠAltered": 27799, + "ĠIdentity": 27800, + "Ġunfolded": 27801, + "CES": 27802, + "ingular": 27803, + "ĠFraction": 27804, + "ĠProliferation": 27805, + "ĠVienna": 27806, + "acia": 27807, + "pless": 27808, + "ĠSevent": 27809, + "Ġturbines": 27810, + "lysine": 27811, + "Ġperoxis": 27812, + "ARP": 27813, + "ĠEpis": 27814, + "ĠSYBR": 27815, + "Builder": 27816, + "Ġspherically": 27817, + "Ġdefend": 27818, + "Performance": 27819, + "Ġmortar": 27820, + "ĠConcepts": 27821, + "works": 27822, + "Ġreinforce": 27823, + "á¹": 27824, + "Ġcus": 27825, + "ĠCIF": 27826, + "ĠAgricultural": 27827, + "crystalline": 27828, + "rish": 27829, + "Ġreferenced": 27830, + "Ġactress": 27831, + "Ġboundedness": 27832, + "SiC": 27833, + "Ġâ": 27834, + "Ġjack": 27835, + "Ġterminate": 27836, + "ĠJA": 27837, + "ĠKrish": 27838, + "MMP": 27839, + "kx": 27840, + "ĠPSR": 27841, + "endl": 27842, + "WHO": 27843, + "ĠSão": 27844, + "ĠCultural": 27845, + "ĠEh": 27846, + "ulis": 27847, + "vik": 27848, + "prises": 27849, + "ixel": 27850, + "ĠMetrics": 27851, + "Ġdiscontinuities": 27852, + "ĠUne": 27853, + "SCR": 27854, + "Ġprojecting": 27855, + "ĠOriginal": 27856, + "ĠHumans": 27857, + "transcriptional": 27858, + "HK": 27859, + "ĠJain": 27860, + "atisfaction": 27861, + "mesenchymal": 27862, + "Ġpyramid": 27863, + "Ġascorbic": 27864, + "game": 27865, + "Ġnoun": 27866, + "otoxins": 27867, + "peptide": 27868, + "Ġglassy": 27869, + "Ġtalking": 27870, + "Dem": 27871, + "ĠSchro": 27872, + "ĠAssumptions": 27873, + "Ġðx": 27874, + "Ġaneurysms": 27875, + "MASS": 27876, + "ĠHou": 27877, + "exposure": 27878, + "ĠLLC": 27879, + "Ġnoises": 27880, + "CTG": 27881, + "ĠElementary": 27882, + "flip": 27883, + "Ġdysp": 27884, + "Ġmessenger": 27885, + "ĠImportant": 27886, + "Ġimposes": 27887, + "Ġorganelles": 27888, + "assertEqual": 27889, + "Ġjustif": 27890, + "ucine": 27891, + "Ġformic": 27892, + "ormalization": 27893, + "ĠRadial": 27894, + "ĠCurve": 27895, + "ĠCrohn": 27896, + "Ġbrowser": 27897, + "Ġeffusion": 27898, + "Ġhandles": 27899, + "varsigma": 27900, + "Ġspecialists": 27901, + "Ġpainful": 27902, + "Ġerythematosus": 27903, + "Ġfen": 27904, + "nitrophenyl": 27905, + "Ġlegacy": 27906, + "ĠQDs": 27907, + "rapper": 27908, + "Ġmonotherapy": 27909, + "ĠBelt": 27910, + "ZZ": 27911, + "Ġsintered": 27912, + "enedi": 27913, + "Hb": 27914, + "tv": 27915, + "ĠNas": 27916, + "ovis": 27917, + "Ġmucin": 27918, + "Ġaccelerates": 27919, + "Ġacquiring": 27920, + "luc": 27921, + "Ġdilaton": 27922, + "ĠPitts": 27923, + "Ġequivariant": 27924, + "ĠLyman": 27925, + "ĠYa": 27926, + "Ġprogressed": 27927, + "ĠAfterwards": 27928, + "ĠCHAR": 27929, + "Don": 27930, + "Ġhistologic": 27931, + "Ġcircuitry": 27932, + "pene": 27933, + "opres": 27934, + "ĠStefan": 27935, + "Ġsemiclassical": 27936, + "mund": 27937, + "ĠWaste": 27938, + "BQ": 27939, + "Ġadiponectin": 27940, + "Ġunseen": 27941, + "Ġbiomechanical": 27942, + "Ġhazardous": 27943, + "ructive": 27944, + "xyl": 27945, + "opf": 27946, + "Ġprion": 27947, + "ĠInfinite": 27948, + "Ġtracers": 27949, + "ĠHarrison": 27950, + "Ġfibrinogen": 27951, + "Ġhydrolys": 27952, + "Ġislets": 27953, + "Ġparallelism": 27954, + "Spect": 27955, + "Ġimperative": 27956, + "Ġcured": 27957, + "ĠDSB": 27958, + "idefinite": 27959, + "icker": 27960, + "Ġdivergences": 27961, + "ĠShapiro": 27962, + "abd": 27963, + "ĠLum": 27964, + "ĠVD": 27965, + "Ġfisheries": 27966, + "geon": 27967, + "copenia": 27968, + "ĠClay": 27969, + "Ġmaximized": 27970, + "ĠGrey": 27971, + "ĠBatch": 27972, + "Ġinfest": 27973, + "Ġample": 27974, + "Ġestate": 27975, + "ĠSupreme": 27976, + "AO": 27977, + "isia": 27978, + "ĠSmad": 27979, + "Carlo": 27980, + "ĠSubst": 27981, + "Ġmonoidal": 27982, + "Ġnumeric": 27983, + "Plot": 27984, + "Ġdystrophy": 27985, + "hypertensive": 27986, + "Ġstool": 27987, + "alsy": 27988, + "Ġcheese": 27989, + "nih": 27990, + "Ġbought": 27991, + "ĠSQ": 27992, + "Ġclues": 27993, + "Ġmeiotic": 27994, + "Ġgoats": 27995, + "ĠGTPase": 27996, + "Ġrescaling": 27997, + "NUM": 27998, + "icing": 27999, + "ĠÄĢ": 28000, + "Ġpretty": 28001, + "ligand": 28002, + "English": 28003, + "ĠIntelligent": 28004, + "Every": 28005, + "ĠPolitical": 28006, + "enton": 28007, + "Ġpassages": 28008, + "ĠRemarks": 28009, + "sb": 28010, + "Network": 28011, + "ĠLRR": 28012, + "Ġcurl": 28013, + "ursion": 28014, + "ĠAver": 28015, + "ĠGLP": 28016, + "heren": 28017, + "atan": 28018, + "ICENSE": 28019, + "Ġlatex": 28020, + "EMI": 28021, + "quasi": 28022, + "ĠOm": 28023, + "Ġreviewing": 28024, + "Background": 28025, + "Ġsom": 28026, + "Ġsnapshots": 28027, + "brow": 28028, + "who": 28029, + "ĠTail": 28030, + "ĠMSM": 28031, + "ĠGm": 28032, + "Ġphi": 28033, + "rency": 28034, + "separated": 28035, + "Ġgig": 28036, + "osides": 28037, + "Ġpean": 28038, + "Ġappealing": 28039, + "PU": 28040, + "nk": 28041, + "Ġquer": 28042, + "ĠCharg": 28043, + "ĠMolecules": 28044, + "localization": 28045, + "Idx": 28046, + "lap": 28047, + "ĠTax": 28048, + "ĠExponential": 28049, + "ĠInhibitor": 28050, + "ĠBiomedical": 28051, + "urethane": 28052, + "lerene": 28053, + "rogenesis": 28054, + "ĠLai": 28055, + "ĠAggregation": 28056, + "ĠCaCl": 28057, + "Ġsensible": 28058, + "Ġconjunc": 28059, + "paper": 28060, + "ĠCovid": 28061, + "ĠProcedures": 28062, + "Ġknew": 28063, + "Ġsetae": 28064, + "ĠAlle": 28065, + "ĠExcept": 28066, + "Ġpresynaptic": 28067, + "flower": 28068, + "Ġultrasonography": 28069, + "Ġentertain": 28070, + "iors": 28071, + "ĠEry": 28072, + "ĠInteger": 28073, + "Ġrepressor": 28074, + "Ġlaterally": 28075, + "Ġcomplemented": 28076, + "TAG": 28077, + "ĠAround": 28078, + "ĠLister": 28079, + "bitrary": 28080, + "backward": 28081, + "MeV": 28082, + "Ġwhisk": 28083, + "AMs": 28084, + "ĠBulk": 28085, + "Ġquiver": 28086, + "Ġdamaging": 28087, + "ĠQuantifying": 28088, + "Ġsuprem": 28089, + "tel": 28090, + "Ġtear": 28091, + "oters": 28092, + "vidin": 28093, + "Ġtubules": 28094, + "Ġipsilateral": 28095, + "isive": 28096, + "Ġsuitably": 28097, + "riel": 28098, + "Ġtuber": 28099, + "Ġfavors": 28100, + "Ġcentim": 28101, + "Ġtransversal": 28102, + "ĠCHO": 28103, + "Ġtrimester": 28104, + "CAC": 28105, + "cognitive": 28106, + "ĠUTC": 28107, + "pute": 28108, + "Ġmidline": 28109, + "amers": 28110, + "evaluation": 28111, + "Dav": 28112, + "Ġbags": 28113, + "timer": 28114, + "Ġshortcomings": 28115, + "ĠErd": 28116, + "Ġdiscriminator": 28117, + "Ant": 28118, + "sizes": 28119, + "Ġbist": 28120, + "ingual": 28121, + "ĠCategory": 28122, + "Ġpulsars": 28123, + "ĠSchwartz": 28124, + "ĠDrop": 28125, + "Sequence": 28126, + "Ġtann": 28127, + "ĠSymptoms": 28128, + "Dict": 28129, + "ĠBlu": 28130, + "Supplemental": 28131, + "Ġdisabled": 28132, + "ĠKoz": 28133, + "Ġinvoked": 28134, + "ĠCQ": 28135, + "ĠConnectivity": 28136, + "Ġtelescopes": 28137, + "oso": 28138, + "Ġphytochemical": 28139, + "Ġorthogonality": 28140, + "Ġinvisible": 28141, + "ĠSCF": 28142, + "ĠAvoid": 28143, + "ĠHus": 28144, + "micron": 28145, + "aternity": 28146, + "Project": 28147, + "Ġadvancing": 28148, + "ĠLorentzian": 28149, + "Sa": 28150, + "tÃŀ": 28151, + "ĠUP": 28152, + "Ġarts": 28153, + "Ġzer": 28154, + "asket": 28155, + "Ġappeal": 28156, + "nick": 28157, + "ĠCloning": 28158, + "Ġswap": 28159, + "Ġphospholipids": 28160, + "bg": 28161, + "othel": 28162, + "asco": 28163, + "Track": 28164, + "Ġsubmanifold": 28165, + "Offset": 28166, + "ĠBird": 28167, + "problems": 28168, + "DCs": 28169, + "Ġdow": 28170, + "Ġdeionized": 28171, + "Ġsubclass": 28172, + "Ġpublishing": 28173, + "ĠCarter": 28174, + "Ġsynergy": 28175, + "Ġweakened": 28176, + "ĠGlas": 28177, + "ĠPie": 28178, + "henko": 28179, + "Ġsetups": 28180, + "ĠBernstein": 28181, + "Ġÿ": 28182, + "ĠShu": 28183, + "ĠChanging": 28184, + "osov": 28185, + "ĠMeteor": 28186, + "inth": 28187, + "rah": 28188, + "paramet": 28189, + "rena": 28190, + "Ġnewborns": 28191, + "ische": 28192, + "rotating": 28193, + "Ġconfident": 28194, + "fac": 28195, + "ĠTerr": 28196, + "Ġlinewidth": 28197, + "ICP": 28198, + "thony": 28199, + "Ġlanes": 28200, + "Ġsmoother": 28201, + "mony": 28202, + "ĠCNNs": 28203, + "Port": 28204, + "Ġtransiently": 28205, + "Ġsurgeries": 28206, + "Ġsubmerged": 28207, + "Ġpuncture": 28208, + "Ġdichlor": 28209, + "Ġsystematics": 28210, + "Ġcontigs": 28211, + "Ġresiding": 28212, + "BW": 28213, + "EO": 28214, + "Gold": 28215, + "ionate": 28216, + "vocab": 28217, + "dW": 28218, + "STAR": 28219, + "ĠPLC": 28220, + "athi": 28221, + "ĠInfectious": 28222, + "Light": 28223, + "á»": 28224, + "ĠRal": 28225, + "Ġpropagates": 28226, + "ĠLikelihood": 28227, + "hill": 28228, + "curl": 28229, + "checkpoint": 28230, + "rax": 28231, + "Ġvancomycin": 28232, + "ĠUSD": 28233, + "opheles": 28234, + "Ġfiltr": 28235, + "Ġstoichiometry": 28236, + "âĶĢâĶĢ": 28237, + "ĠNad": 28238, + "accessible": 28239, + "Ġtoy": 28240, + "Ġnude": 28241, + "ĠSut": 28242, + "essential": 28243, + "ĠOL": 28244, + "Ġpertin": 28245, + "Ġrecur": 28246, + "Ġcapill": 28247, + "Ġcomputable": 28248, + "Ġsuction": 28249, + "Ġsoftening": 28250, + "ĠESI": 28251, + "Ġmonitors": 28252, + "Ġpyridine": 28253, + "ĠSensors": 28254, + "ĠCombinatorial": 28255, + "atta": 28256, + "ĠAMS": 28257, + "ĠDul": 28258, + "pleteness": 28259, + "Eth": 28260, + "Ġû": 28261, + "Ġexcised": 28262, + "ĠDiabetic": 28263, + "ĠIowa": 28264, + "Ġimmunostaining": 28265, + "Ġillnesses": 28266, + "Ġenumer": 28267, + "ĠIranian": 28268, + "Ġthumb": 28269, + "orphisms": 28270, + "Ġlegitimate": 28271, + "lg": 28272, + "ĠSVD": 28273, + "Ġdesk": 28274, + "Format": 28275, + "Bon": 28276, + "Ġgarden": 28277, + "Ġinterpersonal": 28278, + "Ġelbow": 28279, + "ĠDemonstr": 28280, + "Ġnonspecific": 28281, + "Ferm": 28282, + "ivalently": 28283, + "phthalene": 28284, + "ARGET": 28285, + "Valid": 28286, + "Ġsunlight": 28287, + "Ġrescued": 28288, + "DAR": 28289, + "ĠInvariant": 28290, + "Ġidle": 28291, + "Ġalkaloids": 28292, + "scales": 28293, + "ses": 28294, + "obicity": 28295, + "beat": 28296, + "Ġcentrifugal": 28297, + "analytical": 28298, + "pv": 28299, + "Ġtutorial": 28300, + "ĠNation": 28301, + "generator": 28302, + "Ġcollisional": 28303, + "ĠCME": 28304, + "Ġscrap": 28305, + "ĠQSO": 28306, + "Ġwax": 28307, + "ĠScenario": 28308, + "Ġminimizer": 28309, + "ĠMDPI": 28310, + "Ġprostaglandin": 28311, + "olites": 28312, + "ocysteine": 28313, + "Ġcompactification": 28314, + "Ġfrailty": 28315, + "opsin": 28316, + "Ġjunior": 28317, + "loud": 28318, + "Ġtitled": 28319, + "Ġeconomically": 28320, + "thiophene": 28321, + "ĠInvestigating": 28322, + "ĠEsp": 28323, + "Ġelusive": 28324, + "Ġmalware": 28325, + "ĠTHP": 28326, + "imidazole": 28327, + "Ġretains": 28328, + "ĠMIR": 28329, + "ffl": 28330, + "jac": 28331, + "ĠPART": 28332, + "ĠDCM": 28333, + "transport": 28334, + "MAPK": 28335, + "Problem": 28336, + "Su": 28337, + "Ġdelim": 28338, + "Ġpsychometric": 28339, + "vitably": 28340, + "Ġhypergeometric": 28341, + "Ġuterus": 28342, + "Ġanaesthesia": 28343, + "ĠAvenue": 28344, + "Ġmeanings": 28345, + "Ġrapidity": 28346, + "Ġdendrites": 28347, + "grain": 28348, + "ĠNile": 28349, + "Ġfacies": 28350, + "Ġpipelines": 28351, + "ĠCampylobacter": 28352, + "ĠMembers": 28353, + "benzoate": 28354, + "Request": 28355, + "Ġpk": 28356, + "Ġrefused": 28357, + "caus": 28358, + "ĠSay": 28359, + "lane": 28360, + "ĠPSO": 28361, + "Ġgathering": 28362, + "Ġrefriger": 28363, + "RCC": 28364, + "Ġfibronectin": 28365, + "help": 28366, + "ĠIntensity": 28367, + "CLC": 28368, + "Que": 28369, + "elly": 28370, + "Ġilluminated": 28371, + "Ġpedestrian": 28372, + "ĠMercury": 28373, + "Ġafforded": 28374, + "Ġpathophysiological": 28375, + "ĠNGS": 28376, + "assa": 28377, + "Ġendors": 28378, + "Ġsensation": 28379, + "Ġstreamflow": 28380, + "avin": 28381, + "ĠGABAergic": 28382, + "Ġretirement": 28383, + "Cells": 28384, + "oca": 28385, + "Ġoptimizations": 28386, + "Ġdigraph": 28387, + "ĠAutism": 28388, + "octurnal": 28389, + "oscience": 28390, + "ĠEllis": 28391, + "ĠAj": 28392, + "ĠWSN": 28393, + "Ġshooting": 28394, + "iper": 28395, + "îĦĥ": 28396, + "ĠWeather": 28397, + "Ġreceptive": 28398, + "Ġquartic": 28399, + "ocyclic": 28400, + "PATH": 28401, + "sizeof": 28402, + "Ġmelts": 28403, + "Ġdipoles": 28404, + "Ġbimodal": 28405, + "summary": 28406, + "Ġinsomnia": 28407, + "opyran": 28408, + "Ġwrapped": 28409, + "ĠJosé": 28410, + "AH": 28411, + "cia": 28412, + "Ġobeys": 28413, + "ĠKay": 28414, + "intervention": 28415, + "Ġrouter": 28416, + "ĠDrugs": 28417, + "owska": 28418, + "ĠArr": 28419, + "ĠCaptain": 28420, + "ĠTMS": 28421, + "adv": 28422, + "Ġboat": 28423, + "Ġtrusted": 28424, + "sever": 28425, + "illars": 28426, + "ĠMissouri": 28427, + "Ġequivalents": 28428, + "ĠHarvard": 28429, + "ĠClarke": 28430, + "resonant": 28431, + "rady": 28432, + "triggered": 28433, + "Ġcleft": 28434, + "Ġunic": 28435, + "Ġbrainstem": 28436, + "Ġthrombin": 28437, + "ĠFlight": 28438, + "Ġsectional": 28439, + "Ġconcatenation": 28440, + "Ġcantilever": 28441, + "eton": 28442, + "Ġdecode": 28443, + "ofacial": 28444, + "Action": 28445, + "ĠIllustration": 28446, + "vertical": 28447, + "chall": 28448, + "ĠRegistry": 28449, + "MAT": 28450, + "Ġconson": 28451, + "Ġneoadjuvant": 28452, + "ĠWistar": 28453, + "ĠImper": 28454, + "Ġaltitudes": 28455, + "Ġsubpopulation": 28456, + "ĠScene": 28457, + "tensorflow": 28458, + "slow": 28459, + "Ġhint": 28460, + "Ġbeamforming": 28461, + "ein": 28462, + "Ġimpregn": 28463, + "ĠRFID": 28464, + "ĠAnalyzing": 28465, + "ĠPent": 28466, + "ĠDNS": 28467, + "ĠGilbert": 28468, + "Ġcrater": 28469, + "Comparing": 28470, + "Ġbf": 28471, + "Ġflights": 28472, + "Ġmalnutrition": 28473, + "SMC": 28474, + "Ġerythrop": 28475, + "ĠTumors": 28476, + "Tx": 28477, + "Ġisospin": 28478, + "ĠKub": 28479, + "iking": 28480, + "Ġcorticosteroids": 28481, + "ursor": 28482, + "ĠBurg": 28483, + "inspired": 28484, + "ĠIgn": 28485, + "Ġmycel": 28486, + "prediction": 28487, + "methods": 28488, + "Copy": 28489, + "ĠRW": 28490, + "ĠKnight": 28491, + "Ġdemethyl": 28492, + "ìĦ": 28493, + "Ġcili": 28494, + "Ġbes": 28495, + "ĠEck": 28496, + "Ġdilatation": 28497, + "Ġanimation": 28498, + "abstract": 28499, + "Ġcircumvent": 28500, + "Ġinoculum": 28501, + "Seg": 28502, + "ĠCaps": 28503, + "erers": 28504, + "PLS": 28505, + "ĠPeer": 28506, + "Ġverifies": 28507, + "ategy": 28508, + "ogenetics": 28509, + "Ġoligonucleotides": 28510, + "ractical": 28511, + "Ġdiverges": 28512, + "ĠStanford": 28513, + "ĠAi": 28514, + "Ġweighing": 28515, + "Tg": 28516, + "reinfor": 28517, + "ĠAlam": 28518, + "quiry": 28519, + "ĠNob": 28520, + "Ġlinearization": 28521, + "ĠVenez": 28522, + "nexin": 28523, + "levels": 28524, + "Lip": 28525, + "ĠPatel": 28526, + "ĠMagnitude": 28527, + "etitive": 28528, + "ĠEagle": 28529, + "Ġsputum": 28530, + "ĠCOS": 28531, + "Ġincubator": 28532, + "Ul": 28533, + "ĠReceptors": 28534, + "ĠSchott": 28535, + "GCG": 28536, + "ĠZeiss": 28537, + "ĠEntanglement": 28538, + "ĠVaccine": 28539, + "orted": 28540, + "Ġnb": 28541, + "ĠSj": 28542, + "ĠMrs": 28543, + "Ġcalf": 28544, + "Ġintegrability": 28545, + "ĠPhoton": 28546, + "Ġgondii": 28547, + "ĠMIL": 28548, + "Ġaliph": 28549, + "ĠDip": 28550, + "falls": 28551, + "ctrl": 28552, + "ku": 28553, + "etent": 28554, + "plt": 28555, + "Ġpersisted": 28556, + "ĠManager": 28557, + "Ġprerequisite": 28558, + "filling": 28559, + "ĠMEA": 28560, + "Sym": 28561, + "ĠGrain": 28562, + "Ġductal": 28563, + "ĠTODO": 28564, + "Ġaffinities": 28565, + "Ġdegenerative": 28566, + "ĠFitz": 28567, + "ovar": 28568, + "ĠTriple": 28569, + "Ġdendrim": 28570, + "ĠFranklin": 28571, + "mag": 28572, + "otely": 28573, + "Ġstabilizes": 28574, + "Ġcash": 28575, + "ĠSquad": 28576, + "Ġchampion": 28577, + "PDB": 28578, + "Ġurg": 28579, + "Ġalcoholic": 28580, + "Ġtar": 28581, + "yled": 28582, + "Version": 28583, + "Ġsale": 28584, + "ĠMLP": 28585, + "outer": 28586, + "Ġsimplifying": 28587, + "ĠExtract": 28588, + "Param": 28589, + "ĠRestric": 28590, + "Ġtractable": 28591, + "ĠArchive": 28592, + "Response": 28593, + "ADDR": 28594, + "Ġcommutation": 28595, + "Rich": 28596, + "ĠAndrews": 28597, + "Ġosteoclast": 28598, + "romic": 28599, + "ĠShift": 28600, + "Ġaccelerometer": 28601, + "ĠSent": 28602, + "Ġchances": 28603, + "osting": 28604, + "Ġmethacrylate": 28605, + "Ġgluons": 28606, + "Ġôı½": 28607, + "Ġpolygons": 28608, + "ĠRCTs": 28609, + "Ġinfancy": 28610, + "Ġproceeded": 28611, + "ĠHorizontal": 28612, + "COR": 28613, + "Ġcaching": 28614, + "ĠNHS": 28615, + "phobic": 28616, + "ĠXMM": 28617, + "Ġmicrobiological": 28618, + "GMP": 28619, + "ÙĨ": 28620, + "ĠTSS": 28621, + "ĠSul": 28622, + "ĠFact": 28623, + "ĠWE": 28624, + "Ġcertainty": 28625, + "ensitivity": 28626, + "Ġdeconvolution": 28627, + "ĠGain": 28628, + "Ġblots": 28629, + "Ġseeks": 28630, + "Ġcosh": 28631, + "ennessee": 28632, + "Ġslave": 28633, + "ĠTran": 28634, + "Ġtranspose": 28635, + "reated": 28636, + "Ġshading": 28637, + "ĠBU": 28638, + "ĠOV": 28639, + "ĠLook": 28640, + "Ġcomprehensively": 28641, + "ĠFreder": 28642, + "Handler": 28643, + "fibr": 28644, + "Ġmissense": 28645, + "targets": 28646, + "promoting": 28647, + "ĠPep": 28648, + "varpi": 28649, + "ĠHarmonic": 28650, + "ĠAIS": 28651, + "Ġmonocyt": 28652, + "Ġthinning": 28653, + "Ġpheromone": 28654, + "Water": 28655, + "anase": 28656, + "ĠSang": 28657, + "Ġsubstructure": 28658, + "wp": 28659, + "ĠKansas": 28660, + "DEBUG": 28661, + "ĠProbe": 28662, + "Ġpatterned": 28663, + "clean": 28664, + "Ġbroiler": 28665, + "odextrin": 28666, + "aided": 28667, + "oprol": 28668, + "ublin": 28669, + "inum": 28670, + "Ġanatomic": 28671, + "Ġplating": 28672, + "arro": 28673, + "ucal": 28674, + "Ġspeedup": 28675, + "Ġhaemorrh": 28676, + "eptidase": 28677, + "Ġsagittal": 28678, + "Ġintim": 28679, + "ĠFISH": 28680, + "Ġscarc": 28681, + "ATCC": 28682, + "incor": 28683, + "Ġserological": 28684, + "ente": 28685, + "Ġshale": 28686, + "Ġoverfitting": 28687, + "ĠExcess": 28688, + "ĠALP": 28689, + "Pool": 28690, + "dry": 28691, + "yu": 28692, + "ĠPMMA": 28693, + "ĠHypoxia": 28694, + "nothing": 28695, + "chestra": 28696, + "coloneqq": 28697, + "Ġbibli": 28698, + "ĠEXPECT": 28699, + "BAL": 28700, + "ethan": 28701, + "ĠâĪĺ": 28702, + "Ġjourney": 28703, + "Ġbiocompatibility": 28704, + "PAN": 28705, + "Ġbon": 28706, + "ĠRoh": 28707, + "Ġpolarisation": 28708, + "Spin": 28709, + "idences": 28710, + "ĠBCR": 28711, + "ĠHIP": 28712, + "ĠThick": 28713, + "Ġrecognizes": 28714, + "Ġsar": 28715, + "Ġamend": 28716, + "questions": 28717, + "Ġcaregiver": 28718, + "ĠMarie": 28719, + "Ġmetalloproteinase": 28720, + "Ġaldehydes": 28721, + "Ġinterneurons": 28722, + "Ġtetrahedral": 28723, + "guez": 28724, + "Ġquasiparticle": 28725, + "Ġot": 28726, + "decreasing": 28727, + "stre": 28728, + "Ġphotoperiod": 28729, + "Ġprioriti": 28730, + "Ġapo": 28731, + "Ġimmunosuppression": 28732, + "ĠPierre": 28733, + "LPS": 28734, + "Ġclumps": 28735, + "ĠPlane": 28736, + "Ġturbidity": 28737, + "Ġpollutant": 28738, + "Ġbioch": 28739, + "ĠTRE": 28740, + "Ġdesigners": 28741, + "Ġrenders": 28742, + "Ġreplaces": 28743, + "ĠPLS": 28744, + "Ġhumoral": 28745, + "Bas": 28746, + "reira": 28747, + "ĠAedes": 28748, + "vitamin": 28749, + "curves": 28750, + "ociceptive": 28751, + "Ġindisp": 28752, + "Ġoxy": 28753, + "Ġedible": 28754, + "ĠMesenchymal": 28755, + "ĠDegree": 28756, + "ž": 28757, + "ĠOak": 28758, + "ĠBhatt": 28759, + "onso": 28760, + "ĠSBP": 28761, + "ĠAux": 28762, + "Ġmartingale": 28763, + "ĠMicrobiota": 28764, + "glow": 28765, + "Ġexud": 28766, + "apolis": 28767, + "Ġsomehow": 28768, + "Ġcentred": 28769, + "Channel": 28770, + "ĠNormalized": 28771, + "ilitation": 28772, + "Ġtranscriptase": 28773, + "Ġcryo": 28774, + "predicted": 28775, + "ĠDAG": 28776, + "Ġrf": 28777, + "endor": 28778, + "INTER": 28779, + "ĠMesh": 28780, + "ĠFundament": 28781, + "ycle": 28782, + "Ġprimitives": 28783, + "radiated": 28784, + "Ġrho": 28785, + "enesulf": 28786, + "ĠFSH": 28787, + "ĠEcos": 28788, + "localized": 28789, + "Ġenterprise": 28790, + "cephalus": 28791, + "Ġcarcass": 28792, + "AY": 28793, + "ecurity": 28794, + "ĠTMD": 28795, + "Ġlb": 28796, + "ĠAeros": 28797, + "ĠMER": 28798, + "Attr": 28799, + "ĠACL": 28800, + "ĠBarb": 28801, + "cout": 28802, + "Ġdeoxy": 28803, + "atios": 28804, + "Ġpersists": 28805, + "Ġviolent": 28806, + "Abelian": 28807, + "Ġellips": 28808, + "iong": 28809, + "Ġsuccessor": 28810, + "ĠGonzález": 28811, + "living": 28812, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 28813, + "imentin": 28814, + "Ġcapsules": 28815, + "VIS": 28816, + "ĠPOP": 28817, + "arithmic": 28818, + "OO": 28819, + "wl": 28820, + "inoic": 28821, + "ĠCenters": 28822, + "roblasts": 28823, + "those": 28824, + "ĠMJ": 28825, + "Ġfronts": 28826, + "Ġunint": 28827, + "Ġfacile": 28828, + "coherent": 28829, + "avour": 28830, + "ceptive": 28831, + "tah": 28832, + "Ġrelatedness": 28833, + "dE": 28834, + "ungen": 28835, + "#####": 28836, + "Ġamphi": 28837, + "ĠGuy": 28838, + "stars": 28839, + "ectom": 28840, + "Ġlaying": 28841, + "Ġspider": 28842, + "ACs": 28843, + "Ġseedling": 28844, + "Ġduplicated": 28845, + "iche": 28846, + "ĠMST": 28847, + "grass": 28848, + "Ġprophylactic": 28849, + "eks": 28850, + "Ġlaryngeal": 28851, + "ĠSper": 28852, + "ĠWals": 28853, + "Ġcholec": 28854, + "ĠPlanet": 28855, + "ĠHEPES": 28856, + "Ġdiploid": 28857, + "constraint": 28858, + "Pyx": 28859, + "ACh": 28860, + "ĠCui": 28861, + "ĠShared": 28862, + "ĠCand": 28863, + "ĠGö": 28864, + "Ġdetached": 28865, + "Ġpassengers": 28866, + "Ġaliphatic": 28867, + "Ġpour": 28868, + "Ġaccesses": 28869, + "ĠWald": 28870, + "Ġdecorated": 28871, + "Ġcarotenoids": 28872, + "uestions": 28873, + "ĠImpacts": 28874, + "SAT": 28875, + "aru": 28876, + "ĠPir": 28877, + "ĠConfiguration": 28878, + "ĠCongo": 28879, + "ĠLing": 28880, + "Ġdesic": 28881, + "Ġmacrom": 28882, + "Ġlacked": 28883, + "Ġencompasses": 28884, + "Ġpumped": 28885, + "ĠForty": 28886, + "rexate": 28887, + "ifferentiated": 28888, + "Ġnoble": 28889, + "Ġradion": 28890, + "Ġimmigrants": 28891, + "Ġbiodegradable": 28892, + "Ġmigrating": 28893, + "argv": 28894, + "COM": 28895, + "ĠObservational": 28896, + "Ġcannabis": 28897, + "yama": 28898, + "Ġconcentric": 28899, + "Conn": 28900, + "talion": 28901, + "Ġresponders": 28902, + "utenant": 28903, + "ĠTrim": 28904, + "Ġcontributors": 28905, + "Ġcontracted": 28906, + "ĠXenopus": 28907, + "Ġloud": 28908, + "ĠEnhancing": 28909, + "Ġinfarct": 28910, + "Ġok": 28911, + "Ġasks": 28912, + "relin": 28913, + "Ġillustrative": 28914, + "vdash": 28915, + "dg": 28916, + "Ġfoc": 28917, + "Ġlivers": 28918, + "ĠOtt": 28919, + "ĠTSP": 28920, + "logger": 28921, + "depending": 28922, + "Ġdisproportion": 28923, + "Ġintric": 28924, + "Ġimmunized": 28925, + "varez": 28926, + "Ġsalic": 28927, + "ĠInstitutes": 28928, + "KEY": 28929, + "Ġendoscopy": 28930, + "erk": 28931, + "eliness": 28932, + "ĠSag": 28933, + "athyroid": 28934, + "Ġacidity": 28935, + "arov": 28936, + "ĠVoronoi": 28937, + "Experimental": 28938, + "Ġgently": 28939, + "Measure": 28940, + "ïĺº": 28941, + "Ġwonder": 28942, + "ĠPancreatic": 28943, + "ĠHispanic": 28944, + "ĠEug": 28945, + "reducing": 28946, + "tainment": 28947, + "Ġsurprise": 28948, + "Ġæ": 28949, + "criter": 28950, + "ĠHypertension": 28951, + "tique": 28952, + "ĠCris": 28953, + "compatible": 28954, + "enson": 28955, + "Ġdistributional": 28956, + "ĠNAT": 28957, + "widths": 28958, + "Ġisotherms": 28959, + "ĠPrad": 28960, + "Ġbiodies": 28961, + "Ġorbifold": 28962, + "ĠEOS": 28963, + "Ġatax": 28964, + "ĠBod": 28965, + "ĠNMD": 28966, + "Ġmonoxide": 28967, + "ĠUkraine": 28968, + "foli": 28969, + "ĠDro": 28970, + "Ġunavailable": 28971, + "Ġbrighter": 28972, + "âĬĹ": 28973, + "omethane": 28974, + "Ġdream": 28975, + "Ġspo": 28976, + "ĠMaur": 28977, + "Ġoccasional": 28978, + "Ġinconsistency": 28979, + "ĠTac": 28980, + "opts": 28981, + "ĠGAB": 28982, + "ĠTao": 28983, + "ĠMatthew": 28984, + "ý": 28985, + "Ġpiano": 28986, + "ĠRCC": 28987, + "ĠOK": 28988, + "ĠKul": 28989, + "methan": 28990, + "ĠPROC": 28991, + "Ġconversations": 28992, + "ĠCSI": 28993, + "angent": 28994, + "ĠXue": 28995, + "Ġgraphic": 28996, + "dening": 28997, + "healthy": 28998, + "Ġfp": 28999, + "azone": 29000, + "Ġdiscipline": 29001, + "Ġprogresses": 29002, + "Ġbamboo": 29003, + "Ġcharm": 29004, + "ĠActivated": 29005, + "ĠSharp": 29006, + "ynes": 29007, + "Ġtoolbox": 29008, + "Ġheterostructures": 29009, + "piperazin": 29010, + "Ġarose": 29011, + "ĠInterval": 29012, + "Ġstripe": 29013, + "ĠChak": 29014, + "Ġcuff": 29015, + "RESS": 29016, + "Ġnonuniform": 29017, + "Ġbeetle": 29018, + "Prec": 29019, + "zc": 29020, + "Thread": 29021, + "bet": 29022, + "Ġee": 29023, + "ĠOptional": 29024, + "Ġtroph": 29025, + "ĠPuer": 29026, + "ĠFron": 29027, + "Ġmultiplet": 29028, + "Ġcalorimetry": 29029, + "Ġmonocytogenes": 29030, + "ĠHimal": 29031, + "Ġdrill": 29032, + "AGA": 29033, + "Ġferritin": 29034, + "Ġdpi": 29035, + "ĠCarm": 29036, + "Ġgone": 29037, + "Ġunidirectional": 29038, + "Ġreminis": 29039, + "Ġadjustable": 29040, + "ĠAustin": 29041, + "SARS": 29042, + "dal": 29043, + "Ġcef": 29044, + "equivariant": 29045, + "baseline": 29046, + "Ġspinors": 29047, + "ĠPrint": 29048, + "Ġmile": 29049, + "ĠLinc": 29050, + "mutation": 29051, + "Ġmucus": 29052, + "ĠHSC": 29053, + "Ġthermod": 29054, + "Ġpaint": 29055, + "Ġdistinctly": 29056, + "athy": 29057, + "Ġpharmacy": 29058, + "ĠBulg": 29059, + "ĠGang": 29060, + "hicle": 29061, + "ogan": 29062, + "ĠJian": 29063, + "ĠIndiana": 29064, + "Ġinstanton": 29065, + "Ġpalladium": 29066, + "fiber": 29067, + "npy": 29068, + "ĠUA": 29069, + "ĠQT": 29070, + "ceptible": 29071, + "etine": 29072, + "ĠHoles": 29073, + "Ġdependences": 29074, + "Ġthresholding": 29075, + "ĠMaintenance": 29076, + "Ġparticipates": 29077, + "ĠGenomes": 29078, + "factorial": 29079, + "ĠLiber": 29080, + "ĠThermodynamic": 29081, + "Ġelective": 29082, + "ucher": 29083, + "Ġhyperther": 29084, + "Ġstomatal": 29085, + "ĠBirth": 29086, + "cholesterol": 29087, + "Ġnotch": 29088, + "Ġsymbiotic": 29089, + "Ġbusinesses": 29090, + "Ġappreciable": 29091, + "Ġspecialization": 29092, + "ár": 29093, + "actyl": 29094, + "ĠGraphPad": 29095, + "osper": 29096, + "Ġorchestr": 29097, + "Ġdihydro": 29098, + "Ġconcluding": 29099, + "CLK": 29100, + "Ġeqs": 29101, + "ĠProgression": 29102, + "Ġclubs": 29103, + "aku": 29104, + "events": 29105, + "Ġsplenic": 29106, + "Ġbunch": 29107, + "ĠTm": 29108, + "ĠMobility": 29109, + "Ġtwofold": 29110, + "Ġradially": 29111, + "LSTM": 29112, + "MH": 29113, + "ĠCoal": 29114, + "Ġfrontier": 29115, + "Jan": 29116, + "Jun": 29117, + "ĠSimpson": 29118, + "Ġabstracts": 29119, + "Pal": 29120, + "Ġunim": 29121, + "Ġrobo": 29122, + "ĠIIB": 29123, + "depleted": 29124, + "Ġmorphologically": 29125, + "Ġenforcement": 29126, + "Ġdwell": 29127, + "Ġstagn": 29128, + "Ġlimestone": 29129, + "Ġmicrov": 29130, + "Ġïĥ¸": 29131, + "Luc": 29132, + "pacs": 29133, + "cyano": 29134, + "Ġintraocular": 29135, + "ĠCalculate": 29136, + "Support": 29137, + "SYS": 29138, + "ĠVS": 29139, + "CMs": 29140, + "Constant": 29141, + "ĠDj": 29142, + "Ġunbalanced": 29143, + "Ġrepeatability": 29144, + "gins": 29145, + "irect": 29146, + "ĠMOR": 29147, + "ĠBailey": 29148, + "Ġadvancement": 29149, + "Ġpursuit": 29150, + "Ġarom": 29151, + "proced": 29152, + "ĠInitiative": 29153, + "Ġincentives": 29154, + "Ġsurpass": 29155, + "genes": 29156, + "ĠIND": 29157, + "LH": 29158, + "Ġsuicidal": 29159, + "Ġbiodiesel": 29160, + "xz": 29161, + "ÙĬ": 29162, + "lea": 29163, + "ĠAnthony": 29164, + "Learning": 29165, + "Ġundo": 29166, + "Ġïĥº": 29167, + "ĠCommunities": 29168, + "hua": 29169, + "itime": 29170, + "ĠDean": 29171, + "Ġplasmin": 29172, + "ÃŃnez": 29173, + "ohydrate": 29174, + "Ġneurodevelop": 29175, + "Ġstoichiometric": 29176, + "ĠOncology": 29177, + "Ġshower": 29178, + "ĠDMS": 29179, + "WOR": 29180, + "ĠPIP": 29181, + "Ġsteric": 29182, + "mittees": 29183, + "istol": 29184, + "oxins": 29185, + "noon": 29186, + "FFT": 29187, + "Ġá»": 29188, + "opoiesis": 29189, + "Ġresembling": 29190, + "ĠBord": 29191, + "Ġprobiotics": 29192, + "ocysts": 29193, + "grey": 29194, + "ĠCatalog": 29195, + "IZATION": 29196, + "illes": 29197, + "ĠAlan": 29198, + "ĠÅ·": 29199, + "ĠLeib": 29200, + "ĠReasoning": 29201, + "biological": 29202, + "uterine": 29203, + "vacizumab": 29204, + "lecommun": 29205, + "ĠWarm": 29206, + "epage": 29207, + "variants": 29208, + "BSA": 29209, + "Ġïĥ¶": 29210, + "Ġhepatocyte": 29211, + "ketch": 29212, + "Ġstripping": 29213, + "ĠAdverse": 29214, + "ĠFeas": 29215, + "Ġïĥ¯": 29216, + "Pac": 29217, + "Ġindentation": 29218, + "Ġsecular": 29219, + "Ġidentifiable": 29220, + "running": 29221, + "Ġrd": 29222, + "Ġzyg": 29223, + "ĠDictionary": 29224, + "Ġresveratrol": 29225, + "inesterase": 29226, + "Ġtetracycline": 29227, + "ubles": 29228, + "Ġthroat": 29229, + "ĠLamb": 29230, + "aryon": 29231, + "ĠSQL": 29232, + "ĠÃľ": 29233, + "Ġglycemic": 29234, + "Ġcompetent": 29235, + "ĠAgreement": 29236, + "oiced": 29237, + "Ġconstitutively": 29238, + "Ġelectrocardi": 29239, + "oplasma": 29240, + "ĠîĦĥ": 29241, + "anide": 29242, + "Ġreorganization": 29243, + "Ġuninfected": 29244, + "UTE": 29245, + "Ġroyal": 29246, + "ĠSit": 29247, + "Ġmarital": 29248, + "ĠKobayashi": 29249, + "Barr": 29250, + "ĠTennessee": 29251, + "ĠChromat": 29252, + "ĠDerm": 29253, + "projection": 29254, + "ĠJob": 29255, + "Ġâīł": 29256, + "ĠTrip": 29257, + "Ġisop": 29258, + "Ġprojector": 29259, + "Ġatmospheres": 29260, + "Ġperforation": 29261, + "storage": 29262, + "iths": 29263, + "Ġmonomeric": 29264, + "ĠUSB": 29265, + "ĠEve": 29266, + "Ġspore": 29267, + "ĠmT": 29268, + "oxazole": 29269, + "ĠDeformation": 29270, + "Ġtextual": 29271, + "Ġwarf": 29272, + "Ġneuropathic": 29273, + "prepared": 29274, + "Ġblended": 29275, + "ĠHouston": 29276, + "************************************************************************": 29277, + "esters": 29278, + "Equals": 29279, + "Ġallergen": 29280, + "Ġpertinent": 29281, + "facts": 29282, + "uctions": 29283, + "Ġclocks": 29284, + "ĠVia": 29285, + "ĠCDF": 29286, + "Ġestuary": 29287, + "Ġphenomenology": 29288, + "arus": 29289, + "APH": 29290, + "Ġargues": 29291, + "Ġinserts": 29292, + "gow": 29293, + "hart": 29294, + "Ġchemotaxis": 29295, + "Ġpv": 29296, + "Ġrein": 29297, + "ĠGrim": 29298, + "ĠVF": 29299, + "Ġeffic": 29300, + "ĠProfiling": 29301, + "Ġanodic": 29302, + "ĠDENV": 29303, + "ĠWit": 29304, + "ĠSYSTEM": 29305, + "ĠCayley": 29306, + "Eng": 29307, + "ĠAQP": 29308, + "interactions": 29309, + "iliarity": 29310, + "ĠPromotes": 29311, + "Ġdams": 29312, + "ington": 29313, + "ffff": 29314, + "Ġintran": 29315, + "ĠTurbulence": 29316, + "ĠBianchi": 29317, + "CRE": 29318, + "ĠNOD": 29319, + "apine": 29320, + "ĠKane": 29321, + "ĠPDGF": 29322, + "ĠAxis": 29323, + "ĠCausal": 29324, + "ĠPoor": 29325, + "ĠWords": 29326, + "ĠHRV": 29327, + "Ġcyanobacteria": 29328, + "Ġreminiscent": 29329, + "ĠRemarkably": 29330, + "heet": 29331, + "@@": 29332, + "bil": 29333, + "Ġdiscriminating": 29334, + "ĠBaltic": 29335, + "ĠQuebec": 29336, + "Ġdefensive": 29337, + "âĪ©": 29338, + "kr": 29339, + "ĠRPE": 29340, + "seeking": 29341, + "ĠMovie": 29342, + "Ġinnovations": 29343, + "lept": 29344, + "Ġkw": 29345, + "Ġtibia": 29346, + "Ġneat": 29347, + "ytest": 29348, + "Ġthinner": 29349, + "Ġosteoblasts": 29350, + "ĠNorthwest": 29351, + "MOS": 29352, + "ĠPQ": 29353, + "Ġspi": 29354, + "Ġresponds": 29355, + "Ġhistorically": 29356, + "ĠPackage": 29357, + "ĠCoastal": 29358, + "ĠMississippi": 29359, + "ĠPVA": 29360, + "pering": 29361, + "indole": 29362, + "Ġprospectively": 29363, + "ĠHemisphere": 29364, + "Ġbarely": 29365, + "ánchez": 29366, + "aggered": 29367, + "yptian": 29368, + "ĠGest": 29369, + "yline": 29370, + "Ġphotochemical": 29371, + "oscalar": 29372, + "porated": 29373, + "Ġmetabolomics": 29374, + "Ġosteoblast": 29375, + "EGFP": 29376, + "eriatric": 29377, + "DW": 29378, + "quest": 29379, + "ĠHave": 29380, + "Ġspondyl": 29381, + "ĠPrimer": 29382, + "Ġsinks": 29383, + "Ġgaussian": 29384, + "ĠKhal": 29385, + "Enc": 29386, + "ĠAnopheles": 29387, + "Thanks": 29388, + "Ġconstrued": 29389, + "ĠUSS": 29390, + "ĠZeeman": 29391, + "Ġexported": 29392, + "ĠLevi": 29393, + "Ġcommander": 29394, + "connect": 29395, + "Ġnomenclature": 29396, + "therefore": 29397, + "ulata": 29398, + "Ġentrepreneur": 29399, + "Ġneuroscience": 29400, + "zan": 29401, + "Ġextant": 29402, + "ATIVE": 29403, + "opez": 29404, + "Ġenforced": 29405, + "ĠInnovation": 29406, + "earance": 29407, + "Ġimpressive": 29408, + "ĠPlac": 29409, + "ĠMoz": 29410, + "ĠStark": 29411, + "Ġrival": 29412, + "ĠCapital": 29413, + "Ġgranularity": 29414, + "Ġdiaphragm": 29415, + "utaneous": 29416, + "inds": 29417, + "Ġphotograph": 29418, + "Ġrectangles": 29419, + "TGF": 29420, + "Ġseaf": 29421, + "Ġmaze": 29422, + "ĠHW": 29423, + "Ġcorrelators": 29424, + "Ġdistinguishable": 29425, + "Ġconfounders": 29426, + "Ġlandslide": 29427, + "Ġtoll": 29428, + "Ġwastes": 29429, + "ĠWF": 29430, + "Ġendoc": 29431, + "Ġcapsid": 29432, + "ecund": 29433, + "ĠRBD": 29434, + "psin": 29435, + "Ġobstetric": 29436, + "Ġnanosheets": 29437, + "ocols": 29438, + "rens": 29439, + "ĠSubstituting": 29440, + "Ġcustomized": 29441, + "Ġresuscitation": 29442, + "Ġtubulin": 29443, + "ophyte": 29444, + "~~~~~~~~": 29445, + "plants": 29446, + "hicillin": 29447, + "halo": 29448, + "ruitment": 29449, + "ĠConcrete": 29450, + "Ġnanorods": 29451, + "ĠForms": 29452, + "Ġdying": 29453, + "discharge": 29454, + "Ġwellbeing": 29455, + "Ġwarmer": 29456, + "ĠSSD": 29457, + "ĠAUT": 29458, + "ĠConjug": 29459, + "Ġjuveniles": 29460, + "Ġinevitably": 29461, + "ĠMCS": 29462, + "approach": 29463, + "ĠMason": 29464, + "ĠGust": 29465, + "ĠThermodynamics": 29466, + "Ġpeel": 29467, + "ĠTranscriptome": 29468, + "Ġindispensable": 29469, + "urgery": 29470, + "posity": 29471, + "Ġpolarizations": 29472, + "ĠOthers": 29473, + "Ġsandy": 29474, + "Ġgliomas": 29475, + "Ġpursued": 29476, + "VEL": 29477, + "Ġrst": 29478, + "posium": 29479, + "nearest": 29480, + "Ġdisseminated": 29481, + "ĠMYC": 29482, + "Ġaldehyde": 29483, + "ĠDiagnostics": 29484, + "mans": 29485, + "Ġasphal": 29486, + "ĠSelect": 29487, + "ĠRecon": 29488, + "andro": 29489, + "DIM": 29490, + "Ġfeces": 29491, + "illon": 29492, + "ĠMALDI": 29493, + "nf": 29494, + "ĠElim": 29495, + "Ġhappy": 29496, + "ĠKarl": 29497, + "ĠInser": 29498, + "Ġinterrog": 29499, + "Intern": 29500, + "Ġtensorflow": 29501, + "Ġhaloes": 29502, + "Ġanticipate": 29503, + "ĠDPPH": 29504, + "rÃŃguez": 29505, + "Her": 29506, + "anate": 29507, + "Ġdressing": 29508, + "ĠHoly": 29509, + "Ġnewer": 29510, + "rides": 29511, + "placed": 29512, + "inetobacter": 29513, + "ĠOccurrence": 29514, + "edema": 29515, + "ĠIk": 29516, + "abad": 29517, + "ĠTransitions": 29518, + "Ġoutlines": 29519, + "Ġcochlear": 29520, + "Gy": 29521, + "success": 29522, + "ĠMEM": 29523, + "astype": 29524, + "Ġnormalizing": 29525, + "Ġterminates": 29526, + "Ġsuddenly": 29527, + "bbox": 29528, + "ĠPul": 29529, + "ĠPTP": 29530, + "aginal": 29531, + "Ġpretrained": 29532, + "Ġunreliable": 29533, + "ĠGraphical": 29534, + "ĠSeyfert": 29535, + "Ġcharacterizations": 29536, + "Ġtx": 29537, + "Ġbicarbonate": 29538, + "mathord": 29539, + "Ġheritability": 29540, + "stackexchange": 29541, + "iri": 29542, + "âĢĸ": 29543, + "ipit": 29544, + "attle": 29545, + "Ġarena": 29546, + "iba": 29547, + "ĠAX": 29548, + "ĠGPs": 29549, + "ophilia": 29550, + "SEL": 29551, + "osystem": 29552, + "ĠâĬ¢": 29553, + "ĠNucleus": 29554, + "redited": 29555, + "ACR": 29556, + "ĠAntenna": 29557, + "ĠCdc": 29558, + "orie": 29559, + "Ġequilibration": 29560, + "elong": 29561, + "stability": 29562, + "ĠSchist": 29563, + "Ġinjecting": 29564, + "hp": 29565, + "Ġvitamins": 29566, + "Poisson": 29567, + "ortal": 29568, + "ĠÃĬ": 29569, + "ĠÄı": 29570, + "Ill": 29571, + "Ġutils": 29572, + "ов": 29573, + "ĠGrom": 29574, + "::::": 29575, + "ĠGnRH": 29576, + "ĠSierra": 29577, + "Ġdrafted": 29578, + "Ġcapita": 29579, + "ships": 29580, + "Ġtimestamp": 29581, + "Ġsubstituents": 29582, + "ĠNotable": 29583, + "ĠPurpose": 29584, + "inol": 29585, + "Ġai": 29586, + "Ġfog": 29587, + "otone": 29588, + "ĠPlaces": 29589, + "byshev": 29590, + "tiology": 29591, + "ription": 29592, + "Ġyards": 29593, + "ĠXI": 29594, + "Ġtechnically": 29595, + "GAM": 29596, + "ĠABS": 29597, + "platform": 29598, + "ĠWO": 29599, + "PROC": 29600, + "Ġreconstit": 29601, + "ĠAnomalous": 29602, + "ĠBiol": 29603, + "Stage": 29604, + "ĠReviews": 29605, + "Ġrecalling": 29606, + "Ġillegal": 29607, + "lund": 29608, + "¬": 29609, + "uthenium": 29610, + "ĠPes": 29611, + "Ġovaries": 29612, + "solutions": 29613, + "massive": 29614, + "ĠRAW": 29615, + "Ġreconnection": 29616, + "ĠSusceptibility": 29617, + "Ġeconomical": 29618, + "cultured": 29619, + "ĠSham": 29620, + "sqcup": 29621, + "Ġpear": 29622, + "deposition": 29623, + "uchs": 29624, + "ĠSaw": 29625, + "Ġembolism": 29626, + "Bur": 29627, + "nar": 29628, + "oule": 29629, + "Ġtextile": 29630, + "seven": 29631, + "thio": 29632, + "Ġdenoising": 29633, + "CEP": 29634, + "Ġubiquitination": 29635, + "ĠCarlos": 29636, + "aP": 29637, + "Ġfolder": 29638, + "Ġhematological": 29639, + "iluminescence": 29640, + "ĠFuel": 29641, + "icion": 29642, + "aculture": 29643, + "ARB": 29644, + "ĠTravel": 29645, + "Func": 29646, + "acles": 29647, + "ĠInte": 29648, + "Ġvacua": 29649, + "Ġcocktail": 29650, + "ĠInsp": 29651, + "Ġcorporate": 29652, + "Ġdepicting": 29653, + "Ġsprint": 29654, + "ĠmTORC": 29655, + "Ġcimg": 29656, + "ocarbon": 29657, + "ĠDave": 29658, + "ĠGb": 29659, + "iji": 29660, + "targeting": 29661, + "Ġsequestration": 29662, + "Bri": 29663, + "IGF": 29664, + "Ġanalytics": 29665, + "ĠAcinetobacter": 29666, + "gets": 29667, + "MPS": 29668, + "ogluc": 29669, + "Cent": 29670, + "Ġverbs": 29671, + "Ġinductance": 29672, + "diagram": 29673, + "Ġrecalled": 29674, + "Ġcosme": 29675, + "Ġautomotive": 29676, + "ĠPDEs": 29677, + "ĠReid": 29678, + "Ġadapter": 29679, + "ĠOliver": 29680, + "Ġavalanche": 29681, + "Vir": 29682, + "ĠToxicity": 29683, + "ĠLeu": 29684, + "Conclusions": 29685, + "Ġtetragonal": 29686, + "ĠDMF": 29687, + "umannii": 29688, + "ĠRequirements": 29689, + "toc": 29690, + "ité": 29691, + "Ġcontinent": 29692, + "ĠHank": 29693, + "ĠDefinitions": 29694, + "GPU": 29695, + "origin": 29696, + "Ġdichro": 29697, + "Mus": 29698, + "Ġbival": 29699, + "Ġimpulsive": 29700, + "Ġassemble": 29701, + "Ġpipes": 29702, + "docs": 29703, + "Ġexchanger": 29704, + "Ġallograft": 29705, + "loyd": 29706, + "ĠÌĭ": 29707, + "Ġantenatal": 29708, + "Ġgrassland": 29709, + "Ġhystere": 29710, + "ĠAntigen": 29711, + "ĠGeneric": 29712, + "ĠTuring": 29713, + "ĠExcell": 29714, + "ĠHein": 29715, + "aja": 29716, + "uminum": 29717, + "citabine": 29718, + "facial": 29719, + "iteration": 29720, + "Ġslurry": 29721, + "AML": 29722, + "ergetic": 29723, + "ĠTHF": 29724, + "Ġkilometers": 29725, + "fg": 29726, + "educ": 29727, + "idian": 29728, + "Ġpredicates": 29729, + "Ġradios": 29730, + "ĠPeri": 29731, + "ĠShell": 29732, + "Ġarcsec": 29733, + "Ġstriatal": 29734, + "Ġceiling": 29735, + "olithic": 29736, + "Ġexhaustion": 29737, + "PUT": 29738, + "thers": 29739, + "ymp": 29740, + "ĠQian": 29741, + "ĠProgressive": 29742, + "Ġwel": 29743, + "ĠConvention": 29744, + "ĠCurie": 29745, + "ĠMans": 29746, + "ĠNova": 29747, + "ĠWells": 29748, + "dew": 29749, + "Standard": 29750, + "realistic": 29751, + "transpose": 29752, + "serial": 29753, + "ĠTx": 29754, + "ĠAMR": 29755, + "Ġindeterm": 29756, + "ĠLiouville": 29757, + "hookrightarrow": 29758, + "ARs": 29759, + "Ġbaseball": 29760, + "acious": 29761, + "agnetization": 29762, + "estimate": 29763, + "ĠPAS": 29764, + "Ġmeals": 29765, + "multiple": 29766, + "ĠBiomarkers": 29767, + "Wide": 29768, + "ĠTomography": 29769, + "////////////////////////////////": 29770, + "Ġresins": 29771, + "Ġanywhere": 29772, + "INC": 29773, + "ĠTeaching": 29774, + "ĠSamuel": 29775, + "Ġhallmark": 29776, + "ĠThyroid": 29777, + "othi": 29778, + "Ġconstraining": 29779, + "ĠBarrett": 29780, + "ĠErrors": 29781, + "Cole": 29782, + "sharing": 29783, + "HDL": 29784, + "Effect": 29785, + "ĠTolerance": 29786, + "Ġstressful": 29787, + "ĠBalance": 29788, + "ĠTech": 29789, + "Ġvalleys": 29790, + "setup": 29791, + "ĠRadical": 29792, + "ĠMacrophages": 29793, + "Ġinterrupt": 29794, + "Ġdiatom": 29795, + "colored": 29796, + "Ġpyrid": 29797, + "FDG": 29798, + "æ": 29799, + "Ġreared": 29800, + "ĠRating": 29801, + "Ġopaque": 29802, + "package": 29803, + "Ġnasopharyngeal": 29804, + "Ġpreconditioning": 29805, + "Diptera": 29806, + "ĠMing": 29807, + "ĠCaro": 29808, + "ĠImmunity": 29809, + "rifuge": 29810, + "ĠObjectives": 29811, + "ghan": 29812, + "uccin": 29813, + "ĠFors": 29814, + "ĠFITC": 29815, + "Ġseats": 29816, + "ĠImpaired": 29817, + "Ġreefs": 29818, + "emaker": 29819, + "Ġoffices": 29820, + "Ġaccepting": 29821, + "ĠTRAN": 29822, + "ĠTargets": 29823, + "Ġcorrelator": 29824, + "Ġsupercapac": 29825, + "inburgh": 29826, + "Ġcollider": 29827, + "Ġenteric": 29828, + "ĠSTRUCTURE": 29829, + "Ġminister": 29830, + "ĠArchae": 29831, + "Loop": 29832, + "ĠASA": 29833, + "Ġcontacted": 29834, + "Ġhistidine": 29835, + "folded": 29836, + "Search": 29837, + "Ġrespects": 29838, + "ĠATF": 29839, + "Ġtrouble": 29840, + "Ġprevailing": 29841, + "Cp": 29842, + "ĠTCM": 29843, + "ĠSpinal": 29844, + "Ġguides": 29845, + "evitable": 29846, + "Ġbrick": 29847, + "strings": 29848, + "ĠHungary": 29849, + "Ġeps": 29850, + "entricular": 29851, + "Specifically": 29852, + "ando": 29853, + "issues": 29854, + "osomiasis": 29855, + "kDa": 29856, + "Ġaside": 29857, + "Ġadenine": 29858, + "Ġmotivate": 29859, + "stratig": 29860, + "BLE": 29861, + "ĠDeposition": 29862, + "motor": 29863, + "ĠHers": 29864, + "Ġnebul": 29865, + "ĠBarrier": 29866, + "Unlike": 29867, + "Ġballistic": 29868, + "Ġsouthwestern": 29869, + "ĠMontreal": 29870, + "Scan": 29871, + "Ġmould": 29872, + "Ġinterrup": 29873, + "smallmatrix": 29874, + "Ġelaborated": 29875, + "ucks": 29876, + "APS": 29877, + "ĠConsumption": 29878, + "capacity": 29879, + "innitus": 29880, + "Ġgovernance": 29881, + "Ġpalsy": 29882, + "Ġsubmission": 29883, + "Ġtemple": 29884, + "ĠIIA": 29885, + "methionine": 29886, + "Ġkerat": 29887, + "Ġridges": 29888, + "Promega": 29889, + "cols": 29890, + "ISP": 29891, + "Ġapnea": 29892, + "ĠFlat": 29893, + "ĠEpigenetic": 29894, + "Ġparish": 29895, + "ĠParametric": 29896, + "dash": 29897, + "future": 29898, + "rise": 29899, + "Ġcontracting": 29900, + "algia": 29901, + "Ġgoto": 29902, + "stadt": 29903, + "Ġfabricate": 29904, + "Ġdimerization": 29905, + "dump": 29906, + "ĠLyn": 29907, + "Ġrecycled": 29908, + "posedness": 29909, + "ĠSensory": 29910, + "ïĿ": 29911, + "ĠWet": 29912, + "Ġdiethyl": 29913, + "Ġblades": 29914, + "Ġtimed": 29915, + "Ġkeyword": 29916, + "Ġpolytope": 29917, + "ĠGot": 29918, + "Ġapproximates": 29919, + "Without": 29920, + "ĠBere": 29921, + "ĠLp": 29922, + "oplasty": 29923, + "ĠFibr": 29924, + "modulated": 29925, + "ĠARM": 29926, + "Ġunderestimate": 29927, + "ĠCBS": 29928, + "ĠLectures": 29929, + "uncan": 29930, + "ĠSeismic": 29931, + "Soft": 29932, + "Ġzooplankton": 29933, + "Ġencephalopathy": 29934, + "ĠSSA": 29935, + "ĠCros": 29936, + "ĠHann": 29937, + "Ġshuffle": 29938, + "scription": 29939, + "ĠRevers": 29940, + "Studies": 29941, + "Ġsocially": 29942, + "Ġsubcl": 29943, + "ĠYong": 29944, + "ogh": 29945, + "Ġïģ³": 29946, + "UDY": 29947, + "ĠHaar": 29948, + "ĠDoctor": 29949, + "Ġintakes": 29950, + "Ġbarrel": 29951, + "ĠTRPV": 29952, + "ĠAggreg": 29953, + "nyi": 29954, + "tuned": 29955, + "acquired": 29956, + "Ġhook": 29957, + "FGF": 29958, + "«": 29959, + "ĠInjection": 29960, + "Ġgravel": 29961, + "Ġmicrog": 29962, + "Ġmenstrual": 29963, + "Feature": 29964, + "IRE": 29965, + "uu": 29966, + "ĠSrc": 29967, + "ĠStore": 29968, + "Ġinitiator": 29969, + "PSO": 29970, + "Ġepileptic": 29971, + "Ġcingulate": 29972, + "IJ": 29973, + "Row": 29974, + "Ġsinging": 29975, + "ĠMethan": 29976, + "ĠAldrich": 29977, + "Ġtremendous": 29978, + "amining": 29979, + "Ġtracts": 29980, + "Ġâİ£": 29981, + "klah": 29982, + "Div": 29983, + "indol": 29984, + "Ġindole": 29985, + "exper": 29986, + "Ġglycer": 29987, + "Ġbenzyl": 29988, + "Ġworsening": 29989, + "Ġunambiguous": 29990, + "uart": 29991, + "Ġparsim": 29992, + "ricks": 29993, + "Ġtrail": 29994, + "ĠBlanc": 29995, + "Ġaminotransferase": 29996, + "ĠDOC": 29997, + "Ġfumig": 29998, + "idic": 29999, + "ĠConsequences": 30000, + "Ġacidification": 30001, + "ĠCIFAR": 30002, + "ĠDatasets": 30003, + "ĠAMI": 30004, + "Ġexplants": 30005, + "ĠDiverse": 30006, + "Ġdephasing": 30007, + "Ġparliament": 30008, + "ipient": 30009, + "Ġhoneycomb": 30010, + "heavy": 30011, + "Ġwatermark": 30012, + "MED": 30013, + "datasets": 30014, + "waters": 30015, + "Provid": 30016, + "interpret": 30017, + "rovirus": 30018, + "Io": 30019, + "RAD": 30020, + "Ġlunar": 30021, + "Ġweaning": 30022, + "Ġsensorimotor": 30023, + "uca": 30024, + "Ġinfect": 30025, + "ĠUnique": 30026, + "GRP": 30027, + "QoL": 30028, + "ospec": 30029, + "Ġforwarding": 30030, + "Estim": 30031, + "ÅĦski": 30032, + "ĠMs": 30033, + "achn": 30034, + "Ġrota": 30035, + "Ġappointment": 30036, + "ĠMedal": 30037, + "Ġadenovirus": 30038, + "quinol": 30039, + "Ġdeuterium": 30040, + "tep": 30041, + "ĠStyle": 30042, + "Nd": 30043, + "ayama": 30044, + "ĠHamm": 30045, + "ĠSpecification": 30046, + "vability": 30047, + "tha": 30048, + "Ġjitter": 30049, + "Ġâݦ": 30050, + "aqu": 30051, + "wire": 30052, + "Ġclassically": 30053, + "Ġsuperpotential": 30054, + "ĠSpecim": 30055, + "ĠVariance": 30056, + "Ġalbums": 30057, + "ĠSenior": 30058, + "Ġneurotransmitter": 30059, + "ĠRecombinant": 30060, + "DCS": 30061, + "vl": 30062, + "Ġpf": 30063, + "Ġinevitable": 30064, + "ĠNick": 30065, + "Ġmanipulating": 30066, + "ituximab": 30067, + "ceiver": 30068, + "ĠBren": 30069, + "ĠRace": 30070, + "Ġretarded": 30071, + "modulin": 30072, + "Clinical": 30073, + "Ġneurologic": 30074, + "ĠRegiment": 30075, + "Ġzoom": 30076, + "ĠOrthogonal": 30077, + "ĠConcerning": 30078, + "ĠJurassic": 30079, + "ĠArtem": 30080, + "ĠMelbourne": 30081, + "bins": 30082, + "jl": 30083, + "Ġinhab": 30084, + "Ġsqrt": 30085, + "Ġsemisimple": 30086, + "astric": 30087, + "ĠProxim": 30088, + "ĠVariants": 30089, + "Ġaesthetic": 30090, + "Ġsummarised": 30091, + "ĠBecker": 30092, + "OCH": 30093, + "dale": 30094, + "Ġmounting": 30095, + "andering": 30096, + "Ġsoftmax": 30097, + "Ġneuroinflammation": 30098, + "Ġesophagus": 30099, + "operators": 30100, + "ĠADAM": 30101, + "Ġviolate": 30102, + "ĠPHY": 30103, + "ede": 30104, + "ĠCher": 30105, + "orsal": 30106, + "Ġmetamorphic": 30107, + "ĠICM": 30108, + "ĠAbcam": 30109, + "slot": 30110, + "serine": 30111, + "Ġduplicates": 30112, + "ĠMEMS": 30113, + "ĠAbl": 30114, + "ĠChel": 30115, + "ĠAuthority": 30116, + "Ġgeo": 30117, + "Ġhomeomorphism": 30118, + "Ġimmunomodulatory": 30119, + "ĠTU": 30120, + "ĠKT": 30121, + "aterally": 30122, + "oxides": 30123, + "tebral": 30124, + "Ġcataract": 30125, + "leaved": 30126, + "igu": 30127, + "ateur": 30128, + "ĠRé": 30129, + "Ġdiscoveries": 30130, + "boson": 30131, + "ocated": 30132, + "jpg": 30133, + "ĠSato": 30134, + "ĠPROP": 30135, + "ĠImplement": 30136, + "ELISA": 30137, + "iqueness": 30138, + "Ġsymbion": 30139, + "ĠFaraday": 30140, + "ĠPPARγ": 30141, + "witz": 30142, + "reward": 30143, + "ĠBush": 30144, + "stressed": 30145, + "ĠAbor": 30146, + "Ġairways": 30147, + "Ġinterferometry": 30148, + "Circ": 30149, + "Ġimmunoprecipitation": 30150, + "ĠApache": 30151, + "rophosph": 30152, + "ĠoC": 30153, + "Ġfrog": 30154, + "ĠGU": 30155, + "ffe": 30156, + "ĠStro": 30157, + "Ġdodecyl": 30158, + "dan": 30159, + "folds": 30160, + "ĠMust": 30161, + "Ġsurroundings": 30162, + "Ġcodons": 30163, + "onda": 30164, + "tb": 30165, + "odge": 30166, + "avas": 30167, + "ĠSeason": 30168, + "tude": 30169, + "ĠPlasticity": 30170, + "ĠHawaii": 30171, + "DEG": 30172, + "ĠCMD": 30173, + "Ġsingleton": 30174, + "keley": 30175, + "Ġalgebraically": 30176, + "Ġnanostructured": 30177, + "easible": 30178, + "Ġoverlooked": 30179, + "ĠPulse": 30180, + "romechanical": 30181, + "ĠElse": 30182, + "Ġexcitons": 30183, + "ĠConstrained": 30184, + "Ġcohesion": 30185, + "Ġrealizing": 30186, + "ĠRadiative": 30187, + "Ġtrypan": 30188, + "xs": 30189, + "ĠTas": 30190, + "Ġmainstream": 30191, + "Ġcompactly": 30192, + "growing": 30193, + "esc": 30194, + "ĠdN": 30195, + "ĠSignatures": 30196, + "ĠFundamentals": 30197, + "Ġexpose": 30198, + "ĠRang": 30199, + "Ġhanded": 30200, + "Ġfunctionalization": 30201, + "Ġpassiv": 30202, + "altern": 30203, + "agul": 30204, + "Ġschematically": 30205, + "OW": 30206, + "ĠÖ": 30207, + "ĠPOD": 30208, + "Ġhear": 30209, + "ymore": 30210, + "ĠPremier": 30211, + "South": 30212, + "Ä«": 30213, + "ĠOBS": 30214, + "ĠAlg": 30215, + "glia": 30216, + "ĠTransmembrane": 30217, + "Ġspheroids": 30218, + "ĠRHS": 30219, + "Ġinches": 30220, + "ĠKato": 30221, + "Ġie": 30222, + "ĠCommercial": 30223, + "Ġanalytes": 30224, + "Ġrisky": 30225, + "Ġpiston": 30226, + "ĠMarkovian": 30227, + "Ġdrama": 30228, + "Ġci": 30229, + "ĠHistological": 30230, + "Ġactuation": 30231, + "discrete": 30232, + "carbamoyl": 30233, + "SMA": 30234, + "Ġfeeds": 30235, + "Ġneoplasia": 30236, + "ĠController": 30237, + "been": 30238, + "glutamine": 30239, + "injected": 30240, + "Ġcrab": 30241, + "ĠCauses": 30242, + "ĠStory": 30243, + "Ġvanadium": 30244, + "ĠTitan": 30245, + "enix": 30246, + "assign": 30247, + "Ġimmunogenicity": 30248, + "ĠApparent": 30249, + "Ġenhancers": 30250, + "ĠSou": 30251, + "alloy": 30252, + "mathbin": 30253, + "Ġsedation": 30254, + "ĠWorkshop": 30255, + "gover": 30256, + "lst": 30257, + "Ġupwelling": 30258, + "mez": 30259, + "Ġpolypropylene": 30260, + "ĠColorectal": 30261, + "ĠRelaxation": 30262, + "Ġfragile": 30263, + "Äĥ": 30264, + "Ġsubgraphs": 30265, + "theoretical": 30266, + "Operator": 30267, + "lywood": 30268, + "awn": 30269, + "ĠPercentage": 30270, + "methylation": 30271, + "corrhizal": 30272, + "Grad": 30273, + "dens": 30274, + "ĠHα": 30275, + "Ġupcoming": 30276, + "Ġvirgin": 30277, + "Names": 30278, + "ĠRyd": 30279, + "Ġâݤ": 30280, + "phosphorylation": 30281, + "renewal": 30282, + "Year": 30283, + "Init": 30284, + "Ġselling": 30285, + "ĠMASS": 30286, + "rophin": 30287, + "ijn": 30288, + "Conversely": 30289, + "Ġuniversally": 30290, + "orhombic": 30291, + "Ġunpredictable": 30292, + "Fock": 30293, + "chair": 30294, + "ivas": 30295, + "networks": 30296, + "Ġterritories": 30297, + "thia": 30298, + "ĠAmplification": 30299, + "March": 30300, + "Ġflam": 30301, + "ĠChart": 30302, + "Ġshortage": 30303, + "AMET": 30304, + "Ġgrape": 30305, + "Ġvoltammetry": 30306, + "د": 30307, + "ĠSCH": 30308, + "Ġepithel": 30309, + "ĠChromosome": 30310, + "ĠXL": 30311, + "ĠPersistent": 30312, + "Ġtraveled": 30313, + "Ġmeridional": 30314, + "Ġfprintf": 30315, + "Ġgum": 30316, + "visory": 30317, + "Unfortunately": 30318, + "Ġanteced": 30319, + "Ġfrictional": 30320, + "DAT": 30321, + "acl": 30322, + "ĠPregnancy": 30323, + "ĠBZ": 30324, + "regulatory": 30325, + "stimulating": 30326, + "Japan": 30327, + "machine": 30328, + "uti": 30329, + "ĠLer": 30330, + "Ġnanoflu": 30331, + "prototype": 30332, + "identification": 30333, + "klahoma": 30334, + "ĠEmploy": 30335, + "Schwarz": 30336, + "Ġincorrectly": 30337, + "atto": 30338, + "rization": 30339, + "ismuth": 30340, + "Ġiris": 30341, + "imentary": 30342, + "Ġinflationary": 30343, + "Ġoutflows": 30344, + "ĠLic": 30345, + "oreductase": 30346, + "Ġproceeding": 30347, + "ĠTAC": 30348, + "ĠHTL": 30349, + "Ġresides": 30350, + "stral": 30351, + "ĠTransf": 30352, + "Ġdichotom": 30353, + "Filter": 30354, + "June": 30355, + "isure": 30356, + "ĠAde": 30357, + "Ġijk": 30358, + "ĠPhilos": 30359, + "Ġstayed": 30360, + "Ġtamoxifen": 30361, + "Ġasparagine": 30362, + "exception": 30363, + "Ġaccumulating": 30364, + "astro": 30365, + "Change": 30366, + "uzi": 30367, + "Ġlon": 30368, + "Instead": 30369, + "Ġcentrally": 30370, + "ĠDental": 30371, + "classified": 30372, + "ĠEgyptian": 30373, + "Address": 30374, + "ĠQuaternary": 30375, + "ĠUSP": 30376, + "coin": 30377, + "Ġembryogenesis": 30378, + "ï̍": 30379, + "Null": 30380, + "ĠMixing": 30381, + "intensive": 30382, + "Ġnormative": 30383, + "ĠLef": 30384, + "Ġrumen": 30385, + "ĠThai": 30386, + "Ġswallow": 30387, + "Component": 30388, + "Ġrobotics": 30389, + "ĠCad": 30390, + "ĠCIP": 30391, + "ĠAcids": 30392, + "ĠOffic": 30393, + "urer": 30394, + "ĠWick": 30395, + "Ġkink": 30396, + "ĠScha": 30397, + "ĠCharacteristic": 30398, + "families": 30399, + "ĠGCs": 30400, + "ĠOptimizing": 30401, + "Ġtimer": 30402, + "él": 30403, + "jin": 30404, + "reversal": 30405, + "Ġsandstone": 30406, + "HN": 30407, + "tk": 30408, + "Ġptr": 30409, + "Ġmonochromatic": 30410, + "Ġfeedforward": 30411, + "dington": 30412, + "Ġcriticism": 30413, + "Ġsig": 30414, + "Ġpace": 30415, + "ĠTK": 30416, + "ĠWas": 30417, + "Ġcertificate": 30418, + "Ġstuck": 30419, + "Ġcorrid": 30420, + "Ġlocalisation": 30421, + "Ġsilk": 30422, + "Ġdigest": 30423, + "ĠTemple": 30424, + "ĠPosterior": 30425, + "Ġcommutator": 30426, + "tsch": 30427, + "perme": 30428, + "ysed": 30429, + "Ġmenu": 30430, + "Ġmidw": 30431, + "ocatalytic": 30432, + "Ġppb": 30433, + "Types": 30434, + "arri": 30435, + "ĠLOD": 30436, + "Ġloan": 30437, + "secret": 30438, + "Ġcarbons": 30439, + "ĠHolog": 30440, + "olipids": 30441, + "Ġuplo": 30442, + "ĠDNase": 30443, + "Ġpuzzle": 30444, + "Ġstance": 30445, + "ĠManchester": 30446, + "ĠDetector": 30447, + "ims": 30448, + "ĠTerms": 30449, + "ĠPGC": 30450, + "Ġincidents": 30451, + "ieh": 30452, + "ĠIDs": 30453, + "ĠAhmad": 30454, + "Ġnights": 30455, + "Ġbiomo": 30456, + "ĠMethylation": 30457, + "uator": 30458, + "resize": 30459, + "ĠFinger": 30460, + "ĠWo": 30461, + "Ġposter": 30462, + "Ġsolidification": 30463, + "ĠValidity": 30464, + "ĠDendritic": 30465, + "Ġadherent": 30466, + "issions": 30467, + "inction": 30468, + "Ġantagonistic": 30469, + "ĠPreliminaries": 30470, + "Ġcoval": 30471, + "Ġmovies": 30472, + "Ġbudding": 30473, + "Kn": 30474, + "ĠGit": 30475, + "ĠThereafter": 30476, + "Ġcapacitive": 30477, + "Az": 30478, + "ĠTLS": 30479, + "Ġinitiates": 30480, + "ĠDMR": 30481, + "Ġâī«": 30482, + "ĠMyocardial": 30483, + "ĠRotation": 30484, + "CONFIG": 30485, + "Ġvowel": 30486, + "Ġolivine": 30487, + "Hamiltonian": 30488, + "Ġstalk": 30489, + "Neu": 30490, + "Rest": 30491, + "anical": 30492, + "Ġdst": 30493, + "Ġresh": 30494, + "Ġexpressive": 30495, + "Ġinfectivity": 30496, + "oku": 30497, + "CTL": 30498, + "Frequency": 30499, + "Ġpremise": 30500, + "Walk": 30501, + "ĠâĹ": 30502, + "Ġrelapsed": 30503, + "tured": 30504, + "ĠUML": 30505, + "ovan": 30506, + "ĠResearchers": 30507, + "Ġconveniently": 30508, + "usk": 30509, + "INIT": 30510, + "Eqs": 30511, + "Factory": 30512, + "Ġunsteady": 30513, + "ĠAnsw": 30514, + "Ala": 30515, + "nitine": 30516, + "qp": 30517, + "ulous": 30518, + "research": 30519, + "ĠBrom": 30520, + "ĠDemoc": 30521, + "configuration": 30522, + "ulosic": 30523, + "Ġfra": 30524, + "Ġgift": 30525, + "Third": 30526, + "Claim": 30527, + "ÄŁ": 30528, + "odiazep": 30529, + "Ġprox": 30530, + "ocystis": 30531, + "ĠRPA": 30532, + "ĠLikert": 30533, + "RMS": 30534, + "tech": 30535, + "Ġacous": 30536, + "TLR": 30537, + "buck": 30538, + "ĠTherap": 30539, + "ussions": 30540, + "helor": 30541, + "ĠEmotion": 30542, + "bird": 30543, + "Ġthio": 30544, + "Ġquantitation": 30545, + "bracket": 30546, + "Ġpercept": 30547, + "Ġsubcategory": 30548, + "Ġlightning": 30549, + "Ġhernia": 30550, + "Ġneurotrophic": 30551, + "SDS": 30552, + "ĠAnders": 30553, + "Ġslowing": 30554, + "strongly": 30555, + "ĠCounting": 30556, + "ĠIncluding": 30557, + "ductions": 30558, + "ubated": 30559, + "ĠStorm": 30560, + "correlated": 30561, + "Ġautoantibodies": 30562, + "ĠMerg": 30563, + "ocer": 30564, + "micutes": 30565, + "Ġnonlinearities": 30566, + "ĠCentury": 30567, + "ĠLandscape": 30568, + "ĠDerivatives": 30569, + "ĠContrary": 30570, + "Ġcompile": 30571, + "ĠHepatic": 30572, + "Ġponds": 30573, + "Ġorganize": 30574, + "DMSO": 30575, + "Position": 30576, + "Ġbrach": 30577, + "Ġinflat": 30578, + "ospace": 30579, + "Ġskewness": 30580, + "Ġagitation": 30581, + "ĠHOMO": 30582, + "EU": 30583, + "Ġcommented": 30584, + "Ġcorpora": 30585, + "Ġmalt": 30586, + "Hermitian": 30587, + "iday": 30588, + "ĠHelmholtz": 30589, + "roblast": 30590, + "ĠCTR": 30591, + "unching": 30592, + "ĠMond": 30593, + "ĠComment": 30594, + "Ġosteosarcoma": 30595, + "posterior": 30596, + "Ġthymus": 30597, + "Ġcigarettes": 30598, + "NW": 30599, + "olem": 30600, + "ĠHox": 30601, + "ĠNFL": 30602, + "ĠAvailable": 30603, + "ĠSiber": 30604, + "ĠFeld": 30605, + "Ġborderline": 30606, + "Ġbeats": 30607, + "Ġorganised": 30608, + "Ġdistinguishes": 30609, + "Ġdialog": 30610, + "ĠBerger": 30611, + "oleic": 30612, + "Ġnumbered": 30613, + "Ġreachable": 30614, + "ĠRobertson": 30615, + "ĠChamber": 30616, + "ndarray": 30617, + "Ġcytoskeletal": 30618, + "Ġblending": 30619, + "blood": 30620, + "Import": 30621, + "Ġoverwhelming": 30622, + "Ġio": 30623, + "Ġoutage": 30624, + "ĠScholar": 30625, + "placing": 30626, + "ĠPolyp": 30627, + "Decl": 30628, + "ĠMEDLINE": 30629, + "ĠKM": 30630, + "ĠDAP": 30631, + "errors": 30632, + "ĠSHR": 30633, + "ĠDex": 30634, + "ĠGAS": 30635, + "ĠGian": 30636, + "Ġclinicopathological": 30637, + "Ġïģ·": 30638, + "ĠPredictions": 30639, + "ĠQuadratic": 30640, + "Ġarrhythmias": 30641, + "arid": 30642, + "Ġclothing": 30643, + "ĠFracture": 30644, + "ĉĠĠĠĠĠ": 30645, + "addy": 30646, + "ĠAlberta": 30647, + "ĠWed": 30648, + "phire": 30649, + "ĠEncryp": 30650, + "ĠLAB": 30651, + "ĠFano": 30652, + "CTT": 30653, + "Ġoryz": 30654, + "iliac": 30655, + "ĠLiao": 30656, + "versus": 30657, + "Ġmeso": 30658, + "Ġmidpoint": 30659, + "Ġstator": 30660, + "ĠJenn": 30661, + "ovsky": 30662, + "Ġuncover": 30663, + "erenn": 30664, + "ĠMcM": 30665, + "âīĪ": 30666, + "ĠCircuits": 30667, + "Ġfetuses": 30668, + "Ġagglomer": 30669, + "Ġfb": 30670, + "Ġyy": 30671, + "atech": 30672, + "ARG": 30673, + "Ġbaumannii": 30674, + "Ġellipsoid": 30675, + "Ġloses": 30676, + "Ġunve": 30677, + "Ġbutt": 30678, + "Ġmulticentre": 30679, + "iline": 30680, + "Ġresort": 30681, + "Ġcerebrovascular": 30682, + "ĠDecreased": 30683, + "jud": 30684, + "sus": 30685, + "amol": 30686, + "constraints": 30687, + "Ġteen": 30688, + "ĠPassive": 30689, + "ĠCaucasian": 30690, + "Ġcran": 30691, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 30692, + "ün": 30693, + "ĠDNMT": 30694, + "Ġterror": 30695, + "adrenal": 30696, + "Ġangiogenic": 30697, + "ĠInhibitory": 30698, + "prag": 30699, + "Ġcob": 30700, + "elsh": 30701, + "Ġenhancements": 30702, + "ĠShaw": 30703, + "ĠTakahashi": 30704, + "Ġsulphur": 30705, + "Ġgravitation": 30706, + "ĠPVDF": 30707, + "must": 30708, + "¢": 30709, + "asymptotic": 30710, + "elman": 30711, + "ĠPros": 30712, + "ĠMAD": 30713, + "ĠLen": 30714, + "therapy": 30715, + "efully": 30716, + "sulfur": 30717, + "ĠTCA": 30718, + "additive": 30719, + "talk": 30720, + "Ġpiglets": 30721, + "Ġprospect": 30722, + "ecundity": 30723, + "ĠXiang": 30724, + "handler": 30725, + "Ġclath": 30726, + "Ġmillimeter": 30727, + "jar": 30728, + "Ġbiophysical": 30729, + "Ġcomplexities": 30730, + "ĠHerb": 30731, + "Ġrecovers": 30732, + "ĠVincent": 30733, + "ĠPuerto": 30734, + "Earth": 30735, + "RAM": 30736, + "Ġcables": 30737, + "designed": 30738, + "ĠOscillation": 30739, + "Ġmeiosis": 30740, + "Ġfleet": 30741, + "ĠHuntington": 30742, + "ĠBeg": 30743, + "ĠECs": 30744, + "ĠAntic": 30745, + "Ġpractitioner": 30746, + "cultural": 30747, + "kat": 30748, + "Ġrecoil": 30749, + "ĠImplicit": 30750, + "Ġsummaries": 30751, + "Ġdiscontinued": 30752, + "Ġencompassing": 30753, + "ĠAltogether": 30754, + "ĠDIST": 30755, + "Ġconstellation": 30756, + "ĠExisting": 30757, + "Ġconductors": 30758, + "oplasm": 30759, + "ĠCosmology": 30760, + "Zero": 30761, + "ĠInform": 30762, + "Ġendangered": 30763, + "Ġweapons": 30764, + "atype": 30765, + "ĠAsc": 30766, + "Ġfluence": 30767, + "Ġferric": 30768, + "ĠLaurent": 30769, + "Early": 30770, + "Ġsgn": 30771, + "ĠHadamard": 30772, + "Ġastron": 30773, + "Cys": 30774, + "ĠThm": 30775, + "Ġdece": 30776, + "erencing": 30777, + "ĠMeans": 30778, + "Ġhydrated": 30779, + "ÙĪ": 30780, + "Ġrigorously": 30781, + "Ġambulatory": 30782, + "ĠDOI": 30783, + "Handle": 30784, + "ĠEnterobacteriaceae": 30785, + "ĠRQ": 30786, + "ĠGFR": 30787, + "prote": 30788, + "Ġmigrated": 30789, + "thening": 30790, + "ĠHopkins": 30791, + "ĠPsychology": 30792, + "igl": 30793, + "ĠEDS": 30794, + "Ġâζ": 30795, + "Ġremotely": 30796, + "ĠÂ¥": 30797, + "Ġinspiration": 30798, + "ĠâĮ¬": 30799, + "olian": 30800, + "Ġsaliency": 30801, + "ĠDog": 30802, + "ĠRosa": 30803, + "oya": 30804, + "Ġoccupies": 30805, + "camera": 30806, + "Ġdecompression": 30807, + "Ġscatt": 30808, + "Ġinvestigator": 30809, + "Ġcounterex": 30810, + "ĠIFNγ": 30811, + "ĠPittsburgh": 30812, + "Ġadminister": 30813, + "negl": 30814, + "ussis": 30815, + "MPC": 30816, + "ĠSwitching": 30817, + "Ġcooler": 30818, + "Ġbronchi": 30819, + "Ġparalle": 30820, + "Ġspeckle": 30821, + "Ġphysiologic": 30822, + "INVAL": 30823, + "Ġheterologous": 30824, + "|||": 30825, + "orghum": 30826, + "GAL": 30827, + "Ġmalformations": 30828, + "Ġweakening": 30829, + "Ġpsycho": 30830, + "ĠIH": 30831, + "Ġcontradictory": 30832, + "Ġphonological": 30833, + "ĠPerturbation": 30834, + "bB": 30835, + "ĠNos": 30836, + "TRUE": 30837, + "folding": 30838, + "phenol": 30839, + "ĠLSM": 30840, + "ĠâĪĹ": 30841, + "ĠAngle": 30842, + "Ġprovincial": 30843, + "FeO": 30844, + "ÅĽ": 30845, + "ĠIber": 30846, + "ressors": 30847, + "Ġproliferating": 30848, + "zers": 30849, + "organism": 30850, + "âĨĵ": 30851, + "ZO": 30852, + "cimg": 30853, + "Ġunperturbed": 30854, + "Ġjj": 30855, + "Ġelectrodynamics": 30856, + "ĠEpit": 30857, + "NTs": 30858, + "ĠBloom": 30859, + "Ġlanth": 30860, + "aminant": 30861, + "ĠSwift": 30862, + "European": 30863, + "Ġafferent": 30864, + "Reduce": 30865, + "published": 30866, + "ĠFitting": 30867, + "ĠFungal": 30868, + "Ġtribe": 30869, + "recting": 30870, + "Ġconjugacy": 30871, + "imeters": 30872, + "ĠCec": 30873, + "ĠKH": 30874, + "castle": 30875, + "Ġseptal": 30876, + "releasing": 30877, + "Ġoss": 30878, + "Ġ¦": 30879, + "ĠMissing": 30880, + "ĠFatigue": 30881, + "ĠBaseball": 30882, + "Ġimmunoblotting": 30883, + "Ġoh": 30884, + "orations": 30885, + "Ġvine": 30886, + "azy": 30887, + "serum": 30888, + "Ġlookup": 30889, + "Ġneovascular": 30890, + "iah": 30891, + "soil": 30892, + "Ġairflow": 30893, + "ĠSloan": 30894, + "him": 30895, + "çļ": 30896, + "located": 30897, + "zantine": 30898, + "ĠSuccessful": 30899, + "eminal": 30900, + "ĠDimensional": 30901, + "ĠNSA": 30902, + "ĠLogistic": 30903, + "emetery": 30904, + "Ġbrak": 30905, + "antal": 30906, + "south": 30907, + "Ġprototypes": 30908, + "Ġadvised": 30909, + "Ġidealized": 30910, + "ophytic": 30911, + "nbsp": 30912, + "Binary": 30913, + "Hyp": 30914, + "Joh": 30915, + "polation": 30916, + "Ġpolyvinyl": 30917, + "estimated": 30918, + "Ġoxytocin": 30919, + "ĠLetter": 30920, + "ĠImpair": 30921, + "Ġenvelopes": 30922, + "mainly": 30923, + "Ġmys": 30924, + "Ġintras": 30925, + "Ġbiogenic": 30926, + "cysteine": 30927, + "Ġuric": 30928, + "ĠCyan": 30929, + "ryption": 30930, + "Ġphotoreceptor": 30931, + "ĠToxic": 30932, + "ĠGamm": 30933, + "Ġcontainment": 30934, + "IgG": 30935, + "Squ": 30936, + "Ġperfused": 30937, + "Ġbiosensors": 30938, + "Ġmagmatic": 30939, + "Rate": 30940, + "ĠTf": 30941, + "Ġsecrete": 30942, + "Ġcriticality": 30943, + "Ġcompositionally": 30944, + "ĠBruce": 30945, + "SZ": 30946, + "ĠSport": 30947, + "ĠEI": 30948, + "Ġdiseased": 30949, + "Ġpreschool": 30950, + "ĠHarvey": 30951, + "ĠPTH": 30952, + "Ġbilayers": 30953, + "ĠOscillations": 30954, + "ĠHonor": 30955, + "ĠCCN": 30956, + "ĠMOT": 30957, + "ĠLloyd": 30958, + "Ġtrapez": 30959, + "Ġbuds": 30960, + "OFFSET": 30961, + "Ġmacromolecules": 30962, + "Ġbilirubin": 30963, + "olly": 30964, + "Ġutilities": 30965, + "ministered": 30966, + "Ġglobe": 30967, + "OLOGY": 30968, + "ropods": 30969, + "ĠMDM": 30970, + "ĠPyObject": 30971, + "macroph": 30972, + "ĠPBMCs": 30973, + "ospheres": 30974, + "Ġcatastrophic": 30975, + "ĠNavigation": 30976, + "ĠLSD": 30977, + "Ġcream": 30978, + "Ġdereg": 30979, + "bonded": 30980, + "rents": 30981, + "Ġpotentiation": 30982, + "Ġstro": 30983, + "Ġsteeper": 30984, + "ulinum": 30985, + "Ġperiodontitis": 30986, + "arization": 30987, + "âĪª": 30988, + "amicin": 30989, + "Ġmagnetized": 30990, + "ĠNutritional": 30991, + "Ġaccord": 30992, + "gaard": 30993, + "FTIR": 30994, + "ramethyl": 30995, + "ĠGle": 30996, + "Mel": 30997, + "ĠCTL": 30998, + "Ġtranslating": 30999, + "Ġautoimmunity": 31000, + "olerant": 31001, + "triangleq": 31002, + "amo": 31003, + "Ġvel": 31004, + "ĠHCN": 31005, + "ĠHamming": 31006, + "ĠVenus": 31007, + "ĠGad": 31008, + "ĠOwing": 31009, + "Information": 31010, + "ĠSchemes": 31011, + "carotene": 31012, + "Its": 31013, + "anis": 31014, + "Ġreplay": 31015, + "Ġtouc": 31016, + "LECT": 31017, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 31018, + "Ġtabulated": 31019, + "ĠSchottky": 31020, + "Far": 31021, + "amation": 31022, + "ĠRies": 31023, + "Ġexpects": 31024, + "ĠInstability": 31025, + "Ġsons": 31026, + "Ġdeck": 31027, + "Ġïģ¥": 31028, + "ĠSignature": 31029, + "Ġlithosphere": 31030, + "WW": 31031, + "makers": 31032, + "ughters": 31033, + "Ġâİ¡": 31034, + "ardian": 31035, + "à¦": 31036, + "Ġaccepts": 31037, + "ĠOSA": 31038, + "Ġγδ": 31039, + "nonumber": 31040, + "Select": 31041, + "lite": 31042, + "ĠAqueous": 31043, + "agawa": 31044, + "ĠEdinburgh": 31045, + "ĠMembranes": 31046, + "ĠSIG": 31047, + "akia": 31048, + "Ġtestes": 31049, + "Ġheli": 31050, + "++++": 31051, + "Ġultrafast": 31052, + "Ġmaneuver": 31053, + "ĠDate": 31054, + "phin": 31055, + "ĠKad": 31056, + "Ġtransferase": 31057, + "Pers": 31058, + "Ġtones": 31059, + "ĠSGD": 31060, + "anto": 31061, + "ĠOrange": 31062, + "ĠGeography": 31063, + "ĠAccumulation": 31064, + "aty": 31065, + "Ġbeating": 31066, + "Ġoverlying": 31067, + "ĠNDVI": 31068, + "ĠTownship": 31069, + "jing": 31070, + "ĠNOS": 31071, + "player": 31072, + "ĠMDD": 31073, + "ĠHungarian": 31074, + "Ġdw": 31075, + "ĠHin": 31076, + "Ġvalidating": 31077, + "Ġcolorimetric": 31078, + "ĠSupersymmetric": 31079, + "FUNC": 31080, + "gically": 31081, + "ofuran": 31082, + "-------": 31083, + "Ġimping": 31084, + "similarity": 31085, + "ĠDOX": 31086, + "ĠGlo": 31087, + "ivirus": 31088, + "listed": 31089, + "Ġbusy": 31090, + "iprofloxacin": 31091, + "Ġanxi": 31092, + "Ġblunt": 31093, + "Ġprocedural": 31094, + "Ġunknowns": 31095, + "AdS": 31096, + "thickness": 31097, + "follows": 31098, + "closing": 31099, + "environmental": 31100, + "ĠFeeding": 31101, + "unami": 31102, + "ende": 31103, + "ipine": 31104, + "Ġimpacting": 31105, + "Ġpenetrating": 31106, + "ambia": 31107, + "ĠWavelet": 31108, + "Ġfilamentous": 31109, + "Ġleng": 31110, + "ĠSCA": 31111, + "ĠEther": 31112, + "metall": 31113, + "Ġfringe": 31114, + "ĠAdjust": 31115, + "usz": 31116, + "ĠRey": 31117, + "ĠBoyd": 31118, + "Ġburnout": 31119, + "Ġcook": 31120, + "Ġnowadays": 31121, + "ĠDispersion": 31122, + "ĠRodriguez": 31123, + "Factor": 31124, + "ĠOklahoma": 31125, + "Ġunital": 31126, + "Ġpredictability": 31127, + "Ġlithography": 31128, + "ès": 31129, + "Willi": 31130, + "unal": 31131, + "asting": 31132, + "correction": 31133, + "ĠDed": 31134, + "ĠSocio": 31135, + "ĠChapman": 31136, + "ĠEco": 31137, + "Ġoncogene": 31138, + "ĠDrive": 31139, + "Ġfunnel": 31140, + "uis": 31141, + "ĠGENER": 31142, + "ĠACR": 31143, + "Ġworkloads": 31144, + "Ġoctahedral": 31145, + "vich": 31146, + "enburg": 31147, + "Ġimproper": 31148, + "decoded": 31149, + "Ġimmunosorbent": 31150, + "Ġinhomogeneity": 31151, + "RK": 31152, + "onically": 31153, + "Ġglycoproteins": 31154, + "onics": 31155, + "ĠFok": 31156, + "ĠBras": 31157, + "ĠCalculus": 31158, + "ĠMoss": 31159, + "ĠRK": 31160, + "Ġviolet": 31161, + "Ġlymphomas": 31162, + "enspace": 31163, + "ĠPalae": 31164, + "Ġrenin": 31165, + "phant": 31166, + "ĠRED": 31167, + "Ġfaulty": 31168, + "Riemann": 31169, + "Ãī": 31170, + "ĠElli": 31171, + "Bol": 31172, + "Tn": 31173, + "Yang": 31174, + "gender": 31175, + "Ġdetuning": 31176, + "Ġoperon": 31177, + "Ġinsecticide": 31178, + "esi": 31179, + "amon": 31180, + "ĠSCD": 31181, + "ĠBath": 31182, + "ĠâĢĸ": 31183, + "ĠGeographic": 31184, + "Ġcyclohex": 31185, + "ĠConfidence": 31186, + "Ġcomet": 31187, + "Ġfolate": 31188, + "observer": 31189, + "Ġvisitors": 31190, + "extra": 31191, + "ateness": 31192, + "ĠSPT": 31193, + "arcane": 31194, + "Ġholistic": 31195, + "semi": 31196, + "ĠMild": 31197, + "Ġsmear": 31198, + "Ġcyclase": 31199, + "Ġanymore": 31200, + "Ġseagrass": 31201, + "Ġconsortium": 31202, + "Ġfinishes": 31203, + "cyan": 31204, + "ductance": 31205, + "frost": 31206, + "hereafter": 31207, + "Ġprescriptions": 31208, + "Ġcmd": 31209, + "ĠPerceived": 31210, + "coordinates": 31211, + "Ġstyl": 31212, + "ĠBard": 31213, + "ĠHoll": 31214, + "ĠsiRNAs": 31215, + "sugg": 31216, + "Ġthr": 31217, + "Ġmainland": 31218, + "SCH": 31219, + "Ġassertions": 31220, + "Ġbabies": 31221, + "Ġrecapit": 31222, + "Tok": 31223, + "Ġresected": 31224, + "construct": 31225, + "Ber": 31226, + "Ġcholine": 31227, + "Ġunitarity": 31228, + "Ġcatalyzes": 31229, + "detector": 31230, + "ĠSMB": 31231, + "tery": 31232, + "cluded": 31233, + "ĠAbbreviations": 31234, + "ĠOliveira": 31235, + "LOC": 31236, + "zin": 31237, + "ĠLorenz": 31238, + "Kernel": 31239, + "lyn": 31240, + "ĠLEP": 31241, + "soni": 31242, + "Ġseptum": 31243, + "TMS": 31244, + "Ġunmodified": 31245, + "borough": 31246, + "ĠAudio": 31247, + "Ġdollars": 31248, + "CMD": 31249, + "Ġnorthwestern": 31250, + "Ġpalmit": 31251, + "ragalactic": 31252, + "ĠMiz": 31253, + "FH": 31254, + "confidence": 31255, + "NEXT": 31256, + "ĠAGE": 31257, + "ĠEqn": 31258, + "ĠClasses": 31259, + "Ġmisleading": 31260, + "ĠPKA": 31261, + "Ġanchored": 31262, + "ĠRip": 31263, + "phag": 31264, + "Ġintubation": 31265, + "ĠAngular": 31266, + "ĠBEC": 31267, + "Thr": 31268, + "Ġorganisations": 31269, + "Ġcomfortable": 31270, + "Ġcommissioned": 31271, + "poll": 31272, + "ydia": 31273, + "instead": 31274, + "Ġpassword": 31275, + "Ġcompliant": 31276, + "ĠPrecipitation": 31277, + "ophosphamide": 31278, + "usters": 31279, + "Ġpneumococcal": 31280, + "Ġtomographic": 31281, + "tidae": 31282, + "ĠFirmicutes": 31283, + "bw": 31284, + "ĠPDB": 31285, + "ĠGPUs": 31286, + "ĠPlanar": 31287, + "Ġverbose": 31288, + "Summary": 31289, + "lance": 31290, + "ĠEGFP": 31291, + "ongru": 31292, + "Complex": 31293, + "ĠWheat": 31294, + "uche": 31295, + "ĠMCA": 31296, + "ĠProjection": 31297, + "Ġstats": 31298, + "Ġsummand": 31299, + "dimethoxyphenyl": 31300, + "ĠABSTRACT": 31301, + "Ġcarotenoid": 31302, + "Ġbroke": 31303, + "ĠDesigning": 31304, + "ĠHetero": 31305, + "ĠCarlsbad": 31306, + "Cov": 31307, + "ineral": 31308, + "Ġanalyte": 31309, + "ĠColeman": 31310, + "Ġeigenstate": 31311, + "ĠHolland": 31312, + "ERSION": 31313, + "ĠDak": 31314, + "ellers": 31315, + "ĠÃĺ": 31316, + "missing": 31317, + "deposited": 31318, + "ĠLincoln": 31319, + "anion": 31320, + "ĠSPEC": 31321, + "Ġfertilizer": 31322, + "ĠCPS": 31323, + "Ġcofactor": 31324, + "Ġtren": 31325, + "Ġcalendar": 31326, + "Ġyoungest": 31327, + "STATUS": 31328, + "ĠEXPERIMENTAL": 31329, + "Ġsr": 31330, + "Ġnl": 31331, + "ĠMES": 31332, + "Study": 31333, + "padding": 31334, + "Ġatopic": 31335, + "ĠOG": 31336, + "Ġentrainment": 31337, + "AFM": 31338, + "ĠCou": 31339, + "Web": 31340, + "ĠMicroscopic": 31341, + "Ġunambiguously": 31342, + "Day": 31343, + "yotrophic": 31344, + "reous": 31345, + "Ġsarcom": 31346, + "ĠVAL": 31347, + "Ġhindered": 31348, + "ĠREM": 31349, + "otrexate": 31350, + "ocarcin": 31351, + "ĠAlk": 31352, + "Ġbrevity": 31353, + "factual": 31354, + "Cer": 31355, + "diox": 31356, + "ophical": 31357, + "Ġlytic": 31358, + "Take": 31359, + "Ġintend": 31360, + "ĠCla": 31361, + "Ġasteroid": 31362, + "ĠSEP": 31363, + "apenem": 31364, + "universal": 31365, + "Ġoceans": 31366, + "Ġmonoid": 31367, + "Ġseparator": 31368, + "ĠPorous": 31369, + "Ġpostoperatively": 31370, + "Ġsemin": 31371, + "ĠDisplay": 31372, + "Ġhydrolase": 31373, + "transferases": 31374, + "Ġthrombus": 31375, + "ĠOv": 31376, + "ĠDielectric": 31377, + "Ġcompelling": 31378, + "assing": 31379, + "ĠMAS": 31380, + "ullary": 31381, + "ĠMori": 31382, + "ĠPathogenesis": 31383, + "ĠBreaking": 31384, + "ĠPLGA": 31385, + "cooling": 31386, + "§": 31387, + "Ġfee": 31388, + "Ġreducible": 31389, + "Ġdiverge": 31390, + "Ġqueues": 31391, + "Ġmushroom": 31392, + "Ġdeacetylase": 31393, + "YFP": 31394, + "Ġdisreg": 31395, + "ĠArrays": 31396, + "processes": 31397, + "ĠTransportation": 31398, + "Ġundetectable": 31399, + "bursts": 31400, + "Ġphospholipase": 31401, + "Option": 31402, + "asin": 31403, + "Ġnocturnal": 31404, + "tez": 31405, + "ĠDisruption": 31406, + "oserine": 31407, + "behavior": 31408, + "ĠTony": 31409, + "ĠKot": 31410, + "ieval": 31411, + "Ġmyofib": 31412, + "Ġhalogen": 31413, + "ĠCPR": 31414, + "ployed": 31415, + "ĠPolymers": 31416, + "Ġadenoma": 31417, + "Ġquartile": 31418, + "Ġquaternary": 31419, + "ĠIraq": 31420, + "Ġsieve": 31421, + "Ġintractable": 31422, + "Ġfabrics": 31423, + "continuum": 31424, + "ĠEmergence": 31425, + "Pot": 31426, + "itism": 31427, + "veness": 31428, + "hoe": 31429, + "Ġredes": 31430, + "ĠHRP": 31431, + "ploidy": 31432, + "picuous": 31433, + "ogo": 31434, + "ĠGag": 31435, + "Ġnominated": 31436, + "occupied": 31437, + "Ġquench": 31438, + "ropolis": 31439, + "nucleotide": 31440, + "ĠEventually": 31441, + "Ñı": 31442, + "ĠClock": 31443, + "ĠSteady": 31444, + "opolymers": 31445, + "ĠARE": 31446, + "irnov": 31447, + "helf": 31448, + "blob": 31449, + "download": 31450, + "PLL": 31451, + "UNT": 31452, + "predictions": 31453, + "Ġoccipital": 31454, + "toxic": 31455, + "ĠVice": 31456, + "Ġangio": 31457, + "CuO": 31458, + "Ġresistances": 31459, + "fflffl": 31460, + "Distribution": 31461, + "Gre": 31462, + "onamide": 31463, + "ĠIOP": 31464, + "UNEL": 31465, + "Ġaids": 31466, + "ĠHUV": 31467, + "ECM": 31468, + "ĠPAD": 31469, + "ĠAgNPs": 31470, + "Print": 31471, + "Ġlamellar": 31472, + "ĠUltrason": 31473, + "severe": 31474, + "ĠAnnotation": 31475, + "NIR": 31476, + "sgn": 31477, + "ĠOften": 31478, + "Ġiterate": 31479, + "Ġcarriage": 31480, + "spherical": 31481, + "ĠFrid": 31482, + "Ġdiffract": 31483, + "ĠBasal": 31484, + "Ġunsatisf": 31485, + "ĠDysfunction": 31486, + "arboxylic": 31487, + "ĠCollective": 31488, + "Ġdegrading": 31489, + "Ġadiposity": 31490, + "Ġfifty": 31491, + "Ġpars": 31492, + "ĠOptimized": 31493, + "ocaine": 31494, + "Ġbb": 31495, + "ĠShip": 31496, + "ĠLW": 31497, + "Ġtremor": 31498, + "Ġã": 31499, + "Ġnucleons": 31500, + "Ġscientist": 31501, + "ĠMish": 31502, + "gression": 31503, + "ĠMerc": 31504, + "ĠFlem": 31505, + "Ġcorals": 31506, + "Incre": 31507, + "ĠDSP": 31508, + "Ġdefenses": 31509, + "dimer": 31510, + "atherine": 31511, + "otubes": 31512, + "stride": 31513, + "ĠAlterations": 31514, + "Ġoest": 31515, + "ĠBIC": 31516, + "Ġradiated": 31517, + "Ġketamine": 31518, + "Ġdissimilarity": 31519, + "ĠAncient": 31520, + "ĠHed": 31521, + "Ġattr": 31522, + "ĠIsa": 31523, + "Ġionospheric": 31524, + "Ġgovernor": 31525, + "ĠEstimated": 31526, + "Ġultrathin": 31527, + "Update": 31528, + "Ġimmunoassay": 31529, + "Ġconjectured": 31530, + "ĠREF": 31531, + "ĠSiegel": 31532, + "Adv": 31533, + "Mem": 31534, + "Ġpups": 31535, + "ĠAPPL": 31536, + "ecomposable": 31537, + "journal": 31538, + "ĠRol": 31539, + "ĠLob": 31540, + "rington": 31541, + "Ġnonsingular": 31542, + "Ġcitric": 31543, + "iones": 31544, + "ositis": 31545, + "ALY": 31546, + "Ġmentions": 31547, + "ĠMarkers": 31548, + "algebraic": 31549, + "Ġflattened": 31550, + "Ġmail": 31551, + "ĠTGA": 31552, + "ĠPMA": 31553, + "ĠNaval": 31554, + "Ġfacilitation": 31555, + "Ġunidentified": 31556, + "Ġempathy": 31557, + "jectories": 31558, + "logits": 31559, + "Ġpermanently": 31560, + "Ġbottles": 31561, + "ĠBengal": 31562, + "Ġpeanut": 31563, + "Ġcapillaries": 31564, + "erents": 31565, + "ĠLooking": 31566, + "changes": 31567, + "ĠMagell": 31568, + "ĠCMC": 31569, + "ĠVerm": 31570, + "Ġsubscales": 31571, + "demand": 31572, + "orexia": 31573, + "Ġachievements": 31574, + "ĠRobustness": 31575, + "ĠWallace": 31576, + "ĠDTT": 31577, + "ogels": 31578, + "ocker": 31579, + "ĠSpike": 31580, + "Ġpainter": 31581, + "Ġbuses": 31582, + "Ġpolluted": 31583, + "Ġtort": 31584, + "ĠPPP": 31585, + "nex": 31586, + "extended": 31587, + "ucalypt": 31588, + "Ġprostatic": 31589, + "ĠFCC": 31590, + "Ġkick": 31591, + "oyal": 31592, + "epochs": 31593, + "hss": 31594, + "yon": 31595, + "Ġdans": 31596, + "ĠAw": 31597, + "Ġadversely": 31598, + "Ġaltogether": 31599, + "Ġophthalm": 31600, + "Ġcpu": 31601, + "ĠFRET": 31602, + "Ġforensic": 31603, + "Ġhotspots": 31604, + "Ġpaintings": 31605, + "Ġomn": 31606, + "ĠpS": 31607, + "oglu": 31608, + "ofol": 31609, + "FTs": 31610, + "Ġdermat": 31611, + "pragma": 31612, + "Ġbump": 31613, + "ĠCir": 31614, + "aS": 31615, + "Ġnaked": 31616, + "ĠNLS": 31617, + "ĠSpitzer": 31618, + "Ġsalvage": 31619, + "Ġintuitively": 31620, + "Ġcasual": 31621, + "Ġfired": 31622, + "verages": 31623, + "ĠBurden": 31624, + "Wang": 31625, + "ylem": 31626, + "Ġradiographs": 31627, + "ĠSchiff": 31628, + "OLUTION": 31629, + "Cross": 31630, + "Ġhints": 31631, + "owing": 31632, + "ĠStreng": 31633, + "ĠANY": 31634, + "Ġworry": 31635, + "ĠRoger": 31636, + "Ġtrabecular": 31637, + "Band": 31638, + "ĠNec": 31639, + "ipes": 31640, + "tool": 31641, + "ĠILC": 31642, + "iÄĩ": 31643, + "ocean": 31644, + "ĠAri": 31645, + "AMA": 31646, + "ĠVertex": 31647, + "activate": 31648, + "Location": 31649, + "onts": 31650, + "Ġhs": 31651, + "Ġslender": 31652, + "refring": 31653, + "ĠEndogenous": 31654, + "adiabatic": 31655, + "Ġcryptic": 31656, + "Ġeradication": 31657, + "ĠKevin": 31658, + "Ġmc": 31659, + "Ġcardio": 31660, + "Ġphosphoryl": 31661, + "Witten": 31662, + "Ġscl": 31663, + "ĠIw": 31664, + "ĠMade": 31665, + "Ġfounding": 31666, + "oflag": 31667, + "aline": 31668, + "horizontal": 31669, + "ĠGeneralization": 31670, + "psychiatric": 31671, + "ĠDuncan": 31672, + "ĠSnO": 31673, + "ĠAar": 31674, + "Ġgg": 31675, + "Ġpremi": 31676, + "ĠStrom": 31677, + "ĠExplan": 31678, + "Ġlethality": 31679, + "ÏĤ": 31680, + "odo": 31681, + "Ġsubscrib": 31682, + "ĠSTUDY": 31683, + "Ġoutperformed": 31684, + "Ġcovalently": 31685, + "MHC": 31686, + "fail": 31687, + "ĠKac": 31688, + "EGR": 31689, + "ĠTRI": 31690, + "robot": 31691, + "ĠCandidate": 31692, + "ĠTNBC": 31693, + "Ġarchaeological": 31694, + "Eukary": 31695, + "Ġlava": 31696, + "dipole": 31697, + "Ġuncons": 31698, + "Anti": 31699, + "Ġprednis": 31700, + "ĠRobin": 31701, + "Ġstratigraphic": 31702, + "Ġ¤": 31703, + "Ġfinance": 31704, + "ĠStudio": 31705, + "render": 31706, + "Ġrearing": 31707, + "Ġger": 31708, + "ĠOpt": 31709, + "ĠManifolds": 31710, + "Ġdestabil": 31711, + "Ġtelomerase": 31712, + "Ġpicking": 31713, + "Ġamplicon": 31714, + "Ġyearly": 31715, + "ĠNCC": 31716, + "inser": 31717, + "ĠEnrichment": 31718, + "ĠMicrostructure": 31719, + "ĠWarren": 31720, + "ophysics": 31721, + "Ġfifteen": 31722, + "Åij": 31723, + "Ġreviewer": 31724, + "Ġskilled": 31725, + "Ġmagnetoresistance": 31726, + "Ġreconfiguration": 31727, + "Ġpoet": 31728, + "Ġpredetermined": 31729, + "Ġcryopres": 31730, + "Ġattractors": 31731, + "Ġprojectile": 31732, + "ĠCrystals": 31733, + "ĠMCM": 31734, + "ĠXanth": 31735, + "Ġclockwise": 31736, + "regnant": 31737, + "Ġgated": 31738, + "ryza": 31739, + "ĠProsp": 31740, + "adin": 31741, + "Ġmolybdenum": 31742, + "ĠAlps": 31743, + "ĠBald": 31744, + "Ġhalluc": 31745, + "udo": 31746, + "Ġmont": 31747, + "ĠFlash": 31748, + "Ġpulling": 31749, + "ĠLQ": 31750, + "ĠWalsh": 31751, + "ĠThomson": 31752, + "meson": 31753, + "Ġintercal": 31754, + "Ġelapsed": 31755, + "FFFF": 31756, + "ĠForecasting": 31757, + "à¯": 31758, + "ĠLSP": 31759, + "endorf": 31760, + "Ġxml": 31761, + "substrate": 31762, + "Mu": 31763, + "during": 31764, + "oconstr": 31765, + "EMA": 31766, + "Ġïĥ«": 31767, + "ĠDFS": 31768, + "ĠVon": 31769, + "Ġfathers": 31770, + "Ġunco": 31771, + "ĠUnderg": 31772, + "Ġmultiplexing": 31773, + "atra": 31774, + "Ġcohesive": 31775, + "ĠUI": 31776, + "ĠPrev": 31777, + "çļĦ": 31778, + "cum": 31779, + "hf": 31780, + "ĠSCN": 31781, + "atalysis": 31782, + "ĠArsen": 31783, + "amping": 31784, + "ĠPlastic": 31785, + "ĠMadison": 31786, + "Ġsupremum": 31787, + "ĠCited": 31788, + "Ġaren": 31789, + "iski": 31790, + "inel": 31791, + "stro": 31792, + "Ġcorrupted": 31793, + "Ġglab": 31794, + "Ġcardiopulmonary": 31795, + "Ġpragmatic": 31796, + "CAG": 31797, + "Stack": 31798, + "thioxo": 31799, + "ĠReproductive": 31800, + "Ġsteatosis": 31801, + "Best": 31802, + "ĠBars": 31803, + "Ġracing": 31804, + "ĠUtah": 31805, + "equivalence": 31806, + "ĠFifty": 31807, + "ĠCytokine": 31808, + "Ġutilised": 31809, + "horizon": 31810, + "ouracil": 31811, + "iversary": 31812, + "emer": 31813, + "ĠQuestions": 31814, + "Ġlinkages": 31815, + "anchez": 31816, + "VV": 31817, + "Ġphotodet": 31818, + "kowski": 31819, + "REST": 31820, + "Ġhosting": 31821, + "Ġpushing": 31822, + "Ġneurotoxicity": 31823, + "SQ": 31824, + "rst": 31825, + "Ġhockey": 31826, + "Ġtrips": 31827, + "ĠIndoor": 31828, + "ematics": 31829, + "Ġtransect": 31830, + "ĠABI": 31831, + "agar": 31832, + "âĪļ": 31833, + "egenerate": 31834, + "ĠQP": 31835, + "MID": 31836, + "ĠAccept": 31837, + "ĠCyber": 31838, + "North": 31839, + "Ġdθ": 31840, + "alla": 31841, + "Ġbraid": 31842, + "finding": 31843, + "alin": 31844, + "ĠLST": 31845, + "ĠLax": 31846, + "udin": 31847, + "ĠiNOS": 31848, + "convert": 31849, + "ACA": 31850, + "ĠGuan": 31851, + "Ġlymphocytic": 31852, + "Ġsyllable": 31853, + "ĠTOR": 31854, + "ĠSCR": 31855, + "ĠAJ": 31856, + "Ġoutburst": 31857, + "bladder": 31858, + "OTA": 31859, + "audio": 31860, + "chromen": 31861, + "ÑģÑĤ": 31862, + "Ġgratefully": 31863, + "Ġtiling": 31864, + "Ġquit": 31865, + "shan": 31866, + "ĠAccretion": 31867, + "Ġnarrowing": 31868, + "ĠInduces": 31869, + "Mic": 31870, + "Ġfuc": 31871, + "Ġthalamus": 31872, + "ANES": 31873, + "Ġquaternion": 31874, + "ĠListeria": 31875, + "duality": 31876, + "hend": 31877, + "ande": 31878, + "Ġparo": 31879, + "Ġinspected": 31880, + "question": 31881, + "ĠHoney": 31882, + "Ġchunks": 31883, + "Ġforearm": 31884, + "radients": 31885, + "ificantly": 31886, + "obank": 31887, + "Ġsomewhere": 31888, + "Ġmonetary": 31889, + "ĠLouisiana": 31890, + "Ġemulsions": 31891, + "Ġprogrammable": 31892, + "Ġmanifests": 31893, + "ĠMartinez": 31894, + "Ġted": 31895, + "emen": 31896, + "anni": 31897, + "Ġoverlaid": 31898, + "Ġvirulent": 31899, + "Mask": 31900, + "ĠUtility": 31901, + "Ġwk": 31902, + "osexual": 31903, + "ĠEarl": 31904, + "dar": 31905, + "hdr": 31906, + "ractors": 31907, + "Ġconstructor": 31908, + "Ġnascent": 31909, + "inzburg": 31910, + "ĠCraig": 31911, + "Ġplexus": 31912, + "reverse": 31913, + "ograv": 31914, + "tags": 31915, + "Ġcalibrate": 31916, + "à®": 31917, + "Ġhide": 31918, + "ĠFol": 31919, + "Ġinteracted": 31920, + "Ġconfron": 31921, + "market": 31922, + "Ġsociodemographic": 31923, + "ĠLucas": 31924, + "ĠMCT": 31925, + "ĠRSS": 31926, + "Ġmicroplate": 31927, + "underst": 31928, + "Ital": 31929, + "ĠCMR": 31930, + "recy": 31931, + "ĠPCOS": 31932, + "Ġdetoxification": 31933, + "Ġsubtree": 31934, + "Ġsubsections": 31935, + "Ġpropositions": 31936, + "Acknowledgements": 31937, + "reinforced": 31938, + "lis": 31939, + "ĠCIR": 31940, + "Ġimprinted": 31941, + "vium": 31942, + "afic": 31943, + "Ġchecklist": 31944, + "ĠRx": 31945, + "ĠEph": 31946, + "Ġsolder": 31947, + "transformation": 31948, + "ĠStrait": 31949, + "azar": 31950, + "Ġhandler": 31951, + "kelet": 31952, + "BCL": 31953, + "Math": 31954, + "Ġwishes": 31955, + "uminescent": 31956, + "ĠPEC": 31957, + "irt": 31958, + "ylidene": 31959, + "Ġloosely": 31960, + "naissance": 31961, + "ILs": 31962, + "foil": 31963, + "ĠGNU": 31964, + "ĠKet": 31965, + "vix": 31966, + "ĠPlain": 31967, + "ĠRES": 31968, + "Ġparenting": 31969, + "ĠConnection": 31970, + "Ġrhizosphere": 31971, + "oprevalence": 31972, + "iatic": 31973, + "ĠpA": 31974, + "ĠVil": 31975, + "setting": 31976, + "ĠReLU": 31977, + "ĠBOOST": 31978, + "Ġappreciate": 31979, + "bx": 31980, + "orest": 31981, + "ologie": 31982, + "Ġpalp": 31983, + "foo": 31984, + "usual": 31985, + "Ġquestioned": 31986, + "Ġtrigon": 31987, + "ĠGFAP": 31988, + "ĠKyoto": 31989, + "dise": 31990, + "antile": 31991, + "ück": 31992, + "ĠQuantization": 31993, + "Ġscler": 31994, + "Ġbehalf": 31995, + "ĠDuality": 31996, + "Ġmagnetically": 31997, + "Ġelegant": 31998, + "UA": 31999, + "epis": 32000, + "Ġsubclinical": 32001, + "ontrol": 32002, + "ĠChemicals": 32003, + "Utils": 32004, + "Ġlowers": 32005, + "extraction": 32006, + "Ġamplifiers": 32007, + "ĠEntry": 32008, + "ĠWORK": 32009, + "Ġthrombocytopenia": 32010, + "Mil": 32011, + "idus": 32012, + "embry": 32013, + "manager": 32014, + "ĠCoordination": 32015, + "ĠPhenotypic": 32016, + "chunk": 32017, + "Ġhypotension": 32018, + "Ġcryogenic": 32019, + "Ġreactants": 32020, + "ĠMMSE": 32021, + "Ġcentros": 32022, + "ĠButler": 32023, + "Ġcavitation": 32024, + "ĠLessons": 32025, + "estion": 32026, + "ĠMIS": 32027, + "associ": 32028, + "APE": 32029, + "ĠEulerian": 32030, + "Ġrecreational": 32031, + "ĠNeo": 32032, + "ĠCDM": 32033, + "repeat": 32034, + "details": 32035, + "Bal": 32036, + "STA": 32037, + "Ġâīº": 32038, + "ĠCamero": 32039, + "ĠTelevision": 32040, + "Ġworkforce": 32041, + "Ġcomputerized": 32042, + "Ġextraordinary": 32043, + "Ġribonucle": 32044, + "Ġhydrophobicity": 32045, + "ĠFeasibility": 32046, + "Ol": 32047, + "Tw": 32048, + "ĠMam": 32049, + "ĠFAC": 32050, + "profit": 32051, + "negligible": 32052, + "ĠFruit": 32053, + "Ġears": 32054, + "Ġshearing": 32055, + "ĠCorresponding": 32056, + "fun": 32057, + "ieck": 32058, + "mos": 32059, + "ĠEMI": 32060, + "ĠSometimes": 32061, + "Ġfluorine": 32062, + "Ġdetergent": 32063, + "Ġalg": 32064, + "races": 32065, + "ivable": 32066, + "COMM": 32067, + "ĠSwitch": 32068, + "Ġstrained": 32069, + "virtual": 32070, + "Temperature": 32071, + "Ġcredible": 32072, + "ĠGPCR": 32073, + "ĠDebye": 32074, + "ĠLit": 32075, + "Ġhemic": 32076, + "Ġtransducers": 32077, + "metast": 32078, + "adiene": 32079, + "Ġoryzae": 32080, + "tn": 32081, + "Ġafternoon": 32082, + "ĠArabian": 32083, + "ĠChromatin": 32084, + "Ġxenografts": 32085, + "Ġcryptographic": 32086, + "Ġaxillary": 32087, + "Ġvolunteer": 32088, + "ĠNevada": 32089, + "Ġpions": 32090, + "unknown": 32091, + "ĠFU": 32092, + "venously": 32093, + "radio": 32094, + "ĠLabour": 32095, + "ĠVillage": 32096, + "Ric": 32097, + "Ġmetat": 32098, + "Ġserotypes": 32099, + "regression": 32100, + "saturation": 32101, + "rera": 32102, + "Ġfarther": 32103, + "Ġrounding": 32104, + "Ġlibitum": 32105, + "Ġshuff": 32106, + "ĠOw": 32107, + "Ġlocalised": 32108, + "ĠALG": 32109, + "Ġhypertrophic": 32110, + "ppm": 32111, + "imine": 32112, + "ĠAthe": 32113, + "Ġanhydro": 32114, + "Ġsupramolecular": 32115, + "Ġmacros": 32116, + "aceted": 32117, + "ĠOliv": 32118, + "Ġmotivational": 32119, + "ĠCave": 32120, + "enzie": 32121, + "Ġaffiliated": 32122, + "Fermi": 32123, + "Ġequalities": 32124, + "ĠMilan": 32125, + "Ġdressed": 32126, + "Ġanger": 32127, + "ados": 32128, + "Ġavg": 32129, + "ĠPhon": 32130, + "Ġradioactivity": 32131, + "ĠEch": 32132, + "Ġorganoids": 32133, + "Ġïģ§": 32134, + "ĠAnthrop": 32135, + "lateral": 32136, + "Ġalpine": 32137, + "Ġaudit": 32138, + "WER": 32139, + "ĠCSC": 32140, + "Ġrankings": 32141, + "ĠERR": 32142, + "GLER": 32143, + "Obviously": 32144, + "ĠMadrid": 32145, + "obenzene": 32146, + "othermia": 32147, + "Ġresponsibilities": 32148, + "omestic": 32149, + "ĠInflation": 32150, + "Ġepidemics": 32151, + "Ġtaut": 32152, + "phos": 32153, + "ĠUnless": 32154, + "Ġgeomagnetic": 32155, + "ĠCFTR": 32156, + "veld": 32157, + "arietal": 32158, + "Ġendotoxin": 32159, + "ADP": 32160, + "Ġsuppressive": 32161, + "randial": 32162, + "Ġïĥ©": 32163, + "excited": 32164, + "ĠInnate": 32165, + "ĠLópez": 32166, + "omycetes": 32167, + "Ġbeautiful": 32168, + "irk": 32169, + "ĠHwang": 32170, + "ĠUSE": 32171, + "ÏĢi": 32172, + "Record": 32173, + "Attribute": 32174, + "Ġreacts": 32175, + "ĠBund": 32176, + "Ġcowork": 32177, + "Ġconfluence": 32178, + "ĠRegardless": 32179, + "Ġmetagenomic": 32180, + "MAL": 32181, + "Ġaided": 32182, + "anga": 32183, + "Ġamn": 32184, + "ĠICI": 32185, + "ĠPML": 32186, + "Ġdelivers": 32187, + "Ġkeyp": 32188, + "Ġbeetles": 32189, + "Ġoxidant": 32190, + "Immun": 32191, + "Ġrhythmic": 32192, + "female": 32193, + "JC": 32194, + "PAD": 32195, + "genitor": 32196, + "AMS": 32197, + "catalytic": 32198, + "ĠMom": 32199, + "ĠHert": 32200, + "adish": 32201, + "Ġcontention": 32202, + "Ġyolk": 32203, + "Ġdemyel": 32204, + "Ġsucc": 32205, + "Ġtravels": 32206, + "Ve": 32207, + "ĠFul": 32208, + "ĠRif": 32209, + "Ġintrons": 32210, + "encaps": 32211, + "colour": 32212, + "Ġhotel": 32213, + "Access": 32214, + "adoop": 32215, + "Ġcoalition": 32216, + "ĠMuh": 32217, + "ĠLTP": 32218, + "autom": 32219, + "ĠLak": 32220, + "Ġremedi": 32221, + "Ġtrailing": 32222, + "insulator": 32223, + "ĠRelig": 32224, + "ĠHudson": 32225, + "emics": 32226, + "OAc": 32227, + "ourt": 32228, + "Ġrelic": 32229, + "ĠMixture": 32230, + "Ġcalorimeter": 32231, + "ĠRDF": 32232, + "ĠHodgkin": 32233, + "Newtonian": 32234, + "ĠDelayed": 32235, + "ĠNortheast": 32236, + "hering": 32237, + "Ġhelices": 32238, + "Ġprincipally": 32239, + "Ġsuspicion": 32240, + "Ġextremities": 32241, + "Ġdeadline": 32242, + "ĠEnterococcus": 32243, + "mj": 32244, + "Ġhp": 32245, + "ĠNAS": 32246, + "ouss": 32247, + "Ġintramuscular": 32248, + "LIN": 32249, + "Ġchicks": 32250, + "Score": 32251, + "Ġfür": 32252, + "ĠRSA": 32253, + "Ġkr": 32254, + "Ġphotography": 32255, + "Ġclearing": 32256, + "holomorphic": 32257, + "them": 32258, + "Ġpom": 32259, + "ĠLis": 32260, + "Ġdiscard": 32261, + "Ġguan": 32262, + "cx": 32263, + "ubov": 32264, + "ĠConsistency": 32265, + "Ġplei": 32266, + "ĠUrinary": 32267, + "Ġbreadth": 32268, + "EI": 32269, + "mechan": 32270, + "Ġdq": 32271, + "ĠBlast": 32272, + "coeff": 32273, + "ILD": 32274, + "Ġunemployment": 32275, + "Arm": 32276, + "ĠCn": 32277, + "moderate": 32278, + "Ġaggress": 32279, + "Ġcircumf": 32280, + "los": 32281, + "Ġbaro": 32282, + "velope": 32283, + "Ġulcerative": 32284, + "Ġhelicase": 32285, + "HW": 32286, + "KG": 32287, + "rion": 32288, + "Ġgenotyped": 32289, + "Ġarid": 32290, + "ĠAndreas": 32291, + "Ġthereof": 32292, + "ĠOperating": 32293, + "ĠNEW": 32294, + "ĠAntibacterial": 32295, + "ĠDarwin": 32296, + "Ġreferee": 32297, + "Ġdome": 32298, + "agus": 32299, + "ĠDMD": 32300, + "ATOR": 32301, + "Currently": 32302, + "ĠInequalities": 32303, + "dN": 32304, + "olymer": 32305, + "empirical": 32306, + "ĠBraun": 32307, + "FIN": 32308, + "ĠOber": 32309, + "prone": 32310, + "Ġdiminish": 32311, + "ĠGraduate": 32312, + "ĠTSH": 32313, + "ĠHsu": 32314, + "oidosis": 32315, + "Ġepidural": 32316, + "Ġreinforcing": 32317, + "Ġtheatre": 32318, + "Ġvib": 32319, + "ĠHob": 32320, + "collection": 32321, + "MANGLER": 32322, + "ĠHecke": 32323, + "Ġtruck": 32324, + "Ġmotivates": 32325, + "ĠVOC": 32326, + "Ġunbound": 32327, + "ramid": 32328, + "iously": 32329, + "ĠFernández": 32330, + "ĠFacial": 32331, + "oxazol": 32332, + "Ġtreadm": 32333, + "ĠResid": 32334, + "Loader": 32335, + "ĠRunning": 32336, + "otinib": 32337, + "PAC": 32338, + "VII": 32339, + "iu": 32340, + "Ġcite": 32341, + "ĠHockey": 32342, + "ESC": 32343, + "rhoea": 32344, + "Ġmacaques": 32345, + "Ġmediast": 32346, + "atim": 32347, + "ĠTMP": 32348, + "ĠAGB": 32349, + "ĠRup": 32350, + "uga": 32351, + "Ġassurance": 32352, + "pay": 32353, + "energies": 32354, + "ĠKend": 32355, + "tillery": 32356, + "Ġanesthetic": 32357, + "Window": 32358, + "Ġbeverages": 32359, + "aguchi": 32360, + "ĠFLT": 32361, + "ĠBounded": 32362, + "ĠPolymerase": 32363, + "Sam": 32364, + "ĠOrbit": 32365, + "Ġseasonality": 32366, + "Ġtachycardia": 32367, + "esteem": 32368, + "ĠPerfect": 32369, + "SEC": 32370, + "later": 32371, + "tale": 32372, + "ĠFormally": 32373, + "LG": 32374, + "zyn": 32375, + "Ġmicroalgae": 32376, + "Ġindium": 32377, + "erennial": 32378, + "ĠIPT": 32379, + "Ġkj": 32380, + "ĠPDA": 32381, + "Ġassimil": 32382, + "wheel": 32383, + "ĠSOS": 32384, + "ĠPFC": 32385, + "Ġdecoded": 32386, + "ATS": 32387, + "Ġsocietal": 32388, + "Ġdiffeomorphisms": 32389, + "Ġtraverse": 32390, + "Ġcollateral": 32391, + "gives": 32392, + "ĠCEN": 32393, + "Ġrand": 32394, + "Ġherself": 32395, + "Ġpayments": 32396, + "Ġpsi": 32397, + "âIJ£": 32398, + "ĠGromov": 32399, + "Ġaccidental": 32400, + "ĠReality": 32401, + "Ġlogistics": 32402, + "Ġrobustly": 32403, + "ĠSarah": 32404, + "NU": 32405, + "dates": 32406, + "ĠCUR": 32407, + "ĠDream": 32408, + "Ġdegrades": 32409, + "ĠGEO": 32410, + "Ġbutterfly": 32411, + "Ġpendulum": 32412, + "qa": 32413, + "Ġaspartate": 32414, + "pseudo": 32415, + "Ġallosteric": 32416, + "derr": 32417, + "ĠQoL": 32418, + "Agilent": 32419, + "ĠHardware": 32420, + "ĠCumulative": 32421, + "Ġpn": 32422, + "quantitative": 32423, + "Ġappraisal": 32424, + "Ġpolyacrylamide": 32425, + "Ġmildly": 32426, + "Ġcontraceptive": 32427, + "ĠPublished": 32428, + "Ġuplift": 32429, + "beh": 32430, + "Ġadaptor": 32431, + "ĠEqual": 32432, + "thienyl": 32433, + "atched": 32434, + "Ġreply": 32435, + "Ġupwards": 32436, + "Ġautopsy": 32437, + "simulation": 32438, + "Ġgranite": 32439, + "Ġpelvis": 32440, + "Ġhatching": 32441, + "ĠSPS": 32442, + "ĠGEM": 32443, + "illiard": 32444, + "ĠRetrospective": 32445, + "ĠEarthqu": 32446, + "ĠInvestigations": 32447, + "ĠMerck": 32448, + "Ġcholangi": 32449, + "Ġinfiltrating": 32450, + "Ġoverestimated": 32451, + "focused": 32452, + "Amin": 32453, + "Ġpreeclampsia": 32454, + "ospatial": 32455, + "ĠTRAIL": 32456, + "Pair": 32457, + "Ġsubmarine": 32458, + "Ġproteolysis": 32459, + "Ġcomplements": 32460, + "ĠKirch": 32461, + "Ġcentrom": 32462, + "Ġnap": 32463, + "ĠWear": 32464, + "Ġpunishment": 32465, + "Ġautoregressive": 32466, + "Ġcomposer": 32467, + "ĠEngel": 32468, + "Ġanaemia": 32469, + "ĠKronecker": 32470, + "ĠDid": 32471, + "ĠCarp": 32472, + "peer": 32473, + "Ġbugs": 32474, + "ĠIslamic": 32475, + "ithromycin": 32476, + "Ġconsec": 32477, + "Ġfamiliarity": 32478, + "etaxel": 32479, + "Ġintensively": 32480, + "ĠUpt": 32481, + "Ġindica": 32482, + "ADA": 32483, + "ĠChebyshev": 32484, + "Ġhierarchies": 32485, + "Ġworthwhile": 32486, + "Ġburned": 32487, + "ĠHMGB": 32488, + "Ġpolygonal": 32489, + "brile": 32490, + "Ġzoon": 32491, + "warning": 32492, + "Eukaryota": 32493, + "dA": 32494, + "ĠRepeated": 32495, + "ĠCastro": 32496, + "Ġmetropolitan": 32497, + "ontinuous": 32498, + "ĠBarnes": 32499, + "ĠPostoperative": 32500, + "Ġcytology": 32501, + "Ġspotted": 32502, + "versity": 32503, + "affine": 32504, + "sorted": 32505, + "ĠProto": 32506, + "ĠDescriptive": 32507, + "Ġhitting": 32508, + "Ġanalogously": 32509, + "feedback": 32510, + "Ġspiritual": 32511, + "ĠLINE": 32512, + "ressin": 32513, + "ophthal": 32514, + "Ġpolyunsaturated": 32515, + "Ġpiper": 32516, + "observations": 32517, + "ĭ¤": 32518, + "irre": 32519, + "ĠWNT": 32520, + "Ġundifferentiated": 32521, + "erald": 32522, + "ĠCTC": 32523, + "Ġhomomorphisms": 32524, + "ĠNeonatal": 32525, + "Fin": 32526, + "rozen": 32527, + "ĠLux": 32528, + "Ġmodifier": 32529, + "ĠKA": 32530, + "osaur": 32531, + "Ġinterventional": 32532, + "ĠHapl": 32533, + "Ġluminance": 32534, + "Ġunfortunately": 32535, + "Ġsleeping": 32536, + "Ġcitrus": 32537, + "resonance": 32538, + "Ġmoss": 32539, + "ulay": 32540, + "ĠPenn": 32541, + "administration": 32542, + "ĠNGF": 32543, + "Ġsecured": 32544, + "ĠAEs": 32545, + "ĠPWM": 32546, + "occo": 32547, + "obuf": 32548, + "Ġphotocurrent": 32549, + "ĠScilabDouble": 32550, + "April": 32551, + "Ġforamin": 32552, + "Ġparalysis": 32553, + "ĠQuark": 32554, + "eqref": 32555, + "ĠBrooks": 32556, + "ĠCollision": 32557, + "War": 32558, + "Ġig": 32559, + "amylase": 32560, + "istered": 32561, + "Ġretraction": 32562, + "ĠMultiplex": 32563, + "ĠMao": 32564, + "Common": 32565, + "ĠEconomics": 32566, + "ĠCriterion": 32567, + "ĠCCC": 32568, + "ĠLei": 32569, + "Ġorthorhombic": 32570, + "Ġaliquots": 32571, + "Ġstric": 32572, + "ĠLenn": 32573, + "Ġdisclosure": 32574, + "ameth": 32575, + "Ġnormalisation": 32576, + "Ġphylogen": 32577, + "ĠQTLs": 32578, + "ĠVersus": 32579, + "ĠUtilization": 32580, + "yne": 32581, + "unted": 32582, + "ĠDuff": 32583, + "ĠGJ": 32584, + "Ġoptimised": 32585, + "iformis": 32586, + "ĠIncreases": 32587, + "ĠFDG": 32588, + "ĠBattery": 32589, + "Phe": 32590, + "ĠCCS": 32591, + "Ġchrys": 32592, + "ofen": 32593, + "Ġmulticomponent": 32594, + "discussed": 32595, + "bonding": 32596, + "oretically": 32597, + "ĠAlliance": 32598, + "Ġheadquarters": 32599, + "ĠGlasgow": 32600, + "Ġbout": 32601, + "Ġeighth": 32602, + "Ġincurred": 32603, + "ĠBarry": 32604, + "Ġquadric": 32605, + "Ġduties": 32606, + "Ġmindfulness": 32607, + "rastructural": 32608, + "Train": 32609, + "shitz": 32610, + "CDC": 32611, + "Ġdyslipidemia": 32612, + "Ġalleged": 32613, + "Ġbronze": 32614, + "Ġattainment": 32615, + "QD": 32616, + "rombin": 32617, + "Ġapolipoprotein": 32618, + "owned": 32619, + "Ġgeographically": 32620, + "working": 32621, + "ĠBlind": 32622, + "Ġdonation": 32623, + "ĠSerge": 32624, + "Ġspreads": 32625, + "ĠHeterogeneity": 32626, + "ĠFré": 32627, + "Ġdefer": 32628, + "Ġlifts": 32629, + "EGFR": 32630, + "ĠPortland": 32631, + "Ġbrothers": 32632, + "ĠTrypanosoma": 32633, + "inian": 32634, + "Ġpressed": 32635, + "Ġtransduced": 32636, + "Ġpolyn": 32637, + "Ġlisteners": 32638, + "boards": 32639, + "ĠSustainable": 32640, + "alan": 32641, + "ĠSullivan": 32642, + "Assumption": 32643, + "often": 32644, + "jp": 32645, + "orative": 32646, + "plers": 32647, + "Ġmodularity": 32648, + "ĠHermite": 32649, + "Ġhydroxyapatite": 32650, + "ĠHirsch": 32651, + "Determ": 32652, + "facing": 32653, + "irradiated": 32654, + "Ġharsh": 32655, + "Ġtolerate": 32656, + "ĠTrap": 32657, + "ĠAware": 32658, + "otax": 32659, + "ATING": 32660, + "Ġhistopathology": 32661, + "ĠIsraeli": 32662, + "clockwise": 32663, + "zig": 32664, + "ĠJC": 32665, + "ĠQuick": 32666, + "ĠSLAM": 32667, + "Ġfox": 32668, + "ĠRav": 32669, + "generating": 32670, + "Ġhematoxylin": 32671, + "yltransferase": 32672, + "Ġcorroborated": 32673, + "FDR": 32674, + "oard": 32675, + "Ġequid": 32676, + "Ġ»": 32677, + "Ġneuropsychological": 32678, + "Ġbreakup": 32679, + "Ġemphasizing": 32680, + "Ġemissivity": 32681, + "blocking": 32682, + "Ġparall": 32683, + "Ġtilting": 32684, + "Ġpeng": 32685, + "ĠScan": 32686, + "Ġionosphere": 32687, + "Ġmount": 32688, + "forest": 32689, + "Ġcallus": 32690, + "αβ": 32691, + "ĠChristmas": 32692, + "ĠMagazine": 32693, + "evaluate": 32694, + "ĠPag": 32695, + "ĠBeat": 32696, + "Ġaccumulates": 32697, + "Ġcrowding": 32698, + "unneling": 32699, + "ĠÑ": 32700, + "ĠACP": 32701, + "geometry": 32702, + "MPT": 32703, + "Ġpharmacists": 32704, + "Ġpullback": 32705, + "Ġductility": 32706, + "Supervised": 32707, + "Ġlymphoblastic": 32708, + "pea": 32709, + "typical": 32710, + "broken": 32711, + "Fc": 32712, + "Ġlining": 32713, + "ĠDum": 32714, + "Ġmultiples": 32715, + "ów": 32716, + "Ġmerits": 32717, + "Ġextinct": 32718, + "ĠNursing": 32719, + "ĠExploiting": 32720, + "ĠBhattach": 32721, + "July": 32722, + "tze": 32723, + "thromb": 32724, + "teenth": 32725, + "Ġtoxicities": 32726, + "Ġdenitrification": 32727, + "Ġexposition": 32728, + "Ġimperf": 32729, + "Ġsurname": 32730, + "pointer": 32731, + "ĠErn": 32732, + "ĠAbundance": 32733, + "ĠDunn": 32734, + "ophora": 32735, + "Ġtoolkit": 32736, + "Load": 32737, + "ĠDerivation": 32738, + "could": 32739, + "ĠCaspase": 32740, + "ĠSprague": 32741, + "ĠTrp": 32742, + "Ġbrightest": 32743, + "illard": 32744, + "Ġinterdisciplinary": 32745, + "Ġquarant": 32746, + "Ġhypersurfaces": 32747, + "eliac": 32748, + "ĠALMA": 32749, + "Ġacrylic": 32750, + "Ġgentle": 32751, + "Deep": 32752, + "ĠPandemic": 32753, + "Ġinfeasible": 32754, + "Ġradiol": 32755, + "ABP": 32756, + "Ġmesenteric": 32757, + "ylinder": 32758, + "packed": 32759, + "Ġsomatosensory": 32760, + "Ġpave": 32761, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 32762, + "Ġpharmacology": 32763, + "Ġtanh": 32764, + "ĠMtb": 32765, + "Ġchimpan": 32766, + "Ġautophagic": 32767, + "Ġwithdrawn": 32768, + "ĠMCC": 32769, + "ZF": 32770, + "ĠSpl": 32771, + "ĠLau": 32772, + "Ġbiologic": 32773, + "electrons": 32774, + "Ġunderestimation": 32775, + "Ġcharacterise": 32776, + "circular": 32777, + "ĠTHEORY": 32778, + "Brown": 32779, + "FBS": 32780, + "Jo": 32781, + "dG": 32782, + "mars": 32783, + "articular": 32784, + "ĠPren": 32785, + "ĠMSA": 32786, + "ĠItem": 32787, + "Ġsemidefinite": 32788, + "ĠGibson": 32789, + "Ġtourism": 32790, + "ĠKok": 32791, + "Ġexposing": 32792, + "Ġintravenously": 32793, + "driver": 32794, + "ĠFortunately": 32795, + "ĠSach": 32796, + "Ġcontaminant": 32797, + "Ġabrog": 32798, + "ĠEmotional": 32799, + "VALUE": 32800, + "dispersion": 32801, + "Jacobi": 32802, + "ĠImperial": 32803, + "Ion": 32804, + "Lin": 32805, + "fidelity": 32806, + "ĠBirds": 32807, + "ĠConcurrent": 32808, + "matism": 32809, + "coal": 32810, + "Ġtq": 32811, + "ĠMnO": 32812, + "Ġfossils": 32813, + "Ġtender": 32814, + "Ġrhesus": 32815, + "Ġbloom": 32816, + "abdominal": 32817, + "Ġscalp": 32818, + "Ġhomeostatic": 32819, + "ĠHunt": 32820, + "ĠPharmacokine": 32821, + "brown": 32822, + "ĠHYP": 32823, + "Ġdissociated": 32824, + "ĠSoccer": 32825, + "ĠInequality": 32826, + "maker": 32827, + "Ġshade": 32828, + "ĠZur": 32829, + "observation": 32830, + "altered": 32831, + "UU": 32832, + "Ġtheor": 32833, + "epit": 32834, + "Ġphylum": 32835, + "Ġvigorous": 32836, + "ĠACM": 32837, + "Ġmethotrexate": 32838, + "demographic": 32839, + "Ġsingly": 32840, + "ĠPhysiology": 32841, + "Ġremodelling": 32842, + "ĠKrist": 32843, + "ropies": 32844, + "flows": 32845, + "hardness": 32846, + "ighteen": 32847, + "breve": 32848, + "ĠRetinal": 32849, + "Ġscintill": 32850, + "Ġutterance": 32851, + "Ġmonolithic": 32852, + "ĠVlad": 32853, + "ĠLMC": 32854, + "ipt": 32855, + "arrows": 32856, + "ĠPublishing": 32857, + "ĠStreptomyces": 32858, + "fal": 32859, + "Ġtroposphere": 32860, + "Ben": 32861, + "candid": 32862, + "ĠSic": 32863, + "timore": 32864, + "Len": 32865, + "inen": 32866, + "ampered": 32867, + "ĠMonth": 32868, + "Ġopponent": 32869, + "August": 32870, + "Ġstaggered": 32871, + "centre": 32872, + "expect": 32873, + "Ġreddening": 32874, + "ĠTl": 32875, + "hibition": 32876, + "Ġmicroparticles": 32877, + "ĠIntrac": 32878, + "ĠInitialize": 32879, + "Ġdictated": 32880, + "Dig": 32881, + "äº": 32882, + "healing": 32883, + "ĠdV": 32884, + "Ġappetite": 32885, + "Ġunusually": 32886, + "ĠAstronomy": 32887, + "Ġware": 32888, + "Ġovercoming": 32889, + "Ġcolliders": 32890, + "ĠUSING": 32891, + "ocarditis": 32892, + "Pick": 32893, + "Ġdub": 32894, + "ĠJason": 32895, + "ĠEditor": 32896, + "ê³": 32897, + "Ġlags": 32898, + "Ġcls": 32899, + "Ġsurgically": 32900, + "ĠPVC": 32901, + "particularly": 32902, + "Ġredist": 32903, + "Ġlogics": 32904, + "skii": 32905, + "ĠDVD": 32906, + "Ġcomply": 32907, + "azi": 32908, + "ĠInteracts": 32909, + "boolean": 32910, + "ĠERP": 32911, + "ĠErr": 32912, + "otranspiration": 32913, + "ĠPérez": 32914, + "Asp": 32915, + "amiliar": 32916, + "ĠFetal": 32917, + "Ġdeclaration": 32918, + "kinson": 32919, + "tube": 32920, + "Ġphysiologically": 32921, + "cue": 32922, + "ĠEri": 32923, + "Ġenvision": 32924, + "external": 32925, + "intermediate": 32926, + "Ġshopping": 32927, + "ĠFras": 32928, + "ĠHaj": 32929, + "ĠAlger": 32930, + "Ġanthropometric": 32931, + "Ġcancelled": 32932, + "HPV": 32933, + "kers": 32934, + "afa": 32935, + "Ġvulnerabilities": 32936, + "electrolyte": 32937, + "ĠGonzalez": 32938, + "íķĺ": 32939, + "qv": 32940, + "Ġdeaf": 32941, + "Ġbutyrate": 32942, + "ĠCoefficient": 32943, + "Ġstarburst": 32944, + "Ġpolymorph": 32945, + "ĠERA": 32946, + "ĠMaximal": 32947, + "ĠMueller": 32948, + "Ġabsorbers": 32949, + "Ġarab": 32950, + "retions": 32951, + "Ġnebula": 32952, + "Ġmines": 32953, + "ен": 32954, + "%%%%%%%%%%%%%%%%": 32955, + "Ġbandpass": 32956, + "Ġpolyurethane": 32957, + "ReLU": 32958, + "ĠFerro": 32959, + "picillin": 32960, + "CAD": 32961, + "Ty": 32962, + "ĠPCD": 32963, + "ĠBAC": 32964, + "Ġplanktonic": 32965, + "Fer": 32966, + "Ġcricket": 32967, + "Ġmanure": 32968, + "ouns": 32969, + "âΧ": 32970, + "Ġtorques": 32971, + "mitian": 32972, + "Ġtion": 32973, + "ĠGarden": 32974, + "Ġfolk": 32975, + "Ġsuspicious": 32976, + "ÃĤ": 32977, + "odia": 32978, + "istencies": 32979, + "ãĢī": 32980, + "ĠInvitrogen": 32981, + "ĠSUN": 32982, + "ĠSuperior": 32983, + "Ġdiscontinuation": 32984, + "cock": 32985, + "knot": 32986, + "Ġextens": 32987, + "ĠWhitney": 32988, + "Ġharbour": 32989, + "PID": 32990, + "Ġpmol": 32991, + "olymph": 32992, + "Ġgard": 32993, + "ĠOvarian": 32994, + "Ġrepressed": 32995, + "ĠAlab": 32996, + "ĠÃĦ": 32997, + "ulex": 32998, + "ĠAustrian": 32999, + "Ġaflat": 33000, + "Ġparathyroid": 33001, + "Ġgroupoid": 33002, + "Ġdevast": 33003, + "ĠKv": 33004, + "Ġborrow": 33005, + "Ġunconventional": 33006, + "Ġborehole": 33007, + "ÑĮ": 33008, + "ĠDays": 33009, + "Ġlexic": 33010, + "Nor": 33011, + "ĠHerc": 33012, + "assays": 33013, + "Ġdrawings": 33014, + "defin": 33015, + "evoked": 33016, + "Ġȳ": 33017, + "ĠSunday": 33018, + "ĠChes": 33019, + "considered": 33020, + "opedic": 33021, + "larger": 33022, + "ominant": 33023, + "ĠBomb": 33024, + "Ġfiss": 33025, + "Ġhinge": 33026, + "ĠIonic": 33027, + "Ġdestro": 33028, + "Ġcomplementarity": 33029, + "Higgs": 33030, + "oria": 33031, + "ourcing": 33032, + "ĠXin": 33033, + "Ġworkspace": 33034, + "ĠLigand": 33035, + "Ġstruggle": 33036, + "ĠImmunohistochemical": 33037, + "Ġnick": 33038, + "ĠGuard": 33039, + "rigid": 33040, + "Ġaquaculture": 33041, + "Experiment": 33042, + "ËĪ": 33043, + "tir": 33044, + "ĠSMS": 33045, + "Ġbevacizumab": 33046, + "Ġmodulations": 33047, + "Ġgeophysical": 33048, + "Properties": 33049, + "Ġpainted": 33050, + "Ġsanc": 33051, + "Ġintimate": 33052, + "Ġnail": 33053, + "identity": 33054, + "Ġdatum": 33055, + "anthus": 33056, + "Ġdyadic": 33057, + "Ġconvincing": 33058, + "elem": 33059, + "Ġhiding": 33060, + "Ġrugby": 33061, + "ĠXe": 33062, + "ĠIssue": 33063, + "Ġvesicular": 33064, + "ĠKelvin": 33065, + "Ġdistancing": 33066, + "echnology": 33067, + "afers": 33068, + "ĠAuthentic": 33069, + "PubMed": 33070, + "Ġdeformity": 33071, + "ĠChaos": 33072, + "ĠShield": 33073, + "oxetine": 33074, + "ĠWorkers": 33075, + "ĠMOI": 33076, + "Ġdehydrated": 33077, + "ĠGastric": 33078, + "Ġmonomials": 33079, + "odox": 33080, + "ĠDublin": 33081, + "Ġleishman": 33082, + "Ġplanner": 33083, + "circle": 33084, + "Ġfractured": 33085, + "ĠLocally": 33086, + "ĠActions": 33087, + "Ġlichen": 33088, + "hannel": 33089, + "ĠTAG": 33090, + "Ġdecisive": 33091, + "ĠQM": 33092, + "Ġbiomaterials": 33093, + "ĠViruses": 33094, + "hydroxyphenyl": 33095, + "ĠIAA": 33096, + "ĠRU": 33097, + "violating": 33098, + "Ġpockets": 33099, + "chant": 33100, + "iberg": 33101, + "lectomy": 33102, + "olerae": 33103, + "Ġattracting": 33104, + "Ġketone": 33105, + "ĠCod": 33106, + "Ġmicroarrays": 33107, + "ĠMetals": 33108, + "benzoyl": 33109, + "Ġsemigroups": 33110, + "Ġreconstituted": 33111, + "sites": 33112, + "anabe": 33113, + "ĠComposites": 33114, + "Ġwildtype": 33115, + "Ġleukaemia": 33116, + "Ġmurder": 33117, + "Ġdentin": 33118, + "Hub": 33119, + "Orient": 33120, + "onn": 33121, + "synchron": 33122, + "Ġchronically": 33123, + "methyleneamino": 33124, + "Ġdopant": 33125, + "Ġfecundity": 33126, + "delete": 33127, + "remia": 33128, + "ĠNHL": 33129, + "itidis": 33130, + "Ġcopep": 33131, + "XI": 33132, + "Ġlocating": 33133, + "ĠZIKV": 33134, + "hexa": 33135, + "ĠFactorization": 33136, + "ynchus": 33137, + "Methyl": 33138, + "hagen": 33139, + "ĠPaw": 33140, + "neath": 33141, + "bsite": 33142, + "Ġtrache": 33143, + "Bre": 33144, + "uw": 33145, + "roit": 33146, + "Ġreacting": 33147, + "ĠBae": 33148, + "Ġquotients": 33149, + "Ġpins": 33150, + "ĠVARI": 33151, + "Ġequine": 33152, + "ĠRunge": 33153, + "Ġcolonial": 33154, + "measurement": 33155, + "ĠAbbott": 33156, + "Ġortho": 33157, + "Ġmetaphor": 33158, + "benzoic": 33159, + "ĠTransformers": 33160, + "Lower": 33161, + "ĠOVA": 33162, + "radial": 33163, + "Flag": 33164, + "authorbs": 33165, + "Ġtreadmill": 33166, + "Ġenterica": 33167, + "ĠJulia": 33168, + "Ġplumes": 33169, + "Ġinvoke": 33170, + "chloric": 33171, + "olino": 33172, + "Ġinterruption": 33173, + "subunit": 33174, + "ĠMDP": 33175, + "Ġmanipulator": 33176, + "ĠScales": 33177, + "ĠHTML": 33178, + "ĠFrederick": 33179, + "Garc": 33180, + "Ġbell": 33181, + "ĠRect": 33182, + "romised": 33183, + "Word": 33184, + "oples": 33185, + "operated": 33186, + "Ġcollects": 33187, + "ĠHorizon": 33188, + "Ġsafer": 33189, + "dup": 33190, + "ĠMills": 33191, + "ALP": 33192, + "Ġexopl": 33193, + "ATTR": 33194, + "wara": 33195, + "ĉĉĉĉĉĉĉ": 33196, + "Ġdebug": 33197, + "Descriptor": 33198, + "statistics": 33199, + "ĠCub": 33200, + "STER": 33201, + "ĠStabilization": 33202, + "ĠIRAS": 33203, + "Ġconformally": 33204, + "Adap": 33205, + "ÂŃ": 33206, + "ĠQS": 33207, + "Ġmicrostrip": 33208, + "Ġdelicate": 33209, + "Ġpublisher": 33210, + "Ġhos": 33211, + "ĠSv": 33212, + "ĠDesert": 33213, + "ĠGuer": 33214, + "ĠCapture": 33215, + "EBP": 33216, + "dust": 33217, + "å¤": 33218, + "ĠOls": 33219, + "Ġsuperscript": 33220, + "ĠFluctuations": 33221, + "illium": 33222, + "Ġcaption": 33223, + "Ġconcur": 33224, + "Ġquantifies": 33225, + "sterdam": 33226, + "Ġspiked": 33227, + "Nan": 33228, + "usin": 33229, + "ĠLAN": 33230, + "Ġobserves": 33231, + "ĠAla": 33232, + "ĠIntuitively": 33233, + "curr": 33234, + "Ġshrinking": 33235, + "Ġcompressibility": 33236, + "orporeal": 33237, + "Ġdebt": 33238, + "çĶ": 33239, + "ĠTil": 33240, + "ĠWAT": 33241, + "odyne": 33242, + "Ġgateway": 33243, + "Ġductile": 33244, + "ĠJesus": 33245, + "ositol": 33246, + "ĠMales": 33247, + "Ġsolvation": 33248, + "Ġdisagree": 33249, + "Ġorthologs": 33250, + "San": 33251, + "igo": 33252, + "Ġphages": 33253, + "Ġnegatives": 33254, + "Ġinterpre": 33255, + "AAA": 33256, + "Ġgratings": 33257, + "ĠMoll": 33258, + "ĠRivers": 33259, + "Ġcruzi": 33260, + "ĠGenerate": 33261, + "ĠBarbara": 33262, + "ĠHeritage": 33263, + "ĠFluorescent": 33264, + "ĠLaws": 33265, + "ArrayExpr": 33266, + "Ġmultipole": 33267, + "Ġsqueezing": 33268, + "SPSS": 33269, + "lf": 33270, + "nlm": 33271, + "Ġworn": 33272, + "ĠKuz": 33273, + "Ġgenesis": 33274, + "ĠEmperor": 33275, + "volatile": 33276, + "Ġsibling": 33277, + "ivir": 33278, + "oen": 33279, + "Ġprotost": 33280, + "Ġtransformers": 33281, + "ennium": 33282, + "Ġproposing": 33283, + "Ġbroadcasting": 33284, + "QM": 33285, + "ĠDependent": 33286, + "Ġdisable": 33287, + "ĠUAS": 33288, + "Ġwarnings": 33289, + "Ġarmed": 33290, + "Ġjournalist": 33291, + "Ġmonoclinic": 33292, + "olium": 33293, + "aping": 33294, + "toon": 33295, + "Ġorthodontic": 33296, + "ĠNormalization": 33297, + "Ġmandible": 33298, + "aban": 33299, + "ĠWak": 33300, + "extend": 33301, + "Multiple": 33302, + "investig": 33303, + "iscal": 33304, + "uttered": 33305, + "Ġburg": 33306, + "decode": 33307, + "empor": 33308, + "ĠDuration": 33309, + "anny": 33310, + "oprost": 33311, + "ĠRenormalization": 33312, + "ĠFUNCTION": 33313, + "ytorch": 33314, + "Ġsynapt": 33315, + "ĠFormat": 33316, + "ĠCRT": 33317, + "ĠJonathan": 33318, + "ĠOFF": 33319, + "orr": 33320, + "Ġresur": 33321, + "Ġcorruption": 33322, + "dwelling": 33323, + "Ġbackup": 33324, + "AGT": 33325, + "ĠSafe": 33326, + "dorfer": 33327, + "Ġataxia": 33328, + "Ġparv": 33329, + "reader": 33330, + "Ġsubtract": 33331, + "embolism": 33332, + "Ġtinnitus": 33333, + "Ġcytomegalovirus": 33334, + "Ġdeleting": 33335, + "Tex": 33336, + "ĠCSS": 33337, + "ardt": 33338, + "Ġoutgrowth": 33339, + "Ġmyocytes": 33340, + "digital": 33341, + "Ġsubscale": 33342, + "uspension": 33343, + "Ġhamster": 33344, + "Ġinflaton": 33345, + "hara": 33346, + "urches": 33347, + "ĠCLE": 33348, + "ĠYas": 33349, + "ĠEncoding": 33350, + "ĠAuger": 33351, + "Ġanastomosis": 33352, + "Agent": 33353, + "ĠSIL": 33354, + "ĠCCT": 33355, + "Ġbrine": 33356, + "Ġoligo": 33357, + "Ġfluoro": 33358, + "Ġgallery": 33359, + "ddots": 33360, + "Ġcilia": 33361, + "ĠPPV": 33362, + "ĠUTR": 33363, + "Ġintertidal": 33364, + "ocalized": 33365, + "Ġcrowds": 33366, + "odor": 33367, + "Ġcov": 33368, + "Ġnonetheless": 33369, + "Ġïģ¤": 33370, + "Ġboosted": 33371, + "ĠChakra": 33372, + "Hal": 33373, + "Pear": 33374, + "Ġimprec": 33375, + "ĠSupplement": 33376, + "goal": 33377, + "Ġôı¼ģ": 33378, + "Ġstall": 33379, + "Ġherd": 33380, + "smaller": 33381, + "Ġreconstructing": 33382, + "Ġartefacts": 33383, + "Ġteg": 33384, + "conventional": 33385, + "radical": 33386, + "Ġliteral": 33387, + "framework": 33388, + "iprocal": 33389, + "EEG": 33390, + "Ġgins": 33391, + "odermal": 33392, + "ĠAgu": 33393, + "ĠTwelve": 33394, + "Mul": 33395, + "ب": 33396, + "irl": 33397, + "ĠBelief": 33398, + "Ġincont": 33399, + "ICC": 33400, + "hexane": 33401, + "Ġejected": 33402, + "ĠPSC": 33403, + "ĠHPC": 33404, + "ĠVH": 33405, + "Ġequivalences": 33406, + "plotlib": 33407, + "enital": 33408, + "rians": 33409, + "prov": 33410, + "ĠVibr": 33411, + "Ġgrammatical": 33412, + "bachia": 33413, + "acceptable": 33414, + "odicity": 33415, + "abb": 33416, + "Ġherbs": 33417, + "Ġpredominance": 33418, + "ĠOrientation": 33419, + "Ġinvertebrate": 33420, + "Ġpelagic": 33421, + "country": 33422, + "ĠOrigins": 33423, + "ĠAdolescents": 33424, + "ĠTuning": 33425, + "rainian": 33426, + "ĠScar": 33427, + "Ġlightest": 33428, + "Ġemitters": 33429, + "ĠTsai": 33430, + "ritical": 33431, + "ĠExpert": 33432, + "authors": 33433, + "ECTION": 33434, + "ĠSeverity": 33435, + "Nam": 33436, + "publ": 33437, + "ĠAbe": 33438, + "Ġnanocrystalline": 33439, + "ĠNakamura": 33440, + "ĠPec": 33441, + "ĠBug": 33442, + "Ġsensed": 33443, + "ONS": 33444, + "ICs": 33445, + "Ġelectrochem": 33446, + "ĠROM": 33447, + "ĠRecruitment": 33448, + "Ġ⣩": 33449, + "Ġbiomolecules": 33450, + "ĠBrac": 33451, + "Ġtransposition": 33452, + "ĠWP": 33453, + "ĠOmega": 33454, + "Ġdiagon": 33455, + "platelet": 33456, + "JM": 33457, + "acre": 33458, + "ĠASR": 33459, + "ĠKath": 33460, + "Ġpriv": 33461, + "oplasts": 33462, + "Samples": 33463, + "dF": 33464, + "atti": 33465, + "ĠSanger": 33466, + "ipitated": 33467, + "Ġricher": 33468, + "ĠGRA": 33469, + "Ġplantar": 33470, + "Ġfoams": 33471, + "Ġmathematic": 33472, + "Ġstaphyl": 33473, + "ĠUptake": 33474, + "Ġcant": 33475, + "ĠSZ": 33476, + "Ġdismiss": 33477, + "Ġselections": 33478, + "plitz": 33479, + "Ġexemplified": 33480, + "Ġtorsional": 33481, + "Ev": 33482, + "Ġvoters": 33483, + "ĠNest": 33484, + "yscale": 33485, + "Ġspeci": 33486, + "Ġpolished": 33487, + "Ġlatencies": 33488, + "qing": 33489, + "Ġonwards": 33490, + "llvm": 33491, + "theorem": 33492, + "logging": 33493, + "ĠALK": 33494, + "ĠBaum": 33495, + "ĠGhosh": 33496, + "Ġchairman": 33497, + "paired": 33498, + "ĠPAP": 33499, + "notes": 33500, + "olesterolem": 33501, + "Ġestuarine": 33502, + "ĠTibetan": 33503, + "ĠVER": 33504, + "Ġchecker": 33505, + "FLAGS": 33506, + "rolimus": 33507, + "ĠMutant": 33508, + "Ġspraying": 33509, + "ĠChest": 33510, + "olinium": 33511, + "ĠTriassic": 33512, + "Ġlidar": 33513, + "Art": 33514, + "ĠMilk": 33515, + "Ġindecomposable": 33516, + "Ġrocket": 33517, + "ĠPartners": 33518, + "Ġsemantically": 33519, + "entinel": 33520, + "Large": 33521, + "Pen": 33522, + "ĠTru": 33523, + "Ġheritage": 33524, + "ĠMutual": 33525, + "ĠChemotherapy": 33526, + "Ġdoubles": 33527, + "ĠEmbedded": 33528, + "itual": 33529, + "ĠBPA": 33530, + "Ġcholerae": 33531, + "ĠInside": 33532, + "ĠKatz": 33533, + "convergence": 33534, + "Ġindividualized": 33535, + "kinje": 33536, + "Ġdiscovering": 33537, + "Ġintricate": 33538, + "Ġinland": 33539, + "RECT": 33540, + "ĠChick": 33541, + "ĠSUR": 33542, + "Ġyeasts": 33543, + "luminosity": 33544, + "Ġfain": 33545, + "ioni": 33546, + "ĠTig": 33547, + "ounder": 33548, + "Ġdeliber": 33549, + "ĠConservative": 33550, + "ĠDelhi": 33551, + "BER": 33552, + "ĠYB": 33553, + "oley": 33554, + "ĠBeau": 33555, + "TEXT": 33556, + "Ġsqueezed": 33557, + "Ġsocket": 33558, + "ĠpT": 33559, + "pyrazol": 33560, + "coefficients": 33561, + "Ġrecruiting": 33562, + "Ġducts": 33563, + "Ġfoster": 33564, + "omeration": 33565, + "ĠPSI": 33566, + "ĠDup": 33567, + "Ġks": 33568, + "ĠOptics": 33569, + "Ġliterary": 33570, + "ĠNiO": 33571, + "ĠVEGFR": 33572, + "Ġgraviton": 33573, + "Ġutterances": 33574, + "Ġbrady": 33575, + "Ġforty": 33576, + "ĠTransplantation": 33577, + "Ġagreements": 33578, + "Leftrightarrow": 33579, + "waves": 33580, + "Ġacidosis": 33581, + "Ġwooden": 33582, + "ĠCytoplasmic": 33583, + "safe": 33584, + "Ġjumping": 33585, + "ennial": 33586, + "Various": 33587, + "ĠEryth": 33588, + "ulins": 33589, + "unlock": 33590, + "methylated": 33591, + "asserstein": 33592, + "Ġheterozygosity": 33593, + "oxycycl": 33594, + "Ġcreativity": 33595, + "MPLE": 33596, + "inative": 33597, + "Ġconvolutions": 33598, + "Ġnouns": 33599, + "egan": 33600, + "ĠAbraham": 33601, + "Ġdenser": 33602, + "Che": 33603, + "lc": 33604, + "ĉĉĉĠ": 33605, + "Ġsemim": 33606, + "ĠOuter": 33607, + "Ġcand": 33608, + "odule": 33609, + "esthesia": 33610, + "ĠJoy": 33611, + "ĠProtocols": 33612, + "ĠCalculated": 33613, + "atop": 33614, + "ĠFALSE": 33615, + "Ġrefin": 33616, + "Ġmigrants": 33617, + "ĠïĤ´": 33618, + "ĠSpecificity": 33619, + "ĠFellowship": 33620, + "ĠPMT": 33621, + "Ġdisclose": 33622, + "unches": 33623, + "Ġdiatoms": 33624, + "corr": 33625, + "Ġskyrm": 33626, + "Ġrenewal": 33627, + "gcd": 33628, + "cereb": 33629, + "Ġupright": 33630, + "Ġmesoscopic": 33631, + "hydraz": 33632, + "BAS": 33633, + "FLO": 33634, + "HCC": 33635, + "Mouse": 33636, + "Ġposet": 33637, + "Ġproteinuria": 33638, + "Ġreapp": 33639, + "ĠNickel": 33640, + "Ġstripes": 33641, + "Ġripple": 33642, + "September": 33643, + "odomain": 33644, + "ĠPope": 33645, + "ĠNons": 33646, + "Ġtechnic": 33647, + "Ġneutrop": 33648, + "descriptor": 33649, + "Ġdissipated": 33650, + "Ġglaciers": 33651, + "ĠHIGH": 33652, + "ĠLav": 33653, + "retely": 33654, + "Ġbackwards": 33655, + "Ġcritics": 33656, + "ĠExtending": 33657, + "bic": 33658, + "ĠChao": 33659, + "ofibr": 33660, + "Ġcounters": 33661, + "Ġstreets": 33662, + "Ġprosthetic": 33663, + "Ġbiodegradation": 33664, + "complexity": 33665, + "ĠSPL": 33666, + "ĠCAC": 33667, + "Ġadducts": 33668, + "Ġmorphometric": 33669, + "ĠMatt": 33670, + "Ġinducer": 33671, + "Ġastrocyte": 33672, + "Ġtriplets": 33673, + "Ġpertussis": 33674, + "PES": 33675, + "idy": 33676, + "uncertain": 33677, + "Ġhyperparameter": 33678, + "ĠInfrastructure": 33679, + "ìĿĺ": 33680, + "ZW": 33681, + "Ġaddr": 33682, + "Ġdisrupts": 33683, + "Ġoverestimate": 33684, + "ĠDYNA": 33685, + "Ġvolatiles": 33686, + "emerg": 33687, + "issue": 33688, + "cpp": 33689, + "Äħ": 33690, + "ĠVIP": 33691, + "Ġuve": 33692, + "ĠCNV": 33693, + "ylethyl": 33694, + "onazole": 33695, + "ĠHiro": 33696, + "Ġcn": 33697, + "tik": 33698, + "ubted": 33699, + "ĠJacobs": 33700, + "Ġadvocated": 33701, + "ĠBifid": 33702, + "material": 33703, + "Ġstyrene": 33704, + "ĠKeller": 33705, + "rocytic": 33706, + "pinephrine": 33707, + "ĠWritten": 33708, + "ĠRecommendation": 33709, + "bled": 33710, + "ĠBootstrap": 33711, + "thirds": 33712, + "Ġcaptain": 33713, + "equals": 33714, + "SRC": 33715, + "ĠKentucky": 33716, + "Ġeosinophils": 33717, + "Average": 33718, + "Hi": 33719, + "Whe": 33720, + "ĠDAT": 33721, + "ĠUM": 33722, + "Ġtendencies": 33723, + "ĠPeterson": 33724, + "Ġoccult": 33725, + "Ġexhibition": 33726, + "ĠINS": 33727, + "Ġadipocyte": 33728, + "Just": 33729, + "hift": 33730, + "tensors": 33731, + "Ġciliary": 33732, + "ipation": 33733, + "Ġmotivations": 33734, + "Ġwitnessed": 33735, + "itches": 33736, + "ĠSoy": 33737, + "Ġgib": 33738, + "eptic": 33739, + "ĠKOH": 33740, + "Ġïģ¨": 33741, + "ĠTorres": 33742, + "ο": 33743, + "arpo": 33744, + "okinase": 33745, + "ĠBudd": 33746, + "ĠGMM": 33747, + "Ġunderpin": 33748, + "Ġoptimistic": 33749, + "ogeography": 33750, + "numerical": 33751, + "ogg": 33752, + "Ġdisequilibrium": 33753, + "Ġswab": 33754, + "EDS": 33755, + "ĠPDFs": 33756, + "ĠSupernova": 33757, + "phospho": 33758, + "Ġlysosomes": 33759, + "galactic": 33760, + "ĠPerme": 33761, + "Ġfishery": 33762, + "ĠBOLD": 33763, + "Ġunravel": 33764, + "ĠEncryption": 33765, + "JP": 33766, + "hur": 33767, + "Ġdiscount": 33768, + "ĠWatanabe": 33769, + "ĠRheumat": 33770, + "FITC": 33771, + "Ġterahertz": 33772, + "ĠFont": 33773, + "iances": 33774, + "ĠAdditive": 33775, + "ĠEither": 33776, + "metadata": 33777, + "amphetamine": 33778, + "ĠPalmer": 33779, + "Ġleveraging": 33780, + "John": 33781, + "OCT": 33782, + "infer": 33783, + "ĠMSD": 33784, + "ĠâĪĵ": 33785, + "ouver": 33786, + "ĠAndersen": 33787, + "Ġworlds": 33788, + "Ġtori": 33789, + "Ġïģ°": 33790, + "engineering": 33791, + "ĠSquadron": 33792, + "Aff": 33793, + "åı": 33794, + "oxel": 33795, + "yletic": 33796, + "ĠCharacterizing": 33797, + "VT": 33798, + "rational": 33799, + "eremia": 33800, + "Ġcomplexation": 33801, + "ĠERα": 33802, + "carboxylic": 33803, + "ïĤ·": 33804, + "Ġgalactose": 33805, + "ĠAurora": 33806, + "Ġplasminogen": 33807, + "uren": 33808, + "igne": 33809, + "Ġrepaired": 33810, + "Ġblockers": 33811, + "ĠMNIST": 33812, + "Ïħ": 33813, + "ĠAxi": 33814, + "Ġstadium": 33815, + "diethyl": 33816, + "âĢİ": 33817, + "Ġcyclotron": 33818, + "Ġlymphaden": 33819, + "Ġvin": 33820, + "ĠMayer": 33821, + "Ġendometrium": 33822, + "ĠSpherical": 33823, + "Ġpersu": 33824, + "Ġimmortal": 33825, + "benzenesulf": 33826, + "ĠÅľ": 33827, + "Ġbite": 33828, + "ugged": 33829, + "ĠDiffraction": 33830, + "GTG": 33831, + "iate": 33832, + "Ġtp": 33833, + "Ġaber": 33834, + "ĠRein": 33835, + "Program": 33836, + "Style": 33837, + "ĠRegularization": 33838, + "ĠLeukemia": 33839, + "Ġprokaryotic": 33840, + "ocomial": 33841, + "skb": 33842, + "Ġdeviates": 33843, + "Ġfuse": 33844, + "ĠNull": 33845, + "ĠïĥĹ": 33846, + "ĠOperational": 33847, + "Ġcompressor": 33848, + "ĠRydberg": 33849, + "Ġfought": 33850, + "Ġeco": 33851, + "ĠSSP": 33852, + "CDs": 33853, + "ĠMEK": 33854, + "ĠAnisotropic": 33855, + "ĠDirection": 33856, + "ĠSpectrometry": 33857, + "Ġgluten": 33858, + "ĠPowell": 33859, + "recognized": 33860, + "Ġpsychotic": 33861, + "Ġhinder": 33862, + "Ġaccommodation": 33863, + "ĠNorman": 33864, + "Qx": 33865, + "Ġperiv": 33866, + "ĠUnknown": 33867, + "Ġjoins": 33868, + "ĠMinimization": 33869, + "ĠSons": 33870, + "ĠCin": 33871, + "Ġunavoid": 33872, + "ĠPTX": 33873, + "Ġcada": 33874, + "ĠLuk": 33875, + "Ġruling": 33876, + "Ġbiphasic": 33877, + "ĠComplications": 33878, + "ĠDefects": 33879, + "Content": 33880, + "ĠGregory": 33881, + "ĠWerner": 33882, + "ĠWeibull": 33883, + "eldom": 33884, + "Ġactivators": 33885, + "GLAPI": 33886, + "mathring": 33887, + "Ġhens": 33888, + "NSC": 33889, + "however": 33890, + "ĠTME": 33891, + "mafrost": 33892, + "coefficient": 33893, + "ĠInsect": 33894, + "ĠROIs": 33895, + "ĠBorrel": 33896, + "ĠQiu": 33897, + "Ġinhaled": 33898, + "idate": 33899, + "Ġantihypertensive": 33900, + "Ġtreats": 33901, + "ĠNearly": 33902, + "succ": 33903, + "ĠOrbital": 33904, + "eradish": 33905, + "administered": 33906, + "ĠÏĤ": 33907, + "ĠColony": 33908, + "ĠâĮĬ": 33909, + "ĠIndonesian": 33910, + "ĠBauer": 33911, + "ĠKod": 33912, + "manned": 33913, + "Resistant": 33914, + "Ġdaughters": 33915, + "ĠPredicted": 33916, + "Ġvocab": 33917, + "Ġcontrasted": 33918, + "margin": 33919, + "ĠDirected": 33920, + "EDTA": 33921, + "Ġsynchrony": 33922, + "icki": 33923, + "ĠSalv": 33924, + "treat": 33925, + "incess": 33926, + "varnothing": 33927, + "Ġhexane": 33928, + "Empty": 33929, + "Ġgemcitabine": 33930, + "omib": 33931, + "orepinephrine": 33932, + "proc": 33933, + "ĠMetS": 33934, + "ĠDRAM": 33935, + "Ġanticoagulant": 33936, + "nom": 33937, + "amater": 33938, + "ĠLiDAR": 33939, + "Ġmobil": 33940, + "Ġameliorates": 33941, + "niz": 33942, + "Ġja": 33943, + "Ġemuls": 33944, + "ĠZa": 33945, + "Ġastronomical": 33946, + "ĠAlfred": 33947, + "Hilbert": 33948, + "ĠKF": 33949, + "CRT": 33950, + "quadratic": 33951, + "Ġdifferentials": 33952, + "robacterium": 33953, + "ĠHippocampal": 33954, + "pull": 33955, + "ÄĻ": 33956, + "Ġsad": 33957, + "allyl": 33958, + "Ġhotspot": 33959, + "ĠElectronics": 33960, + "Ġconstitution": 33961, + "itonin": 33962, + "اÙĦ": 33963, + "Pc": 33964, + "Ġrevascular": 33965, + "Ġusable": 33966, + "ĠScatter": 33967, + "Ġgraphically": 33968, + "liminf": 33969, + "Ġrestaurant": 33970, + "ucalyptus": 33971, + "ACG": 33972, + "Analy": 33973, + "ĠMillipore": 33974, + "Ġmunicipalities": 33975, + "ĠMacrophage": 33976, + "Ġmacromolecular": 33977, + "License": 33978, + "gc": 33979, + "Ġlavage": 33980, + "ĠAES": 33981, + "ĠFCS": 33982, + "peritone": 33983, + "Ġmeasles": 33984, + "TEX": 33985, + "ĠVirulence": 33986, + "Ġhematoma": 33987, + "ĠFres": 33988, + "ĠNutrient": 33989, + "apar": 33990, + "ĠSpot": 33991, + "coplasma": 33992, + "ĠExpect": 33993, + "Ġciprofloxacin": 33994, + "phylaxis": 33995, + "ĠAtlanta": 33996, + "routing": 33997, + "arate": 33998, + "ĠCis": 33999, + "ensure": 34000, + "carriers": 34001, + "ĠVariant": 34002, + "surgical": 34003, + "ĠEstimate": 34004, + "à¹": 34005, + "ĠLiqu": 34006, + "Ġamalg": 34007, + "Ġbla": 34008, + "Ġthematic": 34009, + "IRQ": 34010, + "ACTION": 34011, + "ĠChristi": 34012, + "æľ": 34013, + "Ġnpy": 34014, + "death": 34015, + "Ġhairpin": 34016, + "Ġmultiplicities": 34017, + "Gibco": 34018, + "heated": 34019, + "afety": 34020, + "mutable": 34021, + "quarks": 34022, + "Sun": 34023, + "ql": 34024, + "Ġproductions": 34025, + "Ġgeology": 34026, + "Ġtides": 34027, + "atrix": 34028, + "Ġadmixture": 34029, + "translated": 34030, + "ĠAbu": 34031, + "nucleus": 34032, + "Ġweaknesses": 34033, + "Ġflavors": 34034, + "ĠLuis": 34035, + "ĠPutative": 34036, + "sentence": 34037, + "ĠMast": 34038, + "ĠMPS": 34039, + "ĠESS": 34040, + "Ġcompose": 34041, + "Ġbirefring": 34042, + "ĠRamsey": 34043, + "ĠCLL": 34044, + "Ġlignocell": 34045, + "ĠLamin": 34046, + "ĠWelsh": 34047, + "von": 34048, + "Ġpests": 34049, + "Ġfiction": 34050, + "ĠHRT": 34051, + "Ġassure": 34052, + "CTs": 34053, + "ĠPAHs": 34054, + "Ġcryptography": 34055, + "enerated": 34056, + "Ġops": 34057, + "ĠSynerg": 34058, + "iginal": 34059, + "ĠCraw": 34060, + "Ġkne": 34061, + "Ġcurvatures": 34062, + "Ġlux": 34063, + "ĠKenn": 34064, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 34065, + "println": 34066, + "Ġvertebrae": 34067, + "Ġrutile": 34068, + "ĠAerosol": 34069, + "referred": 34070, + "lactamase": 34071, + "vehicle": 34072, + "adir": 34073, + "izards": 34074, + "Ġcallback": 34075, + "Cluster": 34076, + "Ġsilt": 34077, + "Ġresearched": 34078, + "ĠGenerator": 34079, + "ĠRestoration": 34080, + "ĠChin": 34081, + "ometrical": 34082, + "ĠCoefficients": 34083, + "rachid": 34084, + "Face": 34085, + "Men": 34086, + "counts": 34087, + "Ġpeg": 34088, + "Ġecl": 34089, + "Ġcomedy": 34090, + "ĠLn": 34091, + "obuty": 34092, + "ĠSharing": 34093, + "Ġadequacy": 34094, + "urtosis": 34095, + "ĠPicard": 34096, + "Ġfestival": 34097, + "Ġdisposition": 34098, + "ĠComplement": 34099, + "ĠExclusion": 34100, + "Ġdextran": 34101, + "mons": 34102, + "ĠInterpolation": 34103, + "ĠSteven": 34104, + "Ġcelebrated": 34105, + "ĠhPa": 34106, + "ofrequency": 34107, + "Ġexceptionally": 34108, + "Ġenergetically": 34109, + "psychotic": 34110, + "Landau": 34111, + "Tuple": 34112, + "distributions": 34113, + "ĠRichards": 34114, + "Ġpolyps": 34115, + "ĠAbsence": 34116, + "Ġceleb": 34117, + "XG": 34118, + "Ġsimulates": 34119, + "mitters": 34120, + "Ġheatmap": 34121, + "ĠSDN": 34122, + "ĠSteps": 34123, + "Ġshallower": 34124, + "ĠTurbulent": 34125, + "YT": 34126, + "Ġnal": 34127, + "plicative": 34128, + "phae": 34129, + "ĠLeica": 34130, + "ĠAPPRO": 34131, + "Ġarrhythmia": 34132, + "Ġrewriting": 34133, + "Ġunsafe": 34134, + "Ġcoworkers": 34135, + "ĠGAD": 34136, + "ivol": 34137, + "Ġdisrupting": 34138, + "ĠUltraviolet": 34139, + "eree": 34140, + "ĠLopez": 34141, + "Ġnegation": 34142, + "Ġjaponica": 34143, + "ecessor": 34144, + "ĠPatch": 34145, + "Ġsoap": 34146, + "ĠYing": 34147, + "MSK": 34148, + "Ġtracheal": 34149, + "icos": 34150, + "Ġvp": 34151, + "FAIL": 34152, + "Ġcatabolism": 34153, + "solver": 34154, + "font": 34155, + "esp": 34156, + "ĠZou": 34157, + "Ġdarker": 34158, + "Ġlysozyme": 34159, + "covered": 34160, + "Ġmultitude": 34161, + "requently": 34162, + "Ġmetamorph": 34163, + "Ġchapters": 34164, + "hh": 34165, + "chl": 34166, + "redundant": 34167, + "acking": 34168, + "Ġentail": 34169, + "ĠPacket": 34170, + "ĠHabitat": 34171, + "imedia": 34172, + "ĠCof": 34173, + "phrase": 34174, + "Ġcloth": 34175, + "arsal": 34176, + "Ġdrums": 34177, + "TPUT": 34178, + "Args": 34179, + "ductory": 34180, + "ĠUltimately": 34181, + "icates": 34182, + "antigen": 34183, + "Though": 34184, + "ĠFlore": 34185, + "probs": 34186, + "Ġcirculatory": 34187, + "ĠContemporary": 34188, + "eplitz": 34189, + "Ġhatch": 34190, + "rized": 34191, + "ĠKop": 34192, + "mitting": 34193, + "Ġhyperspectral": 34194, + "ĠAbst": 34195, + "SIM": 34196, + "Ġfruitful": 34197, + "Ġrecipe": 34198, + "Ġimidazole": 34199, + "Ġsynonymous": 34200, + "Ġattribution": 34201, + "ĠMartÃŃnez": 34202, + "ĠRodrÃŃguez": 34203, + "particular": 34204, + "ĠInteracting": 34205, + "Conf": 34206, + "ORE": 34207, + "ĠTMA": 34208, + "ucidation": 34209, + "Ġbiochemistry": 34210, + "ĠLevy": 34211, + "Ġconcentrates": 34212, + "Ġinductor": 34213, + "Ġpyrophosph": 34214, + "Ġrespondent": 34215, + "Zhang": 34216, + "Ġrope": 34217, + "Ġdesignation": 34218, + "ĠClim": 34219, + "Ġconstrains": 34220, + "shelf": 34221, + "ĠdÏĥ": 34222, + "ĠTLC": 34223, + "ĠAhar": 34224, + "ĠMatch": 34225, + "ĠMOL": 34226, + "Ġfees": 34227, + "wealth": 34228, + "Ġhyperactivity": 34229, + "ĠBruker": 34230, + "ĠFreund": 34231, + "dichlorophenyl": 34232, + "rero": 34233, + "ĠFear": 34234, + "dotsc": 34235, + "Ġhyg": 34236, + "ĠTexture": 34237, + "Tak": 34238, + "ampled": 34239, + "Ġalgeb": 34240, + "subt": 34241, + "Ġdocumentary": 34242, + "ĠJE": 34243, + "CNS": 34244, + "Ġdeclar": 34245, + "Height": 34246, + "Ki": 34247, + "enoid": 34248, + "ĠCervical": 34249, + "fractory": 34250, + "Ġplanted": 34251, + "IFI": 34252, + "Ġconceptually": 34253, + "Ġfillers": 34254, + "icola": 34255, + "lean": 34256, + "Ġclump": 34257, + "Ġwriters": 34258, + "Generally": 34259, + "Ġost": 34260, + "opening": 34261, + "CLASS": 34262, + "Ġherpesvirus": 34263, + "Instit": 34264, + "Ġdrinks": 34265, + "ĠIntensive": 34266, + "Ġmusician": 34267, + "Ġanchors": 34268, + "Series": 34269, + "ĠFAM": 34270, + "ĠBott": 34271, + "ĠECC": 34272, + "Ġinversions": 34273, + "Ġacres": 34274, + "Ġswabs": 34275, + "ĠÍī": 34276, + "ĠBerkeley": 34277, + "Ġplum": 34278, + "Ġempower": 34279, + "Ġphotoemission": 34280, + "ĠRabi": 34281, + "East": 34282, + "Taylor": 34283, + "OSE": 34284, + "Ġdenied": 34285, + "ĠHTTP": 34286, + "MU": 34287, + "hew": 34288, + "Ġthri": 34289, + "ĠCERN": 34290, + "Ġsuffice": 34291, + "functionalized": 34292, + "Ġcrabs": 34293, + "Ġidempotent": 34294, + "Ġpostulate": 34295, + "ĠCBF": 34296, + "discrim": 34297, + "Character": 34298, + "ĠRecombination": 34299, + "Cache": 34300, + "omit": 34301, + "ĠAda": 34302, + "Ġcursor": 34303, + "EMT": 34304, + "Ġmesoscale": 34305, + "guide": 34306, + "Hyper": 34307, + "Ġht": 34308, + "renes": 34309, + "ussen": 34310, + "whereas": 34311, + "Ġintegrator": 34312, + "Ġsyncy": 34313, + "arous": 34314, + "Ġcounteract": 34315, + "halose": 34316, + "ĠNotation": 34317, + "ĠRelevance": 34318, + "vf": 34319, + "Ġinbred": 34320, + "Ġrecirc": 34321, + "Ġende": 34322, + "Ġpresidential": 34323, + "Ġlactose": 34324, + "acional": 34325, + "ospi": 34326, + "ĠVGG": 34327, + "oselectivity": 34328, + "ĠConfig": 34329, + "Ġfingerprints": 34330, + "Interface": 34331, + "purple": 34332, + "etus": 34333, + "ĠNin": 34334, + "ĠKras": 34335, + "ĠReports": 34336, + "ĠSeattle": 34337, + "ADC": 34338, + "Ġlipoproteins": 34339, + "cyclohexyl": 34340, + "opressin": 34341, + "Ġwavefront": 34342, + "tetrazol": 34343, + "thys": 34344, + "Ġdivor": 34345, + "aminophen": 34346, + "ĠPerry": 34347, + "ĠConsiderations": 34348, + "ĠHalo": 34349, + "Ġreflexive": 34350, + "thiazolidin": 34351, + "oxycycline": 34352, + "CW": 34353, + "odim": 34354, + "ĠChong": 34355, + "Ġequilibrated": 34356, + "rime": 34357, + "ymology": 34358, + "Ġdevoid": 34359, + "rigel": 34360, + "amatergic": 34361, + "Ġidentifications": 34362, + "Ġcontrollability": 34363, + "ecticut": 34364, + "ĠSynchronization": 34365, + "ulatus": 34366, + "Ġcorrelating": 34367, + "Ġmuons": 34368, + "Ġcompartmental": 34369, + "Ġinhomogeneities": 34370, + "Ġevacuation": 34371, + "respiratory": 34372, + "dimethoxy": 34373, + "Ġinterferometric": 34374, + "Ġastronomy": 34375, + "ZD": 34376, + "ĦĦ": 34377, + "elia": 34378, + "bler": 34379, + "Ġpioneering": 34380, + "Ġpits": 34381, + "Ġmansoni": 34382, + "ĠCOND": 34383, + "Ġcodeword": 34384, + "imura": 34385, + "ĠDopamine": 34386, + "ĠGiov": 34387, + "ĠCameroon": 34388, + "Sem": 34389, + "dong": 34390, + "otto": 34391, + "emies": 34392, + "Ġinterquartile": 34393, + "llbracket": 34394, + "otropies": 34395, + "Ġhappening": 34396, + "ĠPalm": 34397, + "Ġstuff": 34398, + "Ġparking": 34399, + "egal": 34400, + "ĠCOP": 34401, + "Ġorganizing": 34402, + "Ġpolyhedral": 34403, + "Ġprovenance": 34404, + "Js": 34405, + "chains": 34406, + "egu": 34407, + "mercap": 34408, + "leveland": 34409, + "Ġerythroid": 34410, + "ymptomatic": 34411, + "Ġzigzag": 34412, + "Ġinferring": 34413, + "Ġapprox": 34414, + "Ġdownlink": 34415, + "ĠDeficiency": 34416, + "rbracket": 34417, + "ĠTIM": 34418, + "STS": 34419, + "ainen": 34420, + "Ġunloading": 34421, + "ĠXP": 34422, + "ĠWhilst": 34423, + "ĠIDH": 34424, + "ĠTIMP": 34425, + "rrbracket": 34426, + "acities": 34427, + "Ġwhale": 34428, + "ĠWAR": 34429, + "Ġinfl": 34430, + "ĠPresentation": 34431, + "authorbsnm": 34432, + "Ġbactericidal": 34433, + "SPEC": 34434, + "Ġdysregulated": 34435, + "ĠICAM": 34436, + "nano": 34437, + "Ġwafers": 34438, + "ĠMUC": 34439, + "Ġalien": 34440, + "chke": 34441, + "Ġslabs": 34442, + "Ġbacking": 34443, + "nsis": 34444, + "Ġbalances": 34445, + "ethane": 34446, + "Linked": 34447, + "Chen": 34448, + "Hymenoptera": 34449, + "itations": 34450, + "ĠOUT": 34451, + "transplant": 34452, + "conditioned": 34453, + "ĠBenefits": 34454, + "Tyr": 34455, + "atmosp": 34456, + "ĠAdhesion": 34457, + "Ġthorac": 34458, + "activator": 34459, + "Ġphosphatidylinositol": 34460, + "Ġreportedly": 34461, + "ĠCLASS": 34462, + "Ġrenewed": 34463, + "ĠPharmacological": 34464, + "Ġminimise": 34465, + "glucosidase": 34466, + "adenosyl": 34467, + "Ġovip": 34468, + "initializer": 34469, + "Ġforage": 34470, + "rms": 34471, + "ĠImag": 34472, + "ĠAnnexin": 34473, + "ĠVehicles": 34474, + "Ġfles": 34475, + "sta": 34476, + "ĠGBS": 34477, + "ĠChat": 34478, + "measurements": 34479, + "ĠAuditory": 34480, + "Cut": 34481, + "Fv": 34482, + "Ġmaker": 34483, + "application": 34484, + "Ġreversing": 34485, + "Ġstip": 34486, + "Ġfaecalis": 34487, + "icycle": 34488, + "Ġtrimmed": 34489, + "Ġexacerbation": 34490, + "Ġtranscranial": 34491, + "ĠMomentum": 34492, + "Ġfc": 34493, + "ĠFOV": 34494, + "Ġangina": 34495, + "Ġnanostructure": 34496, + "Ġantagonism": 34497, + "ĠLEDs": 34498, + "ìĹIJ": 34499, + "Ġfals": 34500, + "aporation": 34501, + "ĠInvasive": 34502, + "ĠKm": 34503, + "ertation": 34504, + "Ġharness": 34505, + "Ġfertile": 34506, + "ĠTRUE": 34507, + "Ġshelter": 34508, + "ĠWolbachia": 34509, + "shoot": 34510, + "Ġsess": 34511, + "ĠHous": 34512, + "ĠAce": 34513, + "ĠCML": 34514, + "Ġproactive": 34515, + "Ġshots": 34516, + "Ġcoup": 34517, + "restling": 34518, + "uniformly": 34519, + "yam": 34520, + "olase": 34521, + "ĠICS": 34522, + "ĠEbola": 34523, + "rolling": 34524, + "trunc": 34525, + "ĠRepresentatives": 34526, + "Ġgrasping": 34527, + "ĠAnomaly": 34528, + "ĠMine": 34529, + "ĠMPO": 34530, + "leright": 34531, + "Ġinstitute": 34532, + "Ġsugarcane": 34533, + "ÑĢа": 34534, + "Ġoccluded": 34535, + "ĠMagellanic": 34536, + "BEC": 34537, + "Wi": 34538, + "oA": 34539, + "Ġgapped": 34540, + "ĠPRC": 34541, + "ĠMAE": 34542, + "Ġmusicians": 34543, + "ĠSignificantly": 34544, + "Ġforthcoming": 34545, + "Ġacclimation": 34546, + "required": 34547, + "verbal": 34548, + "ĠFX": 34549, + "ĠMLE": 34550, + "Ġcompass": 34551, + "ĠMultimodal": 34552, + "Grant": 34553, + "Ġtvb": 34554, + "Instruction": 34555, + "Ġsenses": 34556, + "urbed": 34557, + "hamn": 34558, + "Ġframed": 34559, + "Ġurothel": 34560, + "orin": 34561, + "seal": 34562, + "Ġflasks": 34563, + "shops": 34564, + "Ġwheels": 34565, + "ĠRadon": 34566, + "ĠPlanetary": 34567, + "Ġhedge": 34568, + "Ġdk": 34569, + "Ġevidently": 34570, + "threads": 34571, + "Ġtad": 34572, + "elim": 34573, + "imov": 34574, + "istem": 34575, + "andi": 34576, + "Ġleisure": 34577, + "ostom": 34578, + "Ġcaring": 34579, + "ĠSmoking": 34580, + "Ġcompetitors": 34581, + "AFS": 34582, + "xl": 34583, + "ĠSatur": 34584, + "ĠFerg": 34585, + "Ġchin": 34586, + "ĠCDR": 34587, + "ĠSOM": 34588, + "osaccharide": 34589, + "MODEL": 34590, + "ECC": 34591, + "Ġdas": 34592, + "agonist": 34593, + "stery": 34594, + "Ġrelays": 34595, + "zek": 34596, + "Ġneoplasm": 34597, + "Chip": 34598, + "Ġgill": 34599, + "lamed": 34600, + "cerning": 34601, + "Ġinconsistencies": 34602, + "aceans": 34603, + "ĠAdri": 34604, + "ĠAfghan": 34605, + "Ġniches": 34606, + "Ġtunnelling": 34607, + "gus": 34608, + "ĠIan": 34609, + "Ġburial": 34610, + "Transform": 34611, + "ocompatible": 34612, + "Ġseldom": 34613, + "Ġdisclosed": 34614, + "âĪķ": 34615, + "Ġrefining": 34616, + "Ġtyph": 34617, + "Ġcooperate": 34618, + "Ġasphalt": 34619, + "ĠConstitution": 34620, + "flavor": 34621, + "Ġwarp": 34622, + "ż": 34623, + "Ġcraw": 34624, + "ĠIndigenous": 34625, + "ĠPrevent": 34626, + "Ġtrigeminal": 34627, + "ĠFriedrich": 34628, + "ĠInterferon": 34629, + "iosity": 34630, + "warm": 34631, + "uson": 34632, + "Ġunderlies": 34633, + "Ġmultiplets": 34634, + "ĠSUPER": 34635, + "ĠManufacturing": 34636, + "Ġvimentin": 34637, + "ramine": 34638, + "Ġefficacious": 34639, + "iced": 34640, + "ĠVall": 34641, + "othorax": 34642, + "Ġaudi": 34643, + "Qs": 34644, + "ĠPAL": 34645, + "ĠHold": 34646, + "hattan": 34647, + "idding": 34648, + "wana": 34649, + "Ġpending": 34650, + "Ġperennial": 34651, + "Ġtouching": 34652, + "xpected": 34653, + "Distance": 34654, + "nav": 34655, + "Ġisomeric": 34656, + "ĠMCI": 34657, + "numbers": 34658, + "Ġreverses": 34659, + "Ġpolycystic": 34660, + "Hem": 34661, + "uities": 34662, + "optional": 34663, + "Ġsubcortical": 34664, + "ĠSupply": 34665, + "ĠCalder": 34666, + "Ġmangrove": 34667, + "Ġpads": 34668, + "urfaces": 34669, + "ĠFaster": 34670, + "Ġunderneath": 34671, + "Ġprolactin": 34672, + "Ġclearer": 34673, + "Ġscintillation": 34674, + "Ġhumidified": 34675, + "ĠWound": 34676, + "ĠHPA": 34677, + "Ġcollapsing": 34678, + "Ġbaryonic": 34679, + "ĠMEASU": 34680, + "ĠGü": 34681, + "Ġdetr": 34682, + "Ġsubstituent": 34683, + "ĠRomania": 34684, + "ĠInvolved": 34685, + "Ġduodenal": 34686, + "ĠAmp": 34687, + "ĠSIS": 34688, + "scher": 34689, + "auth": 34690, + "ĠRespond": 34691, + "ĠRanking": 34692, + "trip": 34693, + "xF": 34694, + "istin": 34695, + "Ġpauc": 34696, + "reflection": 34697, + "Ġcornea": 34698, + "Ġbolus": 34699, + "Ġpivot": 34700, + "October": 34701, + "ĠSERS": 34702, + "ĠXing": 34703, + "ANET": 34704, + "Chinese": 34705, + "ĠMusc": 34706, + "Dynamic": 34707, + "Mesh": 34708, + "Ġdiphosphate": 34709, + "Ġconspecific": 34710, + "lector": 34711, + "ĠEcu": 34712, + "ĠCoverage": 34713, + "ĠãĢĪ": 34714, + "COD": 34715, + "among": 34716, + "Ġposit": 34717, + "imumab": 34718, + "ĠpN": 34719, + "Ġcoaching": 34720, + "exports": 34721, + "Ġrealm": 34722, + "ĠFerreira": 34723, + "Ġnationally": 34724, + "Ġturtle": 34725, + "ubtedly": 34726, + "ĠDraft": 34727, + "Ġendl": 34728, + "ĠContinuum": 34729, + "embeddings": 34730, + "Ġá¹½": 34731, + "ĠCrime": 34732, + "Ġimmigration": 34733, + "ĠFilip": 34734, + "Ġgarnet": 34735, + "Ġobscure": 34736, + "ĠTYPE": 34737, + "Ġultrastructural": 34738, + "caemia": 34739, + "ĠSeman": 34740, + "rink": 34741, + "tiff": 34742, + "uccal": 34743, + "kee": 34744, + "itudinally": 34745, + "ĠAlloy": 34746, + "ĠAnalyzer": 34747, + "continue": 34748, + "ĠAlabama": 34749, + "QOL": 34750, + "Ġpollin": 34751, + "Ġcorrespondences": 34752, + "ĠResol": 34753, + "FIR": 34754, + "ulare": 34755, + "tawa": 34756, + "URCE": 34757, + "Ġurbanization": 34758, + "zd": 34759, + "Ġgloss": 34760, + "ERA": 34761, + "ĠDetermine": 34762, + "Date": 34763, + "ĠPSP": 34764, + "ĠShig": 34765, + "repta": 34766, + "ĠGait": 34767, + "neutrino": 34768, + "Ġpervasive": 34769, + "ĠâĢ¢âĢ¢âĢ¢": 34770, + "Ġhomozyg": 34771, + "Ġadaptively": 34772, + "graphic": 34773, + "ĠJohnston": 34774, + "zt": 34775, + "explicit": 34776, + "Ġhelmin": 34777, + "Ġpes": 34778, + "ARF": 34779, + "ĠFram": 34780, + "ĠAmsterdam": 34781, + "Ġlogarithms": 34782, + "ĠCreative": 34783, + "PageIndex": 34784, + "Ġpacing": 34785, + "ĠPCS": 34786, + "Ġforebrain": 34787, + "ĠCTCF": 34788, + "decomposition": 34789, + "Ġbearings": 34790, + "Ġanhydrous": 34791, + "Ġcb": 34792, + "ĠMON": 34793, + "ĠNodes": 34794, + "strum": 34795, + "ĠJans": 34796, + "Ġdelineate": 34797, + "Ġdichroism": 34798, + "conformal": 34799, + "Ġretreat": 34800, + "glial": 34801, + "Ġnuclease": 34802, + "ĠBaltimore": 34803, + "Ġpaying": 34804, + "Ġboreal": 34805, + "tipation": 34806, + "Root": 34807, + "SQL": 34808, + "sources": 34809, + "endo": 34810, + "ĠOrion": 34811, + "Plus": 34812, + "ĠDEL": 34813, + "ĠThan": 34814, + "Ġmonoph": 34815, + "Ġreflector": 34816, + "Ze": 34817, + "ĠLinking": 34818, + "sync": 34819, + "ĠCREB": 34820, + "national": 34821, + "urized": 34822, + "ĠPeptides": 34823, + "ĠBegin": 34824, + "borg": 34825, + "piperidyl": 34826, + "Ġoverestimation": 34827, + "RGB": 34828, + "TK": 34829, + "Ġbeings": 34830, + "Ġattains": 34831, + "Ġreservation": 34832, + "ĠMotivation": 34833, + "Ġtrimethyl": 34834, + "ĠTerminal": 34835, + "Ġintentional": 34836, + "Negative": 34837, + "ĠCronbach": 34838, + "dorferi": 34839, + "Daw": 34840, + "VAR": 34841, + "dP": 34842, + "imath": 34843, + "overex": 34844, + "Ġfibrotic": 34845, + "Ġsmartphones": 34846, + "Ġontologies": 34847, + "Good": 34848, + "utively": 34849, + "ĠVB": 34850, + "SPE": 34851, + "ĠMcDonald": 34852, + "galaxies": 34853, + "Ġbiochar": 34854, + "ĠEMS": 34855, + "ĠNf": 34856, + "orship": 34857, + "Ġbackscattering": 34858, + "Ġп": 34859, + "Ġanthocyanin": 34860, + "ĠPhoenix": 34861, + "contained": 34862, + "ĠPSII": 34863, + "hlung": 34864, + "ĠLAI": 34865, + "Ġlectures": 34866, + "Ġdispatch": 34867, + "VF": 34868, + "ĠMEC": 34869, + "ĠWes": 34870, + "Ġbackscatter": 34871, + "otite": 34872, + "ĠSRC": 34873, + "Ġcurrency": 34874, + "onyms": 34875, + "aspartate": 34876, + "Ġcoset": 34877, + "ĠCPP": 34878, + "orbing": 34879, + "ĠEmbeddings": 34880, + "ĠSurveys": 34881, + "Ġneurodevelopmental": 34882, + "ĠSRE": 34883, + "ĠInterior": 34884, + "ĠARDS": 34885, + "experiments": 34886, + "bromophenyl": 34887, + "ĠECL": 34888, + "ĠOPE": 34889, + "mediation": 34890, + "Ġthermoc": 34891, + "Ġinterpretable": 34892, + "ĠMicrobiome": 34893, + "eastern": 34894, + "¿": 34895, + "ĠTDP": 34896, + "athon": 34897, + "ĠByzantine": 34898, + "anyon": 34899, + "Ġepitaxy": 34900, + "Ġcriticized": 34901, + "Millipore": 34902, + "ĠDEP": 34903, + "ĠFreedom": 34904, + "junctions": 34905, + "ĠASM": 34906, + "ĠGren": 34907, + "Ġsigning": 34908, + "Ġconstituting": 34909, + "oproterozoic": 34910, + "ĠSynech": 34911, + "ĠVoice": 34912, + "Ġcholecyst": 34913, + "bilities": 34914, + "online": 34915, + "ĠEdd": 34916, + "ĠKup": 34917, + "ĠLett": 34918, + "ĠMarin": 34919, + "ĠGoal": 34920, + "ĠSYM": 34921, + "introduced": 34922, + "naphthyl": 34923, + "ĠLü": 34924, + "Ġmx": 34925, + "Ġblu": 34926, + "Ġrm": 34927, + "ĠDeletion": 34928, + "ĠConnecticut": 34929, + "Coleoptera": 34930, + "try": 34931, + "Ġsoot": 34932, + "ĠCountries": 34933, + "Ġsickle": 34934, + "Meta": 34935, + "ĠSib": 34936, + "ĠHNO": 34937, + "ĠUD": 34938, + "Ġexpr": 34939, + "Ġallowable": 34940, + "ĠIndirect": 34941, + "tisation": 34942, + "Ġadenomas": 34943, + "electronics": 34944, + "RNN": 34945, + "ĠTCF": 34946, + "Ġglucagon": 34947, + "ĠCitation": 34948, + "Ġgamb": 34949, + "andez": 34950, + "Ġtransmits": 34951, + "ajima": 34952, + "Ġholonomy": 34953, + "ìł": 34954, + "actam": 34955, + "ĠThreat": 34956, + "ĠPearl": 34957, + "Ġeruptions": 34958, + "ĠImmunohistochemistry": 34959, + "Yes": 34960, + "patrick": 34961, + "Ġama": 34962, + "Ġdrew": 34963, + "ĠTasks": 34964, + "ĠPIM": 34965, + "Ġdispat": 34966, + "ĠDetroit": 34967, + "Ġcoexist": 34968, + "arboxylase": 34969, + "IBM": 34970, + "ĠTUNEL": 34971, + "ĠUF": 34972, + "ĠANG": 34973, + "Ġsarcopenia": 34974, + "Ġhaptic": 34975, + "Ġcarbonates": 34976, + "Ġmitophagy": 34977, + "Ġcitizen": 34978, + "ĠCONTROL": 34979, + "fif": 34980, + "Ġwi": 34981, + "ĠGLO": 34982, + "ensored": 34983, + "ĠPara": 34984, + "ĠAbdel": 34985, + "oietin": 34986, + "Ġtoe": 34987, + "ĠSQU": 34988, + "ĠRag": 34989, + "Ġxylem": 34990, + "Ġliberal": 34991, + "ĠMargaret": 34992, + "Wa": 34993, + "kp": 34994, + "ĠPEM": 34995, + "ĠDDR": 34996, + "Ġgenotypic": 34997, + "ĠYM": 34998, + "INGS": 34999, + "keras": 35000, + "ĠEducational": 35001, + "ĠCultures": 35002, + "instr": 35003, + "ĠFuchs": 35004, + "agasc": 35005, + "factant": 35006, + "Ġtenth": 35007, + "ABL": 35008, + "Ġpermeable": 35009, + "ĠCameron": 35010, + "BrN": 35011, + "ĠMuller": 35012, + "ĠReversible": 35013, + "wild": 35014, + "Ġfusions": 35015, + "osulf": 35016, + "ĠEoS": 35017, + "ĠKö": 35018, + "detected": 35019, + "ĠCollagen": 35020, + "Ġdescendants": 35021, + "election": 35022, + "arange": 35023, + "Ġbounce": 35024, + "Ġcontag": 35025, + "Invalid": 35026, + "ĠCoating": 35027, + "tasks": 35028, + "arma": 35029, + "ĠKC": 35030, + "Ġdiar": 35031, + "ĠSuppress": 35032, + "Ġfractionated": 35033, + "Ġsnail": 35034, + "Ġmicrophone": 35035, + "ĠScienti": 35036, + "Ġchemiluminescence": 35037, + "software": 35038, + "Ġburgdorferi": 35039, + "Ġboot": 35040, + "ĠCSCs": 35041, + "ĠMSI": 35042, + "tsev": 35043, + "Ġheater": 35044, + "fractal": 35045, + "Ġendosomes": 35046, + "Uniform": 35047, + "Ġathlete": 35048, + "ĠDriven": 35049, + "Ġvivax": 35050, + "Kind": 35051, + "satisfies": 35052, + "Ġcorticosteroid": 35053, + "ĠEstablishment": 35054, + "calibration": 35055, + "Ġdimeric": 35056, + "Ġcereal": 35057, + "ĠSupervised": 35058, + "ĠSPM": 35059, + "MBER": 35060, + "Ġhemispheres": 35061, + "Ġpercentiles": 35062, + "Leu": 35063, + "Major": 35064, + "Ġexagger": 35065, + "ĠdsRNA": 35066, + "December": 35067, + "ĠZrO": 35068, + "Ġasymmetrical": 35069, + "ĠVAS": 35070, + "ĠJM": 35071, + "Ġintegrations": 35072, + "Ġhandover": 35073, + "Cycl": 35074, + "implant": 35075, + "Ġquote": 35076, + "Ġcyclone": 35077, + "ĠStephan": 35078, + "ĠFranco": 35079, + "Ġawake": 35080, + "Ġfeeder": 35081, + "CHAR": 35082, + "Condition": 35083, + "ĠCharl": 35084, + "ĠBrigade": 35085, + "Ġremediation": 35086, + "cig": 35087, + "ĠBohr": 35088, + "ĠVacuum": 35089, + "ĠToxoplasma": 35090, + "Ġghrelin": 35091, + "ĠTRAF": 35092, + "aye": 35093, + "Client": 35094, + "iliation": 35095, + "xyz": 35096, + "mingham": 35097, + "ĠSUB": 35098, + "ïĢł": 35099, + "Ġconversions": 35100, + "Ġmultipath": 35101, + "missive": 35102, + "Ġeqn": 35103, + "bulk": 35104, + "myc": 35105, + "Ġexacerbated": 35106, + "ت": 35107, + "Ġproteinase": 35108, + "Ġbuilder": 35109, + "ahara": 35110, + "Ġinvert": 35111, + "ĠReception": 35112, + "axanthin": 35113, + "Ġprimed": 35114, + "Ġcopula": 35115, + "Ġproceedings": 35116, + "Ġnondegenerate": 35117, + "Ġintox": 35118, + "Ġneedles": 35119, + "lengths": 35120, + "Ġtransposon": 35121, + "hon": 35122, + "ĠTPC": 35123, + "pland": 35124, + "oxyn": 35125, + "ICH": 35126, + "Ġintrauterine": 35127, + "Ġlaminated": 35128, + "ĠOBSERV": 35129, + "Match": 35130, + "ĠInsur": 35131, + "ĠAmyloid": 35132, + "Ġwarped": 35133, + "ematical": 35134, + "ĠPractices": 35135, + "ãģ®": 35136, + "ĠBrassica": 35137, + "Ġhyperthermia": 35138, + "Ġdn": 35139, + "ĠLIF": 35140, + "ĠMetropolitan": 35141, + "ĠBrdU": 35142, + "impact": 35143, + "filtered": 35144, + "ĠReagent": 35145, + "vp": 35146, + "ĠTip": 35147, + "ĠProportional": 35148, + "Ġbloodstream": 35149, + "Simple": 35150, + "Ġtyros": 35151, + "ĠHenri": 35152, + "Ġretrotrans": 35153, + "aciens": 35154, + "Ġmistakes": 35155, + "acylglycerol": 35156, + "ĠMirror": 35157, + "VERSION": 35158, + "vre": 35159, + "Ġbact": 35160, + "ocked": 35161, + "epsis": 35162, + "Ġsonication": 35163, + "ĠPurkinje": 35164, + "Ġmismatches": 35165, + "ĠAOD": 35166, + "Ġhypergraph": 35167, + "ĠMiami": 35168, + "ammed": 35169, + "Ġconversely": 35170, + "ĠGabor": 35171, + "ĠGDM": 35172, + "Ġcoiled": 35173, + "onica": 35174, + "Ġevolutions": 35175, + "ĠRBM": 35176, + "ĠReef": 35177, + "ĠAbram": 35178, + "ĠPrecise": 35179, + "increase": 35180, + "ĠPlatelet": 35181, + "Generator": 35182, + "Arch": 35183, + "ĠBened": 35184, + "preceq": 35185, + "measurable": 35186, + "CAS": 35187, + "ĠTourn": 35188, + "Ġgiants": 35189, + "Ġeddies": 35190, + "Ġcolumnar": 35191, + "aggregation": 35192, + "Ġzirconia": 35193, + "ducibility": 35194, + "Ġservo": 35195, + "Ġbeauty": 35196, + "Ġheap": 35197, + "ĠâĪĴâĪĴâĪĴ": 35198, + "Ġconductivities": 35199, + "Ġdarkness": 35200, + "Ġoccupying": 35201, + "ĠClean": 35202, + "bash": 35203, + "ulans": 35204, + "appy": 35205, + "ĠMarker": 35206, + "runtime": 35207, + "Ġhaemoglobin": 35208, + "Ġdesktop": 35209, + "mis": 35210, + "ĠSof": 35211, + "osse": 35212, + "Ġcomoving": 35213, + "Ġclutter": 35214, + "ourced": 35215, + "Ġsubj": 35216, + "arching": 35217, + "ĠSolomon": 35218, + "locking": 35219, + "Ġparap": 35220, + "Ġrotator": 35221, + "ĠACKNOWLEDGMENTS": 35222, + "Ter": 35223, + "yster": 35224, + "ĠWebb": 35225, + "Ġsubsample": 35226, + "osilicate": 35227, + "Training": 35228, + "orpha": 35229, + "Ġtimeout": 35230, + "otinamide": 35231, + "ĠFabry": 35232, + "ĠReceiver": 35233, + "Ġconjunctiv": 35234, + "ĠEcuador": 35235, + "ĠIda": 35236, + "Ġcasein": 35237, + "Ġïģ¸": 35238, + "ĠBarn": 35239, + "ĠSchools": 35240, + "elona": 35241, + "dip": 35242, + "ĠChrys": 35243, + "ICI": 35244, + "Ġposteriori": 35245, + "Ġbleaching": 35246, + "ĠPersonality": 35247, + "umbers": 35248, + "ĠModes": 35249, + "Ġnotification": 35250, + "Ġsupine": 35251, + "alued": 35252, + "keep": 35253, + "ĠFranz": 35254, + "Ġwounded": 35255, + "YL": 35256, + "Ġdilemma": 35257, + "ĠClara": 35258, + "ĠCarroll": 35259, + "Ġsickness": 35260, + "Ġproxies": 35261, + "ecks": 35262, + "ĠÏ«": 35263, + "Ġplanting": 35264, + "Ġciphertext": 35265, + "ĠFamilies": 35266, + "iesel": 35267, + "Ġincongru": 35268, + "ĠExcitation": 35269, + "Ġconferred": 35270, + "ĠButter": 35271, + "Impl": 35272, + "collision": 35273, + "idol": 35274, + "Ġacquires": 35275, + "ĠOwen": 35276, + "SAM": 35277, + "ĠGUT": 35278, + "lects": 35279, + "Ġdeleg": 35280, + "Shot": 35281, + "Ġanthrac": 35282, + "Russian": 35283, + "ĠPCE": 35284, + "ĠâĥĹ": 35285, + "ĠKab": 35286, + "NAC": 35287, + "Ġargparse": 35288, + "ĠViol": 35289, + "Ġanticoagulation": 35290, + "Ġcredibility": 35291, + "Ġrotavirus": 35292, + "ĠInvest": 35293, + "Ġrecol": 35294, + "variety": 35295, + "Ġdeformable": 35296, + "Ġenergetics": 35297, + "Ġconsultations": 35298, + "letics": 35299, + "ĠFoss": 35300, + "ĠLIGO": 35301, + "php": 35302, + "ĠChal": 35303, + "ĠMalawi": 35304, + "Ġstrokes": 35305, + "horm": 35306, + "Ġbs": 35307, + "Ġplural": 35308, + "strategy": 35309, + "Ġmisalignment": 35310, + "previous": 35311, + "filters": 35312, + "ĠDemographics": 35313, + "deterministic": 35314, + "Ġcyclophosphamide": 35315, + "Ġstreak": 35316, + "ĠBiosynthesis": 35317, + "Ġsubcutaneously": 35318, + "jn": 35319, + "Ġampicillin": 35320, + "ĠChag": 35321, + "iformes": 35322, + "IFICATION": 35323, + "Ġyourself": 35324, + "Ġtolerability": 35325, + "Ġautocl": 35326, + "rhs": 35327, + "Ġpupils": 35328, + "Ġgauged": 35329, + "Lay": 35330, + "ĠSanti": 35331, + "ĠDBP": 35332, + "ĠGary": 35333, + "drive": 35334, + "Ġtrustworth": 35335, + "Ġcontingency": 35336, + "Cube": 35337, + "Host": 35338, + "fu": 35339, + "Ġhsa": 35340, + "issner": 35341, + "ITT": 35342, + "ĠSrTiO": 35343, + "Ġcounselling": 35344, + "integrable": 35345, + "Ġunderway": 35346, + "Ġstandardised": 35347, + "bius": 35348, + "Firstly": 35349, + "Ġporphyrin": 35350, + "Area": 35351, + "iw": 35352, + "Ġub": 35353, + "ĠLynch": 35354, + "ĠWBC": 35355, + "ilden": 35356, + "Ġhomeless": 35357, + "Ġmagnetosphere": 35358, + "Ġnighttime": 35359, + "ncbi": 35360, + "Ġdownt": 35361, + "lethal": 35362, + "Ġinterim": 35363, + "ĠResil": 35364, + "Ġcontinually": 35365, + "ĠImmunofluorescence": 35366, + "Design": 35367, + "Ġadvocate": 35368, + "reptavidin": 35369, + "fw": 35370, + "story": 35371, + "ĠPSS": 35372, + "Ġfiled": 35373, + "Ġbedrock": 35374, + "Ġisoflurane": 35375, + "Ġreluct": 35376, + "eward": 35377, + "ĠIndependence": 35378, + "ĠBurkholder": 35379, + "Ġcinn": 35380, + "Ġcaptive": 35381, + "Ġcomposing": 35382, + "Ġrestraint": 35383, + "Ġquestionable": 35384, + "ĠTomato": 35385, + "Ġzeroth": 35386, + "rins": 35387, + "omez": 35388, + "Ġglia": 35389, + "ĠGlac": 35390, + "Independent": 35391, + "Ġobjectively": 35392, + "pA": 35393, + "Ġfavoring": 35394, + "ipelago": 35395, + "Ġincontinence": 35396, + "bium": 35397, + "ĠLZ": 35398, + "ĠLed": 35399, + "hexyl": 35400, + "Ġceased": 35401, + "Ġoleic": 35402, + "ĠImpairment": 35403, + "Ñĸ": 35404, + "ongo": 35405, + "Ġrunner": 35406, + "Ġcucumber": 35407, + "ĠPerform": 35408, + "Ġdoublets": 35409, + "Ġeigenfunction": 35410, + "Ġ̺": 35411, + "ĠHenderson": 35412, + "Klein": 35413, + "Tab": 35414, + "Ġbeer": 35415, + "ocom": 35416, + "unciation": 35417, + "------": 35418, + "ĠTSC": 35419, + "ogas": 35420, + "Ġrud": 35421, + "Ġincis": 35422, + "ĠLOG": 35423, + "FBQ": 35424, + "Ġinterconnection": 35425, + "î": 35426, + "arbox": 35427, + "ĠIBS": 35428, + "ĠNCT": 35429, + "ĠGand": 35430, + "Ġyaw": 35431, + "ĠTransverse": 35432, + "ĠSudan": 35433, + "Ġconstriction": 35434, + "Hor": 35435, + "Ġevasion": 35436, + "Ġmeromorphic": 35437, + "ĠPBMC": 35438, + "IUM": 35439, + "reed": 35440, + "ĠBö": 35441, + "ĠEMB": 35442, + "ukh": 35443, + "Ġwinners": 35444, + "Ġascites": 35445, + "Mes": 35446, + "Ġeclipse": 35447, + "ĠEocene": 35448, + "adiazol": 35449, + "Ġrecoveries": 35450, + "Starting": 35451, + "rema": 35452, + "ĠÃİ": 35453, + "monotonic": 35454, + "ĠMeOH": 35455, + "ĠFlood": 35456, + "Ġwatching": 35457, + "GTP": 35458, + "iel": 35459, + "müller": 35460, + "åħ": 35461, + "Ġpolyphenol": 35462, + "ĠLMI": 35463, + "redit": 35464, + "therm": 35465, + "Ġneurite": 35466, + "Quantum": 35467, + "rachlor": 35468, + "ĠRubin": 35469, + "Ġbfnm": 35470, + "Are": 35471, + "arachn": 35472, + "Ġduck": 35473, + "ĠTrajectory": 35474, + "ĠNitric": 35475, + "lv": 35476, + "uid": 35477, + "imag": 35478, + "ĠMULT": 35479, + "Ġgenre": 35480, + "arie": 35481, + "Ġtrifluor": 35482, + "ĠCorpus": 35483, + "oliosis": 35484, + "ĠCCK": 35485, + "Kit": 35486, + "father": 35487, + "Ġtennis": 35488, + "itsch": 35489, + "HCV": 35490, + "lantic": 35491, + "ĠAQ": 35492, + "izu": 35493, + "astatin": 35494, + "othio": 35495, + "ĠAnatomy": 35496, + "Ġáŀ": 35497, + "globulin": 35498, + "Ġinterpol": 35499, + "Ġtunnels": 35500, + "Ġnatri": 35501, + "imed": 35502, + "ĠDew": 35503, + "Ġsubscripts": 35504, + "tites": 35505, + "Ġhistologically": 35506, + "Opt": 35507, + "xn": 35508, + "Ġresampling": 35509, + "aney": 35510, + "Ġtrast": 35511, + "Ġsinensis": 35512, + "Ġsenescent": 35513, + "Fast": 35514, + "Ġhampered": 35515, + "Ġblocker": 35516, + "ushima": 35517, + "Ġhospitalizations": 35518, + "Lim": 35519, + "oons": 35520, + "ÿ": 35521, + "ĠAPS": 35522, + "ĠYok": 35523, + "ĠZam": 35524, + "Ġexperimenter": 35525, + "ĠDisks": 35526, + "Ġà¬": 35527, + "ĠScop": 35528, + "ĠAph": 35529, + "ĠParents": 35530, + "ĠPlots": 35531, + "ĠCONT": 35532, + "ĠÐĪ": 35533, + "Ġhomologue": 35534, + "ĠCooling": 35535, + "reth": 35536, + "Ġovari": 35537, + "ĠTamil": 35538, + "vrule": 35539, + "ĠPCP": 35540, + "arious": 35541, + "Active": 35542, + "oprotection": 35543, + "ĠAlfv": 35544, + "Ġinfra": 35545, + "ĠCoherence": 35546, + "closures": 35547, + "hydroxymethyl": 35548, + "EH": 35549, + "Ġmaser": 35550, + "ĠNIST": 35551, + "leck": 35552, + "concat": 35553, + "Ġtraine": 35554, + "Ġmixes": 35555, + "Ġribosomes": 35556, + "lia": 35557, + "puls": 35558, + "Ġascer": 35559, + "ĠBanks": 35560, + "ellin": 35561, + "applied": 35562, + "Ġclips": 35563, + "Ġmetap": 35564, + "Ġcopro": 35565, + "Ġepidid": 35566, + "ĠEpidemiological": 35567, + "ĠNicholas": 35568, + "ĠKings": 35569, + "Ġlarva": 35570, + "BsAg": 35571, + "ĠSánchez": 35572, + "ĠâĪİ": 35573, + "AMD": 35574, + "ĠHao": 35575, + "ĠBillboard": 35576, + "ĠAboriginal": 35577, + "Ġnylon": 35578, + "ĠNAN": 35579, + "cores": 35580, + "ĠCrop": 35581, + "Ġcommittees": 35582, + "Ġdihedral": 35583, + "ĠJuli": 35584, + "ĠAndy": 35585, + "hydration": 35586, + "corresponds": 35587, + "Mut": 35588, + "Ġtorn": 35589, + "ĠFEV": 35590, + "Ġxs": 35591, + "amphen": 35592, + "Ġsummarization": 35593, + "ĠErg": 35594, + "ËĨ": 35595, + "ĠJunction": 35596, + "ancouver": 35597, + "ĠExamining": 35598, + "ĠMarco": 35599, + "Pointer": 35600, + "Ġscarcity": 35601, + "uncing": 35602, + "Ġbijective": 35603, + "ĠMaine": 35604, + "ĠRHIC": 35605, + "Ġtowers": 35606, + "Ġgentamicin": 35607, + "Ġtonic": 35608, + "ĠkT": 35609, + "Ġclimbing": 35610, + "Ġrecruits": 35611, + "ĠHotel": 35612, + "ĠJews": 35613, + "ĠRUNX": 35614, + "Ġaustenite": 35615, + "ĠOfficer": 35616, + "inent": 35617, + "ucc": 35618, + "ĠBidirectional": 35619, + "Ġmayor": 35620, + "ĠAssays": 35621, + "ĠERG": 35622, + "SNPs": 35623, + "dine": 35624, + "China": 35625, + "starting": 35626, + "Ġirrational": 35627, + "ĠDIFFE": 35628, + "Ġmilliseconds": 35629, + "Lik": 35630, + "inone": 35631, + "ĠâģĦ": 35632, + "Ġconspicuous": 35633, + "Ġsurplus": 35634, + "ĠXiong": 35635, + "Ġupgrade": 35636, + "Ġtimep": 35637, + "ĠÄĮ": 35638, + "TeV": 35639, + "orbidities": 35640, + "invalid": 35641, + "Ġvide": 35642, + "terra": 35643, + "Ġantin": 35644, + "emens": 35645, + "ocese": 35646, + "ĠKI": 35647, + "Ġevolutionarily": 35648, + "Ker": 35649, + "ĠLES": 35650, + "clamp": 35651, + "Ġslowed": 35652, + "glycoprotein": 35653, + "entieth": 35654, + "Ġabroad": 35655, + "Ġinterpolating": 35656, + "Ġcatalyze": 35657, + "ĠBelgian": 35658, + "Ġphotographed": 35659, + "Ġpector": 35660, + "ĠSIV": 35661, + "ĠELECT": 35662, + "Ġdesal": 35663, + "oneph": 35664, + "ĠClos": 35665, + "Ġaffordable": 35666, + "birds": 35667, + "gom": 35668, + "Ġrr": 35669, + "Ġuni": 35670, + "ĠGenus": 35671, + "ĠRegge": 35672, + "ĠMultidimensional": 35673, + "Ġpsychopathology": 35674, + "Ġcertification": 35675, + "Pattern": 35676, + "ĠTower": 35677, + "Ġstern": 35678, + "Ġsublattice": 35679, + "Ġgrat": 35680, + "Ġlyrics": 35681, + "fmt": 35682, + "oceptive": 35683, + "ĠdP": 35684, + "ĠHolmes": 35685, + "Ġbudgets": 35686, + "Ġeutectic": 35687, + "ĠPv": 35688, + "ĠGott": 35689, + "Ġdisinfection": 35690, + "Ġretinoic": 35691, + "ĠObst": 35692, + "Ġreplen": 35693, + "Ġâĸł": 35694, + "Kutta": 35695, + "Please": 35696, + "ĠCAG": 35697, + "ĠStir": 35698, + "speaking": 35699, + "Ġinsecticides": 35700, + "ĠFungi": 35701, + "Hod": 35702, + "RON": 35703, + "coil": 35704, + "ĠVisible": 35705, + "Ġinception": 35706, + "ĠeGFR": 35707, + "Ġreionization": 35708, + "Ġdomination": 35709, + "ĠMetro": 35710, + "Ġswept": 35711, + "MDS": 35712, + "Ġsubsidence": 35713, + "ĠFalls": 35714, + "ĠDrum": 35715, + "ĠConserved": 35716, + "ĠMyers": 35717, + "Ġadaptability": 35718, + "Ġlyophil": 35719, + "ulina": 35720, + "arelli": 35721, + "ocycles": 35722, + "ĠSOA": 35723, + "ĠdsDNA": 35724, + "ĠCEO": 35725, + "Ġanchoring": 35726, + "Ġdeactivation": 35727, + "yler": 35728, + "Ġinterestingly": 35729, + "Ġiliac": 35730, + "ĠBorg": 35731, + "ĠPTC": 35732, + "ocyanin": 35733, + "Ġunused": 35734, + "ĠCarrier": 35735, + "Which": 35736, + "Ġintervening": 35737, + "Ġprivile": 35738, + "hit": 35739, + "Ġcheaper": 35740, + "ĠCyclin": 35741, + "plying": 35742, + "ĠCleveland": 35743, + "ĠHahn": 35744, + "Ġagglut": 35745, + "ĠAnch": 35746, + "ĠRedox": 35747, + "Will": 35748, + "ĠLinn": 35749, + "rones": 35750, + "ĠNewcastle": 35751, + "ĠExpected": 35752, + "ĠOpportunities": 35753, + "ĠLarger": 35754, + "Ġleach": 35755, + "Ġpepper": 35756, + "Sha": 35757, + "sector": 35758, + "you": 35759, + "Ġreplications": 35760, + "cholesterolem": 35761, + "ĠInvasion": 35762, + "Ġbony": 35763, + "ĠHuber": 35764, + "thend": 35765, + "Ġrealised": 35766, + "Ġinvestments": 35767, + "Cataly": 35768, + "ĠWitt": 35769, + "ĠKai": 35770, + "Ġetched": 35771, + "ĠSTEM": 35772, + "Ġexcludes": 35773, + "Exec": 35774, + "ĠStrongly": 35775, + "ĠSymposium": 35776, + "ĠTuberculosis": 35777, + "ilance": 35778, + "ĠRIS": 35779, + "apia": 35780, + "ensated": 35781, + "neb": 35782, + "ĠChains": 35783, + "Ġenthus": 35784, + "quadrup": 35785, + "decl": 35786, + "Ġbinned": 35787, + "Ġsynergistically": 35788, + "Ġgauges": 35789, + "whether": 35790, + "disease": 35791, + "Western": 35792, + "Ġhypothermia": 35793, + "ĠGardner": 35794, + "Ġaberration": 35795, + "Rod": 35796, + "Íĺ": 35797, + "Ġfd": 35798, + "Ġstood": 35799, + "Ġconditionally": 35800, + "Ġthrombol": 35801, + "PSC": 35802, + "Ġmk": 35803, + "ĠTER": 35804, + "odds": 35805, + "ĠKri": 35806, + "ĠIVF": 35807, + "Ġmites": 35808, + "ĠCHE": 35809, + "Ġqq": 35810, + "ĠInfants": 35811, + "ĠCharlot": 35812, + "becco": 35813, + "etom": 35814, + "ĠCDS": 35815, + "Ġarchaeal": 35816, + "ĠHNSCC": 35817, + "Ġmonodromy": 35818, + "amphenicol": 35819, + "apers": 35820, + "reactivity": 35821, + "Ġunderm": 35822, + "Internal": 35823, + "ĠLandsat": 35824, + "German": 35825, + "Ġcervix": 35826, + "idazole": 35827, + "ĠSketch": 35828, + "ĠLAM": 35829, + "ĠNerve": 35830, + "ĠTeh": 35831, + "Ġmussel": 35832, + "з": 35833, + "ĠMicroarray": 35834, + "wei": 35835, + "Ġwhey": 35836, + "Ġmixer": 35837, + "Ġreconfigurable": 35838, + "Ġvasculitis": 35839, + "Ġkwargs": 35840, + "Ġreus": 35841, + "correlations": 35842, + "Ġwoody": 35843, + "carbonate": 35844, + "ectomized": 35845, + "Ġretrans": 35846, + "Ġcytometric": 35847, + "ĠWildlife": 35848, + "ĠAnswering": 35849, + "Ġpencil": 35850, + "ĠDAS": 35851, + "akrish": 35852, + "CEPT": 35853, + "ĠÄĿ": 35854, + "ĠPersian": 35855, + "converting": 35856, + "Ġcater": 35857, + "Ġmeanwhile": 35858, + "TPA": 35859, + "Ġrum": 35860, + "ĠGros": 35861, + "upe": 35862, + "Ġregurg": 35863, + "Ġpenalties": 35864, + "Positive": 35865, + "****************************************************************************": 35866, + "XF": 35867, + "eenth": 35868, + "ĠCory": 35869, + "odulation": 35870, + "Ġquorum": 35871, + "codes": 35872, + "aram": 35873, + "ĠTSA": 35874, + "ĠPn": 35875, + "âĪij": 35876, + "prison": 35877, + "Ġconfidentiality": 35878, + "EPS": 35879, + "Xiv": 35880, + "iensis": 35881, + "estones": 35882, + "ĠZag": 35883, + "Ġpredecessor": 35884, + "Ġprize": 35885, + "Ġâݨ": 35886, + "steroidal": 35887, + "opard": 35888, + "Ġimpractical": 35889, + "Ġdemonstrations": 35890, + "Ġpredisposition": 35891, + "Ġkk": 35892, + "Ġmodifiers": 35893, + "Ġpreca": 35894, + "Ġexecutes": 35895, + "Ġbinning": 35896, + "Ġpedig": 35897, + "ĠKLF": 35898, + "ĠSkeletal": 35899, + "ĠCIN": 35900, + "atured": 35901, + "Ġdecomposes": 35902, + "Ġaphid": 35903, + "Bern": 35904, + "Pur": 35905, + "ĠEPO": 35906, + "merge": 35907, + "ĠCOSM": 35908, + "amyloid": 35909, + "monia": 35910, + "ĠScores": 35911, + "ĠRegistration": 35912, + "ĠAgrobacterium": 35913, + "Ġenterprises": 35914, + "locality": 35915, + "ĠITO": 35916, + "Ġtess": 35917, + "Ġfcc": 35918, + "ĠNc": 35919, + "Ġcoaxial": 35920, + "ĠAdvant": 35921, + "APC": 35922, + "ĠDemand": 35923, + "adjust": 35924, + "Points": 35925, + "Ġheterostructure": 35926, + "iffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiff": 35927, + "DQ": 35928, + "Ġtensions": 35929, + "abund": 35930, + "ĠHutch": 35931, + "brew": 35932, + "Ġvitreous": 35933, + "ĠEZH": 35934, + "Ġmerc": 35935, + "Ġdebated": 35936, + "Ġpalate": 35937, + "ocolate": 35938, + "Ġevapotranspiration": 35939, + "ĠẼ": 35940, + "ĠHoffman": 35941, + "ĠGALAXIES": 35942, + "CAL": 35943, + "caps": 35944, + "legal": 35945, + "died": 35946, + "ĠIsolates": 35947, + "Ġaggrav": 35948, + "qs": 35949, + "ĠICT": 35950, + "Ġseals": 35951, + "Ġspinel": 35952, + "ĠGeor": 35953, + "Blue": 35954, + "Ġureter": 35955, + "spline": 35956, + "ĠIntroducing": 35957, + "thendieck": 35958, + "opper": 35959, + "Ġafterglow": 35960, + "Ġendosomal": 35961, + "Ġrealizes": 35962, + "solving": 35963, + "Ġmistake": 35964, + "ĠAtheros": 35965, + "ĠSBS": 35966, + "ĠRut": 35967, + "exist": 35968, + "Prof": 35969, + "ĠNeisser": 35970, + "MSG": 35971, + "ĠEarlier": 35972, + "ĠdT": 35973, + "ĠSpread": 35974, + "ĠReflection": 35975, + "secondary": 35976, + "approximate": 35977, + "Ġnigra": 35978, + "Solution": 35979, + "anone": 35980, + "ĠItems": 35981, + "Ġwavelets": 35982, + "ĠSoluble": 35983, + "Ġcircularly": 35984, + "ĠCUDA": 35985, + "Ġregenerated": 35986, + "SPI": 35987, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 35988, + "aturing": 35989, + "REQ": 35990, + "Ġinteroper": 35991, + "reev": 35992, + "ONT": 35993, + "ischen": 35994, + "ĠChoosing": 35995, + "phosphorylated": 35996, + "áĪ": 35997, + "Ġdress": 35998, + "ĠConform": 35999, + "Ġrememb": 36000, + "Ġischaemic": 36001, + "Basic": 36002, + "ĠPang": 36003, + "Ġcrit": 36004, + "ĠOrn": 36005, + "Ġgm": 36006, + "ĠFog": 36007, + "ĠBd": 36008, + "racheal": 36009, + "Ġphenols": 36010, + "ĠDistingu": 36011, + "Ġâİ©": 36012, + "ĠGRBs": 36013, + "ĠCeO": 36014, + "ĠBiomass": 36015, + "Ġaptamer": 36016, + "visc": 36017, + "hetically": 36018, + "Ġsid": 36019, + "omeg": 36020, + "Ġproportionality": 36021, + "ÃŃs": 36022, + "toplasmic": 36023, + "ĠConnected": 36024, + "Ġlaminin": 36025, + "strahlung": 36026, + "ĠLad": 36027, + "TRAN": 36028, + "är": 36029, + "Ġbasalt": 36030, + "ĠCurvature": 36031, + "Ġmitigating": 36032, + "opaedic": 36033, + "ĠMuhammad": 36034, + "CAR": 36035, + "Gi": 36036, + "Ġetch": 36037, + "hair": 36038, + "Ġpurine": 36039, + "Ġbenchmarking": 36040, + "reich": 36041, + "Ġmethicillin": 36042, + "âĪ¥": 36043, + "Ġmanages": 36044, + "solvent": 36045, + "ĠShao": 36046, + "hc": 36047, + "Ġstruck": 36048, + "Ġnucleosome": 36049, + "ĠPublication": 36050, + "Metric": 36051, + "Ġwines": 36052, + "ĠMBL": 36053, + "ĠHub": 36054, + "ĠAssistant": 36055, + "Ġreliance": 36056, + "Ġrouters": 36057, + "ĠHerz": 36058, + "ĠTobacco": 36059, + "rogram": 36060, + "ĠHSD": 36061, + "ĠLBP": 36062, + "Ġinflection": 36063, + "school": 36064, + "Ġsponsored": 36065, + "ĠCenozoic": 36066, + "Ġentertainment": 36067, + "atian": 36068, + "architecture": 36069, + "browse": 36070, + "REC": 36071, + "isture": 36072, + "ĠCholesterol": 36073, + "ĠSimplified": 36074, + "Ġpolypeptides": 36075, + "Ġpunctures": 36076, + "arachnoid": 36077, + "Self": 36078, + "Ġanorexia": 36079, + "ĠOle": 36080, + "ĉĉĠĠĠĠ": 36081, + "GBT": 36082, + "Ġcardiomyocyte": 36083, + "ĠFloquet": 36084, + "analog": 36085, + "Ġsensitized": 36086, + "ĠCephe": 36087, + "catch": 36088, + "chial": 36089, + "Ġceremony": 36090, + "Ġterat": 36091, + "Ġameliorate": 36092, + "olysin": 36093, + "etooth": 36094, + "akin": 36095, + "haem": 36096, + "Ġentropies": 36097, + "Ġargu": 36098, + "Ġcopied": 36099, + "lington": 36100, + "ĠHerpes": 36101, + "ĠSchwann": 36102, + "yk": 36103, + "ĠCEA": 36104, + "ĠICH": 36105, + "Ġwrink": 36106, + "Ġrunners": 36107, + "Ġgalvan": 36108, + "Ġconsolidated": 36109, + "ĠâĢ¡": 36110, + "ĠClassic": 36111, + "Ġepidemiologic": 36112, + "ĠDriving": 36113, + "Ġtrastuzumab": 36114, + "CYP": 36115, + "NCT": 36116, + "tability": 36117, + "Ġslee": 36118, + "ĠNeck": 36119, + "Ġassesses": 36120, + "Ġsymmetrically": 36121, + "ĠPotts": 36122, + "ĠRibosomal": 36123, + "diction": 36124, + "gall": 36125, + "ĠAcceleration": 36126, + "CLA": 36127, + "ACTER": 36128, + "xed": 36129, + "Ġgeriatric": 36130, + "threonine": 36131, + "Ġabort": 36132, + "Ġartem": 36133, + "ĠDisney": 36134, + "ĠCorrespondence": 36135, + "Ġrent": 36136, + "ĠNUM": 36137, + "ĠChun": 36138, + "ĠRecogn": 36139, + "Ġcrystallized": 36140, + "Ġcontradicting": 36141, + "visors": 36142, + "malignant": 36143, + "rophysiology": 36144, + "Infrared": 36145, + "gz": 36146, + "Ġsublim": 36147, + "omatosis": 36148, + "osyltransferase": 36149, + "Ġholography": 36150, + "orenstein": 36151, + "¾±": 36152, + "ĠSebas": 36153, + "accum": 36154, + "Upper": 36155, + "antenna": 36156, + "Ġblur": 36157, + "Ġsmell": 36158, + "Ġthreefold": 36159, + "ĠPlayers": 36160, + "Ġalleviated": 36161, + "Bin": 36162, + "Ġninet": 36163, + "ĠDna": 36164, + "Ġgeneralizing": 36165, + "Ġbreakage": 36166, + "ĠMorrison": 36167, + "macro": 36168, + "Reader": 36169, + "ogravimetric": 36170, + "Ġdh": 36171, + "lew": 36172, + "xton": 36173, + "Ġdeceleration": 36174, + "ĠCorrelated": 36175, + "ĠLegion": 36176, + "Ġgambling": 36177, + "Binding": 36178, + "ĠInAs": 36179, + "lowering": 36180, + "Ġeuthanized": 36181, + "ĠDallas": 36182, + "ĠDw": 36183, + "ĠDijk": 36184, + "ĠPolic": 36185, + "ĠTIME": 36186, + "ĠHEL": 36187, + "ĠLanguages": 36188, + "Ġparabol": 36189, + "porating": 36190, + "Ġfrustration": 36191, + "μM": 36192, + "balls": 36193, + "ĠArmstrong": 36194, + "Ġcontractility": 36195, + "Ġmetalloproteinases": 36196, + "americ": 36197, + "ĠZak": 36198, + "ĠCosts": 36199, + "Alex": 36200, + "dog": 36201, + "pw": 36202, + "ĠTight": 36203, + "ĠAnterior": 36204, + "Ġpeaking": 36205, + "Ġnegativity": 36206, + "Ġhydride": 36207, + "ĠLiv": 36208, + "Ġsterilized": 36209, + "Ġverbatim": 36210, + "Alternatively": 36211, + "REQU": 36212, + "ĠTyphimurium": 36213, + "ĠWeinberg": 36214, + "DSC": 36215, + "rq": 36216, + "Ġcorrug": 36217, + "Ġmicrons": 36218, + "coord": 36219, + "ioid": 36220, + "sat": 36221, + "Ġflocc": 36222, + "ĠAccelerated": 36223, + "Ġsixteen": 36224, + "absence": 36225, + "ĠSpeaker": 36226, + "omological": 36227, + "ĠApr": 36228, + "Ġmatroid": 36229, + "tight": 36230, + "ogenetically": 36231, + "rump": 36232, + "ĠInhibits": 36233, + "ĠOlympus": 36234, + "Ġpossession": 36235, + "Ġsupervisor": 36236, + "Ġconcise": 36237, + "optimized": 36238, + "vivo": 36239, + "Ġstepped": 36240, + "ocyanine": 36241, + "Five": 36242, + "anas": 36243, + "arten": 36244, + "ĠCaco": 36245, + "Ġsolutes": 36246, + "ITAL": 36247, + "ĠReddy": 36248, + "Ġwarping": 36249, + "Ġoligomer": 36250, + "Ġcapped": 36251, + "Ġvoted": 36252, + "ĠRico": 36253, + "ĠTrem": 36254, + "Ġlime": 36255, + "ĠISP": 36256, + "ĠLayers": 36257, + "skin": 36258, + "ranged": 36259, + "áz": 36260, + "Ġbioactivity": 36261, + "Ġdurable": 36262, + "Ġhn": 36263, + "ĠCAB": 36264, + "Ġva": 36265, + "ĠUWB": 36266, + "ĠStuart": 36267, + "Ġlengthy": 36268, + "Ġinvasiveness": 36269, + "ĠâĩĶ": 36270, + "joining": 36271, + "ĠRBCs": 36272, + "Ġresilient": 36273, + "ĠManipulation": 36274, + "Germ": 36275, + "contribution": 36276, + "Ġqualify": 36277, + "ĠDashed": 36278, + "Ġaccelerations": 36279, + "ĠCytochrome": 36280, + "Ġcircumstellar": 36281, + "cavity": 36282, + "Ġanatase": 36283, + "ĠDevi": 36284, + "Ġpursu": 36285, + "ĠMicroRNAs": 36286, + "Ġnorthward": 36287, + "Ġsunflower": 36288, + "ĠEntertainment": 36289, + "Pacific": 36290, + "ĠHolographic": 36291, + "uj": 36292, + "erell": 36293, + "methanol": 36294, + "Surface": 36295, + "opositive": 36296, + "Ġthreatening": 36297, + "Ġtranscend": 36298, + "Depend": 36299, + "Ġqi": 36300, + "tised": 36301, + "ĠBristol": 36302, + "ummation": 36303, + "Ġextractor": 36304, + "Ġfavoured": 36305, + "ĠPyro": 36306, + "ĠEngineers": 36307, + "flatten": 36308, + "tolerance": 36309, + "Ġxt": 36310, + "ĠTot": 36311, + "Ġtestbed": 36312, + "ICU": 36313, + "ĠSwarm": 36314, + "Ġinternationally": 36315, + "Ġantine": 36316, + "ĠInsurance": 36317, + "bai": 36318, + "nh": 36319, + "Ñĭ": 36320, + "osac": 36321, + "ĠLec": 36322, + "thor": 36323, + "Ġoutermost": 36324, + "Ġdoors": 36325, + "Ġbiometric": 36326, + "glutamate": 36327, + "ĠWoods": 36328, + "ĠMunich": 36329, + "uximab": 36330, + "places": 36331, + "Ġamyotrophic": 36332, + "ĠParam": 36333, + "ĠChristensen": 36334, + "Age": 36335, + "enne": 36336, + "Ġanim": 36337, + "Ġrecrystallization": 36338, + "ĠPropositions": 36339, + "Ġsnails": 36340, + "Secondly": 36341, + "ĠPUFA": 36342, + "France": 36343, + "Src": 36344, + "vitro": 36345, + "omass": 36346, + "uru": 36347, + "ĠLever": 36348, + "ectonic": 36349, + "embl": 36350, + "PCL": 36351, + "Ġcoordinator": 36352, + "ĠFoxp": 36353, + "ĠBirmingham": 36354, + "ĠLiberal": 36355, + "Ġcruise": 36356, + "Ġiθ": 36357, + "Ġsymp": 36358, + "azaki": 36359, + "ĠParse": 36360, + "Ġhydrologic": 36361, + "Ġprolongation": 36362, + "ĠHayes": 36363, + "Ġsubmuc": 36364, + "Ġagglomeration": 36365, + "ARE": 36366, + "ĠFMR": 36367, + "ĠLomb": 36368, + "mathchar": 36369, + "Ġstructuring": 36370, + "Ġelectrophoretic": 36371, + "Ġdiminishing": 36372, + "Ġbrake": 36373, + "chenko": 36374, + "ĠPereira": 36375, + "lens": 36376, + "Ġbackend": 36377, + "Ġillustrations": 36378, + "Ġdemanded": 36379, + "Ġnoticeably": 36380, + "ĠKaiser": 36381, + "ĠDavidson": 36382, + "Ġbraking": 36383, + "Tp": 36384, + "Forward": 36385, + "μν": 36386, + "ĠCdS": 36387, + "Ġasteroids": 36388, + "Provider": 36389, + "ĠEut": 36390, + "Ġtril": 36391, + "ungs": 36392, + "Ġdiving": 36393, + "ĠUAVs": 36394, + "ĠiPSC": 36395, + "iint": 36396, + "Ġ×": 36397, + "thrombin": 36398, + "Ġcoordinating": 36399, + "extrem": 36400, + "Ġembolization": 36401, + "ĠAdip": 36402, + "plated": 36403, + "ĠHag": 36404, + "ĠETS": 36405, + "Ġbrood": 36406, + "Ang": 36407, + "ĠPCV": 36408, + "detail": 36409, + "RSS": 36410, + "bens": 36411, + "Ġtier": 36412, + "ĠCock": 36413, + "Ġgay": 36414, + "Ġquint": 36415, + "Ġagenda": 36416, + "Ġaffairs": 36417, + "ĠModerate": 36418, + "helical": 36419, + "ĠEquivalent": 36420, + "Ġproportionally": 36421, + "Column": 36422, + "FWHM": 36423, + "Air": 36424, + "Enum": 36425, + "ifice": 36426, + "arcsec": 36427, + "ĠTRIM": 36428, + "ĠLabeling": 36429, + "QAM": 36430, + "pies": 36431, + "Ġisotropy": 36432, + "ĠGó": 36433, + "Ġpointers": 36434, + "tigraphy": 36435, + "ramers": 36436, + "Ġmacaque": 36437, + "Ġmisses": 36438, + "Ġellipticity": 36439, + "presented": 36440, + "galactosidase": 36441, + "ÉĽ": 36442, + "inion": 36443, + "Ġmite": 36444, + "lll": 36445, + "Objective": 36446, + "Ġprisoners": 36447, + "ĠHercules": 36448, + "Ġantis": 36449, + "Ġclosures": 36450, + "ĠMartian": 36451, + "Ġterpen": 36452, + "robust": 36453, + "Ġsequelae": 36454, + "alarial": 36455, + "ĠCSA": 36456, + "ĠBland": 36457, + "ĠGent": 36458, + "Ġorphan": 36459, + "Ġindent": 36460, + "bigwedge": 36461, + "Ġdefinable": 36462, + "Ġoligosaccharides": 36463, + "ĠBattalion": 36464, + "Ġisometries": 36465, + "azolin": 36466, + "ĠShown": 36467, + "spectra": 36468, + "Visual": 36469, + "<<<<<<<<": 36470, + "Ġlentiviral": 36471, + "othelioma": 36472, + "Ġtedious": 36473, + "ĠBCI": 36474, + "Ġgeologic": 36475, + "Ġconsumes": 36476, + "ĠAblation": 36477, + "least": 36478, + "Ġthigh": 36479, + "Ġsecrecy": 36480, + "covering": 36481, + "eiro": 36482, + "õ": 36483, + "ĠTBS": 36484, + "Ġisomerase": 36485, + "Ġrecommends": 36486, + "ĠVortex": 36487, + "ĠBray": 36488, + "Ġsubd": 36489, + "ĠOptions": 36490, + "Ġmetamaterial": 36491, + "ĠSquares": 36492, + "trap": 36493, + "imon": 36494, + "Ġhesit": 36495, + "Ġabc": 36496, + "cessing": 36497, + "ĠRET": 36498, + "Ġpinned": 36499, + "Ġketones": 36500, + "Ġwelded": 36501, + "ĠMitochondria": 36502, + "Ġingested": 36503, + "ĠQFT": 36504, + "Ġcomparator": 36505, + "Ġoxidoreductase": 36506, + "Ġtetrad": 36507, + "ĠSensitive": 36508, + "Ġcatchments": 36509, + "Ġrefugees": 36510, + "Ġpuberty": 36511, + "Arab": 36512, + "Ġinterannual": 36513, + "scattered": 36514, + "ĠMetam": 36515, + "Ġcyclization": 36516, + "pertures": 36517, + "ĠLINC": 36518, + "rules": 36519, + "ĠPont": 36520, + "PTH": 36521, + "ĉĉĉĉĉĉĉĉ": 36522, + "Santa": 36523, + "ĠLNC": 36524, + "Ġsubmodular": 36525, + "rective": 36526, + "Ġtrif": 36527, + "Ġsentinel": 36528, + "ĠTwin": 36529, + "keletons": 36530, + "miral": 36531, + "aming": 36532, + "ĠGay": 36533, + "Ġinterspecific": 36534, + "Ġrelieve": 36535, + "Ġendomorphism": 36536, + "ĠExpanding": 36537, + "ĠRuntime": 36538, + "yang": 36539, + "requires": 36540, + "odine": 36541, + "ometabolic": 36542, + "Store": 36543, + "planet": 36544, + "Ġrenov": 36545, + "___": 36546, + "adenosine": 36547, + "uitive": 36548, + "Ġkel": 36549, + "ĠProlong": 36550, + "ĠAdvance": 36551, + "Ġantimicrobials": 36552, + "ĠMunicipal": 36553, + "ĠNeutrophil": 36554, + "FAs": 36555, + "ĠFame": 36556, + "ibus": 36557, + "ETE": 36558, + "Ġstepping": 36559, + "ĠBlot": 36560, + "ĠLaura": 36561, + "Ġrocky": 36562, + "ĠLima": 36563, + "Ġmitigated": 36564, + "ĠLambert": 36565, + "Ġunexplored": 36566, + "Ġtrigonometric": 36567, + "pig": 36568, + "ĠHeli": 36569, + "Ġfinely": 36570, + "Ġoxidizing": 36571, + "Ġcolonoscopy": 36572, + "activities": 36573, + "ĠEasy": 36574, + "Ġunexplained": 36575, + "aky": 36576, + "ASM": 36577, + "worker": 36578, + "ĠCrist": 36579, + "ãĢģ": 36580, + "ulk": 36581, + "ĠSugg": 36582, + "ĠMim": 36583, + "Ġiterates": 36584, + "Ġsulfoxide": 36585, + "glucan": 36586, + "Ġreactant": 36587, + "Ġphagocytic": 36588, + "Brain": 36589, + "ucted": 36590, + "ĠScand": 36591, + "ĠCaCO": 36592, + "Ġaffiliation": 36593, + "Policy": 36594, + "ĠInfantry": 36595, + "Functional": 36596, + "rtimes": 36597, + "Ġwond": 36598, + "ardment": 36599, + "ĠWeil": 36600, + "Ġdirectors": 36601, + "uffix": 36602, + "ĠRuiz": 36603, + "ĠPhenomena": 36604, + "Ġmicrob": 36605, + "cosm": 36606, + "Ġutilisation": 36607, + "persed": 36608, + "Ġconsole": 36609, + "ticulate": 36610, + "Ġdesens": 36611, + "Ġreplicas": 36612, + "Ġpluripotency": 36613, + "ĠUkrainian": 36614, + "Ġhydrolyzed": 36615, + "ĠBiodiversity": 36616, + "Efficient": 36617, + "ĠKash": 36618, + "minor": 36619, + "Ġconclusive": 36620, + "Ġtentative": 36621, + "jira": 36622, + "Ġmb": 36623, + "ĠIPA": 36624, + "ĠPis": 36625, + "Ġgoverns": 36626, + "ĠSouthwest": 36627, + "oeba": 36628, + "ĠMohammad": 36629, + "albumin": 36630, + "circles": 36631, + "ĠHedge": 36632, + "ĠAmph": 36633, + "BACK": 36634, + "Old": 36635, + "histor": 36636, + "acular": 36637, + "ĠNOR": 36638, + "henius": 36639, + "visions": 36640, + "missibility": 36641, + "Ġthromboembolism": 36642, + "atized": 36643, + "Ġwil": 36644, + "awing": 36645, + "ASI": 36646, + "Ġheterodimer": 36647, + "Ġbuffering": 36648, + "ĠIdeally": 36649, + "ĠEgg": 36650, + "ographies": 36651, + "ĠAppl": 36652, + "ĠCIs": 36653, + "meaning": 36654, + "ĠSMAD": 36655, + "Ġphenylalanine": 36656, + "ĠTitanium": 36657, + "ĠZariski": 36658, + "Ġnymph": 36659, + "Ġhired": 36660, + "ĠPPC": 36661, + "ĠKG": 36662, + "ĠGuill": 36663, + "oglycans": 36664, + "erial": 36665, + "Dele": 36666, + "ilus": 36667, + "ĠFitness": 36668, + "Ġwhales": 36669, + "grant": 36670, + "mostly": 36671, + "Ġclimates": 36672, + "ĠCampaign": 36673, + "MgO": 36674, + "Ġepistemic": 36675, + "Lipschitz": 36676, + "ĠLAT": 36677, + "Ġcladding": 36678, + "vacuum": 36679, + "agglutinin": 36680, + "kill": 36681, + "Ġsail": 36682, + "Ġartistic": 36683, + "answ": 36684, + "ĠSDF": 36685, + "ĠKeith": 36686, + "Ġsorafenib": 36687, + "Ġgallbladder": 36688, + "directory": 36689, + "Ġphotoreceptors": 36690, + "ĠFokker": 36691, + "DU": 36692, + "Ġeditors": 36693, + "Ġtelecommun": 36694, + "ardia": 36695, + "ĠPublications": 36696, + "Ġscrews": 36697, + "ĠMathematica": 36698, + "RSV": 36699, + "ĠApply": 36700, + "ĠSTS": 36701, + "ĠMurine": 36702, + "Ġdump": 36703, + "Ġlingu": 36704, + "ĠDixon": 36705, + "Ġovercomes": 36706, + "ĠPreoperative": 36707, + "Ġmigrant": 36708, + "Ġbelieves": 36709, + "BK": 36710, + "actively": 36711, + "ĠISC": 36712, + "quas": 36713, + "Ġalga": 36714, + "ichael": 36715, + "Ġdisasters": 36716, + "Ġpracticed": 36717, + "hydrophobic": 36718, + "ĠNiño": 36719, + "ĠEthanol": 36720, + "QE": 36721, + "ĠSJ": 36722, + "ĠDengue": 36723, + "Ġappl": 36724, + "ĠYoon": 36725, + "enzo": 36726, + "IFY": 36727, + "Ġchronological": 36728, + "erin": 36729, + "ĠPeg": 36730, + "ĠRelevant": 36731, + "Ġqualification": 36732, + "evine": 36733, + "Ġdendrite": 36734, + "DTD": 36735, + "cholinesterase": 36736, + "watch": 36737, + "ĠSanchez": 36738, + "Ġwashes": 36739, + "Ġpermafrost": 36740, + "ĠTertiary": 36741, + "Ġsynthesizing": 36742, + "Ġexpedition": 36743, + "routine": 36744, + "ĠSearching": 36745, + "ĠSé": 36746, + "residual": 36747, + "ĠLCD": 36748, + "entities": 36749, + "Ġendovascular": 36750, + "Ġparamount": 36751, + "pher": 36752, + "Ġstraightforwardly": 36753, + "Ġvasodil": 36754, + "ĠSchistosoma": 36755, + "Ġpermissions": 36756, + "centred": 36757, + "Ġfrustrated": 36758, + "structuring": 36759, + "ĠSchl": 36760, + "ĠInitiation": 36761, + "Ġcuticle": 36762, + "Ġforgetting": 36763, + "ĠSas": 36764, + "ĠSult": 36765, + "uno": 36766, + "Ġdisintegration": 36767, + "ĠVG": 36768, + "Ġwards": 36769, + "ĠIRE": 36770, + "upro": 36771, + "Ġsubgen": 36772, + "Ġsubclasses": 36773, + "ĠStand": 36774, + "ĠHeight": 36775, + "interpretation": 36776, + "Ġglycan": 36777, + "ĠSolvent": 36778, + "ĠMalignant": 36779, + "Ġunsuitable": 36780, + "ĠCoxeter": 36781, + "Ġspermatogenesis": 36782, + "Ġfullerene": 36783, + "Fox": 36784, + "SOC": 36785, + "wet": 36786, + "armstadt": 36787, + "Ġpropofol": 36788, + "indexed": 36789, + "Ġsnakes": 36790, + "Edit": 36791, + "ĠmJy": 36792, + "RIB": 36793, + "Ġey": 36794, + "ĠAlkal": 36795, + "Ġtriaxial": 36796, + "PSK": 36797, + "neo": 36798, + "Ġendo": 36799, + "Ġglycosides": 36800, + "Ġsyllables": 36801, + "Ġsorghum": 36802, + "loor": 36803, + "Ġgeothermal": 36804, + "guinal": 36805, + "ĠSerbia": 36806, + "æĸ": 36807, + "ĠSentinel": 36808, + "ighters": 36809, + "Ġkeyboard": 36810, + "Ġbanana": 36811, + "granular": 36812, + "Ġdeciduous": 36813, + "ĠHAR": 36814, + "neuron": 36815, + "ĠCarn": 36816, + "Ġburns": 36817, + "Boost": 36818, + "ĠDeterministic": 36819, + "pipe": 36820, + "ĠFAD": 36821, + "ĠBovine": 36822, + "ĠRou": 36823, + "Ġkan": 36824, + "autonomous": 36825, + "utrients": 36826, + "Ġhypothyroidism": 36827, + "ĠSINR": 36828, + "stret": 36829, + "Ġunaltered": 36830, + "ĠZika": 36831, + "valley": 36832, + "Ġlongitudinally": 36833, + "Ġfluorescein": 36834, + "catheter": 36835, + "ĠCongenital": 36836, + "Ġpiez": 36837, + "Ġabbreviated": 36838, + "ĠChlamydia": 36839, + "Ġaired": 36840, + "Ġqueen": 36841, + "Ġinstructive": 36842, + "Ġabruptly": 36843, + "Ġrecurrences": 36844, + "IMP": 36845, + "Ġexosome": 36846, + "ĠHSCs": 36847, + "Writer": 36848, + "elis": 36849, + "ĠArithmetic": 36850, + "enarios": 36851, + "Ġligated": 36852, + "ĠLocalized": 36853, + "ĠFreeman": 36854, + "Ġcarniv": 36855, + "ĠCereb": 36856, + "Ġgrac": 36857, + "ĠGond": 36858, + "ĠVancouver": 36859, + "obox": 36860, + "Ġtyped": 36861, + "ĠÄ¥": 36862, + "Upon": 36863, + "Future": 36864, + "ENG": 36865, + "dead": 36866, + "Ġserpent": 36867, + "ĠAssignment": 36868, + "ĠUpdated": 36869, + "Ġhistorian": 36870, + "Ġtropospheric": 36871, + "Cloud": 36872, + "bumin": 36873, + "ĠPras": 36874, + "ĠBasket": 36875, + "ĠâĪĴâĪĴ": 36876, + "benzodi": 36877, + "ĠTrauma": 36878, + "ĠBehaviors": 36879, + "Ġpter": 36880, + "irradiation": 36881, + "Ġspoke": 36882, + "ariatric": 36883, + "Ġplugin": 36884, + "Ġsupersonic": 36885, + "Ġdocetaxel": 36886, + "itigation": 36887, + "Ġdigestibility": 36888, + "nem": 36889, + "Ġpb": 36890, + "ĠCSR": 36891, + "Ġfouling": 36892, + "Ġrheology": 36893, + "Ġfloods": 36894, + "Ġgluing": 36895, + "agascar": 36896, + "jets": 36897, + "pti": 36898, + "eston": 36899, + "ĠKü": 36900, + "Ġopenings": 36901, + "Ġisolating": 36902, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 36903, + "Ġsemiconducting": 36904, + "rative": 36905, + "ecology": 36906, + "urization": 36907, + "Ġmultifactorial": 36908, + "shadow": 36909, + "Ġcrosslinked": 36910, + "Ġphyla": 36911, + "Ġpremises": 36912, + "ĠLOW": 36913, + "generalized": 36914, + "ĠPolynomials": 36915, + "Ġbismuth": 36916, + "ĠRoz": 36917, + "ĠDecoding": 36918, + "ĠClassifier": 36919, + "conducting": 36920, + "Ġlitterm": 36921, + "Mann": 36922, + "Ġfant": 36923, + "ĠCZ": 36924, + "ĠPSNR": 36925, + "Ġstarring": 36926, + "ĠPolyg": 36927, + "ĠHolm": 36928, + "rg": 36929, + "additional": 36930, + "guan": 36931, + "professional": 36932, + "Ġinquiry": 36933, + "ĠPg": 36934, + "ĠSchmid": 36935, + "Ġheaded": 36936, + "chaft": 36937, + "ĠExpand": 36938, + "Ġcompanions": 36939, + "Van": 36940, + "ĠSie": 36941, + "Ġcanals": 36942, + "oredoxin": 36943, + "Ġcolliding": 36944, + "absolute": 36945, + "ĠPhotos": 36946, + "ĠLegacy": 36947, + "Ġrevascularization": 36948, + "ĠPSM": 36949, + "Ġexpenses": 36950, + "ISMA": 36951, + "intervals": 36952, + "Ġmulticellular": 36953, + "Ġnonsm": 36954, + "Ġresemblance": 36955, + "Hep": 36956, + "Ġwool": 36957, + "Ġniger": 36958, + "essa": 36959, + "asci": 36960, + "Ġrotates": 36961, + "Ġcompetitions": 36962, + "Ġarrivals": 36963, + "Ġlutein": 36964, + "Ġscholarship": 36965, + "Fran": 36966, + "Ġreused": 36967, + "ĠEquivalence": 36968, + "ĠGLUT": 36969, + "grading": 36970, + "salt": 36971, + "Ġcommensal": 36972, + "Ġfraud": 36973, + "oxib": 36974, + "Ġgastroenter": 36975, + "Ġrainy": 36976, + "Ġasserts": 36977, + "Operation": 36978, + "Ġflattening": 36979, + "Put": 36980, + "XB": 36981, + "ĠpM": 36982, + "Ġconic": 36983, + "obtain": 36984, + "ĠRober": 36985, + "November": 36986, + "ĠJP": 36987, + "Ġfebrile": 36988, + "ĠBarriers": 36989, + "================================================================": 36990, + "Ġhemicell": 36991, + "ĠSCS": 36992, + "ĠNem": 36993, + "Ġraster": 36994, + "clude": 36995, + "Ġïģ¦": 36996, + "ĠElliott": 36997, + "border": 36998, + "ĠdÏĨ": 36999, + "ribose": 37000, + "ĠEnv": 37001, + "ĠDiffuse": 37002, + "ĠSupersymmetry": 37003, + "Pearson": 37004, + "FETs": 37005, + "yah": 37006, + "ulia": 37007, + "ĠDwarf": 37008, + "ĠHull": 37009, + "ĠAttribution": 37010, + "Ġrepositories": 37011, + "ĠGNSS": 37012, + "ĠVectors": 37013, + "Ġsuccesses": 37014, + "ĠManhattan": 37015, + "umbent": 37016, + "digit": 37017, + "Ġcircumferential": 37018, + "Between": 37019, + "Deg": 37020, + "oue": 37021, + "й": 37022, + "ĠDere": 37023, + "ĠRf": 37024, + "Ġride": 37025, + "ĠVoc": 37026, + "Ġprotest": 37027, + "Ġpurpos": 37028, + "ĠProofs": 37029, + "namese": 37030, + "Ġbanking": 37031, + "ĠGastrointestinal": 37032, + "ĠUnt": 37033, + "Ġwhence": 37034, + "ĠYue": 37035, + "ĠRehabilitation": 37036, + "Ġexchanging": 37037, + "ĠACTH": 37038, + "Ġcapping": 37039, + "amido": 37040, + "ĠBap": 37041, + "Ġplat": 37042, + "toString": 37043, + "Ġelectroencephal": 37044, + "Ġelectrospun": 37045, + "Mpc": 37046, + "jord": 37047, + "onv": 37048, + "Ġcraft": 37049, + "ĠCCl": 37050, + "ĠStrip": 37051, + "Ġmeditation": 37052, + "oxidative": 37053, + "ĠReduce": 37054, + "ĠCommonwealth": 37055, + "Ġrifamp": 37056, + "Flu": 37057, + "Ġreanalysis": 37058, + "otrich": 37059, + "ĠESA": 37060, + "Ġjth": 37061, + "helin": 37062, + "ĠGenotype": 37063, + "Ġdiagonalization": 37064, + "ĠGabriel": 37065, + "Ġquarantine": 37066, + "ĠCrab": 37067, + "ĠDict": 37068, + "accumulation": 37069, + "bek": 37070, + "ĠDifferentially": 37071, + "Ġlactis": 37072, + "tetrahydrofuran": 37073, + "laser": 37074, + "ĠUm": 37075, + "Ġmega": 37076, + "rme": 37077, + "ĠIndians": 37078, + "ĠLeonard": 37079, + "Ġcommodity": 37080, + "Ġfumigatus": 37081, + "iou": 37082, + "ĠEchin": 37083, + "ostream": 37084, + "Ġmembran": 37085, + "simulations": 37086, + "backend": 37087, + "ĠOBJECT": 37088, + "giving": 37089, + "ÅĻ": 37090, + "Ġinfective": 37091, + "Alg": 37092, + "ĠHuh": 37093, + "ĠMICR": 37094, + "Ġfollowers": 37095, + "ferro": 37096, + "Ġcyanide": 37097, + "Present": 37098, + "ĠEND": 37099, + "ĠMCs": 37100, + "Ġtimeline": 37101, + "ĠEmbryonic": 37102, + "Identifier": 37103, + "Ġinconclusive": 37104, + "ĠGammaproteobacteria": 37105, + "nets": 37106, + "ĠHeating": 37107, + "ankar": 37108, + "thr": 37109, + "ĠKIT": 37110, + "ĠChip": 37111, + "Ġblob": 37112, + "Ġcalculator": 37113, + "Ġtextural": 37114, + "Ġalloying": 37115, + "Application": 37116, + "ĠProteomic": 37117, + "Ġantidepressants": 37118, + "urk": 37119, + "Ġcrystallography": 37120, + "Ġcredits": 37121, + "Ġmussels": 37122, + "Tom": 37123, + "ĠFST": 37124, + "ĠFold": 37125, + "ĠHew": 37126, + "Ann": 37127, + "brook": 37128, + "Ġglycolytic": 37129, + "Torch": 37130, + "Ġvm": 37131, + "ĠMare": 37132, + "ĠJy": 37133, + "Ġheterojunction": 37134, + "ĠBorrelia": 37135, + "Risk": 37136, + "ĠNaturally": 37137, + "Ġsupplying": 37138, + "signature": 37139, + "lk": 37140, + "Ġarachid": 37141, + "olov": 37142, + "ĠSok": 37143, + "ĠHö": 37144, + "ĠRaz": 37145, + "ĠVander": 37146, + "Ġdelib": 37147, + "Ġmyth": 37148, + "Ġmidbrain": 37149, + "Ġdeceased": 37150, + "ĠSCO": 37151, + "ĠThromb": 37152, + "Ġcurr": 37153, + "Ġsummit": 37154, + "miRNAs": 37155, + "dimethylamino": 37156, + "Ġphotocatalyst": 37157, + "verbose": 37158, + "gomery": 37159, + "Ġwed": 37160, + "ĠMate": 37161, + "Ġsigni": 37162, + "rastructures": 37163, + "Ġreciprocity": 37164, + "bner": 37165, + "mast": 37166, + "neck": 37167, + "Ġcoins": 37168, + "ĠHistogram": 37169, + "crit": 37170, + "Bbbk": 37171, + "AW": 37172, + "town": 37173, + "displacement": 37174, + "ĠNeph": 37175, + "separable": 37176, + "Ġdiastere": 37177, + "ĠMODELS": 37178, + "Depth": 37179, + "ĠNeisseria": 37180, + "pdev": 37181, + "uvial": 37182, + "ĠBMS": 37183, + "ĠDennis": 37184, + "Ġrp": 37185, + "Ġnanometer": 37186, + "rocyt": 37187, + "ĠRomanian": 37188, + "Ġconceivable": 37189, + "COS": 37190, + "alveolar": 37191, + "astig": 37192, + "abwe": 37193, + "encode": 37194, + "rolactone": 37195, + "Ġreadmission": 37196, + "intersection": 37197, + "Ġamplicons": 37198, + "timulated": 37199, + "Ġcollapses": 37200, + "ochromatin": 37201, + "Haw": 37202, + "ectrum": 37203, + "ftype": 37204, + "rica": 37205, + "Ġamid": 37206, + "MPO": 37207, + "ĠExtensions": 37208, + "Ġvaric": 37209, + "Ġdiminishes": 37210, + "Ġcatheters": 37211, + "Nodes": 37212, + "Ġbbox": 37213, + "emination": 37214, + "Ġtsunami": 37215, + "diagnosis": 37216, + "cod": 37217, + "qr": 37218, + "ĠFen": 37219, + "Ġworthy": 37220, + "ĠâĩIJ": 37221, + "informatic": 37222, + "ographer": 37223, + "Ġundetected": 37224, + "ĠNCAA": 37225, + "Ġcarcinogenic": 37226, + "RU": 37227, + "Ġaneu": 37228, + "plitudes": 37229, + "keeper": 37230, + "ĠÄģ": 37231, + "Ġautistic": 37232, + "Ġcompromising": 37233, + "Ġunimodal": 37234, + "Ġrumin": 37235, + "apa": 37236, + "Ġintolerance": 37237, + "Ġdirecting": 37238, + "Ġpea": 37239, + "Ġcommenced": 37240, + "Ġshadowing": 37241, + "Center": 37242, + "Ġclad": 37243, + "Ġblues": 37244, + "binits": 37245, + "Ġmisclassification": 37246, + "ĠFAST": 37247, + "Wat": 37248, + "ĠmCherry": 37249, + "Ġbrig": 37250, + "estradiol": 37251, + "Ġwavefunctions": 37252, + "Ġblooms": 37253, + "Ġaccent": 37254, + "aji": 37255, + "occurring": 37256, + "arrest": 37257, + "Ġspecialty": 37258, + "Ġunconditional": 37259, + "Ġsponges": 37260, + "Ġdysfunctional": 37261, + "ĠNOX": 37262, + "Ġultracold": 37263, + "Ġmartensite": 37264, + "OUS": 37265, + "nier": 37266, + "isic": 37267, + "ĠMatsum": 37268, + "Ġleukemic": 37269, + "ĠBradley": 37270, + "Density": 37271, + "ĠSemiconductor": 37272, + "ĠCause": 37273, + "ĠInset": 37274, + "ĠKem": 37275, + "ĠUPR": 37276, + "para": 37277, + "echst": 37278, + "ymet": 37279, + "Ġagro": 37280, + "ĠYY": 37281, + "ĠRegeneration": 37282, + "Ġancestors": 37283, + "ĠTissues": 37284, + "Ġsulfuric": 37285, + "kd": 37286, + "Ġlasing": 37287, + "ĠPup": 37288, + "aei": 37289, + "Ġmammal": 37290, + "ĠBradford": 37291, + "Ġsegregated": 37292, + "isolated": 37293, + "ĠCuba": 37294, + "Ġblockage": 37295, + "Ġseamless": 37296, + "Ġperoxisome": 37297, + "hui": 37298, + "Ġinaug": 37299, + "Ġinfecting": 37300, + "ĠChampion": 37301, + "ĠAttitudes": 37302, + "calculate": 37303, + "Ġtighter": 37304, + "ĠSAC": 37305, + "ĠEpi": 37306, + "Ġatm": 37307, + "Ġphysico": 37308, + "Ġnth": 37309, + "ĠCanyon": 37310, + "Ġseroprevalence": 37311, + "Ġhomo": 37312, + "ĠUniversit": 37313, + "Evaluation": 37314, + "ĠAPOE": 37315, + "job": 37316, + "ĠmK": 37317, + "Ġreign": 37318, + "abo": 37319, + "ĠRugby": 37320, + "ĠNets": 37321, + "Ġrituximab": 37322, + "ativeness": 37323, + "Ġphy": 37324, + "ornis": 37325, + "Ġfeedbacks": 37326, + "United": 37327, + "Princ": 37328, + "imbabwe": 37329, + "ĠGirls": 37330, + "Ġunavoidable": 37331, + "ĠSemantics": 37332, + "Break": 37333, + "FISH": 37334, + "Mix": 37335, + "Ġnx": 37336, + "ĠBao": 37337, + "dimethylphenyl": 37338, + "ĠTOF": 37339, + "ĠCrown": 37340, + "ĠGGA": 37341, + "ĠJH": 37342, + "Ġsuperstring": 37343, + "ĠCRY": 37344, + "Ġkindly": 37345, + "YN": 37346, + "Ġundoped": 37347, + "excluding": 37348, + "ĠLeo": 37349, + "ĠPROPERT": 37350, + "peritoneally": 37351, + "mant": 37352, + "ê°": 37353, + "Ġfranch": 37354, + "ĠProst": 37355, + "DEs": 37356, + "Ġcotrans": 37357, + "Ġrk": 37358, + "Ġgeneralizability": 37359, + "Author": 37360, + "ĠAndrea": 37361, + "ĠConfocal": 37362, + "ĠAdipose": 37363, + "îĹ": 37364, + "erjee": 37365, + "Ġanimated": 37366, + "ĠFad": 37367, + "ĠCorrosion": 37368, + "ĠCircadian": 37369, + "Ġaccelerators": 37370, + "ĠArkansas": 37371, + "Ġmars": 37372, + "ĠCuc": 37373, + "ĠInterfaces": 37374, + "Ġretrievals": 37375, + "Ġmelanin": 37376, + "ĠssDNA": 37377, + "vastava": 37378, + "Ġallergens": 37379, + "bud": 37380, + "Ġinaccessible": 37381, + "ictions": 37382, + "ĠMood": 37383, + "inda": 37384, + "Ġameric": 37385, + "Ġsymbiosis": 37386, + "bersome": 37387, + "occur": 37388, + "ĠMarcus": 37389, + "ĠSuperconductivity": 37390, + "ĠCort": 37391, + "ĠHMS": 37392, + "Ġphased": 37393, + "ĠJess": 37394, + "Ġpropulsion": 37395, + "extract": 37396, + "Ġsuccinate": 37397, + "ĠÖĴ": 37398, + "inkel": 37399, + "Ġsilence": 37400, + "ĠSUV": 37401, + "Ġconstituency": 37402, + "Ġbacteriophage": 37403, + "gem": 37404, + "ĠMCL": 37405, + "orene": 37406, + "ĠGoss": 37407, + "ICD": 37408, + "Ġglutamic": 37409, + "Ġcoexisting": 37410, + "STEMS": 37411, + "opotential": 37412, + "ĠEy": 37413, + "ĠLecture": 37414, + "ellae": 37415, + "Ġimmunoprec": 37416, + "Ġtimber": 37417, + "ĠVulner": 37418, + "Ġaroma": 37419, + "Ġsands": 37420, + "ĠSpan": 37421, + "Ġhern": 37422, + "Ġincubating": 37423, + "Ġtransmitters": 37424, + "ĠHomogeneous": 37425, + "ĠConstructing": 37426, + "dit": 37427, + "Ġtc": 37428, + "alass": 37429, + "Ġstents": 37430, + "ĠMID": 37431, + "Ġanoxic": 37432, + "Ġprovisions": 37433, + "ĠCapac": 37434, + "neutron": 37435, + "ĠVOCs": 37436, + "January": 37437, + "VAS": 37438, + "once": 37439, + "ĠCache": 37440, + "opulation": 37441, + "ĠVTE": 37442, + "Ġinterphase": 37443, + "Ġblog": 37444, + "ocusing": 37445, + "hiro": 37446, + "ĠREC": 37447, + "Ġanisotropies": 37448, + "benef": 37449, + "Ġconstipation": 37450, + "ĠCanal": 37451, + "Ġportrait": 37452, + "silyl": 37453, + "ĠLinked": 37454, + "ĠBowl": 37455, + "Ġmonopoles": 37456, + "ĠPerez": 37457, + "WIN": 37458, + "ĠTAP": 37459, + "Ġruthenium": 37460, + "ĠAdherence": 37461, + "ĠEnzymatic": 37462, + "Ġspecificities": 37463, + "Ġski": 37464, + "ĠCST": 37465, + "Ġpoetry": 37466, + "ATES": 37467, + "rama": 37468, + "lores": 37469, + "ALU": 37470, + "Ġvasoconstr": 37471, + "Ġgranulocyte": 37472, + "ibi": 37473, + "Ġopts": 37474, + "avesdrop": 37475, + "eptin": 37476, + "··": 37477, + "ĠJeong": 37478, + "Ġmedullary": 37479, + "ĠDemonstration": 37480, + "ĠFIB": 37481, + "ĠBRD": 37482, + "ĠVV": 37483, + "Ġallo": 37484, + "Rule": 37485, + "Tf": 37486, + "Ġunrealistic": 37487, + "Ġlatitudinal": 37488, + "ROP": 37489, + "ĠCorrelates": 37490, + "IU": 37491, + "ĠPore": 37492, + "ocrit": 37493, + "ĠKall": 37494, + "Ġcharcoal": 37495, + "ĠMongolia": 37496, + "âĪħ": 37497, + "ĠEntity": 37498, + "Ġgrams": 37499, + "graphene": 37500, + "mine": 37501, + "entric": 37502, + "ĠPp": 37503, + "ĠWelfare": 37504, + "ĠJets": 37505, + "Ġaffirm": 37506, + "ĠBelle": 37507, + "ĠStrategic": 37508, + "APIENTR": 37509, + "KH": 37510, + "rmann": 37511, + "Ġassociating": 37512, + "ĠSurviv": 37513, + "Ġnicotinic": 37514, + "ĠWLAN": 37515, + "п": 37516, + "Ġtears": 37517, + "ĠRevised": 37518, + "Ġphosphodies": 37519, + "Ġhorseradish": 37520, + "ĠLAR": 37521, + "took": 37522, + "ĠDescent": 37523, + "ĠNOx": 37524, + "ĠSteiner": 37525, + "ĠPermian": 37526, + "ĠVenezuela": 37527, + "Ġdesiccation": 37528, + "DIS": 37529, + "ĠMSP": 37530, + "Ġpopl": 37531, + "rels": 37532, + "Ġ": 37533, + "Ġlearnt": 37534, + "ĠBiofilm": 37535, + "ĠPCNA": 37536, + "ĠAttribute": 37537, + "ĠGrothendieck": 37538, + "ĠAdolescent": 37539, + "nv": 37540, + "stderr": 37541, + "obalt": 37542, + "ĠYamamoto": 37543, + "Ġaliquot": 37544, + "rater": 37545, + "ĠOre": 37546, + "ĠKIR": 37547, + "acker": 37548, + "Ġïĥ»": 37549, + "Ġstratosphere": 37550, + "ĠCust": 37551, + "respect": 37552, + "Ġglutamatergic": 37553, + "Ġencourages": 37554, + "ctic": 37555, + "itched": 37556, + "phins": 37557, + "Ġsuburb": 37558, + "Ġhomeomorphic": 37559, + "hexah": 37560, + "Ġminiatur": 37561, + "CAN": 37562, + "ahead": 37563, + "ĠBLE": 37564, + "ĠRBF": 37565, + "Ġacutely": 37566, + "Ġ": 37567, + "Ġantenn": 37568, + "URN": 37569, + "ĠGirl": 37570, + "Ġbioreactor": 37571, + "ĠLeibniz": 37572, + "Ġvial": 37573, + "ĠLich": 37574, + "obac": 37575, + "ĠWhenever": 37576, + "inhibition": 37577, + "Cast": 37578, + "Ġstripped": 37579, + "ĠAstrophysics": 37580, + "presence": 37581, + "ĠFloer": 37582, + "ipotent": 37583, + "dichloro": 37584, + "CLE": 37585, + "finger": 37586, + "onates": 37587, + "stri": 37588, + "ĠSperm": 37589, + "ĠDBS": 37590, + "opeptide": 37591, + "separation": 37592, + "athing": 37593, + "mathp": 37594, + "ouples": 37595, + "Ġentropic": 37596, + "Ġswollen": 37597, + "Ġdonated": 37598, + "Ġsettlements": 37599, + "ovenous": 37600, + "Perm": 37601, + "ĠSard": 37602, + "egen": 37603, + "ĠAlph": 37604, + "ĠCooperation": 37605, + "ĠPDAC": 37606, + "Final": 37607, + "lapse": 37608, + "Ġrevol": 37609, + "ĠIx": 37610, + "ĠLens": 37611, + "Ġkth": 37612, + "relaxation": 37613, + "ClO": 37614, + "ichloro": 37615, + "Ġwrapper": 37616, + "ĠSimultaneously": 37617, + "Compute": 37618, + "ëĬ": 37619, + "implantation": 37620, + "ĠVLA": 37621, + "heme": 37622, + "ĠMayor": 37623, + "ĠFacilit": 37624, + "Ġbatt": 37625, + "immer": 37626, + "Ġcurated": 37627, + "Ġconfluent": 37628, + "generational": 37629, + "starts": 37630, + "Ġgranulosa": 37631, + "arboxylate": 37632, + "ĠRiesz": 37633, + "Ġtextbook": 37634, + "Ġconstitutional": 37635, + "ĠPeace": 37636, + "ĠCommander": 37637, + "Ġobscured": 37638, + "vil": 37639, + "addition": 37640, + "ĠWasserstein": 37641, + "coords": 37642, + "ĠProbes": 37643, + "Ġdelineated": 37644, + "TZVP": 37645, + "ĠINF": 37646, + "Ġdosages": 37647, + "Ġoligomerization": 37648, + "ĠNADP": 37649, + "MKII": 37650, + "omin": 37651, + "Ġlhs": 37652, + "ughen": 37653, + "ĠJong": 37654, + "ancel": 37655, + "letter": 37656, + "ĠANC": 37657, + "FUNCTION": 37658, + "Ġtram": 37659, + "Their": 37660, + "ĠGenerated": 37661, + "Ġpolycyclic": 37662, + "Ġculmin": 37663, + "Ġrectum": 37664, + "Ġceft": 37665, + "Ġmetamaterials": 37666, + "ĠBiotech": 37667, + "Ġmyself": 37668, + "Ġunifying": 37669, + "Ġeman": 37670, + "ĠSinger": 37671, + "triangleright": 37672, + "omel": 37673, + "ĠCFA": 37674, + "ocha": 37675, + "ĠGSM": 37676, + "Ġcentrifuge": 37677, + "ĠIndo": 37678, + "Ġtransporting": 37679, + "LIB": 37680, + "Ġoxalate": 37681, + "ĠDulbecco": 37682, + "Ġali": 37683, + "arginal": 37684, + "hoo": 37685, + "ischem": 37686, + "APIENTRYP": 37687, + "Apart": 37688, + "LDA": 37689, + "ensile": 37690, + "settings": 37691, + "Ġephem": 37692, + "ampa": 37693, + "Ġduplications": 37694, + "ĠWheeler": 37695, + "Physical": 37696, + "ĠCompletion": 37697, + "ĠOrdered": 37698, + "Logger": 37699, + "Ġinterferences": 37700, + "ĠPollution": 37701, + "Optimal": 37702, + "Sv": 37703, + "aicin": 37704, + "Ġpicks": 37705, + "diversity": 37706, + "tigens": 37707, + "Ġdimorphism": 37708, + "feres": 37709, + "ĠRobotic": 37710, + "Ġconfirmatory": 37711, + "Ġcathodic": 37712, + "Ġspirals": 37713, + "Ġspruce": 37714, + "Lagrange": 37715, + "wat": 37716, + "ĠAllan": 37717, + "denote": 37718, + "CID": 37719, + "always": 37720, + "ithe": 37721, + "ĠChim": 37722, + "conditional": 37723, + "barrier": 37724, + "Ġvisualizing": 37725, + "Ġïĥ¹": 37726, + "Schmidt": 37727, + "Ġconventionally": 37728, + "ĠQUANT": 37729, + "GROUND": 37730, + "Ġug": 37731, + "ĠCWE": 37732, + "ĠInspired": 37733, + "Ġbuyer": 37734, + "Ġthermost": 37735, + "Ġkinematical": 37736, + "anolic": 37737, + "Ġdif": 37738, + "Ġ": 37739, + "ĠGeo": 37740, + "Examples": 37741, + "consistency": 37742, + "ĠPalace": 37743, + "ĠVaccination": 37744, + "Ġnatriuretic": 37745, + "YAG": 37746, + "ĠCTCs": 37747, + "Univers": 37748, + "ĠAcknowledgement": 37749, + "membered": 37750, + "vv": 37751, + "ĠSession": 37752, + "Ġinstar": 37753, + "ĠLevin": 37754, + "AVI": 37755, + "Ġproliferator": 37756, + "oliths": 37757, + "ĠTemperatures": 37758, + "imming": 37759, + "ĠToeplitz": 37760, + "ICATIONS": 37761, + "ĠIntegrals": 37762, + "Ġspliced": 37763, + "Dest": 37764, + "resulting": 37765, + "ĠHope": 37766, + "Ġenclosure": 37767, + "ieves": 37768, + "flav": 37769, + "ĠAbdul": 37770, + "Ġleishmaniasis": 37771, + "ò": 37772, + "oskeleton": 37773, + "Ġadduct": 37774, + "ĠInfluences": 37775, + "EQU": 37776, + "ĠSitu": 37777, + "Ġseas": 37778, + "ĠReich": 37779, + "cyst": 37780, + "ĠEVOLUTION": 37781, + "Ġwithstand": 37782, + "ĠGinzburg": 37783, + "RNAi": 37784, + "ĠNonparametric": 37785, + "ĠPrincess": 37786, + "Ġintravascular": 37787, + "UTIONS": 37788, + "Ġglutar": 37789, + "Ġcoincided": 37790, + "ĠSaito": 37791, + "pretrained": 37792, + "combined": 37793, + "ĠTAM": 37794, + "Ġalarms": 37795, + "Ġcyclooxygenase": 37796, + "Ġbn": 37797, + "Ġplagi": 37798, + "Particle": 37799, + "GGG": 37800, + "etics": 37801, + "amber": 37802, + "ABSTRACT": 37803, + "ĠExtracts": 37804, + "ĉĉĉĠĠĠĠ": 37805, + "ĠPhylogeny": 37806, + "tow": 37807, + "ĠContaining": 37808, + "Ġendonuclease": 37809, + "incubation": 37810, + "Ġofficinal": 37811, + "Ġexplosions": 37812, + "layout": 37813, + "Ġtouchdown": 37814, + "ĠRevealed": 37815, + "Ġinfiltrate": 37816, + "enith": 37817, + "timulation": 37818, + "ĠKind": 37819, + "ervices": 37820, + "PDA": 37821, + "Ġcereus": 37822, + "Env": 37823, + "Ġlapa": 37824, + "kamp": 37825, + "mult": 37826, + "enthal": 37827, + "ĠGoldstone": 37828, + "siRNA": 37829, + "strept": 37830, + "Qual": 37831, + "mother": 37832, + "dio": 37833, + "Ġinfrequent": 37834, + "Ġcyclospor": 37835, + "hepatitis": 37836, + "thrombotic": 37837, + "GST": 37838, + "ĠLj": 37839, + "ĠUR": 37840, + "ofect": 37841, + "ĠArrow": 37842, + "ethnic": 37843, + "ĠBarcelona": 37844, + "Care": 37845, + "titious": 37846, + "Ġeta": 37847, + "Ġvirions": 37848, + "smash": 37849, + "ĠâIJ¤": 37850, + "Ġavenues": 37851, + "obarb": 37852, + "ĠComments": 37853, + "Ġanyway": 37854, + "afil": 37855, + "ĠBea": 37856, + "ĠBoys": 37857, + "ĠAutomata": 37858, + "ĠSuperconducting": 37859, + "Pic": 37860, + "kHz": 37861, + "Ġnorepinephrine": 37862, + "ĠGPC": 37863, + "Ġunderlined": 37864, + "brahim": 37865, + "Ġelectrospray": 37866, + "Ġsesqu": 37867, + "ĠTournament": 37868, + "Austr": 37869, + "ĠGrowing": 37870, + "ĠWebsite": 37871, + "LDH": 37872, + "covariance": 37873, + "several": 37874, + "stabilized": 37875, + "Ġdecarboxylase": 37876, + "Ġremed": 37877, + "rhoe": 37878, + "ĠSRS": 37879, + "ĠTreated": 37880, + "ĠMadagascar": 37881, + "ĠMagic": 37882, + "Ġweapon": 37883, + "ĠYoshida": 37884, + "Ġhypoglycemia": 37885, + "ĠBifidobacterium": 37886, + "entitious": 37887, + ":::": 37888, + "ĠSingles": 37889, + "Ġnicely": 37890, + "Ġunexpectedly": 37891, + "ibles": 37892, + "ariae": 37893, + "Ġcentroids": 37894, + "Ġbroadened": 37895, + "ĠJohns": 37896, + "ĠBacteroid": 37897, + "Ġframing": 37898, + "Primary": 37899, + "ĠPicture": 37900, + "government": 37901, + "Ġreq": 37902, + "ĠTry": 37903, + "ibo": 37904, + "Ġliquef": 37905, + "osensitivity": 37906, + "Ġslaughter": 37907, + "ĠDAR": 37908, + "Ġlogit": 37909, + "Ġpromises": 37910, + "Ġlawyer": 37911, + "ĠFPG": 37912, + "TCP": 37913, + "Ġintercalation": 37914, + "ĠBoe": 37915, + "Ġwideband": 37916, + "Ġjudgement": 37917, + "romagnets": 37918, + "Lastly": 37919, + "ĠIschemic": 37920, + "IMA": 37921, + "food": 37922, + "much": 37923, + "Ġavenue": 37924, + "Ġschistosomiasis": 37925, + "ĠExecution": 37926, + "DQU": 37927, + "GIS": 37928, + "kines": 37929, + "akage": 37930, + "echt": 37931, + "ĠScaff": 37932, + "ĠStrings": 37933, + "ĠMultilevel": 37934, + "Ġcumbersome": 37935, + "ĠRaymond": 37936, + "Ġirregularities": 37937, + "ĠAGNs": 37938, + "ĠMetastatic": 37939, + "ĠIberian": 37940, + "Mb": 37941, + "RNP": 37942, + "hong": 37943, + "isinin": 37944, + "Ġthirteen": 37945, + "ĠFAS": 37946, + "Ġsealing": 37947, + "Ġapatite": 37948, + "Ġserially": 37949, + "ĠÅĿ": 37950, + "DEL": 37951, + "Fo": 37952, + "ĠSoph": 37953, + "ĠBear": 37954, + "ĠJosh": 37955, + "reck": 37956, + "uller": 37957, + "Ġexcursion": 37958, + "Ġembodied": 37959, + "Ġhybridized": 37960, + "ĠLieutenant": 37961, + "Period": 37962, + "Ġmollus": 37963, + "CVD": 37964, + "Ren": 37965, + "REAM": 37966, + "ĠBACK": 37967, + "Ġaccreting": 37968, + "Ġculturing": 37969, + "ĠBurst": 37970, + "ĠSegment": 37971, + "Ġasterisk": 37972, + "ĠIdeal": 37973, + "Ġintertw": 37974, + "ĠAtoms": 37975, + "ĠSTE": 37976, + "Ġïģª": 37977, + "Ġremarked": 37978, + "Ġhairs": 37979, + "âľ": 37980, + "ĠMetropolis": 37981, + "ĠPartially": 37982, + "ĠObserver": 37983, + "Ġhematologic": 37984, + "obilization": 37985, + "ĠBergman": 37986, + "Ġcartesian": 37987, + "Ġclathrin": 37988, + "ĠSung": 37989, + "Ġration": 37990, + "Ġscoliosis": 37991, + "ohl": 37992, + "mutant": 37993, + "NNs": 37994, + "ĠRahman": 37995, + "ĠSpatially": 37996, + "PIP": 37997, + "Yb": 37998, + "Ġdiaz": 37999, + "vertebral": 38000, + "adzu": 38001, + "alski": 38002, + "answer": 38003, + "Ġgeochemistry": 38004, + "Ġstemming": 38005, + "wes": 38006, + "oxys": 38007, + "Ġmats": 38008, + "eva": 38009, + "ĠHyperbolic": 38010, + "arbage": 38011, + "Ġclipping": 38012, + "ĠSugar": 38013, + "ĠCognition": 38014, + "ĠDIV": 38015, + "Ġtempt": 38016, + "ĠPathogen": 38017, + "ĠPedro": 38018, + "Ġwak": 38019, + "entries": 38020, + "ĠGCM": 38021, + "projective": 38022, + "Ġproficiency": 38023, + "ĠKnown": 38024, + "Ġlexicon": 38025, + "ĠMendelian": 38026, + "Ġzoonotic": 38027, + "leans": 38028, + "ĠTalk": 38029, + "Ġkurtosis": 38030, + "NAS": 38031, + "ĠNowadays": 38032, + "ĠLil": 38033, + "ĠWMAP": 38034, + "Ġdisperse": 38035, + "Ġcolloids": 38036, + "ebra": 38037, + "OMET": 38038, + "ĠDCT": 38039, + "ĠRise": 38040, + "Ġintergenic": 38041, + "GTH": 38042, + "Ġtapered": 38043, + "Markovian": 38044, + "Protocol": 38045, + "ĠVegetation": 38046, + "rats": 38047, + "Ġdivalent": 38048, + "ĠCrust": 38049, + "zyg": 38050, + "Ġpigmentation": 38051, + "graduate": 38052, + "ĠRicc": 38053, + "Ġcounterexample": 38054, + "Ġsativ": 38055, + "Ġls": 38056, + "ĠCirculation": 38057, + "isotropic": 38058, + "ĠENSO": 38059, + "Ġtroponin": 38060, + "Ġdissolving": 38061, + "Ġcosmetic": 38062, + "Hf": 38063, + "further": 38064, + "Ġpanc": 38065, + "Ġhops": 38066, + "intra": 38067, + "ĠZhe": 38068, + "ĠReliable": 38069, + "ivolumab": 38070, + "MX": 38071, + "Rab": 38072, + "ĠPES": 38073, + "ĠBü": 38074, + "Ġadhered": 38075, + "Ġfluency": 38076, + "ĠClaus": 38077, + "Ġdelamination": 38078, + "Ġguanine": 38079, + "ĠMultiscale": 38080, + "ĠEquip": 38081, + "ĠIllustr": 38082, + "Ġtetrahydro": 38083, + "fel": 38084, + "lists": 38085, + "Îŀ": 38086, + "emulsion": 38087, + "ĠNZ": 38088, + "Ġwasn": 38089, + "aira": 38090, + "Ġarguing": 38091, + "miRNA": 38092, + "ĠExpressed": 38093, + "Ġspectrophotometric": 38094, + "Ġileum": 38095, + "Ġflames": 38096, + "Fit": 38097, + "Gon": 38098, + "ĠCulex": 38099, + "Ġunweighted": 38100, + "Ġnanob": 38101, + "SHV": 38102, + "Ġaligning": 38103, + "Ġshuttle": 38104, + "Ġchloroquine": 38105, + "Ġpyrite": 38106, + "ĠRica": 38107, + "Ġrift": 38108, + "Ġcathepsin": 38109, + "ĠPROCESS": 38110, + "Pf": 38111, + "Raw": 38112, + "rayfish": 38113, + "SAL": 38114, + "collapse": 38115, + "................": 38116, + "atases": 38117, + "Ġworkshops": 38118, + "ophile": 38119, + "ĠâĬĥ": 38120, + "Ġbifurcations": 38121, + "Trace": 38122, + "Ġpause": 38123, + "Ġorbiting": 38124, + "oliubov": 38125, + "ĠCurtis": 38126, + "ĠRevisiting": 38127, + "oret": 38128, + "Ġinfused": 38129, + "luents": 38130, + "Ġplastid": 38131, + "Ġïģ¹": 38132, + "Ġexecutions": 38133, + "ĠGraves": 38134, + "locally": 38135, + "ĠAtmosphere": 38136, + "diabetes": 38137, + "ĠPradesh": 38138, + "ĠCofactor": 38139, + "isomorphic": 38140, + "Ġbod": 38141, + "ĠCBD": 38142, + "Ġincap": 38143, + "Ġretrovirus": 38144, + "Ġlipophilic": 38145, + "Ġlinoleic": 38146, + "Ġtravelled": 38147, + "covalent": 38148, + "pick": 38149, + "upl": 38150, + "ĠPole": 38151, + "ĠThym": 38152, + "ĠTeich": 38153, + "Ġcollaborators": 38154, + "Ġinstantons": 38155, + "ĠMEGA": 38156, + "ĠHepatocellular": 38157, + "Ġinfestation": 38158, + "ĠPiezo": 38159, + "ĠLub": 38160, + "ĠNCs": 38161, + "Ġnucleoside": 38162, + "Ġosteogenesis": 38163, + "Eigen": 38164, + "RMSE": 38165, + "Ġlax": 38166, + "ĠKost": 38167, + "ĠVero": 38168, + "ĠChou": 38169, + "electrochemical": 38170, + "Ġcompeti": 38171, + "chia": 38172, + "Ġsubmodule": 38173, + "ĠAllow": 38174, + "Ġresolvent": 38175, + "Ġsweeps": 38176, + "Ġsuperconformal": 38177, + "pyrrolidine": 38178, + "lofen": 38179, + "åŃ": 38180, + "Ġdeserves": 38181, + "ĠZimbabwe": 38182, + "azines": 38183, + "ĠConsult": 38184, + "Ġcastle": 38185, + "Ġpharmaceuticals": 38186, + "Ġparacrine": 38187, + "Ġjejuni": 38188, + "Ġarguably": 38189, + "ĠeNOS": 38190, + "Ġherds": 38191, + "Ġvehicular": 38192, + "Ġtriangulated": 38193, + "Ġîµ": 38194, + "ĠGrande": 38195, + "Ġanthocyanins": 38196, + "ĠDuan": 38197, + "ĠVibration": 38198, + "Ġtriad": 38199, + "Ġhousekeeping": 38200, + "Bor": 38201, + "Ġpub": 38202, + "Ġmalformation": 38203, + "glucosamine": 38204, + "inhibitory": 38205, + "Dirac": 38206, + "ĠCSD": 38207, + "ĠRotating": 38208, + "ĠHTLV": 38209, + "Ġdemol": 38210, + "infiltr": 38211, + "Ġhemolytic": 38212, + "Ġcarbapenem": 38213, + "Ġluminescent": 38214, + "ĠPlanets": 38215, + "Ġmellifera": 38216, + "Ġcorticosterone": 38217, + "ĠAddress": 38218, + "Ġhubs": 38219, + "omethacin": 38220, + "åIJ": 38221, + "ĠChampions": 38222, + "ĠRevision": 38223, + "ĠHerbert": 38224, + "Ġambiguities": 38225, + "KERN": 38226, + "Ġdé": 38227, + "Ġlp": 38228, + "Ġenvis": 38229, + "ĠChol": 38230, + "ropin": 38231, + "Ġdrone": 38232, + "meyer": 38233, + "Ġisotype": 38234, + "ĠVu": 38235, + "ERC": 38236, + "Ġversatility": 38237, + "Speed": 38238, + "Ġaetiology": 38239, + "Ġgonadotropin": 38240, + "Ġcognate": 38241, + "ĠCotton": 38242, + "reasonable": 38243, + "disable": 38244, + "Ġdevastating": 38245, + "Pier": 38246, + "POL": 38247, + "ĠBé": 38248, + "incter": 38249, + "aluable": 38250, + "Ġpolyhedron": 38251, + "ĠRelay": 38252, + "Ġworkflows": 38253, + "FEM": 38254, + "inp": 38255, + "Ġmph": 38256, + "softmax": 38257, + "mur": 38258, + "vr": 38259, + "Ġerent": 38260, + "ĠKN": 38261, + "Ġstatin": 38262, + "Ġflatness": 38263, + "ĠArchitectures": 38264, + "ĠVeterinary": 38265, + "Ġnosocomial": 38266, + "Sk": 38267, + "XML": 38268, + "ĠFos": 38269, + "ĠLor": 38270, + "Ġradiography": 38271, + "ĠBlum": 38272, + "ĠDiscrimination": 38273, + "Ġpunc": 38274, + "Ġexits": 38275, + "ĠBilateral": 38276, + "msstrahlung": 38277, + "Ġcolonized": 38278, + "ĠFibrosis": 38279, + "Ġchaperones": 38280, + "aboratory": 38281, + "ĠPersistence": 38282, + "Ġlumped": 38283, + "Ġrabies": 38284, + "ĠBurns": 38285, + "Dense": 38286, + "ontium": 38287, + "acetylation": 38288, + "ĠFET": 38289, + "Ġhandful": 38290, + "biology": 38291, + "Ġundesired": 38292, + "Limit": 38293, + "ĠNBA": 38294, + "ĠSeoul": 38295, + "APT": 38296, + "ĠTransgenic": 38297, + "oxygenation": 38298, + "Button": 38299, + "ĠTreatments": 38300, + "ZV": 38301, + "isomorphism": 38302, + "octa": 38303, + "iffe": 38304, + "odeoxy": 38305, + "Ġorganelle": 38306, + "Ġcolloid": 38307, + "Ġceramide": 38308, + "Ġtqdm": 38309, + "GPS": 38310, + "ĠISR": 38311, + "oclinic": 38312, + "ĠLyme": 38313, + "Ġepig": 38314, + "ĠTrail": 38315, + "IPS": 38316, + "Ġsorts": 38317, + "ĠZebrafish": 38318, + "Ġhydroxylase": 38319, + "Smirnov": 38320, + "Bax": 38321, + "ĠDance": 38322, + "ĠHors": 38323, + "Ġreachability": 38324, + "Parallel": 38325, + "ĠESBL": 38326, + "Ġuplink": 38327, + "Ġpostprandial": 38328, + "solar": 38329, + "itabine": 38330, + "ordism": 38331, + "Neasy": 38332, + "Ġabandon": 38333, + "IMI": 38334, + "fake": 38335, + "statistical": 38336, + "ĠCars": 38337, + "ibia": 38338, + "ĠÃĩ": 38339, + "spc": 38340, + "MDP": 38341, + "tizations": 38342, + "International": 38343, + "ularis": 38344, + "Ġvacuoles": 38345, + "KC": 38346, + "ĠAPT": 38347, + "ĠBt": 38348, + "ĠBom": 38349, + "ĠGMP": 38350, + "Ġpioneer": 38351, + "ĠChairman": 38352, + "ĠTucker": 38353, + "ĠRAF": 38354, + "ĠNASH": 38355, + "ĠWIT": 38356, + "ynyl": 38357, + "Ġsupplier": 38358, + "ansky": 38359, + "Ġdecomposing": 38360, + "ĠUVB": 38361, + "ophenol": 38362, + "Ġbarium": 38363, + "ĠSMT": 38364, + "otocin": 38365, + "lytic": 38366, + "ranking": 38367, + "ĠDirections": 38368, + "Ġinnervation": 38369, + "switching": 38370, + "dac": 38371, + "ĠhT": 38372, + "Ġdoctr": 38373, + "ĠIncremental": 38374, + "ĠEarthquake": 38375, + "Has": 38376, + "Lee": 38377, + "mates": 38378, + "proline": 38379, + "ĠREE": 38380, + "Ġviolates": 38381, + "ðx": 38382, + "Ġhomogenates": 38383, + "Boolean": 38384, + "Ġdoxycycline": 38385, + "ĠMOF": 38386, + "iophen": 38387, + "Ġappreciation": 38388, + "finals": 38389, + "characteristic": 38390, + "ĠContinental": 38391, + "Bus": 38392, + "Esc": 38393, + "XP": 38394, + "ÛĮ": 38395, + "ĠCTA": 38396, + "Maxwell": 38397, + "Ġarchaea": 38398, + "Nik": 38399, + "NONE": 38400, + "TW": 38401, + "tering": 38402, + "ĠPerman": 38403, + "Ġrestores": 38404, + "opathogenic": 38405, + "ĠMontgomery": 38406, + "Ġglucocorticoids": 38407, + "Ġud": 38408, + "ĠNuss": 38409, + "ĠNé": 38410, + "ĠSturm": 38411, + "Ġattaching": 38412, + "Ġintraperitoneally": 38413, + "lasov": 38414, + "Ġstellate": 38415, + "Ġantiproliferative": 38416, + "Ġmicroorganism": 38417, + "Ġvisu": 38418, + "Ġjudges": 38419, + "randomized": 38420, + "allowed": 38421, + "Ġdeprived": 38422, + "development": 38423, + "scribed": 38424, + "etherian": 38425, + "ĠFraser": 38426, + "Ram": 38427, + "bib": 38428, + "Ġliner": 38429, + "Ġguns": 38430, + "resnet": 38431, + "ĠLTR": 38432, + "ighting": 38433, + "Initi": 38434, + "ĠZimm": 38435, + "ĠGeology": 38436, + "Ġantioxidative": 38437, + "Ġmagenta": 38438, + "ĠNigerian": 38439, + "galaxy": 38440, + "ĠMelanoma": 38441, + "Found": 38442, + "Ġbum": 38443, + "ĠTrop": 38444, + "ĠDos": 38445, + "Ġmetab": 38446, + "Ġinvoking": 38447, + "ĠSchizophrenia": 38448, + "CFG": 38449, + "Ġgelation": 38450, + "Ġopioids": 38451, + "pis": 38452, + "Ġchurches": 38453, + "Ġcanonically": 38454, + "Ġjug": 38455, + "Ġacceptors": 38456, + "DMEM": 38457, + "Ġobliqu": 38458, + "ĠMedicare": 38459, + "arpoon": 38460, + "ZIP": 38461, + "oreactive": 38462, + "Ġimprinting": 38463, + "ĠVinc": 38464, + "Ġ¿": 38465, + "Ġrestart": 38466, + "Ġdentate": 38467, + "enzymatic": 38468, + "Ġinguinal": 38469, + "ĠNt": 38470, + "Ġunobserved": 38471, + "uctuation": 38472, + "Ġbiasing": 38473, + "Ġintegrins": 38474, + "Ġurl": 38475, + "FPGAM": 38476, + "ĠCLUST": 38477, + "omatology": 38478, + "Ġmetallicities": 38479, + "Ġintentionally": 38480, + "FPGAMGR": 38481, + "Typ": 38482, + "Ġally": 38483, + "Ġcomic": 38484, + "ĠLions": 38485, + "Ġimputed": 38486, + "ĠÃŁ": 38487, + "lexia": 38488, + "ĠJanus": 38489, + "Ġbrass": 38490, + "ĠDownloaded": 38491, + "BUFF": 38492, + "identical": 38493, + "Ġpsychiatry": 38494, + "CCT": 38495, + "ifar": 38496, + "ĠMandel": 38497, + "Ġoptoelectronic": 38498, + "Ġisomerization": 38499, + "ĠFant": 38500, + "ĠLion": 38501, + "ĠLov": 38502, + "ĠNaf": 38503, + "esta": 38504, + "Ġbiocompatible": 38505, + "Ġsecretions": 38506, + "sci": 38507, + "ĠRetro": 38508, + "oisomerase": 38509, + "ĠSnap": 38510, + "Ġsplittings": 38511, + "Ġscavenger": 38512, + "procedure": 38513, + "Dawley": 38514, + "ëĭ¤": 38515, + "unate": 38516, + "ĠDye": 38517, + "ĠNEC": 38518, + "Ġnanocl": 38519, + "Ġplanetes": 38520, + "ĠTRPM": 38521, + "Ġvoices": 38522, + "ĠHierarchy": 38523, + "mv": 38524, + "Ġlasts": 38525, + "Ġhoped": 38526, + "Ġmedians": 38527, + "ĠAndreev": 38528, + "Ġheightened": 38529, + "ä»": 38530, + "Ġindefinite": 38531, + "ĠKamp": 38532, + "angel": 38533, + "grids": 38534, + "archae": 38535, + "Ġtherapists": 38536, + "ĠMiR": 38537, + "Ġnegotiation": 38538, + "HSP": 38539, + "ĠCustom": 38540, + "Ġstria": 38541, + "Ġunacceptable": 38542, + "retin": 38543, + "penet": 38544, + "ĠORR": 38545, + "ĠLifetime": 38546, + "ĠPhosphate": 38547, + "Ġtropics": 38548, + "ĠWelch": 38549, + "ĠPyr": 38550, + "Ġamputation": 38551, + "ĠArtin": 38552, + "ĠCaO": 38553, + "Ġconjectures": 38554, + "Ġatrium": 38555, + "ĠComplementary": 38556, + "ĠAluminum": 38557, + "Ġmicrow": 38558, + "iliated": 38559, + "ĠImmuno": 38560, + "Ġbinocular": 38561, + "ĠWeakly": 38562, + "Ġimmunogenic": 38563, + "Ġbathym": 38564, + "ĠPhenotype": 38565, + "Ġsialic": 38566, + "Six": 38567, + "Ġakin": 38568, + "rotor": 38569, + "helm": 38570, + "CCESS": 38571, + "Ġneuroprotection": 38572, + "ĠFifth": 38573, + "Ġcontingent": 38574, + "Ġsketched": 38575, + "Imp": 38576, + "Ġcached": 38577, + "urement": 38578, + "ĠBic": 38579, + "ĠKah": 38580, + "beration": 38581, + "atterson": 38582, + "Ġglycation": 38583, + "Ġinvestors": 38584, + "Assisted": 38585, + "iales": 38586, + "science": 38587, + "Ġpilots": 38588, + "uscripts": 38589, + "MICS": 38590, + "Ġorthopedic": 38591, + "warfs": 38592, + "greater": 38593, + "ĠArtery": 38594, + "Video": 38595, + "Ġarrange": 38596, + "avar": 38597, + "charges": 38598, + "dialdehyde": 38599, + "ĠTPA": 38600, + "Ġspelling": 38601, + "ĠSeiberg": 38602, + "Ġnavigate": 38603, + "ĠPowder": 38604, + "ĠRings": 38605, + "ĠChron": 38606, + "ĠAtg": 38607, + "Ġhomocysteine": 38608, + "ĠIdentify": 38609, + "Ġoak": 38610, + "Ġliability": 38611, + "Ġoperands": 38612, + "ĠCTD": 38613, + "Ġalleviates": 38614, + "mA": 38615, + "ĠLanger": 38616, + "Ġsubmanifolds": 38617, + "ĠJag": 38618, + "Ġradiance": 38619, + "constants": 38620, + "ĠMorocco": 38621, + "Engine": 38622, + "á¸": 38623, + "âĤ¬": 38624, + "revers": 38625, + "PCI": 38626, + "unsqueeze": 38627, + "oconversion": 38628, + "Ġintensified": 38629, + "Ġrefinements": 38630, + "ofectamine": 38631, + "ayas": 38632, + "Ġincidental": 38633, + "ĠThur": 38634, + "Ġoverd": 38635, + "Ġbitter": 38636, + "Ġignores": 38637, + "ан": 38638, + "ĠOTU": 38639, + "Ġserr": 38640, + "aby": 38641, + "ĠGCN": 38642, + "ĠConsumer": 38643, + "Ġconcordant": 38644, + "ĠMRC": 38645, + "ĠEconomy": 38646, + "satisfying": 38647, + "Ġbiotinylated": 38648, + "Numerical": 38649, + "ĠRashba": 38650, + "stochastic": 38651, + "ĠLal": 38652, + "Ġburdens": 38653, + "Alloc": 38654, + "ĠGraphics": 38655, + "ĠLRRK": 38656, + "AIC": 38657, + "ĠTed": 38658, + "ĠSark": 38659, + "owl": 38660, + "Ġhemost": 38661, + "ĠAnat": 38662, + "Ġhoming": 38663, + "ĠCharlie": 38664, + "ĠBruc": 38665, + "ihara": 38666, + "ingen": 38667, + "ĠVern": 38668, + "ĠYers": 38669, + "Ġids": 38670, + "ĠcircRNAs": 38671, + "Ġconducive": 38672, + "ĠBRST": 38673, + "Ġgallium": 38674, + "Ġdichotomy": 38675, + "Fr": 38676, + "etition": 38677, + "Ġcesarean": 38678, + "olan": 38679, + "Ġrn": 38680, + "ubstituted": 38681, + "ĠLeaves": 38682, + "ĠLeader": 38683, + "coloring": 38684, + "Draw": 38685, + "Ġserous": 38686, + "Err": 38687, + "Ġinnermost": 38688, + "ĠHamburg": 38689, + "Stor": 38690, + "jes": 38691, + "Ġtol": 38692, + "idade": 38693, + "Ġrv": 38694, + "ĠInversion": 38695, + "Ġmultiphase": 38696, + "Ġpseudor": 38697, + "ĠGoodman": 38698, + "ĠJSON": 38699, + "Ġcorridor": 38700, + "Ġpork": 38701, + "ĠSale": 38702, + "ĠNatal": 38703, + "Ġattacking": 38704, + "ĠSheet": 38705, + "Ġstreamwise": 38706, + "Ġatomistic": 38707, + "Ġfirmly": 38708, + "ĠAchie": 38709, + "Ġpir": 38710, + "ĠIKK": 38711, + "ĠFalk": 38712, + "ileptic": 38713, + "ĠTRPC": 38714, + "Ġadhesions": 38715, + "HRP": 38716, + "Ġpaucity": 38717, + "Split": 38718, + "UDI": 38719, + "ĠSend": 38720, + "ĠPine": 38721, + "ĠLon": 38722, + "ĠLost": 38723, + "efer": 38724, + "concaten": 38725, + "Ġloyal": 38726, + "Ġglycop": 38727, + "ĠObserving": 38728, + "ĠMohamed": 38729, + "YR": 38730, + "ĠFilters": 38731, + "cas": 38732, + "pages": 38733, + "ĠdA": 38734, + "Ġareal": 38735, + "adis": 38736, + "ĠLHS": 38737, + "ĠThereby": 38738, + "Ġvisualizations": 38739, + "Ġtwistor": 38740, + "unitary": 38741, + "Ġarchives": 38742, + "Ġphenolics": 38743, + "hik": 38744, + "sson": 38745, + "ĠIK": 38746, + "ĠStudying": 38747, + "Ġtwisting": 38748, + "ĠHydrodynamic": 38749, + "Ġsplitter": 38750, + "Ġurothelial": 38751, + "Ġalken": 38752, + "ĠGPI": 38753, + "Ġcortices": 38754, + "Ġcropping": 38755, + "Patient": 38756, + "ĠChlamyd": 38757, + "inberg": 38758, + "ĠAircraft": 38759, + "cele": 38760, + "ectral": 38761, + "Ġconferences": 38762, + "Ġcreatine": 38763, + "alty": 38764, + "proportional": 38765, + "Ġleptonic": 38766, + "Ġovulation": 38767, + "uerre": 38768, + "tezomib": 38769, + "dle": 38770, + "initeness": 38771, + "ĠSpecimens": 38772, + "Ġcoma": 38773, + "inephrine": 38774, + "Ġepim": 38775, + "ĠPercent": 38776, + "CoO": 38777, + "ĠLoading": 38778, + "Ġvenue": 38779, + "ĠTNM": 38780, + "Ġpacemaker": 38781, + "ĠHoffmann": 38782, + "Tech": 38783, + "nie": 38784, + "ĠOrleans": 38785, + "Ġmagnetron": 38786, + "Ġhospitality": 38787, + "ĠNordic": 38788, + "oproliferative": 38789, + "Ġundoubtedly": 38790, + "ĠSrin": 38791, + "Ġhumic": 38792, + "ĠIntegrative": 38793, + "ĠCampus": 38794, + "Ġplantarum": 38795, + "radiative": 38796, + "Ġiterator": 38797, + "ĠMesozoic": 38798, + "APs": 38799, + "carinic": 38800, + "Ġcheckpoints": 38801, + "ĠïĤ£": 38802, + "ĠmAbs": 38803, + "ĠLiverpool": 38804, + "ìĿ´": 38805, + "ĠEcosystem": 38806, + "Ġneovascularization": 38807, + "Ġdemoc": 38808, + "loops": 38809, + "ĠSURF": 38810, + "Ġpassivation": 38811, + "Ġconsecutively": 38812, + "ĠAlfvén": 38813, + "ĠSSE": 38814, + "Ġouts": 38815, + "stimulation": 38816, + "Ġphilosophical": 38817, + "ĠSask": 38818, + "Ġflakes": 38819, + "Ġfingerprinting": 38820, + "Ġbuffalo": 38821, + "ĠWikimedia": 38822, + "Ġreconstitution": 38823, + "Ġepithelia": 38824, + "onk": 38825, + "eny": 38826, + "ĠMQ": 38827, + "ĠFork": 38828, + "endance": 38829, + "Ġgeneralisation": 38830, + "Ġpeoples": 38831, + "Ġconnector": 38832, + "gesia": 38833, + "interference": 38834, + "Ġcoloration": 38835, + "calculation": 38836, + "ĠAxial": 38837, + "ĠDESIGN": 38838, + "Ġrecession": 38839, + "Ġdissolve": 38840, + "ĠPartitioning": 38841, + "QxMD": 38842, + "GES": 38843, + "Vo": 38844, + "khar": 38845, + "ĠEAE": 38846, + "Ġcoarser": 38847, + "Ġposttraumatic": 38848, + "Ġsynthesised": 38849, + "silica": 38850, + "tetrahydropy": 38851, + "ĠPorter": 38852, + "vark": 38853, + "entanyl": 38854, + "Ġconve": 38855, + "Ġrafts": 38856, + "brecht": 38857, + "Ġrectifier": 38858, + "Ġoroph": 38859, + "ĠCEP": 38860, + "Ġhistones": 38861, + "Ġstandpoint": 38862, + "Ġancillary": 38863, + "ĠHurricane": 38864, + "cro": 38865, + "Ġreb": 38866, + "ĠiT": 38867, + "Ġgeography": 38868, + "olarization": 38869, + "ĠManaging": 38870, + "Ġxylose": 38871, + "utherland": 38872, + "ĠTaqMan": 38873, + "KN": 38874, + "Ġtm": 38875, + "ĠTAS": 38876, + "istle": 38877, + "âĢ«": 38878, + "Ġmycorrhizal": 38879, + "ĠTerrestrial": 38880, + "hausen": 38881, + "observable": 38882, + "Brien": 38883, + "Ġneutropenia": 38884, + "Taken": 38885, + "ĠSMI": 38886, + "Ġpolishing": 38887, + "Ġphotop": 38888, + "Ġthermalization": 38889, + "Ġpseudoscalar": 38890, + "ĠDominic": 38891, + "romyalgia": 38892, + "Ġechocardiographic": 38893, + "Illumina": 38894, + "ĠIPC": 38895, + "ĠHuss": 38896, + "essive": 38897, + "uptake": 38898, + "Ġweekend": 38899, + "Ġcorroborate": 38900, + "ĠTasman": 38901, + "herty": 38902, + "Ġperine": 38903, + "Ġtransports": 38904, + "Ġglance": 38905, + "retinal": 38906, + "Proto": 38907, + "igenes": 38908, + "Ġprohibited": 38909, + "behavioral": 38910, + "opherol": 38911, + "ë¡": 38912, + "ĠNecess": 38913, + "obiology": 38914, + "okk": 38915, + "Ġtraversal": 38916, + "ĠAndes": 38917, + "Resource": 38918, + "olitic": 38919, + "ça": 38920, + "irie": 38921, + "arctan": 38922, + "Ġmorphogenetic": 38923, + "ĠHui": 38924, + "losses": 38925, + "Ġfulfilling": 38926, + "Ġhurricane": 38927, + "ombo": 38928, + "Ġgs": 38929, + "ĠLv": 38930, + "ĠNerv": 38931, + "ellosis": 38932, + "Ġconfront": 38933, + "Ġorthologous": 38934, + "Ġwettability": 38935, + "Ġcyanobacterial": 38936, + "Ġcassava": 38937, + "AUT": 38938, + "avi": 38939, + "hlen": 38940, + "ĠSLA": 38941, + "Ġconvol": 38942, + "Ġintermetallic": 38943, + "inside": 38944, + "Ġpolarizability": 38945, + "Ġensuing": 38946, + "Ġchloroplasts": 38947, + "lid": 38948, + "lips": 38949, + "Ġrebound": 38950, + "ĠCary": 38951, + "ĠLambda": 38952, + "ĠViv": 38953, + "Ġcalcination": 38954, + "ĠÌĨ": 38955, + "Ġcounterfactual": 38956, + "ĠSilica": 38957, + "Referee": 38958, + "Ġhomologues": 38959, + "ĠSpatiotemporal": 38960, + "ĠArrhenius": 38961, + "Ġinflamed": 38962, + "ĠZambia": 38963, + "Ġantipsychotic": 38964, + "helper": 38965, + "Blood": 38966, + "Ġpurchasing": 38967, + "ĠSchwinger": 38968, + "ĠWilkinson": 38969, + "Ġfainter": 38970, + "Ġrash": 38971, + "ĠJang": 38972, + "ĠConductivity": 38973, + "ropoda": 38974, + "ĠSeq": 38975, + "Ġpropolis": 38976, + "Ġtubule": 38977, + "ĠLieb": 38978, + "optimization": 38979, + "mounted": 38980, + "emes": 38981, + "canic": 38982, + "oradiotherapy": 38983, + "ĠJenkins": 38984, + "Nc": 38985, + "Together": 38986, + "Ġfove": 38987, + "Ġmv": 38988, + "ĠDefect": 38989, + "ät": 38990, + "ĠFinance": 38991, + "umarin": 38992, + "mittance": 38993, + "erel": 38994, + "ĠFren": 38995, + "ĠRhyth": 38996, + "ramified": 38997, + "Ġhypercholesterolem": 38998, + "Ġstimulatory": 38999, + "ĠRichmond": 39000, + "Ġadvancements": 39001, + "bles": 39002, + "xu": 39003, + "allation": 39004, + "Ġintral": 39005, + "iterpene": 39006, + "Concerning": 39007, + "Ġbulky": 39008, + "Ġá¾±": 39009, + "computation": 39010, + "ĠAgarwal": 39011, + "Central": 39012, + "XPS": 39013, + "Ġtalks": 39014, + "ĠTap": 39015, + "imilar": 39016, + "ĠNCI": 39017, + "Ġaccused": 39018, + "Ġtranscriptomes": 39019, + "Ġprovisioning": 39020, + "ĠEtOH": 39021, + "gm": 39022, + "Ġtid": 39023, + "ĠPOC": 39024, + "ffman": 39025, + "ĠIner": 39026, + "ĠUB": 39027, + "incubated": 39028, + "ĠAtrial": 39029, + "Ġfourteen": 39030, + "ĠAstronomical": 39031, + "ĠMiguel": 39032, + "ĠKov": 39033, + "Ġscipy": 39034, + "Ġthermoplastic": 39035, + "ĠManuel": 39036, + "ĠPromotion": 39037, + "ĠAccessed": 39038, + "Ġterritorial": 39039, + "inas": 39040, + "ĠMPs": 39041, + "monitoring": 39042, + "ĠSimulating": 39043, + "Ġpanor": 39044, + "Ġrheumatic": 39045, + "selectin": 39046, + "ĠLaparoscopic": 39047, + "HLA": 39048, + "ĠYale": 39049, + "spread": 39050, + "ETS": 39051, + "Ġglycans": 39052, + "Ġimmigrant": 39053, + "Donald": 39054, + "ĠCASE": 39055, + "ĠHII": 39056, + "glomer": 39057, + "Ġïĥİ": 39058, + "ĠExperiences": 39059, + "ĠVietnamese": 39060, + "Hodgkin": 39061, + "oader": 39062, + "heart": 39063, + "Ġremedy": 39064, + "Ġfacilitators": 39065, + "openhagen": 39066, + "dodec": 39067, + "ĠFriend": 39068, + "ĠTouch": 39069, + "arms": 39070, + "CRs": 39071, + "Ġultrahigh": 39072, + "ĠDriver": 39073, + "GEMENTS": 39074, + "ĠOu": 39075, + "Ġendocarditis": 39076, + "Ġautoencoder": 39077, + "Ġich": 39078, + "Ġfetch": 39079, + "urian": 39080, + "ĠORFs": 39081, + "Ġpermeabilized": 39082, + "ĠWiFi": 39083, + "ĠLithuan": 39084, + "Structure": 39085, + "Ln": 39086, + "houses": 39087, + "Ġought": 39088, + "ĠConcluding": 39089, + "Ġanniversary": 39090, + "ĠCreation": 39091, + "Ġblindness": 39092, + "ĠpcDNA": 39093, + "ĠSusan": 39094, + "ĠBenjamini": 39095, + "ĠSentence": 39096, + "Ġsnd": 39097, + "Ġfins": 39098, + "phis": 39099, + "ĠModules": 39100, + "Ġneuropsychiatric": 39101, + "ĠPotassium": 39102, + "Ġsacrifice": 39103, + "Ġdyspnea": 39104, + "Ġdeliberately": 39105, + "omegaly": 39106, + "Media": 39107, + "Temporal": 39108, + "Ġshark": 39109, + "SCAN": 39110, + "splitting": 39111, + "Ġmisuse": 39112, + "Ġbirefringence": 39113, + "ĠÖĴâĨĴ": 39114, + "Ġpier": 39115, + "Ġnurs": 39116, + "ĠSclerosis": 39117, + "adhy": 39118, + "Ġundetermined": 39119, + "Ġcomplementation": 39120, + "ĠAffect": 39121, + "ĠHamps": 39122, + "Ġgob": 39123, + "ĠFate": 39124, + "ĠHAL": 39125, + "ĠKiss": 39126, + "Ġmicrobe": 39127, + "Ġcarbonaceous": 39128, + "Ġliposome": 39129, + "ĠUsage": 39130, + "Ġquasiparticles": 39131, + "Ġcasp": 39132, + "ĠNarrow": 39133, + "Ġoutlook": 39134, + "ĠChord": 39135, + "Ġclaiming": 39136, + "Ġdiverging": 39137, + "ĠBioinformatics": 39138, + "ĠPsychiatric": 39139, + "ĠMasters": 39140, + "Ġllvm": 39141, + "ĠIQR": 39142, + "phases": 39143, + "ĠThy": 39144, + "erger": 39145, + "ĠDipl": 39146, + "SFR": 39147, + "Ġcredited": 39148, + "ĠTetra": 39149, + "âĭ¯": 39150, + "Ġamniotic": 39151, + "ĠCharlotte": 39152, + "Cox": 39153, + "Hard": 39154, + "article": 39155, + "ĠDEA": 39156, + "ĠEclipse": 39157, + "ĠLMP": 39158, + "Ġimprison": 39159, + "ĠVarying": 39160, + "ESCs": 39161, + "ĠTHEO": 39162, + "Ġnervosa": 39163, + "Ġprecedes": 39164, + "Ġgyro": 39165, + "ĠWORDS": 39166, + "ĠDakota": 39167, + "utory": 39168, + "ĠEmer": 39169, + "adam": 39170, + "ĠNah": 39171, + "ĠVirgo": 39172, + "Setting": 39173, + "PQ": 39174, + "å®": 39175, + "erus": 39176, + "Ġcep": 39177, + "Ġbd": 39178, + "dier": 39179, + "Ġimbalanced": 39180, + "Ġtimestep": 39181, + "än": 39182, + "ĠRabbit": 39183, + "Ġhamsters": 39184, + "Ġmedulla": 39185, + "ĠChromatography": 39186, + "INPUT": 39187, + "Ġlossy": 39188, + "Pseud": 39189, + "ĠPBL": 39190, + "ĠDomestic": 39191, + "iau": 39192, + "ancell": 39193, + "Ġmultilayers": 39194, + "Ġsubsidi": 39195, + "ĠUtilizing": 39196, + "tune": 39197, + "rehend": 39198, + "arte": 39199, + "Ġburs": 39200, + "ĠNHE": 39201, + "Ġcloseness": 39202, + "ĠColour": 39203, + "ĠHomo": 39204, + "Equations": 39205, + "Ġsutures": 39206, + "acus": 39207, + "Ġknocked": 39208, + "Ġsecretary": 39209, + "Ġascertained": 39210, + "Ġinpatients": 39211, + "irts": 39212, + "Ġplut": 39213, + "ansson": 39214, + "rami": 39215, + "Ġosteotomy": 39216, + "ĠPrimers": 39217, + "ĠLegislative": 39218, + "ĠCardiology": 39219, + "Ġadmitting": 39220, + "Ġexcavation": 39221, + "ĠHedgehog": 39222, + "WG": 39223, + "frozen": 39224, + "Ġliber": 39225, + "ĠICE": 39226, + "chosen": 39227, + "ĠKohn": 39228, + "Stop": 39229, + "Phil": 39230, + "phagia": 39231, + "ĠBCA": 39232, + "Ġempt": 39233, + "Ġzz": 39234, + "opers": 39235, + "ĠSixty": 39236, + "eckman": 39237, + "Ġtransferrin": 39238, + "Ġpenalized": 39239, + "Being": 39240, + "Ġextruded": 39241, + "Ġminiature": 39242, + "Ġeditorial": 39243, + "Ġinterconnect": 39244, + "gro": 39245, + "kv": 39246, + "olen": 39247, + "ĠSYSTEMS": 39248, + "ĠColonel": 39249, + "ĠMediated": 39250, + "ĠEMD": 39251, + "Ġknife": 39252, + "Ġcytogenetic": 39253, + "Ġdigitized": 39254, + "abinoids": 39255, + "arterial": 39256, + "Ġdiarrhoea": 39257, + "bag": 39258, + "Ġbuccal": 39259, + "stay": 39260, + "ĠLAMP": 39261, + "oko": 39262, + "ĠPolyt": 39263, + "masked": 39264, + "ĠTunable": 39265, + "Ġcoagul": 39266, + "paras": 39267, + "Ġterminating": 39268, + "ICAg": 39269, + "ĠExcellence": 39270, + "Ġregurgitation": 39271, + "DQUFD": 39272, + "Jack": 39273, + "Ġapertures": 39274, + "ĠIp": 39275, + "ĠHCMV": 39276, + "ĠGom": 39277, + "Ġnucleophilic": 39278, + "Ġparenteral": 39279, + "TIM": 39280, + "oine": 39281, + "ĠnT": 39282, + "ĠSense": 39283, + "ĠFocal": 39284, + "ranges": 39285, + "Ġhept": 39286, + "ĠPlat": 39287, + "Ġmyx": 39288, + "Ġcodebook": 39289, + "Expl": 39290, + "ĠRhoA": 39291, + "Ġrhinitis": 39292, + "ĠErratum": 39293, + "Oriented": 39294, + "Well": 39295, + "doping": 39296, + "Ġbup": 39297, + "ĠImpedance": 39298, + "Ġsubstitutes": 39299, + "actorily": 39300, + "Ġcollaborations": 39301, + "ĠWayne": 39302, + "Ġvowels": 39303, + "ĠShadow": 39304, + "Ġphenology": 39305, + "Ġconcurrency": 39306, + "having": 39307, + "ĠCES": 39308, + "ĠFIN": 39309, + "ĠLoh": 39310, + "oxa": 39311, + "ĠAlN": 39312, + "ĠAlvarez": 39313, + "instit": 39314, + "Ġgermplasm": 39315, + "ĠBoliv": 39316, + "ĠRCP": 39317, + "assador": 39318, + "Ġesp": 39319, + "Ġphenotyping": 39320, + "Ġskipping": 39321, + "ĠFractal": 39322, + "ĠPEDOT": 39323, + "wake": 39324, + "ĠFIT": 39325, + "ĠESD": 39326, + "ĠAntif": 39327, + "ubiquitin": 39328, + "ĠAerial": 39329, + "ĠPrognosis": 39330, + "ĠRELATED": 39331, + "Ġstratigraphy": 39332, + "vatron": 39333, + "ĠPROPERTIES": 39334, + "Ġicon": 39335, + "isers": 39336, + "Ġwal": 39337, + "Ġstamp": 39338, + "ĠOptimum": 39339, + "Ġoligomeric": 39340, + "Ġinnerv": 39341, + "YA": 39342, + "Abcam": 39343, + "Ġvials": 39344, + "ĠGrig": 39345, + "Ġunaware": 39346, + "Ġopera": 39347, + "ĠWarner": 39348, + "Ġprotonated": 39349, + "ĠDRG": 39350, + "Ġtroubles": 39351, + "Ġpropositional": 39352, + "ĠAfghanistan": 39353, + "ĠHampshire": 39354, + "Gd": 39355, + "lung": 39356, + "Ġaviation": 39357, + "Ġapartment": 39358, + "Ġinfusions": 39359, + "Ġbroilers": 39360, + "ĠDisability": 39361, + "ĠRobots": 39362, + "Ġdebugging": 39363, + "ĠìĿ": 39364, + "Wilson": 39365, + "uprofen": 39366, + "obarbital": 39367, + "JB": 39368, + "isance": 39369, + "itizer": 39370, + "MIS": 39371, + "ĠARF": 39372, + "Ġprostheses": 39373, + "Ġdichloromethane": 39374, + "mCherry": 39375, + "ĠSSS": 39376, + "ĠLPA": 39377, + "SCF": 39378, + "attract": 39379, + "Ġcalibrations": 39380, + "Ġfibril": 39381, + "Ġhaploid": 39382, + "usalem": 39383, + "ĠNut": 39384, + "Ġdeut": 39385, + "chronic": 39386, + "NAP": 39387, + "ĠCytokines": 39388, + "rageen": 39389, + "ĠCategories": 39390, + "rains": 39391, + "Ġsummands": 39392, + "Ġproliferate": 39393, + "rylov": 39394, + "Ġpleasure": 39395, + "Ġdensit": 39396, + "ĠSURVE": 39397, + "HIP": 39398, + "hall": 39399, + "ĠFUS": 39400, + "Ġwasting": 39401, + "ERY": 39402, + "Ġstatins": 39403, + "Ġeastward": 39404, + "sometimes": 39405, + "Ġwrapping": 39406, + "ĠTWO": 39407, + "vine": 39408, + "Ġsacchar": 39409, + "Ġamateur": 39410, + "ĠÃĽ": 39411, + "Ġmyster": 39412, + "ĠMyo": 39413, + "Ġrhabd": 39414, + "ĠProtease": 39415, + "Ġcholera": 39416, + "ĠGov": 39417, + "ĠGCC": 39418, + "Ġclays": 39419, + "transmission": 39420, + "ĠHollywood": 39421, + "Ġxenob": 39422, + "FLOAT": 39423, + "Ġascent": 39424, + "Ġsharks": 39425, + "Ġinterferes": 39426, + "ĠFormer": 39427, + "ĠHartmann": 39428, + "sha": 39429, + "ĠSave": 39430, + "Ġparks": 39431, + "ĠVenn": 39432, + "Ġunions": 39433, + "Ġdiscour": 39434, + "Ġsuperlattices": 39435, + "Ġcoupler": 39436, + "proteins": 39437, + "ĠStationary": 39438, + "ĠEthernet": 39439, + "ĠFréchet": 39440, + "Ġkines": 39441, + "Ġjazz": 39442, + "Asn": 39443, + "Ġextensional": 39444, + "Ġtelomeres": 39445, + "Ġpermitting": 39446, + "Ġexhausted": 39447, + "ĠSphing": 39448, + "Turn": 39449, + "mind": 39450, + "Ġsf": 39451, + "ĠHak": 39452, + "ranolol": 39453, + "portation": 39454, + "Consistent": 39455, + "Ġventilated": 39456, + "ĠDISTRIB": 39457, + "Ġtelling": 39458, + "Ġmannose": 39459, + "ÃŃaz": 39460, + "Ġborne": 39461, + "Ġintensification": 39462, + "Ġenjoyed": 39463, + "ĠBruno": 39464, + "ĠSaturday": 39465, + "Ġcocycle": 39466, + "itate": 39467, + "Ġgolf": 39468, + "approved": 39469, + "ĠNikol": 39470, + "itri": 39471, + "ĠSentiment": 39472, + "Ġglow": 39473, + "Ġgyp": 39474, + "ĠPCT": 39475, + "aber": 39476, + "ĠWis": 39477, + "porum": 39478, + "Ġhyphae": 39479, + "feas": 39480, + "ĠTraits": 39481, + "ĠConflicts": 39482, + "degrading": 39483, + "Raman": 39484, + "pharmac": 39485, + "Ġimmunocyt": 39486, + "ĠBlake": 39487, + "Ġpseudoc": 39488, + "ĠCharacterisation": 39489, + "ĠGalileo": 39490, + "Enabl": 39491, + "Jy": 39492, + "Ġclav": 39493, + "Ġϳ": 39494, + "Ġcommunicated": 39495, + "eutical": 39496, + "Ġnanotechnology": 39497, + "ĠHassan": 39498, + "ĠTec": 39499, + "Ġhanging": 39500, + "ĠBSD": 39501, + "ĠContour": 39502, + "Ġfragility": 39503, + "Ġdisruptions": 39504, + "Ġfiniteness": 39505, + "ĠPhilippine": 39506, + "nicity": 39507, + "Ùĩ": 39508, + "ĠCrim": 39509, + "ĠCNF": 39510, + "ĠISI": 39511, + "adapter": 39512, + "ĠUCP": 39513, + "Ġtextured": 39514, + "AAV": 39515, + "keto": 39516, + "Np": 39517, + "counting": 39518, + "hynchus": 39519, + "Ġprosec": 39520, + "ĠAnnot": 39521, + "ĠHarbor": 39522, + "degrees": 39523, + "akar": 39524, + "ĠVik": 39525, + "bfd": 39526, + "Ġdrip": 39527, + "ĠCaucas": 39528, + "Ġtrench": 39529, + "Ġweed": 39530, + "Ġdistractor": 39531, + "genetic": 39532, + "specifically": 39533, + "ulfite": 39534, + "ĠConsistently": 39535, + "Ġbreakfast": 39536, + "Ġbullet": 39537, + "Ġlegisl": 39538, + "ĠTraumatic": 39539, + "Ġcollectors": 39540, + "ĠBullet": 39541, + "ĠMYB": 39542, + "ĠPink": 39543, + "versive": 39544, + "ĠAttem": 39545, + "Ġculturally": 39546, + "Bell": 39547, + "undef": 39548, + "vii": 39549, + "Ġhistocompatibility": 39550, + "letcher": 39551, + "ĠStef": 39552, + "Amp": 39553, + "ĠRid": 39554, + "ĠEucl": 39555, + "Ġdecryption": 39556, + "ĠSpencer": 39557, + "ĠBitcoin": 39558, + "wic": 39559, + "Ġcomplicate": 39560, + "ĠProposal": 39561, + "ĠÄĪ": 39562, + "aviruses": 39563, + "ĠFay": 39564, + "ĠRd": 39565, + "ĠGale": 39566, + "ĠMetastasis": 39567, + "ĠImprovements": 39568, + "©": 39569, + "Ġpolyester": 39570, + "Ġstratospheric": 39571, + "ĠSAH": 39572, + "Ġamphip": 39573, + "ĠAFP": 39574, + "ĠHair": 39575, + "ĠEPI": 39576, + "ĠUltrast": 39577, + "Ġâĭ¯": 39578, + "Ġgapless": 39579, + "Ham": 39580, + "etto": 39581, + "Ġthreonine": 39582, + "ĠECO": 39583, + "Ġia": 39584, + "Ġundist": 39585, + "Ġradiology": 39586, + "Ġsuperlattice": 39587, + "ibraries": 39588, + "Ġturbid": 39589, + "ĠPotentials": 39590, + "ĠPipeline": 39591, + "Ġwarfarin": 39592, + "WISE": 39593, + "ĠLid": 39594, + "Ġrecurring": 39595, + "ĠMono": 39596, + "ĠGovern": 39597, + "ĠAwareness": 39598, + "olab": 39599, + "iflora": 39600, + "stris": 39601, + "INDEX": 39602, + "ĠDementia": 39603, + "Does": 39604, + "wright": 39605, + "Íī": 39606, + "Ġsb": 39607, + "ĠDOM": 39608, + "ĠHBsAg": 39609, + "clinic": 39610, + "ĠExped": 39611, + "Ġproteas": 39612, + "Ġsterilization": 39613, + "ĠBanerjee": 39614, + "ĠPersonnel": 39615, + "âĮĭ": 39616, + "onephritis": 39617, + "omite": 39618, + "ĠCCF": 39619, + "ositi": 39620, + "ĠEucalyptus": 39621, + "ĠIsotope": 39622, + "coli": 39623, + "possibility": 39624, + "Ġstrontium": 39625, + "Ġraref": 39626, + "ĠInterstellar": 39627, + "kinin": 39628, + "ylethanol": 39629, + "JT": 39630, + "north": 39631, + "Ġcensored": 39632, + "istive": 39633, + "Ġnoticing": 39634, + "Ġshipping": 39635, + "Embed": 39636, + "Observ": 39637, + "Ġzeolites": 39638, + "ubit": 39639, + "Ġflaps": 39640, + "Ġdrifts": 39641, + "Ġtherapist": 39642, + "Ġpollination": 39643, + "aliplatin": 39644, + "Johnson": 39645, + "Ġimperfections": 39646, + "NY": 39647, + "Ġthalamic": 39648, + "ocarb": 39649, + "ozotocin": 39650, + "Ġtetramer": 39651, + "Plas": 39652, + "Ġmultichannel": 39653, + "ĠInsight": 39654, + "opods": 39655, + "ĠNacional": 39656, + "Ġimatinib": 39657, + "actual": 39658, + "ĠXOR": 39659, + "Ġblight": 39660, + "ĠLeading": 39661, + "amese": 39662, + "ĠAmplitude": 39663, + "ĠMonitor": 39664, + "ĠNeurological": 39665, + "propagating": 39666, + "Ġpaddle": 39667, + "ĠHarvest": 39668, + "Ġodont": 39669, + "BUF": 39670, + "Ġtactics": 39671, + "ĠAnisotropy": 39672, + "adip": 39673, + "ĠAlpine": 39674, + "Ġfeels": 39675, + "Ġmedieval": 39676, + "Ġelucidation": 39677, + "Ġheterotrophic": 39678, + "Ġrelaxing": 39679, + "Ġhappiness": 39680, + "ĠCytotoxicity": 39681, + "ĠRANKL": 39682, + "Walker": 39683, + "mig": 39684, + "ĠSSL": 39685, + "ĠSepsis": 39686, + "ĠGes": 39687, + "Ġhydrochloric": 39688, + "Ġclarification": 39689, + "Ġdisparate": 39690, + "tested": 39691, + "Ġdatap": 39692, + "Ġnovels": 39693, + "ĠMicroc": 39694, + "ál": 39695, + "ĠARC": 39696, + "ĠYangtze": 39697, + "etomidine": 39698, + "ĠMatrigel": 39699, + "ihilation": 39700, + "ĠcDNAs": 39701, + "Ġprostat": 39702, + "ĠRailroad": 39703, + "UBLE": 39704, + "ĠPARTIC": 39705, + "ĠSax": 39706, + "Ġinsecurity": 39707, + "Ġcrushed": 39708, + "Ġhalves": 39709, + "giant": 39710, + "ĠCroatia": 39711, + "icyclo": 39712, + "ĠUnexpected": 39713, + "Ġloneliness": 39714, + "anu": 39715, + "Ġchampions": 39716, + "uberculosis": 39717, + "Ġequi": 39718, + "Ġaccreted": 39719, + "Ġinvading": 39720, + "Ġafferents": 39721, + "Ġalternation": 39722, + "Ġkinet": 39723, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 39724, + "ĠMAGNET": 39725, + "ĠFIFA": 39726, + "zadeh": 39727, + "iphenyl": 39728, + "ĠKro": 39729, + "ĠEvaluate": 39730, + "illiant": 39731, + "curvature": 39732, + "ĠPierce": 39733, + "better": 39734, + "nos": 39735, + "à¥": 39736, + "ĠKCN": 39737, + "ĠStrand": 39738, + "caemic": 39739, + "ĠHoechst": 39740, + "ĠEXT": 39741, + "ĠLLVM": 39742, + "BZ": 39743, + "tgt": 39744, + "ondialdehyde": 39745, + "ĠEvid": 39746, + "ĠGul": 39747, + "Ġmultiplications": 39748, + "Ġauth": 39749, + "ĠAustral": 39750, + "Ġstaying": 39751, + "ĠGlutamate": 39752, + "Ġstray": 39753, + "ĠISA": 39754, + "Ġlowland": 39755, + "Ġparallels": 39756, + "Ġattractiveness": 39757, + "Ġelectrospinning": 39758, + "Ġportrayed": 39759, + "ospecific": 39760, + "folate": 39761, + "Ġcoeff": 39762, + "ĠEstrogen": 39763, + "tumour": 39764, + "Ġhysterectomy": 39765, + "Ġinositol": 39766, + "ĠBaz": 39767, + "istein": 39768, + "Ġcrucially": 39769, + "Ġdinoflag": 39770, + "ÍĶÍĴ": 39771, + "ĠDragon": 39772, + "ĠSpor": 39773, + "ĠMater": 39774, + "ĠHero": 39775, + "plicing": 39776, + "ĠANT": 39777, + "ĠFormic": 39778, + "Queue": 39779, + "ocarcinomas": 39780, + "UPS": 39781, + "ĠPc": 39782, + "encoders": 39783, + "Ġinvaded": 39784, + "ĠPhases": 39785, + "Ġpostmortem": 39786, + "Ġslows": 39787, + "ĠMcL": 39788, + "ĠVerma": 39789, + "ĠViability": 39790, + "Ġcompensating": 39791, + "Ġclamped": 39792, + "jm": 39793, + "ĠRiv": 39794, + "upon": 39795, + "ĠDickinson": 39796, + "initiated": 39797, + "Ġsider": 39798, + "ĠSelen": 39799, + "ĠAka": 39800, + "idelberg": 39801, + "Ġqualifying": 39802, + "Ġenforcing": 39803, + "otrophs": 39804, + "ĠSNAP": 39805, + "Ġrust": 39806, + "imburs": 39807, + "Ġimmunocompromised": 39808, + "ĠFleming": 39809, + "Ġlizards": 39810, + "dialysis": 39811, + "ĠUnivariate": 39812, + "Ġgasoline": 39813, + "Ġtenure": 39814, + "Ġsustaining": 39815, + "Ġmotone": 39816, + "bay": 39817, + "wani": 39818, + "orestation": 39819, + "ĠXII": 39820, + "Ġradiofrequency": 39821, + "ĠGuided": 39822, + "Individual": 39823, + "ĠSpectrometer": 39824, + "ĠGoing": 39825, + "ĠMartins": 39826, + "Approxim": 39827, + "amak": 39828, + "ĠâĪı": 39829, + "ĠOmn": 39830, + "Ġoutpatients": 39831, + "Ġhyperbol": 39832, + "ĠPerceptual": 39833, + "ĠBurke": 39834, + "Boltzmann": 39835, + "ĠMd": 39836, + "Ġpaw": 39837, + "ĠCathedral": 39838, + "Ġhyaluron": 39839, + "Ġbrachial": 39840, + "Ġaflatoxin": 39841, + "imo": 39842, + "Ġenrol": 39843, + "Ġdetonation": 39844, + "Ġoverly": 39845, + "thest": 39846, + "Ġsecondly": 39847, + "ĠSchiz": 39848, + "ĠIGFBP": 39849, + "atechin": 39850, + "Ġsaves": 39851, + "tiers": 39852, + "ĠBates": 39853, + "Ġalliance": 39854, + "Ġattri": 39855, + "Ġastro": 39856, + "ĠPathological": 39857, + "Ġgambiae": 39858, + "Park": 39859, + "idable": 39860, + "ĠNil": 39861, + "ĠJas": 39862, + "Ġneeding": 39863, + "meier": 39864, + "Ġferroptosis": 39865, + "ĠGuidance": 39866, + "AZ": 39867, + "iol": 39868, + "Ġacknowledg": 39869, + "exual": 39870, + "Ġmenopause": 39871, + "Ġadjunct": 39872, + "capture": 39873, + "ĠDeputy": 39874, + "Ġbial": 39875, + "ifa": 39876, + "ĠChitosan": 39877, + "ĠTopics": 39878, + "ĠPlasmid": 39879, + "calculations": 39880, + "give": 39881, + "responders": 39882, + "ulla": 39883, + "ĠMoreno": 39884, + "Ġcommentary": 39885, + "ĠMahm": 39886, + "": 39887, + "onacci": 39888, + "ĠCould": 39889, + "ĠTRP": 39890, + "seconds": 39891, + "GraphPad": 39892, + "Little": 39893, + "hey": 39894, + "Ġalike": 39895, + "ĠDias": 39896, + "aroo": 39897, + "Ġı": 39898, + "Ġtaxes": 39899, + "phenanth": 39900, + "ĠCheung": 39901, + "ĠPiet": 39902, + "Df": 39903, + "GU": 39904, + "mectin": 39905, + "zee": 39906, + "Ġdλ": 39907, + "Ġsyntheses": 39908, + "ĠáĪ": 39909, + "Simulation": 39910, + "ĠEleven": 39911, + "worms": 39912, + "lymphocyte": 39913, + "Ġhaemorrhage": 39914, + "ĠOwn": 39915, + "ĠKant": 39916, + "Ġoverse": 39917, + "Ġideation": 39918, + "ĠHarper": 39919, + "Acknowledgments": 39920, + "vili": 39921, + "yna": 39922, + "ĠRecurrence": 39923, + "oza": 39924, + "Ġhenceforth": 39925, + "zees": 39926, + "Ġquasic": 39927, + "Ġchoroidal": 39928, + "Ġantimalarial": 39929, + "Ġcoarsening": 39930, + "Deb": 39931, + "diam": 39932, + "ĠWeights": 39933, + "Ġbuying": 39934, + "Ġmessaging": 39935, + "February": 39936, + "Extended": 39937, + "ĠRossi": 39938, + "Ġmistaken": 39939, + "Ġutero": 39940, + "jas": 39941, + "icitis": 39942, + "ĠTidal": 39943, + "Ġpharyngeal": 39944, + "click": 39945, + "Ġmyo": 39946, + "knock": 39947, + "Ġprominence": 39948, + "Ġamphiphilic": 39949, + "corn": 39950, + "Ġonboard": 39951, + "ĠDud": 39952, + "ĠWoman": 39953, + "ĠOutbreak": 39954, + "Ġpreferably": 39955, + "Ġsketches": 39956, + "Sat": 39957, + "fixing": 39958, + "ĠMey": 39959, + "ĠLetters": 39960, + "ITIES": 39961, + "ĠSDP": 39962, + "ĠLNCaP": 39963, + "DX": 39964, + "Fluor": 39965, + "Rv": 39966, + "Sect": 39967, + "ĠIons": 39968, + "Ġtrachom": 39969, + "Ġultrastructure": 39970, + "qvist": 39971, + "rophe": 39972, + "Ġreceipt": 39973, + "ĠQuint": 39974, + "Ġswapping": 39975, + "aminidase": 39976, + "Ġarchival": 39977, + "ĠCreating": 39978, + "ĠBarton": 39979, + "diagnosed": 39980, + "atological": 39981, + "olph": 39982, + "ĠPFA": 39983, + "ĠLAP": 39984, + "Ġunphysical": 39985, + "eqn": 39986, + "Ġquartiles": 39987, + "olytica": 39988, + "ĠFreed": 39989, + "Ġventilator": 39990, + "Ġkaryotype": 39991, + "Sta": 39992, + "still": 39993, + "ĠTate": 39994, + "urability": 39995, + "ĠGron": 39996, + "Ġtrimer": 39997, + "IPA": 39998, + "adeca": 39999, + "ĠImplementing": 40000, + "sity": 40001, + "itr": 40002, + "Ġbom": 40003, + "Ġnonrelativistic": 40004, + "Ġmicelle": 40005, + "ĠAdminist": 40006, + "Ġelectrolysis": 40007, + "harmon": 40008, + "OLOGICAL": 40009, + "Liter": 40010, + "ĠGUI": 40011, + "ĠQL": 40012, + "months": 40013, + "Ġsuperflu": 40014, + "cuts": 40015, + "Ġelicits": 40016, + "Ġmultiplexed": 40017, + "overlap": 40018, + "Ġcadaver": 40019, + "Ġou": 40020, + "ĠSheng": 40021, + "erea": 40022, + "ĠNBC": 40023, + "Ġdeter": 40024, + "tyrosine": 40025, + "ĠParts": 40026, + "Ġessay": 40027, + "kas": 40028, + "itted": 40029, + "ĠPZT": 40030, + "essler": 40031, + "Ġsimulators": 40032, + "Ġradiating": 40033, + "cutting": 40034, + "ĠCalculating": 40035, + "THER": 40036, + "ĠROCK": 40037, + "communic": 40038, + "Ġbonus": 40039, + "ĠCPA": 40040, + "ĠPUR": 40041, + "ulton": 40042, + "ĠZhi": 40043, + "Ġcaloric": 40044, + "Ġinterpolate": 40045, + "ĠSecretion": 40046, + "Ġneurocognitive": 40047, + "Ġgadolinium": 40048, + "frequencies": 40049, + "ĠTract": 40050, + "Ġminimax": 40051, + "ĠBrock": 40052, + "rypsin": 40053, + "ĠResonant": 40054, + "ĠACKNOWLEDGEMENTS": 40055, + "Dom": 40056, + "Ġholotype": 40057, + "Special": 40058, + "Ġimmunoreactive": 40059, + "ARNING": 40060, + "Panel": 40061, + "ĠJohannes": 40062, + "RFP": 40063, + "zzi": 40064, + "ĠPomer": 40065, + "Ġtransects": 40066, + "Ġpoured": 40067, + "EDs": 40068, + "ĠCircum": 40069, + "Ġabnormally": 40070, + "ĠPunj": 40071, + "Gol": 40072, + "Hop": 40073, + "Hex": 40074, + "ILE": 40075, + "Ġsourced": 40076, + "oclase": 40077, + "protobuf": 40078, + "Ġfrogs": 40079, + "ĠOttawa": 40080, + "Ġbiogeochemical": 40081, + "Ġlentivirus": 40082, + "Young": 40083, + "ĠIPS": 40084, + "assen": 40085, + "Ġunrestricted": 40086, + "Ġmatplotlib": 40087, + "Ġchloramphenicol": 40088, + "ĠContextual": 40089, + "ĠHawaiian": 40090, + "Legend": 40091, + "Sparse": 40092, + "bore": 40093, + "gaussian": 40094, + "uke": 40095, + "Ġâ̰": 40096, + "retest": 40097, + "SSE": 40098, + "preting": 40099, + "ĠPanama": 40100, + "ĠBroadband": 40101, + "conjugate": 40102, + "Bytes": 40103, + "GSH": 40104, + "Uns": 40105, + "rina": 40106, + "Ġdrained": 40107, + "Ġscap": 40108, + "Ġinvested": 40109, + "Ġsatisfactorily": 40110, + "Ġherbivores": 40111, + "Ġarachidonic": 40112, + "ymetrix": 40113, + "Ġnect": 40114, + "Ġconges": 40115, + "ĠMerr": 40116, + "ĠMai": 40117, + "Chain": 40118, + "Ġretrieving": 40119, + "Collection": 40120, + "ĠMTX": 40121, + "ĠFernando": 40122, + "hg": 40123, + "ĠRams": 40124, + "thresh": 40125, + "apsules": 40126, + "Ġconduit": 40127, + "swap": 40128, + "Ġblowing": 40129, + "ĠNyquist": 40130, + "Ġunconscious": 40131, + "ĠDIFFERENT": 40132, + "Techn": 40133, + "hiz": 40134, + "îĤ": 40135, + "Ġdξ": 40136, + "ĠSto": 40137, + "ĠFlavon": 40138, + "David": 40139, + "Ġfiltrate": 40140, + "lith": 40141, + "ĠWool": 40142, + "ĠKnot": 40143, + "Ġhalide": 40144, + "Ġbioassay": 40145, + "ĠGoldberg": 40146, + "ĠTrichoderma": 40147, + "Ġintraspecific": 40148, + "crystall": 40149, + "ĠRend": 40150, + "ourg": 40151, + "Ġundertake": 40152, + "ĠEnum": 40153, + "infect": 40154, + "Ġmidgut": 40155, + "attack": 40156, + "ĠCircle": 40157, + "Ġpleiotropic": 40158, + "escent": 40159, + "ĠFri": 40160, + "philis": 40161, + "astings": 40162, + "Ġbiogas": 40163, + "ĠÄľ": 40164, + "Ġaccompany": 40165, + "Ġrolled": 40166, + "Ġchirp": 40167, + "Ġsomatostatin": 40168, + "varkappa": 40169, + "Scal": 40170, + "Ġdrow": 40171, + "romed": 40172, + "ĠLup": 40173, + "ĠLuminosity": 40174, + "ĠNig": 40175, + "ferromagnetic": 40176, + "ĠToy": 40177, + "Ġcannabinoid": 40178, + "ĠHOX": 40179, + "iele": 40180, + "ĠCTX": 40181, + "Ġhydrop": 40182, + "Ġfavorite": 40183, + "Ġstretches": 40184, + "evaluated": 40185, + "ogroups": 40186, + "acal": 40187, + "ollo": 40188, + "Ġgenders": 40189, + "ĠGraft": 40190, + "Ġincidences": 40191, + "Ġreplacements": 40192, + "ĠTRUNC": 40193, + "CRF": 40194, + "Ġequalization": 40195, + "ĠRenew": 40196, + "Ġplethora": 40197, + "ĠEncoder": 40198, + "Mit": 40199, + "Ġcaches": 40200, + "orate": 40201, + "endors": 40202, + "ĠCaution": 40203, + "ĠAbel": 40204, + "compression": 40205, + "ĠLarsen": 40206, + "ĠElimination": 40207, + "Ġtester": 40208, + "Ġninth": 40209, + "ĠLö": 40210, + "Ġspiders": 40211, + "Ġpoem": 40212, + "Ġeducators": 40213, + "ĠEnhances": 40214, + "destructive": 40215, + "Fourier": 40216, + "Ġseismicity": 40217, + "ĠYunnan": 40218, + "Riemannian": 40219, + "WID": 40220, + "vular": 40221, + "ĠBorder": 40222, + "Ġcombin": 40223, + "singlet": 40224, + "ĠEddington": 40225, + "ĠTemplate": 40226, + "ĠPAX": 40227, + "Ġbasalts": 40228, + "Enh": 40229, + "Ġassistants": 40230, + "ĠCascade": 40231, + "Ġinbreeding": 40232, + "chini": 40233, + "Ġupgraded": 40234, + "ĠTransit": 40235, + "survival": 40236, + "Ġinjector": 40237, + "ĠPascal": 40238, + "DEVICE": 40239, + "Ġfost": 40240, + "ĠKand": 40241, + "Ġextragalactic": 40242, + "ependently": 40243, + "Ġexcite": 40244, + "Ġfulfil": 40245, + "Ġriparian": 40246, + "Ġuploaded": 40247, + "aun": 40248, + "lod": 40249, + "saving": 40250, + "ĠHib": 40251, + "ĠEra": 40252, + "obese": 40253, + "Ġui": 40254, + "Ġspectrally": 40255, + "keV": 40256, + "xxx": 40257, + "ĠOtto": 40258, + "Ġétale": 40259, + "LAT": 40260, + "dermal": 40261, + "diaz": 40262, + "ĠPli": 40263, + "Ġlegume": 40264, + "Ġinspect": 40265, + "Ġthymic": 40266, + "ĠHormone": 40267, + "áĢ": 40268, + "inot": 40269, + "ĠShib": 40270, + "ĠBCC": 40271, + "ĠVital": 40272, + "Ġprofits": 40273, + "ĠFederated": 40274, + "Ġflipped": 40275, + "Ġproprietary": 40276, + "incorporated": 40277, + "Ġbacteremia": 40278, + "Ġáŀĩ": 40279, + "fins": 40280, + "ä½": 40281, + "esia": 40282, + "ĠHollow": 40283, + "geons": 40284, + "Ġtrehalose": 40285, + "ERO": 40286, + "osterol": 40287, + "omus": 40288, + "ĠCrystall": 40289, + "Ġcuration": 40290, + "Ġmagnon": 40291, + "ĠAmend": 40292, + "Ġharb": 40293, + "Ġneutrality": 40294, + "ĠDelphi": 40295, + "Ġnonsense": 40296, + "ĠHomeostasis": 40297, + "Ġexpenditures": 40298, + "Sequential": 40299, + "imodular": 40300, + "Ġzenith": 40301, + "ĠMoran": 40302, + "Ġbootstrapping": 40303, + "iomy": 40304, + "lactic": 40305, + "iture": 40306, + "Ġnat": 40307, + "Ġgab": 40308, + "Ġchat": 40309, + "regional": 40310, + "Ġcrashes": 40311, + "ĠAFB": 40312, + "Ġcrowded": 40313, + "Ġtweet": 40314, + "engineered": 40315, + "ĠCharged": 40316, + "Sche": 40317, + "ITIONS": 40318, + "ĠCoral": 40319, + "ĠEli": 40320, + "Ġinverting": 40321, + "Ġpedag": 40322, + "ĠSanders": 40323, + "Meanwhile": 40324, + "ĠGriffiths": 40325, + "PSCs": 40326, + "tize": 40327, + "ĠMail": 40328, + "Ġundec": 40329, + "Ġhermitian": 40330, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 40331, + "ĠExplos": 40332, + "Ġwestward": 40333, + "ĠConfirm": 40334, + "Begin": 40335, + "Ġfactories": 40336, + "ĠPRL": 40337, + "shear": 40338, + "Header": 40339, + "ĠFLAGS": 40340, + "anomal": 40341, + "ĠQW": 40342, + "ĠÌħ": 40343, + "oinositi": 40344, + "Ġmammography": 40345, + "Ġdepositional": 40346, + "EXP": 40347, + "residue": 40348, + "Ġunsatisfactory": 40349, + "Aβ": 40350, + "MUX": 40351, + "Ġstaged": 40352, + "ĠMMT": 40353, + "ĠKus": 40354, + "llo": 40355, + "Ġtrainer": 40356, + "adden": 40357, + "Ġpinch": 40358, + "WARE": 40359, + "Ġcabinet": 40360, + "CSP": 40361, + "ecum": 40362, + "oteric": 40363, + "ĠHav": 40364, + "Ġresume": 40365, + "Ġnetworked": 40366, + "share": 40367, + "ĠColle": 40368, + "Ġchemotactic": 40369, + "ĠGlyc": 40370, + "olkit": 40371, + "Ġbotulinum": 40372, + "ĠNeighborhood": 40373, + "mV": 40374, + "ĠHQ": 40375, + "efaciens": 40376, + "gett": 40377, + "Ġgeost": 40378, + "ĠCDW": 40379, + "Ģ̇": 40380, + "Ġfloors": 40381, + "representing": 40382, + "odiode": 40383, + "ĠInstance": 40384, + "Ġmonodis": 40385, + "drying": 40386, + "reasing": 40387, + "igi": 40388, + "Ġgout": 40389, + "ĠIEC": 40390, + "Ġflush": 40391, + "Ġtraded": 40392, + "Review": 40393, + "ĠïĤ¢": 40394, + "Ġà¤": 40395, + "Ġabbreviations": 40396, + "otherapies": 40397, + "Ġindeterminate": 40398, + "Ġglutaraldehyde": 40399, + "Ġattrition": 40400, + "jump": 40401, + "inde": 40402, + "ĠGri": 40403, + "arction": 40404, + "TRAIN": 40405, + "Ġescaped": 40406, + "atement": 40407, + "ĠPam": 40408, + "ĠGAM": 40409, + "productive": 40410, + "ĠAmericas": 40411, + "agenesis": 40412, + "ĠMixtures": 40413, + "ĠHooft": 40414, + "ĠWindow": 40415, + "Ġnodular": 40416, + "Ġechin": 40417, + "DOF": 40418, + "ĠDDT": 40419, + "electrical": 40420, + "ĠDecentralized": 40421, + "Ġcontradict": 40422, + "French": 40423, + "Ġaustr": 40424, + "ĠAPD": 40425, + "ĠDIM": 40426, + "ĠSten": 40427, + "ronomic": 40428, + "ĠPolymorphism": 40429, + "Ġcocc": 40430, + "itings": 40431, + "Ġsubcritical": 40432, + "ĠUniqueness": 40433, + "OPEN": 40434, + "rotoxicity": 40435, + "GenBank": 40436, + "atabases": 40437, + "Nets": 40438, + "uistic": 40439, + "yric": 40440, + "ĠSID": 40441, + "Ġcooked": 40442, + "ĠJudge": 40443, + "Ġparameterizations": 40444, + "Ġenumerated": 40445, + "ĠAsthma": 40446, + "Develop": 40447, + "Ġcake": 40448, + "ĠAges": 40449, + "venile": 40450, + "Ġflor": 40451, + "Ġcouldn": 40452, + "detach": 40453, + "Ġpipette": 40454, + "ĠInstant": 40455, + "Ġtentatively": 40456, + "ĠINTEGR": 40457, + "HQ": 40458, + "Mapping": 40459, + "cq": 40460, + "åĪ": 40461, + "SUM": 40462, + "fractions": 40463, + "ĠClaud": 40464, + "Formula": 40465, + "Axis": 40466, + "ĠBilly": 40467, + "ĠMethane": 40468, + "ĠIGM": 40469, + "cannot": 40470, + "س": 40471, + "Ġciting": 40472, + "ĠDynam": 40473, + "Ġlett": 40474, + "egler": 40475, + "ĠPhysicians": 40476, + "xFF": 40477, + "Ġoyster": 40478, + "ĠTOC": 40479, + "Ġsubarachnoid": 40480, + "ĠCOM": 40481, + "ITER": 40482, + "Ġbenzodiazep": 40483, + "Ġuncomplicated": 40484, + "tillo": 40485, + "Carbon": 40486, + "atem": 40487, + "Ġsel": 40488, + "ingo": 40489, + "IVITY": 40490, + "Ġcavern": 40491, + "Ġspacelike": 40492, + "ĠCollisions": 40493, + "straint": 40494, + "Ġmycobacterial": 40495, + "Ġtrachomatis": 40496, + "Ai": 40497, + "mf": 40498, + "ĠTric": 40499, + "tico": 40500, + "ĠElection": 40501, + "ĠKDM": 40502, + "ĠExosomes": 40503, + "afluor": 40504, + "Ġformalized": 40505, + "ĠELF": 40506, + "vphantom": 40507, + "ĠSME": 40508, + "ichuan": 40509, + "ĠVMs": 40510, + "Ġrostral": 40511, + "ofer": 40512, + "ramanian": 40513, + "intercal": 40514, + "Merck": 40515, + "ĠFerguson": 40516, + "HU": 40517, + "lj": 40518, + "Ġrack": 40519, + "Ġmicrograph": 40520, + "CTS": 40521, + "Ġpassively": 40522, + "ĠMasses": 40523, + "rangians": 40524, + "ĠADM": 40525, + "ĠProvided": 40526, + "ĠVeterans": 40527, + "sound": 40528, + "gex": 40529, + "ĠSpiral": 40530, + "Ġfossa": 40531, + "Ġthermodynamically": 40532, + "ĠStaining": 40533, + "Ġkar": 40534, + "eflon": 40535, + "ĠBruns": 40536, + "VAE": 40537, + "olyticus": 40538, + "Ġintranasal": 40539, + "ĠProspects": 40540, + "athers": 40541, + "Ġnumbering": 40542, + "ĠReplacement": 40543, + "ĠNoncommutative": 40544, + "quisitions": 40545, + "ĠSIMD": 40546, + "ĠArterial": 40547, + "ĠHGF": 40548, + "ĠGPP": 40549, + "Ġfluvial": 40550, + "neri": 40551, + "ĠCompressed": 40552, + "VIDIA": 40553, + "Ġshocked": 40554, + "dys": 40555, + "invariance": 40556, + "enstein": 40557, + "ĠSCM": 40558, + "ĠDod": 40559, + "Ġsho": 40560, + "Chlor": 40561, + "duino": 40562, + "Ġurgently": 40563, + "soc": 40564, + "etching": 40565, + "Ġdiffractive": 40566, + "ĠZF": 40567, + "Ġhyperplanes": 40568, + "Ġmyri": 40569, + "Ġferromagnetism": 40570, + "filament": 40571, + "Ġjustifies": 40572, + "fault": 40573, + "ĠHTS": 40574, + "ĠEPC": 40575, + "too": 40576, + "ĠTRAP": 40577, + "ión": 40578, + "rv": 40579, + "ĠBPD": 40580, + "ĠNod": 40581, + "posit": 40582, + "ĠConvers": 40583, + "Ġzeroes": 40584, + "ĠGlen": 40585, + "ĠDisturb": 40586, + "Ġtableau": 40587, + "Ġpseudot": 40588, + "ĠCollider": 40589, + "Ġadsorbents": 40590, + "ĠGrove": 40591, + "Ġkingdom": 40592, + "Est": 40593, + "Xs": 40594, + "czyk": 40595, + "ĠWille": 40596, + "ĠVOL": 40597, + "scar": 40598, + "ĠAdler": 40599, + "ĠOrchestra": 40600, + "Ġsparsely": 40601, + "glycosylation": 40602, + "Lac": 40603, + "otions": 40604, + "ĠIle": 40605, + "Ġbeacon": 40606, + "ĠRn": 40607, + "ullah": 40608, + "Ġtimelike": 40609, + "ĠForests": 40610, + "Ġupload": 40611, + "jit": 40612, + "ĠEDM": 40613, + "Ġtransplants": 40614, + "marker": 40615, + "ĠBreeding": 40616, + "ÎĶÎĶ": 40617, + "Ġfavorably": 40618, + "ĠTransformations": 40619, + "abeled": 40620, + "ĠPolitics": 40621, + "episode": 40622, + "Ġfut": 40623, + "Ġdithi": 40624, + "ĠMw": 40625, + "Ġtranspiration": 40626, + "Ġunlimited": 40627, + "ĠAntiv": 40628, + "PPV": 40629, + "Ġnomogram": 40630, + "Ġinvented": 40631, + "ĠSchedule": 40632, + "allows": 40633, + "Ġtransvers": 40634, + "Ġworkpiece": 40635, + "blacksquare": 40636, + "callback": 40637, + "ĠAthletic": 40638, + "hans": 40639, + "poles": 40640, + "Ġeavesdrop": 40641, + "ĠCone": 40642, + "oclim": 40643, + "ĠGhost": 40644, + "iterations": 40645, + "Ġweaken": 40646, + "Ġalkaloid": 40647, + "Ġveterans": 40648, + "Ġprostatectomy": 40649, + "Ġbog": 40650, + "ĠCed": 40651, + "ĠFever": 40652, + "ylan": 40653, + "archive": 40654, + "Ġattackers": 40655, + "Making": 40656, + "bane": 40657, + "ĠPull": 40658, + "ĠFLO": 40659, + "Ġcoaches": 40660, + "ĠVSM": 40661, + "okh": 40662, + "ĠSpo": 40663, + "amilial": 40664, + "principle": 40665, + "Ġaggressiveness": 40666, + "Ġgardens": 40667, + "SIG": 40668, + "Ġmbar": 40669, + ".....": 40670, + "Ġoptimizes": 40671, + "Ġmorphologic": 40672, + "hani": 40673, + "Ġgermanium": 40674, + "Enabled": 40675, + "wb": 40676, + "Ġforamen": 40677, + "ĠSPA": 40678, + "Ġmagnified": 40679, + "ĠSlater": 40680, + "ĠSyrian": 40681, + "Ġtert": 40682, + "Ġtraditions": 40683, + "Ġoffensive": 40684, + "Ġhydrology": 40685, + "ergetics": 40686, + "Phot": 40687, + "Ġperovskites": 40688, + "Ġwavenumbers": 40689, + "Ġosteoclasts": 40690, + "imedean": 40691, + "ĠBasketball": 40692, + "benzodiox": 40693, + "ĠTRUNCATED": 40694, + "Ġbishop": 40695, + "ĠSgr": 40696, + "ĠSatisfaction": 40697, + "agnostic": 40698, + "numeric": 40699, + "Ġnormals": 40700, + "Ġhumor": 40701, + "Ġcontinents": 40702, + "NATION": 40703, + "Lagrangian": 40704, + "Ġknees": 40705, + "ĠIDE": 40706, + "adas": 40707, + "adia": 40708, + "ĠOU": 40709, + "onds": 40710, + "ĠChaud": 40711, + "Ġslicing": 40712, + "ĠActor": 40713, + "Alt": 40714, + "Ġbroadcasts": 40715, + "osaurs": 40716, + "Ġpickle": 40717, + "Ġunfamiliar": 40718, + "allus": 40719, + "Ġhashing": 40720, + "incidence": 40721, + "Ġmetabolized": 40722, + "Ġhomogeneously": 40723, + "ĠFalcon": 40724, + "ĠÑģ": 40725, + "ĠCere": 40726, + "ĠCLA": 40727, + "ĠPaste": 40728, + "ĠFry": 40729, + "ĠDre": 40730, + "adult": 40731, + "Ġdiscounted": 40732, + "sensitized": 40733, + "erculous": 40734, + "ĠPixel": 40735, + "ĠBram": 40736, + "allo": 40737, + "ipative": 40738, + "ipality": 40739, + "ĠStrict": 40740, + "ĠTrinity": 40741, + "ĠClassifiers": 40742, + "ĠBasel": 40743, + "ĠCurcumin": 40744, + "ĠLUMO": 40745, + "Ġmediastinal": 40746, + "ĠFFA": 40747, + "Ġplenty": 40748, + "prised": 40749, + "Ġprinter": 40750, + "Ġcalcare": 40751, + "insn": 40752, + "ontology": 40753, + "Ġgrounding": 40754, + "ĠALDH": 40755, + "STRING": 40756, + "ĠFemales": 40757, + "ĠFocusing": 40758, + "assessment": 40759, + "ĠBluetooth": 40760, + "ëĬĶ": 40761, + "Ġego": 40762, + "ĠDAC": 40763, + "onding": 40764, + "randa": 40765, + "ĠLudwig": 40766, + "Ġantecedent": 40767, + "ĠErnst": 40768, + "dX": 40769, + "odeling": 40770, + "âĢĭ": 40771, + "Inser": 40772, + "ognormal": 40773, + "ĠTevatron": 40774, + "Ġcovariances": 40775, + "riging": 40776, + "ĠMgSO": 40777, + "carbonitrile": 40778, + "ĠLoren": 40779, + "Ġpolytopes": 40780, + "ĠParental": 40781, + "Ġunhealthy": 40782, + "itherto": 40783, + "ĠMotif": 40784, + "DataType": 40785, + "ĠMIPS": 40786, + "ĠPhosphorus": 40787, + "MoO": 40788, + "ĠPerturbations": 40789, + "Ġaphids": 40790, + "Ġanhydride": 40791, + "ideration": 40792, + "ĠMits": 40793, + "gravit": 40794, + "Ġdestinations": 40795, + "Commun": 40796, + "Ġtetrahedron": 40797, + "implicit": 40798, + "Ġassort": 40799, + "ĠSubt": 40800, + "ĠAcetyl": 40801, + "ecium": 40802, + "ĠNie": 40803, + "Ġoperand": 40804, + "ĠScher": 40805, + "azoles": 40806, + "tlement": 40807, + "ĠBlocking": 40808, + "Ġbottlenecks": 40809, + "ĠOccupational": 40810, + "HAS": 40811, + "Teller": 40812, + "Ġvague": 40813, + "esting": 40814, + "SNE": 40815, + "Ġphotoionization": 40816, + "Ġcomplaint": 40817, + "uspid": 40818, + "owler": 40819, + "Ġendocytic": 40820, + "Ġflocks": 40821, + "epsin": 40822, + "colors": 40823, + "otopes": 40824, + "ĠDepletion": 40825, + "ELLAR": 40826, + "armed": 40827, + "ĠTIR": 40828, + "Ġbullying": 40829, + "Ġâݧ": 40830, + "osporidium": 40831, + "Mr": 40832, + "ĠCic": 40833, + "ogal": 40834, + "Ġsectioned": 40835, + "Chapter": 40836, + "ĠContents": 40837, + "ĠPaths": 40838, + "ĠExplain": 40839, + "computing": 40840, + "Ġshrub": 40841, + "ĠMalaysian": 40842, + "Beta": 40843, + "Mad": 40844, + "Ros": 40845, + "Ġeyel": 40846, + "ĠACF": 40847, + "ĠMm": 40848, + "texture": 40849, + "Ġinterpretability": 40850, + "ĠTopic": 40851, + "Ġbadly": 40852, + "ĠmAh": 40853, + "Eg": 40854, + "RQ": 40855, + "pins": 40856, + "etc": 40857, + "ĠVogel": 40858, + "Ġhypoc": 40859, + "Ġrunaway": 40860, + "Ġpersonally": 40861, + "Ġbinders": 40862, + "sensory": 40863, + "ĠIPv": 40864, + "ranked": 40865, + "Ġfibrations": 40866, + "Ġstrawberry": 40867, + "arotomy": 40868, + "FLI": 40869, + "rator": 40870, + "odys": 40871, + "iran": 40872, + "ĠBead": 40873, + "ĠDAM": 40874, + "âĪĥ": 40875, + "Ġillusion": 40876, + "pidium": 40877, + "Place": 40878, + "Region": 40879, + "Ġallocations": 40880, + "Ġohmic": 40881, + "Ġnf": 40882, + "imino": 40883, + "ĠBris": 40884, + "itizing": 40885, + "proper": 40886, + "subgroup": 40887, + "Ġsalience": 40888, + "rangement": 40889, + "ĠMeaning": 40890, + "Ġbarcode": 40891, + "Ġneuropeptide": 40892, + "Ġendosperm": 40893, + "imab": 40894, + "Ġnanod": 40895, + "ĠMetz": 40896, + "Ġcollocation": 40897, + "ĠInfected": 40898, + "Ġpackaged": 40899, + "ĠADA": 40900, + "ĠBarth": 40901, + "ĠCNC": 40902, + "Ġcascaded": 40903, + "ĠStockholm": 40904, + "itas": 40905, + "ĠMMR": 40906, + "ĠDrought": 40907, + "Ġpermissible": 40908, + "Ġsciatic": 40909, + "Ġfringes": 40910, + "Ġexecutable": 40911, + "Ġstemness": 40912, + "ĠEndoscopic": 40913, + "aporin": 40914, + "TOP": 40915, + "eB": 40916, + "tur": 40917, + "ĠStages": 40918, + "anches": 40919, + "Ġnonperturbative": 40920, + "Ġmaritime": 40921, + "Ġcoverslips": 40922, + "Ġlagoon": 40923, + "Experiments": 40924, + "Ġcodewords": 40925, + "Encoder": 40926, + "das": 40927, + "prac": 40928, + "Ġpaddy": 40929, + "Ġdraining": 40930, + "Ġkids": 40931, + "Ġenemies": 40932, + "Ġmotile": 40933, + "Ġslack": 40934, + "bees": 40935, + "ĠSuppl": 40936, + "ĠBarber": 40937, + "ĠSPH": 40938, + "Ġcrystallite": 40939, + "fitted": 40940, + "cyclopent": 40941, + "ĠRMSD": 40942, + "Ġparkinson": 40943, + "Ġuncorrected": 40944, + "ĠSyntax": 40945, + "Ġmultinomial": 40946, + "ĠIncorporating": 40947, + "akrishnan": 40948, + "JL": 40949, + "NESS": 40950, + "mim": 40951, + "ĠTET": 40952, + "ĠPorph": 40953, + "ĠSchwe": 40954, + "Ġcatalogs": 40955, + "ĠAuthentication": 40956, + "Bro": 40957, + "ugar": 40958, + "ĠAmpl": 40959, + "Ġsapiens": 40960, + "ĠGANs": 40961, + "Ġnecessitates": 40962, + "tg": 40963, + "edal": 40964, + "ĠRear": 40965, + "opeptidase": 40966, + "ĠInformed": 40967, + "Ġtailor": 40968, + "ĠNNLO": 40969, + "Ġhemodynamics": 40970, + "Sy": 40971, + "dating": 40972, + "achers": 40973, + "ĠTodd": 40974, + "ĠMario": 40975, + "ĠUGT": 40976, + "ĠValent": 40977, + "Ġstreamlines": 40978, + "Ġwarrants": 40979, + "Ġrew": 40980, + "ĠMud": 40981, + "ĠGK": 40982, + "Ġcoke": 40983, + "ĠUran": 40984, + "Ġgrooves": 40985, + "ronate": 40986, + "ĠRadius": 40987, + "ĠSuite": 40988, + "atumoral": 40989, + "Workspace": 40990, + "ĠSynergistic": 40991, + "ĠAtherosclerosis": 40992, + "maz": 40993, + "argmax": 40994, + "shield": 40995, + "ontin": 40996, + "Ġlistener": 40997, + "ocytoma": 40998, + "ĠGrav": 40999, + "ĠJerusalem": 41000, + "pyrrolidin": 41001, + "ĠSprings": 41002, + "Ġseafloor": 41003, + "Ġdips": 41004, + "istani": 41005, + "obis": 41006, + "Ġphotodynamic": 41007, + "ADO": 41008, + "Ġionisation": 41009, + "Ġbarn": 41010, + "igenetics": 41011, + "Ġeconomies": 41012, + "ĠGlacier": 41013, + "Ġç": 41014, + "imes": 41015, + "ĠSasaki": 41016, + "chio": 41017, + "Ġassisting": 41018, + "ostin": 41019, + "Ġindiff": 41020, + "ĠShot": 41021, + "ĠNeuron": 41022, + "CDD": 41023, + "ĠCONST": 41024, + "ĠBSs": 41025, + "tas": 41026, + "association": 41027, + "parg": 41028, + "Ġescal": 41029, + "exercise": 41030, + "ĠAdela": 41031, + "Ġmyogenic": 41032, + "ĠNOAA": 41033, + "yclo": 41034, + "linal": 41035, + "ĠHut": 41036, + "Ġintroductory": 41037, + "Ġheterochromatin": 41038, + "Ġchemoresistance": 41039, + "Ġsimplifications": 41040, + "pyridin": 41041, + "Ġamyloidosis": 41042, + "Ġnanofiber": 41043, + "ĠSutton": 41044, + "ĠResilience": 41045, + "Parent": 41046, + "ĠLMS": 41047, + "Ġlets": 41048, + "ĠElderly": 41049, + "Ġirrevers": 41050, + "sheets": 41051, + "Effects": 41052, + "Ġellipses": 41053, + "ĠPhilosophy": 41054, + "Ġphotographic": 41055, + "HEAD": 41056, + "Ġreversibility": 41057, + "Ġfederated": 41058, + "ĠPhosphoserine": 41059, + "estimation": 41060, + "ĠHumph": 41061, + "Json": 41062, + "Ġfills": 41063, + "Ġverm": 41064, + "ĠSeif": 41065, + "withstanding": 41066, + "ĠYamada": 41067, + "eria": 41068, + "ĠFLA": 41069, + "ĠVick": 41070, + "toid": 41071, + "annier": 41072, + "Ġcancerous": 41073, + "PRINT": 41074, + "ĠMechanistic": 41075, + "Ġdusty": 41076, + "ĠAppend": 41077, + "ycin": 41078, + "Ġazo": 41079, + "Ġsizing": 41080, + "Ġcrayfish": 41081, + "avis": 41082, + "ĠAdvent": 41083, + "ĠCommunist": 41084, + "ĠIMU": 41085, + "pixels": 41086, + "Hall": 41087, + "past": 41088, + "ĠRous": 41089, + "ressional": 41090, + "aird": 41091, + "ĠADD": 41092, + "Ġsummarizing": 41093, + "Ġelectrol": 41094, + "Station": 41095, + "ĠLyα": 41096, + "ĠTMEM": 41097, + "Ġpeptidase": 41098, + "Dual": 41099, + "git": 41100, + "ĠBOD": 41101, + "ĠRham": 41102, + "ĠKak": 41103, + "Ġreadiness": 41104, + "ĠCompare": 41105, + "ĠRamos": 41106, + "significantly": 41107, + "Ġhairy": 41108, + "Ġvasopressin": 41109, + "ĠGuideline": 41110, + "BNP": 41111, + "Ġdirty": 41112, + "Ġinfimum": 41113, + "ĠAless": 41114, + "ĠVolcano": 41115, + "Magn": 41116, + "YY": 41117, + "ughlin": 41118, + "boplatin": 41119, + "ĠCantor": 41120, + "Ġclothes": 41121, + "ĠRw": 41122, + "Ġuseless": 41123, + "ĠKdV": 41124, + "operoxidase": 41125, + "ĠCorrect": 41126, + "Ġfatality": 41127, + "ĠRestriction": 41128, + "Computer": 41129, + "Department": 41130, + "Il": 41131, + "gang": 41132, + "ĠElectroc": 41133, + "obaric": 41134, + "Phen": 41135, + "Ġned": 41136, + "adh": 41137, + "issing": 41138, + "tooth": 41139, + "Ġmanuscripts": 41140, + "Ġbiotechnology": 41141, + "Supp": 41142, + "ĠPairwise": 41143, + "ĠParsing": 41144, + "dH": 41145, + "melt": 41146, + "rz": 41147, + "ĠCatalyst": 41148, + "emption": 41149, + "Ġshowers": 41150, + "BLEM": 41151, + "ĠBrothers": 41152, + "banon": 41153, + "Ġbrachy": 41154, + "metallicity": 41155, + "ĠCIS": 41156, + "ĠCopenhagen": 41157, + "Ġelder": 41158, + "Ġfinanc": 41159, + "odesic": 41160, + "Ġdevise": 41161, + "Ġsurvives": 41162, + "ĠðtÃŀ": 41163, + "Ġfascinating": 41164, + "Ġparallax": 41165, + "HOR": 41166, + "yth": 41167, + "onins": 41168, + "ĠEHR": 41169, + "ĠGates": 41170, + "obase": 41171, + "ĠConway": 41172, + "operations": 41173, + "manuel": 41174, + "ĠAbdominal": 41175, + "ĠARG": 41176, + "ĠGrö": 41177, + "Ġphotosens": 41178, + "ĠCoulter": 41179, + "ĠJulian": 41180, + "ĠLevine": 41181, + "Ġsarcoidosis": 41182, + "Ġpillars": 41183, + "ĠdR": 41184, + "ĠWG": 41185, + "Ġspeculation": 41186, + "anski": 41187, + "ĠGaussians": 41188, + "Schw": 41189, + "ĠNambu": 41190, + "parents": 41191, + "intrinsic": 41192, + "ĠKendall": 41193, + "ĠRg": 41194, + "Ġprototypical": 41195, + "brella": 41196, + "Ġtetrap": 41197, + "ĠPathophys": 41198, + "nmt": 41199, + "Ġergodicity": 41200, + "ĠYersinia": 41201, + "QO": 41202, + "ĠIAV": 41203, + "Ġchocolate": 41204, + "Ġconferring": 41205, + "ClNO": 41206, + "otia": 41207, + "Complete": 41208, + "ĠAMPA": 41209, + "ïĢŃ": 41210, + "Ġḡ": 41211, + "ĠiPSCs": 41212, + "ĠApparently": 41213, + "Ġintoxication": 41214, + "ĠFather": 41215, + "percent": 41216, + "Ġshaker": 41217, + "Ġfinancing": 41218, + "Ġgenitalia": 41219, + "members": 41220, + "Ġstagnation": 41221, + "hmatic": 41222, + "rored": 41223, + "Ġconidia": 41224, + "ataloader": 41225, + "ĠNeil": 41226, + "Ġliteratures": 41227, + "ĠGlc": 41228, + "ĠDevelopments": 41229, + "differentiation": 41230, + "ĠRevisited": 41231, + "nil": 41232, + "tom": 41233, + "diol": 41234, + "ĠAbell": 41235, + "Ġplastics": 41236, + "ĠLuke": 41237, + "adjacent": 41238, + "ĠBHs": 41239, + "ĠPositioning": 41240, + "ør": 41241, + "overexpressing": 41242, + "Ec": 41243, + "Pref": 41244, + "orting": 41245, + "Ġinch": 41246, + "ĠCatherine": 41247, + "ĠDMP": 41248, + "ĠOe": 41249, + "endothelial": 41250, + "ICES": 41251, + "ĠHadron": 41252, + "Ġrevisit": 41253, + "ĠPictures": 41254, + "ĠKnockdown": 41255, + "ilian": 41256, + "ĠALA": 41257, + "opamine": 41258, + "ĠLah": 41259, + "climate": 41260, + "Ġdistraction": 41261, + "arski": 41262, + "ĠAccount": 41263, + "reflex": 41264, + "Ġelongate": 41265, + "ĠAmbient": 41266, + "Cx": 41267, + "Machine": 41268, + "ĠJPEG": 41269, + "Ġclassifies": 41270, + "ĠRegulate": 41271, + "oplasia": 41272, + "injury": 41273, + "neighbors": 41274, + "ĠFORMATION": 41275, + "FIS": 41276, + "Sz": 41277, + "ĠOSC": 41278, + "Ġassembling": 41279, + "Ġintracerebral": 41280, + "supers": 41281, + "ĠpF": 41282, + "Ġheal": 41283, + "ĠVries": 41284, + "arche": 41285, + "ĠDecom": 41286, + "ĠDiffic": 41287, + "agenta": 41288, + "ĠSpirit": 41289, + "ĠIntersection": 41290, + "Ġendorsed": 41291, + "ĠNobel": 41292, + "iÏī": 41293, + "wu": 41294, + "phaly": 41295, + "Ġqueu": 41296, + "ĠForum": 41297, + "lander": 41298, + "Ġspectrometric": 41299, + "ĠHankel": 41300, + "ĠCSE": 41301, + "Ġresumed": 41302, + "ĠGRE": 41303, + "ACES": 41304, + "CTA": 41305, + "Ġbehaved": 41306, + "Monitor": 41307, + "ĠNikon": 41308, + "ĠCHARACTER": 41309, + "ĠSAL": 41310, + "ĠIch": 41311, + "ĠHSF": 41312, + "Ġgenotoxic": 41313, + "ificance": 41314, + "ĠChiang": 41315, + "ĠZen": 41316, + "ĠArrows": 41317, + "ĠPDT": 41318, + "ĠFLASH": 41319, + "ocortex": 41320, + "onstructing": 41321, + "Treatment": 41322, + "ĉĠĠĠĠĠĠ": 41323, + "edullary": 41324, + "ilty": 41325, + "indentation": 41326, + "Ġamended": 41327, + "Ġfled": 41328, + "rophication": 41329, + "ĠGLM": 41330, + "ĠOpera": 41331, + "HLH": 41332, + "Lite": 41333, + "bmod": 41334, + "Ġaversion": 41335, + "ĠSweet": 41336, + "Ġstreptavidin": 41337, + "ĠPairs": 41338, + "ugos": 41339, + "epoxy": 41340, + "Ġunspecified": 41341, + "Ġmicrochannel": 41342, + "ĠVictorian": 41343, + "Could": 41344, + "informed": 41345, + "Ġsits": 41346, + "Ġrx": 41347, + "Ġnep": 41348, + "touch": 41349, + "ROI": 41350, + "Ġheaders": 41351, + "flush": 41352, + "ĠPathogenic": 41353, + "Ġspacings": 41354, + "hetti": 41355, + "Ġmotivating": 41356, + "Ġstakeholder": 41357, + "ĠSymbolic": 41358, + "ĠCrani": 41359, + "Ġdispute": 41360, + "Ġassists": 41361, + "indler": 41362, + "ĠSpatio": 41363, + "ohm": 41364, + "Ġextrapolate": 41365, + "Ġelaboration": 41366, + "ĠGTPases": 41367, + "Ġcellulase": 41368, + "ĠTuc": 41369, + "olide": 41370, + "ĠAIM": 41371, + "plast": 41372, + "ĠBible": 41373, + "opoly": 41374, + "ubo": 41375, + "acean": 41376, + "ĠPenrose": 41377, + "ĠMapReduce": 41378, + "ĠKwon": 41379, + "Wall": 41380, + "Ġgcd": 41381, + "ĠArbitrary": 41382, + "Product": 41383, + "ĠGitHub": 41384, + "Fn": 41385, + "Ġck": 41386, + "ĠAus": 41387, + "Ġgrave": 41388, + "Ġmetabolically": 41389, + "Ġlisten": 41390, + "Ġextractions": 41391, + "ĠTrunc": 41392, + "ĠRadiology": 41393, + "conserving": 41394, + "ĠCompositional": 41395, + "reporting": 41396, + "sustain": 41397, + "îĢ": 41398, + "ĠObl": 41399, + "ĠkN": 41400, + "Stre": 41401, + "ĠSupergravity": 41402, + "ĠPVP": 41403, + "Ġcivilian": 41404, + "ĠTunnel": 41405, + "Ġhelicopter": 41406, + "ĠCambrian": 41407, + "Ġrg": 41408, + "ĠUPF": 41409, + "Ġpolyd": 41410, + "Ġobservability": 41411, + "container": 41412, + "nitros": 41413, + "ĠCutting": 41414, + "Ġdecouple": 41415, + "Ġcarboxy": 41416, + "crow": 41417, + "Ġcx": 41418, + "ĠKell": 41419, + "Ġprojectors": 41420, + "Ġmyocarditis": 41421, + "itoneum": 41422, + "conditioning": 41423, + "ĠETH": 41424, + "oyama": 41425, + "Ġphosphates": 41426, + "ĠSubjective": 41427, + "ĠSerre": 41428, + "Ġcollagenase": 41429, + "Ġvibrating": 41430, + "streptomycin": 41431, + "zhen": 41432, + "Ġcres": 41433, + "Ġcull": 41434, + "Ġhaven": 41435, + "ĠGPL": 41436, + "lessness": 41437, + "Ġviewpoints": 41438, + ",,,": 41439, + "ochromic": 41440, + "uyama": 41441, + "Ġpartnerships": 41442, + "LICENSE": 41443, + "ĠSIFT": 41444, + "resources": 41445, + "ĠGos": 41446, + "ivac": 41447, + "Ġneurogenic": 41448, + "Adj": 41449, + "Ġaquifers": 41450, + "ĠGlycos": 41451, + "ĠMatthews": 41452, + "ĠFriday": 41453, + "ĠpX": 41454, + "Ġante": 41455, + "ĠFenton": 41456, + "ĠEukary": 41457, + "ibal": 41458, + "ideae": 41459, + "Attention": 41460, + "ĠPolymerization": 41461, + "Ġflipping": 41462, + "ĠMediates": 41463, + "Ġstationarity": 41464, + "Ġechoes": 41465, + "alidomide": 41466, + "Ġdelineation": 41467, + "Ġnaval": 41468, + "ĠSomatic": 41469, + "Ġstub": 41470, + "ĠBever": 41471, + "ĠRiz": 41472, + "Ġresummation": 41473, + "Ġassault": 41474, + "Ġpreexisting": 41475, + "Ġhypermethylation": 41476, + "Ġconserving": 41477, + "recorded": 41478, + "amn": 41479, + "Ġchow": 41480, + "ĠKill": 41481, + "ĠProduced": 41482, + "Ġrefs": 41483, + "ĠEnzymes": 41484, + "Ġdeepest": 41485, + "&&&": 41486, + "ĠFRP": 41487, + "Ġmilieu": 41488, + "Ġirrigated": 41489, + "ĠAnatomical": 41490, + "Ġdissect": 41491, + "iliensis": 41492, + "razolo": 41493, + "ĠProbable": 41494, + "solve": 41495, + "confirmed": 41496, + "ohydrodynamic": 41497, + "library": 41498, + "ĠCiti": 41499, + "ĠPHA": 41500, + "itsky": 41501, + "Ġelectrone": 41502, + "naive": 41503, + "Ġribs": 41504, + "ĠPhotonic": 41505, + "Ġsubstantia": 41506, + "ĠESTIM": 41507, + "Ġduodenum": 41508, + "ĠChagas": 41509, + "ĠSURVEY": 41510, + "Press": 41511, + "bian": 41512, + "¤": 41513, + "hei": 41514, + "ĠVAR": 41515, + "Ġcolocalization": 41516, + "Ġpolyl": 41517, + "Ġdough": 41518, + "Ġmicrocontroller": 41519, + "Ġinternalized": 41520, + "Ġdischarging": 41521, + "ĠChlamydomonas": 41522, + "orad": 41523, + "itel": 41524, + "ĠWend": 41525, + "Ġlogits": 41526, + "Ġelectrocataly": 41527, + "ĠAmar": 41528, + "Ġappreciably": 41529, + "Ġneurotransmitters": 41530, + "formerly": 41531, + "cul": 41532, + "rata": 41533, + "ĠBalk": 41534, + "ĠOsm": 41535, + "Ġsymptomatology": 41536, + "ĠFIELD": 41537, + "ĠAPs": 41538, + "Ġgymn": 41539, + "ĠMMS": 41540, + "Ġrefresh": 41541, + "ĠIndices": 41542, + "Ġimplantable": 41543, + "shuffle": 41544, + "ĠHeath": 41545, + "Ġcrisp": 41546, + "Ġdissertation": 41547, + "ĠUlt": 41548, + "Description": 41549, + "ĠOriginally": 41550, + "ĠFn": 41551, + "ĠFLOW": 41552, + "ubility": 41553, + "Ġexams": 41554, + "ĠShor": 41555, + "ĠCdTe": 41556, + "psycho": 41557, + "Ġdictates": 41558, + "Ġparenchymal": 41559, + "ĠPretreatment": 41560, + "Ġremembered": 41561, + "Ġbras": 41562, + "otid": 41563, + "Ġrecommender": 41564, + "Ġflesh": 41565, + "pitch": 41566, + "inist": 41567, + "Ġbtitle": 41568, + "Ġlc": 41569, + "assigned": 41570, + "ĠAdvisory": 41571, + "ĠGeneva": 41572, + "weighting": 41573, + "ĠABTS": 41574, + "Ġhexagon": 41575, + "ovskite": 41576, + "ĠAPIs": 41577, + "Ġbolometric": 41578, + "Ġmultifaceted": 41579, + "iak": 41580, + "Ġtn": 41581, + "ĠBibli": 41582, + "prosy": 41583, + "ĠJama": 41584, + "Ġinfrastructures": 41585, + "ĠShare": 41586, + "Ġintrogression": 41587, + "transforms": 41588, + "Report": 41589, + "ĠTRANS": 41590, + "ĠEXP": 41591, + "ĠCBT": 41592, + "ĠUbiquitin": 41593, + "ĠThickness": 41594, + "pub": 41595, + "toxin": 41596, + "ĠFriction": 41597, + "ĠLAG": 41598, + "mails": 41599, + "ĠImmediately": 41600, + "Ġweakest": 41601, + "ĠMRS": 41602, + "Ġcalcareous": 41603, + "bath": 41604, + "Ġcg": 41605, + "urational": 41606, + "tero": 41607, + "ĠInoue": 41608, + "Ġinstructor": 41609, + "acceptor": 41610, + "ĠEvolving": 41611, + "ĠLuther": 41612, + "Ġresigned": 41613, + "ĠChond": 41614, + "ERF": 41615, + "Ġselector": 41616, + "Ġnewspapers": 41617, + "GRA": 41618, + "Spe": 41619, + "VH": 41620, + "rA": 41621, + "otine": 41622, + "ĠFACT": 41623, + "composition": 41624, + "riding": 41625, + "PCM": 41626, + "Ġmiddleware": 41627, + "ĠGRP": 41628, + "Phosph": 41629, + "ĠEPIC": 41630, + "speech": 41631, + "vortex": 41632, + "ĠHerschel": 41633, + "amis": 41634, + "otube": 41635, + "ĠGomez": 41636, + "comyc": 41637, + "ĠPhyto": 41638, + "ĠConserv": 41639, + "Ġcava": 41640, + "arrhyth": 41641, + "ĠRestricted": 41642, + "ilicity": 41643, + "ogap": 41644, + "CTP": 41645, + "ĠLatino": 41646, + "attenuated": 41647, + "mobility": 41648, + "anen": 41649, + "Ġnif": 41650, + "ĠVideos": 41651, + "ĠSchubert": 41652, + "Features": 41653, + "opropanol": 41654, + "ĠThirdly": 41655, + "atula": 41656, + "ĠCemetery": 41657, + "entist": 41658, + "Ġdeli": 41659, + "trials": 41660, + "Ġgranulation": 41661, + "TTG": 41662, + "Ġteleost": 41663, + "morill": 41664, + "orse": 41665, + "otypically": 41666, + "ĠAbility": 41667, + "Amino": 41668, + "aqueous": 41669, + "ĠpCO": 41670, + "econ": 41671, + "ĠLign": 41672, + "essels": 41673, + "Ġformulating": 41674, + "ĠToo": 41675, + "ĠTranslational": 41676, + "ourses": 41677, + "ubiquit": 41678, + "statistic": 41679, + "Ġbursting": 41680, + "Ġestuaries": 41681, + "ĠNanocom": 41682, + "Ġexports": 41683, + "Ġê": 41684, + "contaminated": 41685, + "Ġtubing": 41686, + "Ġautomobile": 41687, + "Ġmissile": 41688, + "Ġhierarchically": 41689, + "Ġrepairs": 41690, + "ĠImproves": 41691, + "ĠEFFECTS": 41692, + "QDs": 41693, + "roz": 41694, + "aric": 41695, + "Ġparsed": 41696, + "ĠBrink": 41697, + "Ġprogressing": 41698, + "ĠpermNeigh": 41699, + "Agg": 41700, + "ZX": 41701, + "sink": 41702, + "Ġwise": 41703, + "etence": 41704, + "ĠIc": 41705, + "loo": 41706, + "meida": 41707, + "Ġpolariton": 41708, + "ĠConnections": 41709, + "Ġhallmarks": 41710, + "Longrightarrow": 41711, + "Ġtheater": 41712, + "esar": 41713, + "Ġreimburs": 41714, + "Ġlogo": 41715, + "Ġexcreted": 41716, + "ĠNoisy": 41717, + "Ġleaks": 41718, + "ĠDaph": 41719, + "Ġparagraphs": 41720, + "Ġlandslides": 41721, + "Ġpreclude": 41722, + "Ġcoercivity": 41723, + "ĠBurkholderia": 41724, + "ĠGómez": 41725, + "price": 41726, + "Theory": 41727, + "surgery": 41728, + "fname": 41729, + "failure": 41730, + "liness": 41731, + "refer": 41732, + "rique": 41733, + "ĠDogs": 41734, + "ĠEUV": 41735, + "ĠVapor": 41736, + "CSR": 41737, + "Biggl": 41738, + "Constraint": 41739, + "gravitational": 41740, + "Ġcombinatorics": 41741, + "Ġarticulated": 41742, + "ĠBaxter": 41743, + "ĠUltrasonic": 41744, + "LTE": 41745, + "lop": 41746, + "Ġinteratomic": 41747, + "intuitive": 41748, + "simplex": 41749, + "Ġexperimented": 41750, + "organizing": 41751, + "ĠOsaka": 41752, + "hadron": 41753, + "Ġdendrimers": 41754, + "ĠElsevier": 41755, + "CIP": 41756, + "ĠBAP": 41757, + "ĠAlonso": 41758, + "artet": 41759, + "antis": 41760, + "Ġextracorporeal": 41761, + "Ġpowdered": 41762, + "ĠSettings": 41763, + "etallic": 41764, + "ĠTEC": 41765, + "ĠArena": 41766, + "Ġanod": 41767, + "ĠReagents": 41768, + "licenses": 41769, + "ĠRemove": 41770, + "Ġpronunciation": 41771, + "thinspace": 41772, + "ĠClinically": 41773, + "gative": 41774, + "Ġwage": 41775, + "ĠHap": 41776, + "ĠGrac": 41777, + "fft": 41778, + "Ġformate": 41779, + "infeld": 41780, + "ĠQuin": 41781, + "Ġglomerul": 41782, + "Way": 41783, + "Ġkills": 41784, + "Ġtransversely": 41785, + "variation": 41786, + "ennas": 41787, + "ĠPLL": 41788, + "Ġinstrumented": 41789, + "ĠSpark": 41790, + "Ġpillar": 41791, + "ĠCaval": 41792, + "aders": 41793, + "issen": 41794, + "scene": 41795, + "otherm": 41796, + "ées": 41797, + "Ġpracticing": 41798, + "ĠBMSCs": 41799, + "ĠFernandez": 41800, + "Ġbeside": 41801, + "few": 41802, + "ĠCru": 41803, + "Ġprod": 41804, + "anders": 41805, + "azoline": 41806, + "Ġlegislative": 41807, + "balances": 41808, + "URL": 41809, + "Ġstereotactic": 41810, + "Ġtribes": 41811, + "Ġá¹¼": 41812, + "ĠPANI": 41813, + "adreno": 41814, + "gotten": 41815, + "cranial": 41816, + "ĠMick": 41817, + "ĠMMC": 41818, + "adiazole": 41819, + "entiation": 41820, + "ĠGln": 41821, + "ĠHolstein": 41822, + "ĠExplorer": 41823, + "Ġosse": 41824, + "arthy": 41825, + "ĠEVALU": 41826, + "adrenaline": 41827, + "JJ": 41828, + "ĠCMA": 41829, + "ĠInactivation": 41830, + "ABS": 41831, + "ĠSTZ": 41832, + "Configuration": 41833, + "ĠOlfactory": 41834, + "ĠSulfur": 41835, + "symbols": 41836, + "ĠASTM": 41837, + "divergence": 41838, + "ĠOCR": 41839, + "medical": 41840, + "Ġviewer": 41841, + "Ġbombardment": 41842, + "fair": 41843, + "nice": 41844, + "elberg": 41845, + "ĠGPT": 41846, + "ĠKow": 41847, + "Ġphotosphere": 41848, + "Ġlabile": 41849, + "ĠShang": 41850, + "Ġhomotopic": 41851, + "SVD": 41852, + "becomes": 41853, + "Ġgonor": 41854, + "Ġdeuteron": 41855, + "Ġphylogenies": 41856, + "ĠSAF": 41857, + "rapment": 41858, + "ĠCHF": 41859, + "Plan": 41860, + "ĠLegal": 41861, + "ĠFredholm": 41862, + "Ġsharper": 41863, + "Ġnanorib": 41864, + "ĠBuffalo": 41865, + "BMD": 41866, + "Ġlg": 41867, + "osup": 41868, + "ĠOPC": 41869, + "Ġendophytic": 41870, + "ATR": 41871, + "ĠExpressions": 41872, + "ĠMusical": 41873, + "Introduction": 41874, + "ĠSLM": 41875, + "çois": 41876, + "oglycos": 41877, + "aglia": 41878, + "mussen": 41879, + "formans": 41880, + "Ġsubstructures": 41881, + "ympan": 41882, + "hae": 41883, + "shi": 41884, + "ĠInterpret": 41885, + "Ġcatabolic": 41886, + "Ġoccupations": 41887, + "ĠBifurc": 41888, + "Hydroxy": 41889, + "ĠKauf": 41890, + "sleep": 41891, + "amas": 41892, + "ĠSf": 41893, + "ĠMBP": 41894, + "Ġnonalcoholic": 41895, + "Ġdiscordant": 41896, + "Ġepigen": 41897, + "PRI": 41898, + "ĠRedshift": 41899, + "warn": 41900, + "Ġlaptop": 41901, + "Ġabrasive": 41902, + "îĤĿ": 41903, + "lh": 41904, + "ĠKnee": 41905, + "Ġscrat": 41906, + "Ġpoloidal": 41907, + "ĠUniv": 41908, + "omyosin": 41909, + "ĠAugmented": 41910, + "Ġtaxonom": 41911, + "ZrO": 41912, + "Ġphytochemicals": 41913, + "iten": 41914, + "ĠPatterson": 41915, + "thym": 41916, + "dihydropy": 41917, + "Ġky": 41918, + "ĠMetazoa": 41919, + "ALLY": 41920, + "Ġretinoblastoma": 41921, + "concatenate": 41922, + "Male": 41923, + "Ġomission": 41924, + "icher": 41925, + "ĠAzer": 41926, + "opp": 41927, + "pleasant": 41928, + "ningham": 41929, + "Ġaxially": 41930, + "HDFS": 41931, + "Ġfictional": 41932, + "Ï«": 41933, + "Ġnarc": 41934, + "Ġundertook": 41935, + "Ġmicrocirc": 41936, + "ONLY": 41937, + "IVER": 41938, + "ĠCycles": 41939, + "Meas": 41940, + "ĠGriffin": 41941, + "ĠPliocene": 41942, + "ĠpI": 41943, + "ĠAviation": 41944, + "ĠCategorical": 41945, + "ĠNils": 41946, + "Ġresidency": 41947, + "ĠLaur": 41948, + "Ġprefers": 41949, + "ĠassertEquals": 41950, + "Ġliquor": 41951, + "dM": 41952, + "osperm": 41953, + "ĠFUT": 41954, + "AlO": 41955, + "Ġcytometer": 41956, + "Ġstabilizers": 41957, + "Ġpremium": 41958, + "Serial": 41959, + "ĠWalking": 41960, + "íķľ": 41961, + "Ġconfronted": 41962, + "encapsulated": 41963, + "Card": 41964, + "ĠSeeds": 41965, + "abular": 41966, + "ukov": 41967, + "Listener": 41968, + "Choose": 41969, + "ĠSjö": 41970, + "Make": 41971, + "Ġisoc": 41972, + "amount": 41973, + "ATC": 41974, + "ija": 41975, + "Ġsulcus": 41976, + "ĠMöbius": 41977, + "ĠPrenatal": 41978, + "Ġß": 41979, + "Ġisochron": 41980, + "Ġbeans": 41981, + "ĠDens": 41982, + "ĠWelling": 41983, + "ĠOman": 41984, + "Stats": 41985, + "ĠValid": 41986, + "ĠReward": 41987, + "GK": 41988, + "Ġâ©": 41989, + "Ġelectroporation": 41990, + "ĠSNRs": 41991, + "Ġgarlic": 41992, + "ĠParticipant": 41993, + "ĠSplitting": 41994, + "ĠMeteorological": 41995, + "morillonite": 41996, + "Ġoedema": 41997, + "ĠDots": 41998, + "ĠClare": 41999, + "Ġstarter": 42000, + "Ġclimatology": 42001, + "Ġcommence": 42002, + "Ġfallen": 42003, + "ĠAuNPs": 42004, + "attrs": 42005, + "Ġconsultant": 42006, + "twisted": 42007, + "Solving": 42008, + "Ġcoercive": 42009, + "ë¡ľ": 42010, + "Kar": 42011, + "Ġstit": 42012, + "ĠSSB": 42013, + "ĠIW": 42014, + "Ġcanvas": 42015, + "pyruvate": 42016, + "methylsulfanyl": 42017, + "Ġastrophysics": 42018, + "ĠTraditionally": 42019, + "Ġexcitonic": 42020, + "wear": 42021, + "ĠTin": 42022, + "rosh": 42023, + "ĠClient": 42024, + "ĠCorrections": 42025, + "ĠPopular": 42026, + "ĠLiquids": 42027, + "finder": 42028, + "Ġstran": 42029, + "pline": 42030, + "orella": 42031, + "Ġincur": 42032, + "Ġarche": 42033, + "Ġmedically": 42034, + "Mur": 42035, + "peter": 42036, + "Ġbeverage": 42037, + "ĠNWs": 42038, + "Ġfolic": 42039, + "Ġspeculative": 42040, + "Ġñ": 42041, + "Ġribbons": 42042, + "ĠPriest": 42043, + "Quanti": 42044, + "Ġundisturbed": 42045, + "atche": 42046, + "assi": 42047, + "ĠPerforming": 42048, + "ĠElong": 42049, + "Ġmatchings": 42050, + "Ġfranchise": 42051, + "gio": 42052, + "ĠSarg": 42053, + "Ġaboard": 42054, + "cyclodextrin": 42055, + "ĠTHER": 42056, + "Ġsaturate": 42057, + "ĠKinematics": 42058, + "Ġpeptidoglycan": 42059, + "ĠShelf": 42060, + "tocopherol": 42061, + "Bottom": 42062, + "mith": 42063, + "rdx": 42064, + "zos": 42065, + "ĠtRNAs": 42066, + "utf": 42067, + "ENA": 42068, + "Ġlesson": 42069, + "Ġpolaron": 42070, + "braska": 42071, + "Ġathletic": 42072, + "Ġscrambled": 42073, + "Ġpursuing": 42074, + "Ġbodily": 42075, + "Ġcac": 42076, + "imen": 42077, + "ĠIκB": 42078, + "ĠRö": 42079, + "ĠRFC": 42080, + "ĠLPC": 42081, + "ĠiÏī": 42082, + "Ġdiary": 42083, + "Ġqueueing": 42084, + "ĠDivergence": 42085, + "ĠShigella": 42086, + "ĠUltrastruct": 42087, + "Ġtriphosphate": 42088, + "ĠImplant": 42089, + "Ġferrous": 42090, + "ĠBurton": 42091, + "ĠHertz": 42092, + "fabric": 42093, + "turing": 42094, + "ĠSSM": 42095, + "ograd": 42096, + "Ġmetazo": 42097, + "Chang": 42098, + "Ġadipogenesis": 42099, + "Ġnuisance": 42100, + "Ġanonymity": 42101, + "Ġrefrigerator": 42102, + "ìľ": 42103, + "oction": 42104, + "Ġsparing": 42105, + "Ġchalcogen": 42106, + "Ġobservatory": 42107, + "Ġbooster": 42108, + "ĠAndré": 42109, + "ĠSTO": 42110, + "yryl": 42111, + "ĠEDX": 42112, + "ĠDenver": 42113, + "Ġhomogenate": 42114, + "Callback": 42115, + "aC": 42116, + "hours": 42117, + "kova": 42118, + "ĠAUD": 42119, + "Ġspare": 42120, + "Ġpartons": 42121, + "ĠIntake": 42122, + "Ġrecognizable": 42123, + "ĠGoldstein": 42124, + "Ġstrikingly": 42125, + "Ġsanitation": 42126, + "Finder": 42127, + "Generation": 42128, + "boy": 42129, + "tam": 42130, + "ĠRPM": 42131, + "ivious": 42132, + "ylak": 42133, + "ophiles": 42134, + "Ġpriest": 42135, + "Ġeasiest": 42136, + "Ġdeliveries": 42137, + "Elmer": 42138, + "Ġzirconium": 42139, + "ĠMishra": 42140, + "ĠâĶ": 42141, + "ĠWDM": 42142, + "Ġperid": 42143, + "ĠZT": 42144, + "Ġlocalizes": 42145, + "ĠORs": 42146, + "ĠIDO": 42147, + "Ġpleasant": 42148, + "ĠMWCNTs": 42149, + "ĠJimmy": 42150, + "ĠYeh": 42151, + "gathered": 42152, + "kil": 42153, + "ĠKappa": 42154, + "Ġcartoon": 42155, + "ĠHeuristic": 42156, + "Ġsz": 42157, + "Ġorifice": 42158, + "Ġmannit": 42159, + "ĠCOMM": 42160, + "ICK": 42161, + "Ġfarmer": 42162, + "ĠSilencing": 42163, + "Ġprefixes": 42164, + "qc": 42165, + "ineurin": 42166, + "Ġinflated": 42167, + "ĠRez": 42168, + "Ġhydrodynamical": 42169, + "Ġoscillate": 42170, + "Ġpedestrians": 42171, + "ĠAngiotensin": 42172, + "ĠViscosity": 42173, + "Ġoligodendrocytes": 42174, + "Ġparotid": 42175, + "Layout": 42176, + "rageenan": 42177, + "Ġè": 42178, + "ĠmN": 42179, + "Ġdozen": 42180, + "exclusion": 42181, + "Ġpanic": 42182, + "ĠPDI": 42183, + "Ġtwentieth": 42184, + "ĠElectroph": 42185, + "Ġmicrobiology": 42186, + "Server": 42187, + "ĠParticipation": 42188, + "DET": 42189, + "Poss": 42190, + "ĠNemat": 42191, + "ĠNRF": 42192, + "arguments": 42193, + "Ġamylase": 42194, + "Ġargv": 42195, + "Ġresolves": 42196, + "Ġrevisions": 42197, + "Packet": 42198, + "Tools": 42199, + "YE": 42200, + "Ġtire": 42201, + "induction": 42202, + "asive": 42203, + "tonic": 42204, + "ém": 42205, + "carrying": 42206, + "ĠImmunoblot": 42207, + "ĠIPF": 42208, + "Ġdeteriorated": 42209, + "Ġjurisdiction": 42210, + "released": 42211, + "osmotic": 42212, + "Ġrestaurants": 42213, + "ï¸": 42214, + "ĠNm": 42215, + "Ġflips": 42216, + "Ġseparability": 42217, + "ĠRecursive": 42218, + "Ġpasture": 42219, + "ĠÄī": 42220, + "Ġblastocyst": 42221, + "MCP": 42222, + "Tb": 42223, + "uene": 42224, + "esulf": 42225, + "essim": 42226, + "Ġhen": 42227, + "ĠKull": 42228, + "ylum": 42229, + "arev": 42230, + "uests": 42231, + "ĠZip": 42232, + "Ġboats": 42233, + "Command": 42234, + "Continu": 42235, + "ĠBogoliubov": 42236, + "Ġmannitol": 42237, + "Know": 42238, + "г": 42239, + "ĠHack": 42240, + "Ġmassively": 42241, + "ĠAlloys": 42242, + "ĠCDP": 42243, + "ĠStereo": 42244, + "ĠElectrode": 42245, + "Ġisoflav": 42246, + "Ġinteroperability": 42247, + "ĠAdelaide": 42248, + "ĠPPD": 42249, + "ĠKou": 42250, + "Ġdiap": 42251, + "Ġconserve": 42252, + "Ġaggregating": 42253, + "Gluc": 42254, + "Ġîģ": 42255, + "Ġgust": 42256, + "ĠLeb": 42257, + "ETIC": 42258, + "ĠConsol": 42259, + "ĠMorita": 42260, + "Relative": 42261, + "Ġpaleo": 42262, + "Ġwitnesses": 42263, + "ĠLauren": 42264, + "azepine": 42265, + "ĠTY": 42266, + "ĠIdi": 42267, + "ĠMent": 42268, + "ĠRCA": 42269, + "igenin": 42270, + "ĠDefence": 42271, + "Ġpyrimidine": 42272, + "ĠTiN": 42273, + "Ġendothelin": 42274, + "Ġpandas": 42275, + "Ġswallowing": 42276, + "Ġcongestive": 42277, + "Ġvinc": 42278, + "ĠDIP": 42279, + "ĠHough": 42280, + "Ġzw": 42281, + "ĠKimura": 42282, + "representations": 42283, + "ĠPromote": 42284, + "ĠTerry": 42285, + "Ġhatched": 42286, + "lookup": 42287, + "Electron": 42288, + "Ġdormancy": 42289, + "Ġresign": 42290, + "Ġvaluations": 42291, + "Ġmakeup": 42292, + "ĠAmy": 42293, + "CLUD": 42294, + "SEP": 42295, + "tubule": 42296, + "Ġsoldier": 42297, + "ĠTz": 42298, + "ĠTrump": 42299, + "ĠKramer": 42300, + "coni": 42301, + "Ġengraft": 42302, + "Ġvacuole": 42303, + "Ġreplicating": 42304, + "itonitis": 42305, + "ĠBacteri": 42306, + "vaccinated": 42307, + "olt": 42308, + "ĠAhn": 42309, + "Ġanem": 42310, + "ĠBIT": 42311, + "geo": 42312, + "Ġmicrogravity": 42313, + "ĠShir": 42314, + "ĠWorldwide": 42315, + "Ġignor": 42316, + "ĠËĩ": 42317, + "Ġlubrication": 42318, + "java": 42319, + "vt": 42320, + "Ġyl": 42321, + "Ġhills": 42322, + "ĠFOL": 42323, + "Ġbasaltic": 42324, + "Neill": 42325, + "ĠEthiopian": 42326, + "ĠNOTCH": 42327, + "ĠMOSFET": 42328, + "leaving": 42329, + "ĠPter": 42330, + "ĠWeld": 42331, + "aple": 42332, + "Ġsandwic": 42333, + "Ġazide": 42334, + "ĠStimuli": 42335, + "Ġlizard": 42336, + "ĠCinc": 42337, + "ĠHain": 42338, + "icals": 42339, + "Ġcontacting": 42340, + "ĠMarx": 42341, + "Ġpsychotherapy": 42342, + "ĠRetin": 42343, + "Ġcatheterization": 42344, + "ĠNanoparticle": 42345, + "ĠTCC": 42346, + "vermectin": 42347, + "ĠBASE": 42348, + "Ġnotor": 42349, + "Ġelectronically": 42350, + "steroid": 42351, + "Ġbilaterally": 42352, + "Ġnephritis": 42353, + "Ġirritation": 42354, + "ĠProlonged": 42355, + "Your": 42356, + "heuristic": 42357, + "urgeon": 42358, + "Ġleftmost": 42359, + "ĠREVIEW": 42360, + "Ġgastrectomy": 42361, + "ENTIAL": 42362, + "Means": 42363, + "ĠDyson": 42364, + "Ġbrands": 42365, + "yields": 42366, + "mercapto": 42367, + "rub": 42368, + "ouncement": 42369, + "errno": 42370, + "Ġviewers": 42371, + "butan": 42372, + "ĠMalay": 42373, + "ylindrical": 42374, + "Ġprominently": 42375, + "Ġescaping": 42376, + "Ġquerying": 42377, + "Storage": 42378, + "Fos": 42379, + "codec": 42380, + "ĠcM": 42381, + "strates": 42382, + "glove": 42383, + "ĠTrajectories": 42384, + "Ġsterol": 42385, + "Ġhematopoiesis": 42386, + "Ġcuprates": 42387, + "Ok": 42388, + "daily": 42389, + "ĠBIO": 42390, + "ĠLICENSE": 42391, + "ellations": 42392, + "assy": 42393, + "SURE": 42394, + "Ġepinephrine": 42395, + "Ġdownwards": 42396, + "corner": 42397, + "assertTrue": 42398, + "Ġáºij": 42399, + "ĠSouza": 42400, + "MAG": 42401, + "porph": 42402, + "Ġeffluents": 42403, + "loem": 42404, + "oaddition": 42405, + "obutyl": 42406, + "elestial": 42407, + "Fem": 42408, + "mpi": 42409, + "ĠRs": 42410, + "ellates": 42411, + "ĠKag": 42412, + "Ġuncoupled": 42413, + "Ġlegumes": 42414, + "Ġomitting": 42415, + "û": 42416, + "ĠTABLE": 42417, + "haled": 42418, + "ĠÅģ": 42419, + "Ġmisfit": 42420, + "Ġmolars": 42421, + "otechnological": 42422, + "Markov": 42423, + "Ġpraised": 42424, + "ĠDab": 42425, + "ĠVij": 42426, + "entilation": 42427, + "ĠChatter": 42428, + "Ġboiled": 42429, + "Ġcatches": 42430, + "annotation": 42431, + "Signal": 42432, + "Ġleverages": 42433, + "Ġadvisory": 42434, + "song": 42435, + "ondition": 42436, + "Ġfug": 42437, + "raps": 42438, + "ĠMCD": 42439, + "particip": 42440, + "obian": 42441, + "Ġcounsel": 42442, + "ĠPRP": 42443, + "ediol": 42444, + "ĠŨ": 42445, + "Ġbruce": 42446, + "Shanghai": 42447, + "DataFrame": 42448, + "ĠCorrespondingly": 42449, + "Ġacrylamide": 42450, + "fellow": 42451, + "lob": 42452, + "igt": 42453, + "ĠCrystallization": 42454, + "Ġindomethacin": 42455, + "ĠPDR": 42456, + "giate": 42457, + "ĠPanels": 42458, + "complexes": 42459, + "ĠNicol": 42460, + "Ġfoliar": 42461, + "cubic": 42462, + "ĠdE": 42463, + "ĠCCM": 42464, + "plating": 42465, + "Ġresistors": 42466, + "ĠGaz": 42467, + "Ġoverturn": 42468, + "Ġrepress": 42469, + "Ġpots": 42470, + "ĠPIK": 42471, + "Ġdermis": 42472, + "Represent": 42473, + "ĠAndersson": 42474, + "Ġretrotranspos": 42475, + "ASA": 42476, + "Counter": 42477, + "Tet": 42478, + "imin": 42479, + "performed": 42480, + "ĠNept": 42481, + "Ġheel": 42482, + "rold": 42483, + "aires": 42484, + "Ġreadability": 42485, + "Ġbenefited": 42486, + "Ġpulsation": 42487, + "ĠBalancing": 42488, + "Ġevaporator": 42489, + "Ġeigens": 42490, + "ĠHospit": 42491, + "Ġtrails": 42492, + "ĠCoordinate": 42493, + "accase": 42494, + "ĠHRMS": 42495, + "signaling": 42496, + "ĠNPY": 42497, + "Ġameliorated": 42498, + "tuples": 42499, + "Ġmetasurface": 42500, + "ĠFrancesco": 42501, + "Ġspoof": 42502, + "îŸ": 42503, + "Fu": 42504, + "JK": 42505, + "ej": 42506, + "Ġgoss": 42507, + "ĠHim": 42508, + "Ġprioritized": 42509, + "Ġmesothelioma": 42510, + "idxs": 42511, + "Ġreconnaissance": 42512, + "Ġlamps": 42513, + "ãĢĤ": 42514, + "Ġreformulation": 42515, + "Ġdelirium": 42516, + "ĠNPR": 42517, + "ĠGamb": 42518, + "illas": 42519, + "-----": 42520, + "Ġdrilled": 42521, + "ĠGenotyping": 42522, + "ĠBlank": 42523, + "Ġpropeller": 42524, + "Ġcereals": 42525, + "ĠAirborne": 42526, + "ĠPhotocatalytic": 42527, + "ĠCavity": 42528, + "Ġdolphins": 42529, + "ĠsgRNA": 42530, + "understood": 42531, + "eous": 42532, + "Ġsax": 42533, + "olayer": 42534, + "ĠPend": 42535, + "ĠGET": 42536, + "cled": 42537, + "Ġü": 42538, + "Ġcytosine": 42539, + "Ġparallelization": 42540, + "MMs": 42541, + "ĠOrganisation": 42542, + "Models": 42543, + "Ġaccommodated": 42544, + "Ġcholest": 42545, + "Ġinactivity": 42546, + "ĠBoss": 42547, + "ĠGCS": 42548, + "Ġsoaked": 42549, + "ĠSecreted": 42550, + "Ġvacuolar": 42551, + "zoan": 42552, + "ĠGreene": 42553, + "Ġbolt": 42554, + "bj": 42555, + "ĠTall": 42556, + "Ġstor": 42557, + "ĠMob": 42558, + "Ġblurred": 42559, + "INO": 42560, + "ĠMetallic": 42561, + "uffs": 42562, + "ĠNOTE": 42563, + "Ġsonicated": 42564, + "obutyric": 42565, + "ĠtDCS": 42566, + "ĠNes": 42567, + "ospir": 42568, + "weigh": 42569, + "ĠRegulator": 42570, + "Ġhemolysis": 42571, + "Ġsounding": 42572, + "Ġcruciate": 42573, + "Ġcapsaicin": 42574, + "ĠTyrosine": 42575, + "ĠTell": 42576, + "ĠPEP": 42577, + "ĠRc": 42578, + "ĠEating": 42579, + "ĠGoals": 42580, + "uret": 42581, + "Ġearn": 42582, + "Ġcolleges": 42583, + "Ġchemoattract": 42584, + "Ġỹ": 42585, + "ĠEchocardi": 42586, + "Fort": 42587, + "sodium": 42588, + "amined": 42589, + "ĠNPP": 42590, + "ĠKalu": 42591, + "Ġdecipher": 42592, + "tetramethyl": 42593, + "ĠOPN": 42594, + "straight": 42595, + "Ġtempered": 42596, + "ĠHindu": 42597, + "ĠOrdinary": 42598, + "ĠAChE": 42599, + "JNK": 42600, + "fos": 42601, + "vcpu": 42602, + "enamide": 42603, + "ĠCrack": 42604, + "apical": 42605, + "Ġantiserum": 42606, + "triplet": 42607, + "decision": 42608, + "Ġcancels": 42609, + "ĠPMN": 42610, + "Ġporphy": 42611, + "ĠDSA": 42612, + "Ġsubmatrix": 42613, + "Ġjas": 42614, + "Ġreptiles": 42615, + "ĠSoon": 42616, + "ĠStatistically": 42617, + "Ġleveraged": 42618, + "Williams": 42619, + "FLD": 42620, + "folk": 42621, + "Ġbang": 42622, + "ĠSCL": 42623, + "asses": 42624, + "Ġtendons": 42625, + "founded": 42626, + "ĠRicketts": 42627, + "inset": 42628, + "Ġspun": 42629, + "Ġunramified": 42630, + "Ġrape": 42631, + "ĠZZ": 42632, + "ĠNebula": 42633, + "Ġthrombotic": 42634, + "ĠBoron": 42635, + "ĠArgon": 42636, + "pooling": 42637, + "ĠMarginal": 42638, + "Ġfellowship": 42639, + "Ġerythropoietin": 42640, + "mathpzc": 42641, + "xL": 42642, + "ĠSik": 42643, + "ĠBayer": 42644, + "Ġoverdose": 42645, + "ĠCOI": 42646, + "ĠLesions": 42647, + "ĠCompetitive": 42648, + "ĠODEs": 42649, + "wrap": 42650, + "achlor": 42651, + "Ġsubordinate": 42652, + "ĠYBa": 42653, + "headed": 42654, + "Ġgrasses": 42655, + "Ġbirational": 42656, + "ĠJeffrey": 42657, + "Ġmolding": 42658, + "Ġlidocaine": 42659, + "ĠFOXO": 42660, + "terminated": 42661, + "ĠâĩIJâĩĴ": 42662, + "ĠMEL": 42663, + "ticulum": 42664, + "Ġré": 42665, + "Ġclaud": 42666, + "Ġjamming": 42667, + "Static": 42668, + "Ġtributary": 42669, + "atet": 42670, + "edonia": 42671, + "ĠCMP": 42672, + "ĠVN": 42673, + "represents": 42674, + "SOURCE": 42675, + "uckland": 42676, + "ĠInterests": 42677, + "ĠNanoscale": 42678, + "oconjug": 42679, + "Ġcatalogues": 42680, + "ĠActinobacteria": 42681, + "Fixed": 42682, + "basal": 42683, + "Ġantiparallel": 42684, + "Ġconfusing": 42685, + "Ġmarkings": 42686, + "Ġdistinctions": 42687, + "ĠHua": 42688, + "ĠWatts": 42689, + "Ġnanofluid": 42690, + "Ġdiffractometer": 42691, + "Later": 42692, + "migration": 42693, + "Ġcoplanar": 42694, + "Ġhypomethyl": 42695, + "PDS": 42696, + "SOs": 42697, + "Correspond": 42698, + "Ġelucidating": 42699, + "IZED": 42700, + "EVs": 42701, + "gart": 42702, + "mTc": 42703, + "ĠTUR": 42704, + "uracies": 42705, + "Ġfollower": 42706, + "Ġhaze": 42707, + "OUTPUT": 42708, + "ĠMyeloid": 42709, + "BUFFER": 42710, + "Camp": 42711, + "anim": 42712, + "ĠTES": 42713, + "ĠCricket": 42714, + "ĠPaired": 42715, + "ĠPAGE": 42716, + "ĠBid": 42717, + "Ġyrs": 42718, + "Ġendow": 42719, + "ircase": 42720, + "ĠSupported": 42721, + "Ġleaflet": 42722, + "ĠPromoter": 42723, + "Ġconvinced": 42724, + "liers": 42725, + "hera": 42726, + "Ġseller": 42727, + "agreement": 42728, + "Ġunary": 42729, + "onstructed": 42730, + "Ġrestrained": 42731, + "ĠPetersen": 42732, + "Analog": 42733, + "Ġexacerbations": 42734, + "Ġperforated": 42735, + "tids": 42736, + "ĠMSH": 42737, + "oui": 42738, + "ĠCori": 42739, + "ĠCruc": 42740, + "Ġfracturing": 42741, + "Ġinfertile": 42742, + "ĠPROBLEM": 42743, + "ĠFriedmann": 42744, + "Ġspectrophotometry": 42745, + "ERGY": 42746, + "otus": 42747, + "proposed": 42748, + "ĠMoisture": 42749, + "ĠNoether": 42750, + "ĠLaunch": 42751, + "ĠLearn": 42752, + "Ġvena": 42753, + "Ġfasci": 42754, + "Ġquiescence": 42755, + "ĠPrand": 42756, + "ĠConvert": 42757, + "Ġtriage": 42758, + "ANE": 42759, + "Ġfeedstock": 42760, + "ĠdBm": 42761, + "Ġneoformans": 42762, + "GSE": 42763, + "ĠAPE": 42764, + "Ġcardiometabolic": 42765, + "Ġmagnetometer": 42766, + "Environment": 42767, + "Ġfulfills": 42768, + "ĠManganese": 42769, + "BMP": 42770, + "ĠRatios": 42771, + "istable": 42772, + "assume": 42773, + "Ġrespected": 42774, + "Ġscars": 42775, + "Ġsupporters": 42776, + "ĠAugmentation": 42777, + "Ġglycosylated": 42778, + "ĠUltrafast": 42779, + "Ġdemethylation": 42780, + "metastatic": 42781, + "cylinder": 42782, + "Ġhang": 42783, + "ĠMAV": 42784, + "disjoint": 42785, + "pharose": 42786, + "ĠLebanon": 42787, + "PIs": 42788, + "labeling": 42789, + "Ġneutralino": 42790, + "ĠSOCS": 42791, + "xcb": 42792, + "ĠTerritory": 42793, + "ĠPolicies": 42794, + "King": 42795, + "Ġallied": 42796, + "Ġsaturates": 42797, + "muscle": 42798, + "odimers": 42799, + "Ġbt": 42800, + "ĠHang": 42801, + "ĠEb": 42802, + "Ġchimer": 42803, + "Ġnotational": 42804, + "Ġcolder": 42805, + "ultz": 42806, + "transverse": 42807, + "HOUT": 42808, + "ĠKarls": 42809, + "torsion": 42810, + "JI": 42811, + "Ġmari": 42812, + "emon": 42813, + "Ġlogarithmically": 42814, + "ĠâIJ¦": 42815, + "ìĿĦ": 42816, + "Ġaeration": 42817, + "Ġsoma": 42818, + "ĠSomal": 42819, + "Ġspoil": 42820, + "diver": 42821, + "Ġbreakpoints": 42822, + "ĠHarmon": 42823, + "Ġpharmacologic": 42824, + "ĠMosquito": 42825, + "ĠModifications": 42826, + "Ġadjo": 42827, + "ĠPapers": 42828, + "generally": 42829, + "ïĺ¹": 42830, + "TARGET": 42831, + "ĠPrix": 42832, + "ocaps": 42833, + "ĠEin": 42834, + "Ġmicrogrid": 42835, + "ĠInterplay": 42836, + "Ġcopying": 42837, + "Alpha": 42838, + "ĠSlope": 42839, + "ĠLipofectamine": 42840, + "highest": 42841, + "DRO": 42842, + "ĠHipp": 42843, + "Ġshaken": 42844, + "Ġunderline": 42845, + "Ġfilmed": 42846, + "maturity": 42847, + "icture": 42848, + "ILS": 42849, + "Span": 42850, + "Ġinverters": 42851, + "QUE": 42852, + "determining": 42853, + "Ġeosinophilic": 42854, + "DY": 42855, + "ĠLID": 42856, + "ĠGig": 42857, + "Ġintraepithelial": 42858, + "NbO": 42859, + "freedom": 42860, + "Ġassured": 42861, + "ĠArche": 42862, + "ĠSubstitution": 42863, + "ĠSrivastava": 42864, + "ĠMozamb": 42865, + "Ġaro": 42866, + "orc": 42867, + "ĠIbrahim": 42868, + "ĠDST": 42869, + "Ġabl": 42870, + "Ġxer": 42871, + "ountable": 42872, + "Ġlossless": 42873, + "Ġconcentrating": 42874, + "Ġstains": 42875, + "ĠSolve": 42876, + "continuity": 42877, + "ĠTorr": 42878, + "Ġpitfalls": 42879, + "bestos": 42880, + "Otherwise": 42881, + "adhyay": 42882, + "bard": 42883, + "ĠCAA": 42884, + "odetic": 42885, + "Ġasthmatic": 42886, + "Ġrationality": 42887, + "ĠYorkshire": 42888, + "neighborhood": 42889, + "Ġheroin": 42890, + "Ġscatterers": 42891, + "ĠHearing": 42892, + "ĠEFT": 42893, + "ĠNurses": 42894, + "ĠGLI": 42895, + "ĠZeta": 42896, + "ĠNeigh": 42897, + "Ġventure": 42898, + "Ġtoxicological": 42899, + "Ġrolls": 42900, + "fv": 42901, + "Ġcrick": 42902, + "ĠdÏī": 42903, + "avia": 42904, + "elder": 42905, + "Ġinvade": 42906, + "extracted": 42907, + "MLP": 42908, + "ĠPAI": 42909, + "ĠMellitus": 42910, + "Ġbrucei": 42911, + "gpio": 42912, + "emotional": 42913, + "ĠDale": 42914, + "ĠEz": 42915, + "Ġtransactivation": 42916, + "Ġquantiles": 42917, + "Ġnucleosynthesis": 42918, + "ĠAmel": 42919, + "Ġchromophore": 42920, + "Ġliterally": 42921, + "bandwidth": 42922, + "atohepatitis": 42923, + "Ġultrafiltration": 42924, + "Martin": 42925, + "Ġangioplasty": 42926, + "insertion": 42927, + "Dan": 42928, + "squeeze": 42929, + "usr": 42930, + "uconazole": 42931, + "ĠFAR": 42932, + "Ġshadows": 42933, + "Ġimitation": 42934, + "ĠKann": 42935, + "hesi": 42936, + "Ġmicellar": 42937, + "vester": 42938, + "ĠPerse": 42939, + "acetamol": 42940, + "GRAPH": 42941, + "ĠAIPS": 42942, + "Ġpromptly": 42943, + "anchor": 42944, + "Ġischaemia": 42945, + "pump": 42946, + "Ġmafic": 42947, + "Ġlazy": 42948, + "ĠCEL": 42949, + "ĠGorenstein": 42950, + "ĠWGS": 42951, + "Ġsignifies": 42952, + "Ġsplines": 42953, + "determination": 42954, + "Ġrelaying": 42955, + "piperazine": 42956, + "Ġsyncytial": 42957, + "ĠAub": 42958, + "ĠDX": 42959, + "Ġorthotopic": 42960, + "ĠLinkage": 42961, + "Ġharmony": 42962, + "ĠKazakh": 42963, + "ĠVladimir": 42964, + "Ġpray": 42965, + "imolar": 42966, + "Ġgrayscale": 42967, + "Ġanalyst": 42968, + "ĠTransl": 42969, + "Ġmeniscus": 42970, + "ĠMedica": 42971, + "osaurus": 42972, + "Ġв": 42973, + "Ġinfiltrated": 42974, + "Ġâĸ³": 42975, + "Ġsaccades": 42976, + "Ġdisentangle": 42977, + "Hart": 42978, + "fined": 42979, + "Ġbicycle": 42980, + "ository": 42981, + "unlikely": 42982, + "erephthal": 42983, + "ĠLia": 42984, + "Ġgroupings": 42985, + "Ġcategorize": 42986, + "Ġbiogeography": 42987, + "ĠAPPROACH": 42988, + "ĠNing": 42989, + "ĠGrap": 42990, + "versa": 42991, + "Ġradiologists": 42992, + "ĠRecording": 42993, + "Ġboiler": 42994, + "adders": 42995, + "Candid": 42996, + "MQ": 42997, + "Ġbw": 42998, + "ĠSector": 42999, + "ĠHIT": 43000, + "ĠESCC": 43001, + "essence": 43002, + "orean": 43003, + "estyles": 43004, + "SUCCESS": 43005, + "nein": 43006, + "ultra": 43007, + "ramp": 43008, + "Thomas": 43009, + "ĠPrepar": 43010, + "ĠInstitut": 43011, + "Ġherbicide": 43012, + "ĠChaotic": 43013, + "Ġsphincter": 43014, + "Ġcompactifications": 43015, + "Clear": 43016, + "Trp": 43017, + "Decoder": 43018, + "Ġsapphire": 43019, + "ĠIdaho": 43020, + "persing": 43021, + "chiral": 43022, + "ĠDischarge": 43023, + "Accordingly": 43024, + "ĠArthritis": 43025, + "ĠJaneiro": 43026, + "nj": 43027, + "ĠKd": 43028, + "Ġoutlets": 43029, + "Ġsusceptibilities": 43030, + "Ġdiverged": 43031, + "Ġroller": 43032, + "sufficient": 43033, + "clustering": 43034, + "ĠTehran": 43035, + "Ġtb": 43036, + "blank": 43037, + "Ġdigitally": 43038, + "Ġnecrotizing": 43039, + "FALSE": 43040, + "Ġwhor": 43041, + "errals": 43042, + "ĠMotivated": 43043, + "enzae": 43044, + "ĠRefinement": 43045, + "Ġticket": 43046, + "Ġprotrusions": 43047, + "ĠDonaldson": 43048, + "ĠBeth": 43049, + "Ġsputtered": 43050, + "Ġautocrine": 43051, + "copene": 43052, + "Ġcollar": 43053, + "Ġuppermost": 43054, + "Ġoxygenated": 43055, + "Intro": 43056, + "âĨIJ": 43057, + "ĠHippo": 43058, + "Ġdune": 43059, + "idines": 43060, + "ĠHä": 43061, + "Ġregi": 43062, + "Ġnois": 43063, + "Ġphotodiode": 43064, + "ĠFeb": 43065, + "mutated": 43066, + "ĠCFL": 43067, + "stepping": 43068, + "Selection": 43069, + "ĠWebster": 43070, + "ĠHERA": 43071, + "indicating": 43072, + "Ġtrainees": 43073, + "Rot": 43074, + "ĠFAK": 43075, + "ĠAsn": 43076, + "Ġfats": 43077, + "foliation": 43078, + "Ġarticulation": 43079, + "Ġcusps": 43080, + "ĠJennifer": 43081, + "Ġintimately": 43082, + "ĠPing": 43083, + "sov": 43084, + "oxious": 43085, + "hydrate": 43086, + "ĠArchives": 43087, + "Gonz": 43088, + "Ġé": 43089, + "Ġchl": 43090, + "ĠOLS": 43091, + "coph": 43092, + "Ġairline": 43093, + "Ġfoetal": 43094, + "ĠRolling": 43095, + "ĠGENERAL": 43096, + "ONAL": 43097, + "agons": 43098, + "ĠDorsal": 43099, + "Ġritual": 43100, + "butyrate": 43101, + "oglut": 43102, + "Ġhexa": 43103, + "ĠSyria": 43104, + "Ġontogeny": 43105, + "ĠFBG": 43106, + "coverage": 43107, + "Ġtachyon": 43108, + "ĠPermanent": 43109, + "lum": 43110, + "Ġsv": 43111, + "Ġoo": 43112, + "energetic": 43113, + "altitude": 43114, + "Inc": 43115, + "ĠNebraska": 43116, + "ĠRESP": 43117, + "Ġdysbiosis": 43118, + "Ġmarketed": 43119, + "oxicillin": 43120, + "ĠBroadcast": 43121, + "racyclo": 43122, + "ĠFifteen": 43123, + "ĠNarayan": 43124, + "Ġlettuce": 43125, + "orea": 43126, + "Ġintercepts": 43127, + "Ġworkstation": 43128, + "ĠPlains": 43129, + "CCL": 43130, + "Ġorientable": 43131, + "ĠBoosting": 43132, + "ĠSOI": 43133, + "ĠChecking": 43134, + "ĠFIFO": 43135, + "Ġinsets": 43136, + "ĠSRT": 43137, + "Ġacrom": 43138, + "owner": 43139, + "MIX": 43140, + "ĠArb": 43141, + "Ġfaeces": 43142, + "ĠCarlson": 43143, + "Ġperivascular": 43144, + "infiltrating": 43145, + "Ìħ": 43146, + "Ġmalle": 43147, + "ocate": 43148, + "ĠBold": 43149, + "unctive": 43150, + "excess": 43151, + "Ġloosen": 43152, + "Ġprioritization": 43153, + "Ġannotate": 43154, + "Ġgrammars": 43155, + "Ġbred": 43156, + "Ġexocytosis": 43157, + "ĠDahl": 43158, + "athyroidism": 43159, + "veli": 43160, + "Ġopted": 43161, + "Ġsmoked": 43162, + "ĠPlates": 43163, + "EMG": 43164, + "ROW": 43165, + "IFIC": 43166, + "OLS": 43167, + "oregulatory": 43168, + "Ġwhiskers": 43169, + "secretase": 43170, + "Ġexaggerated": 43171, + "ĠBib": 43172, + "deformed": 43173, + "Ġzur": 43174, + "ropine": 43175, + "Ġpairings": 43176, + "chromosome": 43177, + "Elements": 43178, + "priority": 43179, + "Ġlyophilized": 43180, + "ĠChaudh": 43181, + "Wilk": 43182, + "ĠCation": 43183, + "otta": 43184, + "Ġnonconvex": 43185, + "Ġdepolymer": 43186, + "MMARY": 43187, + "Controlled": 43188, + "carboxy": 43189, + "Ġaugmenting": 43190, + "Ġappointments": 43191, + "Ġtraversed": 43192, + "ĠFletcher": 43193, + "Ġexpiratory": 43194, + "Ġelephant": 43195, + "ĠBlocks": 43196, + "ĠFluids": 43197, + "walls": 43198, + "increased": 43199, + "propanamide": 43200, + "ĠAkaike": 43201, + "ĠCBM": 43202, + "ĠEcho": 43203, + "admissible": 43204, + "Ġdisassembly": 43205, + "ĠarXiv": 43206, + "icke": 43207, + "LIST": 43208, + "phenotype": 43209, + "ĠProvincial": 43210, + "legend": 43211, + "PAS": 43212, + "rnn": 43213, + "sand": 43214, + "Ġbariatric": 43215, + "ĠPush": 43216, + "ĠApoE": 43217, + "caprolactone": 43218, + "modeling": 43219, + "Ġŵ": 43220, + "Ġsupercapacitors": 43221, + "oron": 43222, + "ĠpK": 43223, + "strophy": 43224, + "ĠSuc": 43225, + "unda": 43226, + "team": 43227, + "Ġitiner": 43228, + "Ġswell": 43229, + "ĠBioactive": 43230, + "ĠIndicators": 43231, + "ĠIFT": 43232, + "ĠDK": 43233, + "Ġcapit": 43234, + "shapes": 43235, + "Ġtrachea": 43236, + "delayed": 43237, + "ĠGuangdong": 43238, + "Lepid": 43239, + "TGA": 43240, + "hk": 43241, + "olon": 43242, + "ogenin": 43243, + "ĠAck": 43244, + "Ġlogically": 43245, + "contributions": 43246, + "ĠCleavage": 43247, + "hurst": 43248, + "bdd": 43249, + "STD": 43250, + "ĠFut": 43251, + "tek": 43252, + "ĠInher": 43253, + "Ġchemis": 43254, + "Ġbreakpoint": 43255, + "estimates": 43256, + "ĠOttoman": 43257, + "ĠNafion": 43258, + "WIDTH": 43259, + "Ġsizable": 43260, + "ĠTsu": 43261, + "embolic": 43262, + "Ġrightmost": 43263, + "ĠCellulose": 43264, + "ictionaries": 43265, + "ĠMycoplasma": 43266, + "ĠBurgers": 43267, + "ĠKeplerian": 43268, + "UCTION": 43269, + "VB": 43270, + "Ġbcc": 43271, + "raid": 43272, + "ENDIX": 43273, + "Ġscoping": 43274, + "ĠPRI": 43275, + "ĠCdSe": 43276, + "ĠGreedy": 43277, + "ĠHammer": 43278, + "ĠBacteroides": 43279, + "informative": 43280, + "Ġresembled": 43281, + "yllium": 43282, + "Twenty": 43283, + "Ġpounds": 43284, + "Ġunpolarized": 43285, + "Ġconfigure": 43286, + "Ġtranscriptionally": 43287, + "Ġmicroscale": 43288, + "ĠPutting": 43289, + "Ġpyrrol": 43290, + "ĠLASSO": 43291, + "filtration": 43292, + "Ġtech": 43293, + "performing": 43294, + "Along": 43295, + "ĠCTLA": 43296, + "Ġauthorization": 43297, + "URAL": 43298, + "Ġleaky": 43299, + "Optical": 43300, + "ĠReveal": 43301, + "ĠHUVECs": 43302, + "Wu": 43303, + "custom": 43304, + "dible": 43305, + "Ġ": 43306, + "CDCl": 43307, + "Ġemphys": 43308, + "Neut": 43309, + "collagen": 43310, + "necessarily": 43311, + "ĠRoots": 43312, + "Pose": 43313, + "Tu": 43314, + "Ġclue": 43315, + "Ġperturbing": 43316, + "ĠHelium": 43317, + "ĠCombustion": 43318, + "nitrogen": 43319, + "amplified": 43320, + "prove": 43321, + "ĠSoils": 43322, + "normalization": 43323, + "ĠCHOP": 43324, + "ĠMcLe": 43325, + "Ġstrikes": 43326, + "Ġcropped": 43327, + "ĠKuo": 43328, + "Ġvagal": 43329, + "Ġdinucleotide": 43330, + "ĠIsaac": 43331, + "ĠLOX": 43332, + "Ġdirectionality": 43333, + "Ġchemoradiotherapy": 43334, + "calculus": 43335, + "ĠMohammed": 43336, + "mapped": 43337, + "Ġreforms": 43338, + "Ġreordering": 43339, + "ĠBm": 43340, + "ĠESCs": 43341, + "ĠNUC": 43342, + "thaw": 43343, + "Ġnanoporous": 43344, + "Ġtrainable": 43345, + "ĠATT": 43346, + "feats": 43347, + "OFDM": 43348, + "ĠSHP": 43349, + "ĠRichter": 43350, + "Ġsprayed": 43351, + "ĠJefferson": 43352, + "FOX": 43353, + "bh": 43354, + "otte": 43355, + "Ġleiomy": 43356, + "ospores": 43357, + "specificity": 43358, + "ĠRefer": 43359, + "ĠHaas": 43360, + "Move": 43361, + "Materials": 43362, + "tec": 43363, + "utility": 43364, + "entional": 43365, + "ĠMPP": 43366, + "chond": 43367, + "Ġseepage": 43368, + "Ġpeach": 43369, + "ĠÎĶt": 43370, + "embryonic": 43371, + "Yan": 43372, + "Ġliposomal": 43373, + "ĠValencia": 43374, + "ĠEndo": 43375, + "ĠPAO": 43376, + "Ġdialect": 43377, + "Ġchondrocyte": 43378, + "ĠMillimeter": 43379, + "ĠRegularity": 43380, + "destroy": 43381, + "ĠCondensation": 43382, + "Bayes": 43383, + "abundance": 43384, + "ĠdU": 43385, + "ĠSSI": 43386, + "ĠHAND": 43387, + "Ġconsulted": 43388, + "Ġsuppliers": 43389, + "Ġdemo": 43390, + "registered": 43391, + "Ġmicrosomal": 43392, + "Ġlambs": 43393, + "responsiveness": 43394, + "Dy": 43395, + "GAS": 43396, + "UME": 43397, + "Ġaero": 43398, + "Ġcalmodulin": 43399, + "Ġcalcined": 43400, + "Ġinsula": 43401, + "ĠMei": 43402, + "ĠREAL": 43403, + "Ġcontractible": 43404, + "ĠEssentially": 43405, + "Ġgaming": 43406, + "Ġspillover": 43407, + "residues": 43408, + "âİ": 43409, + "ĠEMC": 43410, + "ĠSDE": 43411, + "ĠSerine": 43412, + "ecki": 43413, + "ĠPrinceton": 43414, + "ĠBACKGROUND": 43415, + "masks": 43416, + "ĠLom": 43417, + "ffield": 43418, + "efitinib": 43419, + "Ġpatents": 43420, + "ĠBez": 43421, + "loads": 43422, + "Ġgonadal": 43423, + "Ġnitrocellulose": 43424, + "âĻĤ": 43425, + "Ġthrown": 43426, + "Ġrectification": 43427, + "mina": 43428, + "iscid": 43429, + "ĠBiobank": 43430, + "paramagnetic": 43431, + "GSK": 43432, + "ĠDerivative": 43433, + "criterion": 43434, + "ĠMonthly": 43435, + "ë¥": 43436, + "ĠSichuan": 43437, + "Ġimmunologic": 43438, + "Ġheterotic": 43439, + "ĠMcCl": 43440, + "ĠSMART": 43441, + "ĠBatteries": 43442, + "Ġpremiered": 43443, + "Ġcryopreservation": 43444, + "Nu": 43445, + "valho": 43446, + "Ġflotation": 43447, + "topological": 43448, + "ĠNanjing": 43449, + "Ġjuxt": 43450, + "ĠFeder": 43451, + "Ġprofoundly": 43452, + "cad": 43453, + "ienced": 43454, + "chuk": 43455, + "ĠIng": 43456, + "ĠKSHV": 43457, + "aminobenz": 43458, + "ĉĉĉĠĠĠ": 43459, + "Ġmetaph": 43460, + "ĠEpidemic": 43461, + "ĠAssociate": 43462, + "Ġsaccade": 43463, + "Ġdawn": 43464, + "Ġreheating": 43465, + "Ġspell": 43466, + "fractive": 43467, + "ĠToolkit": 43468, + "Ġrecognise": 43469, + "pathogen": 43470, + "Ġophthalmic": 43471, + "Ġqueried": 43472, + "thens": 43473, + "ithine": 43474, + "umably": 43475, + "Ġstrides": 43476, + "haul": 43477, + "Ġpassion": 43478, + "Ġdysfunctions": 43479, + "Byte": 43480, + "Ġcaesarean": 43481, + "prey": 43482, + "ĠHorse": 43483, + "ĠGABAA": 43484, + "Natural": 43485, + "kos": 43486, + "inators": 43487, + "odings": 43488, + "ARRAY": 43489, + "Ġunipotent": 43490, + "Ġelectromy": 43491, + "compart": 43492, + "Liu": 43493, + "encephalic": 43494, + "ĠCOMPAR": 43495, + "Ġsymbionts": 43496, + "ivacaine": 43497, + "OI": 43498, + "PVA": 43499, + "ĠNVIDIA": 43500, + "calibrated": 43501, + "Ġquest": 43502, + "NAD": 43503, + "ĠXyl": 43504, + "Ġpharmacist": 43505, + "directly": 43506, + "Ġquadruple": 43507, + "ethanone": 43508, + "ĠBulgaria": 43509, + "Ġoviposition": 43510, + "runs": 43511, + "Ġnociceptive": 43512, + "Ġasexual": 43513, + "SULT": 43514, + "Ġwouldn": 43515, + "ĠIndustries": 43516, + "abilizing": 43517, + "ĠCompressive": 43518, + "COOH": 43519, + "USH": 43520, + "kiewicz": 43521, + "Ġigneous": 43522, + "Ġdisappoint": 43523, + "ĠCKM": 43524, + "ĠDiagrams": 43525, + "ĠFlam": 43526, + "ĠGould": 43527, + "Ġcoenzyme": 43528, + "Ġparan": 43529, + "Ġ¶": 43530, + "Ġprogrammer": 43531, + "ĠTransforming": 43532, + "Ġmuscarinic": 43533, + "onucleotide": 43534, + "FIELD": 43535, + "ĠFuji": 43536, + "Ġnondec": 43537, + "Ġblanket": 43538, + "Ġpredisposing": 43539, + "ĠTrigger": 43540, + "Ġwelcome": 43541, + "Family": 43542, + "UINT": 43543, + "hfill": 43544, + "tvb": 43545, + "ĠBatt": 43546, + "Ġunmet": 43547, + "ĠApo": 43548, + "otient": 43549, + "Ġfundus": 43550, + "ĠLearned": 43551, + "Ġintrusions": 43552, + "Ġsolubilization": 43553, + "fundamental": 43554, + "ĠSantiago": 43555, + "Ġhpi": 43556, + "throw": 43557, + "ĠInto": 43558, + "timeout": 43559, + "Ġthickened": 43560, + "iasm": 43561, + "Ġgravitino": 43562, + "branched": 43563, + "VIII": 43564, + "Ġoch": 43565, + "Ġgym": 43566, + "ĠKrylov": 43567, + "Ġcorrective": 43568, + "ĠInstitution": 43569, + "Ġcrimes": 43570, + "ĠBacteroidetes": 43571, + "ĠEhr": 43572, + "Ġseated": 43573, + "rolizumab": 43574, + "Ġfactorized": 43575, + "rotational": 43576, + "Ġadministrators": 43577, + "âĭĨ": 43578, + "ineralization": 43579, + "lining": 43580, + "âĹ": 43581, + "urai": 43582, + "ĠFAP": 43583, + "ĠFisheries": 43584, + "ĠESO": 43585, + "temper": 43586, + "Biggr": 43587, + "ĠAlternating": 43588, + "twin": 43589, + "amatsu": 43590, + "Ġintrad": 43591, + "overflow": 43592, + "Ġcomparability": 43593, + "Ġsynoptic": 43594, + "USB": 43595, + "dbg": 43596, + "demonstr": 43597, + "ĠAchieving": 43598, + "Ġtectonics": 43599, + "ĠRandall": 43600, + "ĠPrepared": 43601, + "Ġsublimation": 43602, + "ĠBaj": 43603, + "Ġclutch": 43604, + "Ġsubdomain": 43605, + "Ġflaws": 43606, + "influ": 43607, + "Ġwidening": 43608, + "Ġmelted": 43609, + "Ġadministrator": 43610, + "Ġsubsidiary": 43611, + "ĠPricing": 43612, + "ticus": 43613, + "ogi": 43614, + "ĠAlign": 43615, + "ĠADV": 43616, + "Ġvastly": 43617, + "benchmark": 43618, + "Ġprioritize": 43619, + "Radi": 43620, + "essed": 43621, + "Ġsuprac": 43622, + "accard": 43623, + "Ġbiomimetic": 43624, + "ĠIrradiation": 43625, + "ĠALGOR": 43626, + "Ġpedigree": 43627, + "ĠCMT": 43628, + "odym": 43629, + "ĠVig": 43630, + "ĠBiochemistry": 43631, + "ĠAccum": 43632, + "Indices": 43633, + "hardtii": 43634, + "ĠRalph": 43635, + "Ġruminants": 43636, + "iT": 43637, + "onau": 43638, + "aner": 43639, + "planned": 43640, + "evers": 43641, + "Ġretroviral": 43642, + "Ġquantifier": 43643, + "ĠExtracting": 43644, + "Ġacetylated": 43645, + "Orth": 43646, + "ĠSenator": 43647, + "Ġnanosecond": 43648, + "Ġanticipation": 43649, + "ĠECMO": 43650, + "Ġsemicirc": 43651, + "ĠCryptosporidium": 43652, + "ĠTARGET": 43653, + "Ġapples": 43654, + "efield": 43655, + "Ġreman": 43656, + "Ġserovar": 43657, + "ĠTransactions": 43658, + "transitions": 43659, + "ursions": 43660, + "ĠMelatonin": 43661, + "Ġcholecystectomy": 43662, + "ĠAntiviral": 43663, + "hous": 43664, + "Ġotol": 43665, + "Ġmaj": 43666, + "Ġeclip": 43667, + "arel": 43668, + "ATIONAL": 43669, + "MIM": 43670, + "ĠCImg": 43671, + "ĠEndomet": 43672, + "ĠHayashi": 43673, + "Ġchimpanzees": 43674, + "mbf": 43675, + "ĠIPV": 43676, + "actoring": 43677, + "outside": 43678, + "neapolis": 43679, + "Ġdiscarding": 43680, + "numtype": 43681, + "ĠREST": 43682, + "Ġflagellar": 43683, + "ĠChandrase": 43684, + "hofer": 43685, + "Ġelectrocardiogram": 43686, + "Gb": 43687, + "mock": 43688, + "oeb": 43689, + "ĠSMO": 43690, + "ĠMord": 43691, + "ĠBoz": 43692, + "Ġminors": 43693, + "INLINE": 43694, + "Ġthermogravimetric": 43695, + "ĠMelting": 43696, + "ĠNSW": 43697, + "Sham": 43698, + "lotinib": 43699, + "Ġacquisitions": 43700, + "taz": 43701, + "Ġdefaults": 43702, + "Ġoscillates": 43703, + "ĠCaption": 43704, + "Ġdisruptive": 43705, + "Ġsweeping": 43706, + "ĠToolbox": 43707, + "Ġurethral": 43708, + "HBV": 43709, + "ĠRCS": 43710, + "Ġoxys": 43711, + "immuno": 43712, + "htm": 43713, + "oflavin": 43714, + "HIF": 43715, + "ĠSBA": 43716, + "ĠCPE": 43717, + "Ġwhites": 43718, + "ĠReactor": 43719, + "Ġpurp": 43720, + "Ġelectrocatalytic": 43721, + "Ġnonex": 43722, + "Ġtyphimurium": 43723, + "Ġeurop": 43724, + "concave": 43725, + "macrophage": 43726, + "SER": 43727, + "Ġlapse": 43728, + "Ġanatom": 43729, + "ĠRAC": 43730, + "tax": 43731, + "Ġmins": 43732, + "Ġsensu": 43733, + "ĠHebrew": 43734, + "Ġrealism": 43735, + "ĠMicroglia": 43736, + "Ġserialized": 43737, + "ĠHazard": 43738, + "Ġmetamorphosis": 43739, + "ĠIRA": 43740, + "Ġsmearing": 43741, + "Ġphotolysis": 43742, + "Ġchildbirth": 43743, + "Ġsilenced": 43744, + "rawal": 43745, + "Ġquadrants": 43746, + "butanol": 43747, + "Ġstochastically": 43748, + "ĠChambers": 43749, + "ĠNavarro": 43750, + "Ġprocurement": 43751, + "cite": 43752, + "ĠSle": 43753, + "ĠHadoop": 43754, + "Ġdelaying": 43755, + "Atlantic": 43756, + "Spain": 43757, + "falfa": 43758, + "odialysis": 43759, + "ynia": 43760, + "Ġplateaus": 43761, + "Ġmultimode": 43762, + "RESET": 43763, + "ĠRocky": 43764, + "ĠRodrigues": 43765, + "fMRI": 43766, + "rint": 43767, + "ĠTAL": 43768, + "Ġspecular": 43769, + "construction": 43770, + "ĠAthens": 43771, + "Ġcrosslink": 43772, + "Ġcountably": 43773, + "Ġspreadsheet": 43774, + "cribes": 43775, + "consistently": 43776, + "Ġfloodplain": 43777, + "EINVAL": 43778, + "Maca": 43779, + "Ġei": 43780, + "Ġhitherto": 43781, + "Ġsemif": 43782, + "Ġcontinual": 43783, + "ĠHomology": 43784, + "Ġphotocatalysts": 43785, + "isable": 43786, + "ĠHAT": 43787, + "Ġpolyhedra": 43788, + "ĠMayo": 43789, + "Ġlactating": 43790, + "sampler": 43791, + "Ġappliances": 43792, + "TU": 43793, + "Ġchess": 43794, + "ĠTing": 43795, + "Ġinvitation": 43796, + "Ġdistributing": 43797, + "ashima": 43798, + "Ġultral": 43799, + "trend": 43800, + "Ġrelaxations": 43801, + "ĠHelen": 43802, + "Ġbedding": 43803, + "Ġglandular": 43804, + "Ġincrementally": 43805, + "Ġconceal": 43806, + "claimed": 43807, + "ĠEddy": 43808, + "Ġmos": 43809, + "ĠTube": 43810, + "ĠToda": 43811, + "raj": 43812, + "ĠMü": 43813, + "ĠUll": 43814, + "Ġune": 43815, + "berine": 43816, + "Ġpolicym": 43817, + "Ġscholarly": 43818, + "Ġshoreline": 43819, + "Ġaldosterone": 43820, + "ĠBrucella": 43821, + "THE": 43822, + "REAL": 43823, + "Ġexome": 43824, + "ĠDAB": 43825, + "Ġextras": 43826, + "Ġbanding": 43827, + "ĠSiemens": 43828, + "ĠBoost": 43829, + "ĠSupernovae": 43830, + "ĠTracing": 43831, + "Ġascorbate": 43832, + "Italy": 43833, + "bund": 43834, + "Ġdecrement": 43835, + "Ġneurophysiological": 43836, + "Ġblackbody": 43837, + "ĠMcN": 43838, + "Ġcompetencies": 43839, + "oscape": 43840, + "ĠHonours": 43841, + "Ġmastitis": 43842, + "criteria": 43843, + "Ġbiaxial": 43844, + "Ġthawed": 43845, + "ĠFoll": 43846, + "oreceptor": 43847, + "Ġinvention": 43848, + "ADs": 43849, + "Show": 43850, + "------------------------------------------------": 43851, + "Ġellipsoidal": 43852, + "Ġfocussed": 43853, + "ĠDat": 43854, + "ĠRim": 43855, + "ĠLX": 43856, + "ĠGER": 43857, + "insler": 43858, + "Ġtopoisomerase": 43859, + "Ġhyperlipidemia": 43860, + "Ġmystery": 43861, + "Ġnitrification": 43862, + "Ġoncogenes": 43863, + "ĠFuller": 43864, + "ĠBartlett": 43865, + "Ġamphibians": 43866, + "SST": 43867, + "bly": 43868, + "leads": 43869, + "ecycle": 43870, + "ampl": 43871, + "ĠPOM": 43872, + "ĠDCF": 43873, + "strass": 43874, + "antibody": 43875, + "nonlinear": 43876, + "ĠBroadway": 43877, + "ĠCatalogue": 43878, + "ĠμA": 43879, + "Ġacetaminophen": 43880, + "Ġcrystallites": 43881, + "ĠNanotubes": 43882, + "ĠAcknowledgment": 43883, + "Ġmetamorphism": 43884, + "Ġtwinning": 43885, + "ĠAzerbai": 43886, + "xA": 43887, + "CCC": 43888, + "ĠSolids": 43889, + "preds": 43890, + "ĠMontana": 43891, + "WRITE": 43892, + "Ratio": 43893, + "Ġpunch": 43894, + "Ġriding": 43895, + "Ġacne": 43896, + "ĠUre": 43897, + "Ġcorr": 43898, + "ĠQOL": 43899, + "Ġinsult": 43900, + "Ġdominantly": 43901, + "Ġsubsamples": 43902, + "rews": 43903, + "ĠPreservation": 43904, + "ĠAffine": 43905, + "methanone": 43906, + "Ġhedgehog": 43907, + "JH": 43908, + "Ġlined": 43909, + "Ġsten": 43910, + "ĠDarmstadt": 43911, + "ĠLasso": 43912, + "Ġdeproton": 43913, + "Ġshoes": 43914, + "Ġmotives": 43915, + "Ġmicroscop": 43916, + "ophthora": 43917, + "Ġmacron": 43918, + "Ġencouragement": 43919, + "acrylic": 43920, + "ĠTensorFlow": 43921, + "Wrapper": 43922, + "oise": 43923, + "ayak": 43924, + "Ġrepresses": 43925, + "Ġpruned": 43926, + "ĠClar": 43927, + "ĠâĬ²": 43928, + "ĠUnderlying": 43929, + "Implemented": 43930, + "Ġsweat": 43931, + "Ġmeteorites": 43932, + "Ġtweez": 43933, + "Ġprosocial": 43934, + "Ġabrasion": 43935, + "hail": 43936, + "Ġshorth": 43937, + "ismatch": 43938, + "INTR": 43939, + "ĠTrin": 43940, + "Ġphysicists": 43941, + "ĠPEO": 43942, + "ĠMagneto": 43943, + "ĠJacobson": 43944, + "ĠMMPs": 43945, + "ĠIntravenous": 43946, + "Ġneurotransmission": 43947, + "ĠPneumonia": 43948, + "Lind": 43949, + "yre": 43950, + "Ġmaternity": 43951, + "ĠIris": 43952, + "riatal": 43953, + "ĠâĢļ": 43954, + "medetomidine": 43955, + "Ġtriterpen": 43956, + "ĠManuscript": 43957, + "ĠEndoplasmic": 43958, + "ĠPotter": 43959, + "Ġgerminal": 43960, + "Ġphotosystem": 43961, + "Guided": 43962, + "Ġguitarist": 43963, + "ĠChilean": 43964, + "ĠSalvador": 43965, + "ÉĻ": 43966, + "Ġcelestial": 43967, + "omand": 43968, + "Ġnk": 43969, + "Ġvendors": 43970, + "ĠPINK": 43971, + "ĠInorganic": 43972, + "Ġmoderated": 43973, + "SUS": 43974, + "ĠJoshi": 43975, + "ĠStata": 43976, + "ikes": 43977, + "oye": 43978, + "ĠJohnny": 43979, + "Leica": 43980, + "Ġkaon": 43981, + "ĠEquipment": 43982, + "Kim": 43983, + "gado": 43984, + "tures": 43985, + "Ġelem": 43986, + "ĠAAC": 43987, + "cea": 43988, + "odality": 43989, + "Ġaniline": 43990, + "Ġexothermic": 43991, + "ĠGunn": 43992, + "ĠJU": 43993, + "plicable": 43994, + "scapes": 43995, + "typed": 43996, + "Ġinspiratory": 43997, + "REGIST": 43998, + "ĠBryan": 43999, + "Ġanxious": 44000, + "ĠCarpenter": 44001, + "ĠPharmacokinetics": 44002, + "inferior": 44003, + "Frag": 44004, + "ZY": 44005, + "Ġoesophageal": 44006, + "ĠSuk": 44007, + "ĠPron": 44008, + "ĠCDI": 44009, + "AGC": 44010, + "keywords": 44011, + "susceptible": 44012, + "Germany": 44013, + "MAS": 44014, + "iC": 44015, + "anmar": 44016, + "Ġexiting": 44017, + "ĠHale": 44018, + "Ġrhamn": 44019, + "industrial": 44020, + "Ġraft": 44021, + "embrolizumab": 44022, + "Ġdeploying": 44023, + "Ġsalicylic": 44024, + "Rn": 44025, + "Ġcensor": 44026, + "ĠdX": 44027, + "Ġforum": 44028, + "MSI": 44029, + "blad": 44030, + "Ġsquir": 44031, + "CPP": 44032, + "Ġgrapevine": 44033, + "ĠRAFT": 44034, + "Monte": 44035, + "Ġmicroflora": 44036, + "rcl": 44037, + "Ġdecap": 44038, + "ANC": 44039, + "Ġbroaden": 44040, + "Ġfreed": 44041, + "Ġsouthward": 44042, + "ĠJacques": 44043, + "Ġrequesting": 44044, + "ĠAspect": 44045, + "arajan": 44046, + "Failed": 44047, + "fprintf": 44048, + "pytest": 44049, + "ʹ": 44050, + "ĠCm": 44051, + "until": 44052, + "neiss": 44053, + "Ġmonos": 44054, + "ospinal": 44055, + "olsky": 44056, + "contrib": 44057, + "Container": 44058, + "ĠVolunte": 44059, + "ĠAttributes": 44060, + "ĠTumour": 44061, + "Ġreinhardtii": 44062, + "Ġcentromere": 44063, + "ĠSymph": 44064, + "ĠAo": 44065, + "agens": 44066, + "pleted": 44067, + "ieder": 44068, + "Ġactivist": 44069, + "ĠAlmeida": 44070, + "Ġdisturbing": 44071, + "Ġreflexes": 44072, + "DSS": 44073, + "Ġforwards": 44074, + "ronym": 44075, + "ĠIntegrity": 44076, + "WEEN": 44077, + "Ġôı¼Į": 44078, + "Ġfaithfully": 44079, + "Ġpericardial": 44080, + "Japanese": 44081, + "ĠCENP": 44082, + "Kr": 44083, + "Ġdefending": 44084, + "Ġzon": 44085, + "insensitive": 44086, + "Ġlabs": 44087, + "ĠCaM": 44088, + "ĠEurop": 44089, + "MEA": 44090, + "BLAST": 44091, + "xN": 44092, + "alen": 44093, + "Ġclk": 44094, + "lineage": 44095, + "coating": 44096, + "Ġtailoring": 44097, + "CONTR": 44098, + "Ġadrenergic": 44099, + "interpreted": 44100, + "NIH": 44101, + "amoeba": 44102, + "ĠCyr": 44103, + "Ġtriplicates": 44104, + "defining": 44105, + "ĠLogan": 44106, + "tesy": 44107, + "ĠTwist": 44108, + "ĠGrammar": 44109, + "ĠSustained": 44110, + "Ġanharmonic": 44111, + "Ġalve": 44112, + "Ġruler": 44113, + "Ġquanta": 44114, + "Ġdirects": 44115, + "Ġoffloading": 44116, + "ĠGeophysical": 44117, + "Require": 44118, + "Ġhepatoma": 44119, + "Ġfoo": 44120, + "ĠGeorges": 44121, + "Ġbouts": 44122, + "ĠTAK": 44123, + "Ġantidiabetic": 44124, + "ĠReported": 44125, + "presenting": 44126, + "ĠLayered": 44127, + "RENCE": 44128, + "Ġuveitis": 44129, + "bage": 44130, + "Ġfentanyl": 44131, + "ensemble": 44132, + "ĠOSCC": 44133, + "Ġminers": 44134, + "looking": 44135, + "ĠBeer": 44136, + "precipitation": 44137, + "ĠEnterprise": 44138, + "Ġserotonergic": 44139, + "Ġseesaw": 44140, + "ĠAthletics": 44141, + "Ġhydrolytic": 44142, + "Ġtalent": 44143, + "Ġdiscernible": 44144, + "FIL": 44145, + "lives": 44146, + "ĠSales": 44147, + "ĠSSc": 44148, + "erend": 44149, + "clim": 44150, + "antid": 44151, + "INTS": 44152, + "Ġattenuating": 44153, + "Ġtwists": 44154, + "Ġoxygenase": 44155, + "rini": 44156, + "Macaulay": 44157, + "zm": 44158, + "ĠPOT": 44159, + "ĠMp": 44160, + "ĠHey": 44161, + "ĠOVER": 44162, + "illion": 44163, + "Ġinvaluable": 44164, + "Ġantiplatelet": 44165, + "Ġmutans": 44166, + "Ġgraduates": 44167, + "GRAM": 44168, + "ispheric": 44169, + "Ġincompatibility": 44170, + "Ġcarboxylase": 44171, + "Ġbiocontrol": 44172, + "ĠPhysicochemical": 44173, + "ïĻ": 44174, + "Ġlae": 44175, + "ĠAortic": 44176, + "ĠRacing": 44177, + "ĠECD": 44178, + "ivic": 44179, + "Ġelectromechanical": 44180, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 44181, + "Ġsteer": 44182, + "ĠOutside": 44183, + "Ġadenocarcinomas": 44184, + "ĠKeep": 44185, + "Ġcocon": 44186, + "Ġmoderating": 44187, + "Ġreformulated": 44188, + "Ġfundamentals": 44189, + "ĠTesla": 44190, + "ĠStirling": 44191, + "orated": 44192, + "opid": 44193, + "Ġparox": 44194, + "Ġtrivalent": 44195, + "Ġexchangeable": 44196, + "Ġdebuted": 44197, + "Very": 44198, + "reements": 44199, + "ĠTomm": 44200, + "ĠCyn": 44201, + "ĠCatalysts": 44202, + "quat": 44203, + "ĠFER": 44204, + "ĠRum": 44205, + "Ġscanners": 44206, + "ĠâĨĴâĪŀ": 44207, + "otropical": 44208, + "Ġvenues": 44209, + "estimator": 44210, + "Ġemptying": 44211, + "GPP": 44212, + "VIR": 44213, + "Ġcomplicates": 44214, + "ĠNIS": 44215, + "ĠZhen": 44216, + "ĠBlues": 44217, + "Ġtextbooks": 44218, + "Ġsixty": 44219, + "Ġethers": 44220, + "Ġfinancially": 44221, + "ĠmHealth": 44222, + "ĠTut": 44223, + "Ġlaryng": 44224, + "ĠGs": 44225, + "ĠWatch": 44226, + "Ġsev": 44227, + "Ġital": 44228, + "assed": 44229, + "Ġ÷": 44230, + "ĠConsent": 44231, + "Ġnuts": 44232, + "vitreal": 44233, + "Ġmetaphase": 44234, + "Ġtitania": 44235, + "Ġfoils": 44236, + "Ġgalectin": 44237, + "initialize": 44238, + "Ġadvisor": 44239, + "Ġadministering": 44240, + "Bool": 44241, + "Ġcem": 44242, + "Ġreforming": 44243, + "Ġgn": 44244, + "ysh": 44245, + "Ġattor": 44246, + "SCI": 44247, + "Exc": 44248, + "builder": 44249, + "Ġcerium": 44250, + "Ġregistries": 44251, + "ĠMatsumoto": 44252, + "Ġtempting": 44253, + "isha": 44254, + "Ġreorientation": 44255, + "ĠMold": 44256, + "ĠRAGE": 44257, + "yson": 44258, + "Ġunequiv": 44259, + "Ġrelocation": 44260, + "ĠÃķ": 44261, + "ĠReform": 44262, + "ĠREQU": 44263, + "Ġcommensurate": 44264, + "catalog": 44265, + "ĠTPS": 44266, + "Ġlamb": 44267, + "Ġprefactor": 44268, + "archy": 44269, + "Ġdopants": 44270, + "drv": 44271, + "ĠPARAMET": 44272, + "schedule": 44273, + "ochemically": 44274, + "ĠeHealth": 44275, + "unas": 44276, + "ĠPinus": 44277, + "ĠHSA": 44278, + "Ġinterrelations": 44279, + "Ġdepot": 44280, + "ĠPlatinum": 44281, + "Ġlifelong": 44282, + "Ġpersistently": 44283, + "ĠParadox": 44284, + "ĠConformational": 44285, + "esophag": 44286, + "ĠAAT": 44287, + "plin": 44288, + "ĠFCN": 44289, + "ĠDt": 44290, + "oposide": 44291, + "Ġchal": 44292, + "Ġhalt": 44293, + "ĠDetect": 44294, + "Ġdiscriminated": 44295, + "ĠLagrangians": 44296, + "Appro": 44297, + "Ġȧ": 44298, + "Ġimpulsivity": 44299, + "BAT": 44300, + "Chemical": 44301, + "gather": 44302, + "ĠUNC": 44303, + "intron": 44304, + "ĠSimulator": 44305, + "ĠGla": 44306, + "TTT": 44307, + "ĠVolatile": 44308, + "Ġsubsid": 44309, + "ĠBroadcasting": 44310, + "Ġstreptozotocin": 44311, + "Ġfumar": 44312, + "ĠMPEG": 44313, + "Ġinfluenzae": 44314, + "subjects": 44315, + "Ġappropriateness": 44316, + "Ġarcmin": 44317, + "Ġstranded": 44318, + "oylation": 44319, + "ĠDEX": 44320, + "oviral": 44321, + "ĠQuarter": 44322, + "colytic": 44323, + "Ġfriendship": 44324, + "HES": 44325, + "loxacin": 44326, + "Ġere": 44327, + "ĠTrad": 44328, + "uristics": 44329, + "ĠECT": 44330, + "ĠEGCG": 44331, + "ĠLRP": 44332, + "ĠGAG": 44333, + "ĠInP": 44334, + "Ġcontempor": 44335, + "Ġmicror": 44336, + "ierstrass": 44337, + "ĠElectrosp": 44338, + "needed": 44339, + "atmosphere": 44340, + "nT": 44341, + "Ġbandwidths": 44342, + "Ġdiversified": 44343, + "ĠAppropriate": 44344, + "restore": 44345, + "rocnem": 44346, + "ĠLaguerre": 44347, + "ĠSongs": 44348, + "ĠKaluza": 44349, + "ĠSymmetries": 44350, + "ĠSchmitt": 44351, + "Ġbiomolecular": 44352, + "scalebox": 44353, + "Ġintrahepatic": 44354, + "understanding": 44355, + "ĠABCG": 44356, + "Ġunderestimates": 44357, + "ĠStreaming": 44358, + "Ġfictitious": 44359, + "oplasmosis": 44360, + "resident": 44361, + "ĠBary": 44362, + "ĠComa": 44363, + "scrip": 44364, + "Ġdegran": 44365, + "ĠCaMKII": 44366, + "ĠBalmer": 44367, + "ĠPlasm": 44368, + "Ġchelating": 44369, + "ĠParadigm": 44370, + "Ġopponents": 44371, + "EK": 44372, + "Pin": 44373, + "Ġmsec": 44374, + "adone": 44375, + "acht": 44376, + "CCG": 44377, + "ECO": 44378, + "normalize": 44379, + "ĠDesigns": 44380, + "Ġyellowish": 44381, + "glutamyl": 44382, + "Ġdomestication": 44383, + "Ġmonophyletic": 44384, + "dles": 44385, + "nested": 44386, + "ĠGrace": 44387, + "ĠStudios": 44388, + "ĠDiscussions": 44389, + "ophenoxy": 44390, + "Ġveterin": 44391, + "Ġendosym": 44392, + "uttinger": 44393, + "batches": 44394, + "ĠFiji": 44395, + "ĠRNF": 44396, + "ousa": 44397, + "ĠKY": 44398, + "Ġspectrograph": 44399, + "ERIC": 44400, + "ĠMyanmar": 44401, + "ĠConstraining": 44402, + "Ġecologically": 44403, + "Ġfrost": 44404, + "arboux": 44405, + "ĠFibonacci": 44406, + "Ġcanceled": 44407, + "ĠISSN": 44408, + "Rect": 44409, + "another": 44410, + "ĠMMA": 44411, + "OLO": 44412, + "ĠTruth": 44413, + "Ġorthopaedic": 44414, + "Ġtraversing": 44415, + "ischemic": 44416, + "ĠMozambique": 44417, + "ĠMSR": 44418, + "apace": 44419, + "ĠThread": 44420, + "ologia": 44421, + "Ġcalm": 44422, + "methyltransferase": 44423, + "Ġͪ": 44424, + "Ġdrove": 44425, + "Ġcommanded": 44426, + "Ġfeline": 44427, + "ĠRush": 44428, + "ĠLisa": 44429, + "Ġsuperspace": 44430, + "arcy": 44431, + "ĠRegulated": 44432, + "ĠResting": 44433, + "causing": 44434, + "psychotics": 44435, + "qt": 44436, + "Ġtulare": 44437, + "Ġrelocated": 44438, + "Ġrepell": 44439, + "Ġpredatory": 44440, + "people": 44441, + "traits": 44442, + "Euclidean": 44443, + "FDA": 44444, + "XRT": 44445, + "pC": 44446, + "pand": 44447, + "ĠÆ": 44448, + "reve": 44449, + "Ġbids": 44450, + "Ġcousin": 44451, + "Ġsubdomains": 44452, + "tilb": 44453, + "énez": 44454, + "linearly": 44455, + "oproteins": 44456, + "Ġcodec": 44457, + "Ġcontraception": 44458, + "ĠCdk": 44459, + "Ġrailroad": 44460, + "Bench": 44461, + "rng": 44462, + "ĠDLA": 44463, + "entile": 44464, + "ĠCOCO": 44465, + "ĠMatth": 44466, + "ĠOverl": 44467, + "ĠRoutine": 44468, + "Ġmultifocal": 44469, + "Ġartefact": 44470, + "Ġsculpture": 44471, + "cies": 44472, + "mate": 44473, + "ĠØ": 44474, + "urek": 44475, + "ĠBend": 44476, + "ĠNathan": 44477, + "Ġdew": 44478, + "ymia": 44479, + "azzi": 44480, + "ĠErk": 44481, + "Ġgraduation": 44482, + "Boundary": 44483, + "Gra": 44484, + "Ġbfd": 44485, + "ĠSert": 44486, + "Ġovershoot": 44487, + "ĠSeo": 44488, + "Ġsklearn": 44489, + "Ġconservatively": 44490, + "piracy": 44491, + "Ġlaunching": 44492, + "XD": 44493, + "arbitrary": 44494, + "perone": 44495, + "Ġshops": 44496, + "competitive": 44497, + "ĠPakistani": 44498, + "Ġcompetitor": 44499, + "biotics": 44500, + "raits": 44501, + "ĠNoble": 44502, + "Ġsubregions": 44503, + "ĠJump": 44504, + "roller": 44505, + "tris": 44506, + "Ġmacrol": 44507, + "ós": 44508, + "ĠPenic": 44509, + "Ġmicrosomes": 44510, + "Ġimprecise": 44511, + "Ġdowntown": 44512, + "ĠeQTL": 44513, + "ifest": 44514, + "ĠMFI": 44515, + "Ġrarity": 44516, + "âĢĻâĢĻ": 44517, + "Ġbelts": 44518, + "Ġglycosyl": 44519, + "ĠNicolas": 44520, + "synthesis": 44521, + "Oh": 44522, + "hierarch": 44523, + "pps": 44524, + "anets": 44525, + "roads": 44526, + "ATIC": 44527, + "MIMO": 44528, + "ĠContract": 44529, + "Leib": 44530, + "opyrox": 44531, + "Ġinformational": 44532, + "Synonyms": 44533, + "challenge": 44534, + "ĠProximal": 44535, + "ĠCrawford": 44536, + "Ġisopropyl": 44537, + "Ġsubfamilies": 44538, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 44539, + "Ġannotators": 44540, + "Ġreconcile": 44541, + "Ġparsimony": 44542, + "Ġcaspases": 44543, + "cott": 44544, + "environments": 44545, + "Ġdrm": 44546, + "ĠPIL": 44547, + "ĠMec": 44548, + "ĠInfer": 44549, + "ĠSirt": 44550, + "Shell": 44551, + "agulants": 44552, + "seismic": 44553, + "Ġsuburban": 44554, + "ĠXXX": 44555, + "iodes": 44556, + "Ġbackpropagation": 44557, + "traditional": 44558, + "Ġphotocon": 44559, + "ĠMicrobiology": 44560, + "QT": 44561, + "uridine": 44562, + "Ġchop": 44563, + "ĠThé": 44564, + "Ġprejud": 44565, + "Ġencoders": 44566, + "collected": 44567, + "remark": 44568, + "Ġsunspot": 44569, + "ĠPhenolic": 44570, + "Understanding": 44571, + "Ġrejecting": 44572, + "Ġromantic": 44573, + "Ġcentimeters": 44574, + "Ġhallucinations": 44575, + "Home": 44576, + "casted": 44577, + "Ġcw": 44578, + "rai": 44579, + "ĠDisplacement": 44580, + "PHY": 44581, + "carbam": 44582, + "Ġxenon": 44583, + "Ġnarratives": 44584, + "Ġdollar": 44585, + "Ġdynasty": 44586, + "ì§": 44587, + "Ġinforming": 44588, + "ĠOCD": 44589, + "ák": 44590, + "Ġoverheads": 44591, + "juana": 44592, + "ĠKraus": 44593, + "fx": 44594, + "kaya": 44595, + "Ġnid": 44596, + "ĠGrab": 44597, + "Ġinflores": 44598, + "Arc": 44599, + "============": 44600, + "Ġcondenser": 44601, + "Ġnanocar": 44602, + "ommens": 44603, + "Ġsaturating": 44604, + "rece": 44605, + "elif": 44606, + "ĠALE": 44607, + "ĠBub": 44608, + "ĠLaf": 44609, + "andran": 44610, + "Ġpouch": 44611, + "roline": 44612, + "ACHE": 44613, + "CCD": 44614, + "Ġcoolant": 44615, + "Ġgrasslands": 44616, + "ĠSynchronous": 44617, + "izziness": 44618, + "Ġcetuximab": 44619, + "Ġdichotomous": 44620, + "roch": 44621, + "ĠAuckland": 44622, + "obesity": 44623, + "ikit": 44624, + "Ġoperad": 44625, + "ĠOnset": 44626, + "Ġbeforehand": 44627, + "Ġuncomp": 44628, + "USED": 44629, + "ubbing": 44630, + "ĠSMBH": 44631, + "ĠExpedition": 44632, + "Ġhib": 44633, + "ĠPPR": 44634, + "ĠNED": 44635, + "udio": 44636, + "ĠJal": 44637, + "ĠArp": 44638, + "ĠBee": 44639, + "ĠVarieties": 44640, + "Comm": 44641, + "About": 44642, + "ĠAttachment": 44643, + "ODULE": 44644, + "Calculate": 44645, + "Tan": 44646, + "inism": 44647, + "Ġara": 44648, + "Ġcabin": 44649, + "Ġconnexin": 44650, + "Ġcomets": 44651, + "umptive": 44652, + "Ġdestabilization": 44653, + "ĠHolt": 44654, + "ructose": 44655, + "anishi": 44656, + "plasticity": 44657, + "omycosis": 44658, + "ovician": 44659, + "________________": 44660, + "rar": 44661, + "Ġwore": 44662, + "udine": 44663, + "ĠInvariance": 44664, + "Ġperitonitis": 44665, + "Ġmetrology": 44666, + "Ġcloses": 44667, + "Ġcolorless": 44668, + "Noise": 44669, + "DIO": 44670, + "ĠLifshitz": 44671, + "zul": 44672, + "estive": 44673, + "ĠMPA": 44674, + "ĠBooth": 44675, + "ĠDoll": 44676, + "arene": 44677, + "geness": 44678, + "Ġmolecularly": 44679, + "ĠPerkin": 44680, + "Ġdosimetry": 44681, + "ĠSOFT": 44682, + "ĠPyTorch": 44683, + "Ġquarters": 44684, + "ĠKuhn": 44685, + "Ġsplenocytes": 44686, + "RW": 44687, + "cart": 44688, + "leb": 44689, + "Ġcondom": 44690, + "ĠHoc": 44691, + "Ġextents": 44692, + "Ġslug": 44693, + "ĠSupplementation": 44694, + "diffic": 44695, + "esterly": 44696, + "Yu": 44697, + "antigens": 44698, + "ĠÃĴ": 44699, + "Changes": 44700, + "Ġpropylene": 44701, + "ĠPrison": 44702, + "ĠAlgorithmic": 44703, + "Ġtolerances": 44704, + "Adam": 44705, + "Ġesterase": 44706, + "Ġmilder": 44707, + "ĠConvection": 44708, + "PTR": 44709, + "kpc": 44710, + "Ġexo": 44711, + "ĠFah": 44712, + "ĠYFP": 44713, + "ĠCRM": 44714, + "Ġhepatotoxicity": 44715, + "Ġnicotinamide": 44716, + "Ġpatchy": 44717, + "depends": 44718, + "ĠpB": 44719, + "Ġeel": 44720, + "Ġnv": 44721, + "ĠSes": 44722, + "ĠHZ": 44723, + "Ġimprint": 44724, + "epileptic": 44725, + "fluctuations": 44726, + "Ġformalize": 44727, + "chev": 44728, + "Ġdipping": 44729, + "ĠPyramid": 44730, + "Ġholo": 44731, + "ĠMTs": 44732, + "Ġlaminates": 44733, + "Ġwormhole": 44734, + "LAP": 44735, + "hape": 44736, + "Ġak": 44737, + "Ġreals": 44738, + "Ġbystand": 44739, + "Ġinterleaved": 44740, + "Ġxz": 44741, + "ovy": 44742, + "Ġcoprime": 44743, + "uclides": 44744, + "Ġtrimming": 44745, + "MICAL": 44746, + "pyrrole": 44747, + "Ia": 44748, + "NLS": 44749, + "Quality": 44750, + "takes": 44751, + "zinc": 44752, + "ĠPione": 44753, + "ĠEwing": 44754, + "ĠLCA": 44755, + "ĠÃĶ": 44756, + "ictus": 44757, + "Ġcollim": 44758, + "Ġphylogenetically": 44759, + "ĠKeeping": 44760, + "ĠFaith": 44761, + "bonds": 44762, + "titer": 44763, + "Ġsubcategories": 44764, + "shaded": 44765, + "Ġphotospheric": 44766, + "ĠAppearance": 44767, + "ĠUniversities": 44768, + "Ġglomeruli": 44769, + "ĠPrefrontal": 44770, + "Ġprivilege": 44771, + "iH": 44772, + "uya": 44773, + "ĠLCL": 44774, + "ĠInGaAs": 44775, + "Inspired": 44776, + "atalog": 44777, + "ĠPerceptions": 44778, + "ĠNaHCO": 44779, + "Ġstreamline": 44780, + "trajectory": 44781, + "ĠMicrom": 44782, + "Ġbedside": 44783, + "ĠRomero": 44784, + "Ġgaugino": 44785, + "DEN": 44786, + "Fa": 44787, + "Olymp": 44788, + "eal": 44789, + "uels": 44790, + "icylic": 44791, + "Ġgod": 44792, + "Ġattaining": 44793, + "Ġprotests": 44794, + "Ġnowhere": 44795, + "desorption": 44796, + "ĠHydroxy": 44797, + "ĠErbB": 44798, + "ĠSPAR": 44799, + "Ġhinders": 44800, + "herenkov": 44801, + "KERNEL": 44802, + "Ġsect": 44803, + "ulong": 44804, + "Ġpreprocessed": 44805, + "fractional": 44806, + "oyage": 44807, + "Ġphosphatases": 44808, + "Ġcoastline": 44809, + "Ġhref": 44810, + "ĠSutherland": 44811, + "oxone": 44812, + "Ġhomomorphic": 44813, + "DEM": 44814, + "Ġbovis": 44815, + "ĠCBP": 44816, + "plen": 44817, + "ĠBuc": 44818, + "ĠGior": 44819, + "Ġcompost": 44820, + "ĠOracle": 44821, + "ĠSphere": 44822, + "ĠSchre": 44823, + "derivatives": 44824, + "lytes": 44825, + "ĠYo": 44826, + "Ġcyclones": 44827, + "ĠMaize": 44828, + "Ġunfair": 44829, + "Template": 44830, + "Ġimpregnation": 44831, + "Ġlaparoscopy": 44832, + "Ġhamiltonian": 44833, + "ignore": 44834, + "Ġdisposable": 44835, + "earic": 44836, + "Ġelectoral": 44837, + "ccos": 44838, + "ĠShh": 44839, + "Ġturbo": 44840, + "Ġintrusive": 44841, + "Ġprecedence": 44842, + "annotated": 44843, + "Ġdystonia": 44844, + "Fat": 44845, + "uins": 44846, + "Ġsway": 44847, + "arizing": 44848, + "illen": 44849, + "Ġyi": 44850, + "Ġnormed": 44851, + "ĠÌĤ": 44852, + "ĠExtr": 44853, + "ĠProteome": 44854, + "Document": 44855, + "ĠQUANTUM": 44856, + "titi": 44857, + "ĠCPC": 44858, + "ĠMiles": 44859, + "ĠBoc": 44860, + "ĠRTS": 44861, + "CTX": 44862, + "Ġsafegu": 44863, + "ĠNormally": 44864, + "ĠÃľber": 44865, + "onious": 44866, + "ĠSCE": 44867, + "Ġalfalfa": 44868, + "ĠLut": 44869, + "Ġcout": 44870, + "Ġenlarge": 44871, + "ĠEnable": 44872, + "Ġvirion": 44873, + "ĠShallow": 44874, + "definitely": 44875, + "ĠColin": 44876, + "ĠRetention": 44877, + "Ġmimicry": 44878, + "################################################################": 44879, + "NSCLC": 44880, + "Ġgratitude": 44881, + "Ġtending": 44882, + "ĠIDS": 44883, + "eret": 44884, + "rican": 44885, + "Ġxn": 44886, + "ĠYoo": 44887, + "Ġoptimise": 44888, + "Arrow": 44889, + "ĠTransferase": 44890, + "PKC": 44891, + "ĠGuangzhou": 44892, + "ruc": 44893, + "yrid": 44894, + "isz": 44895, + "ĠFIX": 44896, + "ĠDatabases": 44897, + "astron": 44898, + "Ġplayback": 44899, + "Ġnarrowly": 44900, + "Correlation": 44901, + "ĠAffinity": 44902, + "Ġfunctorial": 44903, + "Ġlectins": 44904, + "Ġruptured": 44905, + "Display": 44906, + "ĠSymptom": 44907, + "Ġequidistant": 44908, + "ĠRiccati": 44909, + "ĠAchievement": 44910, + "grand": 44911, + "onated": 44912, + "ĠdH": 44913, + "ĠFID": 44914, + "ĠDER": 44915, + "ĠCoA": 44916, + "Ġgasification": 44917, + "ĠCONS": 44918, + "Ġaccompanies": 44919, + "Ġimpede": 44920, + "Ġprecede": 44921, + "Ġkitchen": 44922, + "progress": 44923, + "Ġwiring": 44924, + "lerenes": 44925, + "ĠGius": 44926, + "Ġtransp": 44927, + "retrie": 44928, + "ijer": 44929, + "affer": 44930, + "Ġbirthday": 44931, + "ĠHald": 44932, + "Ġmusculus": 44933, + "ĠToken": 44934, + "ĠBowel": 44935, + "Ġskipped": 44936, + "Cha": 44937, + "bv": 44938, + "ĠBlow": 44939, + "Ġpreoperatively": 44940, + "Ġglove": 44941, + "ĠLeven": 44942, + "Ġmesop": 44943, + "ĠAuxiliary": 44944, + "ensuremath": 44945, + "jus": 44946, + "Å©": 44947, + "Ġvoter": 44948, + "ĠHitch": 44949, + "proxy": 44950, + "ĠKut": 44951, + "Ġpoems": 44952, + "ĠAngl": 44953, + "cera": 44954, + "Ġstarred": 44955, + "AGES": 44956, + "Science": 44957, + "Analyses": 44958, + "Ġreferees": 44959, + "Ġabrogated": 44960, + "Ġdesalination": 44961, + "ĠPrandtl": 44962, + "Pit": 44963, + "Ġnatal": 44964, + "ogran": 44965, + "ystitis": 44966, + "Ġdesm": 44967, + "Ġcurious": 44968, + "Ġdemon": 44969, + "uzzi": 44970, + "ochondrial": 44971, + "ĠTreaty": 44972, + "Tracker": 44973, + "rhoeae": 44974, + "LW": 44975, + "furt": 44976, + "Ġomp": 44977, + "isational": 44978, + "Ġmemorial": 44979, + "ĠLatency": 44980, + "ĠHypot": 44981, + "Ġglued": 44982, + "exactly": 44983, + "Ġcontraind": 44984, + "Cancer": 44985, + "Ġffi": 44986, + "ĠNAA": 44987, + "ĠChr": 44988, + "egg": 44989, + "ĠMotiv": 44990, + "Ġlayouts": 44991, + "Ġclimb": 44992, + "Ġappendicitis": 44993, + "CUDA": 44994, + "Ġphotoproduction": 44995, + "ĠSIP": 44996, + "Ġveto": 44997, + "perin": 44998, + "ĠUnity": 44999, + "byear": 45000, + "Ġforwarded": 45001, + "ĠDominant": 45002, + "holz": 45003, + "ĠThoracic": 45004, + "DEFINE": 45005, + "Ġtyrosinase": 45006, + "Bad": 45007, + "INA": 45008, + "fuel": 45009, + "Ġgi": 45010, + "ĠVIS": 45011, + "astolic": 45012, + "Ġoxaliplatin": 45013, + "effector": 45014, + "ĉĉĉĉĠ": 45015, + "еÑĢ": 45016, + "ĠBaby": 45017, + "Ġwashout": 45018, + "pituitary": 45019, + "NGC": 45020, + "Ġdns": 45021, + "ĠPoz": 45022, + "ĠUz": 45023, + "positron": 45024, + "ĠElectrons": 45025, + "Ġhemangi": 45026, + "ĠZnS": 45027, + "ĠTEMP": 45028, + "ĠExperimentally": 45029, + "fluorouracil": 45030, + "Ġlaparotomy": 45031, + "analyzer": 45032, + "ocorticoid": 45033, + "ĠIMPL": 45034, + "ĠDNNs": 45035, + "ĠFresnel": 45036, + "Mont": 45037, + "Ġtapes": 45038, + "ulomb": 45039, + "impedance": 45040, + "ĠHET": 45041, + "atha": 45042, + "modulation": 45043, + "ĠCortic": 45044, + "Ġâľĵ": 45045, + "ĠFairness": 45046, + "ĠStiff": 45047, + "Ġbuttons": 45048, + "css": 45049, + "Ġandroid": 45050, + "elast": 45051, + "ĠTeflon": 45052, + "ĠMBC": 45053, + "ĠJT": 45054, + "Ġmultilayered": 45055, + "ĠRee": 45056, + "uitar": 45057, + "ĠPhilips": 45058, + "ĠSkip": 45059, + "doctoral": 45060, + "iyama": 45061, + "ĠLeadership": 45062, + "ĠCrisis": 45063, + "Ġdesensitization": 45064, + "vous": 45065, + "ĠSPP": 45066, + "ĠPGA": 45067, + "ĠNever": 45068, + "Ġdefeating": 45069, + "Ġfibromyalgia": 45070, + "ĠMRP": 45071, + "ĠABCA": 45072, + "ĠLowe": 45073, + "Ġeroded": 45074, + "Ġaugments": 45075, + "ĠBoris": 45076, + "Ġnephrectomy": 45077, + "ĠSherman": 45078, + "Ġrefrigeration": 45079, + "ĠHernández": 45080, + "Ãĺ": 45081, + "ĠTors": 45082, + "chus": 45083, + "ĠVarg": 45084, + "Ġroset": 45085, + "CLR": 45086, + "DEP": 45087, + "Strong": 45088, + "Ġcinerea": 45089, + "ĠHeinrich": 45090, + "Rout": 45091, + "odus": 45092, + "ĠPhone": 45093, + "ĠPerl": 45094, + "Ġseasonally": 45095, + "holding": 45096, + "Ġencephalomyelitis": 45097, + "Ġfascia": 45098, + "Ġlittermates": 45099, + "ĠWITHOUT": 45100, + "б": 45101, + "Ġalerts": 45102, + "ĠKoll": 45103, + "ĠUrs": 45104, + "elfand": 45105, + "ĠRNAP": 45106, + "Ġinvariably": 45107, + "Ġscintigraphy": 45108, + "ĠSebastian": 45109, + "kinesia": 45110, + "CUR": 45111, + "inants": 45112, + "ĠpET": 45113, + "idial": 45114, + "ĠUPLC": 45115, + "Ġsuis": 45116, + "Ġbasolateral": 45117, + "ĠModulates": 45118, + "orbic": 45119, + "Img": 45120, + "Ġparasitism": 45121, + "Ġlaminate": 45122, + "ogeographic": 45123, + "ĠRibeiro": 45124, + "ĠGlutathione": 45125, + "ĠAberrant": 45126, + "Ġsclero": 45127, + "ĠDLS": 45128, + "ĠRuth": 45129, + "Ġrecast": 45130, + "recated": 45131, + "okie": 45132, + "ĠParks": 45133, + "Ġfoliations": 45134, + "ĠDawson": 45135, + "Ġtannins": 45136, + "ĠAaron": 45137, + "pS": 45138, + "itating": 45139, + "ĠITC": 45140, + "ipients": 45141, + "ohy": 45142, + "CCs": 45143, + "Ġethanolic": 45144, + "corhynchus": 45145, + "Ġorientational": 45146, + "Ġhabituation": 45147, + "Ġconversational": 45148, + "ĠVentricular": 45149, + "Ġintercalated": 45150, + "Ġphosphodiesterase": 45151, + "ĠSeifert": 45152, + "wk": 45153, + "algesia": 45154, + "Ġstegan": 45155, + "ĠLus": 45156, + "ophantine": 45157, + "Ġcorrects": 45158, + "ĠObama": 45159, + "latency": 45160, + "Ġsonar": 45161, + "ORMAL": 45162, + "Ġseaweed": 45163, + "ĠPowers": 45164, + "ĠShapley": 45165, + "Lore": 45166, + "Ġawa": 45167, + "alach": 45168, + "ĠFon": 45169, + "ensate": 45170, + "Ġoptima": 45171, + "INF": 45172, + "Ġpolygenic": 45173, + "Ġmesoderm": 45174, + "Conver": 45175, + "BRID": 45176, + "ĠHelp": 45177, + "ĠRasmussen": 45178, + "Ġprokaryotes": 45179, + "ĠEurasian": 45180, + "ĠPermeability": 45181, + "Ġnau": 45182, + "ĠClem": 45183, + "odilation": 45184, + "ĠDiaz": 45185, + "itious": 45186, + "ĠChad": 45187, + "ORA": 45188, + "ĠSimons": 45189, + "ĠDistances": 45190, + "Ġastrometric": 45191, + "ĠCPUs": 45192, + "Ġthioredoxin": 45193, + "perturbation": 45194, + "Ġdendrimer": 45195, + "algal": 45196, + "Ġceliac": 45197, + "asz": 45198, + "ĠPPE": 45199, + "qua": 45200, + "ĠBoll": 45201, + "chr": 45202, + "Ġpreview": 45203, + "ĠProjections": 45204, + "ĠAsians": 45205, + "ĠInferring": 45206, + "ĠNaive": 45207, + "ĠHiggins": 45208, + "ĠLocated": 45209, + "cardiac": 45210, + "ĠLarson": 45211, + "hazard": 45212, + "ĠScientists": 45213, + "Ġpinn": 45214, + "ENCY": 45215, + "forme": 45216, + "chitects": 45217, + "ofluorescent": 45218, + "ĠPortal": 45219, + "Ġpupae": 45220, + "interesting": 45221, + "įĢ": 45222, + "react": 45223, + "atos": 45224, + "enin": 45225, + "tio": 45226, + "ĠCapp": 45227, + "ĠMau": 45228, + "ĠLSC": 45229, + "ĠVlasov": 45230, + "Ġsubsum": 45231, + "Ġdeserve": 45232, + "ASD": 45233, + "Rece": 45234, + "Ġconsonant": 45235, + "Ġimpregnated": 45236, + "Ġlignocellulosic": 45237, + "Ġsows": 45238, + "lement": 45239, + "ĠTier": 45240, + "ĠMEF": 45241, + "ĠHugh": 45242, + "inck": 45243, + "pyrazole": 45244, + "ULATIONS": 45245, + "ĠALI": 45246, + "ĠDrift": 45247, + "Ġsolubilized": 45248, + "Ġdrafting": 45249, + "icyclic": 45250, + "Ġredesign": 45251, + "Ġdeliberate": 45252, + "Ġtapping": 45253, + "ĠTomas": 45254, + "ĠTunneling": 45255, + "ĠCBR": 45256, + "Ġanodes": 45257, + "ĠLSR": 45258, + "ĠNath": 45259, + "rosive": 45260, + "ĠHeidelberg": 45261, + "Ġcrushing": 45262, + "ĠShore": 45263, + "Ġmalondialdehyde": 45264, + "ĠMRD": 45265, + "ogloss": 45266, + "ncia": 45267, + "Ġgranuloma": 45268, + "Ġplaintext": 45269, + "Ġarteriovenous": 45270, + "Ġrifampicin": 45271, + "Lepidoptera": 45272, + "Oct": 45273, + "Ġlone": 45274, + "ĠAppe": 45275, + "ĠIntermitt": 45276, + "compile": 45277, + "potentials": 45278, + "ĠStandardized": 45279, + "Ġventilatory": 45280, + "Ġhypercholesterolemia": 45281, + "ĠEVALUATION": 45282, + "ked": 45283, + "xC": 45284, + "enos": 45285, + "Ġbauthorbsnm": 45286, + "ĠRost": 45287, + "mathopen": 45288, + "Ġcontested": 45289, + "Ġros": 45290, + "otho": 45291, + "Ġemits": 45292, + "erozo": 45293, + "Ġpropranolol": 45294, + "Ġexacerbate": 45295, + "Integrating": 45296, + "ĠWarsaw": 45297, + "Ñĩ": 45298, + "refractory": 45299, + "ĠMort": 45300, + "phosphonate": 45301, + "GLT": 45302, + "ĠChloride": 45303, + "ĠLUAD": 45304, + "ĠSQUID": 45305, + "ĠOBSERVATIONS": 45306, + "Ħĺ": 45307, + "agles": 45308, + "uger": 45309, + "Ġdiffusing": 45310, + "ylar": 45311, + "Ġantip": 45312, + "renormal": 45313, + "Ġsheared": 45314, + "ĠAndr": 45315, + "ymptotics": 45316, + "ĠIdentified": 45317, + "Ġflexor": 45318, + "Liouville": 45319, + "ĠCytotoxic": 45320, + "Lock": 45321, + "donald": 45322, + "ĠSHA": 45323, + "projected": 45324, + "plicial": 45325, + "Ġbasics": 45326, + "ĠCarvalho": 45327, + "Ġheterocyclic": 45328, + "Ġfluorophore": 45329, + "ĠIntrigu": 45330, + "ĠAnnealing": 45331, + "Gln": 45332, + "Hispanic": 45333, + "Ġsaus": 45334, + "ĠTCS": 45335, + "ĠHAP": 45336, + "Ġytt": 45337, + "Ġconsulting": 45338, + "rects": 45339, + "Ġinfall": 45340, + "LEV": 45341, + "triazole": 45342, + "Ġnarrowed": 45343, + "Ġamphoteric": 45344, + "ĠSorting": 45345, + "ĠMoments": 45346, + "Ġarabin": 45347, + "Ġcoconut": 45348, + "ĠIntriguingly": 45349, + "Ġpushes": 45350, + "Ġmec": 45351, + "ĠNair": 45352, + "Ġcolistin": 45353, + "ĠObtained": 45354, + "dfs": 45355, + "Ġcompetency": 45356, + "WORD": 45357, + "ĠAAS": 45358, + "ĠBNP": 45359, + "ĠHAS": 45360, + "ĠLun": 45361, + "ĠLnc": 45362, + "Ġhydrocephalus": 45363, + "Ġhomological": 45364, + "Ġcarbonic": 45365, + "ĠHiSeq": 45366, + "community": 45367, + "Ġcephalospor": 45368, + "Ġhostile": 45369, + "provide": 45370, + "Ġskyrmion": 45371, + "DAG": 45372, + "Ġcnt": 45373, + "Ġhay": 45374, + "Ġorderings": 45375, + "Ġflock": 45376, + "HEA": 45377, + "ĠNeurom": 45378, + "Ġboosts": 45379, + "ĠCardinal": 45380, + "ĠBachelor": 45381, + "Ġdecent": 45382, + "ĠYak": 45383, + "Ġcalcd": 45384, + "ĠBoer": 45385, + "Ġtranscriptomics": 45386, + "Ġrearranged": 45387, + "ĠPolymorphisms": 45388, + "ĠPrasad": 45389, + "oinositide": 45390, + "bars": 45391, + "Ġãģ": 45392, + "ĠSAA": 45393, + "Ġonion": 45394, + "agel": 45395, + "ĠHp": 45396, + "ogrel": 45397, + "divisions": 45398, + "andan": 45399, + "arias": 45400, + "Ġcolo": 45401, + "ragon": 45402, + "Ġschizophren": 45403, + "âī¡": 45404, + "Ġreplicative": 45405, + "Ġdegenerated": 45406, + "Ġsteepest": 45407, + "Volume": 45408, + "IENT": 45409, + "Public": 45410, + "Ten": 45411, + "enberger": 45412, + "ĠCoun": 45413, + "ĠEpp": 45414, + "izo": 45415, + "Ġcomplexed": 45416, + "Ġferroc": 45417, + "kenstein": 45418, + "ĠJerry": 45419, + "Ġparadoxical": 45420, + "xg": 45421, + "icer": 45422, + "osol": 45423, + "Ġannu": 45424, + "Ġankyl": 45425, + "chung": 45426, + "entious": 45427, + "Ġpreshe": 45428, + "enetic": 45429, + "ĠHealing": 45430, + "ĠParabolic": 45431, + "Ġfigs": 45432, + "ĠKinematic": 45433, + "Ġobligate": 45434, + "ĠLayout": 45435, + "Ġtelemedicine": 45436, + "ĠLennard": 45437, + "pci": 45438, + "arone": 45439, + "ĠZach": 45440, + "Ġprototyping": 45441, + "ĠMetagen": 45442, + "IMAL": 45443, + "conscious": 45444, + "Ġquadrilateral": 45445, + "ĠUncertainties": 45446, + "ĠPrefecture": 45447, + "GBM": 45448, + "rals": 45449, + "alus": 45450, + "Ġhopes": 45451, + "Ġclicks": 45452, + "ĠJD": 45453, + "lectance": 45454, + "Ġpathologists": 45455, + "ussels": 45456, + "tisone": 45457, + "CPT": 45458, + "Ġmiscon": 45459, + "ĠNeurode": 45460, + "Ġmutagenic": 45461, + "ĠMultimedia": 45462, + "Original": 45463, + "ĠDrake": 45464, + "PWM": 45465, + "Ġpiles": 45466, + "stant": 45467, + "ARA": 45468, + "ĠRING": 45469, + "modifying": 45470, + "Ġastrocyt": 45471, + "ĠCyst": 45472, + "Ġlegends": 45473, + "glucuron": 45474, + "Ġincompletely": 45475, + "ĠConfed": 45476, + "ĠDLBCL": 45477, + "ĠPapua": 45478, + "Ġcontrastive": 45479, + "ĠSIMULATION": 45480, + "ĠJuvenile": 45481, + "aggregated": 45482, + "ĠcGMP": 45483, + "ictive": 45484, + "ĠHNF": 45485, + "ĠNPV": 45486, + "ĠKoc": 45487, + "ometallic": 45488, + "mini": 45489, + "ĠQuantit": 45490, + "ĠCornell": 45491, + "Ġdeduction": 45492, + "Ġcoinciding": 45493, + "ĠIrr": 45494, + "Precision": 45495, + "Ġginseng": 45496, + "ões": 45497, + "jer": 45498, + "ĠReader": 45499, + "ĠByr": 45500, + "corrections": 45501, + "devices": 45502, + "Ġambul": 45503, + "Ġpedicle": 45504, + "ĠDependency": 45505, + "ĠStriking": 45506, + "Ġwarehouse": 45507, + "Ġrecirculation": 45508, + "Ġgonorrhoeae": 45509, + "ĠPRES": 45510, + "ĠBhar": 45511, + "Ġflushing": 45512, + "torus": 45513, + "ĠIRB": 45514, + "glycine": 45515, + "Ġmethamphetamine": 45516, + "Ġmirrored": 45517, + "ĠWilliamson": 45518, + "Ġcathodes": 45519, + "hydroxylase": 45520, + "Radio": 45521, + "Ġfurniture": 45522, + "ĠRosenberg": 45523, + "ĠNSAIDs": 45524, + "semiconductor": 45525, + "Ġasynchron": 45526, + "ĠBerm": 45527, + "ĠInten": 45528, + "ibe": 45529, + "Force": 45530, + "pathogenic": 45531, + "smokers": 45532, + "Ġdiphenyl": 45533, + "Ġи": 45534, + "Ġstandalone": 45535, + "Ġlithospheric": 45536, + "Ġtradeoffs": 45537, + "Ġantich": 45538, + "Ġthymidine": 45539, + "ĠMedicinal": 45540, + "Ġentrepreneurial": 45541, + "Ġtrapezoidal": 45542, + "ĠAsynchronous": 45543, + "tifying": 45544, + "ĠCollapse": 45545, + "ĠHEV": 45546, + "ĠFrozen": 45547, + "ĠTeichmüller": 45548, + "rocnemius": 45549, + "Ġfern": 45550, + "Ġws": 45551, + "omol": 45552, + "Ġenclosing": 45553, + "rapid": 45554, + "Ġlogged": 45555, + "varvec": 45556, + "Ġamplifying": 45557, + "differences": 45558, + "otonin": 45559, + "ĠPromoting": 45560, + "ĠFritz": 45561, + "Ġattainable": 45562, + "Ġaltim": 45563, + "ĠOGD": 45564, + "Ġthermometer": 45565, + "Solver": 45566, + "ĠBirk": 45567, + "LENBQU": 45568, + "ĠGateway": 45569, + "Ġengraftment": 45570, + "FIF": 45571, + "HSD": 45572, + "Ġrestructuring": 45573, + "ĠTensile": 45574, + "ĠCele": 45575, + "ylus": 45576, + "Ġfeather": 45577, + "Ġdrifting": 45578, + "ĠPreclinical": 45579, + "yrrole": 45580, + "Ġcommem": 45581, + "Ġfixations": 45582, + "Petsc": 45583, + "ĠIschemia": 45584, + "aA": 45585, + "asoro": 45586, + "ĠSony": 45587, + "ĠUt": 45588, + "Ġextensor": 45589, + "ĠChau": 45590, + "ĠIsotopic": 45591, + "ILI": 45592, + "CNP": 45593, + "ĠDEF": 45594, + "Ġmountainous": 45595, + "Ġsarcomas": 45596, + "ugoslav": 45597, + "CALL": 45598, + "Sensitive": 45599, + "atro": 45600, + "Ġuncoupling": 45601, + "skew": 45602, + "ĠEmissions": 45603, + "innati": 45604, + "Ġconceptualization": 45605, + "Ġowns": 45606, + "Ġsquadron": 45607, + "ĠStrengths": 45608, + "Coh": 45609, + "UAL": 45610, + "magenta": 45611, + "usb": 45612, + "ĠSPC": 45613, + "cones": 45614, + "ĠSelecting": 45615, + "ĠParish": 45616, + "Ġvalidates": 45617, + "ĠÍĹ": 45618, + "Ġposteriorly": 45619, + "omonad": 45620, + "VOL": 45621, + "jectivity": 45622, + "ĠCLO": 45623, + "ĠVTA": 45624, + "Ġunpleasant": 45625, + "Ġcareers": 45626, + "Ġautomorphic": 45627, + "ĠNanow": 45628, + "Ġasterisks": 45629, + "ĠSchulz": 45630, + "publication": 45631, + "Ġbiv": 45632, + "Ġrug": 45633, + "recognition": 45634, + "Ġreferrals": 45635, + "Ġneurones": 45636, + "ĠCaffe": 45637, + "Connor": 45638, + "ĠSheffield": 45639, + "unitinib": 45640, + "ĠAntagon": 45641, + "Ġpneumatic": 45642, + "Ġcleaner": 45643, + "ĠBAO": 45644, + "ĠScilabString": 45645, + "neighbour": 45646, + "Euler": 45647, + "ĠTuple": 45648, + "oty": 45649, + "dian": 45650, + "Ġyoga": 45651, + "Ġevanes": 45652, + "Ġstarved": 45653, + "Ġfluctuate": 45654, + "ĠBiomarker": 45655, + "Ġimpulses": 45656, + "Ġossification": 45657, + "Ġdemyelination": 45658, + "ĠSAD": 45659, + "essing": 45660, + "Ġreddish": 45661, + "Ġsynth": 45662, + "Ġcurvilinear": 45663, + "ĠDenis": 45664, + "Ġphonetic": 45665, + "Ġhammer": 45666, + "Ġepidermidis": 45667, + "Ġplagioclase": 45668, + "Ġĉ": 45669, + "Ġwolf": 45670, + "osced": 45671, + "Ġphotothermal": 45672, + "Ġchewing": 45673, + "Maximum": 45674, + "Ġmismatched": 45675, + "ĠFcγ": 45676, + "Ġumbrella": 45677, + "ĠSiberian": 45678, + "arra": 45679, + "ipped": 45680, + "ympathetic": 45681, + "acceleration": 45682, + "Ġeigenmodes": 45683, + "ĠEquivalently": 45684, + "ĠPRISMA": 45685, + "conservative": 45686, + "ñez": 45687, + "Ġvolcanoes": 45688, + "Ġtelemetry": 45689, + "mile": 45690, + "ĠBoch": 45691, + "oprim": 45692, + "Ġincipient": 45693, + "Ġunderstandable": 45694, + "atricyclo": 45695, + "ĠLogical": 45696, + "ĠQueue": 45697, + "Ġcryostat": 45698, + "definecolor": 45699, + "ĠSae": 45700, + "Ġarct": 45701, + "Ġsoul": 45702, + "ĠHistopathological": 45703, + "ĠNeurot": 45704, + "Ġmethanolic": 45705, + "Px": 45706, + "ĠTitle": 45707, + "otomic": 45708, + "ĠEld": 45709, + "ĠEMA": 45710, + "Ġdebrid": 45711, + "timulatory": 45712, + "ĠZan": 45713, + "Ġnormot": 45714, + "Ġfluidity": 45715, + "Ġfluidized": 45716, + "previously": 45717, + "Ġcracked": 45718, + "ĠExplaining": 45719, + "ĠONE": 45720, + "ĠFlora": 45721, + "ĠHybridization": 45722, + "Ġreticul": 45723, + "FK": 45724, + "notic": 45725, + "ĠnA": 45726, + "ĠPab": 45727, + "ticum": 45728, + "andy": 45729, + "ugia": 45730, + "ilet": 45731, + "MING": 45732, + "Ġrests": 45733, + "ompact": 45734, + "Ġtrackers": 45735, + "phosphatase": 45736, + "ĠTransfection": 45737, + "ĠHospitals": 45738, + "acrine": 45739, + "ĠDell": 45740, + "ĠVAE": 45741, + "ĠThroughput": 45742, + "hevsky": 45743, + "ĠSommer": 45744, + "PSA": 45745, + "ìļ": 45746, + "Ġbush": 45747, + "Ġlunch": 45748, + "ĠSwe": 45749, + "ĠInstruction": 45750, + "akami": 45751, + "Ġdisinfect": 45752, + "Ġcorps": 45753, + "ĉĉĠĠ": 45754, + "Ġprompts": 45755, + "MSH": 45756, + "ĠAgrawal": 45757, + "Ġlysosome": 45758, + "integrin": 45759, + "ĠỸ": 45760, + "Ġnondecreasing": 45761, + "ĠRequest": 45762, + "ĠREP": 45763, + "occus": 45764, + "Ġlagrangian": 45765, + "oregulation": 45766, + "ол": 45767, + "ĠBoson": 45768, + "Iso": 45769, + "atellites": 45770, + "resectable": 45771, + "riv": 45772, + "Ġdeaminase": 45773, + "Ġcoheren": 45774, + "Ġdecoy": 45775, + "ĠExtinction": 45776, + "acetone": 45777, + "Ġgovernmental": 45778, + "Ġcumulants": 45779, + "Ġviscosities": 45780, + "Register": 45781, + "documented": 45782, + "Ġimmortalized": 45783, + "DPP": 45784, + "Gel": 45785, + "bron": 45786, + "kow": 45787, + "ĠProportion": 45788, + "ĠChase": 45789, + "ĠClad": 45790, + "Ġadapts": 45791, + "ĠCAV": 45792, + "Ġż": 45793, + "Ġpelleted": 45794, + "Ġpenguin": 45795, + "ĠZhejiang": 45796, + "feasible": 45797, + "DIV": 45798, + "iya": 45799, + "Ġthrowing": 45800, + "resia": 45801, + "ĠNr": 45802, + "ESP": 45803, + "CDF": 45804, + "suppressed": 45805, + "Ġtetrachlor": 45806, + "Ġaerospace": 45807, + "Until": 45808, + "Ġpayoffs": 45809, + "Ġtownship": 45810, + "Ġesterification": 45811, + "ĠAchilles": 45812, + "Ġracem": 45813, + "opyranoside": 45814, + "ĠCSM": 45815, + "assis": 45816, + "Ġsupercell": 45817, + "ĠRegime": 45818, + "IRA": 45819, + "Ġsubsequences": 45820, + "ĠPenet": 45821, + "ĠAnalytics": 45822, + "ĠLVEF": 45823, + "Ġbiphenyl": 45824, + "Gradient": 45825, + "osylation": 45826, + "ĠWRF": 45827, + "ofs": 45828, + "conductors": 45829, + "Ġbacked": 45830, + "pidal": 45831, + "ĠNFAT": 45832, + "ĠRemember": 45833, + "Ġtelomeric": 45834, + "Ġtaurine": 45835, + "increases": 45836, + "Ġunintended": 45837, + "ĠNervous": 45838, + "Ras": 45839, + "ylyl": 45840, + "Ġaestiv": 45841, + "ĠSick": 45842, + "ĠTheta": 45843, + "Ġcliques": 45844, + "Ġsofter": 45845, + "ĠQRS": 45846, + "lliptic": 45847, + "ĠImmunotherapy": 45848, + "QUF": 45849, + "onomously": 45850, + "ĠFLU": 45851, + "ĠIncorporation": 45852, + "ĠFormicidae": 45853, + "JR": 45854, + "whole": 45855, + "Ġcasing": 45856, + "Ġnob": 45857, + "ĠDou": 45858, + "Ġintronic": 45859, + "Ġentrapment": 45860, + "orbits": 45861, + "Ġsalam": 45862, + "ĠCRS": 45863, + "ĠSwan": 45864, + "ĠEdgar": 45865, + "Ġconcomitantly": 45866, + "atetracyclo": 45867, + "ĠAHR": 45868, + "ticks": 45869, + "ĠBing": 45870, + "ĠRift": 45871, + "Ġplugging": 45872, + "ĠscRNA": 45873, + "Ġoutreach": 45874, + "inskii": 45875, + "Ġcustomary": 45876, + "Ġmd": 45877, + "ĠOzone": 45878, + "ussing": 45879, + "others": 45880, + "Ġentirety": 45881, + "Arth": 45882, + "Acet": 45883, + "ĠFleet": 45884, + "ĠBehavioural": 45885, + "ĠQSOs": 45886, + "arina": 45887, + "Ġprodrug": 45888, + "ĠBros": 45889, + "ĠWorth": 45890, + "Ġyz": 45891, + "contig": 45892, + "ĠAmorphous": 45893, + "ĠErlang": 45894, + "Ġhonour": 45895, + "ĠâIJ¥": 45896, + "Ġinfiltrates": 45897, + "ĠIvanov": 45898, + "ĠMunicipality": 45899, + "ĠDialogue": 45900, + "tone": 45901, + "Ġpytest": 45902, + "iculus": 45903, + "ĠGoth": 45904, + "ĠXC": 45905, + "ĠSUMMARY": 45906, + "Ġshrinks": 45907, + "Ġinverses": 45908, + "iomas": 45909, + "robi": 45910, + "ĠTPR": 45911, + "ĠANA": 45912, + "istries": 45913, + "Ġregiment": 45914, + "indo": 45915, + "ĠReproduction": 45916, + "loqu": 45917, + "inflation": 45918, + "ETX": 45919, + "Ġïĺ»": 45920, + "ĠAPPENDIX": 45921, + "Ġworsened": 45922, + "Ġpsoriatic": 45923, + "Ġmidwives": 45924, + "Ġtouched": 45925, + "Ëĩ": 45926, + "ĠPatric": 45927, + "ĠDON": 45928, + "ĠLIM": 45929, + "akos": 45930, + "ĠVie": 45931, + "ĠAntit": 45932, + "Ġflake": 45933, + "ĠSchle": 45934, + "ĠCoronal": 45935, + "Ġsalary": 45936, + "slight": 45937, + "ĠCAF": 45938, + "Ġsummarise": 45939, + "Ġflavus": 45940, + "ĠBalanced": 45941, + "ĠPHOT": 45942, + "Ġmillet": 45943, + "Ġurgency": 45944, + "ĠGleason": 45945, + "ĠMie": 45946, + "ĠDp": 45947, + "ĠGarg": 45948, + "Ġleprosy": 45949, + "Ġunoccupied": 45950, + "ĠStret": 45951, + "ilept": 45952, + "ĠChor": 45953, + "ibrate": 45954, + "ĠÍļ": 45955, + "ĠPHB": 45956, + "Ġmonoter": 45957, + "ĠJavaScript": 45958, + "btn": 45959, + "ĠPulsar": 45960, + "ĠKirchhoff": 45961, + "Ġoverseas": 45962, + "Ġdephosphorylation": 45963, + "ortin": 45964, + "ĠPolyakov": 45965, + "Ġinsightful": 45966, + "ĠPurified": 45967, + "Ġanchorage": 45968, + "ĠGlycoprotein": 45969, + "studies": 45970, + "Ġchronology": 45971, + "roxine": 45972, + "ĠNeptune": 45973, + "Ban": 45974, + "Ġlion": 45975, + "PSD": 45976, + "ĠBarr": 45977, + "Ġdonkey": 45978, + "Ġlikelihoods": 45979, + "atchewan": 45980, + "otet": 45981, + "ospha": 45982, + "ticism": 45983, + "Ġry": 45984, + "asthen": 45985, + "rhotic": 45986, + "ĠSubgroup": 45987, + "yev": 45988, + "ĠPatri": 45989, + "provides": 45990, + "SGD": 45991, + "berell": 45992, + "vw": 45993, + "ĠAACR": 45994, + "Ġsmears": 45995, + "ODS": 45996, + "supplemented": 45997, + "ĠEngagement": 45998, + "oglobulins": 45999, + "Ġirregularly": 46000, + "ĠSzeg": 46001, + "ĠWolff": 46002, + "Ġenantiomers": 46003, + "Ġobeying": 46004, + "Ġdestroying": 46005, + "omially": 46006, + "ĠAti": 46007, + "ĠGAT": 46008, + "ĠInvariants": 46009, + "ĠScoring": 46010, + "Ġhalides": 46011, + "Ġtransformants": 46012, + "Ġforested": 46013, + "Ġgallic": 46014, + "ĠBetti": 46015, + "threaded": 46016, + "ĠBudget": 46017, + "junctive": 46018, + "ĠInnovative": 46019, + "Ġpositrons": 46020, + "Brazil": 46021, + "eira": 46022, + "Ġlavas": 46023, + "ĠLt": 46024, + "photo": 46025, + "Ġspam": 46026, + "Ġih": 46027, + "ustering": 46028, + "Ġbioluminescence": 46029, + "ĠShapes": 46030, + "ULTI": 46031, + "triangles": 46032, + "ĠSMN": 46033, + "enhancing": 46034, + "ĠReduces": 46035, + "ĠTHEOREM": 46036, + "Dop": 46037, + "ĠdL": 46038, + "emptive": 46039, + "Ġreminder": 46040, + "Ġgonads": 46041, + "Ġxylan": 46042, + "cultures": 46043, + "tles": 46044, + "Ġtd": 46045, + "Ġerected": 46046, + "terone": 46047, + "ĠPDC": 46048, + "Ġincongruent": 46049, + "Ġmembranous": 46050, + "pac": 46051, + "yless": 46052, + "Ġsubalgebras": 46053, + "ĠChir": 46054, + "ĠZIP": 46055, + "autious": 46056, + "Ġlightly": 46057, + "ĠPhotometric": 46058, + "Transfer": 46059, + "Ġketo": 46060, + "Ġexercised": 46061, + "dispersive": 46062, + "ĠBETWEEN": 46063, + "rou": 46064, + "Ġgarbage": 46065, + "ĠMaf": 46066, + "ĠDoming": 46067, + "ĠSubspace": 46068, + "ĠMarÃŃa": 46069, + "Ġtetrahedra": 46070, + "ĠBarker": 46071, + "Side": 46072, + "bishop": 46073, + "iD": 46074, + "reversible": 46075, + "orman": 46076, + "orescein": 46077, + "ĠContrib": 46078, + "Ġderivatization": 46079, + "romeres": 46080, + "ĠALD": 46081, + "EEK": 46082, + "ĠTreating": 46083, + "combination": 46084, + "ïĺ»": 46085, + "restriction": 46086, + "supseteq": 46087, + "ĠRAPD": 46088, + "Ġamendment": 46089, + "zynski": 46090, + "Ġcaves": 46091, + "ilot": 46092, + "Ġabundantly": 46093, + "на": 46094, + "Ġinjectable": 46095, + "ĠReinforced": 46096, + "ĠWidth": 46097, + "ĠHaemophilus": 46098, + "ilane": 46099, + "props": 46100, + "Ġintervertebral": 46101, + "Ġscroll": 46102, + "Ġamput": 46103, + "ĠUnusual": 46104, + "Ġstatically": 46105, + "Ġsynergies": 46106, + "Ġdims": 46107, + "plasmic": 46108, + "Ġneutralized": 46109, + "Selected": 46110, + "Ġinherits": 46111, + "ĠAutomation": 46112, + "Ġprotoplanetary": 46113, + "Statement": 46114, + "ĠAPOBEC": 46115, + "Ġcertificates": 46116, + "ĠCitrus": 46117, + "quadruplex": 46118, + "Nord": 46119, + "Ġfran": 46120, + "ĠCarcin": 46121, + "utan": 46122, + "ĠPump": 46123, + "ĠBav": 46124, + "ĠGras": 46125, + "tingales": 46126, + "Ġcausally": 46127, + "Ġradon": 46128, + "Compare": 46129, + "Ġclamping": 46130, + "irreducible": 46131, + "IHC": 46132, + "ĠÙ": 46133, + "Ġcyp": 46134, + "ĠTPP": 46135, + "ĠSuff": 46136, + "undra": 46137, + "ĠVilla": 46138, + "Ġrelieved": 46139, + "ĠJCM": 46140, + "Ġtreaty": 46141, + "IGEN": 46142, + "ĠDevonian": 46143, + "Ġerythropo": 46144, + "RAP": 46145, + "Ġaversive": 46146, + "entate": 46147, + "odactyl": 46148, + "ĠParal": 46149, + "Ġmilled": 46150, + "Ġbioinformatic": 46151, + "okinetic": 46152, + "ĠSTRING": 46153, + "ĠPedersen": 46154, + "database": 46155, + "inorganic": 46156, + "Ġdeput": 46157, + "Ġneb": 46158, + "iped": 46159, + "Ġdiffused": 46160, + "othione": 46161, + "Ġnonstationary": 46162, + "Ġundertaking": 46163, + "ĠEnabling": 46164, + "Ġdenatured": 46165, + "Ġloader": 46166, + "ĠLyon": 46167, + "iparametric": 46168, + "Ġmeristem": 46169, + "ĠAngiogenesis": 46170, + "ĠPulsed": 46171, + "Ġexcer": 46172, + "ĠDf": 46173, + "arches": 46174, + "Ġcollide": 46175, + "ĠRelational": 46176, + "ĠNFκB": 46177, + "Metadata": 46178, + "ĠAddressing": 46179, + "Ġpercussion": 46180, + "ĠFlorence": 46181, + "Ġnymphs": 46182, + "Cn": 46183, + "storm": 46184, + "ĠGraz": 46185, + "composite": 46186, + "ĠAdmiral": 46187, + "ĠScotia": 46188, + "Ġbremsstrahlung": 46189, + "apsack": 46190, + "Ġminimizers": 46191, + "Ġmanageable": 46192, + "Ġcarboxylate": 46193, + "Ġintermediary": 46194, + "ĠBranching": 46195, + "scheduler": 46196, + "inoculated": 46197, + "ĠExtremely": 46198, + "Ġantennae": 46199, + "ĠTill": 46200, + "RESH": 46201, + "Ġopacities": 46202, + "Ġchemopre": 46203, + "Ġadenylate": 46204, + "Ġcircumstance": 46205, + "ĠHashimoto": 46206, + "ÄĽ": 46207, + "ceae": 46208, + "ĠFm": 46209, + "ĠBX": 46210, + "Ġmeantime": 46211, + "accurate": 46212, + "collinear": 46213, + "ACTIC": 46214, + "ĠSlovenia": 46215, + "Fed": 46216, + "Kh": 46217, + "Tm": 46218, + "fork": 46219, + "inology": 46220, + "lef": 46221, + "ĠDCS": 46222, + "Ġheritable": 46223, + "Ġannouncement": 46224, + "Ġbusinessman": 46225, + "Ġbortezomib": 46226, + "Ġtourist": 46227, + "ĠEtymology": 46228, + "Ġdoctrine": 46229, + "BIN": 46230, + "suffix": 46231, + "aras": 46232, + "ĠSau": 46233, + "unboldmath": 46234, + "ĠMEP": 46235, + "inker": 46236, + "Ġoptimism": 46237, + "ĠLeuc": 46238, + "efulness": 46239, + "crust": 46240, + "ĠKeys": 46241, + "ĠâϦ": 46242, + "ĠBrandt": 46243, + "âĮ¬": 46244, + "ĠSeventy": 46245, + "Ġnursery": 46246, + "Ġdeputy": 46247, + "ì": 46248, + "onis": 46249, + "amus": 46250, + "ĠCig": 46251, + "Ġexergy": 46252, + "ĠFrequent": 46253, + "Ġabor": 46254, + "ĠJazz": 46255, + "Ġstatue": 46256, + "ĠScenarios": 46257, + "Ġcytological": 46258, + "figures": 46259, + "MCI": 46260, + "dirname": 46261, + "Ġcytokinesis": 46262, + "delivery": 46263, + "ĠBowen": 46264, + "Ġflanked": 46265, + "Ġregenerating": 46266, + "ĠFerrari": 46267, + "kiss": 46268, + "ĠAval": 46269, + "ĠCIT": 46270, + "ĠMum": 46271, + "ĠLSB": 46272, + "ogging": 46273, + "Ġunited": 46274, + "Ġtritium": 46275, + "ontamination": 46276, + "coef": 46277, + "Ġpropell": 46278, + "triple": 46279, + "Ġimmense": 46280, + "Ġcomplained": 46281, + "Ġdielectrics": 46282, + "ĠCardiomy": 46283, + "Ġflooded": 46284, + "ĠCovariance": 46285, + "Attendance": 46286, + "TMP": 46287, + "Ġsob": 46288, + "ĠSonic": 46289, + "ĠFTS": 46290, + "ĠRSD": 46291, + "essors": 46292, + "ĠWon": 46293, + "iffs": 46294, + "Ġflowchart": 46295, + "ĠElemental": 46296, + "Ġìŀ": 46297, + "Ġfoliage": 46298, + "differentiated": 46299, + "ĠGlobular": 46300, + "Ġperceptron": 46301, + "candidate": 46302, + "Social": 46303, + "Witt": 46304, + "dyn": 46305, + "paces": 46306, + "ĠmGlu": 46307, + "Ġbanned": 46308, + "olinite": 46309, + "ĠFriends": 46310, + "ĠLibraries": 46311, + "unces": 46312, + "ĠReach": 46313, + "ĠSkills": 46314, + "Ġrecipes": 46315, + "Ġcannula": 46316, + "ĠOrthodox": 46317, + "ĠCarbohydrate": 46318, + "Ġaromatase": 46319, + "Åijs": 46320, + "Ġemanating": 46321, + "elected": 46322, + "Ġtense": 46323, + "ĠFLC": 46324, + "ĠLET": 46325, + "herjee": 46326, + "Ġsubband": 46327, + "ophone": 46328, + "ĠActual": 46329, + "msgs": 46330, + "EMD": 46331, + "ISON": 46332, + "leyball": 46333, + "ĠNiu": 46334, + "Ġberries": 46335, + "diagnostic": 46336, + "NER": 46337, + "ĠdΩ": 46338, + "percentage": 46339, + "ĠHerman": 46340, + "ĠGSD": 46341, + "Ġsubproblem": 46342, + "overall": 46343, + "ophor": 46344, + "Ġdelocalized": 46345, + "account": 46346, + "ĠGeographical": 46347, + "distances": 46348, + "Ġàµ": 46349, + "Ġneurotoxic": 46350, + "opodia": 46351, + "ĠDicer": 46352, + "ĠðxÃŀ": 46353, + "Ġdunes": 46354, + "Ġwhit": 46355, + "ĠImmediate": 46356, + "Ġ̸": 46357, + "Ġadhesives": 46358, + "ĠNSs": 46359, + "Ġguessing": 46360, + "ĠColumbus": 46361, + "ĠUrugu": 46362, + "behaviour": 46363, + "ĠSerbian": 46364, + "benzodioxol": 46365, + "implementation": 46366, + "osensitive": 46367, + "ĠFill": 46368, + "phage": 46369, + "recovery": 46370, + "ESR": 46371, + "Ġanalysts": 46372, + "Ġdissatisfaction": 46373, + "banded": 46374, + "ĠDepressive": 46375, + "ĠRTs": 46376, + "Refs": 46377, + "millimeter": 46378, + "ĠOlsen": 46379, + "ampton": 46380, + "ĠACA": 46381, + "ĠAvian": 46382, + "ĠFowler": 46383, + "ubini": 46384, + "estamps": 46385, + "ĠProtest": 46386, + "Connection": 46387, + "Ġmerchant": 46388, + "ĠENC": 46389, + "ĠRyu": 46390, + "ĠLymphoma": 46391, + "ĠLarry": 46392, + "Ġjaponicum": 46393, + "ĠSymbols": 46394, + "Lib": 46395, + "VG": 46396, + "ĠTav": 46397, + "ĠAssim": 46398, + "ĠLeung": 46399, + "dependency": 46400, + "largest": 46401, + "ĠDOE": 46402, + "Ġaligns": 46403, + "oflurane": 46404, + "ĠAdjusted": 46405, + "Ġpeculiarities": 46406, + "decrease": 46407, + "ĠPlacement": 46408, + "vig": 46409, + "zak": 46410, + "Ġpenta": 46411, + "Ġfres": 46412, + "Ġacros": 46413, + "Ġsolvability": 46414, + "ansions": 46415, + "ALA": 46416, + "Ġmalfunction": 46417, + "ĠGiovanni": 46418, + "AOR": 46419, + "Had": 46420, + "Ġporn": 46421, + "undice": 46422, + "ĠUi": 46423, + "Ġexpelled": 46424, + "ĠAnk": 46425, + "Ġdiscounting": 46426, + "ĠRegulating": 46427, + "astery": 46428, + "phenylethyl": 46429, + "Ġcastration": 46430, + "Ġerythromycin": 46431, + "Ġbifunctional": 46432, + "��": 46433, + "ĠAlgeria": 46434, + "mess": 46435, + "Ġwis": 46436, + "ĠTay": 46437, + "assumed": 46438, + "Ġescalation": 46439, + "Ġhydroper": 46440, + "Ġcallosum": 46441, + "Ġatomization": 46442, + "ĠSAW": 46443, + "Ġacetylcholinesterase": 46444, + "Ġsucceeds": 46445, + "Ġphysiotherapy": 46446, + "tro": 46447, + "Ġmason": 46448, + "ĠTMB": 46449, + "Ġphant": 46450, + "Ġadjusts": 46451, + "antha": 46452, + "ĠEisenstein": 46453, + "Ġshorthand": 46454, + "GABA": 46455, + "Ġprover": 46456, + "Ġpatrol": 46457, + "ĠModal": 46458, + "ollaries": 46459, + "ĠInterfacial": 46460, + "ĠCIA": 46461, + "attn": 46462, + "ĠCryptococcus": 46463, + "athecal": 46464, + "ĠFreshwater": 46465, + "Ġspectrogram": 46466, + "opidogrel": 46467, + "morphism": 46468, + "Ġrelapsing": 46469, + "Ġgeneralizable": 46470, + "ĠShale": 46471, + "ĠTransplant": 46472, + "contraction": 46473, + "URI": 46474, + "ĠPetrov": 46475, + "ĠSliding": 46476, + "Ġanteriorly": 46477, + "Ġquasilinear": 46478, + "Ġripples": 46479, + "ZP": 46480, + "bacterial": 46481, + "spr": 46482, + "animal": 46483, + "Ġreporters": 46484, + "ĠBSS": 46485, + "ĠDia": 46486, + "ĠRSC": 46487, + "ounding": 46488, + "ITHM": 46489, + "logical": 46490, + "Ġpolycarbonate": 46491, + "Animal": 46492, + "umbai": 46493, + "Ġarchived": 46494, + "ĠDurham": 46495, + "âĸĪ": 46496, + "ĠVermont": 46497, + "Ġpw": 46498, + "essen": 46499, + "Ġconstexpr": 46500, + "ĠPruss": 46501, + "Ġsharpness": 46502, + "divide": 46503, + "primitive": 46504, + "Ġacrylate": 46505, + "MYC": 46506, + "ĠMonday": 46507, + "ĠSrinivas": 46508, + "Born": 46509, + "attice": 46510, + "omorpha": 46511, + "ĠMERS": 46512, + "ĠFactory": 46513, + "ĠWN": 46514, + "rectile": 46515, + "Ġheats": 46516, + "UNK": 46517, + "Ġsynchronize": 46518, + "ĠAttenuation": 46519, + "Children": 46520, + "Pat": 46521, + "pregnant": 46522, + "Ġwished": 46523, + "Ġthawing": 46524, + "ĠBey": 46525, + "ĠDÃŃaz": 46526, + "Ġleather": 46527, + "ĠUnic": 46528, + "Ġspecialised": 46529, + "Ġcatalytically": 46530, + "PLGA": 46531, + "hydroxyethyl": 46532, + "Ġmagmas": 46533, + "Ġpronoun": 46534, + "Ġeutrophication": 46535, + "ĠWeekly": 46536, + "MHD": 46537, + "malloc": 46538, + "ecologic": 46539, + "ilo": 46540, + "ĠFrequencies": 46541, + "Ġorchestra": 46542, + "Ġmetabolomic": 46543, + "ĠBlockade": 46544, + "Ġasserted": 46545, + "ĠLewy": 46546, + "Ġalleviating": 46547, + "Ġocclusions": 46548, + "Ġchoroid": 46549, + "technical": 46550, + "Ġenvisioned": 46551, + "ĠHousing": 46552, + "Pn": 46553, + "ĠTECH": 46554, + "ĠSSH": 46555, + "ĠValle": 46556, + "ylmethyl": 46557, + "Ġphloem": 46558, + "ĠProjects": 46559, + "button": 46560, + "Ġaccelerometers": 46561, + "umni": 46562, + "ĠHandling": 46563, + "Ġvaso": 46564, + "permeable": 46565, + "Ġcords": 46566, + "ĠCf": 46567, + "ĠDz": 46568, + "Ġeditions": 46569, + "Ġhumerus": 46570, + "doors": 46571, + "Ġdorsolateral": 46572, + "Ġaptamers": 46573, + "Ġcommodities": 46574, + "osperms": 46575, + "Ġprednisone": 46576, + "IQ": 46577, + "Metal": 46578, + "tus": 46579, + "Ġisotopy": 46580, + "ĠTheater": 46581, + "iffi": 46582, + "Ġyarn": 46583, + "deletion": 46584, + "ĠQPO": 46585, + "Ġmultiobjective": 46586, + "Ġurchin": 46587, + "Ġpulsations": 46588, + "ĠSRP": 46589, + "ðtÃŀ": 46590, + "glucoside": 46591, + "Ġdepartures": 46592, + "PyObject": 46593, + "ĠBandwidth": 46594, + "ĠAcceptance": 46595, + "reys": 46596, + "ĠION": 46597, + "Ġcompuls": 46598, + "ĠJW": 46599, + "Ġparthen": 46600, + "Close": 46601, + "ĠBaTiO": 46602, + "ñoz": 46603, + "aggregate": 46604, + "Initially": 46605, + "qh": 46606, + "ĠCancers": 46607, + "opin": 46608, + "never": 46609, + "isman": 46610, + "Ġconstancy": 46611, + "Ġtrucks": 46612, + "Ġvisualisation": 46613, + "ĠIllness": 46614, + "Ġsulphide": 46615, + "ĠMetabolites": 46616, + "Ġoxysporum": 46617, + "HPP": 46618, + "Ġnoradrenaline": 46619, + "Ġcommutativity": 46620, + "Quad": 46621, + "NiO": 46622, + "ĠGetting": 46623, + "Ġbait": 46624, + "Ġë°": 46625, + "Ġmentally": 46626, + "Ġauroral": 46627, + "ĠDrawing": 46628, + "Sin": 46629, + "receiver": 46630, + "atov": 46631, + "isotope": 46632, + "Ġisothi": 46633, + "ĠSenes": 46634, + "ĠACO": 46635, + "ĠGCT": 46636, + "ysmal": 46637, + "ĠVog": 46638, + "Ġdistractors": 46639, + "Ġconnectedness": 46640, + "Ġaccumbens": 46641, + "äck": 46642, + "hydrated": 46643, + "Ġpharmacodynamic": 46644, + "Ġmineralogy": 46645, + "Ġarthropods": 46646, + "Ġmycotoxins": 46647, + "Ġbattles": 46648, + "ĠSara": 46649, + "ĠEIS": 46650, + "ĠWinn": 46651, + "Ġlimbic": 46652, + "WORK": 46653, + "Ž": 46654, + "Ġeaten": 46655, + "ĠTod": 46656, + "apillary": 46657, + "oxyp": 46658, + "ĠNewly": 46659, + "Ġcamel": 46660, + "arrison": 46661, + "ECTOR": 46662, + "Ġhopefully": 46663, + "ĠHurwitz": 46664, + "Ġibuprofen": 46665, + "ĠFIRST": 46666, + "Ġbistable": 46667, + "Ġdismissed": 46668, + "gat": 46669, + "inogen": 46670, + "ĠPON": 46671, + "phas": 46672, + "ĠKorn": 46673, + "Ġpolyaniline": 46674, + "ĠMicroscope": 46675, + "Ġmucous": 46676, + "Ġcollisionless": 46677, + "hydrogenase": 46678, + "Build": 46679, + "pairing": 46680, + "ĠWIMP": 46681, + "builtin": 46682, + "ĠSeparate": 46683, + "ĠCunningham": 46684, + "ĠNecessary": 46685, + "Ġbry": 46686, + "ecrosis": 46687, + "ĠLSS": 46688, + "Ġsyphilis": 46689, + "ĠVid": 46690, + "Ġcarrot": 46691, + "ĠResistant": 46692, + "registration": 46693, + "Ġmyopathy": 46694, + "Ġangry": 46695, + "MDR": 46696, + "Ġhypothesised": 46697, + "ĠVolterra": 46698, + "elevation": 46699, + "Ġmycobacteria": 46700, + "Ġcaudate": 46701, + "iidae": 46702, + "ĠÇ": 46703, + "ĠDich": 46704, + "ĠReth": 46705, + "ellus": 46706, + "chamber": 46707, + "shine": 46708, + "ochore": 46709, + "ĠColumns": 46710, + "COUNT": 46711, + "Ġïĥ²": 46712, + "ĠPrimordial": 46713, + "Ġnegotiations": 46714, + "stedt": 46715, + "RII": 46716, + "UES": 46717, + "tiques": 46718, + "ĠPfe": 46719, + "Ġplast": 46720, + "pron": 46721, + "ĠZw": 46722, + "inkler": 46723, + "Ġmetabolome": 46724, + "EGA": 46725, + "ĠSpectrophot": 46726, + "Ġubiquity": 46727, + "ĠElectrodes": 46728, + "Ġchondro": 46729, + "DomainIs": 46730, + "ĠResidues": 46731, + "ĠdnsDomainIs": 46732, + "DIC": 46733, + "pth": 46734, + "Ġaest": 46735, + "Ġcient": 46736, + "Ġpessim": 46737, + "Ġreinst": 46738, + "ĠSans": 46739, + "endazole": 46740, + "ĠUrine": 46741, + "Ġsubacute": 46742, + "iximab": 46743, + "Ġprofitable": 46744, + "Ġmaximise": 46745, + "ĠDelaware": 46746, + "Ġclinicopathologic": 46747, + "ThermoFisher": 46748, + "FAR": 46749, + "RAS": 46750, + "witch": 46751, + "inactivated": 46752, + "enesis": 46753, + "unless": 46754, + "ĠPanc": 46755, + "ĠMTS": 46756, + "ĠBast": 46757, + "Ġchilling": 46758, + "Ġincumbent": 46759, + "Ġjelly": 46760, + "Ġdistributive": 46761, + "Ġcyto": 46762, + "schen": 46763, + "Ġinducers": 46764, + "ĠNonequilibrium": 46765, + "ĠRobotics": 46766, + "ĠArgentine": 46767, + "Ġmeridian": 46768, + "Ġhunger": 46769, + "Adaptive": 46770, + "Ġgor": 46771, + "ilepsy": 46772, + "Ġnonvanishing": 46773, + "Ġpeti": 46774, + "ĠMetformin": 46775, + "Ġbiomaterial": 46776, + "Ġantennal": 46777, + "ĠAffective": 46778, + "ĠAquatic": 46779, + "enediamine": 46780, + "ĠSiberia": 46781, + "ĠPenicillium": 46782, + "Functions": 46783, + "Ġlec": 46784, + "Ġfeld": 46785, + "ĠSpart": 46786, + "ĠCement": 46787, + "addi": 46788, + "sek": 46789, + "ĠNp": 46790, + "olesky": 46791, + "ĠMacroscopic": 46792, + "ères": 46793, + "Ġcaveat": 46794, + "Ġcourtship": 46795, + "mice": 46796, + "Ġfence": 46797, + "Ġmined": 46798, + "ulink": 46799, + "IDA": 46800, + "Ġtruncate": 46801, + "ĠCatalan": 46802, + "Ġtranst": 46803, + "Ġamendments": 46804, + "uncertainty": 46805, + "Ġoropharyngeal": 46806, + "ĠAid": 46807, + "oulder": 46808, + "ĠIncident": 46809, + "ĠáIJ": 46810, + "angiogenesis": 46811, + "ĠBEH": 46812, + "Ġicosa": 46813, + "ĠFOXP": 46814, + "fragment": 46815, + "Ġscintillator": 46816, + "JO": 46817, + "Law": 46818, + "ĠpL": 46819, + "Ġetoposide": 46820, + "Ġpolyaden": 46821, + "Ġhabitual": 46822, + "Ġtaxi": 46823, + "Ġcumulant": 46824, + "Ġhindrance": 46825, + "trigger": 46826, + "ratios": 46827, + "ilio": 46828, + "ĠPIR": 46829, + "ĠTheod": 46830, + "ĠMorton": 46831, + "ĠHaf": 46832, + "ĠOch": 46833, + "ĠExo": 46834, + "Ġurtic": 46835, + "ĠCFRP": 46836, + "Screen": 46837, + "Slice": 46838, + "Ġmushrooms": 46839, + "Ġevanescent": 46840, + "Sx": 46841, + "ËIJ": 46842, + "ìŀ": 46843, + "Ġsigm": 46844, + "icl": 46845, + "Ġguests": 46846, + "ĠGIST": 46847, + "Ġdeformities": 46848, + "polyacrylamide": 46849, + "Significant": 46850, + "Ġimpressions": 46851, + "jmath": 46852, + "emoral": 46853, + "ĠBn": 46854, + "ĠHDR": 46855, + "ĠKeck": 46856, + "Ġvaline": 46857, + "spi": 46858, + "iterate": 46859, + "Ġsync": 46860, + "otiana": 46861, + "Interval": 46862, + "ĠBrauer": 46863, + "Ġsticky": 46864, + "ĠNeuroscience": 46865, + "Baxter": 46866, + "Ġcasts": 46867, + "allocation": 46868, + "neal": 46869, + "Ġbiop": 46870, + "Ġrestorations": 46871, + "Images": 46872, + "mitic": 46873, + "ĠElevation": 46874, + "Ġabstinence": 46875, + "ĠLesser": 46876, + "ĠRainfall": 46877, + "PAM": 46878, + "Wol": 46879, + "usch": 46880, + "Ġpromisc": 46881, + "naïve": 46882, + "Ġdeduc": 46883, + "accharide": 46884, + "Ġnominally": 46885, + "ĠExploratory": 46886, + "Ġreconciliation": 46887, + "linalg": 46888, + "TCR": 46889, + "Ġsore": 46890, + "ĠNab": 46891, + "Ġoutgroup": 46892, + "Ġmonophosphate": 46893, + "insu": 46894, + "ĠAddis": 46895, + "SPR": 46896, + "pointing": 46897, + "HERE": 46898, + "ĠTechnological": 46899, + "Ġcochlea": 46900, + "Ġspheroidal": 46901, + "ĠBaldwin": 46902, + "Feed": 46903, + "Ġfusing": 46904, + "Ġasper": 46905, + "Ġexosomal": 46906, + "ĠLinguistic": 46907, + "SCA": 46908, + "ĠEmpty": 46909, + "Ġvacant": 46910, + "glycol": 46911, + "immunoprecipitation": 46912, + "ĠITER": 46913, + "SnO": 46914, + "patterns": 46915, + "continental": 46916, + "ĠAccelerating": 46917, + "ĠAveraging": 46918, + "Ġchemoattractant": 46919, + "hb": 46920, + "sulph": 46921, + "ĠBx": 46922, + "Ġcomplicating": 46923, + "ĠWare": 46924, + "Ġsoaking": 46925, + "Ġupregulate": 46926, + "---------": 46927, + "Ġsemester": 46928, + "ĠBrod": 46929, + "Ġcascading": 46930, + "ĠCastell": 46931, + "Ġẽ": 46932, + "ĠEQUATIONS": 46933, + "Ġparsimonious": 46934, + "Ġsorbent": 46935, + "Ġeug": 46936, + "odin": 46937, + "ĠWig": 46938, + "ĠThir": 46939, + "Ġsolv": 46940, + "Ġcarboplatin": 46941, + "Ġzebra": 46942, + "venient": 46943, + "ĠmedRxiv": 46944, + "Ġautobi": 46945, + "Ġrepeatable": 46946, + "Ġmigrations": 46947, + "Ġд": 46948, + "holonomic": 46949, + "Ġmoderator": 46950, + "Ġchimera": 46951, + "ĠGrassmannian": 46952, + "ĠRonald": 46953, + "ĠVega": 46954, + "astes": 46955, + "Ġquotes": 46956, + "Ġmonic": 46957, + "Ġprecoding": 46958, + "ĠAssisted": 46959, + "ĠNetworking": 46960, + "Ġfabricating": 46961, + "Ġbotanical": 46962, + "Ġswarms": 46963, + "Ġmartensitic": 46964, + "elliptic": 46965, + "pherd": 46966, + "baryon": 46967, + "xfe": 46968, + "route": 46969, + "ĠFIL": 46970, + "opies": 46971, + "ĠPCBs": 46972, + "Ġerasure": 46973, + "ĠRemodeling": 46974, + "Ġanaer": 46975, + "Smad": 46976, + "injured": 46977, + "Ġimmunocompetent": 46978, + "dell": 46979, + "failed": 46980, + "Ġsinking": 46981, + "oracic": 46982, + "Ġdred": 46983, + "ĠVDR": 46984, + "Ġconnectors": 46985, + "Ġintratumoral": 46986, + "Ġcommutators": 46987, + "ĠAleks": 46988, + "ĠDicty": 46989, + "Ak": 46990, + "Ġrecalc": 46991, + "Ġisl": 46992, + "otrim": 46993, + "ncephal": 46994, + "ĠRees": 46995, + "Ġsteatohepatitis": 46996, + "ĠPolarized": 46997, + "SBATCH": 46998, + "ĠCrossing": 46999, + "Accuracy": 47000, + "ĠGiardia": 47001, + "ĠNovo": 47002, + "Ġvigilance": 47003, + "Ġphosphatidylcholine": 47004, + "ĠUEFA": 47005, + "Jim": 47006, + "Ġfasted": 47007, + "ĠTiny": 47008, + "Ġlang": 47009, + "issociation": 47010, + "Auto": 47011, + "ĠNorfolk": 47012, + "ĠArms": 47013, + "ĠSWI": 47014, + "ĠAmbros": 47015, + "transfection": 47016, + "Oryza": 47017, + "harm": 47018, + "ĠDs": 47019, + "Ġintrag": 47020, + "Ġcaller": 47021, + "Ġwritings": 47022, + "ĠElast": 47023, + "ĠMarvel": 47024, + "ĠImmunodeficiency": 47025, + "ĠMillion": 47026, + "Texture": 47027, + "ĠIceCube": 47028, + "snap": 47029, + "Ġenjoys": 47030, + "ĠChapel": 47031, + "ĠEstablishing": 47032, + "Actually": 47033, + "Ġphosphorylates": 47034, + "Ġchinensis": 47035, + "Ġrhabdomy": 47036, + "Ġemphysema": 47037, + "Middle": 47038, + "nant": 47039, + "Ñħ": 47040, + "Ġtart": 47041, + "lowest": 47042, + "hemia": 47043, + "Ġutilising": 47044, + "constit": 47045, + "Ġmagmatism": 47046, + "оÑĢ": 47047, + "ĠHasan": 47048, + "dispersed": 47049, + "Hear": 47050, + "Qt": 47051, + "zations": 47052, + "alon": 47053, + "ĠStau": 47054, + "ĠAmer": 47055, + "osystems": 47056, + "Ġdemarc": 47057, + "ĠNeoproterozoic": 47058, + "ĠMek": 47059, + "ĠDisclosure": 47060, + "Ġhematocrit": 47061, + "ĠCytoscape": 47062, + "Ġramification": 47063, + "Ġcommunicative": 47064, + "Ġbutterflies": 47065, + "Ġantisera": 47066, + "Ġaestivum": 47067, + "Bra": 47068, + "LTP": 47069, + "socket": 47070, + "ĠCherenkov": 47071, + "Ġchlam": 47072, + "angial": 47073, + "ultured": 47074, + "enged": 47075, + "ĠClinton": 47076, + "Ġmyoblasts": 47077, + "ĠCompensation": 47078, + "ymmetrically": 47079, + "Ġemployer": 47080, + "ozol": 47081, + "ĠSAXS": 47082, + "Ġretinas": 47083, + "piperidine": 47084, + "XYZ": 47085, + "ĠRoughly": 47086, + "Prep": 47087, + "Ġbinge": 47088, + "Ġerect": 47089, + "ĠOPER": 47090, + "Ġstressor": 47091, + "Christ": 47092, + "ĠPDZ": 47093, + "Ġsubstan": 47094, + "ĠSnail": 47095, + "Ġlamellae": 47096, + "ĠCycling": 47097, + "shifting": 47098, + "ĠHsieh": 47099, + "verify": 47100, + "Ġpreimage": 47101, + "Ġartillery": 47102, + "Ġepil": 47103, + "ĠApost": 47104, + "Ġhelmet": 47105, + "Ġmachined": 47106, + "ĠMinneapolis": 47107, + "ĠCryp": 47108, + "Ġsituational": 47109, + "passing": 47110, + "quinazolin": 47111, + "ĠCroatian": 47112, + "Ġstaircase": 47113, + "Bonnet": 47114, + "NLP": 47115, + "cium": 47116, + "Ġskeletons": 47117, + "Ġoxim": 47118, + "orib": 47119, + "Ġreticular": 47120, + "ĠSLS": 47121, + "ĠAromatic": 47122, + "ĠKes": 47123, + "Ġphor": 47124, + "Ġinvocation": 47125, + "Ġdozens": 47126, + "aively": 47127, + "Ġdetectability": 47128, + "Ġconcerted": 47129, + "yrins": 47130, + "ĠProcessor": 47131, + "Ġtolerable": 47132, + "attached": 47133, + "Ġannexin": 47134, + "ĠROSAT": 47135, + "ĠAlternate": 47136, + "ĠWavelength": 47137, + "ĠWillis": 47138, + "Ġsemicontinuous": 47139, + "Ġadvocacy": 47140, + "Ġobligation": 47141, + "chanter": 47142, + "ĠInsertion": 47143, + "Ġsymbiont": 47144, + "ZM": 47145, + "Ġtars": 47146, + "rof": 47147, + "Ġrevival": 47148, + "ĠTST": 47149, + "ĠEMP": 47150, + "Ġmex": 47151, + "ullin": 47152, + "ĠAdop": 47153, + "ĠDNAs": 47154, + "Ġemployers": 47155, + "MTs": 47156, + "ĠMartÃŃn": 47157, + "electrodes": 47158, + "ĠMedicaid": 47159, + "Ġtgt": 47160, + "Ġlognormal": 47161, + "ĠFrames": 47162, + "Ġpermissive": 47163, + "ĠArduino": 47164, + "Ġsemilinear": 47165, + "ĠAssign": 47166, + "ĠPrEP": 47167, + "ĠSiamese": 47168, + "benzimidazol": 47169, + "connectivity": 47170, + "ĠPEI": 47171, + "Ġbisulfite": 47172, + "Ġacetyltransferase": 47173, + "Ġswimmer": 47174, + "juven": 47175, + "Ġjejunum": 47176, + "ĠCincinnati": 47177, + "tai": 47178, + "ĠQI": 47179, + "ĠCommut": 47180, + "spacing": 47181, + "Ġaffords": 47182, + "itisation": 47183, + "elasticity": 47184, + "Ġdragon": 47185, + "Ġproteasomal": 47186, + "Ġpant": 47187, + "ĠNitro": 47188, + "Ġspic": 47189, + "Ġnanopl": 47190, + "ĠAllied": 47191, + "Ġthorax": 47192, + "ĠFTO": 47193, + "ĠJurkat": 47194, + "chiatry": 47195, + "young": 47196, + "directions": 47197, + "Ġneocortex": 47198, + "ĠKik": 47199, + "ango": 47200, + "clay": 47201, + "iodo": 47202, + "Ġabovementioned": 47203, + "ĠGuardian": 47204, + "Conjecture": 47205, + "ĠTrend": 47206, + "Ġfertilized": 47207, + "ĠSulfate": 47208, + "ochronology": 47209, + "Ġcraniofacial": 47210, + "ĠSaskatchewan": 47211, + "QQ": 47212, + "hman": 47213, + "Ġzym": 47214, + "logs": 47215, + "Ġïģ®": 47216, + "Ġgraduating": 47217, + "pinene": 47218, + "ĠîĢ": 47219, + "Ġetiological": 47220, + "ĠComprehension": 47221, + "Ġwandering": 47222, + "Ġlan": 47223, + "Ġsyst": 47224, + "returns": 47225, + "MOF": 47226, + "choalveolar": 47227, + "ĠArmen": 47228, + "Ġbimetallic": 47229, + "ĠPollen": 47230, + "Files": 47231, + "Ġssp": 47232, + "ENSI": 47233, + "ĠYus": 47234, + "Ġfinest": 47235, + "AGEN": 47236, + "Ġmicrobiomes": 47237, + "Ġpalind": 47238, + "Ġpetals": 47239, + "ĠRadiotherapy": 47240, + "ophenone": 47241, + "speaker": 47242, + "Ġcopepods": 47243, + "Ġkanamycin": 47244, + "Ġdegranulation": 47245, + "Construct": 47246, + "alter": 47247, + "ĠFgf": 47248, + "ĠNBS": 47249, + "ĠIncomplete": 47250, + "Ġparcel": 47251, + "neau": 47252, + "ĠÃIJ": 47253, + "ĠCHA": 47254, + "Ġduals": 47255, + "Ġsilicates": 47256, + "ĠGlobally": 47257, + "Ġkinesin": 47258, + "fid": 47259, + "ĠCPD": 47260, + "ĠYad": 47261, + "Ġdepress": 47262, + "ODY": 47263, + "ĠHistograms": 47264, + "ĠSummarization": 47265, + "automatic": 47266, + "ĠDomin": 47267, + "otransformation": 47268, + "Ġventricles": 47269, + "Widget": 47270, + "ĠPetersburg": 47271, + "Ġcholangiocarcinoma": 47272, + "Ġnectar": 47273, + "PIC": 47274, + "Scope": 47275, + "Tek": 47276, + "nitz": 47277, + "ĠPHD": 47278, + "Ġspiro": 47279, + "ĠCOG": 47280, + "ĠDioxide": 47281, + "conductivity": 47282, + "ĠGranger": 47283, + "ĠWearable": 47284, + "ĠKenneth": 47285, + "CCR": 47286, + "LINK": 47287, + "ĠÜ": 47288, + "retic": 47289, + "lya": 47290, + "Ġdemocratic": 47291, + "Ġradiograph": 47292, + "ĠRelax": 47293, + "ĠIncubation": 47294, + "ĠDenoising": 47295, + "COLOR": 47296, + "ĠClosure": 47297, + "HMM": 47298, + "urd": 47299, + "rada": 47300, + "ĠRv": 47301, + "ĠLuz": 47302, + "alls": 47303, + "Ġmultispectral": 47304, + "INED": 47305, + "SCN": 47306, + "Ġdyslexia": 47307, + "Ġsettlers": 47308, + "ĠVLSI": 47309, + "Ġavid": 47310, + "Ġlarynx": 47311, + "ĠChess": 47312, + "ĠFAA": 47313, + "Ġdefender": 47314, + "Ġlipolysis": 47315, + "ĠElmer": 47316, + "ĠAffymetrix": 47317, + "Ġrhodamine": 47318, + "Morph": 47319, + "Site": 47320, + "purity": 47321, + "ĠÊ": 47322, + "ĠTank": 47323, + "ĠMiao": 47324, + "Ġrecrystall": 47325, + "Weyl": 47326, + "ĠGuil": 47327, + "Ġmisfolded": 47328, + "suited": 47329, + "ĠApproximations": 47330, + "ĠABCB": 47331, + "donor": 47332, + "GWAS": 47333, + "---------------": 47334, + "Ġputida": 47335, + "Ġimpingement": 47336, + "yaml": 47337, + "Hill": 47338, + "Ġtl": 47339, + "agua": 47340, + "timing": 47341, + "Ġregenerate": 47342, + "Ġmultilingual": 47343, + "rador": 47344, + "classifier": 47345, + "ĠJohansson": 47346, + "Ġsulfides": 47347, + "hammer": 47348, + "Ġwalked": 47349, + "Ġallocating": 47350, + "ĠGustav": 47351, + "Ġimmunoprecipitated": 47352, + "ĠBrisbane": 47353, + "Ġsandwiched": 47354, + "ĠChatterjee": 47355, + "omandibular": 47356, + "Ġosc": 47357, + "Ġassass": 47358, + "Ġmultistage": 47359, + "Ġmultipartite": 47360, + "Ġpigmented": 47361, + "ĠVisualizing": 47362, + "Keys": 47363, + "pipeline": 47364, + "Ġdubbed": 47365, + "Ġcroc": 47366, + "ĠDLC": 47367, + "ĠRAT": 47368, + "ĠNex": 47369, + "plica": 47370, + "tingham": 47371, + "ĠSpider": 47372, + "Ġuncle": 47373, + "auts": 47374, + "ĠHowe": 47375, + "Ġarthropod": 47376, + "ĠPapad": 47377, + "urgy": 47378, + "Ġacclim": 47379, + "Broad": 47380, + "acer": 47381, + "vez": 47382, + "ĠDivers": 47383, + "Ġmodifiable": 47384, + "Ġantipsychotics": 47385, + "Prog": 47386, + "osahexa": 47387, + "ambrian": 47388, + "ĠIonization": 47389, + "ZA": 47390, + "oate": 47391, + "Ġpays": 47392, + "Ġewes": 47393, + "Ġbeaches": 47394, + "Ġevil": 47395, + "ĠCDs": 47396, + "naud": 47397, + "Ġconformity": 47398, + "ĠDMN": 47399, + "Ġcollaborate": 47400, + "Ġdeteriorate": 47401, + "VALID": 47402, + "ĠVegas": 47403, + "Ġultracent": 47404, + "BRA": 47405, + "Rub": 47406, + "YC": 47407, + "fh": 47408, + "åľ": 47409, + "ĠOWL": 47410, + "oseismic": 47411, + "oferrin": 47412, + "ochthon": 47413, + "ĠTNFR": 47414, + "smallsetminus": 47415, + "ĠArgument": 47416, + "Ġgranulocytes": 47417, + "Ġramified": 47418, + "Ġepiphy": 47419, + "fusc": 47420, + "ecdot": 47421, + "Ġhw": 47422, + "ĠNMS": 47423, + "ercus": 47424, + "Ġtether": 47425, + "ĠTrait": 47426, + "AgCl": 47427, + "ĠNearby": 47428, + "Ġhelminth": 47429, + "Ġlaevis": 47430, + "ĠBAR": 47431, + "ĠNancy": 47432, + "ĠGyn": 47433, + "Ġsecreting": 47434, + "Stellar": 47435, + "Ġsilhou": 47436, + "IMT": 47437, + "Ġscaffolding": 47438, + "ĠConverter": 47439, + "hid": 47440, + "Ġnud": 47441, + "estrian": 47442, + "anno": 47443, + "Ġdepiction": 47444, + "oremost": 47445, + "ĠShand": 47446, + "ABCD": 47447, + "ĠPDL": 47448, + "Ġdysphagia": 47449, + "Ġintrat": 47450, + "Ġhemip": 47451, + "Ġadaptable": 47452, + "longmapsto": 47453, + "ssbauer": 47454, + "ĠMcCarthy": 47455, + "ĠAutoimmune": 47456, + "ĠCutaneous": 47457, + "Inserting": 47458, + "Material": 47459, + "ĠAa": 47460, + "ĠGav": 47461, + "Ġmonocular": 47462, + "equil": 47463, + "ĠGeoff": 47464, + "Ġtethered": 47465, + "obilized": 47466, + "ĠShortly": 47467, + "Details": 47468, + "Ġrefugee": 47469, + "Ġabscisic": 47470, + "FBQyx": 47471, + "Ġdemocracy": 47472, + "crafted": 47473, + "difluor": 47474, + "yder": 47475, + "essment": 47476, + "Ġhistopathologic": 47477, + "Ġastrocytic": 47478, + "Ġwithdrew": 47479, + "Ġmoles": 47480, + "athic": 47481, + "mono": 47482, + "manual": 47483, + "Ġfoodborne": 47484, + "ĠRepository": 47485, + "Ġcovert": 47486, + "OTE": 47487, + "Ġtightness": 47488, + "Ġinstantiated": 47489, + "Ġwatermarking": 47490, + "Ġartemisinin": 47491, + "Language": 47492, + "OES": 47493, + "cant": 47494, + "already": 47495, + "unts": 47496, + "itia": 47497, + "ĠKaren": 47498, + "Ġalluvial": 47499, + "stratigraphy": 47500, + "ĠPIV": 47501, + "ĠFaces": 47502, + "ĠBim": 47503, + "applications": 47504, + "tails": 47505, + "Ġeld": 47506, + "IRB": 47507, + "ĠINTE": 47508, + "ĠNotImplemented": 47509, + "Ġmisclassified": 47510, + "Ġfertilizers": 47511, + "ĠElectricity": 47512, + "Ġtributaries": 47513, + "ĠDeutsch": 47514, + "Ġsleeve": 47515, + "fuzzy": 47516, + "ĠMTL": 47517, + "ĠBres": 47518, + "ĠWyn": 47519, + "Ġkyr": 47520, + "neuronal": 47521, + "oxymethyl": 47522, + "disorder": 47523, + "inches": 47524, + "ramidal": 47525, + "Ġpolyimide": 47526, + "ResNet": 47527, + "ĠEdmund": 47528, + "Ġdegeneracies": 47529, + "utherford": 47530, + "Dropout": 47531, + "ijĢ": 47532, + "Ġvoiced": 47533, + "ĠGomes": 47534, + "ivities": 47535, + "conductance": 47536, + "compl": 47537, + "vecs": 47538, + "Ġtuna": 47539, + "ĠKinect": 47540, + "Ġconveyed": 47541, + "Ġsphingosine": 47542, + "bat": 47543, + "ĠPurs": 47544, + "ounded": 47545, + "ĠStam": 47546, + "ĠXIII": 47547, + "ĠComics": 47548, + "MSM": 47549, + "SSL": 47550, + "Ġperfluor": 47551, + "Ġfluorinated": 47552, + "folios": 47553, + "Ġreposition": 47554, + "ĠSerr": 47555, + "ĠCors": 47556, + "ĠLabs": 47557, + "Ġcox": 47558, + "ĠAcquired": 47559, + "Ġreasoned": 47560, + "Genome": 47561, + "ĠPiper": 47562, + "Ġcompactified": 47563, + "Ġherbivore": 47564, + "lofenac": 47565, + "Ġboss": 47566, + "ĠBs": 47567, + "ĠEMR": 47568, + "Ġshoe": 47569, + "Ġcarers": 47570, + "Chrom": 47571, + "SVP": 47572, + "ĠTriangle": 47573, + "Ġhematite": 47574, + "dorf": 47575, + "ĠMovements": 47576, + "ĠVesicles": 47577, + "Olympus": 47578, + "Mol": 47579, + "Ġlend": 47580, + "uras": 47581, + "ĠASE": 47582, + "ĠWKB": 47583, + "proved": 47584, + "ĠKV": 47585, + "ĠUART": 47586, + "logarithmic": 47587, + "ĠADI": 47588, + "ĠDoing": 47589, + "Ġcease": 47590, + "Ġlengthening": 47591, + "Ġpyrophosphate": 47592, + "Fre": 47593, + "ĠCLD": 47594, + "ĠMLS": 47595, + "ĠPlum": 47596, + "Ġpropionate": 47597, + "ĠGuatem": 47598, + "CKD": 47599, + "Ġisos": 47600, + "ĠManning": 47601, + "neuro": 47602, + "OPER": 47603, + "ĠWilhelm": 47604, + "Ġacademia": 47605, + "AChR": 47606, + "ĠInertial": 47607, + "Occ": 47608, + "ujan": 47609, + "onas": 47610, + "Ġinulin": 47611, + "icia": 47612, + "andal": 47613, + "ĠKahn": 47614, + "Ġunmanned": 47615, + "ĠCoarse": 47616, + "Ġguilty": 47617, + "ĠPei": 47618, + "ĠLuca": 47619, + "ĠFibroblast": 47620, + "avian": 47621, + "vx": 47622, + "Ġdizziness": 47623, + "ĠDox": 47624, + "ĠHour": 47625, + "Ġdecoration": 47626, + "Ġverifier": 47627, + "rado": 47628, + "Ġfootprints": 47629, + "Ġdispensable": 47630, + "ĠAnaerobic": 47631, + "IoT": 47632, + "ĠRisks": 47633, + "ĠGLS": 47634, + "Ġchords": 47635, + "oidy": 47636, + "Ġneurolog": 47637, + "ruh": 47638, + "Ġvirtualization": 47639, + "Ġprotonation": 47640, + "ĠConstantin": 47641, + "Ġkeypoints": 47642, + "Buck": 47643, + "Hopf": 47644, + "Much": 47645, + "regime": 47646, + "Ġpromised": 47647, + "aij": 47648, + "ĠDesulf": 47649, + "ĠFormulas": 47650, + "Ġhump": 47651, + "lnc": 47652, + "ĠSuicide": 47653, + "ĠHOMA": 47654, + "oglycer": 47655, + "ĠProteomics": 47656, + "Ġdictate": 47657, + "ĠSpermat": 47658, + "Fun": 47659, + "Ġsag": 47660, + "ĠFam": 47661, + "eppe": 47662, + "ĠJah": 47663, + "Ġarisen": 47664, + "opharmaceutical": 47665, + "SAGE": 47666, + "ĠTHIS": 47667, + "enhance": 47668, + "Ġnapus": 47669, + "roe": 47670, + "ensch": 47671, + "deformation": 47672, + "bones": 47673, + "ĠErnest": 47674, + "irability": 47675, + "decom": 47676, + "Ġcrustaceans": 47677, + "Ġguaranteeing": 47678, + "OVAs": 47679, + "ĠMulticenter": 47680, + "ĠctDNA": 47681, + "Ġforaminifera": 47682, + "Linn": 47683, + "Ġcups": 47684, + "esch": 47685, + "ĠdF": 47686, + "ĠTah": 47687, + "pll": 47688, + "projects": 47689, + "ĠUCI": 47690, + "Ġhumanized": 47691, + "Ġabsl": 47692, + "ĠScho": 47693, + "Ġliterals": 47694, + "ĠSVR": 47695, + "Ġtoxicology": 47696, + "pgf": 47697, + "ĠIPTG": 47698, + "ĠMEASUREM": 47699, + "oing": 47700, + "ĠPasc": 47701, + "ĠBau": 47702, + "ĠWannier": 47703, + "Ġhypre": 47704, + "attributes": 47705, + "Ġpreconditioner": 47706, + "Writing": 47707, + "Ġgypsum": 47708, + "yuan": 47709, + "Ġupregulates": 47710, + "Ġtelec": 47711, + "ĠDiscre": 47712, + "guard": 47713, + "Ġdebates": 47714, + "Ġparasitoid": 47715, + "Lam": 47716, + "tige": 47717, + "Ġisopropanol": 47718, + "ĠIwas": 47719, + "plify": 47720, + "indolin": 47721, + "ĠApollo": 47722, + "Ġlanded": 47723, + "Ġbeamline": 47724, + "Union": 47725, + "Ġreciproc": 47726, + "ĠRossby": 47727, + "principal": 47728, + "Ġdescendant": 47729, + "ĠAnalogously": 47730, + "Ġderegulation": 47731, + "DSM": 47732, + "cta": 47733, + "Ġrebuilt": 47734, + "ĠMund": 47735, + "ĠFEC": 47736, + "ryn": 47737, + "plice": 47738, + "ĠYugoslav": 47739, + "ĠNorthwestern": 47740, + "ĠHomogen": 47741, + "ĠLISA": 47742, + "Ġinvestor": 47743, + "HSA": 47744, + "HPO": 47745, + "Ġdictionaries": 47746, + "ĠCategor": 47747, + "Ġcompacted": 47748, + "tilled": 47749, + "ç»": 47750, + "Ġfines": 47751, + "urans": 47752, + "Ġbetweenness": 47753, + "ĠZig": 47754, + "schema": 47755, + "Ġcommune": 47756, + "ĠQuinn": 47757, + "Ġanaphylaxis": 47758, + "TIES": 47759, + "Ġsnowpack": 47760, + "ĠDOA": 47761, + "agos": 47762, + "ĠOdd": 47763, + "arde": 47764, + "Ġevoke": 47765, + "ĠOcular": 47766, + "Ġfaulting": 47767, + "Ġvolcanism": 47768, + "ĠPaleozoic": 47769, + "Ġmycelium": 47770, + "ĠAdjustment": 47771, + "ICT": 47772, + "Nov": 47773, + "alias": 47774, + "ĠTul": 47775, + "ĠHh": 47776, + "Ġevade": 47777, + "ORs": 47778, + "Ġstrengthens": 47779, + "ĠUSGS": 47780, + "Ġlicensing": 47781, + "ĠClement": 47782, + "ĠPhytophthora": 47783, + "rified": 47784, + "Ġeighteen": 47785, + "Ġtops": 47786, + "ĠCLP": 47787, + "Ġstabilities": 47788, + "ĠPPT": 47789, + "ĠBIN": 47790, + "ĠRak": 47791, + "Ġgenistein": 47792, + "volve": 47793, + "Ġquicker": 47794, + "ĠCaused": 47795, + "benefit": 47796, + "YB": 47797, + "lift": 47798, + "Ġhood": 47799, + "ĠSCs": 47800, + "ofa": 47801, + "ĠMicron": 47802, + "angiotensin": 47803, + "Ġfeathers": 47804, + "Ġantiferromagnet": 47805, + "DECREF": 47806, + "yledons": 47807, + "Ġmyriad": 47808, + "Ġiz": 47809, + "ĠTrough": 47810, + "âĪ«": 47811, + "hemoglobin": 47812, + "ĠEnvelope": 47813, + "ĠClick": 47814, + "soliton": 47815, + "ĠSynchrotron": 47816, + "Ġlagged": 47817, + "MYB": 47818, + "Ġtrophoblast": 47819, + "Ġinterrogation": 47820, + "onvuls": 47821, + "Bac": 47822, + "Ġaperiodic": 47823, + "Ġgpu": 47824, + "Ġpropidium": 47825, + "teps": 47826, + "ĠKarp": 47827, + "ĠVaz": 47828, + "ackage": 47829, + "onson": 47830, + "Instr": 47831, + "filer": 47832, + "rifugation": 47833, + "KOV": 47834, + "fourth": 47835, + "Ġôı¼IJ": 47836, + "hyperbolic": 47837, + "schetz": 47838, + "Discussion": 47839, + "ĠOriented": 47840, + "jad": 47841, + "Ġauctions": 47842, + "usivity": 47843, + "ĠCran": 47844, + "Ġkd": 47845, + "Ġintest": 47846, + "rosarcoma": 47847, + "ugger": 47848, + "ĠILP": 47849, + "ĠSTA": 47850, + "Ġreversals": 47851, + "Ġgrapes": 47852, + "ĠPopulus": 47853, + "ĠKitaev": 47854, + "ĠAVP": 47855, + "Previously": 47856, + "Ġquadratically": 47857, + "ĠLOCAL": 47858, + "Bert": 47859, + "PED": 47860, + "live": 47861, + "à¬": 47862, + "Ġbidding": 47863, + "Ġtoss": 47864, + "ento": 47865, + "Ġthylak": 47866, + "Ġcomprehend": 47867, + "Ġdive": 47868, + "Ġapplicants": 47869, + "ĠÄħ": 47870, + "ĠVolcanic": 47871, + "adaptation": 47872, + "Ġá¹Ģ": 47873, + "ĠJanssen": 47874, + "Ġadjoining": 47875, + "ozolomide": 47876, + "CIS": 47877, + "dC": 47878, + "ducted": 47879, + "ĠAnast": 47880, + "ĠEmployment": 47881, + "ĠEndocrine": 47882, + "siloxane": 47883, + "Session": 47884, + "ĠNarr": 47885, + "ĠâĪĴâĪĨ": 47886, + "deev": 47887, + "othiaz": 47888, + "ringing": 47889, + "pointed": 47890, + "Ġacetylene": 47891, + "Ġglobulin": 47892, + "packing": 47893, + "ĠUses": 47894, + "AES": 47895, + "Hen": 47896, + "ĠSavage": 47897, + "ĠCanc": 47898, + "isto": 47899, + "ĠChromosomal": 47900, + "Ġcemented": 47901, + "Ġpyrox": 47902, + "ĠConstitutive": 47903, + "Ġphthalate": 47904, + "mechanism": 47905, + "Ġcyclosporine": 47906, + "PAP": 47907, + "arted": 47908, + "ĠRDT": 47909, + "Ġplains": 47910, + "Clone": 47911, + "propanol": 47912, + "regularity": 47913, + "Ġcotangent": 47914, + "ĠLeslie": 47915, + "ĠNitrate": 47916, + "ĠKawasaki": 47917, + "ĠPageRank": 47918, + "Ġanhydrase": 47919, + "ĠKrishna": 47920, + "Ġhemicellulose": 47921, + "Ġery": 47922, + "llis": 47923, + "Ġmicrogram": 47924, + "ĠDeligne": 47925, + "Ġenforces": 47926, + "Ġthrombolysis": 47927, + "Parse": 47928, + "orvastatin": 47929, + "Ġmated": 47930, + "ĠCrystalline": 47931, + "Ġautoradi": 47932, + "Ġthermophilic": 47933, + "infectious": 47934, + "Ġultram": 47935, + "ĠMLL": 47936, + "ĠFibers": 47937, + "Ġulceration": 47938, + "omedial": 47939, + "stratigraphic": 47940, + "Ġtouches": 47941, + "rhe": 47942, + "Ġtame": 47943, + "ĠCulic": 47944, + "ARDS": 47945, + "chter": 47946, + "Ġcounterclockwise": 47947, + "Ġcamps": 47948, + "VDC": 47949, + "Ġmethadone": 47950, + "dependently": 47951, + "validate": 47952, + "Ġprecludes": 47953, + "Ġparliamentary": 47954, + "ĠINTEREST": 47955, + "ĠSerg": 47956, + "ĠCBC": 47957, + "erella": 47958, + "ayi": 47959, + "ĠRAB": 47960, + "Ġchym": 47961, + "Ġnanospheres": 47962, + "Ġdiabetics": 47963, + "conservation": 47964, + "Ġpermeate": 47965, + "plotted": 47966, + "Ġnaphthalene": 47967, + "ĠBonn": 47968, + "ĠElectrostatic": 47969, + "Ġinventories": 47970, + "Gaussianity": 47971, + "ĠAdenosine": 47972, + "Delay": 47973, + "ĠBeginning": 47974, + "Ġsided": 47975, + "ĠCushing": 47976, + "ĠHv": 47977, + "Ġcoined": 47978, + "ĠAlm": 47979, + "scanning": 47980, + "fertil": 47981, + "Ġαv": 47982, + "ĠReactivity": 47983, + "Ġproximate": 47984, + "dependencies": 47985, + "Ġdensification": 47986, + "Ġôı¼ij": 47987, + "Ġbacteriocin": 47988, + "weakly": 47989, + "Ġdentistry": 47990, + "ĠOriental": 47991, + "Ġdormant": 47992, + "ĠpC": 47993, + "Ġmum": 47994, + "REs": 47995, + "Ġconval": 47996, + "Ġbiota": 47997, + "Ġmultilinear": 47998, + "ĠPTFE": 47999, + "Ġnarrowband": 48000, + "ĠRotational": 48001, + "Ġhoneybee": 48002, + "ĠChlorophyll": 48003, + "Baseline": 48004, + "Fern": 48005, + "Ġlk": 48006, + "ĠMash": 48007, + "rived": 48008, + "ĠBases": 48009, + "ĠDah": 48010, + "ĠKui": 48011, + "ĠÃĵ": 48012, + "ĠRecycl": 48013, + "AGN": 48014, + "PDE": 48015, + "Ġclimatological": 48016, + "ĠBasically": 48017, + "conserved": 48018, + "absorbing": 48019, + "ĠKoszul": 48020, + "oussines": 48021, + "Ġmdx": 48022, + "ithymia": 48023, + "ĠHinton": 48024, + "Ġkh": 48025, + "Ġadmittance": 48026, + "ĠVy": 48027, + "Ġextrema": 48028, + "Ġcreftype": 48029, + "subst": 48030, + "Ġbleomycin": 48031, + "LINEAR": 48032, + "AQ": 48033, + "iom": 48034, + "Ġnong": 48035, + "opian": 48036, + "sein": 48037, + "udal": 48038, + "Ġearning": 48039, + "Ġstandardize": 48040, + "ĠParticular": 48041, + "Ġwavevector": 48042, + "dxdy": 48043, + "ĠMacDonald": 48044, + "ĠEstuary": 48045, + "validated": 48046, + "ĠHurst": 48047, + "ĠMukherjee": 48048, + "Ġbivalves": 48049, + "Ġjugular": 48050, + "Ub": 48051, + "vill": 48052, + "enough": 48053, + "Ġinforms": 48054, + "anatomical": 48055, + "ulou": 48056, + "resa": 48057, + "ĠPMC": 48058, + "ĠMira": 48059, + "ĠRPL": 48060, + "ĠSDC": 48061, + "Ġhemi": 48062, + "MoS": 48063, + "ĠFloat": 48064, + "Ġocclusal": 48065, + "ĠRainbow": 48066, + "ĠProviding": 48067, + "Ġsupercapacitor": 48068, + "osf": 48069, + "ĠIRT": 48070, + "Ġadm": 48071, + "Ġdecoders": 48072, + "ĠXR": 48073, + "ĠRescue": 48074, + "Ġentom": 48075, + "Ġmortal": 48076, + "Angle": 48077, + "India": 48078, + "ĠMali": 48079, + "Ġinspecting": 48080, + "ĠGALAXY": 48081, + "ĠEriks": 48082, + "YF": 48083, + "rings": 48084, + "Ġsir": 48085, + "Ġgsl": 48086, + "ĠBubble": 48087, + "ĠDCA": 48088, + "ĠWidespread": 48089, + "assignment": 48090, + "Ġgeomorph": 48091, + "ĠPreference": 48092, + "COPD": 48093, + "processors": 48094, + "cutoff": 48095, + "ĠFlower": 48096, + "phenomen": 48097, + "music": 48098, + "ĠSlovakia": 48099, + "Supporting": 48100, + "blow": 48101, + "edit": 48102, + "ĠTrophy": 48103, + "ĠASF": 48104, + "ĠMoses": 48105, + "Ġindels": 48106, + "Ġnonhuman": 48107, + "Ġhandic": 48108, + "Ġrepairing": 48109, + "Ġmicrometer": 48110, + "ĠPhilippe": 48111, + "Ġexudates": 48112, + "ĠâĹĭ": 48113, + "Ġamalgam": 48114, + "Kin": 48115, + "fors": 48116, + "fron": 48117, + "Ġanabolic": 48118, + "ĠEich": 48119, + "NAN": 48120, + "Ġpseudogap": 48121, + "analyzed": 48122, + "Ġtackled": 48123, + "aginous": 48124, + "Ġlubricant": 48125, + "Ġradionuclides": 48126, + "arrestin": 48127, + "oussinesq": 48128, + "Lif": 48129, + "Î¥": 48130, + "received": 48131, + "astive": 48132, + "ĠPBC": 48133, + "Ġamoxicillin": 48134, + "copper": 48135, + "ubling": 48136, + "ophages": 48137, + "ĠSeas": 48138, + "ĠElite": 48139, + "PMMA": 48140, + "Ġcholang": 48141, + "Depending": 48142, + "Ġasbestos": 48143, + "ĠFecal": 48144, + "ĠRath": 48145, + "ĠLey": 48146, + "Ġfactored": 48147, + "bbles": 48148, + "Ġtokenizer": 48149, + "Ġofficinalis": 48150, + "ĠNUCLE": 48151, + "ĠSemicon": 48152, + "ĠBous": 48153, + "ĠRis": 48154, + "Ġloans": 48155, + "ACP": 48156, + "âĻĢ": 48157, + "phosate": 48158, + "Ġcherry": 48159, + "anan": 48160, + "arre": 48161, + "ĠCredit": 48162, + "isexual": 48163, + "ĠActa": 48164, + "ĠLetting": 48165, + "ĠInfarction": 48166, + "ĠAccounting": 48167, + "Ġcounterstained": 48168, + "Ġaerogel": 48169, + "standardized": 48170, + "Ġlyase": 48171, + "segments": 48172, + "Ġbachelor": 48173, + "Ġhue": 48174, + "ĠNETs": 48175, + "Ġunadjusted": 48176, + "Ġmicrohardness": 48177, + "Ġsinglets": 48178, + "ĠSPACE": 48179, + "ĠHydraulic": 48180, + "METHOD": 48181, + "ĠBjör": 48182, + "ĠKU": 48183, + "Ġrepur": 48184, + "Ġradiocarbon": 48185, + "Ġheterogeneities": 48186, + "Ġgastrocnemius": 48187, + "ĠLTD": 48188, + "Ġaccidentally": 48189, + "Processing": 48190, + "Doppler": 48191, + "TBI": 48192, + "Ġlingual": 48193, + "ĠAGS": 48194, + "ĠFrontal": 48195, + "ĠBrack": 48196, + "thema": 48197, + "Ġrepresentable": 48198, + "Ġpressurized": 48199, + "ADR": 48200, + "ĠMicrofluid": 48201, + "Ġê°": 48202, + "Ġreusable": 48203, + "Ġvendor": 48204, + "aller": 48205, + "Ġdiversion": 48206, + "FAST": 48207, + "ĠKirby": 48208, + "ĠStimulus": 48209, + "Ġattachments": 48210, + "ĠBridging": 48211, + "ĠRoberto": 48212, + "Ġqueuing": 48213, + "tling": 48214, + "roots": 48215, + "ĠMx": 48216, + "ĠMarrow": 48217, + "ĠLocus": 48218, + "Ġunimportant": 48219, + "ergarten": 48220, + "ÃŃk": 48221, + "ĠPotent": 48222, + "ĠBrunswick": 48223, + "ĠSCT": 48224, + "ĠMour": 48225, + "emias": 48226, + "ĠNCS": 48227, + "chicine": 48228, + "ĠOryza": 48229, + "Ġwherever": 48230, + "ĠXGB": 48231, + "COX": 48232, + "Ġhydrogenated": 48233, + "Ġhydraz": 48234, + "ĠPersons": 48235, + "Ġframeshift": 48236, + "Ġelectrolytic": 48237, + "ĠSenegal": 48238, + "Ġphagocyt": 48239, + "Ġinstantaneously": 48240, + "ĠGroundwater": 48241, + "Ġimperial": 48242, + "ĠRhode": 48243, + "ÅĦska": 48244, + "ovisual": 48245, + "ontsize": 48246, + "ĠExplanation": 48247, + "Ġempowerment": 48248, + "NTA": 48249, + "Pu": 48250, + "Por": 48251, + "Sched": 48252, + "eats": 48253, + "Ġys": 48254, + "inous": 48255, + "Ġwilt": 48256, + "ĠMov": 48257, + "ecton": 48258, + "ĠGins": 48259, + "introduction": 48260, + "inception": 48261, + "ĠInterpreting": 48262, + "Ġstartup": 48263, + "Ġalbino": 48264, + "Ġtetras": 48265, + "ĠHousehold": 48266, + "ĠELM": 48267, + "Ġsporulation": 48268, + "Ġosmol": 48269, + "Bis": 48270, + "erule": 48271, + "ĠEAR": 48272, + "Ġimbalances": 48273, + "Ġkt": 48274, + "Ġjl": 48275, + "gesterone": 48276, + "erala": 48277, + "ĠPointer": 48278, + "ĠHRQoL": 48279, + "ĠRiet": 48280, + "ĠEscape": 48281, + "purified": 48282, + "Ġinstantiation": 48283, + "matis": 48284, + "iona": 48285, + "Ġnoxious": 48286, + "ĠNog": 48287, + "Ġjam": 48288, + "ĠAntoni": 48289, + "ĠGodd": 48290, + "ĠPersonalized": 48291, + "Ġpermuted": 48292, + "ĠSHE": 48293, + "ĠOblast": 48294, + "ĠForbes": 48295, + "ĠResveratrol": 48296, + "ĠFeSe": 48297, + "Ġelectrodeposition": 48298, + "Ġhomeobox": 48299, + "Ġpyogenes": 48300, + "Ġviolin": 48301, + "Ġisoelectric": 48302, + "ĠPPG": 48303, + "probably": 48304, + "AMPK": 48305, + "ĠWolfe": 48306, + "Ġultrafine": 48307, + "Beyond": 48308, + "onat": 48309, + "edian": 48310, + "ENABLE": 48311, + "ĠHAM": 48312, + "sout": 48313, + "ĠOpinion": 48314, + "rinted": 48315, + "typing": 48316, + "Unknown": 48317, + "Ġbuckets": 48318, + "Ġintuitionistic": 48319, + "algorithms": 48320, + "SSC": 48321, + "bir": 48322, + "ĠPond": 48323, + "advert": 48324, + "ipin": 48325, + "Ġupwind": 48326, + "ĠClaire": 48327, + "ĠMaturation": 48328, + "ĠPrP": 48329, + "OPO": 48330, + "FORMANCE": 48331, + "ĠdM": 48332, + "ĠCities": 48333, + "Ġinterrelated": 48334, + "ĠApparatus": 48335, + "Ġprecious": 48336, + "criptors": 48337, + "Ġpreparedness": 48338, + "ĠARCH": 48339, + "ĠPathogens": 48340, + "HOST": 48341, + "ĠGibbons": 48342, + "Ġirregularity": 48343, + "ĠLipids": 48344, + "Ġcfu": 48345, + "Ġvasodilation": 48346, + "imetre": 48347, + "improved": 48348, + "mq": 48349, + "ĠHens": 48350, + "ĠLoci": 48351, + "uncredited": 48352, + "Ġmultigrid": 48353, + "tigo": 48354, + "Ġaccountability": 48355, + "enchyme": 48356, + "Ġdisadvantaged": 48357, + "Ġbisphenol": 48358, + "Ġtic": 48359, + "Ġforks": 48360, + "ĠWester": 48361, + "ĠVii": 48362, + "ĠJere": 48363, + "simultaneous": 48364, + "ĠGuarant": 48365, + "ĠDoyle": 48366, + "Ġpotentiates": 48367, + "lassified": 48368, + "Ġileal": 48369, + "Ġvasoconstriction": 48370, + "MODULE": 48371, + "Nano": 48372, + "Wood": 48373, + "ĠTAT": 48374, + "urious": 48375, + "unya": 48376, + "Ġinstillation": 48377, + "ĠSimmons": 48378, + "ĠDirectional": 48379, + "Ġmalate": 48380, + "Ġplantation": 48381, + "Ġunsolved": 48382, + "ĠTauri": 48383, + "Ġovine": 48384, + "Ġkeratinocyte": 48385, + "ĠKullback": 48386, + "ĠKazakhstan": 48387, + "Ġhirs": 48388, + "ĠAerobic": 48389, + "ĠHai": 48390, + "ĠRiley": 48391, + "ensible": 48392, + "Ġinterplanetary": 48393, + "Ġtransits": 48394, + "Ġgenerous": 48395, + "Ġcalpain": 48396, + "Ġappended": 48397, + "ĠHydrodynamics": 48398, + "Ġcolonize": 48399, + "Ġheartbeat": 48400, + "Ġmetastas": 48401, + "Ġpyreth": 48402, + "ĠPAK": 48403, + "ĠС": 48404, + "multiplet": 48405, + "ĠBrady": 48406, + "Ġpropria": 48407, + "ĠFrontier": 48408, + "ĠJoyce": 48409, + "ĠPGF": 48410, + "ĠMcl": 48411, + "recurrent": 48412, + "ĠReplacing": 48413, + "inference": 48414, + "ĠWhitt": 48415, + "Ġschooling": 48416, + "ĠHarold": 48417, + "Ġabstractions": 48418, + "âĬķ": 48419, + "memcpy": 48420, + "Ġmicronucle": 48421, + "Ġradionuclide": 48422, + "otyl": 48423, + "ĠMIF": 48424, + "ĠMUS": 48425, + "Ġexfoli": 48426, + "ĠFamilial": 48427, + "Ġclam": 48428, + "ONO": 48429, + "Ġvanilla": 48430, + "Ġpastoris": 48431, + "ĠATL": 48432, + "ĠBursts": 48433, + "Quantitative": 48434, + "Ġeliciting": 48435, + "Ġgranulomatous": 48436, + "Ġbrowsing": 48437, + "tracks": 48438, + "Ġhij": 48439, + "ĠBCP": 48440, + "incomp": 48441, + "azid": 48442, + "ckpt": 48443, + "Ġlinkers": 48444, + "Ġsquid": 48445, + "Ġheadaches": 48446, + "ĠMoral": 48447, + "Ġstabilisation": 48448, + "&&&&": 48449, + "ĠSufficient": 48450, + "ĠArchaea": 48451, + "Ġìł": 48452, + "ĠLuciferase": 48453, + "Camera": 48454, + "expanded": 48455, + "Ġmysterious": 48456, + "HPS": 48457, + "ĠBJ": 48458, + "ĠKNN": 48459, + "Ġsuperhydrophobic": 48460, + "ĠHydrothermal": 48461, + "ĠRusso": 48462, + "ĠArsenic": 48463, + "Ġnormotensive": 48464, + "ultimate": 48465, + "ĠCMIP": 48466, + "examined": 48467, + "Ġmicroporous": 48468, + "Ġforever": 48469, + "ĠSTING": 48470, + "IGS": 48471, + "ĉĉĉĠĠ": 48472, + "Plant": 48473, + "Ġcoherently": 48474, + "charging": 48475, + "Ġinherit": 48476, + "alternative": 48477, + "ĠBaptist": 48478, + "Fm": 48479, + "bipy": 48480, + "Ġoler": 48481, + "ĠSubstit": 48482, + "Ġultrap": 48483, + "freeze": 48484, + "pergill": 48485, + "POSE": 48486, + "Ġadvertisements": 48487, + "ECHAN": 48488, + "Bayesian": 48489, + "Ġcobordism": 48490, + "¸°": 48491, + "ĠAER": 48492, + "ĠAIP": 48493, + "ĠLNA": 48494, + "essentially": 48495, + "reciprocal": 48496, + "ĠAnand": 48497, + "Ġsmeared": 48498, + "onese": 48499, + "ethylamine": 48500, + "ĠERS": 48501, + "Ġjudicial": 48502, + "Ġwoodland": 48503, + "ĠGregor": 48504, + "Ġtabular": 48505, + "avirin": 48506, + "mirror": 48507, + "Ġjaundice": 48508, + "astigotes": 48509, + "ĠLGBT": 48510, + "ĠNaj": 48511, + "Ġsubscheme": 48512, + "Ġmultiuser": 48513, + "Ġdrains": 48514, + "Ġevacuated": 48515, + "phosphoryl": 48516, + "ĠFeldman": 48517, + "ĠTRIzol": 48518, + "ĠBLEU": 48519, + "aromatic": 48520, + "oviÄĩ": 48521, + "pion": 48522, + "repr": 48523, + "roth": 48524, + "ĠFES": 48525, + "ĠLeeds": 48526, + "Ġung": 48527, + "obranch": 48528, + "Ġpatency": 48529, + "ĠScr": 48530, + "ĠSimplex": 48531, + "pecies": 48532, + "Ġbenefici": 48533, + "Ġpolymerases": 48534, + "ĠCygn": 48535, + "octadec": 48536, + "Ġpunctured": 48537, + "Ġjaponicus": 48538, + "ĠFPGAs": 48539, + "frown": 48540, + "Ġeb": 48541, + "utiny": 48542, + "ĠPoy": 48543, + "ĠBrent": 48544, + "ĠBAM": 48545, + "ĠHick": 48546, + "ĠNPS": 48547, + "ĠGDF": 48548, + "ĠVIRT": 48549, + "Ġinterl": 48550, + "ĠscFv": 48551, + "Ġteamm": 48552, + "Ġparticipatory": 48553, + "Ġexistential": 48554, + "Ġosteomyelitis": 48555, + "Ġpneumothorax": 48556, + "stdout": 48557, + "Ġsingletons": 48558, + "hypothesis": 48559, + "stratified": 48560, + "USD": 48561, + "onasal": 48562, + "eris": 48563, + "imits": 48564, + "ĠICs": 48565, + "ĠEncephal": 48566, + "izi": 48567, + "ĠGradients": 48568, + "Ġallop": 48569, + "Ġcorp": 48570, + "constructed": 48571, + "Ġmonument": 48572, + "simulator": 48573, + "ĠFermions": 48574, + "ĠWyoming": 48575, + "Ġprednisolone": 48576, + "Lang": 48577, + "Notes": 48578, + "eer": 48579, + "Ġfighter": 48580, + "entrant": 48581, + "ĠNij": 48582, + "ĠGPD": 48583, + "ĠProl": 48584, + "Ġrealisation": 48585, + "Ġpackings": 48586, + "ĠDiscovering": 48587, + "ĠAnglo": 48588, + "ĠCassini": 48589, + "execute": 48590, + "Ġinhabited": 48591, + "across": 48592, + "ĠCram": 48593, + "ĠNBR": 48594, + "antes": 48595, + "Ġdispersing": 48596, + "achandran": 48597, + "ĠUND": 48598, + "Ġshoulders": 48599, + "Ġcrises": 48600, + "ustrine": 48601, + "Ġpropane": 48602, + "UNE": 48603, + "brush": 48604, + "Ġetiologies": 48605, + "Ġshotgun": 48606, + "showing": 48607, + "ĠPhytochemical": 48608, + "ĠMehta": 48609, + "orrhea": 48610, + "ĠImagery": 48611, + "Tre": 48612, + "wc": 48613, + "Ġeluent": 48614, + "ondin": 48615, + "ĠAttitude": 48616, + "Ġferromagnet": 48617, + "Ġcountermeasures": 48618, + "Ġalkanes": 48619, + "ĠCapillary": 48620, + "latent": 48621, + "Ġsolubil": 48622, + "Viewer": 48623, + "ázquez": 48624, + "ĠPunjab": 48625, + "aas": 48626, + "tang": 48627, + "Ġimports": 48628, + "ĠYounger": 48629, + "roughly": 48630, + "Weinberg": 48631, + "ĠAtkinson": 48632, + "bfa": 48633, + "MPa": 48634, + "steel": 48635, + "PCP": 48636, + "chlorinated": 48637, + "ĠPsychometric": 48638, + "Ġpyroptosis": 48639, + "Ġwatched": 48640, + "ĠPercutaneous": 48641, + "RBD": 48642, + "VARI": 48643, + "atu": 48644, + "ĠWake": 48645, + "Ġcanyon": 48646, + "iparous": 48647, + "Ġscall": 48648, + "completely": 48649, + "interfer": 48650, + "ophyceae": 48651, + "Ġfatalities": 48652, + "czak": 48653, + "ĠPathophysiology": 48654, + "Lem": 48655, + "lach": 48656, + "tuary": 48657, + "Ġalex": 48658, + "Ġsisters": 48659, + "Ġpum": 48660, + "ĠCatch": 48661, + "ĠEber": 48662, + "inex": 48663, + "phthe": 48664, + "Ġboar": 48665, + "ĠSoul": 48666, + "Ġcatfish": 48667, + "Ġcloudy": 48668, + "ĠBuilt": 48669, + "ophylline": 48670, + "ĠRibosome": 48671, + "ĠAnomalies": 48672, + "YD": 48673, + "categorical": 48674, + "wor": 48675, + "openta": 48676, + "ĠLIB": 48677, + "Ġrick": 48678, + "Ġradiations": 48679, + "Ġhypercube": 48680, + "Ġmaltreatment": 48681, + "ĠîĦĦ": 48682, + "dispersity": 48683, + "continent": 48684, + "Digital": 48685, + "ĠCoryneb": 48686, + "Ġrevert": 48687, + "ĠTEA": 48688, + "ĠMLR": 48689, + "ĠFCM": 48690, + "ĠLamp": 48691, + "izabilities": 48692, + "Ġcarved": 48693, + "ĠMonoclonal": 48694, + "Ġpenis": 48695, + "ĠMorales": 48696, + "Enter": 48697, + "esterification": 48698, + "Ġcabbage": 48699, + "RANTIES": 48700, + "Ġdebridement": 48701, + "Lead": 48702, + "cAMP": 48703, + "Ġcesium": 48704, + "ĠCubic": 48705, + "Ġunimodular": 48706, + "ĠExport": 48707, + "Ġanalyser": 48708, + "denotes": 48709, + "Ġradically": 48710, + "ĠHistology": 48711, + "Ġmelanomas": 48712, + "Ġworship": 48713, + "ĠHimalayan": 48714, + "ĠIntegrable": 48715, + "benzenesulfonamide": 48716, + "Ġharbored": 48717, + "Putting": 48718, + "ĠTir": 48719, + "ĠUTI": 48720, + "centers": 48721, + "ĠPluripot": 48722, + "Ġharbors": 48723, + "Ġcarbam": 48724, + "ĠAppalach": 48725, + "ĠJoan": 48726, + "ĠCommissioner": 48727, + "ĠGemini": 48728, + "Near": 48729, + "OPS": 48730, + "QG": 48731, + "pytorch": 48732, + "staining": 48733, + "ĠhCG": 48734, + "Ġgavage": 48735, + "perhaps": 48736, + "ĠGrib": 48737, + "ĠZah": 48738, + "Ġcomparably": 48739, + "ĠBioscience": 48740, + "SPL": 48741, + "Connell": 48742, + "ĠAirway": 48743, + "primed": 48744, + "Ġsubmucosal": 48745, + "Enhanced": 48746, + "Ġwisdom": 48747, + "VN": 48748, + "ĠMumbai": 48749, + "rius": 48750, + "ĠRGD": 48751, + "ĠRNeasy": 48752, + "mai": 48753, + "ĠADL": 48754, + "Ġadoptive": 48755, + "Outlined": 48756, + "ĠWARRANTIES": 48757, + "ĠViolence": 48758, + "Ġcaterp": 48759, + "Fund": 48760, + "dθ": 48761, + "ĠPok": 48762, + "ĠBenson": 48763, + "ĠRIG": 48764, + "ĠVs": 48765, + "Ġinstants": 48766, + "ĠMultidrug": 48767, + "PDMS": 48768, + "CONST": 48769, + "Ġcartridge": 48770, + "ĠLifestyle": 48771, + "ĠCONDITIONS": 48772, + "odysplastic": 48773, + "CONTROL": 48774, + "LHC": 48775, + "tire": 48776, + "ĠStain": 48777, + "Ġyx": 48778, + "Ġjunctional": 48779, + "obo": 48780, + "annah": 48781, + "ĠCPAP": 48782, + "Ġsoundness": 48783, + "ĠUltimate": 48784, + "silicon": 48785, + "Ġparalog": 48786, + "Events": 48787, + "Gas": 48788, + "JE": 48789, + "ĠJorge": 48790, + "Ġoverproduction": 48791, + "Ġmaxilla": 48792, + "ĠReasons": 48793, + "weeks": 48794, + "ĠNearest": 48795, + "Ġheadspace": 48796, + "ĠATC": 48797, + "balancing": 48798, + "Ġjudging": 48799, + "ĠUniversality": 48800, + "Ġsinuses": 48801, + "Ġretroperitoneal": 48802, + "Detection": 48803, + "Ġhydrolysate": 48804, + "Hoch": 48805, + "wrapper": 48806, + "ĠpKa": 48807, + "Ġlasso": 48808, + "ĠAlu": 48809, + "ĠAPR": 48810, + "ĠDors": 48811, + "ĠDarboux": 48812, + "ĠRFS": 48813, + "ĠKhar": 48814, + "ĠThrom": 48815, + "Ġdesignate": 48816, + "arco": 48817, + "Ġthermostat": 48818, + "ĠGlacial": 48819, + "IFF": 48820, + "ĠManifest": 48821, + "Ġinterspersed": 48822, + "hauser": 48823, + "ĠDDX": 48824, + "Ġale": 48825, + "tides": 48826, + "Ġlaccase": 48827, + "ĠHered": 48828, + "ĠRacial": 48829, + "ĠKats": 48830, + "ajo": 48831, + "ĠCoordinated": 48832, + "ĠProbably": 48833, + "Ġtitanate": 48834, + "SLAM": 48835, + "driving": 48836, + "ĠEmergent": 48837, + "ĠDrives": 48838, + "Ġobligations": 48839, + "Ġnebulae": 48840, + "fried": 48841, + "ithin": 48842, + "ĠPGD": 48843, + "occlusion": 48844, + "ĠUH": 48845, + "Ġsubroutine": 48846, + "oidin": 48847, + "Ġannoy": 48848, + "ĠVirasoro": 48849, + "instances": 48850, + "ĠDerby": 48851, + "Ġtriangulations": 48852, + "Ġcutoffs": 48853, + "ĠOrganizational": 48854, + "ĠVenk": 48855, + "ĠEGTA": 48856, + "ĠDeutsche": 48857, + "Ġantineut": 48858, + "ĠVulnerability": 48859, + "iated": 48860, + "Ġavium": 48861, + "Ġhsp": 48862, + "emulsions": 48863, + "adherence": 48864, + "ĠUPS": 48865, + "maph": 48866, + "ĠVAP": 48867, + "relatively": 48868, + "Ġmaxill": 48869, + "ophase": 48870, + "Threshold": 48871, + "ĠSupp": 48872, + "ethoprim": 48873, + "Ġpenetrated": 48874, + "Ġblasting": 48875, + "ĠAdvantages": 48876, + "BUS": 48877, + "olson": 48878, + "recv": 48879, + "Ġcarnitine": 48880, + "tening": 48881, + "Ġprovoked": 48882, + "various": 48883, + "ĠCalab": 48884, + "leneck": 48885, + "ĠParkin": 48886, + "Ġblowup": 48887, + "ĠDWI": 48888, + "synthesized": 48889, + "Ġdisproportionately": 48890, + "Ġcardiorespiratory": 48891, + "ĠXanthomonas": 48892, + "Ġpuncta": 48893, + "bddc": 48894, + "ĠPACS": 48895, + "aseg": 48896, + "Ġincurs": 48897, + "osta": 48898, + "ĠJL": 48899, + "ĠWeierstrass": 48900, + "oleucine": 48901, + "Ġfinals": 48902, + "Ġcausation": 48903, + "ĠDirective": 48904, + "ĠPorto": 48905, + "ĠFlores": 48906, + "arbonyl": 48907, + "----------------------------------------------------------------------------": 48908, + "historic": 48909, + "Kähler": 48910, + "onna": 48911, + "Ġcel": 48912, + "ĠTBA": 48913, + "ĠOphthal": 48914, + "Ġsubthreshold": 48915, + "Ġlips": 48916, + "ĠSubstrates": 48917, + "Ġpeninsula": 48918, + "Ġadsor": 48919, + "Ġdryness": 48920, + "masses": 48921, + "ème": 48922, + "strok": 48923, + "ĠExpanded": 48924, + "Ġgc": 48925, + "ĠGolf": 48926, + "Ġcritique": 48927, + "ĠÍ©": 48928, + "Ġlensed": 48929, + "ĠKingma": 48930, + "ĠGoldman": 48931, + "ĠFacile": 48932, + "Carl": 48933, + "Ġchondrites": 48934, + "ĠCohomology": 48935, + "ĠSocioeconomic": 48936, + "ĠDominican": 48937, + "ĠAzerbaijan": 48938, + "ĠAne": 48939, + "ĠMidd": 48940, + "ĠNed": 48941, + "Ġemulate": 48942, + "ĠShakes": 48943, + "Ġliked": 48944, + "Ġbuildup": 48945, + "Ġexcessively": 48946, + "ĠŶ": 48947, + "ĠAdapted": 48948, + "Ġauthenticated": 48949, + "Ġlocomotive": 48950, + "Ġsubmill": 48951, + "Ġinterpreter": 48952, + "ĠVibrational": 48953, + "Rα": 48954, + "laden": 48955, + "pkl": 48956, + "rw": 48957, + "yet": 48958, + "enzymes": 48959, + "Ġwav": 48960, + "ĠNMT": 48961, + "athion": 48962, + "Ġbiotechnological": 48963, + "arcs": 48964, + "Ġactuated": 48965, + "Ġherring": 48966, + "ECG": 48967, + "OCI": 48968, + "Activated": 48969, + "Ġparaph": 48970, + "Observation": 48971, + "ĠEkman": 48972, + "ancellor": 48973, + "velihood": 48974, + "Gauss": 48975, + "HAL": 48976, + "rdev": 48977, + "tbl": 48978, + "icits": 48979, + "ĠRoux": 48980, + "opram": 48981, + "Ġseropositive": 48982, + "ĠPhysically": 48983, + "ĠEdu": 48984, + "Ġdocumenting": 48985, + "Ġо": 48986, + "ĠSmaller": 48987, + "chery": 48988, + "Ġlanthanide": 48989, + "Today": 48990, + "ÑĨ": 48991, + "Ġotitis": 48992, + "ĠCores": 48993, + "ifolium": 48994, + "ĠZel": 48995, + "Ġtimings": 48996, + "coarse": 48997, + "repair": 48998, + "ĠLDPC": 48999, + "Ġbowl": 49000, + "ĠEpidermal": 49001, + "Ġâľ²": 49002, + "Ġsynonyms": 49003, + "ĠENT": 49004, + "Ġbilliard": 49005, + "Ġejac": 49006, + "ĠBAA": 49007, + "Ġscientif": 49008, + "Ġγγ": 49009, + "Ġcapsular": 49010, + "Ġazithromycin": 49011, + "Ġcredentials": 49012, + "Ġḳ": 49013, + "ĠGlioblastoma": 49014, + "Ġuncoated": 49015, + "Ġhibern": 49016, + "ĠTos": 49017, + "ĠBaro": 49018, + "ĠKass": 49019, + "yland": 49020, + "ĠXM": 49021, + "Ġaggra": 49022, + "Ġneutralize": 49023, + "licted": 49024, + "Ġsoundtrack": 49025, + "ĠKnud": 49026, + "ensorship": 49027, + "empfer": 49028, + "ĠHaldane": 49029, + "ĠRocks": 49030, + "ĠGou": 49031, + "ĠOpi": 49032, + "ibacterium": 49033, + "eptives": 49034, + "usta": 49035, + "pars": 49036, + "ukawa": 49037, + "bein": 49038, + "elius": 49039, + "averaging": 49040, + "ĠMWCNT": 49041, + "Ġshielded": 49042, + "Ġquaternionic": 49043, + "BatchNorm": 49044, + "Ġdella": 49045, + "ĠTp": 49046, + "Ġbyproduct": 49047, + "ĠGow": 49048, + "ĠJO": 49049, + "Ġparameterize": 49050, + "gler": 49051, + "Ġfacult": 49052, + "Ġ͵": 49053, + "Ġnomination": 49054, + "Ġbaths": 49055, + "Ġinstallations": 49056, + "ĠJustin": 49057, + "Ġchampionships": 49058, + "tap": 49059, + "ĠSanc": 49060, + "ĠSusp": 49061, + "Ġsubleading": 49062, + "Ġdefended": 49063, + "Ġbutyl": 49064, + "remote": 49065, + "Ġcarbides": 49066, + "ĠPredicts": 49067, + "ĠPriority": 49068, + "ĠAntibiotics": 49069, + "ĠPUFAs": 49070, + "ĠMICs": 49071, + "ĠMaximization": 49072, + "bare": 49073, + "ĠPCN": 49074, + "Ġinfested": 49075, + "Ġsolenoid": 49076, + "Ġagronomic": 49077, + "ANGE": 49078, + "Rev": 49079, + "ĠNKG": 49080, + "Ġsaponins": 49081, + "Recommend": 49082, + "Ġsharpen": 49083, + "othioyl": 49084, + "suspended": 49085, + "atron": 49086, + "usage": 49087, + "ilter": 49088, + "ĠNER": 49089, + "CRIPT": 49090, + "infections": 49091, + "Ġheterosexual": 49092, + "Ġmesoc": 49093, + "ĠBobby": 49094, + "allocate": 49095, + "ĠPayne": 49096, + "ĠSultan": 49097, + "è¡": 49098, + "racles": 49099, + "ribe": 49100, + "ĠDoub": 49101, + "ĠPAF": 49102, + "communication": 49103, + "Ġnineteenth": 49104, + "Ġpoplar": 49105, + "pgfstrok": 49106, + "pgfstrokecolor": 49107, + "SLE": 49108, + "ecia": 49109, + "Ġdetach": 49110, + "Ġcharity": 49111, + "Ġmonochrom": 49112, + "Ġprescribe": 49113, + "Ġsupermassive": 49114, + "Ġguards": 49115, + "Ġcycloaddition": 49116, + "Cohen": 49117, + "phosphatidyl": 49118, + "created": 49119, + "ĠElectrodynamics": 49120, + "Ġplasmons": 49121, + "Ñģк": 49122, + "ĠDaphnia": 49123, + "easy": 49124, + "Ġaq": 49125, + "Ġfimb": 49126, + "Ġwrest": 49127, + "ĠTend": 49128, + "hipp": 49129, + "Ġorganisational": 49130, + "MAE": 49131, + "OPY": 49132, + "Ġpotentiated": 49133, + "Ġbrute": 49134, + "omassie": 49135, + "aunay": 49136, + "luster": 49137, + "Ġophi": 49138, + "unge": 49139, + "ĠPom": 49140, + "Ġplague": 49141, + "berries": 49142, + "Ġinviscid": 49143, + "ĠQE": 49144, + "incident": 49145, + "ximide": 49146, + "Ġestrogens": 49147, + "ĠTransparent": 49148, + "vereign": 49149, + "ĠPreferred": 49150, + "Ġelastase": 49151, + "Civ": 49152, + "JF": 49153, + "Ku": 49154, + "caster": 49155, + "Ġspends": 49156, + "Ġabstracted": 49157, + "otechnical": 49158, + "Ġbreeders": 49159, + "Ġsyringae": 49160, + "Ġclotting": 49161, + "African": 49162, + "PEC": 49163, + "usep": 49164, + "Ġstark": 49165, + "solete": 49166, + "ofovir": 49167, + "Ġsensations": 49168, + "azawa": 49169, + "Ġbiomechanics": 49170, + "Ġemergencies": 49171, + "Ġspectrometers": 49172, + "Ġhemispheric": 49173, + "Ġdiscriminatory": 49174, + "ĠInspection": 49175, + "ndim": 49176, + "REP": 49177, + "ĠWess": 49178, + "Ġhyperalgesia": 49179, + "IRC": 49180, + "Ġauthorship": 49181, + "CPA": 49182, + "Ġrotationally": 49183, + "ĠPyth": 49184, + "ĠYamaguchi": 49185, + "Fields": 49186, + "Ġenrolment": 49187, + "ĠRethinking": 49188, + "Gate": 49189, + "ìĺ": 49190, + "Ġcements": 49191, + "ĠTTS": 49192, + "ĠFink": 49193, + "adus": 49194, + "ĠLl": 49195, + "Ġimplicate": 49196, + "annihilation": 49197, + "Ġfeeders": 49198, + "ĠPDX": 49199, + "ĠFrançois": 49200, + "Spearman": 49201, + "ĠBenchmarking": 49202, + "Forest": 49203, + "evidence": 49204, + "enoyl": 49205, + "olactone": 49206, + "cephaly": 49207, + "ĠPEA": 49208, + "ĠNSE": 49209, + "Ġnotified": 49210, + "Ġpolyelectrolyte": 49211, + "ĠMalik": 49212, + "anthine": 49213, + "tetrad": 49214, + "Ġflagella": 49215, + "ãĥ¼": 49216, + "orpion": 49217, + "Ġbuyers": 49218, + "Ġoligodendrocyte": 49219, + "ĠNMDAR": 49220, + "ĠHarvesting": 49221, + "Ġkarst": 49222, + "IBD": 49223, + "ĠFolk": 49224, + "Ġsubcarrier": 49225, + "Ġnotices": 49226, + "ĠYous": 49227, + "awak": 49228, + "Ġadversaries": 49229, + "Looking": 49230, + "Ġthymocytes": 49231, + "Ġmeningioma": 49232, + "Ġilluminate": 49233, + "ĠSPDX": 49234, + "Ġimpinging": 49235, + "associative": 49236, + "Ġtiger": 49237, + "leon": 49238, + "Ġstature": 49239, + "ĠHood": 49240, + "ĠRutherford": 49241, + "ĠEIT": 49242, + "Ġinfantile": 49243, + "ĠQubit": 49244, + "Ġpacks": 49245, + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ": 49246, + "azolam": 49247, + "า": 49248, + "ĠTunisia": 49249, + "dilution": 49250, + "Ġsympatric": 49251, + "Ġliquefaction": 49252, + "porphyrin": 49253, + "Gn": 49254, + "Rib": 49255, + "isothermal": 49256, + "apo": 49257, + "Ġregressors": 49258, + "mani": 49259, + "ĠILs": 49260, + "oxidants": 49261, + "Atom": 49262, + "ligo": 49263, + "ĠSRAM": 49264, + "alcone": 49265, + "csr": 49266, + "Ġcautious": 49267, + "Ġhandlers": 49268, + "Ġgastritis": 49269, + "ĠSupervision": 49270, + "Ġevaporative": 49271, + "RUN": 49272, + "ĠICG": 49273, + "ĠPrague": 49274, + "ĠMLC": 49275, + "ĠMoney": 49276, + "ĠRm": 49277, + "ĠECS": 49278, + "velt": 49279, + "ĠVg": 49280, + "Ġbiography": 49281, + "Ġministry": 49282, + "convolution": 49283, + "ogenomics": 49284, + "rounding": 49285, + "ĠPhag": 49286, + "Ġaudiences": 49287, + "ĠHCWs": 49288, + "Ġblastocysts": 49289, + "Ġdiagonals": 49290, + "Ġprecautions": 49291, + "Íĵ": 49292, + "ecewise": 49293, + "ĠToxin": 49294, + "ĠHapp": 49295, + "ĠâĢĭ": 49296, + "Ġpopulate": 49297, + "mmol": 49298, + "ĠPRS": 49299, + "Ġreinforces": 49300, + "ISTIC": 49301, + "ozoites": 49302, + "arrival": 49303, + "Ġpavement": 49304, + "REGISTER": 49305, + "ĠGases": 49306, + "ĠExhib": 49307, + "Ġfactorizations": 49308, + "Ġmyopia": 49309, + "Ġmovable": 49310, + "ĠLIMIT": 49311, + "Ġsoleus": 49312, + "DOUBLE": 49313, + "ĠInputs": 49314, + "footnotes": 49315, + "BITS": 49316, + "ĠCyprus": 49317, + "reports": 49318, + "ĠPAA": 49319, + "istal": 49320, + "Ġgroupoids": 49321, + "orphin": 49322, + "ĠCoordinates": 49323, + "boro": 49324, + "ĠOslo": 49325, + "whenever": 49326, + "Ġplausibility": 49327, + "ĠFoxO": 49328, + "ĠIntrusion": 49329, + "Ġsimplices": 49330, + "ĠFaso": 49331, + "Ġpicosecond": 49332, + "ĠAnsatz": 49333, + "Importantly": 49334, + "ĠHutchinson": 49335, + "ovani": 49336, + "ĠAsymptotics": 49337, + "Ġappra": 49338, + "ĠExogenous": 49339, + "Ġcaptions": 49340, + "ĠAcanth": 49341, + "Ġillicit": 49342, + "ĠBladder": 49343, + "Ġboom": 49344, + "ĠSalinity": 49345, + "Ġmuscul": 49346, + "eptidyl": 49347, + "Ġavalanches": 49348, + "Helper": 49349, + "Ġbivalve": 49350, + "Ġreimbursement": 49351, + "zzo": 49352, + "romatosis": 49353, + "Ġparacetamol": 49354, + "vio": 49355, + "Ġvalpro": 49356, + "clamation": 49357, + "Ġuu": 49358, + "ĠSoC": 49359, + "ĠGiac": 49360, + "Ġlycopene": 49361, + "Flags": 49362, + "Ġsticking": 49363, + "Ġsqueeze": 49364, + "synthetic": 49365, + "osahexaenoic": 49366, + "mobile": 49367, + "vect": 49368, + "ĠBaryon": 49369, + "Ġnef": 49370, + "Ġflatter": 49371, + "SSI": 49372, + "Ġschw": 49373, + "ancreas": 49374, + "Buf": 49375, + "Solid": 49376, + "ĠRIPA": 49377, + "Square": 49378, + "Ġtranscendental": 49379, + "Ġcyn": 49380, + "Ġmf": 49381, + "ĠSew": 49382, + "ĠPIT": 49383, + "ocs": 49384, + "ĠBash": 49385, + "Ġsurprised": 49386, + "Ġautonomously": 49387, + "Ġlocalizing": 49388, + "Ġvisitor": 49389, + "Ġpersisting": 49390, + "Ġlandfill": 49391, + "datetime": 49392, + "Ġfiref": 49393, + "ĠEngineered": 49394, + "ĠSnyder": 49395, + "ochromes": 49396, + "fractionated": 49397, + "GPI": 49398, + "Ġwoven": 49399, + "ĠTMR": 49400, + "Ġforgotten": 49401, + "ĠMult": 49402, + "ĠBipolar": 49403, + "ĠHisp": 49404, + "opeptides": 49405, + "apamil": 49406, + "Ġrouted": 49407, + "Ġagn": 49408, + "Ġdaylight": 49409, + "ĠÍĶ": 49410, + "BBB": 49411, + "ĠMajority": 49412, + "Ġconfounded": 49413, + "ĠCaroline": 49414, + "ĠShimura": 49415, + "ruction": 49416, + "Ġtympan": 49417, + "acio": 49418, + "ĠTFE": 49419, + "ĠTutorial": 49420, + "Ġallyl": 49421, + "ĠFrost": 49422, + "ĠRPS": 49423, + "ĠWah": 49424, + "Ġiy": 49425, + "Ġsubproblems": 49426, + "Ġairfoil": 49427, + "Ġembeds": 49428, + "ĠMorning": 49429, + "Ġminorities": 49430, + "ĠMembership": 49431, + "Ġquadriceps": 49432, + "yly": 49433, + "ĠBodies": 49434, + "ĠRAND": 49435, + "Ġrationally": 49436, + "ĠManifold": 49437, + "phosphine": 49438, + "considering": 49439, + "ezvous": 49440, + "ĠKnowing": 49441, + "Ġtumorigenic": 49442, + "Ġilluminating": 49443, + "ĠFernandes": 49444, + "polynomials": 49445, + "ĠBulgarian": 49446, + "ĠBhattacharya": 49447, + "ospira": 49448, + "Api": 49449, + "henne": 49450, + "Ġmays": 49451, + "ĠArgin": 49452, + "interpol": 49453, + "ĠAndean": 49454, + "ĠPDS": 49455, + "ĠCNP": 49456, + "Ġtransfusions": 49457, + "ĠNanom": 49458, + "Ġsynergism": 49459, + "Ġbentonite": 49460, + "Ġgravitons": 49461, + "aquette": 49462, + "Ġfissure": 49463, + "tandem": 49464, + "wash": 49465, + "ĠEyes": 49466, + "Ġdilepton": 49467, + "Ġrectified": 49468, + "ĠArist": 49469, + "iscible": 49470, + "Ġirq": 49471, + "Ġligaments": 49472, + "security": 49473, + "Ġvascularization": 49474, + "NaCl": 49475, + "ĠStraight": 49476, + "ĠLeptin": 49477, + "ĠAbundances": 49478, + "ĠKEY": 49479, + "ĠMothers": 49480, + "ĠRenewable": 49481, + "Ġmasonry": 49482, + "ëı": 49483, + "raceutical": 49484, + "Ġarity": 49485, + "ĠAlves": 49486, + "ospectral": 49487, + "Ġimmunod": 49488, + "Ġmarble": 49489, + "Ġcoverings": 49490, + "ĠConstants": 49491, + "ĠReversal": 49492, + "Works": 49493, + "ĠNurse": 49494, + "ĠAggregate": 49495, + "acillin": 49496, + "plug": 49497, + "Ġjury": 49498, + "oneogenesis": 49499, + "Ġamoeb": 49500, + "aukee": 49501, + "Ġphosphoric": 49502, + "ĠRemoving": 49503, + "Ġworsen": 49504, + "ĠESRD": 49505, + "ĠHernandez": 49506, + "ĠEugene": 49507, + "viscosity": 49508, + "FID": 49509, + "¦": 49510, + "ĠÝ": 49511, + "ĠStig": 49512, + "ĠSING": 49513, + "ĠIMRT": 49514, + "ĠBq": 49515, + "ĠDME": 49516, + "ĠHOM": 49517, + "physis": 49518, + "obes": 49519, + "Ġsuperfields": 49520, + "Ġargc": 49521, + "Ġmaladaptive": 49522, + "ĠEditing": 49523, + "Ġcondem": 49524, + "ubei": 49525, + "stimulus": 49526, + "Ġabbreviation": 49527, + "Haus": 49528, + "ĠNeeds": 49529, + "Ġadhering": 49530, + "ĠVPA": 49531, + "ofrontal": 49532, + "ĠŪ": 49533, + "ĠVerde": 49534, + "ĠSlav": 49535, + "ĠPropag": 49536, + "Ġcongeners": 49537, + "Ġtilapia": 49538, + "ĠRachel": 49539, + "Less": 49540, + "Ġmasc": 49541, + "entangled": 49542, + "ĠDTI": 49543, + "atik": 49544, + "rolases": 49545, + "ĠYen": 49546, + "armor": 49547, + "ĠDecisions": 49548, + "Ġηp": 49549, + "Intuitively": 49550, + "ĠPharmaceuticals": 49551, + "Ju": 49552, + "uddin": 49553, + "ĠWASP": 49554, + "nton": 49555, + "Ġbiot": 49556, + "ĠZNF": 49557, + "Ġcrush": 49558, + "ĠParity": 49559, + "SIST": 49560, + "EVENT": 49561, + "ĠSquamous": 49562, + "Student": 49563, + "Ġgingivalis": 49564, + "fused": 49565, + "ĠMises": 49566, + "ĠFDTD": 49567, + "oreceptors": 49568, + "Ġdiscretion": 49569, + "ORTC": 49570, + "MSP": 49571, + "Ġexposes": 49572, + "Ġchlorinated": 49573, + "ĠUpregulation": 49574, + "ĠLimb": 49575, + "Ġanticor": 49576, + "Regular": 49577, + "Advanced": 49578, + "Xe": 49579, + "aghan": 49580, + "ĠBLA": 49581, + "Ġcoasts": 49582, + "ĠThirteen": 49583, + "hesin": 49584, + "ĠMethanol": 49585, + "rotus": 49586, + "ĠStephens": 49587, + "Book": 49588, + "ĠHistorically": 49589, + "ĠEmploying": 49590, + "Ġcorrugated": 49591, + "mercaptoethanol": 49592, + "ĠDnmt": 49593, + "ĠQueries": 49594, + "DRB": 49595, + "ĠGRU": 49596, + "FLU": 49597, + "ĠCarboniferous": 49598, + "OBJECT": 49599, + "ĠDiscriminative": 49600, + "ĠBurgess": 49601, + "Ġplanetesimals": 49602, + "ĠAmendment": 49603, + "ĠStrikingly": 49604, + "tric": 49605, + "ecure": 49606, + "Ġtransposable": 49607, + "rolateral": 49608, + "Ġhisti": 49609, + "egaard": 49610, + "Ġskim": 49611, + "ĠSPF": 49612, + "Statistics": 49613, + "Ġintestines": 49614, + "feng": 49615, + "lain": 49616, + "Ġtheat": 49617, + "Ġorogen": 49618, + "Ġpill": 49619, + "odopa": 49620, + "Ġcorrelative": 49621, + "ACO": 49622, + "Ġadjunction": 49623, + "ĠCarey": 49624, + "Ġteleportation": 49625, + "ĠBoundaries": 49626, + "ĠGoodfellow": 49627, + "ĠLinda": 49628, + "ĠPolymeric": 49629, + "Ġexertion": 49630, + "Ġsteeply": 49631, + "Ġprotrusion": 49632, + "Ġhyaluronic": 49633, + "ĠRochester": 49634, + "ENSIONAL": 49635, + "Dar": 49636, + "fet": 49637, + "ĠFSS": 49638, + "hemically": 49639, + "Ġflashes": 49640, + "Ġdeviated": 49641, + "feldt": 49642, + "Ġsticks": 49643, + "Ġoctet": 49644, + "Ġgravitationally": 49645, + "footnotesize": 49646, + "Lex": 49647, + "ovi": 49648, + "Ġwired": 49649, + "ĠSMP": 49650, + "ermans": 49651, + "Ġunbroken": 49652, + "Ġemulation": 49653, + "simulated": 49654, + "Ġminimality": 49655, + "ardiac": 49656, + "Ġshipw": 49657, + "Genetic": 49658, + "ĠHermann": 49659, + "ynchronization": 49660, + "ĠNapole": 49661, + "Ġmonodisperse": 49662, + "Rho": 49663, + "rists": 49664, + "Ġfx": 49665, + "ĠFUV": 49666, + "ĠGelfand": 49667, + "hemispheric": 49668, + "ronidazole": 49669, + "Ġsupersaturation": 49670, + "oudh": 49671, + "olitical": 49672, + "ĠAiry": 49673, + "Ġmanifestly": 49674, + "ĠHMG": 49675, + "Ġadvertisement": 49676, + "ĠBrooklyn": 49677, + "Ġparalleled": 49678, + "answered": 49679, + "ĠNotImplementedError": 49680, + "UPD": 49681, + "oust": 49682, + "ĠTeng": 49683, + "Ġfortified": 49684, + "ĠSort": 49685, + "ENE": 49686, + "ĠFris": 49687, + "ĠHIS": 49688, + "ĠROT": 49689, + "ĠNested": 49690, + "produce": 49691, + "ĠKerala": 49692, + "genomic": 49693, + "ĠIsab": 49694, + "Ġuracil": 49695, + "burger": 49696, + "ĠLogarithmic": 49697, + "Ġsterility": 49698, + "Ġunemployed": 49699, + "Ġoriental": 49700, + "Ko": 49701, + "jima": 49702, + "ĠCTP": 49703, + "ĠLAD": 49704, + "Ġconformers": 49705, + "CGG": 49706, + "Perkin": 49707, + "Ġbridged": 49708, + "ĠDissociation": 49709, + "ĠQiagen": 49710, + "Ġwealthy": 49711, + "Ġanaesthetic": 49712, + "ĠMinimizing": 49713, + "Ġacoustics": 49714, + "bucket": 49715, + "ĠSertoli": 49716, + "rath": 49717, + "saw": 49718, + "Ġgarn": 49719, + "ĠTheoretically": 49720, + "ticul": 49721, + "ĠThinking": 49722, + "iker": 49723, + "ĠChit": 49724, + "Ġtrin": 49725, + "ALITY": 49726, + "ĠFeO": 49727, + "Ġpolymerized": 49728, + "Encoding": 49729, + "Ġanalgesics": 49730, + "ĠLexical": 49731, + "Ġmarijuana": 49732, + "âĸĪâĸĪ": 49733, + "crops": 49734, + "entropic": 49735, + "olocation": 49736, + "ĠPomp": 49737, + "Ġcofactors": 49738, + "boxtimes": 49739, + "ĠArri": 49740, + "Angel": 49741, + "ĠRequirement": 49742, + "Ġmicrolensing": 49743, + "ĠTRANSF": 49744, + "åº": 49745, + "Ġdma": 49746, + "Ġrerio": 49747, + "undancy": 49748, + "antel": 49749, + "Ġradiometric": 49750, + "ĠSean": 49751, + "randn": 49752, + "ĠCRL": 49753, + "halos": 49754, + "ubertal": 49755, + "Ġquinone": 49756, + "TES": 49757, + "ĠdW": 49758, + "ĠCGM": 49759, + "Ġhealed": 49760, + "iane": 49761, + "Ġobtainable": 49762, + "ĠAdrian": 49763, + "Ġlikes": 49764, + "ĠMedication": 49765, + "Ġcognitively": 49766, + "Whether": 49767, + "Bob": 49768, + "did": 49769, + "alcohol": 49770, + "Ġnivolumab": 49771, + "ĠFY": 49772, + "Ġatresia": 49773, + "achs": 49774, + "ĠKip": 49775, + "Ġunigenes": 49776, + "ĠJaccard": 49777, + "ustri": 49778, + "Ġconfine": 49779, + "Ġautofluorescence": 49780, + "Ġpyg": 49781, + "Sea": 49782, + "Settings": 49783, + "Ġtruncatula": 49784, + "Literal": 49785, + "Fab": 49786, + "Mah": 49787, + "Ven": 49788, + "Ġtig": 49789, + "Ġcher": 49790, + "ĠCCI": 49791, + "ĠFunk": 49792, + "ĠBess": 49793, + "ĠNasal": 49794, + "iffer": 49795, + "Ġobsessive": 49796, + "ĠOpening": 49797, + "ochondral": 49798, + "ĠTRPA": 49799, + "ĠRabin": 49800, + "Ġtaper": 49801, + "Ġdeafness": 49802, + "DOS": 49803, + "isites": 49804, + "anite": 49805, + "leost": 49806, + "ĠSTP": 49807, + "ĠBACE": 49808, + "ĠHenn": 49809, + "ĠESM": 49810, + "Ġsuperfield": 49811, + "ĠOrland": 49812, + "ĠAMPs": 49813, + "ĠHemorrh": 49814, + "Ġrescues": 49815, + "Ġtourists": 49816, + "ĠVLBI": 49817, + "Ġneighbourhoods": 49818, + "communicable": 49819, + "gx": 49820, + "ratase": 49821, + "ĠNRT": 49822, + "Ġobstructions": 49823, + "Ġdeforestation": 49824, + "Ġqp": 49825, + "ĠPhan": 49826, + "ĠSTI": 49827, + "imento": 49828, + "ĠIRI": 49829, + "SVs": 49830, + "Ġstriped": 49831, + "Poinc": 49832, + "ĠBedford": 49833, + "ĠFragment": 49834, + "ĠReligion": 49835, + "Ġdrones": 49836, + "imulation": 49837, + "ĠCet": 49838, + "Ġgills": 49839, + "ĠNorton": 49840, + "ibatch": 49841, + "estructive": 49842, + "ĠJav": 49843, + "ĠϽ": 49844, + "Ġmica": 49845, + "AGB": 49846, + "RAW": 49847, + "ĠMyD": 49848, + "ctl": 49849, + "Ġreversibly": 49850, + "Ġsuppressors": 49851, + "ĠFAIL": 49852, + "ĠFukushima": 49853, + "Evidence": 49854, + "pink": 49855, + "asarray": 49856, + "ĠTann": 49857, + "Ġloved": 49858, + "Ġbiologists": 49859, + "Ġendothermic": 49860, + "Ġbroker": 49861, + "ĠPerkins": 49862, + "Ġcategorised": 49863, + "ĠSOME": 49864, + "hydroxyvitamin": 49865, + "rogates": 49866, + "ĠAgeing": 49867, + "Ġtournaments": 49868, + "ĠStromal": 49869, + "Ġdeferred": 49870, + "ĠSREBP": 49871, + "MAD": 49872, + "Say": 49873, + "cgi": 49874, + "phe": 49875, + "olini": 49876, + "ĠDü": 49877, + "Ġdehydro": 49878, + "apeptide": 49879, + "Ġhes": 49880, + "Ġdistally": 49881, + "versions": 49882, + "Ġmedals": 49883, + "Ġflaw": 49884, + "Ġduo": 49885, + "Ġimpairing": 49886, + "toplasts": 49887, + "ĠHFILL": 49888, + "Ġesculent": 49889, + "Classification": 49890, + "ĠGriffith": 49891, + "ĠWellington": 49892, + "Ġattorney": 49893, + "Ast": 49894, + "kA": 49895, + "Ġmilit": 49896, + "Ġnite": 49897, + "ĠCasp": 49898, + "ĠChester": 49899, + "ĠMok": 49900, + "ĠRAR": 49901, + "Ġchr": 49902, + "unctor": 49903, + "Ġabduction": 49904, + "Ġuniv": 49905, + "ovars": 49906, + "ouk": 49907, + "ERICAL": 49908, + "éri": 49909, + "orbance": 49910, + "ĠIdentifies": 49911, + "amento": 49912, + "Ġparenthesis": 49913, + "ĠMEFs": 49914, + "Ġabsorbs": 49915, + "ĠArrayList": 49916, + "Ġcaregiving": 49917, + "ĠFILE": 49918, + "Ġfeldspar": 49919, + "ochthonous": 49920, + "Sort": 49921, + "jal": 49922, + "Ġtantal": 49923, + "arabine": 49924, + "ĠSaid": 49925, + "ĠBCE": 49926, + "ĠNGO": 49927, + "ynure": 49928, + "doteq": 49929, + "ĠLeyd": 49930, + "modality": 49931, + "ĠGeometrical": 49932, + "Almost": 49933, + "Ġhardened": 49934, + "noea": 49935, + "news": 49936, + "Ġcleanup": 49937, + "ĠArmed": 49938, + "ĠSnake": 49939, + "multiply": 49940, + "ĠMillennium": 49941, + "ĠSmoothing": 49942, + "positely": 49943, + "enary": 49944, + "isse": 49945, + "ĠYuc": 49946, + "Ġgeneal": 49947, + "Ġsupers": 49948, + "Ġhandheld": 49949, + "Ġembark": 49950, + "ĠBla": 49951, + "horst": 49952, + "ĠPDGFR": 49953, + "Ġcitr": 49954, + "Ġcalorie": 49955, + "ĠBuddhist": 49956, + "Member": 49957, + "Ġfears": 49958, + "Ġfiscal": 49959, + "ĠAIF": 49960, + "LOAD": 49961, + "peare": 49962, + "Ġbitumen": 49963, + "Particip": 49964, + "ĠIndianapolis": 49965, + "ĠAlbum": 49966, + "Ġscrutiny": 49967, + "acylglycer": 49968, + "ĠSakai": 49969, + "Ġthermodynamical": 49970, + "ZB": 49971, + "Ġhpf": 49972, + "ĠLIP": 49973, + "Ġexpiration": 49974, + "tilt": 49975, + "Ġflax": 49976, + "ĠSelectivity": 49977, + "ĠSchol": 49978, + "anya": 49979, + "orbents": 49980, + "Ġincubations": 49981, + "Ġmarginals": 49982, + "involved": 49983, + "Ġenthalpies": 49984, + "macrophages": 49985, + "constructor": 49986, + "ĠRoland": 49987, + "ĠPm": 49988, + "ĠRY": 49989, + "Ġblobs": 49990, + "Ġannuli": 49991, + "Ġunstimulated": 49992, + "ĠPetroleum": 49993, + "Ġmerges": 49994, + "Ġenveloping": 49995, + "ĠInitialization": 49996, + "Ġsheds": 49997, + "Ġadvisable": 49998, + "ylethanolamine": 49999 + }, + "merges": [ + "Ġ t", + "i n", + "Ġ a", + "h e", + "o n", + "r e", + "a t", + "Ġt he", + "e r", + "Ġ s", + "Ġ o", + "e n", + "a l", + "Ġ c", + "t i", + "o r", + "e d", + "e s", + "i s", + "Ġ p", + "Ġo f", + "n d", + "Ġ in", + "Ġ f", + "Ġ w", + "Ġ Ġ", + "i t", + "a n", + "r o", + "a r", + "Ġ d", + "Ġ m", + "Ġ b", + "Ġa nd", + "i c", + "l e", + "in g", + "i on", + "a s", + "Ġ e", + "Ġ re", + "at ion", + "Ġt o", + "e l", + "en t", + "a c", + "e t", + "e c", + "ti on", + "o m", + "s t", + "Ġ T", + "Ġ n", + "Ġt h", + "o l", + "u l", + "i m", + "R E", + "i g", + "u s", + "RE F", + "Ġ l", + "Ġ h", + "u r", + "Ġ is", + "ĠĠ ĠĠ", + "Ġf or", + "i d", + "a m", + "Ġ S", + "v e", + "i l", + "Ġ A", + "Ġ C", + "Ġ g", + "o t", + "it h", + "l y", + "c e", + "Ġc on", + "o w", + "Ġs t", + "u t", + "o s", + "Ġw ith", + "o d", + "r a", + "Ġ v", + "Ġp ro", + "u m", + "Ġ I", + "i f", + "u c", + "t er", + "u n", + "A R", + "S T", + "re s", + "Ġ on", + "E N", + "e re", + "Ġ P", + "ĠT he", + "Ġ M", + "Ġa s", + "AR T", + "Ġa n", + "EN D", + "ST ART", + "Ġth at", + "q u", + "e m", + "Ġb e", + "Ġe x", + "r i", + "a b", + "it y", + "ti c", + "v er", + "Ġa l", + "p l", + "t s", + "Ġ F", + "Ġ â", + "u re", + "Ġb y", + "at e", + "a g", + "i r", + "o c", + "p er", + "Ġ B", + "a y", + "Ġ D", + "Ġc om", + "Ġ H", + "at ed", + "Ġ R", + "Ġa re", + "ro m", + "Ġ E", + "o p", + "a d", + "s e", + "Ġ L", + "ig h", + "Ġ N", + "m ent", + "he r", + "o g", + "a in", + "ec t", + "u d", + "Ġd e", + "Ġ r", + "Ġa t", + "Ġw as", + "Ġ us", + "Ġre s", + "el l", + "i z", + "in e", + "p h", + "Ġa c", + "es s", + "o re", + "ic al", + "t h", + "u nd", + "r ac", + "Ġw e", + "at h", + "Ġ G", + "Ġf rom", + "at i", + "u p", + "is t", + "an t", + "Ġo r", + "f f", + "Ġcom p", + "Ġw h", + "Ġ W", + "c h", + "er s", + "Ġs p", + "or m", + "Ġc h", + "ation s", + "r an", + "u b", + "t e", + "d i", + "Ġs h", + "g e", + "as e", + "Ġw ere", + "ĠĠĠĠ ĠĠĠĠ", + "Ġ Î", + "a p", + "ĠI n", + "a nd", + "Ġs e", + "v el", + "Ġ im", + "Ġâ Ī", + "en s", + "i es", + "ic h", + "igh t", + "d uc", + "Ġ O", + "Ġ it", + "tion s", + "en d", + "Ġc o", + "Ġth is", + "Ġc an", + "Ġ k", + "â Ģ", + "le c", + "t ed", + "Ġm od", + "m ath", + "Ġcon t", + "Ġn e", + "Ġp ar", + "i b", + "ĠĠ Ġ", + "Ġ le", + "i v", + "u g", + "en ce", + "ig n", + "o us", + "ent s", + "y s", + "a ve", + "re d", + "res s", + "ab le", + "p or", + "al l", + "if f", + "es t", + "Ġa p", + "Ġin c", + "n t", + "ar y", + "i ti", + "Ġwh ich", + "Ġn ot", + "f orm", + "Ġs y", + "Ġa d", + "l ow", + "a k", + "Ġp er", + "Ġ he", + "p ro", + "an ce", + "i al", + "u e", + "Ġ en", + "Ġc l", + "as s", + "i p", + "ran s", + "Ġo b", + "Ġg en", + "ti m", + "Ġd is", + "un c", + "Ġin t", + "e p", + "et w", + "Ġd iff", + "ac h", + "t her", + "im e", + "ag e", + "p le", + "il l", + "y p", + "Ġ K", + "ac t", + "ar i", + "Ġm et", + "or s", + "Ġh ave", + "Ġst ud", + "on g", + "Ġ U", + "Ġp l", + "id e", + "m a", + "he n", + "if ic", + "om e", + "Ġ i", + "ul ar", + "Ġ V", + "al ly", + "Ġsh ow", + "ri b", + "i a", + "en ti", + "Ġas s", + "on d", + "f t", + "Ġa b", + "Ġin ter", + "ĠT h", + "T he", + "st r", + "Ġc ell", + "c al", + "Ġmod el", + "at a", + "as t", + "Ġe ff", + "Ġt rans", + "at es", + "as ed", + "o st", + "v i", + "an g", + "o ur", + "Ġm e", + "ar d", + "Ġdiff ere", + "Ġp re", + "Ġd i", + "ĠâĪ Ĵ", + "ol og", + "u tion", + "o und", + "ac e", + "Ġres ul", + "er m", + "p os", + "he re", + "ti ve", + "or d", + "s o", + "st em", + "y l", + "Ġp h", + "Ġ y", + "am e", + "or k", + "ati ve", + "Ġ qu", + "r ic", + "S U", + "w o", + "Ġ un", + "Ġe v", + "a re", + "# #", + "d e", + "e en", + "ti v", + "Ġg ro", + "or y", + "Ġcon s", + "Ġs ub", + "t a", + "- -", + "Ġst r", + "b er", + "er v", + "etw een", + "en c", + "Ġan al", + "in t", + "Ġh as", + "uc h", + "Ġre g", + "Ġb etween", + "Ġd et", + "Ġal l", + "c ess", + "Ġex p", + "ec tion", + "Ġâ Ģ", + "in d", + "at er", + "Ġs ign", + "p t", + "ug h", + "it e", + "il ity", + "Ġus ing", + "Ġv al", + "Ġ ro", + "re e", + "Ġre l", + "o ut", + "Ġf unc", + "i tion", + "Ġc or", + "Ġal so", + "Ġt wo", + "n e", + "Ġ J", + "Ġsy stem", + "c l", + "uc t", + "Ġs im", + "t ain", + "u st", + "i ed", + "por t", + "Ġre c", + "Ġres p", + "Ġd ata", + "r m", + "res ent", + "ul d", + "x t", + "Ġ j", + "r y", + "ac k", + "Ġ ra", + "p ar", + "Ġfor m", + "Ġs c", + "f rac", + "ĠW e", + "at ing", + "ec h", + "h od", + "Ġf ol", + "in ed", + "ĠS t", + "u al", + "Ġus ed", + "Ġon e", + "Ġd es", + "Ġ Ï", + "Ġv ari", + "Ġd ist", + "Ġn um", + "y m", + "e w", + "re c", + "o b", + "Ġin f", + "Ġa r", + "lec t", + "l l", + "on s", + "ĠTh is", + "os e", + "i le", + "pl ay", + "e ar", + "o x", + "u res", + "on e", + "Ġstud y", + "ys is", + "Ġfol low", + "y le", + "rac t", + "d is", + "Ġp os", + "r ight", + "Ġth an", + "ro s", + "a v", + "F ig", + "Ġt ime", + "iz ation", + "ul ation", + "iz ed", + "Ġs ur", + "ot h", + "Ġo ut", + "Ġc ol", + "at ure", + "i ve", + "Ġs ol", + "Ġ x", + "el d", + "Ġo ther", + "pl ic", + "Ġde f", + "er g", + "Ġgen er", + "el y", + "Ġbe en", + "Ġinc re", + "Ġthe se", + "Ġn o", + "a x", + "st yle", + "ar g", + "i an", + "Ġin d", + "Ġs uch", + "Ġfunc tion", + "t ing", + "Ġe qu", + "a us", + "Ġ und", + "math b", + "tic al", + "Ġh igh", + "ra in", + "Ġa m", + "i eld", + "o un", + "ress ion", + "Ġsp ec", + "Ġo p", + "Ġd ec", + "Ġo ver", + "Ġmet hod", + "Ġs et", + "â Ī", + "Ġ if", + "di tion", + "u es", + "ec ts", + "dis play", + "he m", + "Ġp ati", + "Ġresul ts", + "ol d", + "an c", + "display style", + "Ġe ach", + "Ġm ore", + "l es", + "p r", + "ac ter", + "Ġthe ir", + "Ġac c", + "Ġap pro", + "is s", + "iz e", + "Ġin v", + "as es", + "Ġcell s", + "ir st", + "l u", + "a il", + "Ġme as", + "Ġl ow", + "o v", + "t he", + "i k", + "* *", + "e f", + "Ġb ut", + "he s", + "f ter", + "Ġdiffere nt", + "vel y", + "Ġex t", + "Ġthe re", + "oc i", + "Ġpro b", + "Ġit s", + "r on", + "ment s", + "Ġa g", + "N A", + "Ġp o", + "ic e", + "yp e", + "Ġgro up", + "âĢ ĵ", + "e ver", + "ul t", + "is m", + "ter n", + "ab ility", + "ion s", + "ar k", + "Ġn on", + "t o", + "ĠĠĠĠ ĠĠĠ", + "Ġob s", + "Ġt re", + "al s", + "le ft", + "ĠP ro", + "Ġon ly", + "Ġm an", + "d er", + "Ġp ol", + "ur ing", + "am et", + "ro l", + "I n", + "y n", + "Ġund er", + "ĠC h", + "Ġw here", + "o od", + "Ġ X", + "n ce", + "Ġpar tic", + "ect ed", + "ĠF ig", + "Ġe m", + "Ġf act", + "ĠA n", + "Ġper form", + "Ġs o", + "Ġanal ysis", + "st ract", + "he d", + "Ġm ay", + "at ic", + "Ġre p", + "te in", + "duc ed", + "Ġ up", + "Ġint o", + "Ġnum ber", + "Ġo ur", + "Ġe t", + "e g", + "it le", + "o ver", + "i x", + "at or", + "ul ti", + "Ġinc l", + "o uld", + "ic i", + "b stract", + "Ġcomp le", + "Ġpati ents", + "Ġd o", + "Ġex per", + "v id", + "an ge", + "Ġle vel", + "Ġpro cess", + "math cal", + "p s", + "Ġsign ific", + "Ġs am", + "T itle", + "Ġb l", + "Ġstr uct", + "et a", + "Ġobs erv", + "ra ph", + "g r", + "Ġac tiv", + "Ġf irst", + "vel op", + "g en", + "ib le", + "Ġs m", + "Ġw ill", + "Ġ Q", + "Ġmeas ure", + "p ut", + "Ġl oc", + "Ġm o", + "ver s", + "o f", + "t al", + "ere d", + "ow n", + "Ġm at", + "iti es", + "ti l", + "in al", + "Ġc ar", + "ph a", + "Ġb oth", + "Ġc ur", + "SU B", + "it s", + "re l", + "Ġw hen", + "Ġ z", + "Ġch ar", + "Ġb i", + "c ent", + "Ġthe n", + "is e", + "ow ever", + "Ġm in", + "ĠF or", + "Ġ Y", + "p tion", + "Ġ es", + "m un", + "Ġincl ud", + "is tic", + "c on", + "Ġob tain", + "a red", + "duc tion", + "Ġsignific ant", + "Ġ Z", + "Ġp resent", + "an n", + "Ġ id", + "enc y", + "Ġv er", + "v al", + "y d", + "ro ugh", + "SU P", + "f ore", + "Ġs ome", + "ĠA s", + "Ġs up", + "Ġa fter", + "olog ical", + "enti f", + "Ġc ase", + "Ġs ec", + "el f", + "Ġde p", + "k s", + "Ġc al", + "v ed", + "Ġt em", + "Ġus e", + "ĠC om", + "l am", + "in es", + "ay s", + "Ġg iv", + "Ġcons id", + "Ġe lect", + "ation al", + "ĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠ", + "i qu", + "ti es", + "Ġl ine", + "Ġs u", + "A bstract", + "oun t", + "Ġde velop", + "ĠC on", + "olog y", + "al pha", + "an s", + "pr ime", + "c c", + "og en", + "Ġw ork", + "v en", + "i um", + "ec tive", + "Ġp a", + "t en", + "ĠA l", + "Ġ ï", + "Ġf e", + "âĢ Ļ", + "enti al", + "l ine", + "Ġpar amet", + "Ġpro tein", + "Ġdis c", + "f ace", + "c es", + "Ġw ell", + "ur al", + "en g", + "Ġd uring", + "ro w", + "an ts", + "Ġre m", + "form ation", + "Ġex am", + "Ġm ic", + "âĪ Ĵ", + "le m", + "erg y", + "Ġass oci", + "Ġ Ã", + "ro p", + "Ġf ield", + "t y", + "Ġcl ass", + "Ġ u", + "i e", + "Ġb ec", + "Ġexper im", + "s p", + "Ġp r", + "il ar", + "ti al", + "Ġcon st", + "ĠI t", + "Ġcont rol", + "d a", + "Ġm ulti", + "iti ve", + "ic s", + "ur n", + "Ġind ic", + "Ġf ound", + "te xt", + "Ġne w", + "Ġre f", + "g or", + "ra p", + "Ġdes c", + "Ġs ame", + "Ġfollow ing", + "Ġdist rib", + "Fig ure", + "il d", + "Ġan ti", + "etw ork", + "o ve", + "Ġth rough", + "Ġm ost", + "c er", + "Ġdet erm", + "h a", + "el ta", + "ar ge", + "Ġshow n", + "in ce", + "Ġan y", + "re n", + "d ot", + "r al", + "r ation", + "am ma", + "o id", + "Ġm ed", + "ens ion", + "ar t", + "Ġp red", + "m et", + "mathb b", + "ak e", + "Ġcal c", + "Ġh ig", + "Ġth ree", + "Ġb ased", + "m on", + "ar ch", + "-- --", + "pl es", + "ag es", + "aus e", + "is h", + "ti vely", + "qu i", + "res p", + "Ġchar acter", + "oc k", + "Ġtre at", + "Ġpro per", + "e x", + "Ġsm all", + "Ġt erm", + "b da", + "Ġk n", + "od e", + "ing s", + "Ġexp ression", + "Ġm on", + "em b", + "ut e", + "ech n", + "h ib", + "Ġdi rec", + "in ation", + "ith m", + "ul ated", + "Ġc y", + "Ġp ot", + "Ġor der", + "ot e", + "ical ly", + "Ġval ues", + "or t", + "ur ther", + "ce pt", + "yn am", + "o ugh", + "ech an", + "Ġâ ī", + "o k", + "em ent", + "ĠÎ ¼", + "Ġes tim", + "Ġeff ect", + "Ġp ath", + "Ġcon f", + "Ġap p", + "Ġgiv en", + "Ġ end", + "s et", + "Ġg l", + "Ġthe y", + "n ing", + "Ġt est", + "Ġtem per", + "v es", + "Ġval ue", + "it ed", + "al ity", + "Ġl im", + "Ġsp ect", + "ent ly", + "ti t", + "Ġse qu", + "Ġid entif", + "/ /", + "ig ma", + "Ġen ergy", + "in c", + "n ess", + "ens ity", + "Ġprob lem", + "yd ro", + "ag n", + "an e", + "re nt", + "c om", + "j ect", + "Ġim port", + "ĉ ĉ", + "Ġo per", + "ol ution", + "Ġa ut", + "ec tively", + "ĠH owever", + "h o", + "ent al", + "Ġs ing", + "e y", + "m u", + "ros s", + "ac tion", + "ep end", + "ĠE x", + "vi ous", + "Ġstud ies", + "s c", + "orm al", + "Ġh ad", + "Ġm ain", + "al th", + "gor ithm", + "Ġf l", + "om et", + "Ġ Â", + ". .", + "er r", + "Ġpos s", + "Ġdiffere n", + "Ġobserv ed", + "ra y", + "Ġpred ic", + "Ġgen e", + "Ġst ate", + "W e", + "Ġstruct ure", + "Ġre t", + "resp ond", + "re qu", + "il y", + "ĠâĪ Ī", + "Ġs er", + "Ġb ound", + "Ġrep resent", + "ph i", + "Ġtreat ment", + "h at", + "Ġre qui", + "ap p", + "um an", + "Ġhig her", + "Ġl arge", + "Ġt ra", + "w ard", + "Ġobtain ed", + "Ġco uld", + "ti g", + "ĠU n", + "Ġdesc rib", + "Ġsim ilar", + "por ted", + "in s", + "Ġad dition", + "os is", + "Ġn etwork", + "Ġe le", + "p i", + "ri x", + "Ġr ate", + "g an", + "ug g", + "us s", + "Ġm echan", + "Ġdis e", + "Ġeff ects", + "Ġmodel s", + "or ph", + "ik e", + "Ġsec ond", + "mathb f", + "Ġd ue", + "Ġ q", + "Ġp res", + "Ġt echn", + "el s", + "Ġcor respond", + "Ġassoci ated", + "pos ed", + "Ġm ass", + "ro und", + "vi ew", + "Ġin s", + "ĠâĢ ¢", + "di tions", + "Ġwh ile", + "o le", + "Ġl ong", + "al u", + "Ġc ap", + "Ġsur face", + "Ġcomple x", + "Ġc ent", + "Ġcomp ared", + "Ġf ind", + "arg et", + "at ory", + "f er", + "Ġs ize", + "Ġcont ain", + "us ion", + "u tions", + "Ġd em", + "E S", + "Ġdep end", + "at is", + "s um", + "ff ici", + "Ġb as", + "lam bda", + "i er", + "A T", + "Ġm ax", + "Ġim p", + "Ġev alu", + "Ġtemper ature", + "in k", + "ect or", + "Ġs cal", + "Ġgro w", + "ow er", + "Ġresp ectively", + "le ar", + "s h", + "ic k", + "Ġf il", + "ir c", + "il on", + "r am", + "ĠÎ ±", + "ific ation", + "Ġo cc", + "Ġy ear", + "Ġs ugg", + "Ġra di", + "if ied", + "ha vi", + "Ġwith in", + "Ġs ens", + "Ġin te", + "Ġw ould", + "Ġcon cent", + "Ġmic ro", + "Ġsing le", + "ĠS p", + "o u", + "Ġat t", + "Ġs elf", + "Ġab out", + "eng th", + "Ġe l", + "ĠR e", + "x im", + "Ġcon ditions", + "ud e", + "ĠA t", + "w here", + "m ed", + "Ġne ed", + "ir on", + "Ġp op", + "Ġresul t", + "Ġpo int", + "Ġl o", + "Ġal gorithm", + "Ġactiv ity", + "le q", + "ple ment", + "ĠR es", + "Ġsy m", + "on str", + "at ures", + "Ġim pro", + "f or", + "Ġgener al", + "it er", + "Ġex pl", + "## #", + "Ġd om", + "Ġt ri", + "m in", + "Ġdistrib ution", + "Ġt r", + "ĠThe re", + "os s", + "u ce", + "math rm", + "ul l", + "E R", + "re g", + "Ġp e", + "Ġto tal", + "Ġle ad", + "= =", + "i od", + "Ġass um", + "Ġch ang", + "Ġg ra", + "M I", + "Ġcomp ut", + "Ġcom b", + "Ġin formation", + "Ġdes ign", + "Ġin iti", + "Ġf requ", + "im ension", + "c op", + "Ġproper ties", + "Ġconsid er", + "Ġlevel s", + "en e", + "Ġt ype", + "iv ed", + "ĠH e", + "epend ent", + "Ġap plic", + "Ġinv es", + "Ġpre vious", + "a w", + "Ġsp ace", + "Ġpro vid", + "h yl", + "Ġinves tig", + "Ġappro ach", + "ater ial", + "on se", + "lec ular", + "Ġparamet ers", + "Ġph ase", + "ul ations", + "ub l", + "b eta", + "Ġa v", + "Ġf lu", + "Ġpot ential", + "ĠThe se", + "s igma", + "l o", + "tim es", + "Ġop tim", + "is ion", + "Ġa ff", + "Ġme an", + "Ġbe havi", + "Ġv ol", + "ore m", + "ag ne", + "Ġdec re", + "tion al", + "Ġsol ution", + "Ġh uman", + "g er", + "Ġpa per", + "Ġcomp ar", + "Ġlow er", + "and ard", + "Ġcor rel", + "c ri", + "Ġcur rent", + "Ġd er", + "iss ion", + "ĠFig ure", + "Ġpro duc", + "Ġw ater", + "ĠT o", + "Ġth ose", + "Ġac id", + "Ġcan cer", + "Ġloc al", + "t on", + "Ġf low", + "Ġreg ion", + "Ġhe alth", + "Ġimport ant", + "og raph", + "ab l", + "Ġse lec", + "Ġg re", + "Ġin di", + "ad e", + "r id", + "Ġsh ould", + "b ased", + "Ġab ove", + "l d", + "Ġsystem s", + "ic ation", + "Ġ ed", + "Ġt yp", + "Ġph ys", + "o per", + "Ġcomp on", + "O N", + "Ġsu per", + "g a", + "hem ical", + "is k", + "op h", + "Ġh y", + "Ġanal y", + "in u", + "Ġt arget", + "ĠA d", + "Ġp at", + "g amma", + "Ġsam ples", + "Ġs l", + "Ġpar t", + "old s", + "Ġb el", + "im um", + "ĠI m", + "Ġdise ase", + "I I", + "is ts", + "i ver", + "Ġperform ance", + "ĠĠĠĠĠĠĠĠ ĠĠĠ", + "g le", + "Ġo x", + "nd om", + "ĠĠĠĠ Ġ", + "Ġbec ause", + "ay er", + "Ġr ange", + "Ġco un", + "Ġincre ased", + "oc h", + "on al", + "Ġver y", + "Ġd ynam", + "an ti", + "Ġad d", + "Ġin hib", + "Ġmethod s", + "id ence", + "in ical", + "ere nce", + "iv al", + "u le", + "Ġfact or", + "Ġf in", + "in ts", + "v iron", + "Ġs our", + "ver age", + "e qu", + "Ġe ar", + "Ġshow ed", + "it es", + "Ġperform ed", + "Ġre se", + "ĠE n", + "Ġspec ies", + "A C", + "ĠC l", + "h ip", + "til de", + "i o", + "at ely", + "T h", + "od y", + "Ġincre ase", + "ĠP h", + "âĢ Ŀ", + "Ġshow s", + "ĠA c", + "Ġp ost", + "ord ing", + "enc es", + "o y", + "n er", + "Ġresp onse", + "Ġocc ur", + "r ho", + "Ġper iod", + "ar s", + "Ġre d", + "ĠO n", + "Ġd ensity", + "Ġexam ple", + "g et", + "Ġre al", + "ĠC ount", + "ac y", + "Ġp ower", + "Ġab s", + "it al", + "Ġpr im", + "âĢ IJ", + "Ġdef ined", + "Ġn ormal", + "a j", + "Ġin st", + "Ġal low", + "Ġposs ible", + "Ġv is", + "Ġre ported", + "Ġsign al", + "the ta", + "Ġd en", + "ab les", + "Ġde g", + "Ġindi vid", + "agne tic", + "Ġgroup s", + "a e", + "ar row", + "Ġst at", + "Ġmechan ism", + "os p", + "m er", + "ot her", + "Ġpro t", + "Ġc ases", + "Ġc r", + "Ġt e", + "Ġinte gr", + "et s", + "Ġdevelop ment", + "Ġra ndom", + "Ġinv ol", + "Ġinclud ing", + "Ġ err", + "gr am", + "Ġpartic ular", + "ep s", + "Ġst andard", + "pos ition", + "Ġcont rib", + "se qu", + "Ġman y", + "Ġf urther", + "Ġsignificant ly", + "at ors", + "ur b", + "Ġag ain", + "b ar", + "Ġwith out", + "Ġse ver", + "Ġto p", + "re t", + "l ed", + "Ġmat rix", + "Ġspec ific", + "ate g", + "Ĩ Ĵ", + "Ġdirec t", + "Ġsam ple", + "Ġthe m", + "S A", + "o int", + "Ġro le", + "Ġchang es", + "rac tion", + "Ġs um", + "Ġindivid ual", + "I N", + "Ġim mun", + "c ed", + "o h", + "Ġstr ong", + "Ġe p", + "Ġline ar", + "u ally", + "d elta", + "w ay", + "as ing", + "Ġt im", + "Ġv i", + "is on", + "Ġfunc tions", + "Ġam ong", + "Ġse e", + "ere st", + "Ġgrow th", + "Ġr ati", + "ĠS c", + "ix ed", + "R NA", + "e ed", + "ta u", + "Ġ ent", + "Ġd r", + "o res", + "Ġappro xim", + "f ul", + "Ġre le", + "Ġfact ors", + "Ġdisc uss", + "Ġph ot", + "Ġpro posed", + "er o", + "ome ga", + "Ġf our", + "as tic", + "Ġyear s", + "hes is", + "iqu e", + "Ġm aterial", + "Ġb re", + "Ġpro f", + "ĠA p", + "Ġne g", + "Ġb u", + "Ġass ess", + "ĠâĢ ľ", + "Ġv ir", + "at ter", + "Ġdescrib ed", + "istic s", + "Ġcomp os", + "a z", + "str uc", + "Ġt um", + "par tial", + "a f", + "Ġwh o", + "at al", + "Ġdem onstr", + "anc es", + "y t", + "Ġrem ain", + "Ġl ess", + "Ġpos itive", + "om ic", + "Ġs ince", + "og n", + "Ġcon dition", + ": :", + "Ġdo es", + "ti ce", + "os ph", + "Ġpro v", + "ĠC O", + "Ġr at", + "Ġterm s", + "b ox", + "Ġt ak", + "Ġpat tern", + "al e", + "Ġn an", + "ul es", + "Ġm ut", + "is hed", + "Ġrel ated", + "Ġthe ory", + "b ol", + "c dot", + "viron ment", + "a ir", + "i vers", + "ĠA r", + "Ġï £", + "ress ed", + "Ġâī ¤", + "ĠM et", + "I D", + "ul ts", + "ĠÎ ²", + "Ġd at", + "pos e", + "Ġor ig", + "Ġret urn", + "Ġch ange", + "Ġl arg", + "a u", + "ac es", + "Ġare a", + "Ġgen es", + "A S", + "Ġh ydro", + "Ġcons ist", + "m an", + "Ġrese arch", + "ĠD e", + "Ġor gan", + "as k", + "Ġb ack", + "Ġfollow s", + "un g", + "ro ll", + "Ġequ ation", + "pl ied", + "t r", + "Ġcorrespond ing", + "od es", + "es ted", + "Ġrel ations", + "n al", + "Ġf r", + "Ġlim it", + "m it", + "Ġof f", + "ut ed", + "Ġr isk", + "re ad", + "Ġkn own", + "pl it", + "tiv ity", + "Ġsequ ence", + "Ġconsid ered", + "x i", + "ĠM od", + "v ity", + "Ġn uc", + "c le", + "ic es", + "Ġl ength", + "Ġsever al", + "s ing", + "o ot", + "n ot", + "Ġst ress", + "ĠI f", + "C T", + "ro ph", + "Ġcom mun", + "Ġcl ust", + "ĠL e", + "m e", + "ant um", + "Ġm emb", + "Ġl ab", + "Ġev en", + "Ġinf lu", + "c k", + "Ġà Ĺ", + "Ġl og", + "v ing", + "es ts", + "Ġh is", + "an k", + "ĠI nd", + "ac tions", + "ft y", + "m od", + "Ġre view", + "th ough", + "Ġeff ici", + "Ġm ap", + "in fty", + "Ġbe ing", + "l and", + "Ġcl inical", + "Ġmeasure d", + "er ing", + "ĠT able", + "Ġs he", + "se e", + "Ġs ection", + "Ġav ail", + "om en", + "Ġv ers", + "Ġd el", + "it her", + "er ation", + "Ġh and", + "Ġcont inu", + "Ġcon n", + "h ors", + "ra d", + "Ġf am", + "Ġle ar", + "Ġiniti al", + "y stem", + "Ġg e", + "ĠâĢ ²", + "Ġc irc", + "Ġp ubl", + "ĠI s", + "Ġv ia", + "Ġcom mon", + "if e", + "Ġm ark", + "Ġe ver", + "ar c", + "b ig", + "er tain", + "\\ \\", + "v ar", + "A s", + "ros cop", + "Ġa ge", + "Ġh ow", + "ĠL et", + "str uct", + "Ġa verage", + "v ant", + "ĠS h", + "imension al", + "S C", + "ap e", + "n u", + "Ġl oss", + "as on", + "id es", + "Ġpop ulation", + "Ġdom ain", + "ind ing", + "w e", + "A L", + "Ġacc ur", + "et y", + "Ġc aus", + "D elta", + "rap y", + "Ġpro m", + "tim e", + "Ġint ro", + "Ġmulti ple", + "Ġconst ant", + "pl ing", + "in o", + "aj or", + "i or", + "ab ol", + "de f", + "Ġpo ints", + "ver se", + "n ame", + "ĠS e", + "it or", + "P ro", + "ar m", + "Ġt iss", + "Ġf ib", + "Ġg raph", + "Ġc all", + "atis f", + "Ġcon duc", + "de x", + "ĠN e", + "Ġp ers", + "er n", + "C R", + "ang le", + "Ġfrequ ency", + "A P", + "Ġpresent ed", + "am p", + "Ġbe fore", + "ord s", + "Ġin put", + "Ġâ ĨĴ", + "Ġpartic ip", + "O R", + "Ġch ild", + "Ġc re", + "ffici ent", + "Ġse par", + "ur ation", + "Î ±", + "Ġex ist", + "is ed", + "Ġl ight", + "im al", + "** **", + "ĠD NA", + "he l", + "Ġint erest", + "b f", + "k e", + "Ġcol lec", + "Ġt rain", + "a i", + "ĠP l", + "ĠÎ »", + "ĠC o", + "Ġim age", + "Ġh yp", + "om a", + "Ġwe ight", + "Ġc ross", + "r t", + "Ġdiffere nce", + "Ġfe atures", + "med i", + "t ype", + "Ġp ress", + "I C", + "Ġthe rm", + "Ġst ates", + "u str", + "ti ll", + "Ġh ist", + "Ġrati o", + "ag ing", + "ĠA ll", + "Ġhe l", + "b on", + "Ġbehavi or", + "Ġp ri", + "Ġsy nt", + "end ed", + "ĠIn t", + "t t", + "Ġvari ous", + "rec t", + "Ġpre c", + "Ġtim es", + "M S", + "Ġanaly z", + "Ġc are", + "m at", + "Ġal ong", + "Ġp ur", + "ati vely", + "Ġst ar", + "j ects", + "i i", + "ist ance", + "ĠThe n", + "A N", + "Ġparamet er", + "ul ate", + "Ġever y", + "Ġs atisf", + "Ġdeterm ined", + "in a", + "ran e", + "Ġpa ir", + "o ol", + "T able", + "Ġth us", + "ogen e", + "ĠÏ Ĩ", + "Ġpro gram", + "as c", + "Ġen vironment", + "M P", + "Ġre ad", + "Ġac h", + "Ġpres ence", + "Ġm ice", + "F or", + "Ġpro duction", + "Ġdifferen ces", + "Ġprov ide", + "st e", + "am es", + "ĉ Ġ", + "Ġ ±", + "ro up", + "Ġelect ron", + "Ġhy per", + "b it", + "ĠR ec", + "Ġv ector", + "ub le", + "ran gle", + "Ġw r", + "w ide", + "Ġâ Ĭ", + "rac k", + "ry st", + "Ġin j", + "eg a", + "Ġw he", + "ps ilon", + "Ġagain st", + "Ġdi agn", + "Ġh om", + "Ġach ie", + "n s", + "Ġre ce", + "---- ----", + "Ġavail able", + "in f", + "Ġs uc", + "Ġg u", + "Ġm ajor", + "ĠTh us", + "w are", + "Ġsup port", + "l or", + "Ġexperim ental", + "ĠM o", + "Ġconcent ration", + "tic s", + "Ġn ec", + "Ġp hen", + "s q", + "Ġcl os", + "s ub", + "Ġkn ow", + "Ġform ation", + "Ġd id", + "ous e", + "in ary", + "ic t", + "ĠC D", + "Th is", + "l ess", + "Ġne ar", + "Ġimpro ve", + "ab il", + "Ġre ve", + "Ġexperim ents", + "i ence", + "ul a", + "ore d", + "Ġ unc", + "_ _", + "Ġap plied", + "Ġre duced", + "Ġdet ail", + "st and", + "Ġch o", + "om y", + "Ġcalc ulated", + "Ġen h", + "L ES", + "it ro", + "Ġresp ons", + "Ġ est", + "Ġm i", + "Ġco e", + "ĠThere fore", + "ĠM ore", + "b l", + "anc ed", + "um e", + "Ġb and", + "Ġac t", + "Ġe ither", + "om es", + "ĠG en", + "v are", + "E T", + "re en", + "ĠP ar", + "ĠS im", + "Ġidentif ied", + "Ġinter action", + "Ġm ade", + "Ġsour ce", + "ti s", + "ot s", + "m ega", + "Ġs erv", + "m s", + "al ysis", + "v ent", + "en se", + "g l", + "Ġl ines", + "Ġapp ear", + "ti f", + "Ġf ree", + "om s", + "in ing", + "ere n", + "Ġch ann", + "vare psilon", + "s im", + "Ġco u", + " °", + "Ġerr or", + "Ġqu anti", + "ĠE q", + "b y", + "ĠI I", + "te x", + "ĠS ch", + "sq rt", + "oc us", + "Ġde v", + "qu ad", + "ter s", + "Ġrelations hip", + "ol l", + "Ġg o", + "Ġw ave", + "Ġle ft", + "w ays", + "h i", + "Ġr ight", + "ob al", + "Ġd own", + "u k", + "Ġcol l", + "Ġm agnetic", + "Ġpro g", + "dot s", + "Ġstr ateg", + "b s", + "unc tion", + "Ġen c", + "Ġc lear", + "Ġco st", + "ge b", + "et ter", + "MI LES", + "lam m", + "Ġm ust", + "Ġeff ective", + "Ġex c", + "Ġpl as", + "Ġsugg est", + "i tions", + "Ġle ast", + "y ing", + "ly ing", + "Ġl ik", + "O mega", + "ak ing", + "Ġmax imum", + "Ġrel ative", + "à ©", + "Ġacc ording", + "i ent", + "Ġw ay", + "Ġs em", + "at ural", + "l ike", + "res h", + "ĠM e", + "P s", + "ĠT rans", + "is c", + "Ġp rac", + "Ġr un", + "Ġcon ver", + "Ġs k", + "Ġy ield", + "ge q", + "ab ly", + "Ġanti b", + "iz ing", + "Î ²", + "m ission", + "Ġn ow", + "Ġdet ection", + "el oc", + "Ġg et", + "er t", + "Ġvari ables", + "Ġop en", + "Ġpress ure", + "Ġst rain", + "um ent", + "ĠF urther", + "Ġqu antum", + "Ġim plement", + "Ġear ly", + "Ġfr ame", + "Ġsh ort", + "Ġdr ug", + "Ġrequi red", + "P S", + "Ġm y", + "Ġm uch", + "Ġm em", + "C C", + "Ġqu ality", + "Ġprotein s", + "Ġl ayer", + "Ġqu es", + "Ġre cept", + "Ġhe re", + "Ġpro ced", + "ure d", + "Ġdevelop ed", + "Ġpos ition", + "r um", + "Ġl at", + "Ġincre asing", + "E M", + "Ġmeasure ments", + "Ġb en", + "Ġis ol", + "w h", + "T o", + "Ġval id", + "Ġfunction al", + "em ma", + ".. .", + "or ld", + "ri es", + "Ġprob ability", + "ĠN ew", + "Ġm m", + "O S", + "A D", + "ĠÎ ´", + "Ġscal e", + "ĠF e", + "ĠThe orem", + "ĠQ u", + "Ġcompon ents", + "Ġbl ood", + "ĠÏ ĥ", + "ac c", + "Ġb etter", + "Ġst ep", + "ĠÎ ³", + "Ġf ac", + "ane ous", + "Ġlo ad", + "Ġmet abol", + "Ġev olution", + "s on", + "re am", + "Ġe as", + "ir d", + "d imensional", + "b or", + "Ġm us", + "Ġequ ations", + "ps i", + "ord er", + "ol ar", + "Ġnum er", + "Ġk ey", + "or th", + "Ġsim ple", + "if t", + "cal e", + "Ġin dex", + "ĠâĢ ĵ", + "Ġconcent r", + "g es", + "Ġneg ative", + "Ġv eloc", + "Ġa x", + "ĠE ff", + "Ġfin ite", + "Ġ ill", + "ch ing", + "Ġpati ent", + "eps ilon", + "Ġm en", + "Ġc ri", + "I S", + "C l", + "Ġcon cl", + "ĠÎ ¸", + "ib ility", + "Ġsym met", + "ent er", + "Ġdist ance", + "Ġpol ym", + "igh ts", + "Ġc ult", + "Ġpe ak", + "Ġac ross", + "in ition", + "Ġle t", + "Ġcon struc", + "Ġinclud ed", + "Ġh owever", + "Ġreg ions", + "Ġlear ning", + "Ġev idence", + "in ally", + "Ġne ut", + "it ation", + "Ġwhe ther", + "Ġout put", + "ĠS ection", + "Ġg ood", + "I T", + "u ation", + "Ġtyp es", + "b m", + "c os", + "w ith", + "l im", + "o tic", + "Ġs till", + "Ġd ays", + "Ġstud ied", + "Ġim ages", + "b le", + "Ġar g", + "line ar", + "Ġprocess es", + "Ġw id", + "Ġtrain ing", + "Ġind ependent", + "pl ac", + "Ġres id", + "Ġsuc cess", + "Ġnuc le", + "G F", + "le t", + "pl oy", + "Ġtum or", + "G amma", + "Ġthere fore", + "r ast", + "Ġf ocus", + "as h", + "Ġbel ow", + "ial ly", + "Ġcompar ison", + "Ġad j", + "Ġl ike", + "Ġmo lecular", + "ri ed", + "Ġf it", + "ĠD i", + "l og", + "Ġpl ay", + "w ork", + "ec tions", + "Ġelect ro", + "u it", + "m ore", + "Ġm ight", + "Ġanal ys", + "Ġme ans", + "Ġcorrel ation", + "k n", + "Ġcont roll", + "I V", + "C h", + "p ec", + "ra g", + "Ġm agn", + "Ġphys ical", + "I ON", + "Ġreve al", + "Ġph osph", + "Ġr ates", + "Ġlarg er", + "Ġs tim", + "Ġso ft", + "Ġcomp ound", + "b e", + "ch i", + "ĠN o", + "Ġimp act", + "t or", + "Ġprim ary", + "oc ial", + "Ġapplic ation", + "Ġsol utions", + "d uce", + "Ġcharacter istics", + "Ġele ments", + "Ġvi ew", + "Ġl ater", + "ut ure", + "Ġfam ily", + "ri al", + "Ġtrans cri", + "or ption", + "Ġs w", + "C D", + "E D", + "Ġem b", + "Ġz ero", + "ol s", + "Ġl ife", + "ce p", + "ĠL i", + "th s", + "Ġser ies", + "Ġa round", + "Ġtrans ition", + "ĠC or", + "ĠâĪ Ĥ", + "Ġdat as", + "Ġ her", + "ĠB y", + "A M", + "sp ec", + "ol es", + "ograph y", + "t le", + "ĠC ar", + "al le", + "Ġest abl", + "ag ement", + "Ġsc hem", + "g round", + "Ġf ail", + "Ġexp ected", + "Ġrequi re", + "ar ray", + "Ġexperim ent", + "Ġele ment", + "Ġne u", + "Ġgener ated", + "Ġs ite", + "ĠCon t", + "ĠR NA", + "er al", + "Ġcont ent", + "Ġb acter", + "l er", + "Ġtrans fer", + "ul f", + "right arrow", + "an y", + "ĠS ince", + "in duced", + "Ġre action", + "he ck", + "Ġstruct ures", + "Ġcoun t", + "Ġdeterm ine", + "z ym", + "ĠB l", + "Ġunder stand", + "oc al", + "Ġsy n", + "Ġpol y", + "ur y", + "Ġb est", + "Ġf ixed", + "ren g", + "Ġc hemical", + "Ġtiss ue", + "Ġp ul", + "Ġbound ary", + "is ing", + "Ġb ro", + "atis tical", + "ic ity", + "s k", + "r ing", + "Ġl ast", + "Ġchild ren", + "r im", + "Ġre duction", + "Ġsp in", + "Ġb ody", + "oper ator", + "v ari", + "Ġd iv", + "ym bol", + "Ġm al", + "Ġsp ati", + "a h", + "ĠB i", + "b ack", + "s y", + "Ġse en", + "ĠW ith", + "id s", + "plic ations", + "Ġnec ess", + "Ġs ide", + "Ġb rain", + "Ġf ew", + "Ġapplic ations", + "ut es", + "ac hes", + "Ġac tive", + "var phi", + "ter m", + "Ġm om", + "ivers ity", + "Ġf inal", + "led ge", + "Ġdynam ics", + "av ing", + "er c", + "orph ism", + "on es", + "o ff", + "p m", + "Ġac tion", + "Ġn atural", + "ĠG e", + "Ġy ou", + "le x", + "ĠĠĠĠ ĠĠ", + "s tit", + "Ġg as", + "Ġm ake", + "Ġin duced", + "ĠA fter", + "ĠW h", + "Ġcompon ent", + "Ġinf ection", + "ĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠ", + "Ġconf ir", + "ig en", + "ĠS ystem", + "tic le", + "Ġprovid ed", + "tern al", + "b ers", + "O D", + "ĠIn ter", + "ot t", + "av es", + "ĠSt ud", + "p y", + "Ġres istance", + "ĠS ur", + "at ch", + "Ġd im", + "Ġinter p", + "Ġcy cl", + "on t", + "it ing", + "A G", + "Ġequ ival", + "ot ype", + "Ġprevious ly", + "Ġaddition al", + "out h", + "Ġim pl", + "Ġ ion", + "Ġ ir", + "Ġc op", + "Ġh al", + "Ġactiv ation", + "l angle", + "Ġf ull", + "S S", + "ĠO p", + "id d", + "Ġpro of", + "Ġproblem s", + "Ġtrans form", + "Ġinter actions", + "Ġsup p", + "d es", + "ĠR eg", + "operator name", + "eg in", + "Ġc ryst", + "Ġincre ases", + "ron ic", + "Ġad ap", + "in ant", + "Ġveloc ity", + "ĠAs s", + "iqu es", + "Ġcontinu ous", + "ĠCom p", + "ĠPro per", + "Ġpri or", + "or b", + "Ġno vel", + "Ġbl ock", + "Ġvol ume", + "Ġreg ard", + "omet ry", + "E C", + "Ġresul ting", + "ĠO r", + "Ġcar bon", + "are nt", + "Ġb inding", + "i j", + "Ġac cess", + "Ġwe ak", + "Ġun it", + "Ġ ide", + "\" \"", + "Ġc m", + "Ġcri tical", + "Ġresp ect", + "t rans", + "Ġâī ¥", + "Ġs al", + "e ad", + "Ġsim ulation", + "Ġcap ac", + "iti vity", + "Ġrec ord", + "ra k", + "Ġne ur", + "on ic", + "op le", + "Ġm g", + "Ġst reng", + "er ve", + "Ġre duc", + "Ġp ass", + "ord in", + "ex p", + "j ective", + "ens or", + "Ġpartic les", + "Ġa ir", + "Ġl ink", + "ĠÏ Ħ", + "Ġl ist", + "c in", + "ĠO ur", + "p ri", + "ve re", + "ib r", + "if orm", + "Ġexpl ain", + "Ġf em", + "Ġu til", + "S t", + "over line", + "Ġof ten", + "er y", + "op e", + "ĠU sing", + "b egin", + "Ġdifferen ti", + "per s", + "s elf", + "iz es", + "Ġconcentr ations", + "I R", + "ĠS up", + "Ġbas is", + "Ġinclud e", + "ĠB ond", + "Ġext rac", + "ĠMet hod", + "ĠD ata", + "ĠD ef", + "w n", + "Ġnetwork s", + "ign ed", + "âĢ ¢", + "Ġexp ressed", + "Ġcont rast", + "es is", + "c ol", + "in ter", + "p id", + "Ġd ri", + "Ġdef ine", + "Ġinflu ence", + "Ġselec ted", + "E L", + "Ġcontain ing", + "Ġs il", + "geb ra", + "re at", + "b olds", + "Ġinvestig ated", + "ĠC ol", + "ym met", + "yt es", + "Ġmo lec", + "Ġinvol ved", + "Ġd ay", + "Ġch ain", + "ĠMore over", + "Ġdi ag", + "Ġan g", + "Ġlik ely", + "Ġspect rum", + "Ġder iv", + "bolds ymbol", + "Ġhel p", + "ĠA m", + "Ġtre ated", + "Ġvari able", + "ell ular", + "ĠD es", + "ap s", + "Ġn m", + "ĠÏ ģ", + "ĠW hen", + "Ġhigh ly", + "am in", + "Ġwh at", + "rel ated", + "Ġch rom", + "Ġsur v", + "ĠAn alysis", + "Ġs it", + "f act", + "od ing", + "Ġproduc t", + "Ġev ents", + "r as", + "ĠP er", + "ma x", + "ĠA g", + "con t", + "ic ro", + "Ġad v", + "Ġcall ed", + "Ġdeg ree", + "A B", + "T R", + "Ġse g", + "ĠC an", + "Ġdemonstr ated", + "w ise", + "Ġ ve", + "ĠC a", + "Ġdet ected", + "c o", + "Ġder ived", + "Ġex hib", + "Ġgl obal", + "al ax", + "ul ating", + "A l", + "ang u", + "b o", + "Ġrec om", + "Ġfe ature", + "d ependent", + "Ġro t", + "ven tion", + "Ġrem ov", + "Ġw ind", + "Ġaccur acy", + "s ize", + "Ġsum m", + "Ġmeasure ment", + "Ġfield s", + "ward s", + "Ġl iter", + "atal y", + "ĠSt r", + "Ġre port", + "Ġcent ral", + "Ġs qu", + "Ġthe rapy", + "he st", + "Ġfe ed", + "S MILES", + "ĠA N", + "Ġs ites", + "âĢ ²", + "our s", + "om al", + "Ġl ip", + "Ġanalyz ed", + "Ġ °", + "Ġwe e", + "t em", + "Ġan other", + "il es", + "Ġcomple te", + "Ġne xt", + "ĠO ne", + "b i", + "ri p", + "st ate", + "ĠMod el", + "Ġfind ings", + "ĠP re", + "Ġrec ent", + "asc ular", + "Ġestim ate", + "Ġmechanism s", + "ĠRes ults", + "Ġparticip ants", + "Ġen g", + "m ost", + "omet ric", + "Ġequ al", + "Ġro b", + "Ġpol ar", + "Ġgene tic", + "Ġb o", + "Ġre st", + "ĠÏ Ģ", + "Ġrel ation", + "Ġques tion", + "ep ti", + "Ġdiff ic", + "em s", + "Ġf uture", + "if y", + "Ġmod e", + "Ġmemb rane", + "Ġhe at", + "A ut", + "d ing", + "Ġox id", + "Ġconf ig", + "plic ation", + "ĠM on", + "alle l", + "id ed", + "Ġdirec tion", + "pl ed", + "Ġprovid es", + "Ġindic ate", + "Ġset s", + "Ġtechn ique", + "Ġm ac", + "Ġhyp ot", + "Ġat ten", + "Ġev ent", + "Ġst age", + "Ġn ode", + "Ġref erence", + "Ġup per", + "Ġtechn iques", + "Ġgre ater", + "Ġdirect ly", + "Ġare as", + "Ġdis s", + "h or", + "ĠP ol", + "Ġevalu ation", + "Ġpattern s", + "ĠA bstract", + "Ġvir us", + "ve y", + "P C", + "Ġw omen", + "ri ent", + "Ġplas ma", + "Ġpro duced", + "ĠÎ µ", + "Ġanalys es", + "ĠS ub", + "Ġset ting", + "Ġmom ent", + "Ġtherm al", + "Ġoptim al", + "Ġtak en", + "Ġrec ogn", + "Ġvari ation", + "ĠL emma", + "Ġs us", + "f rak", + "ĠI L", + "Ġproced ure", + "h ood", + "Ġa im", + "ar ies", + "math frak", + "Ġpl ant", + "b rid", + "e lect", + "Ġvis ual", + "ur s", + "c ence", + "Ġf ive", + "Ġspati al", + "Ġrecept or", + "Ġindic ated", + "Ġ ess", + "Ġconsist ent", + "Ġt urn", + "tic es", + "Ġex ists", + "ect ors", + "Ġen zym", + "mer ic", + "Ġno ise", + "Ġgro und", + "Ġestim ated", + "el ine", + "Ġchann el", + "ti tion", + "Ġdiscuss ed", + "om er", + "ot es", + "Ġex act", + "ĠS ec", + "Ġt ake", + "Ġknow ledge", + "Ġpro p", + "Ġinf lamm", + "Ġdo uble", + "I t", + "Ġcon text", + "ĠM ed", + "M A", + "Ġf at", + "am s", + "d ata", + "and s", + "Ġcar di", + "ĠFurther more", + "oc y", + "Ġobserv ations", + "app ing", + "ĠIn f", + "om ial", + "Ġpubl ic", + "Ġem ploy", + "Ġre ason", + "y gen", + "Ġfollow ed", + "Ġam ount", + "Ġc ertain", + "wh ich", + "ot yp", + "ĠC ell", + "Ġch all", + "Ġpartic le", + "am bda", + "Ġ ens", + "Ġpe ople", + "a ult", + "ĠU nd", + "ĠB e", + "um in", + "roscop y", + "M R", + "l ation", + "Ġrep e", + "Ġab le", + "ĠS o", + "ĠâĪ ŀ", + "Ġen ti", + "Ġmo ve", + "Ġt rac", + "C O", + "Ġhe ter", + "Ġsp eed", + "Ġeffici ency", + "Ġop tical", + "Ġcomb ination", + "en ess", + "Ġc hem", + "L E", + "app a", + "Ġdecre ase", + "Î ¼", + "p ed", + "n ote", + "ĠM ulti", + "Ġal tern", + "Ġassum e", + "ĠF orm", + "str ic", + "qu e", + "Ġis s", + "ur rent", + "Ġpr inc", + "Ġt ask", + "op s", + "Ġwhere as", + "C H", + "Ġreveal ed", + "Ġcan not", + "ac tive", + "en z", + "Ġf ore", + "Ġoper ator", + "Ġcol um", + "at in", + "Ġorig inal", + "Ġsmall er", + "Ġmaterial s", + "h ydro", + "Ġcur ve", + "Ġselec tion", + "ak es", + "Ġex pos", + "at s", + "ĠÏ ī", + "Ġp ack", + "Ġst ability", + "Ġover all", + "Ġm orph", + "Ġmet ric", + "Ġo l", + "Ġb ar", + "ĠI N", + "I M", + "c y", + "et hyl", + "S P", + "Ġrespons es", + "anc y", + "Ġl ay", + "spec ific", + "Ġv s", + "ag ed", + "Ġs ocial", + "Ġc ut", + "I P", + "Ġlim ited", + "enc ies", + "Ġprot oc", + "Ġcompos ition", + "ĠThe y", + "Ġnum bers", + "m box", + "Ġdecre ased", + "v ec", + "R O", + "Aut hors", + "Ġth ick", + "Ġco ordin", + "Ġm es", + "Ġaff ect", + "Ġcl ose", + "Ġtrans port", + "C A", + "re te", + "c ome", + "Ġcollec ted", + "ĠF rom", + "Ġcontain s", + "ch it", + "ĠD et", + "Ġflu x", + "over y", + "e u", + "a ff", + "Ġconduc ted", + "Ġcr iter", + "Ġliter ature", + "Ġmem ory", + "Ġsequ ences", + "Ġp an", + "plic it", + "Ġtr ue", + "Ġmed ium", + "Ġd am", + "i re", + "c ell", + "L et", + "ef ul", + "ĠA meric", + "Ġn odes", + "get her", + "Ġto gether", + "T P", + "Ġrat her", + "Ġaut hors", + "Ġs ch", + "Ġprocess ing", + "Ġspect ra", + "Ġevalu ated", + "al k", + "Ġred uce", + "ĠH igh", + "ĠC ons", + "Ġcy cle", + "or n", + "i ers", + "Ġpro por", + "or ies", + "r ate", + "Ġh ost", + "o oth", + "y nt", + "Ġsour ces", + "Ġindividual s", + "Ġacc ount", + "ĠAl though", + "Ġcor rec", + "Ġpl an", + "enti ally", + "Ġdist inc", + "Ġso il", + "Ġse arch", + "Ġman agement", + "Ġvers ion", + "âĢ Ķ", + "Ġf ig", + "ĠN ote", + "Ġhe ad", + "dition al", + "Ġbu ild", + "ĠG l", + "as is", + "g roup", + "Ġdis play", + "ĠUn iversity", + "oot note", + "amet er", + "min ist", + "o pl", + "ym ph", + "L ambda", + "Ġidentif y", + "ĠSt ere", + "Ġï Ģ", + "Ġpro l", + "our ce", + "ic ial", + "Ġsim ulations", + "Ġth resh", + "p oint", + "ear ch", + "ell ing", + "ĠA cc", + "Ġframe work", + "Ġstreng th", + "ĠA b", + "tic les", + "Ġc os", + "F ootnote", + "r u", + "osp ital", + "Ġst able", + "Ġmo tion", + "Ġt ested", + "Ġt ests", + "as ter", + "l dots", + "C L", + "in ite", + "Ġspec ial", + "== ==", + "Ġappro aches", + "p ing", + "Ġcons um", + "S D", + "Ġj ust", + "k appa", + "Ġth ough", + "f aces", + "Ġra pid", + "ens ive", + "Ġnecess ary", + "Ġt ub", + "Ġfor ce", + "Ġbl ack", + "v olution", + "ĠAt om", + "ĠH ere", + "it ude", + "ens ions", + "ff er", + "r ich", + "Ġgiv es", + "Ġsh ape", + "Ġh ard", + "om p", + "Ġrepresent ation", + "l ing", + "ĠD ec", + "Ġnumer ical", + "Ġpl ace", + "Ġlead ing", + "Ġben ef", + "Ġreg ular", + "Ġclust er", + "Ġrel atively", + "Ġper cent", + "Ġaut om", + "Ġsym pt", + "ib ri", + "c hes", + "hen yl", + "c ar", + "Ġill ustr", + "por ts", + "em ic", + "Ġg ive", + "Ġcon ven", + "lec tion", + "ĠĠĠĠĠĠĠĠ ĠĠĠĠ", + "ĠA nd", + "Ġf ood", + "m ic", + "ograph ic", + "Ġc heck", + "Ġab ility", + "iqu id", + "Ġsub str", + "ĠâĪ Ĩ", + "Ġed ge", + "ĠP D", + "Ġclass ification", + "Ġsurv ival", + "ĠC al", + "er ate", + "Ġus eful", + "Ġcar ried", + "Ġint ensity", + "H E", + "oc enter", + "Ġpath way", + "Ġdef inition", + "Ġschem e", + "Ġsub sequ", + "ĠF irst", + "Ġcon sequ", + "ĠD iff", + "Ġinhib it", + "Ġam plit", + "as er", + "ĠN etwork", + "n ormal", + "ĠS T", + "Ġsol id", + "per im", + "com es", + "Ġcy t", + "od ies", + "I F", + "ra di", + "Ġm or", + "Ġc ore", + "B S", + "**** ****", + "Ġsoft ware", + "ĠG u", + "i red", + "id ent", + "Ġdiffic ult", + "us e", + "Ġadd ed", + "le y", + "Ġcaus ed", + "g ence", + "Ġb ase", + "## ##", + "ogen ic", + "f rom", + "Ġstat us", + "Ġassoci ation", + "ĠStere ocenter", + "Ġg alax", + "N O", + "angu age", + "Ġd imension", + "ogen esis", + "Ġem ission", + "Ġde ath", + "ul in", + "Ġag re", + "t urb", + "n abl", + "por al", + "Ġp or", + "Ġcomb ined", + "Ġalgorithm s", + "C s", + "Ġsens itivity", + "Ġallow s", + "Ġcapac ity", + "vers ion", + "Ġre stric", + "rom e", + "Ġexpos ure", + "h y", + "ann ing", + "Ġob ject", + "Ġc ode", + "f l", + "ro duction", + "res ents", + "r up", + "Ġte xt", + "ĠM at", + "Ġlead s", + "Ġres on", + "Ġproduc ts", + "Ġwh ole", + "Ġmat ter", + "P hi", + "op t", + "enc ing", + "ffici ents", + "n a", + "pec ially", + "Ġh aving", + "rop y", + "Ġunc ertain", + "en ari", + "r ical", + "Ġmin im", + "Ġorig in", + "u per", + "ĠN on", + "Ġevalu ate", + "Pro of", + "c ap", + "Ġsignal ing", + "Ġpolym er", + "tic ally", + "it ten", + "an tit", + "Ġus er", + "le vel", + "Ġmeas ures", + "Ġdynam ic", + "Ġmon ths", + "o ti", + "ra nd", + "Ġun til", + "Ġden ote", + "Ġnot e", + "Ġmain tain", + "Ġk in", + "sc ill", + "Ġim aging", + "Ġp ain", + "av y", + "Ġm it", + "ot he", + "Ġreg ul", + "kn own", + "Ġpl ot", + "nabl a", + "Ġf raction", + "w er", + "Ġstrateg y", + "Ġgre at", + "Ġdatas et", + "Ġun ique", + "C M", + "Ġt w", + "h an", + "ĠE u", + "and id", + "Ġback ground", + "Ġbro ad", + "il t", + "Ġimpro ved", + "Ġdiagn osis", + "i ous", + "Ġd ig", + "re m", + "er a", + "Ġex cl", + "Ġmet al", + "Ġs ix", + "Ġmin imum", + "us ions", + "e e", + "Ġcompound s", + "Ġas p", + "Ġe th", + "Ġdet ect", + "f erence", + "ĠÎ ·", + "Ġst atistical", + "ati ves", + "r is", + "Ġthe orem", + "ĠO F", + "w w", + "ar ily", + "ce ption", + "iv ing", + "Ġtest ing", + "Ġdiagn os", + "Ġrep resents", + "S igma", + "on ical", + "Ġequival ent", + "Ġbi om", + "Ġsub st", + "rain ts", + "ĠR ef", + "Ġsc ore", + "Ġd oc", + "Ġimpl ies", + "et er", + "Ġsynt hesis", + "il ibri", + "atter ing", + "C S", + "al se", + "Ġneu ro", + "Ġal though", + "ir us", + "met hyl", + "Ġtranscri ption", + "Ï Ģ", + "ĠMo lecular", + "Ġc ause", + "m ut", + "ĠI d", + "Î »", + "ad d", + "Ġpl ac", + "Ġag g", + "t ure", + "Ġl ack", + "Ġpredic tion", + "ra w", + "A n", + "Ġ ult", + "yn omial", + "Ġimmun e", + "il i", + "Ġpre p", + "Î ³", + "cl ass", + "Ġm ach", + "am ple", + "Ġres olution", + "Ġcou pling", + "se ud", + "Ġindic ates", + "Ġgener ation", + "Ġh ar", + "Ġf und", + "s cale", + "Ġe igen", + "ĠR el", + "ab or", + "ĠC H", + "e xt", + "am m", + "Ġcor rect", + "Ġsc reen", + "Ġstruct ural", + "Ġp H", + "Ġrele vant", + "Ġan gle", + "I G", + "Ġal gebra", + "hel ial", + "Ġw orld", + "Ġcur ves", + "ĠInt roduction", + "Ġth ird", + "Ġintro duced", + "B ig", + "n o", + "aus s", + "sub set", + "Ġtrans mission", + "Ġprof ile", + "ĠÎ ½", + "Ġes pecially", + "Ġatt rib", + "uc tion", + "Ġcoe fficients", + "Ġremain s", + "Ġne igh", + "os en", + "Ġrel i", + "Ġhig hest", + "Ġun iform", + "Ġf ar", + "chit ect", + "| |", + "Ġappro pri", + "ple x", + "ĠM ass", + "ogene ous", + "al es", + "Ġref er", + "Ġneed ed", + "Ġdifferen tial", + "ce ed", + "$ $", + "ynam ic", + "Ġse x", + "Ġspect ral", + "ch ar", + "P E", + "T S", + "Ġapproxim ately", + "val ue", + "Ġhal f", + "end ing", + "Ġgra di", + "Ġcoe fficient", + "ĠPh ys", + "Ġcon cer", + "Ġlab el", + "ir al", + "Ġchar ge", + "Ġox ygen", + "Ġde vi", + "Ġinter nal", + "Ġexp ans", + "lo ad", + "ĠS m", + "ran g", + "C on", + "ĠN a", + "Ġk e", + "Ġdi ab", + "ac hed", + "Ġloc ation", + "Ġvol t", + "ĠD isc", + "-- -", + "oc ytes", + "ore tical", + "Ġg ain", + "Ġmed i", + "ym pt", + "ot ed", + "ĠV al", + "Ġcommun ity", + "plement ary", + "Ġt ree", + "ĠT wo", + "Ġwh ose", + "Ġd one", + "am ine", + "Ġbi ological", + "in ks", + "Ġal most", + "Ġsl ight", + "Ġre pro", + "ģ Ħ", + "Ġthe rap", + "oc ation", + "Ġg ly", + "ĠE qu", + "Ġcol or", + "Ġn am", + "s ection", + "ĠE m", + "read y", + "H z", + "P D", + "f unction", + "ch ange", + "Ġprinc ip", + "Ġbec ome", + "ĠâĢ ĺ", + "Ġco ur", + "Ġloc ated", + "Ġr ang", + "in ity", + "Ġinter val", + "g in", + "Ġinvestig ate", + "f ree", + "Ġv itro", + "Ġsub set", + "Ġm ov", + "Ġpro ve", + "Ġl iver", + "ate gor", + "et es", + "Ġl ymph", + "d om", + "ĠE lect", + "Ġser um", + "Ġsc enari", + "end s", + "ĠF inally", + "Ġfil ter", + "I L", + "Ġab und", + "ment ation", + "im als", + "n um", + "enc ed", + "Ġproper ty", + "mat rix", + "ĠCom par", + "Ġl and", + "ĠCh ar", + "ress ive", + "ul us", + "Ġb one", + "E x", + "Ġradi ation", + "Ġsugg ested", + "ĠCom put", + "Ġthresh old", + "ĠA D", + "Ġh or", + "Ġin duc", + "Ġapproxim ation", + "Ġad minist", + "Ġor d", + "Ġl ung", + "Ġrece ived", + "Ġn orm", + "Ġestim ates", + "Ġl aw", + "Ġout comes", + "ĠP r", + "Ġdep th", + "Ġel se", + "Ġcontrib ution", + "he tic", + "Ġcons erv", + "Ġup on", + "Ġde ep", + "M D", + "Ġm el", + "Ġfil m", + "ilibri um", + "Ġo scill", + "ol ved", + "Ġbre ast", + "C P", + "ĠD ist", + "ric es", + "in ated", + "Ġoptim ization", + "Ġpredic ted", + "s f", + "d im", + "ĠS N", + "Ġav oid", + "Ġne ural", + "Ġw a", + "rop e", + "Ġdistrib utions", + "ox id", + "Ġsm ooth", + "p ath", + "Ġflu id", + "Ġs af", + "Ġcho ice", + "A A", + "Ġmolec ules", + "U S", + "Ġal ways", + "iv o", + "Ġreg ression", + "Ġsuccess ful", + "Ġw all", + "oun g", + "Ġactiv ities", + "Ġdepend ence", + "Ġrequi res", + "Ġpl ane", + "Ġdesign ed", + "P I", + "d own", + "Ġpop ulations", + "c or", + "medi ate", + "Ġd ose", + "Ġb ond", + "C o", + "ĠM an", + "Ġdiag ram", + "g s", + "Ġto ol", + "Ġisol ated", + "Ġvers us", + "ne y", + "Ġem erg", + "ĠA ut", + "a im", + "f ield", + "Ġexam ined", + "Ġs at", + "S M", + "ĠSp ec", + "Ġpar allel", + "is ation", + "Ġdistinc t", + "Ġpredic t", + "Ġf er", + "Ġunderstand ing", + "ĠSim ilar", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ", + "ud es", + "Ġo rient", + "h ic", + "u z", + "Ġmod ified", + "ĠâĪ ¼", + "F F", + "The re", + "Ġtri al", + "x y", + "ger y", + "Ġal ready", + "def ine", + "m ing", + "ĠS D", + "Ġmon itor", + "Ġp sy", + "Ġbec omes", + "ist ry", + "ĠÎ ĵ", + "Ġh um", + "ri er", + "ess ion", + "Ġhist ory", + "à ¶", + "ĠÎ ¾", + "Ġestabl ished", + "Ġachie ved", + "es tern", + "Ï Ĩ", + "ĠH ence", + "Ġassess ment", + "ot or", + "Ġdescrib e", + "och ond", + "yl ation", + "st s", + "sp ace", + "Ġdise ases", + "j ection", + "Ġs low", + "Ġnon linear", + "p ly", + "m l", + "Ġemb ed", + "com p", + "Ġeffici ent", + "Ġoper ation", + "Ġcont act", + "o z", + "Ġinv ari", + "Ġcent er", + "Ġcon c", + "wide tilde", + "Ġbe am", + "Ġclos ed", + "ĠMethod s", + "Ġch ronic", + "al ing", + "Ġse vere", + "Ġform s", + "il it", + "s ide", + "p en", + "Ġb ran", + "o ud", + "tal ity", + "Ġmap s", + "ac ts", + "O L", + "P R", + "Ġ Í", + "s l", + "Ġinst ance", + "ul ly", + "Ġestim ation", + "Ġpl ate", + "Ġdev ice", + "ĠI II", + "s in", + "Ġpl ants", + "it tle", + "Ġpro duce", + "Ġhe nce", + "Ġn ature", + "Ġrele ase", + "ĠM in", + "ric t", + "Ġconn ected", + "ott om", + "ell ar", + "Ġform ed", + "Ġm ob", + "Ġcomput ed", + "Ġ RE", + "Ġpol ynomial", + "Ġl iquid", + "g n", + "Ġass ay", + "Ġman if", + "ĠS i", + "re nce", + "Ġax is", + "V ID", + "Ġsign als", + "Î ¸", + "to k", + "d s", + "Ġrat s", + "Ġt or", + "o lecular", + "c hed", + "Ġdesc ri", + "Ġexp on", + "Ġper turb", + "Ġgl uc", + "Ġcolum n", + "U L", + "Ġmain ly", + "Ġm ul", + "id er", + "ĠC R", + "Ġc ataly", + "Ġl aser", + "tion ed", + "d en", + "Ġsugg ests", + "f ig", + "Ġprop ag", + "or g", + "re p", + "Ġcharacter ized", + "olog ies", + "Ġacc um", + "Ġv ary", + "Ġcontroll ed", + "Ġup d", + "ĠB r", + "Ġenti re", + "Ġ @", + "â ģĦ", + "Ġ Ì", + "Ġdat ab", + "an o", + "am il", + "Ġadj ust", + "y e", + "p ression", + "eren ces", + "Ġess ential", + "ĠH ydro", + "ĠT r", + "Ġappropri ate", + "Ġform ula", + "Ġlat tice", + "Ġac ute", + "Ġus ually", + "it able", + "Ġm ar", + "Ġμ m", + "ĠU SA", + "Ġinc ub", + "oc ks", + "Ġp epti", + "idd le", + "Ġdec om", + "Ġdam age", + "Ġgen ome", + "Ġm ouse", + "c irc", + "Ġlay ers", + "Ġt rack", + "Ġto x", + "Ġre plac", + "Ġad vant", + "iz on", + "Ġrecord ed", + "Ġst art", + "Ġr ank", + "s er", + "ĠG ene", + "auss ian", + "ing u", + "Ġconst raints", + "f low", + "Ġm ig", + "P L", + "Ġinc or", + "ap pro", + "Ġf ast", + "Ġmus cle", + "Ġh ome", + "e q", + "ĠÏ Ī", + "Ġstrong ly", + "ĠEu rope", + "Ġsub jects", + "Ġob jects", + "t est", + "t ered", + "ĠWh ile", + "Ġsymmet ry", + "Ġquanti f", + "` `", + "Ġbre ak", + "ĠEx perim", + "Ġmi xt", + "< <", + "ĠCh ina", + "ĠId entif", + "Ġaff ected", + "Ġsecond ary", + "Ġin equ", + "in cl", + "E G", + "F T", + "Ġfail ure", + "ec tiv", + "Ġk m", + "Ġsam pling", + "Ġexpans ion", + "Ġprac tice", + "u ations", + "ogn itive", + "Ġdi et", + "Ġtemper atures", + "Ġcontrol s", + "Ġch osen", + "Ġgener ally", + "anc er", + "Ġdeg rad", + "ul i", + "s m", + "othe rapy", + "Ġto wards", + "ĠProper ties", + "Ġclust ers", + "Ġdel ay", + "Ġhe p", + "P A", + "ĠStud y", + "antit ative", + "Ġclass ical", + "ĠZ h", + "ĠÎ ©", + "ĠB o", + "Ġse ed", + "ĠStr uct", + "Ġtre nd", + "i ological", + "Ġconfir med", + "Ġdistrib uted", + "b ial", + "Ġn ame", + "C N", + "val ence", + "er ior", + "iv en", + "n ed", + "Ġbehavi our", + "as ks", + "g ra", + "m ark", + "Ġerr ors", + "ĠR ep", + "l ight", + "cri pt", + "I f", + "Ġc andid", + "Ġdepend s", + "ĠN ational", + "Ġh olds", + "Ġprotoc ol", + "ĠUn ited", + "Ġinter face", + "Ġexp ect", + "Ġï ģ", + "ĠH IV", + "Ġro ot", + "Ġsc attering", + "w ords", + "Ġobserv ation", + "ot op", + "Ġoccur s", + "our ces", + "p ite", + "ĠS te", + "Ġor th", + "Ġst ain", + "Ġst eps", + "Ġcomp are", + "Ġbas ic", + "Ġinhib ition", + "Ġsympt oms", + "ĠHe alth", + "Ġpubl ished", + "f old", + "Ġt un", + "Ġv ivo", + "Ġrec onstr", + "Ġm RNA", + "ic y", + "Ġhy brid", + "y r", + "Ġm ixed", + "v is", + "Ch I", + "Ġmed ical", + "Ġf rag", + "Ġan imals", + "Ġimport ance", + "Ġeng ine", + "ĠC T", + "Ġpair s", + "Ġb al", + "ĠE ar", + "her s", + "Ġsy nd", + "Ġar chitect", + "Ġidentif ication", + "Ġstrateg ies", + "Ġreg ulation", + "ĠL a", + "r or", + "Ġflu ores", + "ur ity", + "Ġcon cept", + "Ġatten tion", + "Ġtrans formation", + "uc le", + "ĠRes earch", + "Ġsim pl", + "Ġcult ure", + "ar ing", + "if ically", + "p ir", + "z e", + "P T", + "m osp", + "Ġsw it", + "Ġn or", + "Ġenh ance", + "Ġenvironment al", + "r ary", + "ĠM icro", + "Ġw ide", + "op ath", + "au ge", + "z eta", + "Ġst e", + "ĠE l", + "Ġw ords", + "Ġnuc lear", + "Ġl anguage", + "Ġdetail s", + "op ar", + "ĠR ed", + "w ater", + "Ġc ategor", + "Ġf ile", + "Ġco ver", + "Ġachie ve", + "à ¡", + "um m", + "Ġl ig", + "Ġsur vey", + "Ġext ended", + "l ab", + "ĠIn c", + "Ġdis pers", + "Ġrecom m", + "ĠB ased", + "Ġabs ence", + "Ġconstruc tion", + "Ġpo or", + "Ġvolt age", + "Ġcell ular", + "Ġmor tality", + "Ġshow ing", + "Ġprol if", + "m p", + "Ġneur ons", + "Ġsup ported", + "Ġpre vent", + "el i", + "ox y", + "ic a", + "Ġf ully", + "Ġen ough", + "o times", + "ĠM R", + "Ġb ul", + "Ġphen omen", + "F A", + "Ġdec ision", + "Ġd ual", + "Ġdec ay", + "Ġo wn", + "Ġus es", + "Ġchall eng", + "Ġadd ress", + "O C", + "tiv ation", + "Ġm ill", + "Ġmod es", + "at us", + "ic tion", + "Ġabs orption", + "Ġep it", + "Ġconst ra", + "Ġagre ement", + "ĠA f", + "Ġbi as", + "ud ed", + "Ġpar ts", + "Ġv an", + "Ġcol on", + "Ġex ternal", + "Ġthe oretical", + "as i", + "Ġl es", + "abil ities", + "L A", + "tt ps", + "Ġinst ead", + "Ġmemb ers", + "+ +", + "Ġrec ently", + "Ġprep ared", + "Ġar ticle", + "d ay", + "Ġext ract", + "Ġâ İ", + "Ġpath ways", + "Ï Ħ", + "m id", + "or age", + "Ġcommun ication", + "Ġacc el", + "Ġun its", + "iti s", + "ynt hesis", + "Ġamplit ude", + "ri e", + "ult aneous", + "ĠL ear", + "ec ause", + "d o", + "e ff", + "Ġex plicit", + "Ġcriter ia", + "b re", + "Ġex ec", + "Ġmechan ical", + "er os", + "ĠCon cl", + "ĠE xt", + "Ġclass es", + "Ġlong er", + "Ġcalc ulations", + "eu tic", + "oci ated", + "ar di", + "Ġcour se", + "Ġpar tial", + "Ġsens or", + "Ï ĥ", + "Ġoper ators", + "ĠAmeric an", + "Ġm M", + "Ġv acc", + "oc c", + "ic on", + "Ġout come", + "Ġanal og", + "Ġthick ness", + "Ġre ach", + "Ġassum ed", + "end er", + "Ġm ale", + "S E", + "Ġint ra", + "Ġimplement ation", + "em ia", + "Ġenh anced", + "b ility", + "Ġeas ily", + "um p", + "Ġcar cin", + "os a", + "Ġcorrespond s", + "ne g", + "Ġmagn itude", + "con st", + "Ġl atter", + "Ġrepresent ed", + "Ġs ed", + "Ġparticular ly", + "Ġwr itten", + "par t", + "Ġo il", + "ber g", + "ĠB ar", + "Ġd ys", + "ĠS ome", + "ĠM ar", + "Ġaltern ative", + "ĠG erm", + "Ġgener ate", + "Ġcon struct", + "ian s", + "st ream", + "Ġe c", + "oc hemical", + "ib ration", + "oper ative", + "is ter", + "Ġrob ust", + "t re", + "Ġmodel ing", + "or ing", + "es e", + "d ed", + "ide o", + "Ġhydro gen", + "um ents", + "Ġdemonstr ate", + "Ġcorrel ated", + "Ġsystem atic", + "Ġsur gery", + "Ġindic ating", + "Ġhypot hesis", + "y ear", + "mit ted", + "Ġst ars", + "Ġprof iles", + "Ġcons ists", + "t ri", + "Ġdepend ent", + "ish ing", + "t op", + "Ġhe art", + "at ically", + "Ġinj ury", + "Ġqu ad", + "Ġwee ks", + "ut ing", + "ĠT e", + "Ġid enti", + "Ġgradi ent", + "Ġcalc ulation", + "Ġ ur", + "R T", + "z ation", + "Ġed uc", + "en ing", + "P P", + "z ed", + "us h", + "Ġcharacter istic", + "Ġstrain s", + "et h", + "Ġdi vers", + "âĪ Ī", + "oid s", + "ol ic", + "Ġinterp ret", + "K ey", + "Ġatt ack", + "p ective", + "Ġlab or", + "Ġmet ast", + "N F", + "Ġtiss ues", + "Ġradi us", + "ĠE ach", + "Ġc at", + "Ġd on", + "Ġele v", + "Ġass emb", + "r ons", + "Ġar bit", + "Ġpan el", + "Ġg rid", + "Ġt able", + "roscop ic", + "Ġc le", + "ĠIn tern", + "ob acter", + "Ġassum ption", + "ĠCO VID", + "Ġbound ed", + "Ġother s", + "Ġsch ool", + "Ġh ospital", + "lec ted", + "ĠC u", + "à Ĺ", + "Ġcomple t", + "Ġwid th", + "Ġl inks", + "p o", + "ol low", + "Ġn ut", + "Ġappear s", + "row n", + "a ro", + "Ġus ers", + "Ġcl im", + "Ġslight ly", + "Ġbl ue", + "ra b", + "ĠS er", + "Ġfig ure", + "ĠR ad", + "Ġelect ric", + "m m", + "och astic", + "ri ef", + "Ġcollec tion", + "Ġst em", + "Ġg over", + "Ġb ur", + "Ġtyp ical", + "s up", + "Ġagg reg", + "ra z", + "ĉĉ ĉ", + "Ġst ation", + "Ġar ter", + "i vely", + "itro gen", + "Ġcons tit", + "em pt", + "ĠEff ect", + "Ġdescri ption", + "Ġsc ores", + "Ġmet hyl", + "ĠO b", + "ĠSt ates", + "Ġs plit", + "ĠV ari", + "ĠW ang", + "Ġc ere", + "ĠF ran", + "Ġneed s", + "ĠF our", + "Ġpro ject", + "Ġdev ices", + "Ġintegr al", + "ĠE s", + "ymmet ric", + "Ġm ess", + "Ġpl ays", + "ĠLear ning", + "Ġover l", + "H ere", + "ign ment", + "Ġdel iver", + "ap an", + "C E", + "Ġg auge", + "ĠJ oh", + "-------- --------", + "Ġunder lying", + "Ġth in", + "Ġassess ed", + "Ġdiff usion", + "Ġhe ight", + "ĠS w", + "Ġd ark", + "pr int", + "ran ge", + "ĠC I", + "is es", + "l ier", + "r ant", + "om orphism", + "Ġcomp act", + "ip s", + "ĠN ame", + "Ġtechn ology", + "ag en", + "Ġconfig uration", + "Ġd uration", + "ĠCl ass", + "Ġp ut", + "Ġm aking", + "Ġas ympt", + "a id", + "Ġco h", + "Ġcomplex ity", + "Ġsec tions", + "ĠM D", + "ĠĠĠĠĠĠĠĠ Ġ", + "Ġra d", + "Ġsubstr ate", + "d d", + "Ġan n", + "Ġorgan ic", + "Ġtak ing", + "Ġinclud es", + "Ġk ine", + "a res", + "Ġro w", + "ateg ory", + "Ġmit ochond", + "U T", + "Ġsynd rome", + "ĠPro b", + "re tion", + "Ġfl uct", + "ĠD is", + "Ġtrans l", + "pl as", + "Ġpsy ch", + "Ġsur faces", + "Ġdetail ed", + "amil ton", + "Ġh old", + "ĠâĬ Ĺ", + "ĠC N", + "Ġd il", + "ĠO ver", + "at form", + "Ġver tical", + "Ġcomput ation", + "Ġp ure", + "Ġm akes", + "Ġexist ing", + "Ġexam ples", + "S O", + "ord ers", + "Ġm ix", + "Ġincor por", + "Ġre qu", + "an tic", + "D NA", + "Î ´", + "Ġcl oud", + "ĠT echn", + "Ġï ĥ", + "em ents", + "Ġbas eline", + "ste in", + "Ġbel ong", + "Ġtri als", + "Ġhor izon", + "Ġphosph or", + "Ġan s", + "di x", + "ro id", + "Ġapp ly", + "u ed", + "ern el", + "Ġfem ale", + "ic acy", + "Ġv ectors", + "Ġmat rices", + "at ric", + "ĠM c", + "Ġp y", + "Ġch lor", + "l en", + "Ġclear ly", + "st atic", + "re f", + "ĠS outh", + "Ġmed ia", + "ĠS he", + "ĠB ay", + "Ġag ents", + "B y", + "Ġdifferenti ation", + "ist ant", + "orph ic", + "Ġvari ety", + "Ġserv ice", + "Ġm apping", + "vel ength", + "Ġchann els", + "Ġcomp ute", + "Ġst ream", + "ul s", + "am ide", + "ok ing", + "v it", + "Ġyield s", + "om b", + "ĠG aussian", + "Ġp en", + "un e", + "Ġexper ience", + "b and", + "ĠD o", + "math sf", + "Ġallow ed", + "A r", + "R A", + "Ġbacter ial", + "Ġm iss", + "Ġbacter ia", + "Ġmoment um", + "Ġh ours", + "uc k", + "ĠPro position", + "ber t", + "ot rop", + "Ġvari ance", + "Ġtr ig", + "Ġsh ift", + "Ġequ ilibrium", + "b u", + "IN G", + "Ġwh ite", + "Ġk ind", + "Ġj oint", + "Ġtem poral", + "ĠI V", + "ĠAf ric", + "Ġsub ject", + "ĠP o", + "he ad", + "id el", + "Ġantib ody", + "ĠEff ects", + "Ġsp e", + "Ġsu fficient", + "j ected", + "re es", + "ĠT op", + "Ġmut ations", + "is ions", + "B C", + "Ġin duction", + "Ġinterest ing", + "ell a", + "c an", + "Ġsus p", + "ĠG roup", + "Ġextrac ted", + "istic ally", + "c oh", + "m ap", + "Ġaccur ate", + "Ġto o", + "Ġdim ensions", + "te gr", + "Ġgre en", + "ĠR o", + "Ġw ild", + "Ġlo op", + "Ġmet a", + "Ġsub stit", + "os ome", + "Ġsuggest ing", + "Ġspec im", + "am ental", + "im ent", + "Ġi j", + "Ġcl aim", + "Ġaut hor", + "Ġfil ms", + "Ġcoun ter", + "Ġconven tional", + "r in", + "otyp es", + "Ġp ast", + "S ince", + "medi ated", + "reat ment", + "Ġext ension", + "Ġbi o", + "Ġs ent", + "h al", + "Ġob jective", + "Ġar ray", + "Ġsu itable", + "ĠB ut", + "ĠH uman", + "or gan", + "b ut", + "mod el", + "S I", + "Ġhealth y", + "Ġv ac", + "Ġl ate", + "Ġr ing", + "Ġl ittle", + "M T", + "Ġsqu are", + "Ġge ometry", + "ĠT HE", + "ĠS ing", + "j ug", + "Ġstud ents", + ", ,", + "Ġad ult", + "Ġcharacter ization", + "Ġat mosp", + "Ġmonitor ing", + "an i", + "n et", + "ĠP a", + "opt osis", + "Ġcont in", + "ĠS ol", + "Ġdatab ase", + "im port", + "m ann", + "ĠPro cess", + "ĠC hen", + "Ġg ap", + "Ġenzym e", + "O T", + "Ġsim ultaneous", + "Ġexist ence", + "B P", + "ĠJ apan", + "oun ts", + "Ġt urb", + "Ġsp aces", + "ĠWe ight", + "oph il", + "Ġa st", + "Ġwr ite", + "Ġdiab etes", + "ĠC A", + "Ġneut ral", + "Ġvari ations", + "ax on", + "Ġbe gin", + "und er", + "Ġext raction", + "ĠP ati", + "Ġf ron", + "ef ined", + "Ġacid s", + "Ġserv ices", + "Ġs ense", + "Ġag ent", + "hen s", + "elect ric", + "val ues", + "Ġimprove ment", + "here nt", + "ac tic", + "Ġac et", + "cdot s", + "Ġam ino", + "Ġro om", + "Ġexp ress", + "Ġex cept", + "Ġo ld", + "pl ant", + "cep ti", + "ĠP CR", + "ĠE R", + "ĠB oth", + "ve x", + "Ġad ults", + "Ġp seud", + "Ġal le", + "Ġwork s", + "Ġconsum ption", + "ip her", + "c m", + "c ast", + "Ġnan opar", + "Ï ī", + "Ġe con", + "ynam ics", + "Ġal ter", + "Ġsk in", + "Ġdi ameter", + "G C", + "ĠS ign", + "v ial", + "Ġgluc ose", + "ĠN orth", + "ot ox", + "Ġpro te", + "d x", + "ĠC r", + "Ġf ract", + "Ġins ide", + "Ġst atic", + "w id", + "Ġst orage", + "ĠA L", + "ĠM ark", + "ĠA T", + "Ġsens itive", + "Ġad s", + "Ġed ges", + "an a", + "R e", + "Ġsumm ar", + "ĠAN D", + "Ġremain ing", + "dition ally", + "Ġm id", + "ĠThe ory", + "M C", + "Ġf lex", + "ol y", + "Ġdegrad ation", + "Ġint r", + "ot a", + "ism s", + "Ġam pl", + "ĠA re", + "Ġwork ing", + "Ġdivers ity", + "Ġt ensor", + "Ġb inary", + "\"\" \"", + "v als", + "Ġhe m", + "M L", + "Ġμ g", + "ne q", + "ens ities", + "Ġtak es", + "Ġch arg", + "Ġinter vention", + "Ġal b", + "Ġqu al", + "Ġmen tioned", + "Ġon es", + "ĠAcc ording", + "ill ed", + "O H", + "S up", + "Ġgalax ies", + "ail y", + "Ġr ule", + "Ġc ognitive", + "her n", + "Ġrecogn ition", + "Ġbu ffer", + "Ġm arg", + "ĠN i", + "ĠâĪ ļ", + "Ġcl in", + "Ġintegr ation", + "Ġs in", + "ĠAl so", + "Ġmach ine", + "w r", + "id ity", + "Ġsubsequ ent", + "F e", + "Ġn ames", + "at her", + "ĠC y", + "Ġmetabol ism", + "Ġre actions", + "Ġit er", + "Ġnot ed", + "Ġcaus es", + "ĠH amilton", + "g o", + "Ġra re", + "V A", + "ĠM y", + "v ol", + "as ure", + "Ġsignific ance", + "ĠN one", + "Ġve hic", + "S R", + "Ġvari ability", + "ĠDe velop", + "are n", + "Ġprom ot", + "ard s", + "Ġcomput ational", + "Ġsh all", + "iz ations", + "ĠHydro gen", + "Ġprolif eration", + "Ġcou pled", + "ch ron", + "Ġconver gence", + "Ġg ast", + "Ġcalc ulate", + "ra ft", + "par ation", + "her ic", + "ĠP C", + "pl ate", + "p tions", + "ĠAl gorithm", + "Ġresul ted", + "D E", + "Ġinvestig ation", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠ", + "ol ation", + "Ġt asks", + "Ġle g", + "in ess", + "Ġemploy ed", + "O n", + "Ġexper i", + "Ġtra ject", + "G A", + "Ġpur pose", + "ĠN um", + "Ġcomplet ely", + "th at", + "ĠOp tim", + "Ġform al", + "ec k", + "ĠPro tein", + "Ġgo al", + "Ġthrough out", + "Ġconsider ing", + "Ġref lect", + "tre ated", + "or ation", + "rib ution", + "Ġtherap eutic", + "Ġfind ing", + "U N", + "T hen", + "il ities", + "Ġun known", + "ove red", + "Ġver tex", + "Ġex change", + "Ġdrug s", + "ĠC P", + "Ġin str", + "Ġsymmet ric", + "ĠD ep", + "Ġconstruc ted", + "Ġpre valence", + "Ġdecre ases", + "Ġmi R", + "Ġy et", + "Ġb ox", + "g raph", + "wide hat", + "al ian", + "u fact", + "L R", + "cri ption", + "Ġn p", + "ĠChar acter", + "Ġep id", + "Î ½", + "Ġst ages", + "Ġs ay", + "ĠD uring", + "at ur", + "i entif", + "ab ric", + "à ¼", + "am ent", + "in ations", + "Ġsol ar", + "Ġdisc rete", + "ĠE r", + "ĠGen eral", + "b al", + "ĠC ent", + "u el", + "Ġmixt ure", + "Ġwid ely", + "ĠSec ond", + "Ġres ources", + "ĠAp pro", + "ĠI R", + "Ġstr ing", + "op ro", + "Ġin ner", + "ĠCom plex", + "O P", + "Ġat oms", + "Ġph ases", + "Ġdomain s", + "ad a", + "Ġcount ries", + "ac et", + "oci ation", + "iz er", + "Ġits elf", + "Ġmin imal", + "ĠCont rol", + "tt p", + "Ġb ottom", + "b all", + "ĠM ay", + "de v", + "n ow", + "em ber", + "Ġpercent age", + "ĠO ther", + "om as", + "Ġl ed", + "R es", + "ĠEn g", + "k g", + "Ġfrequ encies", + "k in", + "Ġinc idence", + "Ġan imal", + "Ġad op", + "Ġidenti ty", + "ĠR T", + "Ġy oung", + "ist ent", + "we ight", + "g u", + "Ġse ason", + "Ġexplain ed", + "ĠUnd er", + "io tic", + "w ell", + "Ġmetabol ic", + "g ical", + " ±", + "The orem", + "ad es", + "plic ated", + "Ġcontain ed", + "Ġs ulf", + "Ġco ol", + "Ġpers on", + "Ï ģ", + "Ġp ix", + "ĠS al", + "l ink", + "in i", + "t ual", + "S H", + "g ed", + "k y", + "as ts", + "erc ise", + "ĠH ar", + "Ġrel ax", + "equ iv", + "Ġy our", + "Ġund erg", + "Ġrec overy", + "Ġcom m", + "Ġden otes", + "form ed", + "ari a", + "e tic", + "Ġtum ors", + "ĠH y", + "Ġmark ers", + "Ġplac ed", + "ol ute", + "Ġw aves", + "Ġuncertain ty", + "Ġcontrib ute", + "ĠH ist", + "Ġa ver", + "Ġf av", + "Ġp ow", + "ĠSe e", + "Ġte am", + "Ġscal es", + "ientif ic", + "ier arch", + "Ġear lier", + "Ġsatisf ies", + "Ġcryst al", + "Ġpre gn", + "Ġobs erve", + "Ġon line", + "Ġcontrib utions", + "og ram", + "ĠM a", + "Ġf rac", + "Ġsp read", + "Ġon ce", + "d et", + "Ġresp ond", + "Ġpl atform", + "Ġinflamm atory", + "u tive", + "ĠS umm", + "pl ace", + "Ġ ions", + "Ġwind ow", + "ax is", + "est inal", + "Ġdepend ing", + "Ġsepar ation", + "Ġfor ward", + "ĠT i", + "Ġgl ass", + "Ġac cept", + "Ġfeed back", + "Ġon to", + "M E", + "mer c", + "unc tional", + "Ġap optosis", + "ĠProper ty", + "Ġintegr ated", + "Ġor b", + "Ġdevi ation", + "Ġantib odies", + "Ġremov ed", + "Ġlip id", + "arm ac", + "Ġarbit rary", + "ag ger", + "Ġemb ry", + "Ġg rain", + "Ġd rop", + "Ġstar ting", + "Ġrelationship s", + "ĠÏ ĩ", + "S F", + "Ġsim ply", + "Ġfac ilit", + "Ġz one", + "il s", + "Ps i", + "Ġinequ ality", + "Key words", + "Ġto ler", + "ed ge", + "Ġeas y", + "Ġal pha", + "Ġper f", + "wid th", + "in it", + "Ġimplement ed", + "C F", + "os ity", + "ocy te", + "Ġpropor tion", + "re st", + "ĠS uper", + "Ġpre f", + "Ġw ord", + "e v", + "Ġext ent", + "Ġinj ection", + "all ed", + "ĠAn ti", + "Ġb eta", + "ĠJ an", + "ĠG a", + "ĠZh ang", + "Ġ iron", + "Ġqu antitative", + "ro c", + "Ġf all", + "Ġregard ing", + "Ġf ix", + "Ġdatas ets", + "Ġt end", + "Ġscal ar", + "Ġresid ual", + "Ġrati os", + "ĠÎ ¦", + "k ing", + "Ġinflamm ation", + "Ġsing ular", + "ĠP ark", + "om atic", + "unc tions", + "Ġw ar", + "Í Ĵ", + "hem at", + "Ġf ace", + "ĠH u", + "Ġfund amental", + "Ġwa velength", + "el ing", + "ĠS uch", + "RNA s", + "c t", + "Ġid en", + "ce an", + "ne w", + "T ype", + "ĠForm ula", + "Ġmed ic", + "uss ion", + "Ġdist ingu", + "Ġreson ance", + "AT ION", + "ine ar", + "Ġh yd", + "l n", + "â ĨĴ", + "ĠU p", + "Ġact ual", + "Ġadap t", + "hen e", + "Ġm otor", + "l ist", + "ab it", + "I nd", + "ot al", + "Ġneigh bor", + "ĠP T", + "gen er", + "Ġposs ibility", + "erg ies", + "Ġse ems", + "ĠU S", + "Ġim m", + "Ġtyp ically", + "Ġsim ulated", + "ĠSystem s", + "ectiv eness", + "ry ing", + "Ġkin ase", + "Ġdecom position", + "ater al", + "Ġrot ation", + "pen dix", + "en n", + "at t", + "v ate", + "Ġtarget s", + "Ġsit uation", + "Ġinvol ve", + "Ġcre ated", + "hes ized", + "Ġal one", + "c i", + "Ġm L", + "Ġdiv ided", + "Ġbul k", + "o in", + "H C", + "Ġa rm", + "L O", + "ill s", + "Ġmed ian", + "h am", + "im er", + "f lu", + "Ġfib er", + "ĠS U", + "f ile", + "tiv ated", + "Ġradi o", + "ĠN ames", + "p e", + "Ġo ste", + "Ġel im", + "Ġsus cepti", + "re hens", + "Ġdiscuss ion", + "ĠS ep", + "Ġarchitect ure", + "Ġd est", + "t yp", + "r ame", + "Ġpar tition", + "Ġoccur red", + "Ġs izes", + "cl es", + "Ġsc hed", + "M olecular", + "ĠÎ º", + "Ġinv as", + "c up", + "P CR", + "ĠS MILES", + "ti ally", + "ox ide", + "ĠE d", + "Ġman ufact", + "ĠM aterial", + "Ġfl at", + "Ġmut ation", + "Ġintro duce", + "b ound", + "Ġdis orders", + "reg ulated", + "ĠM or", + "Ġf alse", + "ing er", + "ĠT R", + "Ġext rem", + "w ar", + "Ġsym bol", + "Ġan omal", + "ĠA R", + "Ġiss ues", + "Ġcoordin ates", + "Ġrecept ors", + "Ġprog ression", + "ĠF l", + "ubl ic", + "Ġelectron ic", + "Ġasp ects", + "Ġdoc ument", + "f lo", + "ĠP red", + "Ġgraph s", + "Ġtra ditional", + "D M", + "Ġsaf ety", + "ĠD r", + "ĠS equ", + "Ġcompos ite", + "ĠÎ Ľ", + "Ġrespons ible", + "Ġg ran", + "Ġinter mediate", + "od ium", + "pos ite", + "ph ase", + "d t", + "Ġwee k", + "Ġd os", + "Ġst abil", + "L C", + "ĠK ey", + "Ġver tices", + "Ġcomput er", + "ĠCan onical", + "Ġinvari ant", + "em ark", + "b enz", + "Ġ ice", + "ti le", + "z y", + "ĠO ut", + "Ġmove ment", + "Ġsh if", + "le ep", + "Ġd aily", + "Ġpos itions", + "Ġh im", + "Ġcre ate", + "O ur", + "Ġrese arc", + "Ġprog n", + "duc t", + "Ġscreen ing", + "Ġcho ose", + "pro cess", + "m al", + "Ġlabor atory", + "Ġoper ations", + "Ġto ols", + "olog ic", + "q quad", + "Ġcommon ly", + "Ġv oid", + "Ġocc up", + "ass ociated", + "Ġcorrel ations", + "Ġcarcin oma", + "l in", + "Ġv ideo", + "Ġhe avy", + "Ġlarg est", + "Ġm iddle", + "ĉĉ ĉĉ", + "ĠB as", + "as ons", + "id ing", + "Ġet c", + "ac he", + "ĠE val", + "i ra", + "rom agnetic", + "Ġco vari", + "L I", + "Ġde le", + "Ġst ra", + "am ples", + "od er", + "Ġc ategory", + "ĠIn stit", + "Ġpol icy", + "B ased", + "ib ly", + "Ġdeterm ination", + "Ġresp ir", + "otrop ic", + "Ġol der", + "ĠM al", + "Ġcy tok", + "Ġdeg rees", + "a ut", + "ill ing", + "et ing", + "Ġreduc es", + "Ġide al", + "b inding", + "ĠSp ect", + "un it", + "Ġdi ver", + "ĠW orld", + "Ġmark ed", + "al y", + "Ġcomplex es", + "ĠSumm ary", + "Ġpro pose", + "ĠA ustr", + "Ġmax im", + "Ġro und", + "Ġinhib itor", + "Ġeff icacy", + "act or", + "b ur", + "Ġtrans f", + "ĠG al", + "Ġpro ved", + "ĠDef ined", + "A t", + "Ġse lect", + "Ġnanopar ticles", + "W h", + "k en", + "ĠS P", + "en ge", + "Ġdeliver y", + "Ġdis order", + "ĠIn ChI", + "ĠCompar ison", + "if ying", + "ĠM echan", + "Ġconcl ude", + "Ġrepe ated", + "ell ow", + "Ġà Ģ", + "C I", + "ĠH z", + "an alysis", + "T r", + "à Ń", + "eli hood", + "Ġexp and", + "ĠDevelop ment", + "ĠSt ate", + "Ġt et", + "ff ic", + "Ġp arent", + "Ġscenari o", + "r s", + "ĠW hat", + "â ī", + "Ġstim ulation", + "ĠO bs", + "z ero", + "Ġman ner", + "as hed", + "ĠL og", + "Ġox ide", + "ph osph", + "Ġmig ration", + "Ġsub group", + "ros is", + "ip p", + "D R", + "d ec", + "os omal", + "Ġseg ment", + "ogen ous", + "F P", + "h and", + "ĠSur face", + "it z", + "Ġcryst all", + "th is", + "Ġbuild ing", + "t ag", + "Ġreduc ing", + "Ġun s", + "Ġrecom b", + "Ġc am", + "Ġlim its", + "oc ardi", + "& &", + "Ġsepar ate", + "Ġsup plement", + "ke le", + "Ġgra d", + "Ġiss ue", + "ĠQu antum", + "Ġcurrent ly", + "Ġqu ite", + "E P", + "Ġr ules", + "Ġwe ights", + "u ary", + "ill i", + "Ġbec ame", + "à ³", + "Ġnormal ized", + "ĠNetwork s", + "erv ed", + "Ġstat istics", + "ĠT ime", + "ĠU V", + "Ġc av", + "us ed", + "Ġf ish", + "Ġmajor ity", + "ĠP e", + "Ġcoh ort", + "Ġsem i", + "Ġg ame", + "mon ary", + "M M", + "od ed", + "Ġv ent", + "Ġaut o", + "Ġabund ance", + "n ov", + "Ġasympt otic", + "Ġtreat ments", + "ul y", + "Ġconstra int", + "Ġbe y", + "ĠS O", + "Ġst d", + "Ġdevelop ing", + "ĠN ot", + "L emma", + "Ġapp arent", + "Ġcirc uit", + "F rom", + "ĠEurope an", + "Ġsol ve", + "ĠÍ ij", + "u x", + "Ġbey ond", + "ep t", + "Ġapp e", + "requ ency", + "Ġvac u", + "ĠInd eed", + "ĠC hemical", + "ĠUnd efined", + "N ote", + "Ġn ull", + "Ġin verse", + "Ġnam ely", + "Ġshe ar", + "m L", + "A ll", + "R ec", + "Ġgeneral ized", + "ran es", + "ĠT est", + "il ing", + "Ġfluores cence", + "ĠÎ £", + "Ġind epend", + "d iff", + "Ġprovid ing", + "p henyl", + "h ing", + "Ġvir al", + "ĠB ecause", + "Ġint rac", + "ĠH ig", + "Ġw ant", + "Ġprincip le", + "an ol", + "Ġh a", + "ov ascular", + "Ġform er", + "Ġestabl ish", + "Ġadvant age", + "II I", + "Ġsequ encing", + "Ġproced ures", + "t ra", + "in dex", + "f e", + "Ġp i", + "Ġob vious", + "Ġreg ime", + "s ur", + "Ġpres ents", + "Ġdis plac", + "Ġdec l", + "ĠAp pendix", + "Ġinter act", + "land s", + "in ate", + "om orphic", + "Ġlow est", + "Ġar tif", + "Ġinvol ving", + "Ġcom merc", + "Ġd op", + "Ġcon form", + "ĠI g", + "rol og", + "v ised", + "Ġfl o", + "Ġcardi ac", + "p ts", + "r ig", + "Ġens ure", + "Ġaccum ulation", + "Ġent ropy", + "Ġide a", + "per ature", + "Ġques tions", + "ĠP R", + "Ġstat istically", + "d agger", + "Ġn itrogen", + "sc r", + "ĠDisc ussion", + "Ġre ports", + "Ġpul se", + "Ġrequire ments", + "Ġcompar ing", + "qui red", + "l ayer", + "Ġspect roscopy", + "viron ments", + "Ġscal ing", + "Ġex posed", + "M B", + "Î ¾", + "Ġh ole", + "Ġ á", + "Ġsimilar ity", + "Ġvari ants", + "b ody", + "Ġke ep", + "ĠC ancer", + "ed i", + "os omes", + "Ç «", + "A d", + "âĪ ŀ", + "mon ic", + "g ing", + "s plit", + "kn ow", + "Ġro ugh", + "hemat ical", + "v ision", + "Ġd ed", + "Ġcycl es", + "Ġfam il", + "Ġadminist ration", + "et al", + "Ġcor on", + "Ġinf ections", + "Ġmac roph", + "atic s", + "Ġpredic tions", + "is her", + "ere nt", + "re ted", + "incl ude", + "Ġclim ate", + "s ec", + "==== ====", + "ĠM S", + "Ġcomp e", + "r atic", + "l ig", + "pos es", + "Ġpolar ization", + "ll ip", + "der ived", + "Ġrele ased", + "Ġconn ection", + "l ic", + "Ġcol i", + "Ġout side", + "Ġabs olute", + "es ian", + "ĠE nd", + "ĠO f", + "Ġiden tical", + "Ġmod ule", + "Ġmitochond rial", + "Ġadv anced", + "ing ly", + "form ance", + "Ġto ward", + "ud ing", + "e k", + "Ġmean ing", + "c rib", + "ul ator", + "F N", + "k ey", + "c ons", + "Ġapp lying", + "is hes", + "Ġm amm", + "Ġderiv atives", + "Ġorient ation", + "Ġst ochastic", + "ĠA ug", + "Ġre nal", + "ĠG reen", + "Ġcomple ment", + "ob l", + "pir ical", + "or ts", + "B M", + "Ġex cess", + "Ġmorph ology", + "Ġs ound", + "if ier", + "Ġim plications", + "ĠDes ign", + "appro x", + "pro p", + "Ġcandid ate", + "Ġde pos", + "Ġequ ip", + "ust ain", + "ines e", + "et ry", + "Ġpot entially", + "Ġstra ight", + "Ġcr uc", + "i ology", + "Ġk ernel", + "Ġal coh", + "idd en", + "ret urn", + "Ġcorrec tion", + "ro t", + "Ġmic roscopy", + "Ġf oot", + "G L", + "ĠCell s", + "ir th", + "y g", + "ĠP ath", + "out hern", + "ĠL ong", + "Ġre vers", + "Î µ", + "ar se", + "Ġcere b", + "ist ed", + "Ġpul s", + "Ġdis k", + "it ud", + "Ġd u", + "Ġang ular", + "c hem", + "l ength", + "Ġexact ly", + "ro ke", + "ut h", + "Ġcon d", + "ins ic", + "Ġr ise", + "t ake", + "Ġtop ological", + "Ġrem ark", + "oll ary", + "Ġc er", + "T E", + "n ment", + "Ġbu ilt", + "Ġf re", + "Ġen ergies", + "ect ing", + "ĠT em", + "ra red", + "ĠN ow", + "ch arge", + "Ġloc ations", + "Ġbal ance", + "Ġl a", + "Ġre ached", + "lamm atory", + "Ġf abric", + "ific ations", + "Ġdiagnos tic", + "Ġmut ant", + "ĠN O", + "H D", + "ĠA B", + "Ġdisc rim", + "Ġprec ip", + "ĠTh ree", + "Ġins er", + "Ġinf ected", + "Ġconst ants", + "Î ©", + "neg ative", + "Ġconf idence", + "ĠPati ents", + "ollow ing", + "ad s", + "Ġhyper t", + "ĠIntern ational", + "D ef", + "ari ate", + "Ġinter vals", + "Ġex ercise", + "Ġeduc ation", + "Ġremov al", + "ther n", + "st er", + "Ġinte ger", + "ĠP A", + "Ġk id", + "Ġcategor ies", + "ĠG iven", + "Ġv ascular", + "here nce", + "math scr", + "ĠR et", + "Ġins ulin", + "tic ip", + "ĠC F", + "Ġlo ok", + "ymmet ry", + "Ġfor ces", + "ĠPhys ical", + "L S", + "c are", + "Ġh ouse", + "Ġind uce", + "Ġbel ie", + "ri a", + "ĠAs sum", + "Ġcomput ing", + "Ġb us", + "âĪ İ", + "Ġprac tical", + "t rain", + "T T", + "Ġpl astic", + "ĠN or", + "Ġfe as", + "ĠHamilton ian", + "Ġt ail", + "ĠZ n", + "Ġinterpret ation", + "duc ing", + "I s", + "Ġexam ine", + "ul ates", + "Ġmat ch", + "Ġ Ä", + "iv es", + "amet ers", + "Ġμ M", + "Ġexhib it", + "Ġn it", + "ot o", + "ĠCl inical", + "erv ation", + "ĠAd ditionally", + "ar ant", + "Ġel astic", + "D A", + "otop ic", + "Ġactiv ated", + "Ġt er", + "Ġconsequ ence", + "Ġend ot", + "oph ag", + "Ġcompar able", + "Ġdom inant", + "Î ·", + "Ġvalid ation", + "I m", + "Ġ Å", + "Ġle af", + "Ġf ung", + "tain ing", + "Ġun ivers", + "Ġph yl", + "Ġl ibr", + "Ġext ra", + "Ġpr int", + "medi ately", + "Ġmax imal", + "id ae", + "Ġor al", + "b in", + "Ġpepti de", + "ĠM ax", + "ar p", + "Ġconcl usion", + "Ġsatisf y", + "Ġanalyz e", + "o is", + "Ġinf er", + "Ġd raw", + "Ġdep ression", + "Ġmet all", + "Ġpost erior", + "Ġpeak s", + "s ol", + "Ġhorizon tal", + "Ġlater al", + "ĠO R", + "N N", + "Ġem o", + "P V", + "T A", + "Ġincub ated", + "Ġret rie", + "Ġhum ans", + "Ġ ri", + "Ġs oci", + "on ia", + "Ġinter ven", + "Ġvary ing", + "Ġs ti", + "ĠIm mun", + "Ġon set", + "Ġle aves", + "Ġother wise", + "Ġbl ocks", + "Ġass igned", + "SC s", + "Ġbi os", + "Ġmix ing", + "ar a", + "l i", + "Ġde formation", + "Ġcost s", + "Ġper ipher", + "ĠT ra", + "Ġat omic", + "Ġrandom ly", + "Ġarg ument", + "Ġit ems", + "Ġsu ff", + "Ġprob ably", + "n ers", + "Ġinhibit ors", + "Ġbe h", + "ĠDe ep", + "Ġp ig", + "ĠT ype", + "ĠM ost", + "ur a", + "itud inal", + "Ġderiv ative", + "Ġexpl ore", + "ĠIn formation", + "Ġg rap", + "ĠÎ Ķ", + "Ġprog ress", + "******** ********", + "Ġ ul", + "AR S", + "or al", + "os tic", + "C om", + "ĠEx ternal", + "ĠSt atistical", + "ĠR am", + "ĠL o", + "Ġelect rical", + "l ong", + "N et", + "EN T", + "v a", + "à ¤", + "ur ations", + "Ġdes ired", + "ir ing", + "Ġphys ics", + "Ġmass es", + "k i", + "Ġband s", + "Ġal k", + "ĠSimilar ly", + "Ġsur round", + "Ġcon vex", + "ost er", + "Ġlink ed", + "Ġfocus ed", + "Ġh ot", + "Ġmat ching", + "Ġoxid ation", + "Ġan ten", + "m iss", + "Ġm ental", + "il le", + "ici ency", + "ĠLi u", + "Ġprob e", + "ĠEs tim", + "Ġindic es", + "c he", + "ĠR ob", + "Ġcon v", + "ĠV er", + "ap se", + "S i", + "ph al", + "Ġles ions", + "Ġmolec ule", + "Ġa di", + "Ġd ate", + "Ġcompos ed", + "Ġa ud", + "struct ure", + "ot on", + "in for", + "Ġclust ering", + "ac ent", + "st ar", + "P O", + "ĠCh inese", + "Ġspec ifically", + "eren tial", + "Ġcap ture", + "ĠL ow", + "Ġf ine", + "Ġfem ales", + "ĠH ow", + "Ġa er", + "v ector", + "port un", + "form s", + "z o", + "Ġprec ision", + "yp t", + "Ġmin utes", + "Î º", + "Ġoxid ative", + "con n", + "ens us", + "Ġtrac e", + "Ġcon jug", + "Ġhigh light", + "s s", + "ĠExperim ental", + "ĠTh at", + "art ment", + "M O", + "' '", + "omet er", + "Ġst op", + "Ġ rib", + "Ġout er", + "r h", + "ri pt", + "Ġfluct uations", + "ob s", + "n on", + "Ġqu ark", + "Ġà °", + "ĠM ac", + "Ġperiod s", + "roll ed", + "A V", + "ĠO c", + "ĠIm age", + "ĠB el", + "Ġpropag ation", + "ĠD on", + "ww w", + "gl ish", + "Ġexhib ited", + "ogene ity", + "ĠB ack", + "Ġac tions", + "sk i", + "ĠAm ong", + "Ġb rief", + "ri ers", + "ĠN F", + "pos itive", + "sequ ently", + "ul ence", + "Ġen vironments", + "Ġcur v", + "om ics", + "Ġb it", + "Ġg el", + "Ġrepresent ations", + "Ġa way", + "ĠF ield", + "ob ic", + "C G", + "Ġcomp rehens", + "Ġh ierarch", + "Ġinduc es", + "B D", + "Ġh app", + "Ġe ight", + "Ġgra vity", + "Ġadap tive", + "B L", + "gen ic", + "Ġin struc", + "Ġanaly tical", + "ĠO x", + "ĠC ON", + "Ġsur gical", + "Ġd ip", + "at o", + "Ġrandom ized", + "Ġro les", + "d ep", + "ĠâĪ ĩ", + "ch ang", + "Ġdispers ion", + "Ġsepar ated", + "ĠOr gan", + "ĠV i", + "ĠJoh n", + "Ġan not", + "Ġres ource", + "en ergy", + "rel ation", + "me an", + "ĠB en", + "Ġconfir m", + "W ith", + "Ġinf inite", + "ĠSc ience", + "Ġsuccessful ly", + "Ġlocal ization", + "m ode", + "h ttps", + "geb ras", + "idel ines", + "Ġeff ectiveness", + "h yd", + "Ġs aid", + "ic o", + "Ġtrans itions", + "ed ing", + "Ġprogram s", + "Ġmob ile", + "Ġim mediately", + "ec tivity", + "ĠThe rm", + "ogene tic", + "Ġse ven", + "Ġem ph", + "G E", + "ne um", + "Ġf usion", + "lim its", + "Ġcalc ium", + "ra f", + "min us", + "Ġt rap", + "Ġspecim ens", + "anc ing", + "ĠM arch", + "Ġt en", + "Ġfamil ies", + "ĠH D", + "is ons", + "Ġpre paration", + "h old", + "et her", + "ĠV ol", + "ĠD ise", + "Ġrun ning", + "Ġqual it", + "Ġeff ectively", + "ffici ently", + "B I", + "Ġden oted", + "ĠEqu ation", + "Ġdem and", + "it ory", + "ach ing", + "Ġs odium", + "Ġrepro duc", + "ch o", + "Ġb il", + "P i", + "um b", + "Ġreconstr uction", + "for ward", + "O ne", + "Ġcon version", + "Ġform ulation", + "Ġnear ly", + "ĠL ag", + "S tr", + "ter ior", + "Ġoper ating", + "and om", + "Ġmov ing", + "ĠRe view", + "// //", + "n ai", + "p p", + "oti de", + "lab el", + "oc occ", + "Ġne ver", + "ak er", + "Ġdig ital", + "B l", + "U n", + "Ġmem ber", + "s el", + "Ġpot enti", + "Ġcop y", + "Ġelect rons", + "ch lor", + "ann el", + "yl ind", + "Ġm is", + "ĠS et", + "Ġnut ri", + "Ġdescrib es", + "Ġassum ptions", + "Ġvir tual", + "Ġcoordin ate", + "Ġv or", + "ĠA rab", + "ĠIm p", + "Ġde position", + "Ġins tit", + "Ġrepresent ative", + "ever al", + "Ġmill ion", + "ĠM A", + "Ġmal es", + "Ġcruc ial", + "Ġcol d", + "Ġload ing", + "Ġtrans lation", + "Ġst ead", + "ra ys", + "Ġchall enge", + "ac tivity", + "id al", + "u ff", + "Ġse em", + "Ġn ational", + "Ġf a", + "Ġmin or", + "Ġunderg o", + "c r", + "Ġcap t", + "e le", + "up le", + "ĠM g", + "le ge", + "G R", + "Ġr ig", + "Ġar ri", + "Ġdet ector", + "Ġst rict", + "Ġad hes", + "Ġse a", + "the less", + "Ġs leep", + "ĠCom mun", + "Ġanti oxid", + "Ġmark er", + "Ġflow s", + "anc re", + "ĠJan uary", + "in put", + "U P", + "Ġst ored", + "ad ing", + "iti vely", + "Ġsl ope", + "Ġshe ll", + "Ġelev ated", + "il k", + "Ġfrequ ently", + "Ġb all", + "urb an", + "Ġm l", + "us ive", + "ĠA nt", + "am ino", + "S im", + "Ġphys iological", + "reg ulation", + "es ity", + "Ġexpl an", + "Ġad en", + "re me", + "Ġdiff er", + "Ġmod ification", + "Ġir radi", + "H e", + "ac ial", + "Ġsupp ress", + "qu is", + "Ġd ry", + "er ated", + "Ġpro jection", + "Ġpo ol", + "ple te", + "Ġdirec tions", + "Ġchang ed", + "ĠI ts", + "Ġst er", + "Ġradi al", + "Ġg r", + "Ġperiod ic", + "Ġb in", + "Ġp ip", + "m en", + "t hen", + "p c", + "am ily", + "ĠD M", + "Ġsed iment", + "m i", + "Ġclos ely", + "Ġrep air", + "Ġrespir atory", + "Ġh orm", + "A ns", + "d r", + "l s", + "Ġhom ogeneous", + "et ric", + "D S", + "Ġresid ues", + "ĠVal ue", + "F s", + "Ġwh y", + "S p", + "Ġc a", + "Ġn arrow", + "g ent", + "Ġb r", + "Ġqu asi", + "Ġp ict", + "m o", + "Ġat om", + "Ġh abit", + "Ġlimit ations", + "con duc", + "Ġsh ock", + "cept or", + "ĠDet ection", + "S h", + "ub e", + "Ġe llip", + "U R", + "Ġstain ing", + "Ġrapid ly", + "ĠB ur", + "ĠB ro", + "Ġup take", + "Ġchalleng es", + "S N", + "Ġan is", + "Ġbound s", + "st ep", + "omer ic", + "ten tion", + "ĠEval uation", + "Ġrecomm end", + "M e", + "Ġmod erate", + "ell ed", + "Ġt it", + "ĠY ang", + "Ġph armac", + "inf lammatory", + "ĠJ une", + "Ġsens ors", + "ai red", + "Ġapproxim ate", + "S V", + "Ġb und", + "r c", + "om an", + "Ġvis ible", + "Ġmeas uring", + "og onal", + "ĠFour ier", + "Ġthe ories", + "Ġprof ession", + "tain ed", + "at as", + "ĠInt erest", + "par am", + "ĠStruct ure", + "Ġl iving", + "D ata", + "ĠS M", + "Ġn et", + "Ġsimultaneous ly", + "cont inu", + "Ġsh or", + "#### ####", + "Ġdecre asing", + "Ġrefer red", + "g g", + "Th us", + "Ġd ro", + "pr il", + "ĠP ers", + "Ġenc oding", + "Ġar c", + "Ġregul atory", + "Ġtra ined", + "cep ts", + "Ġro ut", + "ly s", + "P ar", + "ĠU l", + "ĠG raph", + "âĪ Ĥ", + "Ġir re", + "oid al", + "Ġex ceed", + "Ġmost ly", + "ĠP at", + "ater nal", + "Ġ er", + "Ġco verage", + "F S", + "ĠR ot", + "Ġclass ified", + "Ġexc itation", + "Ġconduc tivity", + "Ġcommerc ial", + "ĠD el", + "ĠP olar", + "H R", + "Ġtra ffic", + "z ing", + "Ġsetting s", + "Ġincl usion", + "Ans wer", + "Ġv it", + "vit ational", + "Ġb ind", + "Ġo c", + "ĠW estern", + "Ġpro sp", + "Ġn orth", + "it ch", + "ĠR iver", + "Ġvehic le", + "Ġlik elihood", + "L D", + "Ġin sp", + "âĪ Ĩ", + "Ġle uk", + "ĠB re", + "Ġsynt hetic", + "ĠGerm any", + "ĠThe ir", + "t arget", + "ĠEn glish", + "Ġnot ation", + "ĠA TP", + "ĠMod els", + "Ġab normal", + "ĠConcl usions", + "Ġoccur rence", + "Ġmicro bi", + "ĠW ar", + "tem ber", + "Ġloc ally", + "bor n", + "Ġbar rier", + "Ġexpression s", + "ov al", + "Ġfl av", + "emb le", + "Ġdynam ical", + "Ġphot on", + "app ed", + "Ġgl ut", + "Ġkine tic", + "Ġalcoh ol", + "Ġtrans plant", + "L P", + "Ġdef ault", + "Ġop portun", + "arg s", + "ĠD av", + "Ġfron t", + "h om", + "Ġw ays", + "ĠAss ociation", + "Ġkid ney", + "Ġpropor tional", + "W hen", + "Ġepit helial", + "Ġf resh", + "Ġrec all", + "Ġenzym es", + "b r", + "Ġmal ign", + "text rm", + "ĠU se", + "N ow", + "ĠL ie", + "Ġimp air", + "Ġgu arant", + "Ġin ver", + "Ġtranscri pt", + "Ġs ustain", + "Ġact ually", + "al ities", + "ĠM ic", + "ĠI C", + "ĠMe asure", + "Ġï£ ´", + "Ġd ensities", + "Ġgalax y", + "Ġsu fficiently", + "Ġor bit", + "f ord", + "Ġpar tially", + "ĠP y", + "Ġre verse", + "Ġsur ve", + "ĠW ork", + "Ġas k", + "H owever", + "Ġsit u", + "Ġvacu um", + "to ber", + "Ġsp ac", + "an th", + "O r", + "ag s", + "Ġb ig", + "her ical", + "er ge", + "ell ite", + "Ġinvol ves", + "ĠV is", + "Ġsumm ary", + "ĠSup plementary", + "ĠC oll", + "Ġadj acent", + "ont aneous", + "ab s", + "Ġresearc hers", + "k a", + "Ġinter n", + "Ġmon th", + "ĠNe ural", + "ap or", + "ĠN an", + "Ġst ri", + "E E", + "Ġconsist ing", + "Ġupd ate", + "Ġphot o", + "V al", + "s ens", + "Ġve get", + "B R", + "Ġco inc", + "ĠJ uly", + "til ity", + "ĠEx pression", + "Ġtop ology", + "Ġgrow ing", + "ap tic", + "uc ed", + "Ġperipher al", + "en es", + "Ġpl ots", + "Ġexpl o", + "Ġw or", + "b a", + "ati tis", + "i ef", + "w ave", + "Ġprot ection", + "Ġdef ects", + "Ġads orption", + "Ġsh ared", + "Ġst ellar", + "ĠB a", + "ĠEn ergy", + "que ous", + "ĠAug ust", + "Ġl ys", + "Ġpl us", + "i rel", + "ĠG P", + "ĠNe u", + "d ist", + "g ers", + "if er", + "is p", + "Ġstr at", + "ion e", + "ĠMaterial s", + "Ġl n", + "Ġpul monary", + "en ed", + "pl an", + "M od", + "Ġorgan ization", + "Ġrelax ation", + "Ġcor tex", + "Ġmod ulation", + "og l", + "sh ift", + "Ġsec urity", + "Ġfat ty", + "Ġm s", + "l ocal", + "erg ic", + "Ġinter ference", + "ins on", + "c f", + "Ġre asons", + "p red", + "Ġinterven tions", + "Ġj o", + "ĠI D", + "ĠAre a", + "ĠH a", + "u its", + "out put", + "L e", + "y cl", + "in ted", + "Ġnan o", + "N C", + "ĠC ap", + "Ġchang ing", + "Ġc ust", + "Ġappe ared", + "Ġgrow n", + "ĠU K", + "Ġrad ical", + "ĠP ot", + "ĠPro gram", + "ĠS R", + "Ġsh ap", + "os cop", + "ĠCh ang", + "Ġquanti ty", + "ĠT axon", + "id ation", + "Ġadd ing", + "ĠLe e", + "Ġam ounts", + "Ġdes pite", + "Ġremain ed", + "Ġscenari os", + "le ts", + "om ing", + "Ġcurv ature", + "Ġd imensional", + "Ġprom ising", + "ĠF il", + "str ing", + "Ġattrib uted", + "ym er", + "Ġneigh b", + "Ġinput s", + "Ġmagn et", + "Ġtre es", + "Ġent er", + "r uit", + "st able", + "to plas", + "Ġmess age", + "roph ic", + "Ġisol ates", + "t z", + "Ġdisplay ed", + "H A", + "oc l", + "Ġder ive", + "Ġsyn chron", + "Q U", + "à ŀ", + "Ġexam ination", + "Ġde b", + "Ġdef in", + "Ġf ault", + "Ġstead y", + "Ġphen otype", + "Ġpers pective", + "Ġstat ement", + "d f", + "v oid", + "Ġprom ote", + "ill ary", + "ĠE th", + "Ġw alk", + "Ġrepresent ing", + "Ġgen omic", + "ĠG r", + "sh ape", + "ĠP et", + "ĠL ocal", + "plic ity", + "ĠProb lem", + "G S", + "Ġcomple ted", + "ink ing", + "Ġread s", + "Ġin de", + "ce ived", + "ĠP L", + "ĠMe an", + "ĠSch ool", + "Ġbiom ark", + "irel ess", + "c ut", + "os ing", + "n el", + "ĠA pril", + "ĠB al", + "Ġadop ted", + "Ġcom plications", + "Ġassemb ly", + "f ort", + "h ar", + "Ġad oles", + "Ġans wer", + "Ġcommun ities", + "ĠInstit ute", + "Ġvari ant", + "F inally", + "mit te", + "Ġrestric ted", + "Ġman ip", + "at ers", + "E X", + "Ġd ust", + "Ġsupp ly", + "Ġper me", + "Ġreli able", + "ĠRes p", + "Ġsub t", + "o ks", + "Ġpol l", + "Ġcan c", + "ĠUn it", + "Ġendot helial", + "d y", + "ĠBl ack", + "Ġem pirical", + "Ġp ort", + "op y", + "Ġiniti ally", + "Ġcond ens", + "Ġe ye", + "Ġlist ed", + "ur rence", + "Ġreplac ed", + "Ġselec tive", + "Ġdist ances", + "Ġpar as", + "ĠP ost", + "ĠSep tember", + "Ġmiss ing", + "vere x", + "E r", + "Ġthough t", + "th al", + "Ġchrom at", + "Ġbenef it", + "ram es", + "ĠSup pose", + "Ġsub s", + "Ġang i", + "or i", + "Ġre plic", + "Ġschem es", + "p re", + "pl ane", + "Ġs outh", + "ag er", + "Ġbegin ning", + "v ents", + "on ent", + "i ples", + "ĠH er", + "Ġspect rom", + "Ġden se", + "Ġto ok", + "iver se", + "Ġdist urb", + "p ass", + "Ġillustr ated", + "Ġreve als", + "am a", + "Ġref lec", + "Ġallow ing", + "Ġexpon ential", + "ous tic", + "subset eq", + "Ġs n", + "Ġ urban", + "Ġext end", + "Ġass ays", + "ric e", + "Co V", + "quis ition", + "r ine", + "ĠIn tegr", + "f il", + "V D", + "Ġfib ro", + "Ġcomp ens", + "ĠIm pro", + "ĠĠĠĠĠĠĠĠ ĠĠ", + "ĠG R", + "Ï Ī", + "Ġbas al", + "Ġol ig", + "H T", + "Ġv ess", + "uz zy", + "Ġposs ibly", + "Ġtoler ance", + "The ta", + "Ġvi ol", + "uc lear", + "ĠL im", + "g el", + "Ġmetric s", + "ĠM us", + "am ination", + "Ġelectro de", + "Ġpers onal", + "Ġcool ing", + "Ġac quired", + "ĠF unction", + "ow s", + "oles ter", + "D P", + "Ġreli ability", + "Ġm uc", + "ĠOc tober", + "Ġg old", + "c a", + "Ġc ul", + "f it", + "Ġle m", + "Ġexc it", + "Ġnucle us", + "i ation", + "Ġpregn ancy", + "Ġsynt hesized", + "hem istry", + "Ġmemb ranes", + "ver t", + "ĠK im", + "ten ance", + "Ġquanti ties", + "Ġecon omic", + "Ġbenef its", + "Ġc ylind", + "pl er", + "ĠL arge", + "Ġengine ering", + "ĠE p", + "Ġco ating", + "ati v", + "Ġconduc t", + "Ġabs orb", + "ĠDec ember", + "Ġop posite", + "ĠGl obal", + "Ġl if", + "ĠD ue", + "Ġint ake", + "od ynamic", + "T M", + "Ġf ed", + "Ġspec ified", + "Ġge ometric", + "Ġresp ective", + "Ġb irth", + "ĠComp ound", + "Ġstar ted", + "Ġm other", + "ar r", + "Ġprim arily", + "Ġp aren", + "Ġtub e", + "Ġinter s", + "Ġgrap hene", + "iti al", + "ous ly", + "Ġcardi ovascular", + "Ġe V", + "Ġhe ating", + "Ġmat hematical", + "Ġindepend ently", + "B A", + "Ġaff ects", + "um or", + "ĠM P", + "ĠD em", + "ĠW est", + "ĠD om", + "it ter", + "Ġdis rup", + "op ed", + "Ġphenomen on", + "Ġl umin", + "A c", + "Ġpre fer", + "om ers", + "Ġg ender", + "ĠG L", + "F C", + "Ġinde ed", + "Ġr ational", + "ĠS C", + "Ġprincip al", + "Ġperf ect", + "Ġintro duction", + "t es", + "Ġpi ec", + "Ġc ity", + "Ġpop ular", + "Ġc oding", + "cl er", + "ag ue", + "ĠH R", + "Ġtrack ing", + "k er", + "Ġphosphor ylation", + "Ġpath s", + "Ġsol ving", + "Ġd y", + "Ġplay ed", + "Ġprec ise", + "ĠS l", + "ĠS em", + "Ġgener ating", + "ĠS un", + "Ġcriter ion", + "Ġbran ch", + "ĠÎ ¶", + "ti sh", + "S e", + "Ġanti gen", + "Ġcal ibration", + "E s", + "ĠI tal", + "Ġmass ive", + "E n", + "N o", + "Y P", + "y a", + "Ġsatisf ying", + "Ġqu ick", + "H O", + "Ġbehavi ors", + "icro bial", + "Ġam b", + "Ġpro ton", + "S L", + "Ġus ual", + "row s", + "en ch", + "U C", + "Ġweight ed", + "Ġrec ords", + "ĠA C", + "G T", + "in n", + "Ġe q", + "ĠW il", + "y roid", + "Ġset up", + "I A", + "p ress", + "is ely", + "Ġent ry", + "% %", + "ĠS il", + "e ast", + "ĠE volution", + "ĠR andom", + "Ġcav ity", + "Ġnam ed", + "know led", + "m ber", + "ues tion", + "ĠâĪ ©", + "g i", + "Ġdeterm ining", + "t in", + "Ġgen us", + "Ġtox icity", + "oc yt", + "Ġperturb ation", + "rough t", + "ĠB ri", + "Ġcar b", + "ĠG ra", + "ĠF lu", + "un s", + "Ġdri ven", + "Ġb atch", + "r if", + "P l", + "Ġdisplac ement", + "ĠC L", + "Ġdep ic", + "Ġpredic tive", + "I nt", + "hydro xy", + "ti d", + "d ri", + "Ġp ancre", + "Ġdiag onal", + "Ġsever ity", + "Ġlong itudinal", + "ĠE D", + "ati ble", + "d ir", + "ĠAn other", + "ĠH el", + "v an", + "Ġp neum", + "Ġspecific ity", + "s qu", + "Ġ ign", + "Ġb ed", + "ĠW T", + "aw a", + "es ter", + "Ġk g", + "Ġcomp ression", + "ever theless", + "Ġm ask", + "-------- ---", + "Ġt ens", + "row th", + "ĠG o", + "Ġf aster", + "Ġcan onical", + "Ġdeterm in", + "ust rial", + "ĠEar th", + "wh ile", + "our nal", + "Ġcount ry", + "Ġf erm", + "r ist", + "Ġpro xim", + "Ġmicro bial", + "Ġext ensive", + "Ġch am", + "Ġ §", + "s uch", + "w ent", + "Ġl ar", + "U sing", + "ĠP M", + "Ġoff set", + "ĠP I", + "ĠBay esian", + "H S", + "ĠAfric a", + "Ġsuscepti bility", + "ĠâĬ Ĥ", + "ococc us", + "ĠD ir", + "Ġb os", + "Ġdys function", + "ove mber", + "Ġunder st", + "Ġlarg ely", + "ĠC M", + "Ġmaintain ed", + "Ġposs ess", + "Ġexcl uded", + "ens is", + "ĠD C", + "ops is", + "Ġtor ch", + "id ine", + "Ġfore st", + "ĠEx act", + "ĠStud ies", + "iff iff", + "ĠC am", + "ang ular", + "Ġrem ove", + "o ir", + "av a", + "id a", + "Ġm ant", + "L og", + "Ġrang ing", + "ro g", + "Ġchain s", + "Ġ Ç«", + "ĠC ase", + "ĠA P", + "po ints", + "Ġtarget ing", + "Ġsc ience", + "Ġep is", + "ĠS oci", + "Ġphys ic", + "Ġpromot er", + "ĠEar ly", + "es tic", + "tiv es", + "Ġassum ing", + "ĠM i", + "Ġlem ma", + "Ġconfig urations", + "al ia", + "Ġp ay", + "r ino", + "e b", + "Ġvari ed", + "oun ted", + "Ġinter view", + "ĠGe V", + "O M", + "ogn ition", + "Ġenhance ment", + "ĠM ach", + "pl ies", + "O b", + "set minus", + "Ġintr insic", + "Ġcompar isons", + "b old", + "xi ety", + "Ġst roke", + "G B", + "anc ial", + "ste ad", + "Ġro ck", + "th on", + "ĠC urrent", + "c at", + "Ġgu idelines", + "cy cl", + "Ġintrac ellular", + "one y", + "k o", + "Ġdirec ted", + "rip ts", + "Ġtra vel", + "Ġl ens", + "id i", + "ĠAss ess", + "Ġd x", + "ĠP os", + "Ġmethod ology", + "Ġpred om", + "def ined", + "ĠP op", + "Ġgover nment", + "ell ig", + "ph yl", + "ol i", + "rop ical", + "Ġembed ded", + "ed om", + "crib ed", + "ĠDise ase", + "Ġmedi ated", + "Ġcirc ular", + "ĠTop ological", + "Ġear th", + "ri tis", + "g al", + "m ass", + "Ġcomprehens ive", + "ĠA ir", + "Ġn erve", + "Ġimpl ant", + "Ġextrem ely", + "ĠS E", + "Ġmark et", + "Ġconserv ed", + "emb rane", + "Ġsched ul", + "Ġrun s", + "P h", + "Ġtechn ical", + "T L", + "Ġregion al", + "Ġg erm", + "ĠPro t", + "Ġb right", + "Ġarter y", + "Ġmacroph ages", + "mitte e", + "ĠSing le", + "Ġcom e", + "w a", + "ac char", + "ple t", + "Ġsens ing", + "ros p", + "at om", + "Ġcomp r", + "ĠL u", + "Ġavail ability", + "pro t", + "Ġfit ting", + "sel ves", + "ĠP rim", + "re w", + "Ġwas te", + "ĠK ing", + "p ot", + "Ġinstr ument", + "ĠY ork", + "A F", + "an tial", + "stand ing", + "Ġpl anning", + "ust er", + "Ġâ Ĩ", + "N T", + "ic ular", + "Ġmel an", + "Ġexc ell", + "ill er", + "ĠL D", + "inf o", + "Ġsh are", + "v as", + "Ġl um", + "Ġa queous", + "Ġqu ery", + "Ġm ag", + "ult ure", + "ĠB er", + "Ġof fer", + "ĠN MR", + "ace ae", + "Ġmod ern", + "Ġcirc um", + "Ġcult ures", + "Ġd og", + "Ġc ir", + "Ġpol i", + "Ġchem otherapy", + "Ġpl ates", + "Ġrestric tion", + "st ack", + "ĠF low", + "ĠB u", + "ĠC enter", + "Ġpro ceed", + "tim icrobial", + "s he", + "Ġthere by", + "Ġkn ock", + "Ġdi verse", + "ustr y", + "Ġst ated", + "ĠH ol", + "M ore", + "Ġconserv ation", + "Ġpre vention", + "n orm", + "Ġp al", + "ĠCal c", + "Ġcle an", + "ĠPl as", + "`` `", + "per p", + "pro d", + "Ġâī ¡", + "por ter", + "Ġtrans ient", + "as p", + "Ġtarget ed", + "ĠP ri", + "Sup plementary", + "ĠT reatment", + "z en", + "ĠM art", + "ĠF erm", + "us cript", + "ĠS ynthesis", + "Ġcomb inations", + "UL L", + "Ġwe b", + "Ġth rom", + "Ġexplicit ly", + "an ks", + "Ġadapt ation", + "ĠSequ ence", + "Ġac ts", + "Ġrang es", + "f s", + "b ru", + "Ġsystem ic", + "Ġste el", + "Ġpri vate", + "Ġob esity", + "ĠP art", + "ment ed", + "bre ak", + "ER T", + "Ġfib ers", + "Ġis o", + "Ġtrans verse", + "CT ION", + "ĠR i", + "it in", + "ĠRep resent", + "oph ys", + "Ġco ast", + "Ġal ignment", + "AC T", + "es ides", + "op en", + "g ly", + "Ġsal t", + "unc ed", + "ia z", + "Ġcos m", + "Ġang les", + "ĠâĢ ł", + "ĠIdentif ication", + "he x", + "ĠH all", + "Ġhep at", + "Ġseg ments", + "ĠPh ase", + "ĠL and", + "form ing", + "h box", + "ic ations", + "Ġsubsequ ently", + "ĠC ur", + "Ġlab els", + "vid ence", + "ual ity", + "Ġhe ld", + "em ann", + "Ġcam era", + "c ing", + "ub ic", + "ĠS ARS", + "ul atory", + "kele tal", + "ĠInf lu", + "ĠInd ia", + "am ic", + "Ġs and", + "Ġcom es", + "Ġassoci ations", + "Ġcharg ed", + "Ġs per", + "opro tein", + "ii i", + "od al", + "Ġbound aries", + "ti zation", + "ĠHe avy", + "ĠRe al", + "ĠA F", + "Ġcontroll er", + "Ġantioxid ant", + "Ġb ars", + "Ġw et", + "en er", + "ĠComplex ity", + "Ġst ack", + "There fore", + "Ġre plication", + "Ġappear ance", + "Ġtraject ory", + "Ġunderst ood", + "Ġd ot", + "Ġim ag", + "Ġsc anning", + "T i", + "r uct", + "ĠL y", + "Ġsp ontaneous", + "l at", + "om on", + "Ġro ots", + "Ġl ive", + "Ġfin ally", + "¿ ½", + "Ġappro ved", + "ĠAp plications", + "ĠP an", + "Ġl ost", + "Ġsatisf ied", + "Ġg amma", + "ion al", + "Ġimpro ving", + "Ġmanif old", + "Ġc odes", + "b b", + "ĠN ovember", + "Ġr ich", + "N P", + "ĠE le", + "S B", + "Ġde al", + "Ġop tions", + "Ġcult ured", + "Ġv ul", + "> >", + "ar ithm", + "o ys", + "The se", + "ĠDet erm", + "Ġquad ratic", + "ĠCom b", + "iss on", + "ĠPer formance", + "Ġex ception", + "Ġnucle i", + "Ġad verse", + "k et", + "ĠP al", + "ĠM any", + "Ġdiff raction", + "Ġtrans mit", + "Ġphosph ate", + "olester ol", + "Ġquestion nai", + "ĠSe a", + "bru ary", + "Ġmod elling", + "ĠD R", + "ol in", + "ch mark", + "Ġprec isely", + "g ans", + "v in", + "rid ge", + "ĠInc re", + "Ġneur onal", + "Ġâī Ī", + "Ġexcell ent", + "et ary", + "Ġoverl ap", + "Ġstrong er", + "Ġfract ure", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠ", + "Ġclin ic", + "ĠL ist", + "Ġhist or", + "gen eration", + "ric hed", + "ill us", + "Ġà ħ", + "ĠR ole", + "Ġlabel ed", + "Ġorth ogonal", + "Ġis chem", + "Ġinst ability", + "lo op", + "Ġplot ted", + "ĠProcess ing", + "ĠT a", + "ĠConcl usion", + "Ġm agne", + "Ġunivers al", + "Ġj et", + "Ġreg im", + "flo at", + "Ġc od", + "ad j", + "bold math", + "Ġar rang", + "Ġtrend s", + "Ġprecip itation", + "f requency", + "Ġcont rad", + "Ġtransfer red", + "Ġmain tenance", + "Î Ķ", + "n p", + "ist ence", + "he res", + "lec tive", + "ĠSur vey", + "Ġ Ð", + "Ġst and", + "Ġdisc overy", + "ain s", + "vers ely", + "Ġnumer ous", + "yl ated", + "Ġembed ding", + "Ġcoll abor", + "en ame", + "im mun", + "Ġadjust ed", + "i res", + "c ur", + "Ġvacc ine", + "Ġtra its", + "Ġmorph ological", + "Ġprec urs", + "roscop e", + "ad i", + "ec utive", + "u an", + "Ġt ract", + "ĠP res", + "Ġmy el", + "Ġad equ", + "Ġeth anol", + "i h", + "Ġmet h", + "Ġcoun ts", + "Ġqualit ative", + "Ġmus ic", + "Ġre infor", + "A fter", + "Ġac quisition", + "Ġh ttps", + "all ing", + "it a", + "ic ate", + "sc ript", + "Ġoptim ized", + "ĠH o", + "Ġm ild", + "opl as", + "Ġo verex", + "ĠâĪ §", + "Ġcol lect", + "ĠM ain", + "Ġextrac ellular", + "Ġan c", + "ra wn", + "Ġexpl ored", + "Ġres erv", + "ĠAp plication", + "c ase", + "Ġmar ine", + "ĠĠĠĠĠĠĠĠ ĠĠĠĠĠ", + "il ed", + "Ġmes h", + "ĠMon te", + "cl os", + "Ġperform ing", + "A g", + "reg ular", + "Ġc atal", + "Ġpotenti als", + "ant ly", + "U RE", + "Ġacc omp", + "Ġreason able", + "Ġpresent ation", + "abol ic", + "ĠOn ly", + "ann ed", + "Ġsubst antial", + "Ġdiet ary", + "Ġsubstr ates", + "ap ter", + "Ġint estinal", + "Ġproduc es", + "Pro position", + "ro gen", + "ĠSt at", + "bur g", + "ren ch", + "text bf", + "ystem s", + "at able", + "ĠV ir", + "Ġsol ved", + "ic ense", + "Ġs ong", + "Ġext reme", + "pt y", + "ĠC ity", + "ve red", + "ĠMR I", + "Ġtw ice", + "ĠM n", + "Ġm erg", + "ac tivation", + "Ġn g", + "Ġo dd", + "Ġatt rac", + "Ġatt empt", + "Ġsepar ately", + "Ġrob ot", + "ĠMulti ple", + "Ġsc ientific", + "ĠP P", + "Ġmin eral", + "Ġprotoc ols", + "Ġsuper ior", + "oc amp", + "box yl", + "Ġuniform ly", + "ĠS everal", + "Ġm ol", + "C or", + "under line", + "Ġinflu enced", + "Ġcur ren", + "us ing", + "rac e", + "ĠN evertheless", + "Ġacc om", + "Ġgra vitational", + "Ġindi rect", + "Ġcap able", + "Ġanalys ed", + "Ġdis charge", + "Ġv es", + "Ġlig and", + "l ik", + "Ġs i", + "Ġag ed", + "Ġcryst als", + "Ġspe ech", + "Ġcop per", + "ĠS an", + "ĠA rm", + "Ġman uscript", + "Ġsec retion", + "w edge", + " ·", + "Ġra w", + "Ġaim ed", + "Ġevolution ary", + "Ġconsequ ences", + "Ġit em", + "Ġw estern", + "Ġsol vent", + "Ġstim uli", + "Ġrequire ment", + "h ttp", + "ef ore", + "ĠAt l", + "Ġatmosp heric", + "Ġpack age", + "Ġmy ocardi", + "Ġd ashed", + "Ġver ify", + "ativ istic", + "Ġto m", + "av irus", + "ak en", + "ĠNum er", + "Ġadvant ages", + "F R", + "ĠS elf", + "rec ted", + "con fig", + "Ġit eration", + "Ġeigen values", + "Ġprob abilities", + "F IG", + "ĠW ater", + "ĠA u", + "Ġg ave", + "Ġv ar", + "ric ular", + "opath y", + "Ġr h", + "ord ance", + "Ġw in", + "ĠS cale", + "Ġann ual", + "atas et", + "Ġp el", + "ĠâĪ ª", + "ĠC C", + "it ors", + "Ġl ith", + "Ġchrom osome", + "Ġf uel", + "Ġmul tiv", + "Ġmanufact ure", + "l a", + "ĠS a", + "um es", + "ig m", + "Ġnan oc", + "E GF", + "Ġsign ature", + "N S", + "Ġme et", + "Ġf air", + "met h", + "Ġlocal ized", + "ĠCent ral", + "de g", + "Ġsurround ing", + "Ġn one", + "ĠM O", + "ĠInterest ingly", + "Ġmul tic", + "ĠK e", + "Ġinhib ited", + "ĠC are", + "ĠOp en", + "Ġgl ob", + "E A", + "ĠF ound", + "Ġpix el", + "ok e", + "R D", + "l oc", + "ti ous", + "Ġdistingu ish", + "Ġan terior", + "ur ch", + "Ġj ud", + "ĠP ower", + "Ġswit ch", + "ĠS yn", + "Ġinvolve ment", + "uc l", + "Ġlibr ary", + "ĠCon st", + "Ġsp herical", + "ĠT NF", + "Ġal tered", + "v ance", + "trans fer", + "M s", + "ĠO per", + "in ement", + "se q", + "C ons", + "ho le", + "ĠPh ot", + "Ġg ut", + "acter ial", + "ĠI P", + "un t", + "Ġn om", + "h as", + "ĠFe bruary", + "Ġpro state", + "ĠM L", + "h igh", + "ĠBack ground", + "ul ent", + "Ġo cean", + "a fter", + "ĠO ff", + "l oss", + "Ġfav or", + "Ġwork ers", + "Ġh idden", + "Ġextrac ts", + "raz il", + "s ign", + "N one", + "Ġcolum ns", + "Ġfrac tions", + "Ġco vered", + "ĠS erv", + "Ġin form", + "b ed", + "Ġatt em", + "rain ing", + "Ġneut ron", + "Ġr ice", + "Ġmo tif", + "Ġartif icial", + "Ġinhibit ory", + "Ġd t", + "AG E", + "Ġsam pled", + "Ġb atter", + "Ġsub jected", + "Ġgener ic", + "ĠN H", + "Ġcontin ue", + "ution al", + "Ġa ug", + "i us", + "Ġexec ution", + "ĠW illi", + "ĠDes pite", + "A MI", + "Ġcont ents", + "ĠS ens", + "og ens", + "C ol", + "Ġf o", + "Ġad di", + "u ated", + "Ġrecomm ended", + "ĠS W", + "Ġar ch", + "ĠY es", + "Ġh ol", + "atur ally", + "ti tive", + "Ġc he", + "Ġs ector", + "ĠDef inition", + "Ġcon cepts", + "or ous", + "sm all", + "ers on", + "in ator", + "ĠM T", + "Ġhypert ension", + "c ks", + "Ġn ative", + "Ġt ax", + "r yl", + "Ġre active", + "r b", + "duc ible", + "om m", + "Ġdiagnos ed", + "Ġdri ving", + "Ġbiom ass", + "u ate", + "Ġp il", + "c alled", + "Ġser ve", + "Ġinter fer", + "ipp ocamp", + "Ġalgebra ic", + "Ġbe gan", + "Ġpict ure", + "ind ependent", + "Ġutil ized", + "go ing", + "or a", + "n m", + "Ġdown stream", + "Ġorb ital", + "oun tain", + "ĠH is", + "Ġres ol", + "Ġcorrec tions", + "on ym", + "sc ripts", + "Ġsil icon", + "Ġc um", + "ĠT ri", + "Ġpepti des", + "Ġrece iving", + "Ġstation ary", + "Ġμ L", + "cler osis", + "Ġmod ules", + "em a", + "ĠAfric an", + "struc tion", + "Ġf arm", + "Ġlear n", + "n ode", + " ®", + "Ġsuper conduc", + "ĠL inear", + "Ġtechn ologies", + "Ġnecess arily", + "Ġcoron ary", + "ĠE ast", + "Ġf rames", + "Ġseg mentation", + "V s", + "Ġbehavior al", + "Î ĵ", + "Ġlog ic", + "Ġaccomp an", + "tif ied", + "han ol", + "ĠIn hib", + "il ation", + "and er", + "Ġeff ort", + "ĠD en", + "D I", + "op tim", + "term inal", + "Ġmob ility", + "Ġconsider ation", + "O VA", + "Ġpar ad", + "ox o", + "Ġdef iciency", + "ult ural", + "Ġvalid ity", + "Ġord ers", + "Ġloc us", + "Ġar th", + "em at", + "Ġfeed ing", + "Ġprogram ming", + "Ġtem plate", + "el ian", + "Ġop tion", + "ĠF ollowing", + "Ġen able", + "Ġass ign", + "Ġform ul", + "p u", + "Ġatmosp here", + "sl ant", + "ĠR uss", + "ĠE vidence", + "Ġsimilar ly", + "Ġc amp", + "Ġw ound", + "ĠCharacter ization", + "ĠP BS", + "e es", + "ĠDi rect", + "ĠS L", + "Ġfr uit", + "Ġg ate", + "it o", + "C hem", + "Ġcoll ision", + "or tic", + "Ġpolym orphism", + "enz a", + "w hat", + "Ġexperiment ally", + "Ġult ra", + "e z", + "Ġn erv", + "Ġess entially", + "ĠAustr alia", + "ĠSt andard", + "Ġmedic ine", + "ad ian", + "ĠHig gs", + "u ge", + "Ġsup ports", + "um a", + "Ġcom plicated", + "d ate", + "ophag y", + "ĠMark ov", + "Ġoccur ring", + "opl us", + "P ub", + "pro b", + "ur able", + "Ġk ept", + "Ġisol ation", + "Ġev ol", + "ili ary", + "Ġreg ist", + "Ġh oles", + "Ġcl ar", + "ip ar", + "Ġen rich", + "Ġro ute", + "ay ers", + "edi atric", + "Ġpolynomial s", + "Ġtri vial", + "ĠS am", + "vari ant", + "Ġfre edom", + "pos s", + "Ġinf erence", + "ol a", + "Ġinterp reted", + "C a", + "em ory", + "Ġcent ury", + "ĠR em", + "ĠW u", + "Ġsupp ression", + "Ġgener ator", + "ĠH om", + "Ġvis cos", + "Ġpseud o", + "ĠCh ild", + "ĠS A", + "ib er", + "Ġequival ence", + "if ies", + "ĠCons ider", + "ol ine", + "âī ¤", + "Ġde ple", + "Ġaver aged", + "Ġs outhern", + "Ġord ered", + "ĠB rown", + "Ġmethyl ation", + "ĠAd ap", + "Ġm aternal", + "ond ed", + "ĠBe havi", + "Ġidentif iers", + "Ġprocess ed", + "G G", + "V I", + "Ġch a", + "un k", + "ĠF unctional", + "Ġhydro ph", + "Ġfin ancial", + "ec ond", + "ĠÎ ¨", + "Ġemph as", + "Ġdef ect", + "m ar", + "Ġnor thern", + "c ore", + "Ġadhes ion", + "Ġte le", + "Ġw arm", + "rif ug", + "rang ian", + "res olution", + "Ġhe x", + "h bar", + "Ġhar monic", + "Ġcont rac", + "Ġread ing", + "Ġeff orts", + "ĠO l", + "Ġan xiety", + "b ul", + "T C", + "ip id", + "R emark", + "Ġform ing", + "il bert", + "am ond", + "Ġanaly tic", + "ore c", + "ch a", + "ĠCon sequently", + "ĠS u", + "for all", + "Ġà ŀ", + "Ġasp ect", + "Ġins ights", + "ati vity", + "io tics", + "he imer", + "ĠL abor", + "Ġa ware", + "ĠBri tish", + "c hemical", + "Ġâ ĭ", + "cl usion", + "ĠM ich", + "Ġgra de", + "ĠS EM", + "ĠC irc", + "hes es", + "W L", + "Ġen abl", + "Ġd end", + "Ġind ustry", + "Ġimpro ves", + "t et", + "Ġt el", + "Ġwas hed", + "Ġshor ter", + "Ġinc ident", + "ĠAc tivity", + "Ġdos es", + "ĠB razil", + "Ġtransform ations", + "Ġform at", + "ĠPro of", + "Ġl en", + "ul ative", + "Ġcycl ic", + "Ġrec ruit", + "pt r", + "T H", + "Ġrece ive", + "ĠNe xt", + "ĠEx p", + "i ant", + "in stein", + "S et", + "re ne", + "Ġge omet", + "Ġconsider able", + "S o", + "ugh t", + "Ġpaper s", + "ĠC S", + "z a", + "Ġis omorphism", + "ho u", + "Ġmut ants", + "Ġpor tion", + "Ġà ¾", + "Ġcontinu um", + "C u", + "ĠComput ed", + "Ġcomb ining", + "ov a", + "ĠN P", + "Ġc rack", + "Ġsome times", + "Ġcontinu ed", + "Def inition", + "arc in", + "ĠC d", + "ĠMed ical", + "i ences", + "ĠC ross", + "Ġtranscription al", + "ĠZ e", + "st d", + "if orn", + "Ġfail ed", + "Ġidentif ying", + "Ġm ir", + "Ġmetast asis", + "O F", + "n n", + "ĠC ID", + "Ġoscill ations", + "anc ies", + "wr ite", + "Ġband width", + "Ġtra de", + "Ġag ing", + "ĠModel ing", + "Ġass ert", + "Ġcurren ts", + "Ġf ire", + "ub iqu", + "Ġalb um", + "Ġfrequ ent", + "N ame", + "Ġpur ch", + "Ġpl ayer", + "ĠE sc", + "Ġno tion", + "Ġintern ational", + "ul um", + "o ic", + "Ġincub ation", + "Ġphenomen a", + "Ġser ver", + "ut er", + "Ġv en", + "qu in", + "Ġhyp ox", + "ĠR F", + "it on", + "Er ror", + "Ġhe mat", + "Ġthem selves", + "Ġper p", + "id ual", + "Ġpur poses", + "m es", + "w ing", + "ro v", + "Ġem iss", + "Ġexperi enced", + "qu es", + "ĠL C", + "ĠRec ent", + "bo ok", + "Ġalk al", + "id x", + "hy th", + "Ġconc rete", + "Ġswit ching", + "Ġexplan ation", + "ird s", + "Ġsign s", + "Ġob j", + "Ġcytok ines", + "ub ble", + "ad der", + "Ġuncertain ties", + "Ġprom otes", + "Ġcom pl", + "Ġsc an", + "Ġpr ime", + "P H", + "Ġheter ogeneous", + "ĠY ou", + "Al though", + "Ġser ious", + "Ġdri ve", + "Ġheter ogeneity", + "ryst all", + "Ġo d", + "Ġcon volution", + "ĠâĬ Ĩ", + "ĠSp ace", + "Ġgast ric", + "ĠSt re", + "ĠP V", + "b ase", + "M et", + "Ġloss es", + "Ġcyt otox", + "Ġcontroll ing", + "le ase", + "Ġreg ulated", + "ĠEng ine", + "ĠH ospital", + "B r", + "on om", + "hy de", + "st age", + "Ġgiv ing", + "ĠP en", + "ĠSoci ety", + "dri ven", + "i ang", + "Ġmod ifications", + "B V", + "Ġaccel eration", + "Ġm ilk", + "on omic", + "Ġth ink", + "ogl ob", + "Ġfeas ible", + "n am", + "Ġref lection", + "ĠPol y", + "Ġsummar ized", + "F L", + "Ġrec t", + "Ġpredom inant", + "Ġbl ot", + "de hyde", + "Ġtransform ed", + "Ġfacilit ate", + "ĠCar lo", + "Ġgreat ly", + "ĠS ocial", + "Ġparen ts", + "big g", + "ros pective", + "Ġprogn osis", + "Ġcharacter ize", + "Ġconn ectivity", + "Ġtraject ories", + "ĠS H", + "Ġl ies", + "Ġcandid ates", + "rom y", + "Ġs or", + "ĠIn s", + "Ġth or", + "Ġmet als", + "ĠS V", + "Ġtim ing", + "Ġutil ity", + "Ġnew ly", + "ĠI FN", + "Ġaffect ing", + "ce ment", + "ĠM el", + "ĠÌ ģ", + "typ es", + "lys is", + "erc ul", + "Ġdist or", + "act ors", + "ps y", + "Ġbo ok", + "ĠE ven", + "tem perature", + "Ġinvas ion", + "Ġrecogn ized", + "fact or", + "N e", + "Ġinter section", + "Ġcor tical", + "n g", + "Ġde ploy", + "Ġamplit udes", + "Ġd a", + "ĠG C", + "Ġchalleng ing", + "Ġpre lim", + "G M", + "A cc", + "Ġfour th", + "al c", + "ĠP S", + "ĠGene tic", + "l ock", + "err or", + "sk ip", + "s ime", + "Ġan a", + "sime q", + "Ġcereb ral", + "ĠE X", + "av ed", + "roph y", + "id opsis", + "Ġbeh ind", + "Ġen ables", + "Ġind ustrial", + "ĠP ac", + "Ġdefin itions", + "Ġcataly tic", + "Ġdiss ip", + "erv ical", + "Ġcom mut", + "Ġrepe at", + "Ġch iral", + "Ġp ron", + "p ol", + "Ġgo ing", + "Ġmic roscope", + "Ġhealth care", + "ĠClass ification", + "tit ude", + "ĠFerm i", + "Ġh ttp", + "are st", + "Ġsupport ing", + "Ġw ood", + "n ight", + "Ġkine tics", + "Ġsubset s", + "Ġsub unit", + "ĠCan ada", + "at on", + "Ġaccur ately", + "Ġres istant", + "ĠïĢ ½", + "ric tion", + "Ġcham ber", + "ig ue", + "ĠPh il", + "Ġrec over", + "c s", + "Ġsp here", + "ĠSpec ifically", + "Ġan ne", + "Ġiniti ation", + "ĠT H", + "Ġb ud", + "ord ered", + "Ġdi electric", + "ĠCol lege", + "Ġproduc ing", + "Ġanten na", + "B s", + "ĠF rench", + "O X", + "ĠAmeric a", + "ĠâĢ Ķ", + "oun ting", + "ful ly", + "Ġserv ed", + "Ġresid ue", + "Ġarg uments", + "Ġp and", + "Ġcomp any", + "Ġcondition al", + "m ia", + "ĠQ CD", + "Ġviscos ity", + "Ġprosp ective", + "as onal", + "Ġdom inated", + "Ġpen et", + "op o", + "Ġn ine", + "ĠI ll", + "ĠVis ual", + "Ġfil es", + "Ġy east", + "Ġthan k", + "G N", + "re al", + "Ġver ified", + "ĠInd ian", + "Ġsti ff", + "rolog ical", + "Ġd ram", + "Ġt ight", + "ĠGerm an", + "ĠTechn ology", + "ĠAppro ach", + "rom atic", + "Ġac oustic", + "ti an", + "os in", + "ĠDep artment", + "ot ropy", + "Ġem pty", + "tri vial", + "of il", + "Ġal gebras", + "tex ts", + "Ġwe bs", + "Ġp ore", + "Ġpack et", + "T ime", + "im g", + "on y", + "ri tic", + "Ġveloc ities", + "ĠD ynamics", + "Ġcanc ers", + "Ġtr unc", + "ĠForm ation", + "ĠDon or", + "ĠM it", + "I ST", + "Ġconcl uded", + "Ġan tag", + "ĠSo ft", + "app end", + "Ġfrag ments", + "ĠPro f", + "Ġflu or", + "ĠJ ac", + "ĠS n", + "Ġle pt", + "Ġsplit ting", + "Ġsex ual", + "ĠF ore", + "ĠGen er", + "Ġneighbor hood", + "Ġben chmark", + "ĠR A", + "Ġdiv ision", + "iforn ia", + "Tr ue", + "Ġf uzzy", + "Ġt ro", + "c ents", + "Ġconstit u", + "ati al", + "as tern", + "ĠT im", + "Ġper ception", + "Ġsubst anti", + "Ġmac ro", + "Ġout l", + "ĠObs erv", + "pr ising", + "ok ed", + "orec tal", + "ĠCh o", + "ĠDiff erent", + "Ġinvestig ations", + "Ġconsist ency", + "i ents", + "ĠF OR", + "AS S", + "ĠV an", + "Ġsit uations", + "ĠB R", + "Ġinf rared", + "ym al", + "Ġpix els", + "Ġcar rier", + "s en", + "IN T", + "Ġeffici ently", + "D T", + "ĠEx pl", + "ion ic", + "Ġn aturally", + "Ġpro pos", + "Ġgu ide", + "Ġconcl usions", + "o on", + "Ġg rant", + "Ġinst ances", + "Ġreview ed", + "Ġelect romagnetic", + "Ġth reat", + "ed ia", + "ĠOptim ization", + "ĠB io", + "Ġtrig ger", + "ici ent", + "otyp ic", + "Ġst ret", + "Ġan tic", + "Ġtox ic", + "Ġsp inal", + "UP AC", + "Ġover view", + "o tion", + "Ġstraight forward", + "Ġpos itively", + "as te", + "Ġref erences", + "ul ose", + "ĠG re", + "Ġantag on", + "Ġshif ts", + "Ġd rawn", + "ĠWh ite", + "Ġfrac tional", + "Ġbund le", + "Ġexhib its", + "Ġreserv oir", + "ĠA lex", + "Ġaggreg ation", + "Ġcirc le", + "Ġprac tices", + "ĠC oval", + "ĠDist ribution", + "Ġt ang", + "ĠM ut", + "Ġreg ulate", + "osp here", + "i ro", + "AMI NO", + "v est", + "Ġphot os", + "Ġev ident", + "Ġbus iness", + "cont rol", + "Ġw orth", + "ĠPo isson", + "ĠArab idopsis", + "ĠT arget", + "Ġregul ates", + "ĠI r", + "ĠAd v", + "Ġens emble", + "pr ing", + "Ġp rice", + "ĠF L", + "ĠImp act", + "Ġevent ually", + "in ating", + "Ġcent rifug", + "f rame", + "Ġdiagram s", + "Ġt ag", + "Ġt ry", + "sur face", + "ĠIdentif iers", + "ra ined", + "Ġs ides", + "Ġin n", + "Ġflex ible", + "Ġsat ellite", + "Ġaff inity", + "Ġsum mer", + "G P", + "am b", + "Ġa qu", + "Str ing", + "t reatment", + "ĠD ynamic", + "math op", + "Ġno tice", + "n es", + "row ave", + "ves tig", + "Ġoutput s", + "Ġco herent", + "Ġillustr ate", + "Ġvalid ated", + "ĠSc hem", + "Ġask ed", + "b atch", + "Ġpur ified", + "Ġminim ize", + "ĠD E", + "U M", + "c heck", + "vari an", + "ĠG old", + "yl ene", + "I O", + "Ġch olesterol", + "Pub Chem", + "ĠK ore", + "ĠCount y", + "Ġi i", + "ĠM AP", + "ect omy", + "Ġsem antic", + "Ġcoll agen", + "Ġper ceived", + "ich ia", + "Ġadminist ered", + "con taining", + "ran k", + "In ChI", + "Ġirradi ation", + "Ġlog arithm", + "Ġg ames", + "Ġinj ected", + "ĠM Hz", + "Ġd ors", + "Ġevalu ating", + "ĠHy per", + "Ġchromat ography", + "p hen", + "ĠK ar", + "Ġan timicrobial", + "ri end", + "Ġdescrib ing", + "Ġw t", + "Ġhorm one", + "A K", + "ĠI UPAC", + "G a", + "Ġvit amin", + "Ġconn ections", + "u ous", + "ĠL ine", + "Ġbenef icial", + "c ases", + "ic ated", + "is ks", + "p arent", + "I d", + "er ies", + "r un", + "Ġm ind", + "it t", + "s ulf", + "z heimer", + "Ġinter f", + "V ert", + "Ġan th", + "olog ous", + "ĠL ife", + "Ġm ur", + "Ġper mut", + "ot ing", + "Ġneut rino", + "Ġb orn", + "p matrix", + "ĠCal ifornia", + "ag ent", + "Ġcoll isions", + "ĠN S", + "Ġh ippocamp", + "Ġpow der", + "Ġv aries", + "Ġepid em", + "ĠWe b", + "ul er", + "Ġinterest ed", + "Ġdevelopment al", + "Ġlength s", + "Ġcol our", + "Ġqu as", + "ĠR ich", + "E q", + "Ġinf ants", + "ĠP H", + "ophil a", + "Ġcaus ing", + "G e", + "mod ule", + "I B", + "Ġcontrib uted", + "ro se", + "Ġcy toplas", + "---------------- ----------------", + "Ġro ad", + "s ymmetric", + "U s", + "Ġweak ly", + "ti te", + "Ġdef ines", + "ĠP E", + "Ġmetabol ites", + "Ġl ob", + "Ġterm inal", + "Ġdemonstr ates", + "ĠAc ceptor", + "ĠC lo", + "Ġinfer red", + "Ġv ill", + "F irst", + "Ġneg lig", + "Ġw ireless", + "A b", + "par ticle", + "ois otopic", + "Ġexc ited", + "P M", + "Ġcons ecutive", + "ĠIs otype", + "Ġstim ulus", + "ĠM C", + "tim ate", + "ĠCoval ently", + "B onded", + "Ġy ellow", + "Ġall oy", + "d ensity", + "Ġfil ters", + "Ġampl ification", + "Ġw on", + "h t", + "Ġimp acts", + "Ġst aff", + "ĠâĪ Ģ", + "ĠIs omeric", + "Ġsm oking", + "Q u", + "Ġcapt ured", + "h aps", + "ĠN ULL", + "Ġri ver", + "c ount", + "Ġmanif est", + "Ġdiab etic", + "Ġalter ations", + "ĠRot atable", + "ĠP RO", + "ĠMon oisotopic", + "Ġï Ĥ", + "sp ect", + "Ġcataly st", + "Ġmodel ed", + "Ġp age", + "ĠR OS", + "ĠCanonical ized", + "ĠT w", + "Ġa ux", + "av age", + "ĠRam an", + "st o", + "per f", + "Ġreplac ement", + "ĠEn vironment", + "Ġac ting", + "p ati", + "ific ant", + "th rough", + "Ġsat uration", + "Ġt ip", + "Ġrec urrence", + "ĠHist ory", + "Ġprot ective", + "Ġbur den", + "ad o", + "y es", + "in st", + "A p", + "ĠS y", + "Ġph on", + "ĠâĪ ij", + "Ġgen otype", + "Ġcovari ance", + "Ġquick ly", + "ĠD u", + "Ġs ug", + "Ġdec line", + "ĠT B", + "Ġstrict ly", + "Ġmo ist", + "und red", + "ĠC B", + "ati le", + "ĠH F", + "Ġar ticles", + "Ġp s", + "ĠEn h", + "ist ing", + "Ġbi ology", + "Ġb odies", + "ĠA k", + "ĠNumer ical", + "ĠLag rangian", + "Ġdisc overed", + "Ġv ic", + "op es", + "Ġfrag ment", + "Ġt y", + "ism ic", + "Ġhep atic", + "Ġen riched", + "p an", + "Ġinflu ences", + "ĠL ake", + "col or", + "Ġenrich ment", + "oc hemistry", + "Ġun stable", + "ĠIg G", + "der ly", + "Ġe cos", + "Ġconcer ning", + "ĠR isk", + "Ġmarg in", + "Ġpath ogenesis", + "Ġp ump", + "Ġprelim inary", + "Ġtum our", + "F urther", + "az ole", + "Ġelectro des", + "Ġd ial", + "ub es", + "ĠN atural", + "ĠM ul", + "ĠïĢ Ń", + "Ġn ic", + "Ġimp ed", + "on ly", + "Ġcompar ative", + "rec tion", + "ak i", + "Ġre nd", + "Ġsp arse", + "Ġindic ator", + "l ocation", + "tis m", + "ac tivated", + "ĠP b", + "epti de", + "Ġend ogenous", + "Ġcent ers", + "a o", + "s w", + "Ġcons ensus", + "Ġattrib utes", + "Ġsaf e", + "Ġbelie ve", + "ov irus", + "Ġimmun ity", + "Ġfit ted", + "Ġcontrib utes", + "i able", + "Ġvirus es", + "Ġins ight", + "ĠNo vel", + "ĠAl zheimer", + "cep ted", + "ĠP t", + "Ġcent re", + "n at", + "Ġbios ynthesis", + "m its", + "Ġchem istry", + "Ġj us", + "an ish", + "Ġre frac", + "ĠT or", + "Ġpan els", + "Ġimp ly", + "Ġmat ched", + "us c", + "w ord", + "v ae", + "ĠSt ar", + "s yn", + "M at", + "Ġapplic able", + "ĠP seud", + "amp ions", + "ĠR en", + "Ġus age", + "ĠL ight", + "p rec", + "Ġfib rosis", + "Ġreconstr uc", + "ĠO N", + "ĠG Hz", + "G D", + "al gebra", + "ig er", + "Ġdec isions", + "inf ected", + "knowled g", + "Ġexpress ing", + "Ġmyocardi al", + "ord ination", + "Ġprogn ostic", + "Ġfibro bl", + "Ġaccel er", + "ĠAssess ment", + "Ġconstra ined", + "Ġalle le", + "r ide", + "Ġrequ est", + "abil istic", + "te b", + "Ġg a", + "Ġrec overed", + "Ġpro min", + "urs es", + "ĠH C", + "ĠM ur", + "ĠEq s", + "Ġdef ining", + "Ġm er", + "im age", + "Ġorgan isms", + "g rad", + "Ġref lected", + "el astic", + "e ties", + "dim ethyl", + "EL O", + "ra ndom", + "ĠDi agn", + "ercul osis", + "ro b", + "Ġmom ents", + "ĠE C", + "Ġexperi ences", + "erv ing", + "ĠN C", + "Ġvor tex", + "g re", + "struct ures", + "el t", + "Ġcar ry", + "ĠTh rough", + "Ġpre ced", + "rast ruct", + "it us", + "Ġpsych ological", + "Ġlimit ing", + "t wo", + "ĠB ound", + "ĠC re", + "ĠSm ith", + "Ġc ast", + "Ġcompe tition", + "s ch", + "Ġcap ability", + "ach ment", + "Ġinhib its", + "à °", + "ĠDiff erential", + "Ġautom atically", + "Ġg est", + "Ġw aters", + "Ġun iqu", + "z er", + "E qu", + "Ġstudy ing", + "Ġdi ed", + "Ġo s", + "Ġrecomb ination", + "unc il", + "Ġpath ogen", + "GF R", + "U V", + "en eration", + "ĠS ta", + "Ġinst ant", + "Ġpro ven", + "Ġd s", + "Ġd amp", + "N ext", + "ĠY oung", + "Ġpower ful", + "Ġwr iting", + "k l", + "Ġcare er", + "ĠCor ollary", + "N s", + "Ġï ¿½", + "ĠM il", + "Ġb urn", + "tic ular", + "ond on", + "P r", + "ĠL in", + "ĠJapan ese", + "ĠL ab", + "Ġst rip", + "pro tein", + "Ġh our", + "angle ment", + "angu ages", + "r d", + "par se", + "Ġemiss ions", + "H ence", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠ", + "Ġj ob", + "ĠA S", + "Ġax ial", + "ĠT ur", + "car bon", + "M F", + "ĠN E", + "Ġar ise", + "Ġlinear ly", + "Ġprol ong", + "Ġle ak", + "ĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠ", + "Ġmov ed", + "orb idity", + "Ġprofession al", + "c ode", + "os ine", + "Ġpol ic", + "Ġbond s", + "m ask", + "Ġconver ted", + "v ille", + "ec tious", + "par allel", + "ĠH al", + "ĠT GF", + "ment al", + "Ġread er", + "Ġstandard s", + "ag o", + "Ġ EN", + "Ġst ations", + "Ġnormal ization", + "ĠÎ ĺ", + "ch ain", + "W hat", + "Ġtom ography", + "Ġent ries", + "bl ue", + "ĠPre vious", + "i as", + "Ġquestionnai re", + "Ġh az", + "Ġhom ology", + "ver y", + "Ġnucle otide", + "ĠGen ome", + "Ġμ l", + "Ġutil ization", + "Ġpolym ers", + "ro te", + "Ġsmall est", + "cal c", + "Ġs pl", + "Ġt ension", + "Ġdis continu", + "al a", + "h ol", + "Ġdeterm ines", + "Ġpro j", + "ĠOver all", + "Ġb le", + "f o", + "Ġprinc iples", + "Ġinteract ing", + "Ġhard ware", + "l ife", + "ail s", + "Ġdifficult y", + "Ġcho ices", + "Ġc ard", + "Ġl act", + "Ġro ll", + "Ġquantif ied", + "ĠSc ientific", + "Ġland sc", + "al igned", + "Ġcompos ites", + "her ichia", + "Ġen velop", + "iti g", + "S te", + "Ġcomp et", + "Ġimpair ment", + "Ġclos ure", + "Ġreturn ed", + "Ġrece iver", + "Ġpe er", + "Ġcons ent", + "Ġult ras", + "Ġphot ons", + "Ġsup pose", + "Ġpredic ting", + "ĠâĬ ķ", + "Ġcomp an", + "Ġneglig ible", + "c urrent", + "um ber", + "Ġcomp atible", + "i op", + "ĠStruct ural", + "R ef", + "Ġs on", + "Ġequ ality", + "Ġconsist ed", + "Ġv ibr", + "ou pling", + "v ation", + "Ġover come", + "s uper", + "l ict", + "Ġpancre atic", + "G s", + "ap ed", + "as al", + "w an", + "Ġlat ent", + "Ġcover ing", + "Ġles ion", + "i ance", + "ĠF T", + "wo od", + "ject ure", + "ĠB C", + "link ed", + "ĠL aw", + "Ġem it", + "Ġunc lear", + "Ġpre m", + "ac ted", + "p olar", + "c re", + "Ġmod ulus", + "rop ath", + "S ub", + "am i", + "Ġp ick", + "ER R", + "Ġmove ments", + "N i", + "Ġmechan ics", + "od ic", + "Ġg al", + "ĠMan agement", + "h ost", + "ew ise", + "ĠT otal", + "ĠInflu ence", + "Ġ ubiqu", + "roph ys", + "Ġcap s", + "Ġparticip ant", + "Ġpol yp", + "t d", + "Ġiter ations", + "dom inal", + "B B", + "Ġcharacter s", + "Ġdevi ations", + "res istant", + "Ġmal aria", + "Ġrem ote", + "h skip", + "Ġunder went", + "u til", + "bl ock", + "ucl ide", + "Î ¦", + "elect ron", + "Ġsens ory", + "ĠSim ulation", + "Ġre ward", + "Ġpand emic", + "Ġb or", + "ynt hetic", + "Ġinvas ive", + "R F", + "ĠSm all", + "ĠF isher", + "val ent", + "ĠM I", + "roc ytes", + "ĠT E", + "Ġst re", + "Ġperturb ations", + "Ġsim plicity", + "ĠG rowth", + "ĠÎ ł", + "Ġin oc", + "ard ing", + "at um", + "m ulti", + "ĠD iv", + "an es", + "ac illus", + "Ġlife time", + "ĠH ep", + "Ġa z", + "us p", + "ĠAssum e", + "Ġbre aking", + "ĠAt t", + "ticip ants", + "Ġlumin osity", + "Ġdon or", + "par ams", + "oh yd", + "Ġpro gen", + "ĠP O", + "G O", + "ĠL eg", + "Ġbiomark ers", + "Ġr ural", + "Ġne on", + "gl uc", + "ĠP B", + "Ġgu id", + "Ġc ervical", + "p ace", + "Ġc ord", + "um n", + "Ġsub space", + "Ġatt ached", + "Ġdepos ited", + "Ġindic ators", + "ĠS F", + "qui re", + "Ġdiss olved", + "r ite", + "ĠN A", + "Ġj u", + "Ġadd ressed", + "Ġsupp ressed", + "Ġpneum onia", + "Ġs ession", + "ĠC he", + "ĠF er", + "Ġacc ordance", + "D es", + "Ġqu ar", + "Ġfit ness", + "Ġvi ability", + "os h", + "Ġphyl ogenetic", + "ect in", + "p at", + "ĠFran ce", + "Ġmess ages", + "Ġl oci", + "Ġconf lict", + "Ġrele vance", + "Ġinstruc tions", + "Ġsome what", + "chang ed", + "Ġcorrect ly", + "oz yg", + "av ig", + "ĠL at", + "Ġo varian", + "ĠR emark", + "j oint", + "ain t", + "w est", + "s ample", + "Ġdiver gence", + "Ġh air", + "ag onal", + "Ġm im", + "Ġim mediate", + "ĠP ort", + "Ġoff ers", + "Ġdepic ted", + "Ġhydro x", + "ĠT ow", + "Ġemerg ing", + "ou pled", + "Ġh undred", + "Ġadap ted", + "ell er", + "ĠRel ations", + "et te", + "Ġgast ro", + "Ġm orphism", + "Ġequip ment", + "p op", + "un ately", + "Ġtransplant ation", + "if iers", + "Ġel derly", + "on ucle", + "Ġref ers", + "ar ial", + "ĠCom mittee", + "Ġmalign ant", + "omon as", + "Ġall ocation", + "og ether", + "Ġnan ot", + "pl ot", + "ĠM es", + "Ġplan ar", + "ell s", + "s ource", + "ow ski", + "Ġn a", + "Ġcl ock", + "Ġamb ient", + "oc ene", + "Ġfluores cent", + "Ġval u", + "ĠM agnetic", + "Ġde part", + "phosph ate", + "Ġrough ly", + "Ġne ither", + "ĠAl tern", + "Ġst ay", + "Ġsp ot", + "ĠE nt", + "Ġsecond s", + "h ard", + "Ġrec urrent", + "Ġpat ch", + "Ġlimit ation", + "ĠD er", + "Ġsh arp", + "Ġexpect ation", + "ĠL ore", + "d ict", + "R eg", + "Ġneut roph", + "Ġn ur", + "Ġstar ts", + "ost asis", + "Ġorgan ized", + "Ġc DNA", + "or ient", + "ĠEx ample", + "ĠF und", + "ay lor", + "id ering", + "Ġtri ple", + "n ic", + "Ġattack s", + "ĠD ros", + "à ¨", + "ĠE M", + "Ġoptim um", + "Ġp ull", + "Ġc e", + "ery th", + "Ġr ating", + "Ġreproduc tive", + "Ġdec ades", + "Ġre place", + "L ist", + "ĠF ast", + "Ġred shift", + "op sy", + "ill a", + "do uble", + "ter a", + "Ġgo als", + "ĠS k", + "IN E", + "Ġbi ochemical", + "u int", + "Ġf etal", + "ĠRi emann", + "ur ies", + "Ġp p", + "Ġsymbol s", + "ĠK a", + "D i", + "ĠG alax", + "ĠComp ared", + "Ġc asc", + "Ġb its", + "Ġsc aff", + "Ġestim ator", + "ĠAd ditional", + "Ġimprove ments", + "ectiv es", + "Ġh ous", + "ĠM agn", + "Ġmultiv ariate", + "Ġag ric", + "v o", + "ut ter", + "ĠAc knowledg", + "s u", + "Ġam mon", + "Ġaim s", + "Ġz inc", + "Ġel ong", + "ĠG O", + "Q uestion", + "incl uding", + "Log P", + "Ġint ellig", + "Ġcon e", + "ĠFound ation", + "Ġimp aired", + "Ġill ness", + "ĠEsc herichia", + "Ġabund ant", + "s cal", + "ens ively", + "Ġneg atively", + "par ameter", + "Ġperme ability", + "dom ain", + "r ated", + "Ġep och", + "Ġadoles cents", + "Ġdef ic", + "ĠEstim ation", + "Ġrout ine", + "P er", + "t ol", + "Ġellip tic", + "ĠH E", + "obl ast", + "Ġre aches", + "Ġflux es", + "Ġs un", + "ĠAn aly", + "âĢ ľ", + "ĠX LogP", + "Ġfilter ing", + "ri an", + "ĠS cal", + "Ġp in", + "ĠTi O", + "im ents", + "Ġmarg inal", + "Ġrecomb inant", + "Ġenc our", + "Ġal umin", + "Ġt f", + "ataly tic", + "Ġobserv ational", + "Ġgeneral ization", + "Ġï£ ¬", + "Ġantib iotic", + "Ġgener ates", + "Ġd B", + "S pec", + "r ically", + "Ġvalu able", + "Ġtop ic", + "Ġterm in", + "Ġsem icon", + "Ġquantif ication", + "ub b", + "Ġkin em", + "err ing", + "Ġa eros", + "p ack", + "Ġfew er", + "Ġfat igue", + "Ġgo es", + "Ġn ight", + "ĠU s", + "âĢ ¬", + "ĠPr inc", + "Ġsp ring", + "Ġconcer ns", + "Ġsm art", + "Ġsec ret", + "Ġmm ol", + "Ġbel ief", + "D C", + "Ġsubstanti ally", + "âĪ ĩ", + "Ġsubstit ution", + "map sto", + "sk y", + "ill ance", + "Ġstud ent", + "ok ine", + "Ġinter ior", + "Ġeigen value", + "m y", + "Ġclos er", + "eren ti", + "Ġec ological", + "ĠFig ures", + "oly tic", + "Ġar rays", + "ĠC as", + "Ġlo ops", + "Ġcorrec ted", + "Ġr he", + "Ġin version", + "Ġprefer red", + "um ab", + "ĠD I", + "Ġadequ ate", + "ir m", + "Ġim plicit", + "sh ip", + "Ġplay ers", + "Ġdelay ed", + "Ġw inter", + "Ġvul ner", + "Ġshap es", + "Ġstain ed", + "ĠM ajor", + "Ġhierarch ical", + "ĠD ig", + "ers ion", + "ĠE fficient", + "Ġwall s", + "d frac", + "Ġclass ifier", + "Ġmon ol", + "Ġupd ated", + "Ġm ature", + "ĠL I", + "ear ing", + "Ġf inger", + "oun ter", + "ank ton", + "Wh ile", + "Ġreal istic", + "ĠC amp", + "Ġf illed", + "Ġde ad", + "ĠPac ific", + "Ï ĩ", + "ĠDav id", + "Ġaddi tive", + "ench ymal", + "Ġobs er", + "Ġst ere", + "Ġultras ound", + "ĠPred ic", + "Ġend s", + "section al", + "m as", + "om at", + "iv ity", + "Ġhand le", + "Ġmetast atic", + "ole t", + "r yp", + "AC E", + "Ġpor ous", + "Ġconcer n", + "it ored", + "Ġcir cles", + "Ġemo tional", + "g ered", + "Ġf riction", + "f irst", + "oph y", + "es cop", + "ad ed", + "Ġres olved", + "ER S", + "Ġpath ogens", + "Ġgrad ually", + "ĠB rain", + "x f", + "an ium", + "a el", + "N ew", + "Ġcytok ine", + "ĠB P", + "Ġspecim en", + "ole an", + "Ġt axon", + "Ġsequ ential", + "κ B", + "ad emic", + "pl ings", + "~ ~", + "erm al", + "t ree", + "Ġcaus al", + "ari an", + "Ġc rop", + "op ol", + "ch annel", + "ĠM ex", + "Ġcl on", + "ĠRec ently", + "ĠIn vestig", + "Ġrecommend ations", + "form at", + "ĠM ET", + "Ġsent ence", + "Ġb p", + "ĠG W", + "Ġrec ording", + "Ġp le", + "to tic", + "Ġï£ ·", + "Ġrang ed", + "en tion", + "obacter ia", + "cep tions", + "ĠIm port", + "d ynamic", + "por ary", + "G iven", + "Ġturb ulence", + "Ġg ram", + "Ġequ ally", + "c d", + "ĠO s", + "Ġturn s", + "Ġdetect ing", + "ati o", + "gen erate", + "gra de", + "Ġcirc ulation", + "Ġmanufacture r", + "L a", + "ĠH ilbert", + "T s", + "in tegr", + "Ġbelong s", + "ĠIntern et", + "ang l", + "ĠâĬ ¥", + "ĠDros ophila", + "uclide an", + "t an", + "Ġext ends", + "Ġexpand ed", + "ill in", + "squ are", + "ys acchar", + "Ġquantif y", + "Ġpuls es", + "Ġves ic", + "ĠN K", + "ores cence", + "ĠPh osph", + "Ġv ision", + "ĠHu ang", + "ĠResp onse", + "h ouse", + "ear s", + "Ġe g", + "Ġac cepted", + "ĠT M", + "amet ric", + "Ġpath ological", + "Ġrecruit ment", + "AT A", + "Ġfig ures", + "ĠP ress", + "Ġal igned", + "Ġpost operative", + "ĠMe V", + "Ġconsider ably", + "Ġconform al", + "ĠIs land", + "num ber", + "Ġautom atic", + "Ġs plic", + "Ġcyt os", + "Ġdesc rip", + "ĠS ant", + "l ies", + "u ity", + "it one", + "E CT", + "ĠB on", + "Ġdis app", + "bo ard", + "or rh", + "Ġcalc ulating", + "ne e", + "ĠMe as", + "Ġgen omes", + "Ġphot oc", + "Ġread ily", + "ov ine", + "ĠDe v", + "Ġsat ur", + "Ġkind s", + "ĠP K", + "Ġro d", + "Ġj unction", + "ĠH A", + "Ġdesign s", + "h n", + "Ġorder ing", + "Ġcosm ological", + "Ġpil ot", + "Ġcol orectal", + "ĠL ondon", + "ĠDir ac", + "C ont", + "ĠW ind", + "ĠT re", + "id in", + "ĠïĢ «", + "ilt ration", + "More over", + "Ġre tention", + "tim ately", + "hydro gen", + "d el", + "bol ic", + "ĠQu anti", + "per iod", + "Ġretrie val", + "at ase", + "end icular", + "ulti es", + "R S", + "N H", + "Ġin formed", + "Ġfil tered", + "m embrane", + "Ġstiff ness", + "ĠO cean", + "ĠS Y", + "Ġl ot", + "ĠFig s", + "Ġans w", + "ĠEng land", + "ĠAtl antic", + "process ing", + "Ġdog s", + "Ġl ie", + "Ġun ion", + "ĠT an", + "Ġhal o", + "Ġcontinuous ly", + "B u", + "A MP", + "ĠAp p", + "Ġmoist ure", + "Ġth yroid", + "Ġaccompan ied", + "Ġfol d", + "Ġorig inally", + "Ġsp an", + "ĠF A", + "conn ected", + "Ġrec urs", + "vi an", + "ĠEqu ations", + "en a", + "arcin oma", + ".. ..", + "Ġdisc rep", + "U H", + "Ð ¾", + "ang er", + "Ġmon itored", + "Ġinflu enza", + "Ġs ure", + "bl ack", + "o e", + "Ġall oc", + "Ġhabit at", + "op henyl", + "Ġvent ricular", + "Ġpolic ies", + "am ate", + "Ġreport ing", + "Ġsol uble", + "======== ========", + "Ġdip ole", + "Ġirre ducible", + "ĠP rec", + "acet yl", + "Ġth read", + "ĠAppro xim", + "Ġm apped", + "i pro", + "Ġt ropical", + "S ch", + "ĠAN OVA", + "Ġl anguages", + "ic ine", + "ĠF amily", + "f unctions", + "E F", + "Ġnut rient", + "Ġanalyz ing", + "ines cence", + "Ġthrom b", + "Ġk it", + "Ġmamm alian", + "op totic", + "Ġequip ped", + "on a", + "Ġqu e", + "Ġc ame", + "Ġsimpl ified", + "Ġdec ays", + "Ġpass ive", + "Ġdele tion", + "Ġobtain ing", + "Ġmixt ures", + "Ġprim ers", + "ĠP sy", + "os c", + "om ent", + "Ġchlor ide", + "ĠPa ul", + "st art", + "int estinal", + "hel ium", + "ar th", + "od ot", + "Ġf its", + "Ġsqu ares", + "ĠCar di", + "ak a", + "rib uted", + "Ġinequ alities", + "omet hing", + "hed ral", + "ĠF uture", + "Ġgl i", + "Ġmetall ic", + "Ġfac ilities", + "Ġob st", + "poss ible", + "Ġz ones", + "uc id", + "Ġdr ift", + "d epend", + "val ued", + "Ġn ons", + "Ġworld wide", + "Ġtr ust", + "Ġso le", + "ĠLe vel", + "ĠS ha", + "Ġregard less", + "Ġspectrom etry", + "duc tor", + "le uk", + "Ġsk ills", + "Ġincorpor ated", + "Ġlear ned", + "Ġ ure", + "Ġext inc", + "OD U", + "Ġgrain s", + "ater n", + "ĠInd ex", + "com put", + "u a", + "Ġcont amination", + "ĠA ff", + "un ing", + "Ġas ymmetric", + "Ġopen ing", + "Ġb at", + "Ġag ree", + "IT Y", + "ĠChang es", + "organ ic", + "ĠR ay", + "ĠH and", + "n i", + "in ic", + "Ġr isks", + "Ġst ock", + "Ġnec k", + "Ġvol umes", + "ĠP rac", + "Ġincreasing ly", + "S c", + "os es", + "GF P", + "Ġass ignment", + "ĠF ed", + "osp it", + "Ġoverex pression", + "Ġm aster", + "Ġo pt", + "il er", + "in variant", + "Ġconver ges", + "Sim ilar", + "n y", + "Ġst ore", + "Ġelev ation", + "Ġco al", + "he t", + "it em", + "PL C", + "oh ist", + "G en", + "ĠC hem", + "ĠC ost", + "p air", + "Ġnumer ically", + "Ġpre ference", + "ĠN ucle", + "ĠB D", + "T I", + "ĠH yp", + "ro y", + "T e", + "ĠF in", + "Ġclaim s", + "ib ilities", + "Ġlar vae", + "im a", + "emb ly", + "Ġc it", + "L L", + "Ġsil ica", + "ĠV I", + "Ġreach ing", + "O f", + "ĠAustr alian", + "t ub", + "w orld", + "on i", + "ĠF P", + "Ġbrief ly", + "ĠDes cription", + "Î ¶", + "ch arg", + "Ġc is", + "ĠC at", + "Ġrec ip", + "Ġemerg ency", + "Ġst rand", + "Ġreal ized", + "pos ing", + "ot ope", + "Ġmaintain ing", + "ĠCh rist", + "Ġcre ating", + "Ġembry os", + "Ġs keletal", + "Ġag es", + "rep resent", + "C r", + "Ġestim ating", + "Ġre ar", + "ĠY u", + "ĠP i", + "m g", + "Ġflo at", + "ĠR oy", + "p us", + "Ġch ick", + "Ġmicrobi ota", + "vas ive", + "ĠB ern", + "ĠPat tern", + "l ines", + "Ġfl ood", + "ĠL ou", + "ilit ary", + "ros ion", + "Ġsurve ys", + "F I", + "ia e", + "Ġse arc", + "m ol", + "Ġt itle", + "ĠMach ine", + "Ġcirc uits", + "ĠNum ber", + "z i", + "ĠB MI", + "Ġautom ated", + "plic ate", + "ĠL PS", + "Ġelectro chemical", + "Ġwebs ite", + "Ġanis otropy", + "Ġr ings", + "Ġin nov", + "b its", + "w in", + "ĠN AD", + "Acc ording", + "ĠCon n", + "ure us", + "ĠFe ature", + "ĠIn stead", + "C omp", + "it udes", + "M o", + "Ġsc ope", + "tif ication", + "ĠI S", + "ĠNe ut", + "Ġreg ulating", + "c oding", + "Ġrow s", + "h l", + "ĠK n", + "ist or", + "ampions hip", + "Ġpromin ent", + "Ġr s", + "um atic", + "A m", + "Ġdifferenti ally", + "ug in", + "Ġadv ance", + "ph ys", + "Ġsh aring", + "Ġar t", + "v acy", + "ti tions", + "Ġst yle", + "Fig ures", + "Ġg lu", + "Ġvacc ination", + "ĠOp tical", + "flu id", + "ĠF re", + "Ġgradi ents", + "op hyl", + "ĠP ubl", + "Ġacc retion", + "Ġâ̲ â̲", + "ress ing", + "Ġtrans mitted", + "Ġnerv ous", + "um ar", + "Ġreview s", + "Ġgen otypes", + "low er", + "ĠE V", + "Ġcont ract", + "ati bility", + "Ġchild hood", + "Ġon c", + "Ġbi ofil", + "Ġaut ophagy", + "Ġads orb", + "ĠSup port", + "Ġlig ands", + "p ower", + "rec tional", + "ĠR ap", + "sim ilar", + "Ġinf arc", + "Ġelectro ly", + "Ġinc ome", + "ar ity", + "ĠA v", + "er ic", + "Ġclin ically", + "un ch", + "Ġattrib ute", + "Ġcomm and", + "rib utions", + "Ġgly c", + "Ġtranscri pts", + "ogram s", + "Ġassess ing", + "F O", + "script style", + "j i", + "ric k", + "en vironment", + "Ġlaw s", + "Ġnorm ally", + "Ġdeple tion", + "ĠR O", + "Ġenc oded", + "h ma", + "Ġbran ches", + "Ġarg s", + "oun ger", + "or ge", + "um ps", + "Ġview ed", + "Ġult r", + "R R", + "uls ion", + "ĠH or", + "Ġf ro", + "ĠMeasure ment", + "x x", + "erm an", + "ĠO nce", + "Ġorient ed", + "ĠP oint", + "Ġto wn", + "Ġformul as", + "S Y", + "ĠA M", + "Ġconsider ations", + "ĠT C", + "ĠK it", + "Ġact in", + "Ġplas mid", + "Ġhistor ical", + "Ġd ye", + "Ġhe ur", + "ĠLe ague", + "ĠM ad", + "Ġgra ft", + "Ġsil ver", + "O ver", + "ĠC os", + "ograph ical", + "Ġprecurs or", + "r us", + "Ġregard ed", + "ĠH am", + "f unctional", + "iv eness", + "ffici ency", + "ig ene", + "oc ol", + "Ġcum ulative", + "Ġse asonal", + "Ġm u", + "ĠB an", + "omy cin", + "Ġb ool", + "ĠM ag", + "ĠAn al", + "enti a", + "a ign", + "Ġfoot ball", + "act ing", + "Ġreturn s", + "ĠT om", + "sh aped", + "it ance", + "ĠExperim ent", + "ĠO S", + "Ġabs ent", + "ran ial", + "Ġtherap ies", + "O p", + "o unced", + "AT E", + "Val ue", + "g reen", + "Ġveget ation", + "D s", + "Ġinc om", + "à §", + "Ġm arrow", + "ĠCo uncil", + "Ġinv est", + "Ġcl ub", + "T rans", + "dev ice", + "Ġv ibration", + "ĠX u", + "//// ////", + "ĠH en", + "vi er", + "Ġanalog ous", + "Ġd elta", + "Ġsal ine", + "Ġrequ iring", + "Ġneur on", + "o o", + "ĠQ uality", + "Ġte ac", + "ĠE c", + "L i", + "Ġpubl ication", + "ĠPhys ics", + "Ġp pm", + "th ase", + "Ġcre ation", + "ĠA ge", + "Ġbelong ing", + "Ġion ic", + "ĠS I", + "u ating", + "end if", + "ĠC our", + "Ð °", + "Ġd ots", + "Ġe ast", + "ar com", + "Ġâ ĩ", + "Ġr ights", + "ess ions", + "Ġvers ions", + "ĠF ree", + "ĠSt ress", + "Ġsed iments", + "Ġm itig", + "Ġb ow", + "ĠAc t", + "ĠCar bon", + "t here", + "te en", + "Ġphen otypes", + "Ġne arest", + "ĠPot ential", + "Ġde form", + "Ġreflec ts", + "Ġpart ners", + "Ġan est", + "Ġad vers", + "ĠF actor", + "Ġconven ient", + "ul os", + "ĠP ur", + "ĠM er", + "Ġfl ag", + "Ġtri ang", + "Ġseed s", + "Ġf if", + "ob il", + "ĠC K", + "men tioned", + "Ġv apor", + "og ue", + "Ġpredic tor", + "O ut", + "Ġcomple tion", + "ĠS eg", + "Ġdiff use", + "Ġra ised", + "Ġco ordination", + "Ġsyn aptic", + "ĠB or", + "ĠB ol", + "Ġpolymer ase", + "Ġwhe at", + "Ġinser tion", + "Ġes c", + "ĠW al", + "Ġdist al", + "transfer ase", + "Ġinter faces", + "Ġins u", + "Ġpoor ly", + "Ġa ureus", + "Ġben z", + "Ġun iverse", + "ĠInter action", + "ĠF rame", + "ĠIm aging", + "Ġexpl oration", + "ĠEngine ering", + "ĠB esides", + "ti a", + "Ġen um", + "an ine", + "Ġto t", + "ĠE duc", + "Ġderiv ation", + "Ar ray", + "yl oid", + "ĠAr ch", + "is en", + "ac ity", + "ak ers", + "Ġshe et", + "ĠE st", + "Ġwe ar", + "Ġ eryth", + "EC K", + "hem atics", + "Ġarter ial", + "cript style", + "scripts criptstyle", + "echan ical", + "Ġparticip ation", + "c her", + "ur ance", + "ĠF R", + "ĠC V", + "Ġcomplement ary", + "ain e", + "empt y", + "Ġdig es", + "Ġexpon ent", + "Ġsim ulate", + "U E", + "Ġantib iotics", + "ĠUn ivers", + "Ġpath ology", + "ther mal", + "p a", + "Ġstress es", + "ĠLabor atory", + "N ode", + "Ġle ave", + "ash ing", + "Ġdisc re", + "Ġsusp ension", + "ree k", + "Ġschedul ing", + "ĠD A", + "ary n", + "ĠNa Cl", + "st rain", + "ST R", + "ĠC ong", + "ol f", + "Ġcal ibr", + "ĠOptim al", + "Ġ ó", + "G l", + "ĠR h", + "Ġdiffic ulties", + "Ġvess els", + "Ġas ymmetry", + "Ġco herence", + "ĠTaxon omy", + "Ġp ed", + "ĠH ouse", + "tit udes", + "ĠF ar", + "O Y", + "Ġconcentr ated", + "Ġsign alling", + "Ġfung al", + "Ġconsist ently", + "Ġenh ances", + "Ġfore cast", + "Ġc ubic", + "ĠE P", + "Ġparticip ate", + "ĠPl ant", + "r isk", + "A nd", + "ad ic", + "of lu", + "Ġsper m", + "ĠCh ris", + "N D", + "col on", + "Ġf aces", + "Ġtub erculosis", + "ryst al", + "flo or", + "up s", + "Ġg ray", + "ĠP ublic", + "t ensor", + "Ġrig id", + "Ġeas tern", + "ĠItal y", + "Ġsign atures", + "Ġshall ow", + "ó n", + "ĠC e", + "Ġpro jects", + "Ġro uting", + "Ġpredic ts", + "ĠFe atures", + "ĠDist rict", + "Ġcar rying", + "ĉ ĠĠĠĠ", + "ĠT O", + "H M", + "d ings", + "Ġre normal", + "Ġb ring", + "p in", + "al ed", + "Ġcloud s", + "n ames", + "ox in", + "Ġperp endicular", + "W T", + "ers hip", + "Ġrec on", + "Ġwork ed", + "ĠâĢ «", + "rastruct ure", + "Ġpo inted", + "E V", + "ĠT aylor", + "Ġhep atitis", + "Ġorb its", + "ĠF actors", + "c ellular", + "Ġf ocal", + "Ġbo ost", + "Ġmic rowave", + "ĠPro ject", + "B F", + "Ġpoli tical", + "Ġsupplement ed", + "Ġillustr ates", + "Ġide as", + "ĠDr ug", + "ob ile", + "ĠH O", + "Ġrobust ness", + "ros ine", + "ĠN ormal", + "Ġstim ulated", + "Ġimped ance", + "fort unately", + "zym e", + "Ġbar riers", + "act ory", + "lear ly", + "Ġpre print", + "sens itive", + "Ġturb ulent", + "th ing", + "Ġbo ard", + "Ġp it", + "Ġintegr ity", + "Ġrot ating", + "ud a", + "Ġv enti", + "ĠSN Ps", + "Ġcorrespond ence", + "Ġvisual ization", + "av ail", + "Ġbe ams", + "ĠCont inu", + "Ġpers istent", + "Ġb ath", + "Ġmi RNAs", + "Ġcust om", + "Ġord inary", + "Ġgener ators", + "Ġb ridge", + "Ġdom in", + "am y", + "Ġlo oking", + "t able", + "F alse", + "Ġsoil s", + "Ġmat ches", + "Ġprog ressive", + "st ates", + "ĠSh ort", + "Ġco res", + "Ġintro ducing", + "Ġar rest", + "Ġtext ure", + "Ġdors al", + "Ġd rain", + "iz oph", + "ĠQ ue", + "à ±", + "dis c", + "Ind ex", + "Ġext ensively", + "Ġplastic ity", + "Ġre ally", + "ĠEr ror", + "Ġsugg es", + "Ġconsequ ently", + "Ġperform s", + "lik ely", + "ive red", + "Ġtherm odynamic", + "Ġk er", + "Ġacet ate", + "Ġg ets", + "leq slant", + "Ġpredict ors", + "ĠSw ed", + "n an", + "he ter", + "Ġanomal y", + "Ġoper ational", + "Ġret rospective", + "Ġt ends", + "ad en", + "Ġb order", + "Ġmet hanol", + "ĠEn ter", + "Ġcoll apse", + "Ġpurch ased", + "D a", + "ĠH T", + "Ġf ulf", + "Ġcr ust", + "st one", + "Ġpen al", + "Ġtun n", + "ĠTem perature", + "Ġpot ent", + "lec ule", + "Ġco vers", + "Ġbatter y", + "Ġbe g", + "Ġor gans", + "ĠTh omas", + "Ġsol ub", + "oc rine", + "ĠSp in", + "Ġinterest s", + "d oc", + "Ġundergo ing", + "u i", + "Ġin herent", + "Ġintegr als", + "ira ble", + "as hi", + "Ġreg eneration", + "Ġinf lation", + "man if", + "ĠRec ognition", + "Ġdisplay s", + "An other", + "Ġcont amin", + "j unction", + "Ġcop ies", + "MR I", + "Ġvehic les", + "G et", + "Ġper haps", + "Ġw est", + "Ġint ensive", + "Ġs omething", + "Ġhypox ia", + "Ġcou plings", + "Ġfeas ibility", + "az ine", + "un ic", + "in er", + "ĠI T", + "Ġdist rict", + "ĠJ ames", + "e val", + "Ġplace bo", + "a que", + "Ġel ucid", + "ĠJac ob", + "Ġcoun ting", + "Ġflex ibility", + "Ġper man", + "Ġadv ances", + "ul ph", + "Ġent anglement", + "Ġinte gers", + "Ġfocus ing", + "k ov", + "Ġh ospit", + "Ġap plies", + "Ġc ot", + "S m", + "ass ium", + "Ġdocument ed", + "Ġload ed", + "Ġre ly", + "Ġinf ectious", + "Ġprob es", + "Ġhighlight ed", + "Ġp ediatric", + "Ġwe ather", + "Ġman ual", + "Ġc ation", + "Ġinterp olation", + "ĠSte p", + "ĠK al", + "D H", + "d b", + "izoph ren", + "ad er", + "car b", + "Ġag on", + "orph ous", + "t ors", + "at z", + "Ġb if", + "Ġcharg es", + "ĠAg ain", + "Ġb ron", + "ĠG over", + "Ġmin ing", + "a ver", + "Ġearth qu", + "Ġview s", + "Ġsc ene", + "par ameters", + "Ġbro ken", + "T est", + "ĠS um", + "ĠP rom", + "Î Ľ", + "Ġcut off", + "Ġb irds", + "Ġar ising", + "ĠA I", + "ĠC E", + "Ġpron ounced", + "asp ase", + "Ġint ended", + "Ġaff ine", + "Ġur ine", + "Ġbelie ved", + "ĠPrim ary", + "ĠCon f", + "Ġab dominal", + "sp in", + "un iform", + "ĠSt ochastic", + "ĠPro v", + "Ġmi RNA", + "ĠB ell", + "B O", + "ĠSoft ware", + "ĠT s", + "ut ri", + "ick ing", + "i en", + "Ġmic ros", + "ĠN R", + "Ġleuk emia", + "Ġsuper nat", + "f amily", + "Ġall oys", + "ĠP ET", + "ĠA bs", + "ĠG A", + "ĠQu antitative", + "L o", + "Ġis land", + "sec ond", + "pec tives", + "Ġlat ency", + "ang i", + "Ġfl ight", + "ĠE uclidean", + "em y", + "ĠBl ood", + "leuk in", + "L T", + "en h", + "Ġs we", + "Ġunit ary", + "ĠRep ublic", + "Ġstructure d", + "ĠS en", + "M n", + "cent ric", + "Ġtrans genic", + "Ġhelp ful", + "py x", + "Ġhome ostasis", + "N a", + "Ġpass ed", + "Ġe yes", + "Ġab stract", + "ul se", + "Ġmir ror", + "Ġregul ator", + "Ġmur ine", + "load ed", + "Ġmod ular", + "Ġlandsc ape", + "ic ks", + "Ġs now", + "Ġb ovine", + "ell i", + "Ġdatab ases", + "Ġout break", + "l arg", + "ĠR un", + "B E", + "Ġsur prising", + "Ġaccept able", + "Ġrot ational", + "p g", + "F E", + "w ik", + "Ġy ounger", + "ash ion", + "Ġmic roscopic", + "reg ation", + "Ġfib r", + "ĠPl an", + "Ġha pl", + "Ġmanif olds", + "Ġout per", + "Ġcho osing", + "e per", + "Ġke V", + "ĠT yp", + "p read", + "nt z", + "ĠRe port", + "ĠMat rix", + "Ġint u", + "Ġproper ly", + "og ly", + "oscop ic", + "ĠA MP", + "ĠB M", + "Ġelement ary", + "kele ton", + "Ġsyn thase", + "Ġion ization", + "b es", + "oph age", + "duc es", + "acc o", + "Ġprot ect", + "ĠCo ul", + "Ġsp ent", + "Ġm and", + "Ġh ind", + "flu or", + "ĠG ood", + "Ġdo ing", + "Ob ject", + "duc ts", + "o yl", + "chi atric", + "Ġo v", + "c el", + "Ġb ases", + "Ġmitochond ria", + "p ted", + "art z", + "Ġb rown", + "Ġequ als", + "ti ble", + "Ġopportun ity", + "az ol", + "Ġoff icial", + "ail ed", + "Ġur inary", + "ĠH an", + "B e", + "res ult", + "un its", + "Ġb ad", + "ĠSt ring", + "iz able", + "con dition", + "ĠElect ron", + "immun e", + "ĠM E", + "ha o", + "Î £", + "ĠM AT", + "Ġad opt", + "Ġel ic", + "Ġsh r", + "Ġproxim al", + "F D", + "ĠS S", + "Ġentire ly", + "es ium", + "ĠE EG", + "Ġpa ired", + "ĠT P", + "ĠD O", + "NA L", + "ides pread", + "Ġmov es", + "s ite", + "Ġra in", + "Ġl ap", + "ĠF u", + "ĠM eta", + "irc raft", + "Ġmagne tization", + "oper ation", + "Ġpro st", + "Ste p", + "Ġsubgroup s", + "ĠS outhern", + "Ġat he", + "lu or", + "ĠTaxon omic", + "ĠE instein", + "Ġr ace", + "ĠK en", + "Ġattem pts", + "Ġcos mic", + "ĠD op", + "Ġfix ation", + "Ġremov ing", + "B T", + "Ġlim b", + "Ġal ign", + "Ġd ried", + "d u", + "Ġput ative", + "uc cess", + "per t", + "Ġslow ly", + "al so", + "ol ip", + "Ġcl ient", + "Ġbas in", + "Ġsuscepti ble", + "Ġcom ing", + "ns on", + "ĠN GC", + "ass ert", + "Ġtens ile", + "Ġar ises", + "cut aneous", + "Ġc aro", + "B i", + "Ġdiscuss ions", + "Ġabnormal ities", + "Ġpoll ution", + "ĠA x", + "Ġload s", + "D o", + "ia o", + "Ġmed ication", + "Ġint act", + "ĠC X", + "Ġbre eding", + "ĠUn ion", + "ĠB at", + "ĠPar ticipants", + "ĠReg ulation", + "Ġcontrad iction", + "Ġint ensities", + "ence phal", + "ri le", + "ĠT LR", + "Ġred und", + "Ġpers ons", + "ĠAr c", + "sol id", + "l aw", + "Res ults", + "il ic", + "z one", + "ocyt osis", + "Ġtri angle", + "ST M", + "ĠV irus", + "Ġa id", + "so ft", + "Ġso on", + "exp ected", + "Ġan ch", + "ĠM u", + "ĠS r", + "ĠL O", + "Ġc ry", + "Ġup stream", + "ox ic", + "math it", + "ĠK le", + "Ġis otropic", + "Ġspati ally", + "ĠH ard", + "Ġext r", + "b as", + "e or", + "iv il", + "y an", + "Ġshif ted", + "Ġbi opsy", + "Ġfe el", + "gl ut", + "S ize", + "Ġ erg", + "ĠT er", + "Ġdeath s", + "bor ne", + "Ġrel ativistic", + "ĠV EGF", + "at ab", + "s pring", + "res tim", + "ĠS earch", + "yp henyl", + "ec al", + "ur c", + "Ġl amin", + "Ġser ial", + "l as", + "ĠPro duction", + "Ġsoci o", + "Ġmod ify", + "ĠServ ice", + "Ġb ary", + "Ġradi ative", + "big l", + "Ġparad igm", + "pati ent", + "Ġsp p", + "ph one", + "Ġ î", + "Ġro cks", + "ĠMart in", + "m n", + "Ġflu ids", + "ĠIN TR", + "od s", + "Ġdiv is", + "Cons ider", + "comp onent", + "Ġanomal ies", + "Ġk nee", + "ĠRelations hip", + "a ud", + "Ġover night", + "Ġra inf", + "Ġanne aling", + "Ġtre ating", + "Ġco arse", + "Mod el", + "Ġp ose", + "Ġocc as", + "ĠWilli am", + "o or", + "Ġadjust ment", + "ĠF unctions", + "im eter", + "Ġdet ectors", + "Ġinstit utional", + "Ġthrough put", + "iv idual", + "Ġenti ties", + "Ġprolong ed", + "Ġsh ip", + "Ġpres erved", + "ODU CTION", + "Ġlog istic", + "ĠPred iction", + "ti zed", + "ĠOr ig", + "ĠH em", + "onom ous", + "######## ########", + "ĠGen eration", + "b ottom", + "ĠK now", + "cl inical", + "Ġtra uma", + "Ġiter ative", + "Ġfac ility", + "ron t", + "ĠB us", + "Ġret inal", + "Ġcon duction", + "Ġcheck ed", + "Ġcall s", + "olog ists", + "C ON", + "ĠSc iences", + "Ġnon zero", + "Ġb rack", + "Ġmel ting", + "Ġas c", + "Ġmen tion", + "ĠB L", + "Ġver ification", + "uk ary", + "ĠSp atial", + "ĠG ram", + "Ġpl aces", + "Ġnec rosis", + "ĠChild ren", + "Ġdel ivered", + "Ġres ection", + "Ġdetermin istic", + "S ection", + "Ġmul tim", + "D F", + "Ġhypot heses", + "Ġra ise", + "Ġse ismic", + "Ġl am", + "ĠH CC", + "big r", + "Ġhe aling", + "is y", + "Ġoptim ize", + "obacter ium", + "ed y", + "Ġtr uth", + "Ġspace time", + "Ġchrom atin", + "Ġdom estic", + "Ġrec ru", + "ĠJ ose", + "ĠTherm al", + "Ġenvelop e", + "v able", + "Ġinc ons", + "Ġn od", + "Ð ¸", + "Ġcontrib uting", + "Ġguarant ee", + "ĠP hen", + "Ġra b", + "M an", + "Ġsurve illance", + "Ġth ings", + "Ġpre v", + "ĠNon linear", + "Ġg aps", + "ay a", + "ĠC ri", + "Ġcrystall ine", + "str ict", + "Ġcomput ations", + "Ġun able", + "h abil", + "um ina", + "Ġpromot ing", + "eg rad", + "Ġreg ister", + "Ġcross ing", + "ul ators", + "ĠL anguage", + "ĠA A", + "Ġin er", + "ĠL V", + "os an", + "Ġcoast al", + "Ġbi od", + "ĠM OD", + "Ġneighb our", + "Ġpredominant ly", + "ĠNew ton", + "ĠStr ateg", + "be ing", + "Ġ ì", + "Ġcap abilities", + "Ġun less", + "form al", + "Ġvess el", + "b matrix", + "ES S", + "Ġrainf all", + "à £", + "Ġpre par", + "ax ial", + "Ġd ental", + "ĠPro te", + "Ġwor se", + "d oped", + "hen tic", + "Ġvalid ate", + "Z n", + "Ġspec ification", + "s i", + "ĠAn g", + "Ġtub es", + "ul ic", + "ĠAn y", + "ĠM ap", + "Ġfabric ated", + "Ġfor ced", + "ĠWil son", + "ol ysis", + "ĠW ave", + "ĠC ast", + "Ġast hma", + "Ġper i", + "ĠC yt", + "ast y", + "Ġsk y", + "rup t", + "D ec", + "Ġmelan oma", + "P ER", + "Ġcontinu ity", + "B ox", + "s ystem", + "Ġn avig", + "Ġcirc ulating", + "Ġcolon y", + "less sim", + "ad ium", + "Ġtet ra", + "Ġacc ounts", + "Ġpresent ing", + "ĠL ik", + "Ġres is", + "Ġdamp ing", + "ĠG ly", + "ĠNeu ro", + "us er", + "Ġcap ital", + "ur ate", + "ĠM W", + "Ġcorrel ates", + "ĠG ib", + "Ġhapp ens", + "Ġg all", + "ĠWith in", + "Ġcomb ine", + "Ġsin us", + "ĠK in", + "**************** ****************", + "M ap", + "Ġmat uration", + "Ġblock ing", + "ĠClo ud", + "Ġcont acts", + "Ġs ac", + "AL L", + "ĠR ab", + "z z", + "ut ch", + "Ġcar riers", + "ĠSN R", + "er b", + "Ġprot ected", + "rack ing", + "radi ent", + "Ġattrac tive", + "Ġl ag", + "Ġop in", + "ĠG i", + "Ġdef ense", + "Ġtun ing", + "Ġelect roph", + "Ġgreat est", + "Ġreconstruc ted", + "ĠPop ulation", + "M AP", + "Ġw rote", + "AN D", + "ec onomic", + "ĠMich ael", + "ĠBl ock", + "Ġv o", + "op rop", + "Ġprof iling", + "oot st", + "ĠAs ian", + "Ġoscill ation", + "ĠâĨ IJ", + "U D", + "Ġsign ed", + "ĠE uler", + "ĠCompar ative", + "ĠW here", + "ĠJ ack", + "Ġpass ing", + "Ġvill age", + "Ġa u", + "ĠNor thern", + "ess age", + "m atic", + "Ġaff ili", + "ĠF ac", + "Ġoverl apping", + "she ll", + "Ġobst ac", + "Ġbec oming", + "enti ve", + "Ġeas ier", + "init ely", + "Ġcent ered", + "Ġac ademic", + "ann els", + "Ġir regular", + "Ġproj ections", + "Ġpro position", + "Ġdiscrim ination", + "Ġrem od", + "Ġsh oot", + "mon th", + "ess or", + "Ġdiff ers", + "ĠT V", + "ĠZ hou", + "Ġin her", + "Ġmach ines", + "Ġm ell", + "Ġconjug ate", + "Ġc oc", + "un a", + "an yl", + "Ġoff ic", + "Ġopportun ities", + "Ġve in", + "ĠCharacter istics", + "Ġpath ogenic", + "OY SA", + "ĠPark inson", + "ĠGal actic", + "FF FA", + "ys es", + "UH FFFA", + "UHFFFA OYSA", + "act in", + "Ġun us", + "hes ia", + "ace u", + "ad ow", + "os ide", + "Ġgly cos", + "Ġdil uted", + "ĠS ource", + "ol ated", + "arm aceu", + "ant om", + "Ġmus c", + "Ġaver aging", + "Ġvis it", + "Ġc atch", + "Ġsatisf action", + "Ġv on", + "val id", + "Ġyield ed", + "Ġpack ets", + "Ġreson ant", + "p ret", + "ĠG FP", + "Ġcut ting", + "Ġreplac ing", + "az e", + "P a", + "Ġto day", + "Ġdec ided", + "il ateral", + "im ate", + "l ings", + "ĠRob ust", + "ĠA st", + "od ynamics", + "Ġlack ing", + "izophren ia", + "Ġcont raction", + "um ann", + "ĠS ample", + "Ġdi amond", + "met hod", + "T OR", + "Ġcom ments", + "se y", + "Ġmanufact uring", + "ĠD a", + "N R", + "Ġoper ated", + "r ates", + "Ġextinc tion", + "u vant", + "ĠF inite", + "Ġlymph ocytes", + "b ro", + "om ology", + "Ġinstr uments", + "b ec", + "og le", + "Ġqu oti", + "Ġhyper bolic", + "Ġtr im", + "Ġp ap", + "atur ated", + "h aus", + "Ġs essions", + "Ġcamp aign", + "Ġvari eties", + "Ġpro jected", + "Ġr id", + "b one", + "Ġanc est", + "ĠE T", + "ma il", + "ĠTrans port", + "// /", + "ĠAn n", + "Ġcompos itions", + "ĠINTR ODUCTION", + "ĠâĪĴ âĨĴ", + "Ġwhen ever", + "ĠL ip", + "par ts", + "Ġis omorphic", + "Ġsulf ate", + "Ġh op", + "Ġg on", + "ĠOb ject", + "Ġpip eline", + "Ġm a", + "ĠG as", + "Ġtend ency", + "ob ject", + "Ġparamet ric", + "ĠRet urn", + "Ġd war", + "Ġpress ures", + "ĠBi os", + "Ġmulti plication", + "Ġdim in", + "Ġcol ors", + "ĠTr ue", + "M ax", + "ĠD epend", + "Ġpair wise", + "Ġl ake", + "Ġhierarch y", + "Ġthresh olds", + "ĠAdap tive", + "m aking", + "Ġcataly sts", + "ip al", + "Ġeg gs", + "Ġw ire", + "ophyl l", + "ict or", + "label ed", + "Ġmus cles", + "ĠUnder standing", + "Ġfib re", + "cont rolled", + "Ġinvari ance", + "Ġc ache", + "Ġbos on", + "Ġnear by", + "ĠW omen", + "ĠIn itial", + "Ġprob abilistic", + "Ġembry onic", + "ĠB etween", + "Ġcon jecture", + "i enti", + "t x", + "g ens", + "anc k", + "Ġg ir", + "ĠL ower", + "Ġhospit als", + "brid ge", + "Met hod", + "Ġthe ta", + "j a", + "Ġconcept ual", + "Ġcol le", + "ĠS af", + "d ic", + "Ġp et", + "Ġprim er", + "ĠO h", + "Ġun treated", + "long rightarrow", + "Ġl icense", + "Ġhel ps", + "Ġcle avage", + "Ġampl ified", + "Ð µ", + "Ġaccess ible", + "ĠSe lection", + "ĠLore ntz", + "P y", + "Ġpolar ized", + "ĠST AT", + "mit t", + "U p", + "Ġon going", + "Ġne ph", + "e fficient", + "ac tiv", + "ĠR R", + "Ġfunction ing", + "ot in", + "Ġl ists", + "Ġformal ism", + "Ġoscill ator", + "Ġgastro intestinal", + "ootst rap", + "ĠAs ia", + "ĠD ay", + "Ġcomp eting", + "ival ent", + "Ġbl adder", + "Ġh it", + "Ġapproxim ations", + "ĠE g", + "ĠCl ust", + "Ġrel ies", + "N E", + "cop ro", + "Ġb ank", + "Ġintegr ating", + "ĠH ear", + "Ġiniti ated", + "ac ryl", + "ĠB H", + "rac ted", + "y c", + "ĠR a", + "Ġremark able", + "Ġ Ë", + "ten ess", + "Ġemploy ing", + "ste ine", + "Ġï£ Ń", + "Ġtransf ected", + "Ġinj uries", + "ĠB rief", + "Ġw idespread", + "ĠA K", + "IV E", + "Ġh arm", + "Ġpo le", + "Ġanis otropic", + "at en", + "gen e", + "iv ariate", + "In ter", + "duct ors", + "Ġaccom pl", + "oglob in", + "c ong", + "Ġqu eries", + "escop e", + "ĠH op", + "Ġenti ty", + "Ġoff ered", + "St ate", + "ĠExperim ents", + "ann er", + "ĠW ood", + "ard ed", + "ag on", + "Ġfibrobl asts", + "Ġnan os", + "Ġper oxid", + "Ġev id", + "Ġï£ ¸", + "Ġre tained", + "os qu", + "Ġle aving", + "Ġf ashion", + "Ġn M", + "Ġmut ual", + "appro xim", + "Ġwalk ing", + "Ġim possible", + "Ġdemonstr ating", + "Ġde gener", + "ĠA V", + "Ġcont rary", + "us tion", + "ocl onal", + "A nal", + "Ġperform ances", + "Ġcomp rom", + "orm s", + "Ġbud get", + "ĠH aw", + "Ġarth ritis", + "ob j", + "no ise", + "Ti O", + "och rome", + "Ġge odes", + "be an", + "Ġselec tivity", + "ĠF ood", + "ugh ter", + "Ġpermut ation", + "ĠR P", + "os al", + "Ġadi p", + "armaceu tical", + "w hen", + "ĠT ext", + "we ek", + "Ġbond ing", + "ar b", + "oc or", + "Ġv oc", + "Ġup regulated", + "Ġneighb ors", + "Ġtra it", + "Ġthe ore", + "Ġc f", + "ĠB erg", + "ĠL A", + "Ġl as", + "un te", + "cept ual", + "AS E", + "Ġischem ic", + "Ġb ending", + "d ataset", + "Ġkeep ing", + "Ġar rows", + "Ġsubst ances", + "Ġn s", + "Ġext ending", + "ĠR u", + "Ġsupplement ation", + "cri tical", + "ĠT raining", + "bul let", + "Ġpar a", + "ta il", + "ĠRef erence", + "Ġï£ ¶", + "Ġdissip ation", + "Ġaux iliary", + "ĠCy cl", + "s tim", + "Ġdil ution", + "bu f", + "ĠM iss", + "Ġul timately", + "Ġpow ers", + "Ġst ands", + "ust ed", + "ĠO H", + "habil itation", + "an aly", + "ĠB ra", + "ad ding", + "Cor ollary", + "Ġd rought", + "qu ality", + "Ġstandard ized", + "ĠJ e", + "ĠAc id", + "Ġm ism", + "ĠCh rom", + "d raw", + "ĠBi om", + "ĠSt ability", + "Further more", + "l ast", + "v ic", + "Ġab st", + "Ġb is", + "Ġemerg ence", + "Ġg iant", + "D e", + "ĠS amples", + "AB A", + "n as", + "Ġon t", + "Ġev ap", + "le vant", + "m ain", + "ĠR od", + "Ġc ros", + "it ary", + "Ġdo ub", + "r ö", + "igene tic", + "Ġincom plete", + "dep th", + "ï ģ", + "Ġsatur ated", + "Ġaeros ol", + "As sum", + "Ġimmun os", + "Ġlip ids", + "itone al", + "Ġbe aring", + "ĠIm plications", + "Ġsustain ed", + "Ġcompe titive", + "Ġmo tivation", + "Ġdisturb ance", + "rystall ine", + "Ġtax a", + "Ġdem entia", + "Ġconcer ned", + "PI O", + "hom ogeneous", + "ĠE v", + "ĠGe orge", + "ĠAlgorithm s", + "ick el", + "us ively", + "Ġcor ner", + "ĠR est", + "Ġinf inity", + "ĠTrans form", + "hen g", + "Ġneuro de", + "ol im", + "Í ij", + "Ġsk ew", + "ĠB S", + "sc ore", + "Y PE", + "em an", + "el le", + "ĠCor relation", + "Ġcult ural", + "oph osph", + "Ġatten uation", + "Ġaggreg ate", + "Ġam big", + "Ġanomal ous", + "Ġt ors", + "Ġplan et", + "ĠN Ps", + "h r", + "ĠDiv ision", + "ĠEduc ation", + "lec tic", + "Ġb rought", + "ĠM orph", + "Ġplan es", + "Ġsug ar", + "Ġdend ritic", + "Ġcont our", + "Ġcylind er", + "p ost", + "Ġw ent", + "R L", + "Ġad mission", + "MS E", + "I X", + "Ġdis joint", + "Ġannot ation", + "Ġis otope", + "Ġμ ν", + "Ġelim inate", + "Ġre actor", + "on ents", + "Ġreason ing", + "Ġm orbidity", + "Ġcor rosion", + "other mal", + "arc tic", + "ĠM B", + "ĠZ hao", + "Ġhist ological", + "Ġsuperconduc ting", + "at tered", + "Ġhouse hold", + "ĠPro p", + "Ġass er", + "he red", + "Ġte ams", + "Ġvan ishes", + "P re", + "am ents", + "Ġam orphous", + "ĠDeterm ination", + "miss ions", + "Ġover head", + "det erm", + "Ġutil izing", + "f a", + "ip olar", + "Ġform ulated", + "Ġext rap", + "gr id", + "Ġhum idity", + "ub er", + "t umor", + "ro us", + "Ġdistor tion", + "d ynamics", + "ĠL oss", + "Ġscal ed", + "Ġischem ia", + "Ġax es", + "Ġqu antit", + "n it", + "ĠReg ion", + "ain ed", + "Ġf ill", + "Ġbran ching", + "ĠT iss", + "c ross", + "Ġplate let", + "iffiff iffiff", + "ro ps", + "lu x", + "j oin", + "ur acy", + "ic ide", + "ĠLou is", + "Ġï£ «", + "Ġstr ings", + "ys et", + "Ġfac ial", + "ĠM MP", + "RE S", + "Ġhydro lysis", + "ĠCan adian", + "Ġpro jective", + "Ġsc atter", + "ur on", + "ĠPsy ch", + "com plex", + "ĠN am", + "Ġconc urrent", + "ION S", + "Ġth ous", + "Ġch ance", + "Ġplac ement", + "Ġaware ness", + "Ġt rib", + "ĠT ex", + "ĠTh ird", + "Ġlabel ing", + "cer ol", + "Ġs aw", + "ĠB and", + "ĠP ear", + "Ġpregn ant", + "ĠD own", + "pl atin", + "S eq", + "x e", + "ethyl ene", + "ĠHig her", + "Ġre ality", + "ur is", + "ĠP AR", + "l b", + "d ose", + "sh if", + "ili ar", + "t otal", + "S W", + "Ġval ve", + "nd er", + "Ð ½", + "am ous", + "Ġend omet", + "LI SA", + "Ġfract ures", + "Ġfil t", + "ro le", + "Ġmicro structure", + "ĠSN P", + "T ER", + "ĠZn O", + "ov ing", + "al i", + "ĠG M", + "unc t", + "Ġext ensions", + "exp ression", + "Ġesc ape", + "ĠM as", + "ĠSp anish", + "Ġflo or", + "ĠCom mon", + "otop y", + "plement ation", + "Ġr hyth", + "Ġserv es", + "y to", + "Ġwavelength s", + "empt yset", + "ĠH ill", + "n or", + "ĠElect ro", + "Ġde hydrogen", + "Ġwh om", + "im etric", + "ĠR oman", + "ĠV e", + "âī ¥", + "ĠK u", + "ĠTrans fer", + "Ä ĩ", + "ĠT F", + "b rain", + "copro tein", + "ĠG reat", + "av en", + "ĠInd ividual", + "ur i", + "Ġfung i", + "Ġpar am", + "pt on", + "s ymmetry", + "Ġloc k", + "me as", + "Ġha em", + "Ġh ip", + "As s", + "eng er", + "Ġpot assium", + "an al", + "ibr ary", + "Ġschool s", + "n atal", + "Ġalle les", + "ĠH LA", + "ox ygen", + "ĠC up", + "Ġpure ly", + "D O", + "Ġch ip", + "ô ı", + "C ar", + "s il", + "Ġun likely", + "cor respond", + "ĠD P", + "Ġint ense", + "Ġfor cing", + "ĠJ ournal", + "Ġar row", + "ocy an", + "Ġcul tiv", + "Ġbl ind", + "Ġselect ing", + "oc arcinoma", + "ran ce", + "Ġhydroph obic", + "clos ed", + "Ġens ures", + "Ġprom oted", + "Ġdetect able", + "rane an", + "Ġsched ule", + "Ġpart ly", + "Ġgl and", + "Ġco uple", + "ĠEm erg", + "Ġtrac es", + "p oly", + "Ġprote ase", + "ys tic", + "Ġdoc uments", + "pos itions", + "Ġdri ver", + "ti um", + "ĠC YP", + "cl ose", + "ĠRec ep", + "Ġper mit", + "Ġblock ed", + "Ġinvestig ating", + "ĠT umor", + "ĠB ig", + "Ġwave gu", + "Ġsubst ance", + "Ġweak er", + "ĠM ont", + "ro vers", + "ĠMex ico", + "p res", + "ĠAc ute", + "Ġmicro gl", + "ĠE S", + "itor ing", + "ĠSer ies", + "l ights", + "Ġhypot hesized", + "Ġconstruc ts", + "Ġfilt ration", + "Bl ack", + "Ġun changed", + "Ġobserv able", + "Ġra y", + "b etween", + "Ġï£ ¯", + "ĠPos ition", + "Ġth i", + "ĠSystem atic", + "Cl ass", + "k m", + "ĠT ak", + "Ġrespond ents", + "Ġinn ate", + "Ġan t", + "Ġconn ecting", + "R el", + "Ġmanip ulation", + "ĠN eg", + "N Ps", + "ĠDi ab", + "ĠAc tive", + "ĠG all", + "ĠCoul omb", + "Ġspac ing", + "ĠF lor", + "Ġconduct ance", + "Ġtrac ks", + "ĠZh u", + "weight ed", + "ro cy", + "Ġfat her", + "id ium", + "struct ured", + "ĠT el", + "Ġst rom", + "ith ub", + "cer tain", + "B ut", + "ĠAc cess", + "Ġprevent ing", + "rest rial", + "ĠCons idering", + "tr ue", + "Ġhost s", + "Ġwor st", + "ĠP d", + "gre di", + "Ġgly col", + "Ġst ory", + "osqu ito", + "par atus", + "Ġme eting", + "Ġepis ode", + "n c", + "ĠS and", + "Ġu int", + "ynam ical", + "ur t", + "Ġeduc ational", + "Ġfocus es", + "g t", + "ĠH S", + "Ġdeterm inant", + "Ġlith ium", + "ĠDig ital", + "Ġguid ance", + "Ġprior ity", + "Ġpar ty", + "or ial", + "T wo", + "ĠProblem s", + "Ġsem an", + "ĠCN N", + "ĠE pid", + "Ġplay ing", + "Ġelim ination", + "ĠS at", + "Ġobj ectives", + "p lectic", + "Ġcircum st", + "ĠG S", + "oc ellular", + "ot rans", + "Ġfind s", + "Ġa romatic", + "iz ers", + "Ġfavor able", + "st andard", + "ich lor", + "mod els", + "otyp ing", + "Ġstabil ization", + "Ġhand ling", + "Ġco ated", + "e ven", + "Ġlet ter", + "Z E", + "Ġultr ason", + "Ġf riend", + "Ġsens iti", + "Ġatt achment", + "Ġap art", + "Ġgre y", + "Ġa ircraft", + "Ġr RNA", + "Ġenabl ed", + "Ġbu ff", + "Ġred ox", + "ass isted", + "Ġgener ality", + "PS S", + "Ġe lection", + "resp onse", + "Ġded icated", + "Ġdem ographic", + "Ġim posed", + "ĠK ir", + "ĠRad io", + "ĠE LISA", + "ga e", + "Ġres c", + "ĠR ic", + "raph ic", + "Ġra il", + "Ġj ournal", + "ol er", + "W S", + "Ġincorpor ation", + "w ind", + "Ġaud itory", + "A E", + "t ask", + "Ġp c", + "w all", + "Ġapp rec", + "aterial s", + "Ġpart ner", + "Ġcollec tive", + "Ġsc oring", + "ĠFran k", + "Ġperman ent", + "ĠI ran", + "um ination", + "M ed", + "ĠHy brid", + "Ġphen otypic", + "Ġdisrup tion", + "vi olet", + "osp heric", + "Ġregim es", + "ĠCol or", + "ĠPati ent", + "Ġf ever", + "Ġn n", + "Ġvari ational", + "ke ys", + "Ġdis till", + "Ġspect roscopic", + "ĠAr chitect", + "ac ing", + "Ġpro ves", + "Ġver teb", + "ĠComput er", + "Ġexp ensive", + "Ġfro zen", + "arcom a", + "N K", + "Ġhist one", + "Ġpolymer ization", + "Ġto b", + "Ġturn ed", + "eff ective", + "ĠAut hor", + "AP I", + "Ġdec ade", + "ĠRo bert", + "Ex ample", + "over set", + "AB LE", + "ĠBehavi or", + "f eed", + "ĠT ai", + "Ġï£ º", + "Ġe gg", + "Ġc ath", + "au x", + "ĠJoh nson", + "Ġtor que", + "Ġpur ification", + "Wh ite", + "c ious", + "ĠS ong", + "Ġprecip it", + "resh old", + "Ġm ilitary", + "Ġconv ection", + "ĠM iddle", + "ĠW he", + "Ġ ôı", + "al and", + "ar ation", + "fig ure", + "Ġded uce", + "chlor o", + "c ost", + "ithm etic", + "ĠItal ian", + "miss ible", + "ĠCommun ity", + "ĠN ature", + "Ġdi oxide", + "Ġbal anced", + "et t", + "ST AT", + "ild ing", + "Ġev olved", + "Ġmon ot", + "p ur", + "Ġpref erences", + "ding er", + "Ġarg ue", + "Ġmo tions", + "Ġinf ant", + "Ġaccel erated", + "Ġobser ver", + "Ġfabric ation", + "ĠMechan isms", + "Ġfunc tor", + "Ġhar ves", + "r ase", + "ĠSpec ial", + "Ġdepos its", + "Ġr ub", + "à ¸", + "ĠCP U", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ", + "atom ical", + "Ġfin it", + "Ġsec ure", + "Ġnutri tional", + "ren al", + "ĠF alse", + "Ġshe l", + "Ġrecru ited", + "am big", + "ĠSign aling", + "K O", + "organ isms", + "ĠL T", + "el en", + "ĠM arc", + "ab atic", + "Ġt ables", + "Ġconf ined", + "ĠA z", + "Ġproduc tivity", + "Ġad herence", + "Ġreplic ates", + "Ġvir t", + "f in", + "Ġagric ultural", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠ", + "ĠCh ampionship", + "and a", + "ĠCh urch", + "D uring", + "Ġinser ted", + "igh ter", + "Ġx en", + "Ġs ave", + "Ġtang ent", + "ven ous", + "Ġconver ge", + "Ġdistingu ished", + "Ġexpl os", + "Ġa ortic", + "Ġj ump", + "Ġneon atal", + "ud den", + "Ġslow er", + "Ġinfarc tion", + "Ġpre vents", + "u er", + "Ġ eros", + "R P", + "Ġcontin ues", + "OR T", + "Ġconsid ers", + "ĠN uclear", + "ly mp", + "Ġacc ounted", + "ores is", + "Ġneighbor ing", + "ĠRich ard", + "Ġen for", + "ĠCh ronic", + "Ġdisc over", + "ĠH ong", + "cell s", + "ĠCh all", + "Ġhom ogen", + "Ġathe ros", + "Ġisol ate", + "ĠPlas ma", + "ĠD L", + "par ametric", + "ĠUp per", + "H P", + "Ġintro duces", + "Ġmother s", + "Ġatt ract", + "Ġexcl usion", + "gra vity", + "ĠK r", + "Ġsp ike", + "ĠHe at", + "v ival", + "ĠRNA s", + "b ach", + "ator ial", + "ĠL td", + "on omy", + "in vasive", + "l ass", + "Ġwell s", + "Ġimag inary", + "Ġcarb ohyd", + "od a", + "Ġactiv ate", + "µ Ħ", + "Ġenzym atic", + "p es", + "Ġstat ements", + "Ġapproxim ated", + "ĠSal mon", + "ophage al", + "ĠH PV", + "con f", + "um at", + "Ġsulf ur", + "ĠRec all", + "Ġch ond", + "Ġvi able", + "por ation", + "Ġcare fully", + "tet ra", + "Ġlymph oma", + "st at", + "Ġconserv ative", + "atab ase", + "m and", + "Ġsc ored", + "Ġv as", + "Ġpri vacy", + "onym ous", + "Ġlogarithm ic", + "ĠE con", + "Ġachie ves", + "Ġabund ances", + "c am", + "Ġcy an", + "ĠE L", + "idel ity", + "j o", + "Ġan ticip", + "re ported", + "Ġarrang ement", + "iter ranean", + "ps is", + "ich i", + "Ġt a", + "um ping", + "ĠAc tivation", + "Ġmel t", + "Ġan no", + "og e", + "ĠD am", + "optim al", + "Ġneu rological", + "s a", + "ĠPar ameters", + "off set", + "Ġc ement", + "Ġinhib iting", + "Ġch ose", + "itz er", + "at tr", + "Ġmod er", + "ator ies", + "Ġte aching", + "ĠC ore", + "ph thal", + "ĠL uc", + "Ġin gredi", + "Ġclear ance", + "Ġachie ving", + "t age", + "Ġbur st", + "vi e", + "ĠSp ain", + "pt o", + "Ġtrans membrane", + "Ġsup plementary", + "Ġto ken", + "Ġobvious ly", + "ĠV ector", + "Ġdest r", + "H OD", + "Ġassum es", + "Ġpenet ration", + "Ġsub jective", + "h olds", + "ã o", + "Ġmo tiv", + "Ġprovid ers", + "v ascular", + "Ġdepart ment", + "ock et", + "F ile", + "Ġbre ath", + "ĠB est", + "gra ble", + "Ġl iqu", + "ĠAr g", + "ĠB ob", + "Ġfrag mentation", + "ec tic", + "Ġv ital", + "s ince", + "all oc", + "ox yphenyl", + "Ġradi otherapy", + "ĠSD S", + "Ġcyt ometry", + "n ucle", + "ĠI M", + "ĠTe V", + "raf ish", + "ĠKore a", + "Ġstreng then", + "Ġb are", + "Ġw oman", + "Ġrad ar", + "Ġplatform s", + "ozyg ous", + "ĠA h", + "Ġsub types", + "py rid", + "ĠTrans cription", + "Ġá º", + "ĠMeasure ments", + "Ġsurv iv", + "ĠN ear", + "Ġcasc ade", + "out he", + "B U", + "Ġexpon entially", + "Ġhaz ard", + "Ġsi RNA", + "Ġcell ulose", + "Fig s", + "Ġdifferenti ated", + "Ġim plicated", + "met ric", + "Ġcorrel ate", + "Ġm ission", + "Ġmant le", + "ĠP hyl", + "ĠH art", + "Ġg ases", + "Ġun ity", + "Ġexper t", + "Ġchar t", + "Ġd ict", + "Ġep ile", + "Ġoff spring", + "Ġemerg ed", + "Ġdem ands", + "Ġpres um", + "orb id", + "ĠMed icine", + "Ġstream s", + "tic ed", + "ĠN ic", + "Ġf illing", + "ĠC ro", + "Ġrestric tions", + "S ee", + "ĠM ill", + "Ġparent al", + "Ġdetermin ants", + "Ġecos ystem", + "ĠW all", + "ĠM emory", + "ple ts", + "Ġaggreg ates", + "per turb", + "Ġresid ents", + "AC K", + "v ectors", + "Ġman ually", + "Ġï ĺ", + "ĠFrame work", + "Ġv ag", + "eb rafish", + "l ib", + "ĠHear t", + "ĠAn imal", + "Ġwid er", + "G ene", + "ĠR os", + "Ġoper ate", + "Ġposs ibilities", + "ĠStr ong", + "Ġpy ro", + "resp ectively", + "Ġhybrid ization", + "ip edia", + "x in", + "Ġst om", + "f ish", + "ĠFor ce", + "Ġdim er", + "SU L", + "el se", + "Ġund e", + "g ar", + "con v", + "Ġarri val", + "Ġmon oclonal", + "I AL", + "Ġl y", + "Ġsymmet ries", + "Ġnur sing", + "rac h", + "Ġó µĦ", + "Ġbi ased", + "Ġc ues", + "Ġbiomark er", + "d ers", + "Ġc row", + "ern els", + "Ġbil ateral", + "Ġphys ically", + "Ġpat ches", + "Ġunc on", + "ĠB efore", + "def ault", + "est yle", + "t frac", + "ĠC ox", + "Ġinf iltration", + "Ġconver t", + "Ġstreng ths", + "ĠS ar", + "ig ible", + "oc omp", + "Ġsti r", + "Ġsch izophrenia", + "w as", + "Ġo w", + "et erm", + "ĠOr der", + "Ġf oss", + "Ġline age", + "Ġrab bit", + "Ġregular ization", + "ran ch", + "opl astic", + "T O", + "Ġmeas urable", + "Ġm ang", + "in itial", + "Ġbuild ings", + "Ġsystem atically", + "Ġferm ions", + "Ġlibr aries", + "Ġab lation", + "ide os", + "ĠW i", + "ph oton", + "ĠTest ing", + "ĠComput ing", + "ti er", + "in et", + "Ġprim itive", + "Ġcap illary", + "Ġsl ip", + "ver gence", + "rap eutic", + "ĠBl ue", + "ĠAc ad", + "ha i", + "ĠL ew", + "Ġtri angular", + "MS O", + "Ġsal inity", + "Ġnanoc om", + "o a", + "Ġhom omorphism", + "ĠM M", + "Ġres in", + "D B", + "um inescence", + "d ashed", + "ĠK h", + "qu ark", + "emb les", + "Ġidentif ies", + "Ġfol lic", + "Ġmet am", + "ĠH erm", + "Ġtob acco", + "Ġreal ization", + "hydro x", + "ĠB et", + "B ecause", + "Ġpiec es", + "Ġt alk", + "Ġopen ed", + "as ome", + "Ġsur ge", + "Ġfluct uation", + "g ithub", + "ĠB acter", + "Ġbind s", + "ĠRap id", + "au er", + "p H", + "emb ed", + "ĠD oc", + "uch i", + "ĠC andid", + "Ġrare ly", + "Ġm ountain", + "ĠF at", + "Ġs end", + "ov sk", + "ĠOrgan ization", + "ĠFran c", + "ĠO P", + "âĪ ¼", + "ok es", + "ec e", + "def icient", + "Ġlink age", + "od on", + "Ġf ly", + "Ġt idal", + "ĠEx amples", + "ĠR out", + "Ġaccom mod", + "Sup pose", + "ad ap", + "Ġdi e", + "ro ot", + "Ġh on", + "Ġminim izing", + "Ġrough ness", + "Ġgr ass", + "ent a", + "ĠL ang", + "ed u", + "ĠSim ple", + "en ic", + "Ġinduc ing", + "t f", + "Ġcon texts", + "ĠGeneral ized", + "ĠW nt", + "P b", + "at omic", + "d em", + "ĠPre paration", + "Ġinsu fficient", + "s am", + "ĠSpec ies", + "ĠS olar", + "Ġuns igned", + "ĠH ER", + "â Ĭ", + "Ġpar ity", + "Ġnit rate", + "ĠC er", + "p tic", + "id entif", + "ge al", + "Ġemo tion", + "ĠL P", + "Ġenh ancing", + "Ġmeaning ful", + "st ation", + "Ġrel ig", + "y o", + "Ġpers pectives", + "Ġsc ans", + "ugin osa", + "Ġsummar ize", + "rel ations", + "Ġdist ant", + "Ġfunction ality", + "Ġde eper", + "ol ate", + "ĠP or", + "graph s", + "ĠW a", + "ophil ic", + "CL US", + "ropath y", + "Ġc red", + "Ġun iversity", + "se g", + "ve e", + "O G", + "ĠM en", + "ĠCri tical", + "ã ģ", + "Ġex it", + "var theta", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠ", + "Ġun f", + "Ġpropos al", + "Ġty rosine", + "oti des", + "Ġproxim ity", + "Ġbox es", + "cat en", + "ĠEnvironment al", + "bound ed", + "down arrow", + "Ġfall s", + "Ġfer til", + "Ġcompr ised", + "Ġmell itus", + "Ġleak age", + "ut y", + "Ġchrom osomes", + "ĠStat istics", + "%% %%", + "Ġcomb inator", + "Ġk et", + "ad vant", + "T her", + "Ġtop ics", + "fl at", + "n ia", + "ĠSpect ral", + "Ġsynchron ization", + "var rho", + "Ġcolon ies", + "ĠF ive", + "ag ues", + "ĠF C", + "ID S", + "Ġa ward", + "Ġyield ing", + "Ġarchitect ures", + "ashing ton", + "chit z", + "per ty", + "Ġmod uli", + "m oment", + "sp eed", + "Ġmes enchymal", + "op tera", + "Ġinc omp", + "C ell", + "ĠM ice", + "Ġg ot", + "te ger", + "Ġt au", + "ĠAd S", + "Ġb ill", + "Ġdr inking", + "uls ive", + "Ġknock down", + "Ġarm s", + "ĠAut om", + "ĠIncre ased", + "H F", + "Ġglob ally", + "Ġdop ing", + "Ġat h", + "ĠC op", + "Ġsuccess ive", + "UL T", + "el ess", + "Ġble eding", + "Ġfood s", + "Ġimmun ohist", + "Ġdef inite", + "ĠJ ones", + "ĠT S", + "Ġjo ined", + "ĠTow ards", + "ĠC s", + "Ġun like", + "Ġval ence", + "d or", + "o S", + "Ġp ush", + "Ġoff ice", + "Ġalumin um", + "id yl", + "idi rectional", + "wr itten", + "Ġb ubble", + "H I", + "Ġmarked ly", + "ĠT ok", + "Ġvesic les", + "Ġquoti ent", + "Ġrepro duce", + "Ġelse where", + "ĠMy c", + "Ġinf rastructure", + "Ġgain ed", + "ab el", + "ĠS ex", + "ĠT ables", + "et in", + "Ġhom olog", + "Ġleg al", + "he a", + "Ġsoci ety", + "Ġman aged", + "id ase", + "ĠInhib ition", + "Ġparas ite", + "Ġvol unte", + "AT P", + "i os", + "Ġse psis", + "Ġrib osomal", + "Ġconf ound", + "ĠSta phyl", + "aryn geal", + "ï Ģ", + "com b", + "ĠOb jective", + "SUL TS", + "Ġthor ough", + "m t", + "Ġc hest", + "V ector", + "ele ment", + "Ġvir ulence", + "Ġhem isp", + "Ġso ught", + "ĠK o", + "Ġnutri tion", + "ul ing", + "ian a", + "Ġprot otype", + "ĠO nt", + "c ine", + "Ġdot ted", + "Ġob ese", + "ount ered", + "Ġphysic ians", + "Ġmin i", + "Ľ ľ", + "sp aces", + "Ġexcl usively", + "ĠCon volution", + "Ġc aspase", + "ĠL ink", + "di v", + "ĠRoy al", + "h ist", + "it ness", + "Ġes ter", + "Ġconduc ting", + "Ġparticip ated", + "Ġair way", + "Ġaer uginosa", + "E xt", + "arg ument", + "ock ing", + "Ġintegr ate", + "Ġcont rovers", + "ap es", + "train ing", + "ĠPre valence", + "tem p", + "b oth", + "Ġre activity", + "Ġrank ing", + "Ġtunn eling", + "OD E", + "ĠMed iterranean", + "Ġreson ances", + "M g", + "Ġl ib", + "ĠH eter", + "Ġnot hing", + "Ġindic ation", + "ĠH M", + "ocy tic", + "st rand", + "Ġcollabor ation", + "Ġelectro static", + "Ġindepend ence", + "h ab", + "Ġconf lic", + "Ġi od", + "in us", + "Ġdepend ency", + "ĠL am", + "Ġexam ining", + "Ġoccup ied", + "Ġque ue", + "ĠB ul", + "Ġregist ered", + "Ġindivid ually", + "R x", + "aus al", + "V E", + "Ġbright ness", + "resp ons", + "bal ance", + "Ġcytotox ic", + "f all", + "com mut", + "IC AL", + "ur an", + "ain ing", + "ra ulic", + "res ults", + "Ġepis odes", + "Y S", + "ĠG ar", + "Ġsur fact", + "dr ug", + "Ġc ities", + "ĠCh ange", + "os ition", + "Ġtrig gered", + "Ġcytoplas mic", + "erv es", + "Ġle x", + "Ġasymptotic ally", + "ph y", + "Ġfron tal", + "ĠD ensity", + "Ġsyn erg", + "cy cle", + "ĠImpro ved", + "à ¸", + "Ġmon o", + "Ġaccum ulated", + "orient ed", + "b our", + "Ġtun nel", + "com ing", + "Ġap paratus", + "Ġenc ountered", + "C re", + "Ġlet ters", + "et ch", + "Ġexcess ive", + "Ġbiofil m", + "Ġrear rang", + "Ġpolymorphism s", + "er obic", + "Ġconn ect", + "res olved", + "ĠN N", + "Ġret ro", + "ĠIn iti", + "ĠQuanti f", + "Ġp up", + "T ensor", + "Ġsent ences", + "l ay", + "ran ts", + "pl oid", + "ĠAnd erson", + "Ġdes irable", + "st ud", + "i ability", + "Ġd rying", + "ec ess", + "Ġd ens", + "Ġdescri pt", + "ĠË Ĩ", + "Ġcl ones", + "Ġju ven", + "b p", + "Ġk il", + "H L", + "Ġhem orrh", + "ĠK i", + "H ow", + "Ġen erge", + "Ġsub section", + "ĠS ac", + "di al", + "Ġcardi omy", + "Ġto uch", + "d m", + "Ġsc ienti", + "oid es", + "Ġà Ĥ", + "ysacchar ide", + "Ġs clerosis", + "ĠZe aland", + "in ine", + "Ġunus ual", + "ĠB A", + "ips chitz", + "g ap", + "ĠDiff erences", + "Ġdual ity", + "ed ical", + "Ġl ign", + "Ġfail s", + "Ġ lect", + "Ġrel ate", + "Ġincor rect", + "Ġspec ify", + "Ġcylind rical", + "ĠP F", + "ĠL ind", + "Ġdet erior", + "Ġher b", + "d z", + "Ġw eld", + "Ġnom inal", + "cop y", + "Ġacet yl", + "ht ml", + "Ġrecogn ize", + "** *", + "iti an", + "W A", + "ĠM N", + "ĠF ind", + "Ġaut hentic", + "per ture", + "Ġcytotox icity", + "of l", + "ĠG et", + "Ġcoh omology", + "Ġremain der", + "Ġexpand ing", + "Ġhe av", + "oster one", + "R ight", + "Ġcop ol", + "Ġs hed", + "Ġcompl iance", + "Ġacid ic", + "or ic", + "Ġam yloid", + "Ġevap oration", + "d l", + "Ġdel ays", + "P o", + "ĠCH ECK", + "tain s", + "Ġrevers ed", + "ĠMP a", + "Ġprocess or", + "Ġh all", + "ĠL ast", + "Ġplas m", + "ĠAss ociated", + "ĠBas ic", + "in os", + "Ġsympt om", + "ã Ģ", + "Ġanth rop", + "Ġjud g", + "Ġe ti", + "k le", + "Ġwr ong", + "ro om", + "Ġdevelop ments", + "ĠMax imum", + "Ġcoating s", + "Ġheur istic", + "ron tal", + "S ome", + "Ġutil ize", + "ĠâĪ ħ", + "c oll", + "ĠRel ated", + "Ġde generation", + "tem plate", + "Ġmod ulated", + "Ġparamet ri", + "Ġsal iv", + "ĠPseud omonas", + "Ġanti gens", + "Ġhar mon", + "ĠL HC", + "do i", + "ens itive", + "ĠNo tice", + "ĠM oh", + "til age", + "AC S", + "Ġdiscrep ancy", + "Ġsp ik", + "Ġre strict", + "it rile", + "le g", + "ĠB ase", + "Ġconvolution al", + "ĠRes istance", + "Ġappear ing", + "ĠIm ages", + "ĠM ann", + "Ġre act", + "Ġmacroph age", + "Ġwave let", + "och rom", + "Ġfair ly", + "Ġpreced ing", + "Ġsp ir", + "n etwork", + "ĠN ak", + "IF T", + "Ġag o", + "Ġenc ryp", + "al d", + "ens in", + "Ġs ulph", + "ĠPol ymer", + "ĠAr t", + "Ġsub units", + "sh ot", + "Ġbeg ins", + "Ġex er", + "pro pto", + "Ġn urses", + "Ġsuff ices", + "Ġgra ded", + "ĠR ock", + "Ġuniqu ely", + "it ol", + "Ġsp iral", + "Ġthan ks", + "char acter", + "ĠDist ributed", + "ĠC art", + "F orm", + "Ġform ulations", + "iction ary", + "Ġspread ing", + "Ġsingular ity", + "Ġpig s", + "it u", + "ot rophic", + "Ñ Ģ", + "Ġsemicon ductor", + "Ġd rag", + "ne xt", + "ma xim", + "un n", + "Ġarg ued", + "pl astic", + "Ġdehydrogen ase", + "Ġreinfor cement", + "ent ral", + "ĠD S", + "Ġcompan ies", + "Ġquanti zation", + "ĠD ri", + "Ġsimpl er", + "Ġradi i", + "ĠEth ics", + "ĠElect ronic", + "t aken", + "Ġpharmac ological", + "ps on", + "Ġpair ing", + "Ġn est", + "ĠR S", + "Ġl ic", + "oc on", + "Ġobserv ing", + "ĠF M", + "I ES", + "Ġsub mitted", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠ", + "Ġno isy", + "Ġvan ishing", + "ĠTechn ologies", + "il st", + "ag ic", + "Ġembed dings", + "Ġpl ans", + "re ak", + "oc t", + "Ġepit helium", + "Ġrevers ible", + "Ġrequ ests", + "V i", + "ĠPro g", + "meth oxy", + "ur ia", + "Ġsl ice", + "Ġmetast ases", + "ĠM ary", + "Ġprior i", + "Ġexplain s", + "ĠS igma", + "ĠArm y", + "Ġpre y", + "K L", + "ĠP ass", + "Ġrepro duction", + "Ġfer mentation", + "ul o", + "Ġproof s", + "ĠAccording ly", + "ti st", + "ĠïĢ ©", + "Ġme at", + "Ġpl anned", + "Ġangi ogenesis", + "W R", + "ĠA ust", + "Similar ly", + "ĠW ashington", + "Ġref inement", + "Ġembry o", + "Ġdiss ociation", + "á n", + "plas ia", + "ĠG ro", + "Ġsimilar ities", + "Ġsolub ility", + "Ġimm obil", + "ĠSc ot", + "ĠSub sequently", + "di vid", + "Ġclos est", + "ĠW at", + "Ġâ Į", + "ĠA GN", + "Ġpres cribed", + "Ġm osquito", + "Ġf irm", + "Ġde generate", + "Ġeth yl", + "Ġhar vest", + "ĠSpec ific", + "Ġcomp artment", + "p ublic", + "ĠBi ological", + "Ġpiec e", + "Ġat titudes", + "Ġsp ray", + "ĠS ix", + "Ġprofession als", + "Ġsl ot", + "Ġretrie ved", + "ve ment", + "Ġexec uted", + "se ed", + "Ġout flow", + "d istance", + "ĠT erm", + "ad y", + "ĠProv ince", + "ĠCent re", + "ĠD FT", + "Ġs udden", + "Ġse iz", + "r at", + "rom o", + "ot echn", + "Ġhigh lights", + "Ġelectroly te", + "ĠAdv anced", + "all ow", + "p x", + "os ed", + "sub array", + "rac ks", + "P RO", + "ogen y", + "Ġpool ed", + "Ġd type", + "Ġop posed", + "ĠG rand", + "Ġdesign ing", + "b el", + "it ability", + "Ġminim ization", + "Ġdram atically", + "Ġso y", + "ag ents", + "ĠMet al", + "ĠM V", + "rib ute", + "D D", + "it an", + "Ġspeed s", + "Ġmar ried", + "Ġevalu ations", + "ĠKing dom", + "Ġcl ay", + "ĠTiss ue", + "left arrow", + "Ġcompens ation", + "ch ild", + "p ool", + "up arrow", + "ĠDom ain", + "spec ies", + "Ġmeth ane", + "ĠE GFR", + "Ġpar ser", + "h ave", + "Ġneg lected", + "f unc", + "aps ed", + "Ġs ays", + "ad ata", + "bin om", + "C ase", + "Ġre porter", + "S n", + "Ġmaxim ize", + "Ġbif urc", + "ĠCN S", + "ĠO lymp", + "Ġdecl are", + "Ġenc oder", + "Ġab elian", + "Ġsingular ities", + "Ġe ch", + "Î ¨", + "Ġpro to", + "Ġph ag", + "Ġpoly g", + "Ġb ott", + "Ġadi pose", + "u ing", + "j k", + "uch y", + "ĠStud ent", + "Ġnan ow", + "Ġth ym", + "E d", + "E nd", + "Ġtransform s", + "ĠP CA", + "k ern", + "reg n", + "Ġcom ment", + "ĠL L", + "ell es", + "Ġeng agement", + "ĠP eter", + "IS PR", + "ĠCh annel", + "in y", + "Ġbund les", + "A ld", + "Ġpublic ations", + "T G", + "st ra", + "Ġf ear", + "Ġre tic", + "ple ments", + "Ġcor pus", + "ĠCl uster", + "ĠR ate", + "Ġsimpl est", + "ac ic", + "rb rack", + "Ġb low", + "Ġcomp ress", + "ĠD ark", + "Ġpsy chiatric", + "ĠCon versely", + "Ġo wing", + "Ġabs or", + "ĠH P", + "Ġcr ude", + "equ al", + "ĠAr ray", + "ĠRel ative", + "Ġcomb ustion", + "R ed", + "k t", + "Ġm A", + "Ġt ex", + "por ters", + "Ġdiffere d", + "Ġaud io", + "z on", + "od i", + "Ġmac roscopic", + "ac in", + "Ġz eros", + "Ġfore ign", + "Ġd uct", + "b ow", + "w orth", + "ĠRo ad", + "re y", + "ace ous", + "Ġbl ast", + "Ġgran ul", + "Ġw ing", + "Ġannot ated", + "ĠF ull", + "Ġinflu encing", + "v y", + "iaz ol", + "Ġp itch", + "Ġre habilitation", + "ĠPri or", + "com it", + "math tt", + "di a", + "ĠI on", + "Ġab use", + "Ġharves ted", + "Ġepid emic", + "Ġfil ament", + "Ġnucle ation", + "ĠKnow ledge", + "rin os", + "Ġb ent", + "Ġsqu ared", + "Ġhippocamp al", + "ĠT G", + "AN T", + "mod ified", + "ari o", + "ĠF ace", + "Ġgrow s", + "Ġfa ults", + "v irus", + "Ġpartition ing", + "air s", + "Ġhe aring", + "Ġcon gen", + "Ġ rip", + "ĠColl abor", + "Ġinterview s", + "Ġh uge", + "Ġbreak down", + "Ġmonth ly", + "ĠCON CLUS", + "E ach", + "D iff", + "Ġrel ay", + "ĠM use", + "oscop y", + "Ġre new", + "g b", + "Ġb rid", + "Ġoutl ined", + "or ig", + "e at", + "ĠWith out", + "Ġsp or", + "ĠT N", + "ĠJ o", + "ĠA U", + "N ot", + "Ġret in", + "ĠAn gel", + "Ġtri ed", + "ey ond", + "j e", + "ĠRuss ian", + "ĠUn fortunately", + "ĠMean while", + "ograph s", + "Ġacc ounting", + "ĠA β", + "m b", + "Ġdop amine", + "ĠBrief ly", + "ĠF requency", + "Mat rix", + "ĠJose ph", + "Ġexper ts", + "Ġdro ps", + "ĠRE SULTS", + "Ġrect angular", + "ath ione", + "cent er", + "ĠLe ft", + "in form", + "k ins", + "Ġm il", + "ĠM ah", + "Ġmed ial", + "ĠComp any", + "Ġpass age", + "Ġlead er", + "Ġscreen ed", + "er i", + "pos ites", + "r arily", + "Ġph one", + "ie tic", + "Ġexpect ations", + "ĠPar ticle", + "ĠM ountain", + "Ġinter leukin", + "Ġfif th", + "Ġv ast", + "Ġlog ical", + "Ġt err", + "Ġcre ates", + "Ġfinit ely", + "Ġsw im", + "Ġsupernat ant", + "opath ological", + "ĠUl tra", + "ĠT y", + "Ġgra nd", + "Ġconstit ute", + "olog ist", + "ĠBro ad", + "aw are", + "Ġvic inity", + "ag ulation", + "uns igned", + "ĠS ize", + "ĠC ognitive", + "Ġsusp ected", + "Ġu pl", + "Ġauto immune", + "ĠS K", + "C B", + "Ġsl ices", + "ĠCh i", + "Ġobserv ables", + "Ġhippocamp us", + "so ver", + "Ġfund ing", + "Ġcon formation", + "ĠQ uestion", + "ĠS qu", + "ĠW ill", + "Ġsc attered", + "ir ty", + "Ġpl aus", + "cor relation", + "Ġventi lation", + "ĠGen es", + "Ġben ign", + "Ġheter o", + "St atus", + "ang led", + "Ġb ootstrap", + "Ġvacc ines", + "Ġmicro organisms", + "Ġvis its", + "Ġtheorem s", + "d rop", + "ĠT A", + "Ġcycl ing", + "Ġspectrom eter", + "Ġground water", + "Ġnanot ubes", + "Ġjo ints", + "ĠE ll", + "Ġcons ult", + "Ġwindow s", + "Ġdis ability", + "Ġgain s", + "Ġdis charg", + "Ġhe ated", + "Ġa fore", + "ary ing", + "inc re", + "Ġagg ressive", + "Ġhe mod", + "ari um", + "ĠIn st", + "v m", + "Ġdro plet", + "p tive", + "vious ly", + "Ġst arch", + "Ġd f", + "os yl", + "Ġdon ors", + "ĠUn like", + "Ġalkal ine", + "Ġintellig ence", + "a a", + "Ġaccept ance", + "Ġsl iding", + "aps es", + "ĠD iss", + "ist an", + "a uc", + "Ġb ins", + "Ġmod ulate", + "Ġman age", + "out s", + "Ġs enes", + "Ġdifferenti ate", + "Ġcoun ted", + "AS K", + "Ġantib acterial", + "Ġent ered", + "Ġdis advant", + "ĠSalmon ella", + "Ġis otopic", + "Ġanno unced", + "ĠBo ard", + "Ġrest oration", + "Ġalle vi", + "Ġprogram me", + "Ġalb umin", + "Ġcatal og", + "est ine", + "Ġdifferent ly", + "Ġm olar", + "rö dinger", + "ĠE vent", + "minist ration", + "ĠSer um", + "RO M", + "k w", + "b ot", + "Ġj ets", + "ĠDo uble", + "el er", + "Ġinf usion", + "Ġconsum ed", + "ĠI ron", + "ĠProcess es", + "Ġad mits", + "Ġj uris", + "ĠPer iod", + "Ġremod eling", + "alle y", + "Ġenabl ing", + "Ġback ward", + "ĠM id", + "bre vi", + "Ġclass ify", + "Ġcr ypt", + "Ġhel ix", + "ĠJ iang", + "Ġh oney", + "ges tion", + "x c", + "Ġcoinc ides", + "ĠD N", + "Ġap optotic", + "Ġinst all", + "ĠR ever", + "ĠDop pler", + "ic ago", + "er als", + "Ġp ie", + "ĠM ars", + "ĠStaphyl ococcus", + "Ġnot ing", + "Ġgener a", + "ĠI o", + "Ġh ope", + "Ġpres erve", + "MA X", + "yn chron", + "Ġr up", + "Ġcompr ising", + "ĠW ay", + "Ġvi olation", + "Q R", + "Ġreflect ing", + "Ġregular ity", + "ĠSi O", + "ĠJ un", + "Ġcommun ications", + "r ating", + "Ġfam iliar", + "Ġinstant aneous", + "Ġcor tic", + "Ġapparent ly", + "X X", + "Ġexcit ations", + "ĠA ward", + "N um", + "ĠU N", + "Ġqu bit", + "ĠAc tion", + "ĠF ried", + "Ġelim inated", + "Ġasp ir", + "h ler", + "Ġdec oding", + "un ov", + "Ġanalog ue", + "ul monary", + "Ġge ographic", + "Ġs ort", + "ĠCR C", + "Ald rich", + "Ġk Da", + "ĠN D", + "Ġset tle", + "ex ists", + "Ġstat istic", + "ĠB ow", + "ĠC G", + "Ġorgan izations", + "ĠM obile", + "Ġinv ent", + "Ġincorpor ate", + "ĠF ib", + "ord an", + "Ġcolle agues", + "ĠSt ation", + "Ġs en", + "Ġenc aps", + "ĠR H", + "rel im", + "Ġcarbon ate", + "ĠN ether", + "m em", + "EE E", + "Ġafore mentioned", + "Ġp ent", + "ĠSign al", + "Ġsusp ended", + "Col or", + "Ġsp ins", + "Ġpropor tions", + "ult y", + "Ġen rolled", + "ĠT EM", + "ĠRecep tor", + "Ġpre valent", + "l arge", + "v s", + "Ġtrunc ated", + "Ġâĭ ħ", + "l m", + "an il", + "Ġann ih", + "ĠGalax y", + "er as", + "Ġep igenetic", + "Ġto oth", + "Ġcondens ation", + "ĠT ensor", + "Ġin organic", + "ym ers", + "u f", + "an ese", + "are t", + "Ġar ithmetic", + "â Ĩ", + "Ġt rying", + "Ġimplement ing", + "x d", + "Ġill umination", + "el a", + "Ġdefic its", + "Ġsp ots", + "Ġdoes n", + "Ġrest ing", + "tra ined", + "Ġeros ion", + "Ġgran ular", + "Ġsc ar", + "Ġpol len", + "l ie", + "Ġcon vers", + "Ġdisturb ances", + "ĠG od", + "Ġen larg", + "ĠL ate", + "yl ase", + "Ġfac ts", + "ent y", + "ĠStre et", + "sequ ence", + "Ġven ous", + "ĠC heck", + "ag g", + "Ġabsorb ed", + "Ġcom mit", + "set s", + "Ġdest roy", + "Ġbow el", + "Ġfin ished", + "ĠF eed", + "Ġdop ed", + "ĠAl b", + "ĠMit ochond", + "Ġtheore tically", + "R I", + "Ġmet eor", + "ĠM G", + "Ġn ation", + "ĠBas in", + "n ik", + "Ġdep ths", + "ĠMechan ism", + "Ġmotif s", + "ĠH ay", + "Ġmo tivated", + "ĠC opy", + "ĠE astern", + "Ġpers istence", + "Ġra ys", + "F B", + "and em", + "l ayers", + "ey er", + "ĠStre pt", + "Ġregist ration", + "ĠAnt arctic", + "C V", + "ĠP ap", + "ĠSp e", + "Ġsplic ing", + "per formance", + "Ġseman tics", + "Ġloc om", + "oblast oma", + "Ġm oney", + "Ġtrans parent", + "Ġh r", + "ĠInter actions", + "Ġs ap", + "Ġbi ases", + "Ġte eth", + "yn olds", + "omet hyl", + "Ġm V", + "Ġsole ly", + "Ġor ange", + "bl ast", + "ATION S", + "c all", + "opo ietic", + "s ided", + "ĠF ox", + "ĠV ideo", + "Ġinsp ection", + "Ġb uck", + "hes ize", + "p resent", + "ĠAnti b", + "Ġh am", + "al am", + "ĠP G", + "ĠA E", + "Ġj oin", + "Ġmon ocytes", + "es tiv", + "Ġrandom ised", + "Ġtransl ocation", + "Ġincorpor ating", + "Ġprolif er", + "Ġod ds", + "IT H", + "Ġr an", + "Ġinstruc tion", + "Ġresol ve", + "Ġf t", + "ĠHe ad", + "Ġre agent", + "Ġad mitted", + "h uman", + "pos ure", + "ĠCh a", + "ĠF r", + "Ġbroad cast", + "Ġnutri ents", + "n ob", + "Ġnot able", + "ĠI GF", + "ĠC learly", + "Ġquark s", + "Ġe ukary", + "ĠAd d", + "it osan", + "Ġinter active", + "it ting", + "ĠComput ational", + "Ġdiss olution", + "ist ribution", + "pro duct", + "ĠA BC", + "olim its", + "bi ased", + "Ġtrap ped", + "P K", + "ĠH PLC", + "roph ot", + "z es", + "our se", + "ĠH ot", + "Ġrec ipro", + "n olimits", + "ell o", + "Ġassess ments", + "EN TS", + "Ġalter ation", + "t w", + "Ġcha otic", + "ĠL oc", + "Ġcat tle", + "R ay", + "Ġform ally", + "le ave", + "text style", + "Ġvent ral", + "ĠWilli ams", + "ĠPe ople", + "ix ing", + "ĠThe rapy", + "Ġi ii", + "ĠD T", + "Ġb ic", + "Ġsp heres", + "Ġvis c", + "Ġestablish ment", + "Ġdescrip tions", + "ĠA verage", + "Ġto ur", + "ĠInf ection", + "ĠL icense", + "Ġprep are", + "H s", + "f inite", + "ri um", + "ore g", + "ent ry", + "Ġdis ks", + "Ġelong ation", + "c pu", + "ĠChar les", + "FIG URE", + "st on", + "ĠObserv ations", + "A dd", + "ĠT ask", + "at omy", + "ig ration", + "ĠD atabase", + "ĠTex as", + "Ġph yt", + "ll er", + "con jug", + "onal d", + "Ġheav ily", + "Ġsp le", + "Ġass ist", + "ĠC p", + "Ġhapp en", + "u v", + "ĠUn iverse", + "ĠG PS", + "W E", + "X i", + "Ġadminist r", + "str ong", + "Ġmagn itudes", + "Ġsimpl ify", + "Ġele gans", + "es h", + "ĠB ody", + "ĠNether lands", + "à ¯", + "omet ers", + "B o", + "F M", + "ĠN iger", + "pl us", + "inst ance", + "Ġdist ress", + "Or gan", + "C as", + "Ġsym plectic", + "Ġbreak s", + "Ñ Ĥ", + "Ġferm ion", + "em poral", + "Ġs omatic", + "e vent", + "ne ut", + "lamm ation", + "ĠL ibrary", + "Ġmulti plic", + "ĠIn str", + "et hel", + "ur ys", + "Ġhelp ed", + "Ġcol lege", + "Ġcar tilage", + "Ġr pm", + "w estern", + "res is", + "Ġlob e", + "Q L", + "In put", + "Ġemph asis", + "b est", + "Ġtot ally", + "ĠMET HOD", + "ĠF a", + "ĠRed uction", + "ici ous", + "Ġimplant ation", + "pot ential", + "prob lem", + "Ġobtain s", + "ur ons", + "Ġconstruct ing", + "ĠMus ic", + "Ġcan cell", + "Ġnew s", + "ĠCh apter", + "Ġlab elled", + "Ġz ebrafish", + "ĠSol id", + "Ġglut amate", + "ĉĉ ĉĉĉ", + "Ġch apter", + "ĠPres ident", + "M in", + "Ġat rial", + "c p", + "f i", + "f inal", + "Ġto k", + "Ġeffect or", + "Ġsp ine", + "Ġidenti ties", + "isc o", + "ol is", + "ĠC le", + "Ġinvari ants", + "P ath", + "ĠG on", + "fact ory", + "Ġex ogenous", + "ĠMAP K", + "Ġansw ers", + "Ġget ting", + "R s", + "I H", + "ĠDef ine", + "ĠConvolution al", + "Ġgeomet rical", + "ĠIn put", + "Ġ à", + "Ġatten uated", + "Ġradical s", + "ĠAcad emy", + "ã ĥ", + "ich let", + "Ġtor us", + "ĠThe oretical", + "ĠT D", + "Ġan tiv", + "on ge", + "Ġintra venous", + "Ġhyp oth", + "Ġwaste water", + "ĠF lo", + "Ġpor osity", + "Ġp all", + "ac i", + "Ġrecord ings", + "Ġe ating", + "ĠD W", + "un ting", + "ĠD im", + "Ġemit ted", + "ĠJ oint", + "of ib", + "Ġearthqu ake", + "Ġm unic", + "Ġreduc tions", + "Ġcon junction", + "ĠL ocation", + "Ġestabl ishing", + "ĠMat hematical", + "ĠS olution", + "bu ffer", + "ar in", + "ile y", + "ĠCom mission", + "ĠG ABA", + "ĠMuse um", + "Ġver b", + "lec ules", + "inf ection", + "Ġins ect", + "is er", + "Ġprov ision", + "Ġagre ed", + "Ġaff ord", + "the ory", + "know ledge", + "Pro tein", + "Ġk ernels", + "Ġd erm", + "Ġw ish", + "Ġv ox", + "S cale", + "h u", + "Ġcounter parts", + "ĠR oss", + "Ġun p", + "ĠOn line", + "Ġtrans porter", + "G raph", + "Ġ uter", + "Ġmin ute", + "Ġautom orphism", + "il tr", + "ĠResp ons", + "ĠS ym", + "Ġfactor ization", + "s em", + "Ġmedi ates", + "Ġun expected", + "Ġorgan ism", + "Ġattem pted", + "ar an", + "ven ue", + "ethel ess", + "Ġno ticed", + "ĠInvestig ation", + "Ġcare g", + "Ġgroup ed", + "or bit", + "Ġshort est", + "Ġbroad er", + "ĠM IM", + "ris es", + "vel oper", + "ĠH i", + "Ġk Hz", + "Ġbe ads", + "Ġph yto", + "ĠDo es", + "Ġmamm als", + "Ġref ined", + "vol ume", + "S er", + "Ġresis tivity", + "Ġter restrial", + "Ġa xi", + "if luor", + "Ġ £", + "Ġv ice", + "ĠK el", + "V M", + "ĠT own", + "ad m", + "pl ates", + "Ġhol omorphic", + "ĠR ib", + "ĠS B", + "ĠTem poral", + "s rc", + "Ġupd ates", + "Ġsee k", + "en dix", + "ore tic", + "war z", + "Ġro utes", + "Ġstand ing", + "Ġà ģ", + "Ġclass ic", + "Ġp ale", + "lec tions", + "Ġclass ifiers", + "Ġpath ophys", + "Ġm ounted", + "Ġdesign ated", + "Ġv ideos", + "Ġinc oming", + "Ġguarant ees", + "Ġparas ites", + "ĠB acillus", + "f our", + "ĠâĪ ¨", + "Ġcommut ative", + "stack rel", + "ĠBan ach", + "Ġde aling", + "em porary", + "M ulti", + "ot omy", + "re ting", + "Ġn ond", + "ĠCon ference", + "tz mann", + "Ġphosphor us", + "Ġchemical s", + "Ġdis par", + "deg ree", + "Ġarbit rarily", + "rocy te", + "Ġpar abolic", + "Ġdimension less", + "Ġo sm", + "Ġphon on", + "ti ary", + "ĠS ect", + "ophys ical", + "ĠM apping", + "b is", + "ĠCommun ication", + "Ġmim ic", + "Ġregul ators", + "Ġneutroph ils", + "f n", + "ĠImport antly", + "Ġm ere", + "Ġconfir ms", + "ag ram", + "Ġatt end", + "ung al", + "ĠGroup s", + "Ġz o", + "Ġm outh", + "Ġste ep", + "Ġprevent ed", + "Ġdep ressive", + "ac ies", + "ĠL S", + "Ġnit ric", + "Ġvisual ized", + "Ġtranscript ome", + "Ġga it", + "erc ury", + "Ġsh ot", + "ĠV en", + "Ġex chang", + "Ġint ention", + "ĠT ang", + "Ġfav our", + "ve olar", + "Ġper mission", + "Ġhabit ats", + "Ġma ize", + "inc t", + "Ġtele vision", + "ryst als", + "ĠRad i", + "Ġflav on", + "Ġcan n", + "i ota", + "ĠO T", + "p ic", + "R ad", + "ti tial", + "ĠOr th", + "st ellar", + "ĠK ine", + "Ġnavig ation", + "f ast", + "ĠCR ISPR", + "Ġkinem atic", + "Ġsearch ing", + "Ġmic rom", + "Ġinst alled", + "ĠTai wan", + "il a", + "r f", + "ri age", + "pl inary", + "Ġe cho", + "ra v", + "ĠL es", + "cre ate", + "Ġubiqu it", + "Ġprecurs ors", + "K E", + "Ġdiv ide", + "Ġln c", + "ĠCon struction", + "an ic", + "es tim", + "is ters", + "Ġfe et", + "ari ant", + "ĠSch w", + "Ġexcl ude", + "Ġvol can", + "ĠOver view", + "Ġy r", + "ol k", + "Ġ ©", + "ĠF E", + "Ġsper mat", + "Ġcapac itance", + "ĠSch rödinger", + "ĠG E", + "Ġcalibr ated", + "S EM", + "Ġlat tices", + "pl ier", + "Ar g", + "ĠN T", + "ĠEnh anced", + "Ġb rom", + "Ġmulti p", + "Ġcer tified", + "Ġis lands", + "Ġcy st", + "Ġal titude", + "ed ef", + "Ġconst rain", + "Ġsatisf actory", + "Ġspecial ized", + "Ġj unctions", + "Ġcoron avirus", + "ud ge", + "ex c", + "Ġal t", + "ĠB acterial", + "Ġse asons", + "ĠL M", + "Ġhist ogram", + "Ġsol vents", + "a verage", + "Ġcard inal", + "ch rom", + "py thon", + "d ered", + "en ia", + "ĠG H", + "ĠEs s", + "__ __", + "ĠP ak", + "s ized", + "ĠH g", + "Ġel if", + "ĠSchem atic", + "Ġcytoplas m", + "ĠFor t", + "an ia", + "Ġcare ful", + "ĠD ual", + "Ġtransl ated", + "Ġn asal", + "In v", + "Ġda ughter", + "Ġemphas ize", + "mod ules", + "Ġl ives", + "Ġhom otopy", + "Ġb ot", + "Ġdis ordered", + "mat o", + "S econd", + "Ġclaim ed", + "add le", + "Ġinterf acial", + "Ġvisc ous", + "Ġdest ination", + "ĠPl anck", + "Ġabsorb ance", + "Ġvol atile", + "Ġst orm", + "Ġcar boxyl", + "ĠB ank", + "ĠP ack", + "Ġscaff old", + "te br", + "ip ot", + "Ġtum ours", + "ĠG ol", + "Ġelectroph oresis", + "Ġreal ize", + "Ġconstitu ents", + "S ol", + "ĠE very", + "Ġmedi ate", + "Ġcoinc ide", + "Ġexplo it", + "Ġmon oton", + "me asure", + "Ġsup plied", + "rac ellular", + "Ġfer ro", + "Ġpur s", + "eren tially", + "tr ast", + "ĠR B", + "Ġdiss em", + "as y", + "Ġrel ating", + "n ull", + "u ates", + "const ant", + "ĠContinu ous", + "Ġgeomet ries", + "r ust", + "ĠS TR", + "cl uster", + "Ġprogen itor", + "ĠC SF", + "ĠY am", + "ĠRe ynolds", + "ĠM Y", + "ĠK O", + "ĠW alk", + "ari able", + "ind er", + "ĠR ight", + "ĠAl gebra", + "ĠW ik", + "Ġin activation", + "t mp", + "ac cess", + "ĠL ater", + "Ġmicrobi ome", + "Ġgeodes ic", + "Ġre jection", + "us es", + "Ġhard ness", + "Ġhydro dynamic", + "Ġvan ish", + "Ġpoll ut", + "amy cin", + "ĠÏ Ń", + "ip itation", + "Ġaug mented", + "ĠT T", + "av al", + "Ġenc ode", + "Ġtox in", + "et o", + "igh bor", + "add r", + "Ġdam aged", + "o i", + "Ġtrans duction", + "Ġinter acts", + "ÃŃ a", + "ĠC all", + "ri ends", + "ĠMon itoring", + "ĠVari ation", + "Ġôı ¼", + "Ġd ich", + "Ġsp ars", + "al ign", + "Ġan atomical", + "Ġcentrifug ed", + "ur ally", + "ĠZ r", + "ĠCar l", + "Rec all", + "Ġopin ion", + "Ġ era", + "Ġdrain age", + "Ġmicro array", + "st atus", + "um ental", + "Ġcomp rises", + "press ure", + "Ġprac tition", + "m ac", + "Ġcon gr", + "urn al", + "ĠA PI", + "ĠL R", + "Ġtransf ection", + "Ġsl opes", + "ĠC ode", + "Ġph il", + "b ool", + "W s", + "Ġâ Ļ", + "Ġassoci ate", + "otox icity", + "ra de", + "ĠM iller", + "ĠÏ ª", + "Ġshor ten", + "Ġaddition ally", + "ĠEff ective", + "Ġsuper vised", + "Ġel abor", + "ĠC ellular", + "Ġt ell", + "ĠR C", + "s ave", + "im id", + "Ġrating s", + "ĠT aking", + "Ġappro val", + "Ġpenal ty", + "K K", + "con text", + "ak s", + "pec ific", + "Ġtem por", + "Ġup regulation", + "V AL", + "Ġenc odes", + "in in", + "Ġnot es", + "ĠFore st", + "Ġcombinator ial", + "ympt otic", + "Ġsqu amous", + "ĠAs h", + "our n", + "Ġmyel oid", + "el ines", + "B io", + "Ġbre ed", + "ĠR ub", + "uz z", + "Ġsingle t", + "en na", + "Ġcri tically", + "d ig", + "dis ci", + "Ġdrop ped", + "Ġlip oprotein", + "ĠE t", + "Ġno v", + "op hen", + "Ġanc ient", + "B ase", + "Ġsmooth ing", + "iti ves", + "p ine", + "Ġsol ver", + "per m", + "ĠH ome", + "Ġaz im", + "l Vert", + "Ġtransport ation", + "Ġde x", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠ", + "opath ic", + "ex perim", + "âĢ¢ âĢ¢", + "perf usion", + "Ġdo i", + "ĠL act", + "Ġhepat ocellular", + "Ġmism atch", + "Ġaden ocarcinoma", + "ĠP ain", + "Ġsp r", + "Ġconf inement", + "Ġexceed s", + "Ġhas h", + "ĠCompar ing", + "ĠS ensor", + "Ġf iring", + "k es", + "v ir", + "ine a", + "aff ected", + "Ġmod elled", + "Ġe ther", + "Ġsu ffer", + "â̲ â̲", + "о Ð", + "ĠB ir", + "Ä ģ", + "Ġsec reted", + "Ġcat heter", + "Ġy outh", + "ex pl", + "ĠD ar", + "ĠW HO", + "Ġfound ation", + "Ġhyd raulic", + "ĠCa rol", + "SS ION", + "Ġá ¹", + "f eld", + "av or", + "Ġpass es", + "vis iae", + "Ġapplic ability", + "Ġn ested", + "F l", + "ĠC atal", + "Ġmicro environment", + "lab els", + "Ġcrystall ization", + "In fo", + "Ġposition ing", + "Ġtriang les", + "Ġtr yp", + "ĠTrans ition", + "Ġset t", + "Ġneuro t", + "M on", + "Ġdro plets", + "ĠA RT", + "Ġcor ne", + "Ġmulti plicity", + "Ġec centric", + "Ġ iv", + "ĠM atter", + "lear ning", + "elect ro", + "ĠWe yl", + "Ġdec ide", + "ĠW r", + "ĠH ierarch", + "Ġap ical", + "Ġfail ures", + "Ġdiges tion", + "MI C", + "Ġge ographical", + "ĠEle ment", + "ĠTh ough", + "Ġch ron", + "lim ited", + "ĠDI SC", + "ĠArchitect ure", + "Ġvibr ational", + "ĠVari ous", + "Ġdynam ically", + "ak ed", + "Ġconven ience", + "ĠIs ra", + "ĠMD A", + "i tic", + "A u", + "Ġass istance", + "ven tional", + "mid t", + "os por", + "F ollowing", + "Ġinf erior", + "Ġn ickel", + "ra ine", + "p aren", + "Ġtit anium", + "F ield", + "Ġh oc", + "ĠCa uchy", + "ĠMc C", + "ĠSc reen", + "Ġneg lect", + "class es", + "ĠI F", + "Ġstrat ified", + "ens es", + "ĠPl ate", + "oz oic", + "Ġinstit utions", + "ĠTh ose", + "Ġgener ations", + "trans form", + "Ġpar titions", + "Rx iv", + "ent h", + "Ġs tic", + "ol ith", + "ĠF em", + "Ġag ar", + "be am", + "Ġprot ons", + "L U", + "Ġwork load", + "Ġmin erals", + "Ġm t", + "ll a", + "ĠPh armac", + "Ġconver ter", + "ĠMechan ical", + "Ġflav or", + "Ġphosph atase", + "Ġsum s", + "PC s", + "Ġiso forms", + "ig roup", + "py r", + "fe atures", + "Ġper c", + "Ġcomple teness", + "Ġfore sts", + "Ġdiv iding", + "ĠL ipschitz", + "period ic", + "Ġrec ycl", + "ĠN ag", + "Ġtw in", + "epti des", + "Ġco hor", + "Ġsearc hes", + "e ated", + "H g", + "ĠP U", + "ĠT ree", + "all ic", + "P F", + "Ġapp endix", + "ĠC ov", + "Ġcheck ing", + "Ġback bone", + "Ther mo", + "Ġactiv ating", + "ĠV ictor", + "Ġcri tic", + "ĠL em", + "group s", + "RE G", + "ĠO cc", + "SC C", + "ĠX RD", + "ĠVal ues", + "Ġsub type", + "Ġstret ching", + "OR M", + "s ome", + "Ġfl ip", + "Ġphen olic", + "Ġk illed", + "Ġsequ enced", + "usc ular", + "ab in", + "Ġquad r", + "Ġtransl ational", + "Ġsol ids", + "di rect", + "Ġprom otion", + "Ġcohor ts", + "ĠCl imate", + "ĠO ld", + "ĠS ir", + "g ue", + "str ate", + "ĠP oss", + "Ġrece ives", + "ĠVal idation", + "uc tive", + "Ġcere visiae", + "G u", + "is is", + "ce il", + "ĠPear son", + "ĠP relim", + "ĠG ran", + "CS F", + "Ġster ile", + "oflu orescence", + "b ad", + "Ġcol ored", + "comp ass", + "equ ation", + "j an", + "Ġcondition ing", + "Ġvo ice", + "Ġmen ing", + "Ġgrant ed", + "Ġrenormal ization", + "ĠLim it", + "th i", + "Ġa perture", + "Ġdos age", + "di rected", + "ĠBre ast", + "oc ular", + "b earing", + "s al", + "asc ul", + "uper vised", + "Ġmonol ayer", + "Ġmembers hip", + "ĠW ireless", + "sh ow", + "ĠMed ia", + "ĠV L", + "ess el", + "Ġdec oder", + "ĠM F", + "ĠCom position", + "ĠCl ark", + "P oint", + "ĠN ano", + "ĠD eg", + "N L", + "ĠB ox", + "Ġexpl oring", + "m olecular", + "O ther", + "ĠDiab etes", + "he ight", + "Ġkin ases", + "Ġadjust ing", + "Ġsp orts", + "off s", + "ĠI EEE", + "Ġt il", + "ĠInt ra", + "Ġplan ets", + "ĠEpid em", + "Ġto mato", + "Ġscaff olds", + "ĠMet abol", + "ĠGe ometry", + "im etry", + "ĠT en", + "th read", + "o hex", + "Ġpro poses", + "pr im", + "ĠPar ty", + "Ġquar ter", + "ĠSh i", + "Ġab err", + "ĠIn tr", + "Ġdirect or", + "aff e", + "ĠS us", + "ens ors", + "E le", + "Ġpol es", + "Ad ditional", + "Ġby pass", + "caten in", + "Ġunder taken", + "im ation", + "op or", + "Ġpres erving", + "Ġmultiple x", + "ĠRepresent ative", + "s is", + "ĠA G", + "ach y", + "Ġfr uits", + "Ġreconstr uct", + "ens en", + "Ġstrong est", + "Ġsc av", + "ĠChen g", + "ĠCor on", + "ĠObs ervation", + "ĠA ch", + "ĠGe org", + "ĠSV M", + "ĠC hern", + "Ġrevers al", + "v ia", + "im p", + "Ġdeploy ment", + "ĠH ad", + "Ġcircumst ances", + "ob i", + "Ġcur ved", + "Ind uced", + "ĠPos itive", + "im b", + "ĠPar is", + "ĠS tein", + "ic z", + "ĠC ath", + "Ġdraw ing", + "t ory", + "Ġcontin ental", + "Ġquantit atively", + "ac erb", + "Ġnorm s", + "ĠB E", + "S everal", + "do or", + "Ġplate au", + "G al", + "Ġc ivil", + "ĠF ix", + "L AB", + "oc cal", + "Ġsor ted", + "ĠâĢ Ŀ", + "Ġed iting", + "ĠChris tian", + "Ġclar ify", + "Ġwavegu ide", + "b ell", + "Ġded uced", + "od ec", + "utri tion", + "Ġcomp ressive", + "ĠE U", + "ĠReg ression", + "Ġrank ed", + "Ġestim ators", + "Ġab ilities", + "Ġbelief s", + "th ree", + "Ġâĩ Ĵ", + "rolog y", + "Ġaut onomous", + "ĠS z", + "sc hem", + "ĠAL T", + "ĠPattern s", + "Ġex on", + "Ġlif estyle", + "f ill", + "ĠC AR", + "ĠDom ains", + "Ġpa id", + "Ġt ab", + "ĠCo hen", + "air y", + "Ġshe ep", + "Ġse aw", + "ĠK ong", + "g as", + "Ġres erved", + "Ġres il", + "Ġob l", + "car box", + "ĠGover nment", + "up per", + "ract ing", + "Ġg angl", + "ĠR V", + "Ġbron ch", + "Method s", + "ĠL iver", + "Ġgu ess", + "cha romy", + "IC E", + "Ġcongen ital", + "Ġk a", + "Ġsp anning", + "ĠRec omm", + "e a", + "Ġcon vention", + "Ġshe ets", + "Ġtherm o", + "Ġqualit atively", + "Ġox ides", + "Ġcongr u", + "ĠJ er", + "Ġpres ervation", + "ĠB T", + "ĠD MSO", + "Ġcom plication", + "Ġsurviv ors", + "Ġreduc t", + "Ġdes cent", + "Ġsuc rose", + "ĠCour t", + "Ġmetabol ite", + "ĠM ath", + "ĠSec urity", + "ĠNot ably", + "ĠSt em", + "Ġdwar f", + "b c", + "Ġre vis", + "ĠK l", + "ĠG h", + "Ġman ager", + "Ġinvest ment", + "Ġmo tility", + "E m", + "ĠM r", + "as ic", + "ĠB os", + "Ġinsp ired", + "plac ian", + "Ġe ase", + "Ġtors ion", + "ĠDir ichlet", + "Ġsple en", + "ag ation", + "on ate", + "ĠT rial", + "Ġturn over", + "Ġselec tively", + "ĠÍ Ĵ", + "ian o", + "Ġnon trivial", + "i asis", + "Ñ ģ", + "ĠGu o", + "Ġaddress es", + "Ġuniqu eness", + "Ġwith draw", + "ri z", + "Ġcomputation ally", + "Ġperson ality", + "A X", + "went y", + "Ġgover n", + "ber ts", + "Ġrob ots", + "Ġread y", + "Ġdi ets", + "l it", + "M y", + "ĠRe ve", + "ĠL os", + "inf rared", + "Ġint ram", + "l ated", + "pl ankton", + "ĠG rant", + "pi per", + "Ġanten nas", + "Ġb ol", + "f p", + "ĠV it", + "Com par", + "ok en", + "Ġke ys", + "ĠCl ub", + "in ery", + "ĠF oot", + "Ġwarm ing", + "m ond", + "Ġm iles", + "Ġspe aking", + "ĠI v", + "Ġconform ational", + "ĠO k", + "Ġun ified", + "Ġassemb led", + "Ġinver ted", + "Ġf elt", + "correspond ing", + "ĠE CM", + "ĠN SC", + "Ġind oor", + "g ov", + "Ġantagon ist", + "unc hed", + "ĠJ ava", + "ĠComb ined", + "tiv ities", + "Ġaltern ating", + "ã Ĥ", + "ĠDiagn osis", + "Ġdistinc tion", + "le igh", + "ĠT ogether", + "Ġparticip ating", + "Ġgl omer", + "oc he", + "Ġcopy right", + "ĠG TP", + "ĠV ar", + "Ġammon ium", + "Ġfacilit ates", + "Ġperf usion", + "ĠL B", + "f ull", + "Ġre ti", + "ifer ase", + "Ġimmunos up", + "ĠIm plementation", + "Ġpo res", + "ĠB B", + "ĠB ud", + "ĠV O", + "ĠV o", + "Ġphysic ian", + "ĠA UC", + "Ġcertain ly", + "μ m", + "ĠK ol", + "Ġw rap", + "m iddle", + "Ġsil encing", + "Ġfresh water", + "ig an", + "are a", + "A I", + "Ġmicro tub", + "Ġarrang ed", + "struc tive", + "ĠReg ular", + "ĠF ile", + "al ks", + "Ġpl ain", + "Ġinte grable", + "ĠM embrane", + "ist ors", + "Ġaqu atic", + "Ġwork flow", + "ĠG er", + "ul ant", + "Ġactiv ates", + "T erm", + "ĠUp on", + "ĠP ut", + "V ar", + "ĠO D", + "hal f", + "Ġul cer", + "ĠB O", + "ĠG y", + "ren ces", + "Ġpur ity", + "Ġarri ve", + "ĠSign ificant", + "ĠM AC", + "ĠOther wise", + "o ured", + "Ġt an", + "ĠR L", + "ĠQ TL", + "Ġammon ia", + "v mode", + "Ġmagn esium", + "Ġac knowled", + "Ġaltern atives", + "id ents", + "r Vert", + "ĠCom plete", + "ĠB one", + "y er", + "ĠB ab", + "Ġe ut", + "Ġno vo", + "disci plinary", + "Ġsevere ly", + "uk i", + "ĠP N", + "leave vmode", + "cl ip", + "ĠSy nd", + "ĠMIM O", + "ade qu", + "ĠArc tic", + "ly cer", + "RE T", + "ens ed", + "co ated", + "V P", + "Ġl akes", + "Ġch urch", + "Ġhom ologous", + "Ġoxid ase", + "ĠA ud", + "Ġincre ment", + "Ġneut rinos", + "ar bon", + "T YPE", + "iz umab", + "ut able", + "Ġimp lying", + "ĠMo tion", + "Ġâī ĥ", + "Ġp ages", + "Ġplaus ible", + "ĠN L", + "Ġis otop", + "ĠH yd", + "A tt", + "lat tice", + "sh ore", + "Ġsuc ceed", + "Ġsup posed", + "ĠTrans mission", + "D imensional", + "ingu istic", + "Ġcont ours", + "Ġcon comit", + "Ġagre es", + "ĠD ani", + "qu ar", + "Ġsh ield", + "Ġo zone", + "ĠT et", + "lb rack", + "Ġw at", + "Ġcyt ochrome", + "ta iled", + "p ix", + "Ġco ex", + "ĠVi ew", + "od ef", + "ĠW ild", + "ĠL E", + "h op", + "Ġpoint ing", + "unc ture", + "Ġec ology", + "Ġb ab", + "re a", + "eg o", + "Ġviol ence", + "Ġt RNA", + "ĠR N", + "p ent", + "ore l", + "ĠPar allel", + "Ġdri ves", + "nob reak", + "Ġh olog", + "Ġprob able", + "Ġent ering", + "Ġs ink", + "Ġsw elling", + "pro ducing", + "âĨĴ âĪŀ", + "ĠSaf ety", + "Ġanaly se", + "ser ies", + "Ġdri vers", + "K S", + "ĠR MS", + "Ġgene tics", + "ĠF red", + "Ġsub m", + "Ġscienti sts", + "ĠF D", + "ĠSol utions", + "ĠF ab", + "Ġen compass", + "commut ative", + "Ġadi abatic", + "but yl", + "PE G", + "Ġα β", + "ĠSt an", + "Ġclust ered", + "Ġhold ing", + "ĠB eck", + "ĠY an", + "Ġast er", + "Ġecon om", + "Ġign ored", + "u ro", + "yl es", + "ubb les", + "Ġf ate", + "Ġper ceptions", + "Ġl in", + "é n", + "Ġact u", + "Ġar sen", + "Ġb a", + "ep och", + "ĠS tim", + "Ġmedic ations", + "EC s", + "ĠMin istry", + "ĠPubl isher", + "Ġdep ri", + "Ġob struction", + "ĠmRNA s", + "Ġbro ther", + "Ġcros sover", + "ĠT urb", + "t ation", + "Ġt ank", + "ĠM em", + "Ġint estine", + "Ġmicrogl ia", + "ĠMax well", + "Ġjuris dic", + "Ġphen yl", + "hy per", + "um s", + "ĠH IF", + "ĠS hen", + "Ġcheck point", + "ĠBrown ian", + "Ġâĭ Ĩ", + "ĠSt rain", + "ĠExt raction", + "Ġbatter ies", + "ĠP le", + "ĠCon ditions", + "Ġincons istent", + "ĠH ost", + "yp ical", + "Ġc rops", + "al g", + "ĠF I", + "ant a", + "Ġfound ed", + "Ġmark s", + "dist ribution", + "ĠÎ ¹", + "Ġh ors", + "Ġsn ap", + "W M", + "Ġmanifest ations", + "em pl", + "Ġprov ing", + "le ading", + "ĠA CE", + "ĠL ED", + "ch annels", + "Ġl ift", + "F unction", + "in ase", + "super vised", + "ĠU ser", + "Ġphys iology", + "Ġlink ing", + "p ressed", + "Ġ iff", + "ĠJ im", + "Ġglut athione", + "ĠT I", + "Ġan e", + "en osis", + "Ġcollec tions", + "Ġgenetic ally", + "ĠFil ter", + "ĠCh icago", + "ĠServ ices", + "Ġsuper symmetric", + "Ġstri king", + "Ġir rig", + "oc occal", + "Ġfib res", + "Ġecos ystems", + "um ing", + "f ly", + "Ġlung s", + "Ġcovari ates", + "Ġlay out", + "ĠR aj", + "Ġsumm ation", + "abl ed", + "Ġfre ely", + "Ġre vised", + "Ġcut s", + "ĠIntegr ated", + "Ġph armaceutical", + "Ġrespir ation", + "ĠB ill", + "Ġest rogen", + "ra int", + "Ġpercent ages", + "ĠP f", + "ĠG F", + "methyl ene", + "Ġorig ins", + "tr im", + "mat ch", + "it ney", + "ĠY e", + "Ġalloc ated", + "manif old", + "ĠT ris", + "ĠL ys", + "Ġcomp ressed", + "ore r", + "Ġhim self", + "Ġqu in", + "ĠAss embly", + "sing le", + "tem poral", + "Ġs oph", + "Ġepidem iological", + "Ġknock out", + "Ġcomp ares", + "ĠSens itivity", + "Ġgir ls", + "ĠV alley", + "al id", + "ĠSchem e", + "ĠCO MP", + "Ġrefrac tive", + "ĠOff ice", + "Ġlat est", + "Ġp rices", + "car boxyl", + "Ġecon omy", + "Ġbo oks", + "ĠD D", + "Ġne oplas", + "app ings", + "Ġfol ding", + "moment um", + "pot ent", + "Ġpref ix", + "ĠRiemann ian", + "ĠER K", + "ĠPath way", + "Ġlar val", + "ol or", + "Ġat titude", + "geq slant", + "Ġg ates", + "Ġagon ist", + "ĠïĢ ¨", + "ĠM CF", + "ost atic", + "m icro", + "Ġdo ubl", + "ĠPar ameter", + "Ġequival ently", + "Ġs rc", + "M ost", + "ĉ ĠĠĠ", + "Ġrhe umat", + "ĠH um", + "reg ion", + "Ġwind s", + "Ġquad rup", + "cal es", + "ulf ide", + "bal anced", + "U nder", + "gener ated", + "oplas mic", + "Ġweight ing", + "ĠN ov", + "vel oc", + "util s", + "ĠA CT", + "Ġvulner able", + "d c", + "Ġstrom al", + "Ġex acerb", + "H V", + "Ġperfect ly", + "t xt", + "di rection", + "og on", + "Ġb im", + "ĠM arg", + "it ons", + "Ġterm ination", + "ed a", + "Ġpre treatment", + "Ġimportant ly", + "Ġd uc", + "Ġartif acts", + "St ud", + "ot ensin", + "rel and", + "ah n", + "Ġdeploy ed", + "ĠE F", + "ens ing", + "ĠC ard", + "ĠJ ordan", + "ap unov", + "Ġanest hesia", + "Ġatheros clerosis", + "in ner", + "struct ural", + "ĠAs p", + "through put", + "ur ities", + "Ġin set", + "with out", + "Ġac quire", + "Ġcomb ines", + "ĠSh ar", + "M ASK", + "ĠL iter", + "Ġcons cious", + "isc ell", + "cons istent", + "y st", + "Ġfil aments", + "ĠAl ice", + "ĠG round", + "Ġm TOR", + "vers al", + "Ġline ages", + "par ticles", + "a roscopic", + "ĠPro ced", + "Ġorient ations", + "ĠM ouse", + "Ġaccording ly", + "Ġsuppress or", + "Ġdestr uction", + "O V", + "ĠProtein s", + "PE CT", + "Ġc up", + "Ġmon omer", + "plement al", + "Ġneutroph il", + "Ġer up", + "Ġt ac", + "Ġasympt omatic", + "ĠEm bed", + "ĠRad iation", + "ĠG ame", + "Ġneed le", + "Ġre use", + "ĠD utch", + "Ġjuven ile", + "Ġmoment a", + "ĠB ose", + "Ġde veloper", + "Ġresidual s", + "Å ¡", + "Ġc ognition", + "ĠReg ional", + "Y ou", + "ĠCon cent", + "oc in", + "ĠPar tial", + "Ġcomplet es", + "ĠSing h", + "ĠEx c", + "ĠIs olation", + "ĠStruct ures", + "Ġinter mitt", + "Ex ception", + "Ġanaly tically", + "Ġelectric ity", + "â ĭ", + "Ä į", + "Ġprote ome", + "Ġ ic", + "k al", + "inu x", + "ĠB eyond", + "Ġim plied", + "AS H", + "Ġcl one", + "ĠRuss ia", + "ĠH od", + "tebr ates", + "Ġpro xy", + "hold er", + "el ve", + "Ġval ley", + "ut ely", + "Ġj obs", + "rup tion", + "ro ids", + "ĠWh y", + "ep ing", + "ĠY et", + "Ġp yl", + "Ġb ra", + "il ization", + "et ers", + "Ġad ver", + "Ġo ve", + "k ernel", + "s amples", + "ordin ate", + "ĠAssum ing", + "Ġcontamin ated", + "Ġb ipolar", + "Ġl ac", + "Ġl uc", + "Ġcentrifug ation", + "B oth", + "Ġ nd", + "Ġt ib", + "B efore", + "ĠImmun e", + "Ġas h", + "Ġcondition ed", + "ĠR ank", + "N OS", + "Ġnanopar ticle", + "Ġdepend encies", + "Ġhouse holds", + "ag ers", + "Ġspect rophot", + "Ġb ile", + "ĠH ans", + "ĠAcknowledg ements", + "r atio", + "ĠSecond ary", + "Ġdown regulated", + "f ixed", + "O bs", + "ĠH L", + "Ġs ends", + "ting s", + "Ġf i", + "ĠPa per", + "Ġultra violet", + "ĠB all", + "Ġdr astic", + "ail ure", + "o il", + "ex change", + "ĠD an", + "ĠAut o", + "Ġarch ae", + "ĠCol lection", + "Ġantiv iral", + "ĠC hemistry", + "Ġf err", + "cho ice", + "v ac", + "ol ipid", + "Ġd anger", + "ĠL ittle", + "Ġde hyd", + "Ġoccas ion", + "oprop yl", + "ab e", + "Ġinterfer on", + "Ġex port", + "on itrile", + "p d", + "ĠCon text", + "ru z", + "ĠD ys", + "Ġassemb l", + "Ġoil s", + "Im age", + "row ing", + "Ġane urys", + "Ġliqu ids", + "Ġac tively", + "Ġev apor", + "ĠP resent", + "Ġconstit utive", + "ĠS ite", + "Ġsc ript", + "Ġrepe ats", + "ĠS IR", + "ĠFil m", + "ĠSant a", + "ĠRepresent ation", + "ĠA ma", + "ord on", + "ĠMo lecule", + "Ġgover ning", + "ĠSo il", + "V er", + "Ġphot onic", + "tif y", + "ĠLew is", + "at hered", + "Ġcategor ical", + "iscell aneous", + "up date", + "Ġdefic it", + "Ġadj uvant", + "ĠHen ry", + "G roup", + "ist ency", + "ag raph", + "ĠImpro ving", + "E l", + "Ġfl ame", + "rog ate", + "om orph", + "Ġqu bits", + "Ġillustr ation", + "ĠFlor ida", + "ĠD G", + "big cup", + "Ġprov ince", + "egrad ation", + "ĠLand au", + "Ġgr ating", + "Ġins ects", + "Ġd raft", + "ĠH b", + "Ġs s", + "ĠR as", + "Ġmuc osa", + "Ġhydrox yl", + "Ġmod est", + "Ġconfir ming", + "ĠGalax ies", + "G aussian", + "ĠRet rie", + "Ġrest ored", + "m emory", + "Ġreinfor ced", + "r ific", + "Ġass isted", + "Ġaffili ations", + "R C", + "duc er", + "ĠInt ellig", + "ĠA SD", + "mod ium", + "Ġo mitted", + "ok ers", + "Ġgu ided", + "Ġgraph ical", + "ĠQ ual", + "D ue", + "Ġn emat", + "vari able", + "Ġsenes cence", + "Ġpip e", + "Ġsustain able", + "Ġteac her", + "Ġth ing", + "ĠGP U", + "T B", + "Ġre form", + "Ġref lex", + "Ġindic ative", + "ab out", + "Ġop i", + "eff ect", + "Ġdispers ed", + "k h", + "it helial", + "ĠT reg", + "i pl", + "ĠAut omatic", + "Ġn itro", + "com plete", + "Ġbos ons", + "Ġp ac", + "Ġavoid ing", + "is l", + "pl asty", + "respons ive", + "d est", + "ĠB rad", + "ĠDec ision", + "ĠDisc overy", + "Ġchick en", + "m us", + "ĠW ITH", + "Ġt ric", + "Ġqu artz", + "onstr uction", + "ĠField s", + "Ġass im", + "opro t", + "Ġguarant eed", + "f at", + "ic ts", + "Ġch ol", + "id o", + "ĠK L", + "Ġch itosan", + "ĠN d", + "ĠO scill", + "Ġevol ve", + "c u", + "Ġm ast", + "Ġam ph", + "tor ch", + "V is", + "enti ty", + "ĠAd am", + "Ġdev oted", + "Ġeth ical", + "Ġprem ature", + "Ġconsum er", + "Ġrecurs ive", + "Ġglu on", + "Ġmoder ately", + "Ġmod alities", + "Ġcan al", + "for ce", + "ĠCh lor", + "sl ash", + "st en", + "Ġcommerc ially", + "ong s", + "Ġstim ulate", + "atin um", + "ĠR ail", + "Ġconv ective", + "Ġarter ies", + "in v", + "ĠW ol", + "ĠL ung", + "let es", + "raph y", + "ĠH I", + "Ġgraph ite", + "Ġhous ing", + "e ach", + "Ġcal or", + "acet amide", + "roc hemical", + "Ġhand s", + "Ġelucid ate", + "ĠCh and", + "ro ad", + "nov a", + "ĠLine age", + "Ġr am", + "Ġf ight", + "Ġrecommend ation", + "Ġamong st", + "Ġswit ches", + "ber ry", + "Ġthere in", + "al gebras", + "ĠT aken", + "az z", + "Ġf urn", + "Ġam el", + "Ġteac hers", + "ar n", + "Ġavoid ed", + "Ġaver ages", + "am er", + "ĠCon dition", + "Ġdis location", + "ir con", + "Ġadoles cent", + "Ġt ur", + "en v", + "Ġz e", + "D L", + "load ing", + "ic idal", + "c ategory", + "ĠD B", + "Ġmuc osal", + "ĠR G", + "Ġtaxon omic", + "Ġmut agen", + "ĠSt age", + "n ecess", + "ĠP erm", + "Ġoc clusion", + "Ġexplo ited", + "Ġana erobic", + "ul ed", + "Ġwant ed", + "ĠComb ining", + "Ġsub cutaneous", + "Rec omm", + "Ġdiscuss es", + "Ġcounter part", + "ĠF B", + "Ġadsorb ed", + "d on", + "M any", + "ĠSwed en", + "ĠAnd rew", + "enh anced", + "Ġdoc tor", + "ĠKore an", + "ĠS AR", + "Ġm ating", + "at uration", + "ĠL atin", + "Ġsor ting", + "Ġsk ip", + "O s", + "Ġw ife", + "Ġcom mittee", + "l vert", + "ĠA CC", + "ĠCom m", + "Ġsub tle", + "ĠSur vival", + "b ecause", + "Ġfe at", + "ĠPort ug", + "AR Y", + "ĠI SB", + "it ron", + "Ġs ectors", + "Ġadj oint", + "ĠAlex ander", + "Ġimp urity", + "ĠMar ine", + "l act", + "Ġtrap ping", + "Ġgeneral ize", + "fil ter", + "Ġpolar ity", + "Al so", + "Ġstabil ized", + "ĠVir gin", + "Ġst ores", + "P AGE", + "Ġdraw back", + "Ġâİ ª", + "j et", + "Ġsubstit uted", + "L INE", + "Ġoutper forms", + "Ġterm ed", + "Ġweek ly", + "Ġpoly c", + "Ġf used", + "Ġfer romagnetic", + "l r", + "ell ites", + "ĠT urn", + "ĠC ulture", + "pr ise", + "Å Ĥ", + "om position", + "elf are", + "ĠGo ogle", + "o arth", + "Ġ ë", + "Ġm ist", + "ĠMat hematics", + "S ET", + "Ġepoch s", + "Ġcont ras", + "ish ment", + "ĠFirst ly", + "Ġdecl ared", + "a ur", + "ĠP ed", + "Ġreplic ate", + "Ġel igible", + "Ġconc aten", + "Ġc ig", + "Ġtri plet", + "f ound", + "ĠC z", + "Ġaccompl ished", + "Ġgover ned", + "on uclear", + "ĠN Y", + "ĠEth iop", + "Ġin ject", + "Ġe osin", + "ann on", + "ol o", + "ĠM HC", + "Ġpre operative", + "Ġd ates", + "Ġs igma", + "L ong", + "ĠRes on", + "Ġsympt omatic", + "Ġvolunte ers", + "Ġco operation", + "Ġar r", + "Ġclon ed", + "Ġd ent", + "ĠS ob", + "Ġcath ode", + "ct x", + "Ġ encephal", + "Ġp iv", + "vi ve", + "um etric", + "ĠF F", + "Ġunde restim", + "Ġc oded", + "Ġanal ges", + "spect ral", + "Ġattrac ted", + "Ġtw enty", + "Ġin active", + "Ġvic tim", + "Ġhold er", + "ogen es", + "Ġsuff ering", + "re x", + "Ġpro phyl", + "ĠUnivers al", + "Ġden om", + "st olic", + "ans ion", + "SI ZE", + "ĠHC V", + "Ġtechn ological", + "CN N", + "en ching", + "Ġdeb ris", + "ĠBound ary", + "link ing", + "Ġstop ped", + "ĠD ie", + "ĠCos m", + "Ġturn ing", + "Ġgly coprotein", + "ĠK umar", + "Ġp g", + "ĠB Y", + "Ġr ising", + "ĠR OC", + "Des pite", + "ĠBo olean", + "il der", + "Ġexpon ents", + "in ters", + "print f", + "Ġl it", + "t rack", + "Ġf idelity", + "Ġsm oke", + "ot emporal", + "Ġad missible", + "ĠBol tzmann", + "T F", + "ol ite", + "li ament", + "Ġcalc ulus", + "iti zed", + "Ġdiver gent", + "Ġcolon ization", + "Ġconver gent", + "ĠH as", + "Ġconsum ers", + "Ġmy c", + "Ġcon tig", + "Ġepidem iology", + "é s", + "ĠAss oci", + "g iven", + "Ġwh ilst", + "ĠK ur", + "Ġreason ably", + "Ġaer obic", + "se par", + "Ġche cks", + "ĠSem antic", + "Ġserv ing", + "ĠAt mosp", + "Ġoxid ized", + "c oupled", + "Ġbio Rxiv", + "Ġtun ed", + "usp ended", + "Ġindirect ly", + "ĠC AD", + "ĠCurrent ly", + "Ġbehavi ours", + "ĠPP AR", + "r ors", + "ere b", + "Ġwid ths", + "di agonal", + "erv ice", + "Ġo le", + "me ans", + "IM E", + "ĠT racking", + "Ġac knowledge", + "ĠH on", + "ĠTechn iques", + "ĠOx id", + "bl ind", + "Ġdi ast", + "nam ed", + "asi tic", + "Ġprepar ations", + "ĠAr th", + "Ġpres erves", + "Ġf asc", + "Ġwave form", + "ĠC rystal", + "Ġunc om", + "Ġel ast", + "Ġfunction ally", + "H om", + "ĠCo ast", + "Ġop tic", + "ĠAltern atively", + "on yl", + "ĠL ig", + "al dehyde", + "Ġsim ulator", + "Ġdram atic", + "if era", + "Ġexhib iting", + "Ġbehaviour al", + "th ick", + "xt ure", + "Ġexec utive", + "Ġcondens ate", + "ĠOut comes", + "T ext", + "oin ted", + "ĠCopy right", + "Ġd c", + "od d", + "ĠD iversity", + "ch ip", + "ĠBu ilding", + "Ġpuls ed", + "har monic", + "Ġclinic ians", + "d p", + "Ġq PCR", + "mark s", + "Ġapprec i", + "ĠL aser", + "Ġsize of", + "y rene", + "Ġco operative", + "gener ative", + "ĠL ib", + "Ġdispers al", + "Ġevol ving", + "ĠSt atus", + "Ġsuper con", + "ĠM amm", + "Ġinters titial", + "isen berg", + "Ġâ ľ", + "Ġconf ocal", + "Ġmod ulates", + "h our", + "Ġper oxide", + "depend ence", + "Ġperturb ed", + "ill ation", + "Ġpl aque", + "ĠNe umann", + "Ġtrig gers", + "om ain", + "ĠAd ministration", + "ol ia", + "ĠM IC", + "osa ic", + "ĠG B", + "text normal", + "Ġdomin ance", + "ĠEx per", + "C AM", + "ĠAb out", + "ĠG arc", + "Ġsummar izes", + "A pp", + "charomy ces", + "tif icial", + "Ġgly cerol", + "ĠAssum ption", + "Ġt ect", + "ĠF W", + "Ġcot ton", + "gen eral", + "ĠF ern", + "P t", + "Ġwork er", + "Ġan ion", + "gram s", + "re q", + "Ġlo oks", + "Ġimplement ations", + "ĠCol umb", + "ag i", + "ĠAt tention", + "ĠTe am", + "on ing", + "on ential", + "tin y", + "ĠHigh ly", + "text up", + "Ġinver tible", + "oc ortic", + "In f", + "ĠOff icial", + "ĠMod elling", + "Ġincl usions", + "Ġbl ank", + "Ġs ight", + "ĠG amma", + "Ġlept on", + "Ġpneumonia e", + "Ġro tor", + "Ġeth nic", + "Ġre tain", + "v arying", + "ĠE B", + "Ġast rocytes", + "ĠN orm", + "Ġnan om", + "class ical", + "Ġsh adow", + "ĠRef erences", + "ĠF S", + "Ġnon negative", + "b ond", + "ĠC oh", + "Ġnum py", + "Ġo ct", + "sp an", + "rac ts", + "Ġnot ably", + "Ġsoph istic", + "P AR", + "Ġhorm ones", + "Ġtens ors", + "ĠÌ Ħ", + "ĠConst raints", + "Ġâ IJ", + "Ġtrans it", + "Ġrun time", + "aut hor", + "Ġprom pt", + "ĠS G", + "Ġg rate", + "ce mia", + "ĠLy apunov", + "con vex", + "Ġforecast ing", + "p ush", + "Ġjurisdic tional", + "à Ģ", + "Ġbiom edical", + "Ġepile psy", + "fe ature", + "wik i", + "Vi ew", + "Ġless er", + "Ġconjug ated", + "Ġwa iting", + "ĠW ord", + "I Z", + "Ġhydro xy", + "Ġdis p", + "Ġseed ed", + "fit ting", + "Ġstrat ification", + "Ġend point", + "Ġmedi ators", + "duc tive", + "Ġinj ections", + "ĠMicro bi", + "Ġins ert", + "ĠEm b", + "Ġstop ping", + "w elling", + "Ġirradi ated", + "Ġmetall icity", + "vin yl", + "Ġplasm ids", + "R ep", + "ĠDiff erenti", + "ĠSm art", + "ĠIdentif ier", + "ĠB F", + "rop ic", + "Ġkinem atics", + "Ġinoc ulated", + "C K", + "aus es", + "ĠReturn s", + "re ement", + "Ġantic ancer", + "Ġspecific ations", + "Ġadd s", + "Ġst ake", + "Ġwhe el", + "ü ller", + "ĠS on", + "Ġrup ture", + "Ġsol d", + "th an", + "Ġinter medi", + "ĠN ik", + "Ġt uple", + "est abl", + "Ġnor the", + "Ġsuppress es", + "Ġf et", + "Ġwas hing", + "Ġinter play", + "Ġregular ly", + "EX T", + "Ġemploy ees", + "y z", + "rup ted", + "et ts", + "ĠU AV", + "Ġdifferenti able", + "ing e", + "MD A", + "Ġh o", + "Ġt ags", + "Ġcomp atibility", + "Ġà ĥ", + "b us", + "ĠU C", + "Ġtok ens", + "Ġcl ients", + "Ġpres cription", + "ĠÌ Ī", + "ĠRe action", + "veloc ity", + "ĠN LR", + "ĠG ast", + "ĠPlas modium", + "ĠC ut", + "Ġn as", + "gra ined", + "Ġchrom osomal", + "Ġpossess es", + "Ġm ath", + "Ġe lected", + "plac ement", + "Ġcollect ing", + "Ġg els", + "ai re", + "Ġdeform ations", + "ra ise", + "Ġfl ank", + "sulf anyl", + "z ens", + "pri ate", + "Ġchlor ophyll", + "ab i", + "avail able", + "Ø §", + "Ġt ack", + "field s", + "Ġrich ness", + "Ġimpl ants", + "ob enz", + "id ential", + "Ġbill ion", + "ut or", + "ĠISB N", + "Ġins urance", + "N ET", + "Ġin adequ", + "Ġmerg ed", + "ĠR ange", + "Ġavoid ance", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠ", + "ric s", + "Ġexcl usive", + "L V", + "Ġï£ ²", + "Ġcategor ized", + "Ġultrason ic", + "ip e", + "ic ans", + "ĠA PP", + "Ġtra umatic", + "B a", + "ĠAss ay", + "ĠG rid", + "ĠClass ical", + "ĠD ES", + "Ġsoy bean", + "Ġtop ography", + "ĠCont roll", + "Ġemo tions", + "Ġcarbohyd rate", + "Ġcons ol", + "ox yl", + "Ġbifurc ation", + "Ġco il", + "f ind", + "Ġw itness", + "ĠL F", + "th reshold", + "Ġaddress ing", + "Ġsc rew", + "Ġact or", + "ĠW ell", + "Ġï£ °", + "ï ĺ", + "ĠD F", + "ĠCor poration", + "ĠMitochond rial", + "Ġk pc", + "und ers", + "Ġfib rin", + "ax el", + "Ġpol yt", + "Ġshap ed", + "re z", + "ste resis", + "ĠComp rehens", + "Ġï£ ³", + "d h", + "Ġsem ic", + "Ġm ot", + "ĠDav is", + "sk a", + "ĠL H", + "Ġexpans ions", + "ack s", + "Ġoptim izing", + "e ak", + "ĠQ i", + "m ul", + "og raft", + "Ġsu icide", + "cal ar", + "ĠSc ott", + "Ġth inking", + "Ġdirec tional", + "Ġsurfact ant", + "Ġdegrad ed", + "Ġregim en", + "it ative", + "ĠV ersion", + "ĠM aster", + "ĠSim ulations", + "NC BI", + "l ip", + "Ġre agents", + "Ġpost ed", + "os us", + "Ġlay ered", + "ĠSpect rum", + "ĠGraph s", + "bur st", + "Ġl ived", + "Ġelement al", + "Ġï£ »", + "ĠDisc rete", + "Ġexcl uding", + "Ġorigin ating", + "ĠG ames", + "continu ous", + "AT ED", + "Ġpy ram", + "lu ent", + "Ġtw isted", + "ĠN b", + "ox icity", + "Ġsc r", + "Ġf un", + "ĠSeg mentation", + "Ġphen ol", + "Ġmet ers", + "ĠE igen", + "ĠWe ak", + "Ġschem atic", + "r one", + "Ġphil os", + "ti tis", + "ĠI reland", + "Ġg y", + "ĠPT M", + "Ġpack ing", + "il inear", + "z eros", + "Ġubiqu itin", + "ĠPress ure", + "Ġinf iltr", + "EN S", + "val idation", + "Ġpr one", + "Ġout line", + "h s", + "reng th", + "Ġat tain", + "Ġt we", + "Ġt andem", + "C an", + "Ġlat itude", + "uit ary", + "Ġvolt ages", + "ĠGa o", + "Ġpharmac okine", + "Ġcontext ual", + "Ġx yl", + "els on", + "ĠMet abolic", + "od en", + "ti les", + "ff icking", + "Ġdistill ed", + "Ġal ph", + "Ġpie zo", + "g rowth", + "Ġb ore", + "Ġredund ant", + "Ġdemonstr ation", + "Ġi k", + "Ġround s", + "ĠS ri", + "fig uration", + "ĠRay leigh", + "L ine", + "ov ol", + "Ġobstac le", + "c n", + "Ġbio active", + "ĠO A", + "phys ical", + "at idyl", + "AC C", + "h ow", + "Ġresult ant", + "ĠH ubble", + "ĠV or", + "Ġens uring", + "Ġannot ations", + "ac yl", + "stit uted", + "ĠAm b", + "feed ing", + "Ġpresum ably", + "Ġblock ade", + "Ġs oc", + "ĠU rb", + "Ġmulti plied", + "Ġdiff e", + "Ġreflect ance", + "ĠKey words", + "ĠBay es", + "odef iciency", + "ĠB inding", + "in ely", + "ex cept", + "ĠUl tr", + "ĠBrazil ian", + "N umber", + "Ġmass less", + "ĠCons istent", + "Ġcr isis", + "og s", + "Ġres idence", + "Ġim per", + "f ts", + "Ġcapt ures", + "ĠSynd rome", + "Ġdimension ality", + "j un", + "Ġex haus", + "ĠMod ern", + "Ġperc enti", + "Le vel", + "ĠRespons es", + "Ġla unched", + "Ġre pos", + "ĠK am", + "at ility", + "Ġcaro tid", + "ro tic", + "ĠM and", + "U B", + "ĠM ixed", + "Ġindex es", + "Ġcis platin", + "ic an", + "ion ine", + "Ġh ab", + "ĠI ce", + "ĠG T", + "ĠAg g", + "ĠLD L", + "Ġvolcan ic", + "d B", + "ĠElect ric", + "Ġt mp", + "Ġgrid s", + "l iquid", + "p rom", + "ĠG AL", + "Ġp estic", + "Ġhel ium", + "Ġï£ ¹", + "ĠD ong", + "Ġmagn ification", + "k ip", + "ĠG rad", + "ĠWe i", + "ĠPD F", + "ĠGl uc", + "P ol", + "Ġtumor igen", + "yr in", + "Ġshel f", + "ad her", + "enti als", + "s n", + "Ġcultiv ars", + "Ġorbit als", + "ĠP EG", + "ĠAn ne", + "en o", + "Ġatt ended", + "oph ore", + "ish op", + "Ġf riends", + "pos able", + "Ġim pose", + "Ġend emic", + "Ġs ick", + "shif ts", + "ĠOut put", + "L M", + "ĠM iscellaneous", + "Ġthous ands", + "ĠD ataset", + "Ġperturb ative", + "op rec", + "Ġb ene", + "Ġre ef", + "Ġfoss il", + "Ġc ited", + "plic ates", + "Ġrel ates", + "ĠV II", + "Ġanti fer", + "Ġglass es", + "clos ure", + "Ġrub ber", + "Ġb ird", + "Ġsuper symmetry", + "Ġmes on", + "he ll", + "Ġpar ties", + "k ar", + "ĠH ur", + "ĠE A", + "ĠSt ars", + "oth ing", + "h ot", + "ill ar", + "AS P", + "he v", + "ï ĥ", + "a ques", + "Ġcoordin ated", + "ĠIs lands", + "en able", + "Si O", + "Ġexception al", + "C omb", + "ĠL ike", + "Ġbroad ly", + "ĠB ac", + "Ġn il", + "ipar tite", + "r ations", + "Ġre write", + "Ġsal ts", + "d imension", + "ĠVe hic", + "Ġhundred s", + "ĠU r", + "Ġend points", + "ĠMOD EL", + "ĠH BV", + "ĠVir tual", + "ĠCon fl", + "ĠPrac tice", + "ĠAF M", + "Ġadvers arial", + "Ġdi ameters", + "Ġtrans ported", + "RE M", + "ĠB art", + "Ġed ition", + "Ġturb ine", + "Ġmin us", + "otechn ology", + "I g", + "Ġbig ger", + "ab ul", + "Ġperoxid ase", + "wh ite", + "ĠS ed", + "di hydro", + "Ġseg regation", + "Ġreduct ase", + "Ġhor iz", + "Ġinf initely", + "avail ability", + "Ġactiv ator", + "Ġc ensus", + "press ing", + "Ġspir it", + "con ver", + "ĠQuantif ication", + "omer ase", + "Ġrel apse", + "ĠF inal", + "Ġover weight", + "a per", + "Ġformul ae", + "r r", + "Ġfem oral", + "Ġfo am", + "o tics", + "Ġprovid er", + "Ġinstr umental", + "Ġadv ice", + "Ġoccup ation", + "ass embly", + "bi as", + "ĠN OT", + "re stric", + "ĠProt ocol", + "ĠCandid a", + "ĠR hod", + "ard en", + "f under", + "os ens", + "Ġpar ams", + "f ront", + "Ġex erc", + "Ġgal actic", + "r vert", + "Ġim balance", + "Ġk illing", + "ĠGen omic", + "Ġ ip", + "Ġc ave", + "Ġf alc", + "ĠR M", + "Ġcar ries", + "gl obal", + "Ġc ube", + "Ġrig orous", + "Ġcomput es", + "Q P", + "Ġexpos ures", + "c over", + "ological ly", + "O per", + "Ġp ec", + "Ġin homogeneous", + "Ġser vers", + "alian a", + "n b", + "Ġexplain ing", + "Ġshr ink", + "Ġcom orbid", + "eth oxy", + "outhe ast", + "Ġco urses", + "ĠN M", + "ĠSh ape", + "Ġfl ies", + "ĠM ir", + "Ġpublic ly", + "Ġphot ometric", + "vers ible", + "ole v", + "Ġvulner ability", + "Ġc ations", + "Ġsee king", + "U TR", + "Ġdecom posed", + "Ġh us", + "Ġdisapp ear", + "Ġenc ounter", + "Ġtransform ing", + "Ġpolymer ic", + "Ġdiscre tization", + "otox ic", + "ĠI ter", + "ĠM ari", + "Ġun fold", + "ĠAd ult", + "ob acillus", + "met al", + "ber ger", + "rap hene", + "resp ective", + "Ġsur vive", + "ov ich", + "Ġprot ects", + "ĠR og", + "Ġimmun otherapy", + "ĠD SM", + "Ġanalog y", + "ĠP ER", + "ĠPy thon", + "h um", + "ĠAd j", + "ĠLik ewise", + "Ġï£ ®", + "Ġstom ach", + "Ġin it", + "Ġw ires", + "Ġingredi ents", + "Ġper ceptual", + "H and", + "B ack", + "Ġm ood", + "Ġde formed", + "ĠRe ad", + "Ġrh iz", + "ĠOrgan ism", + "ĠInd ones", + "ann ot", + "ict ory", + "Ġt ended", + "ĠS ound", + "ia x", + "S r", + "ĠT ab", + "ĠLa placian", + "ol uminescence", + "back slash", + "i ologic", + "Ġtyp ename", + "ĠY ear", + "D ependent", + "Ġsl ides", + "Ġsac rific", + "Ġconcomit ant", + "ops ies", + "Big g", + "pe ak", + "ĠApp lying", + "Ġcod on", + "ĠSim ultaneous", + "ti se", + "Ġter tiary", + "ĠP oll", + "Ġre vision", + "RA F", + "x mm", + "Ġsu ited", + "ĠRecomm end", + "ĠR y", + "Ġs ake", + "Ġstret ch", + "ĠSam pling", + "Ġtub ular", + "Ġpar k", + "Ġul timate", + "Ġl ands", + "ĠCr iter", + "ass ay", + "m or", + "Ġd ocking", + "Ġgrad ual", + "Ġed itor", + "Ġpol ice", + "aff in", + "ĠDe ath", + "Ġpromot ers", + "ass ic", + "Ġwr iter", + "ĠVol ume", + "is o", + "Ġdis ag", + "tok en", + "Ġster oid", + "N on", + "ĠMet hyl", + "A meric", + "d ue", + "ĠL ess", + "Ġdy st", + "ĠStat ement", + "ĠT wenty", + "Ġaccess ed", + "Ġblot ting", + "ĠCO PD", + "Ġste am", + "Ġdescrip tive", + "ĠV ery", + "Ġcapac ities", + "ĠPers onal", + "ac id", + "ä hler", + "estiv al", + "Con text", + "Ġa str", + "Anal ysis", + "Ġse pt", + "Ġpr inted", + "d ual", + "am an", + "ere r", + "Ġweak ness", + "ì Ŀ", + "ĠTrans lation", + "Ġpropag ating", + "ĠS ections", + "ac a", + "Ġconf usion", + "I K", + "Ġframework s", + "Ġsitu ated", + "Ġst ays", + "n odes", + "c hen", + "art ments", + "Ġfree zing", + "w s", + "net t", + "Ġcontroll ers", + "Ġsil ic", + "LA ST", + "f oot", + "ĠDISC U", + "R H", + "rid ine", + "ĠRe v", + "per g", + "py rim", + "fl ags", + "ĠGu ide", + "Ġspe aker", + "tis ol", + "re ll", + "ĠD EG", + "Ġf u", + "ĠG ut", + "Ġsh ar", + "Ġgro ss", + "Ġcross es", + "wa velength", + "ĠAp plied", + "ï ve", + "ĠH B", + "ĠEd ge", + "Ġiner tial", + "Ġv ocal", + "pro duction", + "pat hetic", + "Ġplan etary", + "Ġs ister", + "Ġminim a", + "Ġlong est", + "Ġfl ash", + "Ġperiod on", + "Ġepid ermal", + "Ġflo ating", + "G ET", + "ĠT ake", + "p df", + "ĠL iquid", + "Ġremark ably", + "S ign", + "Ġshell s", + "oglob ulin", + "qu ilibrium", + "ĠMo ore", + "ĠAd vers", + "ĠMyc obacterium", + "Inv itrogen", + "Ġth aliana", + "B Y", + "ĠB it", + "Ġt s", + "Ġsynchron ous", + "y x", + "Ġpropag ator", + "ĠIncre asing", + "ipar um", + "Ġfree ze", + "ĠSe lective", + "af e", + "Ġstre pt", + "ph antom", + "ĠGener ally", + "Ġaltern ate", + "ĠCon vergence", + "//////// ////////", + "eng ing", + "ĠRandom ized", + "de velop", + "pred ict", + "ress or", + "Ġmat hematics", + "f r", + "ĠComput ation", + "ĠMal ays", + "Ġbreath ing", + "Th rough", + "ĠS IM", + "Ġan ode", + "o ad", + "ĠAT CC", + "Ġconstitu ent", + "ĠMeas uring", + "Ġf MRI", + "Ġan emia", + "lies t", + "Ġhemisp here", + "Ġmaxim a", + "Ġtem porary", + "Ġd z", + "otox in", + "C ount", + "on ed", + "à º", + "Ġcollabor ative", + "Ġk b", + "Ġvers a", + "ĠSwed ish", + "ik a", + "Ġdial ysis", + "Ġper ovsk", + "Ġwill ing", + "ĠG reek", + "Out put", + "Ġsem igroup", + "Ġbott len", + "ĠGib bs", + "d ark", + "Ġrheumat oid", + "ur ring", + "mat ched", + "Ġsophistic ated", + "Ġcust omer", + "tetra hydro", + "X Y", + "b ug", + "Ġmor ning", + "ĠC VD", + "Ġm appings", + "ĠM SCs", + "ĠD H", + "Ġqu atern", + "he alth", + "Ä ±", + "Ġtem p", + "ĠJ ew", + "ĠI l", + "Ġvor tices", + "Ġser ine", + "ĠOx ygen", + "w eg", + "Ġexplan ations", + "P G", + "Ġc iti", + "Ġloc ality", + "== =", + "ĠTh om", + "Ġd airy", + "Bl ock", + "or dial", + "ak ov", + "Ġgli oma", + "Ġtrans action", + "Ġincre mental", + "anc he", + "R et", + "m agnetic", + "pyr rol", + "ĠP ic", + "Ġamel ior", + "oxid ant", + "rov iral", + "or atory", + "Ġs av", + "ĠSt ream", + "Ġsuper f", + "ĠIC U", + "Ġevid enced", + "Ġrepeated ly", + "Ġr ated", + "ĠP it", + "FA ULT", + "Ġh at", + "ĠCont ent", + "Ġiso form", + "V ER", + "Ġn odal", + "Ġschedul ed", + "Ġshould er", + "Ġt ap", + "Ġpor tal", + "Ġtra ps", + "ae v", + "ĠS OD", + "em atic", + "Ġen j", + "Ġretic ulum", + "ĠMin ister", + "ĠS el", + "Ġfall ing", + "ro st", + "N G", + "f d", + "n itro", + "ĠM ove", + "rel ativistic", + "eng es", + "ĠS ST", + "ĠIn v", + "Ġfin ish", + "ĠPol and", + "os econd", + "ĠB AL", + "oarth ritis", + "Ġop tics", + "ĠS ky", + "Ġadv oc", + "Ġhemorrh age", + "Ġmod ulating", + "n is", + "Ġmach inery", + "Ġupd ating", + "Ġcharacter izing", + "ish man", + "Ġtem plates", + "ĠLa place", + "ĠEn s", + "Rec ently", + "or us", + "ar ts", + "diff usion", + "ĠLevel s", + "ag a", + "ĠIn j", + "ĠL ayer", + "Ġrem n", + "Ġelastic ity", + "Ġmere ly", + "Ġf ission", + "eng ue", + "m ake", + "Ġmon op", + "Ġure a", + "ĠSim on", + "mi R", + "ĠSecond ly", + "ur ic", + "ĠVari able", + "il is", + "Ġmultiplic ative", + "ĠNo ise", + "Ġswit ched", + "Ġnic ot", + "Ġeffici encies", + "he ma", + "Ġapp ointed", + "gu ided", + "Ġwin ning", + "ĠMechan ics", + "Ġne o", + "ĠBR CA", + "ud i", + "Ġcontain er", + "sh op", + "Ġsugges tions", + "K B", + "Ġsubstit ute", + "O x", + "V C", + "Ġst one", + "ann a", + "ĠDep ression", + "Ġcont emporary", + "Ġoutl iers", + "qu et", + "ĠZ heng", + "Ġoc cl", + "Ġal veolar", + "exp ressing", + "Ġcom fort", + "Ġign ore", + "Am ong", + "ĠKle in", + "Ġrhyth m", + "Ġimm ers", + "Ġfa ith", + "bl ing", + "Ġaug mentation", + "ĠPre vention", + "Ġhe par", + "Ġnot ations", + "Ġhemat opoietic", + "perf ect", + "Ġsh ares", + "not in", + "Ġpict ures", + "ĠAcknowledg ments", + "Ġt ick", + "Ġun related", + "ĠTo ol", + "Ġm as", + "os ocial", + "g est", + "us hed", + "Ġphosphor ylated", + "Ġcer amic", + "c ool", + "or ylation", + "Ġdef icient", + "Ġrelax ed", + "ĠAnal yses", + "ec raft", + "Ġret ina", + "ĠIn ternal", + "Ġsp ite", + "Ġrecip ients", + "Ġsh ut", + "Ġeth ylene", + "ĠG ulf", + "Ġun affected", + "ĠRes ource", + "ĠN et", + "Ġperp et", + "Ġsl ab", + "re port", + "Ġμm ol", + "Ġid x", + "Ġsk ill", + "ĠInd uction", + "Ġmalign ancy", + "Ġc v", + "Ġdiff ering", + "Ġappropri ately", + "ij ing", + "Ġwar rant", + "r ally", + "Ġal gae", + "we ights", + "c asts", + "Ġoc ular", + "rac ycl", + "Ġdomin ates", + "Ġle uc", + "W here", + "ph on", + "Ġsocio economic", + "itzer land", + "Ġresil ience", + "Ġneighbour hood", + "Ġt one", + "psy ch", + "ĠOrgan ic", + "Ġg ather", + "Ġfalc iparum", + "Ġengine ered", + "ĠAv ail", + "inter ing", + "Ġclim atic", + "ĠEvolution ary", + "N MR", + "Ġre v", + "cent ral", + "ĠS in", + "Ġdecl ined", + "op ausal", + "Ġal arm", + "Right arrow", + "se x", + "Ġenerge tic", + "ï Ĥ", + "Ġdisc s", + "Ġol factory", + "ur ipot", + "spect rum", + "sp ot", + "Ġhem oglobin", + "M ark", + "c ov", + "ar boxyl", + "Ġindic ations", + "Ġsal mon", + "Ġsearc hed", + "Ġend ed", + "rolog ic", + "r floor", + "Ġau tism", + "Ġs elen", + "ĠH ung", + "ĠInf erence", + "Ġmamm ary", + "l floor", + "Ġser oton", + "Ġfund ed", + "ĠVi et", + "Ġri vers", + "ĠRe infor", + "ur g", + "Ġalb icans", + "ĠTherm o", + "ERR OR", + "Ġmut ually", + "Ġir r", + "ĠR at", + "Ġim g", + "Ġlymph ocyte", + "ĠRef s", + "ĠS parse", + "hold ers", + "F ree", + "RE D", + "ĠG auss", + "Ġcirc adian", + "ĠJ in", + "Ġconstit utes", + "Ġw ors", + "Ġfeature d", + "oc ent", + "le te", + "Ġont ology", + "Ġbil ayer", + "ĠCam bridge", + "Ġencryp tion", + "rot ron", + "et ti", + "ĠA er", + "Ġcou ples", + "ra il", + "Ġtw ist", + "Ġrid ge", + "G AN", + "id ers", + "SH IFT", + "Ġdiff us", + "Ġme ant", + "ĠSch warz", + "S b", + "Ġarc s", + "No tice", + "i y", + "Ġem erge", + "kw args", + "E ff", + "E nt", + "ion ization", + "ch oline", + "ust ries", + "ac her", + "s pl", + "pop ulation", + "f ol", + "Ġquestionnai res", + "Ġall ergic", + "w ich", + "ĠV acc", + "Ġat tained", + "ĠAn imals", + "am ics", + "ĠReg arding", + "ĠSem i", + "Ġgl ac", + "ĠEff icacy", + "Ġsynerg istic", + "IS H", + "Ġmaintain s", + "Ġsong s", + "ĠNeg ative", + "am oto", + "ĠMod ified", + "Ġsepar able", + "Ġbin aries", + "Ġaccess ibility", + "I ter", + "d in", + "ĠB inary", + "equ ilibrium", + "Ġc ue", + "m agn", + "Ġed ema", + "ï ¿½", + "Ġposition ed", + "Ġcharg ing", + "Ġun ivariate", + "he p", + "Ġcl ade", + "Ġcy steine", + "rac le", + "Ġresc ue", + "h abit", + "ĠDISCU SSION", + "Ġdepic ts", + "p ole", + "Ġst enosis", + "Ġv eter", + "pr inger", + "ĠP ow", + "Ġcovari ant", + "Ġmod ifying", + "Al gorithm", + "aver aged", + "al o", + "res on", + "Ġcharacter ised", + "Ġn i", + "Ġseem ed", + "ĠR om", + "sh ort", + "N V", + "Ġfer tility", + "ĠM emb", + "Ġl ying", + "Ġinstit ution", + "im ages", + "ĠB orel", + "fs ys", + "c ataly", + "Ġsepar ating", + "b iotic", + "m el", + "pg fsys", + "ĠJack son", + "Ġb ag", + "og rap", + "prop yl", + "ĠProgram ming", + "oc ratic", + "Ġp ion", + "ĠG radient", + "Ġsp he", + "Ġin line", + "Ġdom inate", + "Ġsuff ered", + "ĠDise ases", + "igen ous", + "w ill", + "Ġam in", + "adher in", + "ĠT ro", + "adj usted", + "E W", + "Ġde but", + "ne a", + "ĠD un", + "Ġd ictionary", + "oper atively", + "K A", + "be it", + "Ġperson nel", + "ĠÅ ½", + "re view", + "int o", + "ĠTok yo", + "Ġt rop", + "Ġvent ric", + "ĠMETHOD S", + "Ġim plication", + "ak is", + "ĠC MB", + "Ġtransmit ter", + "o ichi", + "ĠNiger ia", + "ĠK on", + "Ġbe ar", + "ĠK an", + "ĠPl ot", + "ĠS PSS", + "ĠBi ology", + "Ġbary on", + "Ġmicro RNA", + "Ġreproduc ibility", + "Ġlact ate", + "Ġpolyp hen", + "ĠM t", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ", + "end it", + "Ġhydro thermal", + "Ġwe alth", + "Ġhad ron", + "Ġwhere by", + "ell um", + "ĠDiff usion", + "ĠOrig in", + "Ġnonlinear ity", + "Ġinform ative", + "Ġvis ited", + "Ġvirt ually", + "ĠT un", + "Ġres et", + "ĠElect rical", + "ĠG lu", + "ĠS AM", + "ĠI sing", + "ĠSt ra", + "ond er", + "Ġd ies", + "Ġrecipro cal", + "C heck", + "ĠGu idelines", + "hest er", + "Ġproblem atic", + "ĠAt omic", + "Ġconcentr ate", + "st eps", + "j son", + "Recomm ended", + "ĠScreen ing", + "Ġna ive", + "Ġpractition ers", + "Ġfast ing", + "Ġmechan istic", + "op tions", + "P tr", + "IT E", + "W ork", + "âĢ ĺ", + "raf ts", + "Ġun w", + "Ġannih ilation", + "ob jective", + "ĠD ynamical", + "ad ec", + "ĠL ith", + "Ġextract ing", + "Ġcor al", + "ĠSt able", + "Ġbackground s", + "omorphism s", + "ĠâĪ «", + "Ġgre w", + "In st", + "g els", + "Ġin hal", + "d am", + "he im", + "benz yl", + "Ġpel vic", + "Ġdi arr", + "Ġdi ode", + "Ġem pir", + "ĠAl f", + "ĠUn certain", + "ĠH Cl", + "Ġjoint ly", + "Ġde par", + "Ġmerg ing", + "Ġch i", + "ap t", + "Ġpl t", + "Ġid i", + "Ġper for", + "stit uting", + "p age", + "ar é", + "ind ices", + "put ation", + "diff erent", + "b urn", + "Ġsurround ed", + "ĠT L", + "unt ary", + "st rip", + "l an", + "Ġc ow", + "ĠS ab", + "ĠGa As", + "p f", + "Ġes ophageal", + "ĠAl t", + "Ġhospital ization", + "ĠApproxim ation", + "Organ ism", + "ĠF air", + "Ġtrac ing", + "Ġpref erentially", + "Ġlower ing", + "uli ar", + "ĠDer iv", + "Ġphyto plankton", + "omy c", + "T hat", + "ĠIsra el", + "Ġminim ized", + "Ġany thing", + "r ule", + "p ow", + "Ġfam ous", + "ĠAcc uracy", + "Ġphotoc atalytic", + "ĠNon etheless", + "Ġdivis or", + "v b", + "Ġcam eras", + "ĠW ales", + "ĠCont ributions", + "Ġdisplac ements", + "ĠT am", + "Ġvol umetric", + "ession al", + "Ġcompens ate", + "Ġa ce", + "tri angle", + "bu ff", + "Ġnames pace", + "Ġbound ing", + "ynchron ous", + "m d", + "Ġimag ery", + "it ated", + "Ġorigin ated", + "ĠBel g", + "ĠE CG", + "ex isting", + "ĠSt okes", + "sens itivity", + "tid ine", + "ĠW M", + "Ġmonot one", + "Ġproceed s", + "ĠClust ering", + "ĠIo T", + "ern ary", + "al amic", + "ĠCollabor ation", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠĠ", + "OL D", + "Î ĺ", + "ĠNan opar", + "ĠMul tiv", + "Ġc ystic", + "pi re", + "Ġoper ates", + "Ġmedi ating", + "Ġbene ath", + "ob e", + "g ate", + "Ġo ocytes", + "Ġmarg ins", + "ymmet ries", + "Ġrelig ious", + "ĠN it", + "Ġcut aneous", + "AN S", + "Ġdevelop s", + "as ia", + "ĠRo berts", + "a vier", + "Ġsim plic", + "Ġreveal ing", + "UN D", + "Ġte a", + "Ġl ysis", + "Ġaggreg ated", + "ĠR GB", + "Ġcor ro", + "Ġb ir", + "in ae", + "v d", + "Ġcour t", + "Ġcontrovers ial", + "Ġto w", + "Ġhy steresis", + "en berg", + "Ġent ers", + "p ng", + "ĠF lex", + "Assum e", + "ĠB ad", + "ĠSimilar ities", + "Ex perim", + "AT H", + "Ġ ut", + "ter ms", + "ĠM ol", + "Ġvis ually", + "Ġadop tion", + "Ġprint ing", + "Ġequ iv", + "ĠP ert", + "Ġper col", + "Ġsome one", + "abul ary", + "Ġle ver", + "ĠH aus", + "ic illin", + "it ar", + "Ġto urn", + "Al tern", + "Ex p", + "~~ ~~", + "ĠF o", + "Ġab ol", + "med ian", + "Ġroll ing", + "h m", + "Ġtel escope", + "ĠC av", + "Ġseed lings", + "in hib", + "Ġd in", + "Ġimp urities", + "Ġampl ifier", + "ĠK er", + "Ġdimin ished", + "P B", + "f ib", + "ro ck", + "ĠB in", + "Ġphotos ynthetic", + "ĠCr ypt", + "Ġpre term", + "Ġh its", + "Ġfract al", + "Ġdisc arded", + "Ġend ocrine", + "os hi", + "Ġmod ulo", + "w t", + "Ġqu enching", + "Ġsound s", + "ĠED TA", + "re active", + "Ġres ist", + "ang hai", + "Ġn arr", + "Ġiniti ate", + "ĠS aint", + "X R", + "Ge V", + "ĠInd ependent", + "Ġinj ective", + "up us", + "Ġl inguistic", + "Ġanalog ues", + "Ġdiss ection", + "Ġlas ers", + "di ab", + "ĠTe le", + "Ġc racks", + "Ġb rane", + "V O", + "ĠExt ended", + "Ġt ells", + "Ġremark s", + "ul ting", + "ĠB urn", + "d L", + "ress ible", + "ĠCh ap", + "Ġs q", + "Ġrepro duced", + "ĠB cl", + "Ġsw arm", + "opath ology", + "ch rotron", + "Ġm ine", + "Ġhad ronic", + "ĠLocal ization", + "ĠM otor", + "Ġvisual ize", + "Ġc ats", + "Ġbal ancing", + "ĠSc hed", + "Co A", + "Ġtherm odynamics", + "ĠDiagn ostic", + "Ġreli ef", + "Ġpos itivity", + "Ġh ub", + "ĠInf rared", + "S ur", + "om ed", + "Ġop tically", + "Ġv ascul", + "is ations", + "enc oder", + "Ġcopol ymer", + "Ġrest ore", + "Ġiner tia", + "ubic in", + "Ġeti ology", + "ĠSec ret", + "ĠC W", + "Con st", + "ĠBr it", + "ĠConst ant", + "ĠD IS", + "Ġdisc ipl", + "b ra", + "ĠO ral", + "ĠU L", + "Ġdel ine", + "Ġnucle on", + "Ġemploy ment", + "ĠR D", + "q q", + "ĠCarol ina", + "ĠG ab", + "Ġasser tion", + "CM C", + "r gb", + "F rame", + "ĠJ ust", + "Ġinoc ulation", + "cl uding", + "Ġoscill atory", + "Ġcanc el", + "ĠPo inc", + "por a", + "ĠJ ul", + "ru vate", + "Ġpoli tic", + "ur us", + "ĠAdv ances", + "ĠR oot", + "th ood", + "oxygen ase", + "ms g", + "Ġk V", + "Ġad mit", + "Ġrefrac tory", + "Ġclon ing", + "Ġf atal", + "plant ation", + "ĠG ir", + "Ġt es", + "ĠR ho", + "oh n", + "Ġinnov ation", + "Ġs ending", + "Ġc able", + "Ġnic he", + "Ġres erve", + "Ġat rophy", + "ath an", + "Ġà ij", + "iti zation", + "Ġf an", + "Ġb ubbles", + "ĠTheorem s", + "ĠSw itzerland", + "ĠHe isenberg", + "ĠRed uced", + "R a", + "Z r", + "ĠPoss ible", + "U psilon", + "ĠAg ric", + "el lect", + "nd s", + "math ds", + "at re", + "Ġfor aging", + "Ġup ward", + "id ene", + "Ġgl ands", + "f ed", + "uccess ful", + "ĠW olf", + "Ġuseful ness", + "op orous", + "Ġp unct", + "ard o", + "Ġsy stolic", + "ĠTarget ing", + "Ġill umin", + "Ġpig ment", + "Ġsim ulating", + "Ġpor tions", + "ĠPrinc iples", + "ĠHop f", + "l ipid", + "ĠL U", + "ub ation", + "ĠAr tificial", + "Ġpr ison", + "an ing", + "ĠG N", + "ĠStrateg ies", + "ĠP as", + "T a", + "ĠProb ability", + "or um", + "Ġs keleton", + "Ġcomp artments", + "R ead", + "Ġco ach", + "Ġmod ality", + "ĠReg ister", + "Ġj e", + "Ġhe ights", + "in yl", + "Ġsub spaces", + "ti p", + "Ġá ¸", + "ĠG I", + "Ch ar", + "ro genic", + "ret t", + "eu tics", + "Ġadhes ive", + "ĠP ier", + "Le ft", + "id ental", + "NA c", + "Ġconjug ation", + "or ov", + "id ge", + "im aging", + "ĠT W", + "Ġpres ident", + "ĠO ste", + "ass emb", + "Ġinter net", + "Ġde als", + "ĠG AP", + "Ġform ulate", + "ĠUp date", + "ĠRNA i", + "cl ero", + "Ġpermut ations", + "Ġisotop es", + "op ic", + "ĠQ U", + "rom es", + "ĠPol icy", + "ĠC reek", + "ĠWind ows", + "Ġm erge", + "Ġacc ident", + "Ġsuper position", + "Ġdeb ate", + "Ġdocument ation", + "Ġeigen vectors", + "s or", + "ĠPh oto", + "Ġdepos it", + "Ġgerm ination", + "Ġsub graph", + "ĠRec ords", + "Ġchem ically", + "ĠPredic ting", + "ĠK y", + "se lective", + "yn man", + "dis pers", + "Ġlum bar", + "Ġmus ical", + "in ates", + "Ġinher ited", + "j u", + "Ġtrac er", + "Ġend ing", + "Ġeng aged", + "hand ed", + "Ġproduc er", + "Ġent angled", + "ĠD elta", + "Ġpiec ewise", + "NA ME", + "st op", + "Ġmut ated", + "Ġre cess", + "Ġimmun o", + "c ancer", + "ĠAk t", + "it ers", + "ĠB MP", + "Ġcompan ion", + "Ġcommun icate", + "Ġh ollow", + "Ġp ad", + "Ġs ph", + "om od", + "Ġpar ton", + "Ġspontaneous ly", + "e ared", + "Ġrot ations", + "Ġcosm ology", + "Ġmore over", + "pr inc", + "Ġevery where", + "b rane", + "l ational", + "em e", + "Ġbeh ave", + "um en", + "ost on", + "ov es", + "Ġg ar", + "Ġad renal", + "ĠEstim ating", + "N b", + "Ġech ocardi", + "Ġemphas ized", + "Ġeng ines", + "Ġbrack ets", + "Ġlead ers", + "Ġdistinc tive", + "ĠL ymph", + "Ġex ert", + "Ġinnov ative", + "c oupling", + "ĠSign ific", + "she et", + "ĠC over", + "ĠC CD", + "ĠF all", + "stim ulated", + "Ġsuper oxide", + "Ġpollut ants", + "Ġby tes", + "ĠL ipid", + "Ġtra fficking", + "Ġlead ership", + "inform atics", + "Ġbiod iversity", + "ad or", + "Ġinter conn", + "Ġharmon ics", + "Ġseaw ater", + "ĠIll umina", + "necess ary", + "ĠAnt on", + "Ġprocess ors", + "typ ename", + "D et", + "pro ton", + "Ġsubt raction", + "Ġshif ting", + "Ġcust omers", + "K e", + "ĠO B", + "aton in", + "at ellite", + "ĠS US", + "ĠCol on", + "ĠTim es", + "T V", + "ĠM ink", + "ĠIntegr ation", + "Ġprof ound", + "IT C", + "Ġg ras", + "ĠNA SA", + "ĠAC K", + "radi ol", + "ĠM ale", + "ĠWork ing", + "tic ity", + "ilibri a", + "bound ary", + "ĠR I", + "ĠAl i", + "car di", + "ĠF GF", + "b ranes", + "Ġbe et", + "Ġmiss ed", + "S ource", + "ĠB ot", + "ie ve", + "Ġis other", + "ne ys", + "n l", + "or tion", + "Ġcool ed", + "M V", + "Ġo mit", + "Ġver bal", + "aret te", + "Ġconf erence", + "Ġtransform er", + "Ġre jected", + "Ġprogress ively", + "ĠTur key", + "Ġath letes", + "Ġan atomy", + "E Q", + "Ġdeterior ation", + "ĠDi etary", + "Ġcor n", + "Ġcaps ule", + "Ġvibr ations", + "Ġoccup ational", + "Ġex osomes", + "Ġre written", + "Ġlign in", + "Ġbi opsies", + "ĠAdvers arial", + "Ġm ercury", + "Ġpl atinum", + "Ġirre levant", + "Ġker atin", + "ĠE mission", + "Ġeukary otic", + "Ġinte g", + "Ġkn ot", + "Ġser a", + "Ġcav ities", + "ĠMed i", + "Ind eed", + "E u", + "Ġâ Ł", + "Ġsc enes", + "Ġlap aroscopic", + "Ġsen ior", + "ĠD istance", + "pred ic", + "Ġear liest", + "Ġor g", + "ĠTh or", + "b ury", + "obl asts", + "Ġp umping", + "target ed", + "Ġra p", + "ĠP il", + "Î ł", + "Ġneu rom", + "o ft", + "ost at", + "Ġp adding", + "Ġconflic ts", + "Ġst ems", + "ĠSac charomyces", + "eng ine", + "Ġalk yl", + "Ġt ill", + "ĠQu ad", + "g ood", + "ro x", + "ĠF uzzy", + "Ġrob otic", + "ĠDen ote", + "ĠN IR", + "ĠY uk", + "paren cy", + "Ġle gs", + "yl van", + "Ġtight ly", + "Ġdec or", + "ĠV P", + "ĠM un", + "at oms", + "ĠSil ver", + "Ġneurode generative", + "Ġrespond ed", + "Ġrec ons", + "G EN", + "ĠF ine", + "f c", + "Ġpar agraph", + "Ġint ens", + "Ġalong side", + "Ġb rand", + "mon ium", + "Ġp m", + "Ġsimple x", + "ĠPrelim inary", + "Ġdown regulation", + "Ġx y", + "ĠM ak", + "op ter", + "ush ing", + "ĠB og", + "ox ia", + "================ ================", + "com mon", + "ĠA SS", + "ĠHD L", + "alam us", + "Ġirrig ation", + "N M", + "Ġf ading", + "Ġprev entive", + "Ġreli ably", + "ĠEthiop ia", + "ot hesis", + "iz ability", + "O B", + "Ġtrig lycer", + "Ġgest ational", + "Ġb esides", + "ĠI ii", + "ĠZ one", + "Ġcop ing", + "Ġminor ity", + "Ġdepri vation", + "Ġhex agonal", + "chlor ophenyl", + "ĠóµĦ ¨", + "Ġg yr", + "Ġview ing", + "New ton", + "ĠHierarch ical", + "o L", + "ec es", + "Ġconcl udes", + "Ġfung us", + "Ġpyl ori", + "Ġobstac les", + "th iazol", + "conjug ated", + "r ass", + "Ġl ose", + "Ġfor th", + "ĠAll en", + "opl ast", + "ĠProt ection", + "Ġintermitt ent", + "Ġluc iferase", + "ĠM K", + "Ġga ug", + "ĠF an", + "Ġmod al", + "ĠEx ercise", + "sc attering", + "ĠSh im", + "Ġexc retion", + "Ġat ypical", + "Ġmalign ancies", + "angl ades", + "ĠSpect roscopy", + "Ġaden osine", + "l if", + "Ġnucle ic", + "Ġincl ination", + "ĠC ass", + "Ġeth n", + "Ġex empl", + "ĠD y", + "Ġl ambda", + "Ġj ac", + "ĠP RE", + "Ġrail way", + "Ġf le", + "Ġreflec tions", + "Ġnano structures", + "ti sts", + "pr ints", + "ĠC AT", + "Ġs ib", + "Ġchlor o", + "Ġrecip ient", + "op tic", + "Ġcoun ty", + "Ġnucle otides", + "Ġz ircon", + "Ġhors es", + "ĠM ental", + "in line", + "ĠNor way", + "The y", + "Ġmusc ular", + "ace tic", + "ĠJ u", + "Ġcommun ic", + "f iles", + "f illed", + "H B", + "Ġreg ulations", + "Ġaccum ulate", + "ĠPan el", + "C y", + "ö l", + "ĠPak istan", + "Ġthor acic", + "ĠM PI", + "por tion", + "Ġinduc tive", + "ĠCong ress", + "Ġfibrobl ast", + "cl ust", + "Ġcent res", + "ad el", + "Ġsubstit utions", + "Ġtrunc ation", + "r ification", + "ok a", + "F low", + "ĠRed uc", + "polar ized", + "ib ular", + "P e", + "ĠA ML", + "ĠAg ency", + "Ġt ilt", + "ubl ished", + "Ġdep olar", + "Ġbel t", + "Ġoptim izer", + "EL L", + "ĠHand book", + "ĠVirgin ia", + "s ense", + "ĠD ur", + "Ġpiezo electric", + "Ġaward ed", + "ail ing", + "P os", + "p ref", + "ĠSum mer", + "ed o", + "ĠI de", + "ĠB SA", + "Ġmon omers", + "Ġco agulation", + "Ġg am", + "Ġhom es", + "Ġhead s", + "adm ium", + "ĠO C", + "Ġoccup ancy", + "ĠEm pirical", + "ĠI i", + "Ġch ir", + "Ġdegener acy", + "Ġflow ers", + "Ġsuperconduc tivity", + "Ġin versely", + "op tical", + "w ere", + "ĠAs ymptotic", + "S ec", + "tit le", + "pos al", + "ĠPro gn", + "Ġpos es", + "ĠB orn", + "Ġcontinu ation", + "Ġcul tivated", + "enti ment", + "Ġman aging", + "Ġthromb osis", + "a ug", + "CN T", + "ure a", + "Ġsp ind", + "ĠWhere as", + "ĠPers on", + "Ġb ipartite", + "Ġres cal", + "Ġmark ets", + "ph an", + "per ties", + "Ġferm ionic", + "Ġmunic ip", + "Ġachie vable", + "t ab", + "Å į", + "ĠRel ation", + "T otal", + "x ia", + "Ġintellig ent", + "ĠU T", + "ĠD al", + "Ġmedic inal", + "Ġinadequ ate", + "i ently", + "ers en", + "Ġpre condition", + "Ġmethod ological", + "Ġcan opy", + "Ġbacter ium", + "col umn", + "C al", + "ĠDi ego", + "ĠS ak", + "ĠComprehens ive", + "Ġanti tumor", + "Ġflow er", + "ĠK han", + "Ġmet adata", + "Ġphot ore", + "ogen icity", + "Ġle ague", + "ol ating", + "Ġprom ise", + "ĠP ere", + "Ġper mits", + "Ġthread s", + "ĠD Cs", + "ĠCh am", + "raz ol", + "B ank", + "Ġwithdraw al", + "Ġapp end", + "ot helial", + "ĠMeas ures", + "Ġguid eline", + "Ġmitig ate", + "adj oint", + "Ġbrack et", + "P ad", + "M ills", + "Bu ffer", + "Ġc ass", + "h oc", + "manif olds", + "her ry", + "Ġfacilit ated", + "E vent", + "Ġ È", + "ĠC ruz", + "ĠB rand", + "Ġnecess ity", + "burg h", + "Ġme V", + "Ġc AMP", + "O ff", + "se lected", + "Ġeng age", + "Ġredund ancy", + "Ġnanocom posites", + "s olution", + "ons et", + "ĠEx posure", + "Ġrepe titive", + "à ł", + "ĠR AD", + "ĠTur k", + "Ġcorne al", + "Ġexplo iting", + "Ġob structive", + "gram ming", + "ĠM ED", + "Ġmat hem", + "Ġconduc tive", + "Ġphotos ynthesis", + "E instein", + "ĠP eng", + "M W", + "ĠSch midt", + "Ġrepe tition", + "identif ied", + "Ġinj ured", + "Ġdef ective", + "ĠP el", + "Ġcul tivation", + "Ġfirst ly", + "Ġanalyz er", + "Ġstain less", + "Ġjo ining", + "ĠOxid ative", + "Ġph age", + "Ġexp endit", + "Ġhom ogeneity", + "ip le", + "ov ic", + "Ġcross ed", + "ĠTr ust", + "ĠF ract", + "rophys iological", + "Ġbas ically", + "Ġco ales", + "Ġgra vit", + "ful ness", + "c ano", + "Ġcol itis", + "Ġcha os", + "carb ons", + "O nce", + "ĠTow ard", + "or f", + "top ic", + "ĠPl ay", + "ĠCor respond", + "ĠS leep", + "ticular ly", + "c umin", + "v dots", + "ĠR he", + "Ġult raf", + "Ġtimes cale", + "ĠDet ails", + "ang les", + "Ġsur rogate", + "ĠFlu id", + "c z", + "Ġinitial ization", + "ĠTel escope", + "r ases", + "ĠSt ock", + "ĠC ond", + "Ġimmun odeficiency", + "B el", + "os er", + "sh own", + "Ġk cal", + "Equ ation", + "prot ective", + "Ġcall ing", + "Ġanticip ated", + "Ġambig uity", + "ĠN ode", + "ĠG D", + "Ġin let", + "Ġbre ad", + "Ġexceed ed", + "Ġimmun ization", + "Ġpro hib", + "y tic", + "Ġbo ys", + "t u", + "Ġto wer", + "L ike", + "ĠAn omal", + "â Į", + "ĠSh ow", + "Ġim aged", + "Ġequ il", + "Ġrend ering", + "ob ility", + "Ġge ological", + "f riend", + "ö r", + "carbox amide", + "ovol ta", + "C urrent", + "ĠS ti", + "ĠM U", + "Ġval ued", + "Ġpo ison", + "Ġprac tically", + "Ġrequ ested", + "C ode", + "Ġbr ings", + "Ġdim ethyl", + "h yp", + "ce mic", + "V ol", + "qu anti", + "Ġex ha", + "Ġrespons ibility", + "ĠCont rolled", + "Ġf ur", + "Ġres emb", + "ĠK aw", + "Ġev oked", + "Ġuter ine", + "Ð »", + "Ġan onymous", + "ĠChall enges", + "Ġanch or", + "ĠAb d", + "D er", + "Ġtherm ally", + "ĠC AP", + "obl ot", + "ĠF ire", + "Ġdiagnos tics", + "Ġexec ute", + "al is", + "ron i", + "ĠHar ris", + "ĠGon z", + "Ġv ig", + "ĠProf essor", + "Ġinvent ory", + "int ensity", + "ĠNSC LC", + "Ġinterf ere", + "ysacchar ides", + "Ġreg ener", + "ĠAut hors", + "Ġtransl ate", + "ĠT ests", + "ĠL ove", + "ĠInd uced", + "enn is", + "ĠG EN", + "Ġolig onucle", + "Ġmet er", + "s atisf", + "hes ion", + "Ġtrans porters", + "B IT", + "ĠCon c", + "Ġgl auc", + "sc ores", + "Ġmerg er", + "G H", + "Ġst oichi", + "ĠX ia", + "eff ects", + "ĠExpl oring", + "dor ff", + "Ġcardinal ity", + "ĠK az", + "f alse", + "ĠH SP", + "Ġuns upervised", + "ingu ish", + "isc her", + "Ġrel ativity", + "on ormal", + "oot hed", + "ed ges", + "ĠI MP", + "Ġimp ulse", + "ĠColumb ia", + "Ġpartic ulate", + "ĠSupport ing", + "ĠSD SS", + "vol tage", + "ĠAma zon", + "Ġep oxy", + "C all", + "Big l", + "Ġme ets", + "Ġequ atorial", + "Ġneu ros", + "Ġper itoneal", + "des c", + "input s", + "Ġex terior", + "ac o", + "Ġme al", + "ĠDani el", + "Ġintu itive", + "Ġcoun s", + "dep ress", + "in is", + "ph ot", + "ĠA min", + "Ġreservoir s", + "ĠW hole", + "Ġca ud", + "Ġbos onic", + "Ġread ers", + "Ġcr im", + "Ġpathophys iology", + "arg o", + "the se", + "inc ome", + "Ġiss ued", + "Ġhepat ocytes", + "ĠC i", + "der iv", + "up ta", + "t uple", + "ĠCh an", + "Ġauthentic ation", + "yg d", + "Ġinf in", + "Ġaccel erate", + "ep tive", + "Ġhydro gel", + "ask a", + "ON E", + "Ġfed eral", + "ograph ics", + "Ġmu on", + "Ġsl ide", + "Ġellip tical", + "at ite", + "Ġc c", + "ET s", + "Ġclar ity", + "ocy cl", + "is al", + "rec tions", + "ay an", + "row eak", + "ĠS OC", + "od erm", + "t un", + "as m", + "ĠH ir", + "lik elihood", + "Ġad ul", + "t l", + "H igh", + "Ġal ters", + "plit ude", + "ĠRe lease", + "Ġharm ful", + "l ate", + "ound s", + "ĠFed eral", + "ĠEcon omic", + "Ġra bb", + "Ġaccommod ate", + "em ission", + "ĠB ah", + "c ox", + "ĠMod ulation", + "Ġconstruc tions", + "ign er", + "ĠUrb an", + "Ġw ake", + "Ġadvers ary", + "wik ipedia", + "Ġsu ite", + "w ick", + "exp ressed", + "ro d", + "K D", + "Ġcomput ers", + "ĠB anglades", + "Ġpers ist", + "Ġburn ing", + "Ġadministr ative", + "Ġpl ug", + "ĠRepresent ations", + "ĠSc attering", + "Ġendomet rial", + "Ġdescript ors", + "Ġcom mission", + "B ar", + "igh th", + "ĠMar sh", + "sam pling", + "Ġh ull", + "ic in", + "Pro b", + "Ġnur se", + "Ġsh am", + "ĠK err", + "Ġpref rontal", + "Ġfix ing", + "O K", + "Ġb old", + "Ġcor ollary", + "cf g", + "ĠOx ford", + "Ġbor on", + "R B", + "ĠC ab", + "Big r", + "ĠPred ict", + "Ġpec uliar", + "h idden", + "is a", + "id en", + "appro priate", + "or h", + "ellect ual", + "Ġseiz ures", + "ass er", + "til is", + "hand le", + "iax ial", + "s ym", + "Ġcarcin omas", + "se a", + "sp ired", + "Ġab rupt", + "t ests", + "Ġw elfare", + "ĠO il", + "ĠLo ad", + "FL AG", + "ut hal", + "Ġfac ing", + "Americ an", + "L AS", + "Ġir respective", + "Ġrout inely", + "w al", + "Ġsettle ment", + "ĠA qu", + "Ġelectron ics", + "Ġhand led", + "Ġbiological ly", + "sm ooth", + "ĠBel ongs", + "ti b", + "Ġtra v", + "p ressive", + "ourn als", + "Ð º", + "fil ename", + "Ġhel ical", + "Ġbacter i", + "Ġsat ellites", + "B H", + "ent ed", + "ĠFoot ball", + "Ġï£ ±", + "ĠH V", + "Ġtri p", + "ĠCK D", + "ran i", + "Ġclean ing", + "lim it", + "ĠT CP", + "Ġsc in", + "Ġsl udge", + "Ġsymbol ic", + "ĠSequ encing", + "ad al", + "ĠPhil ipp", + "IC S", + "Ġvag inal", + "Ġcommit ment", + "ĠA wards", + "tr ig", + "Ġgu itar", + "acet ate", + "Ġb et", + "Cl N", + "Ġagric ulture", + "Ġch ief", + "Ġem bol", + "bu ild", + "Ġtex ts", + "ĠCo oper", + "l ived", + "ĠDel ay", + "ĠM ode", + "y al", + "B N", + "Ġindex ed", + "ex pr", + "ER N", + "v ens", + "Ġpo inter", + "c v", + "ac on", + "t ance", + "ĠâĪ Ŀ", + "Ġlow ered", + "Ġmit otic", + "rh osis", + "ĠP age", + "ü r", + "im m", + "ĠThe rapeutic", + "Ġoste opor", + "Ġbil inear", + "ĠCath olic", + "ĠAltern ative", + "oxid ation", + "Ġiniti o", + "benz o", + "ĠA di", + "per son", + "per itoneal", + "ĉĉ Ġ", + "Ġatt raction", + "Ġdiarr hea", + "Ġre n", + "ĠI SO", + "im ir", + "Ġtermin ology", + "uk ey", + "Ġreson ator", + "Ġsubstit uting", + "Ġhar bor", + "pro vid", + "dec ay", + "ĠHD AC", + "ĠAnaly tical", + "Ġpost natal", + "Ġund es", + "Spec ific", + "d ichlor", + "AR I", + "t ot", + "Ġdig it", + "op ing", + "ĠZ inc", + "Ġle thal", + "Wh itney", + "F i", + "qu antum", + "ĠF ailure", + "Ġsol ves", + "ĠSp aces", + "ear man", + "Ġgo at", + "Ġsyn apses", + "Ġres uspended", + "Ġresid ent", + "Ġcomp ac", + "Ġcor tisol", + "Ġphot ometry", + "W P", + "se lect", + "Ġc ele", + "or ubicin", + "ĠMul tic", + "ĠJe an", + "Ġcl ip", + "Ġs a", + "oc o", + "ge ometric", + "Ġhel ic", + "Ġempir ically", + "Ġmicro fluid", + "id is", + "Ġaut ocor", + "W F", + "ĠRes pir", + "radi ation", + "Ġthem es", + "Ġt aste", + "ric ing", + "Ġexam inations", + "ĠSens ing", + "s ame", + "DE FAULT", + "Ġphyl ogeny", + "h ig", + "Ġplate lets", + "ĠHist or", + "ab a", + "Ġresid ential", + "Ġun bounded", + "and ing", + "hed ron", + "r ys", + "ĠC CR", + "Ġcon ce", + "Ġpar asitic", + "c b", + "ĠFe ynman", + "ĠKe pler", + "à ´", + "ĠG il", + "ĠMAT LAB", + "b en", + "sc ope", + "Ġdiscrim in", + "Ġjus tified", + "plas ma", + "ĠCho i", + "Ġro of", + "PC A", + "ĠT CR", + "Ġvox el", + "ĠW ard", + "Ġunc or", + "S tok", + "Ġsp ur", + "TR A", + "Ġdiagnos es", + "rophys ical", + "ategor ies", + "Ġove restim", + "Ġstream ing", + "ĠRec overy", + "Ġevery thing", + "LO W", + "G ener", + "Ġun biased", + "Ġvari ances", + "comp act", + "es pan", + "in j", + "Ġend oscopic", + "Ġide als", + "ĠR ice", + "ĠKa plan", + "Ġf ecal", + "fer red", + "ĠCy cle", + "Ġimplant ed", + "Ġw ine", + "P ET", + "Ġassign ments", + "Ġabs ol", + "X T", + "Ġswim ming", + "M N", + "ĠGe ometric", + "ĠHealth care", + "Ġpow ders", + "ĠG el", + "Ġdown ward", + "Ġexceed ing", + "ĠHE K", + "ly m", + "ĠB V", + "Ġvis co", + "i et", + "ĠCO X", + "ploy ment", + "ins ki", + "Ġout door", + "ĠLiter ature", + "ant ed", + "meth oxyphenyl", + "ĠMed ium", + "Ġd ia", + "ail and", + "vari ance", + "ĠEval uating", + "ox acin", + "Ġanti f", + "Ġpul p", + "Ġcorro bor", + "ĠO t", + "Ġrabb its", + "R u", + "Ġfunction als", + "â ĩ", + "Ġimm ersion", + "Ġcre atin", + "Ġq RT", + "Ġcondens ed", + "n r", + "ĠV A", + "h ad", + "Ġk ing", + "ob le", + "Ġexist ed", + "Ġthe sis", + "ubb ard", + "ap optotic", + "Ġflow ering", + "ĠAdap tation", + "ĠKal man", + "tr l", + "Ġm ent", + "ut ation", + "ĠCon v", + "Ġhist ories", + "Ġen anti", + "n ell", + "on ian", + "ĠF abric", + "Ġx x", + "Ġf ell", + "Ġcytos olic", + "Ġm ud", + "Ġsusp ensions", + "ĠMicro bial", + "meas ured", + "Ġdown load", + "Ġinv alid", + "Ġcapt uring", + "ĠH H", + "ĠG ray", + "ĠA Z", + "ĠN ash", + "vi ation", + "nai re", + "or tium", + "yn ch", + "amin ergic", + "Ġwa it", + "S chem", + "t race", + "ĠV ill", + "Ġpo ols", + "Ġhypox ic", + "x p", + "Ġsh aded", + "OR Y", + "t urn", + "inter acting", + "Ġdestroy ed", + "ak h", + "ĠCp G", + "dot ted", + "ĠTrans cript", + "plan ar", + "Ġpre clinical", + "ĠRe pro", + "ĠSur gery", + "Stok es", + "if def", + "Ġdiscrim inate", + "ĠG ross", + "Ġfl ags", + "i ety", + "umm y", + "Ġtransf ers", + "S G", + "ĠSc i", + "Ġhead er", + "ĠFund ing", + "Ġdet rim", + "Ġinst abilities", + "ĠPhyl ogenetic", + "ym ethyl", + "ĠAssess ing", + "RO C", + "els en", + "Equ al", + "Ġc as", + "Ġver tically", + "Ġvis ibility", + "ĠFT IR", + "sc rib", + "Ġbur sts", + "ĠDo ug", + "ĠFranc isco", + "ĠM SC", + "Ġpred is", + "establ ished", + "Ġfac ed", + "ĠW I", + "S l", + "Ġchar ts", + "orth y", + "izon tal", + "ial ysis", + "Ġtun able", + "Ġexplos ion", + "S w", + "T NF", + "Ġdiscontinu ous", + "ect ure", + "ci ences", + "mathbb m", + "lo ok", + "Ġt achy", + "Ġb row", + "obs erved", + "Ġana est", + "S al", + "q PCR", + "Ġse es", + "Ġspac ecraft", + "Ġsal es", + "ĠT rac", + "T em", + "iv est", + "ĠF c", + "ĠNew s", + "Ġharvest ing", + "ĠE G", + "p ad", + "Ġnanow ires", + "Ġpot ato", + "pl iers", + "on in", + "Ġw orm", + "s ue", + "ti e", + "Ġm asks", + "Ġth row", + "! !", + "be havi", + "Ġp ine", + "og y", + "TE ST", + "on to", + "Ġcreatin ine", + "ĠB oston", + "Ġch air", + "pl oys", + "ov en", + "Ġent rance", + "Ġc och", + "Ġdy es", + "T or", + "ĠPD E", + "unders et", + "atas ets", + "Ġt ernary", + "cho ose", + "f ive", + "chlor ide", + "on ium", + "Pro perty", + "Ġt u", + "Ġadequ ately", + "romy cin", + "Ġco oper", + "ï Ľľ", + "Ġpap ill", + "ĠStrept ococcus", + "ĠC Y", + "Ġgroup ing", + "Ġbi oc", + "ĠCardi ac", + "ĠBo ok", + "re ference", + "Ġconfirm ation", + "iver y", + "Ġwar ning", + "pret ation", + "Ġl ove", + "Ġoscill ators", + "s ed", + "ĠT X", + "il ent", + "ĠV as", + "Ġcl amp", + "Ġa head", + "ac s", + "Ġdeple ted", + "Ġmethod ologies", + "m ay", + "Ġc affe", + "Ġsequ entially", + "os acchar", + "Ġcompr ise", + "Ġc hel", + "Ġin acc", + "Ġtend on", + "S equ", + "ough t", + "ser ver", + "ĠPert urb", + "Ġter rain", + "cur ve", + "ĠAr gent", + "T ABLE", + "Ġimplicit ly", + "Ġenj oy", + "ĠS itter", + "Ġmic ron", + "ĠEv ans", + "ns ylvan", + "Ġlook ed", + "sp e", + "vol ving", + "ĠL STM", + "agne tism", + "ĠNot ch", + "ĠT al", + "ĠDEG s", + "lem an", + "Ġbo olean", + "Ġob ey", + "organ ization", + "se en", + "ĠEn c", + "sch ild", + "ĠOnt ario", + "Ele ment", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠ", + "m ouse", + "Ġpoly ethylene", + "Ġace tic", + "s ections", + "ur onal", + "ĠD ick", + "Ġk ill", + "Ġbroad ening", + "Ġfluor ide", + "Ġs aved", + "Ġde em", + "St ream", + "ac ed", + "ĠJ eff", + "Q A", + "Ġscal able", + "ĠF if", + "ĠMin i", + "Ġsuper gravity", + "Ġcoll oidal", + "L Y", + "O A", + "Ġper ic", + "Ġshort ly", + "Ġv ap", + "Ġspl its", + "m ove", + "Ġstim ulating", + "ĠBe ijing", + "Ġp yr", + "Ï Ń", + "Ġlex ical", + "âĢ ł", + "Å Ħ", + "itor ies", + "oler ance", + "Ġins ulator", + "ĠLe on", + "Ġpropag ate", + "ĠEle ments", + "y en", + "Mod ule", + "ĠWhe ther", + "Ġa ph", + "ĠLa ure", + "ĠMut ations", + "Ġhypert rophy", + "Ġocean ic", + "ograph ically", + "pati ents", + "ĠAngel es", + "Ġp he", + "Ġsqu ee", + "Ġcaro ten", + "f ine", + "Ġsk etch", + "Ġans atz", + "tit ution", + "ĠF us", + "ĠS ug", + "obacter ial", + "Ħ ĥ", + "Rel ated", + "Ġar tist", + "Ġac ryl", + "l ined", + "raf ted", + "ĠQ oS", + "ĠF eng", + "se arch", + "Ġnanot ube", + "ĠV M", + "ah l", + "Ġstr ide", + "ĠT ag", + "ĠL ar", + "Ġdes orption", + "d type", + "Ġb ug", + "Ġcareg ivers", + "ĠH un", + "ĠPrac tical", + "Ġob lig", + "re r", + "ĠK ang", + "ĠPro ducts", + "omet h", + "ĠHe La", + "Ġlabor atories", + "n atural", + "Ġf ul", + "Ġm old", + "ab ine", + "ĠS pring", + "Ġco bal", + "Ġhighlight ing", + "ĠPre f", + "cycl ic", + "ĠCONCLUS ION", + "ĠS ources", + "Ġap ex", + "par ser", + "ĠLog ic", + "Ġp ond", + "Ġto ld", + "ĠSh ap", + "perg illus", + "Ġsay ing", + "Ġmutagen esis", + "Ġmm Hg", + "ĠP AN", + "Ġsm okers", + "od ay", + "Ġhere in", + "CM V", + "ĠP W", + "Ġred shifts", + "ĠMin im", + "ym an", + "ull i", + "d ense", + "Ġarsen ic", + "ĠE MT", + "og aster", + "carboxyl ate", + "s ys", + "R o", + "an ch", + "ĠAl pha", + "ĠTechn ical", + "s v", + "Ġb ones", + "Ġaccept or", + "Ġnew born", + "pri vate", + "Ġnan or", + "ĠSw iss", + "a round", + "Ġsynt ax", + "ĠK ähler", + "Ġaer ial", + "ĠP ale", + "typ edef", + "names pace", + "Ġconfound ing", + "vi Äĩ", + "Ġret ard", + "Ġz eta", + "ĠT um", + "is ch", + "Ġsulf ide", + "ĠT ian", + "u y", + "Ġintu ition", + "Ġphosph olip", + "ĠS her", + "ric ts", + "-------------------------------- --------------------------------", + "ok ines", + "gluc ose", + "tol er", + "ifer ative", + "ĠFlu or", + "Ġencour age", + "Ġrespons ive", + "perturb ative", + "Ġs addle", + "l ers", + "nd ez", + "ĠZ ero", + "ĠDi et", + "Ġdeveloper s", + "S yn", + "Ġconf er", + "Ġorigin ate", + "rop ol", + "ha w", + "le tion", + "ms kip", + "Ġb er", + "Ġpe at", + "v ially", + "Ġgran ules", + "ĠÌ ĥ", + "Ġpl uripot", + "Ġassim ilation", + "Ġdenom inator", + "abil ization", + "ĠEpidem iology", + "MI N", + "eed s", + "ĠV R", + "E val", + "st ore", + "ĠBas eline", + "Ġc u", + "ĠSpect ra", + "Ġfraction ation", + "Ġplac ing", + "Ġbur ied", + "el eration", + "Ġalkal i", + "ĠI U", + "C alc", + "we ak", + "Ġmorphism s", + "Ġlig ase", + "Ġf s", + "Ġutil izes", + "Com put", + "à ¢", + "Ġs tig", + "rel ative", + "Ġimm ature", + "ĠF rac", + "ap i", + "Ġout patient", + "Ġachieve ment", + "Ġstack ing", + "Ġnod ules", + "IN D", + "ĠGP a", + "Ġpercol ation", + "m space", + "Ġbrain s", + "uff le", + "ent ropy", + "L ab", + "Ġstabil ize", + "ĠRic ci", + "ĠAn timicrobial", + "pers onal", + "Ġfarm s", + "ĠP in", + "Ġpor cine", + "Ġoccasion ally", + "w he", + "Ġundergo es", + "Ġregim ens", + "Ġbl ade", + "Ġlinear ized", + "Ġdec on", + "Ġpack ed", + "Ġf ishes", + "ĠM end", + "Ġapproach ing", + "Ġball s", + "Ġpro inflammatory", + "imer ic", + "ĠDirect or", + "Ġsol iton", + "Ġm osaic", + "vi et", + "Me an", + "ĠP ad", + "Ġtri plicate", + "sup ported", + "Ġcar t", + "<< <<", + "Ġrem ission", + "ase ous", + "astic ity", + "ĠM ik", + "ĠStrateg y", + "ram er", + "ĠPol ish", + "Ġent hal", + "Ġheter ozygous", + "ĠGra vity", + "A x", + "Ġorganization al", + "Ġmo vie", + "Ġexpl oratory", + "WL ED", + "Ġmo iety", + "dec re", + "ĠS till", + "Ġ ¡", + "Ġgreen house", + "Ġsupercon ductors", + "en um", + "el in", + "Ġoffer ing", + "st ad", + "ĠT rich", + "Ġre pl", + "Ġrecycl ing", + "ph or", + "Ġin elastic", + "ock ey", + "ĠâĢ Ļ", + "Ġsequ el", + "E B", + "ĠCh ile", + "Ġfibr illation", + "Ġdis ulfide", + "ob tained", + "ub in", + "Ĥ ¬", + "Ġfacilit ating", + "Ġhop ping", + "Ġmedi ator", + "Ġhyd ration", + "Ġspars ity", + "Ġs ati", + "Ġis othermal", + "Ġreturn ing", + "Ġtravel ing", + "Ġin g", + "Ġst ent", + "Ġcapac itor", + "Ġcomprom ise", + "ĠS ud", + "ĠV ision", + "Ġtop ologies", + "opol ysaccharide", + "ĠPro file", + "ĠR ing", + "Ġdiscrep ancies", + "D is", + "AR D", + "cc cc", + "Ġdirect ory", + "ĠCM OS", + "ow ed", + "ill o", + "ĠIns ights", + "ĠT ib", + "Ġab and", + "aro se", + "Or der", + "Ġ ¬", + "Ġintrac ranial", + "Ġintermedi ates", + "Ġhab its", + "Ġcar p", + "pro perty", + "IM AGE", + "ĠU k", + "Ġhydroph ilic", + "W id", + "Ġab iotic", + "Ġobser vers", + "Ġch or", + "ĠCons ervation", + "ĠEnh ance", + "ĠAutom ated", + "ĠGl ut", + "ir atory", + "Ġsp aw", + "ĠE fficiency", + "v ast", + "in iti", + "Ġop tional", + "ĠScal ing", + "if old", + "Ġmt DNA", + "ĠRec onstruction", + "Ġcount able", + "ĠGr ass", + "D en", + "ĠCh ain", + "en zyme", + "Ġwave forms", + "Ġpancre as", + "ĠDet ailed", + "cm d", + "Ġâİ ľ", + "Ġmagnet o", + "ĠFP GA", + "Ġabsol utely", + "Ġstim ulates", + "ach us", + "ĠAr n", + "m essage", + "ocomp atibility", + "H Cl", + "ĠF ish", + "Ġphenomen ological", + "Ġsaliv ary", + "ond o", + "Ġno tions", + "f ur", + "U CT", + "Ġw ww", + "ab et", + "ĠS ulf", + "F il", + "dom inated", + "ars er", + "Ġpack ages", + "Ġsplic e", + "F lo", + "NO WLED", + "x a", + "ĠY uan", + "Ġacet one", + "ĠVit amin", + "ĠÎ ŀ", + "Ġobs c", + "Ġcha per", + "Ġm ort", + "M AN", + "Ġsub tilis", + "Ġoptim ality", + "Ġcontinu ing", + "Ġdu plication", + "Ġmultip lying", + "Ġimmun ological", + "Ġcir rhosis", + "h ospital", + "ĠProb abilistic", + "Ġdele tions", + "Ġca ution", + "Ġow ner", + "ox orubicin", + "Ġla unch", + "Ġc ure", + "th us", + "ĠHerm itian", + "can onical", + "Ġimmun ore", + "form in", + "Ġbroad band", + "part um", + "op he", + "ĠB eta", + "ĠB I", + "Ġïĺ º", + "Ġj umps", + "Ġparad ox", + "um ped", + "Ġdoc tors", + "Ġhospital ized", + "Ġwas h", + "prec ision", + "Ġr uled", + "Ġdu plicate", + "ant e", + "Ġneuro trans", + "Ġïĥ §", + "Ġthem e", + "T aking", + "ĠPl ants", + "f ollowing", + "Ġage ing", + "Ġcon gestion", + "os arcoma", + "Ġrepos itory", + "ĠH ess", + "ĠC atalytic", + "ĠD V", + "IN K", + "pri v", + "ĠAn a", + "ĠS LE", + "ĠTh ailand", + "í ķ", + "Ġd uty", + "loc ations", + "ot er", + "Ġlys ine", + "Ġind ist", + "Ġagon ists", + "A ck", + "Ġminim ally", + "Ġet ching", + "ugg ing", + "c uda", + "nd ef", + "Ġref erring", + "Ġlys ates", + "Ġseroton in", + "crib ing", + "ĠInter face", + "d V", + "Ġd urations", + "Ġphot od", + "Ġd ating", + "Ġirre versible", + "os idase", + "ĠF ROM", + "with in", + "SN R", + "Ġarr hyth", + "ĠR atio", + "ĠTh in", + "cent ered", + "Ġsh ocks", + "ĠV ers", + "Ġnotice able", + "Ġf oci", + "Ġorth onormal", + "Ġâİ Ł", + "Ġlum inescence", + "ĠSUS Y", + "in ternal", + "ĠT our", + "Ġab brevi", + "ĠM AL", + "ver tex", + "Ġem ploys", + "IN S", + "Ġimmunohist ochemistry", + "Ġhepar in", + "Ġidi opathic", + "Ġimmobil ized", + "is he", + "ph th", + "th in", + "ĠSt orage", + "Ġperovsk ite", + "Pro t", + "ĠDepend ing", + "Ġbl ends", + "Ġpred ator", + "Ġdisplay ing", + "Ġvesic le", + "ĠK ra", + "Ġl ane", + "Ġmulti layer", + "Ġhom ozygous", + "cos h", + "Ġsuperf icial", + "Ġ il", + "ĠK R", + "ĠBr un", + "ĠE W", + "op a", + "ĠCart esian", + "ĠCy toplas", + "ĠPen nsylvan", + "b ands", + "Ġangi otensin", + "ĠLat tice", + "G I", + "j ee", + "Ġenlarg ed", + "en ius", + "ĠI a", + "ou x", + "Ġg ent", + "Ġcarbon yl", + "c hers", + "Ġhypot he", + "Ġmic rosp", + "Ġaff ective", + "Ġax ons", + "e i", + "ypt oph", + "ĠJ on", + "que ue", + "ĠG auge", + "men opausal", + "ĠD as", + "ĠEss ential", + "ĠF ault", + "ĠB il", + "Ġtest osterone", + "Ġcham bers", + "d ione", + "Ġelic ited", + "IG N", + "Ġantioxid ants", + "pop ulations", + "Ġov ary", + "Ġâ ĸ", + "Ġabst raction", + "Ġhydro carbons", + "Ġrec tal", + "Ġtrigger ing", + "Ġthorough ly", + "R un", + "acter ia", + "in formation", + "ĠB ed", + "Ġqu enc", + "Ġund ers", + "ĠScot land", + "Ġre volution", + "Ġpit uitary", + "Ġanthrop ogenic", + "f ocus", + "Ġmet han", + "Ġinf low", + "Ġdef lection", + "ĠC ape", + "Ġmulti dimensional", + "Ġarri ved", + "ĠS par", + "d v", + "Ġc ows", + "ĠB h", + "Ġj k", + "tol yl", + "Ġeigen states", + "Ġpre processing", + "ĠR ain", + "ä ¸", + "in z", + "Ġm n", + "RE E", + "atric k", + "D ev", + "Ġfulf illed", + "Ġar tic", + "Ġreal izations", + "ĠComp onent", + "ĠW S", + "Ġinf o", + "pr inted", + "at osis", + "c ache", + "an ov", + "ĠT g", + "cont ent", + "j unc", + "ĠCD K", + "Ġbeh aves", + "ĠK id", + "diff erence", + "ĠP s", + "ĠU g", + "Ġstruct urally", + "ereb ral", + "ĠSur ve", + "he al", + "on ite", + "Ġdele ted", + "iti m", + "St ar", + "ĠSpe ech", + "ĠA str", + "g radient", + "Ġf ellow", + "Ġsy ring", + "N B", + "ĠN B", + "Ġcre ep", + "Ġlog ging", + "Ġint en", + "scal ar", + "ĠAtmosp heric", + "Ġl upus", + "Ġiden tically", + "process ed", + "sign al", + "ĠClo str", + "anc ers", + "Ġd b", + "Ġsubs ystem", + "s itu", + "Ġferro electric", + "Ġï Ľľ", + "Ġo re", + "ĠR b", + "ĠMicro soft", + "ĠC och", + "ĠAc tin", + "Ġnerv es", + "Ġexper tise", + "o tive", + "ĠPoinc aré", + "ĠR ig", + "Ġpsych osocial", + "Ġprogen itors", + "ĠM yr", + "ĠH ug", + "Ġbi ogenesis", + "Ġincorpor ates", + "Ġnever theless", + "ĠDec l", + "obs erv", + "Ġmulti plier", + "Ġrespond ing", + "ho ff", + "Ġimp acted", + "Ġsynd romes", + "k el", + "ĠS ynt", + "ĠCon cer", + "ĠAmeric ans", + "Ġspac ed", + "um ption", + "ĠThom pson", + "ĠJacob ian", + "T ra", + "e volution", + "Ġdid n", + "Ġpercenti le", + "Ġl id", + "equ ivalent", + "Ġantic o", + "Ġmulti ply", + "Ġpen icillin", + "Ġrespons iveness", + "Ġrun off", + "al anine", + "squ ares", + "ĠIns ulin", + "re le", + "ĠL if", + "ĠMink owski", + "Ġbl end", + "ĠP and", + "Ġtw elve", + "Ġhybrid s", + "Ġb ass", + "inter action", + "ĠBanglades h", + "Ġop ens", + "ĠAr ts", + "Ġconc ave", + "Ġped est", + "Ġf ist", + "ĠAd ults", + "open ia", + "EN CE", + "ĠF usion", + "Ġmicro c", + "ĠSur gical", + "yl ate", + "Ġpack aging", + "OC K", + "Q C", + "T ri", + "sc an", + "Ġregard s", + "Ġdiscrim inant", + "Ġind ustries", + "ic us", + "ĠWalk er", + "Ġpe ers", + "sy nt", + "Ġhor se", + "Ġflow ing", + "ur red", + "ĠCR P", + "ĠCare er", + "iffiffiffiff iffiffiffiff", + "ĠM SE", + "han a", + "ĠMor tality", + "Ġtumorigen esis", + "ĠIs lam", + "Ġazim uthal", + "w en", + "Ġs ys", + "az in", + "ne ighbor", + "Con fig", + "the y", + "Ġs orption", + "Ġsp anned", + "Ġview point", + "M OD", + "Ġth rust", + "up lex", + "Ġhist ograms", + "Ġprogram med", + "Ġeth ics", + "ect able", + "represent ation", + "um ns", + "Ġstre et", + "ĠSob olev", + "Ġexc ision", + "ĠR ud", + "qui res", + "Ġown ed", + "Ġthous and", + "Ġantagon ists", + "U ST", + "Ġdrastic ally", + "ĠóµĦ ©", + "ĠD or", + "ĠM OS", + "p n", + "ĠDec re", + "D ep", + "Ġs intering", + "Ġpur ple", + "et hanol", + "Ġhydro carbon", + "ĠF O", + "left rightarrow", + "Ġimmun ofluorescence", + "ĠO M", + "Ġmat urity", + "Ġearthqu akes", + "Ġax on", + "Ġprob ed", + "OR D", + "ĠAD P", + "s g", + "om ere", + "Ġtrans cribed", + "M ar", + "ĠU til", + "ĠI A", + "Ġcomp iled", + "Ġsuper vision", + "ĠX en", + "ĠJ ur", + "com par", + "Ġhypert ensive", + "il ized", + "ra e", + "Con clusion", + "'' '", + "Do uble", + "ĠF as", + "Ġins ectic", + "ĠPre m", + "P ri", + "ĠCa o", + "ĠQuestion naire", + "Ġg athered", + "G W", + "ĠN V", + "ĠLact obacillus", + "Ġcycl in", + "Ġre ject", + "Ġsk ull", + "Ġa w", + "ĠC old", + "Ġmes ons", + "b d", + "Ġdetrim ental", + "ap ore", + "now led", + "ĠCX CL", + "Ġspik es", + "Ġt ent", + "ĠL ength", + "Ġdo or", + "Ġfl our", + "ustr ation", + "He alth", + "Ġtrans parency", + "Ġdisrup ted", + "H y", + "over l", + "ĠReinfor cement", + "cept ors", + "ĠK os", + "ret roviral", + "ĠIN T", + "ĠS or", + "Ġadop ting", + "Ġend oplasmic", + "Ġsu it", + "Ġopi oid", + "Ġintegr in", + "aw ay", + "Ġtail ored", + "ĠS oc", + "Ġqu ies", + "Ġhus band", + "Ġ umb", + "ĠC ai", + "ĠAs pergillus", + "ĠGa N", + "Ġdistingu ishing", + "Ġextrap olation", + "Ġc age", + "Ġscav enging", + "K F", + "T ree", + "ĠConfl ict", + "UN C", + "Ġmang anese", + "d ays", + "à Ł", + "ĠL ive", + "s d", + "ract or", + "Ġl ute", + "Ġdis similar", + "Ġ ib", + "ĠV eg", + "Ġoccur rences", + "Ġbin omial", + "Schem e", + "Ġt ape", + "ĠC ant", + "Ġelect rosp", + "C d", + "m ade", + "Ġse vent", + "sh ared", + "Ġaccess ion", + "or p", + "D ATA", + "le ted", + "V ari", + "Ġro se", + "tag ged", + "ĠA th", + "Ġed dy", + "est one", + "Ġes ters", + "Ġtyp ing", + "ĠStud ents", + "y i", + "ores istance", + "ino is", + "Ġgluc ocortic", + "i osis", + "Ġcor onal", + "Ġshe ath", + "ĠT rack", + "Ġequ ilibria", + "amm ing", + "Ġp ione", + "Ġsc iences", + "Ġsuppress ing", + "Ġdec o", + "if ndef", + "H is", + "Ġpel let", + "L inear", + "orb ent", + "Ġflat ten", + "Ġst raw", + "Ġal beit", + "ĠPredic tive", + "Ġg aze", + "Ġhydro ly", + "ut her", + "od ers", + "Ġfl ap", + "Ġsimplic ial", + "S ystem", + "Ġst ressed", + "Ġimmun oglobulin", + "il ia", + "Ġconsum ing", + "Ġà ©", + "gal act", + "Ġadul thood", + "Ġvor ticity", + "ycl ic", + "ovolta ic", + "ivest ock", + "Ġbed s", + "ĠPl anning", + "Ġparameter ized", + "Ġg host", + "maxim um", + "Ġsuper im", + "Ġphysic ochemical", + "g p", + "ong ue", + "Ġprim ordial", + "x ff", + "ins ula", + "M c", + "Ġminim izes", + "ĠGra vitational", + "os oma", + "ign ificant", + "Ġelucid ated", + "Ġsub surface", + "sign ificant", + "Ġrel atives", + "fer roni", + "trans f", + "Ġtail s", + "b eck", + "om agnetic", + "Ġun necessary", + "Ġmon omial", + "del ay", + "Ġst a", + "ĠS uz", + "Ġalter ing", + "LO G", + "ĠL ac", + "Ġr anks", + "h w", + "ĠN ep", + "Ġneu ropath", + "ĠComp e", + "G r", + "P ati", + "red uce", + "ĠMalays ia", + "cer al", + "Ġmicro bes", + "Ġlens ing", + "ĠCalc ium", + "ĠDeterm in", + "ĠCost a", + "Ġke eps", + "print ing", + "ĉĉĉĉ ĉĉ", + "ch in", + "ex posed", + "Ġperiod ically", + "Ġrend er", + "ĠCardi ovascular", + "ent in", + "Ġbio availability", + "Ġinterpret ations", + "ĠC U", + "Ġneg oti", + "Ġan tim", + "Ġdeem ed", + "Ġa e", + "Ġhal os", + "ĠMich igan", + "Ġoste oarthritis", + "di ag", + "ĠB eng", + "Ġmet agen", + "Ġparameter ization", + "di agn", + "ĠMat ching", + "Ġcatal ysis", + "ut s", + "Ġdissem ination", + "Ġout let", + "ĠMo on", + "ĠG ST", + "sp here", + "Ġresearc her", + "ambig uation", + "Ġra ises", + "Ġflavon oids", + "ĠMultiv ariate", + "Ġac cl", + "W I", + "Ġn u", + "Ġerg odic", + "un ique", + "atin ib", + "Ġresol utions", + "Ġhous es", + "D EC", + "ig hed", + "Ġsix th", + "Ġpolitic ian", + "ap ache", + "Ġsol ute", + "Ġaug ment", + "st ress", + "H IV", + "ĠS ets", + "Ġtrans istors", + "qu bit", + "am ines", + "Ġfarm ers", + "Ġn t", + "ĠLag range", + "Ġveget ables", + "Ġpre t", + "ĠS ynthetic", + "Ġcon es", + "Ġmedic ines", + "Ġgen omics", + "Ġexperi encing", + "ag land", + "Ġgen ital", + "ĠObserv atory", + "ĠS kin", + "ĠR osen", + "ĠBrit ain", + "gen ome", + "ĠEnt ropy", + "Ġr ac", + "G o", + "Ġw alks", + "cript or", + "ĠB aker", + "ok er", + "Ġprop ensity", + "Ġpopular ity", + "restric ted", + "ĠB ert", + "b efore", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠ", + "aut o", + "R ank", + "ĠR CT", + "Ġp ocket", + "ob ut", + "Ġbenz ene", + "ĠCN T", + "yptoph an", + "all is", + "ĠRes ources", + "ĠBer lin", + "Ġsch olar", + "gl ob", + "ĠSp eed", + "ĠX iao", + "big gl", + "AN CE", + "ĠPr ime", + "Ph ys", + "id ia", + "Ġmon oc", + "ĠCommun ications", + "ĠPrec ision", + "ĠPa uli", + "Ġinvestig ators", + "ĠLi ang", + "Ġmeteor ological", + "m og", + "re ens", + "ub ric", + "Ġrearrang ement", + "or ta", + "E lect", + "ĠT ukey", + "ĠM is", + "Ġepid erm", + "ĠACK NOWLED", + "w art", + "Ġexcit on", + "Ġassoci ative", + "st yrene", + "Ġl osing", + "ĠO d", + "p rep", + "ess ation", + "Ġattrib utable", + "ĠNa vier", + "an z", + "Ġcorrect ness", + "o ints", + "ĠR ather", + "Ġassemb lies", + "Ġbrid ges", + "OS S", + "M ET", + "Ġper m", + "Ġauthor ities", + "Ġiod ine", + "sh ire", + "inter val", + "epti d", + "Ġpot ency", + "Ġrenew able", + "v ard", + "Ġsur jective", + "Ġsubsequ ence", + "ĠE Vs", + "it ching", + "Ġgen otyping", + "ĠAcc urate", + "iop hene", + "G ly", + "pl ified", + "ĠDist inct", + "AC H", + "Ġspe akers", + "hol m", + "Ġpro s", + "ĠDev ice", + "m c", + "ĠD ense", + "ĠV a", + "r ison", + "Ġac yl", + "ĠPrinc ipal", + "ĠV iral", + "Ġcos ine", + "ĠRes idual", + "Ġeff lux", + "ĠSub jects", + "Ġrect angle", + "work ers", + "Ġrot ated", + "Ġb omb", + "ĠRes olution", + "ne ar", + "Ġ ®", + "Ġestabl ishes", + "am ed", + "Ġcompet ence", + "G lu", + "ĠD end", + "ĠH sp", + "ens ation", + "ĠLe ad", + "Ġlog ger", + "sin h", + "Ġint ellectual", + "form er", + "C e", + "Ġmon ocyte", + "ho res", + "Ġdiast olic", + "Ġlif espan", + "ĠSil va", + "ar um", + "Ġtrans ducer", + "Ġout going", + "ent ation", + "Ġabsorb ing", + "it age", + "Ġsynt hesize", + "Ġfe eling", + "as ian", + "Ġcer amics", + "i ph", + "Ġnon local", + "P art", + "Ġimmers ed", + "station ary", + "lect ing", + "Ġweld ing", + "Ġres embles", + "ĠK at", + "m aster", + "Ġinters ect", + "ĠO lig", + "ĠTre nds", + "ag h", + "ĠN av", + "ĠT u", + "Ġep ist", + "Ġclin ics", + "Ġrepresent atives", + "Ġgrate ful", + "G PIO", + "H H", + "Ġun ambig", + "t uning", + "Ġnew sp", + "coh ol", + "################ ################", + "%%%% %%%%", + "represent ed", + "oc ic", + "ĠF uk", + "ĠS und", + "has one", + "M ode", + "ol one", + "ĠS b", + "Th ree", + "L ink", + "ce phal", + "ĠK ap", + "Ġelim inating", + "Ġmelan ogaster", + "â Ł", + "ĠB MD", + "IS E", + "ĠBat tle", + "Ġshrink age", + "ĠSe ven", + "ĠGl ass", + "rom agn", + "Ġk l", + "ĠOb viously", + "pres erving", + "ĠPl atform", + "ĠÌ ĩ", + "om avirus", + "ĠE ight", + "Ġall erg", + "ĠNanopar ticles", + "ary l", + "Ġpri ors", + "pat tern", + "Ġlinear ity", + "Ġtr uly", + "Pro cess", + "Ġdesc ending", + "ĠVictor ia", + "c ond", + "ĠI CP", + "ores cent", + "Ġauthor ity", + "Ġm ock", + "igm oid", + "Ġcomorbid ities", + "sim ple", + "Ġbl o", + "ĠComput e", + "Ġgest ation", + "achus etts", + "Ġph antom", + "ĠEd ward", + "ĠF BS", + "fact ors", + "ĠEstim ates", + "c lear", + "W B", + "pro ducts", + "num py", + "b rief", + "Ġsh op", + "ĠPol i", + "ĠRespir atory", + "Ġsurprising ly", + "Ġnanocom posite", + "divid ual", + "Ġholog raphic", + "ygd ala", + "ro plasty", + "ot actic", + "ĠPennsylvan ia", + "ĠSc ore", + "Ob j", + "Ġst ories", + "Ġmaxim izing", + "Ġgel atin", + "r ites", + "ĠT au", + "Ġtryp sin", + "Ġ ith", + "Ġf aint", + "Ġprim ing", + "ew orthy", + "ĠIn verse", + "Ġkn ots", + "sh arp", + "Ġtrain s", + "Ġcred it", + "ĠBel ow", + "pix el", + "Ġspind le", + "ĠP ast", + "Ġenum erate", + "ol ateral", + "Ġatt ending", + "Ġquanti zed", + "Ġhapl otypes", + "enc l", + "Ġw aven", + "Ġfurther more", + "Ġchalleng ed", + "Ġmanufact ured", + "ipher al", + "Ġinfin ites", + "ĠR and", + "Ġst aging", + "ag an", + "Ġper ox", + "tr ifluor", + "ĠMc K", + "ĠF OX", + "ĠL ank", + "ĠLu o", + "ĠAn th", + "ibri o", + "y el", + "ĠJ i", + "ĠI O", + "ĠB ridge", + "ĠR ow", + "Ġcompens ated", + "ats u", + "Ġhypothe tical", + "Ġtermin als", + "Ġcobal t", + "m ers", + "ĠM ang", + "N I", + "ĠR ac", + "AL S", + "f en", + "ĠU b", + "Ġpred ation", + "c adherin", + "ĠSh anghai", + "Ġtri es", + "Ġsp ort", + "acryl ate", + "ĠAlgebra ic", + "ain ts", + "Ex pr", + "Ġand rogen", + "Ġw edge", + "dis p", + "Ġstir red", + "ĠA le", + "Ġc ock", + "F our", + "Ġsc anner", + "Ġplas mon", + "ĠG ender", + "ĠRec ord", + "ĠInj ury", + "obl astic", + "ĠFlu orescence", + "Ġanti depress", + "Ġdefin itive", + "Ġrep ression", + "ordin ates", + "Ġangi ography", + "ĠHel ical", + "Ġcancell ation", + "re lease", + "Ġrel ational", + "ĠAnd re", + "mo lecule", + "Ġshap ing", + "ĠDen mark", + "ĠAL S", + "ĠN W", + "over rightarrow", + "Ġcomb at", + "box es", + "sub ject", + "Ġnanos cale", + "Ġcan ine", + "Ġs aving", + "Ġstrateg ic", + "St at", + "ĠD ub", + "Ġper mitted", + "ĠTw itter", + "â Ķ", + "Ġmem ories", + "ĠBus iness", + "ad ays", + "Ġpool ing", + "ĠClust ers", + "im ide", + "oun ters", + "frac tion", + "ĠCl iff", + "C am", + "E ven", + "K Y", + "k it", + "ibr ated", + "Ġaccompan ying", + "an us", + "Ġbu oy", + "Ġprolifer ative", + "Ġpro c", + "Ġstabil izing", + "ĠNam ely", + "pos p", + "so on", + "Ġaberr ant", + "Ġinter stellar", + "Over all", + "ĠG n", + "ĠFeed back", + "Ġo racle", + "Ġpre natal", + "com mun", + "Ġoutbreak s", + "Ġfertil ization", + "ĠM AG", + "Ġsing er", + "ĠMic rowave", + "ĠPar liament", + "cast ing", + "Gen eral", + "al gorithm", + "Ġph rase", + "Ġa vian", + "ĠP LA", + "Ġhard ly", + "approxim ately", + "AR CH", + "Ġtrans c", + "Ġdec omp", + "cont in", + "ĠMil ky", + "Ġher pes", + "R ange", + "O FF", + "prising ly", + "l x", + "ĠAB A", + "Ġsh ore", + "Ġderiv ing", + "Ġpel lets", + "nowled g", + "I tem", + "strand ed", + "bu ilt", + "Gl c", + "qu ist", + "ĠSub strate", + "Ġtra ditionally", + "ĠM ount", + "ival ence", + "ax ation", + "Ġloc ate", + "Ġg un", + "Ġvoc abulary", + "ĠPol ym", + "Ġec t", + "Ġm ult", + "Ġsediment ary", + "Ġautocor relation", + "ĠS ympt", + "Ġterr itory", + "Ġexcit atory", + "Ġv ote", + "Ġhe red", + "ace a", + "ĠF ocus", + "am pling", + "ff ee", + "Ġprim es", + "ĠM aking", + "ir s", + "MP s", + "Ġl itter", + "amet hasone", + "Ġk J", + "Ġsecret ory", + "Ġcost ly", + "Ġpartners hip", + "ĠBacter ia", + "Ġperoxid ation", + "st roke", + "ĠS av", + "ĠB W", + "Ġconn ects", + "Ġam ine", + "r il", + "Ġbat tle", + "ĠN otes", + "ĠPro vid", + "ĠInstit utional", + "Ġpro pri", + "f an", + "Ġp un", + "rom b", + "v ities", + "ĠC AM", + "ĠI sh", + "ĠH N", + "ĠRec omb", + "sc he", + "Ġsyn chrotron", + "ri k", + "syn aptic", + "ĠGeorg ia", + "? ?", + "C Y", + "Ġcorrespond ed", + "kin ase", + "ĠI TS", + "Ġpropos als", + "Ġbi oge", + "ĠE SR", + "ĠW en", + "ĠJ a", + "ĠSe vere", + "ĠAd en", + "ĠC CL", + "Ġse at", + "ĠK re", + "Ġhelp ing", + "Ġn ets", + "ĠL ep", + "hed ra", + "opo ulos", + "ĠB ak", + "ans as", + "Ġref rig", + "Ġubiquit ous", + "Ġmat ters", + "Ġsil icate", + "ĠLast ly", + "ĠThe ories", + "Ġag arose", + "big gr", + "trans ition", + "ĠDec omposition", + "b romo", + "Ġstake holders", + "ĠE E", + "On ly", + "ĠKen ya", + "Ġarg on", + "ĠIdentif ying", + "Ġtourn ament", + "cl ock", + "ĠCF U", + "ĠBehavi oral", + "Ġp od", + "Ġtaxon omy", + "ĠPro duct", + "ĠAl ong", + "Ġfamil ial", + "Ġdescript or", + "v ated", + "ĠVari ables", + "t p", + "Ġgood s", + "ĠA ST", + "ĠAn is", + "Ġspin or", + "at tention", + "Ġbas ket", + "Str uct", + "Ġimmunohist ochemical", + "eng ers", + "C AT", + "Ġtang ential", + "C ap", + "ĠP air", + "Ġvisco elastic", + "ĠAd s", + "Ġglycos ylation", + "Ġd ur", + "ĠMin imum", + "Ġrig idity", + "st ats", + "till ation", + "ĠDisc rim", + "ĠLeg end", + "Pre vious", + "fil m", + "Ġalumin ium", + "M icro", + "in ia", + "eg el", + "ĠSub cellular", + "Ġbottlen eck", + "Ġsy ll", + "ic le", + "Ġshe af", + "che ll", + "ex ample", + "ĠSe lected", + "Ġpred ators", + "Ġre per", + "Ġstr ugg", + "ĠM aria", + "ly l", + "L F", + "Ġexerc ises", + "ob ium", + "IL ITY", + "cor rected", + "Ġbenchmark s", + "ĠT ol", + "Ġinter cept", + "ĠCalc ulation", + "ĠIndones ia", + "Ġgli oblastoma", + "K M", + "ĠSup plemental", + "Ġciti zens", + "ad ren", + "Ġmultim odal", + "Ġmosquito es", + "iv a", + "ĠFind ings", + "ĠP ub", + "ĠMac roph", + "Ack nowledg", + "Ġbas ins", + "ex act", + "Ġgra des", + "Ġf ir", + "ig a", + "ĠPol ynomial", + "ĠLong itudinal", + "Ġsemicon ductors", + "T op", + "ip tera", + "Ġlack s", + "ro graph", + "Ġselec ts", + "Ġswe et", + "Ġb ac", + "Ġdown loaded", + "ap onic", + "ij k", + "ot onic", + "normal ized", + "ĠVari ability", + "di vision", + "ĠSu pers", + "il ab", + "H uman", + "Ġlept in", + "Ġosm otic", + "Ġh ur", + "ĠSing apore", + "ĠO PT", + "ĠSo viet", + "lit axel", + "ret aceous", + "ĠOn c", + "ĠI X", + "ul as", + "u ent", + "Ġlymph oid", + "T c", + "Ġrational e", + "L ayer", + "os ities", + "Ġdes ire", + "ĠAnn ual", + "ub a", + "ĠCompound s", + "Ġantif ungal", + "Ġcation ic", + "it ems", + "acter ium", + "amil ies", + "Ġelong ated", + "ĠMass achusetts", + "ĠIr ish", + "ass o", + "az o", + "ĠBur k", + "rob enius", + "Ġis instance", + "b ion", + "Ġgre edy", + "Ġnicot ine", + "Ġretrie ve", + "Ġsym pathetic", + "que e", + "Ġfol i", + "Ġsp utter", + "Ġgra ding", + "determ ined", + "Ġab norm", + "Ġman agers", + "Ġtop ical", + "Ġimm ig", + "ĠD NN", + "g tr", + "Ġdet ections", + "ĠOb esity", + "s uc", + "ĠSc he", + "Ġtr unk", + "Ġto ugh", + "ĠB N", + "Ġr u", + "ox if", + "Ġaim ing", + "ĠExt racellular", + "Ġhapl otype", + "D u", + "ĠD ing", + "ĠD ol", + "Ġhum id", + "b rom", + "Ġoff line", + "Comb ining", + "Ġpuls ar", + "Ġpar i", + "part ate", + "im ated", + "Ġwaters hed", + "acryl amide", + "ex ec", + "ĠCom posite", + "Ġdispers ive", + "Ġt ons", + "rom etry", + "ĠJ ud", + "az a", + "Ġchick ens", + "reg ister", + "n z", + "U til", + "ĠV es", + "e V", + "ĠR ule", + "sub stituted", + "Con v", + "qu ery", + "M ac", + "ĠT ar", + "im plies", + "ĠR ates", + "Ġr ins", + "Ġtimes cales", + "ĠCz ech", + "S uch", + "res timate", + "ĠM b", + "ĠFu j", + "ĠI MD", + "c it", + "Ġra ising", + ".... ....", + "h ome", + "as ted", + "Ġoc ta", + "Ġc admium", + "Ġps ori", + "role um", + "ĠSt ellar", + "ĠKin ase", + "ĠG ard", + "ie u", + "ĠMo S", + "M G", + "ĠG SH", + "Ġhaz ards", + "Ġn ice", + "he ating", + "Ġreproduc ible", + "gen esis", + "ĠIg M", + "Ġbe at", + "onucle ase", + "entral ized", + "ĠL é", + "Ġd ol", + "Ġdeep ly", + "rac tive", + "Ġgl ial", + "i ella", + "Ġinitial ized", + "ĠMethod ology", + "Ġbent hic", + "om i", + "ĠAl ter", + "Or dered", + "ĠL IN", + "Ġun ilateral", + "Ġcortic oster", + "L EN", + "Ġdil ute", + "Ġmetall oprotein", + "ab eth", + "amp ion", + "Ġmor al", + "ĠSi C", + "Ġquadr ature", + "Ġsediment ation", + "et e", + "ĠF rag", + "Ġpeak ed", + "Ġmitig ation", + "Ġsol di", + "Ġdoub ly", + "Ġellip so", + "Ġlnc RNAs", + "Ġâİ ¢", + "ĠS ame", + "ĠS ustain", + "ĠCap acity", + "Ġs omat", + "Ġtrans istor", + "Ġassay ed", + "ĠN ur", + "to ols", + "S ing", + "Ġlig ament", + "ate ver", + "Ġper ce", + "hen ce", + "U X", + "s ent", + "EG G", + "th ird", + "end ers", + "the oretic", + "Ġre wards", + "ut o", + "Ġinstall ation", + "ĠKine tic", + "ĠIn nov", + "ĠSol ving", + "ĠS ymmetry", + "Ġr amp", + "Ġneu ropathy", + "omer ization", + "Ġcat ech", + "P red", + "ĠB oh", + "EM ENT", + "Ġarm y", + "ĠYuk awa", + "Ġalign ments", + "ĠDepend ence", + "Ġen v", + "e an", + "s r", + "Ġinterp reting", + "eloc ity", + "Ġpsych ology", + "Ġbiofil ms", + "Ġeccentric ity", + "l ot", + "analy tic", + "Ġperiod icity", + "n ings", + "ĠK ent", + "fl ag", + "Ġm p", + "ĠN ich", + "hi re", + "Ġfl are", + "Ġcit rate", + "Ġp aste", + "Ġdele te", + "zym es", + "orient ation", + "ĠH Y", + "Ġcomm ands", + "Ġstri ke", + "s ymbol", + "ĠM ind", + "Ġoptim isation", + "Ġosteopor osis", + "ĠInf lammation", + "ĠIntellig ence", + "e h", + "ut um", + "Ġv ec", + "ell ation", + "ĠBl och", + "ĠMajor ana", + "en or", + "ĠN gu", + "Ġde uter", + "oped ia", + "Ġ utter", + "Ġrib osome", + "Ġact ors", + "elect ronic", + "é e", + "Ġfeat uring", + "ag le", + "Ġper in", + "ĠC ivil", + "Ġpred efined", + "l ag", + "ĠJ AK", + "j amin", + "in dividual", + "on c", + "Ġf ishing", + "di tive", + "N orm", + "ĠSc anning", + "van ishing", + "Ġc essation", + "ĠH ole", + "rib utes", + "I E", + "ĠM pc", + "weg ian", + "M a", + "Ġrevis ited", + "ĠPl us", + "abil ized", + "Ġsc anned", + "ĠEx change", + "Ġbrom ide", + "L ife", + "ot roph", + "AD S", + "âĭ ħ", + "Ġoper ative", + "ĠB ERT", + "Ġpl ume", + "Ġpo orer", + "Ġtro ut", + "Ġmicrotub ule", + "Ġphosph atidyl", + "radi us", + "ĠMus cle", + "Ġcarcin ogenesis", + "Ġsee ing", + "ucle in", + "f ollow", + "Ġsup plements", + "ol ars", + "spec ially", + "Ġcomple ting", + "Ġna ïve", + "ĠÏ ©", + "clero tic", + "D isc", + "ĠF estival", + "Ġcl ick", + "cl usive", + "Ġcatal ogue", + "Ġap ps", + "ĠS ED", + "Ġstack ed", + "Ġtun e", + "ĠDM EM", + "Ġaeros ols", + "Ġg ear", + "ant ine", + "ĠSt one", + "Ġpos itives", + "tri ang", + "prob ability", + "Ġdec oupling", + "ĠÍ ĵ", + "ĠV in", + "Ġsurv ived", + "Ġre plicated", + "ut rient", + "Ġtemper ate", + "Ġens embles", + "Ġmultic enter", + "Ġg aseous", + "ide a", + "class ification", + "ĠOut come", + "cl onal", + "Ġdiscontinu ity", + "Ġadvantage ous", + "Ġdist ricts", + "ĠI BM", + "inguish able", + "Ġcar s", + "c ult", + "en riched", + "arg in", + "nov ae", + "stead y", + "Ġbu y", + "pir ation", + "Ġpartition ed", + "Ġin ability", + "p q", + "Ġb ull", + "od end", + "Ġass istant", + "Ġlum en", + "Ġconver ting", + "P Y", + "z ol", + "ut ors", + "ĠNLR P", + "app ly", + "ĠBon ferroni", + "L s", + "Ġt ips", + "ĠL N", + "rol ase", + "Ġadv is", + "ĠMet ast", + "Ġsaliv a", + "Ġin habit", + "Ġr im", + "de bug", + "An y", + "Ġfor b", + "Ġvers atile", + "ĠComp act", + "v oc", + "ĠI so", + "ĠJ us", + "b odies", + "AR M", + "ĠGW AS", + "he tized", + "Ġmicrofluid ic", + "Ġacet onitrile", + "Ġin hom", + "Ġparen ch", + "Ġins ensitive", + "Ġag ency", + "po or", + "ĠAn gi", + "Ġappro ached", + "Ġem ulsion", + "Ġvol untary", + "ut t", + "ĠRec urrent", + "ric ulum", + "à ª", + "Ġt all", + "ĠDep th", + "Ġf f", + "ĠInc idence", + "Ġmanifest ation", + "Ġcomprom ised", + "i aceae", + "ĠM IT", + "otrans fer", + "ĠW D", + "m ov", + "ĠMan ual", + "M edi", + "Ġinterfer ing", + "ĠJacob i", + "K T", + "Ġs arcoma", + "Ġkid neys", + "Ġod or", + "Ġt i", + "yd ay", + "alth ough", + "vis ible", + "Ġd engue", + "ĠC AL", + "str at", + "ĠVari ations", + "in ib", + "comp onents", + "ĠT ob", + "ĠAnti oxidant", + "Í Ķ", + "Ġk iller", + "Ġsubt racted", + "ĠE vents", + "Ġim plements", + "ĠG AN", + "Ġprophyl axis", + "Ġno zz", + "Ġsm oothed", + "Ġdecay ing", + "ĠIniti ally", + "Ġuncom mon", + "Ġconduc tor", + "ĠW OR", + "av ity", + "ĠX ie", + "ĠAc et", + "Ġin e", + "ĠBe am", + "opol ymer", + "ĠX ML", + "ĠW ide", + "Ñ ĥ", + "Ġe jection", + "B MI", + "t c", + "ue z", + "Ġcereb ellar", + "Ġcatch ment", + "cox on", + "ĠSh annon", + "Ġcentral ity", + "Ġsaf ely", + "pro be", + "ĠLabor atories", + "Ġn c", + "Ġsp her", + "Ġprob ing", + "ĠLe v", + "Ġa f", + "ĠM ig", + "ĠV ascular", + "Ġprogram mes", + "Ġcontamin ants", + "sequ ent", + "Ġbond ed", + "integr ation", + "b os", + "ĠF ew", + "ĠIll inois", + "S he", + "W C", + "ĠG PIO", + "o C", + "ĠM aternal", + "erc etin", + "ĠMass ive", + "Ġen orm", + "img ur", + "Ġb idirectional", + "ĠG raphene", + "ins ky", + "ĠObs erve", + "Ġst ops", + "b io", + "ĠL ines", + "ĠG ill", + "Ġeigen vector", + "Sp ace", + "ĠM ining", + "Ġmel atonin", + "ĠS ET", + "onse qu", + "os cale", + "ĠR aw", + "Ġreview ers", + "Ġnan ofib", + "t aking", + "amm ad", + "Ġrecurs ion", + "us al", + "Ġpos itron", + "ĠN IH", + "ĠIN TER", + "ĠDoc ument", + "Ġconstant ly", + "Ġunderg one", + "Ġelect roweak", + "Ġiter atively", + "fol io", + "Ġsub family", + "Ġâİ ¥", + "P age", + "f erm", + "av ir", + "Ġag encies", + "Ġpol ys", + "ĠSqu are", + "ym m", + "Ġhydro gels", + "al most", + "ar ter", + "Ġan kle", + "Ġr ises", + "Ġmed ull", + "g ated", + "Ġmon onuclear", + "Ġdiscuss ing", + "Ġprof essor", + "trans formed", + "Ġcol ours", + "rag g", + "emic on", + "Ġsymmet rical", + "Ġplac ental", + "Ġl i", + "Ġstud io", + "sequ ences", + "Ġt am", + "ĠL ap", + "ĠCriter ia", + "Ġhapp ened", + "Ġantifer romagnetic", + "ĠHaus dorff", + "ĠCONCLUS IONS", + "H ER", + "V R", + "ĠK or", + "ĠA PO", + "Ġprot ecting", + "ĠS OL", + "ĠB uck", + "ph ia", + "ĠMul tim", + "on ine", + "uls ions", + "Ġg p", + "benz amide", + "ĠNAD PH", + "ĠOh io", + "ĠM EG", + "CO VID", + "Ġdisplac ed", + "ĠAb b", + "Ġbran ched", + "ĠN avy", + "ĠN rf", + "ĠO DE", + "ach i", + "ĠTrans ient", + "Ġcircum ference", + "Ġbe es", + "ir ation", + "Ġfac ulty", + "IG HT", + "ĠMetabol ism", + "M K", + "ĠIn equ", + "ĠQual itative", + "P BS", + "ter minus", + "k ary", + "o vian", + "ĠT Hz", + "ĠRel iability", + "f uran", + "Ġcor ners", + "Ġattack er", + "Ġmar riage", + "oprec ipitation", + "ĠC ry", + "ĠâĬ Ļ", + "Ġevol ves", + "Ġb an", + "Ġdi urnal", + "oun ce", + "Ġover w", + "ĠH off", + "Ġextr insic", + "am ps", + "UL AR", + "op her", + "Ġlight ing", + "Ġarchitect ural", + "hes ive", + "Ġsav ings", + "Ġglauc oma", + "oz oa", + "ĠO ption", + "cont roll", + "eck er", + "Ġoste ocl", + "Ġglyc ine", + "anal yses", + "ĠAl d", + "ĠS yd", + "ĠC x", + "Ġscal ars", + "Ġknow ing", + "Ġrem ember", + "ĠEmb ry", + "T EM", + "ĠB ran", + "F ORM", + "Ġsurv iving", + "Ġglob ular", + "Ġincl usive", + "sc hed", + "UT ION", + "Ġquadrup ole", + "ĠH ubbard", + "Ġax onal", + "ĠCos mic", + "Ġsl ots", + "ĠProced ure", + "ag in", + "ĠLo op", + "are r", + "Ġbut ter", + "Ġhist opathological", + "f usion", + "AN OVA", + "Ġclos ing", + "ĠL ord", + "ĠB is", + "ĠR AM", + "ID E", + "Ġj ournals", + "Ġmon keys", + "Ġatten uates", + "Ġsegment ed", + "T OF", + "o tional", + "pol ymer", + "ĠSha h", + "A kt", + "W r", + "l ov", + "Ġpolym orphic", + "Ġarrang ements", + "U F", + "l on", + "Ġdep ressed", + "NA T", + "ĠOper ation", + "Î ¹", + "ĠR an", + "â IJ", + "Ġthere after", + "Ġmyel oma", + "j or", + "à ¥", + "ĠW inter", + "pt osis", + "D ir", + "ver ty", + "ĠF inn", + "Ġorth olog", + "Ġmonoton ically", + "Ġtect onic", + "ĠG BM", + "ĠA O", + "Ġgener ative", + "C learly", + "Ġt ile", + "ĠR NN", + "Ġground s", + "Ga As", + "Ġbe e", + "ĠB oy", + "ĠTranscription al", + "ur in", + "ot om", + "Ġsinus oidal", + "ĠA y", + "ĠCl inic", + "ut orial", + "ĠAD C", + "ER IAL", + "c ation", + "ĠAD HD", + "cycl ohex", + "ĠHaw ai", + "ast om", + "Ġmorph ologies", + "Ġrod ents", + "Ġscal ability", + "R OS", + "a emia", + "Ġdecom pose", + "Ġpiv otal", + "Ġdiffus ivity", + "Ġco valent", + "ĠK D", + "ataly st", + "Ġold est", + "Ġsu itability", + "Ġw ants", + "if ts", + "ĠDist ributions", + "ĠQue en", + "l ich", + "Ġpar se", + "ĠM HD", + "Ġrec re", + "Ġhydrox ide", + "e um", + "Ġle v", + "Ġrefer ral", + "plan es", + "ĠEg ypt", + "Ġl enti", + "Ġtrans actions", + "Ġexp ense", + "Ġcy sts", + "Ġabs cess", + "Ġmicro RNAs", + "eff ectiveness", + "ĠDifferenti ation", + "Ġcer tif", + "c ience", + "ĠRE L", + "Ġread out", + "ĠQu asi", + "Ġround ed", + "ot ti", + "e fficients", + "Ġsynchron ized", + "Ġsil ico", + "Ġfore casts", + "Ġd μ", + "Ġex otic", + "ĠO CT", + "x b", + "Ġas ynchronous", + "ne z", + "chi at", + "Ġha emat", + "Ġfulf ill", + "ĠM ix", + "ib li", + "f m", + "Ġj ava", + "sol uble", + "Ġincomp ressible", + "âĨ ij", + "CD M", + "Ġdil ation", + "L YP", + "as hes", + "ĠS ports", + "Ġfund ament", + "ĠSa udi", + "Ġen roll", + "ĠNa OH", + "Ġcrust al", + "ĠInstr uments", + "Ġïģ ¡", + "Res ult", + "Ġpref erential", + "Ġsug ars", + "Ġdim ers", + "ĠEmerg ing", + "è re", + "diab etic", + "Ġstrengthen ing", + "ep i", + "ĠM eg", + "ĠY our", + "ĠSet ting", + "le z", + "ĠB ou", + "Ġhist ology", + "Ġol ive", + "ĠDis orders", + "Ġdistor ted", + "Ġcompet e", + "c ens", + "ĠA e", + "ĠG G", + "Ġquantif ying", + "Ġa ur", + "ĠW right", + "Ġsuperconduc tor", + "ed s", + "st alk", + "con cent", + "ĠLim ited", + "Ġst yles", + "des ign", + "ĠE llip", + "PL A", + "mog orov", + "ĠR idge", + "Ġrandom ization", + "a ft", + "ic ially", + "ĠBi otechnology", + "Ġseiz ure", + "K I", + "AV E", + "re ceptor", + "Ġgram mar", + "Ġcr ime", + "n ection", + "in ces", + "ĠCom pton", + "Ġventric le", + "Ġred istribution", + "yn aptic", + "Par ameter", + "N ormal", + "P ack", + "erm ann", + "ul ants", + "de generate", + "ĠNewton ian", + "Ġancest ral", + "ph rag", + "Ġimp ression", + "Ġnormal ize", + "Ġambig uous", + "Ġingredi ent", + "ĠCl aim", + "Ġcle aved", + "ĠAppro aches", + "ĠS PECT", + "cs v", + "ĠReve als", + "ĠW aves", + "Ġdwar fs", + "ĠProg ress", + "Ġa orta", + "Ġn ig", + "ĠAd ams", + "ĠM üller", + "ĠY ellow", + "ĠC ord", + "ĠPh ill", + "ĠF ormal", + "bes gue", + "ter min", + "r n", + "b n", + "k ine", + "r it", + "q i", + "ĠRout e", + "en ol", + "ĠA SC", + "ĠP u", + "m ill", + "um er", + "Ġsuper nova", + "i ative", + "diff erenti", + "Ġto lu", + "op us", + "R M", + "Ġpo verty", + "ĠX X", + "ĠïĤ ¶", + "ult ry", + "Op tim", + "Ġgl acial", + "ĠDis pers", + "Ġdifferenti ating", + "á ndez", + "pro ject", + "ĠEl iz", + "scal ing", + "ĠT oll", + "Ġnon empty", + "Ġpredic ate", + "Ġgyr us", + "min ute", + "â ĸ", + "ĠH ind", + "ĠL iving", + "V S", + "pri or", + "ĠVer tical", + "ark s", + "ĠS FR", + "ĠViet nam", + "comp are", + ">> >", + "Ġb anks", + "Ġse ptic", + "ĠB if", + "ĠE PS", + "ĠInt el", + "ĠDis order", + "P N", + "ĠN ord", + "tiv eness", + "Ġdr illing", + "ĠSub ject", + "enari o", + "Ġr ms", + "ph ones", + "h ang", + "ĠTechn ique", + "Ġcl ot", + "Ġinters ections", + "Ġan ions", + "ab ove", + "Ġcl ause", + "Ġgen u", + "oz o", + "rh iz", + "Ġlob es", + "ĠB ian", + "Ġexer ted", + "ure th", + "rom a", + "ĠCh arge", + "ĠSyn chron", + "Ġcont ing", + "othe rapeutic", + "gtr sim", + "ĠReson ance", + "ĠF al", + "und le", + "Ġdrop out", + "ĠPers pective", + "OL OG", + "at ches", + "ĠSequ ences", + "Cons idering", + "Ġprosp ects", + "Ġal iqu", + "Ġstr ata", + "Ġanalog s", + "Ġencour aged", + "ĠP ulmonary", + "Ġch im", + "ĠC FT", + "un ar", + "iz z", + "end ocrine", + "ĠC RE", + "ĠSt roke", + "âĩ Ĵ", + "up uncture", + "trans lational", + "ĠGr iff", + "ĠS ter", + "erg ed", + "ph rine", + "Ġl ivestock", + "ĠH ash", + "Ġdos ing", + "Ġplas mas", + "ĠCompar isons", + "Ġencour aging", + "Ġcompar atively", + "Ġcharacter isation", + "Ġasc ending", + "ĠF ixed", + "Ġveget able", + "es pecially", + "ĠL ange", + "ĠC oding", + "Ġverteb rate", + "F W", + "ĠOR F", + "ĠT ub", + "le e", + "Ġtim ely", + "E p", + "ĠâĪĴ âĪŀ", + "Ġlip osomes", + "Ġextrem al", + "ropol itan", + "ĠC ay", + "ĠB iod", + "o ulli", + "D ri", + "ĠR ats", + "Ġcent roid", + "osp in", + "rosp inal", + "Ġsol itons", + "por tive", + "ĠMc G", + "B bb", + "Ġpar affin", + "lec tively", + "Ġmetast able", + "Ġdissip ative", + "Ġassembl ages", + "Ġcol onic", + "Ġs ized", + "Ġcr yp", + "process or", + "ç ão", + "Ġacknowled ged", + "ĠUncertain ty", + "ĠInd ustrial", + "Ġunc ont", + "Ġref ere", + "ĠN itrogen", + "Ġlif ting", + "Ġfor get", + "Ġfeel ings", + "Ġdig its", + "Ġstrat ig", + "yp es", + "Ġcomposition al", + "Ġsupernat ants", + "Ġconflic ting", + "Ġdisadvant age", + "adel phia", + "P d", + "ĠC oupling", + "Ġexpendit ure", + "ik i", + "des cribed", + "ĠRN ase", + "ĠCon vex", + "ĠB ax", + "ung sten", + "Ġbo iling", + "Ġbas ement", + "ocardi al", + "hist ory", + "int on", + "trim ethyl", + "Ġgraft ing", + "ĠHard y", + "ĠFem ale", + "ĠF ollow", + "ĠE ST", + "tis tic", + "O pen", + "Ġref lux", + "ele ments", + "Ġpol ysaccharide", + "dim s", + "ac ency", + "Ġbi ore", + "cap ac", + "Ġoverex pressed", + "e ither", + "Ġl aid", + "Ġinc ision", + "Ġass ets", + "inf lammation", + "Ġreconstruc tions", + "Ġglomer ular", + "Ġcon vey", + "ĠCX CR", + "or o", + "Ġclass ifying", + "Ġcop e", + "Ġp d", + "lin ic", + "Ġch ord", + "ĠAg ing", + "Ġpal m", + "Ġpermit tivity", + "ĠRever se", + "Ġoff shore", + "Ġdoub t", + "im oto", + "ĠCol omb", + "Ġrod ent", + "ĠElect rochemical", + "ĠImpro vement", + "ines cent", + "ĠTr iton", + "Ġtransf usion", + "Ġlocom otion", + "Ġdanger ous", + "Ġwe ighed", + "ĠH SV", + "t echn", + "ĠDi agram", + "Ġpari etal", + "s ix", + "Ġtit les", + "yl on", + "Ġheur istics", + "Ġj aponic", + "Ġtransl ations", + "Ġtit ers", + "Ġw orms", + "ĠD PP", + "Ġcytos keleton", + "Medi ated", + "ari ance", + "the l", + "à ħ", + "ĠInf lammatory", + "Ġoscill ating", + "Ġavoid s", + "Def ine", + "ĠOlymp ics", + "og el", + "Ġhe me", + "Ġmic rop", + "Ġthreat s", + "Q CD", + "X RD", + "ĠC oupled", + "Ġl m", + "ĠHel ic", + "Ġdischarg ed", + "Ġro oted", + "Ġallevi ate", + "Ġcaus ality", + "ĠC row", + "ĠM ack", + "ĠAir port", + "Ġchem okine", + "Ġl l", + "ĠN ar", + "omy ces", + "eth oxyphenyl", + "ĠD aily", + "ĠFin land", + "Ġh orn", + "ĠO rient", + "Ġion ized", + "ĠY ears", + "Ġquas ipar", + "Ġper cutaneous", + "Ph ase", + "Ġfore ground", + "ĠA NAL", + "Ġincre ments", + "st an", + "Ġspec ulate", + "T X", + "Ġp ile", + "Ġd ic", + "ip y", + "wind ow", + "neut ral", + "ĠAtl as", + "ĠM TT", + "ĠN y", + "ĠV III", + "ĠFil ms", + "sing ular", + "rem ove", + "L ength", + "ĠRec e", + "wa it", + "Ġpurch ase", + "ĠWik ipedia", + "ĠL ars", + "Ġsynt actic", + "Ġactu ator", + "ĠAK T", + "ĠB ry", + "ĠRes ult", + "ĠVari ational", + "Ġjudg ment", + "J ECT", + "xim ab", + "Ġtrac ed", + "Ġcardiomy opathy", + "W N", + "ĠRod rig", + "b t", + "Ġb id", + "ac le", + "am ura", + "Ġep ic", + "Ġp uzz", + "ĠS ox", + "Ġinflu x", + "ÃŃ n", + "ulos keletal", + "D im", + "ĠS CC", + "ĠR AS", + "m r", + "U I", + "Ġj un", + "ĠSp earman", + "Ġfair ness", + "et z", + "ĠP PI", + "in ance", + "en ko", + "Ġgal act", + "ö m", + "Ġex ceptions", + "ĠC retaceous", + "M Y", + "Res p", + "Ġp ep", + "ĠOr d", + "ST E", + "Ġhel icity", + "Ġoffic er", + "T arget", + "ĠNor wegian", + "Ġdehyd ration", + "ĠSIR T", + "ĠRob inson", + "ĠBen chmark", + "v iral", + "Re al", + "Ġd oxorubicin", + "Ġestim ations", + "ĠCa uc", + "Ġaddi tives", + "m odes", + "ĠH end", + "Ġacceler ating", + "ĠG ordon", + "ĠMagn et", + "Ġgon ad", + "Ġpyro lysis", + "coh olic", + "ĠPK C", + "S AR", + "Ġw inding", + "ter ious", + "ĠMountain s", + "ĠS ymbol", + "ĠMat the", + "ĠSh in", + "S cript", + "r ug", + "Ġm W", + "ĠI SM", + "ĠN g", + "Ġapp oint", + "ĠA IDS", + "Ġpor ts", + "diff erential", + "ĠJ es", + "ĠN eed", + "Ġlens es", + "ĠAMP K", + "à ¤", + "le af", + "ĠB ron", + "Ġprof it", + "L ocal", + "ĠEx amination", + "ĠCh ief", + "Ġopin ions", + "ĠR ound", + "form ations", + "Ġcol linear", + "Ġdig ested", + "lass ical", + "erv ative", + "Ġce phal", + "Ġdisadvant ages", + "Ġïĥ ·", + "Ġsubt racting", + "Ġwe igh", + "B ound", + "D G", + "Ġinflu ential", + "Ġtox ins", + "ĠBen jamin", + "ĠNum bers", + "c rystal", + "Ġst ocks", + "ĠB our", + "ĠComp eting", + "Ġac qu", + "t RNA", + "ĠSep aration", + "Ġtag ged", + "Ġcon ject", + "ĠPr ince", + "Ġgra zing", + "Ġrele ases", + "ĠChall enge", + "ATP ase", + "Ġe mail", + "ins ically", + "ĠReg ulatory", + "M essage", + "Ġsl it", + "Ġpolyg on", + "Ġdoubl ing", + "Ġrece ivers", + "Ġtrack ed", + "Ġengine er", + "st ained", + "ĠD anish", + "sh ock", + "ĠM az", + "Ġco ugh", + "ĠImmun ohist", + "C onsequ", + "arm acy", + "Ġchem o", + "ĠM H", + "Ġemerg es", + "Ġanne aled", + "Ġhypot hesize", + "ĠTyp ically", + "ĠB ang", + "ĠP uls", + "Ġgir l", + "Ġherb iv", + "ĠAN N", + "Ġse ism", + "ĠCy tok", + "ĠThrough out", + "Ġadapt ations", + "l ang", + "Ġcl onal", + "um ulation", + "ĠUn iform", + "Ġh i", + "op ent", + "Ġbut ton", + "ten e", + "Ġprote asome", + "b red", + "ĠN elson", + "racycl ine", + "ĠD Y", + "Ġimmun oblot", + "pro l", + "Ġp ic", + "Ġcomp ilation", + "ĠDev ices", + "eterm ined", + "ĠFranc is", + "not ation", + "wr iting", + "ter ase", + "ĠSte phen", + "am el", + "ĠCh u", + "al one", + "Ġexha ust", + "re levant", + "ĠStr at", + "Ġparametri zation", + "ĠB ull", + "ĠRem ote", + "incre asing", + "Ġd d", + "âĢ °", + "yroid ism", + "il in", + "ĠH ip", + "IC A", + "ĠAp optosis", + "Ġmach ining", + "LD L", + "Ġg em", + "ĠF FT", + "ĠGu ang", + "Ġorigin ates", + "d at", + "c one", + "ĠAd oles", + "uc ci", + "av oid", + "ul pt", + "ur ium", + "Ġliter acy", + "Rec ent", + "av g", + "Ġinv ited", + "ĠPe ak", + "ĠEnter obacter", + "Ġaneurys m", + "ĠMor ris", + "ti da", + "ĠS ER", + "ĠMic hel", + "ĠI BD", + "ĠN G", + "Ġscar ce", + "we b", + "Ġexpress es", + "Ġsc hema", + "Ġless ons", + "Ġarg inine", + "Ġphot ographs", + "ĠNe urons", + "ĠATP ase", + "Ġf iller", + "rap ped", + "Ġrandom ness", + "Ġve ins", + "Ġwound s", + "ĠA part", + "Ġr acial", + "Ġnot eworthy", + "Ġremov es", + "Ġgangl ion", + "Ġlamin ar", + "ĠS SR", + "Ġpol ysaccharides", + "Ġbu f", + "Ġendot helium", + "ĠC AS", + "ĠGol gi", + "Ġinher itance", + "is ite", + "CO MP", + "Ġp t", + "Ġmes hes", + "Ġtherap eutics", + "O ST", + "olin ergic", + "U G", + "squ ared", + "Ġdeg rade", + "u um", + "Ġret rosp", + "L oc", + "ĠJ NK", + "O ptions", + "Ġins ulating", + "Ġspec ifies", + "Ġo ven", + "y y", + "ĠCon ver", + "Ġdisapp eared", + "ĠProgn ostic", + "ĠNgu yen", + "Ġperipher y", + "b ank", + "Ġim id", + "Ġassign ing", + "ĠM ess", + "prop an", + "i oma", + "oly b", + "Ġepit ope", + "Ġemit ting", + "D IR", + "yn c", + "Ġimpair ments", + "ĠMic roscopy", + "ĠFW HM", + "g ray", + "Ġf ing", + "uc ial", + "plement ed", + "e as", + "est amp", + "Ġcre st", + "ĠM os", + "Ġneut rons", + "Ġbro th", + "Ġhead ache", + "onge vity", + "Ġre ass", + "ĠP SF", + "ĠB uch", + "vis or", + "Ġden oting", + "in teger", + "ou in", + "eff icacy", + "Ġglut amine", + "Ġpick ed", + "ĠCamp bell", + "ĠK ernel", + "Ġsh ips", + "l t", + "ond yl", + "Ġcre di", + "Ġpepti d", + "ĠEst abl", + "b ons", + "Ġag gl", + "US E", + "sup p", + "ups ilon", + "character ized", + "ishe ries", + "M ay", + "AR C", + "Ġro ads", + "Ġdepar ture", + "ĠMA X", + "ĠT RA", + "im od", + "ĠAl ber", + "Ġterm inated", + "öl der", + "S calar", + "h ash", + "ĠM SS", + "Ġsmooth ness", + "Ġres emble", + "ĠEff ectiveness", + "r x", + "ĠE ye", + "Ġfa ecal", + "à ¾", + "ĠClostr idium", + "ach ine", + "ĠBD NF", + "Ġc ab", + "ĠW ong", + "ĠDoug las", + "Ġre perfusion", + "ĠX i", + "Ġconf used", + "ĠPhil adelphia", + "Ġap ple", + "Ġi le", + "Ġfav ored", + "Ġpl aques", + "Ġtri vially", + "ĠTyp ical", + "Ġcentral ized", + "ĠFace book", + "Ġnorthe ast", + "Ġnorm ality", + "ĠT b", + "Ġap t", + "Ġfac et", + "ĠRen al", + "cl k", + "Ġlig ation", + "iff erenti", + "Ġput ting", + "Ġintr ig", + "w alled", + "E t", + "ĠC ow", + "ĠN ations", + "Ġcamp us", + "ĠKine tics", + "ĠMex ican", + "ER K", + "Ġlat itudes", + "ĠR oll", + "ĠQ D", + "adap tive", + "Ġquenc hed", + "Ġf ram", + "Q i", + "Ġt ongue", + "ed es", + "Ġasc orb", + "ĠGluc ose", + "our i", + "Ġdef eated", + "ophil us", + "ral ateral", + "x rightarrow", + "ĠJ up", + "ax es", + "eg er", + "MI T", + "ĠM ember", + "ĠN u", + "Ġtransl oc", + "ĠFlu x", + "ĠColor ado", + "Ġre lying", + "at rol", + "Ġcontras ts", + "cent age", + "Ġleuk ocyte", + "Ġcoinc idence", + "Ġcontrac tions", + "og a", + "AN N", + "ĠAbs orption", + "Ret urn", + "rep rene", + "ba um", + "tra umatic", + "inc ial", + "Ġaut ophag", + "Ġalgorithm ic", + "rim p", + "Ġdiv ides", + "ĠR ose", + "ĠE ric", + "Ġadd iction", + "pl ification", + "Ġdiff usive", + "ĠVehic le", + "en erate", + "ti sing", + "Ġstar vation", + "abs orption", + "ĠA ra", + "Ġgra v", + "ĠSub unit", + "Ġam ide", + "Ġenh ancer", + "Ġmer id", + "erm ost", + "Ġal gal", + "ĠQue ens", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠ", + "Ġjud ge", + "ĠGreen land", + "b race", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠĠĠ", + "Ġhyper gly", + "Ġemerg ent", + "F isher", + "ĠL as", + "Ġsex es", + "S ep", + "Ġph rases", + "ĠSequ ential", + "ink i", + "Ġaxi oms", + "stud y", + "Ġt iny", + "Ġc d", + "cataly zed", + "as aki", + "ĠW R", + "ĠMin imal", + "Ġsub cellular", + "Ġphosph o", + "ES I", + "Ġv ow", + "Ġsup plies", + "oper and", + "F ix", + "an ian", + "wr iter", + "âĪ ¶", + "Ġwin ner", + "ĠP ID", + "ĠLe besgue", + "Ġsimpl ification", + "ĠRelationship s", + "Ġautom ata", + "ĠCont ribution", + "Ġhered itary", + "err in", + "ĠB LAST", + "ae a", + "yle th", + "ĠT c", + "ade h", + "adj uvant", + "W ave", + "c ounter", + "ĠG upta", + "ĠG hana", + "C ho", + "Ġour selves", + "Ġeven ly", + "lym ph", + "Ġcereb ellum", + "Ġcopol ymers", + "mod ular", + "Ġhard er", + "Ġp lease", + "ĠP SD", + "Ġlim bs", + "Ġexplo itation", + "ir y", + "Ġperiodon tal", + "AT CH", + "Ġmal icious", + "ĠSl ov", + "H Y", + "Consequ ently", + "ore n", + "ĠP are", + "ag ine", + "ĠRO I", + "ĠWh ich", + "ĠN ative", + "am en", + "resh ape", + "opl ankton", + "Ġartif act", + "Ġrh in", + "g pu", + "Ġund et", + "Ġspor adic", + "Ġor ally", + "Ġstep wise", + "ĠCoh ort", + "Ġr hod", + "c yt", + "Ġi err", + "Ġmot ors", + "ĠIg E", + "calc ulated", + "ĠChampionship s", + "p el", + "ĠF err", + "Ġis ometric", + "n utrition", + "Ġuns aturated", + "Ġd oll", + "ĠR MSE", + "Ġsol itary", + "approxim ation", + "Ġreper to", + "s ight", + "Ġc ranial", + "il ical", + "ĠO st", + "o ul", + "Ġd g", + "ĠPro ceed", + "Ġmill ing", + "s z", + "Ġmineral ization", + "Ġcig arette", + "Ġp orph", + "Ġsp ons", + "ĠGre ece", + "ip ore", + "ac cept", + "ĠPT SD", + "Å «", + "Ġc ipher", + "Ġfunctional ized", + "P oly", + "Ġab d", + "fl ight", + "ĠSyd ney", + "Ġdis aster", + "ĠH aving", + "Ġdies el", + "ĠG reg", + "Ġsp ans", + "ĠSe asonal", + "ST EM", + "i err", + "ĠI B", + "Ġle mm", + "an um", + "ĠB ottom", + "Ġse al", + "bo ost", + "Ġleg end", + "b ing", + "ab is", + "Ġch itin", + "Ġmaxim ally", + "Ġimmunosup pressive", + "âĪĴ âĪĴ", + "Ġabol ished", + "ig e", + "Ġes ophag", + "Ġlas ted", + "Ġcarbohyd rates", + "Ġch ips", + "ĠFern and", + "f ar", + "ĠPo ints", + "cal ation", + "ĠReg ions", + "CH K", + "ver atrol", + "tr uth", + "Ġst range", + "Int erest", + "s ho", + "ĠInd uc", + "Ġmig raine", + "ĠV ac", + "op hores", + "Ġerr one", + "scripts ize", + "ĠNeut ron", + "Ġindist inguishable", + "ist ine", + "Ġhel per", + "spec ified", + "Ġju ice", + "ox al", + "ĠJ ung", + "Ġmag azine", + "Ġtele phone", + "ĠPh yt", + "Ġ um", + "ĠAvail ability", + "ĠT ropical", + "ĠC ases", + "Ġdesc end", + "H ar", + "âĪ Ĺ", + "ĠâĨ ĵ", + "K s", + "Ġ ê", + "ol uble", + "Ġch ampionship", + "ĠMove ment", + "ĠX Y", + "kappa B", + "year s", + "m emb", + "qu ine", + "Ġlet ting", + "Ġbig gest", + "Ġc ards", + "Ġbi otin", + "ĠA ur", + "mod al", + "Ġvacc inated", + "Ġtransl ates", + "ĠP AC", + "ll i", + "re onine", + "Ġcur cumin", + "ĠCon struct", + "Ġconv inc", + "ĠN at", + "Ġam ygdala", + "Ġprot r", + "ĠSing ular", + "ĠCont act", + "k ind", + "ĠD aw", + "og roup", + "ĠK Cl", + "Ġhy gi", + "eren ced", + "Ġsurvey ed", + "ĠM ull", + "est hetic", + "Ġms g", + "ĠRe quire", + "Ġdistor tions", + "Cont rol", + "B ERT", + "Ġaut onomic", + "Ġhorm onal", + "Ġstri ps", + "Ġt rophic", + "if ting", + "op od", + "ĠSp ontaneous", + "Ġlog s", + "O PT", + "ĠM ot", + "ĠG mb", + "ah aran", + "ĠP OL", + "Ġvis ceral", + "bl ocks", + "Ġsit ting", + "Ġs ine", + "Ġonc ogenic", + "ERR Q", + "quin one", + "Ġsmart phone", + "ĠTan z", + "lact am", + "ĠSignific ance", + "Ġe u", + "ĠI SS", + "ĠTr ig", + "ĠM aj", + "ting ale", + "Ġdil at", + "enn es", + "ĠBelg ium", + "le v", + "ĠCon tr", + "ĠGal ois", + "ĠComb ination", + "ĠTh i", + "ĠAust ria", + "P rom", + "Ġelic it", + "bi osis", + "Ġlymph atic", + "ĠMur ray", + "ĠX PS", + "Ġcon g", + "sc reen", + "ti de", + "am oyl", + "ĠMc D", + "Ġreti red", + "m ixed", + "EL D", + "ĠM aps", + "ĠV E", + "cess ion", + "num er", + "id ated", + "ĠB ishop", + "Ġneon ates", + "Ġlands l", + "ĠFrac tional", + "Ġspec ifying", + "ĠJ r", + "Ġnanow ire", + "Ġconsult ation", + "l anguage", + "Ġp ricing", + "ĠLimit ations", + "ĠP ediatric", + "ĠD imension", + "Ġprepar ing", + "L ag", + "seg ment", + "Ġsp end", + "at he", + "Ġwe ap", + "ĠJ os", + "tex tit", + "output s", + "ord ering", + "Ġplac enta", + "ation ally", + "ĠK un", + "Ġout standing", + "Ġthickness es", + "ĠCh IP", + "de oxy", + "ĠZ o", + "ĠDevelop ing", + "Ġstring ent", + "i ency", + "per se", + "Ġp end", + "ĠDevelopment al", + "Ġex tern", + "Ġinver ter", + "ĠD API", + "lec tivity", + "Ġtable ts", + "Ġprog ester", + "Ġïģ Ń", + "Ġansw ered", + "ent ary", + "OR S", + "Ġd ir", + "Ġdele terious", + "Ġdop aminergic", + "R andom", + "dis s", + "Ġmonol ayers", + "Ġinteg rand", + "ĠComp onents", + "ĠP erc", + "ag it", + "AR N", + "es ophageal", + "iv an", + "ne ider", + "ĠStar ting", + "P ORT", + "y ellow", + "Ġreg isters", + "pair s", + "Ġethn icity", + "Ġb oy", + "au ti", + "Ġchrom ium", + "P OS", + "v ature", + "ay ashi", + "Ġin appropriate", + "ĠS NA", + "D omain", + "ĠP rice", + "Ġmac ular", + "Ġover load", + "ĠUn ified", + "Ġatt ach", + "ĠScot tish", + "m aps", + "ag l", + "em i", + "Ġse am", + "ĠAnal og", + "d ated", + "u o", + "Ġpl ated", + "Ġass et", + "Ġsc reens", + "Ġspur ious", + "B esides", + "Ġbas elines", + "head s", + "Ġco at", + "ĠRem oval", + "Ġinfinites imal", + "ĠTrans formation", + "Ġcomm ens", + "Flo at", + "A UC", + "ĠL ay", + "Ġint ron", + "ĠDet ecting", + "ĠHere in", + "ĠAssoci ations", + "Ġprogester one", + "B acteria", + "Ġs entiment", + "ĠPhen omen", + "m atter", + "Ġcylind ers", + "Ġtolu ene", + "Ġspati otemporal", + "Ġland ing", + "ĠCoron avirus", + "ĠBer ry", + "ĠB ragg", + "Ġreg istry", + "Ġenthal py", + "tic a", + "raz ine", + "Ġc argo", + "ot ation", + "Ġcontrad icts", + "Ġpestic ides", + "ĠF ischer", + "Ġmechan ically", + "ĠInter fer", + "ĠC yp", + "ĠK as", + "Ġmet res", + "Ġanti retroviral", + "Ġtra vers", + "se lection", + "ĠW A", + "Ġdouble t", + "m eta", + "EN TR", + "son ic", + "Ġmark ing", + "ĠO verex", + "Ġpy ruvate", + "Ġextr usion", + "Ġin gestion", + "Ġcoc aine", + "ĠF ellow", + "CN Ts", + "B G", + "ĠMorph ological", + "Ġdef ence", + "ĠY osh", + "mit ter", + "rystall ization", + "STR ACT", + "Ġinflamm asome", + "ĠG d", + "Ġsh aft", + "Ġerup tion", + "ĠOx ide", + "if olds", + "ĠG am", + "ĠG ap", + "com mand", + "ĠIg A", + "Ġshorten ing", + "assemb led", + "is opropyl", + "Ġal umina", + "ĠAT M", + "Ġc t", + "Ġspin ning", + "ĠPet sc", + "pref ix", + "Ġperpet uity", + "P RE", + "Ġfr uct", + "G Hz", + "el ike", + "en yl", + "Ġwhere in", + "U K", + "vis ual", + "lipid emia", + "re duction", + "an in", + "ol as", + "Ġam plic", + "ĠS AT", + "Ġmod ulator", + "for th", + "r l", + "Ġcre w", + "Ġi P", + "Ġx i", + "AD D", + "ĠAlex and", + "const rained", + "r atory", + "Ġk W", + "ĠMD R", + "Ġlnc RNA", + "M ill", + "ĠMg O", + "circ uit", + "Ġpersonal ized", + "ĠOper ator", + "st ock", + "ĠP SA", + "ens able", + "Ġle an", + "y ield", + "Ġop acity", + "ĠComm ons", + "Ġsum med", + "uck er", + "ec ke", + "ep ithelial", + "Ġas king", + "ues e", + "ĠFl av", + "Ġl actic", + "Ġl ubric", + "Ġis n", + "reg ions", + "sup port", + "Bel ow", + "ĠN om", + "Ġhy al", + "ik h", + "b an", + "ĠB G", + "rom eter", + "ind ic", + "oph aryngeal", + "IT ION", + "ĠProp agation", + "ĠPl ace", + "ĠCirc uit", + "ĠCO L", + "G reen", + "I r", + "l av", + "Ġd S", + "ĠM oment", + "Ġinduc ible", + "Ġdischarg es", + "hab di", + "ĠExper ience", + "Ġs g", + "Ġout ward", + "Ġport able", + "ĠOper ators", + "A v", + "ĠD Q", + "ost atin", + "Ġeosin ophil", + "Ġstri atum", + "ĠCons ensus", + "Ġim perfect", + "NO T", + "ĠDem ocratic", + "; ;", + "B ody", + "di i", + "H o", + "ĠRail way", + "ĠUg anda", + "Ġunp aired", + "friend ly", + "Ġrepro gramming", + "Altern ative", + "R G", + "im et", + "ene z", + "ĠHyp othesis", + "Ġt on", + "ĠCom bin", + "ĠDel ivery", + "L ast", + "Ġown ers", + "raz ole", + "ĠK ob", + "Ġform ats", + "Ġpoly clonal", + "Ġidentif ier", + "IL L", + "Ġsurge on", + "Ġpost p", + "ĠGener ative", + "ĠM all", + "ab c", + "ĠH az", + "Ġsmooth ly", + "Ġcrystall ographic", + "ĠF DA", + "Ġcoex istence", + "ion ized", + "Ġcomp iler", + "ĠAr ter", + "Ġappear ances", + "amilton ian", + "Ġencaps ulated", + "ati a", + "w i", + "re b", + "Ġwa fer", + "ub s", + "ĠU E", + "ĠGS K", + "Ġv iv", + "Ġflood ing", + "ĠG yr", + "Ġst ably", + "Ġdis locations", + "Ġes cap", + "ĠPhys iological", + "tid al", + "ym e", + "ĠMax im", + "iter ator", + "ord ant", + "Ġatten tional", + "Ġcataly zed", + "ĠTr yp", + "P IN", + "ĠCor relations", + "Ġhyd rological", + "Ġn ose", + "ex port", + "Ġde xt", + "ĠBen ef", + "ĠBios ystems", + "ĠP ars", + "Ġread ings", + "Ġinstrument ation", + "ĠI Q", + "R IC", + "Ġgra fts", + "over s", + "ĠMed ic", + "Ġmon od", + "Ġuniform ity", + "ĠAT LAS", + "Ġmask ed", + "R i", + "ĠPhys ic", + "Ġim posing", + "ĠPar ad", + "ime tic", + "Ġdemand ing", + "un ks", + "Ġfol ds", + "ĠAn c", + "Ġvol atility", + "Ġbring ing", + "ac il", + "ĠN MDA", + "re duced", + "ti i", + "Ġnorth west", + "ĠB essel", + "ven tions", + "Ġconsol idation", + "Me ier", + "Ġmicro f", + "Ġqual ified", + "Ġins ignificant", + "ĠMorph ology", + "Ġpoint wise", + "Ġlear ns", + "Ġgu ard", + "CH ECK", + "phon on", + "ĠEnhance ment", + "Ġz onal", + "ER G", + "St art", + "Ġhistor ic", + "ĠP ure", + "ĠGmb H", + "g lu", + "Ġpattern ing", + "Ġstic k", + "umin osity", + "D ataset", + "Ġover ride", + "ĠSte el", + "Ġfu els", + "m echanical", + "Ġaut ologous", + "Ġdepart ments", + "ĠB lo", + "Ġim ported", + "Ġrestric tive", + "e igen", + "ĠR ome", + "ĠÌ Ĭ", + "Ġepit opes", + "Ġlab elling", + "Ġown ership", + "ĠE specially", + "Ġco ffee", + "ĠGR B", + "H ead", + "ĠV ent", + "es are", + "ĠPar ticles", + "UN CTION", + "j j", + "u ents", + "el ic", + "ĠT at", + "ĠF le", + "Ġg ating", + "Ġref uge", + "Ad ditionally", + "Ġrh s", + "Ġmay be", + "ĠF ang", + "Ġad vent", + "otransfer ase", + "sh ould", + "Ġprote omic", + "Ġleg itim", + "PER IM", + "ĠG iant", + "Ġgraph ics", + "onom ical", + "sc atter", + "Ġsugges tive", + "pl ots", + "Ġmulti drug", + "Ġabsor ber", + "X S", + "cons uming", + "Ġsustain ability", + "op re", + "f ix", + "Ġvol cano", + "ĠTyp es", + "ĠCre ate", + "Ġcho oses", + "Ġstir ring", + "Ġsurge ons", + "d S", + "Ġcharacter izes", + "Ġadjust ments", + "text tt", + "et ra", + "Ġclass ifications", + "sp ots", + "ĠâĻ ¯", + "ere x", + "de hyd", + "ĠBr ig", + "ĠSuper conduc", + "Ġgran ts", + "ĠC en", + "ĠY in", + "ĠRe actions", + "des cription", + "trans cription", + "import ant", + "Ġhemod ynamic", + "ĠY i", + "ĠGold en", + "k k", + "al b", + "Ġro oms", + "Ġseg reg", + "Ġsumm ing", + "Ġsuccess ion", + "Ġfollic ular", + "Ġtack le", + "D own", + "Ġevalu ates", + "atic a", + "ann ual", + "ĠAl bert", + "Ġt al", + "orb ital", + "f ted", + "vari ables", + "Ġwet land", + "outhe astern", + "M EM", + "ĠBr ill", + "ĠS odium", + "ĠAlex a", + "um ed", + "BU G", + "ar ine", + "Ġre venue", + "habdi tis", + "Ġdiss ol", + "am plitude", + "Ġar tists", + "Ġnormal ised", + "Ġfluct uating", + "Ġas par", + "ĠF i", + "ol ates", + "isp anic", + "Ġacet ylation", + "ĠConcent ration", + "Ġth ro", + "sh ots", + "Ġnarr ative", + "ĠWa als", + "am monium", + "ure au", + "-------- ----", + "Ġresearc hes", + "Ġbab y", + "Ġshar ply", + "Ù Ħ", + "ĠC el", + "C X", + "um inal", + "Ġgerm line", + "ĠTransform er", + "p seud", + "H G", + "K a", + "ĠS MC", + "ĠN utrition", + "Ġb arc", + "ĠW rite", + "Ġprote ases", + "Ġswe ep", + "ĠKol mogorov", + "m orph", + "in ducible", + "Ġexc iting", + "le in", + "ĠH ass", + "Ġproduc tive", + "mes h", + "ĠC MS", + "Ġhe avier", + "Ġmeet ings", + "ĠCop per", + "Ġvirt ue", + "as ant", + "ĠD EN", + "Ġinherent ly", + "ri o", + "Ġhous ed", + "Ġintra operative", + "Ġc rown", + "con ditions", + "AN G", + "YS IS", + "im an", + "Ġnm ol", + "ĠRetrie val", + "al gae", + "Ġk appa", + "de ep", + "in ence", + "ĠC arcinoma", + "Ġchromat ographic", + "Ġas cribed", + "Ġle verage", + "ĠK K", + "omy el", + "p et", + "ĠN J", + "com m", + "Ġann ually", + "g ran", + "Ġa val", + "ĠN ish", + "Ġev ac", + "Ġmulti f", + "Ġfund s", + "enn y", + "ĠM ong", + "ĠEx ception", + "path s", + "ym en", + "h pp", + "Ġrestric ting", + "s aturated", + "â Ļ", + "Ġlear ners", + "ĠLank a", + "in ities", + "ĠG DP", + "Ġspec iation", + "Ġens ured", + "Ġneutral izing", + "Ġball oon", + "Compar ison", + "ĠCal ibration", + "ĠInflu enza", + "Ġvap our", + "X A", + "t racking", + "ĠI CD", + "fluor o", + "ĠDam age", + "Ġp ra", + "Ġcon ceived", + "ĠCosm ological", + "Ġlo ose", + "inos itol", + "ĠCliff ord", + "ow a", + "Ġoffset s", + "doc ument", + "Ġenorm ous", + "Ġphoto electron", + "rec ord", + "estic ular", + "Ġvoc als", + "Ġconscious ness", + "Ġtre m", + "Ġlandsc apes", + "ĠFund amental", + "teb rate", + "Ġverteb ral", + "Ġregener ative", + "Ġtro posp", + "In tegr", + "Ġassoci ates", + "ov ed", + "uss ed", + "aw s", + "ĠS ide", + "Ġinter connected", + "Ġsuper family", + "ĠCo ok", + "load er", + "Ġpy thon", + "ĠC ounter", + "bo oks", + "Ġïģ ²", + "bre aking", + "g y", + "Ġcar box", + "Ġed ited", + "otyp ed", + "Ġdu oden", + "an ne", + "Ġan astom", + "gin ate", + "ĠBios ciences", + "ra ge", + "ĠCh iral", + "Ġsimpl ifies", + "Ġtes tis", + "str öm", + "ial s", + "Ġmic elles", + "cor rect", + "ĠGene tics", + "al ong", + "R em", + "res istance", + "Ġdr ink", + "orb ed", + "ĠT reat", + "ĠS ho", + "sh ows", + "é r", + "Ġmim ics", + "occ up", + "ec lam", + "ON G", + "Ġmark eting", + "ĠF inding", + "Ġendomet ri", + "âĶ Ģ", + "st rained", + "ĠM uch", + "Ġex ons", + "ĠH il", + "T D", + "ĠW W", + "ĠV ic", + "end a", + "Ġfact ory", + "ĠHep G", + "ĠSt atic", + "blast oma", + "w d", + "ra isal", + "ĠB asis", + "In s", + "ĠUn supervised", + "el o", + "ose lective", + "Ġaccompl ish", + "ĠP rospective", + "Ġuncor related", + "ĠG ate", + "icy cl", + "Ġur gent", + "ĠPath ways", + "Ġobl ique", + "ĠIndividual s", + "Ġiniti ative", + "Ġcat ast", + "j ections", + "Ġaut osomal", + "ĠPhil ip", + "Ġcomprehens ion", + "m M", + "p ain", + "Ġmicro M", + "Ġenc ounters", + "g oto", + "Ġl adder", + "Ġoccup y", + "ĠSur faces", + "D oc", + "ug by", + "Ġexam ines", + "os ynthesis", + "ĠK EGG", + "gl ass", + "sl ice", + "prop agation", + "str y", + "Ġillustr ating", + "im i", + "Ġsp ores", + "Ġast rophysical", + "Ġen closed", + "Ġinf erences", + "Ġbi jection", + "Ġever yday", + "Ġaltern atively", + "re action", + "ian ts", + "cont act", + "Ġg ing", + "ĠBi as", + "Ġautom aton", + "back ground", + "Ġneighbour ing", + "Ġdet ects", + "por ate", + "ĠShar ma", + "H ydro", + "Ġs acc", + "ĠF iber", + "ĠCh lam", + "Ġbuff ers", + "App lying", + "l ceil", + "em ph", + "ĠG SE", + "met ry", + "Ġimmun ost", + "ĠHistor ical", + "ĠD rag", + "Ġtransplant ed", + "Ġf rail", + "Ġanth ocyan", + "in te", + "ĠB hat", + "ĠO g", + "Ġste ering", + "benz ene", + "******************************** ********************************", + "Ġsynt het", + "A ct", + "Ġc in", + "Ġher bal", + "Ġd yn", + "Ġhyper plasia", + "head er", + "Ġcalc ulates", + "ĠDiff erence", + "Ġb ats", + "duc tivity", + "Ġconform ations", + "c ity", + "Ġsepar ates", + "ĠCD C", + "ĠPr ism", + "ĠBehavi our", + "ĠKel ly", + "ĠS ey", + "Ġà ł", + "LE X", + "g kin", + "st rom", + "Ġv om", + "ĠW in", + "ĠW igner", + "Ġcont ralateral", + "ĠMin or", + "Ġstere o", + "ĠApproxim ately", + "L ED", + "s ay", + "ĠJ S", + "Ġalcoh ols", + "Ġs an", + "Ġhard ening", + "IF N", + "Ġretrosp ectively", + "Ġgeneral ised", + "Ġtib ial", + "ĠWe ek", + "Ġar yl", + "ĠPen insula", + "Ġdeterm inations", + "Ġphot ovoltaic", + "Ġsugges tion", + "J ac", + "ĠV itro", + "Ġcycl o", + "Ġfibro us", + "dis ambiguation", + "pro gram", + "Ġgu est", + "ĠD ust", + "r ceil", + "Ġpow ered", + "Ġcardiomy ocytes", + "he at", + "yl ic", + "Ġpresent ations", + "Ġtransmit ting", + "W D", + "add ed", + "In itial", + "D el", + "ĠV elocity", + "Ġmo le", + "Ġo val", + "Ġpl ankton", + "the ir", + "ĠQ ED", + "vol utions", + "Ġmand atory", + "Ġrep ulsive", + "ĉ ĠĠ", + "Ġpost ulated", + "ĠCor tex", + "ĠCar b", + "CHK ERRQ", + "Ġoverl ay", + "ĠF arm", + "enor habditis", + "Ġpos ed", + "Ġinst anti", + "Z T", + "ĠVisual ization", + "ĠGAP DH", + "lec om", + "och ron", + "ĠB j", + "ĠT rib", + "Ġby te", + "Ġsuperim posed", + "Ġund i", + "Ġacceler ator", + "cri ptions", + "ĠSm ooth", + "Ġz ip", + "nes ota", + "ĠE FF", + "ĠC ole", + "ĠB ru", + "re nd", + "ut z", + "Ġdiagn ose", + "b asis", + "di amond", + "ĠIn n", + "ĠMed ian", + "Ġmarg inally", + "Ġlemm as", + "rect omy", + "Ġdial ogue", + "ĠB rid", + "Ġ å", + "ox ane", + "ar is", + "Ġmunicip ality", + "Ġproduc ers", + "Reg arding", + "ĠF V", + "ide al", + "exp onential", + "L abel", + "ĠF robenius", + "Ġe ll", + "ĠL TE", + "Ġlip ase", + "r p", + "Ġd m", + "ot ri", + "cl oud", + "ĠAg ent", + "M SCs", + "os om", + "hyd ropy", + "ne urons", + "Ġsol vable", + "duc ting", + "Ġrend ered", + "Ġattract or", + "Ġb rac", + "à ģ", + "Ġhost ed", + "ĠO ct", + "Ġgu iding", + "Ġdiges tive", + "j s", + "Ġint ent", + "flu x", + "Ġbios ynthetic", + "Ġe lections", + "ĠWil coxon", + "Ġspectrophot ometer", + "Ġimpair s", + "Ġabd omen", + "k b", + "ĠW ho", + "ASS ERT", + "Ġel uted", + "Ġmaxim ization", + "Ġcollect or", + "ĠPrevious ly", + "a q", + "am bo", + "ĠO z", + "C ur", + "Ġcaffe ine", + "M ass", + "p al", + "pi ece", + "ou ville", + "ĠM eyer", + "ut a", + "ch an", + "ĠK S", + "om otor", + "ĠG PR", + "Ġev al", + "ĠCo operative", + "ogly can", + "Ġnozz le", + "ĠS hel", + "Ġinter change", + "Ġunderg rad", + "Ġexplan atory", + "Ġphag ocytosis", + "Ġc tx", + "hes s", + "Ġunivers ality", + "ĠK illing", + "ons in", + "Ġlast ing", + "ĠIm m", + "Ġconc ordance", + "y ma", + "Ġaut umn", + "Ġbar ley", + "Ġconsequ ent", + "is i", + "Ġconjug ates", + "Ġta ught", + "Ġcovari ate", + "Ġadoles cence", + "Ġvill ages", + "Ġeigen functions", + "Ġtempor ally", + "ĠMin nesota", + "y rate", + "ies is", + "def inite", + "Ġalph abet", + "ĠY un", + "ĠM AR", + "Ġse aled", + "ron ectin", + "ĠSep ar", + "n x", + "CA A", + "Ġrece ption", + "uck y", + "ĠPT EN", + "ĠM organ", + "Ġdi odes", + "Ġmet formin", + "Ġsynt hes", + "ĠPar ticip", + "ĠJer sey", + "Ġamph ib", + "c hel", + "Ġl amp", + "ĠH els", + "ĠF N", + "Ġexc av", + "is econd", + "int ro", + "Ġnon commutative", + "Ġsubs ystems", + "sum m", + "Ġcontrast ing", + "ĠSil icon", + "ĠPar tition", + "Glc NAc", + "Ġdisc ern", + "ĠBound s", + "ĠR ah", + "Ġapproxim ating", + "ĠHyper t", + "ĠD il", + "Ġcompact ness", + "Ġca ught", + "ĠImpro ve", + "ĠTor onto", + "ĠBiom ark", + "ĠB ag", + "ĠIn vent", + "Ġelabor ate", + "ĠM ott", + "AB C", + "ĠGra ham", + "Ġpo ultry", + "ĠCon jecture", + "ĠAl gebras", + "ĠN LO", + "ap sing", + "path y", + "ĠEliz abeth", + "ĠT it", + "ĠS CI", + "ant on", + "Ġv oting", + "math rel", + "ĠF ord", + "ig ibility", + "Ġall ergy", + "ac oustic", + "ĠD yn", + "ĠD SC", + "ĠG RO", + "ĠTh irty", + "Ġanalys ing", + "ĠEm pire", + "f ire", + "Ġpath ologic", + "Ġpat ent", + "Ġhe ard", + "ĠF ront", + "isc onsin", + "hy pert", + "uz umab", + "ĠMut ation", + "Ġb iliary", + "Ġsuper fluid", + "ĠW C", + "ust om", + "ĠAc tivities", + "Ġpolyp eptide", + "he ets", + "Ġb orders", + "ear ly", + "Ġorth ogon", + "Ġbul ge", + "ï £", + "Ġcon ical", + "ĠL ept", + "Ġelectroly tes", + "Ġ «", + "reg ulating", + "Ġviol ated", + "â ĺ", + "AL T", + "ĠWork s", + "ĠHep at", + "ur gical", + "ob ar", + "ĠRe active", + "poss ibly", + "ĠAds orption", + "ĠR io", + "ano ic", + "ĠâĨ ij", + "Ġintrig uing", + "Ġo m", + "her tz", + "ĠApproxim ate", + "ĠP arent", + "Ġco in", + "exp and", + "Ð ²", + "Ġnon parametric", + "ex tern", + "ae us", + "gly cerol", + "Ġc p", + "Ġbat ches", + "Ġnanom aterials", + "U se", + "ĠV ivo", + "R h", + "Ġt iles", + "Ġdep ict", + "Ġsouth west", + "ĠCas imir", + "lay ered", + "ĠLe af", + "f em", + "b ered", + "Ġsub algebra", + "Ġdet achment", + "ĠLe uk", + "ol us", + "ĠR ick", + "Ġab ortion", + "Ġclar ified", + "Ġgangl ia", + "Q S", + "o ising", + "ĠFor ward", + "ĠPer ipheral", + "shif ted", + "b ula", + "ram olecular", + "ĠF EM", + "ĠPro ton", + "AM E", + "Ġsched ules", + "Ġa a", + "ĠU DP", + "st ere", + "Ġmorph ine", + "Ġspecial ist", + "ĠAnd roid", + "Id entif", + "Ġun expl", + "Ġheter ozyg", + "Ġf id", + "pyrid yl", + "ĠW y", + "phosph or", + "Ġfriend ly", + "Ġmic rol", + "ĠS plit", + "agn er", + "crib e", + "Ġm oth", + "ĠEu ro", + "ig s", + "ĠCon ditional", + "ĠSte wart", + "pro perties", + "AS C", + "ĠTra ditional", + "ĠPortug al", + "Ġear ned", + "Ġcat he", + "Cre ate", + "ici encies", + "Ġsph ing", + "x ml", + "Ġimmun omod", + "Ġcomm ute", + "Ġselen ium", + "ang es", + "ho ok", + "den oted", + "Ġjus tify", + "ĠP ool", + "Ġgu inea", + "Ġcont ra", + "Ġfol ded", + "Ġlist ing", + "ĠL G", + "ĠL ane", + "Ġsure ly", + "v et", + "fluor ophenyl", + "Ġcoron a", + "ĠAb und", + "ĠOb jects", + "Ġt rough", + "ch t", + "Ġdis h", + "ith i", + "ĠMat lab", + "w orm", + "Ġprote omics", + "Ġinter molecular", + "ĠPet ers", + "Ġmir rors", + "quin oline", + "art ens", + "ĠJew ish", + "k B", + "ĠD egradation", + "Ġrele asing", + "V EGF", + "Ġsub populations", + "ĠTra ffic", + "Ġpro line", + "ĠH f", + "Ġad ren", + "b irth", + "Ġs ender", + "Ġat las", + "Ġwork place", + "Ġreflec tivity", + "ĠEx istence", + "cl s", + "Ġfin er", + "Ġbreast feeding", + "on ectin", + "Ġc ogn", + "ell ate", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ", + "by te", + "Ġsk et", + "N ULL", + "s ystems", + "ĠB ranch", + "ĠPro posed", + "lear n", + "Ġtoler ant", + "Ġver tebrates", + "Ġmulti level", + "ĠPA H", + "Ġaud ience", + "ĠW L", + "nit rop", + "ĠC t", + "Ġsati va", + "e ight", + "Ġme g", + "oc ell", + "Ġst ating", + "dom inant", + "b ytes", + "Ġp u", + "ĠB atter", + "ot axis", + "ĠE BV", + "Ġnanoc rystals", + "Ġmonop ole", + "Ġdia phrag", + "ĠV el", + "Ap pendix", + "at ten", + "im pl", + "Ġland mark", + "encl ature", + "ĠST AR", + "Ġprost agland", + "oprot ective", + "Ġload ings", + "ĠPres ence", + "ĠN SF", + "ress es", + "F U", + "il ers", + "Ġeryth rocytes", + "t rac", + "is lation", + "ĠN ight", + "Ġster oids", + "ti z", + "ĠD MA", + "Ġr ic", + "Ġsal ient", + "ĠF ur", + "spec ial", + "Ġbio informatics", + "ign ant", + "ĠEX PERIM", + "avor able", + "dis k", + "Ġcur riculum", + "imid azol", + "hig her", + "Ġdesign er", + "ĠSt rength", + "Ġcytos ol", + "ĠCh annels", + "L and", + "s par", + "Ex pression", + "Ġday time", + "merc ial", + "v box", + "in ar", + "ie ving", + "ce in", + "ĠNC BI", + "R AN", + "¸ Ģ", + "H ig", + "ĠD HA", + "Ġsub script", + "Ġ ¢", + "or ange", + "Ġknow s", + "ĠN AF", + "pro duced", + "ep id", + "Ġdex amethasone", + "Ġformal dehyde", + "yl l", + "Ġec topic", + "ĠVer ification", + "activ ating", + "ĠI G", + "ĠP av", + "Ġtra ding", + "Ġgrad uate", + "ĠF IR", + "enc il", + "ever y", + "Ġradi ological", + "ĠMamm alian", + "M ES", + "in ium", + "ĠS AS", + "ĠW H", + "Over ride", + "ĠSched uling", + "ĠB es", + "ĠY ao", + "Ġgl ad", + "ĠStandard s", + "Ġprov inces", + "en ers", + "Ġn r", + "Ġtrans pos", + "ĠCar ib", + "Ġfa una", + "um i", + "res et", + "Ġsup ra", + "Ġdiv isions", + "Ġbiod egrad", + "metric s", + "og rafts", + "Ġfunc tors", + "Ġsup portive", + "Ġcaud al", + "Ġexer ts", + "Ġc ub", + "od imer", + "Ġair borne", + "Ġdeliver ing", + "Ġmultiv ariable", + "Ġfurn ace", + "Ġremn ant", + "Ġinc o", + "ĠElect romagnetic", + "m apping", + "Ġdecl ines", + "c old", + "ĠS eed", + "con version", + "Ġglyc ogen", + "d T", + "aw i", + "AP P", + "H ol", + "ataly sts", + "ĠSat ellite", + "gar is", + "c ard", + "ĠBre ak", + "ĠAgain st", + "d dot", + "Ġpr uning", + "ĠCa enorhabditis", + "Ġsucceed ed", + "ub ert", + "ĠÏ ħ", + "ID s", + "Ġasympt otics", + "Ġauto anti", + "ĠScal ar", + "Ġnemat ode", + "h d", + "Ġg yn", + "ist ocene", + "Ġunderg round", + "ĠEth ical", + "Ġs ial", + "ĠM igration", + "cop e", + "Ġstig ma", + "Ġele ven", + "Ġcolor ing", + "in itions", + "ĠJ ay", + "ob a", + "ĠL DA", + "Ġbuild s", + "g ences", + "ĠEc ology", + "schem e", + "ĠUltr as", + "Ġmedi ation", + "ĠTa q", + "Ġf lying", + "ĠEqu ilibrium", + "ophosph ate", + "ĠArgent ina", + "ps ia", + "tt es", + "Ġdispar ity", + "Ġadver tis", + "agg reg", + "I SA", + "od em", + "ĠR ational", + "Ġsil ent", + "divid ed", + "P an", + "J A", + "cl aim", + "Ġradio active", + "Ġp ink", + "Ġcon verse", + "ĠM ell", + "en ib", + "rus kal", + "sl ope", + "hen ol", + "ĠP on", + "par tition", + "SM GR", + "tit led", + "ĠInter ference", + "t osecond", + "Ġse q", + "Ġtrans itive", + "ĠW id", + "review ed", + "× ¥", + "ĠV C", + "rec all", + "ogene ic", + "ĠOverex pression", + "Ġcom mitted", + "Ġsyn apse", + "Sh ort", + "ĠNeut ral", + "ic les", + "IS M", + "Ġintr insically", + "Ġmicros atellite", + "R N", + "ĠâĪ ĥ", + "det ection", + "Ġcod imension", + "Ġdrawback s", + "ĠTurn er", + "Ġsputter ing", + "Ġdis mut", + "Ġhyp ogly", + "Ġspe ak", + "J D", + "Ġs ul", + "Ġperin atal", + "Ġin k", + "ies t", + "Ġoffic ers", + "tic k", + "Ġre taining", + "ĠN ET", + "Ġexchang es", + "Ġany one", + "ĠEnd othelial", + "s end", + "in jection", + "ĠPer u", + "Ġcl ades", + "uct uations", + "Ġsulph ate", + "pi o", + "Ġphys i", + "ĠMi y", + "ĠB AS", + "ari us", + "Ġlip opolysaccharide", + "Ġneurode generation", + "ĠTurk ish", + "Ġo phthal", + "Ġac ted", + "ent re", + "Ġsh aking", + "Ġchlor oplast", + "ĠS id", + "regn ancy", + "as ion", + "ĠH s", + "Ġiniti ating", + "Ġflex ural", + "Ï ª", + "Ġpar ac", + "Ġinter layer", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠĠĠĠ", + "c ause", + "rac tions", + "Ġval uation", + "SY SMGR", + "ĠGarc ia", + "ar rays", + "Ġcast ing", + "ĠP FN", + "ĠL anc", + "ĠGl ob", + "Ġd enti", + "Ġport folio", + "ĠHol ocene", + "ĠMAT ERIAL", + "Ġs arc", + "L ear", + "Ġt in", + "ĠC lear", + "bel ow", + "Ġadv ection", + "Ġoverl aps", + "Ġarth roplasty", + "comput e", + "Ġglycol ysis", + "he pt", + "lor a", + "f rames", + "ĠH ern", + "pro to", + "Ġsw ine", + "Ġje jun", + "Ġrepe ating", + "ancre atic", + "ĠColl ins", + "ĠPrinc iple", + "Ġnan of", + "Ġadj acency", + "Ġsyn ov", + "che t", + "ĠAl most", + "Ġintr usion", + "Ġechocardi ography", + "lif eration", + "Ġquies cent", + "ĠM uk", + "Ġlife times", + "grad ed", + "Ġoverw hel", + "z el", + "Ġnit ride", + "Ġdisturb ed", + "Ġfast est", + "gra bility", + "Ġtoler ated", + "f rag", + "ĠExt ension", + "ano ate", + "ifer ous", + "Ġhydro dynamics", + "IO NAL", + "ĠT oday", + "ĠExp ansion", + "Ġven om", + "ĠHep atitis", + "ñ o", + "on ation", + "syn uclein", + "Ġbasket ball", + "cl usions", + "Ġsett led", + "I QR", + "ĠC ra", + "Ġautom ation", + "ĠHealth y", + "ĠPortug uese", + "ĠAb elian", + "Ġg ad", + "ĠH G", + "ĠR oth", + "Ġcons ume", + "F G", + "in als", + "ĠM CMC", + "Ġpregn ancies", + "D ES", + "por tional", + "ĠBi ochemical", + "Ġmiss ions", + "ĠAnti body", + "ĠB CG", + "ĠL AS", + "mar ine", + "D MA", + "Ġl ongevity", + "ĠD ry", + "ĠR ao", + "Ġinterfer ometer", + "Ġdiscre tized", + "osens ory", + "s it", + "et ta", + "tain er", + "other wise", + "AK T", + "ĠFac ulty", + "Ġas certain", + "ĠSim ulated", + "Ġpay load", + "O UT", + "Ġsuff ers", + "Ġt ungsten", + "ĠAn xiety", + "ĠHeter ogeneous", + "ling ual", + "Ġphe rom", + "b ors", + "l inux", + "Ġmon key", + " £", + "ur l", + "ĠAc ross", + "ĠAK I", + "Ġop p", + "ocal ization", + "Ġmorph ogenesis", + "g ic", + "ĠP CM", + "Ġolig omers", + "Ġexhaus tive", + "ĠG IS", + "Ġpr istine", + "ĠAc tiv", + "ĠSc ilab", + "ĠAc oustic", + "ĠP ick", + "integr al", + "Ġphilos ophy", + "ĠD eng", + "ĠH ab", + "sc ape", + "ĠEmerg ency", + "Ġe pi", + "ĠB ET", + "ric ket", + "Ġann ulus", + "Ġlys osomal", + "Ġstrand s", + "C AP", + "ĠAmin o", + "ĠSt ri", + "epend ence", + "Ġfoot print", + "ĠFat ty", + "ĠN az", + "n est", + "ĠEx plicit", + "plan etary", + "le ad", + "Ġg rip", + "ne ed", + "AT T", + "ER V", + "ĠTarget ed", + "CR P", + "Ġparam agnetic", + "ĠT yr", + "ĠMicro RNA", + "h line", + "g h", + "p it", + "ĠIs olated", + "ject ory", + "Ġclean ed", + "ost e", + "Ġpath ologies", + "prop ylene", + "ĠRe ason", + "ĠIN FO", + "RA Y", + "Val ues", + "Ġal ive", + "Ġbi of", + "ew icz", + "Ġcrack ing", + "go ogle", + "lock ed", + "c rop", + "ec a", + "ur ane", + "SV M", + "ut ta", + "ĠMet ric", + "ĠEn cycl", + "ĠMod ule", + "Ġwarrant ed", + "Ġmulti disciplinary", + "ĠEl astic", + "lab elled", + "ĠSchwarz schild", + "ĠP CC", + "ma jor", + "v ideo", + "Ġst oring", + "ĠM ake", + "ak o", + "ĠJ ia", + "Ġtor oidal", + "ĠH MM", + "Ġmask ing", + "Ag ain", + "Ġneph ropathy", + "g f", + "Ġdom inating", + "er kin", + "ĠFabric ation", + "ĠF el", + "DE F", + "c ulture", + "ĠI ra", + "ĠRE G", + "iling ual", + "Ġm uss", + "pl ain", + "z h", + "ist on", + "ĠÎ ¥", + "min imal", + "c mp", + "Ga N", + "Ġmonot onic", + "Ġinv olution", + "Ġwh atever", + "ĠInstr ument", + "im ple", + "ĠPC I", + "ĠNe uronal", + "Ġfac ets", + "Ġhemod ialysis", + "ap atite", + "ĠK il", + "ont ally", + "Ġinser ting", + "ĠR IP", + "Ġconn ective", + "ĠFed eration", + "n ut", + "ĠG un", + "inu ous", + "M or", + "ĠW isconsin", + "Ġmus h", + "IT S", + "Ġe ject", + "ĠB PS", + "ĠH orn", + "ĠEmbed ding", + "Ġr aces", + "ĠJ am", + "Ġpost ure", + "ĠIn vol", + "ĠIMD b", + "ĠP lease", + "pro portion", + "ĠInter leukin", + "Ġar te", + "Ġsub sp", + "oder ma", + "F ind", + "im it", + "ĠCl in", + "H el", + "FI LE", + "orig inal", + "erv oir", + "Ġple ural", + "clip se", + "enc er", + "in aries", + "Ġv ictory", + "Ġinvestig ates", + "ĠImport ance", + "ĠM IN", + "Ġphon ons", + "integr ated", + "Ġex changed", + "ys tis", + "Ġmig rate", + "R ob", + "el and", + "pro of", + "ĠIntegr al", + "Ġmerg ers", + "Ġpolyphen ols", + "ĠF ully", + "Ġu ro", + "Ġhom ogenous", + "Ġrecogn izing", + "ĠSign als", + "v at", + "ig ms", + "Ġaccur acies", + "Sub stituting", + "Ġpoison ing", + "Ġsh rimp", + "ĠH ölder", + "ĠTanz ania", + "J S", + "M ENT", + "ĠTop ology", + "Ġin vers", + "ĠD U", + "Ġun iaxial", + "ĠS EC", + "par ty", + "Ġcontroll able", + "Ġf um", + "os tics", + "Ġmanif ested", + "Ġpropag ated", + "Ġsuff ix", + "ĠC AN", + "ĠP ret", + "ke eping", + "Assum ing", + "Ġs uture", + "Ġp est", + "Ġg amet", + "ĠAl ignment", + "esare an", + "t um", + "Ġref ine", + "Ġpop ulated", + "Ġest u", + "ĠDef ense", + "ĠPri vacy", + "ĠWe in", + "ĠSen ate", + "Ġazim uth", + "ĠProf essional", + "Ġlab our", + "Ġsem inal", + "ĠInter vention", + "ĠOl der", + "A U", + "W ind", + "d ynamical", + "ĠV eter", + "aci ón", + "Ġco oking", + "Ġâī ª", + "Ġbe ad", + "Ġdens ely", + "Ġpall iative", + "m ort", + "ĠA AV", + "ĠR yan", + "P rim", + "g alax", + "mu ir", + "st ers", + "ĠSal t", + "quee ze", + "ĠPlate au", + "Ġ í", + "Ġl ighter", + "ord inary", + "formal dehyde", + "ĠW er", + "Ġb ark", + "Ġhomogen ized", + "Ġpyram idal", + "Ġin ert", + "ĠA PC", + "ĠMic ros", + "ĠProte obacteria", + "ĠPur ification", + "Ġparametri zed", + "Ġ ille", + "acc uracy", + "embed ding", + "Ġtough ness", + "Ġis ometry", + "back s", + "ĠF IG", + "ĠR on", + "ĠE SP", + "Ġmicrogl ial", + "inter p", + "ĠIntegr ating", + "ĠReduc ing", + "Ġhe arts", + "Ġserious ly", + "Ġspec ially", + "CT RL", + "ĠSur prisingly", + "Ġhyper plane", + "pol ynomial", + "Ġrecon c", + "Ġpharmacokine tic", + "M art", + "ĠB right", + "m able", + "Ġion izing", + "Ġtr ich", + "zym atic", + "Ġlept ons", + "et ting", + "ĠH ex", + "Ġneu rop", + "Ġadip ocytes", + "Ġro ds", + "Ġsuper critical", + "Ġsuc cin", + "Ġan ter", + "ĠN AC", + "ĠSub sequent", + "IG H", + "Ġs outheast", + "Ġend owed", + "Ġconver ging", + "Ġspati o", + "Ġcele br", + "hel ix", + "Ġaccess ions", + "Ġimmobil ization", + "ĠE Q", + "sp atial", + "Ġinform al", + "Ġd ere", + "ĠEn zyme", + "ĠB BC", + "ĠE PR", + "Ġelect rically", + "Ġleuk ocytes", + "Ġal anine", + "Ġmit ogen", + "Ġintram olecular", + "ĠN I", + "Ġpro kary", + "IS O", + "Ġd odec", + "ĠTra de", + "ĠD ai", + "cc c", + "ĠWal ter", + "ĠNe ither", + "Ġvul garis", + "Ġlong itude", + "ĠInt ro", + "op tion", + "ĠQ C", + "Ġâ Ŀ", + "prot ection", + "ĠI MF", + "ap rote", + "Ġlink er", + "Ġfound er", + "Ġaspir ation", + "clust ers", + "ĠP ay", + "ĠR oles", + "Ġac yclic", + "over ing", + "Ġrem ind", + "ĠT ong", + "ĠAt ten", + "Ġengine ers", + "Ġdys regulation", + "ĠFour th", + "Ġfil ename", + "ĠCo ol", + "prot ected", + "Ġnil potent", + "ĠH K", + "cl one", + "ĠSt adium", + "a is", + "os amine", + "AB ILITY", + "rov ascular", + "ĠA H", + "ĠCon cept", + "Ġcereb rospinal", + "ow itz", + "Ġresol ving", + "Ġw ings", + "ĠE GF", + "ĠCom mand", + "iaz ep", + "Ġbe ef", + "Ġsp ines", + "Ġprior ities", + "Ġattempt ing", + "Ġtel omere", + "B QU", + "Ġviol ations", + "L B", + "om nia", + "os m", + "ir q", + "Ġdivers ification", + "al t", + "ĠB RAF", + "Ġorgan isation", + "di e", + "Ġaut oreg", + "ick ed", + "ĠEc ological", + "ĠT rain", + "ĠP Y", + "Ġmusc uloskeletal", + "Ġhoriz ons", + "Ġo mega", + "Ġquas ars", + "ep tion", + "Ġer ad", + "Ġlum inal", + "Interest ingly", + "Ġpay ment", + "c nt", + "Ġdi pl", + "Ġrecogn ised", + "C at", + "ĠCh l", + "Ġmill ions", + "Ġdisappear ance", + "G AP", + "Ġradi ographic", + "Ġpost partum", + "develop ed", + "x ual", + "Ġhe d", + "id ered", + "ĠC ertain", + "Ġdys plasia", + "____ ____", + "ĠHal f", + "Ġas ymmetries", + "ĠAl cohol", + "S um", + "Ġf m", + "Ġch ap", + "Ġpre treated", + "ĠGall ery", + "Ġoutper form", + "Ġbreed s", + "Ġt ied", + "Ġdiffe omorphism", + "Ġcaus ative", + "Ġcollec tively", + "Ġsub optimal", + "Ġins ulation", + "Ġmanip ulate", + "Ġkil omet", + "Ġrep ulsion", + "Ġchloro form", + "Ġbe an", + "Ġhe ro", + "rophys ics", + "ĠP eptide", + "Ġout lier", + "Der ived", + "iss er", + "ĠInf ant", + "sulf onyl", + "Ġrecurs ively", + "H u", + "ĠK oh", + "pyrid ine", + "Ġs quad", + "Ġth irty", + "Ġsp oken", + "ĠZ ar", + "other mic", + "Ġcalc ification", + "ĠHels inki", + "Ġbe ach", + "ĠF DR", + "Ġprob iotic", + "Ġfin ishing", + "ymmet rical", + "Ġvac ancy", + "Ġthrom bo", + "Comp ared", + "A ST", + "st ed", + "othe rap", + "Ġiod ide", + "Ġt t", + "al ignment", + "Ġmicro vascular", + "Ġinitial ize", + "ĠANAL YSIS", + "Ġtop ographic", + "ĠReport ing", + "Ġunderestim ated", + "put ed", + "Ġatheros clerotic", + "Qi agen", + "g ut", + "ĠCor tical", + "Ġdisrup t", + "es te", + "Ġgl ue", + "Ġnarrow er", + "Ġin patient", + "Ġsch olars", + "Ġb c", + "ĠPsych ological", + "ĠHamilton ians", + "Ġhon or", + "tib ular", + "Ġinser tions", + "oscop e", + "Ġpharmacokine tics", + "Ġmathem atically", + "Ġfor k", + "ip ital", + "ĠAr gs", + "abol ism", + "Ġâİ ł", + "ĠRob ot", + "ĠC asc", + "Ġle aching", + "ĠL ack", + "Ġend ocytosis", + "Ġtr is", + "Ġsensiti vities", + "Ġlic ensed", + "Ġsp onge", + "carbon yl", + "fe at", + "Ġpre cl", + "Ġwa ist", + "tif ications", + "Ġol iv", + "b inary", + "at ri", + "ĠBi ot", + "T Z", + "Ġf ake", + "ĠM osc", + "ĠH PS", + "ĠVol tage", + "Ġâİ Ŀ", + "ĠAh med", + "ĠSex ual", + "dehyd es", + "ĠC ot", + "Ġmag ma", + "oxyl in", + "Ð Ī", + "amet hyl", + "ĠL OS", + "di phenyl", + "experim ental", + "Ġpluripot ent", + "agit tal", + "w alk", + "Ġplas monic", + "Ġcontrac ts", + "Ġexp ed", + "ĠArab ia", + "Ġshoot s", + "ĠR AN", + "ustr ated", + "Ġconvex ity", + "Ġm J", + "ĠAbs olute", + "ĠS EL", + "MI P", + "ĠAct ually", + "so le", + "Q I", + "ĠTGF β", + "Ġâİ ŀ", + "Ġrearrang ements", + "Ġc uring", + "exp ensive", + "cepti bility", + "Ġour s", + "ĠKid ney", + "Ġassign s", + "Ġvox els", + "ore al", + "Ġeven ing", + "h us", + "Ġ ãĢ", + "or adi", + "ĠCor rection", + "Ġnanofib ers", + "Ġcan tile", + "big oplus", + "umin ous", + "eclam psia", + "ĠC ult", + "EC H", + "at ology", + "Ġj i", + "cr yp", + "ĠAsp ects", + "en i", + "Ġsem is", + "IR S", + "ĠP ho", + "enc oding", + "ĠJus tice", + "ococc i", + "Ġhypoth alamic", + "ract able", + "ĠOr b", + "Sim ons", + "Ġmanip ulated", + "att ribute", + "on ov", + "or ously", + "end ar", + "ud er", + "ins ert", + "Ġlys ed", + "ĠHod ge", + "Ġfootball er", + "Dev ice", + "ĠLe ast", + "Ġstrat um", + "Ġmit ral", + "Ġs ell", + "ĠM uc", + "gly cer", + "o j", + "Ġpathogen icity", + "ĠDecl aration", + "op ause", + "ĠAr ticle", + "Ġrins ed", + "ĠLé vy", + "re ment", + "Ġan ts", + "ĠD ic", + "Ġk Pa", + "ur ry", + "mo tion", + "cl ient", + "Ġaccess ory", + "Ġdepolar ization", + "nam ely", + "Ġdispar ities", + "Ġfavour able", + "ĠTib et", + "Ġo ocyte", + "ist ration", + "Ġun resolved", + "cri ptive", + "phys ics", + "Ġben zo", + "Ġcrystall inity", + "Ġpay off", + "Ġumb ilical", + "os il", + "ĠSystem ic", + "ĠST M", + "Ġstabil izer", + "U SA", + "ĠJ ensen", + "A ug", + "ĠH at", + "AG G", + "under brace", + "Ġmanip ulations", + "ĠM anc", + "ned y", + "Ġscr atch", + "C herry", + "osacchar ides", + "Ġprecipit ate", + "quar ters", + "ic ul", + "Ġoptim ally", + "man y", + "Ġneoplas ms", + "Ġin ward", + "ary ng", + "Ġm oll", + "ĠW el", + "ĠW iley", + "Ġnewsp aper", + "Ġinhabit ants", + "ĠS uccess", + "Ġbrid ging", + "Ġdis connected", + "Ġhygi ene", + "D ist", + "Ġsc ripts", + "Ġmes oporous", + "Ġrestric ts", + "act one", + "Ġaqu ifer", + "ĠïĤ ·", + "Ġp lex", + "Ġpresum ed", + "Ġ ips", + "ĠM ilitary", + "Ġjud ged", + "Ġal d", + "Ġsequ est", + "comp ared", + "UL ATION", + "adap ted", + "Ġinstruc ted", + "p ulse", + "Ġc usp", + "mat ching", + "car rier", + "Ġenfor ce", + "ĠInter view", + "ometric s", + "Ġnull ptr", + "Ġflav our", + "ĠPare to", + "ĠB ER", + "Ġu v", + "Ġcr ash", + "ĠC ann", + "ĠMin eral", + "ĠOlymp ic", + "Ġpolyc rystalline", + "le tt", + "T ables", + "requ ent", + "Ġsed entary", + "uns aturated", + "ĠBern oulli", + "Ġad missions", + "itor ial", + "ac ute", + "Ġad ditions", + "we et", + "AL E", + "ĠMan ip", + "tok ens", + "prec ed", + "d k", + "cons ider", + "Ġïĺ ¹", + "Ġwr ites", + "car dia", + "ct omy", + "omat ous", + "S ymbol", + "ust en", + "Ġprote olytic", + "c ategories", + "Ġf ic", + "Ġsw ing", + "Ġpass enger", + "Ġoverl apped", + "if i", + "Ġmut ational", + "ĠJoseph son", + "Ġreg ret", + "ĠAr k", + "ĠCF D", + "Ġman eu", + "enc oded", + "texts c", + "Ġdecom positions", + "ĠDe b", + "Ġmand ibular", + "d U", + "ĠP IC", + "Ġtranscript omic", + "Ġtel escop", + "ĠSant os", + "o E", + "ĠM CP", + "Ġind igenous", + "Ġmicrosp heres", + "Ġcod ew", + "z ip", + "Ġfing ers", + "Ġcampaign s", + "¸Ģ ł", + "Ġacc idents", + "ĠTo ols", + "Pl anck", + " »", + "ed er", + "ing ham", + "oxid ase", + "Ġancest or", + "wh ose", + "Ġphosph olipid", + "Ġconvers ation", + "ĠH of", + "cor tical", + "gly cos", + "Ġmanufacture rs", + "op ulmonary", + "Ġincl ined", + "ĠBet he", + "Ġsp ending", + "ĠFus arium", + "u itively", + "Ġfem ur", + "ĠL inks", + "Ġnit rite", + "M ain", + "Ġfl ora", + "ĠPh D", + "ĠWr iting", + "ĠHess ian", + "Ġμ s", + "ool s", + "Ġvictim s", + "ĠR ew", + "ans en", + "E ar", + "Ġor n", + "Ġthermo electric", + "EN SE", + "ĠWeight ed", + "h oles", + "Ġc en", + "Ġac uity", + "Ġvac ancies", + "ĠDu ke", + "Ġpac litaxel", + "Ġconver ts", + "bour ne", + "ĠA CS", + "os i", + "Ġcrim inal", + "ĠI b", + "un es", + "ĠNan oc", + "P ost", + "ĠMD S", + "Ġecon omics", + "Ġthough ts", + "Ġneuro protective", + "Ġinters ects", + "c ers", + "at id", + "us a", + "ĠAn s", + "Ġafter wards", + "ĠOF DM", + "ĠCM V", + "ĠC um", + "AT G", + "ĠImage Net", + "ĠAtt ack", + "ogene ities", + "Ġcouns eling", + "ĠCON TR", + "á lez", + "ĠD h", + "ĠG V", + "Ġposition al", + "Ġg ang", + "ĠInter active", + "w ig", + "ĠT race", + "ĠD SS", + "Ġsynthet ase", + "ĠGal ile", + "us ually", + "ĠB ass", + "ard less", + "Ġexec uting", + "K P", + "ĠNep al", + "RE AD", + "ĠL ock", + "oh ydro", + "rot ation", + "d il", + "roscop ically", + "re perfusion", + "Ġdis hes", + "ĠProceed ings", + "ĠN PC", + "Ġmon soon", + "ĠLem mas", + "ĠChand ra", + "Ġre actors", + "Ġtr yptophan", + "ĠV T", + "ĠD EM", + "Ġleg islation", + "m k", + "Ġtor ic", + "ĠProgram s", + "ĠPub Med", + "Ġr DNA", + "Ġpost s", + "Ġâİ Ľ", + "Ġshed ding", + "toler ant", + "Ġv oids", + "ĠCarib bean", + "C ODE", + "T ube", + "AL SE", + "Ġchlor ine", + "Ġco erc", + "ĠRh iz", + "ĠKir k", + "Ġà ĸ", + "ro ut", + "ic ides", + "ag u", + "ĠK w", + "Ġcr u", + "Obs erve", + "ĠRe vis", + "Ġan onym", + "Ġpre requ", + "ocor tical", + "Ġrest aur", + "ĠPop ulations", + "d st", + "Ġfor t", + "reg s", + "ĠPolar ization", + "Ġpancre atitis", + "a ph", + "th reat", + "ft en", + "ĠAl aska", + "ĠFlex ible", + "Ġreperto ire", + "k an", + "math choice", + "Ġmit osis", + "Ġe at", + "ut in", + "Ġr t", + "Ġd ummy", + "ĠC ys", + "ĠG or", + "ear chers", + "H PLC", + "Ġb ay", + "ĠNi elsen", + "ĠR oc", + "ian i", + "ic it", + "rag ue", + "Ġcour ts", + "test ing", + "Ġampl ify", + "Ġtu ples", + "prol iferative", + "ĠPar as", + "Ġmagn ets", + "Ġchem okines", + "ĠMit chell", + "ĠPet ri", + "hol tz", + "y ch", + "mat rices", + "Ġcorrec ting", + "ĠPC a", + "ynam ically", + "ĠNAF LD", + "Ġeff luent", + "it um", + "Ġth rows", + "ĠGu id", + "och romatic", + "ĠF ro", + "id ad", + "rom agnetism", + "H erm", + "ĠS pi", + "ĠQu as", + "dom ains", + "Ġquad rant", + "ĠSO X", + "ĠGover nor", + "Ġam enable", + "he ld", + "ĠC ul", + "Ġunder water", + "ĠK ron", + "ĠSp ati", + "ano yl", + "C U", + "ov ir", + "Ġdem ographics", + "With in", + "ĠM é", + "texts f", + "ĠLab el", + "Ġgenu ine", + "Ġh ill", + "ĠL az", + "Ġt esticular", + "ĠB row", + "IC ATION", + " ¡", + "ĠA IC", + "anc omycin", + "str ual", + "Ġarrest ed", + "ĠS om", + "ĠI HC", + "ĠP ose", + "ĠM ö", + "ist ar", + "ĠP AM", + "ĠH CT", + "Ġtyp edef", + "ĠMor se", + "ĠLe ishman", + "lim b", + "Ġsphe roid", + "os ely", + "ĠGu inea", + "re new", + "Ġpsori asis", + "ist a", + "ĠCh ung", + "orth ogonal", + "ĠShe ar", + "ĠMus lim", + "ĠP ict", + "In teger", + "Ġspac er", + "L y", + "Ġd ermal", + "Ġonc ology", + "Ġd p", + "Ġphot oluminescence", + "reg on", + "amin ase", + "ĠẠĭ", + "Inst ance", + "ver b", + "Ġmethyl ated", + "ĠG em", + "ist ently", + "ĠMg Cl", + "ĠEle vated", + "⣠©", + "onstr uct", + "Ġsnap shot", + "en em", + "ĠD isk", + "Ġhydro static", + "Ġïĥ ª", + "v or", + "ĠI E", + "ĠL Y", + "OR F", + "Ġfo il", + "m ale", + "Ġdepend ed", + "s parse", + "Ġmet as", + "Ġtext ures", + "Ġstack s", + "M Hz", + "Ġf n", + "Ġult rac", + "ĠSh ould", + "V ec", + "n ine", + "inf inite", + "ĠLaw rence", + "ĠInvent ory", + "ĠPro state", + "Ġgest ure", + "ĠSuz uki", + "A bs", + "ric ane", + "ĠPeriod ic", + "M yc", + "if iable", + "Ġin efficient", + "Ġcoll apsed", + "Ġtopological ly", + "Ġprefer able", + "Ġbronch ial", + "ust on", + "Ġflex ion", + "our ney", + "trans lation", + "Ġepit axial", + "Ġirradi ance", + "Ġneighb ours", + "sw itch", + "Ġactu ators", + "S OD", + "m ir", + "di es", + "ik awa", + "ĠAL L", + "ĠR SV", + "ĠH EP", + "Ġend urance", + "conn ection", + "Ġgest ures", + "odon tic", + "ĠUn c", + "Ġdismut ase", + "H aving", + "m ix", + "Ġneuro genesis", + "Ġmyocardi um", + "ĠRuss ell", + "H ist", + "ĠS PI", + "tri azol", + "ag ulant", + "ĠRe quired", + "Ġsh RNA", + "ĠArth ur", + "Ġspaw ning", + "d ried", + "Ġrec tif", + "Ġà ī", + "Ġoste ogenic", + "re place", + "Ġgain ing", + "Ġneutral ization", + "ĠHart ree", + "Ġfollic les", + "Ġrelig ion", + "Ġd uplex", + "Ġtrans ients", + "amp ed", + "Ġmicrotub ules", + "int erest", + "Ġste els", + "B atch", + "Ġden aturation", + "ĠPhill ips", + "Ġqu iet", + "ĠB ureau", + "ĠR are", + "Ġqu ercetin", + "a ults", + "Ġel ution", + "uk a", + "ĠInter pretation", + "R V", + "ĠE SC", + "ĠK om", + "are ttes", + "Ġï ģĦ", + "Ġtra dition", + "Ġdiss ected", + "Ne igh", + "Ġshe aves", + "Ġbelong ed", + "ĠHist oric", + "ĠO E", + "Ġj son", + "lem ma", + "ĠY AP", + "ode xt", + "inter face", + "Ġextrem ity", + "cross ing", + "preced ented", + "acc ording", + "Ġconstruc tive", + "ĠStim ulation", + "ĠHF D", + "Ġwaven umber", + "Ġh rs", + "Ġpapill omavirus", + "Ġvom iting", + "Ġre activation", + "omet rically", + "ĠDim ensions", + "ob jects", + "ort on", + "ĠMat hem", + "ĠOl ive", + "Ġcros stalk", + "par tite", + "opath ies", + "ĠCN Ts", + "rous al", + "Ġcrow d", + "ĠLang muir", + "ĠT ox", + "echan ics", + "im us", + "ĠSh ock", + "tan h", + "ĠBrill ouin", + "Ġtransf erring", + "Ġellip se", + "ĠAd dition", + "ĠR ural", + "Ġgeodes ics", + "G EM", + "ĠP OS", + "ĠM ission", + "oc arp", + "ĠJ ane", + "L ie", + "f req", + "op ot", + "ĠV ibrio", + "ĠOb j", + "er ts", + "ĠTri als", + "C FT", + "ĠC odes", + "μ g", + "Ref erence", + "ĠF ung", + "ĠSup pression", + "h og", + "Ġresis tive", + "C hi", + "int ered", + "Ġpost menopausal", + "St atistical", + "ĠEd wards", + "Ġs es", + "Ġfarm ing", + "quar tile", + "cool ed", + "Ġnan op", + "ĠProb ing", + "ĠBern ard", + "un i", + "ie ties", + "ĠMark et", + "os um", + "ĠM essage", + "Ġaxi om", + "c g", + "ĠM oving", + "Res olution", + "Ġadsorb ent", + "Ġmult in", + "Ġin effective", + "prop ag", + "hard t", + "S aharan", + "W il", + "ĠI van", + "ir ubin", + "Ġtra bec", + "all i", + "ĠCD Cl", + "Ġse w", + "ĠIs s", + "Ġagg ression", + "ĠJ uan", + "Ġdispers ions", + "Ġaux in", + "F ET", + "l p", + "re ach", + "ĠP GE", + "che str", + "Ġlect ure", + "ĠD onald", + "sl ip", + "ĠHb A", + "ĠSec ure", + "ĠBe h", + "Ġdam ages", + "W H", + "alk yl", + "H a", + "ĠTh anks", + "Ġsensiti zation", + "Ġwat erm", + "Ġtw ins", + "Ġcultiv ar", + "Ġze olite", + "V ariable", + "ĠB ent", + "Ġanti sense", + "ĠHans en", + "reprene ur", + "ĠSN e", + "ĠEM G", + "Ġre acted", + "Ġover flow", + "Ġformal in", + "ĠUs ually", + "olyb den", + "Ġac ad", + "AT URE", + "Ġwavegu ides", + "Ġch unk", + "Ġmod ifies", + "Ġer yt", + "ĠZh ong", + "Ġgran ule", + "Ġc s", + "ĠGra de", + "Ġland marks", + "ur istic", + "Ġam ines", + "ĠIntr insic", + "Ġerrone ous", + "Ġlock down", + "yp ti", + "Ch ild", + "Ġunivers ities", + "Ġparas it", + "Ġign ition", + "T im", + "ar aj", + "ra vel", + "ĠL ands", + "ĠCirc ular", + "Ġrot ate", + "Pati ents", + "ĠW B", + "Ġmyel in", + "ĠWe iss", + "Ġdip olar", + "Ġfollic le", + "ĠWat son", + "ĠIn cor", + "Ġfound ations", + "ĠP ip", + "Ġpress ing", + "Ġforb idden", + "av an", + "Ġm Ab", + "un ion", + "ĠF resh", + "ĠCor p", + "fl oxacin", + "co ordinate", + "Ġsh unt", + "Ġconstit uted", + "anil ine", + "Ġtwe ets", + "ĠCh ow", + "Ġmob ilization", + "zy k", + "E ST", + "ne igh", + "ĠM eng", + "ĠRes Net", + "ĠJ et", + "Ġlumin ous", + "Ġstress ors", + "do es", + "trifluor omethyl", + "Ġconcer t", + "ĠCho ice", + "ph im", + "al coholic", + "oc hem", + "ilt ered", + "Ġpredict able", + "Ġt ran", + "ĠP ra", + "Ġval ves", + "Ġaut onomy", + "reg ulate", + "ĠBe ach", + "ĠOnt ology", + "Ġis ofl", + "Ġqu oted", + "ĠL ex", + "th y", + "Ġcompl aints", + "ĠT rees", + "Ġop posing", + "ĠAcc eler", + "con trast", + "Ġcompet ed", + "O E", + "ĠR oche", + "iss ance", + "Ġpe ace", + "ĠA im", + "Ġinfer tility", + "ĠAntarctic a", + "th ien", + "S umm", + "Ġjudg ments", + "am ides", + "Ġsp ill", + "Ġhere after", + "ĠCons tit", + "comput er", + "Ġbeg un", + "ocent ric", + "Ġp umps", + "med ium", + "ch ol", + "met allic", + "Ġfl ares", + "Ġpet roleum", + "Ġwith d", + "ĠThe atre", + "Ġun labeled", + "Ġregular ized", + "oster ic", + "ĠP FS", + "Ġun em", + "Ġpresent ly", + "Ġbuff ered", + "aff inity", + "ĠDem ographic", + "ĠK ondo", + "Ġcent uries", + "Ġmig ratory", + "aryn x", + "Ass ociated", + "anil ino", + "g rown", + "ĠEx ecutive", + "ĠE k", + "ĠH emat", + "ĠPl ayer", + "ĠCH D", + "f lex", + "ĠS ever", + "alth am", + "im pro", + "an et", + "ocy st", + "ĠA ster", + "CO L", + "ĠSimilar ity", + "ĠHow ard", + "Ġmultic ast", + "ĠEns emble", + "ì Ĺ", + "ol ys", + "ĠGen omics", + "Ġreson ators", + "Ġfist ula", + "on en", + "us ers", + "Ġhyp o", + "rog ens", + "Ġmed al", + "ĠM IP", + "Ġvolt am", + "Ġappreci ated", + "ĠP é", + "ĠGa ia", + "Ġbuck ling", + "Ġcongru ence", + "fur yl", + "ĠEp stein", + "Ġcasc ades", + "g old", + "Ġan hyd", + "Ġgrad uated", + "M emory", + "ĠInd ustry", + "ĠSch neider", + "Ġemploy ee", + "ĠCor n", + "M AC", + "ro ve", + "rop od", + "s ervice", + "ĠOx idation", + "Ġenum eration", + "m ad", + "ĠCl ose", + "ĠMod ular", + "Ġprogen y", + "Ġg t", + "read ing", + "ĠInd ic", + "opath ologic", + "ĠPFN GL", + "X L", + "c is", + "ĠM ike", + "ĠB BB", + "ĠExt reme", + "ĠCho ose", + "Ġhoriz ontally", + "ĠASS ERT", + "Ġglucocortic oid", + "B ay", + "Ġp df", + "Ġcontain ers", + "ĠL OC", + "ĠY ield", + "opro te", + "Ġfruct ose", + "ĠI CC", + "Ġdec id", + "rim idine", + "Ġfrag mented", + "Ġisomorphism s", + "Ð ¼", + "Ġintegr ates", + "Ġfib ration", + "ĠâĬ ¤", + "Ġxen ograft", + "nucle on", + "ĠC SP", + "Ġs ut", + "ĠSp ir", + "Ġdiss oci", + "ĠT BI", + "ĠFor ces", + "Ġhyper surface", + "Ġmy osin", + "ĠQueens land", + "N eg", + "ĠU RL", + "b ind", + "Ap plied", + "ĠD ob", + "ĠK E", + "Ġmem or", + "ĠArab ic", + "ĠL ateral", + "ĠSt art", + "n ose", + "ti bility", + "as ters", + "Ġus ability", + "Ġinc enti", + "ym n", + "ĠAnaly tic", + "P et", + "ĠM ask", + "W orld", + "b rand", + "Ġelim inates", + "Ġmer it", + "ĠPhilipp ines", + "ĠB CL", + "ĠO ri", + "Ġparad igms", + "ĠIn ters", + "riz ona", + "Ġcon ception", + "Ġrel ied", + "ĠJ oe", + "ĠAp ple", + "Ġlight weight", + "mort em", + "ol ig", + "Ġv iz", + "Ġst ones", + "Ġkey words", + "ĠSecret ary", + "T N", + "old er", + "ĠInt estinal", + "Ġpossess ed", + "Ġmonoton icity", + "em itting", + "ĠDef ining", + "ĠPar ticularly", + "Ġautomorphism s", + "Ġeryt hemat", + "ĠW aters", + "ĠCycl ic", + "maxim al", + "xt y", + "ĠS ad", + "Ġur anium", + "Ġhypoth alamus", + "ĠSU MO", + "Ġdeal t", + "Ġk its", + "Ġpain ting", + "ĠS ier", + "ch ool", + "OD O", + "sur faces", + "ĠP neum", + "organ ized", + "ĠC PT", + "Ġins oluble", + "ĠCo herent", + "Ġrecess ive", + "Ġb ivariate", + "Ġed it", + "Ġnation wide", + "M ODE", + "c hest", + "ĠS LC", + "Ġintra peritoneal", + "ĠDis ordered", + "Ġinsu fficiency", + "ie v", + "iaz ole", + "W rite", + "ĠD ATA", + "tor al", + "Ġqual ities", + "Ġpossess ing", + "ĠM ats", + "Ġretin opathy", + "ĠB K", + "Ġnovel ty", + "ce ans", + "Ġreserv es", + "ĠNAD H", + "Ġisother m", + "Ġsoldi ers", + "p b", + "iter pen", + "ĠAg ents", + "z u", + "Ġunw anted", + "Ġhyper parameters", + "ec an", + "ĠS ES", + "ĠF G", + "ĠN avig", + "Ġtriang ulation", + "Ġnetwork ing", + "Ġpoly styrene", + "Ġinduc tively", + "brevi ations", + "Ġneurom uscular", + "ĠL inux", + "stud ied", + "ĠBe ing", + "Ġdef iciencies", + "ĠMat rices", + "Ġwe aring", + "Ġhad rons", + "am yl", + "Ġdisc ourse", + "och lor", + "ĠMel an", + "ĠL an", + "V L", + "Ġmunic ipal", + "Ġenroll ment", + "ĠS ymmetric", + "Ġdiscipl ines", + "ĠBar on", + "Res earch", + "Ġmagne tite", + "om ide", + "polar ization", + "le ys", + "Ġseem ingly", + "hep atic", + "Ġcl o", + "ĠQu atern", + "Ġcompe tit", + "R equ", + "ga uge", + "Ġhydro chloride", + "drop out", + "pan el", + "Ġaspir in", + "ĠR UN", + "Ġrib bon", + "Ġinacc urate", + "ĠP all", + "duc ers", + "Through out", + "Ġcell ul", + "Ġsusp ect", + "Ġalle lic", + "Ġsn ake", + "ordin ated", + "ĠAut ophagy", + "Ġe ig", + "Ġr if", + "ĠKen nedy", + "Ġbot tle", + "ĠY outh", + "aw ed", + "linear ity", + "uk er", + "ĠO X", + "ext ension", + "Ġw ard", + "ĠComplex es", + "Ġbios ensor", + "ĠCart an", + "d n", + "Ġs onic", + "Ġindex ing", + "Ġd v", + "rel iable", + "p k", + "RE NT", + "Ġt anks", + "ĠH et", + "ĠW ing", + "ĠCu O", + "Ġprint f", + "Ġlumin osities", + "c ourse", + "Ġsc ram", + "Ġsam pler", + "Ġmulti pliers", + "Def ault", + "od il", + "int r", + "sequ encing", + "Ġtrans missions", + "ĠWh it", + "ĠOp portun", + "Ġintern ally", + "Ġacknowled ges", + "ĠE dition", + "Ġarter i", + "Ġalb edo", + "ĠNucle otide", + "Ġy es", + "ĠRel ativistic", + "Ġv otes", + "ĠForm ulation", + "usc itation", + "Ġconcurrent ly", + "u in", + "Ġnon invasive", + "Ġprim ates", + "μ l", + "Ġsubt ropical", + "g un", + "ĠS outheast", + "ö n", + "Ġequ ator", + "Ġwork shop", + "Ġsch ist", + "und ant", + "ĠMOD IS", + "t ar", + "Ġa eg", + "Ġplot ting", + "ĠD ET", + "Man ager", + "un ed", + "oxif en", + "ĠIn ver", + "Ġx anth", + "ĠSer ver", + "Ġstret ched", + "Gl obal", + "C ore", + "ĠWe ber", + "y ard", + "Ġexpl ores", + "ĠBi ography", + "SN P", + "ĠNeut rino", + "Ġkilomet res", + "Ġcomm utes", + "Ġaccept ability", + "ĠAntib odies", + "ic ol", + "Ġmus eum", + "Ġden it", + "Ġextrap olated", + "Ġacetyl choline", + "T oken", + "ĠF ock", + "ond e", + "Ġdiscrimin ative", + "ĠM ant", + "Ġess ence", + "cel and", + "ĠCh air", + "Ġintegr ative", + "ĠS PD", + "hen ium", + "arbon ate", + "B ASE", + "reg ulates", + "p atch", + "Ġd ib", + "Ġanti symmetric", + "Ġwear able", + "Ed ge", + "re ts", + "Ġperce ive", + "ĠMagn esium", + "ad ows", + "Ġdis posal", + "Ġair port", + "ause a", + "f its", + "Ġnec ro", + "ĠS IN", + "ĠD uc", + "ĠRe ading", + "b ys", + "Ġreflec tive", + "h is", + "omet ries", + "Ġvi rial", + "Ġartif icially", + "child ren", + "ĠUltras ound", + "VI EW", + "Ġsc ulpt", + "Ġsur f", + "Ġsex ually", + "Ġgeomet rically", + "Ġdivis ors", + "Ġiniti atives", + "acc i", + "Ġkeratin ocytes", + "a R", + "aro t", + "Ġïĥ ¨", + "comput ed", + "ĠTC GA", + "psych ological", + "ĠM AN", + "ĠM PC", + "tic ing", + "lim iting", + "am ins", + "Ġsurfact ants", + "ĠSer b", + "Ġrhyth ms", + "ĠRout ing", + "w ang", + "Ġmicro structures", + "oph ytes", + "Ġanalges ic", + "F OR", + "qu al", + "Ġpubl ish", + "ĠTim ing", + "por ous", + "rang ing", + "er on", + "ĠZ i", + "ĠMarsh all", + "Wid th", + "Ġis omers", + "Ġ ·", + "phen oxy", + "Ġure th", + "ro bl", + "Ġmention ing", + "o zyme", + "ĠL ud", + "Ġop position", + "Ġaband oned", + "Ġrout ines", + "ĠH ST", + "mut ex", + "c oded", + "e ating", + "ter t", + "emicon ductor", + "d w", + "Ġbary ons", + "Ġleuc ine", + "ot ron", + "Ġend os", + "Ġreproduc es", + "Ġanalges ia", + "Ġimmunore activity", + "ĠPre p", + "ĠGarc ÃŃa", + "Ġinco herent", + "an ed", + "le pton", + "and ra", + "ul ae", + "ĠH idden", + "F V", + "Ġgeneral izes", + "ĠSte vens", + "ĠF oster", + "Ġfresh ly", + "Ġh f", + "Den ote", + "o es", + "ĠD in", + "Ġdet ox", + "Ġdec oupled", + "Ġsepar ations", + "ucle otide", + "Ġelect rophysiological", + "ĠBAL B", + "Q TL", + "ĠA Ch", + "ĠRe le", + "que z", + "Mn O", + "ect ures", + "Ġis cha", + "Ġins ulators", + "cell ulose", + "ĠFL AG", + "omb ic", + "ĠUs ed", + "j iang", + "exp ansion", + "ĠRep eat", + "ĠRes erve", + "ab elian", + "ĠH unting", + "G RO", + "ly te", + "ĠB ark", + "Ġcre ative", + "Ġb end", + "el erated", + "dis h", + "Ġhigh way", + "Ġcross ings", + "j ust", + "on o", + "ull ivan", + "ĠDe ad", + "Ġtrade off", + "e on", + "og ical", + "experim ent", + "Ġconf ers", + "ĠD ot", + "Ġco ils", + "Ġax ion", + "ĠIR S", + "ĠÅ ©", + "Ġglac ier", + "ĠMosc ow", + "ĠS pringer", + "Ġinv is", + "ĠArn old", + "Un iversity", + "at tern", + "per or", + "ĠLim its", + "Ġincomp atible", + "r ather", + "ĠT es", + "Ġfail ing", + "Ġthick ening", + "Ġest radiol", + "as se", + "Ġnecess it", + "Ġsacrific ed", + "ĠS ear", + "ĠNor the", + "raise box", + "ĠS low", + "ĠM unic", + "Ġlear ner", + "igen ic", + "Ġderm atitis", + "ut en", + "Ġde er", + "Ġhist amine", + "L at", + "M al", + "il ly", + "Ġge ochemical", + "Ġspermat ozoa", + "Ġv inyl", + "em et", + "Ġeffect ors", + "ĠEncycl opedia", + "Ġord inal", + "Ġcontrovers y", + "ĠPers pectives", + "ovirus es", + "mark ed", + "ĠS PE", + "ĠN utri", + "Ġad here", + "ĠHigh way", + "Ġdistill ation", + "MR T", + "ple tion", + "Ġannih il", + "Ġwave function", + "Ġconfig ured", + "Ġmeth ionine", + "L ow", + "s ensor", + "ĠS now", + "S ample", + "Ġdef initely", + "ĠMet h", + "r ypt", + "Ġprom pted", + "Ġmonol ith", + "ĠEn vironments", + "t m", + "ĠCO D", + "or is", + "equ ations", + "âĺ Ĩ", + "ĠNe ighbor", + "Ġimag ine", + "ĠUs ers", + "ĠCam era", + "ĠMod ification", + "ĠAtt acks", + "Ġinhal ation", + "á º", + "Ġventi l", + "ĠN U", + "ĠCon trast", + "Ġconf ining", + "S ervice", + "W allis", + "ĠA TR", + "Ġsub duction", + "Ġïģ ¢", + "Ġtit ration", + "R oche", + "v iv", + "Ġbe ars", + "bol a", + "Ġblind ed", + "meas ures", + "ĠSt ack", + "occ urrence", + "Ġperme ation", + "l ar", + "ept ors", + "ĠD IF", + "cor rhiz", + "ĠV isc", + "fig urable", + "Ġschedul er", + "Ġoccas ions", + "ambo o", + "Ġam p", + "g ain", + "ĠC it", + "Ġpreced ed", + "Ġtac tile", + "Ġïĥ ¦", + "gener ic", + "Ġretro grade", + "Ġf ans", + "Ġf isher", + "Ġl ights", + "ee per", + "Ġundes irable", + "w ald", + "emb ol", + "Ġwr ist", + "Ġauthor ized", + "Ġchond rocytes", + "ĠE PA", + "ne u", + "ĠOper ations", + "Ġche ap", + "Ġan ionic", + "ĠO regon", + "c ot", + "re ason", + "ex istence", + "ĠFin ancial", + "olybden um", + "c us", + "ĠN ON", + "Ġlock ed", + "B it", + "S il", + "m ixing", + "ĠS ites", + "aprote obacteria", + "ĠIn ner", + "Ġcar c", + "Ġbi otic", + "ĠFl ag", + "Ġmag ic", + "kine tic", + "ic ted", + "Ġbul b", + "sup set", + "pe z", + "deriv ative", + "Ġe IF", + "ĠR ough", + "di rectional", + "ex it", + "ax y", + "xt ures", + "phim urium", + "ĠT Fs", + "ath in", + "Ġor ch", + "Ġspect ro", + "duct ase", + "quin olin", + "Ġgras p", + "Ġpar sing", + "Ġdiffic ile", + "ĠLD H", + "ĠJup iter", + "ĠF IF", + "ĠPri ze", + "Ġinten tions", + "s ession", + "pow ered", + "ĠB am", + "ph asic", + "Ġign oring", + "ĠRichard son", + "princ iples", + "Ġoffic ially", + "C t", + "Ġinc on", + "ĠReg ulates", + "Ġm isc", + "ĠE Z", + "Ġsyn onym", + "Ġunfold ing", + "ĠD EC", + "ĠR X", + "PD F", + "Ġbran es", + "typ ically", + "Ġc ages", + "if olia", + "ug u", + "oll en", + "Ġtable t", + "ĠS ah", + "ĠP VD", + "Ġal ert", + "Ġformer ly", + "ĠKR AS", + "s un", + "Ġde acetyl", + "M er", + "Ġskew ed", + "ĠPle istocene", + "ĠB etter", + "ĠH ud", + "ĠBro ok", + "Ġp ts", + "ĠH U", + "om o", + "ag rass", + "Ġenvironment ally", + "Ġhon est", + "ĠN ine", + "Ġpig ments", + "l inks", + "ĠT OP", + "ĠCytoplas m", + "G ib", + "Ġaccess ing", + "mi as", + "Ġexplos ive", + "Ġres ide", + "art an", + "Ġtransition al", + "Ġun precedented", + "Ġ rom", + "ĠTNF α", + "Ġprecipit ated", + "Ġt ie", + "IS S", + "Ġthick er", + "ĠLat ent", + "ĠValue Error", + "d q", + "d ma", + "Ġchrom atic", + "ĠSub section", + "ĠF ACS", + "Ġrenormal ized", + "P rop", + "m TOR", + "ĠH CO", + "Ġover lo", + "bs iella", + "yl obacter", + "Ġneuro imaging", + "Ġassembl age", + "Ġexp ands", + "Ġî Ī", + "ĠF un", + "Ġc itation", + "IK V", + "Ġtro ops", + "in istic", + "Ġc ubes", + "Ġf ont", + "ĠH os", + "ger ies", + "Ġsuccess ively", + "Ġdeco herence", + "S pringer", + "h in", + "at ine", + "ĠâĪ ¥", + "SA S", + "é t", + "ĠSed iment", + "u ously", + "ĠW ars", + "ind icated", + "Ġfl ask", + "A IDS", + "Ġc ra", + "ĠL ot", + "Ġprim al", + "Ġjus tice", + "z ag", + "Ġmax illary", + "Ġgeneral izations", + "uel a", + "Ġtag ging", + "Ġpup il", + "Ġin expensive", + "Ġw atch", + "ĠA MD", + "ĠF ir", + "Ġneuro blastoma", + "Ġmaxim izes", + "ĠObs erved", + "mi xture", + "Ġopportun istic", + "t rial", + "ah an", + "Ġïģ ¬", + "Ġcat ar", + "ĠControl s", + "ĠNew man", + "Ġmicro structural", + "bor ns", + "Ġoxygen ation", + "ĠMac ro", + "ĠJ ak", + "plic ating", + "Ġolig odend", + "Ġres orption", + "Ġd orm", + "Ġsol vers", + "ĠK ruskal", + "ĠRe volution", + "ĠGast ro", + "Dri ven", + "Ġt iter", + "Ġo ri", + "ĠP CL", + "Ġwet lands", + "Ġar ticular", + "CC A", + "en oic", + "Ġt rick", + "oper iod", + "ĠCoch rane", + "ad ay", + "ĠC erebral", + "Ġmod ulators", + "ĠS SC", + "Ġactiv ations", + "Ġadap ting", + "ĠScal able", + "n one", + "p ip", + "Ġpri vi", + "ĠPseud o", + "Ġdisapp ears", + "ĠE ur", + "Ġuncon strained", + "Ġsub mit", + "Ġrep utation", + "at ar", + "ĠB ai", + "ari ans", + "ĠInt racellular", + "tre es", + "Ġwet ting", + "ĠFran ces", + "Ġel igibility", + "fold er", + "ĠSta ff", + "ok i", + "Ġstrengthen ed", + "ĠC ob", + "ter al", + "ĠY east", + "by e", + "dec oder", + "Ġrain bow", + "perturb ed", + "v c", + "Ġsupplement al", + "Ġbir ths", + "W O", + "con c", + "stit ution", + "hy brid", + "Ġk i", + "Ġhyp ere", + "ĠS MA", + "form ula", + "Ġund efined", + "na phth", + "Ġdecl ining", + "Ġshield ing", + "Y au", + "Ġre ver", + "ĠW ilk", + "Ġdec imal", + "H CO", + "ang ered", + "Ġeryth rocyte", + "ĉĉ ĠĠĠ", + "n uclear", + "Ġabnorm ality", + "P res", + "Par ticipants", + "ĠW agner", + "Ġfibr ils", + "Ġfet us", + "ĠEx press", + "requ est", + "min imum", + "ĠBo oks", + "het amine", + "us hes", + "ĠB ach", + "ĠD OS", + "lect ric", + "ĠTw een", + "ĠHug hes", + "Ġm artens", + "Ġn ematic", + "Ġexperiment ation", + "ĠPark er", + "Ġepis odic", + "Ġte lem", + "AD E", + "col umns", + "Ġfundament ally", + "en et", + "ĠV l", + "ear th", + "Ġquanti le", + "ĠRe plication", + "Ġcle ared", + "En ergy", + "Sm ith", + "Ġantidepress ant", + "m x", + "p mod", + "am id", + "Ġser otype", + "Ġundergrad uate", + "ĠA rizona", + "Ġp ushed", + "ul u", + "ĠN IC", + "Ġrhe ological", + "ome gal", + "ĠQ ing", + "or ch", + "ir med", + "ĠQu ery", + "Ġsand wich", + "Ġclinic ian", + "ĠEllip tic", + "ĠMe h", + "DE V", + "ĠDeterm ining", + "alc ogen", + "b ench", + "az ep", + "ĠMiss iss", + "ti zing", + "ĠR BC", + "Ġofficial s", + "T ag", + "k T", + "lu ence", + "ĠRo om", + "Ġlect in", + "bar a", + "k yl", + "ON D", + "ĠD ose", + "Ġpr ism", + "Ġreduc tive", + "ĠSpect roscopic", + "od ied", + "col one", + "ĠCON FIG", + "Ġbr ittle", + "in verse", + "ĠB uff", + "yt ocin", + "Ġform ations", + "ĠCon ventional", + "pre v", + "Ġferr ite", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ Ġ", + "Ġadop ts", + "ĠMi ocene", + "man agement", + "ĠCR F", + "ĠHel m", + "Ġdoubl ed", + "ĠEFF ECT", + "Ġd ance", + "struc tions", + "ra it", + "if ers", + "ell ip", + "ut ting", + "pro f", + "ĠQ in", + "Ġab sc", + "Ġexplo its", + "Ġcy ber", + "def inition", + "ĠCoron ary", + "Ġdet erg", + "ĠPer ception", + "ĠCur ves", + "Ġnemat odes", + "Ġlist ening", + "Ġcatal ase", + "C oll", + "r é", + "isl ative", + "Ġarri ving", + "Ġviol ating", + "Ð ´", + "he tics", + "ĠJ ar", + "con cept", + "Ġbr ush", + "immun ity", + "Ġfinger print", + "res id", + "Ġelev ations", + "ock ets", + "Ġcatech ol", + "и Ñ", + "Ġprecipit ates", + "Ġsoc cer", + "ins ulin", + "Ġpurs ue", + "ĠI CA", + "ĠPol ice", + "ĠMur phy", + "T ask", + "ĠC oc", + "ĠH abit", + "ĠK P", + "Ġfl oral", + "Ġh un", + "Ġhydrogen ation", + "Ġsp ong", + "Ġch imeric", + "ĠK och", + "g on", + "ĠSch ur", + "ĠGre ater", + "R X", + "Ġc ing", + "ĠW altham", + "ang ling", + "Ġcoun ties", + "Ġlam ina", + "Ġco uncil", + "s ort", + "ĠB arc", + "ĠD ow", + "ĠZ eng", + "Ġdev ised", + "uit able", + "Ġmethyl ene", + "Ġsuperior ity", + "Ġepiderm is", + "Ġp rag", + "ĠP ED", + "threat ening", + "ish i", + "Ġe psilon", + "add ress", + "ENT AL", + "ĠB le", + "ĠAnton io", + "o other", + "ĠAg ar", + "Ġneighborhood s", + "Ġshorten ed", + "ST ATE", + "ĠSer ial", + "M AR", + "O U", + "Ġencaps ulation", + "ĠCons ortium", + "D r", + "pro file", + "Ġem itter", + "Ġnec rotic", + "ĠAut onomous", + "ĠPhosph orylation", + "min im", + "anth in", + "ĠS ph", + "ĠG ur", + "di hydroxy", + "dist ributed", + "ĠRP MI", + "st ones", + "Ġhyper fine", + "Ġis let", + "ĠS lo", + "plet ely", + "Ġin activated", + "ĠAgric ulture", + "Ġtrem end", + "Ġevery one", + "omp onent", + "Zn O", + "MP I", + "ĠDi amond", + "Ġ⣠¨", + "C ost", + "Ġdis abilities", + "in ver", + "ĠC ensus", + "ech o", + "Ġveget ative", + "Ġwilling ness", + "Ġrec ap", + "ĠConst raint", + "ĠP atrick", + "Ġover t", + "Ġmo ieties", + "or ax", + "ip pi", + "Di rect", + "Ġcar ies", + "Ġlocal ities", + "lat tices", + "ĠExpl oration", + "ĠA W", + "Ġloc king", + "Ġcoinc ident", + "Ġmultim edia", + "Ġtempor arily", + "ĠC aus", + "enc ia", + "Ġweather ing", + "ĠHelic obacter", + "ĠTh ings", + "hip s", + "m oving", + "Ġs igmoid", + "is in", + "ĠB ec", + "Ġmicro grams", + "bound s", + "ĠCol umn", + "Ġcommut ing", + "ĠJ en", + "Ġhour ly", + "M SC", + "Ġattend ance", + "ĠâIJ £", + "ĠE O", + "pro g", + "Ġrap amycin", + "ĠPredict ors", + "ĠRetrie ved", + "Ġsub species", + "Ġderiv es", + "ĠÄ ¤", + "ĠGener ating", + "ann ers", + "Ġvol at", + "Ġvis iting", + "ĠCalc ulations", + "ñ a", + "Ġdes ert", + "Ġexpect ancy", + "BM Cs", + "ĠExpl o", + "Ġtrav elling", + "ic um", + "Ġsub division", + "Ġcross linking", + "benz oth", + "ĠT on", + "RE N", + "Ġle th", + "rab bit", + "ĠAb ove", + "ul ted", + "Ġcon stric", + "J ones", + "z hou", + "ver n", + "ĠL ady", + "ĠBu ffer", + "ĠControll ing", + "Ġmulti scale", + "nik ov", + "acy cl", + "Ġprost hesis", + "A f", + "ĠCor ps", + "struc ted", + "G rid", + "in ning", + "old ing", + "Ġthi ol", + "ik ov", + "âĢ¢âĢ¢ âĢ¢", + "Ġgovern ments", + "rap ping", + "Ġthromb ocyt", + "L eg", + "R Y", + "ĠI celand", + "ocy cle", + "ĠMem orial", + "g ot", + "Ġid em", + "ĠBu ild", + "olip oprotein", + "D V", + "Ġph thal", + "rich ment", + "ĠHa em", + "Ġansw ering", + "ĠI J", + "Ġtrans gene", + "Ġre named", + "ĠImage J", + "Ġcass ette", + "Ġcoales cence", + "Ġcomp action", + "Ġwild life", + "Ġw ins", + "Ġsuper novae", + "enter ic", + "isp here", + "Ġtrack er", + "Ġevid ences", + "Ġcom orbidity", + "ĠR ules", + "ph asing", + "ĠLange vin", + "ĠF it", + "Ġpsy chiat", + "Ġbreak through", + "Ġch olinergic", + "ĠMet all", + "bre eding", + "itin ib", + "Ġsol o", + "abl ing", + "eli ef", + "osc ill", + "re v", + "ary a", + "Ġgood ness", + "ĠPB E", + "Ġa wards", + "Ġc rani", + "Ġphot ograp", + "aren ts", + "Ġfix es", + "r ÃŃ", + "ass uming", + "Ġcongru ent", + "ĠM other", + "ĠN ap", + "ĠPro c", + "Ġcategor ization", + "in ch", + "ĠH orm", + "ĠInter ventions", + "Ġnone quilibrium", + "Ġencryp ted", + "prim ary", + "i ens", + "l ac", + "ram s", + "Ġbo ards", + "ĠH ell", + "charg ed", + "Ġperi operative", + "em p", + "ĠInvol vement", + "R uss", + "un ivers", + "ĠD J", + "Ġdisag reement", + "Ġper t", + "Ġstrom a", + "Ġcalc ite", + "Ġrot ary", + "Ġmethyl transferase", + "Ġancest ry", + "ĠW itten", + "CR C", + "ure tic", + "ophy ta", + "provid ed", + "Ġcorresponding ly", + "big cap", + "ĠAg ilent", + "à «", + "ro oms", + "Ġdis ent", + "Ġdil utions", + "ĠMy el", + "Ġquas ar", + "Ġtil ted", + "Ġinternal ization", + "ĠPri vate", + "ĠFried man", + "Ġsevent h", + "ĠCl osed", + "CT C", + "g ren", + "ĠColomb ia", + "od el", + "Ġpoli tics", + "ĠMSS M", + "Ġm ate", + "Ġcom mod", + "ĠR us", + "Ġanest hetized", + "t ogether", + "ĠB CS", + "ew ski", + "romagn et", + "ĠC un", + "Ġcur ative", + "Ġim putation", + "Ġcarb ide", + "D FT", + "ns ic", + "be e", + "Ġspl en", + "ĠMary land", + "Ġoligonucle otide", + "ĠVeg et", + "buff ered", + "N ational", + "le tic", + "ĠS yl", + "Ġse ab", + "ardi al", + "Ġport ray", + "Ġaberr ations", + "Ġst orms", + "ĠSh an", + "ĠGen Bank", + "iss a", + "Ġc et", + "Ġben ch", + "ĠRecommend ations", + "Ġtri ples", + "Ġïĥ ¥", + "ĠNeu ros", + "Ġdisc om", + "se ason", + "ĠEx ec", + "chang ing", + "Ġarri ves", + "H ash", + "m RNA", + "Ġf ric", + "as a", + "ob ia", + "Ġpost synaptic", + "optim izer", + "ĠCloud s", + "Ġhyper sensitivity", + "v acc", + "ĠS ig", + "ph ilic", + "Ġground ed", + "ĠW an", + "ĠCal abi", + "ĠMach ines", + "Ġaxis ymmetric", + "ĠSte ve", + "Ġpull ed", + "ĠEx cel", + "Ġdiamond s", + "K R", + "W est", + "ĠD est", + "Ġann ular", + "Ġarch ive", + "Ġparench yma", + "ĠE H", + "ó pez", + "Ġunp ublished", + "Ġs outheastern", + "Ġn ests", + "dim ensions", + "lat itude", + "O rig", + "ec ed", + "ĠD raw", + "red shift", + "Ġam yl", + "omyel itis", + "W hy", + "c aro", + "i q", + "ass ess", + "ĠCont in", + "Ġchir ality", + "mat ical", + "Ġchaper one", + "Ġendometri osis", + "re lu", + "Ġconver ged", + "bro ad", + "ĠIter ative", + "Ġvascul ature", + "f und", + "ĠF ly", + "Ġanti genic", + "Ġmening itis", + "Ġent ails", + "hor n", + "Ġlocom otor", + "iz ard", + "Ġun even", + "par ity", + "pack et", + "tub ulin", + "Ġsew age", + "Ġdec entralized", + "Ġgra fted", + "Ġse p", + "ĠExt ensive", + "Ġspl ine", + "qu er", + "arch it", + "Ġprim ate", + "Ġïģ ±", + "pyrim idin", + "ĠS AP", + "Ġunder lie", + "Ġanalyz es", + "ĠC CA", + "rec ogn", + "IP T", + "Diff erent", + "ĠTE ST", + "Ġunf avorable", + "ed ic", + "ĠAb normal", + "pyrim idine", + "ur ine", + "embed ded", + "var ies", + "otrop in", + "Ġsem en", + "Ġtransmit tance", + "Ġab ras", + "Ġó ¸Ģł", + "Ġtriglycer ide", + "b undle", + "ĠY b", + "ĠCar r", + "Ġnam ing", + "We ight", + "Ġcondens ates", + "Ġn os", + "am ard", + "ver tices", + "EL S", + "id one", + "Ġcont est", + "Ġhead ing", + "ĠGal erkin", + "G V", + "ĠGl i", + "Ġfer mented", + "Ġb ilingual", + "Ġt icks", + "Ġk ary", + "rag al", + "ĠA ber", + "ĠYou Tube", + "UCT URE", + "b ranch", + "Ø ±", + "ĠF H", + "on oi", + "im otor", + "Ġver ifying", + "ĠCon ceptual", + "ĠDetermin ants", + "ur m", + "ur onic", + "ĠK au", + "ĠCon formal", + "Ġdrop ping", + "ĠFlow s", + "glu on", + "ag ain", + "ĠMR SA", + "war f", + "Ġemphas izes", + "Ent ry", + "ĠA SP", + "res ol", + "vent ricular", + "ĠâĨ Ķ", + "Ġoverex pressing", + "omegal ovirus", + "in oc", + "SC O", + "ĠPAR P", + "ĠSch ul", + "ĠCam b", + "ĠP od", + "ĠP un", + "ĠCompe tition", + "ĠG ATA", + "Ġmo on", + "Ġput s", + "angi ogenic", + "ĠRepublic an", + "ĠUb iqu", + "e ys", + "ĠG ong", + "arg er", + "ĠInter mediate", + "Ġinterp olated", + "Ġenlarg ement", + "Ġin struct", + "Ġr c", + "di oxo", + "ey e", + "ĠCar ls", + "ĠMeas ured", + "ir cles", + "ĠR af", + "Ġar b", + "ex amples", + "M i", + "ĠS tern", + "ĠF K", + "Ġmill isecond", + "ĠIR F", + "ĠEp ithelial", + "ed icine", + "el es", + "s ig", + "âĪ Ģ", + "ĠWi ener", + "b auer", + "ous es", + "Ġcol oured", + "ĠIncre ase", + "Ġtriglycer ides", + "Ġaeg ypti", + "ĠNumer ous", + "Ġretard ation", + "Ġinter cellular", + "ĠKle bsiella", + "ĠD ra", + "ĠD IC", + "ĠTh reshold", + "rain ment", + "Ġrepro ducing", + "Ġul cers", + "Ġa rousal", + "ĠH ills", + "Ġcal ves", + "ĠRes ervoir", + "ĠRad ar", + "Ġpsych osis", + "ĠFOR M", + "d uration", + "ĠAc ademic", + "c atal", + "oll a", + "ol ol", + "ĠC ron", + "ik o", + "Ġextrem es", + "ĠTryp an", + "Ġb ip", + "Ġal ginate", + "ĠH och", + "ĠBen nett", + "ĠH ippocamp", + "ĠGe ological", + "N evertheless", + "ĠH es", + "ĠAd ding", + "Ġextern ally", + "Ġsl ag", + "Ġte ach", + "ĠStan ley", + "controll er", + "ĠUn its", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠ", + "Ġaer odynamic", + "oval ent", + "c ube", + "Å Ł", + "re quire", + "romo lecules", + "ir teen", + "Ġcl auses", + "Ġdef eat", + "pol icy", + "Ġfaith ful", + "Ġp q", + "ĠTan aka", + "ĠE ver", + "Ġun predict", + "aut y", + "ĠGAL AX", + "Ġt ide", + "ĠFilter ing", + "Ġeut han", + "mer ce", + "DE X", + "Ġnest ing", + "D N", + "IR T", + "ĠTh r", + "tis sue", + "Ġpal ae", + "Ï ©", + "Ġdil ated", + "Ġpin ning", + "R b", + "ĠS ap", + "rag onal", + "ĠS PR", + "ĠD ial", + "Ġac upuncture", + "di ameter", + "ĠPC B", + "Par ameters", + "ĠProf iles", + "transf ected", + "l iter", + "ĠR ights", + "Ġcontrib utor", + "ĠCor rel", + "Ġregression s", + "Ġsegment al", + "Sh ape", + "I AN", + "ec om", + "com ings", + "Ġhemorrh agic", + "op os", + "Ġrefrac tion", + "P FC", + "pro j", + "ov o", + "ĠDer ived", + "Ġundi rected", + "Ġl os", + "Ġeng aging", + "c ans", + "Ġdestr uctive", + "P op", + "Ġm akers", + "ĠW or", + "ĠAre as", + "vas ion", + "Ġpara formaldehyde", + "abin oid", + "c py", + "pro xim", + "Ġen amel", + "Ġpa ediatric", + "ĠChild hood", + "Ġp ectin", + "ofil m", + "Ġcarboxyl ic", + "Ġa usten", + "Ġun equal", + "ĠCount ry", + "Ġiter ated", + "Ġflank ing", + "Ġt raction", + "ans on", + "isc us", + "ĠDav ies", + "ra ham", + "ter ozoic", + "ĠBr ass", + "O c", + "Ġun ification", + "met er", + "ĠNe on", + "bu ilding", + "ic ting", + "Ġjus tification", + "Pri or", + "Ġfir ms", + "Ġeduc ated", + "Ġinters ecting", + "Ġboost ing", + "P ass", + "m ember", + "con tains", + "ran o", + "rel ax", + "ĠCollabor ative", + "Ġp x", + "Ġseed ing", + "cri pts", + "ine z", + "ome res", + "Ġsib lings", + "ang ing", + "fer t", + "Ġrecover ing", + "p ure", + "Ġs d", + "ĠV ul", + "ped ance", + "Ġfight ing", + "S uper", + "ĠI to", + "Ġper imeter", + "ĠInhib itors", + "electro de", + "en abled", + "f b", + "ĠP Cs", + "Ġn ausea", + "ĠCon version", + "Ġsl a", + "Ġinver tebrates", + "ĠBri an", + "Ġcontig uous", + "ĠACKNOWLED GM", + "ur face", + "Ġco ars", + "ĠLe h", + "ĠComp ression", + "cy cles", + "Ġsin h", + "ĠOcc up", + "st rength", + "Ġcon str", + "Ġpestic ide", + "Ġb isp", + "ĠT n", + "Ġparent heses", + "deg rad", + "Ġhypergly cemia", + "P W", + "k j", + "ec ological", + "Ġth y", + "Ġele g", + "ĠSyn aptic", + "scal ed", + "ti ty", + "Ġequ ity", + "Ġblock chain", + "ĠLith ium", + "Ġsp ark", + "Ġen titled", + "Ġconven tions", + "Arg ument", + "Ġre tail", + "Ġne oplastic", + "Ġdamp ed", + "ĠSurve illance", + "ĠAn na", + "Ġspace times", + "ing es", + "ah ashi", + "ĠInf ections", + "Ġneglect ing", + "Ġevapor ated", + "vast atin", + "Ġg h", + "ĠN LP", + "Ġph ones", + "Ġlif ted", + "Ġdivis ible", + "Ġdur ability", + "os ited", + "Ġexcit ability", + "Ġbuoy ancy", + "Ġuncont rolled", + "b ran", + "ĠP he", + "Ġimmun ocomp", + "Ġevent ual", + "Ġclass room", + "Ġmicro graphs", + "Ġre charge", + "et tes", + "ĠD iver", + "ĠD all", + "Ġmet ac", + "Ġneuro endocrine", + "top ology", + "ĠHaw king", + "oms on", + "ĠHar ry", + "m outh", + "Ġdec iding", + "Ġunc overed", + "Ġgold en", + "ĠCast le", + "Ġfid ucial", + "A ware", + "ĠG an", + "era hertz", + "ĠSat urn", + "L N", + "Un it", + "ĥ Ĺ", + "Ġbind er", + "IN FO", + "ĠTem per", + "ip el", + "Ġnumer ator", + "Ġwebs ites", + "Ġthreat ened", + "Ġremn ants", + "ĠFinn ish", + "h of", + "med ia", + "concent ration", + "ĠRe ed", + "ĠLeishman ia", + "Ġmulti functional", + "rac y", + "Ġdistrib ute", + "ĠDec ay", + "Ġgr inding", + "L oss", + "MP L", + "ĠL akes", + "ĠQ R", + "ĠStruct ured", + "ĠMal aria", + "Ġflavon oid", + "Ġtow ns", + "op ia", + "ĠV ec", + "oth y", + "Ġsing les", + "Ġpenet rate", + "ĠP ig", + "ie ved", + "Ġderiv ations", + "Ġdiscom fort", + "af enib", + "ĠLegend re", + "ĠP ax", + "ĠM X", + "ĠExt rem", + "ĠFore ign", + "ĠCour se", + "ĠH it", + "v age", + "Ġcl ique", + "Ġcompens atory", + "U ser", + "Ġdraw s", + "ĠProt ective", + "Ġalloc ate", + "ĠP ant", + "Ġd ash", + "Ġpar al", + "ĠCirc ulating", + "ĠHist one", + "ĠÅ «", + "Ġproj ec", + "ĠA AA", + "ĠP MS", + "gl acial", + "ĠMe eting", + "ĠAntib iotic", + "ategor ical", + "Ġatten uate", + "P ower", + "ow icz", + "ĠDef ault", + "Ġmar sh", + "plas m", + "ĠPath ology", + "ĠE f", + "L ys", + "fl ies", + "Ġinterview ed", + "ĠQ A", + "Ġimp uls", + "Ġpap illary", + "d R", + "u h", + "ĠJ ing", + "Ġrescal ed", + "e fficiency", + "Ġe f", + "ĠE isen", + "Ġattack ed", + "Ġopt o", + "Ġspec ulated", + "ha z", + "Ġide ally", + "ymen optera", + "Ġl r", + "ĠI z", + "res ource", + "ĠFac ility", + "ĠAc quisition", + "Ġpost ural", + "auti ful", + "Ġging ival", + "Ġper taining", + "ĠExt ra", + "ĠProgram me", + "hes us", + "ferm ion", + "Ġstead ily", + "Ġtermin us", + "P arser", + "ĠIn clusion", + "ĠWu han", + "Ġrepe titions", + "d one", + "ĠC ep", + "Ġun structured", + "ĠCol lectively", + "Ġsett ling", + "Ġj aw", + "ĠUn i", + "Ġrest oring", + "urt les", + "F ull", + "Ġdynam o", + "IG O", + "ĠB AT", + "ov á", + "ven ues", + "ĠPer haps", + "sens ing", + "ĠI schem", + "odem ographic", + "S s", + "ĠL und", + "Ġel ite", + "prot ocol", + "ĠChrist opher", + "bas ic", + "Ġp uber", + "Ġmagne tism", + "v ars", + "in ducing", + "Ġd ated", + "Ġen emy", + "ĠSt op", + "s ocial", + "Ġd ÏĦ", + "ĠB un", + "Sm all", + "pur pose", + "Ġh unting", + "CP U", + "ĠJun ior", + "RE L", + "Ġcontrac tile", + "Ġsilic one", + "adren ergic", + "b z", + "Ġf us", + "if ted", + "se p", + "âĪĴ âĪŀ", + "Ġdr um", + "-------- --", + "ĠTreg s", + "it arian", + "cent ury", + "âĬ ¥", + "Num er", + "ĠB enz", + "Ġcommunic ating", + "Ġp aternal", + "ĠF GFR", + "Ġâ Ĥ¬", + "Ġdevi ate", + "f re", + "Ġmol ten", + "Ġstandard ization", + "Ġfunctional ities", + "ĠPaul o", + "Ġbuck et", + "ĠConcent rations", + "ĠK um", + "Ġmim icking", + "D rop", + "zo a", + "ĠNucle i", + "b rack", + "ec olor", + "Ġcar n", + "Ġveter inary", + "Ġchem otherapeutic", + "Ġfer ment", + "last ing", + "ĠRog ers", + "ier i", + "Ġconver ters", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠ", + "ĠRep air", + "Eu rope", + "T IME", + "Ġt ies", + "ĠP IN", + "Ġtrib ut", + "Ġhomogen ization", + "exc itation", + "at ization", + "ĠR ash", + "Ġpre cession", + "á s", + "Ġspik ing", + "ĠGrass mann", + "min ister", + "Ġfactor ial", + "ĠDe ut", + "sam pled", + "Ġeukary otes", + "overl apping", + "ag glut", + "Ġpres cribing", + "Ġc ro", + "om echanical", + "iz a", + "ĠMan ufact", + "n ative", + "urs ive", + "ĠIss ues", + "Ġstrept omycin", + "en di", + "ĠS pr", + "ce q", + "arg inine", + "ix on", + "ĠFound ations", + "Sing le", + "Ġox al", + "Ġhyd rate", + "Iter ator", + "k ii", + "amin ated", + "Ġspr ings", + "ol n", + "ĠSet up", + "Ġrip ening", + "Ġtheore tic", + "Ġcf g", + "μ L", + "G ordon", + "S K", + "Ġn ations", + "Qu ery", + "Ù ħ", + "Ġf ores", + "requ encies", + "ĠPh armaceutical", + "ĠAll ocation", + "otyp ical", + "ĠPil ot", + "th ora", + "ĠV and", + "Ġsyring e", + "ĠR AP", + "rom etric", + "Ġïģ ´", + "Ġcit ations", + "wo uld", + "Ġnorthe astern", + "compar ison", + "l ocus", + "et he", + "ĠK B", + "Ġhomolog s", + "Ġencephal itis", + "Ġz ig", + "Ġinc entive", + "Ġconf idential", + "Ġves tibular", + "ĠOT Us", + "Ġsynov ial", + "ĠRel ativity", + "Ġsub divided", + "che z", + "Ġlik ewise", + "ĠPD MS", + "ĠÅ ł", + "Ġsoci eties", + "ocyan ate", + "g ia", + "Ġlocal ize", + "Ġlact ation", + "Ġnod ule", + "ĠCO R", + "Ġharbor ing", + "ĠE QU", + "har vest", + "Ġband gap", + "r k", + "Ġres istor", + "Ġy e", + "ĠAs ymmetric", + "Ġpropag ators", + "Ġdiagnos ing", + "ĠAff airs", + "Ġeject a", + "Ġis omer", + "Ġi x", + "Ġfol iation", + "Ġcapac itors", + "Ġc ad", + "ĠNeut roph", + "pl iance", + "Ġcompress ible", + "ĠHun ter", + "ĠM Z", + "ĠWe ib", + "Ġnon coding", + "Ġmountain s", + "Ġadver tising", + "ale z", + "b right", + "lim sup", + "C i", + "ĠNe v", + "ĠStrain s", + "ost omy", + "op al", + "Ġconcaten ated", + "ĠPer f", + "CH O", + "Ġt urtles", + "ĠF ra", + "Ġall ogeneic", + "Ġuns uccessful", + "Y M", + "er ver", + "Ġc uc", + "Ġf ires", + "ch art", + "Ġinter rupted", + "Ġdec ides", + "Ġa uction", + "ĠUn til", + "ĠAT G", + "Ġdi am", + "magn itude", + "Ġd l", + "Ver tex", + "mon t", + "Ġfem tosecond", + "Par ams", + "Ġlys ate", + "is hers", + "ĠP AT", + "ĠK ev", + "ĠKn ock", + "Ġgro ove", + "L u", + "ĠJoh ann", + "Ġreplic a", + "ĠMATERIAL S", + "Ġl ots", + "Ġgener ically", + "ĠAl tered", + "ĠId entity", + "Ġunfold ed", + "C ES", + "ing ular", + "ĠF raction", + "ĠPro liferation", + "ĠVi enna", + "ac ia", + "pl ess", + "ĠSe vent", + "Ġturb ines", + "lys ine", + "Ġperox is", + "AR P", + "ĠEp is", + "ĠSY BR", + "Bu ilder", + "Ġspher ically", + "Ġdef end", + "Per formance", + "Ġmort ar", + "ĠCon cepts", + "work s", + "Ġreinfor ce", + "á ¹", + "Ġc us", + "ĠC IF", + "ĠAgric ultural", + "c rystalline", + "r ish", + "Ġref erenced", + "Ġact ress", + "Ġbounded ness", + "Si C", + "Ġà ¢", + "Ġj ack", + "Ġterm inate", + "ĠJ A", + "ĠKr ish", + "M MP", + "k x", + "ĠP SR", + "end l", + "W HO", + "ĠS ão", + "ĠC ultural", + "ĠE h", + "ul is", + "vi k", + "pr ises", + "ix el", + "ĠMet rics", + "Ġdiscontinu ities", + "ĠU ne", + "SC R", + "Ġproject ing", + "ĠOrig inal", + "ĠHum ans", + "transcription al", + "H K", + "ĠJ ain", + "atisf action", + "mes enchymal", + "Ġpyram id", + "Ġascorb ic", + "g ame", + "Ġno un", + "otox ins", + "p eptide", + "Ġglass y", + "Ġtalk ing", + "D em", + "ĠSch ro", + "ĠAssum ptions", + "Ġð x", + "Ġaneurys ms", + "M ASS", + "ĠH ou", + "ex posure", + "ĠL LC", + "Ġno ises", + "CT G", + "ĠElement ary", + "fl ip", + "Ġdys p", + "Ġmess enger", + "ĠImport ant", + "Ġim poses", + "Ġorgan elles", + "assert Equal", + "Ġjus tif", + "uc ine", + "Ġform ic", + "ormal ization", + "ĠRad ial", + "ĠCur ve", + "ĠCro hn", + "Ġbrow ser", + "Ġeff usion", + "Ġhand les", + "var sigma", + "Ġspecial ists", + "Ġpain ful", + "Ġerythemat osus", + "Ġf en", + "nitrop henyl", + "Ġleg acy", + "ĠQ Ds", + "rap per", + "Ġmon otherapy", + "ĠBel t", + "Z Z", + "Ġs intered", + "en edi", + "H b", + "t v", + "ĠN as", + "ov is", + "Ġmuc in", + "Ġacceler ates", + "Ġacqu iring", + "l uc", + "Ġdil aton", + "ĠPit ts", + "Ġequiv ariant", + "ĠL yman", + "ĠY a", + "Ġprog ressed", + "ĠAfter wards", + "ĠCH AR", + "D on", + "Ġhist ologic", + "Ġcircuit ry", + "p ene", + "op res", + "ĠSte fan", + "Ġsemic lassical", + "m und", + "ĠW aste", + "B Q", + "Ġadip onectin", + "Ġun seen", + "Ġbiom echanical", + "Ġhazard ous", + "r uctive", + "x yl", + "op f", + "Ġpr ion", + "ĠInf inite", + "Ġtrac ers", + "ĠHar rison", + "Ġfibrin ogen", + "Ġhydro lys", + "Ġis lets", + "Ġparallel ism", + "Sp ect", + "Ġimper ative", + "Ġc ured", + "ĠD SB", + "ide finite", + "ick er", + "Ġdiver gences", + "ĠShap iro", + "ab d", + "ĠL um", + "ĠV D", + "Ġf isheries", + "ge on", + "cop enia", + "ĠCl ay", + "Ġmaxim ized", + "ĠGre y", + "ĠB atch", + "Ġinf est", + "Ġam ple", + "Ġest ate", + "ĠSup reme", + "A O", + "is ia", + "ĠSm ad", + "Car lo", + "ĠSub st", + "Ġmon oidal", + "Ġnumer ic", + "Pl ot", + "Ġdyst rophy", + "hypert ensive", + "Ġst ool", + "als y", + "Ġche ese", + "n ih", + "Ġb ought", + "ĠS Q", + "Ġcl ues", + "Ġme iotic", + "Ġgo ats", + "ĠGTP ase", + "Ġrescal ing", + "N UM", + "ic ing", + "ĠÄ Ģ", + "Ġpret ty", + "lig and", + "En glish", + "ĠIntellig ent", + "E very", + "ĠPoli tical", + "ent on", + "Ġpass ages", + "ĠRemark s", + "s b", + "N etwork", + "ĠL RR", + "Ġcur l", + "urs ion", + "ĠA ver", + "ĠG LP", + "here n", + "at an", + "IC ENSE", + "Ġlate x", + "E MI", + "qu asi", + "ĠO m", + "Ġreview ing", + "Back ground", + "Ġs om", + "Ġsnap shots", + "b row", + "w ho", + "ĠT ail", + "ĠM SM", + "ĠG m", + "Ġph i", + "ren cy", + "separ ated", + "Ġg ig", + "os ides", + "Ġpe an", + "Ġappe aling", + "P U", + "n k", + "Ġqu er", + "ĠCh arg", + "ĠMo lecules", + "local ization", + "I dx", + "l ap", + "ĠT ax", + "ĠExp onential", + "ĠInhib itor", + "ĠBiom edical", + "ureth ane", + "le rene", + "rogen esis", + "ĠL ai", + "ĠAgg regation", + "ĠCa Cl", + "Ġsens ible", + "Ġcon junc", + "pa per", + "ĠCov id", + "ĠProced ures", + "Ġk new", + "Ġset ae", + "ĠAl le", + "ĠEx cept", + "Ġpres ynaptic", + "flow er", + "Ġultrason ography", + "Ġent ertain", + "i ors", + "ĠE ry", + "ĠIn teger", + "Ġrep ressor", + "Ġlater ally", + "Ġcomplement ed", + "T AG", + "ĠA round", + "ĠL ister", + "bit rary", + "back ward", + "Me V", + "Ġwh isk", + "AM s", + "ĠBul k", + "Ġqu iver", + "Ġdam aging", + "ĠQuantif ying", + "Ġsup rem", + "t el", + "Ġt ear", + "ot ers", + "vid in", + "Ġtub ules", + "Ġips ilateral", + "is ive", + "Ġsuit ably", + "ri el", + "Ġtub er", + "Ġfav ors", + "Ġcen tim", + "Ġtrans versal", + "ĠCH O", + "Ġtrim ester", + "C AC", + "c ognitive", + "ĠU TC", + "put e", + "Ġmid line", + "am ers", + "eval uation", + "D av", + "Ġb ags", + "tim er", + "Ġshort comings", + "ĠEr d", + "Ġdiscrim inator", + "A nt", + "s izes", + "Ġb ist", + "ing ual", + "ĠC ategory", + "Ġpuls ars", + "ĠSchw artz", + "ĠD rop", + "Sequ ence", + "Ġt ann", + "ĠSympt oms", + "D ict", + "ĠB lu", + "Sup plemental", + "Ġdis abled", + "ĠK oz", + "Ġinv oked", + "ĠC Q", + "ĠConn ectivity", + "Ġtelescop es", + "os o", + "Ġphyt ochemical", + "Ġorthogon ality", + "Ġinvis ible", + "ĠS CF", + "ĠA void", + "ĠH us", + "mic ron", + "atern ity", + "Pro ject", + "Ġadv ancing", + "ĠLorentz ian", + "S a", + "t Ãŀ", + "ĠU P", + "Ġar ts", + "Ġz er", + "ask et", + "Ġappe al", + "n ick", + "ĠCl oning", + "Ġsw ap", + "Ġphospholip ids", + "b g", + "ot hel", + "asc o", + "T rack", + "Ġsub manifold", + "Off set", + "ĠB ird", + "problem s", + "D Cs", + "Ġd ow", + "Ġde ionized", + "Ġsub class", + "Ġpubl ishing", + "ĠCar ter", + "Ġsyn ergy", + "Ġweak ened", + "ĠGl as", + "ĠP ie", + "hen ko", + "Ġsetup s", + "ĠBern stein", + "Ġà ¿", + "ĠSh u", + "ĠChang ing", + "os ov", + "ĠMet eor", + "in th", + "ra h", + "par amet", + "ren a", + "Ġnew borns", + "isc he", + "rot ating", + "Ġconf ident", + "f ac", + "ĠT err", + "Ġline width", + "IC P", + "thon y", + "Ġl anes", + "Ġsm oother", + "mon y", + "ĠCN Ns", + "P ort", + "Ġtrans iently", + "Ġsur geries", + "Ġsubm erged", + "Ġp uncture", + "Ġd ichlor", + "Ġsystematic s", + "Ġcontig s", + "Ġresid ing", + "B W", + "E O", + "G old", + "ion ate", + "voc ab", + "d W", + "ST AR", + "ĠP LC", + "ath i", + "ĠInf ectious", + "L ight", + "á »", + "ĠR al", + "Ġpropag ates", + "ĠLik elihood", + "h ill", + "cur l", + "check point", + "ra x", + "Ġv ancomycin", + "ĠU SD", + "ophe les", + "Ġfil tr", + "Ġstoichi ometry", + "âĶĢ âĶĢ", + "ĠN ad", + "access ible", + "Ġto y", + "Ġn ude", + "ĠS ut", + "ess ential", + "ĠO L", + "Ġper tin", + "Ġrec ur", + "Ġcap ill", + "Ġcomput able", + "Ġsuc tion", + "Ġsoft ening", + "ĠE SI", + "Ġmon itors", + "Ġpy ridine", + "ĠSens ors", + "ĠCombin atorial", + "at ta", + "ĠA MS", + "ĠD ul", + "ple teness", + "E th", + "Ġà »", + "Ġexc ised", + "ĠDiab etic", + "ĠI owa", + "Ġimmunost aining", + "Ġillness es", + "Ġenum er", + "ĠIran ian", + "Ġth umb", + "orphism s", + "Ġlegitim ate", + "l g", + "ĠS VD", + "Ġdes k", + "Form at", + "B on", + "Ġg arden", + "Ġinter personal", + "Ġel bow", + "ĠDem onstr", + "Ġnons pecific", + "F erm", + "ival ently", + "phthal ene", + "AR GET", + "Val id", + "Ġsun light", + "Ġresc ued", + "D AR", + "ĠIn variant", + "Ġid le", + "Ġalkal oids", + "scal es", + "s es", + "ob icity", + "be at", + "Ġcentrifug al", + "analy tical", + "p v", + "Ġt utorial", + "ĠN ation", + "gener ator", + "Ġcollision al", + "ĠC ME", + "Ġsc rap", + "ĠQ SO", + "Ġw ax", + "ĠSc enario", + "Ġminim izer", + "ĠMD PI", + "Ġprostagland in", + "ol ites", + "ocy steine", + "Ġcompac tification", + "Ġfrail ty", + "ops in", + "Ġjun ior", + "lo ud", + "Ġtit led", + "Ġeconom ically", + "th iophene", + "ĠInvestig ating", + "ĠE sp", + "Ġel usive", + "Ġmal ware", + "ĠTH P", + "imid azole", + "Ġre tains", + "ĠM IR", + "ff l", + "j ac", + "ĠP ART", + "ĠD CM", + "trans port", + "MAP K", + "Prob lem", + "S u", + "Ġdel im", + "Ġpsych ometric", + "vit ably", + "Ġhyper geometric", + "Ġuter us", + "Ġanaest hesia", + "ĠA venue", + "Ġmean ings", + "Ġrapid ity", + "Ġdend rites", + "g rain", + "ĠN ile", + "Ġfac ies", + "Ġpip elines", + "ĠCamp ylobacter", + "ĠMemb ers", + "benzo ate", + "Requ est", + "Ġp k", + "Ġref used", + "c aus", + "ĠS ay", + "l ane", + "ĠP SO", + "Ġgather ing", + "Ġrefrig er", + "R CC", + "Ġfib ronectin", + "hel p", + "ĠInt ensity", + "CL C", + "Q ue", + "el ly", + "Ġillumin ated", + "Ġpedest rian", + "ĠM ercury", + "Ġafford ed", + "Ġpathophys iological", + "ĠN GS", + "ass a", + "Ġend ors", + "Ġsens ation", + "Ġstream flow", + "av in", + "ĠGABA ergic", + "Ġreti rement", + "C ells", + "oc a", + "Ġoptim izations", + "Ġdig raph", + "ĠAu tism", + "oct urnal", + "osc ience", + "ĠEll is", + "ĠA j", + "ĠW SN", + "Ġshoot ing", + "i per", + "î Ħĥ", + "ĠWe ather", + "Ġrece ptive", + "Ġquar tic", + "ocycl ic", + "P ATH", + "size of", + "Ġmel ts", + "Ġdip oles", + "Ġbim odal", + "summ ary", + "Ġins omnia", + "opy ran", + "Ġwrap ped", + "ĠJos é", + "A H", + "c ia", + "Ġob eys", + "ĠK ay", + "inter vention", + "Ġrout er", + "ĠDrug s", + "ow ska", + "ĠAr r", + "ĠCap tain", + "ĠT MS", + "ad v", + "Ġbo at", + "Ġtrust ed", + "se ver", + "ill ars", + "ĠMiss ouri", + "Ġequival ents", + "ĠHar vard", + "ĠClark e", + "reson ant", + "rad y", + "trig gered", + "Ġc left", + "Ġun ic", + "Ġbrain stem", + "Ġthrom bin", + "ĠF light", + "Ġsection al", + "Ġconcaten ation", + "Ġcantile ver", + "et on", + "Ġdec ode", + "of acial", + "Ac tion", + "ĠIll ustration", + "ver tical", + "ch all", + "ĠReg istry", + "M AT", + "Ġcons on", + "Ġneo adjuvant", + "ĠW istar", + "ĠIm per", + "Ġal titudes", + "Ġsub population", + "ĠSc ene", + "tensor flow", + "s low", + "Ġh int", + "Ġbeam forming", + "e in", + "Ġimp regn", + "ĠRF ID", + "ĠAnaly zing", + "ĠP ent", + "ĠD NS", + "ĠG ilbert", + "Ġcr ater", + "Compar ing", + "Ġb f", + "Ġfl ights", + "Ġmal nutrition", + "SM C", + "Ġeryth rop", + "ĠTum ors", + "T x", + "Ġis ospin", + "ĠK ub", + "ik ing", + "Ġcorticoster oids", + "urs or", + "ĠBur g", + "in spired", + "ĠI gn", + "Ġmy cel", + "pred iction", + "method s", + "C opy", + "ĠR W", + "ĠK night", + "Ġdem ethyl", + "ì Ħ", + "Ġc ili", + "Ġb es", + "ĠE ck", + "Ġdilat ation", + "Ġan imation", + "ab stract", + "Ġcircum vent", + "Ġinoc ulum", + "S eg", + "ĠC aps", + "ere rs", + "PL S", + "ĠPe er", + "Ġver ifies", + "ateg y", + "ogene tics", + "Ġoligonucle otides", + "rac tical", + "Ġdiver ges", + "ĠStan ford", + "ĠA i", + "Ġweigh ing", + "T g", + "re infor", + "ĠA lam", + "qu iry", + "ĠN ob", + "Ġlinear ization", + "ĠV enez", + "ne xin", + "level s", + "L ip", + "ĠPat el", + "ĠMagn itude", + "e titive", + "ĠE agle", + "Ġsp utum", + "ĠCO S", + "Ġincub ator", + "U l", + "ĠRec eptors", + "ĠSch ott", + "GC G", + "ĠZe iss", + "ĠEnt anglement", + "ĠVacc ine", + "or ted", + "Ġn b", + "ĠS j", + "ĠM rs", + "Ġcal f", + "Ġinte grability", + "ĠPh oton", + "Ġgon dii", + "ĠM IL", + "Ġal iph", + "ĠD ip", + "fall s", + "c trl", + "k u", + "et ent", + "pl t", + "Ġpers isted", + "ĠMan ager", + "Ġprerequ isite", + "f illing", + "ĠM EA", + "S ym", + "ĠG rain", + "Ġduct al", + "ĠT ODO", + "Ġaff inities", + "Ġdegener ative", + "ĠF itz", + "ov ar", + "ĠTri ple", + "Ġdend rim", + "ĠFrank lin", + "m ag", + "ot ely", + "Ġstabil izes", + "Ġc ash", + "ĠS quad", + "Ġch ampion", + "PD B", + "Ġur g", + "Ġalcoh olic", + "Ġt ar", + "yl ed", + "V ersion", + "Ġs ale", + "ĠM LP", + "out er", + "Ġsimpl ifying", + "ĠExt ract", + "Par am", + "ĠRest ric", + "Ġtract able", + "ĠArch ive", + "Resp onse", + "AD DR", + "Ġcommut ation", + "R ich", + "ĠAndrew s", + "Ġosteocl ast", + "rom ic", + "ĠSh ift", + "Ġacceler ometer", + "ĠS ent", + "Ġch ances", + "ost ing", + "Ġmeth acrylate", + "Ġglu ons", + "Ġôı ½", + "Ġpolyg ons", + "ĠRCT s", + "Ġinf ancy", + "Ġproceed ed", + "ĠHor izontal", + "C OR", + "Ġc aching", + "ĠN HS", + "ph obic", + "ĠX MM", + "Ġmicrobi ological", + "G MP", + "Ù Ĩ", + "ĠT SS", + "ĠS ul", + "ĠF act", + "ĠW E", + "Ġcertain ty", + "ens itivity", + "Ġdecon volution", + "ĠG ain", + "Ġbl ots", + "Ġsee ks", + "Ġcos h", + "ennes see", + "Ġsl ave", + "ĠT ran", + "Ġtrans pose", + "re ated", + "Ġsh ading", + "ĠB U", + "ĠO V", + "ĠLo ok", + "Ġcomprehens ively", + "ĠFred er", + "Hand ler", + "f ibr", + "Ġmiss ense", + "target s", + "prom oting", + "ĠP ep", + "var pi", + "ĠHar monic", + "ĠA IS", + "Ġmon ocyt", + "Ġthin ning", + "Ġpherom one", + "W ater", + "an ase", + "ĠS ang", + "Ġsub structure", + "w p", + "ĠK ansas", + "DE BUG", + "ĠPro be", + "Ġpattern ed", + "cle an", + "Ġbro iler", + "odext rin", + "a ided", + "op rol", + "ubl in", + "in um", + "Ġan atomic", + "Ġpl ating", + "ar ro", + "uc al", + "Ġspeed up", + "Ġhaem orrh", + "eptid ase", + "Ġs agittal", + "Ġin tim", + "ĠF ISH", + "Ġsc arc", + "AT CC", + "inc or", + "Ġser ological", + "ent e", + "Ġsh ale", + "Ġover fitting", + "ĠEx cess", + "ĠAL P", + "P ool", + "d ry", + "y u", + "ĠPM MA", + "ĠHyp oxia", + "n othing", + "chest ra", + "colone qq", + "Ġb ibli", + "ĠEX PECT", + "B AL", + "et han", + "ĠâĪ ĺ", + "Ġj ourney", + "Ġbi ocompatibility", + "P AN", + "Ġb on", + "ĠR oh", + "Ġpolar isation", + "Sp in", + "id ences", + "ĠB CR", + "ĠH IP", + "ĠTh ick", + "Ġrecogn izes", + "Ġs ar", + "Ġam end", + "ques tions", + "Ġcareg iver", + "ĠMari e", + "Ġmetalloprotein ase", + "Ġal dehydes", + "Ġinter neurons", + "Ġtetra hedral", + "gue z", + "Ġquasipar ticle", + "Ġo t", + "decre asing", + "st re", + "Ġphot operiod", + "Ġprior iti", + "Ġap o", + "Ġimmunosup pression", + "ĠPier re", + "L PS", + "Ġcl umps", + "ĠPl ane", + "Ġturb idity", + "Ġpollut ant", + "Ġbi och", + "ĠT RE", + "Ġdesign ers", + "Ġrend ers", + "Ġrepl aces", + "ĠP LS", + "Ġhum oral", + "B as", + "re ira", + "ĠA edes", + "vit amin", + "cur ves", + "ocic eptive", + "Ġin disp", + "Ġox y", + "Ġed ible", + "ĠMes enchymal", + "ĠDeg ree", + "Å ¾", + "ĠO ak", + "ĠBhat t", + "on so", + "ĠS BP", + "ĠA ux", + "Ġmar tingale", + "ĠMicrobi ota", + "g low", + "Ġex ud", + "ap olis", + "Ġsome how", + "Ġcent red", + "Ch annel", + "ĠNormal ized", + "il itation", + "Ġtranscript ase", + "Ġcry o", + "predic ted", + "ĠD AG", + "Ġr f", + "end or", + "INT ER", + "ĠMes h", + "ĠFund ament", + "y cle", + "Ġprim itives", + "radi ated", + "Ġr ho", + "enes ulf", + "ĠF SH", + "ĠE cos", + "local ized", + "Ġenter prise", + "cephal us", + "Ġcarc ass", + "A Y", + "ec urity", + "ĠT MD", + "Ġl b", + "ĠA eros", + "ĠM ER", + "At tr", + "ĠA CL", + "ĠBar b", + "c out", + "Ġde oxy", + "ati os", + "Ġpers ists", + "Ġviol ent", + "Ab elian", + "Ġell ips", + "ion g", + "Ġsuccess or", + "ĠGonz ález", + "l iving", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠ", + "iment in", + "Ġcaps ules", + "V IS", + "ĠP OP", + "arithm ic", + "O O", + "w l", + "ino ic", + "ĠCent ers", + "robl asts", + "th ose", + "ĠM J", + "Ġfron ts", + "Ġun int", + "Ġfac ile", + "co herent", + "av our", + "cep tive", + "ta h", + "Ġrelated ness", + "d E", + "un gen", + "## ###", + "Ġam phi", + "ĠGu y", + "st ars", + "ect om", + "Ġlay ing", + "Ġsp ider", + "AC s", + "Ġseed ling", + "Ġdu plicated", + "ic he", + "ĠM ST", + "gr ass", + "Ġprophyl actic", + "e ks", + "Ġl aryngeal", + "ĠS per", + "ĠW als", + "Ġcho lec", + "ĠPlan et", + "ĠHEP ES", + "Ġdi ploid", + "const raint", + "Py x", + "AC h", + "ĠCu i", + "ĠSh ared", + "ĠC and", + "ĠG ö", + "Ġdet ached", + "Ġpass engers", + "Ġaliph atic", + "Ġp our", + "Ġaccess es", + "ĠWal d", + "Ġdecor ated", + "Ġcaroten oids", + "ues tions", + "ĠImp acts", + "S AT", + "ar u", + "ĠP ir", + "ĠCon figuration", + "ĠCong o", + "ĠL ing", + "Ġdes ic", + "Ġmac rom", + "Ġlack ed", + "Ġencompass es", + "Ġp umped", + "ĠFor ty", + "rex ate", + "ifferenti ated", + "Ġn oble", + "Ġrad ion", + "Ġimmig rants", + "Ġbiodegrad able", + "Ġmig rating", + "arg v", + "CO M", + "ĠObserv ational", + "Ġcann abis", + "y ama", + "Ġconcent ric", + "Con n", + "tal ion", + "Ġrespond ers", + "uten ant", + "ĠT rim", + "Ġcontrib utors", + "Ġcontrac ted", + "ĠXen opus", + "Ġlo ud", + "ĠEnh ancing", + "Ġinfarc t", + "Ġo k", + "Ġas ks", + "rel in", + "Ġillustr ative", + "vd ash", + "d g", + "Ġf oc", + "Ġl ivers", + "ĠO tt", + "ĠT SP", + "log ger", + "depend ing", + "Ġdis proportion", + "Ġint ric", + "Ġimmun ized", + "vare z", + "Ġsal ic", + "ĠInstit utes", + "KE Y", + "Ġend oscopy", + "er k", + "el iness", + "ĠS ag", + "ath yroid", + "Ġacid ity", + "aro v", + "ĠVor onoi", + "Experim ental", + "Ġg ently", + "Me asure", + "ïĺ º", + "Ġw onder", + "ĠP ancreatic", + "ĠH ispanic", + "ĠE ug", + "re ducing", + "tain ment", + "Ġsur prise", + "Ġ æ", + "cr iter", + "ĠHypert ension", + "ti que", + "ĠC ris", + "comp atible", + "ens on", + "Ġdistribution al", + "ĠN AT", + "wid ths", + "Ġisother ms", + "ĠP rad", + "Ġbi odies", + "Ġorb ifold", + "ĠE OS", + "Ġat ax", + "ĠB od", + "ĠN MD", + "Ġmon oxide", + "ĠUk raine", + "f oli", + "ĠD ro", + "Ġun available", + "Ġbr ighter", + "âĬ Ĺ", + "ometh ane", + "Ġd ream", + "Ġsp o", + "ĠMa ur", + "Ġoccas ional", + "Ġincons istency", + "ĠT ac", + "op ts", + "ĠG AB", + "ĠTa o", + "ĠMatthe w", + "à ½", + "Ġp iano", + "ĠR CC", + "ĠO K", + "ĠK ul", + "met han", + "ĠPRO C", + "Ġconvers ations", + "ĠC SI", + "ang ent", + "ĠX ue", + "Ġgraph ic", + "den ing", + "health y", + "Ġf p", + "az one", + "Ġdiscipl ine", + "Ġprogress es", + "Ġb amboo", + "Ġchar m", + "ĠAc tivated", + "ĠSh arp", + "yn es", + "Ġtool box", + "Ġhetero structures", + "piper azin", + "Ġa rose", + "ĠInter val", + "Ġstrip e", + "ĠCh ak", + "Ġc uff", + "RE SS", + "Ġnon uniform", + "Ġbeet le", + "P rec", + "z c", + "Th read", + "b et", + "Ġe e", + "ĠOption al", + "Ġt roph", + "ĠP uer", + "ĠF ron", + "Ġmultiple t", + "Ġcalor imetry", + "Ġmonocyt ogenes", + "ĠH imal", + "Ġdr ill", + "AG A", + "Ġferr itin", + "Ġd pi", + "ĠC arm", + "Ġg one", + "Ġun idirectional", + "Ġrem inis", + "Ġadjust able", + "ĠAust in", + "S ARS", + "d al", + "Ġc ef", + "equiv ariant", + "bas eline", + "Ġspin ors", + "ĠPr int", + "Ġm ile", + "ĠL inc", + "mut ation", + "Ġmuc us", + "ĠH SC", + "Ġtherm od", + "Ġpain t", + "Ġdistinct ly", + "ath y", + "Ġph armacy", + "ĠBul g", + "ĠG ang", + "hic le", + "og an", + "ĠJ ian", + "ĠIndian a", + "Ġinstant on", + "Ġpall adium", + "f iber", + "n py", + "ĠU A", + "ĠQ T", + "cepti ble", + "et ine", + "ĠH oles", + "Ġdepend ences", + "Ġthreshold ing", + "ĠMain tenance", + "Ġparticip ates", + "ĠGen omes", + "factor ial", + "ĠL iber", + "ĠTherm odynamic", + "Ġe lective", + "uc her", + "Ġhyper ther", + "Ġstom atal", + "ĠB irth", + "ch olesterol", + "Ġnot ch", + "Ġsym biotic", + "Ġbusiness es", + "Ġapprec iable", + "Ġspecial ization", + "á r", + "act yl", + "ĠGraph Pad", + "os per", + "Ġor chestr", + "Ġdi hydro", + "Ġconcl uding", + "CL K", + "Ġeq s", + "ĠProg ression", + "Ġclub s", + "ak u", + "ev ents", + "Ġspl enic", + "Ġb unch", + "ĠT m", + "ĠM obility", + "Ġtwo fold", + "Ġradi ally", + "L STM", + "M H", + "ĠCo al", + "Ġfron tier", + "J an", + "J un", + "ĠSim pson", + "Ġabst racts", + "P al", + "Ġun im", + "Ġro bo", + "ĠII B", + "dep leted", + "Ġmorphological ly", + "Ġenfor cement", + "Ġd well", + "Ġst agn", + "Ġlim estone", + "Ġmicro v", + "Ġïĥ ¸", + "L uc", + "p acs", + "cy ano", + "Ġintra ocular", + "ĠCalc ulate", + "Sup port", + "SY S", + "ĠV S", + "CM s", + "Const ant", + "ĠD j", + "Ġun balanced", + "Ġrepeat ability", + "g ins", + "i rect", + "ĠM OR", + "ĠBa iley", + "Ġadvance ment", + "Ġpurs uit", + "Ġa rom", + "pro ced", + "ĠIniti ative", + "Ġincenti ves", + "Ġsur pass", + "gen es", + "ĠIN D", + "L H", + "Ġsu icidal", + "Ġbiodies el", + "x z", + "Ù Ĭ", + "le a", + "ĠAn thony", + "Lear ning", + "Ġund o", + "Ġïĥ º", + "ĠCommun ities", + "h ua", + "iti me", + "ĠDe an", + "Ġplas min", + "ÃŃ nez", + "ohyd rate", + "Ġneurode velop", + "Ġstoichi ometric", + "ĠOnc ology", + "Ġshow er", + "ĠD MS", + "W OR", + "ĠP IP", + "Ġster ic", + "mitte es", + "ist ol", + "ox ins", + "no on", + "FF T", + "Ġá »", + "opo iesis", + "Ġresemb ling", + "ĠB ord", + "Ġprob iotics", + "ocy sts", + "gre y", + "ĠCatal og", + "IZ ATION", + "ill es", + "ĠAl an", + "ĠÅ ·", + "ĠLe ib", + "ĠReason ing", + "bi ological", + "uter ine", + "vac izumab", + "lecom mun", + "ĠW arm", + "ep age", + "vari ants", + "B SA", + "Ġïĥ ¶", + "Ġhepat ocyte", + "ket ch", + "Ġstrip ping", + "ĠAd verse", + "ĠFe as", + "Ġïĥ ¯", + "P ac", + "Ġind entation", + "Ġsec ular", + "Ġidentif iable", + "run ning", + "Ġr d", + "Ġz yg", + "ĠD ictionary", + "Ġres veratrol", + "ines terase", + "Ġtet racycline", + "ub les", + "Ġthro at", + "ĠL amb", + "ary on", + "ĠS QL", + "Ġà ľ", + "Ġgly cemic", + "Ġcompet ent", + "ĠAg reement", + "oic ed", + "Ġconstitu tively", + "Ġelectro cardi", + "oplas ma", + "Ġî Ħĥ", + "an ide", + "Ġre organization", + "Ġun infected", + "UT E", + "Ġro yal", + "ĠS it", + "Ġmar ital", + "ĠKob ayashi", + "B arr", + "ĠT ennessee", + "ĠChrom at", + "ĠD erm", + "pro jection", + "ĠJ ob", + "Ġâī ł", + "ĠT rip", + "Ġis op", + "Ġproject or", + "Ġatmosp heres", + "Ġperfor ation", + "st orage", + "ith s", + "Ġmon omeric", + "ĠUS B", + "ĠE ve", + "Ġsp ore", + "Ġm T", + "ox azole", + "ĠDe formation", + "Ġtext ual", + "Ġwar f", + "Ġneuropath ic", + "prep ared", + "Ġbl ended", + "ĠHo uston", + "**************************************************************** ********", + "es ters", + "Equ als", + "Ġallerg en", + "Ġpertin ent", + "f acts", + "uc tions", + "Ġcl ocks", + "ĠV ia", + "ĠCD F", + "Ġest uary", + "Ġphenomen ology", + "ar us", + "AP H", + "Ġarg ues", + "Ġinser ts", + "g ow", + "h art", + "Ġchem otaxis", + "Ġp v", + "Ġre in", + "ĠG rim", + "ĠV F", + "Ġeff ic", + "ĠProf iling", + "Ġan odic", + "ĠDEN V", + "ĠW it", + "ĠSY STEM", + "ĠCay ley", + "En g", + "ĠA QP", + "inter actions", + "ili arity", + "ĠProm otes", + "Ġd ams", + "ing ton", + "ff ff", + "Ġint ran", + "ĠTurb ulence", + "ĠBian chi", + "C RE", + "ĠN OD", + "ap ine", + "ĠK ane", + "ĠPD GF", + "ĠAx is", + "ĠC ausal", + "ĠPo or", + "ĠW ords", + "ĠHR V", + "Ġcyan obacteria", + "Ġreminis cent", + "ĠRemark ably", + "he et", + "@ @", + "b il", + "Ġdiscrim inating", + "ĠBal tic", + "ĠQue bec", + "Ġdef ensive", + "âĪ ©", + "k r", + "ĠR PE", + "see king", + "ĠMo vie", + "Ġinnov ations", + "le pt", + "Ġk w", + "Ġtib ia", + "Ġne at", + "yt est", + "Ġthin ner", + "Ġoste oblasts", + "ĠNorth west", + "M OS", + "ĠP Q", + "Ġsp i", + "Ġrespond s", + "Ġhistor ically", + "ĠPack age", + "ĠCoast al", + "ĠMississ ippi", + "ĠP VA", + "per ing", + "ind ole", + "Ġprosp ectively", + "ĠHem isphere", + "Ġbare ly", + "án chez", + "ag gered", + "yp tian", + "ĠG est", + "yl ine", + "Ġphot ochemical", + "os calar", + "por ated", + "Ġmetabol omics", + "Ġoste oblast", + "EGF P", + "eri atric", + "D W", + "qu est", + "ĠH ave", + "Ġsp ondyl", + "ĠPrim er", + "Ġs inks", + "Ġg aussian", + "ĠK hal", + "En c", + "ĠAn opheles", + "Th anks", + "Ġconstr ued", + "ĠU SS", + "ĠZe eman", + "Ġex ported", + "ĠLe vi", + "Ġcomm ander", + "conn ect", + "Ġnom enclature", + "there fore", + "ul ata", + "Ġent repreneur", + "Ġneuros cience", + "z an", + "Ġext ant", + "AT IVE", + "ope z", + "Ġenfor ced", + "ĠInnov ation", + "ear ance", + "Ġimp ressive", + "ĠPl ac", + "ĠMo z", + "ĠSt ark", + "Ġri val", + "ĠCap ital", + "Ġgranular ity", + "Ġdiaphrag m", + "ut aneous", + "ind s", + "Ġphot ograph", + "Ġrect angles", + "T GF", + "Ġse af", + "Ġm aze", + "ĠH W", + "Ġcorrel ators", + "Ġdistinguish able", + "Ġconfound ers", + "Ġlandsl ide", + "Ġto ll", + "Ġwas tes", + "ĠW F", + "Ġend oc", + "Ġcaps id", + "ec und", + "ĠR BD", + "ps in", + "Ġobst etric", + "Ġnanos heets", + "oc ols", + "ren s", + "ĠSub stituting", + "Ġcustom ized", + "Ġres uscitation", + "Ġtub ulin", + "ophy te", + "~~~~ ~~~~", + "pl ants", + "hic illin", + "hal o", + "ruit ment", + "ĠConc rete", + "Ġnanor ods", + "ĠForm s", + "Ġd ying", + "dis charge", + "Ġwell being", + "Ġwar mer", + "ĠS SD", + "ĠA UT", + "ĠCon jug", + "Ġjuven iles", + "Ġine vitably", + "ĠM CS", + "appro ach", + "ĠM ason", + "ĠG ust", + "ĠTherm odynamics", + "Ġpe el", + "ĠTranscript ome", + "Ġindisp ensable", + "ur gery", + "pos ity", + "Ġpolar izations", + "ĠOther s", + "Ġsand y", + "Ġgli omas", + "Ġpurs ued", + "V EL", + "Ġr st", + "pos ium", + "ne arest", + "Ġdissem inated", + "ĠMY C", + "Ġal dehyde", + "ĠDiagn ostics", + "m ans", + "Ġas phal", + "ĠSe lect", + "ĠRec on", + "and ro", + "D IM", + "Ġf eces", + "ill on", + "ĠMAL DI", + "n f", + "ĠE lim", + "Ġhapp y", + "ĠKar l", + "ĠIn ser", + "Ġinter rog", + "In tern", + "Ġtensor flow", + "Ġhalo es", + "Ġanticip ate", + "ĠDPP H", + "rÃŃ guez", + "H er", + "an ate", + "Ġd ressing", + "ĠH oly", + "Ġnew er", + "rid es", + "plac ed", + "inet obacter", + "ĠOcc urrence", + "ed ema", + "ĠI k", + "ab ad", + "ĠTrans itions", + "Ġoutl ines", + "Ġcoch lear", + "G y", + "s uccess", + "ĠM EM", + "ast ype", + "Ġnormal izing", + "Ġtermin ates", + "Ġsudden ly", + "b box", + "ĠP ul", + "ĠP TP", + "ag inal", + "Ġpre trained", + "Ġun reliable", + "ĠGraph ical", + "ĠSey fert", + "Ġcharacter izations", + "Ġt x", + "Ġbic arbonate", + "math ord", + "Ġher itability", + "stack exchange", + "i ri", + "âĢ ĸ", + "ip it", + "at tle", + "Ġare na", + "ib a", + "ĠA X", + "ĠG Ps", + "ophil ia", + "S EL", + "os ystem", + "ĠâĬ ¢", + "ĠNucle us", + "red ited", + "AC R", + "ĠAnt enna", + "ĠCd c", + "or ie", + "Ġequil ibration", + "el ong", + "st ability", + "ĠSch ist", + "Ġinject ing", + "h p", + "Ġvit amins", + "Po isson", + "or tal", + "Ġà Ĭ", + "ĠÄ ı", + "I ll", + "Ġutil s", + "оР²", + "ĠG rom", + ":: ::", + "ĠGn RH", + "ĠSier ra", + "Ġd rafted", + "Ġcap ita", + "sh ips", + "Ġtim estamp", + "Ġsubstit uents", + "ĠNot able", + "ĠPur pose", + "in ol", + "Ġa i", + "Ġf og", + "ot one", + "ĠPl aces", + "bys hev", + "ti ology", + "ri ption", + "Ġy ards", + "ĠX I", + "Ġtechn ically", + "G AM", + "ĠA BS", + "pl atform", + "ĠW O", + "PRO C", + "Ġrecons tit", + "ĠAnomal ous", + "ĠBi ol", + "St age", + "ĠReview s", + "Ġrecall ing", + "Ġille gal", + "l und", + " ¬", + "ut henium", + "ĠP es", + "Ġo varies", + "sol utions", + "mass ive", + "ĠRA W", + "Ġrecon nection", + "ĠSus ceptibility", + "Ġeconom ical", + "cult ured", + "ĠSh am", + "sq cup", + "Ġp ear", + "dep osition", + "uch s", + "ĠS aw", + "Ġembol ism", + "B ur", + "n ar", + "ou le", + "Ġtex tile", + "se ven", + "th io", + "Ġden oising", + "CE P", + "Ġubiquit ination", + "ĠCarl os", + "a P", + "Ġfol der", + "Ġhemat ological", + "il uminescence", + "ĠF uel", + "ic ion", + "ac ulture", + "AR B", + "ĠTra vel", + "F unc", + "ac les", + "ĠIn te", + "Ġvacu a", + "Ġcock tail", + "ĠIn sp", + "Ġcor porate", + "Ġdepic ting", + "Ġspr int", + "ĠmTOR C", + "Ġc img", + "oc arbon", + "ĠD ave", + "ĠG b", + "ij i", + "target ing", + "Ġsequest ration", + "B ri", + "I GF", + "Ġanaly tics", + "ĠAc inetobacter", + "get s", + "MP S", + "ogl uc", + "C ent", + "Ġver bs", + "Ġinduc tance", + "di agram", + "Ġrec alled", + "Ġcos me", + "Ġautom otive", + "ĠPD Es", + "ĠRe id", + "Ġadap ter", + "ĠOl iver", + "Ġaval anche", + "V ir", + "ĠT oxicity", + "ĠLe u", + "Con clusions", + "Ġtet ragonal", + "ĠDM F", + "umann ii", + "ĠRequire ments", + "t oc", + "it é", + "Ġcontin ent", + "ĠH ank", + "ĠDef initions", + "GP U", + "orig in", + "Ġdich ro", + "M us", + "Ġb ival", + "Ġimp ulsive", + "Ġassemb le", + "Ġpip es", + "doc s", + "Ġexchang er", + "Ġall ograft", + "lo yd", + "ĠÌ ĭ", + "Ġanten atal", + "Ġgrass land", + "Ġhy stere", + "ĠAnti gen", + "ĠGener ic", + "ĠT uring", + "ĠEx cell", + "ĠHe in", + "aj a", + "umin um", + "cit abine", + "f acial", + "iter ation", + "Ġsl urry", + "AM L", + "erge tic", + "ĠTH F", + "Ġkil ometers", + "f g", + "ed uc", + "id ian", + "Ġpredic ates", + "Ġradi os", + "ĠPer i", + "ĠShe ll", + "Ġarc sec", + "Ġstri atal", + "Ġce iling", + "olith ic", + "Ġexhaus tion", + "P UT", + "ther s", + "ym p", + "ĠQ ian", + "ĠProg ressive", + "Ġw el", + "ĠCon vention", + "ĠCur ie", + "ĠM ans", + "ĠN ova", + "ĠW ells", + "de w", + "St andard", + "real istic", + "trans pose", + "ser ial", + "ĠT x", + "ĠA MR", + "Ġind eterm", + "ĠLi ouville", + "hook rightarrow", + "AR s", + "Ġbase ball", + "ac ious", + "agne tization", + "es timate", + "ĠP AS", + "Ġme als", + "multi ple", + "ĠBiomark ers", + "W ide", + "ĠTom ography", + "//////////////// ////////////////", + "Ġres ins", + "Ġany where", + "IN C", + "ĠTe aching", + "ĠSam uel", + "Ġhall mark", + "ĠTh yroid", + "oth i", + "Ġconst raining", + "ĠBar rett", + "ĠEr rors", + "C ole", + "sh aring", + "HD L", + "Eff ect", + "ĠT olerance", + "Ġstress ful", + "ĠBal ance", + "ĠT ech", + "Ġval leys", + "set up", + "ĠRad ical", + "ĠMacroph ages", + "Ġinter rupt", + "Ġdi atom", + "col ored", + "Ġpy rid", + "FD G", + "à ¦", + "Ġre ared", + "ĠR ating", + "Ġop aque", + "pack age", + "Ġnas opharyngeal", + "Ġprecondition ing", + "D iptera", + "ĠM ing", + "ĠCa ro", + "ĠImmun ity", + "rif uge", + "ĠObj ectives", + "g han", + "uc cin", + "ĠF ors", + "ĠF ITC", + "Ġse ats", + "ĠImp aired", + "Ġreef s", + "em aker", + "Ġoff ices", + "Ġaccept ing", + "ĠTR AN", + "ĠTarget s", + "Ġcorrel ator", + "Ġsuper capac", + "in burgh", + "Ġcoll ider", + "Ġenter ic", + "ĠSTR UCTURE", + "Ġmin ister", + "ĠArch ae", + "Lo op", + "ĠA SA", + "Ġcont acted", + "Ġhis tidine", + "fold ed", + "S earch", + "Ġresp ects", + "ĠAT F", + "Ġtro uble", + "Ġprev ailing", + "C p", + "ĠT CM", + "ĠSp inal", + "Ġgu ides", + "ev itable", + "Ġb rick", + "str ings", + "ĠHung ary", + "Ġe ps", + "ent ricular", + "Spec ifically", + "and o", + "iss ues", + "osom iasis", + "k Da", + "Ġas ide", + "Ġaden ine", + "Ġmotiv ate", + "strat ig", + "B LE", + "ĠDep osition", + "m otor", + "ĠH ers", + "Ġne bul", + "ĠBar rier", + "Un like", + "Ġball istic", + "Ġsouth western", + "ĠMont real", + "S can", + "Ġm ould", + "Ġinter rup", + "small matrix", + "Ġelabor ated", + "uc ks", + "AP S", + "ĠCons umption", + "cap acity", + "inn itus", + "Ġgovern ance", + "Ġp alsy", + "Ġsub mission", + "Ġtem ple", + "ĠII A", + "meth ionine", + "Ġker at", + "Ġrid ges", + "Prom ega", + "c ols", + "IS P", + "Ġap nea", + "ĠFl at", + "ĠEp igenetic", + "Ġpar ish", + "ĠPar ametric", + "d ash", + "f uture", + "r ise", + "Ġcontract ing", + "alg ia", + "Ġg oto", + "stad t", + "Ġfabric ate", + "Ġdimer ization", + "d ump", + "ĠL yn", + "Ġrecycl ed", + "posed ness", + "ĠSens ory", + "ï Ŀ", + "ĠW et", + "Ġdi ethyl", + "Ġbl ades", + "Ġtim ed", + "Ġkey word", + "Ġpolyt ope", + "ĠG ot", + "Ġapproxim ates", + "With out", + "ĠB ere", + "ĠL p", + "opl asty", + "ĠF ibr", + "mod ulated", + "ĠAR M", + "Ġunde restimate", + "ĠC BS", + "ĠL ectures", + "unc an", + "ĠSe ismic", + "So ft", + "Ġzo oplankton", + "Ġencephal opathy", + "ĠS SA", + "ĠC ros", + "ĠH ann", + "Ġsh uffle", + "sc ription", + "ĠRever s", + "Stud ies", + "Ġsoc ially", + "Ġsub cl", + "ĠY ong", + "og h", + "Ġïģ ³", + "UD Y", + "ĠHa ar", + "ĠDoc tor", + "Ġint akes", + "Ġbar rel", + "ĠTR PV", + "ĠAgg reg", + "ny i", + "tun ed", + "ac quired", + "Ġho ok", + "F GF", + " «", + "ĠIn jection", + "Ġgra vel", + "Ġmicro g", + "Ġmen strual", + "Fe ature", + "I RE", + "u u", + "ĠS rc", + "ĠSt ore", + "Ġiniti ator", + "PS O", + "Ġepile ptic", + "Ġcing ulate", + "I J", + "R ow", + "Ġsing ing", + "ĠMet han", + "ĠAld rich", + "Ġtremend ous", + "am ining", + "Ġtrac ts", + "Ġâİ £", + "kl ah", + "D iv", + "ind ol", + "Ġind ole", + "ex per", + "Ġgly cer", + "Ġbenz yl", + "Ġwors ening", + "Ġunambig uous", + "u art", + "Ġpar sim", + "ric ks", + "Ġtra il", + "ĠBl anc", + "Ġamin otransferase", + "ĠD OC", + "Ġfum ig", + "id ic", + "ĠCon sequences", + "Ġacid ification", + "ĠCIF AR", + "ĠD atasets", + "ĠA MI", + "Ġexpl ants", + "ĠD iverse", + "Ġde phasing", + "Ġpar liament", + "ip ient", + "Ġhoney comb", + "he avy", + "Ġwaterm ark", + "M ED", + "d atasets", + "w aters", + "Pro vid", + "inter pret", + "rov irus", + "I o", + "R AD", + "Ġl unar", + "Ġwe aning", + "Ġsensor imotor", + "uc a", + "Ġinf ect", + "ĠUn ique", + "GR P", + "Q oL", + "osp ec", + "Ġforward ing", + "Es tim", + "ÅĦ ski", + "ĠM s", + "ach n", + "Ġro ta", + "Ġappoint ment", + "ĠMed al", + "Ġaden ovirus", + "quin ol", + "Ġdeuter ium", + "te p", + "ĠSt yle", + "N d", + "ay ama", + "ĠH amm", + "ĠSpec ification", + "v ability", + "th a", + "Ġj itter", + "Ġâİ ¦", + "a qu", + "w ire", + "Ġclass ically", + "Ġsuper potential", + "ĠSpec im", + "ĠVari ance", + "Ġalbum s", + "ĠSen ior", + "Ġneurotrans mitter", + "ĠRecomb inant", + "D CS", + "v l", + "Ġp f", + "Ġin evitable", + "ĠN ick", + "Ġmanip ulating", + "itu ximab", + "ce iver", + "ĠB ren", + "ĠR ace", + "Ġret arded", + "mod ulin", + "Cl inical", + "Ġneu rologic", + "ĠReg iment", + "Ġzo om", + "ĠOrth ogonal", + "ĠConcer ning", + "ĠJur assic", + "ĠAr tem", + "ĠMel bourne", + "b ins", + "j l", + "Ġin hab", + "Ġsq rt", + "Ġsemis imple", + "ast ric", + "ĠPro xim", + "ĠVari ants", + "Ġa esthetic", + "Ġsummar ised", + "ĠBeck er", + "O CH", + "d ale", + "Ġm ounting", + "and ering", + "Ġsoft max", + "Ġneuro inflammation", + "Ġesophag us", + "oper ators", + "ĠAD AM", + "Ġviol ate", + "ĠPH Y", + "ed e", + "ĠC her", + "ors al", + "Ġmetam orphic", + "ĠI CM", + "ĠAb cam", + "sl ot", + "ser ine", + "Ġdu plicates", + "ĠME MS", + "ĠA bl", + "ĠC hel", + "ĠAuthor ity", + "Ġge o", + "Ġhome omorphism", + "Ġimmunomod ulatory", + "ĠT U", + "ĠK T", + "ater ally", + "ox ides", + "teb ral", + "Ġcatar act", + "le aved", + "ig u", + "ate ur", + "ĠR é", + "Ġdiscover ies", + "bos on", + "oc ated", + "j pg", + "ĠS ato", + "ĠPRO P", + "ĠIm plement", + "EL ISA", + "iqu eness", + "Ġsym bion", + "ĠFar aday", + "ĠPPAR γ", + "w itz", + "re ward", + "ĠB ush", + "st ressed", + "ĠA bor", + "Ġair ways", + "Ġinterfer ometry", + "C irc", + "Ġimmun oprecipitation", + "ĠAp ache", + "roph osph", + "Ġo C", + "Ġf rog", + "ĠG U", + "ff e", + "ĠSt ro", + "Ġdodec yl", + "d an", + "f olds", + "ĠM ust", + "Ġsurround ings", + "Ġcod ons", + "ond a", + "t b", + "od ge", + "av as", + "ĠSe ason", + "t ude", + "ĠPl asticity", + "ĠHawai i", + "D EG", + "ĠC MD", + "Ġsingle ton", + "ke ley", + "Ġalgebra ically", + "Ġnano structured", + "eas ible", + "Ġoverlo oked", + "ĠP ulse", + "rom echanical", + "ĠEl se", + "Ġexcit ons", + "ĠConst rained", + "Ġco hesion", + "Ġreal izing", + "ĠRadi ative", + "Ġtryp an", + "x s", + "ĠT as", + "Ġmain stream", + "Ġcompact ly", + "g rowing", + "es c", + "Ġd N", + "ĠSign atures", + "ĠFundament als", + "Ġex pose", + "ĠR ang", + "Ġhand ed", + "Ġfunctional ization", + "Ġpass iv", + "al tern", + "ag ul", + "Ġschem atically", + "O W", + "Ġ Ö", + "ĠP OD", + "Ġhe ar", + "ym ore", + "ĠPrem ier", + "S outh", + "Ä «", + "ĠO BS", + "ĠAl g", + "gl ia", + "ĠTrans membrane", + "Ġsphe roids", + "ĠR HS", + "Ġinc hes", + "ĠK ato", + "Ġi e", + "ĠCom mercial", + "Ġanaly tes", + "Ġrisk y", + "Ġp iston", + "ĠMark ovian", + "Ġdram a", + "Ġc i", + "ĠHist ological", + "Ġact uation", + "disc rete", + "carb amoyl", + "S MA", + "Ġfeed s", + "Ġneoplas ia", + "ĠControll er", + "b een", + "glut amine", + "in jected", + "Ġc rab", + "ĠC auses", + "ĠSt ory", + "Ġvan adium", + "ĠT itan", + "en ix", + "ass ign", + "Ġimmun ogenicity", + "ĠAp parent", + "Ġenh ancers", + "ĠS ou", + "all oy", + "mathb in", + "Ġsed ation", + "ĠWork shop", + "g over", + "l st", + "Ġup welling", + "me z", + "Ġpoly propylene", + "ĠCol orectal", + "ĠRel axation", + "Ġfrag ile", + "Ä ĥ", + "Ġsub graphs", + "the oretical", + "Oper ator", + "ly wood", + "aw n", + "ĠPer centage", + "methyl ation", + "corrhiz al", + "G rad", + "d ens", + "ĠH α", + "Ġup coming", + "Ġvir gin", + "N ames", + "ĠR yd", + "Ġâİ ¤", + "phosph orylation", + "renew al", + "Y ear", + "In it", + "Ġs elling", + "ĠM ASS", + "roph in", + "ij n", + "Con versely", + "Ġunivers ally", + "orh ombic", + "Ġunpredict able", + "F ock", + "ch air", + "iv as", + "network s", + "Ġterr itories", + "th ia", + "ĠAm plification", + "M arch", + "Ġf lam", + "ĠCh art", + "Ġshort age", + "AM ET", + "Ġgrap e", + "Ġvoltam metry", + "Ø ¯", + "ĠS CH", + "Ġepit hel", + "ĠChrom osome", + "ĠX L", + "ĠPers istent", + "Ġtravel ed", + "Ġmerid ional", + "Ġf printf", + "Ġg um", + "vis ory", + "Un fortunately", + "Ġant eced", + "Ġfric tional", + "D AT", + "ac l", + "ĠP regnancy", + "ĠB Z", + "reg ulatory", + "stim ulating", + "J apan", + "m achine", + "u ti", + "ĠL er", + "Ġnan oflu", + "prot otype", + "identif ication", + "klah oma", + "ĠEm ploy", + "Sch warz", + "Ġincorrect ly", + "at to", + "ri zation", + "ism uth", + "Ġir is", + "iment ary", + "Ġinflation ary", + "Ġoutflow s", + "ĠL ic", + "ore ductase", + "Ġproceed ing", + "ĠT AC", + "ĠH TL", + "Ġres ides", + "str al", + "ĠTrans f", + "Ġdich otom", + "Fil ter", + "J une", + "is ure", + "ĠA de", + "Ġij k", + "ĠPhil os", + "Ġstay ed", + "Ġtam oxifen", + "Ġaspar agine", + "ex ception", + "Ġaccum ulating", + "ast ro", + "Ch ange", + "uz i", + "Ġl on", + "In stead", + "Ġcent rally", + "ĠD ental", + "class ified", + "ĠEg yptian", + "Add ress", + "ĠQuatern ary", + "ĠU SP", + "co in", + "Ġembry ogenesis", + "ïĢ ¨", + "N ull", + "ĠM ixing", + "int ensive", + "Ġnorm ative", + "ĠL ef", + "Ġr umen", + "ĠTh ai", + "Ġsw allow", + "Comp onent", + "Ġrobo tics", + "ĠC ad", + "ĠC IP", + "ĠAc ids", + "ĠO ffic", + "ure r", + "ĠW ick", + "Ġk ink", + "ĠSch a", + "ĠCharacter istic", + "f amilies", + "ĠG Cs", + "ĠOptim izing", + "Ġtim er", + "é l", + "j in", + "re versal", + "Ġsand stone", + "H N", + "t k", + "Ġp tr", + "Ġmon ochromatic", + "Ġfeed forward", + "ding ton", + "Ġcritic ism", + "Ġs ig", + "Ġp ace", + "ĠT K", + "ĠW as", + "Ġcertif icate", + "Ġst uck", + "Ġcor rid", + "Ġlocal isation", + "Ġsil k", + "Ġdig est", + "ĠTem ple", + "ĠPost erior", + "Ġcommut ator", + "ts ch", + "per me", + "ys ed", + "Ġmen u", + "Ġmid w", + "oc atalytic", + "Ġpp b", + "T ypes", + "ar ri", + "ĠL OD", + "Ġlo an", + "sec ret", + "Ġcarb ons", + "ĠH olog", + "olip ids", + "Ġupl o", + "ĠDN ase", + "Ġpuzz le", + "Ġst ance", + "ĠManc hester", + "ĠDet ector", + "im s", + "ĠTerm s", + "ĠP GC", + "Ġinc idents", + "ie h", + "ĠID s", + "ĠAh mad", + "Ġn ights", + "Ġbiom o", + "ĠMethyl ation", + "u ator", + "res ize", + "ĠF inger", + "ĠW o", + "Ġpost er", + "Ġsolid ification", + "ĠVal idity", + "ĠDend ritic", + "Ġad herent", + "iss ions", + "inc tion", + "Ġantagon istic", + "ĠPrelim inaries", + "Ġco val", + "Ġmov ies", + "Ġbud ding", + "K n", + "ĠG it", + "ĠThere after", + "Ġcapac itive", + "A z", + "ĠT LS", + "Ġiniti ates", + "ĠD MR", + "Ġâī «", + "ĠMy ocardial", + "ĠRot ation", + "CON FIG", + "Ġvow el", + "Ġoliv ine", + "H amiltonian", + "Ġst alk", + "N eu", + "R est", + "an ical", + "Ġd st", + "Ġres h", + "Ġexp ressive", + "Ġinf ectivity", + "ok u", + "CT L", + "F requency", + "Ġprem ise", + "W alk", + "Ġâ Ĺ", + "Ġrel apsed", + "t ured", + "ĠU ML", + "ov an", + "ĠRes earchers", + "Ġconven iently", + "us k", + "IN IT", + "Eq s", + "F actory", + "Ġun steady", + "ĠAn sw", + "Al a", + "nit ine", + "q p", + "ul ous", + "res earch", + "ĠB rom", + "ĠDem oc", + "config uration", + "ulos ic", + "Ġf ra", + "Ġg ift", + "Th ird", + "Cl aim", + "Ä Ł", + "od iazep", + "Ġpro x", + "oc ystis", + "ĠR PA", + "ĠLik ert", + "R MS", + "t ech", + "Ġac ous", + "T LR", + "b uck", + "ĠThe rap", + "uss ions", + "hel or", + "ĠEm otion", + "b ird", + "Ġth io", + "Ġquantit ation", + "brack et", + "Ġper cept", + "Ġsub category", + "Ġlight ning", + "Ġher nia", + "Ġneurot rophic", + "SD S", + "ĠAnd ers", + "Ġslow ing", + "strong ly", + "ĠC ounting", + "ĠIn cluding", + "duc tions", + "ub ated", + "ĠSt orm", + "cor related", + "Ġautoanti bodies", + "ĠM erg", + "oc er", + "mic utes", + "Ġnonlinear ities", + "ĠCent ury", + "ĠLand scape", + "ĠDeriv atives", + "ĠContr ary", + "Ġcomp ile", + "ĠHep atic", + "Ġpond s", + "Ġorgan ize", + "D MSO", + "P osition", + "Ġb rach", + "Ġinf lat", + "osp ace", + "Ġskew ness", + "Ġag itation", + "ĠHO MO", + "E U", + "Ġcom mented", + "Ġcor pora", + "Ġmal t", + "Herm itian", + "id ay", + "ĠHelm holtz", + "ro blast", + "ĠC TR", + "un ching", + "ĠM ond", + "ĠCom ment", + "Ġoste osarcoma", + "post erior", + "Ġthym us", + "Ġcig arettes", + "N W", + "o lem", + "ĠH ox", + "ĠNF L", + "ĠAvail able", + "ĠS iber", + "ĠF eld", + "Ġborder line", + "Ġbe ats", + "Ġorgan ised", + "Ġdistingu ishes", + "Ġdial og", + "ĠBerg er", + "ole ic", + "Ġnum bered", + "Ġreach able", + "ĠRoberts on", + "ĠCham ber", + "nd array", + "Ġcytos keletal", + "Ġbl ending", + "bl ood", + "Im port", + "Ġoverwhel ming", + "Ġi o", + "Ġout age", + "ĠSch olar", + "plac ing", + "ĠPol yp", + "Dec l", + "ĠMED LINE", + "ĠK M", + "ĠD AP", + "err ors", + "ĠS HR", + "ĠD ex", + "ĠG AS", + "ĠG ian", + "Ġclinic opathological", + "Ġïģ ·", + "ĠPredic tions", + "ĠQuad ratic", + "Ġarrhyth mias", + "ar id", + "Ġcl othing", + "ĠFract ure", + "ĉ ĠĠĠĠĠ", + "add y", + "ĠAlber ta", + "ĠW ed", + "phi re", + "ĠEn cryp", + "ĠL AB", + "ĠF ano", + "CT T", + "Ġor yz", + "ili ac", + "ĠL iao", + "vers us", + "Ġmes o", + "Ġmid point", + "Ġst ator", + "ĠJ enn", + "ov sky", + "Ġunc over", + "eren n", + "ĠMc M", + "âī Ī", + "ĠCirc uits", + "Ġfet uses", + "Ġaggl omer", + "Ġf b", + "Ġy y", + "at ech", + "AR G", + "Ġba umannii", + "Ġellipso id", + "Ġl oses", + "Ġun ve", + "Ġbut t", + "Ġmultic entre", + "il ine", + "Ġres ort", + "Ġcereb rovascular", + "ĠDecre ased", + "j ud", + "s us", + "am ol", + "const raints", + "Ġt een", + "ĠPass ive", + "ĠCauc asian", + "Ġc ran", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠ", + "ü n", + "ĠDN MT", + "Ġt error", + "ad renal", + "Ġangi ogenic", + "ĠInhib itory", + "p rag", + "Ġco b", + "els h", + "Ġenhance ments", + "ĠSha w", + "ĠTak ahashi", + "Ġsulph ur", + "Ġgrav itation", + "ĠPVD F", + "m ust", + " ¢", + "as ymptotic", + "el man", + "ĠP ros", + "ĠM AD", + "ĠL en", + "the rapy", + "eful ly", + "sulf ur", + "ĠT CA", + "ad ditive", + "tal k", + "Ġpig lets", + "Ġprosp ect", + "ecund ity", + "ĠX iang", + "hand ler", + "Ġcl ath", + "Ġmill imeter", + "j ar", + "Ġbi ophysical", + "Ġcomplex ities", + "ĠHer b", + "Ġrecover s", + "ĠVin cent", + "ĠPuer to", + "E arth", + "R AM", + "Ġc ables", + "des igned", + "ĠOscill ation", + "Ġme iosis", + "Ġfle et", + "ĠHunting ton", + "ĠB eg", + "ĠE Cs", + "ĠAn tic", + "Ġpractition er", + "c ultural", + "k at", + "Ġrec oil", + "ĠIm plicit", + "Ġsumm aries", + "Ġdiscontinu ed", + "Ġencompass ing", + "ĠAlt ogether", + "ĠD IST", + "Ġconst ellation", + "ĠEx isting", + "Ġconduct ors", + "oplas m", + "ĠCosm ology", + "Z ero", + "ĠIn form", + "Ġend angered", + "Ġweap ons", + "at ype", + "ĠAs c", + "Ġflu ence", + "Ġfer ric", + "ĠLaure nt", + "Ear ly", + "Ġs gn", + "ĠHad amard", + "Ġastr on", + "C ys", + "ĠTh m", + "Ġdec e", + "eren cing", + "ĠMe ans", + "Ġhyd rated", + "Ù Ī", + "Ġrig orously", + "Ġamb ulatory", + "ĠDO I", + "Hand le", + "ĠEnterobacter iaceae", + "ĠR Q", + "ĠG FR", + "pro te", + "Ġmig rated", + "then ing", + "ĠHop kins", + "ĠPsych ology", + "ig l", + "ĠE DS", + "ĠâĪ ¶", + "Ġrem otely", + "Ġ ¥", + "Ġinsp iration", + "ĠâĮ ¬", + "ol ian", + "Ġsal iency", + "ĠD og", + "ĠR osa", + "oy a", + "Ġoccup ies", + "cam era", + "Ġdecomp ression", + "Ġsc att", + "Ġinvestig ator", + "Ġcount erex", + "ĠIFN γ", + "ĠPitts burgh", + "Ġad minister", + "ne gl", + "uss is", + "MP C", + "ĠSw itching", + "Ġcool er", + "Ġbron chi", + "Ġpar alle", + "Ġspec kle", + "Ġphys iologic", + "IN VAL", + "Ġheter ologous", + "|| |", + "org hum", + "G AL", + "Ġmal formations", + "Ġweak ening", + "Ġpsych o", + "ĠI H", + "Ġcontrad ictory", + "Ġphon ological", + "ĠPerturb ation", + "b B", + "ĠN os", + "TR UE", + "fold ing", + "phen ol", + "ĠL SM", + "ĠâĪ Ĺ", + "ĠAn gle", + "Ġprov incial", + "Fe O", + "Å Ľ", + "ĠI ber", + "ress ors", + "Ġprolifer ating", + "z ers", + "organ ism", + "âĨ ĵ", + "Z O", + "c img", + "Ġun perturbed", + "Ġj j", + "Ġelectro dynamics", + "ĠEp it", + "NT s", + "ĠBlo om", + "Ġl anth", + "am inant", + "ĠSw ift", + "Europe an", + "Ġaff erent", + "Red uce", + "p ublished", + "ĠF itting", + "ĠF ungal", + "Ġtrib e", + "rec ting", + "Ġconjug acy", + "im eters", + "ĠC ec", + "ĠK H", + "cast le", + "Ġsept al", + "rele asing", + "Ġo ss", + "Ġ ¦", + "ĠMiss ing", + "ĠFat igue", + "ĠBase ball", + "Ġimmunoblot ting", + "Ġo h", + "or ations", + "Ġv ine", + "az y", + "ser um", + "Ġlook up", + "Ġne ovascular", + "ia h", + "so il", + "Ġair flow", + "ĠSlo an", + "h im", + "ç ļ", + "loc ated", + "z antine", + "ĠS uccessful", + "em inal", + "ĠD imensional", + "ĠN SA", + "ĠLog istic", + "emet ery", + "Ġb rak", + "ant al", + "so uth", + "Ġprot otypes", + "Ġadv ised", + "Ġideal ized", + "ophy tic", + "nb sp", + "B inary", + "H yp", + "J oh", + "p olation", + "Ġpoly vinyl", + "estim ated", + "Ġox ytocin", + "ĠLet ter", + "ĠImp air", + "Ġenvelop es", + "main ly", + "Ġm ys", + "Ġint ras", + "Ġbi ogenic", + "cy steine", + "Ġur ic", + "ĠCy an", + "ryp tion", + "Ġphotore ceptor", + "ĠT oxic", + "ĠG amm", + "Ġcontain ment", + "Ig G", + "S qu", + "Ġperf used", + "Ġbios ensors", + "Ġmag matic", + "R ate", + "ĠT f", + "Ġsec rete", + "Ġcritical ity", + "Ġcomposition ally", + "ĠBr uce", + "S Z", + "ĠS port", + "ĠE I", + "Ġdise ased", + "Ġpres chool", + "ĠHar vey", + "ĠPT H", + "Ġbil ayers", + "ĠOscill ations", + "ĠHon or", + "ĠC CN", + "ĠM OT", + "ĠL loyd", + "Ġtrap ez", + "Ġbud s", + "OFF SET", + "Ġmac romolecules", + "Ġbil irubin", + "ol ly", + "Ġutil ities", + "minist ered", + "Ġglob e", + "OLOG Y", + "rop ods", + "ĠMD M", + "ĠPy Object", + "mac roph", + "ĠP BMCs", + "osp heres", + "Ġcatast rophic", + "ĠNavig ation", + "ĠL SD", + "Ġcre am", + "Ġdere g", + "b onded", + "ren ts", + "Ġpotenti ation", + "Ġst ro", + "Ġst eeper", + "ulin um", + "Ġperiodon titis", + "ar ization", + "âĪ ª", + "amic in", + "Ġmagne tized", + "ĠNutri tional", + "Ġacc ord", + "ga ard", + "FT IR", + "r amethyl", + "ĠG le", + "M el", + "ĠCT L", + "Ġtransl ating", + "Ġauto immunity", + "oler ant", + "triang leq", + "am o", + "Ġv el", + "ĠH CN", + "ĠH amming", + "ĠVen us", + "ĠG ad", + "ĠO wing", + "In formation", + "ĠSchem es", + "caro tene", + "I ts", + "an is", + "Ġre play", + "Ġto uc", + "LE CT", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠ", + "Ġtab ulated", + "ĠSchott ky", + "F ar", + "am ation", + "ĠR ies", + "Ġexp ects", + "ĠInst ability", + "Ġs ons", + "Ġdec k", + "Ġïģ ¥", + "ĠSign ature", + "Ġlith osphere", + "W W", + "m akers", + "ugh ters", + "Ġâİ ¡", + "ardi an", + "à ¦", + "Ġac cepts", + "ĠO SA", + "Ġγ δ", + "non umber", + "S elect", + "l ite", + "ĠA queous", + "ag awa", + "ĠEd inburgh", + "ĠMemb ranes", + "ĠS IG", + "ak ia", + "Ġtest es", + "Ġhel i", + "++ ++", + "Ġultraf ast", + "Ġmaneu ver", + "ĠD ate", + "ph in", + "ĠK ad", + "Ġtransfer ase", + "P ers", + "Ġt ones", + "ĠS GD", + "ant o", + "ĠO range", + "ĠGe ography", + "ĠAcc umulation", + "at y", + "Ġbe ating", + "Ġover lying", + "ĠND VI", + "ĠTown ship", + "j ing", + "ĠN OS", + "play er", + "ĠMD D", + "ĠHung arian", + "Ġd w", + "ĠH in", + "Ġvalid ating", + "Ġcolor imetric", + "ĠSupers ymmetric", + "F UNC", + "g ically", + "of uran", + "---- ---", + "Ġimp ing", + "similar ity", + "ĠD OX", + "ĠG lo", + "iv irus", + "list ed", + "Ġbus y", + "ipro floxacin", + "Ġan xi", + "Ġbl unt", + "Ġproced ural", + "Ġunknown s", + "Ad S", + "thick ness", + "follow s", + "cl osing", + "environment al", + "ĠFeed ing", + "un ami", + "end e", + "ip ine", + "Ġimpact ing", + "Ġpenet rating", + "amb ia", + "ĠWave let", + "Ġfilament ous", + "Ġl eng", + "ĠS CA", + "ĠE ther", + "met all", + "Ġfr inge", + "ĠAdj ust", + "us z", + "ĠR ey", + "ĠBo yd", + "Ġburn out", + "Ġco ok", + "Ġnow adays", + "ĠDispers ion", + "ĠRodrig uez", + "F actor", + "ĠO klahoma", + "Ġun ital", + "Ġpredict ability", + "Ġlith ography", + "è s", + "W illi", + "un al", + "ast ing", + "cor rection", + "ĠD ed", + "ĠSoci o", + "ĠChap man", + "ĠE co", + "Ġonc ogene", + "ĠDri ve", + "Ġfun nel", + "u is", + "ĠGEN ER", + "ĠA CR", + "Ġworkload s", + "Ġocta hedral", + "v ich", + "en burg", + "Ġimpro per", + "dec oded", + "Ġimmunos orbent", + "Ġinhom ogeneity", + "R K", + "on ically", + "Ġglycoprotein s", + "on ics", + "ĠF ok", + "ĠB ras", + "ĠCalc ulus", + "ĠM oss", + "ĠR K", + "Ġvi olet", + "Ġlymph omas", + "ens pace", + "ĠPal ae", + "Ġren in", + "ph ant", + "ĠRE D", + "Ġfault y", + "Ri emann", + "à ī", + "ĠEll i", + "B ol", + "T n", + "Y ang", + "g ender", + "Ġdet uning", + "Ġoper on", + "Ġinsectic ide", + "es i", + "am on", + "ĠS CD", + "ĠB ath", + "ĠâĢ ĸ", + "ĠGe ographic", + "Ġcycl ohex", + "ĠConf idence", + "Ġcom et", + "Ġfol ate", + "ob server", + "Ġvis itors", + "ext ra", + "at eness", + "ĠS PT", + "arc ane", + "Ġhol istic", + "sem i", + "ĠM ild", + "Ġsm ear", + "Ġcycl ase", + "Ġan ymore", + "Ġse agrass", + "Ġcons ortium", + "Ġfin ishes", + "cy an", + "duct ance", + "f rost", + "here after", + "Ġpres criptions", + "Ġcm d", + "ĠPer ceived", + "co ordinates", + "Ġst yl", + "ĠB ard", + "ĠH oll", + "Ġsi RNAs", + "s ugg", + "Ġth r", + "Ġmain land", + "SC H", + "Ġasser tions", + "Ġbab ies", + "Ġrecap it", + "T ok", + "Ġres ected", + "con struct", + "B er", + "Ġch oline", + "Ġunit arity", + "Ġcataly zes", + "det ector", + "ĠS MB", + "ter y", + "cl uded", + "ĠAb breviations", + "ĠOlive ira", + "L OC", + "z in", + "ĠLore nz", + "K ernel", + "ly n", + "ĠL EP", + "son i", + "Ġsept um", + "T MS", + "Ġun modified", + "bor ough", + "ĠAud io", + "Ġdoll ars", + "CM D", + "Ġnorth western", + "Ġpal mit", + "ragal actic", + "ĠM iz", + "F H", + "conf idence", + "N EXT", + "ĠA GE", + "ĠEq n", + "ĠClass es", + "Ġmis leading", + "ĠPK A", + "Ġanch ored", + "ĠR ip", + "ph ag", + "Ġint ubation", + "ĠAng ular", + "ĠB EC", + "Th r", + "Ġorgan isations", + "Ġcomfort able", + "Ġcommission ed", + "p oll", + "y dia", + "in stead", + "Ġpass word", + "Ġcompl iant", + "ĠPrec ipitation", + "ophosph amide", + "ust ers", + "Ġpneum ococcal", + "Ġtom ographic", + "tida e", + "ĠFir micutes", + "b w", + "ĠPD B", + "ĠGP Us", + "ĠPlan ar", + "Ġverb ose", + "Summ ary", + "l ance", + "ĠE GFP", + "ong ru", + "Com plex", + "ĠWhe at", + "uc he", + "ĠM CA", + "ĠPro jection", + "Ġstat s", + "Ġsumm and", + "dim ethoxyphenyl", + "ĠAB STRACT", + "Ġcaroten oid", + "Ġbro ke", + "ĠDesign ing", + "ĠHet ero", + "ĠCarls bad", + "C ov", + "in eral", + "Ġanaly te", + "ĠCo leman", + "Ġeigen state", + "ĠHol land", + "ERS ION", + "ĠD ak", + "ell ers", + "Ġà ĺ", + "miss ing", + "dep osited", + "ĠLinc oln", + "an ion", + "ĠSP EC", + "Ġfertil izer", + "ĠC PS", + "Ġco factor", + "Ġtre n", + "Ġcal endar", + "Ġyoung est", + "STAT US", + "ĠEXPERIM ENTAL", + "Ġs r", + "Ġn l", + "ĠM ES", + "Stud y", + "p adding", + "Ġat opic", + "ĠO G", + "Ġent rainment", + "AF M", + "ĠC ou", + "We b", + "ĠMic roscopic", + "Ġunambig uously", + "D ay", + "y otrophic", + "re ous", + "Ġs arcom", + "ĠV AL", + "Ġhind ered", + "ĠRE M", + "ot rexate", + "oc arcin", + "ĠAl k", + "Ġbre vity", + "fact ual", + "C er", + "di ox", + "oph ical", + "Ġly tic", + "T ake", + "Ġint end", + "ĠCl a", + "Ġaster oid", + "ĠS EP", + "ap enem", + "univers al", + "Ġo ceans", + "Ġmon oid", + "Ġsepar ator", + "ĠP orous", + "Ġpost operatively", + "Ġsem in", + "ĠDis play", + "Ġhyd rolase", + "transfer ases", + "Ġthromb us", + "ĠO v", + "ĠDie lectric", + "Ġcomp elling", + "ass ing", + "ĠM AS", + "ull ary", + "ĠMor i", + "ĠPath ogenesis", + "ĠBre aking", + "ĠPL GA", + "cool ing", + " §", + "Ġfe e", + "Ġreduc ible", + "Ġdiver ge", + "Ġque ues", + "Ġmush room", + "Ġdeacetyl ase", + "Y FP", + "Ġdis reg", + "ĠAr rays", + "process es", + "ĠTransport ation", + "Ġundet ectable", + "bur sts", + "Ġphospholip ase", + "O ption", + "as in", + "Ġn octurnal", + "te z", + "ĠDis ruption", + "oser ine", + "behavi or", + "ĠT ony", + "ĠK ot", + "ie val", + "Ġmy ofib", + "Ġhal ogen", + "ĠC PR", + "ploy ed", + "ĠPol ymers", + "Ġaden oma", + "Ġquar tile", + "Ġquatern ary", + "ĠIra q", + "Ġs ieve", + "Ġint ractable", + "Ġfabric s", + "continu um", + "ĠEmerg ence", + "P ot", + "iti sm", + "ven ess", + "ho e", + "Ġred es", + "ĠHR P", + "ploid y", + "pic uous", + "og o", + "ĠG ag", + "Ġnom inated", + "occup ied", + "Ġqu ench", + "rop olis", + "nucle otide", + "ĠEvent ually", + "Ñ ı", + "ĠCl ock", + "ĠSte ady", + "opol ymers", + "ĠA RE", + "ir nov", + "hel f", + "bl ob", + "down load", + "PL L", + "UN T", + "predic tions", + "Ġocc ipital", + "t oxic", + "ĠV ice", + "Ġang io", + "Cu O", + "Ġresist ances", + "ffl ffl", + "D istribution", + "G re", + "on amide", + "ĠI OP", + "UN EL", + "Ġa ids", + "ĠH UV", + "EC M", + "ĠP AD", + "ĠAg NPs", + "Pr int", + "Ġlam ellar", + "ĠUltr ason", + "se vere", + "ĠAn notation", + "N IR", + "s gn", + "ĠO ften", + "Ġit erate", + "Ġcar riage", + "sp herical", + "ĠF rid", + "Ġdiff ract", + "ĠBas al", + "Ġuns atisf", + "ĠDys function", + "arboxyl ic", + "ĠCol lective", + "Ġdegrad ing", + "Ġadi posity", + "Ġfif ty", + "Ġpar s", + "ĠOptim ized", + "oc aine", + "Ġb b", + "ĠS hip", + "ĠL W", + "Ġtre mor", + "Ġà £", + "Ġnucle ons", + "Ġscienti st", + "ĠM ish", + "g ression", + "ĠM erc", + "ĠF lem", + "Ġcor als", + "In cre", + "ĠD SP", + "Ġdef enses", + "dim er", + "ather ine", + "ot ubes", + "str ide", + "ĠAlter ations", + "Ġo est", + "ĠB IC", + "Ġradi ated", + "Ġket amine", + "Ġdissimilar ity", + "ĠAnc ient", + "ĠH ed", + "Ġatt r", + "ĠIs a", + "Ġion ospheric", + "Ġgover nor", + "ĠEstim ated", + "Ġultr athin", + "Up date", + "Ġimmuno assay", + "Ġconject ured", + "Ġ REF", + "ĠSi egel", + "Ad v", + "M em", + "Ġp ups", + "ĠAP PL", + "ecom posable", + "j ournal", + "ĠR ol", + "ĠL ob", + "ring ton", + "Ġnons ingular", + "Ġcit ric", + "ion es", + "os itis", + "AL Y", + "Ġmen tions", + "ĠMark ers", + "algebra ic", + "Ġflatten ed", + "Ġm ail", + "ĠT GA", + "ĠP MA", + "ĠN aval", + "Ġfac ilitation", + "Ġun identified", + "Ġem pathy", + "ject ories", + "log its", + "Ġperman ently", + "Ġbott les", + "ĠBeng al", + "Ġpean ut", + "Ġcapill aries", + "eren ts", + "ĠLo oking", + "chang es", + "ĠMag ell", + "ĠC MC", + "ĠV erm", + "Ġsubs cales", + "dem and", + "ore xia", + "Ġachieve ments", + "ĠRobust ness", + "ĠWall ace", + "ĠD TT", + "og els", + "ock er", + "ĠSp ike", + "Ġpain ter", + "Ġbus es", + "Ġpoll uted", + "Ġt ort", + "ĠP PP", + "ne x", + "ext ended", + "ucal ypt", + "Ġpro static", + "ĠF CC", + "Ġk ick", + "oy al", + "epoch s", + "h ss", + "y on", + "Ġd ans", + "ĠA w", + "Ġad versely", + "Ġalt ogether", + "Ġophthal m", + "Ġc pu", + "ĠF RET", + "Ġfore nsic", + "Ġhot spots", + "Ġpain tings", + "Ġo mn", + "Ġp S", + "og lu", + "of ol", + "FT s", + "Ġderm at", + "prag ma", + "Ġb ump", + "ĠC ir", + "a S", + "Ġn aked", + "ĠN LS", + "ĠSp itzer", + "Ġsal vage", + "Ġintu itively", + "Ġcas ual", + "Ġf ired", + "ver ages", + "ĠBur den", + "W ang", + "yle m", + "Ġradi ographs", + "ĠSch iff", + "OL UTION", + "C ross", + "Ġh ints", + "ow ing", + "ĠSt reng", + "ĠAN Y", + "Ġwor ry", + "ĠRog er", + "Ġtrabec ular", + "B and", + "ĠN ec", + "ip es", + "to ol", + "ĠIL C", + "i Äĩ", + "o cean", + "ĠA ri", + "AM A", + "ĠVer tex", + "activ ate", + "L ocation", + "on ts", + "Ġh s", + "Ġsl ender", + "ref ring", + "ĠEnd ogenous", + "adi abatic", + "Ġcryp tic", + "Ġerad ication", + "ĠKev in", + "Ġm c", + "Ġcardi o", + "Ġphosphor yl", + "W itten", + "Ġs cl", + "ĠI w", + "ĠM ade", + "Ġfound ing", + "ofl ag", + "al ine", + "hor izontal", + "ĠGeneral ization", + "psy chiatric", + "ĠD uncan", + "ĠSn O", + "ĠA ar", + "Ġg g", + "Ġpre mi", + "ĠSt rom", + "ĠEx plan", + "Ġleth ality", + "Ï Ĥ", + "od o", + "Ġsub scrib", + "ĠST UDY", + "Ġoutper formed", + "Ġcoval ently", + "M HC", + "f ail", + "ĠK ac", + "EG R", + "ĠTR I", + "rob ot", + "ĠCandid ate", + "ĠTN BC", + "Ġarchae ological", + "E ukary", + "Ġl ava", + "di pole", + "Ġunc ons", + "An ti", + "Ġpred nis", + "ĠRob in", + "Ġstratig raphic", + "Ġ ¤", + "Ġfin ance", + "ĠStud io", + "re nder", + "Ġre aring", + "Ġg er", + "ĠO pt", + "ĠMan ifolds", + "Ġdest abil", + "Ġtel omerase", + "Ġpick ing", + "Ġamplic on", + "Ġyear ly", + "ĠN CC", + "ins er", + "ĠEn richment", + "ĠMicro structure", + "ĠWar ren", + "ophys ics", + "Ġfif teen", + "Å ij", + "Ġreview er", + "Ġsk illed", + "Ġmagnet oresistance", + "Ġrecon figuration", + "Ġpo et", + "Ġpred etermined", + "Ġcry opres", + "Ġattract ors", + "Ġprojec tile", + "ĠC rystals", + "ĠM CM", + "ĠX anth", + "Ġclock wise", + "regn ant", + "Ġg ated", + "ry za", + "ĠP rosp", + "ad in", + "Ġm olybdenum", + "ĠAl ps", + "ĠBal d", + "Ġhall uc", + "ud o", + "Ġmon t", + "ĠFl ash", + "Ġpull ing", + "ĠL Q", + "ĠWals h", + "ĠTh omson", + "mes on", + "Ġinter cal", + "Ġel apsed", + "FF FF", + "ĠFore casting", + "à ¯", + "ĠL SP", + "end orf", + "Ġx ml", + "sub strate", + "M u", + "d uring", + "oc onstr", + "EM A", + "Ġïĥ «", + "ĠD FS", + "ĠV on", + "Ġfat hers", + "Ġunc o", + "ĠUnd erg", + "Ġmultiplex ing", + "at ra", + "Ġco hesive", + "ĠU I", + "ĠPre v", + "çļ Ħ", + "c um", + "h f", + "ĠS CN", + "atal ysis", + "ĠAr sen", + "amp ing", + "ĠPl astic", + "ĠMad ison", + "Ġsuprem um", + "ĠC ited", + "Ġare n", + "isk i", + "in el", + "st ro", + "Ġcor rupted", + "Ġgl ab", + "Ġcardi opulmonary", + "Ġprag matic", + "C AG", + "St ack", + "thi oxo", + "ĠRepro ductive", + "Ġste atosis", + "B est", + "ĠB ars", + "Ġr acing", + "ĠU tah", + "equ ivalence", + "ĠFif ty", + "ĠCytok ine", + "Ġutil ised", + "hor izon", + "our acil", + "ivers ary", + "em er", + "ĠQ uestions", + "Ġlink ages", + "anche z", + "V V", + "Ġphotod et", + "k owski", + "RE ST", + "Ġhost ing", + "Ġpush ing", + "Ġneurot oxicity", + "S Q", + "r st", + "Ġh ockey", + "Ġtri ps", + "ĠInd oor", + "em atics", + "Ġtrans ect", + "ĠAB I", + "ag ar", + "âĪ ļ", + "eg enerate", + "ĠQ P", + "MI D", + "ĠAc cept", + "ĠCy ber", + "N orth", + "Ġd θ", + "all a", + "Ġbra id", + "f inding", + "al in", + "ĠL ST", + "ĠL ax", + "ud in", + "Ġi NOS", + "con vert", + "AC A", + "ĠGu an", + "Ġlymph ocytic", + "Ġsyll able", + "ĠT OR", + "ĠS CR", + "ĠA J", + "Ġout burst", + "bl adder", + "OT A", + "aud io", + "chrom en", + "Ñģ ÑĤ", + "Ġgrate fully", + "Ġt iling", + "Ġqu it", + "sh an", + "ĠAcc retion", + "Ġnarrow ing", + "ĠInduc es", + "M ic", + "Ġf uc", + "Ġth alamus", + "AN ES", + "Ġquatern ion", + "ĠLister ia", + "d uality", + "he nd", + "and e", + "Ġpa ro", + "Ġinsp ected", + "ques tion", + "ĠH oney", + "Ġch unks", + "Ġfore arm", + "radi ents", + "ific antly", + "ob ank", + "Ġsome where", + "Ġmon etary", + "ĠLouis iana", + "Ġem ulsions", + "Ġprogram mable", + "Ġmanif ests", + "ĠMart inez", + "Ġt ed", + "em en", + "ann i", + "Ġoverl aid", + "Ġvir ulent", + "M ask", + "ĠU tility", + "Ġw k", + "ose xual", + "ĠEar l", + "d ar", + "h dr", + "ract ors", + "Ġconstruct or", + "Ġnas cent", + "inz burg", + "ĠCra ig", + "Ġplex us", + "re verse", + "og rav", + "tag s", + "Ġcalibr ate", + "à ®", + "Ġh ide", + "ĠF ol", + "Ġinter acted", + "Ġconf ron", + "mark et", + "Ġsoci odemographic", + "ĠLuc as", + "ĠM CT", + "ĠR SS", + "Ġmicro plate", + "under st", + "I tal", + "ĠC MR", + "rec y", + "ĠPC OS", + "Ġdetox ification", + "Ġsubt ree", + "Ġsubs ections", + "Ġpropos itions", + "Acknowledg ements", + "reinfor ced", + "l is", + "ĠC IR", + "Ġim printed", + "vi um", + "af ic", + "Ġcheck list", + "ĠR x", + "ĠE ph", + "Ġsol der", + "trans formation", + "ĠStra it", + "az ar", + "Ġhand ler", + "ke let", + "B CL", + "M ath", + "Ġw ishes", + "um inescent", + "ĠP EC", + "ir t", + "yl idene", + "Ġlo osely", + "na issance", + "IL s", + "fo il", + "ĠGN U", + "ĠK et", + "vi x", + "ĠPl ain", + "ĠRE S", + "Ġparent ing", + "ĠConn ection", + "Ġrhiz osphere", + "opre valence", + "i atic", + "Ġp A", + "ĠV il", + "set ting", + "ĠRe LU", + "ĠBO OST", + "Ġappreci ate", + "b x", + "ore st", + "olog ie", + "Ġpal p", + "fo o", + "us ual", + "Ġquestion ed", + "Ġtrig on", + "ĠGF AP", + "ĠKy oto", + "dis e", + "anti le", + "ü ck", + "ĠQuanti zation", + "Ġs cler", + "Ġbe half", + "ĠD uality", + "Ġmagnetic ally", + "Ġeleg ant", + "U A", + "ep is", + "Ġsub clinical", + "ont rol", + "ĠChemical s", + "Util s", + "Ġlow ers", + "ext raction", + "Ġampl ifiers", + "ĠEnt ry", + "ĠWOR K", + "Ġthrombocyt openia", + "M il", + "id us", + "emb ry", + "man ager", + "ĠCo ordination", + "ĠPhen otypic", + "ch unk", + "Ġhypot ension", + "Ġcry ogenic", + "Ġreact ants", + "ĠM MSE", + "Ġcent ros", + "ĠBut ler", + "Ġcav itation", + "ĠLess ons", + "es tion", + "ĠM IS", + "ass oci", + "AP E", + "ĠEuler ian", + "Ġrecre ational", + "ĠNe o", + "ĠCD M", + "rep eat", + "det ails", + "B al", + "ST A", + "Ġâī º", + "ĠCam ero", + "ĠTele vision", + "Ġwork force", + "Ġcomputer ized", + "Ġextra ordinary", + "Ġrib onucle", + "Ġhydroph obicity", + "ĠFeas ibility", + "O l", + "T w", + "ĠM am", + "ĠF AC", + "pro fit", + "negl igible", + "ĠF ruit", + "Ġear s", + "Ġshe aring", + "ĠCorrespond ing", + "f un", + "i eck", + "m os", + "ĠE MI", + "ĠSome times", + "Ġfluor ine", + "Ġdeterg ent", + "Ġal g", + "rac es", + "iv able", + "CO MM", + "ĠSw itch", + "Ġstra ined", + "vir tual", + "Tem perature", + "Ġcredi ble", + "ĠG PCR", + "ĠDe bye", + "ĠL it", + "Ġhe mic", + "Ġtrans ducers", + "met ast", + "adi ene", + "Ġoryz ae", + "t n", + "Ġafter noon", + "ĠArab ian", + "ĠChrom atin", + "Ġxen ografts", + "Ġcrypt ographic", + "Ġax illary", + "Ġvolunte er", + "ĠNev ada", + "Ġp ions", + "un known", + "ĠF U", + "ven ously", + "radi o", + "ĠLab our", + "ĠVill age", + "R ic", + "Ġmet at", + "Ġser otypes", + "reg ression", + "s aturation", + "re ra", + "Ġfar ther", + "Ġround ing", + "Ġlib itum", + "Ġsh uff", + "ĠO w", + "Ġlocal ised", + "ĠAL G", + "Ġhypert rophic", + "p pm", + "im ine", + "ĠA the", + "Ġan hydro", + "Ġsup ramolecular", + "Ġmac ros", + "acet ed", + "ĠOl iv", + "Ġmotiv ational", + "ĠC ave", + "enz ie", + "Ġaffili ated", + "Ferm i", + "Ġequal ities", + "ĠMil an", + "Ġd ressed", + "Ġan ger", + "ad os", + "Ġav g", + "ĠPh on", + "Ġradio activity", + "ĠE ch", + "Ġorgan oids", + "Ġïģ §", + "ĠAnth rop", + "l ateral", + "Ġal pine", + "Ġaud it", + "W ER", + "ĠC SC", + "Ġrank ings", + "ĠER R", + "GL ER", + "Ob viously", + "ĠMad rid", + "obenz ene", + "other mia", + "Ġrespons ibilities", + "omes tic", + "ĠInf lation", + "Ġepidem ics", + "Ġt aut", + "ph os", + "ĠUn less", + "Ġge omagnetic", + "ĠCF TR", + "vel d", + "ari etal", + "Ġend otoxin", + "AD P", + "Ġsupp ressive", + "rand ial", + "Ġïĥ ©", + "exc ited", + "ĠInn ate", + "ĠL ópez", + "omyc etes", + "Ġbe autiful", + "ir k", + "ĠH wang", + "ĠU SE", + "ÏĢ i", + "Rec ord", + "Att ribute", + "Ġre acts", + "ĠB und", + "Ġcow ork", + "Ġconf luence", + "ĠReg ardless", + "Ġmetagen omic", + "M AL", + "Ġa ided", + "ang a", + "Ġam n", + "ĠI CI", + "ĠP ML", + "Ġdel ivers", + "Ġke yp", + "Ġbeet les", + "Ġoxid ant", + "Im mun", + "Ġrhyth mic", + "fem ale", + "J C", + "P AD", + "gen itor", + "A MS", + "c atalytic", + "ĠM om", + "ĠH ert", + "ad ish", + "Ġcont ention", + "Ġy olk", + "Ġdem yel", + "Ġsuc c", + "Ġtravel s", + "V e", + "ĠF ul", + "ĠR if", + "Ġint rons", + "enc aps", + "col our", + "Ġhot el", + "Ac cess", + "ado op", + "Ġcoal ition", + "ĠMu h", + "ĠL TP", + "aut om", + "ĠL ak", + "Ġrem edi", + "Ġtra iling", + "ins ulator", + "ĠRel ig", + "ĠHud son", + "em ics", + "O Ac", + "our t", + "Ġrel ic", + "ĠMi xture", + "Ġcalor imeter", + "ĠR DF", + "ĠHod gkin", + "Newton ian", + "ĠDelay ed", + "ĠNorthe ast", + "her ing", + "Ġhel ices", + "Ġprincip ally", + "Ġsusp icion", + "Ġextrem ities", + "Ġdead line", + "ĠEnter ococcus", + "m j", + "Ġh p", + "ĠN AS", + "ous s", + "Ġintram uscular", + "L IN", + "Ġch icks", + "S core", + "Ġf ür", + "ĠR SA", + "Ġk r", + "Ġphot ography", + "Ġclear ing", + "hol omorphic", + "t hem", + "Ġp om", + "ĠL is", + "Ġdisc ard", + "Ġgu an", + "c x", + "ub ov", + "ĠCons istency", + "Ġple i", + "ĠUr inary", + "Ġbread th", + "E I", + "m echan", + "Ġd q", + "ĠBl ast", + "co eff", + "IL D", + "Ġunem ployment", + "A rm", + "ĠC n", + "mod erate", + "Ġagg ress", + "Ġcircum f", + "l os", + "Ġb aro", + "velop e", + "Ġulcer ative", + "Ġhelic ase", + "H W", + "K G", + "r ion", + "Ġgen otyped", + "Ġar id", + "ĠAndre as", + "Ġthere of", + "ĠOper ating", + "ĠNE W", + "ĠAntib acterial", + "ĠDar win", + "Ġrefere e", + "Ġd ome", + "ag us", + "ĠD MD", + "AT OR", + "Current ly", + "ĠInequ alities", + "d N", + "ol ymer", + "em pirical", + "ĠBra un", + "F IN", + "ĠO ber", + "pr one", + "Ġdimin ish", + "ĠGrad uate", + "ĠT SH", + "ĠH su", + "oid osis", + "Ġepid ural", + "Ġreinfor cing", + "Ġthe atre", + "Ġv ib", + "ĠH ob", + "col lection", + "MAN GLER", + "ĠH ecke", + "Ġtr uck", + "Ġmotiv ates", + "ĠV OC", + "Ġun bound", + "ram id", + "ious ly", + "ĠFern ández", + "ĠF acial", + "ox azol", + "Ġtre adm", + "ĠRes id", + "Lo ader", + "ĠRun ning", + "otin ib", + "P AC", + "V II", + "i u", + "Ġc ite", + "ĠH ockey", + "ES C", + "rho ea", + "Ġmac aques", + "Ġmedi ast", + "at im", + "ĠT MP", + "ĠA GB", + "ĠR up", + "ug a", + "Ġass urance", + "p ay", + "en ergies", + "ĠK end", + "till ery", + "Ġanest hetic", + "Wind ow", + "Ġbe verages", + "ag uchi", + "ĠFL T", + "ĠBound ed", + "ĠPolymer ase", + "S am", + "ĠOr bit", + "Ġseason ality", + "Ġtachy cardia", + "este em", + "ĠPerf ect", + "S EC", + "l ater", + "tal e", + "ĠForm ally", + "L G", + "z yn", + "Ġmicro algae", + "Ġindi um", + "erenn ial", + "ĠI PT", + "Ġk j", + "ĠPD A", + "Ġassim il", + "whe el", + "ĠS OS", + "ĠP FC", + "Ġdec oded", + "AT S", + "Ġsoci etal", + "Ġdiffe omorphisms", + "Ġtra verse", + "Ġcoll ateral", + "g ives", + "ĠC EN", + "Ġra nd", + "Ġher self", + "Ġpay ments", + "Ġps i", + "âIJ £", + "ĠGrom ov", + "Ġacc idental", + "ĠRe ality", + "Ġlog istics", + "Ġrobust ly", + "ĠSar ah", + "N U", + "d ates", + "ĠC UR", + "ĠD ream", + "Ġdegrad es", + "ĠGE O", + "Ġbutter fly", + "Ġpend ulum", + "q a", + "Ġas partate", + "pseud o", + "Ġall osteric", + "der r", + "ĠQ oL", + "Ag ilent", + "ĠHard ware", + "ĠCum ulative", + "Ġp n", + "qu antitative", + "Ġapp raisal", + "Ġpoly acrylamide", + "Ġmild ly", + "Ġcontrac eptive", + "ĠPubl ished", + "Ġupl ift", + "be h", + "Ġadap tor", + "ĠEqu al", + "thien yl", + "at ched", + "Ġrep ly", + "Ġup wards", + "Ġaut opsy", + "sim ulation", + "Ġgran ite", + "Ġpel vis", + "Ġhat ching", + "ĠS PS", + "ĠG EM", + "illi ard", + "ĠRet rospective", + "ĠEarth qu", + "ĠInvestig ations", + "ĠMer ck", + "Ġchol angi", + "Ġinfiltr ating", + "Ġoverestim ated", + "focus ed", + "A min", + "Ġpre eclampsia", + "osp atial", + "ĠTRA IL", + "P air", + "Ġsub marine", + "Ġprote olysis", + "Ġcomple ments", + "ĠKir ch", + "Ġcent rom", + "Ġn ap", + "ĠWe ar", + "Ġpun ishment", + "Ġautoreg ressive", + "Ġcompos er", + "ĠEng el", + "Ġana emia", + "ĠKron ecker", + "ĠD id", + "ĠCar p", + "pe er", + "Ġbug s", + "ĠIslam ic", + "ith romycin", + "Ġcons ec", + "Ġfam iliarity", + "et axel", + "Ġint ensively", + "ĠU pt", + "Ġindic a", + "AD A", + "ĠChe byshev", + "Ġhierarch ies", + "Ġworth while", + "Ġburn ed", + "ĠHM GB", + "Ġpolyg onal", + "b rile", + "Ġz oon", + "war ning", + "Eukary ota", + "d A", + "ĠRep eated", + "ĠCast ro", + "Ġmet ropolitan", + "ont inuous", + "ĠBar nes", + "ĠPost operative", + "Ġcyt ology", + "Ġspot ted", + "vers ity", + "aff ine", + "sor ted", + "ĠPro to", + "ĠDes criptive", + "Ġhit ting", + "Ġanalog ously", + "feed back", + "Ġspirit ual", + "ĠL INE", + "ress in", + "oph thal", + "Ġpoly unsaturated", + "Ġpi per", + "observ ations", + "ĭ ¤", + "ir re", + "ĠW NT", + "Ġund ifferentiated", + "eral d", + "ĠCT C", + "Ġhomomorphism s", + "ĠNeon atal", + "F in", + "ro zen", + "ĠL ux", + "Ġmod ifier", + "ĠK A", + "osa ur", + "Ġinterven tional", + "ĠHa pl", + "Ġlumin ance", + "Ġun fortunately", + "Ġsleep ing", + "Ġcit rus", + "reson ance", + "Ġm oss", + "ul ay", + "ĠP enn", + "ad ministration", + "ĠN GF", + "Ġsec ured", + "ĠA Es", + "ĠP WM", + "oc co", + "ob uf", + "Ġphotoc urrent", + "ĠScilab Double", + "A pril", + "Ġfor amin", + "Ġpar alysis", + "ĠQu ark", + "eq ref", + "ĠBro oks", + "ĠColl ision", + "W ar", + "Ġ ig", + "am ylase", + "ist ered", + "Ġret raction", + "ĠMulti plex", + "ĠMa o", + "Com mon", + "ĠEcon omics", + "ĠCriter ion", + "ĠC CC", + "ĠLe i", + "Ġorth orhombic", + "Ġaliqu ots", + "Ġst ric", + "ĠL enn", + "Ġdis closure", + "amet h", + "Ġnormal isation", + "Ġphyl ogen", + "ĠQTL s", + "ĠVers us", + "ĠUtil ization", + "y ne", + "un ted", + "ĠD uff", + "ĠG J", + "Ġoptim ised", + "iform is", + "ĠIncre ases", + "ĠFD G", + "ĠBatter y", + "P he", + "ĠC CS", + "Ġch rys", + "of en", + "Ġmultic omponent", + "disc ussed", + "bond ing", + "ore tically", + "ĠAll iance", + "Ġhead quarters", + "ĠGlas gow", + "Ġb out", + "Ġe ighth", + "Ġinc urred", + "ĠBar ry", + "Ġquad ric", + "Ġdu ties", + "Ġmind fulness", + "rastruct ural", + "T rain", + "sh itz", + "CD C", + "Ġdys lipidemia", + "Ġalle ged", + "Ġbron ze", + "Ġattain ment", + "Q D", + "rom bin", + "Ġap olipoprotein", + "own ed", + "Ġge ographically", + "work ing", + "ĠBl ind", + "Ġdon ation", + "ĠSer ge", + "Ġspread s", + "ĠHeter ogeneity", + "ĠFr é", + "Ġdef er", + "Ġlif ts", + "EGF R", + "ĠPort land", + "Ġbrother s", + "ĠTrypan osoma", + "in ian", + "Ġp ressed", + "Ġtrans duced", + "Ġpol yn", + "Ġlist eners", + "bo ards", + "ĠSustain able", + "al an", + "ĠS ullivan", + "Assum ption", + "oft en", + "j p", + "or ative", + "pl ers", + "Ġmodular ity", + "ĠHerm ite", + "Ġhydroxy apatite", + "ĠHir sch", + "D eterm", + "f acing", + "ir radiated", + "Ġhar sh", + "Ġtoler ate", + "ĠT rap", + "ĠA ware", + "ot ax", + "AT ING", + "Ġhist opathology", + "ĠIsra eli", + "clock wise", + "z ig", + "ĠJ C", + "ĠQu ick", + "ĠSL AM", + "Ġf ox", + "ĠR av", + "gener ating", + "Ġhemat oxylin", + "yl transferase", + "Ġcorrobor ated", + "F DR", + "o ard", + "Ġequ id", + "Ġ »", + "Ġneuro psychological", + "Ġbreak up", + "Ġemphas izing", + "Ġemiss ivity", + "block ing", + "Ġpar all", + "Ġtil ting", + "Ġp eng", + "ĠSc an", + "Ġion osphere", + "Ġm ount", + "fore st", + "Ġcall us", + "α β", + "ĠChrist mas", + "ĠMag azine", + "eval uate", + "ĠP ag", + "ĠBe at", + "Ġaccum ulates", + "Ġcrow ding", + "unn eling", + "Ġ Ñ", + "ĠA CP", + "ge ometry", + "MP T", + "Ġpharmac ists", + "Ġpull back", + "Ġduc tility", + "S upervised", + "Ġlymph oblastic", + "pe a", + "typ ical", + "bro ken", + "F c", + "Ġl ining", + "ĠD um", + "Ġmulti ples", + "ó w", + "Ġmer its", + "Ġextinc t", + "ĠNur sing", + "ĠExplo iting", + "ĠBhatt ach", + "J uly", + "t ze", + "th romb", + "te enth", + "Ġtoxic ities", + "Ġdenit rification", + "Ġex position", + "Ġim perf", + "Ġsur name", + "po inter", + "ĠEr n", + "ĠAbund ance", + "ĠD unn", + "oph ora", + "Ġtool kit", + "Lo ad", + "ĠDeriv ation", + "c ould", + "ĠC aspase", + "ĠSp rague", + "ĠTr p", + "Ġbright est", + "ill ard", + "Ġinter disciplinary", + "Ġqu arant", + "Ġhyper surfaces", + "eli ac", + "ĠAL MA", + "Ġacryl ic", + "Ġgent le", + "De ep", + "ĠPand emic", + "Ġinf easible", + "Ġradi ol", + "AB P", + "Ġmes enteric", + "ylind er", + "pack ed", + "Ġsomat osensory", + "Ġp ave", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠ", + "Ġpharmac ology", + "Ġtan h", + "ĠMt b", + "Ġchim pan", + "Ġautophag ic", + "Ġwithd rawn", + "ĠM CC", + "Z F", + "ĠS pl", + "ĠL au", + "Ġbi ologic", + "elect rons", + "Ġunderestim ation", + "Ġcharacter ise", + "circ ular", + "ĠTHE ORY", + "B rown", + "F BS", + "J o", + "d G", + "m ars", + "ar ticular", + "ĠP ren", + "ĠM SA", + "ĠIt em", + "Ġsem idefinite", + "ĠGib son", + "Ġtour ism", + "ĠK ok", + "Ġexpos ing", + "Ġintra venously", + "dri ver", + "ĠFort unately", + "ĠS ach", + "Ġcont aminant", + "Ġab rog", + "ĠEm otional", + "VAL UE", + "dispers ion", + "Jac obi", + "ĠImper ial", + "I on", + "L in", + "f idelity", + "ĠB irds", + "ĠCon current", + "mat ism", + "co al", + "Ġt q", + "ĠMn O", + "Ġfoss ils", + "Ġt ender", + "Ġr hesus", + "Ġblo om", + "ab dominal", + "Ġscal p", + "Ġhome ostatic", + "ĠH unt", + "ĠPharmac okine", + "b rown", + "ĠH YP", + "Ġdiss ociated", + "ĠSoc cer", + "ĠInequ ality", + "m aker", + "Ġsh ade", + "ĠZ ur", + "obs ervation", + "al tered", + "U U", + "Ġthe or", + "ep it", + "Ġphyl um", + "Ġvig orous", + "ĠA CM", + "Ġmeth otrexate", + "dem ographic", + "Ġsing ly", + "ĠPhys iology", + "Ġremod elling", + "ĠK rist", + "rop ies", + "flow s", + "hard ness", + "igh teen", + "bre ve", + "ĠRet inal", + "Ġscin till", + "Ġutter ance", + "Ġmonolith ic", + "ĠVl ad", + "ĠL MC", + "ip t", + "arrow s", + "ĠPubl ishing", + "ĠStrept omyces", + "f al", + "Ġtroposp here", + "B en", + "c andid", + "ĠS ic", + "tim ore", + "L en", + "in en", + "amp ered", + "ĠMon th", + "Ġopp onent", + "Aug ust", + "Ġst aggered", + "cent re", + "exp ect", + "Ġred dening", + "ĠT l", + "hib ition", + "Ġmicro particles", + "ĠInt rac", + "ĠInitial ize", + "Ġdict ated", + "D ig", + "ä º", + "he aling", + "Ġd V", + "Ġappe tite", + "Ġunus ually", + "ĠAstr onomy", + "Ġw are", + "Ġover coming", + "Ġcoll iders", + "ĠUS ING", + "ocardi tis", + "P ick", + "Ġd ub", + "ĠJ ason", + "ĠEd itor", + "ê ³", + "Ġl ags", + "Ġcl s", + "Ġsur gically", + "ĠPV C", + "par ticularly", + "Ġred ist", + "Ġlog ics", + "sk ii", + "ĠD VD", + "Ġcomp ly", + "az i", + "ĠInter acts", + "bo olean", + "ĠER P", + "ĠEr r", + "otrans piration", + "ĠPé rez", + "A sp", + "am iliar", + "ĠF etal", + "Ġdecl aration", + "k inson", + "t ube", + "Ġphysiological ly", + "c ue", + "ĠE ri", + "Ġen vision", + "ex ternal", + "inter mediate", + "Ġshop ping", + "ĠF ras", + "ĠH aj", + "ĠAl ger", + "Ġanthrop ometric", + "Ġcancell ed", + "H PV", + "k ers", + "af a", + "Ġvulner abilities", + "electro lyte", + "ĠGonz alez", + "íķ ĺ", + "q v", + "Ġde af", + "Ġbut yrate", + "ĠCo efficient", + "Ġstar burst", + "Ġpolym orph", + "ĠE RA", + "ĠMax imal", + "ĠMu eller", + "Ġabsor bers", + "Ġa rab", + "re tions", + "Ġne bula", + "Ġmin es", + "е н", + "%%%%%%%% %%%%%%%%", + "Ġband pass", + "Ġpoly urethane", + "Re LU", + "ĠFer ro", + "pic illin", + "C AD", + "T y", + "ĠP CD", + "ĠB AC", + "Ġplankton ic", + "F er", + "Ġc ricket", + "Ġman ure", + "oun s", + "âĪ §", + "Ġtor ques", + "m itian", + "Ġt ion", + "ĠG arden", + "Ġfol k", + "Ġsusp icious", + "à Ĥ", + "od ia", + "ist encies", + "ãĢ ī", + "ĠInv itrogen", + "ĠS UN", + "ĠSuper ior", + "Ġdiscontinu ation", + "c ock", + "k not", + "Ġext ens", + "ĠWh itney", + "Ġhar bour", + "P ID", + "Ġp mol", + "ol ymph", + "Ġg ard", + "ĠO varian", + "Ġrep ressed", + "ĠAl ab", + "Ġà Ħ", + "ule x", + "ĠAust rian", + "Ġa flat", + "Ġpar athyroid", + "Ġgroup oid", + "Ġdev ast", + "ĠK v", + "Ġbor row", + "Ġuncon ventional", + "Ġbore hole", + "Ñ Į", + "ĠD ays", + "Ġlex ic", + "N or", + "ĠH erc", + "ass ays", + "Ġdraw ings", + "def in", + "ev oked", + "ĠÈ ³", + "ĠSund ay", + "ĠC hes", + "cons idered", + "oped ic", + "larg er", + "om inant", + "ĠB omb", + "Ġf iss", + "Ġh inge", + "ĠI onic", + "Ġdest ro", + "Ġcomplement arity", + "Hig gs", + "or ia", + "our cing", + "ĠX in", + "Ġwork space", + "ĠLig and", + "Ġstrugg le", + "ĠImmunohist ochemical", + "Ġn ick", + "ĠGu ard", + "rig id", + "Ġaqu aculture", + "Experim ent", + "Ë Ī", + "ti r", + "ĠS MS", + "Ġbe vacizumab", + "Ġmod ulations", + "Ġge ophysical", + "Pro perties", + "Ġpain ted", + "Ġs anc", + "Ġin timate", + "Ġn ail", + "id entity", + "Ġdat um", + "anth us", + "Ġdy adic", + "Ġconvinc ing", + "e lem", + "Ġh iding", + "Ġr ugby", + "ĠX e", + "ĠIs sue", + "Ġves icular", + "ĠKel vin", + "Ġdist ancing", + "echn ology", + "af ers", + "ĠAut hentic", + "Pub Med", + "Ġdeform ity", + "ĠCha os", + "ĠSh ield", + "ox etine", + "ĠWork ers", + "ĠMO I", + "Ġdehyd rated", + "ĠGast ric", + "Ġmonomial s", + "od ox", + "ĠD ublin", + "Ġle ishman", + "Ġpl anner", + "circ le", + "Ġfract ured", + "ĠLoc ally", + "ĠAc tions", + "Ġlic hen", + "h annel", + "ĠT AG", + "Ġdec isive", + "ĠQ M", + "Ġbiom aterials", + "ĠVirus es", + "hydrox yphenyl", + "ĠI AA", + "ĠR U", + "vi olating", + "Ġp ockets", + "ch ant", + "ib erg", + "lect omy", + "oler ae", + "Ġattract ing", + "Ġket one", + "ĠC od", + "Ġmicro arrays", + "ĠMet als", + "benz oyl", + "Ġsemigroup s", + "Ġreconstit uted", + "s ites", + "an abe", + "ĠCom posites", + "Ġwild type", + "Ġleuk aemia", + "Ġmur der", + "Ġdent in", + "H ub", + "O rient", + "on n", + "syn chron", + "Ġchron ically", + "methylene amino", + "Ġdop ant", + "Ġf ecundity", + "de lete", + "rem ia", + "ĠNH L", + "iti dis", + "Ġcop ep", + "X I", + "Ġloc ating", + "ĠZ IKV", + "hex a", + "ĠFactor ization", + "ynch us", + "M ethyl", + "h agen", + "ĠP aw", + "ne ath", + "bs ite", + "Ġtrac he", + "B re", + "u w", + "ro it", + "Ġre acting", + "ĠB ae", + "Ġquoti ents", + "Ġp ins", + "ĠV ARI", + "Ġequ ine", + "ĠRun ge", + "Ġcolon ial", + "measure ment", + "ĠAbb ott", + "Ġorth o", + "Ġmeta phor", + "benz oic", + "ĠTransform ers", + "L ower", + "ĠO VA", + "radi al", + "Fl ag", + "author bs", + "Ġtreadm ill", + "Ġenter ica", + "ĠJul ia", + "Ġpl umes", + "Ġinv oke", + "chlor ic", + "ol ino", + "Ġinter ruption", + "sub unit", + "ĠMD P", + "Ġmanip ulator", + "ĠScal es", + "ĠHT ML", + "ĠFreder ick", + "G arc", + "Ġb ell", + "ĠR ect", + "rom ised", + "W ord", + "o ples", + "oper ated", + "Ġcollec ts", + "ĠHor izon", + "Ġsa fer", + "d up", + "ĠM ills", + "AL P", + "Ġex opl", + "AT TR", + "war a", + "ĉĉĉĉ ĉĉĉ", + "Ġdeb ug", + "Des criptor", + "stat istics", + "ĠC ub", + "ST ER", + "ĠSt abilization", + "ĠIR AS", + "Ġconform ally", + "Ad ap", + " Ń", + "ĠQ S", + "Ġmicro strip", + "Ġdel icate", + "Ġpubl isher", + "Ġh os", + "ĠS v", + "ĠDes ert", + "ĠGu er", + "ĠCap ture", + "E BP", + "d ust", + "å ¤", + "ĠO ls", + "Ġsuper script", + "ĠFl uctuations", + "ill ium", + "Ġcap tion", + "Ġconc ur", + "Ġquantif ies", + "ster dam", + "Ġspik ed", + "N an", + "us in", + "ĠL AN", + "Ġobserv es", + "ĠAl a", + "ĠInt uitively", + "cur r", + "Ġshr inking", + "Ġcompress ibility", + "orp oreal", + "Ġdeb t", + "ç Ķ", + "ĠT il", + "ĠW AT", + "ody ne", + "Ġgate way", + "Ġduc tile", + "ĠJes us", + "os itol", + "ĠM ales", + "Ġsol vation", + "Ġdisag ree", + "Ġortholog s", + "S an", + "ig o", + "Ġph ages", + "Ġneg atives", + "Ġinterp re", + "AA A", + "Ġgrating s", + "ĠM oll", + "ĠR ivers", + "Ġcr uzi", + "ĠGen erate", + "ĠBar bara", + "ĠHer itage", + "ĠFlu orescent", + "ĠLaw s", + "Array Expr", + "Ġmultip ole", + "Ġsquee zing", + "S PSS", + "l f", + "n lm", + "Ġw orn", + "ĠK uz", + "Ġgenes is", + "ĠEm peror", + "vol atile", + "Ġsib ling", + "iv ir", + "o en", + "Ġprot ost", + "Ġtransform ers", + "enn ium", + "Ġpropos ing", + "Ġbroadcast ing", + "Q M", + "ĠD ependent", + "Ġdis able", + "ĠU AS", + "Ġwar nings", + "Ġarm ed", + "Ġjournal ist", + "Ġmonoc linic", + "ol ium", + "ap ing", + "to on", + "Ġorth odontic", + "ĠNormal ization", + "Ġmand ible", + "ab an", + "ĠW ak", + "ext end", + "Multi ple", + "in vestig", + "is cal", + "ut tered", + "Ġbur g", + "dec ode", + "em por", + "ĠD uration", + "ann y", + "opro st", + "ĠRen ormalization", + "ĠF UNCTION", + "yt orch", + "Ġsyn apt", + "ĠForm at", + "ĠCR T", + "ĠJon athan", + "ĠOF F", + "or r", + "Ġres ur", + "Ġcor ruption", + "d welling", + "Ġback up", + "AG T", + "ĠSaf e", + "dor fer", + "Ġatax ia", + "Ġpar v", + "read er", + "Ġsubt ract", + "embol ism", + "Ġt innitus", + "Ġcyt omegalovirus", + "Ġdele ting", + "T ex", + "ĠC SS", + "ard t", + "Ġout growth", + "Ġmy ocytes", + "dig ital", + "Ġsub scale", + "usp ension", + "Ġham ster", + "Ġinflat on", + "h ara", + "ur ches", + "ĠC LE", + "ĠY as", + "ĠEn coding", + "ĠAug er", + "Ġanastom osis", + "A gent", + "ĠS IL", + "ĠC CT", + "Ġbr ine", + "Ġolig o", + "Ġfluor o", + "Ġgall ery", + "d dots", + "Ġc ilia", + "ĠP PV", + "ĠU TR", + "Ġinter tidal", + "ocal ized", + "Ġcrow ds", + "od or", + "Ġco v", + "Ġnon etheless", + "Ġïģ ¤", + "Ġboost ed", + "ĠChak ra", + "H al", + "P ear", + "Ġimp rec", + "ĠSup plement", + "go al", + "Ġôı¼ ģ", + "Ġst all", + "Ġher d", + "small er", + "Ġreconstruct ing", + "Ġarte facts", + "Ġt eg", + "con ventional", + "rad ical", + "Ġliter al", + "frame work", + "ipro cal", + "E EG", + "Ġg ins", + "od ermal", + "ĠAg u", + "ĠTw elve", + "M ul", + "Ø ¨", + "ir l", + "ĠB elief", + "Ġinc ont", + "IC C", + "hex ane", + "Ġe jected", + "ĠP SC", + "ĠH PC", + "ĠV H", + "Ġequival ences", + "plot lib", + "en ital", + "ri ans", + "pro v", + "ĠV ibr", + "Ġgram matical", + "bach ia", + "accept able", + "od icity", + "ab b", + "Ġher bs", + "Ġpredom inance", + "ĠOrient ation", + "Ġinver tebrate", + "Ġpel agic", + "count ry", + "ĠOrig ins", + "ĠAdoles cents", + "ĠT uning", + "rain ian", + "ĠSc ar", + "Ġlight est", + "Ġemit ters", + "ĠTs ai", + "ri tical", + "ĠEx pert", + "aut hors", + "E CTION", + "ĠSever ity", + "N am", + "p ubl", + "ĠA be", + "Ġnanoc rystalline", + "ĠNak amura", + "ĠP ec", + "ĠB ug", + "Ġsens ed", + "ON S", + "IC s", + "Ġelectro chem", + "ĠR OM", + "ĠRec ruitment", + "Ġ⣠©", + "Ġbiomo lecules", + "ĠB rac", + "Ġtrans position", + "ĠW P", + "ĠO mega", + "Ġdiag on", + "plate let", + "J M", + "ac re", + "ĠA SR", + "ĠK ath", + "Ġpri v", + "opl asts", + "S amples", + "d F", + "at ti", + "ĠS anger", + "ip itated", + "Ġric her", + "ĠG RA", + "Ġplant ar", + "Ġfo ams", + "Ġmathem atic", + "Ġsta phyl", + "ĠUpt ake", + "Ġc ant", + "ĠS Z", + "Ġdis miss", + "Ġselec tions", + "plit z", + "Ġexempl ified", + "Ġtors ional", + "E v", + "Ġv oters", + "ĠN est", + "ys cale", + "Ġspec i", + "Ġpol ished", + "Ġlat encies", + "q ing", + "Ġon wards", + "ll vm", + "the orem", + "log ging", + "ĠAL K", + "ĠBa um", + "ĠGh osh", + "Ġchair man", + "p aired", + "ĠP AP", + "not es", + "olester olem", + "Ġestu arine", + "ĠTibet an", + "ĠV ER", + "Ġcheck er", + "FLAG S", + "rol imus", + "ĠMut ant", + "Ġspray ing", + "ĠC hest", + "olin ium", + "ĠTri assic", + "Ġlid ar", + "A rt", + "ĠM ilk", + "Ġind ecomposable", + "Ġrock et", + "ĠPart ners", + "Ġseman tically", + "entin el", + "L arge", + "P en", + "ĠT ru", + "Ġher itage", + "ĠMut ual", + "ĠChem otherapy", + "Ġdoub les", + "ĠEmbed ded", + "it ual", + "ĠB PA", + "Ġch olerae", + "ĠIn side", + "ĠK atz", + "con vergence", + "Ġindividual ized", + "kin je", + "Ġdiscover ing", + "Ġintric ate", + "Ġin land", + "RE CT", + "ĠCh ick", + "ĠSU R", + "Ġye asts", + "l uminosity", + "Ġf ain", + "ion i", + "ĠT ig", + "ound er", + "Ġdel iber", + "ĠCons ervative", + "ĠDel hi", + "B ER", + "ĠY B", + "ole y", + "ĠBe au", + "TE XT", + "Ġsquee zed", + "Ġs ocket", + "Ġp T", + "py razol", + "co efficients", + "Ġrecruit ing", + "Ġduc ts", + "Ġf oster", + "om eration", + "ĠP SI", + "ĠD up", + "Ġk s", + "ĠOp tics", + "Ġliter ary", + "ĠNi O", + "ĠVEGF R", + "Ġgravit on", + "Ġutter ances", + "Ġb rady", + "Ġfor ty", + "ĠTrans plantation", + "Ġagre ements", + "Left rightarrow", + "w aves", + "Ġacid osis", + "Ġwood en", + "ĠCytoplas mic", + "s afe", + "Ġj umping", + "enn ial", + "Vari ous", + "ĠEry th", + "ul ins", + "un lock", + "methyl ated", + "asser stein", + "Ġheterozyg osity", + "oxy cycl", + "Ġcre ativity", + "MP LE", + "in ative", + "Ġcon volutions", + "Ġno uns", + "eg an", + "ĠAb raham", + "Ġdens er", + "C he", + "l c", + "ĉĉ ĉĠ", + "Ġsem im", + "ĠOut er", + "Ġc and", + "od ule", + "est hesia", + "ĠJ oy", + "ĠProt ocols", + "ĠCalc ulated", + "at op", + "ĠF ALSE", + "Ġref in", + "Ġmig rants", + "ĠïĤ ´", + "ĠSpecific ity", + "ĠFellow ship", + "ĠP MT", + "Ġdis close", + "unc hes", + "Ġdi atoms", + "cor r", + "Ġsky rm", + "Ġrenew al", + "g cd", + "ce reb", + "Ġup right", + "Ġmes oscopic", + "hyd raz", + "B AS", + "F LO", + "H CC", + "M ouse", + "Ġpos et", + "Ġprotein uria", + "Ġre app", + "ĠN ickel", + "Ġstrip es", + "Ġrip ple", + "Sep tember", + "od omain", + "ĠP ope", + "ĠN ons", + "Ġtechn ic", + "Ġneut rop", + "des criptor", + "Ġdissip ated", + "Ġglac iers", + "ĠH IGH", + "ĠL av", + "ret ely", + "Ġback wards", + "Ġcri tics", + "ĠExt ending", + "b ic", + "ĠCh ao", + "of ibr", + "Ġcoun ters", + "Ġstre ets", + "Ġprost hetic", + "Ġbiod egradation", + "complex ity", + "ĠS PL", + "ĠC AC", + "Ġad ducts", + "Ġmorph ometric", + "ĠMat t", + "Ġinduc er", + "Ġast rocyte", + "Ġtriple ts", + "Ġpert ussis", + "P ES", + "id y", + "unc ertain", + "Ġhyper parameter", + "ĠInf rastructure", + "ìĿ ĺ", + "Z W", + "Ġadd r", + "Ġdisrup ts", + "Ġove restimate", + "ĠDY NA", + "Ġvolat iles", + "em erg", + "iss ue", + "c pp", + "Ä ħ", + "ĠV IP", + "Ġu ve", + "ĠCN V", + "yleth yl", + "on azole", + "ĠH iro", + "Ġc n", + "ti k", + "ub ted", + "ĠJac obs", + "Ġadvoc ated", + "ĠBif id", + "m aterial", + "Ġst yrene", + "ĠK eller", + "rocy tic", + "pine phrine", + "ĠWr itten", + "ĠRecommend ation", + "b led", + "ĠB ootstrap", + "th irds", + "Ġcap tain", + "equ als", + "SR C", + "ĠKent ucky", + "Ġeosinophil s", + "A verage", + "H i", + "W he", + "ĠD AT", + "ĠU M", + "Ġtend encies", + "ĠPet erson", + "Ġocc ult", + "Ġexhib ition", + "ĠIN S", + "Ġadip ocyte", + "J ust", + "h ift", + "t ensors", + "Ġc iliary", + "ip ation", + "Ġmotiv ations", + "Ġwitness ed", + "it ches", + "ĠS oy", + "Ġg ib", + "ep tic", + "ĠK OH", + "Ġïģ ¨", + "ĠTor res", + "Î ¿", + "ar po", + "ok inase", + "ĠBud d", + "ĠG MM", + "Ġunder pin", + "Ġoptim istic", + "oge ography", + "numer ical", + "og g", + "Ġdise quilibrium", + "Ġsw ab", + "ED S", + "ĠPD Fs", + "ĠSuper nova", + "phosph o", + "Ġlys osomes", + "gal actic", + "ĠPerm e", + "Ġfisher y", + "ĠB OLD", + "Ġun ravel", + "ĠEncryp tion", + "J P", + "h ur", + "Ġdisc ount", + "ĠWat anabe", + "ĠRhe umat", + "F ITC", + "Ġt erahertz", + "ĠF ont", + "ian ces", + "ĠAd ditive", + "ĠE ither", + "met adata", + "amp hetamine", + "ĠPal mer", + "Ġlever aging", + "J ohn", + "O CT", + "in fer", + "ĠM SD", + "ĠâĪ ĵ", + "ou ver", + "ĠAnd ersen", + "Ġworld s", + "Ġtor i", + "Ġïģ °", + "engine ering", + "ĠSquad ron", + "A ff", + "å ı", + "ox el", + "yle tic", + "ĠCharacter izing", + "V T", + "r ational", + "ere mia", + "Ġcomplex ation", + "ĠER α", + "carboxyl ic", + "ïĤ ·", + "Ġgalact ose", + "ĠAur ora", + "Ġplasmin ogen", + "ure n", + "ign e", + "Ġrep aired", + "Ġblock ers", + "ĠMN IST", + "Ï ħ", + "ĠA xi", + "Ġst adium", + "di ethyl", + "âĢ İ", + "Ġcycl otron", + "Ġlymph aden", + "Ġv in", + "ĠM ayer", + "Ġendomet rium", + "ĠSp herical", + "Ġpers u", + "Ġimm ortal", + "benz enesulf", + "ĠÅ ľ", + "Ġb ite", + "ugg ed", + "ĠDiff raction", + "GT G", + "i ate", + "Ġt p", + "Ġab er", + "ĠRe in", + "Pro gram", + "St yle", + "ĠRegular ization", + "ĠLeuk emia", + "Ġprokary otic", + "oc omial", + "sk b", + "Ġdevi ates", + "Ġf use", + "ĠN ull", + "Ġïĥ Ĺ", + "ĠOper ational", + "Ġcompress or", + "ĠRyd berg", + "Ġf ought", + "Ġe co", + "ĠS SP", + "CD s", + "ĠME K", + "ĠAnis otropic", + "ĠDi rection", + "ĠSpect rometry", + "Ġglut en", + "ĠPow ell", + "recogn ized", + "Ġpsych otic", + "Ġhind er", + "Ġaccommod ation", + "ĠNorm an", + "Q x", + "Ġper iv", + "ĠUn known", + "Ġjo ins", + "ĠMinim ization", + "ĠS ons", + "ĠC in", + "Ġun avoid", + "ĠPT X", + "Ġc ada", + "ĠL uk", + "Ġr uling", + "Ġbi phasic", + "ĠCom plications", + "ĠDef ects", + "Cont ent", + "ĠGreg ory", + "ĠWer ner", + "ĠWeib ull", + "eld om", + "Ġactiv ators", + "GL API", + "math ring", + "Ġhe ns", + "N SC", + "h owever", + "ĠT ME", + "ma frost", + "co efficient", + "ĠIns ect", + "ĠRO Is", + "ĠBor rel", + "ĠQi u", + "Ġinhal ed", + "id ate", + "Ġanti hypertensive", + "Ġtreat s", + "ĠNear ly", + "suc c", + "ĠOrb ital", + "er adish", + "ad ministered", + "ĠÏ Ĥ", + "ĠCol ony", + "ĠâĮ Ĭ", + "ĠIndones ian", + "ĠB auer", + "ĠK od", + "mann ed", + "Res istant", + "Ġda ughters", + "ĠPredic ted", + "Ġvoc ab", + "Ġcontras ted", + "m argin", + "ĠDi rected", + "ED TA", + "Ġsynchron y", + "ick i", + "ĠSal v", + "t reat", + "in cess", + "var nothing", + "Ġhex ane", + "Em pty", + "Ġgem citabine", + "om ib", + "ore pinephrine", + "pro c", + "ĠMet S", + "ĠDR AM", + "Ġantico agulant", + "n om", + "am ater", + "ĠLi DAR", + "Ġmob il", + "Ġamelior ates", + "n iz", + "Ġj a", + "Ġem uls", + "ĠZ a", + "Ġastr onomical", + "ĠAlf red", + "H ilbert", + "ĠK F", + "CR T", + "quad ratic", + "Ġdifferenti als", + "rob acterium", + "ĠHippocamp al", + "p ull", + "Ä Ļ", + "Ġs ad", + "ally l", + "Ġhot spot", + "ĠElectron ics", + "Ġconstit ution", + "iton in", + "ا ÙĦ", + "P c", + "Ġre vascular", + "Ġus able", + "ĠSc atter", + "Ġgraph ically", + "lim inf", + "Ġrestaur ant", + "ucalypt us", + "AC G", + "Anal y", + "ĠMill ipore", + "Ġmunicip alities", + "ĠMacroph age", + "Ġmacrom olecular", + "L icense", + "g c", + "Ġl avage", + "ĠA ES", + "ĠF CS", + "per itone", + "Ġmeas les", + "TE X", + "ĠVir ulence", + "Ġhemat oma", + "ĠF res", + "ĠN utrient", + "ap ar", + "ĠSp ot", + "co plasma", + "ĠExp ect", + "Ġc iprofloxacin", + "phyl axis", + "ĠAtl anta", + "ro uting", + "ar ate", + "ĠC is", + "ens ure", + "car riers", + "ĠVari ant", + "sur gical", + "ĠEstim ate", + "à ¹", + "ĠL iqu", + "Ġam alg", + "Ġbl a", + "Ġthem atic", + "IR Q", + "ACT ION", + "ĠChris ti", + "æ ľ", + "Ġn py", + "de ath", + "Ġhair pin", + "Ġmultiplic ities", + "Gib co", + "he ated", + "af ety", + "mut able", + "quark s", + "S un", + "q l", + "Ġproduc tions", + "Ġge ology", + "Ġt ides", + "at rix", + "Ġad mixture", + "trans lated", + "ĠAb u", + "nucle us", + "Ġweakness es", + "Ġflav ors", + "ĠLu is", + "ĠPut ative", + "sent ence", + "ĠM ast", + "ĠM PS", + "ĠE SS", + "Ġcomp ose", + "Ġbi refring", + "ĠRam sey", + "ĠCL L", + "Ġlign ocell", + "ĠL amin", + "ĠW elsh", + "v on", + "Ġp ests", + "Ġf iction", + "ĠH RT", + "Ġass ure", + "CT s", + "ĠPA Hs", + "Ġcrypt ography", + "en erated", + "Ġop s", + "ĠSyn erg", + "ig inal", + "ĠC raw", + "Ġk ne", + "Ġcurv atures", + "Ġl ux", + "ĠK enn", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠ", + "print ln", + "Ġverteb rae", + "Ġru tile", + "ĠAeros ol", + "re ferred", + "lactam ase", + "ve hicle", + "ad ir", + "iz ards", + "Ġcall back", + "Cl uster", + "Ġsil t", + "Ġresearc hed", + "ĠGener ator", + "ĠRest oration", + "ĠCh in", + "omet rical", + "ĠCo efficients", + "rach id", + "F ace", + "M en", + "c ounts", + "Ġp eg", + "Ġe cl", + "Ġcom edy", + "ĠL n", + "ob uty", + "ĠSh aring", + "Ġadequ acy", + "urt osis", + "ĠPic ard", + "Ġf estival", + "Ġdis position", + "ĠCom plement", + "ĠEx clusion", + "Ġdext ran", + "m ons", + "ĠInter polation", + "ĠSte ven", + "Ġcelebr ated", + "Ġh Pa", + "of requency", + "Ġexception ally", + "Ġenerge tically", + "psych otic", + "Land au", + "T uple", + "dist ributions", + "ĠRich ards", + "Ġpolyp s", + "ĠAbs ence", + "Ġcele b", + "X G", + "Ġsim ulates", + "mit ters", + "Ġheat map", + "ĠSD N", + "ĠSte ps", + "Ġshall ower", + "ĠTurb ulent", + "Y T", + "Ġn al", + "plic ative", + "pha e", + "ĠLe ica", + "ĠAPP RO", + "Ġarrhyth mia", + "Ġre writing", + "Ġuns afe", + "Ġcowork ers", + "ĠG AD", + "iv ol", + "Ġdisrup ting", + "ĠUltra violet", + "ere e", + "ĠL opez", + "Ġneg ation", + "Ġjaponic a", + "ec essor", + "ĠP atch", + "Ġso ap", + "ĠY ing", + "MS K", + "Ġtrac heal", + "ic os", + "Ġv p", + "FA IL", + "Ġcat abolism", + "sol ver", + "f ont", + "es p", + "ĠZ ou", + "Ġdark er", + "Ġlys ozyme", + "c overed", + "Ġmulti tude", + "requ ently", + "Ġmetam orph", + "Ġchap ters", + "h h", + "ch l", + "red undant", + "ack ing", + "Ġent ail", + "ĠPack et", + "ĠHabit at", + "im edia", + "ĠC of", + "ph rase", + "Ġcl oth", + "ars al", + "Ġdr ums", + "TP UT", + "Ar gs", + "duct ory", + "ĠUl timately", + "ic ates", + "anti gen", + "Th ough", + "ĠFl ore", + "pro bs", + "Ġcirc ulatory", + "ĠCont emporary", + "e plitz", + "Ġh atch", + "ri zed", + "ĠK op", + "mit ting", + "Ġhyper spectral", + "ĠAb st", + "S IM", + "Ġfruit ful", + "Ġrecip e", + "Ġimid azole", + "Ġsyn onymous", + "Ġattrib ution", + "ĠMart ÃŃnez", + "ĠRod rÃŃguez", + "par ticular", + "ĠInter acting", + "Con f", + "O RE", + "ĠT MA", + "uc idation", + "Ġbi ochemistry", + "ĠLe vy", + "Ġconcentr ates", + "Ġinduc tor", + "Ġpy rophosph", + "Ġrespond ent", + "Z hang", + "Ġro pe", + "Ġdesign ation", + "ĠCl im", + "Ġconstrain s", + "s helf", + "Ġd Ïĥ", + "ĠT LC", + "ĠA har", + "ĠM atch", + "ĠM OL", + "Ġfe es", + "we alth", + "Ġhyper activity", + "ĠBr uker", + "ĠFre und", + "dichlor ophenyl", + "re ro", + "ĠF ear", + "dot sc", + "Ġhy g", + "ĠText ure", + "T ak", + "am pled", + "Ġal geb", + "sub t", + "Ġdocument ary", + "ĠJ E", + "CN S", + "Ġdecl ar", + "He ight", + "K i", + "en oid", + "ĠC ervical", + "frac tory", + "Ġplant ed", + "IF I", + "Ġconcept ually", + "Ġfill ers", + "ic ola", + "le an", + "Ġcl ump", + "Ġwr iters", + "Gener ally", + "Ġo st", + "op ening", + "CL ASS", + "Ġherpes virus", + "In stit", + "Ġdr inks", + "ĠInt ensive", + "Ġmusic ian", + "Ġanch ors", + "S eries", + "ĠF AM", + "ĠB ott", + "ĠE CC", + "Ġinvers ions", + "Ġac res", + "Ġsw abs", + "ĠÍ ī", + "ĠBer keley", + "Ġpl um", + "Ġem power", + "Ġphoto emission", + "ĠRab i", + "E ast", + "T aylor", + "OS E", + "Ġden ied", + "ĠHT TP", + "M U", + "he w", + "Ġth ri", + "ĠC ERN", + "Ġsuff ice", + "functional ized", + "Ġcra bs", + "Ġidem potent", + "Ġpost ulate", + "ĠCB F", + "disc rim", + "Char acter", + "ĠRecomb ination", + "C ache", + "om it", + "ĠA da", + "Ġcur sor", + "EM T", + "Ġmes oscale", + "gu ide", + "Hy per", + "Ġh t", + "ren es", + "uss en", + "where as", + "Ġintegr ator", + "Ġsyn cy", + "aro us", + "Ġcounter act", + "hal ose", + "ĠNot ation", + "ĠRele vance", + "v f", + "Ġin bred", + "Ġrec irc", + "Ġend e", + "Ġpres idential", + "Ġlact ose", + "ac ional", + "os pi", + "ĠV GG", + "ose lectivity", + "ĠCon fig", + "Ġfinger prints", + "Inter face", + "pur ple", + "et us", + "ĠN in", + "ĠK ras", + "ĠRe ports", + "ĠSe attle", + "AD C", + "Ġlipoprotein s", + "cyclohex yl", + "op ressin", + "Ġwave front", + "tet razol", + "th ys", + "Ġdiv or", + "amin ophen", + "ĠPer ry", + "ĠConsider ations", + "ĠHal o", + "Ġreflex ive", + "thiazol idin", + "oxycycl ine", + "C W", + "od im", + "ĠCh ong", + "Ġequil ibrated", + "r ime", + "ym ology", + "Ġdev oid", + "rig el", + "amater gic", + "Ġidentif ications", + "Ġcontroll ability", + "ectic ut", + "ĠSynchron ization", + "ul atus", + "Ġcorrel ating", + "Ġmu ons", + "Ġcompartment al", + "Ġinhom ogeneities", + "Ġevac uation", + "resp iratory", + "dim ethoxy", + "Ġinterfer ometric", + "Ġastr onomy", + "Z D", + "Ħ Ħ", + "el ia", + "bl er", + "Ġpione ering", + "Ġp its", + "Ġman soni", + "ĠCON D", + "Ġcodew ord", + "im ura", + "ĠDop amine", + "ĠGi ov", + "ĠCamero on", + "S em", + "d ong", + "ot to", + "em ies", + "Ġinter quartile", + "ll bracket", + "otrop ies", + "Ġhapp ening", + "ĠPal m", + "Ġst uff", + "Ġpar king", + "eg al", + "ĠCO P", + "Ġorgan izing", + "Ġpoly hedral", + "Ġproven ance", + "J s", + "ch ains", + "eg u", + "mer cap", + "level and", + "Ġeryth roid", + "ympt omatic", + "Ġzig zag", + "Ġinf erring", + "Ġappro x", + "Ġdown link", + "ĠDef iciency", + "rbrack et", + "ĠT IM", + "ST S", + "ain en", + "Ġun loading", + "ĠX P", + "ĠWh ilst", + "ĠID H", + "ĠTI MP", + "r rbracket", + "ac ities", + "Ġwh ale", + "ĠW AR", + "Ġinf l", + "ĠPresent ation", + "authorbs nm", + "Ġbacter icidal", + "SP EC", + "Ġdys regulated", + "ĠIC AM", + "n ano", + "Ġw afers", + "ĠM UC", + "Ġal ien", + "ch ke", + "Ġsl abs", + "Ġback ing", + "ns is", + "Ġbal ances", + "eth ane", + "Link ed", + "C hen", + "H ymenoptera", + "it ations", + "ĠO UT", + "trans plant", + "condition ed", + "ĠBenef its", + "T yr", + "at mosp", + "ĠAd hesion", + "Ġthor ac", + "activ ator", + "Ġphosphatidyl inositol", + "Ġreported ly", + "ĠCL ASS", + "Ġrenew ed", + "ĠPharmac ological", + "Ġminim ise", + "gluc osidase", + "aden osyl", + "Ġov ip", + "initial izer", + "Ġfor age", + "rm s", + "ĠIm ag", + "ĠAnne xin", + "ĠVehic les", + "Ġf les", + "st a", + "ĠG BS", + "ĠCh at", + "measure ments", + "ĠAud itory", + "C ut", + "F v", + "Ġm aker", + "ap plication", + "Ġrevers ing", + "Ġsti p", + "Ġfaecal is", + "icy cle", + "Ġtrim med", + "Ġexacerb ation", + "Ġtransc ranial", + "ĠMoment um", + "Ġf c", + "ĠF OV", + "Ġang ina", + "Ġnano structure", + "Ġantagon ism", + "ĠLED s", + "ìĹ IJ", + "Ġf als", + "ap oration", + "ĠIn vasive", + "ĠK m", + "ert ation", + "Ġhar ness", + "Ġfer tile", + "ĠTR UE", + "Ġshel ter", + "ĠWol bachia", + "sho ot", + "Ġs ess", + "ĠH ous", + "ĠA ce", + "ĠC ML", + "Ġpro active", + "Ġsh ots", + "Ġco up", + "rest ling", + "uniform ly", + "y am", + "ol ase", + "ĠI CS", + "ĠE bola", + "roll ing", + "tr unc", + "ĠRepresent atives", + "Ġgras ping", + "ĠAnomal y", + "ĠM ine", + "ĠM PO", + "ler ight", + "Ġinstit ute", + "Ġsug arcane", + "ÑĢ Ð°", + "Ġoccl uded", + "ĠMagell anic", + "B EC", + "W i", + "o A", + "Ġg apped", + "ĠPR C", + "ĠMA E", + "Ġmusic ians", + "ĠSignific antly", + "Ġforth coming", + "Ġaccl imation", + "re quired", + "ver bal", + "ĠF X", + "ĠM LE", + "Ġcomp ass", + "ĠMultim odal", + "G rant", + "Ġt vb", + "In struction", + "Ġsens es", + "urb ed", + "ham n", + "Ġfram ed", + "Ġuro thel", + "or in", + "se al", + "Ġfl asks", + "sh ops", + "Ġwhe els", + "ĠRad on", + "ĠPlan etary", + "Ġhed ge", + "Ġd k", + "Ġevid ently", + "thread s", + "Ġt ad", + "el im", + "im ov", + "ist em", + "and i", + "Ġle isure", + "ost om", + "Ġcar ing", + "ĠSm oking", + "Ġcompetit ors", + "A FS", + "x l", + "ĠS atur", + "ĠF erg", + "Ġch in", + "ĠCD R", + "ĠSO M", + "osacchar ide", + "MOD EL", + "E CC", + "Ġd as", + "agon ist", + "st ery", + "Ġrel ays", + "ze k", + "Ġneoplas m", + "C hip", + "Ġg ill", + "lam ed", + "cer ning", + "Ġincons istencies", + "ace ans", + "ĠAd ri", + "ĠAf ghan", + "Ġnic hes", + "Ġtunn elling", + "g us", + "ĠI an", + "Ġbur ial", + "Trans form", + "ocomp atible", + "Ġs eldom", + "Ġdis closed", + "âĪ ķ", + "Ġref ining", + "Ġty ph", + "Ġcooper ate", + "Ġasphal t", + "ĠCons titution", + "fl avor", + "Ġwar p", + "Å ¼", + "Ġc raw", + "ĠInd igenous", + "ĠPre vent", + "Ġtrig eminal", + "ĠFried rich", + "ĠInterfer on", + "i osity", + "w arm", + "us on", + "Ġunder lies", + "Ġmultiple ts", + "ĠSU PER", + "ĠManufact uring", + "Ġv imentin", + "ram ine", + "Ġeffic acious", + "ic ed", + "ĠV all", + "oth orax", + "Ġaud i", + "Q s", + "ĠP AL", + "ĠH old", + "hat tan", + "idd ing", + "w ana", + "Ġp ending", + "Ġp erennial", + "Ġtouch ing", + "xp ected", + "D istance", + "n av", + "Ġis omeric", + "ĠM CI", + "num bers", + "Ġrevers es", + "Ġpolyc ystic", + "H em", + "u ities", + "op tional", + "Ġsub cortical", + "ĠSup ply", + "ĠCal der", + "Ġmang rove", + "Ġp ads", + "ur faces", + "ĠF aster", + "Ġunder neath", + "Ġprol actin", + "Ġcle arer", + "Ġscin tillation", + "Ġhumid ified", + "ĠW ound", + "ĠH PA", + "Ġcoll apsing", + "Ġbary onic", + "ĠMEA SU", + "ĠG ü", + "Ġdet r", + "Ġsubstit uent", + "ĠRoman ia", + "ĠInv olved", + "Ġduoden al", + "ĠAm p", + "ĠS IS", + "sc her", + "aut h", + "ĠResp ond", + "ĠRank ing", + "t rip", + "x F", + "ist in", + "Ġpa uc", + "ref lection", + "Ġcorne a", + "Ġbol us", + "Ġpiv ot", + "Oc tober", + "ĠS ERS", + "ĠX ing", + "AN ET", + "Ch inese", + "ĠMus c", + "D ynamic", + "M esh", + "Ġdi phosphate", + "Ġcons pecific", + "lect or", + "ĠEc u", + "ĠCover age", + "ĠãĢ Ī", + "C OD", + "am ong", + "Ġpos it", + "imum ab", + "Ġp N", + "Ġco aching", + "ex ports", + "Ġreal m", + "ĠFer reira", + "Ġnation ally", + "Ġtur tle", + "ubted ly", + "ĠD raft", + "Ġend l", + "ĠContinu um", + "embed dings", + "Ġá¹ ½", + "ĠCr ime", + "Ġimm igration", + "ĠFil ip", + "Ġgar net", + "Ġobsc ure", + "ĠT YPE", + "Ġult rastructural", + "ca emia", + "ĠSem an", + "r ink", + "ti ff", + "uc cal", + "ke e", + "itud inally", + "ĠAll oy", + "ĠAnaly zer", + "contin ue", + "ĠAlab ama", + "Q OL", + "Ġpol lin", + "Ġcorrespond ences", + "ĠRes ol", + "F IR", + "ul are", + "ta wa", + "UR CE", + "Ġurban ization", + "z d", + "Ġgl oss", + "ER A", + "ĠDeterm ine", + "D ate", + "ĠP SP", + "ĠSh ig", + "rep ta", + "ĠGa it", + "neut rino", + "Ġper vasive", + "ĠâĢ¢ âĢ¢âĢ¢", + "Ġhom ozyg", + "Ġadap tively", + "graph ic", + "ĠJohn ston", + "z t", + "ex plicit", + "Ġhel min", + "Ġp es", + "AR F", + "ĠF ram", + "ĠAm sterdam", + "Ġlogarithm s", + "ĠCre ative", + "Page Index", + "Ġp acing", + "ĠP CS", + "Ġfore brain", + "ĠCT CF", + "dec omposition", + "Ġbear ings", + "Ġanhydro us", + "Ġc b", + "ĠM ON", + "ĠN odes", + "str um", + "ĠJ ans", + "Ġdeline ate", + "Ġdichro ism", + "con formal", + "Ġret reat", + "gl ial", + "Ġnucle ase", + "ĠBal timore", + "Ġpay ing", + "Ġbore al", + "tip ation", + "R oot", + "S QL", + "s ources", + "end o", + "ĠOr ion", + "Pl us", + "ĠD EL", + "ĠTh an", + "Ġmon oph", + "Ġreflect or", + "Z e", + "ĠL inking", + "syn c", + "ĠCRE B", + "n ational", + "ur ized", + "ĠP eptides", + "ĠB egin", + "bor g", + "piper idyl", + "Ġoverestim ation", + "R GB", + "T K", + "Ġbe ings", + "Ġat tains", + "Ġres ervation", + "ĠMo tivation", + "Ġtrim ethyl", + "ĠTerm inal", + "Ġinten tional", + "Neg ative", + "ĠCron bach", + "dorfer i", + "D aw", + "V AR", + "d P", + "im ath", + "ove rex", + "Ġfibro tic", + "Ġsmart phones", + "Ġont ologies", + "G ood", + "u tively", + "ĠV B", + "SP E", + "ĠMcD onald", + "galax ies", + "Ġbioch ar", + "ĠE MS", + "ĠN f", + "ors hip", + "Ġback scattering", + "ĠÐ ¿", + "Ġanthocyan in", + "ĠPho enix", + "con tained", + "ĠPS II", + "hl ung", + "ĠLA I", + "Ġlect ures", + "Ġdisp atch", + "V F", + "ĠM EC", + "ĠW es", + "Ġback scatter", + "oti te", + "ĠSR C", + "Ġcurren cy", + "onym s", + "as partate", + "Ġcos et", + "ĠC PP", + "orb ing", + "ĠEmbed dings", + "ĠSurve ys", + "Ġneurodevelop mental", + "ĠS RE", + "ĠInter ior", + "ĠAR DS", + "experim ents", + "brom ophenyl", + "ĠE CL", + "ĠO PE", + "medi ation", + "Ġtherm oc", + "Ġinterpret able", + "ĠMicrobi ome", + "e astern", + " ¿", + "ĠT DP", + "ath on", + "ĠBy zantine", + "any on", + "Ġepit axy", + "Ġcritic ized", + "Mill ipore", + "ĠD EP", + "ĠFre edom", + "j unctions", + "ĠA SM", + "ĠG ren", + "Ġsign ing", + "Ġconstit uting", + "opro terozoic", + "ĠSyn ech", + "ĠVo ice", + "Ġcholec yst", + "b ilities", + "on line", + "ĠE dd", + "ĠK up", + "ĠLet t", + "ĠMar in", + "ĠGo al", + "ĠSY M", + "intro duced", + "naphth yl", + "ĠL ü", + "Ġm x", + "Ġb lu", + "Ġr m", + "ĠDe letion", + "ĠConn ecticut", + "Cole optera", + "t ry", + "Ġso ot", + "ĠCount ries", + "Ġsick le", + "M eta", + "ĠS ib", + "ĠH NO", + "ĠU D", + "Ġexp r", + "Ġallow able", + "ĠInd irect", + "tis ation", + "Ġaden omas", + "electron ics", + "R NN", + "ĠT CF", + "Ġgluc agon", + "ĠC itation", + "Ġg amb", + "and ez", + "Ġtrans mits", + "aj ima", + "Ġhol onomy", + "ì ł", + "act am", + "ĠTh reat", + "ĠPear l", + "Ġerup tions", + "ĠImmunohist ochemistry", + "Y es", + "p atrick", + "Ġa ma", + "Ġd rew", + "ĠT asks", + "ĠP IM", + "Ġdis pat", + "ĠDet roit", + "Ġcoex ist", + "arboxyl ase", + "I BM", + "ĠT UNEL", + "ĠU F", + "ĠAN G", + "Ġsar copenia", + "Ġh aptic", + "Ġcarbon ates", + "Ġmit ophagy", + "Ġciti zen", + "ĠCONTR OL", + "f if", + "Ġw i", + "ĠG LO", + "ens ored", + "ĠPar a", + "ĠAb del", + "oi etin", + "Ġto e", + "ĠS QU", + "ĠR ag", + "Ġx ylem", + "Ġlib eral", + "ĠMarg aret", + "W a", + "k p", + "ĠP EM", + "ĠD DR", + "Ġgen otypic", + "ĠY M", + "ING S", + "ker as", + "ĠEduc ational", + "ĠCult ures", + "in str", + "ĠF uchs", + "ag asc", + "fact ant", + "Ġt enth", + "AB L", + "Ġperme able", + "ĠCam eron", + "Br N", + "ĠMull er", + "ĠRevers ible", + "w ild", + "Ġf usions", + "os ulf", + "ĠE oS", + "ĠK ö", + "det ected", + "ĠColl agen", + "Ġdescend ants", + "e lection", + "ar ange", + "Ġb ounce", + "Ġcont ag", + "In valid", + "ĠCo ating", + "t asks", + "ar ma", + "ĠK C", + "Ġdi ar", + "ĠSup press", + "Ġfraction ated", + "Ġsn ail", + "Ġmicro phone", + "ĠSc ienti", + "Ġchem iluminescence", + "soft ware", + "Ġburg dorferi", + "Ġb oot", + "ĠC SCs", + "ĠM SI", + "ts ev", + "Ġhe ater", + "frac tal", + "Ġend osomes", + "Un iform", + "Ġath lete", + "ĠDri ven", + "Ġviv ax", + "K ind", + "satisf ies", + "Ġcorticoster oid", + "ĠEstabl ishment", + "cal ibration", + "Ġdim eric", + "Ġcere al", + "ĠSuper vised", + "ĠSP M", + "MB ER", + "Ġhemisp heres", + "Ġpercenti les", + "L eu", + "M ajor", + "Ġex agger", + "Ġds RNA", + "Dec ember", + "ĠZr O", + "Ġas ymmetrical", + "ĠV AS", + "ĠJ M", + "Ġintegr ations", + "Ġhand over", + "C ycl", + "im plant", + "Ġqu ote", + "Ġcycl one", + "ĠSte phan", + "ĠFran co", + "Ġaw ake", + "Ġfeed er", + "CH AR", + "Con dition", + "ĠChar l", + "ĠBrig ade", + "Ġremedi ation", + "c ig", + "ĠBoh r", + "ĠVac uum", + "ĠTox oplasma", + "Ġgh relin", + "ĠT RAF", + "ay e", + "Cl ient", + "ili ation", + "xy z", + "ming ham", + "ĠSU B", + "ïĢ ł", + "Ġconvers ions", + "Ġmulti path", + "miss ive", + "Ġeq n", + "bul k", + "my c", + "Ġexacerb ated", + "Ø ª", + "Ġprotein ase", + "Ġbu ilder", + "ah ara", + "Ġinver t", + "ĠRecep tion", + "ax anthin", + "Ġprim ed", + "Ġcop ula", + "Ġproceed ings", + "Ġnond egenerate", + "Ġint ox", + "Ġneed les", + "length s", + "Ġtranspos on", + "h on", + "ĠT PC", + "pl and", + "ox yn", + "IC H", + "Ġintra uterine", + "Ġlamin ated", + "ĠOBS ERV", + "M atch", + "ĠIn sur", + "ĠAm yloid", + "Ġwar ped", + "emat ical", + "ĠPrac tices", + "ãģ ®", + "ĠBrass ica", + "Ġhyperther mia", + "Ġd n", + "ĠL IF", + "ĠMet ropolitan", + "ĠBr dU", + "imp act", + "f iltered", + "ĠRe agent", + "v p", + "ĠT ip", + "ĠPro portional", + "Ġblood stream", + "Sim ple", + "Ġty ros", + "ĠHen ri", + "Ġretro trans", + "aci ens", + "Ġmist akes", + "acyl glycerol", + "ĠMir ror", + "V ERSION", + "v re", + "Ġb act", + "ock ed", + "eps is", + "Ġson ication", + "ĠPur kinje", + "Ġmism atches", + "ĠA OD", + "Ġhyper graph", + "ĠMi ami", + "am med", + "Ġcon versely", + "ĠG abor", + "ĠG DM", + "Ġco iled", + "onic a", + "Ġevol utions", + "ĠR BM", + "ĠRe ef", + "ĠAb ram", + "ĠPrec ise", + "incre ase", + "ĠPlate let", + "Gener ator", + "Ar ch", + "ĠBen ed", + "pre ceq", + "meas urable", + "C AS", + "ĠT ourn", + "Ġg iants", + "Ġed dies", + "Ġcolumn ar", + "agg regation", + "Ġzircon ia", + "duc ibility", + "Ġserv o", + "Ġbe auty", + "Ġhe ap", + "ĠâĪĴ âĪĴâĪĴ", + "Ġconduc tivities", + "Ġdark ness", + "Ġoccup ying", + "ĠCle an", + "b ash", + "ul ans", + "app y", + "ĠMark er", + "run time", + "Ġhaem oglobin", + "Ġdesk top", + "m is", + "ĠS of", + "os se", + "Ġcom oving", + "Ġcl utter", + "our ced", + "Ġsub j", + "arch ing", + "ĠSol omon", + "lock ing", + "Ġpar ap", + "Ġrot ator", + "ĠACKNOWLEDGM ENTS", + "T er", + "y ster", + "ĠWe bb", + "Ġsubs ample", + "osil icate", + "T raining", + "or pha", + "Ġtime out", + "otin amide", + "ĠFab ry", + "ĠRece iver", + "Ġconjunc tiv", + "ĠEcu ador", + "ĠI da", + "Ġcase in", + "Ġïģ ¸", + "ĠBar n", + "ĠSchool s", + "el ona", + "di p", + "ĠCh rys", + "IC I", + "Ġposterior i", + "Ġble aching", + "ĠPerson ality", + "um bers", + "ĠM odes", + "Ġno tification", + "Ġsup ine", + "alu ed", + "ke ep", + "ĠFran z", + "Ġwound ed", + "Y L", + "Ġdi lemma", + "ĠCl ara", + "ĠCar roll", + "Ġsick ness", + "Ġprox ies", + "ec ks", + "ĠÏ «", + "Ġplant ing", + "Ġcipher text", + "ĠF amilies", + "ies el", + "Ġinc ongru", + "ĠExc itation", + "Ġconfer red", + "ĠBut ter", + "Im pl", + "coll ision", + "id ol", + "Ġac quires", + "ĠO wen", + "SA M", + "ĠG UT", + "lec ts", + "Ġdele g", + "Sh ot", + "Ġanth rac", + "Russ ian", + "ĠP CE", + "Ġâ ĥĹ", + "ĠK ab", + "NA C", + "Ġarg parse", + "ĠVi ol", + "Ġantico agulation", + "Ġcredi bility", + "Ġrota virus", + "ĠIn vest", + "Ġrec ol", + "vari ety", + "Ġdeform able", + "Ġenerge tics", + "Ġconsult ations", + "le tics", + "ĠF oss", + "ĠL IGO", + "ph p", + "ĠCh al", + "ĠMal awi", + "Ġstro kes", + "h orm", + "Ġb s", + "Ġpl ural", + "str ategy", + "Ġmis alignment", + "pre vious", + "fil ters", + "ĠDem ographics", + "determ inistic", + "Ġcycl ophosphamide", + "Ġstre ak", + "ĠBios ynthesis", + "Ġsubcutaneous ly", + "j n", + "Ġam picillin", + "ĠCh ag", + "iform es", + "IF ICATION", + "Ġyour self", + "Ġtoler ability", + "Ġaut ocl", + "rh s", + "Ġpup ils", + "Ġgaug ed", + "L ay", + "ĠS anti", + "ĠD BP", + "ĠG ary", + "dri ve", + "Ġtrust worth", + "Ġconting ency", + "C ube", + "H ost", + "f u", + "Ġh sa", + "iss ner", + "IT T", + "ĠSr TiO", + "Ġcouns elling", + "inte grable", + "Ġunder way", + "Ġstandard ised", + "bi us", + "First ly", + "Ġporph yrin", + "A rea", + "i w", + "Ġ ub", + "ĠL ynch", + "ĠW BC", + "ild en", + "Ġhom eless", + "Ġmagnet osphere", + "Ġnight time", + "nc bi", + "Ġdow nt", + "le thal", + "Ġinter im", + "ĠRes il", + "Ġcontinu ally", + "ĠImmun ofluorescence", + "Des ign", + "Ġadvoc ate", + "repta vidin", + "f w", + "st ory", + "ĠP SS", + "Ġfil ed", + "Ġbed rock", + "Ġisofl urane", + "Ġrel uct", + "ew ard", + "ĠInd ependence", + "ĠBurk holder", + "Ġc inn", + "Ġcap tive", + "Ġcompos ing", + "Ġrest raint", + "Ġquestion able", + "ĠTom ato", + "Ġzer oth", + "r ins", + "ome z", + "Ġgl ia", + "ĠGl ac", + "Ind ependent", + "Ġobj ectively", + "p A", + "Ġfav oring", + "ipel ago", + "Ġincont inence", + "b ium", + "ĠL Z", + "ĠL ed", + "hex yl", + "Ġce ased", + "Ġole ic", + "ĠImpair ment", + "Ñ ĸ", + "ong o", + "Ġrun ner", + "Ġcuc umber", + "ĠPer form", + "Ġdouble ts", + "Ġeigen function", + "ĠÌ º", + "ĠHend erson", + "K lein", + "T ab", + "Ġbe er", + "oc om", + "unc iation", + "---- --", + "ĠT SC", + "og as", + "Ġr ud", + "Ġinc is", + "ĠLO G", + "FB Q", + "Ġinterconn ection", + "à ®", + "ar box", + "ĠI BS", + "ĠN CT", + "ĠG and", + "Ġy aw", + "ĠTrans verse", + "ĠSud an", + "Ġconstric tion", + "H or", + "Ġev asion", + "Ġmer omorphic", + "ĠPB MC", + "I UM", + "re ed", + "ĠB ö", + "ĠE MB", + "uk h", + "Ġwin ners", + "Ġasc ites", + "M es", + "Ġe clipse", + "ĠE ocene", + "ad iazol", + "Ġrecover ies", + "Star ting", + "re ma", + "Ġà İ", + "mon otonic", + "ĠMe OH", + "ĠFl ood", + "Ġwat ching", + "G TP", + "i el", + "m üller", + "å ħ", + "Ġpolyphen ol", + "ĠL MI", + "red it", + "ther m", + "Ġneur ite", + "Qu antum", + "rach lor", + "ĠRub in", + "Ġbf nm", + "A re", + "ar achn", + "Ġd uck", + "ĠTra jectory", + "ĠNit ric", + "l v", + "u id", + "im ag", + "ĠM ULT", + "Ġgen re", + "ari e", + "Ġtr ifluor", + "ĠCor pus", + "oli osis", + "ĠCC K", + "K it", + "f ather", + "Ġt ennis", + "its ch", + "HC V", + "l antic", + "ĠA Q", + "iz u", + "ast atin", + "oth io", + "ĠAn atomy", + "Ġá ŀ", + "glob ulin", + "Ġinterp ol", + "Ġtunn els", + "Ġn atri", + "im ed", + "ĠD ew", + "Ġsub scripts", + "tit es", + "Ġhistological ly", + "O pt", + "x n", + "Ġres ampling", + "ane y", + "Ġtr ast", + "Ġsin ensis", + "Ġsenes cent", + "F ast", + "Ġh ampered", + "Ġblock er", + "ush ima", + "Ġhospital izations", + "L im", + "o ons", + "à ¿", + "ĠA PS", + "ĠY ok", + "ĠZ am", + "Ġexperim enter", + "ĠDis ks", + "Ġà ¬", + "ĠS cop", + "ĠA ph", + "ĠP arents", + "ĠPl ots", + "ĠCON T", + "ĠÐ Ī", + "Ġhomolog ue", + "ĠCool ing", + "re th", + "Ġo vari", + "ĠT amil", + "v rule", + "ĠP CP", + "ari ous", + "Ac tive", + "oprot ection", + "ĠAlf v", + "Ġinf ra", + "ĠCo herence", + "clos ures", + "hydrox ymethyl", + "E H", + "Ġm aser", + "ĠN IST", + "lec k", + "con cat", + "Ġtra ine", + "Ġmix es", + "Ġrib osomes", + "l ia", + "p uls", + "Ġas cer", + "ĠB anks", + "ell in", + "ap plied", + "Ġcl ips", + "Ġmet ap", + "Ġcop ro", + "Ġepid id", + "ĠEpidem iological", + "ĠNich olas", + "ĠK ings", + "Ġlar va", + "Bs Ag", + "ĠS ánchez", + "ĠâĪ İ", + "AM D", + "ĠHa o", + "ĠBill board", + "ĠAbor iginal", + "Ġn ylon", + "ĠN AN", + "c ores", + "ĠC rop", + "Ġcom mittees", + "Ġdi hedral", + "ĠJ uli", + "ĠAnd y", + "hyd ration", + "correspond s", + "M ut", + "Ġt orn", + "ĠF EV", + "Ġx s", + "amp hen", + "Ġsummar ization", + "ĠEr g", + "Ë Ĩ", + "ĠJ unction", + "anc ouver", + "ĠEx amining", + "ĠMarc o", + "Po inter", + "Ġscarc ity", + "unc ing", + "Ġbi jective", + "ĠMain e", + "ĠRH IC", + "Ġtow ers", + "Ġgent amicin", + "Ġt onic", + "Ġk T", + "Ġclim bing", + "Ġrecru its", + "ĠHot el", + "ĠJew s", + "ĠRUN X", + "Ġausten ite", + "ĠOffic er", + "in ent", + "uc c", + "ĠB idirectional", + "Ġmay or", + "ĠAss ays", + "ĠER G", + "SN Ps", + "d ine", + "Ch ina", + "star ting", + "Ġirr ational", + "ĠDIF FE", + "Ġmillisecond s", + "L ik", + "in one", + "Ġâ ģĦ", + "Ġcons picuous", + "Ġsur plus", + "ĠX iong", + "Ġup grade", + "Ġtim ep", + "ĠÄ Į", + "Te V", + "orbid ities", + "in valid", + "Ġv ide", + "ter ra", + "Ġan tin", + "em ens", + "oc ese", + "ĠK I", + "Ġevolution arily", + "K er", + "ĠL ES", + "cl amp", + "Ġslow ed", + "gly coprotein", + "enti eth", + "Ġab road", + "Ġinterp olating", + "Ġcataly ze", + "ĠBelg ian", + "Ġphotograp hed", + "Ġp ector", + "ĠS IV", + "ĠE LECT", + "Ġdes al", + "one ph", + "ĠCl os", + "Ġafford able", + "b irds", + "g om", + "Ġr r", + "Ġun i", + "ĠGen us", + "ĠReg ge", + "ĠMulti dimensional", + "Ġpsych opathology", + "Ġcer tification", + "P attern", + "ĠT ower", + "Ġst ern", + "Ġsub lattice", + "Ġgr at", + "Ġly rics", + "f mt", + "o ceptive", + "Ġd P", + "ĠHol mes", + "Ġbudget s", + "Ġeut ectic", + "ĠP v", + "ĠG ott", + "Ġdis infection", + "Ġret inoic", + "ĠOb st", + "Ġrepl en", + "Ġâĸ ł", + "K utta", + "P lease", + "ĠC AG", + "ĠSti r", + "spe aking", + "Ġinsectic ides", + "ĠFung i", + "H od", + "R ON", + "co il", + "ĠVis ible", + "Ġin ception", + "Ġe GFR", + "Ġre ionization", + "Ġdom ination", + "ĠMet ro", + "Ġsw ept", + "MD S", + "Ġsubs idence", + "ĠFall s", + "ĠD rum", + "ĠCons erved", + "ĠMy ers", + "Ġadapt ability", + "Ġly ophil", + "ul ina", + "are lli", + "ocy cles", + "ĠSO A", + "Ġds DNA", + "ĠCE O", + "Ġanch oring", + "Ġde activation", + "yl er", + "Ġinteresting ly", + "Ġ iliac", + "ĠB org", + "ĠPT C", + "ocyan in", + "Ġun used", + "ĠCar rier", + "Wh ich", + "Ġinterven ing", + "Ġprivi le", + "h it", + "Ġche aper", + "ĠCycl in", + "p lying", + "ĠC leveland", + "ĠH ahn", + "Ġag glut", + "ĠAn ch", + "ĠRed ox", + "W ill", + "ĠL inn", + "ron es", + "ĠNew castle", + "ĠExp ected", + "ĠOpportun ities", + "ĠL arger", + "Ġle ach", + "Ġpep per", + "S ha", + "s ector", + "y ou", + "Ġre plications", + "ch olesterolem", + "ĠIn vasion", + "Ġb ony", + "ĠH uber", + "the nd", + "Ġreal ised", + "Ġinvest ments", + "C ataly", + "ĠW itt", + "ĠK ai", + "Ġet ched", + "ĠST EM", + "Ġexcl udes", + "Ex ec", + "ĠStrong ly", + "ĠSym posium", + "ĠTub erculosis", + "il ance", + "ĠR IS", + "ap ia", + "ens ated", + "ne b", + "ĠCh ains", + "Ġent hus", + "quad rup", + "dec l", + "Ġbin ned", + "Ġsynerg istically", + "Ġgaug es", + "whe ther", + "dise ase", + "W estern", + "Ġhyp othermia", + "ĠGard ner", + "Ġaber ration", + "R od", + "Í ĺ", + "Ġf d", + "Ġst ood", + "Ġcondition ally", + "Ġthrom bol", + "P SC", + "Ġm k", + "ĠT ER", + "od ds", + "ĠK ri", + "ĠIV F", + "Ġm ites", + "ĠC HE", + "Ġq q", + "ĠInf ants", + "ĠChar lot", + "bec co", + "et om", + "ĠCD S", + "Ġarchae al", + "ĠHN SCC", + "Ġmonod romy", + "amphen icol", + "a pers", + "re activity", + "Ġund erm", + "In ternal", + "ĠLands at", + "G erman", + "Ġcer vix", + "id azole", + "ĠS ketch", + "ĠL AM", + "ĠN erve", + "ĠTe h", + "Ġmuss el", + "Ð ·", + "ĠMicro array", + "we i", + "Ġwhe y", + "Ġmix er", + "Ġrecon figurable", + "Ġvascul itis", + "Ġkw args", + "Ġre us", + "cor relations", + "Ġwood y", + "carbon ate", + "ectom ized", + "Ġret rans", + "Ġcyt ometric", + "ĠWild life", + "ĠAnsw ering", + "Ġp encil", + "ĠD AS", + "ak rish", + "CE PT", + "ĠÄ Ŀ", + "ĠPers ian", + "conver ting", + "Ġc ater", + "Ġmean while", + "TP A", + "Ġr um", + "ĠG ros", + "up e", + "Ġreg urg", + "Ġpenal ties", + "Pos itive", + "************************************************************************ ****", + "X F", + "e enth", + "ĠC ory", + "od ulation", + "Ġqu orum", + "c odes", + "ar am", + "ĠT SA", + "ĠP n", + "âĪ ij", + "pr ison", + "Ġconfidential ity", + "E PS", + "X iv", + "i ensis", + "est ones", + "ĠZ ag", + "Ġpred ecessor", + "Ġpri ze", + "Ġâİ ¨", + "ster oidal", + "op ard", + "Ġimp ractical", + "Ġdemonstr ations", + "Ġpredis position", + "Ġk k", + "Ġmod ifiers", + "Ġprec a", + "Ġexec utes", + "Ġbin ning", + "Ġped ig", + "ĠKL F", + "ĠS keletal", + "ĠC IN", + "ature d", + "Ġdecom poses", + "Ġaph id", + "B ern", + "P ur", + "ĠE PO", + "mer ge", + "ĠCO SM", + "am yloid", + "mon ia", + "ĠSc ores", + "ĠReg istration", + "ĠAg robacterium", + "Ġenter prises", + "loc ality", + "ĠIT O", + "Ġt ess", + "Ġf cc", + "ĠN c", + "Ġco axial", + "ĠAd vant", + "AP C", + "ĠDem and", + "adj ust", + "Po ints", + "Ġhetero structure", + "iffiffiffiffiffiffiffiff iffiffiffiffiffiffiffiff", + "D Q", + "Ġt ensions", + "ab und", + "ĠH utch", + "bre w", + "Ġvit reous", + "ĠEZ H", + "Ġm erc", + "Ġdeb ated", + "Ġpal ate", + "ocol ate", + "Ġevap otranspiration", + "ĠẠ¼", + "ĠHoff man", + "ĠGALAX IES", + "C AL", + "c aps", + "le gal", + "di ed", + "ĠIs olates", + "Ġagg rav", + "q s", + "ĠI CT", + "Ġse als", + "Ġspin el", + "ĠGe or", + "Bl ue", + "Ġure ter", + "spl ine", + "ĠIntro ducing", + "thend ieck", + "op per", + "Ġafter glow", + "Ġend osomal", + "Ġreal izes", + "sol ving", + "Ġmist ake", + "ĠAthe ros", + "ĠS BS", + "ĠR ut", + "ex ist", + "Pro f", + "ĠNe isser", + "MS G", + "ĠEar lier", + "Ġd T", + "ĠSp read", + "ĠRef lection", + "second ary", + "approxim ate", + "Ġnig ra", + "S olution", + "an one", + "ĠIt ems", + "Ġwave lets", + "ĠSol uble", + "Ġcircular ly", + "ĠCU DA", + "Ġreg enerated", + "SP I", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠ", + "at uring", + "RE Q", + "Ġinter oper", + "ree v", + "ON T", + "isc hen", + "ĠCho osing", + "phosphor ylated", + "á Ī", + "Ġd ress", + "ĠCon form", + "Ġrem emb", + "Ġischa emic", + "B asic", + "ĠP ang", + "Ġcr it", + "ĠOr n", + "Ġg m", + "ĠF og", + "ĠB d", + "rac heal", + "Ġphen ols", + "ĠDist ingu", + "Ġâİ ©", + "ĠGR Bs", + "ĠCe O", + "ĠBiom ass", + "Ġapt amer", + "v isc", + "he tically", + "Ġs id", + "ome g", + "Ġproportion ality", + "ÃŃ s", + "toplas mic", + "ĠConn ected", + "Ġlamin in", + "stra hlung", + "ĠL ad", + "TR AN", + "ä r", + "Ġbasal t", + "ĠCur vature", + "Ġmitig ating", + "opa edic", + "ĠMuh ammad", + "C AR", + "G i", + "Ġet ch", + "ha ir", + "Ġpur ine", + "Ġbenchmark ing", + "re ich", + "Ġmet hicillin", + "âĪ ¥", + "Ġman ages", + "sol vent", + "ĠSha o", + "h c", + "Ġstr uck", + "Ġnucle osome", + "ĠPubl ication", + "M etric", + "Ġw ines", + "ĠM BL", + "ĠH ub", + "ĠAss istant", + "Ġreli ance", + "Ġrout ers", + "ĠHer z", + "ĠTob acco", + "ro gram", + "ĠH SD", + "ĠL BP", + "Ġinf lection", + "sch ool", + "Ġspons ored", + "ĠCen ozoic", + "Ġentertain ment", + "ati an", + "archit ecture", + "brow se", + "RE C", + "ist ure", + "ĠCh olesterol", + "ĠSim plified", + "Ġpolyp eptides", + "Ġpunct ures", + "arachn oid", + "S elf", + "Ġan orexia", + "ĠO le", + "ĉĉ ĠĠĠĠ", + "GB T", + "Ġcardiomy ocyte", + "ĠFlo quet", + "anal og", + "Ġsensiti zed", + "ĠCep he", + "c atch", + "ch ial", + "Ġcere mony", + "Ġter at", + "Ġamelior ate", + "olys in", + "et ooth", + "ak in", + "ha em", + "Ġent ropies", + "Ġarg u", + "Ġcop ied", + "ling ton", + "ĠHer pes", + "ĠSchw ann", + "y k", + "ĠC EA", + "ĠI CH", + "Ġwr ink", + "Ġrun ners", + "Ġgal van", + "Ġconsol idated", + "ĠâĢ ¡", + "ĠClass ic", + "Ġepidem iologic", + "ĠDri ving", + "Ġtrast uzumab", + "C YP", + "N CT", + "t ability", + "Ġs lee", + "ĠN eck", + "Ġassess es", + "Ġsymmet rically", + "ĠPot ts", + "ĠRib osomal", + "d iction", + "g all", + "ĠAcc eleration", + "CL A", + "ACT ER", + "x ed", + "Ġg eriatric", + "th reonine", + "Ġab ort", + "Ġar tem", + "ĠDis ney", + "ĠCorrespond ence", + "Ġre nt", + "ĠN UM", + "ĠCh un", + "ĠRec ogn", + "Ġcrystall ized", + "Ġcontrad icting", + "vis ors", + "mal ignant", + "rophys iology", + "Inf rared", + "g z", + "Ġsub lim", + "omat osis", + "osyl transferase", + "Ġholog raphy", + "oren stein", + "¾ ±", + "ĠSe bas", + "acc um", + "Up per", + "ant enna", + "Ġbl ur", + "Ġsm ell", + "Ġthree fold", + "ĠPl ayers", + "Ġallevi ated", + "B in", + "Ġn inet", + "ĠD na", + "Ġgeneral izing", + "Ġbreak age", + "ĠMor rison", + "mac ro", + "Read er", + "ograv imetric", + "Ġd h", + "le w", + "xt on", + "Ġdec eleration", + "ĠCor related", + "ĠLeg ion", + "Ġgam bling", + "B inding", + "ĠIn As", + "low ering", + "Ġeuthan ized", + "ĠDall as", + "ĠD w", + "ĠDi jk", + "ĠPol ic", + "ĠT IME", + "ĠH EL", + "ĠL anguages", + "Ġpar abol", + "por ating", + "Ġfr ustration", + "μ M", + "ball s", + "ĠArm strong", + "Ġcontrac tility", + "Ġmetalloprotein ases", + "am eric", + "ĠZ ak", + "ĠCost s", + "A lex", + "d og", + "p w", + "ĠT ight", + "ĠAn terior", + "Ġpe aking", + "Ġneg ativity", + "Ġhyd ride", + "ĠL iv", + "Ġster ilized", + "Ġverb atim", + "Altern atively", + "RE QU", + "ĠTy phimurium", + "ĠWein berg", + "D SC", + "r q", + "Ġcor rug", + "Ġmic rons", + "co ord", + "i oid", + "s at", + "Ġfl occ", + "ĠAcc elerated", + "Ġsix teen", + "abs ence", + "ĠSpe aker", + "om ological", + "ĠA pr", + "Ġmat roid", + "tig ht", + "ogene tically", + "rum p", + "ĠInhib its", + "ĠOlymp us", + "Ġposs ession", + "Ġsuper visor", + "Ġconc ise", + "optim ized", + "v ivo", + "Ġstep ped", + "ocy anine", + "F ive", + "an as", + "ar ten", + "ĠC aco", + "Ġsol utes", + "IT AL", + "ĠRed dy", + "Ġwar ping", + "Ġolig omer", + "Ġc apped", + "Ġv oted", + "ĠR ico", + "ĠT rem", + "Ġl ime", + "ĠI SP", + "ĠL ayers", + "sk in", + "rang ed", + "á z", + "Ġbio activity", + "Ġd urable", + "Ġh n", + "ĠC AB", + "Ġv a", + "ĠU WB", + "ĠSt uart", + "Ġlength y", + "Ġinvas iveness", + "Ġâĩ Ķ", + "jo ining", + "ĠRB Cs", + "Ġresil ient", + "ĠManip ulation", + "G erm", + "cont ribution", + "Ġqual ify", + "ĠD ashed", + "Ġacceler ations", + "ĠCyt ochrome", + "Ġcircumst ellar", + "c avity", + "Ġan atase", + "ĠDe vi", + "Ġpur su", + "ĠMicro RNAs", + "Ġnorth ward", + "Ġsun flower", + "ĠEnter tainment", + "Pac ific", + "ĠHolog raphic", + "u j", + "ere ll", + "met hanol", + "Sur face", + "opos itive", + "Ġthreat ening", + "Ġtransc end", + "D epend", + "Ġq i", + "tis ed", + "ĠBr istol", + "umm ation", + "Ġextract or", + "Ġfav oured", + "ĠPy ro", + "ĠEngine ers", + "flat ten", + "toler ance", + "Ġ xt", + "ĠT ot", + "Ġtest bed", + "IC U", + "ĠSw arm", + "Ġintern ationally", + "Ġant ine", + "ĠInsur ance", + "b ai", + "n h", + "Ñ ĭ", + "os ac", + "ĠL ec", + "th or", + "Ġout ermost", + "Ġdo ors", + "Ġbi ometric", + "glut amate", + "ĠWood s", + "ĠMun ich", + "u ximab", + "pl aces", + "Ġam yotrophic", + "ĠPar am", + "ĠChrist ensen", + "A ge", + "en ne", + "Ġan im", + "Ġrec rystallization", + "ĠPro positions", + "Ġsn ails", + "Second ly", + "ĠPU FA", + "F rance", + "S rc", + "v itro", + "om ass", + "ur u", + "ĠL ever", + "ect onic", + "emb l", + "PC L", + "Ġcoordin ator", + "ĠFox p", + "ĠBir mingham", + "ĠLib eral", + "Ġcru ise", + "Ġi θ", + "Ġsym p", + "az aki", + "ĠPar se", + "Ġhyd rologic", + "Ġprolong ation", + "ĠHay es", + "Ġsubm uc", + "Ġaggl omeration", + "A RE", + "ĠF MR", + "ĠL omb", + "math char", + "Ġstruct uring", + "Ġelectroph oretic", + "Ġdimin ishing", + "Ġbra ke", + "chen ko", + "ĠPere ira", + "l ens", + "Ġback end", + "Ġillustr ations", + "Ġdemand ed", + "Ġnotice ably", + "ĠKa iser", + "ĠDavid son", + "Ġbrak ing", + "T p", + "For ward", + "μ ν", + "ĠCd S", + "Ġaster oids", + "Provid er", + "ĠE ut", + "Ġtr il", + "ung s", + "Ġdiv ing", + "ĠUAV s", + "ĠiP SC", + "i int", + "Ġ ×", + "th rombin", + "Ġcoordin ating", + "ext rem", + "Ġembol ization", + "ĠAdi p", + "pl ated", + "ĠH ag", + "ĠE TS", + "Ġbro od", + "An g", + "ĠPC V", + "det ail", + "R SS", + "b ens", + "Ġt ier", + "ĠC ock", + "Ġg ay", + "Ġqu int", + "Ġag enda", + "Ġaff airs", + "ĠMod erate", + "hel ical", + "ĠEqu ivalent", + "Ġproportion ally", + "Col umn", + "FW HM", + "A ir", + "E num", + "ific e", + "arc sec", + "ĠTR IM", + "ĠLab eling", + "Q AM", + "p ies", + "Ġis otropy", + "ĠG ó", + "Ġpo inters", + "tig raphy", + "ram ers", + "Ġmac aque", + "Ġmiss es", + "Ġelliptic ity", + "present ed", + "galact osidase", + "É Ľ", + "in ion", + "Ġm ite", + "ll l", + "Ob jective", + "Ġprison ers", + "ĠHerc ules", + "Ġanti s", + "Ġclos ures", + "ĠMar tian", + "Ġter pen", + "rob ust", + "Ġsequel ae", + "al arial", + "ĠC SA", + "ĠB land", + "ĠG ent", + "Ġor phan", + "Ġind ent", + "big wedge", + "Ġdefin able", + "Ġolig osaccharides", + "ĠBat talion", + "Ġis ometries", + "az olin", + "ĠSh own", + "spect ra", + "Vis ual", + "<<<< <<<<", + "Ġlenti viral", + "othel ioma", + "Ġted ious", + "ĠB CI", + "Ġge ologic", + "Ġconsum es", + "ĠAbl ation", + "le ast", + "Ġth igh", + "Ġsec recy", + "cover ing", + "e iro", + "à µ", + "ĠT BS", + "Ġis omerase", + "Ġrecomm ends", + "ĠVor tex", + "ĠB ray", + "Ġsub d", + "ĠOp tions", + "Ġmetam aterial", + "ĠSqu ares", + "t rap", + "im on", + "Ġhe sit", + "Ġab c", + "cess ing", + "ĠRE T", + "Ġpin ned", + "Ġket ones", + "Ġweld ed", + "ĠMitochond ria", + "Ġing ested", + "ĠQ FT", + "Ġcompar ator", + "Ġoxid oreductase", + "Ġtet rad", + "ĠSens itive", + "Ġcatch ments", + "Ġrefuge es", + "Ġpuber ty", + "A rab", + "Ġinter annual", + "sc attered", + "ĠMet am", + "Ġcycl ization", + "pert ures", + "ĠLIN C", + "r ules", + "ĠP ont", + "PT H", + "ĉĉĉĉ ĉĉĉĉ", + "S anta", + "ĠL NC", + "Ġsub modular", + "rec tive", + "Ġtr if", + "Ġsent inel", + "ĠTw in", + "kelet ons", + "m iral", + "am ing", + "ĠG ay", + "Ġinter specific", + "Ġrel ieve", + "Ġend omorphism", + "ĠExp anding", + "ĠRun time", + "y ang", + "re quires", + "od ine", + "omet abolic", + "St ore", + "plan et", + "Ġre nov", + "__ _", + "aden osine", + "u itive", + "Ġk el", + "ĠPro long", + "ĠAd vance", + "Ġantimicrobial s", + "ĠMunic ipal", + "ĠNeutroph il", + "F As", + "ĠF ame", + "ib us", + "ET E", + "Ġstep ping", + "ĠBl ot", + "ĠLa ura", + "Ġrock y", + "ĠLim a", + "Ġmitig ated", + "ĠLam bert", + "Ġunexpl ored", + "Ġtrigon ometric", + "p ig", + "ĠH eli", + "Ġfin ely", + "Ġoxid izing", + "Ġcolon oscopy", + "activ ities", + "ĠE asy", + "Ġunexpl ained", + "ak y", + "AS M", + "work er", + "ĠCr ist", + "ãĢ ģ", + "ul k", + "ĠS ugg", + "ĠM im", + "Ġiter ates", + "Ġsulf oxide", + "gluc an", + "Ġreact ant", + "Ġphag ocytic", + "B rain", + "uc ted", + "ĠSc and", + "ĠCa CO", + "Ġaffili ation", + "Pol icy", + "ĠInfant ry", + "F unctional", + "r times", + "Ġw ond", + "ard ment", + "ĠWe il", + "Ġdirect ors", + "uff ix", + "ĠRu iz", + "ĠPhenomen a", + "Ġmicro b", + "cos m", + "Ġutil isation", + "pers ed", + "Ġcon sole", + "tic ulate", + "Ġdes ens", + "Ġreplic as", + "Ġpluripot ency", + "ĠUk rainian", + "Ġhydroly zed", + "ĠBiod iversity", + "E fficient", + "ĠK ash", + "min or", + "Ġconcl usive", + "Ġtent ative", + "j ira", + "Ġm b", + "ĠI PA", + "ĠP is", + "Ġgover ns", + "ĠSouth west", + "oe ba", + "ĠMoh ammad", + "alb umin", + "c ircles", + "ĠH edge", + "ĠAm ph", + "B ACK", + "O ld", + "h istor", + "ac ular", + "ĠN OR", + "hen ius", + "vis ions", + "miss ibility", + "Ġthrombo embolism", + "at ized", + "Ġw il", + "aw ing", + "AS I", + "Ġheter odimer", + "Ġbuff ering", + "ĠIde ally", + "ĠE gg", + "ograph ies", + "ĠAp pl", + "ĠCI s", + "mean ing", + "ĠSM AD", + "Ġphenyl alanine", + "ĠTit anium", + "ĠZar iski", + "Ġn ymph", + "Ġh ired", + "ĠP PC", + "ĠK G", + "ĠGu ill", + "ogly cans", + "er ial", + "D ele", + "il us", + "ĠF itness", + "Ġwh ales", + "gr ant", + "most ly", + "Ġclim ates", + "ĠCamp aign", + "Mg O", + "Ġepist emic", + "L ipschitz", + "ĠL AT", + "Ġcl adding", + "vac uum", + "agglut inin", + "k ill", + "Ġs ail", + "Ġar tistic", + "ans w", + "ĠSD F", + "ĠKe ith", + "Ġsor afenib", + "Ġgall bladder", + "direct ory", + "Ġphotore ceptors", + "ĠFok ker", + "D U", + "Ġed itors", + "Ġte lecommun", + "ardi a", + "ĠPublic ations", + "Ġscrew s", + "ĠMathem atica", + "R SV", + "ĠAp ply", + "ĠST S", + "ĠMur ine", + "Ġd ump", + "Ġl ingu", + "ĠD ixon", + "Ġover comes", + "ĠPre operative", + "Ġmig rant", + "Ġbelie ves", + "B K", + "ac tively", + "ĠI SC", + "qu as", + "Ġal ga", + "ich ael", + "Ġdis asters", + "Ġprac ticed", + "hydro phobic", + "ĠNi ño", + "ĠEth anol", + "Q E", + "ĠS J", + "ĠD engue", + "Ġap pl", + "ĠY oon", + "enz o", + "IF Y", + "Ġchron ological", + "er in", + "ĠP eg", + "ĠRe levant", + "Ġqual ification", + "ev ine", + "Ġdend rite", + "DT D", + "chol inesterase", + "w atch", + "ĠS anchez", + "Ġwas hes", + "Ġper mafrost", + "ĠTer tiary", + "Ġsynthes izing", + "Ġexped ition", + "rout ine", + "ĠSear ching", + "ĠS é", + "res idual", + "ĠL CD", + "enti ties", + "Ġend ovascular", + "Ġparam ount", + "p her", + "Ġstraightforward ly", + "Ġvas odil", + "ĠSchist osoma", + "Ġper missions", + "cent red", + "Ġfr ustrated", + "struct uring", + "ĠSch l", + "ĠIniti ation", + "Ġcu ticle", + "Ġforget ting", + "ĠS as", + "ĠS ult", + "un o", + "Ġdis integration", + "ĠV G", + "Ġw ards", + "ĠI RE", + "up ro", + "Ġsub gen", + "Ġsub classes", + "ĠSt and", + "ĠHe ight", + "inter pretation", + "Ġgly can", + "ĠSol vent", + "ĠMal ignant", + "Ġuns uitable", + "ĠCox eter", + "Ġspermat ogenesis", + "Ġful lerene", + "F ox", + "S OC", + "w et", + "arm stadt", + "Ġprop ofol", + "index ed", + "Ġsn akes", + "Ed it", + "ĠmJ y", + "R IB", + "Ġe y", + "ĠAl kal", + "Ġtri axial", + "PS K", + "ne o", + "Ġend o", + "Ġglycos ides", + "Ġsyll ables", + "Ġs orghum", + "lo or", + "Ġge othermal", + "gu inal", + "ĠSerb ia", + "æ ĸ", + "ĠS entinel", + "igh ters", + "Ġkey board", + "Ġban ana", + "gran ular", + "Ġdecid uous", + "ĠH AR", + "ne uron", + "ĠCar n", + "Ġburn s", + "Bo ost", + "ĠDetermin istic", + "p ipe", + "ĠF AD", + "ĠB ovine", + "ĠR ou", + "Ġk an", + "aut onomous", + "utri ents", + "Ġhypoth yroidism", + "ĠSIN R", + "st ret", + "Ġun altered", + "ĠZ ika", + "val ley", + "Ġlong itudinally", + "Ġfluores cein", + "cat heter", + "ĠCong enital", + "Ġpie z", + "Ġabbrevi ated", + "ĠChlam ydia", + "Ġa ired", + "Ġqu een", + "Ġinstruc tive", + "Ġabrupt ly", + "Ġrecur rences", + "I MP", + "Ġex osome", + "ĠH SCs", + "Wr iter", + "el is", + "ĠAr ithmetic", + "enari os", + "Ġlig ated", + "ĠLocal ized", + "ĠFre eman", + "Ġcarn iv", + "ĠC ereb", + "Ġg rac", + "ĠG ond", + "ĠV ancouver", + "ob ox", + "Ġtyp ed", + "ĠÄ ¥", + "Up on", + "F uture", + "EN G", + "de ad", + "Ġser pent", + "ĠAss ignment", + "ĠUp dated", + "Ġhistor ian", + "Ġtroposp heric", + "C loud", + "b umin", + "ĠP ras", + "ĠB asket", + "ĠâĪĴ âĪĴ", + "benz odi", + "ĠTra uma", + "ĠBehavi ors", + "Ġp ter", + "ir radiation", + "Ġsp oke", + "ari atric", + "Ġpl ugin", + "Ġsuper sonic", + "Ġdoc etaxel", + "itig ation", + "Ġdiges tibility", + "n em", + "Ġp b", + "ĠC SR", + "Ġfo uling", + "Ġrhe ology", + "Ġflood s", + "Ġglu ing", + "agasc ar", + "j ets", + "p ti", + "est on", + "ĠK ü", + "Ġopen ings", + "Ġisol ating", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠ", + "Ġsemicon ducting", + "r ative", + "ec ology", + "ur ization", + "Ġmulti factorial", + "sh adow", + "Ġcross linked", + "Ġphyl a", + "Ġprem ises", + "ĠLO W", + "general ized", + "ĠPolynomial s", + "Ġb ismuth", + "ĠR oz", + "ĠDec oding", + "ĠClass ifier", + "conduc ting", + "Ġlit term", + "M ann", + "Ġf ant", + "ĠC Z", + "ĠP SNR", + "Ġstar ring", + "ĠPol yg", + "ĠHol m", + "r g", + "ad ditional", + "gu an", + "prof essional", + "Ġin quiry", + "ĠP g", + "ĠSch mid", + "Ġhead ed", + "cha ft", + "ĠExp and", + "Ġcompan ions", + "V an", + "ĠS ie", + "Ġcan als", + "ored oxin", + "Ġcoll iding", + "abs olute", + "ĠPhot os", + "ĠLeg acy", + "Ġrevascular ization", + "ĠP SM", + "Ġexp enses", + "IS MA", + "inter vals", + "Ġmultic ellular", + "Ġnons m", + "Ġresemb lance", + "H ep", + "Ġw ool", + "Ġn iger", + "ess a", + "asc i", + "Ġrot ates", + "Ġcompe titions", + "Ġarri vals", + "Ġlute in", + "Ġscholar ship", + "F ran", + "Ġre used", + "ĠEqu ivalence", + "ĠGL UT", + "grad ing", + "sal t", + "Ġcommens al", + "Ġfra ud", + "ox ib", + "Ġgastro enter", + "Ġrain y", + "Ġasser ts", + "Oper ation", + "Ġflatten ing", + "P ut", + "X B", + "Ġp M", + "Ġcon ic", + "ob tain", + "ĠRo ber", + "N ovember", + "ĠJ P", + "Ġfe brile", + "ĠBar riers", + "================================ ================================", + "Ġhemic ell", + "ĠS CS", + "ĠN em", + "Ġr aster", + "cl ude", + "Ġïģ ¦", + "ĠElli ott", + "b order", + "Ġd ÏĨ", + "rib ose", + "ĠEn v", + "ĠDiff use", + "ĠSupers ymmetry", + "Pear son", + "F ETs", + "y ah", + "ul ia", + "ĠD warf", + "ĠH ull", + "ĠAtt ribution", + "Ġrepos itories", + "ĠGN SS", + "ĠV ectors", + "Ġsuccess es", + "ĠMan hattan", + "umb ent", + "dig it", + "Ġcircumf erential", + "B etween", + "D eg", + "o ue", + "Ð ¹", + "ĠD ere", + "ĠR f", + "Ġr ide", + "ĠV oc", + "Ġprot est", + "Ġpur pos", + "ĠProof s", + "names e", + "Ġbank ing", + "ĠGastro intestinal", + "ĠU nt", + "Ġwhen ce", + "ĠY ue", + "ĠRe habilitation", + "Ġexchang ing", + "ĠACT H", + "Ġc apping", + "am ido", + "ĠB ap", + "Ġpl at", + "to String", + "Ġelectro encephal", + "Ġelectrosp un", + "M pc", + "j ord", + "on v", + "Ġc raft", + "ĠC Cl", + "ĠSt rip", + "Ġmed itation", + "oxid ative", + "ĠRed uce", + "ĠCommon wealth", + "Ġrif amp", + "F lu", + "Ġre analysis", + "ot rich", + "ĠE SA", + "Ġj th", + "hel in", + "ĠGen otype", + "Ġdiagonal ization", + "ĠGab riel", + "Ġquarant ine", + "ĠC rab", + "ĠD ict", + "acc umulation", + "be k", + "ĠDiff erentially", + "Ġlac tis", + "tetrahydro furan", + "l aser", + "ĠU m", + "Ġme ga", + "rm e", + "ĠInd ians", + "ĠLeon ard", + "Ġcommod ity", + "Ġfumig atus", + "i ou", + "ĠE chin", + "ost ream", + "Ġmemb ran", + "sim ulations", + "back end", + "ĠOB JECT", + "g iving", + "Å Ļ", + "Ġinf ective", + "Al g", + "ĠHu h", + "ĠMI CR", + "Ġfollow ers", + "fer ro", + "Ġcyan ide", + "P resent", + "Ġ END", + "ĠM Cs", + "Ġtim eline", + "ĠEmbry onic", + "Identif ier", + "Ġincon clusive", + "ĠGamm aproteobacteria", + "n ets", + "ĠHe ating", + "ank ar", + "th r", + "ĠK IT", + "ĠCh ip", + "Ġbl ob", + "Ġcalc ulator", + "Ġtext ural", + "Ġalloy ing", + "Ap plication", + "ĠProte omic", + "Ġantidepress ants", + "ur k", + "Ġcrystall ography", + "Ġcred its", + "Ġmuss els", + "T om", + "ĠF ST", + "ĠF old", + "ĠH ew", + "An n", + "bro ok", + "Ġglycol ytic", + "Tor ch", + "Ġv m", + "ĠM are", + "ĠJ y", + "Ġhetero junction", + "ĠBorrel ia", + "R isk", + "ĠN aturally", + "Ġsupp lying", + "sign ature", + "l k", + "Ġa rachid", + "ol ov", + "ĠS ok", + "ĠH ö", + "ĠR az", + "ĠV ander", + "Ġdel ib", + "Ġmy th", + "Ġmid brain", + "Ġdece ased", + "ĠS CO", + "ĠTh romb", + "Ġcur r", + "Ġsum mit", + "mi RNAs", + "dimethyl amino", + "Ġphotoc atalyst", + "verb ose", + "gom ery", + "Ġw ed", + "ĠM ate", + "Ġsign i", + "rastruct ures", + "Ġrecipro city", + "b ner", + "m ast", + "n eck", + "Ġco ins", + "ĠHist ogram", + "cr it", + "Bbb k", + "A W", + "t own", + "dis placement", + "ĠNe ph", + "separ able", + "Ġdiast ere", + "ĠMODEL S", + "Dep th", + "ĠNeisser ia", + "p dev", + "u vial", + "ĠB MS", + "ĠD ennis", + "Ġr p", + "Ġnan ometer", + "roc yt", + "ĠRoman ian", + "Ġconce ivable", + "C OS", + "al veolar", + "as tig", + "ab we", + "enc ode", + "rol actone", + "Ġread mission", + "inters ection", + "Ġamplic ons", + "tim ulated", + "Ġcoll apses", + "ochrom atin", + "H aw", + "ect rum", + "ft ype", + "ric a", + "Ġam id", + "MP O", + "ĠExt ensions", + "Ġvar ic", + "Ġdimin ishes", + "Ġcathe ters", + "N odes", + "Ġb box", + "em ination", + "Ġts unami", + "diagn osis", + "c od", + "q r", + "ĠF en", + "Ġworth y", + "Ġâĩ IJ", + "inform atic", + "ograp her", + "Ġundet ected", + "ĠN CAA", + "Ġcarcin ogenic", + "R U", + "Ġan eu", + "plit udes", + "ke eper", + "ĠÄ ģ", + "Ġau tistic", + "Ġcomprom ising", + "Ġunim odal", + "Ġr umin", + "ap a", + "Ġint olerance", + "Ġdirec ting", + "Ġpe a", + "Ġcomm enced", + "Ġshadow ing", + "C enter", + "Ġcl ad", + "Ġbl ues", + "bin its", + "Ġmis classification", + "ĠFA ST", + "W at", + "Ġm Cherry", + "Ġb rig", + "est radiol", + "Ġwave functions", + "Ġblo oms", + "Ġacc ent", + "aj i", + "occ urring", + "ar rest", + "Ġspecial ty", + "Ġuncon ditional", + "Ġspong es", + "Ġdys functional", + "ĠNO X", + "Ġultrac old", + "Ġmartens ite", + "O US", + "n ier", + "is ic", + "ĠMat sum", + "Ġleuk emic", + "ĠBrad ley", + "D ensity", + "ĠS emiconductor", + "ĠC ause", + "ĠIn set", + "ĠK em", + "ĠU PR", + "par a", + "ech st", + "ym et", + "Ġag ro", + "ĠY Y", + "ĠReg eneration", + "Ġancest ors", + "ĠTiss ues", + "Ġsulfur ic", + "k d", + "Ġl asing", + "ĠP up", + "ae i", + "Ġmamm al", + "ĠBrad ford", + "Ġsegreg ated", + "is olated", + "ĠC uba", + "Ġblock age", + "Ġseam less", + "Ġperoxis ome", + "h ui", + "Ġin aug", + "Ġinf ecting", + "ĠCh ampion", + "ĠAt titudes", + "calc ulate", + "Ġt ighter", + "ĠS AC", + "ĠE pi", + "Ġat m", + "Ġphys ico", + "Ġn th", + "ĠC anyon", + "Ġser oprevalence", + "Ġhom o", + "ĠUnivers it", + "Eval uation", + "ĠAPO E", + "j ob", + "Ġm K", + "Ġre ign", + "ab o", + "ĠR ugby", + "ĠN ets", + "Ġr ituximab", + "ati veness", + "Ġph y", + "orn is", + "Ġfeedback s", + "Un ited", + "Pr inc", + "imb abwe", + "ĠGir ls", + "Ġunavoid able", + "ĠSeman tics", + "B reak", + "F ISH", + "M ix", + "Ġn x", + "ĠBa o", + "dimethyl phenyl", + "ĠT OF", + "ĠC rown", + "ĠG GA", + "ĠJ H", + "Ġsuper string", + "ĠCR Y", + "Ġkind ly", + "Y N", + "Ġund oped", + "ex cluding", + "ĠLe o", + "ĠPROP ERT", + "peritone ally", + "m ant", + "ê °", + "Ġf ranch", + "ĠPro st", + "DE s", + "Ġcot rans", + "Ġr k", + "Ġgeneral izability", + "Aut hor", + "ĠAnd rea", + "ĠConf ocal", + "ĠAdi pose", + "î Ĺ", + "er jee", + "Ġan imated", + "ĠF ad", + "ĠCor rosion", + "ĠCirc adian", + "Ġacceler ators", + "ĠArk ansas", + "Ġm ars", + "ĠC uc", + "ĠInter faces", + "Ġretrie vals", + "Ġmelan in", + "Ġss DNA", + "vast ava", + "Ġallerg ens", + "b ud", + "Ġin accessible", + "ic tions", + "ĠM ood", + "ind a", + "Ġam eric", + "Ġsym biosis", + "bers ome", + "occ ur", + "ĠMarc us", + "ĠSuperconduc tivity", + "ĠC ort", + "ĠH MS", + "Ġph ased", + "ĠJ ess", + "Ġprop ulsion", + "ext ract", + "Ġsuccin ate", + "ĠÖ Ĵ", + "ink el", + "Ġsil ence", + "ĠSU V", + "Ġconstitu ency", + "Ġbacteri ophage", + "g em", + "ĠM CL", + "ore ne", + "ĠG oss", + "IC D", + "Ġglut amic", + "Ġcoex isting", + "STE MS", + "opot ential", + "ĠE y", + "ĠL ecture", + "ell ae", + "Ġimmun oprec", + "Ġtim ber", + "ĠVul ner", + "Ġa roma", + "Ġs ands", + "ĠSp an", + "Ġher n", + "Ġincub ating", + "Ġtransmit ters", + "ĠHom ogeneous", + "ĠConstruct ing", + "d it", + "Ġt c", + "al ass", + "Ġst ents", + "ĠM ID", + "Ġan oxic", + "Ġprov isions", + "ĠCap ac", + "neut ron", + "ĠVO Cs", + "Jan uary", + "V AS", + "on ce", + "ĠC ache", + "op ulation", + "ĠV TE", + "Ġinter phase", + "Ġbl og", + "ocus ing", + "hi ro", + "ĠRE C", + "Ġanis otropies", + "ben ef", + "Ġcons tipation", + "ĠCan al", + "Ġport rait", + "sil yl", + "ĠLink ed", + "ĠBow l", + "Ġmonop oles", + "ĠPere z", + "W IN", + "ĠT AP", + "Ġr uthenium", + "ĠAd herence", + "ĠEn zymatic", + "Ġspecific ities", + "Ġsk i", + "ĠC ST", + "Ġpo etry", + "AT ES", + "ram a", + "lo res", + "AL U", + "Ġvas oconstr", + "Ġgranul ocyte", + "ib i", + "Ġop ts", + "aves drop", + "ept in", + "· ·", + "ĠJe ong", + "Ġmedull ary", + "ĠDemonstr ation", + "ĠF IB", + "ĠB RD", + "ĠV V", + "Ġall o", + "R ule", + "T f", + "Ġun realistic", + "Ġlat itudinal", + "RO P", + "ĠCorrel ates", + "I U", + "ĠP ore", + "oc rit", + "ĠK all", + "Ġchar coal", + "ĠMong olia", + "âĪ ħ", + "ĠEn tity", + "Ġgram s", + "g raphene", + "m ine", + "ent ric", + "ĠP p", + "ĠW elfare", + "ĠJ ets", + "Ġaff irm", + "ĠBel le", + "ĠStrateg ic", + "API ENTR", + "K H", + "rm ann", + "Ġassoci ating", + "ĠSur viv", + "Ġnicot inic", + "ĠWL AN", + "Ð ¿", + "Ġt ears", + "ĠRe vised", + "Ġphosph odies", + "Ġhors eradish", + "ĠL AR", + "to ok", + "ĠDes cent", + "ĠNO x", + "ĠStein er", + "ĠPerm ian", + "ĠVenez uela", + "Ġdesic cation", + "D IS", + "ĠM SP", + "Ġpo pl", + "rel s", + "Ġï£ ½", + "Ġlear nt", + "ĠBi ofilm", + "ĠPC NA", + "ĠAtt ribute", + "ĠGro thendieck", + "ĠAdoles cent", + "n v", + "st derr", + "obal t", + "ĠYam amoto", + "Ġaliqu ot", + "r ater", + "ĠO re", + "ĠK IR", + "ack er", + "Ġïĥ »", + "Ġstrat osphere", + "ĠC ust", + "resp ect", + "Ġglut amatergic", + "Ġencour ages", + "c tic", + "it ched", + "ph ins", + "Ġsub urb", + "Ġhome omorphic", + "hex ah", + "Ġmini atur", + "C AN", + "a head", + "ĠB LE", + "ĠR BF", + "Ġac utely", + "Ġï£ ¾", + "Ġanten n", + "UR N", + "ĠGir l", + "Ġbiore actor", + "ĠLeib niz", + "Ġv ial", + "ĠL ich", + "ob ac", + "ĠWhen ever", + "inhib ition", + "C ast", + "Ġstrip ped", + "ĠAst rophysics", + "pres ence", + "ĠFlo er", + "ipot ent", + "dichlor o", + "C LE", + "f inger", + "on ates", + "st ri", + "ĠS perm", + "ĠD BS", + "op eptide", + "se paration", + "ath ing", + "math p", + "ou ples", + "Ġent ropic", + "Ġsw ollen", + "Ġdon ated", + "Ġsettle ments", + "oven ous", + "P erm", + "ĠS ard", + "eg en", + "ĠAl ph", + "ĠCo operation", + "ĠPD AC", + "F inal", + "l apse", + "Ġre vol", + "ĠI x", + "ĠL ens", + "Ġk th", + "rel axation", + "Cl O", + "ichlor o", + "Ġwrap per", + "ĠSimultaneous ly", + "Comput e", + "ë Ĭ", + "im plantation", + "ĠV LA", + "hem e", + "ĠMay or", + "ĠFac ilit", + "Ġb att", + "im mer", + "Ġcur ated", + "Ġconf luent", + "gener ational", + "star ts", + "Ġgranul osa", + "arboxyl ate", + "ĠRies z", + "Ġtext book", + "Ġconstit utional", + "ĠPe ace", + "ĠComm ander", + "Ġobsc ured", + "v il", + "ad dition", + "ĠW asserstein", + "co ords", + "ĠProb es", + "Ġdeline ated", + "TZ VP", + "ĠIN F", + "Ġdos ages", + "Ġolig omerization", + "ĠNAD P", + "MK II", + "om in", + "Ġl hs", + "ug hen", + "ĠJ ong", + "anc el", + "let ter", + "ĠAN C", + "F UNCTION", + "Ġt ram", + "The ir", + "ĠGen erated", + "Ġpoly cyclic", + "Ġcul min", + "Ġrect um", + "Ġce ft", + "Ġmetam aterials", + "ĠBiot ech", + "Ġmys elf", + "Ġun ifying", + "Ġem an", + "ĠSing er", + "triang leright", + "om el", + "ĠC FA", + "oc ha", + "ĠG SM", + "Ġcent rifuge", + "ĠInd o", + "Ġtransport ing", + "LI B", + "Ġoxal ate", + "ĠDul becco", + "Ġal i", + "arg inal", + "ho o", + "isc hem", + "APIENTR YP", + "A part", + "L DA", + "ens ile", + "set tings", + "Ġep hem", + "amp a", + "Ġdu plications", + "ĠWhe eler", + "Phys ical", + "ĠCom pletion", + "ĠOr dered", + "Log ger", + "Ġinterf erences", + "ĠPoll ution", + "Optim al", + "S v", + "a icin", + "Ġp icks", + "di versity", + "tig ens", + "Ġdim orphism", + "fe res", + "ĠRob otic", + "Ġconfirm atory", + "Ġcath odic", + "Ġspir als", + "Ġspr uce", + "Lag range", + "w at", + "ĠAll an", + "den ote", + "C ID", + "al ways", + "it he", + "ĠCh im", + "con ditional", + "bar rier", + "Ġvisual izing", + "Ġïĥ ¹", + "Sch midt", + "Ġconvention ally", + "ĠQU ANT", + "GRO UND", + "Ġ ug", + "ĠC WE", + "ĠIn spired", + "Ġbu yer", + "Ġtherm ost", + "Ġkin ematical", + "an olic", + "Ġd if", + "Ġï£ ¼", + "ĠGe o", + "Ex amples", + "cons istency", + "ĠPal ace", + "ĠVacc ination", + "Ġnatri uretic", + "Y AG", + "ĠCT Cs", + "Un ivers", + "ĠAcknowledg ement", + "memb ered", + "v v", + "ĠS ession", + "Ġinst ar", + "ĠLe vin", + "AV I", + "Ġprolifer ator", + "olith s", + "ĠTemper atures", + "im ming", + "ĠTo eplitz", + "IC ATIONS", + "ĠIntegr als", + "Ġsplic ed", + "D est", + "res ulting", + "ĠH ope", + "Ġen closure", + "ie ves", + "fl av", + "ĠAbd ul", + "Ġleishman iasis", + "à ²", + "os keleton", + "Ġad duct", + "ĠInflu ences", + "E QU", + "ĠS itu", + "Ġse as", + "ĠRe ich", + "cy st", + "ĠEV OLUTION", + "Ġwith stand", + "ĠG inzburg", + "RNA i", + "ĠNon parametric", + "ĠPr incess", + "Ġintra vascular", + "UT IONS", + "Ġglut ar", + "Ġcoinc ided", + "ĠSa ito", + "pret rained", + "comb ined", + "ĠT AM", + "Ġalarm s", + "Ġcyclo oxygenase", + "Ġb n", + "Ġpl agi", + "Par ticle", + "GG G", + "e tics", + "am ber", + "AB STRACT", + "ĠExt racts", + "ĉĉĉ ĠĠĠĠ", + "ĠPhyl ogeny", + "t ow", + "ĠCon taining", + "Ġend onuclease", + "inc ubation", + "Ġoffic inal", + "Ġexplos ions", + "lay out", + "Ġtouch down", + "ĠReve aled", + "Ġinfiltr ate", + "en ith", + "tim ulation", + "ĠK ind", + "erv ices", + "PD A", + "Ġcere us", + "En v", + "Ġlap a", + "k amp", + "m ult", + "ent hal", + "ĠGold stone", + "si RNA", + "stre pt", + "Q ual", + "m other", + "di o", + "Ġinf requent", + "Ġcycl ospor", + "hep atitis", + "thromb otic", + "G ST", + "ĠL j", + "ĠU R", + "of ect", + "ĠAr row", + "eth nic", + "ĠBarc elona", + "C are", + "ti tious", + "Ġe ta", + "Ġvir ions", + "sm ash", + "ĠâIJ ¤", + "Ġa venues", + "ob arb", + "ĠCom ments", + "Ġany way", + "af il", + "ĠBe a", + "ĠBo ys", + "ĠAutom ata", + "ĠSuperconduc ting", + "P ic", + "k Hz", + "Ġn orepinephrine", + "ĠG PC", + "Ġunder lined", + "bra him", + "Ġelectrosp ray", + "Ġses qu", + "ĠTourn ament", + "A ustr", + "ĠG rowing", + "ĠWe bsite", + "LD H", + "cov ariance", + "sever al", + "st abilized", + "Ġdec arboxylase", + "Ġrem ed", + "rho e", + "ĠSR S", + "ĠTre ated", + "ĠMad agascar", + "ĠMag ic", + "Ġweap on", + "ĠYosh ida", + "Ġhypogly cemia", + "ĠBifid obacterium", + "enti tious", + ":: :", + "ĠSing les", + "Ġnic ely", + "Ġunexpected ly", + "ib les", + "ari ae", + "Ġcent roids", + "Ġbroad ened", + "ĠJoh ns", + "ĠBacter oid", + "Ġfram ing", + "Prim ary", + "ĠPict ure", + "gover nment", + "Ġre q", + "ĠT ry", + "ib o", + "Ġliqu ef", + "osens itivity", + "Ġsla ughter", + "ĠD AR", + "Ġlog it", + "Ġprom ises", + "Ġlaw yer", + "ĠFP G", + "T CP", + "Ġinter calation", + "ĠBo e", + "Ġwide band", + "Ġjudg ement", + "romagn ets", + "Last ly", + "ĠIschem ic", + "I MA", + "f ood", + "m uch", + "Ġa venue", + "Ġschist osomiasis", + "ĠExec ution", + "D QU", + "G IS", + "k ines", + "ak age", + "ech t", + "ĠSc aff", + "ĠStr ings", + "ĠMulti level", + "Ġcum bersome", + "ĠRay mond", + "Ġirregular ities", + "ĠAGN s", + "ĠMetast atic", + "ĠIber ian", + "M b", + "R NP", + "h ong", + "is inin", + "Ġth irteen", + "ĠF AS", + "Ġse aling", + "Ġap atite", + "Ġser ially", + "ĠÅ Ŀ", + "D EL", + "F o", + "ĠS oph", + "ĠB ear", + "ĠJ osh", + "rec k", + "ull er", + "Ġexc ursion", + "Ġemb odied", + "Ġhybrid ized", + "ĠLie utenant", + "Per iod", + "Ġmoll us", + "C VD", + "R en", + "RE AM", + "ĠB ACK", + "Ġacc reting", + "Ġcult uring", + "ĠBur st", + "ĠSeg ment", + "Ġaster isk", + "ĠIde al", + "Ġinter tw", + "ĠAt oms", + "ĠST E", + "Ġïģ ª", + "Ġremark ed", + "Ġhair s", + "â ľ", + "ĠMet ropolis", + "ĠPar tially", + "ĠObs erver", + "Ġhemat ologic", + "obil ization", + "ĠBerg man", + "Ġcart esian", + "Ġclath rin", + "ĠS ung", + "Ġr ation", + "Ġsc oliosis", + "oh l", + "mut ant", + "NN s", + "ĠRah man", + "ĠSpati ally", + "P IP", + "Y b", + "Ġd iaz", + "ver tebral", + "ad zu", + "als ki", + "ans wer", + "Ġge ochemistry", + "Ġstem ming", + "w es", + "ox ys", + "Ġmat s", + "ev a", + "ĠHyper bolic", + "arb age", + "Ġclip ping", + "ĠSug ar", + "ĠC ognition", + "ĠD IV", + "Ġtem pt", + "ĠPath ogen", + "ĠPed ro", + "Ġw ak", + "ent ries", + "ĠG CM", + "pro jective", + "Ġprof iciency", + "ĠKn own", + "Ġlex icon", + "ĠMend elian", + "Ġzoon otic", + "le ans", + "ĠT alk", + "Ġk urtosis", + "NA S", + "ĠNow adays", + "ĠL il", + "ĠW MAP", + "Ġdis perse", + "Ġcoll oids", + "eb ra", + "OM ET", + "ĠD CT", + "ĠR ise", + "Ġinter genic", + "GT H", + "Ġtap ered", + "Mark ovian", + "Prot ocol", + "ĠVeget ation", + "r ats", + "Ġd ivalent", + "ĠCr ust", + "zy g", + "Ġpig mentation", + "grad uate", + "ĠRic c", + "Ġcounterex ample", + "Ġs ativ", + "Ġl s", + "ĠCirc ulation", + "is otropic", + "ĠEN SO", + "Ġtrop onin", + "Ġdissol ving", + "Ġcosme tic", + "H f", + "f urther", + "Ġp anc", + "Ġh ops", + "int ra", + "ĠZ he", + "ĠRel iable", + "ivol umab", + "M X", + "R ab", + "ĠP ES", + "ĠB ü", + "Ġad hered", + "Ġflu ency", + "ĠCl aus", + "Ġdel amination", + "Ġgu anine", + "ĠMulti scale", + "ĠEqu ip", + "ĠIll ustr", + "Ġtetra hydro", + "f el", + "l ists", + "Î ŀ", + "em ulsion", + "ĠN Z", + "Ġwas n", + "ai ra", + "Ġarg uing", + "mi RNA", + "ĠExp ressed", + "Ġspectrophot ometric", + "Ġile um", + "Ġflam es", + "F it", + "G on", + "ĠC ulex", + "Ġun weighted", + "Ġnan ob", + "SH V", + "Ġalign ing", + "Ġshut tle", + "Ġchloro quine", + "Ġpyr ite", + "ĠR ica", + "Ġr ift", + "Ġcathe psin", + "ĠPROC ESS", + "P f", + "R aw", + "ray fish", + "SA L", + "coll apse", + "........ ........", + "at ases", + "Ġwork shops", + "oph ile", + "ĠâĬ ĥ", + "Ġbifurc ations", + "T race", + "Ġp ause", + "Ġorbit ing", + "oli ubov", + "ĠCur tis", + "ĠRevis iting", + "ore t", + "Ġinf used", + "lu ents", + "Ġplas tid", + "Ġïģ ¹", + "Ġexec utions", + "ĠGra ves", + "loc ally", + "ĠAtmosp here", + "diab etes", + "ĠPrad esh", + "ĠCof actor", + "is omorphic", + "Ġb od", + "ĠC BD", + "Ġinc ap", + "Ġret rovirus", + "Ġlip ophilic", + "Ġlin oleic", + "Ġtrav elled", + "c ovalent", + "p ick", + "u pl", + "ĠP ole", + "ĠTh ym", + "ĠTe ich", + "Ġcollabor ators", + "Ġinstant ons", + "ĠMEG A", + "ĠHepat ocellular", + "Ġinfest ation", + "ĠPie zo", + "ĠL ub", + "ĠN Cs", + "Ġnucle oside", + "Ġoste ogenesis", + "E igen", + "R MSE", + "Ġl ax", + "ĠK ost", + "ĠV ero", + "ĠCh ou", + "elect rochemical", + "Ġcompe ti", + "ch ia", + "Ġsub module", + "ĠAl low", + "Ġresol vent", + "Ġswe eps", + "Ġsupercon formal", + "pyrrol idine", + "l ofen", + "å Ń", + "Ġdes erves", + "ĠZ imbabwe", + "az ines", + "ĠCons ult", + "Ġcast le", + "Ġpharmaceutical s", + "Ġparac rine", + "Ġjejun i", + "Ġargu ably", + "Ġe NOS", + "Ġher ds", + "Ġvehic ular", + "Ġtriang ulated", + "Ġî µ", + "ĠGrand e", + "Ġanthocyan ins", + "ĠD uan", + "ĠV ibration", + "Ġtri ad", + "Ġhouse keeping", + "B or", + "Ġp ub", + "Ġmal formation", + "gluc osamine", + "inhib itory", + "Dir ac", + "ĠC SD", + "ĠRot ating", + "ĠHTL V", + "Ġdem ol", + "inf iltr", + "Ġhem olytic", + "Ġcarb apenem", + "Ġlum inescent", + "ĠPlan ets", + "Ġmell ifera", + "Ġcortic osterone", + "ĠAdd ress", + "Ġhub s", + "ometh acin", + "å IJ", + "ĠCh ampions", + "ĠRe vision", + "ĠHer bert", + "Ġambig uities", + "K ERN", + "Ġd é", + "Ġl p", + "Ġen vis", + "ĠCh ol", + "rop in", + "Ġdr one", + "m eyer", + "Ġis otype", + "ĠV u", + "ER C", + "Ġvers atility", + "Sp eed", + "Ġae tiology", + "Ġgonad otropin", + "Ġcogn ate", + "ĠCot ton", + "reason able", + "dis able", + "Ġdevast ating", + "P ier", + "P OL", + "ĠB é", + "inc ter", + "alu able", + "Ġpoly hedron", + "ĠRel ay", + "Ġworkflow s", + "F EM", + "in p", + "Ġm ph", + "soft max", + "m ur", + "v r", + "Ġe rent", + "ĠK N", + "Ġstat in", + "Ġflat ness", + "ĠArchitect ures", + "ĠVeter inary", + "Ġnos ocomial", + "S k", + "X ML", + "ĠF os", + "ĠL or", + "Ġradi ography", + "ĠBl um", + "ĠDiscrim ination", + "Ġp unc", + "Ġex its", + "ĠB ilateral", + "ms strahlung", + "Ġcolon ized", + "ĠFib rosis", + "Ġchaper ones", + "abor atory", + "ĠPers istence", + "Ġlum ped", + "Ġrab ies", + "ĠBurn s", + "D ense", + "on tium", + "acet ylation", + "ĠF ET", + "Ġhand ful", + "bi ology", + "Ġundes ired", + "L imit", + "ĠN BA", + "ĠSe oul", + "AP T", + "ĠTrans genic", + "oxygen ation", + "But ton", + "ĠTreat ments", + "Z V", + "is omorphism", + "oc ta", + "iff e", + "ode oxy", + "Ġorgan elle", + "Ġcoll oid", + "Ġcer amide", + "Ġtq dm", + "G PS", + "ĠI SR", + "oc linic", + "ĠL yme", + "Ġep ig", + "ĠTra il", + "I PS", + "Ġs orts", + "ĠZ ebrafish", + "Ġhydrox ylase", + "Sm irnov", + "B ax", + "ĠD ance", + "ĠH ors", + "Ġreach ability", + "Par allel", + "ĠES BL", + "Ġupl ink", + "Ġpostp randial", + "s olar", + "it abine", + "ord ism", + "Ne asy", + "Ġaband on", + "I MI", + "f ake", + "st atistical", + "ĠC ars", + "ib ia", + "Ġà ĩ", + "sp c", + "MD P", + "tiz ations", + "Intern ational", + "ular is", + "Ġvacu oles", + "K C", + "ĠA PT", + "ĠB t", + "ĠB om", + "ĠG MP", + "Ġpione er", + "ĠChair man", + "ĠT ucker", + "ĠR AF", + "ĠN ASH", + "ĠW IT", + "yn yl", + "Ġsup plier", + "ans ky", + "Ġdecom posing", + "ĠUV B", + "ophen ol", + "Ġb arium", + "ĠS MT", + "ot ocin", + "ly tic", + "ran king", + "ĠDi rections", + "Ġinn ervation", + "sw itching", + "d ac", + "Ġh T", + "Ġdoc tr", + "ĠIncre mental", + "ĠEarthqu ake", + "H as", + "L ee", + "m ates", + "pro line", + "ĠRE E", + "Ġviol ates", + "ð x", + "Ġhomogen ates", + "Bo olean", + "Ġd oxycycline", + "ĠMO F", + "iop hen", + "Ġapprec iation", + "fin als", + "character istic", + "ĠContin ental", + "B us", + "E sc", + "X P", + "Û Į", + "ĠCT A", + "Max well", + "Ġarchae a", + "N ik", + "N ONE", + "T W", + "ter ing", + "ĠP erman", + "Ġrest ores", + "opath ogenic", + "ĠMont gomery", + "Ġglucocortic oids", + "Ġ ud", + "ĠN uss", + "ĠN é", + "ĠSt urm", + "Ġatt aching", + "Ġintra peritoneally", + "las ov", + "Ġst ellate", + "Ġanti proliferative", + "Ġmicro organism", + "Ġvis u", + "Ġjud ges", + "random ized", + "allow ed", + "Ġdepri ved", + "develop ment", + "scrib ed", + "ethe rian", + "ĠFras er", + "R am", + "b ib", + "Ġl iner", + "Ġg uns", + "res net", + "ĠL TR", + "ight ing", + "In iti", + "ĠZ imm", + "ĠGe ology", + "Ġantioxid ative", + "Ġmag enta", + "ĠNiger ian", + "galax y", + "ĠMelan oma", + "F ound", + "Ġb um", + "ĠT rop", + "ĠD os", + "Ġmet ab", + "Ġinv oking", + "ĠSch izophrenia", + "CF G", + "Ġgel ation", + "Ġopi oids", + "p is", + "Ġch urches", + "Ġcan onically", + "Ġj ug", + "Ġaccept ors", + "DM EM", + "Ġobl iqu", + "ĠMedic are", + "arpo on", + "Z IP", + "ore active", + "Ġim printing", + "ĠV inc", + "Ġ ¿", + "Ġrest art", + "Ġdent ate", + "en zymatic", + "Ġin guinal", + "ĠN t", + "Ġun observed", + "uct uation", + "Ġbi asing", + "Ġintegr ins", + "Ġur l", + "FP GAM", + "ĠCL UST", + "omat ology", + "Ġmetallic ities", + "Ġintention ally", + "FPGAM GR", + "T yp", + "Ġal ly", + "Ġcom ic", + "ĠL ions", + "Ġim puted", + "Ġà Ł", + "lex ia", + "ĠJan us", + "Ġbr ass", + "ĠDown loaded", + "BU FF", + "iden tical", + "Ġpsychiat ry", + "C CT", + "if ar", + "ĠMand el", + "Ġopto electronic", + "Ġis omerization", + "ĠF ant", + "ĠL ion", + "ĠL ov", + "ĠN af", + "est a", + "Ġbi ocompatible", + "Ġsec retions", + "sc i", + "ĠRet ro", + "ois omerase", + "ĠSn ap", + "Ġsplitting s", + "Ġscav enger", + "proced ure", + "Daw ley", + "ë ĭ¤", + "un ate", + "ĠD ye", + "ĠN EC", + "Ġnan ocl", + "Ġplan etes", + "ĠTR PM", + "Ġvo ices", + "ĠHierarch y", + "m v", + "Ġl asts", + "Ġh oped", + "Ġmed ians", + "ĠAnd reev", + "Ġheight ened", + "ä »", + "Ġin definite", + "ĠK amp", + "ang el", + "gr ids", + "arch ae", + "Ġtherap ists", + "ĠMi R", + "Ġnegoti ation", + "H SP", + "ĠC ustom", + "Ġst ria", + "Ġun acceptable", + "ret in", + "pen et", + "ĠOR R", + "ĠLife time", + "ĠPhosph ate", + "Ġtrop ics", + "ĠWel ch", + "ĠP yr", + "Ġam putation", + "ĠAr tin", + "ĠCa O", + "Ġconject ures", + "Ġat rium", + "ĠCom plementary", + "ĠAl uminum", + "Ġmic row", + "ili ated", + "ĠImmun o", + "Ġbin ocular", + "ĠWeak ly", + "Ġimmun ogenic", + "Ġbath ym", + "ĠPhen otype", + "Ġsial ic", + "S ix", + "Ġa kin", + "ro tor", + "hel m", + "CC ESS", + "Ġneuro protection", + "ĠFif th", + "Ġconting ent", + "Ġsket ched", + "I mp", + "Ġc ached", + "ure ment", + "ĠB ic", + "ĠK ah", + "ber ation", + "atter son", + "Ġglyc ation", + "Ġinvest ors", + "Ass isted", + "ial es", + "sc ience", + "Ġpil ots", + "us cripts", + "MI CS", + "Ġorth opedic", + "war fs", + "gre ater", + "ĠArter y", + "V ideo", + "Ġar range", + "av ar", + "charg es", + "dial dehyde", + "ĠT PA", + "Ġsp elling", + "ĠSe iberg", + "Ġnavig ate", + "ĠPow der", + "ĠR ings", + "ĠCh ron", + "ĠAt g", + "Ġhom ocysteine", + "ĠIdentif y", + "Ġo ak", + "Ġl iability", + "Ġoper ands", + "ĠCT D", + "Ġallevi ates", + "m A", + "ĠL anger", + "Ġsub manifolds", + "ĠJ ag", + "Ġradi ance", + "const ants", + "ĠMor occo", + "Eng ine", + "á ¸", + "â Ĥ¬", + "re vers", + "PC I", + "uns queeze", + "ocon version", + "Ġintens ified", + "Ġrefin ements", + "ofect amine", + "ay as", + "Ġinc idental", + "ĠTh ur", + "Ġover d", + "Ġbit ter", + "Ġign ores", + "а н", + "ĠOT U", + "Ġs err", + "ab y", + "ĠG CN", + "ĠCons umer", + "Ġconc ordant", + "ĠMR C", + "ĠEcon omy", + "satisf ying", + "Ġbiotin ylated", + "Numer ical", + "ĠRash ba", + "st ochastic", + "ĠL al", + "Ġbur dens", + "All oc", + "ĠGraph ics", + "ĠLRR K", + "A IC", + "ĠT ed", + "ĠS ark", + "ow l", + "Ġhe most", + "ĠAn at", + "Ġhom ing", + "ĠChar lie", + "ĠBr uc", + "ih ara", + "ing en", + "ĠV ern", + "ĠY ers", + "Ġid s", + "Ġcirc RNAs", + "Ġconduc ive", + "ĠBR ST", + "Ġgall ium", + "Ġdich otomy", + "F r", + "e tition", + "Ġc esarean", + "ol an", + "Ġr n", + "ub stituted", + "ĠLe aves", + "ĠLe ader", + "col oring", + "D raw", + "Ġser ous", + "Er r", + "Ġinn ermost", + "ĠHam burg", + "S tor", + "j es", + "Ġto l", + "id ade", + "Ġr v", + "ĠIn version", + "Ġmulti phase", + "Ġpseud or", + "ĠGood man", + "ĠJS ON", + "Ġcorrid or", + "Ġp ork", + "ĠS ale", + "ĠN atal", + "Ġattack ing", + "ĠShe et", + "Ġstream wise", + "Ġatom istic", + "Ġfirm ly", + "ĠAch ie", + "Ġp ir", + "ĠI KK", + "ĠF alk", + "ile ptic", + "ĠTR PC", + "Ġadhes ions", + "HR P", + "Ġpauc ity", + "S plit", + "U DI", + "ĠS end", + "ĠP ine", + "ĠL on", + "ĠL ost", + "ef er", + "con caten", + "Ġlo yal", + "Ġgly cop", + "ĠObserv ing", + "ĠMoh amed", + "Y R", + "ĠFil ters", + "c as", + "p ages", + "Ġd A", + "Ġare al", + "ad is", + "ĠL HS", + "ĠThere by", + "Ġvisual izations", + "Ġtw istor", + "unit ary", + "Ġarch ives", + "Ġphenol ics", + "h ik", + "s son", + "ĠI K", + "ĠStud ying", + "Ġtw isting", + "ĠHydro dynamic", + "Ġsplit ter", + "Ġurothel ial", + "Ġal ken", + "ĠG PI", + "Ġcor tices", + "Ġcrop ping", + "Pati ent", + "ĠChlam yd", + "in berg", + "ĠA ircraft", + "ce le", + "ect ral", + "Ġconf erences", + "Ġcre atine", + "al ty", + "pro portional", + "Ġlept onic", + "Ġov ulation", + "uer re", + "tez omib", + "d le", + "init eness", + "ĠSpecim ens", + "Ġcom a", + "ine phrine", + "Ġep im", + "ĠPer cent", + "Co O", + "ĠLo ading", + "Ġven ue", + "ĠTN M", + "Ġpac emaker", + "ĠHoff mann", + "T ech", + "n ie", + "ĠOr leans", + "Ġmagnet ron", + "Ġhospit ality", + "ĠNord ic", + "oprol iferative", + "Ġundo ubtedly", + "ĠS rin", + "Ġhum ic", + "ĠIntegr ative", + "ĠCamp us", + "Ġplant arum", + "radi ative", + "Ġiter ator", + "ĠMes ozoic", + "AP s", + "car inic", + "Ġcheck points", + "ĠïĤ £", + "ĠmA bs", + "ĠLiver pool", + "ìĿ ´", + "ĠEcos ystem", + "Ġneovascular ization", + "Ġdem oc", + "lo ops", + "ĠSU RF", + "Ġpassiv ation", + "Ġconsec utively", + "ĠAlfv én", + "ĠS SE", + "Ġout s", + "stim ulation", + "Ġphilos ophical", + "ĠS ask", + "Ġfl akes", + "Ġfinger printing", + "Ġbuff alo", + "ĠWik imedia", + "Ġrecons titution", + "Ġepithel ia", + "on k", + "en y", + "ĠM Q", + "ĠF ork", + "end ance", + "Ġgeneral isation", + "Ġpe oples", + "Ġconn ector", + "ges ia", + "inter ference", + "Ġcolor ation", + "calc ulation", + "ĠAx ial", + "ĠDES IGN", + "Ġrecess ion", + "Ġdissol ve", + "ĠPartition ing", + "Qx MD", + "G ES", + "V o", + "k har", + "ĠE AE", + "Ġco arser", + "Ġpost traumatic", + "Ġsynthesis ed", + "sil ica", + "tetra hydropy", + "ĠPor ter", + "v ark", + "ent anyl", + "Ġcon ve", + "Ġra fts", + "bre cht", + "Ġrectif ier", + "Ġo roph", + "ĠC EP", + "Ġhist ones", + "Ġstand point", + "Ġanc illary", + "ĠHur ricane", + "c ro", + "Ġre b", + "Ġi T", + "Ġge ography", + "olar ization", + "ĠMan aging", + "Ġxyl ose", + "uther land", + "ĠTaq Man", + "K N", + "Ġt m", + "ĠT AS", + "ist le", + "âĢ «", + "Ġmy corrhizal", + "ĠTer restrial", + "haus en", + "observ able", + "Bri en", + "Ġneutrop enia", + "T aken", + "ĠS MI", + "Ġpol ishing", + "Ġphot op", + "Ġthermal ization", + "Ġpseud oscalar", + "ĠDom inic", + "romy algia", + "Ġechocardi ographic", + "Ill umina", + "ĠI PC", + "ĠH uss", + "ess ive", + "up take", + "Ġweek end", + "Ġcorrobor ate", + "ĠTas man", + "her ty", + "Ġper ine", + "Ġtrans ports", + "Ġgl ance", + "ret inal", + "Pro to", + "igen es", + "Ġprohib ited", + "behavi oral", + "ophe rol", + "ë ¡", + "ĠN ecess", + "ob iology", + "ok k", + "Ġtra versal", + "ĠAnd es", + "Res ource", + "oli tic", + "ç a", + "i rie", + "arc tan", + "Ġmorph ogenetic", + "ĠHu i", + "loss es", + "Ġfulf illing", + "Ġhur ricane", + "om bo", + "Ġg s", + "ĠL v", + "ĠN erv", + "ell osis", + "Ġconf ront", + "Ġorth ologous", + "Ġwet tability", + "Ġcyan obacterial", + "Ġcass ava", + "A UT", + "a vi", + "h len", + "ĠS LA", + "Ġcon vol", + "Ġinter metallic", + "ins ide", + "Ġpolar izability", + "Ġens uing", + "Ġchlor oplasts", + "l id", + "l ips", + "Ġre bound", + "ĠC ary", + "ĠL ambda", + "ĠV iv", + "Ġcalc ination", + "ĠÌ Ĩ", + "Ġcounter factual", + "ĠSil ica", + "Ref eree", + "Ġhomolog ues", + "ĠSpati otemporal", + "ĠArr henius", + "Ġinf lamed", + "ĠZ ambia", + "Ġanti psychotic", + "hel per", + "Bl ood", + "Ġpurch asing", + "ĠSchw inger", + "ĠWilk inson", + "Ġfain ter", + "Ġr ash", + "ĠJ ang", + "ĠCon ductivity", + "rop oda", + "ĠSe q", + "Ġprop olis", + "Ġtub ule", + "ĠLie b", + "optim ization", + "m ounted", + "em es", + "can ic", + "oradi otherapy", + "ĠJen kins", + "N c", + "T ogether", + "Ġf ove", + "Ġm v", + "ĠDef ect", + "ä t", + "ĠFin ance", + "umar in", + "mitt ance", + "ere l", + "ĠF ren", + "ĠR hyth", + "ram ified", + "Ġhyper cholesterolem", + "Ġstim ulatory", + "ĠRich mond", + "Ġadvance ments", + "b les", + "x u", + "all ation", + "Ġint ral", + "iter pene", + "Con cerning", + "Ġbul ky", + "Ġá ¾±", + "comput ation", + "ĠAgar wal", + "C entral", + "X PS", + "Ġt alks", + "ĠT ap", + "im ilar", + "ĠN CI", + "Ġacc used", + "Ġtranscript omes", + "Ġprovision ing", + "ĠEt OH", + "g m", + "Ġt id", + "ĠP OC", + "ff man", + "ĠIn er", + "ĠU B", + "inc ubated", + "ĠAt rial", + "Ġfour teen", + "ĠAstr onomical", + "ĠMig uel", + "ĠK ov", + "Ġsc ipy", + "Ġtherm oplastic", + "ĠMan uel", + "ĠProm otion", + "ĠAccess ed", + "Ġterr itorial", + "in as", + "ĠM Ps", + "mon itoring", + "ĠSim ulating", + "Ġpan or", + "Ġrhe umatic", + "select in", + "ĠLap aroscopic", + "H LA", + "ĠY ale", + "sp read", + "ET S", + "Ġglyc ans", + "Ġimmig rant", + "D onald", + "ĠC ASE", + "ĠH II", + "gl omer", + "Ġïĥ İ", + "ĠExper iences", + "ĠViet namese", + "Hod gkin", + "o ader", + "he art", + "Ġrem edy", + "Ġfacilit ators", + "open hagen", + "d odec", + "ĠF riend", + "ĠTo uch", + "arm s", + "CR s", + "Ġultra high", + "ĠDri ver", + "GEM ENTS", + "ĠO u", + "Ġend ocarditis", + "Ġauto encoder", + "Ġ ich", + "Ġf etch", + "ur ian", + "ĠOR Fs", + "Ġperme abilized", + "ĠWi Fi", + "ĠLith uan", + "Struct ure", + "L n", + "h ouses", + "Ġo ught", + "ĠConcl uding", + "Ġann iversary", + "ĠCre ation", + "Ġblind ness", + "Ġpc DNA", + "ĠSus an", + "ĠBenjamin i", + "ĠSent ence", + "Ġs nd", + "Ġf ins", + "ph is", + "ĠMod ules", + "Ġneuro psychiatric", + "ĠPot assium", + "Ġsacrific e", + "Ġdysp nea", + "Ġdeliber ately", + "omeg aly", + "M edia", + "T emporal", + "Ġsh ark", + "SC AN", + "split ting", + "Ġmis use", + "Ġbirefring ence", + "ĠÖĴ âĨĴ", + "Ġp ier", + "Ġn urs", + "ĠS clerosis", + "ad hy", + "Ġund etermined", + "Ġcomple mentation", + "ĠAff ect", + "ĠHam ps", + "Ġg ob", + "ĠF ate", + "ĠH AL", + "ĠK iss", + "Ġmicro be", + "Ġcarbon aceous", + "Ġlip osome", + "ĠUs age", + "Ġquasipar ticles", + "Ġc asp", + "ĠN arrow", + "Ġout look", + "ĠCh ord", + "Ġclaim ing", + "Ġdiver ging", + "ĠBio informatics", + "ĠPsy chiatric", + "ĠMas ters", + "Ġll vm", + "ĠI QR", + "ph ases", + "ĠTh y", + "erg er", + "ĠDi pl", + "SF R", + "Ġcred ited", + "ĠTet ra", + "âĭ ¯", + "Ġamn iotic", + "ĠCharlot te", + "C ox", + "H ard", + "ar ticle", + "ĠD EA", + "ĠE clipse", + "ĠL MP", + "Ġim prison", + "ĠV arying", + "ES Cs", + "ĠTHE O", + "Ġnerv osa", + "Ġpreced es", + "Ġgy ro", + "ĠWOR DS", + "ĠDak ota", + "ut ory", + "ĠE mer", + "ad am", + "ĠN ah", + "ĠVir go", + "Set ting", + "P Q", + "å ®", + "er us", + "Ġc ep", + "Ġb d", + "di er", + "Ġim balanced", + "Ġtimes tep", + "ä n", + "ĠRab bit", + "Ġham sters", + "Ġmedull a", + "ĠChromat ography", + "IN PUT", + "Ġloss y", + "P seud", + "ĠP BL", + "ĠD omestic", + "ia u", + "anc ell", + "Ġmulti layers", + "Ġsubs idi", + "ĠUtil izing", + "t une", + "re hend", + "ar te", + "Ġb urs", + "ĠN HE", + "Ġclos eness", + "ĠCol our", + "ĠHom o", + "Equ ations", + "Ġsut ures", + "ac us", + "Ġknock ed", + "Ġsecret ary", + "Ġascer tained", + "Ġin patients", + "ir ts", + "Ġpl ut", + "ans son", + "ram i", + "Ġoste otomy", + "ĠPrim ers", + "ĠLeg islative", + "ĠCardi ology", + "Ġadmit ting", + "Ġexcav ation", + "ĠHedge hog", + "W G", + "f rozen", + "Ġl iber", + "ĠI CE", + "ch osen", + "ĠK ohn", + "St op", + "Ph il", + "phag ia", + "ĠB CA", + "Ġem pt", + "Ġz z", + "oper s", + "ĠSi xty", + "eck man", + "Ġtransf errin", + "Ġpenal ized", + "Be ing", + "Ġextr uded", + "Ġmini ature", + "Ġeditor ial", + "Ġinterconn ect", + "g ro", + "k v", + "ol en", + "ĠSY STEMS", + "ĠColon el", + "ĠMedi ated", + "ĠE MD", + "Ġkn ife", + "Ġcyt ogenetic", + "Ġdig itized", + "abin oids", + "arter ial", + "Ġdiar rhoea", + "b ag", + "Ġb uccal", + "st ay", + "ĠL AMP", + "ok o", + "ĠPol yt", + "mask ed", + "ĠTun able", + "Ġco agul", + "par as", + "Ġterm inating", + "IC Ag", + "ĠExcell ence", + "Ġregurg itation", + "DQU FD", + "J ack", + "Ġa pertures", + "ĠI p", + "ĠH CMV", + "ĠG om", + "Ġnucle ophilic", + "Ġparen teral", + "T IM", + "o ine", + "Ġn T", + "ĠS ense", + "ĠF ocal", + "ran ges", + "Ġhe pt", + "ĠPl at", + "Ġmy x", + "Ġcode book", + "Ex pl", + "ĠRho A", + "Ġrhin itis", + "ĠErr atum", + "Orient ed", + "W ell", + "d oping", + "Ġb up", + "ĠIm pedance", + "Ġsubstit utes", + "actor ily", + "Ġcollabor ations", + "ĠWay ne", + "Ġvow els", + "ĠSh adow", + "Ġphen ology", + "Ġconcur rency", + "h aving", + "ĠC ES", + "ĠF IN", + "ĠL oh", + "ox a", + "ĠAl N", + "ĠAl varez", + "ins tit", + "Ġgerm plasm", + "ĠBol iv", + "ĠR CP", + "ass ador", + "Ġes p", + "Ġphen otyping", + "Ġskip ping", + "ĠFract al", + "ĠPED OT", + "w ake", + "ĠF IT", + "ĠE SD", + "ĠAn tif", + "ubiqu itin", + "ĠAer ial", + "ĠProgn osis", + "ĠREL ATED", + "Ġstratig raphy", + "vat ron", + "ĠPROPERT IES", + "Ġ icon", + "is ers", + "Ġw al", + "Ġst amp", + "ĠOptim um", + "Ġolig omeric", + "Ġinn erv", + "Y A", + "Ab cam", + "Ġv ials", + "ĠG rig", + "Ġun aware", + "Ġoper a", + "ĠWar ner", + "Ġproton ated", + "ĠDR G", + "Ġtro ubles", + "Ġproposition al", + "ĠAfghan istan", + "ĠHamps hire", + "G d", + "l ung", + "Ġa viation", + "Ġap artment", + "Ġinf usions", + "Ġbro ilers", + "ĠDis ability", + "ĠRob ots", + "Ġdeb ugging", + "Ġì Ŀ", + "Wil son", + "upro fen", + "obarb ital", + "J B", + "is ance", + "iti zer", + "MI S", + "ĠAR F", + "Ġprost heses", + "Ġdichlor omethane", + "m Cherry", + "ĠS SS", + "ĠL PA", + "SC F", + "att ract", + "Ġcalibr ations", + "Ġfibr il", + "Ġhapl oid", + "usal em", + "ĠN ut", + "Ġde ut", + "ch ronic", + "NA P", + "ĠCytok ines", + "rage en", + "ĠC ategories", + "rain s", + "Ġsumm ands", + "Ġprolif erate", + "ryl ov", + "Ġple asure", + "Ġdens it", + "ĠSUR VE", + "H IP", + "h all", + "ĠF US", + "Ġwas ting", + "ER Y", + "Ġstat ins", + "Ġeast ward", + "some times", + "Ġwrap ping", + "ĠTW O", + "v ine", + "Ġs acchar", + "Ġam ateur", + "Ġà Ľ", + "Ġmy ster", + "ĠMy o", + "Ġrh abd", + "ĠProte ase", + "Ġchol era", + "ĠG ov", + "ĠG CC", + "Ġcl ays", + "trans mission", + "ĠHol lywood", + "Ġxen ob", + "FLO AT", + "Ġas cent", + "Ġsh arks", + "Ġinter feres", + "ĠForm er", + "ĠHart mann", + "s ha", + "ĠS ave", + "Ġpar ks", + "ĠV enn", + "Ġun ions", + "Ġdisc our", + "Ġsuper lattices", + "Ġcou pler", + "protein s", + "ĠStation ary", + "ĠEther net", + "ĠFré chet", + "Ġk ines", + "Ġj azz", + "As n", + "Ġextension al", + "Ġtel omeres", + "Ġpermit ting", + "Ġexha usted", + "ĠSph ing", + "T urn", + "m ind", + "Ġs f", + "ĠH ak", + "ran olol", + "port ation", + "Cons istent", + "Ġventi lated", + "ĠDIST RIB", + "Ġt elling", + "Ġman nose", + "ÃŃ az", + "Ġbor ne", + "Ġintens ification", + "Ġenjoy ed", + "ĠBrun o", + "ĠSatur day", + "Ġc ocycle", + "it ate", + "Ġg olf", + "appro ved", + "ĠNik ol", + "it ri", + "ĠS entiment", + "Ġg low", + "Ġg yp", + "ĠP CT", + "ab er", + "ĠW is", + "por um", + "Ġhy phae", + "fe as", + "ĠTra its", + "ĠConfl icts", + "degrad ing", + "R aman", + "ph armac", + "Ġimmun ocyt", + "ĠBl ake", + "Ġpseud oc", + "ĠCharacter isation", + "ĠGalile o", + "E nabl", + "J y", + "Ġcl av", + "ĠÏ ³", + "Ġcommun icated", + "eu tical", + "Ġnanot echnology", + "ĠHass an", + "ĠT ec", + "Ġh anging", + "ĠB SD", + "ĠCont our", + "Ġfrag ility", + "Ġdisrup tions", + "Ġfinit eness", + "ĠPhilipp ine", + "n icity", + "Ù ĩ", + "ĠC rim", + "ĠC NF", + "ĠI SI", + "ad apter", + "ĠU CP", + "Ġtext ured", + "AA V", + "ket o", + "N p", + "c ounting", + "h ynchus", + "Ġpro sec", + "ĠAn not", + "ĠHar bor", + "deg rees", + "ak ar", + "ĠV ik", + "bf d", + "Ġdri p", + "ĠCauc as", + "Ġtren ch", + "Ġwe ed", + "Ġdist ractor", + "gen etic", + "spec ifically", + "ulf ite", + "ĠCons istently", + "Ġbreak fast", + "Ġbul let", + "Ġleg isl", + "ĠTra umatic", + "Ġcollect ors", + "ĠBul let", + "ĠMY B", + "ĠP ink", + "vers ive", + "ĠAt tem", + "Ġcult urally", + "B ell", + "und ef", + "vi i", + "Ġhist ocompatibility", + "let cher", + "ĠSte f", + "A mp", + "ĠR id", + "ĠE ucl", + "Ġdec ryption", + "ĠSp encer", + "ĠBit coin", + "w ic", + "Ġcom plicate", + "ĠPro posal", + "ĠÄ Ī", + "avirus es", + "ĠF ay", + "ĠR d", + "ĠG ale", + "ĠMetast asis", + "ĠImprove ments", + " ©", + "Ġpoly ester", + "Ġstrat ospheric", + "ĠSA H", + "Ġamph ip", + "ĠA FP", + "ĠH air", + "ĠE PI", + "ĠUl trast", + "Ġâĭ ¯", + "Ġga pless", + "H am", + "et to", + "Ġth reonine", + "ĠE CO", + "Ġi a", + "Ġund ist", + "Ġradi ology", + "Ġsuper lattice", + "ibr aries", + "Ġturb id", + "ĠPot entials", + "ĠPip eline", + "Ġwarf arin", + "W ISE", + "ĠL id", + "Ġrec urring", + "ĠMon o", + "ĠGover n", + "ĠAware ness", + "ol ab", + "if lora", + "str is", + "IN DEX", + "ĠDem entia", + "Do es", + "w right", + "Í ī", + "Ġs b", + "ĠD OM", + "ĠH BsAg", + "cl inic", + "ĠEx ped", + "Ġprote as", + "Ġster ilization", + "ĠBan erjee", + "ĠPerson nel", + "âĮ ĭ", + "oneph ritis", + "om ite", + "ĠC CF", + "os iti", + "ĠE ucalyptus", + "ĠIs otope", + "col i", + "poss ibility", + "Ġstr ontium", + "Ġra ref", + "ĠInter stellar", + "kin in", + "yleth anol", + "J T", + "n orth", + "Ġc ensored", + "is tive", + "Ġno ticing", + "Ġship ping", + "Em bed", + "Obs erv", + "Ġze olites", + "ub it", + "Ġfl aps", + "Ġdr ifts", + "Ġtherap ist", + "Ġpoll ination", + "ali platin", + "Joh nson", + "Ġimperf ections", + "N Y", + "Ġth alamic", + "oc arb", + "oz otocin", + "Ġtet ramer", + "Pl as", + "Ġmultic hannel", + "ĠIns ight", + "op ods", + "ĠN acional", + "Ġim atinib", + "act ual", + "ĠX OR", + "Ġbl ight", + "ĠLe ading", + "ames e", + "ĠAm plitude", + "ĠMon itor", + "ĠNeu rological", + "propag ating", + "Ġp addle", + "ĠHar vest", + "Ġod ont", + "BU F", + "Ġtac tics", + "ĠAnis otropy", + "ad ip", + "ĠAl pine", + "Ġfe els", + "Ġmed ieval", + "Ġel ucidation", + "Ġheter otrophic", + "Ġrelax ing", + "Ġhapp iness", + "ĠCyt otoxicity", + "ĠRAN KL", + "Walk er", + "m ig", + "ĠS SL", + "ĠS epsis", + "ĠG es", + "Ġhydro chloric", + "Ġclar ification", + "Ġdispar ate", + "t ested", + "Ġdat ap", + "Ġnovel s", + "ĠMicro c", + "á l", + "ĠAR C", + "ĠYang tze", + "etom idine", + "ĠMat rigel", + "ih ilation", + "ĠcDNA s", + "Ġprost at", + "ĠRail road", + "UB LE", + "ĠPART IC", + "ĠS ax", + "Ġins ecurity", + "Ġcr ushed", + "Ġhal ves", + "gi ant", + "ĠCro atia", + "icycl o", + "ĠUne xpected", + "Ġlon eliness", + "an u", + "Ġch ampions", + "ub erculosis", + "Ġequ i", + "Ġacc reted", + "Ġinv ading", + "Ġaff erents", + "Ġaltern ation", + "Ġkin et", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠĠ", + "ĠMAG NET", + "ĠFIF A", + "z adeh", + "ip henyl", + "ĠK ro", + "ĠEval uate", + "illi ant", + "cur vature", + "ĠPier ce", + "b etter", + "n os", + "à ¥", + "ĠK CN", + "ĠSt rand", + "ca emic", + "ĠHo echst", + "ĠEX T", + "ĠLL VM", + "B Z", + "t gt", + "on dialdehyde", + "ĠE vid", + "ĠG ul", + "Ġmulti plications", + "Ġaut h", + "ĠAustr al", + "Ġstay ing", + "ĠGlut amate", + "Ġst ray", + "ĠI SA", + "Ġlow land", + "Ġparallel s", + "Ġattrac tiveness", + "Ġelectrosp inning", + "Ġportray ed", + "ospec ific", + "f olate", + "Ġcoe ff", + "ĠEst rogen", + "tum our", + "Ġhystere ctomy", + "Ġin ositol", + "ĠB az", + "ist ein", + "Ġcruc ially", + "Ġdin oflag", + "ÍĶ ÍĴ", + "ĠDrag on", + "ĠS por", + "ĠM ater", + "ĠH ero", + "plic ing", + "ĠAN T", + "ĠForm ic", + "Que ue", + "ocarcin omas", + "U PS", + "ĠP c", + "enc oders", + "Ġinv aded", + "ĠPh ases", + "Ġpost mortem", + "Ġslow s", + "ĠMc L", + "ĠVer ma", + "ĠVi ability", + "Ġcompens ating", + "Ġclamp ed", + "j m", + "ĠR iv", + "up on", + "ĠDick inson", + "initi ated", + "Ġs ider", + "ĠS elen", + "ĠA ka", + "idel berg", + "Ġqual ifying", + "Ġenfor cing", + "otroph s", + "ĠSNA P", + "Ġr ust", + "imb urs", + "Ġimmunocomp romised", + "ĠFlem ing", + "Ġl izards", + "di alysis", + "ĠUn ivariate", + "Ġgas oline", + "Ġten ure", + "Ġsustain ing", + "Ġmot one", + "b ay", + "w ani", + "ore station", + "ĠX II", + "Ġradi ofrequency", + "ĠGu ided", + "Ind ividual", + "ĠSpect rometer", + "ĠGo ing", + "ĠMart ins", + "Ap proxim", + "am ak", + "ĠâĪ ı", + "ĠO mn", + "Ġout patients", + "Ġhyper bol", + "ĠPer ceptual", + "ĠBur ke", + "Bol tzmann", + "ĠM d", + "Ġpa w", + "ĠCat hedral", + "Ġhyal uron", + "Ġbrach ial", + "Ġaflat oxin", + "im o", + "Ġen rol", + "Ġdet onation", + "Ġover ly", + "the st", + "Ġsecond ly", + "ĠSch iz", + "ĠIGF BP", + "atech in", + "Ġs aves", + "ti ers", + "ĠB ates", + "Ġall iance", + "Ġatt ri", + "Ġast ro", + "ĠPath ological", + "Ġgamb iae", + "P ark", + "id able", + "ĠN il", + "ĠJ as", + "Ġneed ing", + "me ier", + "Ġferro ptosis", + "ĠGuid ance", + "A Z", + "i ol", + "Ġac knowledg", + "ex ual", + "Ġmen opause", + "Ġadj unct", + "cap ture", + "ĠDep uty", + "Ġb ial", + "if a", + "ĠCh itosan", + "ĠTop ics", + "ĠPlas mid", + "calc ulations", + "g ive", + "respond ers", + "ull a", + "ĠMore no", + "Ġcomment ary", + "ĠMah m", + "ï£ ±", + "on acci", + "ĠC ould", + "ĠTR P", + "second s", + "Graph Pad", + "L ittle", + "he y", + "Ġal ike", + "ĠDi as", + "aro o", + "ĠÄ ±", + "Ġtax es", + "phen anth", + "ĠChe ung", + "ĠPi et", + "D f", + "G U", + "m ectin", + "z ee", + "Ġd λ", + "Ġsynt heses", + "Ġá Ī", + "Sim ulation", + "ĠEle ven", + "w orms", + "lymph ocyte", + "Ġhaemorrh age", + "ĠO wn", + "ĠK ant", + "Ġover se", + "Ġide ation", + "ĠHar per", + "Acknowledg ments", + "v ili", + "yn a", + "ĠRec urrence", + "oz a", + "Ġhence forth", + "ze es", + "Ġquas ic", + "Ġchor oidal", + "Ġantim alarial", + "Ġcoars ening", + "D eb", + "di am", + "ĠWe ights", + "Ġbu ying", + "Ġmess aging", + "Fe bruary", + "Ext ended", + "ĠRoss i", + "Ġmist aken", + "Ġut ero", + "j as", + "ic itis", + "ĠT idal", + "Ġph aryngeal", + "cl ick", + "Ġmy o", + "kn ock", + "Ġpromin ence", + "Ġamphi philic", + "c orn", + "Ġon board", + "ĠD ud", + "ĠW oman", + "ĠOut break", + "Ġprefer ably", + "Ġsket ches", + "S at", + "f ixing", + "ĠM ey", + "ĠLet ters", + "IT IES", + "ĠSD P", + "ĠLNC aP", + "D X", + "F luor", + "R v", + "S ect", + "ĠI ons", + "Ġtrac hom", + "Ġult rastructure", + "qv ist", + "rop he", + "Ġrece ipt", + "ĠQu int", + "Ġsw apping", + "amin idase", + "Ġarch ival", + "ĠCre ating", + "ĠBart on", + "diagn osed", + "at ological", + "ol ph", + "ĠP FA", + "ĠL AP", + "Ġun physical", + "eq n", + "Ġquar tiles", + "olytic a", + "ĠFre ed", + "Ġventil ator", + "Ġkary otype", + "S ta", + "s till", + "ĠT ate", + "ur ability", + "ĠG ron", + "Ġtr imer", + "IP A", + "adec a", + "ĠImplement ing", + "s ity", + "it r", + "Ġb om", + "Ġnon relativistic", + "Ġmic elle", + "ĠAd minist", + "Ġelectro lysis", + "har mon", + "OLOG ICAL", + "L iter", + "ĠG UI", + "ĠQ L", + "mon ths", + "Ġsuper flu", + "cut s", + "Ġelic its", + "Ġmultiplex ed", + "overl ap", + "Ġcada ver", + "Ġo u", + "ĠS heng", + "ere a", + "ĠN BC", + "Ġdet er", + "ty rosine", + "ĠPar ts", + "Ġess ay", + "k as", + "it ted", + "ĠP ZT", + "ess ler", + "Ġsim ulators", + "Ġradi ating", + "cut ting", + "ĠCalc ulating", + "TH ER", + "ĠROC K", + "commun ic", + "Ġbon us", + "ĠC PA", + "ĠP UR", + "ult on", + "ĠZ hi", + "Ġcal oric", + "Ġinterp olate", + "ĠSec retion", + "Ġneuro cognitive", + "Ġgad olinium", + "f requencies", + "ĠT ract", + "Ġminim ax", + "ĠBro ck", + "ryp sin", + "ĠReson ant", + "ĠACKNOWLED GEMENTS", + "D om", + "Ġhol otype", + "Spec ial", + "Ġimmunore active", + "ARN ING", + "Pan el", + "ĠJohann es", + "R FP", + "z zi", + "ĠP omer", + "Ġtrans ects", + "Ġpo ured", + "ED s", + "ĠCirc um", + "Ġabnorm ally", + "ĠPun j", + "G ol", + "H op", + "H ex", + "I LE", + "Ġsour ced", + "ocl ase", + "prot obuf", + "Ġfro gs", + "ĠOt tawa", + "Ġbioge ochemical", + "Ġlenti virus", + "Y oung", + "ĠI PS", + "ass en", + "Ġun restricted", + "Ġmat plotlib", + "Ġchlor amphenicol", + "ĠContext ual", + "ĠHawai ian", + "Leg end", + "S parse", + "b ore", + "g aussian", + "u ke", + "ĠâĢ °", + "ret est", + "SS E", + "pre ting", + "ĠPan ama", + "ĠBroad band", + "conjug ate", + "B ytes", + "G SH", + "U ns", + "r ina", + "Ġd rained", + "Ġsc ap", + "Ġinves ted", + "Ġsatisf actorily", + "Ġherbiv ores", + "Ġarachid onic", + "ymet rix", + "Ġn ect", + "Ġcon ges", + "ĠM err", + "ĠM ai", + "Ch ain", + "Ġretrie ving", + "Col lection", + "ĠMT X", + "ĠFernand o", + "h g", + "ĠR ams", + "th resh", + "aps ules", + "Ġcond uit", + "sw ap", + "Ġblow ing", + "ĠNy quist", + "Ġuncons cious", + "ĠDIFFE RENT", + "T echn", + "h iz", + "î Ĥ", + "Ġd ξ", + "ĠSt o", + "ĠFlav on", + "Dav id", + "Ġfiltr ate", + "l ith", + "ĠW ool", + "ĠK not", + "Ġhal ide", + "Ġbio assay", + "ĠGold berg", + "ĠTrich oderma", + "Ġintras pecific", + "c rystall", + "ĠR end", + "our g", + "Ġunder take", + "ĠEn um", + "inf ect", + "Ġmid gut", + "att ack", + "ĠCirc le", + "Ġplei otropic", + "es cent", + "ĠF ri", + "ph ilis", + "ast ings", + "Ġbi ogas", + "ĠÄ ľ", + "Ġaccomp any", + "Ġroll ed", + "Ġchir p", + "Ġsomat ostatin", + "vark appa", + "S cal", + "Ġd row", + "rom ed", + "ĠL up", + "ĠL uminosity", + "ĠN ig", + "fer romagnetic", + "ĠTo y", + "Ġcann abinoid", + "ĠH OX", + "ie le", + "ĠCT X", + "Ġhyd rop", + "Ġfavor ite", + "Ġstret ches", + "eval uated", + "ogroup s", + "ac al", + "ol lo", + "Ġg enders", + "ĠG raft", + "Ġinc idences", + "Ġreplac ements", + "ĠTR UNC", + "CR F", + "Ġequal ization", + "ĠRen ew", + "Ġple thora", + "ĠEnc oder", + "M it", + "Ġc aches", + "or ate", + "end ors", + "ĠCa ution", + "ĠAb el", + "comp ression", + "ĠLars en", + "ĠElim ination", + "Ġt ester", + "Ġn inth", + "ĠL ö", + "Ġsp iders", + "Ġpo em", + "Ġeduc ators", + "ĠEnh ances", + "dest ructive", + "Four ier", + "Ġseism icity", + "ĠYun nan", + "Riemann ian", + "W ID", + "v ular", + "ĠB order", + "Ġcomb in", + "sing let", + "ĠEd dington", + "ĠTem plate", + "ĠPA X", + "Ġbasal ts", + "En h", + "Ġassist ants", + "ĠCasc ade", + "Ġin breeding", + "ch ini", + "Ġup graded", + "ĠTrans it", + "sur vival", + "Ġinject or", + "ĠPas cal", + "DEV ICE", + "Ġf ost", + "ĠK and", + "Ġext ragalactic", + "epend ently", + "Ġexc ite", + "Ġfulf il", + "Ġrip arian", + "Ġuplo aded", + "a un", + "l od", + "s aving", + "ĠH ib", + "ĠE ra", + "ob ese", + "Ġu i", + "Ġspect rally", + "ke V", + "xx x", + "ĠOt to", + "Ġé tale", + "L AT", + "d ermal", + "di az", + "ĠPl i", + "Ġleg ume", + "Ġinsp ect", + "Ġthym ic", + "ĠHorm one", + "á Ģ", + "in ot", + "ĠS hib", + "ĠB CC", + "ĠV ital", + "Ġprof its", + "ĠFed erated", + "Ġflip ped", + "Ġpropri etary", + "incor porated", + "Ġbact eremia", + "Ġáŀ ĩ", + "f ins", + "ä ½", + "es ia", + "ĠH ollow", + "ge ons", + "Ġtre halose", + "ER O", + "oster ol", + "om us", + "ĠC rystall", + "Ġcur ation", + "Ġmagn on", + "ĠAm end", + "Ġhar b", + "Ġneutral ity", + "ĠDel phi", + "Ġnons ense", + "ĠHome ostasis", + "Ġexpendit ures", + "Sequ ential", + "imod ular", + "Ġz enith", + "ĠMor an", + "Ġbootstrap ping", + "i omy", + "l actic", + "it ure", + "Ġn at", + "Ġg ab", + "Ġch at", + "reg ional", + "Ġcr ashes", + "ĠAF B", + "Ġcrow ded", + "Ġtwe et", + "engine ered", + "ĠCharg ed", + "S che", + "IT IONS", + "ĠCor al", + "ĠEl i", + "Ġinver ting", + "Ġped ag", + "ĠSand ers", + "Mean while", + "ĠGriff iths", + "P SCs", + "ti ze", + "ĠM ail", + "Ġund ec", + "Ġher mitian", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ", + "ĠExpl os", + "Ġwest ward", + "ĠConf irm", + "B egin", + "Ġfactor ies", + "ĠPR L", + "she ar", + "Head er", + "ĠFLAG S", + "an omal", + "ĠQ W", + "ĠÌ ħ", + "oin ositi", + "Ġmamm ography", + "Ġdeposition al", + "EX P", + "resid ue", + "Ġunsatisf actory", + "A β", + "M UX", + "Ġst aged", + "ĠM MT", + "ĠK us", + "ll o", + "Ġtrain er", + "add en", + "Ġpin ch", + "WA RE", + "Ġcab inet", + "C SP", + "ec um", + "ot eric", + "ĠH av", + "Ġres ume", + "Ġnetwork ed", + "sh are", + "ĠCol le", + "Ġchem otactic", + "ĠGly c", + "olk it", + "Ġbot ulinum", + "ĠNeighbor hood", + "m V", + "ĠH Q", + "ef aciens", + "get t", + "Ġge ost", + "ĠCD W", + "ĠÌ §", + "Ġflo ors", + "represent ing", + "odi ode", + "ĠInst ance", + "Ġmonod is", + "d rying", + "re asing", + "ig i", + "Ġg out", + "ĠI EC", + "Ġfl ush", + "Ġtra ded", + "Re view", + "ĠïĤ ¢", + "Ġà ¤", + "Ġabbrevi ations", + "otherap ies", + "Ġindeterm inate", + "Ġglutar aldehyde", + "Ġattri tion", + "j ump", + "in de", + "ĠG ri", + "arc tion", + "TRA IN", + "Ġescap ed", + "at ement", + "ĠP am", + "ĠG AM", + "pro ductive", + "ĠAmeric as", + "agen esis", + "ĠMi xtures", + "ĠHo oft", + "ĠWind ow", + "Ġnod ular", + "Ġech in", + "D OF", + "ĠD DT", + "elect rical", + "ĠDec entralized", + "Ġcontrad ict", + "F rench", + "Ġa ustr", + "ĠA PD", + "ĠD IM", + "ĠSt en", + "ron omic", + "ĠPolym orphism", + "Ġc occ", + "it ings", + "Ġsub critical", + "ĠUn iqueness", + "OP EN", + "rot oxicity", + "Gen Bank", + "atab ases", + "N ets", + "u istic", + "y ric", + "ĠS ID", + "Ġco oked", + "ĠJ udge", + "Ġparameter izations", + "Ġenum erated", + "ĠAst hma", + "De velop", + "Ġc ake", + "ĠA ges", + "ven ile", + "Ġfl or", + "Ġcould n", + "det ach", + "Ġpip ette", + "ĠInst ant", + "Ġtent atively", + "ĠINT EGR", + "H Q", + "M apping", + "c q", + "å Ī", + "SU M", + "frac tions", + "ĠCl aud", + "Form ula", + "Ax is", + "ĠBil ly", + "ĠMeth ane", + "ĠI GM", + "c annot", + "Ø ³", + "Ġc iting", + "ĠD ynam", + "Ġle tt", + "eg ler", + "ĠPhysic ians", + "x FF", + "Ġo yster", + "ĠT OC", + "Ġsub arachnoid", + "ĠCO M", + "IT ER", + "Ġbenz odiazep", + "Ġuncom plicated", + "till o", + "Car bon", + "at em", + "Ġs el", + "ing o", + "IV ITY", + "Ġca vern", + "Ġspac elike", + "ĠColl isions", + "stra int", + "Ġmyc obacterial", + "Ġtrachom atis", + "A i", + "m f", + "ĠT ric", + "tic o", + "ĠE lection", + "ĠK DM", + "ĠEx osomes", + "af luor", + "Ġformal ized", + "ĠEL F", + "v phantom", + "ĠS ME", + "ich uan", + "ĠV Ms", + "Ġro stral", + "of er", + "ram anian", + "inter cal", + "Mer ck", + "ĠFerg uson", + "H U", + "l j", + "Ġr ack", + "Ġmicro graph", + "CT S", + "Ġpass ively", + "ĠMass es", + "rang ians", + "ĠAD M", + "ĠProvid ed", + "ĠVeter ans", + "s ound", + "ge x", + "ĠSp iral", + "Ġfoss a", + "Ġthermod ynamically", + "ĠS taining", + "Ġk ar", + "ef lon", + "ĠBr uns", + "VA E", + "olytic us", + "Ġintran asal", + "ĠProsp ects", + "at hers", + "Ġnumber ing", + "ĠRe placement", + "ĠNon commutative", + "quis itions", + "ĠSIM D", + "ĠArter ial", + "ĠH GF", + "ĠG PP", + "Ġflu vial", + "ner i", + "ĠComp ressed", + "VID IA", + "Ġshock ed", + "d ys", + "in variance", + "en stein", + "ĠS CM", + "ĠD od", + "Ġsh o", + "Ch lor", + "du ino", + "Ġurg ently", + "s oc", + "et ching", + "Ġdiff ractive", + "ĠZ F", + "Ġhyper planes", + "Ġmy ri", + "Ġfer romagnetism", + "fil ament", + "Ġjustif ies", + "f ault", + "ĠH TS", + "ĠE PC", + "to o", + "ĠTR AP", + "i ón", + "r v", + "ĠB PD", + "ĠN od", + "pos it", + "ĠCon vers", + "Ġzero es", + "ĠGl en", + "ĠDist urb", + "Ġtable au", + "Ġpseud ot", + "ĠColl ider", + "Ġadsorb ents", + "ĠGro ve", + "Ġking dom", + "E st", + "X s", + "c zyk", + "ĠW ille", + "ĠV OL", + "sc ar", + "ĠAd ler", + "ĠOr chestra", + "Ġspars ely", + "glycos ylation", + "L ac", + "o tions", + "ĠI le", + "Ġbe acon", + "ĠR n", + "ull ah", + "Ġtim elike", + "ĠFore sts", + "Ġupl oad", + "j it", + "ĠE DM", + "Ġtrans plants", + "mark er", + "ĠBre eding", + "ÎĶ ÎĶ", + "Ġfavor ably", + "ĠTransform ations", + "abel ed", + "ĠPoli tics", + "epis ode", + "Ġf ut", + "Ġd ithi", + "ĠM w", + "Ġtrans piration", + "Ġun limited", + "ĠAn tiv", + "PP V", + "Ġnom ogram", + "Ġinvent ed", + "ĠSched ule", + "all ows", + "Ġtrans vers", + "Ġwork piece", + "black square", + "call back", + "ĠAth letic", + "h ans", + "p oles", + "Ġe avesdrop", + "ĠC one", + "oc lim", + "ĠG host", + "iter ations", + "Ġweak en", + "Ġalkal oid", + "Ġveter ans", + "Ġprostat ectomy", + "Ġb og", + "ĠC ed", + "ĠF ever", + "yl an", + "arch ive", + "Ġattack ers", + "M aking", + "b ane", + "ĠP ull", + "ĠF LO", + "Ġco aches", + "ĠV SM", + "ok h", + "ĠSp o", + "amil ial", + "princ iple", + "Ġaggress iveness", + "Ġgard ens", + "S IG", + "Ġm bar", + ".. ...", + "Ġoptim izes", + "Ġmorph ologic", + "han i", + "Ġgerm anium", + "Enabl ed", + "w b", + "Ġfor amen", + "ĠS PA", + "Ġmagn ified", + "ĠSl ater", + "ĠSy rian", + "Ġt ert", + "Ġtra ditions", + "Ġoff ensive", + "Ġhyd rology", + "erge tics", + "Ph ot", + "Ġperovsk ites", + "Ġwaven umbers", + "Ġosteocl asts", + "imed ean", + "ĠBasket ball", + "benzodi ox", + "ĠTRUNC ATED", + "Ġb ishop", + "ĠS gr", + "ĠS atisfaction", + "agn ostic", + "num eric", + "Ġnorm als", + "Ġhum or", + "Ġcontin ents", + "NAT ION", + "Lag rangian", + "Ġkne es", + "ĠI DE", + "ad as", + "ad ia", + "ĠO U", + "ond s", + "ĠCh aud", + "Ġsl icing", + "ĠAc tor", + "Al t", + "Ġbroad casts", + "osa urs", + "Ġpick le", + "Ġunf amiliar", + "all us", + "Ġhas hing", + "inc idence", + "Ġmetabol ized", + "Ġhomogeneous ly", + "ĠFal con", + "Ġ Ñģ", + "ĠC ere", + "ĠC LA", + "ĠP aste", + "ĠF ry", + "ĠD re", + "ad ult", + "Ġdisc ounted", + "sens itized", + "ercul ous", + "ĠP ixel", + "ĠB ram", + "all o", + "ip ative", + "ip ality", + "ĠSt rict", + "ĠTr inity", + "ĠClass ifiers", + "ĠBas el", + "ĠCur cumin", + "ĠLU MO", + "Ġmediast inal", + "ĠF FA", + "Ġpl enty", + "pr ised", + "Ġpr inter", + "Ġcalc are", + "ins n", + "ont ology", + "Ġground ing", + "ĠAL DH", + "STR ING", + "ĠFem ales", + "ĠFocus ing", + "assess ment", + "ĠBlu etooth", + "ëĬ Ķ", + "Ġe go", + "ĠD AC", + "ond ing", + "rand a", + "ĠLud wig", + "Ġanteced ent", + "ĠErn st", + "d X", + "od eling", + "âĢ ĭ", + "In ser", + "ogn ormal", + "ĠTe vatron", + "Ġcovari ances", + "rig ing", + "ĠMg SO", + "carbon itrile", + "ĠLore n", + "Ġpolyt opes", + "ĠParent al", + "Ġun healthy", + "ither to", + "ĠMo tif", + "Data Type", + "ĠMI PS", + "ĠPhosph orus", + "Mo O", + "ĠPerturb ations", + "Ġaph ids", + "Ġanhyd ride", + "id eration", + "ĠM its", + "gra vit", + "Ġdest inations", + "Com mun", + "Ġtetra hedron", + "im plicit", + "Ġass ort", + "ĠSub t", + "ĠAcet yl", + "ec ium", + "ĠN ie", + "Ġoper and", + "ĠSc her", + "az oles", + "tle ment", + "ĠBlock ing", + "Ġbottlen ecks", + "ĠOccup ational", + "H AS", + "T eller", + "Ġv ague", + "est ing", + "SN E", + "Ġphoto ionization", + "Ġcompl aint", + "us pid", + "ow ler", + "Ġend ocytic", + "Ġfl ocks", + "eps in", + "col ors", + "otop es", + "ĠDep letion", + "ELL AR", + "ar med", + "ĠT IR", + "Ġbul lying", + "Ġâİ §", + "ospor idium", + "M r", + "ĠC ic", + "og al", + "Ġsection ed", + "Ch apter", + "ĠCont ents", + "ĠPath s", + "ĠExpl ain", + "comput ing", + "Ġshr ub", + "ĠMalays ian", + "B eta", + "M ad", + "R os", + "Ġe yel", + "ĠA CF", + "ĠM m", + "text ure", + "Ġinterpret ability", + "ĠTop ic", + "Ġbad ly", + "ĠmA h", + "E g", + "R Q", + "p ins", + "et c", + "ĠV ogel", + "Ġhyp oc", + "Ġrun away", + "Ġperson ally", + "Ġbind ers", + "sens ory", + "ĠIP v", + "rank ed", + "Ġfibr ations", + "Ġstraw berry", + "arot omy", + "F LI", + "r ator", + "od ys", + "ir an", + "ĠB ead", + "ĠD AM", + "âĪ ĥ", + "Ġill usion", + "pid ium", + "Pl ace", + "Reg ion", + "Ġalloc ations", + "Ġoh mic", + "Ġn f", + "im ino", + "ĠB ris", + "iti zing", + "pro per", + "sub group", + "Ġsal ience", + "rang ement", + "ĠMean ing", + "Ġbarc ode", + "Ġneurop eptide", + "Ġendos perm", + "im ab", + "Ġnan od", + "ĠMet z", + "Ġcoll ocation", + "ĠInf ected", + "Ġpack aged", + "ĠAD A", + "ĠBar th", + "ĠCN C", + "Ġcasc aded", + "ĠStock holm", + "it as", + "ĠM MR", + "ĠD rought", + "Ġper missible", + "Ġsc iatic", + "Ġfr inges", + "Ġexec utable", + "Ġstem ness", + "ĠEnd oscopic", + "apor in", + "T OP", + "e B", + "t ur", + "ĠSt ages", + "anc hes", + "Ġnon perturbative", + "Ġmar itime", + "Ġcovers lips", + "Ġlag oon", + "Experim ents", + "Ġcodew ords", + "Enc oder", + "d as", + "p rac", + "Ġp addy", + "Ġd raining", + "Ġk ids", + "Ġen emies", + "Ġmo tile", + "Ġsl ack", + "be es", + "ĠSup pl", + "ĠBar ber", + "ĠSP H", + "Ġcrystall ite", + "fit ted", + "cycl opent", + "ĠRMS D", + "Ġpark inson", + "Ġuncor rected", + "ĠSynt ax", + "Ġmultin omial", + "ĠIncor porating", + "akrish nan", + "J L", + "N ESS", + "m im", + "ĠT ET", + "ĠP orph", + "ĠSch we", + "Ġcatalog s", + "ĠAuthentic ation", + "B ro", + "ug ar", + "ĠAm pl", + "Ġsap iens", + "ĠGAN s", + "Ġnecessit ates", + "t g", + "ed al", + "ĠR ear", + "op eptidase", + "ĠIn formed", + "Ġtail or", + "ĠNN LO", + "Ġhemod ynamics", + "S y", + "d ating", + "ac hers", + "ĠT odd", + "ĠM ario", + "ĠU GT", + "ĠVal ent", + "Ġstream lines", + "Ġwar rants", + "Ġre w", + "ĠM ud", + "ĠG K", + "Ġco ke", + "ĠU ran", + "Ġgro oves", + "ron ate", + "ĠRad ius", + "ĠSu ite", + "atum oral", + "Work space", + "ĠSynerg istic", + "ĠAtheros clerosis", + "ma z", + "arg max", + "sh ield", + "ont in", + "Ġlist ener", + "ocyt oma", + "ĠGra v", + "ĠJer usalem", + "pyrrol idin", + "ĠSpr ings", + "Ġseaf loor", + "Ġd ips", + "ist ani", + "ob is", + "Ġphot odynamic", + "AD O", + "Ġion isation", + "Ġbar n", + "igene tics", + "Ġeconom ies", + "ĠGlac ier", + "Ġ ç", + "im es", + "ĠS asaki", + "ch io", + "Ġass isting", + "ost in", + "Ġind iff", + "ĠSh ot", + "ĠNe uron", + "CD D", + "ĠCON ST", + "ĠBS s", + "t as", + "ass ociation", + "par g", + "Ġes cal", + "ex ercise", + "ĠAd ela", + "Ġmy ogenic", + "ĠNO AA", + "ycl o", + "l inal", + "ĠH ut", + "Ġintro ductory", + "Ġheter ochromatin", + "Ġchem oresistance", + "Ġsimpl ifications", + "pyrid in", + "Ġamyloid osis", + "Ġnanof iber", + "ĠSut ton", + "ĠResil ience", + "P arent", + "ĠL MS", + "Ġle ts", + "ĠEl derly", + "Ġirre vers", + "she ets", + "Eff ects", + "Ġellips es", + "ĠPhilos ophy", + "Ġphot ographic", + "HE AD", + "Ġrevers ibility", + "Ġfed erated", + "ĠPhosph oserine", + "estim ation", + "ĠHum ph", + "J son", + "Ġf ills", + "Ġv erm", + "ĠSe if", + "with standing", + "ĠYam ada", + "er ia", + "ĠF LA", + "ĠV ick", + "to id", + "ann ier", + "Ġcancer ous", + "PR INT", + "ĠMechan istic", + "Ġdust y", + "ĠApp end", + "y cin", + "Ġa zo", + "Ġs izing", + "Ġc rayfish", + "av is", + "ĠAd vent", + "ĠCommun ist", + "ĠIM U", + "pix els", + "H all", + "p ast", + "ĠR ous", + "ression al", + "air d", + "ĠAD D", + "Ġsummar izing", + "Ġelect rol", + "St ation", + "ĠLy α", + "ĠTM EM", + "Ġpeptid ase", + "D ual", + "g it", + "ĠB OD", + "ĠR ham", + "ĠK ak", + "Ġread iness", + "ĠComp are", + "ĠRam os", + "sign ificantly", + "Ġhair y", + "Ġvas opressin", + "ĠGuid eline", + "B NP", + "Ġd irty", + "Ġinf imum", + "ĠAl ess", + "ĠVol cano", + "M agn", + "Y Y", + "ugh lin", + "bo platin", + "ĠCant or", + "Ġclot hes", + "ĠR w", + "Ġus eless", + "ĠK dV", + "oper oxidase", + "ĠCor rect", + "Ġfat ality", + "ĠRest riction", + "Comput er", + "Dep artment", + "I l", + "g ang", + "ĠElect roc", + "obar ic", + "P hen", + "Ġn ed", + "ad h", + "iss ing", + "to oth", + "Ġman uscripts", + "Ġbi otechnology", + "Sup p", + "ĠPair wise", + "ĠPars ing", + "d H", + "m elt", + "r z", + "ĠC atalyst", + "em ption", + "Ġshow ers", + "BL EM", + "ĠBro thers", + "ban on", + "Ġbrac hy", + "metall icity", + "ĠC IS", + "ĠC openhagen", + "Ġel der", + "Ġfin anc", + "odes ic", + "Ġdev ise", + "Ġsurv ives", + "Ġð tÃŀ", + "Ġfasc inating", + "Ġparall ax", + "H OR", + "y th", + "on ins", + "ĠE HR", + "ĠG ates", + "ob ase", + "ĠCon way", + "oper ations", + "man uel", + "ĠAb dominal", + "ĠAR G", + "ĠGr ö", + "Ġphotos ens", + "ĠCoul ter", + "ĠJul ian", + "ĠLev ine", + "Ġsarc oidosis", + "Ġp illars", + "Ġd R", + "ĠW G", + "Ġspec ulation", + "ans ki", + "ĠGaussian s", + "Sch w", + "ĠNam bu", + "paren ts", + "intr insic", + "ĠKend all", + "ĠR g", + "Ġprot otypical", + "bre lla", + "Ġtet rap", + "ĠPath ophys", + "nm t", + "Ġerg odicity", + "ĠYers inia", + "Q O", + "ĠI AV", + "Ġch ocolate", + "Ġconf erring", + "Cl NO", + "oti a", + "Com plete", + "ĠAMP A", + "ïĢ Ń", + "ĠḠ¡", + "ĠiP SCs", + "ĠApparent ly", + "Ġintox ication", + "ĠF ather", + "per cent", + "Ġsh aker", + "Ġfin ancing", + "Ġgenital ia", + "memb ers", + "Ġstagn ation", + "h matic", + "ro red", + "Ġcon idia", + "atal oader", + "ĠNe il", + "Ġliter atures", + "ĠGl c", + "ĠDevelop ments", + "differenti ation", + "ĠRevis ited", + "n il", + "t om", + "di ol", + "ĠAb ell", + "Ġplastic s", + "ĠLu ke", + "adj acent", + "ĠBH s", + "ĠPosition ing", + "ø r", + "overex pressing", + "E c", + "P ref", + "or ting", + "Ġin ch", + "ĠC atherine", + "ĠD MP", + "ĠO e", + "end othelial", + "IC ES", + "ĠHad ron", + "Ġrevis it", + "ĠPict ures", + "ĠKnock down", + "il ian", + "ĠA LA", + "op amine", + "ĠL ah", + "cl imate", + "Ġdist raction", + "ars ki", + "ĠAcc ount", + "ref lex", + "Ġelong ate", + "ĠAmb ient", + "C x", + "M achine", + "ĠJ PEG", + "Ġclass ifies", + "ĠReg ulate", + "oplas ia", + "inj ury", + "neigh bors", + "ĠFORM ATION", + "F IS", + "S z", + "ĠO SC", + "Ġassemb ling", + "Ġintrac erebral", + "su pers", + "Ġp F", + "Ġhe al", + "ĠV ries", + "arc he", + "ĠDec om", + "ĠDiff ic", + "agent a", + "ĠSpir it", + "ĠInters ection", + "Ġendors ed", + "ĠNob el", + "i Ïī", + "w u", + "ph aly", + "Ġqu eu", + "ĠFor um", + "land er", + "Ġspectrom etric", + "ĠHank el", + "ĠC SE", + "Ġres umed", + "ĠG RE", + "AC ES", + "CT A", + "Ġbeh aved", + "Mon itor", + "ĠNik on", + "ĠCHAR ACTER", + "ĠS AL", + "ĠI ch", + "ĠH SF", + "Ġgen otoxic", + "ific ance", + "ĠCh iang", + "ĠZ en", + "ĠAr rows", + "ĠPD T", + "ĠFL ASH", + "ocor tex", + "onstruct ing", + "T reatment", + "ĉ ĠĠĠĠĠĠ", + "ed ullary", + "il ty", + "ind entation", + "Ġam ended", + "Ġfl ed", + "roph ication", + "ĠGL M", + "ĠOper a", + "HL H", + "L ite", + "b mod", + "Ġa version", + "ĠS weet", + "Ġst reptavidin", + "ĠP airs", + "ug os", + "ep oxy", + "Ġun specified", + "Ġmicro channel", + "ĠVictor ian", + "C ould", + "in formed", + "Ġs its", + "Ġr x", + "Ġne p", + "to uch", + "RO I", + "Ġhead ers", + "fl ush", + "ĠPath ogenic", + "Ġspac ings", + "het ti", + "Ġmotiv ating", + "Ġstake holder", + "ĠSymbol ic", + "ĠC rani", + "Ġdis pute", + "Ġass ists", + "ind ler", + "ĠSp atio", + "oh m", + "Ġextrap olate", + "Ġelabor ation", + "ĠGTP ases", + "Ġcellul ase", + "ĠT uc", + "ol ide", + "ĠA IM", + "pl ast", + "ĠB ible", + "op oly", + "ub o", + "ace an", + "ĠPen rose", + "ĠMap Reduce", + "ĠKw on", + "W all", + "Ġg cd", + "ĠAr bitrary", + "Pro duct", + "ĠGit Hub", + "F n", + "Ġc k", + "ĠA us", + "Ġgra ve", + "Ġmetabol ically", + "Ġlist en", + "Ġextrac tions", + "ĠTr unc", + "ĠRad iology", + "cons erving", + "ĠComposition al", + "report ing", + "s ustain", + "î Ģ", + "ĠO bl", + "Ġk N", + "St re", + "ĠSuper gravity", + "ĠPV P", + "Ġcivil ian", + "ĠTun nel", + "Ġhelic opter", + "ĠCamb rian", + "Ġr g", + "ĠU PF", + "Ġpol yd", + "Ġobserv ability", + "con tainer", + "nit ros", + "ĠCut ting", + "Ġdeco uple", + "Ġcarbox y", + "c row", + "Ġc x", + "ĠK ell", + "Ġproject ors", + "Ġmyocardi tis", + "itone um", + "condition ing", + "ĠE TH", + "oy ama", + "Ġphosph ates", + "ĠSub jective", + "ĠSer re", + "Ġcollagen ase", + "Ġvibr ating", + "strept omycin", + "z hen", + "Ġc res", + "Ġc ull", + "Ġh aven", + "ĠG PL", + "less ness", + "Ġview points", + ",, ,", + "ochrom ic", + "uy ama", + "Ġpartnership s", + "L ICENSE", + "ĠS IFT", + "res ources", + "ĠG os", + "iv ac", + "Ġneuro genic", + "Ad j", + "Ġaqu ifers", + "ĠGly cos", + "ĠMatthe ws", + "ĠFrid ay", + "Ġp X", + "Ġan te", + "ĠF enton", + "ĠE ukary", + "ib al", + "ide ae", + "At tention", + "ĠPolymer ization", + "Ġflip ping", + "ĠMedi ates", + "Ġstation arity", + "Ġecho es", + "alid omide", + "Ġdeline ation", + "Ġn aval", + "ĠS omatic", + "Ġst ub", + "ĠB ever", + "ĠR iz", + "Ġres ummation", + "Ġass ault", + "Ġpre existing", + "Ġhyper methylation", + "Ġconserv ing", + "record ed", + "am n", + "Ġch ow", + "ĠK ill", + "ĠPro duced", + "Ġref s", + "ĠEn zymes", + "Ġdeep est", + "&& &", + "ĠFR P", + "Ġmil ieu", + "Ġirrig ated", + "ĠAn atomical", + "Ġdiss ect", + "ili ensis", + "raz olo", + "ĠProb able", + "sol ve", + "conf irmed", + "ohydro dynamic", + "l ibrary", + "ĠC iti", + "ĠP HA", + "its ky", + "Ġelect rone", + "na ive", + "Ġrib s", + "ĠPhot onic", + "Ġsubstanti a", + "ĠEST IM", + "Ġduoden um", + "ĠChag as", + "ĠSURVE Y", + "P ress", + "b ian", + " ¤", + "he i", + "ĠV AR", + "Ġcol ocalization", + "Ġpol yl", + "Ġdo ugh", + "Ġmicro controller", + "Ġinternal ized", + "Ġdischarg ing", + "ĠChlamyd omonas", + "or ad", + "it el", + "ĠW end", + "Ġlog its", + "Ġelectro cataly", + "ĠAm ar", + "Ġappreci ably", + "Ġneurotrans mitters", + "former ly", + "c ul", + "r ata", + "ĠB alk", + "ĠO sm", + "Ġsympt omatology", + "ĠFI ELD", + "ĠA Ps", + "Ġg ymn", + "ĠM MS", + "Ġref resh", + "ĠInd ices", + "Ġimplant able", + "sh uffle", + "ĠHe ath", + "Ġcr isp", + "Ġdiss ertation", + "ĠUl t", + "Des cription", + "ĠOrig inally", + "ĠF n", + "ĠF LOW", + "ub ility", + "Ġexam s", + "ĠSh or", + "ĠCd Te", + "psy cho", + "Ġdict ates", + "Ġparench ymal", + "ĠPret reatment", + "Ġrememb ered", + "Ġb ras", + "oti d", + "Ġrecomm ender", + "Ġfles h", + "p itch", + "in ist", + "Ġb title", + "Ġl c", + "ass igned", + "ĠAd visory", + "ĠGene va", + "weight ing", + "ĠAB TS", + "Ġhex agon", + "ovsk ite", + "ĠAPI s", + "Ġbol ometric", + "Ġmultif aceted", + "i ak", + "Ġt n", + "ĠB ibli", + "pro sy", + "ĠJ ama", + "Ġinf rastructures", + "ĠSh are", + "Ġintro gression", + "trans forms", + "Re port", + "ĠTR ANS", + "ĠEX P", + "ĠCB T", + "ĠUbiqu itin", + "ĠThick ness", + "p ub", + "t oxin", + "ĠF riction", + "ĠL AG", + "ma ils", + "ĠIm mediately", + "Ġweak est", + "ĠMR S", + "Ġcalcare ous", + "b ath", + "Ġc g", + "ur ational", + "ter o", + "ĠIn oue", + "Ġinstruct or", + "ac ceptor", + "ĠE volving", + "ĠL uther", + "Ġres igned", + "ĠCh ond", + "ER F", + "Ġselect or", + "Ġnewsp apers", + "G RA", + "S pe", + "V H", + "r A", + "ot ine", + "ĠF ACT", + "com position", + "rid ing", + "PC M", + "Ġmiddle ware", + "ĠGR P", + "Ph osph", + "ĠEP IC", + "spe ech", + "vor tex", + "ĠHers chel", + "am is", + "ot ube", + "ĠG omez", + "com yc", + "ĠPh yto", + "ĠCons erv", + "Ġcav a", + "arr hyth", + "ĠRestric ted", + "il icity", + "og ap", + "CT P", + "ĠLat ino", + "atten uated", + "m obility", + "an en", + "Ġn if", + "ĠV ideos", + "ĠSch ubert", + "Fe atures", + "oprop anol", + "ĠThird ly", + "at ula", + "ĠC emetery", + "enti st", + "Ġdel i", + "tri als", + "Ġgran ulation", + "TT G", + "Ġtele ost", + "mor ill", + "or se", + "otyp ically", + "ĠAb ility", + "Amin o", + "a queous", + "Ġp CO", + "ec on", + "ĠL ign", + "ess els", + "Ġform ulating", + "ĠTo o", + "ĠTrans lational", + "ours es", + "ubiqu it", + "stat istic", + "Ġburst ing", + "Ġestu aries", + "ĠNanoc om", + "Ġex ports", + "Ġà ª", + "cont aminated", + "Ġtub ing", + "Ġautom obile", + "Ġmiss ile", + "Ġhierarch ically", + "Ġrepair s", + "ĠImpro ves", + "ĠEFFECT S", + "Q Ds", + "ro z", + "ar ic", + "Ġpar sed", + "ĠBr ink", + "Ġprogress ing", + "Ġperm Neigh", + "A gg", + "Z X", + "s ink", + "Ġw ise", + "et ence", + "ĠI c", + "lo o", + "me ida", + "Ġpolar iton", + "ĠConn ections", + "Ġhall marks", + "Long rightarrow", + "Ġthe ater", + "es ar", + "Ġre imburs", + "Ġlog o", + "Ġexc reted", + "ĠNo isy", + "Ġleak s", + "ĠDa ph", + "Ġparagraph s", + "Ġlandsl ides", + "Ġprecl ude", + "Ġcoerc ivity", + "ĠBurkholder ia", + "ĠGó mez", + "p rice", + "The ory", + "sur gery", + "f name", + "f ailure", + "l iness", + "re fer", + "ri que", + "ĠD ogs", + "ĠE UV", + "ĠV apor", + "CS R", + "Big gl", + "Con straint", + "gra vitational", + "Ġcombinator ics", + "Ġartic ulated", + "ĠBax ter", + "ĠUltrason ic", + "L TE", + "l op", + "Ġinter atomic", + "int uitive", + "sim plex", + "Ġexperiment ed", + "organ izing", + "ĠOs aka", + "had ron", + "Ġdendrim ers", + "ĠElse vier", + "C IP", + "ĠB AP", + "ĠAl onso", + "art et", + "anti s", + "Ġextrac orporeal", + "Ġpow dered", + "ĠSet tings", + "et allic", + "ĠT EC", + "ĠA rena", + "Ġan od", + "ĠRe agents", + "lic enses", + "ĠRem ove", + "Ġpron unciation", + "thin space", + "ĠClin ically", + "g ative", + "Ġw age", + "ĠH ap", + "ĠG rac", + "ff t", + "Ġform ate", + "inf eld", + "ĠQu in", + "Ġglomer ul", + "W ay", + "Ġk ills", + "Ġtrans versely", + "vari ation", + "enn as", + "ĠPL L", + "Ġinstrument ed", + "ĠSpar k", + "Ġp illar", + "ĠC aval", + "ad ers", + "iss en", + "sc ene", + "other m", + "é es", + "Ġprac ticing", + "ĠBM SCs", + "ĠFernand ez", + "Ġbes ide", + "f ew", + "ĠC ru", + "Ġpro d", + "and ers", + "az oline", + "Ġleg islative", + "bal ances", + "UR L", + "Ġstere otactic", + "Ġtrib es", + "Ġá¹ ¼", + "ĠPAN I", + "adren o", + "got ten", + "c ranial", + "ĠM ick", + "ĠM MC", + "ad iazole", + "enti ation", + "ĠGl n", + "ĠHol stein", + "ĠExpl orer", + "Ġos se", + "arth y", + "ĠEV ALU", + "adrenal ine", + "J J", + "ĠC MA", + "ĠIn activation", + "AB S", + "ĠST Z", + "Con figuration", + "ĠOl factory", + "ĠSulf ur", + "symbol s", + "ĠA STM", + "di vergence", + "ĠO CR", + "med ical", + "Ġview er", + "Ġbomb ardment", + "f air", + "n ice", + "el berg", + "ĠG PT", + "ĠK ow", + "Ġphot osphere", + "Ġlab ile", + "ĠSh ang", + "Ġhom otopic", + "SV D", + "bec omes", + "Ġgon or", + "Ġdeuter on", + "Ġphylogen ies", + "ĠS AF", + "rap ment", + "ĠCH F", + "Pl an", + "ĠLeg al", + "ĠFred holm", + "Ġshar per", + "Ġnanor ib", + "ĠBuff alo", + "B MD", + "Ġl g", + "os up", + "ĠO PC", + "Ġend ophytic", + "AT R", + "ĠExpression s", + "ĠMus ical", + "Int roduction", + "ĠSL M", + "ç ois", + "ogly cos", + "agl ia", + "m ussen", + "form ans", + "Ġsub structures", + "ym pan", + "ha e", + "sh i", + "ĠInter pret", + "Ġcat abolic", + "Ġoccup ations", + "ĠBif urc", + "Hydro xy", + "ĠKau f", + "s leep", + "am as", + "ĠS f", + "ĠM BP", + "Ġnon alcoholic", + "Ġdisc ordant", + "Ġep igen", + "PR I", + "ĠRed shift", + "war n", + "Ġlap top", + "Ġabras ive", + "îĤ Ŀ", + "l h", + "ĠK nee", + "Ġsc rat", + "Ġpol oidal", + "ĠUn iv", + "omy osin", + "ĠAug mented", + "Ġtaxon om", + "Zr O", + "Ġphytochemical s", + "it en", + "ĠP atterson", + "th ym", + "di hydropy", + "Ġk y", + "ĠMeta zoa", + "ALL Y", + "Ġretin oblastoma", + "concaten ate", + "M ale", + "Ġo mission", + "ic her", + "ĠA zer", + "op p", + "ple asant", + "ning ham", + "Ġax ially", + "HD FS", + "Ġfic tional", + "Ï «", + "Ġn arc", + "Ġunder took", + "Ġmicro circ", + "ON LY", + "IV ER", + "ĠCy cles", + "Me as", + "ĠGriff in", + "ĠPli ocene", + "Ġp I", + "ĠA viation", + "ĠC ategorical", + "ĠN ils", + "Ġresid ency", + "ĠLa ur", + "Ġpref ers", + "Ġassert Equals", + "Ġliqu or", + "d M", + "os perm", + "ĠF UT", + "Al O", + "Ġcyt ometer", + "Ġstabil izers", + "Ġprem ium", + "Ser ial", + "ĠWalk ing", + "íķ ľ", + "Ġconfron ted", + "encaps ulated", + "C ard", + "ĠS eeds", + "ab ular", + "uk ov", + "List ener", + "Cho ose", + "ĠSj ö", + "M ake", + "Ġis oc", + "am ount", + "AT C", + "ij a", + "Ġsul cus", + "ĠMö bius", + "ĠPren atal", + "Ġ ß", + "Ġis ochron", + "Ġbe ans", + "ĠD ens", + "ĠW elling", + "ĠO man", + "St ats", + "ĠVal id", + "ĠRew ard", + "G K", + "Ġâ ©", + "Ġelectro poration", + "ĠSNR s", + "Ġgar lic", + "ĠParticip ant", + "ĠSplit ting", + "ĠMeteor ological", + "morill onite", + "Ġo edema", + "ĠD ots", + "ĠCl are", + "Ġstar ter", + "Ġclim atology", + "Ġcomm ence", + "Ġfall en", + "ĠAu NPs", + "attr s", + "Ġconsult ant", + "tw isted", + "Sol ving", + "Ġcoerc ive", + "ë¡ ľ", + "K ar", + "Ġs tit", + "ĠS SB", + "ĠI W", + "Ġcan vas", + "py ruvate", + "methyl sulfanyl", + "Ġast rophysics", + "ĠTra ditionally", + "Ġexcit onic", + "w ear", + "ĠT in", + "ros h", + "ĠCl ient", + "ĠCor rections", + "ĠPop ular", + "ĠLiqu ids", + "f inder", + "Ġst ran", + "pl ine", + "ore lla", + "Ġinc ur", + "Ġar che", + "Ġmed ically", + "M ur", + "p eter", + "Ġbe verage", + "ĠN Ws", + "Ġfol ic", + "Ġspec ulative", + "Ġà ±", + "Ġrib bons", + "ĠPri est", + "Qu anti", + "Ġundist urbed", + "at che", + "ass i", + "ĠPer forming", + "ĠEl ong", + "Ġmatch ings", + "Ġfranch ise", + "g io", + "ĠS arg", + "Ġab oard", + "cycl odextrin", + "ĠTH ER", + "Ġsatur ate", + "ĠKin ematics", + "Ġpeptid oglycan", + "ĠShel f", + "toc opherol", + "B ottom", + "m ith", + "r dx", + "z os", + "Ġt RNAs", + "ut f", + "EN A", + "Ġless on", + "Ġpolar on", + "br aska", + "Ġath letic", + "Ġscram bled", + "Ġpursu ing", + "Ġbod ily", + "Ġc ac", + "im en", + "ĠI κB", + "ĠR ö", + "ĠR FC", + "ĠL PC", + "Ġi Ïī", + "Ġdi ary", + "Ġqueue ing", + "ĠDiver gence", + "ĠShig ella", + "ĠUltrast ruct", + "Ġtri phosphate", + "ĠIm plant", + "Ġfer rous", + "ĠBur ton", + "ĠHert z", + "f abric", + "t uring", + "ĠS SM", + "og rad", + "Ġmet azo", + "Ch ang", + "Ġadip ogenesis", + "Ġnu isance", + "Ġanonym ity", + "Ġrefriger ator", + "ì ľ", + "oc tion", + "Ġsp aring", + "Ġch alcogen", + "Ġobserv atory", + "Ġbo oster", + "ĠAnd ré", + "ĠST O", + "yr yl", + "ĠED X", + "ĠDen ver", + "Ġhomogen ate", + "Call back", + "a C", + "h ours", + "k ova", + "ĠA UD", + "Ġsp are", + "Ġpart ons", + "ĠInt ake", + "Ġrecogn izable", + "ĠGold stein", + "Ġstriking ly", + "Ġsan itation", + "F inder", + "G eneration", + "b oy", + "t am", + "ĠR PM", + "iv ious", + "yl ak", + "oph iles", + "Ġpri est", + "Ġeas iest", + "Ġdeliver ies", + "El mer", + "Ġzircon ium", + "ĠMish ra", + "Ġâ Ķ", + "ĠW DM", + "Ġper id", + "ĠZ T", + "Ġlocal izes", + "ĠOR s", + "ĠID O", + "Ġple asant", + "ĠMW CNTs", + "ĠJim my", + "ĠYe h", + "g athered", + "k il", + "ĠK appa", + "Ġcar toon", + "ĠHe uristic", + "Ġs z", + "Ġor ifice", + "Ġman nit", + "ĠCO MM", + "IC K", + "Ġfar mer", + "ĠSil encing", + "Ġprefix es", + "q c", + "ine urin", + "Ġinf lated", + "ĠRe z", + "Ġhydro dynamical", + "Ġoscill ate", + "Ġpedest rians", + "ĠAngi otensin", + "ĠVisc osity", + "Ġoligodend rocytes", + "Ġparo tid", + "Lay out", + "rageen an", + "Ġ è", + "Ġm N", + "Ġdo zen", + "ex clusion", + "Ġpan ic", + "ĠPD I", + "Ġtw entieth", + "ĠElect roph", + "Ġmicrobi ology", + "Ser ver", + "ĠParticip ation", + "D ET", + "P oss", + "ĠN emat", + "ĠN RF", + "arg uments", + "Ġam ylase", + "Ġarg v", + "Ġresol ves", + "Ġrevis ions", + "Pack et", + "T ools", + "Y E", + "Ġt ire", + "in duction", + "as ive", + "ton ic", + "é m", + "car rying", + "ĠImmun oblot", + "ĠIP F", + "Ġdeterior ated", + "Ġjuris diction", + "rele ased", + "osm otic", + "Ġrestaur ants", + "ï ¸", + "ĠN m", + "Ġfl ips", + "Ġsepar ability", + "ĠRec ursive", + "Ġpast ure", + "ĠÄ ī", + "Ġblast ocyst", + "M CP", + "T b", + "u ene", + "es ulf", + "ess im", + "Ġhe n", + "ĠK ull", + "yl um", + "are v", + "ues ts", + "ĠZ ip", + "Ġbo ats", + "Com mand", + "Cont inu", + "ĠBog oliubov", + "Ġmannit ol", + "K now", + "Ð ³", + "ĠH ack", + "Ġmass ively", + "ĠAll oys", + "ĠCD P", + "ĠStere o", + "ĠElectro de", + "Ġisofl av", + "Ġinteroper ability", + "ĠAdela ide", + "ĠP PD", + "ĠK ou", + "Ġdi ap", + "Ġcons erve", + "Ġaggreg ating", + "Gl uc", + "Ġî ģ", + "Ġg ust", + "ĠLe b", + "ET IC", + "ĠCons ol", + "ĠMor ita", + "Rel ative", + "Ġpale o", + "Ġwitness es", + "ĠLaure n", + "azep ine", + "ĠT Y", + "ĠI di", + "ĠM ent", + "ĠR CA", + "igen in", + "ĠDef ence", + "Ġpy rimidine", + "ĠTi N", + "Ġendot helin", + "Ġpand as", + "Ġswallow ing", + "Ġconges tive", + "Ġv inc", + "ĠD IP", + "ĠH ough", + "Ġz w", + "ĠKim ura", + "represent ations", + "ĠProm ote", + "ĠTer ry", + "Ġhat ched", + "look up", + "Elect ron", + "Ġdorm ancy", + "Ġres ign", + "Ġval uations", + "Ġmake up", + "ĠAm y", + "CL UD", + "SE P", + "tub ule", + "Ġsoldi er", + "ĠT z", + "ĠT rump", + "ĠK ramer", + "con i", + "Ġeng raft", + "Ġvacu ole", + "Ġreplic ating", + "iton itis", + "ĠBacter i", + "vacc inated", + "ol t", + "ĠA hn", + "Ġan em", + "ĠB IT", + "ge o", + "Ġmicro gravity", + "ĠSh ir", + "ĠWorld wide", + "Ġign or", + "ĠË ĩ", + "Ġlubric ation", + "j ava", + "v t", + "Ġ yl", + "Ġh ills", + "ĠF OL", + "Ġbasal tic", + "Ne ill", + "ĠEthiop ian", + "ĠNOT CH", + "ĠMOS FET", + "le aving", + "ĠP ter", + "ĠW eld", + "ap le", + "Ġsand wic", + "Ġaz ide", + "ĠStim uli", + "Ġl izard", + "ĠC inc", + "ĠH ain", + "ical s", + "Ġcontact ing", + "ĠMar x", + "Ġpsych otherapy", + "ĠRet in", + "Ġcatheter ization", + "ĠNanopar ticle", + "ĠT CC", + "ver mectin", + "ĠB ASE", + "Ġnot or", + "Ġelectron ically", + "ster oid", + "Ġbil aterally", + "Ġneph ritis", + "Ġirr itation", + "ĠProlong ed", + "Y our", + "he uristic", + "ur geon", + "Ġleft most", + "ĠRE VIEW", + "Ġgast rectomy", + "ENT IAL", + "Me ans", + "ĠDys on", + "Ġbrand s", + "yield s", + "mercap to", + "r ub", + "oun cement", + "err no", + "Ġview ers", + "but an", + "ĠMal ay", + "ylind rical", + "Ġpromin ently", + "Ġescap ing", + "Ġquer ying", + "Stor age", + "F os", + "c odec", + "Ġc M", + "str ates", + "gl ove", + "ĠTra jectories", + "Ġster ol", + "Ġhemat opoiesis", + "Ġcup rates", + "O k", + "d aily", + "ĠB IO", + "ĠL ICENSE", + "ell ations", + "ass y", + "SU RE", + "Ġep inephrine", + "Ġdown wards", + "cor ner", + "assert True", + "ĠẠij", + "ĠSou za", + "M AG", + "por ph", + "Ġeff luents", + "lo em", + "oad dition", + "obut yl", + "eles tial", + "F em", + "m pi", + "ĠR s", + "ell ates", + "ĠK ag", + "Ġunc oupled", + "Ġleg umes", + "Ġomit ting", + "à »", + "ĠT ABLE", + "hal ed", + "ĠÅ ģ", + "Ġmis fit", + "Ġmol ars", + "otechn ological", + "Mark ov", + "Ġpra ised", + "ĠD ab", + "ĠV ij", + "enti lation", + "ĠCh atter", + "Ġbo iled", + "Ġcat ches", + "annot ation", + "Sign al", + "Ġlever ages", + "Ġadvis ory", + "s ong", + "on dition", + "Ġf ug", + "ra ps", + "ĠM CD", + "par ticip", + "ob ian", + "Ġcoun sel", + "ĠPR P", + "edi ol", + "ĠÅ ¨", + "Ġbr uce", + "Sh anghai", + "Data Frame", + "ĠCorrespond ingly", + "Ġacryl amide", + "f ellow", + "l ob", + "ig t", + "ĠC rystallization", + "Ġind omethacin", + "ĠPD R", + "gi ate", + "ĠPan els", + "complex es", + "ĠNic ol", + "Ġfoli ar", + "c ubic", + "Ġd E", + "ĠC CM", + "pl ating", + "Ġres istors", + "ĠG az", + "Ġover turn", + "Ġrep ress", + "Ġpot s", + "ĠPI K", + "Ġderm is", + "Rep resent", + "ĠAnders son", + "Ġretrotrans pos", + "A SA", + "C ounter", + "T et", + "im in", + "per formed", + "ĠN ept", + "Ġhe el", + "rol d", + "ai res", + "Ġread ability", + "Ġbenef ited", + "Ġpuls ation", + "ĠBal ancing", + "Ġevapor ator", + "Ġeig ens", + "ĠH ospit", + "Ġtra ils", + "ĠCo ordinate", + "acc ase", + "ĠHR MS", + "sign aling", + "ĠNP Y", + "Ġamelior ated", + "tu ples", + "Ġmetas urface", + "ĠFrances co", + "Ġspo of", + "îĹ ¸", + "F u", + "J K", + "e j", + "Ġg oss", + "ĠH im", + "Ġprior itized", + "Ġmes othelioma", + "idx s", + "Ġrecon naissance", + "Ġlam ps", + "ãĢ Ĥ", + "Ġreform ulation", + "Ġdeli rium", + "ĠN PR", + "ĠG amb", + "ill as", + "---- -", + "Ġdr illed", + "ĠGen otyping", + "ĠBl ank", + "Ġprop eller", + "Ġcere als", + "ĠAir borne", + "ĠPhot ocatalytic", + "ĠCav ity", + "Ġdol phins", + "Ġsg RNA", + "underst ood", + "e ous", + "Ġs ax", + "ol ayer", + "ĠP end", + "ĠG ET", + "cl ed", + "Ġà ¼", + "Ġcyt osine", + "Ġparallel ization", + "MM s", + "ĠOrgan isation", + "Mod els", + "Ġaccommod ated", + "Ġchol est", + "Ġin activity", + "ĠB oss", + "ĠG CS", + "Ġso aked", + "ĠSec reted", + "Ġvacu olar", + "zo an", + "ĠGre ene", + "Ġbol t", + "b j", + "ĠT all", + "Ġst or", + "ĠM ob", + "Ġbl urred", + "IN O", + "ĠMet allic", + "uff s", + "ĠNOT E", + "Ġsonic ated", + "obuty ric", + "Ġt DCS", + "ĠN es", + "osp ir", + "we igh", + "ĠReg ulator", + "Ġhem olysis", + "Ġsound ing", + "Ġcruc iate", + "Ġcaps aicin", + "ĠTy rosine", + "ĠT ell", + "ĠP EP", + "ĠR c", + "ĠE ating", + "ĠGo als", + "ure t", + "Ġear n", + "Ġcolle ges", + "Ġchemo attract", + "Ġá» ¹", + "ĠEch ocardi", + "F ort", + "s odium", + "am ined", + "ĠN PP", + "ĠK alu", + "Ġdec ipher", + "tet ramethyl", + "ĠOP N", + "stra ight", + "Ġtemp ered", + "ĠHind u", + "ĠOrd inary", + "ĠACh E", + "J NK", + "f os", + "v cpu", + "en amide", + "ĠC rack", + "ap ical", + "Ġanti serum", + "tri plet", + "dec ision", + "Ġcanc els", + "ĠPM N", + "Ġporph y", + "ĠD SA", + "Ġsub matrix", + "Ġj as", + "Ġrep tiles", + "ĠSo on", + "ĠStat istically", + "Ġlever aged", + "Willi ams", + "F LD", + "f olk", + "Ġb ang", + "ĠS CL", + "ass es", + "Ġtend ons", + "found ed", + "ĠRick etts", + "in set", + "Ġsp un", + "Ġun ramified", + "Ġra pe", + "ĠZ Z", + "ĠNe bula", + "Ġthromb otic", + "ĠBor on", + "ĠArg on", + "pool ing", + "ĠMarg inal", + "Ġfellow ship", + "Ġerythrop oietin", + "mathp zc", + "x L", + "ĠS ik", + "ĠB ayer", + "Ġover dose", + "ĠCO I", + "ĠLes ions", + "ĠCompe titive", + "ĠODE s", + "w rap", + "ach lor", + "Ġsub ordinate", + "ĠY Ba", + "head ed", + "Ġgrass es", + "Ġbir ational", + "ĠJeff rey", + "Ġmold ing", + "Ġlid ocaine", + "ĠFOX O", + "termin ated", + "ĠâĩIJ âĩĴ", + "ĠM EL", + "tic ulum", + "Ġr é", + "Ġcl aud", + "Ġj amming", + "St atic", + "Ġtribut ary", + "at et", + "ed onia", + "ĠC MP", + "ĠV N", + "rep resents", + "SO URCE", + "uck land", + "ĠInterest s", + "ĠNan oscale", + "ocon jug", + "Ġcatalog ues", + "ĠActin obacteria", + "F ixed", + "b asal", + "Ġanti parallel", + "Ġconf using", + "Ġmark ings", + "Ġdistinc tions", + "ĠHu a", + "ĠWat ts", + "Ġnanoflu id", + "Ġdiffract ometer", + "L ater", + "m igration", + "Ġco planar", + "Ġhyp omethyl", + "PD S", + "SO s", + "Cor respond", + "Ġelucid ating", + "IZ ED", + "E Vs", + "g art", + "m Tc", + "ĠT UR", + "ur acies", + "Ġfollow er", + "Ġhaz e", + "OU TPUT", + "ĠMyel oid", + "BUFF ER", + "C amp", + "an im", + "ĠT ES", + "ĠC ricket", + "ĠP aired", + "ĠP AGE", + "ĠB id", + "Ġy rs", + "Ġend ow", + "irc ase", + "ĠSup ported", + "Ġleaf let", + "ĠProm oter", + "Ġconvinc ed", + "l iers", + "he ra", + "Ġs eller", + "ag reement", + "Ġun ary", + "onstr ucted", + "Ġrest rained", + "ĠPet ersen", + "Anal og", + "Ġexacerb ations", + "Ġperfor ated", + "ti ds", + "ĠM SH", + "ou i", + "ĠCor i", + "ĠCr uc", + "Ġfract uring", + "Ġinfer tile", + "ĠPRO BLEM", + "ĠFried mann", + "Ġspectrophot ometry", + "ERG Y", + "ot us", + "pro posed", + "ĠMo isture", + "ĠNo ether", + "ĠLa unch", + "ĠLear n", + "Ġven a", + "Ġfasc i", + "Ġquies cence", + "ĠP rand", + "ĠCon vert", + "Ġtri age", + "AN E", + "Ġfeed stock", + "ĠdB m", + "Ġneo formans", + "G SE", + "ĠA PE", + "Ġcardi ometabolic", + "Ġmagnet ometer", + "En vironment", + "Ġfulf ills", + "ĠMang anese", + "B MP", + "ĠR atios", + "ist able", + "ass ume", + "Ġresp ected", + "Ġsc ars", + "Ġsup porters", + "ĠAug mentation", + "Ġglycos ylated", + "ĠUltra fast", + "Ġdemethyl ation", + "metast atic", + "c ylinder", + "Ġh ang", + "ĠM AV", + "dis joint", + "pha rose", + "ĠLe banon", + "PI s", + "lab eling", + "Ġneutral ino", + "ĠSO CS", + "xc b", + "ĠTerr itory", + "ĠPolic ies", + "K ing", + "Ġall ied", + "Ġsatur ates", + "mus cle", + "odim ers", + "Ġb t", + "ĠH ang", + "ĠE b", + "Ġch imer", + "Ġnot ational", + "Ġcol der", + "ult z", + "trans verse", + "HO UT", + "ĠKar ls", + "tors ion", + "J I", + "Ġm ari", + "em on", + "Ġlogarithm ically", + "ĠâIJ ¦", + "ìĿ Ħ", + "Ġa eration", + "Ġs oma", + "ĠS omal", + "Ġsp oil", + "di ver", + "Ġbreak points", + "ĠHar mon", + "Ġpharmac ologic", + "ĠM osquito", + "ĠMod ifications", + "Ġadj o", + "ĠPa pers", + "gener ally", + "ïĺ ¹", + "T ARGET", + "ĠP rix", + "oc aps", + "ĠE in", + "Ġmicro grid", + "ĠInter play", + "Ġcop ying", + "Al pha", + "ĠSl ope", + "ĠLip ofectamine", + "hig hest", + "D RO", + "ĠH ipp", + "Ġsh aken", + "Ġunder line", + "Ġfil med", + "mat urity", + "ict ure", + "IL S", + "Sp an", + "Ġinver ters", + "QU E", + "determ ining", + "Ġeosin ophilic", + "D Y", + "ĠL ID", + "ĠG ig", + "Ġintra epithelial", + "Nb O", + "fre edom", + "Ġass ured", + "ĠAr che", + "ĠSub stitution", + "ĠSri vastava", + "ĠMoz amb", + "Ġa ro", + "or c", + "ĠI brahim", + "ĠD ST", + "Ġab l", + "Ġx er", + "ount able", + "Ġloss less", + "Ġconcentr ating", + "Ġstain s", + "ĠSol ve", + "continu ity", + "ĠTor r", + "Ġpit falls", + "best os", + "Other wise", + "adhy ay", + "b ard", + "ĠC AA", + "ode tic", + "Ġast hmatic", + "Ġrational ity", + "ĠYork shire", + "neighbor hood", + "Ġhero in", + "Ġscatt erers", + "ĠH earing", + "ĠE FT", + "ĠN urses", + "ĠG LI", + "ĠZ eta", + "ĠNe igh", + "Ġvent ure", + "Ġtoxic ological", + "Ġroll s", + "f v", + "Ġc rick", + "Ġd Ïī", + "av ia", + "eld er", + "Ġinv ade", + "ext racted", + "ML P", + "ĠPA I", + "ĠMell itus", + "Ġbruce i", + "g pio", + "em otional", + "ĠD ale", + "ĠE z", + "Ġtrans activation", + "Ġquanti les", + "Ġnucle osynthesis", + "ĠAm el", + "Ġchrom ophore", + "Ġliter ally", + "band width", + "ato hepatitis", + "Ġultraf iltration", + "Mart in", + "Ġangio plasty", + "inser tion", + "D an", + "s queeze", + "us r", + "uc onazole", + "ĠF AR", + "Ġsh adows", + "Ġim itation", + "ĠK ann", + "hes i", + "Ġmic ellar", + "ves ter", + "ĠPer se", + "acet amol", + "GR APH", + "ĠAI PS", + "Ġprompt ly", + "anch or", + "Ġischa emia", + "p ump", + "Ġm afic", + "Ġl azy", + "ĠC EL", + "ĠG orenstein", + "ĠW GS", + "Ġsign ifies", + "Ġspl ines", + "determ ination", + "Ġrelay ing", + "piper azine", + "Ġsyncy tial", + "ĠA ub", + "ĠD X", + "Ġorth otopic", + "ĠLink age", + "Ġharmon y", + "ĠKaz akh", + "ĠVlad imir", + "Ġp ray", + "im olar", + "Ġgra yscale", + "Ġanaly st", + "ĠTrans l", + "Ġmen iscus", + "ĠMed ica", + "osa urus", + "ĠÐ ²", + "Ġinfiltr ated", + "Ġâĸ ³", + "Ġsacc ades", + "Ġdisent angle", + "H art", + "f ined", + "Ġb icycle", + "os itory", + "un likely", + "ere phthal", + "ĠL ia", + "Ġgroup ings", + "Ġcategor ize", + "Ġbioge ography", + "ĠAPPRO ACH", + "ĠN ing", + "ĠG rap", + "vers a", + "Ġradi ologists", + "ĠRec ording", + "Ġbo iler", + "add ers", + "C andid", + "M Q", + "Ġb w", + "ĠS ector", + "ĠH IT", + "ĠE SCC", + "ess ence", + "ore an", + "est yles", + "SU CCESS", + "ne in", + "ult ra", + "ram p", + "Th omas", + "ĠPre par", + "ĠInstit ut", + "Ġherb icide", + "ĠCha otic", + "Ġsph incter", + "Ġcompac tifications", + "C lear", + "Tr p", + "Dec oder", + "Ġsap phire", + "ĠIda ho", + "per sing", + "ch iral", + "ĠDis charge", + "According ly", + "ĠArth ritis", + "ĠJane iro", + "n j", + "ĠK d", + "Ġout lets", + "Ġsuscepti bilities", + "Ġdiver ged", + "Ġroll er", + "su fficient", + "clust ering", + "ĠTeh ran", + "Ġt b", + "bl ank", + "Ġdigit ally", + "Ġnecro tizing", + "F ALSE", + "Ġwh or", + "err als", + "ĠMo tivated", + "enz ae", + "ĠRef inement", + "Ġtick et", + "Ġprotr usions", + "ĠDonald son", + "ĠB eth", + "Ġsp uttered", + "Ġaut ocrine", + "cop ene", + "Ġcoll ar", + "Ġupper most", + "Ġoxygen ated", + "Int ro", + "âĨ IJ", + "ĠHip po", + "Ġd une", + "id ines", + "ĠH ä", + "Ġreg i", + "Ġno is", + "Ġphot odiode", + "ĠFe b", + "mut ated", + "ĠCF L", + "step ping", + "Se lection", + "ĠWeb ster", + "ĠHER A", + "indic ating", + "Ġtraine es", + "R ot", + "ĠF AK", + "ĠAs n", + "Ġfat s", + "fol iation", + "Ġartic ulation", + "Ġcus ps", + "ĠJenn ifer", + "Ġin timately", + "ĠP ing", + "so v", + "ox ious", + "hyd rate", + "ĠArch ives", + "Gon z", + "Ġ é", + "Ġch l", + "ĠO LS", + "cop h", + "Ġair line", + "Ġfo etal", + "ĠRoll ing", + "ĠGENER AL", + "O NAL", + "ag ons", + "ĠD orsal", + "Ġr itual", + "but yrate", + "ogl ut", + "Ġhex a", + "ĠSy ria", + "Ġont ogeny", + "ĠFB G", + "cover age", + "Ġtachy on", + "ĠPerman ent", + "l um", + "Ġs v", + "Ġo o", + "en ergetic", + "al titude", + "In c", + "ĠNe braska", + "ĠRE SP", + "Ġdys biosis", + "Ġmarket ed", + "oxic illin", + "ĠBroad cast", + "racycl o", + "ĠFif teen", + "ĠNar ayan", + "Ġlett uce", + "ore a", + "Ġinter cepts", + "Ġwork station", + "ĠPl ains", + "CC L", + "Ġorient able", + "ĠBo osting", + "ĠSO I", + "ĠCheck ing", + "ĠFIF O", + "Ġin sets", + "ĠS RT", + "Ġac rom", + "own er", + "MI X", + "ĠAr b", + "Ġfa eces", + "ĠCarl son", + "Ġperiv ascular", + "infiltr ating", + "Ì ħ", + "Ġm alle", + "oc ate", + "ĠB old", + "unc tive", + "ex cess", + "Ġlo osen", + "Ġprior itization", + "Ġannot ate", + "Ġgram mars", + "Ġb red", + "Ġex ocytosis", + "ĠD ahl", + "ath yroidism", + "vel i", + "Ġop ted", + "Ġsm oked", + "ĠPl ates", + "EM G", + "RO W", + "IF IC", + "OL S", + "oreg ulatory", + "Ġwhisk ers", + "secret ase", + "Ġexagger ated", + "ĠB ib", + "de formed", + "Ġz ur", + "rop ine", + "Ġpair ings", + "chrom osome", + "Ele ments", + "prior ity", + "Ġlyophil ized", + "ĠChaud h", + "W ilk", + "ĠC ation", + "ot ta", + "Ġnon convex", + "Ġdep olymer", + "MM ARY", + "Cont rolled", + "carbox y", + "Ġaugment ing", + "Ġappoint ments", + "Ġtravers ed", + "ĠF letcher", + "Ġexp iratory", + "Ġele phant", + "ĠBl ocks", + "ĠFlu ids", + "wall s", + "incre ased", + "propan amide", + "ĠAka ike", + "ĠC BM", + "ĠE cho", + "ad missible", + "Ġdis assembly", + "Ġar Xiv", + "ick e", + "LI ST", + "phen otype", + "ĠProv incial", + "leg end", + "P AS", + "r nn", + "s and", + "Ġb ariatric", + "ĠP ush", + "ĠAp oE", + "cap rolactone", + "model ing", + "ĠÅ µ", + "Ġsupercapac itors", + "or on", + "Ġp K", + "st rophy", + "ĠS uc", + "und a", + "te am", + "Ġit iner", + "Ġsw ell", + "ĠBio active", + "ĠIndic ators", + "ĠI FT", + "ĠD K", + "Ġcap it", + "sh apes", + "Ġtrac hea", + "delay ed", + "ĠGuang dong", + "L epid", + "T GA", + "h k", + "ol on", + "ogen in", + "ĠAc k", + "Ġlog ically", + "cont ributions", + "ĠCle avage", + "hur st", + "b dd", + "ST D", + "ĠF ut", + "te k", + "ĠIn her", + "Ġchem is", + "Ġbreak point", + "estim ates", + "ĠOtt oman", + "ĠNaf ion", + "WID TH", + "Ġs izable", + "ĠT su", + "emb olic", + "Ġright most", + "ĠCell ulose", + "iction aries", + "ĠMy coplasma", + "ĠBur gers", + "ĠKepler ian", + "U CTION", + "V B", + "Ġb cc", + "ra id", + "END IX", + "Ġsc oping", + "ĠPR I", + "ĠCd Se", + "ĠGre edy", + "ĠHam mer", + "ĠBacter oides", + "inform ative", + "Ġresemb led", + "yll ium", + "T wenty", + "Ġp ounds", + "Ġun polarized", + "Ġconfig ure", + "Ġtranscription ally", + "Ġmicros cale", + "ĠPut ting", + "Ġpyr rol", + "ĠLAS SO", + "f iltration", + "Ġt ech", + "per forming", + "Al ong", + "ĠCT LA", + "Ġauthor ization", + "UR AL", + "Ġleak y", + "Op tical", + "ĠReve al", + "ĠHUV ECs", + "W u", + "c ustom", + "di ble", + "Ġï£ ¦", + "CD Cl", + "Ġemph ys", + "Ne ut", + "coll agen", + "necess arily", + "ĠRoot s", + "P ose", + "T u", + "Ġcl ue", + "Ġperturb ing", + "ĠHel ium", + "ĠComb ustion", + "n itrogen", + "am plified", + "pro ve", + "ĠSo ils", + "normal ization", + "ĠCH OP", + "ĠMc Le", + "Ġstri kes", + "Ġcrop ped", + "ĠKu o", + "Ġvag al", + "Ġdin ucleotide", + "ĠIsa ac", + "ĠL OX", + "Ġdirection ality", + "Ġchem oradiotherapy", + "calc ulus", + "ĠMoh ammed", + "m apped", + "Ġre forms", + "Ġre ordering", + "ĠB m", + "ĠE SCs", + "ĠN UC", + "th aw", + "Ġnan oporous", + "Ġtrain able", + "ĠAT T", + "fe ats", + "OF DM", + "ĠSH P", + "ĠRich ter", + "Ġspray ed", + "ĠJeff erson", + "F OX", + "b h", + "ot te", + "Ġle iomy", + "osp ores", + "specific ity", + "ĠRef er", + "ĠHa as", + "M ove", + "M aterials", + "t ec", + "u tility", + "en tional", + "ĠM PP", + "ch ond", + "Ġse epage", + "Ġpe ach", + "ĠÎĶ t", + "embry onic", + "Y an", + "Ġlip osomal", + "ĠVal encia", + "ĠEnd o", + "ĠPA O", + "Ġdial ect", + "Ġchond rocyte", + "ĠMill imeter", + "ĠRegular ity", + "dest roy", + "ĠCond ensation", + "Bay es", + "abund ance", + "Ġd U", + "ĠS SI", + "ĠH AND", + "Ġcons ulted", + "Ġsup pliers", + "Ġdem o", + "reg istered", + "Ġmicros omal", + "Ġlam bs", + "respons iveness", + "D y", + "G AS", + "U ME", + "Ġa ero", + "Ġcal modulin", + "Ġcalc ined", + "Ġins ula", + "ĠMe i", + "ĠRE AL", + "Ġcontrac tible", + "ĠEss entially", + "Ġgam ing", + "Ġspill over", + "resid ues", + "â İ", + "ĠE MC", + "ĠSD E", + "ĠSer ine", + "eck i", + "ĠPrinc eton", + "ĠBACK GROUND", + "m asks", + "ĠL om", + "ff ield", + "ef itinib", + "Ġpat ents", + "ĠBe z", + "load s", + "Ġgon adal", + "Ġnitro cellulose", + "âĻ Ĥ", + "Ġth rown", + "Ġrec tification", + "min a", + "isc id", + "ĠBi obank", + "param agnetic", + "GS K", + "ĠDeriv ative", + "criter ion", + "ĠMonth ly", + "ë ¥", + "ĠS ichuan", + "Ġimmun ologic", + "Ġheter otic", + "ĠMc Cl", + "ĠSM ART", + "ĠBatter ies", + "Ġpremi ered", + "Ġcryopres ervation", + "N u", + "val ho", + "Ġfl otation", + "top ological", + "ĠNan jing", + "Ġju xt", + "ĠFed er", + "Ġprofound ly", + "c ad", + "i enced", + "ch uk", + "ĠIn g", + "ĠK SHV", + "amin obenz", + "ĉĉĉ ĠĠĠ", + "Ġmeta ph", + "ĠEpid emic", + "ĠAssoci ate", + "Ġsacc ade", + "Ġd awn", + "Ġre heating", + "Ġsp ell", + "frac tive", + "ĠTo olkit", + "Ġrecogn ise", + "path ogen", + "Ġophthal mic", + "Ġquer ied", + "t hens", + "ith ine", + "um ably", + "Ġstr ides", + "ha ul", + "Ġpass ion", + "Ġdys functions", + "By te", + "Ġca esarean", + "pre y", + "ĠHor se", + "ĠGAB AA", + "N atural", + "k os", + "in ators", + "od ings", + "AR RAY", + "Ġun ipotent", + "Ġelect romy", + "com part", + "Li u", + "encephal ic", + "ĠCOMP AR", + "Ġsymbion ts", + "ivac aine", + "O I", + "P VA", + "ĠN VIDIA", + "cal ibrated", + "Ġqu est", + "NA D", + "ĠX yl", + "Ġpharmac ist", + "direct ly", + "Ġquadrup le", + "ethan one", + "ĠBulg aria", + "Ġovip osition", + "r uns", + "Ġn ociceptive", + "Ġas exual", + "SU LT", + "Ġwould n", + "ĠInd ustries", + "abil izing", + "ĠComp ressive", + "CO OH", + "US H", + "ki ewicz", + "Ġign eous", + "Ġdisapp oint", + "ĠCK M", + "ĠDiagram s", + "ĠF lam", + "ĠG ould", + "Ġco enzyme", + "Ġpar an", + "Ġ ¶", + "Ġprogram mer", + "ĠTrans forming", + "Ġmus carinic", + "onucle otide", + "FI ELD", + "ĠFu ji", + "Ġnond ec", + "Ġblank et", + "Ġpredis posing", + "ĠTrig ger", + "Ġwel come", + "F amily", + "U INT", + "h fill", + "t vb", + "ĠB att", + "Ġun met", + "ĠAp o", + "oti ent", + "Ġfund us", + "ĠLear ned", + "Ġintr usions", + "Ġsolub ilization", + "fund amental", + "ĠSanti ago", + "Ġh pi", + "th row", + "ĠIn to", + "time out", + "Ġthick ened", + "ias m", + "Ġgravit ino", + "bran ched", + "V III", + "Ġo ch", + "Ġg ym", + "ĠK rylov", + "Ġcorrec tive", + "ĠInstit ution", + "Ġcrim es", + "ĠBacteroid etes", + "ĠE hr", + "Ġse ated", + "rol izumab", + "Ġfactor ized", + "rot ational", + "Ġadministr ators", + "âĭ Ĩ", + "ineral ization", + "l ining", + "â Ĺ", + "ur ai", + "ĠF AP", + "ĠF isheries", + "ĠE SO", + "tem per", + "Big gr", + "ĠAltern ating", + "t win", + "am atsu", + "Ġint rad", + "over flow", + "Ġcompar ability", + "Ġsyn optic", + "US B", + "db g", + "dem onstr", + "ĠAch ieving", + "Ġtect onics", + "ĠRand all", + "ĠPrep ared", + "Ġsublim ation", + "ĠB aj", + "Ġcl utch", + "Ġsub domain", + "Ġfl aws", + "inf lu", + "Ġwid ening", + "Ġmel ted", + "Ġadministr ator", + "Ġsubsidi ary", + "ĠP ricing", + "tic us", + "og i", + "ĠAl ign", + "ĠAD V", + "Ġvast ly", + "bench mark", + "Ġprioriti ze", + "R adi", + "ess ed", + "Ġsup rac", + "acc ard", + "Ġbiom imetic", + "ĠIr radiation", + "ĠALG OR", + "Ġpedig ree", + "ĠC MT", + "od ym", + "ĠV ig", + "ĠBi ochemistry", + "ĠAcc um", + "Ind ices", + "hard tii", + "ĠRal ph", + "Ġrumin ants", + "i T", + "on au", + "an er", + "pl anned", + "ever s", + "Ġret roviral", + "Ġquantif ier", + "ĠExt racting", + "Ġacet ylated", + "Or th", + "ĠSen ator", + "Ġnanos econd", + "Ġanticip ation", + "ĠECM O", + "Ġsemic irc", + "ĠCrypt osporidium", + "ĠT ARGET", + "Ġap ples", + "ef ield", + "Ġrem an", + "Ġser ovar", + "ĠTrans actions", + "trans itions", + "urs ions", + "ĠMel atonin", + "Ġcholecyst ectomy", + "ĠAntiv iral", + "h ous", + "Ġo tol", + "Ġm aj", + "Ġe clip", + "are l", + "AT IONAL", + "MI M", + "ĠCI mg", + "ĠEnd omet", + "ĠHay ashi", + "Ġchimpan zees", + "m bf", + "ĠI PV", + "act oring", + "out side", + "ne apolis", + "Ġdisc arding", + "num type", + "ĠRE ST", + "Ġflag ellar", + "ĠChand rase", + "hof er", + "Ġelectrocardi ogram", + "G b", + "m ock", + "o eb", + "ĠS MO", + "ĠM ord", + "ĠB oz", + "Ġmin ors", + "IN LINE", + "Ġtherm ogravimetric", + "ĠMel ting", + "ĠNS W", + "S ham", + "l otinib", + "Ġac quisitions", + "ta z", + "Ġdef aults", + "Ġoscill ates", + "ĠCap tion", + "Ġdisrup tive", + "Ġswe eping", + "ĠTool box", + "Ġureth ral", + "H BV", + "ĠR CS", + "Ġox ys", + "immun o", + "ht m", + "ofl avin", + "H IF", + "ĠS BA", + "ĠC PE", + "Ġwh ites", + "ĠRe actor", + "Ġpur p", + "Ġelectro catalytic", + "Ġnone x", + "Ġty phimurium", + "Ġeu rop", + "conc ave", + "macroph age", + "S ER", + "Ġl apse", + "Ġan atom", + "ĠR AC", + "ta x", + "Ġmin s", + "Ġsens u", + "ĠHe brew", + "Ġreal ism", + "ĠMicro glia", + "Ġserial ized", + "ĠHaz ard", + "Ġmetamorph osis", + "ĠI RA", + "Ġsm earing", + "Ġphot olysis", + "Ġchild birth", + "Ġsil enced", + "raw al", + "Ġquad rants", + "but anol", + "Ġstochastic ally", + "ĠCham bers", + "ĠNav arro", + "Ġproc urement", + "c ite", + "ĠS le", + "ĠH adoop", + "Ġdelay ing", + "At lantic", + "Sp ain", + "fal fa", + "od ialysis", + "yn ia", + "Ġplate aus", + "Ġmultim ode", + "RES ET", + "ĠRock y", + "ĠRodrig ues", + "f MRI", + "r int", + "ĠT AL", + "Ġspec ular", + "con struction", + "ĠAt hens", + "Ġcross link", + "Ġcount ably", + "Ġspread sheet", + "crib es", + "cons istently", + "Ġflood plain", + "E INVAL", + "M aca", + "Ġe i", + "Ġh itherto", + "Ġsem if", + "Ġcontin ual", + "ĠHom ology", + "Ġphotoc atalysts", + "is able", + "ĠH AT", + "Ġpoly hedra", + "ĠMay o", + "Ġlact ating", + "sam pler", + "Ġappl iances", + "T U", + "Ġc hess", + "ĠT ing", + "Ġinv itation", + "Ġdistrib uting", + "ash ima", + "Ġult ral", + "tre nd", + "Ġrelax ations", + "ĠHel en", + "Ġbed ding", + "Ġgland ular", + "Ġincrement ally", + "Ġconce al", + "claim ed", + "ĠEdd y", + "Ġm os", + "ĠT ube", + "ĠT oda", + "ra j", + "ĠM ü", + "ĠU ll", + "Ġun e", + "ber ine", + "Ġpolic ym", + "Ġscholar ly", + "Ġshore line", + "Ġald osterone", + "ĠBruc ella", + "T HE", + "RE AL", + "Ġex ome", + "ĠD AB", + "Ġext ras", + "Ġband ing", + "ĠSi emens", + "ĠBo ost", + "ĠSuper novae", + "ĠTrac ing", + "Ġascorb ate", + "Ital y", + "b und", + "Ġdecre ment", + "Ġneu rophysiological", + "Ġblack body", + "ĠMc N", + "Ġcompet encies", + "osc ape", + "ĠHon ours", + "Ġmas titis", + "criter ia", + "Ġb iaxial", + "Ġth awed", + "ĠF oll", + "ore ceptor", + "Ġinv ention", + "AD s", + "Sh ow", + "-------------------------------- ----------------", + "Ġellipso idal", + "Ġfoc ussed", + "ĠD at", + "ĠR im", + "ĠL X", + "ĠG ER", + "ins ler", + "Ġtop oisomerase", + "Ġhyper lipidemia", + "Ġmy stery", + "Ġnit rification", + "Ġonc ogenes", + "ĠFull er", + "ĠBart lett", + "Ġamphib ians", + "S ST", + "b ly", + "le ads", + "ec ycle", + "am pl", + "ĠP OM", + "ĠD CF", + "str ass", + "anti body", + "non linear", + "ĠBroad way", + "ĠCatal ogue", + "Ġμ A", + "Ġacet aminophen", + "Ġcrystall ites", + "ĠNan otubes", + "ĠAcknowledg ment", + "Ġmetam orphism", + "Ġtwin ning", + "ĠAzer bai", + "x A", + "CC C", + "ĠSol ids", + "pred s", + "ĠMont ana", + "WR ITE", + "R atio", + "Ġp unch", + "Ġr iding", + "Ġac ne", + "ĠU re", + "Ġcor r", + "ĠQ OL", + "Ġins ult", + "Ġdominant ly", + "Ġsubs amples", + "rew s", + "ĠPres ervation", + "ĠAff ine", + "methan one", + "Ġhedge hog", + "J H", + "Ġl ined", + "Ġst en", + "ĠD armstadt", + "ĠL asso", + "Ġde proton", + "Ġsh oes", + "Ġmo tives", + "Ġmic roscop", + "oph thora", + "Ġmac ron", + "Ġencour agement", + "acryl ic", + "ĠTensor Flow", + "W rapper", + "o ise", + "ay ak", + "Ġrep resses", + "Ġpr uned", + "ĠCl ar", + "ĠâĬ ²", + "ĠUnder lying", + "Im plemented", + "Ġswe at", + "Ġmeteor ites", + "Ġtwe ez", + "Ġpros ocial", + "Ġabras ion", + "h ail", + "Ġsh orth", + "ism atch", + "IN TR", + "ĠTr in", + "Ġphysic ists", + "ĠPE O", + "ĠMagn eto", + "ĠJacob son", + "ĠMMP s", + "ĠIntra venous", + "Ġneurotrans mission", + "ĠPneum onia", + "L ind", + "y re", + "Ġm aternity", + "ĠI ris", + "ri atal", + "ĠâĢ ļ", + "med etomidine", + "Ġtr iterpen", + "ĠMan uscript", + "ĠEnd oplasmic", + "ĠPot ter", + "Ġgerm inal", + "Ġphotos ystem", + "Gu ided", + "Ġguitar ist", + "ĠChile an", + "ĠSalv ador", + "É Ļ", + "Ġc elestial", + "om and", + "Ġn k", + "Ġv endors", + "ĠP INK", + "ĠIn organic", + "Ġmod erated", + "SU S", + "ĠJ oshi", + "ĠSt ata", + "ik es", + "oy e", + "ĠJohn ny", + "Le ica", + "Ġka on", + "ĠEquip ment", + "K im", + "g ado", + "t ures", + "Ġe lem", + "ĠA AC", + "ce a", + "od ality", + "Ġan iline", + "Ġex othermic", + "ĠG unn", + "ĠJ U", + "plic able", + "sc apes", + "typ ed", + "Ġinsp iratory", + "REG IST", + "ĠBry an", + "Ġanxi ous", + "ĠCarp enter", + "ĠPharmacokine tics", + "infer ior", + "F rag", + "Z Y", + "Ġo esophageal", + "ĠS uk", + "ĠP ron", + "ĠCD I", + "AG C", + "key words", + "sus ceptible", + "Germ any", + "M AS", + "i C", + "an mar", + "Ġex iting", + "ĠH ale", + "Ġr hamn", + "ind ustrial", + "Ġra ft", + "emb rolizumab", + "Ġdeploy ing", + "Ġsalic ylic", + "R n", + "Ġc ensor", + "Ġd X", + "Ġfor um", + "MS I", + "bl ad", + "Ġsqu ir", + "CP P", + "Ġgrap evine", + "ĠRA FT", + "Mon te", + "Ġmicrof lora", + "r cl", + "Ġdec ap", + "AN C", + "Ġbroad en", + "Ġfre ed", + "Ġsouth ward", + "ĠJac ques", + "Ġrequest ing", + "ĠAsp ect", + "araj an", + "F ailed", + "f printf", + "p ytest", + "Ê ¹", + "ĠC m", + "un til", + "ne iss", + "Ġmon os", + "osp inal", + "ols ky", + "cont rib", + "Con tainer", + "ĠVol unte", + "ĠAtt ributes", + "ĠTum our", + "Ġrein hardtii", + "Ġcentrom ere", + "ĠS ymph", + "ĠA o", + "ag ens", + "ple ted", + "ied er", + "Ġactiv ist", + "ĠAl meida", + "Ġdisturb ing", + "Ġreflex es", + "D SS", + "Ġfor wards", + "ron ym", + "ĠIntegr ity", + "WE EN", + "Ġôı¼ Į", + "Ġfaith fully", + "Ġperic ardial", + "Japan ese", + "ĠCEN P", + "K r", + "Ġdef ending", + "Ġz on", + "ins ensitive", + "Ġlab s", + "ĠCa M", + "ĠEu rop", + "ME A", + "B LAST", + "x N", + "al en", + "Ġcl k", + "line age", + "co ating", + "Ġtail oring", + "CON TR", + "Ġadren ergic", + "interp reted", + "N IH", + "am oeba", + "ĠC yr", + "Ġtri plicates", + "def ining", + "ĠLog an", + "tes y", + "ĠTw ist", + "ĠGram mar", + "ĠSustain ed", + "Ġan harmonic", + "Ġal ve", + "Ġr uler", + "Ġqu anta", + "Ġdirec ts", + "Ġoff loading", + "ĠGe ophysical", + "Re quire", + "Ġhepat oma", + "Ġfo o", + "ĠGeor ges", + "Ġb outs", + "ĠT AK", + "Ġanti diabetic", + "ĠRe ported", + "present ing", + "ĠLay ered", + "REN CE", + "Ġuve itis", + "b age", + "Ġf entanyl", + "ens emble", + "ĠO SCC", + "Ġmin ers", + "lo oking", + "ĠBe er", + "prec ipitation", + "ĠEnter prise", + "Ġseroton ergic", + "Ġsees aw", + "ĠAth letics", + "Ġhydroly tic", + "Ġtal ent", + "Ġdiscern ible", + "F IL", + "l ives", + "ĠS ales", + "ĠS Sc", + "ere nd", + "cl im", + "anti d", + "IN TS", + "Ġatten uating", + "Ġtw ists", + "Ġoxygen ase", + "rin i", + "Maca ulay", + "z m", + "ĠP OT", + "ĠM p", + "ĠH ey", + "ĠO VER", + "ill ion", + "Ġinv aluable", + "Ġanti platelet", + "Ġmut ans", + "Ġgrad uates", + "GR AM", + "isp heric", + "Ġincomp atibility", + "Ġcarboxyl ase", + "Ġbioc ontrol", + "ĠPhysic ochemical", + "ï Ļ", + "Ġl ae", + "ĠA ortic", + "ĠR acing", + "ĠE CD", + "iv ic", + "Ġelect romechanical", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠĠĠ", + "Ġste er", + "ĠOut side", + "Ġaden ocarcinomas", + "ĠKe ep", + "Ġcoc on", + "Ġmoder ating", + "Ġreform ulated", + "Ġfundament als", + "ĠTes la", + "ĠStir ling", + "or ated", + "op id", + "Ġpa rox", + "Ġtri valent", + "Ġexchange able", + "Ġdeb uted", + "V ery", + "re ements", + "ĠT omm", + "ĠC yn", + "ĠC atalysts", + "qu at", + "ĠF ER", + "ĠR um", + "Ġsc anners", + "ĠâĨĴ âĪŀ", + "otrop ical", + "Ġven ues", + "estim ator", + "Ġempt ying", + "G PP", + "V IR", + "Ġcom plicates", + "ĠN IS", + "ĠZ hen", + "ĠBl ues", + "Ġtext books", + "Ġsi xty", + "Ġether s", + "Ġfinanc ially", + "Ġm Health", + "ĠT ut", + "Ġl aryng", + "ĠG s", + "ĠW atch", + "Ġse v", + "Ġit al", + "ass ed", + "Ġà ·", + "ĠCons ent", + "Ġnut s", + "vit real", + "Ġmeta phase", + "Ġtit ania", + "Ġfo ils", + "Ġgal ectin", + "initial ize", + "Ġadvis or", + "Ġadminister ing", + "B ool", + "Ġc em", + "Ġre forming", + "Ġg n", + "ys h", + "Ġatt or", + "SC I", + "Ex c", + "bu ilder", + "Ġcer ium", + "Ġregist ries", + "ĠMatsum oto", + "Ġtempt ing", + "is ha", + "Ġre orientation", + "ĠM old", + "ĠR AGE", + "ys on", + "Ġun equiv", + "Ġrel ocation", + "Ġà ķ", + "ĠRe form", + "ĠRE QU", + "Ġcommens urate", + "catal og", + "ĠT PS", + "Ġl amb", + "Ġpre factor", + "arch y", + "Ġdop ants", + "dr v", + "ĠPAR AMET", + "sched ule", + "ochem ically", + "Ġe Health", + "un as", + "ĠP inus", + "ĠH SA", + "Ġinter relations", + "Ġdep ot", + "ĠPl atinum", + "Ġlif elong", + "Ġpersist ently", + "ĠParad ox", + "ĠConform ational", + "es ophag", + "ĠA AT", + "pl in", + "ĠF CN", + "ĠD t", + "op oside", + "Ġch al", + "Ġhal t", + "ĠDet ect", + "Ġdiscrim inated", + "ĠLag rangians", + "Ap pro", + "ĠÈ §", + "Ġimpuls ivity", + "B AT", + "C hemical", + "g ather", + "ĠU NC", + "int ron", + "ĠSim ulator", + "ĠGl a", + "TT T", + "ĠVol atile", + "Ġsubs id", + "ĠBroad casting", + "Ġstrept ozotocin", + "Ġf umar", + "ĠM PEG", + "Ġinflu enzae", + "sub jects", + "Ġappropri ateness", + "Ġarc min", + "Ġstrand ed", + "o ylation", + "ĠD EX", + "ov iral", + "ĠQu arter", + "col ytic", + "Ġfriend ship", + "H ES", + "l oxacin", + "Ġe re", + "ĠT rad", + "ur istics", + "ĠE CT", + "ĠE GCG", + "ĠL RP", + "ĠG AG", + "ĠIn P", + "Ġcont empor", + "Ġmic ror", + "ier strass", + "ĠElect rosp", + "need ed", + "atmosp here", + "n T", + "Ġband widths", + "Ġdivers ified", + "ĠAppro priate", + "rest ore", + "roc nem", + "ĠLag uerre", + "ĠSong s", + "ĠKalu za", + "ĠS ymmetries", + "ĠSch mitt", + "Ġbiom olecular", + "scale box", + "Ġintra hepatic", + "under standing", + "ĠABC G", + "Ġunderestim ates", + "ĠStream ing", + "Ġfic titious", + "oplasm osis", + "res ident", + "ĠB ary", + "ĠCom a", + "sc rip", + "Ġdeg ran", + "ĠCa MKII", + "ĠBal mer", + "ĠPlas m", + "Ġchel ating", + "ĠParad igm", + "Ġopp onents", + "E K", + "P in", + "Ġm sec", + "ad one", + "ach t", + "CC G", + "EC O", + "normal ize", + "ĠDesign s", + "Ġyellow ish", + "glut amyl", + "Ġdomestic ation", + "Ġmonoph yletic", + "d les", + "n ested", + "ĠG race", + "ĠStud ios", + "ĠDisc ussions", + "ophen oxy", + "Ġveter in", + "Ġendos ym", + "utting er", + "b atches", + "ĠF iji", + "ĠR NF", + "ous a", + "ĠK Y", + "Ġspect rograph", + "ER IC", + "ĠMy anmar", + "ĠConst raining", + "Ġecological ly", + "Ġfro st", + "arb oux", + "ĠFib onacci", + "Ġcancel ed", + "ĠISS N", + "R ect", + "an other", + "ĠM MA", + "OL O", + "ĠTr uth", + "Ġorth opaedic", + "Ġtravers ing", + "ischem ic", + "ĠMozamb ique", + "ĠM SR", + "ap ace", + "ĠTh read", + "olog ia", + "Ġcal m", + "methyl transferase", + "ĠÍ ª", + "Ġdro ve", + "Ġcommand ed", + "Ġf eline", + "ĠR ush", + "ĠL isa", + "Ġsuper space", + "arc y", + "ĠReg ulated", + "ĠRest ing", + "caus ing", + "psycho tics", + "q t", + "Ġt ulare", + "Ġrel ocated", + "Ġrep ell", + "Ġpred atory", + "pe ople", + "tra its", + "E uclidean", + "F DA", + "X RT", + "p C", + "p and", + "Ġ Æ", + "re ve", + "Ġb ids", + "Ġco usin", + "Ġsub domains", + "til b", + "é nez", + "linear ly", + "oprotein s", + "Ġcod ec", + "Ġcontrac eption", + "ĠCd k", + "Ġrail road", + "B ench", + "r ng", + "ĠD LA", + "enti le", + "ĠCO CO", + "ĠMat th", + "ĠOver l", + "ĠRout ine", + "Ġmultif ocal", + "Ġarte fact", + "Ġsculpt ure", + "c ies", + "m ate", + "Ġ Ø", + "ure k", + "ĠB end", + "ĠN athan", + "Ġde w", + "ym ia", + "az zi", + "ĠEr k", + "Ġgrad uation", + "Bound ary", + "G ra", + "Ġb fd", + "ĠS ert", + "Ġover shoot", + "ĠSe o", + "Ġsk learn", + "Ġconserv atively", + "pir acy", + "Ġla unching", + "X D", + "ar bitrary", + "per one", + "Ġsh ops", + "comp etitive", + "ĠPak istani", + "Ġcompetit or", + "b iotics", + "ra its", + "ĠN oble", + "Ġsub regions", + "ĠJ ump", + "roll er", + "tr is", + "Ġmac rol", + "ó s", + "ĠPen ic", + "Ġmicros omes", + "Ġimprec ise", + "Ġdownt own", + "Ġe QTL", + "if est", + "ĠM FI", + "Ġr arity", + "âĢĻ âĢĻ", + "Ġbel ts", + "Ġglycos yl", + "ĠNic olas", + "synt hesis", + "O h", + "h ierarch", + "p ps", + "an ets", + "ro ads", + "AT IC", + "MI MO", + "ĠCont ract", + "Le ib", + "opy rox", + "Ġinform ational", + "Syn onyms", + "chall enge", + "ĠProxim al", + "ĠCraw ford", + "Ġis opropyl", + "Ġsub families", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠĠĠĠ", + "Ġannot ators", + "Ġreconc ile", + "Ġparsim ony", + "Ġcasp ases", + "c ott", + "en vironments", + "Ġd rm", + "ĠP IL", + "ĠM ec", + "ĠIn fer", + "ĠSir t", + "S hell", + "ag ulants", + "se ismic", + "Ġsub urban", + "ĠX XX", + "iod es", + "Ġback propagation", + "tra ditional", + "Ġphotoc on", + "ĠMicrobi ology", + "Q T", + "ur idine", + "Ġch op", + "ĠTh é", + "Ġpre jud", + "Ġenc oders", + "col lected", + "rem ark", + "Ġsun spot", + "ĠPhen olic", + "Under standing", + "Ġreject ing", + "Ġrom antic", + "Ġcentim eters", + "Ġhalluc inations", + "H ome", + "c asted", + "Ġc w", + "ra i", + "ĠDis placement", + "PH Y", + "carb am", + "Ġxen on", + "Ġnarr atives", + "Ġdoll ar", + "Ġdyn asty", + "ì §", + "Ġin forming", + "ĠO CD", + "á k", + "Ġoverhead s", + "ju ana", + "ĠKra us", + "f x", + "k aya", + "Ġn id", + "ĠG rab", + "Ġinf lores", + "Ar c", + "======== ====", + "Ġcondens er", + "Ġnanoc ar", + "omm ens", + "Ġsatur ating", + "re ce", + "el if", + "ĠA LE", + "ĠB ub", + "ĠL af", + "and ran", + "Ġpo uch", + "rol ine", + "AC HE", + "CC D", + "Ġcool ant", + "Ġgrass lands", + "ĠSynchron ous", + "izz iness", + "Ġcet uximab", + "Ġdichotom ous", + "ro ch", + "ĠA uckland", + "ob esity", + "ik it", + "Ġoper ad", + "ĠOn set", + "Ġbefore hand", + "Ġunc omp", + "US ED", + "ubb ing", + "ĠSMB H", + "ĠExped ition", + "Ġh ib", + "ĠP PR", + "ĠN ED", + "ud io", + "ĠJ al", + "ĠAr p", + "ĠBe e", + "ĠVari eties", + "Com m", + "Ab out", + "ĠAtt achment", + "ODU LE", + "Calc ulate", + "T an", + "in ism", + "Ġa ra", + "Ġc abin", + "Ġcon nexin", + "Ġcom ets", + "ump tive", + "Ġdest abilization", + "ĠHol t", + "ruct ose", + "anish i", + "plastic ity", + "omyc osis", + "ovic ian", + "________ ________", + "r ar", + "Ġw ore", + "ud ine", + "ĠIn variance", + "Ġper itonitis", + "Ġmet rology", + "Ġclos es", + "Ġcolor less", + "No ise", + "DI O", + "ĠLif shitz", + "z ul", + "es tive", + "ĠM PA", + "ĠB ooth", + "ĠD oll", + "are ne", + "gen ess", + "Ġmolecular ly", + "ĠPer kin", + "Ġdos imetry", + "ĠSO FT", + "ĠPy Torch", + "Ġquar ters", + "ĠKu hn", + "Ġsplen ocytes", + "R W", + "c art", + "le b", + "Ġcon dom", + "ĠH oc", + "Ġext ents", + "Ġsl ug", + "ĠSup plementation", + "diff ic", + "ester ly", + "Y u", + "an tigens", + "Ġà Ĵ", + "Ch anges", + "Ġprop ylene", + "ĠPr ison", + "ĠAlgorithm ic", + "Ġtoler ances", + "Ad am", + "Ġester ase", + "Ġmil der", + "ĠConv ection", + "P TR", + "k pc", + "Ġex o", + "ĠF ah", + "ĠY FP", + "ĠCR M", + "Ġhepat otoxicity", + "Ġnic otinamide", + "Ġpatch y", + "depend s", + "Ġp B", + "Ġe el", + "Ġn v", + "ĠS es", + "ĠH Z", + "Ġim print", + "ep ileptic", + "fl uctuations", + "Ġformal ize", + "che v", + "Ġdip ping", + "ĠPy ramid", + "Ġhol o", + "ĠMT s", + "Ġlamin ates", + "Ġworm hole", + "L AP", + "h ape", + "Ġa k", + "Ġre als", + "Ġby stand", + "Ġinter leaved", + "Ġx z", + "ov y", + "Ġcop rime", + "ucl ides", + "Ġtrim ming", + "MIC AL", + "pyr role", + "I a", + "N LS", + "Q uality", + "t akes", + "z inc", + "ĠP ione", + "ĠE wing", + "ĠL CA", + "Ġà Ķ", + "ict us", + "Ġcoll im", + "Ġphyl ogenetically", + "ĠKe eping", + "ĠFa ith", + "bond s", + "ti ter", + "Ġsub categories", + "sh aded", + "Ġphot ospheric", + "ĠApp earance", + "ĠUnivers ities", + "Ġglomer uli", + "ĠPref rontal", + "Ġprivi lege", + "i H", + "u ya", + "ĠL CL", + "ĠIn GaAs", + "In spired", + "atal og", + "ĠPer ceptions", + "ĠNa HCO", + "Ġstream line", + "tra jectory", + "ĠMic rom", + "Ġbed side", + "ĠRom ero", + "Ġgaug ino", + "D EN", + "F a", + "O lymp", + "e al", + "u els", + "ic ylic", + "Ġg od", + "Ġat taining", + "Ġprot ests", + "Ġnow here", + "des orption", + "ĠHydro xy", + "ĠEr bB", + "ĠSP AR", + "Ġhind ers", + "heren kov", + "KERN EL", + "Ġs ect", + "ul ong", + "Ġpre processed", + "frac tional", + "oy age", + "Ġphosph atases", + "Ġcoast line", + "Ġh ref", + "ĠS utherland", + "ox one", + "Ġhom omorphic", + "D EM", + "Ġb ovis", + "ĠC BP", + "pl en", + "ĠB uc", + "ĠG ior", + "Ġcomp ost", + "ĠO racle", + "ĠSp here", + "ĠSch re", + "deriv atives", + "ly tes", + "ĠY o", + "Ġcycl ones", + "ĠMa ize", + "Ġunf air", + "Tem plate", + "Ġimpregn ation", + "Ġlapa roscopy", + "Ġh amiltonian", + "ign ore", + "Ġdis posable", + "ear ic", + "Ġelect oral", + "cc os", + "ĠSh h", + "Ġturb o", + "Ġintr usive", + "Ġpreced ence", + "annot ated", + "Ġdyst onia", + "F at", + "u ins", + "Ġs way", + "ar izing", + "ill en", + "Ġy i", + "Ġnorm ed", + "ĠÌ Ĥ", + "ĠExt r", + "ĠProte ome", + "Doc ument", + "ĠQUANT UM", + "ti ti", + "ĠC PC", + "ĠM iles", + "ĠB oc", + "ĠR TS", + "CT X", + "Ġsaf egu", + "ĠNorm ally", + "ĠÃľ ber", + "on ious", + "ĠS CE", + "Ġal falfa", + "ĠL ut", + "Ġco ut", + "Ġen large", + "ĠEn able", + "Ġvir ion", + "ĠSh allow", + "def initely", + "ĠCol in", + "ĠRet ention", + "Ġmimic ry", + "################################ ################################", + "NSC LC", + "Ġgrat itude", + "Ġt ending", + "ĠI DS", + "ere t", + "ric an", + "Ġx n", + "ĠY oo", + "Ġoptim ise", + "Ar row", + "ĠTransfer ase", + "PK C", + "ĠGuang zhou", + "r uc", + "y rid", + "is z", + "ĠF IX", + "ĠD atabases", + "ast ron", + "Ġplay back", + "Ġnarrow ly", + "Cor relation", + "ĠAff inity", + "Ġfunctor ial", + "Ġlect ins", + "Ġrup tured", + "Dis play", + "ĠSympt om", + "Ġequid istant", + "ĠRicc ati", + "ĠAchie vement", + "g rand", + "on ated", + "Ġd H", + "ĠF ID", + "ĠD ER", + "ĠCo A", + "Ġgas ification", + "ĠCON S", + "Ġaccompan ies", + "Ġimped e", + "Ġpreced e", + "Ġkit chen", + "prog ress", + "Ġw iring", + "le renes", + "ĠG ius", + "Ġtrans p", + "ret rie", + "ij er", + "aff er", + "Ġbirth day", + "ĠHal d", + "Ġmusc ulus", + "ĠTok en", + "ĠBow el", + "Ġskip ped", + "C ha", + "b v", + "ĠB low", + "Ġpre operatively", + "Ġgl ove", + "ĠLe ven", + "Ġmes op", + "ĠAux iliary", + "ensure math", + "j us", + "Å ©", + "Ġv oter", + "ĠH itch", + "pro xy", + "ĠK ut", + "Ġpo ems", + "ĠAn gl", + "cer a", + "Ġstar red", + "AG ES", + "Sc ience", + "Anal yses", + "Ġrefere es", + "Ġabrog ated", + "Ġdesal ination", + "ĠPrand tl", + "P it", + "Ġn atal", + "og ran", + "ys titis", + "Ġdes m", + "Ġcur ious", + "Ġdem on", + "uz zi", + "ochond rial", + "ĠTreat y", + "Track er", + "rhoe ae", + "L W", + "f urt", + "Ġo mp", + "is ational", + "Ġmem orial", + "ĠLat ency", + "ĠHyp ot", + "Ġglu ed", + "exact ly", + "Ġcontra ind", + "C ancer", + "Ġf fi", + "ĠN AA", + "ĠCh r", + "eg g", + "ĠMo tiv", + "Ġlay outs", + "Ġclim b", + "Ġappend icitis", + "CU DA", + "Ġphotop roduction", + "ĠS IP", + "Ġv eto", + "per in", + "ĠUn ity", + "by ear", + "Ġforward ed", + "ĠDom inant", + "hol z", + "ĠThor acic", + "DEF INE", + "Ġtyros inase", + "B ad", + "I NA", + "f uel", + "Ġg i", + "ĠV IS", + "ast olic", + "Ġox aliplatin", + "eff ector", + "ĉĉĉĉ Ġ", + "е ÑĢ", + "ĠBab y", + "Ġwash out", + "pit uitary", + "N GC", + "Ġd ns", + "ĠP oz", + "ĠU z", + "pos itron", + "ĠElect rons", + "Ġhem angi", + "ĠZn S", + "ĠTE MP", + "ĠExperiment ally", + "fluor ouracil", + "Ġlap arotomy", + "analy zer", + "ocortic oid", + "ĠIMP L", + "ĠDNN s", + "ĠFres nel", + "M ont", + "Ġt apes", + "ul omb", + "im pedance", + "ĠH ET", + "ath a", + "mod ulation", + "ĠCor tic", + "Ġâľ ĵ", + "ĠFair ness", + "ĠSti ff", + "Ġbutt ons", + "c ss", + "Ġand roid", + "el ast", + "ĠT eflon", + "ĠM BC", + "ĠJ T", + "Ġmulti layered", + "ĠRe e", + "uit ar", + "ĠPhil ips", + "ĠSk ip", + "doc toral", + "iy ama", + "ĠLead ership", + "ĠCris is", + "Ġdesens itization", + "v ous", + "ĠS PP", + "ĠP GA", + "ĠN ever", + "Ġdef eating", + "Ġfib romyalgia", + "ĠMR P", + "ĠAB CA", + "ĠLow e", + "Ġer oded", + "Ġaug ments", + "ĠBor is", + "Ġneph rectomy", + "ĠSher man", + "Ġrefrig eration", + "ĠHern ández", + "à ĺ", + "ĠT ors", + "ch us", + "ĠV arg", + "Ġro set", + "CL R", + "DE P", + "Str ong", + "Ġcin erea", + "ĠHein rich", + "R out", + "od us", + "ĠPh one", + "ĠPer l", + "Ġseason ally", + "hold ing", + "Ġencephal omyelitis", + "Ġfasc ia", + "Ġlitterm ates", + "ĠWIT HOUT", + "Ð ±", + "Ġal erts", + "ĠK oll", + "ĠU rs", + "elf and", + "ĠRNA P", + "Ġinvari ably", + "Ġscin tigraphy", + "ĠSebas tian", + "kines ia", + "C UR", + "in ants", + "Ġp ET", + "id ial", + "ĠU PLC", + "Ġsu is", + "Ġbas olateral", + "ĠMod ulates", + "orb ic", + "Im g", + "Ġparas itism", + "Ġlamin ate", + "oge ographic", + "ĠRib eiro", + "ĠGlut athione", + "ĠAber rant", + "Ġs clero", + "ĠD LS", + "ĠR uth", + "Ġrec ast", + "rec ated", + "ok ie", + "ĠPark s", + "Ġfoli ations", + "ĠDaw son", + "Ġtann ins", + "ĠAar on", + "p S", + "it ating", + "ĠI TC", + "ip ients", + "oh y", + "CC s", + "Ġeth anolic", + "cor hynchus", + "Ġorient ational", + "Ġhabit uation", + "Ġconvers ational", + "ĠVent ricular", + "Ġintercal ated", + "Ġphosphodies terase", + "ĠSeif ert", + "w k", + "al gesia", + "Ġst egan", + "ĠL us", + "oph antine", + "Ġcorrec ts", + "ĠOb ama", + "lat ency", + "Ġson ar", + "ORM AL", + "Ġseaw eed", + "ĠPow ers", + "ĠShap ley", + "L ore", + "Ġa wa", + "al ach", + "ĠF on", + "ens ate", + "Ġoptim a", + "IN F", + "Ġpoly genic", + "Ġmes oderm", + "Con ver", + "BR ID", + "ĠHel p", + "ĠRas mussen", + "Ġprokary otes", + "ĠEur asian", + "ĠPerme ability", + "Ġn au", + "ĠC lem", + "od ilation", + "ĠD iaz", + "iti ous", + "ĠCh ad", + "OR A", + "ĠSim ons", + "ĠDist ances", + "Ġast rometric", + "ĠCP Us", + "Ġthi oredoxin", + "perturb ation", + "Ġdendrim er", + "al gal", + "Ġc eliac", + "as z", + "ĠP PE", + "qu a", + "ĠB oll", + "ch r", + "Ġpre view", + "ĠPro jections", + "ĠAs ians", + "ĠInf erring", + "ĠNa ive", + "ĠHig gins", + "ĠLoc ated", + "cardi ac", + "ĠLars on", + "haz ard", + "ĠScienti sts", + "Ġp inn", + "EN CY", + "form e", + "chit ects", + "oflu orescent", + "ĠPor tal", + "Ġpup ae", + "interest ing", + "į Ģ", + "re act", + "at os", + "en in", + "ti o", + "ĠC app", + "ĠM au", + "ĠL SC", + "ĠV lasov", + "Ġsub sum", + "Ġdes erve", + "AS D", + "Rec e", + "Ġconson ant", + "Ġimpregn ated", + "Ġlignocell ulosic", + "Ġs ows", + "le ment", + "ĠT ier", + "ĠM EF", + "ĠH ugh", + "inc k", + "py razole", + "UL ATIONS", + "ĠAL I", + "ĠDr ift", + "Ġsolub ilized", + "Ġdraft ing", + "icycl ic", + "Ġredes ign", + "Ġdelib erate", + "Ġt apping", + "ĠT omas", + "ĠT unneling", + "ĠC BR", + "Ġan odes", + "ĠL SR", + "ĠN ath", + "ros ive", + "ĠHe idelberg", + "Ġcr ushing", + "ĠSh ore", + "Ġmal ondialdehyde", + "ĠMR D", + "ogl oss", + "nc ia", + "Ġgranul oma", + "Ġplain text", + "Ġarteri ovenous", + "Ġrifamp icin", + "Lepid optera", + "O ct", + "Ġl one", + "ĠAp pe", + "ĠInter mitt", + "comp ile", + "pot entials", + "ĠStandard ized", + "Ġventil atory", + "Ġhypercholesterolem ia", + "ĠEVALU ATION", + "k ed", + "x C", + "en os", + "Ġb authorbsnm", + "ĠR ost", + "math open", + "Ġcont ested", + "Ġro s", + "oth o", + "Ġem its", + "ero zo", + "Ġprop ranolol", + "Ġexacerb ate", + "Integr ating", + "ĠWars aw", + "Ñ ĩ", + "re fractory", + "ĠM ort", + "phosph onate", + "GL T", + "ĠChlor ide", + "ĠLU AD", + "ĠSQU ID", + "ĠOBSERV ATIONS", + "Ħ ĺ", + "ag les", + "ug er", + "Ġdiff using", + "yl ar", + "Ġanti p", + "ren ormal", + "Ġshe ared", + "ĠAnd r", + "ympt otics", + "ĠIdentif ied", + "Ġflex or", + "Li ouville", + "ĠCyt otoxic", + "L ock", + "d onald", + "ĠS HA", + "pro jected", + "plic ial", + "Ġbas ics", + "ĠCar valho", + "Ġheter ocyclic", + "Ġfluor ophore", + "ĠIntr igu", + "ĠAnne aling", + "G ln", + "H ispanic", + "Ġs aus", + "ĠT CS", + "ĠH AP", + "Ġy tt", + "Ġcons ulting", + "rec ts", + "Ġinf all", + "LE V", + "tri azole", + "Ġnarrow ed", + "Ġamph oteric", + "ĠSor ting", + "ĠMom ents", + "Ġarab in", + "Ġcocon ut", + "ĠIntrigu ingly", + "Ġp ushes", + "Ġm ec", + "ĠN air", + "Ġcol istin", + "ĠOb tained", + "df s", + "Ġcompet ency", + "W ORD", + "ĠA AS", + "ĠB NP", + "ĠH AS", + "ĠL un", + "ĠL nc", + "Ġhydro cephalus", + "Ġhom ological", + "Ġcarbon ic", + "ĠHi Seq", + "commun ity", + "Ġcephal ospor", + "Ġhos tile", + "prov ide", + "Ġskyrm ion", + "D AG", + "Ġc nt", + "Ġh ay", + "Ġorder ings", + "Ġfl ock", + "HE A", + "ĠNeu rom", + "Ġboost s", + "ĠCard inal", + "ĠBac helor", + "Ġdec ent", + "ĠY ak", + "Ġcalc d", + "ĠBo er", + "Ġtranscript omics", + "Ġrearrang ed", + "ĠPolym orphisms", + "ĠPras ad", + "oinositi de", + "b ars", + "Ġ ãģ", + "ĠS AA", + "Ġon ion", + "ag el", + "ĠH p", + "og rel", + "di visions", + "and an", + "ari as", + "Ġcol o", + "rag on", + "Ġsch izophren", + "âī ¡", + "Ġreplic ative", + "Ġdegener ated", + "Ġsteep est", + "Vol ume", + "I ENT", + "P ublic", + "T en", + "en berger", + "ĠC oun", + "ĠE pp", + "iz o", + "Ġcomplex ed", + "Ġfer roc", + "ken stein", + "ĠJer ry", + "Ġparadox ical", + "x g", + "ic er", + "os ol", + "Ġan nu", + "Ġan kyl", + "ch ung", + "enti ous", + "Ġpres he", + "ene tic", + "ĠHe aling", + "ĠPar abolic", + "Ġfig s", + "ĠKin ematic", + "Ġoblig ate", + "ĠLay out", + "Ġtelem edicine", + "ĠLenn ard", + "p ci", + "ar one", + "ĠZ ach", + "Ġprot otyping", + "ĠMet agen", + "IM AL", + "cons cious", + "Ġquadr ilateral", + "ĠUncertain ties", + "ĠPref ecture", + "G BM", + "r als", + "al us", + "Ġh opes", + "Ġcl icks", + "ĠJ D", + "lect ance", + "Ġpath ologists", + "uss els", + "tis one", + "CP T", + "Ġmis con", + "ĠNeuro de", + "Ġmutagen ic", + "ĠMultim edia", + "Orig inal", + "ĠDra ke", + "P WM", + "Ġp iles", + "st ant", + "AR A", + "ĠR ING", + "mod ifying", + "Ġast rocyt", + "ĠCy st", + "Ġleg ends", + "gluc uron", + "Ġincom pletely", + "ĠConf ed", + "ĠDL BCL", + "ĠPap ua", + "Ġcontras tive", + "ĠSIM ULATION", + "ĠJu venile", + "aggreg ated", + "Ġc GMP", + "ic tive", + "ĠH NF", + "ĠN PV", + "ĠK oc", + "omet allic", + "min i", + "ĠQu antit", + "ĠCor nell", + "Ġded uction", + "Ġcoinc iding", + "ĠIr r", + "Prec ision", + "Ġgins eng", + "õ es", + "j er", + "ĠRe ader", + "ĠBy r", + "cor rections", + "dev ices", + "Ġamb ul", + "Ġped icle", + "ĠDepend ency", + "ĠStri king", + "Ġware house", + "Ġrecirc ulation", + "Ġgonor rhoeae", + "ĠP RES", + "ĠB har", + "Ġfl ushing", + "tor us", + "ĠIR B", + "gly cine", + "Ġmeth amphetamine", + "Ġmir rored", + "ĠWilliam son", + "Ġcath odes", + "hydrox ylase", + "Rad io", + "Ġfurn iture", + "ĠRosen berg", + "ĠNSA IDs", + "s emiconductor", + "Ġas ynchron", + "ĠB erm", + "ĠIn ten", + "ib e", + "For ce", + "path ogenic", + "sm okers", + "Ġdip henyl", + "ĠÐ ¸", + "Ġstand alone", + "Ġlith ospheric", + "Ġtrade offs", + "Ġantic h", + "Ġthym idine", + "ĠMedic inal", + "Ġentrepreneur ial", + "Ġtrapez oidal", + "ĠAs ynchronous", + "tif ying", + "ĠColl apse", + "ĠHE V", + "ĠFro zen", + "ĠTeich müller", + "rocnem ius", + "Ġf ern", + "Ġw s", + "om ol", + "Ġen closing", + "rap id", + "Ġlog ged", + "var vec", + "Ġampl ifying", + "diff erences", + "oton in", + "ĠProm oting", + "ĠFr itz", + "Ġattain able", + "Ġal tim", + "ĠO GD", + "Ġtherm ometer", + "Sol ver", + "ĠBir k", + "LEN BQU", + "ĠGate way", + "Ġengraft ment", + "F IF", + "H SD", + "Ġre structuring", + "ĠT ensile", + "ĠC ele", + "yl us", + "Ġfe ather", + "Ġdr ifting", + "ĠPre clinical", + "yr role", + "Ġcomm em", + "Ġfix ations", + "Pet sc", + "ĠIschem ia", + "a A", + "as oro", + "ĠS ony", + "ĠU t", + "Ġext ensor", + "ĠCh au", + "ĠIs otopic", + "IL I", + "CN P", + "ĠDE F", + "Ġmountain ous", + "Ġsarcom as", + "ugos lav", + "C ALL", + "S ensitive", + "at ro", + "Ġunc oupling", + "sk ew", + "ĠEm issions", + "inn ati", + "Ġconceptual ization", + "Ġow ns", + "Ġsquad ron", + "ĠStreng ths", + "C oh", + "U AL", + "m agenta", + "us b", + "ĠS PC", + "con es", + "ĠSe lecting", + "ĠPar ish", + "Ġvalid ates", + "ĠÍ Ĺ", + "Ġposterior ly", + "omon ad", + "V OL", + "j ectivity", + "ĠC LO", + "ĠV TA", + "Ġun pleasant", + "Ġcare ers", + "Ġautom orphic", + "ĠNan ow", + "Ġaster isks", + "ĠSchul z", + "publ ication", + "Ġb iv", + "Ġr ug", + "rec ognition", + "Ġref errals", + "Ġneur ones", + "ĠCa ffe", + "Con nor", + "ĠShe ffield", + "unit inib", + "ĠAnt agon", + "Ġpneum atic", + "Ġclean er", + "ĠBA O", + "ĠScilab String", + "neigh bour", + "E uler", + "ĠT uple", + "ot y", + "di an", + "Ġy oga", + "Ġev anes", + "Ġstar ved", + "Ġfluct uate", + "ĠBiomark er", + "Ġimpuls es", + "Ġoss ification", + "Ġdemyel ination", + "ĠS AD", + "ess ing", + "Ġred dish", + "Ġsyn th", + "Ġcurv ilinear", + "ĠDen is", + "Ġphone tic", + "Ġham mer", + "Ġepiderm idis", + "Ġplagi oclase", + "Ġ ĉ", + "Ġw olf", + "os ced", + "Ġphot othermal", + "Ġche wing", + "Max imum", + "Ġmism atched", + "ĠFc γ", + "Ġum brella", + "ĠSiber ian", + "ar ra", + "ip ped", + "ym pathetic", + "acc eleration", + "Ġeigen modes", + "ĠEqu ivalently", + "ĠPR ISMA", + "cons ervative", + "ñ ez", + "Ġvolcano es", + "Ġtelem etry", + "m ile", + "ĠB och", + "op rim", + "Ġinc ipient", + "Ġunderstand able", + "atric yclo", + "ĠLog ical", + "ĠQue ue", + "Ġcry ostat", + "defin ecolor", + "ĠS ae", + "Ġar ct", + "Ġso ul", + "ĠHist opathological", + "ĠNeu rot", + "Ġmethan olic", + "P x", + "ĠT itle", + "ot omic", + "ĠE ld", + "ĠE MA", + "Ġde brid", + "tim ulatory", + "ĠZ an", + "Ġnorm ot", + "Ġfluid ity", + "Ġfluid ized", + "pre viously", + "Ġcrack ed", + "ĠExpl aining", + "ĠON E", + "ĠFlor a", + "ĠHybrid ization", + "Ġretic ul", + "F K", + "n otic", + "Ġn A", + "ĠP ab", + "tic um", + "and y", + "ug ia", + "ile t", + "MI NG", + "Ġrest s", + "omp act", + "Ġtrack ers", + "phosph atase", + "ĠTransf ection", + "ĠHospit als", + "ac rine", + "ĠD ell", + "ĠV AE", + "ĠThrough put", + "hev sky", + "ĠSom mer", + "P SA", + "ì ļ", + "Ġb ush", + "Ġl unch", + "ĠS we", + "ĠIn struction", + "ak ami", + "Ġdis infect", + "Ġcor ps", + "ĉĉ ĠĠ", + "Ġprom pts", + "MS H", + "ĠAg rawal", + "Ġlys osome", + "integr in", + "Ġá» ¸", + "Ġnondec reasing", + "ĠRe quest", + "ĠRE P", + "occ us", + "Ġlag rangian", + "oreg ulation", + "оР»", + "ĠBos on", + "I so", + "at ellites", + "res ectable", + "ri v", + "Ġde aminase", + "Ġco heren", + "Ġdec oy", + "ĠExt inction", + "acet one", + "Ġgovernment al", + "Ġcum ulants", + "Ġviscos ities", + "Reg ister", + "document ed", + "Ġimmortal ized", + "D PP", + "G el", + "b ron", + "k ow", + "ĠPro portion", + "ĠCh ase", + "ĠCl ad", + "Ġadap ts", + "ĠCA V", + "ĠÅ ¼", + "Ġpel leted", + "Ġpeng uin", + "ĠZhe jiang", + "feas ible", + "D IV", + "i ya", + "Ġth rowing", + "res ia", + "ĠN r", + "ES P", + "CD F", + "sup pressed", + "Ġtet rachlor", + "Ġaer ospace", + "Un til", + "Ġpay offs", + "Ġtown ship", + "Ġester ification", + "ĠAch illes", + "Ġrac em", + "opyran oside", + "ĠC SM", + "ass is", + "Ġsuper cell", + "ĠReg ime", + "IR A", + "Ġsubsequ ences", + "ĠPen et", + "ĠAnaly tics", + "ĠLV EF", + "Ġbip henyl", + "G radient", + "os ylation", + "ĠW RF", + "of s", + "con ductors", + "Ġback ed", + "pid al", + "ĠNF AT", + "ĠRem ember", + "Ġtel omeric", + "Ġta urine", + "incre ases", + "Ġunint ended", + "ĠNerv ous", + "R as", + "y lyl", + "Ġa estiv", + "ĠS ick", + "ĠThe ta", + "Ġcl iques", + "Ġso fter", + "ĠQ RS", + "llip tic", + "ĠImmun otherapy", + "QU F", + "onom ously", + "ĠFL U", + "ĠIncor poration", + "ĠFormic idae", + "J R", + "w hole", + "Ġc asing", + "Ġn ob", + "ĠD ou", + "Ġint ronic", + "Ġent rapment", + "orb its", + "Ġsal am", + "ĠCR S", + "ĠSw an", + "ĠEd gar", + "Ġconcomit antly", + "atet racyclo", + "ĠA HR", + "tic ks", + "ĠB ing", + "ĠR ift", + "Ġpl ugging", + "Ġsc RNA", + "Ġout reach", + "ins kii", + "Ġcustom ary", + "Ġm d", + "ĠO zone", + "uss ing", + "other s", + "Ġentire ty", + "Ar th", + "Ac et", + "ĠFle et", + "ĠBehaviour al", + "ĠQSO s", + "ar ina", + "Ġpro drug", + "ĠB ros", + "ĠW orth", + "Ġy z", + "con tig", + "ĠAm orphous", + "ĠEr lang", + "Ġhon our", + "ĠâIJ ¥", + "Ġinfiltr ates", + "ĠIvan ov", + "ĠMunic ipality", + "ĠDial ogue", + "t one", + "Ġp ytest", + "ic ulus", + "ĠG oth", + "ĠX C", + "ĠSU MMARY", + "Ġshr inks", + "Ġinvers es", + "i omas", + "ro bi", + "ĠT PR", + "ĠA NA", + "ist ries", + "Ġreg iment", + "ind o", + "ĠRe production", + "lo qu", + "inf lation", + "ET X", + "Ġïĺ »", + "ĠAPP ENDIX", + "Ġwors ened", + "Ġpsori atic", + "Ġmidw ives", + "Ġtouc hed", + "Ë ĩ", + "ĠP atric", + "ĠD ON", + "ĠL IM", + "ak os", + "ĠV ie", + "ĠAn tit", + "Ġfl ake", + "ĠSch le", + "ĠCor onal", + "Ġsal ary", + "sl ight", + "ĠCA F", + "Ġsummar ise", + "Ġflav us", + "ĠBal anced", + "ĠPH OT", + "Ġmil let", + "Ġurg ency", + "ĠGle ason", + "ĠM ie", + "ĠD p", + "ĠG arg", + "Ġle prosy", + "Ġun occupied", + "ĠSt ret", + "ile pt", + "ĠCh or", + "ibr ate", + "ĠÍ ļ", + "ĠPH B", + "Ġmonot er", + "ĠJava Script", + "bt n", + "ĠPuls ar", + "ĠKirch hoff", + "Ġoverse as", + "Ġde phosphorylation", + "ort in", + "ĠPoly akov", + "Ġinsight ful", + "ĠPur ified", + "Ġanch orage", + "ĠGly coprotein", + "stud ies", + "Ġchron ology", + "rox ine", + "ĠNept une", + "B an", + "Ġl ion", + "PS D", + "ĠBar r", + "Ġdon key", + "Ġlikelihood s", + "atche wan", + "ot et", + "os pha", + "tic ism", + "Ġr y", + "ast hen", + "rho tic", + "ĠSub group", + "ye v", + "ĠPat ri", + "provid es", + "S GD", + "b erell", + "v w", + "ĠA ACR", + "Ġsm ears", + "OD S", + "sup plemented", + "ĠEng agement", + "oglob ulins", + "Ġirregular ly", + "ĠSz eg", + "ĠWol ff", + "Ġenanti omers", + "Ġobey ing", + "Ġdestro ying", + "om ially", + "ĠA ti", + "ĠG AT", + "ĠIn variants", + "ĠSc oring", + "Ġhal ides", + "Ġtransform ants", + "Ġforest ed", + "Ġgall ic", + "ĠBet ti", + "thread ed", + "ĠBud get", + "junc tive", + "ĠInnov ative", + "Ġposit rons", + "B razil", + "e ira", + "Ġl avas", + "ĠL t", + "ph oto", + "Ġsp am", + "Ġi h", + "ust ering", + "Ġbi oluminescence", + "ĠSh apes", + "UL TI", + "tri angles", + "ĠSM N", + "enh ancing", + "ĠReduc es", + "ĠTHEO REM", + "D op", + "Ġd L", + "em ptive", + "Ġrem inder", + "Ġgon ads", + "Ġxyl an", + "cult ures", + "t les", + "Ġt d", + "Ġe rected", + "ter one", + "ĠPD C", + "Ġincongru ent", + "Ġmembran ous", + "p ac", + "yl ess", + "Ġsub algebras", + "ĠCh ir", + "ĠZ IP", + "au tious", + "Ġlight ly", + "ĠPhot ometric", + "Trans fer", + "Ġket o", + "Ġexerc ised", + "dispers ive", + "ĠBET WEEN", + "ro u", + "Ġg arbage", + "ĠM af", + "ĠD oming", + "ĠSub space", + "ĠMar ÃŃa", + "Ġtetra hedra", + "ĠBark er", + "S ide", + "b ishop", + "i D", + "re versible", + "orm an", + "ores cein", + "ĠCont rib", + "Ġderiv atization", + "rome res", + "ĠAL D", + "EE K", + "ĠTre ating", + "comb ination", + "ïĺ »", + "restric tion", + "supset eq", + "ĠRAP D", + "Ġamend ment", + "zyn ski", + "Ġc aves", + "il ot", + "Ġabund antly", + "н а", + "Ġinject able", + "ĠReinfor ced", + "ĠWid th", + "ĠHaem ophilus", + "il ane", + "pro ps", + "Ġinter vertebral", + "Ġsc roll", + "Ġam put", + "ĠUn usual", + "Ġstat ically", + "Ġsyn ergies", + "Ġdim s", + "plas mic", + "Ġneutral ized", + "Se lected", + "Ġinher its", + "ĠAutom ation", + "Ġproto planetary", + "Stat ement", + "ĠAPO BEC", + "Ġcertif icates", + "ĠCit rus", + "quadrup lex", + "N ord", + "Ġf ran", + "ĠC arcin", + "ut an", + "ĠP ump", + "ĠB av", + "ĠG ras", + "ting ales", + "Ġcaus ally", + "Ġrad on", + "Comp are", + "Ġclamp ing", + "irre ducible", + "I HC", + "Ġ Ù", + "Ġc yp", + "ĠT PP", + "ĠS uff", + "und ra", + "ĠV illa", + "Ġrel ieved", + "ĠJ CM", + "Ġtreat y", + "IG EN", + "ĠDev onian", + "Ġerythrop o", + "R AP", + "Ġa versive", + "ent ate", + "od actyl", + "ĠPar al", + "Ġmill ed", + "Ġbio informatic", + "okine tic", + "ĠSTR ING", + "ĠPed ersen", + "d atabase", + "in organic", + "Ġde put", + "Ġne b", + "ip ed", + "Ġdiff used", + "oth ione", + "Ġnon stationary", + "Ġunder taking", + "ĠEn abling", + "Ġden atured", + "Ġload er", + "ĠLy on", + "ipar ametric", + "Ġmer istem", + "ĠAngi ogenesis", + "ĠPuls ed", + "Ġex cer", + "ĠD f", + "arc hes", + "Ġcoll ide", + "ĠRel ational", + "ĠNF κB", + "Met adata", + "ĠAdd ressing", + "Ġperc ussion", + "ĠFlore nce", + "Ġnymph s", + "C n", + "st orm", + "ĠG raz", + "com posite", + "ĠAd miral", + "ĠSc otia", + "Ġbre msstrahlung", + "aps ack", + "Ġminim izers", + "Ġmanage able", + "Ġcarboxyl ate", + "Ġintermedi ary", + "ĠBran ching", + "sched uler", + "inoc ulated", + "ĠExtrem ely", + "Ġantenn ae", + "ĠT ill", + "RE SH", + "Ġop acities", + "Ġchem opre", + "Ġaden ylate", + "Ġcircumst ance", + "ĠHash imoto", + "Ä Ľ", + "ce ae", + "ĠF m", + "ĠB X", + "Ġmean time", + "acc urate", + "col linear", + "ACT IC", + "ĠSlov enia", + "F ed", + "K h", + "T m", + "f ork", + "in ology", + "le f", + "ĠD CS", + "Ġher itable", + "Ġann ouncement", + "Ġbusiness man", + "Ġbor tezomib", + "Ġtour ist", + "ĠEt ymology", + "Ġdoctr ine", + "B IN", + "s uffix", + "ar as", + "ĠS au", + "un boldmath", + "ĠM EP", + "ink er", + "Ġoptim ism", + "ĠLe uc", + "eful ness", + "cr ust", + "ĠKe ys", + "ĠâĻ ¦", + "ĠBrand t", + "âĮ ¬", + "ĠSevent y", + "Ġnurs ery", + "Ġdeput y", + "à ¬", + "on is", + "am us", + "ĠC ig", + "Ġex ergy", + "ĠF requent", + "Ġab or", + "ĠJ azz", + "Ġstat ue", + "ĠSc enarios", + "Ġcyt ological", + "fig ures", + "MC I", + "dir name", + "Ġcytokines is", + "del ivery", + "ĠBow en", + "Ġflank ed", + "Ġregener ating", + "ĠFerr ari", + "k iss", + "ĠA val", + "ĠC IT", + "ĠM um", + "ĠL SB", + "og ging", + "Ġun ited", + "Ġtri tium", + "ont amination", + "co ef", + "Ġprop ell", + "tri ple", + "Ġimm ense", + "Ġcompl ained", + "Ġdielectric s", + "ĠCardi omy", + "Ġflood ed", + "ĠCov ariance", + "Att endance", + "T MP", + "Ġs ob", + "ĠS onic", + "ĠF TS", + "ĠR SD", + "ess ors", + "ĠW on", + "iff s", + "Ġflow chart", + "ĠEle mental", + "Ġì ŀ", + "Ġfoli age", + "differenti ated", + "ĠGlob ular", + "Ġpercept ron", + "candid ate", + "S ocial", + "W itt", + "d yn", + "p aces", + "Ġm Glu", + "Ġb anned", + "ol inite", + "ĠF riends", + "ĠL ibraries", + "unc es", + "ĠRe ach", + "ĠSk ills", + "Ġrecip es", + "Ġcann ula", + "ĠOrth odox", + "ĠCarb ohydrate", + "Ġarom atase", + "Åij s", + "Ġeman ating", + "e lected", + "Ġt ense", + "ĠF LC", + "ĠL ET", + "her jee", + "Ġsub band", + "oph one", + "ĠAc tual", + "ms gs", + "EM D", + "IS ON", + "ley ball", + "ĠNi u", + "Ġber ries", + "diagn ostic", + "N ER", + "Ġd Ω", + "per centage", + "ĠH erman", + "ĠG SD", + "Ġsub problem", + "over all", + "oph or", + "Ġdel ocalized", + "acc ount", + "ĠGe ographical", + "dist ances", + "Ġà µ", + "Ġneurot oxic", + "opod ia", + "ĠDic er", + "Ġðx Ãŀ", + "Ġd unes", + "Ġwh it", + "ĠIm mediate", + "ĠÌ ¸", + "Ġadhes ives", + "ĠNS s", + "Ġguess ing", + "ĠColumb us", + "ĠUr ugu", + "behavi our", + "ĠSerb ian", + "benzodiox ol", + "im plementation", + "os ensitive", + "ĠF ill", + "ph age", + "rec overy", + "ES R", + "Ġanaly sts", + "Ġdiss atisfaction", + "band ed", + "ĠDep ressive", + "ĠRT s", + "Ref s", + "mill imeter", + "ĠOls en", + "am pton", + "ĠA CA", + "ĠA vian", + "ĠF owler", + "ub ini", + "est amps", + "ĠPro test", + "Con nection", + "Ġmer chant", + "ĠEN C", + "ĠRy u", + "ĠLymph oma", + "ĠLar ry", + "Ġjaponic um", + "ĠSymbol s", + "L ib", + "V G", + "ĠT av", + "ĠAs sim", + "ĠLe ung", + "depend ency", + "larg est", + "ĠDO E", + "Ġalign s", + "ofl urane", + "ĠAdj usted", + "Ġpeculiar ities", + "decre ase", + "ĠPlac ement", + "v ig", + "z ak", + "Ġp enta", + "Ġf res", + "Ġac ros", + "Ġsol vability", + "ans ions", + "AL A", + "Ġmal function", + "ĠGiov anni", + "A OR", + "H ad", + "Ġp orn", + "und ice", + "ĠU i", + "Ġexp elled", + "ĠAn k", + "Ġdisc ounting", + "ĠReg ulating", + "aster y", + "phen ylethyl", + "Ġcast ration", + "Ġeryth romycin", + "Ġbif unctional", + "� �", + "ĠAlger ia", + "m ess", + "Ġw is", + "ĠT ay", + "ass umed", + "Ġes calation", + "Ġhydro per", + "Ġcall osum", + "Ġatom ization", + "ĠSA W", + "Ġacetyl cholinesterase", + "Ġsucceed s", + "Ġphysi otherapy", + "t ro", + "Ġm ason", + "ĠT MB", + "Ġph ant", + "Ġadjust s", + "anth a", + "ĠEisen stein", + "Ġshorth and", + "G ABA", + "Ġpro ver", + "Ġpat rol", + "ĠMod al", + "oll aries", + "ĠInter facial", + "ĠCI A", + "att n", + "ĠCrypt ococcus", + "athe cal", + "ĠFresh water", + "Ġspectro gram", + "opid ogrel", + "m orphism", + "Ġrel apsing", + "Ġgeneral izable", + "ĠSh ale", + "ĠTrans plant", + "cont raction", + "UR I", + "ĠPet rov", + "ĠSl iding", + "Ġanterior ly", + "Ġquas ilinear", + "Ġrip ples", + "Z P", + "b acterial", + "s pr", + "an imal", + "Ġre porters", + "ĠB SS", + "ĠD ia", + "ĠR SC", + "ound ing", + "IT HM", + "log ical", + "Ġpoly carbonate", + "An imal", + "umb ai", + "Ġarch ived", + "ĠDur ham", + "âĸ Ī", + "ĠVerm ont", + "Ġp w", + "ess en", + "Ġconst expr", + "ĠPr uss", + "Ġsharp ness", + "div ide", + "prim itive", + "Ġacryl ate", + "MY C", + "ĠMond ay", + "ĠSrin ivas", + "B orn", + "at tice", + "om orpha", + "ĠM ERS", + "ĠF actory", + "ĠW N", + "rec tile", + "Ġheat s", + "UN K", + "Ġsynchron ize", + "ĠAtten uation", + "Child ren", + "P at", + "p regnant", + "Ġw ished", + "Ġth awing", + "ĠB ey", + "ĠD ÃŃaz", + "Ġle ather", + "ĠUn ic", + "Ġspecial ised", + "Ġcataly tically", + "PL GA", + "hydroxy ethyl", + "Ġmag mas", + "Ġpron oun", + "Ġeut rophication", + "ĠWeek ly", + "M HD", + "m alloc", + "ec ologic", + "il o", + "ĠF requencies", + "Ġor chestra", + "Ġmetabol omic", + "ĠBlock ade", + "Ġasser ted", + "ĠLew y", + "Ġallevi ating", + "Ġoccl usions", + "Ġchor oid", + "techn ical", + "Ġenvision ed", + "ĠHous ing", + "P n", + "ĠT ECH", + "ĠS SH", + "ĠV alle", + "yl methyl", + "Ġph loem", + "ĠPro jects", + "but ton", + "Ġacceler ometers", + "umn i", + "ĠHand ling", + "Ġvas o", + "perme able", + "Ġc ords", + "ĠC f", + "ĠD z", + "Ġed itions", + "Ġhum erus", + "do ors", + "Ġdors olateral", + "Ġapt amers", + "Ġcommod ities", + "osper ms", + "Ġprednis one", + "I Q", + "M etal", + "t us", + "Ġis otopy", + "ĠThe ater", + "iff i", + "Ġy arn", + "de letion", + "ĠQ PO", + "Ġmulti objective", + "Ġur chin", + "Ġpuls ations", + "ĠSR P", + "ð tÃŀ", + "gluc oside", + "Ġdepart ures", + "Py Object", + "ĠBand width", + "ĠAccept ance", + "re ys", + "ĠI ON", + "Ġcomp uls", + "ĠJ W", + "Ġpart hen", + "Cl ose", + "ĠBa TiO", + "ñ oz", + "aggreg ate", + "Initi ally", + "q h", + "ĠC ancers", + "op in", + "ne ver", + "ism an", + "Ġconst ancy", + "Ġtr ucks", + "Ġvisual isation", + "ĠIll ness", + "Ġsulph ide", + "ĠMetabol ites", + "Ġoxys porum", + "H PP", + "Ġnor adrenaline", + "Ġcommut ativity", + "Qu ad", + "Ni O", + "ĠGet ting", + "Ġba it", + "Ġë °", + "Ġment ally", + "Ġaur oral", + "ĠDraw ing", + "S in", + "re ceiver", + "at ov", + "is otope", + "Ġis othi", + "ĠS enes", + "ĠA CO", + "ĠG CT", + "ys mal", + "ĠV og", + "Ġdist ractors", + "Ġconnected ness", + "Ġaccum bens", + "ä ck", + "hyd rated", + "Ġpharmac odynamic", + "Ġmineral ogy", + "Ġarth ropods", + "Ġmyc otoxins", + "Ġbatt les", + "ĠS ara", + "ĠE IS", + "ĠW inn", + "Ġlimb ic", + "WOR K", + "Å ½", + "Ġe aten", + "ĠT od", + "ap illary", + "ox yp", + "ĠNew ly", + "Ġcam el", + "arr ison", + "ECT OR", + "Ġhop efully", + "ĠHur witz", + "Ġib uprofen", + "ĠFIR ST", + "Ġbist able", + "Ġdismiss ed", + "g at", + "in ogen", + "ĠP ON", + "ph as", + "ĠK orn", + "Ġpoly aniline", + "ĠMic roscope", + "Ġmuc ous", + "Ġcollision less", + "hydrogen ase", + "Bu ild", + "pair ing", + "ĠWI MP", + "built in", + "ĠSepar ate", + "ĠCun ningham", + "ĠNecess ary", + "Ġb ry", + "ec rosis", + "ĠL SS", + "Ġsy philis", + "ĠV id", + "Ġcar rot", + "ĠRes istant", + "reg istration", + "Ġmy opathy", + "Ġang ry", + "MD R", + "Ġhypothesis ed", + "ĠVol terra", + "ele vation", + "Ġmyc obacteria", + "Ġcaud ate", + "i idae", + "Ġ Ç", + "ĠD ich", + "ĠR eth", + "ell us", + "ch amber", + "sh ine", + "och ore", + "ĠCol umns", + "CO UNT", + "Ġïĥ ²", + "ĠPrim ordial", + "Ġnegoti ations", + "sted t", + "R II", + "U ES", + "ti ques", + "ĠP fe", + "Ġpl ast", + "pr on", + "ĠZ w", + "ink ler", + "Ġmetabol ome", + "EG A", + "ĠSpect rophot", + "Ġubiqu ity", + "ĠElectro des", + "Ġchond ro", + "Domain Is", + "ĠResid ues", + "Ġdns DomainIs", + "D IC", + "p th", + "Ġa est", + "Ġc ient", + "Ġp essim", + "Ġre inst", + "ĠS ans", + "end azole", + "ĠU rine", + "Ġsub acute", + "ix imab", + "Ġprof itable", + "Ġmaxim ise", + "ĠDel aware", + "Ġclinic opathologic", + "Thermo Fisher", + "F AR", + "R AS", + "w itch", + "in activated", + "en esis", + "un less", + "ĠP anc", + "ĠM TS", + "ĠB ast", + "Ġch illing", + "Ġinc umbent", + "Ġj elly", + "Ġdistrib utive", + "Ġcy to", + "sc hen", + "Ġinduc ers", + "ĠNone quilibrium", + "ĠRob otics", + "ĠArgent ine", + "Ġmerid ian", + "Ġhun ger", + "Adap tive", + "Ġg or", + "ile psy", + "Ġnon vanishing", + "Ġpe ti", + "ĠMet formin", + "Ġbiom aterial", + "Ġanten nal", + "ĠAff ective", + "ĠAqu atic", + "enedi amine", + "ĠSiber ia", + "ĠPenic illium", + "F unctions", + "Ġ lec", + "Ġf eld", + "ĠS part", + "ĠC ement", + "ad di", + "se k", + "ĠN p", + "oles ky", + "ĠMac roscopic", + "è res", + "Ġcave at", + "Ġcourts hip", + "m ice", + "Ġf ence", + "Ġm ined", + "ul ink", + "ID A", + "Ġtrunc ate", + "ĠCatal an", + "Ġtran st", + "Ġamend ments", + "uncertain ty", + "Ġoroph aryngeal", + "ĠA id", + "ould er", + "ĠInc ident", + "Ġá IJ", + "angi ogenesis", + "ĠBE H", + "Ġic osa", + "ĠFOX P", + "frag ment", + "Ġscintill ator", + "J O", + "L aw", + "Ġp L", + "Ġet oposide", + "Ġpoly aden", + "Ġhabit ual", + "Ġtax i", + "Ġcum ulant", + "Ġhind rance", + "trig ger", + "r atios", + "il io", + "ĠP IR", + "ĠThe od", + "ĠM orton", + "ĠH af", + "ĠO ch", + "ĠEx o", + "Ġur tic", + "ĠCF RP", + "Sc reen", + "Sl ice", + "Ġmush rooms", + "Ġevanes cent", + "S x", + "Ë IJ", + "ì ŀ", + "Ġs igm", + "ic l", + "Ġg uests", + "ĠG IST", + "Ġdeform ities", + "poly acrylamide", + "Sign ificant", + "Ġimpression s", + "j math", + "em oral", + "ĠB n", + "ĠH DR", + "ĠK eck", + "Ġval ine", + "sp i", + "iter ate", + "Ġsyn c", + "oti ana", + "Inter val", + "ĠBra uer", + "Ġstic ky", + "ĠNeuros cience", + "Bax ter", + "Ġc asts", + "all ocation", + "ne al", + "Ġbi op", + "Ġrest orations", + "Im ages", + "mi tic", + "ĠEle vation", + "Ġabst inence", + "ĠLess er", + "ĠRain fall", + "P AM", + "W ol", + "us ch", + "Ġprom isc", + "na ïve", + "Ġded uc", + "acchar ide", + "Ġnom inally", + "ĠExpl oratory", + "Ġreconc iliation", + "linal g", + "T CR", + "Ġs ore", + "ĠN ab", + "Ġout group", + "Ġmon ophosphate", + "ins u", + "ĠAd dis", + "SP R", + "point ing", + "HE RE", + "ĠTechn ological", + "Ġcoch lea", + "Ġspheroid al", + "ĠBald win", + "F eed", + "Ġf using", + "Ġas per", + "Ġex osomal", + "ĠL inguistic", + "SC A", + "ĠEm pty", + "Ġvac ant", + "gly col", + "immun oprecipitation", + "ĠIT ER", + "Sn O", + "pattern s", + "contin ental", + "ĠAcceler ating", + "ĠAver aging", + "Ġchemoattract ant", + "h b", + "s ulph", + "ĠB x", + "Ġcom plicating", + "ĠW are", + "Ġso aking", + "Ġup regulate", + "-------- -", + "Ġsem ester", + "ĠBro d", + "Ġcasc ading", + "ĠCast ell", + "ĠẠ½", + "ĠEQU ATIONS", + "Ġparsim onious", + "Ġs orbent", + "Ġe ug", + "od in", + "ĠW ig", + "ĠTh ir", + "Ġsol v", + "Ġcar boplatin", + "Ġz ebra", + "ven ient", + "Ġmed Rxiv", + "Ġaut obi", + "Ġrepe atable", + "Ġmig rations", + "ĠÐ ´", + "hol onomic", + "Ġmoder ator", + "Ġchim era", + "ĠGrassmann ian", + "ĠR onald", + "ĠV ega", + "ast es", + "Ġqu otes", + "Ġmon ic", + "Ġprec oding", + "ĠAss isted", + "ĠNetwork ing", + "Ġfabric ating", + "Ġbot anical", + "Ġswarm s", + "Ġmartens itic", + "ellip tic", + "pher d", + "b aryon", + "x fe", + "ro ute", + "ĠF IL", + "op ies", + "ĠPC Bs", + "Ġer asure", + "ĠRem odeling", + "Ġana er", + "Sm ad", + "inj ured", + "Ġimmunocomp etent", + "d ell", + "f ailed", + "Ġs inking", + "or acic", + "Ġd red", + "ĠV DR", + "Ġconn ectors", + "Ġintr atumoral", + "Ġcommut ators", + "ĠAle ks", + "ĠDic ty", + "A k", + "Ġre calc", + "Ġis l", + "ot rim", + "nce phal", + "ĠRe es", + "Ġste atohepatitis", + "ĠPolar ized", + "SB ATCH", + "ĠCross ing", + "Acc uracy", + "ĠGi ardia", + "ĠNov o", + "Ġvig ilance", + "Ġphosphatidyl choline", + "ĠUE FA", + "J im", + "Ġf asted", + "ĠT iny", + "Ġl ang", + "iss ociation", + "Aut o", + "ĠNor folk", + "ĠArm s", + "ĠSW I", + "ĠAmb ros", + "transf ection", + "O ryza", + "h arm", + "ĠD s", + "Ġint rag", + "Ġcall er", + "Ġwr itings", + "ĠEl ast", + "ĠMar vel", + "ĠImmun odeficiency", + "ĠMill ion", + "Text ure", + "ĠIce Cube", + "sn ap", + "Ġenj oys", + "ĠChap el", + "ĠEstabl ishing", + "Act ually", + "Ġphosphoryl ates", + "Ġchin ensis", + "Ġrhabd omy", + "Ġemphys ema", + "M iddle", + "n ant", + "Ñ ħ", + "Ġt art", + "low est", + "hem ia", + "Ġutil ising", + "cons tit", + "Ġmag matism", + "о ÑĢ", + "ĠHas an", + "dispers ed", + "H ear", + "Q t", + "z ations", + "al on", + "ĠS tau", + "ĠA mer", + "os ystems", + "Ġdem arc", + "ĠNe oproterozoic", + "ĠMe k", + "ĠDis closure", + "Ġhemat ocrit", + "ĠCyt oscape", + "Ġram ification", + "Ġcommunic ative", + "Ġbutter flies", + "Ġantis era", + "Ġaestiv um", + "B ra", + "L TP", + "s ocket", + "ĠC herenkov", + "Ġch lam", + "ang ial", + "ult ured", + "eng ed", + "ĠCl inton", + "Ġmy oblasts", + "ĠComp ensation", + "ymmet rically", + "Ġemploy er", + "oz ol", + "ĠSA XS", + "Ġretin as", + "piper idine", + "XY Z", + "ĠRough ly", + "P rep", + "Ġb inge", + "Ġe rect", + "ĠO PER", + "Ġstress or", + "Ch rist", + "ĠPD Z", + "Ġsubst an", + "ĠSn ail", + "Ġlam ellae", + "ĠCycl ing", + "shif ting", + "ĠHs ieh", + "ver ify", + "Ġpre image", + "Ġar tillery", + "Ġep il", + "ĠAp ost", + "Ġhel met", + "Ġmach ined", + "ĠMin neapolis", + "ĠCr yp", + "Ġsitu ational", + "pass ing", + "quin azolin", + "ĠCro atian", + "Ġsta ircase", + "Bon net", + "N LP", + "c ium", + "Ġs keletons", + "Ġo xim", + "or ib", + "Ġre ticular", + "ĠS LS", + "ĠA romatic", + "ĠK es", + "Ġph or", + "Ġinv ocation", + "Ġdo zens", + "ai vely", + "Ġdetect ability", + "Ġconcer ted", + "yr ins", + "ĠProcess or", + "Ġtoler able", + "att ached", + "Ġanne xin", + "ĠROS AT", + "ĠAltern ate", + "ĠWa velength", + "ĠWill is", + "Ġsemic ontinuous", + "Ġadvoc acy", + "Ġoblig ation", + "chan ter", + "ĠInser tion", + "Ġsymbion t", + "Z M", + "Ġt ars", + "ro f", + "Ġre vival", + "ĠT ST", + "ĠE MP", + "Ġme x", + "ull in", + "ĠAd op", + "ĠDNA s", + "Ġemploy ers", + "MT s", + "ĠMart ÃŃn", + "electro des", + "ĠMedica id", + "Ġt gt", + "Ġl ognormal", + "ĠF rames", + "Ġper missive", + "ĠAr duino", + "Ġsem ilinear", + "ĠAss ign", + "ĠPr EP", + "ĠSi amese", + "benz imidazol", + "conn ectivity", + "ĠPE I", + "Ġbis ulfite", + "Ġacetyl transferase", + "Ġswim mer", + "ju ven", + "Ġjejun um", + "ĠCinc innati", + "ta i", + "ĠQ I", + "ĠCom mut", + "sp acing", + "Ġaff ords", + "itis ation", + "elastic ity", + "Ġdrag on", + "Ġproteas omal", + "Ġp ant", + "ĠN itro", + "Ġsp ic", + "Ġnan opl", + "ĠAll ied", + "Ġthor ax", + "ĠFT O", + "ĠJur kat", + "chiat ry", + "y oung", + "di rections", + "Ġne ocortex", + "ĠK ik", + "ang o", + "cl ay", + "iod o", + "Ġabove mentioned", + "ĠGu ardian", + "Con jecture", + "ĠTre nd", + "Ġfertil ized", + "ĠSulf ate", + "ochron ology", + "Ġcrani ofacial", + "ĠSask atchewan", + "Q Q", + "h man", + "Ġz ym", + "log s", + "Ġïģ ®", + "Ġgrad uating", + "pin ene", + "Ġî Ģ", + "Ġeti ological", + "ĠComprehens ion", + "Ġw andering", + "Ġl an", + "Ġsy st", + "return s", + "MO F", + "cho alveolar", + "ĠArm en", + "Ġbim etallic", + "ĠPoll en", + "F iles", + "Ġs sp", + "EN SI", + "ĠY us", + "Ġfin est", + "AG EN", + "Ġmicrobi omes", + "Ġpal ind", + "Ġpet als", + "ĠRadi otherapy", + "ophen one", + "spe aker", + "Ġcopep ods", + "Ġkan amycin", + "Ġdegran ulation", + "C onstruct", + "al ter", + "ĠF gf", + "ĠN BS", + "ĠIn complete", + "Ġpar cel", + "ne au", + "Ġà IJ", + "ĠCH A", + "Ġdual s", + "Ġsilic ates", + "ĠGlob ally", + "Ġkines in", + "f id", + "ĠC PD", + "ĠY ad", + "Ġdep ress", + "OD Y", + "ĠHist ograms", + "ĠSumm arization", + "aut omatic", + "ĠDom in", + "otrans formation", + "Ġventric les", + "Wid get", + "ĠPeters burg", + "Ġcholangi ocarcinoma", + "Ġnect ar", + "P IC", + "S cope", + "T ek", + "n itz", + "ĠP HD", + "Ġsp iro", + "ĠCO G", + "ĠDi oxide", + "conduc tivity", + "ĠGran ger", + "ĠWear able", + "ĠKenn eth", + "C CR", + "L INK", + "Ġ Ü", + "re tic", + "ly a", + "Ġdem ocratic", + "Ġradi ograph", + "ĠRel ax", + "ĠInc ubation", + "ĠDen oising", + "COL OR", + "ĠClos ure", + "H MM", + "ur d", + "ra da", + "ĠR v", + "ĠL uz", + "all s", + "Ġmulti spectral", + "IN ED", + "SC N", + "Ġdys lexia", + "Ġsett lers", + "ĠVL SI", + "Ġa vid", + "Ġl arynx", + "ĠC hess", + "ĠF AA", + "Ġdef ender", + "Ġlip olysis", + "ĠEl mer", + "ĠAff ymetrix", + "Ġrhod amine", + "M orph", + "S ite", + "p urity", + "Ġ Ê", + "ĠT ank", + "ĠM iao", + "Ġrec rystall", + "We yl", + "ĠGu il", + "Ġmis folded", + "su ited", + "ĠApproxim ations", + "ĠABC B", + "don or", + "GW AS", + "------------ ---", + "Ġpu tida", + "Ġimping ement", + "yam l", + "H ill", + "Ġt l", + "ag ua", + "tim ing", + "Ġreg enerate", + "Ġmulti lingual", + "rad or", + "class ifier", + "ĠJoh ansson", + "Ġsulf ides", + "ham mer", + "Ġwalk ed", + "Ġalloc ating", + "ĠGust av", + "Ġimmunoprec ipitated", + "ĠBris bane", + "Ġsandwic hed", + "ĠChatter jee", + "omand ibular", + "Ġo sc", + "Ġass ass", + "Ġmulti stage", + "Ġmulti partite", + "Ġpig mented", + "ĠVisual izing", + "Ke ys", + "pip eline", + "Ġdub bed", + "Ġc roc", + "ĠD LC", + "ĠR AT", + "ĠN ex", + "plic a", + "ting ham", + "ĠSp ider", + "Ġunc le", + "aut s", + "ĠHow e", + "Ġarth ropod", + "ĠPap ad", + "urg y", + "Ġaccl im", + "B road", + "ac er", + "ve z", + "ĠD ivers", + "Ġmod ifiable", + "Ġanti psychotics", + "Pro g", + "osa hexa", + "amb rian", + "ĠIon ization", + "Z A", + "o ate", + "Ġp ays", + "Ġe wes", + "Ġbe aches", + "Ġev il", + "ĠCD s", + "na ud", + "Ġconform ity", + "ĠDM N", + "Ġcollabor ate", + "Ġdeterior ate", + "VAL ID", + "ĠVeg as", + "Ġultrac ent", + "B RA", + "R ub", + "Y C", + "f h", + "å ľ", + "ĠO WL", + "ose ismic", + "of errin", + "och thon", + "ĠTNF R", + "small setminus", + "ĠArg ument", + "Ġgranul ocytes", + "Ġram ified", + "Ġepi phy", + "f usc", + "ec dot", + "Ġh w", + "ĠN MS", + "erc us", + "Ġtet her", + "ĠTra it", + "Ag Cl", + "ĠNear by", + "Ġhelmin th", + "Ġlae vis", + "ĠB AR", + "ĠN ancy", + "ĠG yn", + "Ġsec reting", + "St ellar", + "Ġsil hou", + "IM T", + "Ġscaffold ing", + "ĠConver ter", + "h id", + "Ġn ud", + "est rian", + "ann o", + "Ġdep iction", + "orem ost", + "ĠSh and", + "AB CD", + "ĠPD L", + "Ġdys phagia", + "Ġintr at", + "Ġhem ip", + "Ġadapt able", + "long mapsto", + "ss bauer", + "ĠMcC arthy", + "ĠAuto immune", + "ĠCut aneous", + "Inser ting", + "M aterial", + "ĠA a", + "ĠG av", + "Ġmon ocular", + "equ il", + "ĠGe off", + "Ġtet hered", + "obil ized", + "ĠShort ly", + "Det ails", + "Ġrefuge e", + "Ġabsc isic", + "FBQ yx", + "Ġdemoc racy", + "c rafted", + "d ifluor", + "y der", + "ess ment", + "Ġhist opathologic", + "Ġast rocytic", + "Ġwithd rew", + "Ġm oles", + "ath ic", + "mon o", + "man ual", + "Ġfood borne", + "ĠRep ository", + "Ġcover t", + "OT E", + "Ġtight ness", + "Ġinstanti ated", + "Ġwatermark ing", + "Ġartem isinin", + "L anguage", + "O ES", + "c ant", + "al ready", + "un ts", + "iti a", + "ĠK aren", + "Ġall uvial", + "stratig raphy", + "ĠP IV", + "ĠF aces", + "ĠB im", + "ap plications", + "ta ils", + "Ġel d", + "IR B", + "ĠIN TE", + "ĠNot Implemented", + "Ġmis classified", + "Ġfertil izers", + "ĠElectric ity", + "Ġtribut aries", + "ĠDeut sch", + "Ġslee ve", + "f uzzy", + "ĠM TL", + "ĠB res", + "ĠW yn", + "Ġk yr", + "ne uronal", + "ox ymethyl", + "dis order", + "inc hes", + "ram idal", + "Ġpoly imide", + "Res Net", + "ĠEd mund", + "Ġdegener acies", + "uther ford", + "Drop out", + "ij Ģ", + "Ġv oiced", + "ĠG omes", + "iv ities", + "con ductance", + "com pl", + "vec s", + "Ġtun a", + "ĠKin ect", + "Ġconvey ed", + "Ġsphing osine", + "b at", + "ĠP urs", + "ound ed", + "ĠSt am", + "ĠX III", + "ĠCom ics", + "MS M", + "SS L", + "Ġperf luor", + "Ġfluor inated", + "foli os", + "Ġre position", + "ĠS err", + "ĠC ors", + "ĠL abs", + "Ġco x", + "ĠAc quired", + "Ġreason ed", + "Gen ome", + "ĠPi per", + "Ġcompac tified", + "Ġherbiv ore", + "lofen ac", + "Ġb oss", + "ĠB s", + "ĠE MR", + "Ġsh oe", + "Ġcare rs", + "Ch rom", + "SV P", + "ĠTri angle", + "Ġhemat ite", + "dor f", + "ĠMove ments", + "ĠVes icles", + "Olymp us", + "M ol", + "Ġl end", + "ur as", + "ĠA SE", + "ĠW KB", + "pro ved", + "ĠK V", + "ĠU ART", + "log arithmic", + "ĠAD I", + "ĠDo ing", + "Ġce ase", + "Ġleng thening", + "Ġpyrophosph ate", + "F re", + "ĠC LD", + "ĠM LS", + "ĠPl um", + "Ġprop ionate", + "ĠGu atem", + "CK D", + "Ġis os", + "ĠM anning", + "ne uro", + "OP ER", + "ĠWil helm", + "Ġacad emia", + "ACh R", + "ĠIner tial", + "O cc", + "u jan", + "on as", + "Ġin ulin", + "ic ia", + "and al", + "ĠK ahn", + "Ġun manned", + "ĠCo arse", + "Ġgu ilty", + "ĠPe i", + "ĠLuc a", + "ĠFib roblast", + "a vian", + "v x", + "Ġd izziness", + "ĠD ox", + "ĠH our", + "Ġdec oration", + "Ġver ifier", + "rad o", + "Ġfoot prints", + "Ġdisp ensable", + "ĠAna erobic", + "Io T", + "ĠR isks", + "ĠG LS", + "Ġch ords", + "oid y", + "Ġneu rolog", + "ru h", + "Ġvirtual ization", + "Ġproton ation", + "ĠConstant in", + "Ġkeyp oints", + "B uck", + "H opf", + "M uch", + "reg ime", + "Ġprom ised", + "ai j", + "ĠDes ulf", + "ĠForm ulas", + "Ġhum p", + "ln c", + "ĠSu icide", + "ĠHO MA", + "ogly cer", + "ĠProte omics", + "Ġdict ate", + "ĠSper mat", + "F un", + "Ġs ag", + "ĠF am", + "ep pe", + "ĠJ ah", + "Ġar isen", + "oph armaceutical", + "SA GE", + "ĠTH IS", + "enh ance", + "Ġnap us", + "ro e", + "ens ch", + "de formation", + "bon es", + "ĠEr nest", + "ira bility", + "dec om", + "Ġcrust aceans", + "Ġguarantee ing", + "OV As", + "ĠMultic enter", + "Ġct DNA", + "Ġforamin ifera", + "L inn", + "Ġc ups", + "es ch", + "Ġd F", + "ĠT ah", + "pl l", + "pro jects", + "ĠU CI", + "Ġhuman ized", + "Ġabs l", + "ĠSch o", + "Ġliter als", + "ĠSV R", + "Ġtoxic ology", + "pg f", + "ĠIPT G", + "ĠMEASU REM", + "o ing", + "ĠP asc", + "ĠB au", + "ĠW annier", + "Ġhyp re", + "att ributes", + "Ġprecondition er", + "Wr iting", + "Ġgyp sum", + "y uan", + "Ġup regulates", + "Ġte lec", + "ĠDisc re", + "gu ard", + "Ġdeb ates", + "Ġparasit oid", + "L am", + "ti ge", + "Ġis opropanol", + "ĠI was", + "pl ify", + "ind olin", + "ĠAp ollo", + "Ġland ed", + "Ġbeam line", + "Un ion", + "Ġrecipro c", + "ĠRoss by", + "princ ipal", + "Ġdescend ant", + "ĠAnalog ously", + "Ġdereg ulation", + "D SM", + "c ta", + "Ġre built", + "ĠM und", + "ĠF EC", + "ry n", + "plic e", + "ĠY ugoslav", + "ĠNorth western", + "ĠHom ogen", + "ĠLI SA", + "Ġinvest or", + "H SA", + "H PO", + "Ġd ictionaries", + "ĠC ategor", + "Ġcomp acted", + "till ed", + "ç »", + "Ġf ines", + "ur ans", + "Ġbetween ness", + "ĠZ ig", + "sc hema", + "Ġcommun e", + "ĠQu inn", + "Ġana phylaxis", + "TI ES", + "Ġsnow pack", + "ĠDO A", + "ag os", + "ĠO dd", + "ard e", + "Ġev oke", + "ĠOc ular", + "Ġfa ulting", + "Ġvolcan ism", + "ĠPale ozoic", + "Ġmycel ium", + "ĠAdjust ment", + "I CT", + "N ov", + "al ias", + "ĠT ul", + "ĠH h", + "Ġev ade", + "OR s", + "Ġstreng thens", + "ĠUS GS", + "Ġlic ensing", + "ĠCle ment", + "ĠPhyt ophthora", + "r ified", + "Ġe ighteen", + "Ġto ps", + "ĠC LP", + "Ġst abilities", + "ĠP PT", + "ĠB IN", + "ĠR ak", + "Ġgen istein", + "vol ve", + "Ġquick er", + "ĠCaus ed", + "benef it", + "Y B", + "l ift", + "Ġh ood", + "ĠS Cs", + "of a", + "ĠMic ron", + "angi otensin", + "Ġfeat hers", + "Ġantifer romagnet", + "DEC REF", + "yled ons", + "Ġmyri ad", + "Ġ iz", + "ĠT rough", + "âĪ «", + "hem oglobin", + "ĠEn velope", + "ĠCl ick", + "sol iton", + "ĠSyn chrotron", + "Ġlag ged", + "MY B", + "Ġtroph oblast", + "Ġinterrog ation", + "onv uls", + "B ac", + "Ġa periodic", + "Ġg pu", + "Ġpro pidium", + "te ps", + "ĠK arp", + "ĠV az", + "ack age", + "ons on", + "In str", + "fil er", + "rifug ation", + "KO V", + "four th", + "Ġôı¼ IJ", + "hyper bolic", + "sche tz", + "Disc ussion", + "ĠOrient ed", + "j ad", + "Ġa uctions", + "us ivity", + "ĠC ran", + "Ġk d", + "Ġint est", + "ros arcoma", + "ugg er", + "ĠIL P", + "ĠST A", + "Ġrevers als", + "Ġgrap es", + "ĠPop ulus", + "ĠKit aev", + "ĠAV P", + "Pre viously", + "Ġquadr atically", + "ĠLOC AL", + "B ert", + "P ED", + "l ive", + "à ¬", + "Ġb idding", + "Ġto ss", + "ent o", + "Ġth ylak", + "Ġcomp rehend", + "Ġdi ve", + "Ġapplic ants", + "ĠÄ ħ", + "ĠVol canic", + "adap tation", + "Ġá¹ Ģ", + "ĠJans sen", + "Ġadjo ining", + "ozol omide", + "C IS", + "d C", + "duc ted", + "ĠAn ast", + "ĠEm ployment", + "ĠEnd ocrine", + "sil oxane", + "S ession", + "ĠN arr", + "ĠâĪĴ âĪĨ", + "de ev", + "oth iaz", + "ring ing", + "po inted", + "Ġacet ylene", + "Ġglob ulin", + "pack ing", + "ĠUs es", + "A ES", + "H en", + "ĠS avage", + "ĠC anc", + "ist o", + "ĠChrom osomal", + "Ġcement ed", + "Ġpyro x", + "ĠConstit utive", + "Ġphthal ate", + "mechan ism", + "Ġcyclospor ine", + "P AP", + "ar ted", + "ĠR DT", + "Ġpl ains", + "Cl one", + "prop anol", + "regular ity", + "Ġcot angent", + "ĠLes lie", + "ĠNit rate", + "ĠKaw asaki", + "ĠPage Rank", + "Ġanhyd rase", + "ĠKrish na", + "Ġhemicell ulose", + "Ġ ery", + "ll is", + "Ġmicro gram", + "ĠDel igne", + "Ġenfor ces", + "Ġthrombol ysis", + "P arse", + "or vastatin", + "Ġm ated", + "ĠC rystalline", + "Ġaut oradi", + "Ġtherm ophilic", + "inf ectious", + "Ġult ram", + "ĠML L", + "ĠFib ers", + "Ġulcer ation", + "omed ial", + "stratig raphic", + "Ġtouc hes", + "r he", + "Ġt ame", + "ĠC ulic", + "AR DS", + "ch ter", + "Ġcounter clockwise", + "Ġcam ps", + "VD C", + "Ġmeth adone", + "depend ently", + "valid ate", + "Ġprecl udes", + "Ġparliament ary", + "ĠINTE REST", + "ĠS erg", + "ĠC BC", + "ere lla", + "ay i", + "ĠR AB", + "Ġch ym", + "Ġnan ospheres", + "Ġdiab etics", + "cons ervation", + "Ġperme ate", + "plot ted", + "Ġna phthalene", + "ĠBon n", + "ĠElectro static", + "Ġinvent ories", + "Gaussian ity", + "ĠAden osine", + "Del ay", + "ĠBegin ning", + "Ġs ided", + "ĠC ushing", + "ĠH v", + "Ġco ined", + "ĠAl m", + "sc anning", + "fer til", + "Ġα v", + "ĠRe activity", + "Ġproxim ate", + "depend encies", + "Ġdens ification", + "Ġôı¼ ij", + "Ġbacteri ocin", + "weak ly", + "Ġdenti stry", + "ĠOri ental", + "Ġdorm ant", + "Ġp C", + "Ġm um", + "RE s", + "Ġcon val", + "Ġbi ota", + "Ġmulti linear", + "ĠPT FE", + "Ġnarrow band", + "ĠRot ational", + "Ġhoney bee", + "ĠChlor ophyll", + "Bas eline", + "F ern", + "Ġl k", + "ĠM ash", + "ri ved", + "ĠB ases", + "ĠD ah", + "ĠK ui", + "Ġà ĵ", + "ĠRec ycl", + "AG N", + "PD E", + "Ġclim atological", + "ĠBas ically", + "cons erved", + "abs orbing", + "ĠKos zul", + "ouss ines", + "Ġm dx", + "ith ymia", + "ĠH inton", + "Ġk h", + "Ġad mittance", + "ĠV y", + "Ġext rema", + "Ġcre ftype", + "sub st", + "Ġble omycin", + "LINE AR", + "A Q", + "i om", + "Ġn ong", + "op ian", + "se in", + "ud al", + "Ġear ning", + "Ġstandard ize", + "ĠPar ticular", + "Ġwave vector", + "dx dy", + "ĠMac Donald", + "ĠEst uary", + "valid ated", + "ĠHur st", + "ĠMuk herjee", + "Ġbival ves", + "Ġjug ular", + "U b", + "v ill", + "en ough", + "Ġin forms", + "an atomical", + "ul ou", + "res a", + "ĠP MC", + "ĠM ira", + "ĠR PL", + "ĠSD C", + "Ġhem i", + "Mo S", + "ĠFlo at", + "Ġoccl usal", + "ĠRain bow", + "ĠProvid ing", + "Ġsupercapac itor", + "os f", + "ĠI RT", + "Ġad m", + "Ġdec oders", + "ĠX R", + "ĠRes cue", + "Ġent om", + "Ġmor tal", + "An gle", + "Ind ia", + "ĠMal i", + "Ġinsp ecting", + "ĠGALAX Y", + "ĠEri ks", + "Y F", + "r ings", + "Ġs ir", + "Ġg sl", + "ĠB ubble", + "ĠD CA", + "ĠW idespread", + "ass ignment", + "Ġge omorph", + "ĠPre ference", + "CO PD", + "process ors", + "cut off", + "ĠFlow er", + "phen omen", + "mus ic", + "ĠSlov akia", + "Support ing", + "b low", + "ed it", + "ĠT rophy", + "ĠA SF", + "ĠM oses", + "Ġind els", + "Ġnon human", + "Ġhand ic", + "Ġrepair ing", + "Ġmicrom eter", + "ĠPhilip pe", + "Ġexud ates", + "ĠâĹ ĭ", + "Ġamalg am", + "K in", + "f ors", + "f ron", + "Ġan abolic", + "ĠE ich", + "NA N", + "Ġpseud ogap", + "analy zed", + "Ġtack led", + "agin ous", + "Ġlubric ant", + "Ġradion uclides", + "arrest in", + "oussines q", + "L if", + "Î ¥", + "re ceived", + "as tive", + "ĠP BC", + "Ġam oxicillin", + "cop per", + "ubl ing", + "oph ages", + "ĠSe as", + "ĠEl ite", + "PM MA", + "Ġchol ang", + "Depend ing", + "Ġas bestos", + "ĠF ecal", + "ĠR ath", + "ĠL ey", + "Ġfact ored", + "bb les", + "Ġtoken izer", + "Ġofficinal is", + "ĠNUC LE", + "ĠS emicon", + "ĠB ous", + "ĠR is", + "Ġlo ans", + "AC P", + "âĻ Ģ", + "phos ate", + "Ġc herry", + "an an", + "ar re", + "ĠC redit", + "ise xual", + "ĠAc ta", + "ĠLet ting", + "ĠInf arction", + "ĠAcc ounting", + "Ġcounter stained", + "Ġaer ogel", + "standard ized", + "Ġly ase", + "seg ments", + "Ġbac helor", + "Ġh ue", + "ĠN ETs", + "Ġun adjusted", + "Ġmicro hardness", + "Ġsingle ts", + "ĠSP ACE", + "ĠHyd raulic", + "MET HOD", + "ĠBj ör", + "ĠK U", + "Ġrep ur", + "Ġradi ocarbon", + "Ġheter ogeneities", + "Ġgast rocnemius", + "ĠLT D", + "Ġaccident ally", + "Process ing", + "Dop pler", + "T BI", + "Ġl ingual", + "ĠA GS", + "ĠF rontal", + "ĠB rack", + "the ma", + "Ġrepresent able", + "Ġpress urized", + "AD R", + "ĠMicro fluid", + "Ġê °", + "Ġreus able", + "Ġv endor", + "all er", + "Ġdi version", + "FA ST", + "ĠKir by", + "ĠStim ulus", + "Ġattach ments", + "ĠBrid ging", + "ĠRober to", + "Ġqueu ing", + "t ling", + "ro ots", + "ĠM x", + "ĠM arrow", + "ĠL ocus", + "Ġun important", + "erg arten", + "ÃŃ k", + "ĠPot ent", + "ĠBruns wick", + "ĠS CT", + "ĠM our", + "em ias", + "ĠN CS", + "ch icine", + "ĠO ryza", + "Ġwhere ver", + "ĠX GB", + "CO X", + "Ġhydrogen ated", + "Ġhyd raz", + "ĠPers ons", + "Ġframes hift", + "Ġelectroly tic", + "ĠSen egal", + "Ġphag ocyt", + "Ġinstantaneous ly", + "ĠGround water", + "Ġimper ial", + "ĠRhod e", + "ÅĦ ska", + "ovis ual", + "onts ize", + "ĠExplan ation", + "Ġempower ment", + "N TA", + "P u", + "P or", + "S ched", + "e ats", + "Ġ ys", + "in ous", + "Ġw ilt", + "ĠM ov", + "ect on", + "ĠG ins", + "int roduction", + "ince ption", + "ĠInter preting", + "Ġstart up", + "Ġalb ino", + "Ġtet ras", + "ĠHouse hold", + "ĠEL M", + "Ġspor ulation", + "Ġosm ol", + "B is", + "er ule", + "ĠE AR", + "Ġim balances", + "Ġk t", + "Ġj l", + "ges terone", + "eral a", + "ĠPo inter", + "ĠHR QoL", + "ĠRi et", + "ĠEsc ape", + "pur ified", + "Ġinstanti ation", + "m atis", + "ion a", + "Ġn oxious", + "ĠN og", + "Ġj am", + "ĠAnt oni", + "ĠGod d", + "ĠPersonal ized", + "Ġperm uted", + "ĠS HE", + "ĠO blast", + "ĠFor bes", + "ĠRes veratrol", + "ĠFe Se", + "Ġelectro deposition", + "Ġhome obox", + "Ġpy ogenes", + "Ġviol in", + "Ġiso electric", + "ĠPP G", + "prob ably", + "AMP K", + "ĠWol fe", + "Ġultraf ine", + "B eyond", + "on at", + "ed ian", + "EN ABLE", + "ĠH AM", + "so ut", + "ĠOp inion", + "rin ted", + "typ ing", + "Un known", + "Ġbuck ets", + "Ġintuition istic", + "algorithm s", + "S SC", + "b ir", + "ĠP ond", + "ad vert", + "ip in", + "Ġup wind", + "ĠCl aire", + "ĠMat uration", + "ĠPr P", + "OP O", + "FORM ANCE", + "Ġd M", + "ĠC ities", + "Ġinter related", + "ĠAp paratus", + "Ġprec ious", + "cript ors", + "Ġprepared ness", + "ĠAR CH", + "ĠPath ogens", + "HO ST", + "ĠGib bons", + "Ġirregular ity", + "ĠLip ids", + "Ġcf u", + "Ġvas odilation", + "imet re", + "impro ved", + "m q", + "ĠH ens", + "ĠL oci", + "unc redited", + "Ġmulti grid", + "tig o", + "Ġaccount ability", + "ench yme", + "Ġdisadvant aged", + "Ġbisp henol", + "Ġt ic", + "Ġfor ks", + "ĠW ester", + "ĠV ii", + "ĠJ ere", + "sim ultaneous", + "ĠGu arant", + "ĠDo yle", + "Ġpotenti ates", + "lass ified", + "Ġile al", + "Ġvasoconstr iction", + "M ODULE", + "N ano", + "W ood", + "ĠT AT", + "ur ious", + "un ya", + "Ġins tillation", + "ĠSim mons", + "ĠDi rectional", + "Ġmal ate", + "Ġplant ation", + "Ġuns olved", + "ĠTa uri", + "Ġov ine", + "Ġkeratin ocyte", + "ĠKull back", + "ĠKazakh stan", + "Ġh irs", + "ĠA erobic", + "ĠH ai", + "ĠR iley", + "ens ible", + "Ġinter planetary", + "Ġtrans its", + "Ġgener ous", + "Ġcal pain", + "Ġapp ended", + "ĠHydro dynamics", + "Ġcolon ize", + "Ġheart beat", + "Ġmetast as", + "Ġpy reth", + "ĠPA K", + "ĠÐ ¡", + "multi plet", + "ĠBrad y", + "Ġpropri a", + "ĠFron tier", + "ĠJoy ce", + "ĠP GF", + "ĠM cl", + "rec urrent", + "ĠRe placing", + "inf erence", + "ĠWh itt", + "Ġschool ing", + "ĠHa rold", + "Ġabst ractions", + "âĬ ķ", + "mem cpy", + "Ġmicron ucle", + "Ġradion uclide", + "ot yl", + "ĠM IF", + "ĠM US", + "Ġex foli", + "ĠF amilial", + "Ġcl am", + "ON O", + "Ġvan illa", + "Ġpast oris", + "ĠAT L", + "ĠBur sts", + "Qu antitative", + "Ġelic iting", + "Ġgranul omatous", + "Ġbrow sing", + "t racks", + "Ġh ij", + "ĠB CP", + "inc omp", + "az id", + "ck pt", + "Ġlink ers", + "Ġsqu id", + "Ġhead aches", + "ĠMor al", + "Ġstabil isation", + "&& &&", + "ĠSu fficient", + "ĠArch aea", + "Ġì ł", + "ĠLuc iferase", + "Cam era", + "expand ed", + "Ġmyster ious", + "H PS", + "ĠB J", + "ĠK NN", + "Ġsuper hydrophobic", + "ĠHydro thermal", + "ĠRus so", + "ĠArsen ic", + "Ġnormot ensive", + "ul timate", + "ĠC MIP", + "ex amined", + "Ġmicro porous", + "Ġfore ver", + "ĠST ING", + "IG S", + "ĉĉĉ ĠĠ", + "Pl ant", + "Ġcoherent ly", + "charg ing", + "Ġinher it", + "altern ative", + "ĠBap tist", + "F m", + "b ipy", + "Ġo ler", + "ĠSub stit", + "Ġult rap", + "free ze", + "perg ill", + "POS E", + "Ġadvertis ements", + "ECH AN", + "Bay esian", + "Ġcob ordism", + "¸ °", + "ĠA ER", + "ĠA IP", + "ĠL NA", + "ess entially", + "rec iprocal", + "ĠAn and", + "Ġsm eared", + "ones e", + "ethyl amine", + "ĠER S", + "Ġjud icial", + "Ġwood land", + "ĠGre gor", + "Ġtab ular", + "avir in", + "mir ror", + "Ġja undice", + "astig otes", + "ĠL GBT", + "ĠN aj", + "Ġsub scheme", + "Ġmulti user", + "Ġdrain s", + "Ġevac uated", + "phosphor yl", + "ĠFeld man", + "ĠTRI zol", + "ĠBLE U", + "a romatic", + "o viÄĩ", + "p ion", + "re pr", + "ro th", + "ĠF ES", + "ĠL eeds", + "Ġun g", + "ob ranch", + "Ġpat ency", + "ĠSc r", + "ĠSim plex", + "pec ies", + "Ġbenef ici", + "Ġpolymer ases", + "ĠCy gn", + "oct adec", + "Ġpunct ured", + "Ġjaponic us", + "ĠFPG As", + "f rown", + "Ġe b", + "ut iny", + "ĠP oy", + "ĠB rent", + "ĠB AM", + "ĠH ick", + "ĠN PS", + "ĠG DF", + "ĠV IRT", + "Ġinter l", + "Ġsc Fv", + "Ġte amm", + "Ġparticip atory", + "Ġexist ential", + "Ġoste omyelitis", + "Ġpneum othorax", + "std out", + "Ġsinglet ons", + "hyp othesis", + "strat ified", + "U SD", + "on asal", + "er is", + "im its", + "ĠI Cs", + "ĠE ncephal", + "iz i", + "ĠG radients", + "Ġall op", + "Ġcor p", + "con structed", + "Ġmon ument", + "sim ulator", + "ĠFerm ions", + "ĠWy oming", + "Ġprednis olone", + "L ang", + "N otes", + "e er", + "Ġf ighter", + "ent rant", + "ĠN ij", + "ĠG PD", + "ĠPro l", + "Ġreal isation", + "Ġpack ings", + "ĠDisc overing", + "ĠAng lo", + "ĠCass ini", + "exec ute", + "Ġinhab ited", + "ac ross", + "ĠC ram", + "ĠN BR", + "ant es", + "Ġdis persing", + "ach andran", + "ĠU ND", + "Ġshould ers", + "Ġcr ises", + "ustr ine", + "Ġprop ane", + "UN E", + "br ush", + "Ġeti ologies", + "Ġshot gun", + "show ing", + "ĠPhyt ochemical", + "ĠMeh ta", + "orr hea", + "ĠImag ery", + "T re", + "w c", + "Ġe luent", + "ond in", + "ĠAt titude", + "Ġfer romagnet", + "Ġcounter measures", + "Ġalk anes", + "ĠCap illary", + "lat ent", + "Ġsolub il", + "View er", + "áz quez", + "ĠPunj ab", + "a as", + "t ang", + "Ġim ports", + "ĠY ounger", + "rough ly", + "We inberg", + "ĠAt kinson", + "bf a", + "MP a", + "ste el", + "PC P", + "chlor inated", + "ĠPsych ometric", + "Ġpyro ptosis", + "Ġwat ched", + "ĠPerc utaneous", + "R BD", + "V ARI", + "at u", + "ĠW ake", + "Ġcan yon", + "ip arous", + "Ġsc all", + "com pletely", + "inter fer", + "ophy ceae", + "Ġfatal ities", + "cz ak", + "ĠPathophys iology", + "L em", + "l ach", + "t uary", + "Ġa lex", + "Ġs isters", + "Ġp um", + "ĠC atch", + "ĠE ber", + "ine x", + "ph the", + "Ġbo ar", + "ĠSo ul", + "Ġcat fish", + "Ġcloud y", + "ĠBu ilt", + "ophyl line", + "ĠRib osome", + "ĠAnomal ies", + "Y D", + "c ategorical", + "w or", + "op enta", + "ĠL IB", + "Ġr ick", + "Ġradi ations", + "Ġhyper cube", + "Ġmal treatment", + "Ġî ĦĦ", + "dispers ity", + "contin ent", + "Dig ital", + "ĠCory neb", + "Ġre vert", + "ĠT EA", + "ĠM LR", + "ĠF CM", + "ĠL amp", + "iz abilities", + "Ġcar ved", + "ĠMon oclonal", + "Ġpen is", + "ĠMor ales", + "En ter", + "ester ification", + "Ġcab bage", + "RAN TIES", + "Ġdebrid ement", + "L ead", + "c AMP", + "Ġc esium", + "ĠC ubic", + "Ġun imodular", + "ĠEx port", + "Ġanalys er", + "den otes", + "Ġrad ically", + "ĠHist ology", + "Ġmelan omas", + "Ġwors hip", + "ĠHimal ayan", + "ĠInte grable", + "benzenesulf onamide", + "Ġharb ored", + "P utting", + "ĠT ir", + "ĠU TI", + "cent ers", + "ĠPl uripot", + "Ġhar bors", + "Ġcarb am", + "ĠApp alach", + "ĠJo an", + "ĠCommission er", + "ĠGem ini", + "N ear", + "O PS", + "Q G", + "p ytorch", + "st aining", + "Ġh CG", + "Ġg avage", + "per haps", + "ĠG rib", + "ĠZ ah", + "Ġcompar ably", + "ĠBi oscience", + "SP L", + "Con nell", + "ĠAir way", + "prim ed", + "Ġsubmuc osal", + "Enh anced", + "Ġwis dom", + "V N", + "ĠM umbai", + "ri us", + "ĠR GD", + "ĠR Neasy", + "ma i", + "ĠAD L", + "Ġadop tive", + "Out lined", + "ĠWAR RANTIES", + "ĠViol ence", + "Ġcater p", + "F und", + "d θ", + "ĠP ok", + "ĠB enson", + "ĠR IG", + "ĠV s", + "Ġinst ants", + "ĠMulti drug", + "PD MS", + "CON ST", + "Ġcart ridge", + "ĠLif estyle", + "ĠCOND ITIONS", + "odys plastic", + "CONTR OL", + "L HC", + "ti re", + "ĠS tain", + "Ġy x", + "Ġj unctional", + "ob o", + "ann ah", + "ĠCP AP", + "Ġsound ness", + "ĠUl timate", + "sil icon", + "Ġparal og", + "E vents", + "G as", + "J E", + "ĠJ orge", + "Ġover production", + "Ġmax illa", + "ĠRe asons", + "we eks", + "ĠNe arest", + "Ġhead space", + "ĠAT C", + "bal ancing", + "Ġjud ging", + "ĠUnivers ality", + "Ġsinus es", + "Ġretro peritoneal", + "Det ection", + "Ġhydrolys ate", + "H och", + "w rapper", + "Ġp Ka", + "Ġl asso", + "ĠA lu", + "ĠA PR", + "ĠD ors", + "ĠD arboux", + "ĠR FS", + "ĠK har", + "ĠTh rom", + "Ġdesign ate", + "arc o", + "Ġtherm ostat", + "ĠGl acial", + "IF F", + "ĠMan ifest", + "Ġinters persed", + "haus er", + "ĠDD X", + "Ġa le", + "ti des", + "Ġl accase", + "ĠH ered", + "ĠR acial", + "ĠK ats", + "aj o", + "ĠCo ordinated", + "ĠProb ably", + "Ġtit anate", + "SL AM", + "dri ving", + "ĠEmerg ent", + "ĠDri ves", + "Ġoblig ations", + "Ġnebul ae", + "f ried", + "ith in", + "ĠP GD", + "oc clusion", + "ĠU H", + "Ġsub routine", + "oid in", + "Ġann oy", + "ĠVir asoro", + "inst ances", + "ĠDer by", + "Ġtriang ulations", + "Ġcutoff s", + "ĠOrganization al", + "ĠVen k", + "ĠEG TA", + "ĠDeut sche", + "Ġantine ut", + "ĠVulner ability", + "i ated", + "Ġa vium", + "Ġh sp", + "em ulsions", + "ad herence", + "ĠU PS", + "ma ph", + "ĠV AP", + "rel atively", + "Ġmax ill", + "oph ase", + "Th reshold", + "ĠSup p", + "eth oprim", + "Ġpenet rated", + "Ġblast ing", + "ĠAdvant ages", + "B US", + "ol son", + "rec v", + "Ġcar nitine", + "ten ing", + "Ġprov oked", + "vari ous", + "ĠCal ab", + "len eck", + "ĠPark in", + "Ġblow up", + "ĠDW I", + "synt hesized", + "Ġdisproportion ately", + "Ġcardio respiratory", + "ĠXanth omonas", + "Ġpunc ta", + "bdd c", + "ĠP ACS", + "ase g", + "Ġinc urs", + "ost a", + "ĠJ L", + "ĠWe ierstrass", + "ole ucine", + "Ġfin als", + "Ġcaus ation", + "ĠDi rective", + "ĠPor to", + "ĠFlo res", + "arbon yl", + "---------------------------------------------------------------- ------------", + "histor ic", + "K ähler", + "on na", + "Ġc el", + "ĠT BA", + "ĠO phthal", + "Ġsub threshold", + "Ġlip s", + "ĠSub strates", + "Ġpen insula", + "Ġads or", + "Ġdry ness", + "mass es", + "è me", + "stro k", + "ĠExpand ed", + "Ġg c", + "ĠG olf", + "Ġcri tique", + "ĠÍ ©", + "Ġlens ed", + "ĠKing ma", + "ĠGold man", + "ĠFac ile", + "Car l", + "Ġchond rites", + "ĠCoh omology", + "ĠSocio economic", + "ĠDominic an", + "ĠAzerbai jan", + "ĠA ne", + "ĠM idd", + "ĠN ed", + "Ġem ulate", + "ĠSh akes", + "Ġlik ed", + "Ġbuild up", + "Ġexcess ively", + "ĠÅ ¶", + "ĠAdap ted", + "Ġauthentic ated", + "Ġlocom otive", + "Ġsubm ill", + "Ġinterpre ter", + "ĠVibr ational", + "R α", + "l aden", + "p kl", + "r w", + "y et", + "en zymes", + "Ġw av", + "ĠN MT", + "ath ion", + "Ġbi otechnological", + "arc s", + "Ġact uated", + "Ġher ring", + "EC G", + "OC I", + "Ac tivated", + "Ġpara ph", + "Obs ervation", + "ĠEk man", + "ancell or", + "veli hood", + "G auss", + "H AL", + "r dev", + "t bl", + "ic its", + "ĠR oux", + "op ram", + "Ġser opositive", + "ĠPhys ically", + "ĠEd u", + "Ġdocument ing", + "ĠÐ ¾", + "ĠSmall er", + "cher y", + "Ġlanth anide", + "T oday", + "Ñ Ĩ", + "Ġo titis", + "ĠC ores", + "if olium", + "ĠZ el", + "Ġtim ings", + "co arse", + "rep air", + "ĠLD PC", + "Ġbow l", + "ĠEpid ermal", + "Ġâľ ²", + "Ġsynonym s", + "Ġ ENT", + "Ġb illiard", + "Ġe jac", + "ĠB AA", + "Ġsc ientif", + "Ġγ γ", + "Ġcaps ular", + "Ġaz ithromycin", + "Ġcred entials", + "ĠḠł", + "ĠGli oblastoma", + "Ġunco ated", + "Ġhib ern", + "ĠT os", + "ĠB aro", + "ĠK ass", + "yl and", + "ĠX M", + "Ġagg ra", + "Ġneutral ize", + "lic ted", + "Ġsound track", + "ĠKn ud", + "ensors hip", + "emp fer", + "ĠHald ane", + "ĠR ocks", + "ĠG ou", + "ĠO pi", + "ib acterium", + "ep tives", + "ust a", + "par s", + "uk awa", + "be in", + "eli us", + "aver aging", + "ĠMW CNT", + "Ġshield ed", + "Ġquatern ionic", + "Batch Norm", + "Ġd ella", + "ĠT p", + "Ġby product", + "ĠG ow", + "ĠJ O", + "Ġparameter ize", + "gl er", + "Ġfac ult", + "ĠÍ µ", + "Ġnom ination", + "Ġbath s", + "Ġinstall ations", + "ĠJust in", + "Ġchampionship s", + "t ap", + "ĠS anc", + "ĠS usp", + "Ġsub leading", + "Ġdef ended", + "Ġbut yl", + "rem ote", + "Ġcarb ides", + "ĠPredic ts", + "ĠPrior ity", + "ĠAntib iotics", + "ĠPU FAs", + "ĠMIC s", + "ĠMaxim ization", + "b are", + "ĠP CN", + "Ġinf ested", + "Ġsol enoid", + "Ġag ronomic", + "AN GE", + "Re v", + "ĠNK G", + "Ġsap onins", + "Recomm end", + "Ġshar pen", + "othio yl", + "s uspended", + "at ron", + "us age", + "il ter", + "ĠN ER", + "CR IPT", + "inf ections", + "Ġheter osexual", + "Ġmes oc", + "ĠBob by", + "alloc ate", + "ĠPay ne", + "ĠSult an", + "è ¡", + "rac les", + "rib e", + "ĠDo ub", + "ĠPA F", + "commun ication", + "Ġninet eenth", + "Ġpopl ar", + "pgf strok", + "pgfstrok ecolor", + "S LE", + "ec ia", + "Ġdet ach", + "Ġchar ity", + "Ġmon ochrom", + "Ġpres cribe", + "Ġsuper massive", + "Ġgu ards", + "Ġcycl oaddition", + "Co hen", + "phosph atidyl", + "cre ated", + "ĠElectro dynamics", + "Ġplasm ons", + "Ñģ к", + "ĠDaph nia", + "e asy", + "Ġa q", + "Ġf imb", + "Ġw rest", + "ĠT end", + "hip p", + "Ġorgan isational", + "MA E", + "OP Y", + "Ġpotenti ated", + "Ġbr ute", + "omass ie", + "aun ay", + "l uster", + "Ġo phi", + "un ge", + "ĠP om", + "Ġpl ague", + "ber ries", + "Ġinv iscid", + "ĠQ E", + "inc ident", + "xim ide", + "Ġest rogens", + "ĠTrans parent", + "vere ign", + "ĠPre ferred", + "Ġelast ase", + "C iv", + "J F", + "K u", + "c aster", + "Ġsp ends", + "Ġabst racted", + "otechn ical", + "Ġbreed ers", + "Ġsyring ae", + "Ġclot ting", + "Af rican", + "P EC", + "us ep", + "Ġst ark", + "so lete", + "of ovir", + "Ġsens ations", + "az awa", + "Ġbiom echanics", + "Ġemerg encies", + "Ġspectrom eters", + "Ġhemisp heric", + "Ġdiscrimin atory", + "ĠInsp ection", + "nd im", + "RE P", + "ĠW ess", + "Ġhyper algesia", + "IR C", + "Ġauthors hip", + "CP A", + "Ġrotation ally", + "ĠPy th", + "ĠYam aguchi", + "Field s", + "Ġenrol ment", + "ĠReth inking", + "G ate", + "ì ĺ", + "Ġc ements", + "ĠT TS", + "ĠF ink", + "ad us", + "ĠL l", + "Ġim plicate", + "ann ihilation", + "Ġfeed ers", + "ĠPD X", + "ĠFran çois", + "Sp earman", + "ĠBenchmark ing", + "F orest", + "e vidence", + "en oyl", + "ol actone", + "ce phaly", + "ĠP EA", + "ĠN SE", + "Ġno tified", + "Ġpoly electrolyte", + "ĠMal ik", + "anth ine", + "tet rad", + "Ġflag ella", + "ãĥ ¼", + "orp ion", + "Ġbuy ers", + "Ġoligodend rocyte", + "ĠNMD AR", + "ĠHarvest ing", + "Ġkar st", + "I BD", + "ĠF olk", + "Ġsub carrier", + "Ġno tices", + "ĠY ous", + "aw ak", + "Ġadvers aries", + "Lo oking", + "Ġthym ocytes", + "Ġmening ioma", + "Ġillumin ate", + "ĠSPD X", + "Ġimping ing", + "associ ative", + "Ġt iger", + "le on", + "Ġst ature", + "ĠH ood", + "ĠR utherford", + "ĠE IT", + "Ġinf antile", + "ĠQ ubit", + "Ġpack s", + "ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ ĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠĠ", + "azol am", + "ภ²", + "ĠTun isia", + "dil ution", + "Ġsymp atric", + "Ġliquef action", + "porph yrin", + "G n", + "R ib", + "is othermal", + "ap o", + "Ġreg ressors", + "man i", + "ĠIL s", + "oxid ants", + "At om", + "lig o", + "ĠSR AM", + "alc one", + "cs r", + "Ġc autious", + "Ġhand lers", + "Ġgast ritis", + "ĠSuper vision", + "Ġevapor ative", + "R UN", + "ĠI CG", + "ĠP rague", + "ĠM LC", + "ĠM oney", + "ĠR m", + "ĠE CS", + "vel t", + "ĠV g", + "Ġbi ography", + "Ġmin istry", + "con volution", + "ogen omics", + "round ing", + "ĠPh ag", + "Ġaud iences", + "ĠHC Ws", + "Ġblast ocysts", + "Ġdiagon als", + "Ġpreca utions", + "Í ĵ", + "ec ewise", + "ĠT oxin", + "ĠH app", + "ĠâĢ ĭ", + "Ġpop ulate", + "mm ol", + "ĠPR S", + "Ġreinfor ces", + "IST IC", + "ozo ites", + "arri val", + "Ġpave ment", + "REGIST ER", + "ĠG ases", + "ĠEx hib", + "Ġfactor izations", + "Ġmy opia", + "Ġmov able", + "ĠLI MIT", + "Ġsole us", + "DO UBLE", + "ĠInput s", + "foot notes", + "BIT S", + "ĠCyp rus", + "re ports", + "ĠP AA", + "ist al", + "Ġgroup oids", + "orph in", + "ĠCo ordinates", + "bor o", + "ĠOs lo", + "when ever", + "Ġplaus ibility", + "ĠFox O", + "ĠIntr usion", + "Ġsimplic es", + "ĠFas o", + "Ġpic osecond", + "ĠAns atz", + "Import antly", + "ĠHutch inson", + "ov ani", + "ĠAs ymptotics", + "Ġapp ra", + "ĠEx ogenous", + "Ġcap tions", + "ĠAc anth", + "Ġill icit", + "ĠBl adder", + "Ġbo om", + "ĠSal inity", + "Ġmusc ul", + "eptid yl", + "Ġaval anches", + "Hel per", + "Ġbival ve", + "Ġreimburs ement", + "z zo", + "rom atosis", + "Ġpar acetamol", + "vi o", + "Ġval pro", + "cl amation", + "Ġu u", + "ĠSo C", + "ĠGi ac", + "Ġly copene", + "Fl ags", + "Ġstic king", + "Ġsquee ze", + "synt hetic", + "osahexa enoic", + "m obile", + "v ect", + "ĠB aryon", + "Ġne f", + "Ġfl atter", + "SS I", + "Ġsch w", + "ancre as", + "Bu f", + "Sol id", + "ĠRIP A", + "Squ are", + "Ġtranscend ental", + "Ġc yn", + "Ġm f", + "ĠS ew", + "ĠP IT", + "oc s", + "ĠB ash", + "Ġsur prised", + "Ġaut onomously", + "Ġlocal izing", + "Ġvis itor", + "Ġpers isting", + "Ġland fill", + "date time", + "Ġfire f", + "ĠEngine ered", + "ĠSn yder", + "ochrom es", + "fraction ated", + "G PI", + "Ġw oven", + "ĠT MR", + "Ġfor gotten", + "ĠM ult", + "ĠB ipolar", + "ĠH isp", + "op eptides", + "ap amil", + "Ġro uted", + "Ġag n", + "Ġday light", + "ĠÍ Ķ", + "BB B", + "ĠMajor ity", + "Ġconfound ed", + "ĠCarol ine", + "ĠShim ura", + "r uction", + "Ġt ympan", + "ac io", + "ĠT FE", + "ĠT utorial", + "Ġal lyl", + "ĠF rost", + "ĠR PS", + "ĠW ah", + "Ġi y", + "Ġsub problems", + "Ġair foil", + "Ġembed s", + "ĠMor ning", + "Ġminor ities", + "ĠMemb ership", + "Ġquadric eps", + "y ly", + "ĠB odies", + "ĠR AND", + "Ġr ationally", + "ĠMan ifold", + "phosph ine", + "cons idering", + "ez vous", + "ĠKnow ing", + "Ġtumorigen ic", + "Ġillumin ating", + "ĠFernand es", + "polynomial s", + "ĠBulg arian", + "ĠBhattach arya", + "ospi ra", + "A pi", + "hen ne", + "Ġmay s", + "ĠAr gin", + "inter pol", + "ĠAnd ean", + "ĠPD S", + "ĠCN P", + "Ġtransf usions", + "ĠNan om", + "Ġsynerg ism", + "Ġbent onite", + "Ġgravit ons", + "aqu ette", + "Ġfiss ure", + "t andem", + "w ash", + "ĠE yes", + "Ġdi lepton", + "Ġrec tified", + "ĠAr ist", + "isc ible", + "Ġir q", + "Ġlig aments", + "sec urity", + "Ġvascular ization", + "Na Cl", + "ĠStra ight", + "ĠLept in", + "ĠAbund ances", + "ĠKE Y", + "ĠMother s", + "ĠRenew able", + "Ġmason ry", + "ë ı", + "rac eutical", + "Ġar ity", + "ĠAl ves", + "osp ectral", + "Ġimmun od", + "Ġmar ble", + "Ġcover ings", + "ĠConst ants", + "ĠRever sal", + "Work s", + "ĠNur se", + "ĠAggreg ate", + "ac illin", + "pl ug", + "Ġj ury", + "one ogenesis", + "Ġam oeb", + "au kee", + "Ġphosphor ic", + "ĠRem oving", + "Ġwors en", + "ĠESR D", + "ĠHern andez", + "ĠEug ene", + "visc osity", + "F ID", + " ¦", + "Ġ Ý", + "ĠS tig", + "ĠS ING", + "ĠI MRT", + "ĠB q", + "ĠD ME", + "ĠH OM", + "ph ysis", + "ob es", + "Ġsuper fields", + "Ġarg c", + "Ġmal adaptive", + "ĠEd iting", + "Ġcond em", + "ube i", + "stim ulus", + "Ġabbrevi ation", + "H aus", + "ĠN eeds", + "Ġad hering", + "ĠV PA", + "of rontal", + "ĠÅ ª", + "ĠVer de", + "ĠSl av", + "ĠProp ag", + "Ġcongen ers", + "Ġtil apia", + "ĠRac hel", + "L ess", + "Ġm asc", + "ent angled", + "ĠD TI", + "ati k", + "rol ases", + "ĠY en", + "arm or", + "ĠDec isions", + "Ġη p", + "Int uitively", + "ĠPharmaceutical s", + "J u", + "ud din", + "ĠW ASP", + "nt on", + "Ġbi ot", + "ĠZ NF", + "Ġcr ush", + "ĠPar ity", + "SI ST", + "EV ENT", + "ĠSqu amous", + "Stud ent", + "Ġgingival is", + "f used", + "ĠM ises", + "ĠF DTD", + "ore ceptors", + "Ġdisc retion", + "OR TC", + "MS P", + "Ġexpos es", + "Ġchlor inated", + "ĠUp regulation", + "ĠLim b", + "Ġantic or", + "Reg ular", + "Adv anced", + "X e", + "ag han", + "ĠB LA", + "Ġco asts", + "ĠTh irteen", + "hes in", + "ĠMet hanol", + "rot us", + "ĠStep hens", + "Bo ok", + "ĠHistor ically", + "ĠEmploy ing", + "Ġcorrug ated", + "mercapto ethanol", + "ĠD nmt", + "ĠQu eries", + "DR B", + "ĠGR U", + "FL U", + "ĠCarbon iferous", + "OB JECT", + "ĠDiscrim inative", + "ĠBurg ess", + "Ġplanetes imals", + "ĠAmend ment", + "ĠStriking ly", + "t ric", + "ec ure", + "Ġtrans posable", + "rol ateral", + "Ġhis ti", + "ega ard", + "Ġsk im", + "ĠSP F", + "Stat istics", + "Ġintest ines", + "f eng", + "l ain", + "Ġthe at", + "Ġo rogen", + "Ġp ill", + "od opa", + "Ġcorrel ative", + "AC O", + "Ġadj unction", + "ĠCare y", + "Ġtele portation", + "ĠBound aries", + "ĠGood fellow", + "ĠLind a", + "ĠPolymer ic", + "Ġexer tion", + "Ġsteep ly", + "Ġprotr usion", + "Ġhyal uronic", + "ĠRoc hester", + "ENSI ONAL", + "D ar", + "f et", + "ĠF SS", + "hem ically", + "Ġfl ashes", + "Ġdevi ated", + "feld t", + "Ġstic ks", + "Ġoct et", + "Ġgravitation ally", + "footnotes ize", + "L ex", + "o vi", + "Ġw ired", + "ĠS MP", + "erm ans", + "Ġun broken", + "Ġem ulation", + "sim ulated", + "Ġminim ality", + "ardi ac", + "Ġship w", + "Gene tic", + "ĠHerm ann", + "ynchron ization", + "ĠNap ole", + "Ġmonodis perse", + "R ho", + "r ists", + "Ġf x", + "ĠF UV", + "ĠG elfand", + "hem ispheric", + "ron idazole", + "Ġsuper saturation", + "oud h", + "oli tical", + "ĠAir y", + "Ġmanifest ly", + "ĠHM G", + "Ġadvertis ement", + "ĠBrook lyn", + "Ġparalle led", + "answ ered", + "ĠNotImplemented Error", + "U PD", + "o ust", + "ĠT eng", + "Ġfor tified", + "ĠS ort", + "EN E", + "ĠF ris", + "ĠH IS", + "ĠR OT", + "ĠN ested", + "pro duce", + "ĠK erala", + "gen omic", + "ĠIs ab", + "Ġur acil", + "bur ger", + "ĠLog arithmic", + "Ġster ility", + "Ġunem ployed", + "Ġori ental", + "K o", + "j ima", + "ĠC TP", + "ĠL AD", + "Ġconform ers", + "CG G", + "Per kin", + "Ġbrid ged", + "ĠDiss ociation", + "ĠQi agen", + "Ġwealth y", + "Ġanaest hetic", + "ĠMinim izing", + "Ġacous tics", + "buck et", + "ĠSert oli", + "r ath", + "s aw", + "Ġg arn", + "ĠThe oretically", + "tic ul", + "ĠTh inking", + "ik er", + "ĠCh it", + "Ġtr in", + "AL ITY", + "ĠFe O", + "Ġpolymer ized", + "En coding", + "Ġanalges ics", + "ĠLex ical", + "Ġmari juana", + "âĸĪ âĸĪ", + "c rops", + "ent ropic", + "ol ocation", + "ĠP omp", + "Ġco factors", + "box times", + "ĠAr ri", + "An gel", + "ĠRequire ment", + "Ġmicrol ensing", + "ĠTRAN SF", + "å º", + "Ġd ma", + "Ġre rio", + "und ancy", + "ant el", + "Ġradi ometric", + "ĠSe an", + "rand n", + "ĠCR L", + "hal os", + "uber tal", + "Ġquin one", + "T ES", + "Ġd W", + "ĠC GM", + "Ġhe aled", + "ian e", + "Ġobtain able", + "ĠAd rian", + "Ġlik es", + "ĠMed ication", + "Ġcogn itively", + "Whe ther", + "B ob", + "d id", + "al cohol", + "Ġn ivolumab", + "ĠF Y", + "Ġat resia", + "ach s", + "ĠK ip", + "Ġun igenes", + "ĠJ accard", + "ust ri", + "Ġconf ine", + "Ġaut ofluorescence", + "Ġpy g", + "Se a", + "Set tings", + "Ġtrunc atula", + "Liter al", + "F ab", + "M ah", + "V en", + "Ġt ig", + "Ġc her", + "ĠC CI", + "ĠF unk", + "ĠB ess", + "ĠN asal", + "iff er", + "Ġobs essive", + "ĠOp ening", + "ochond ral", + "ĠTR PA", + "ĠRab in", + "Ġta per", + "Ġdeaf ness", + "D OS", + "is ites", + "an ite", + "le ost", + "ĠS TP", + "ĠB ACE", + "ĠH enn", + "ĠE SM", + "Ġsuper field", + "ĠOr land", + "ĠAMP s", + "ĠHem orrh", + "Ġresc ues", + "Ġtour ists", + "ĠVL BI", + "Ġneighbourhood s", + "communic able", + "g x", + "r atase", + "ĠN RT", + "Ġob structions", + "Ġdef orestation", + "Ġq p", + "ĠPh an", + "ĠST I", + "iment o", + "ĠIR I", + "SV s", + "Ġstrip ed", + "Po inc", + "ĠBed ford", + "ĠFrag ment", + "ĠRelig ion", + "Ġd rones", + "im ulation", + "ĠC et", + "Ġg ills", + "ĠN orton", + "ib atch", + "est ructive", + "ĠJ av", + "ĠÏ ½", + "Ġmic a", + "AG B", + "RA W", + "ĠMy D", + "ct l", + "Ġrevers ibly", + "Ġsuppress ors", + "ĠFA IL", + "ĠFuk ushima", + "E vidence", + "p ink", + "as array", + "ĠT ann", + "Ġl oved", + "Ġbi ologists", + "Ġend othermic", + "Ġbro ker", + "ĠPer kins", + "Ġcategor ised", + "ĠSO ME", + "hydroxy vitamin", + "rog ates", + "ĠAge ing", + "Ġtourn aments", + "ĠStrom al", + "Ġdefer red", + "ĠSRE BP", + "M AD", + "S ay", + "c gi", + "p he", + "ol ini", + "ĠD ü", + "Ġde hydro", + "ap eptide", + "Ġhe s", + "Ġdist ally", + "vers ions", + "Ġmed als", + "Ġfl aw", + "Ġdu o", + "Ġimpair ing", + "toplas ts", + "ĠHF ILL", + "Ġesc ulent", + "Class ification", + "ĠGriff ith", + "ĠWelling ton", + "Ġattor ney", + "A st", + "k A", + "Ġm ilit", + "Ġn ite", + "ĠC asp", + "ĠC hester", + "ĠM ok", + "ĠR AR", + "Ġch r", + "unc tor", + "Ġab duction", + "Ġun iv", + "ov ars", + "ou k", + "ER ICAL", + "é ri", + "orb ance", + "ĠIdentif ies", + "ament o", + "Ġparent hesis", + "ĠME Fs", + "Ġabsor bs", + "ĠArray List", + "Ġcareg iving", + "ĠFI LE", + "Ġfeld spar", + "ochthon ous", + "S ort", + "j al", + "Ġt antal", + "ar abine", + "ĠS aid", + "ĠB CE", + "ĠN GO", + "yn ure", + "dot eq", + "ĠLe yd", + "mod ality", + "ĠGe ometrical", + "Al most", + "Ġhard ened", + "no ea", + "new s", + "Ġclean up", + "ĠArm ed", + "ĠSn ake", + "multi ply", + "ĠMill ennium", + "ĠSmooth ing", + "posit ely", + "en ary", + "is se", + "ĠY uc", + "Ġgene al", + "Ġsuper s", + "Ġhand held", + "Ġemb ark", + "ĠBl a", + "hor st", + "ĠPD GFR", + "Ġcit r", + "Ġcalor ie", + "ĠBudd hist", + "M ember", + "Ġf ears", + "Ġf iscal", + "ĠA IF", + "LO AD", + "pe are", + "Ġbit umen", + "Par ticip", + "ĠIndian apolis", + "ĠAlb um", + "Ġscr utiny", + "acyl glycer", + "ĠSak ai", + "Ġthermod ynamical", + "Z B", + "Ġh pf", + "ĠL IP", + "Ġexp iration", + "til t", + "Ġfl ax", + "ĠSe lectivity", + "ĠSch ol", + "any a", + "orb ents", + "Ġincub ations", + "Ġmargin als", + "inv olved", + "Ġenthal pies", + "macroph ages", + "construct or", + "ĠRol and", + "ĠP m", + "ĠR Y", + "Ġbl obs", + "Ġann uli", + "Ġuns timulated", + "ĠPet roleum", + "Ġmerg es", + "Ġenvelop ing", + "ĠInitial ization", + "Ġshed s", + "Ġadvis able", + "ylethanol amine" + ] + } +} diff --git a/ppocr/utils/dict/unimernet_tokenizer/tokenizer_config.json b/ppocr/utils/dict/unimernet_tokenizer/tokenizer_config.json new file mode 100644 index 0000000..75dad5a --- /dev/null +++ b/ppocr/utils/dict/unimernet_tokenizer/tokenizer_config.json @@ -0,0 +1,205 @@ +{ + "added_tokens_decoder": { + "0": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "1": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "2": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "3": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "4": { + "content": "[START_REF]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "5": { + "content": "[END_REF]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "6": { + "content": "[IMAGE]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "7": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "8": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "9": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "10": { + "content": "", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "11": { + "content": "[START_SUP]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "12": { + "content": "[END_SUP]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "13": { + "content": "[START_SUB]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "14": { + "content": "[END_SUB]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "15": { + "content": "[START_DNA]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "16": { + "content": "[END_DNA]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "17": { + "content": "[START_AMINO]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "18": { + "content": "[END_AMINO]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "19": { + "content": "[START_SMILES]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "20": { + "content": "[END_SMILES]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "21": { + "content": "[START_I_SMILES]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + }, + "22": { + "content": "[END_I_SMILES]", + "lstrip": false, + "normalized": false, + "rstrip": false, + "single_word": false, + "special": true + } + }, + "additional_special_tokens": [], + "bos_token": "", + "clean_up_tokenization_spaces": false, + "eos_token": "", + "max_length": 4096, + "model_max_length": 768, + "pad_to_multiple_of": null, + "pad_token": "", + "pad_token_type_id": 0, + "padding_side": "right", + "processor_class": "VariableDonutProcessor", + "stride": 0, + "tokenizer_class": "NougatTokenizer", + "truncation_side": "right", + "truncation_strategy": "longest_first", + "unk_token": "", + "vocab_file": null +} diff --git a/ppocr/utils/dict/ur_dict.txt b/ppocr/utils/dict/ur_dict.txt new file mode 100644 index 0000000..c06786a --- /dev/null +++ b/ppocr/utils/dict/ur_dict.txt @@ -0,0 +1,137 @@ +u +r +_ +i +m +g +/ +3 +I +L +S +V +R +C +2 +0 +1 +v +a +l +9 +7 +8 +. +j +p + +چ +ٹ +پ +ا +ئ +ی +ے +4 +6 +و +ل +ن +ڈ +ھ +ک +ت +ش +ف +ق +ر +د +5 +ب +ج +خ +ہ +س +ز +غ +ڑ +ں +آ +م +ؤ +ط +ص +ح +ع +گ +ث +ض +ذ +ۓ +ِ +ء +ظ +ً +ي +ُ +ۃ +أ +ٰ +ە +ژ +ۂ +ة +ّ +ك +ه +s +c +e +n +w +o +d +t +D +M +T +U +E +b +P +h +y +W +H +A +x +B +O +N +G +Y +Q +F +k +K +q +J +Z +f +z +X +' +@ +& +! +, +: +$ +- +# +? +% +é ++ +( +É diff --git a/ppocr/utils/dict/vi_dict.txt b/ppocr/utils/dict/vi_dict.txt new file mode 100644 index 0000000..5e8f8c6 --- /dev/null +++ b/ppocr/utils/dict/vi_dict.txt @@ -0,0 +1,113 @@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +P +Q +R +S +T +U +V +W +a +b +c +d +e +g +h +i +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +à +á +â +ã +è +é +ê +ì +í +ò +ó +ô +õ +ù +ú +ý +ă +Đ +đ +ĩ +ũ +ơ +ư +ạ +ả +ấ +ầ +ẩ +ẫ +ậ +ắ +ằ +ẳ +ẵ +ặ +ẹ +ẻ +ẽ +ế +ề +ể +ễ +ệ +ỉ +ị +ọ +ỏ +ố +ồ +ổ +ỗ +ộ +ớ +ờ +ở +ỡ +ợ +ụ +ủ +ứ +ừ +ử +ữ +ự +ỳ +ỵ +ỷ +ỹ diff --git a/ppocr/utils/dict/xi_dict.txt b/ppocr/utils/dict/xi_dict.txt new file mode 100644 index 0000000..f195f1e --- /dev/null +++ b/ppocr/utils/dict/xi_dict.txt @@ -0,0 +1,110 @@ +x +i +_ +m +g +/ +1 +0 +I +L +S +V +R +C +2 +v +a +l +3 +6 +4 +5 +. +j +p + +Q +u +e +r +o +8 +7 +n +c +9 +t +b +é +q +d +ó +y +F +s +, +O +í +T +f +" +U +M +h +: +P +H +A +E +D +z +N +á +ñ +ú +% +; +è ++ +Y +- +B +G +( +) +¿ +? +w +¡ +! +X +É +K +k +Á +ü +Ú +« +» +J +' +ö +W +Z +º +Ö +­ +[ +] +Ç +ç +à +ä +û +ò +Í +ê +ô +ø +ª diff --git a/ppocr/utils/dict90.txt b/ppocr/utils/dict90.txt new file mode 100644 index 0000000..46b8795 --- /dev/null +++ b/ppocr/utils/dict90.txt @@ -0,0 +1,90 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ +: +; +< += +> +? +@ +[ +\ +] +_ +` +~ diff --git a/ppocr/utils/e2e_metric/Deteval.py b/ppocr/utils/e2e_metric/Deteval.py new file mode 100755 index 0000000..f4767e1 --- /dev/null +++ b/ppocr/utils/e2e_metric/Deteval.py @@ -0,0 +1,852 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import numpy as np +import scipy.io as io + +from ppocr.utils.utility import check_install + +from ppocr.utils.e2e_metric.polygon_fast import iod, area_of_intersection, area + + +def get_socre_A(gt_dir, pred_dict): + allInputs = 1 + + def input_reading_mod(pred_dict): + """This helper reads input from txt files""" + det = [] + n = len(pred_dict) + for i in range(n): + points = pred_dict[i]["points"] + text = pred_dict[i]["texts"] + point = ",".join( + map( + str, + points.reshape( + -1, + ), + ) + ) + det.append([point, text]) + return det + + def gt_reading_mod(gt_dict): + """This helper reads groundtruths from mat files""" + gt = [] + n = len(gt_dict) + for i in range(n): + points = gt_dict[i]["points"].tolist() + h = len(points) + text = gt_dict[i]["text"] + xx = [ + np.array(["x:"], dtype=" 1): + gt_x = list(map(int, np.squeeze(gt[1]))) + gt_y = list(map(int, np.squeeze(gt[3]))) + for det_id, detection in enumerate(detections): + detection_orig = detection + detection = [float(x) for x in detection[0].split(",")] + detection = list(map(int, detection)) + det_x = detection[0::2] + det_y = detection[1::2] + det_gt_iou = iod(det_x, det_y, gt_x, gt_y) + if det_gt_iou > threshold: + detections[det_id] = [] + + detections[:] = [item for item in detections if item != []] + return detections + + def sigma_calculation(det_x, det_y, gt_x, gt_y): + """ + sigma = inter_area / gt_area + """ + return np.round( + (area_of_intersection(det_x, det_y, gt_x, gt_y) / area(gt_x, gt_y)), 2 + ) + + def tau_calculation(det_x, det_y, gt_x, gt_y): + if area(det_x, det_y) == 0.0: + return 0 + return np.round( + (area_of_intersection(det_x, det_y, gt_x, gt_y) / area(det_x, det_y)), 2 + ) + + ##############################Initialization################################### + # global_sigma = [] + # global_tau = [] + # global_pred_str = [] + # global_gt_str = [] + ############################################################################### + + for input_id in range(allInputs): + if ( + (input_id != ".DS_Store") + and (input_id != "Pascal_result.txt") + and (input_id != "Pascal_result_curved.txt") + and (input_id != "Pascal_result_non_curved.txt") + and (input_id != "Deteval_result.txt") + and (input_id != "Deteval_result_curved.txt") + and (input_id != "Deteval_result_non_curved.txt") + ): + detections = input_reading_mod(pred_dict) + groundtruths = gt_reading_mod(gt_dir) + detections = detection_filtering( + detections, groundtruths + ) # filters detections overlapping with DC area + dc_id = [] + for i in range(len(groundtruths)): + if groundtruths[i][5] == "#": + dc_id.append(i) + cnt = 0 + for a in dc_id: + num = a - cnt + del groundtruths[num] + cnt += 1 + + local_sigma_table = np.zeros((len(groundtruths), len(detections))) + local_tau_table = np.zeros((len(groundtruths), len(detections))) + local_pred_str = {} + local_gt_str = {} + + for gt_id, gt in enumerate(groundtruths): + if len(detections) > 0: + for det_id, detection in enumerate(detections): + detection_orig = detection + detection = [float(x) for x in detection[0].split(",")] + detection = list(map(int, detection)) + pred_seq_str = detection_orig[1].strip() + det_x = detection[0::2] + det_y = detection[1::2] + gt_x = list(map(int, np.squeeze(gt[1]))) + gt_y = list(map(int, np.squeeze(gt[3]))) + gt_seq_str = str(gt[4].tolist()[0]) + + local_sigma_table[gt_id, det_id] = sigma_calculation( + det_x, det_y, gt_x, gt_y + ) + local_tau_table[gt_id, det_id] = tau_calculation( + det_x, det_y, gt_x, gt_y + ) + local_pred_str[det_id] = pred_seq_str + local_gt_str[gt_id] = gt_seq_str + + global_sigma = local_sigma_table + global_tau = local_tau_table + global_pred_str = local_pred_str + global_gt_str = local_gt_str + + single_data = {} + single_data["sigma"] = global_sigma + single_data["global_tau"] = global_tau + single_data["global_pred_str"] = global_pred_str + single_data["global_gt_str"] = global_gt_str + return single_data + + +def get_socre_B(gt_dir, img_id, pred_dict): + allInputs = 1 + + def input_reading_mod(pred_dict): + """This helper reads input from txt files""" + det = [] + n = len(pred_dict) + for i in range(n): + points = pred_dict[i]["points"] + text = pred_dict[i]["texts"] + point = ",".join( + map( + str, + points.reshape( + -1, + ), + ) + ) + det.append([point, text]) + return det + + def gt_reading_mod(gt_dir, gt_id): + gt = io.loadmat("%s/poly_gt_img%s.mat" % (gt_dir, gt_id)) + gt = gt["polygt"] + return gt + + def detection_filtering(detections, groundtruths, threshold=0.5): + for gt_id, gt in enumerate(groundtruths): + if (gt[5] == "#") and (gt[1].shape[1] > 1): + gt_x = list(map(int, np.squeeze(gt[1]))) + gt_y = list(map(int, np.squeeze(gt[3]))) + for det_id, detection in enumerate(detections): + detection_orig = detection + detection = [float(x) for x in detection[0].split(",")] + detection = list(map(int, detection)) + det_x = detection[0::2] + det_y = detection[1::2] + det_gt_iou = iod(det_x, det_y, gt_x, gt_y) + if det_gt_iou > threshold: + detections[det_id] = [] + + detections[:] = [item for item in detections if item != []] + return detections + + def sigma_calculation(det_x, det_y, gt_x, gt_y): + """ + sigma = inter_area / gt_area + """ + return np.round( + (area_of_intersection(det_x, det_y, gt_x, gt_y) / area(gt_x, gt_y)), 2 + ) + + def tau_calculation(det_x, det_y, gt_x, gt_y): + if area(det_x, det_y) == 0.0: + return 0 + return np.round( + (area_of_intersection(det_x, det_y, gt_x, gt_y) / area(det_x, det_y)), 2 + ) + + ##############################Initialization################################### + # global_sigma = [] + # global_tau = [] + # global_pred_str = [] + # global_gt_str = [] + ############################################################################### + + for input_id in range(allInputs): + if ( + (input_id != ".DS_Store") + and (input_id != "Pascal_result.txt") + and (input_id != "Pascal_result_curved.txt") + and (input_id != "Pascal_result_non_curved.txt") + and (input_id != "Deteval_result.txt") + and (input_id != "Deteval_result_curved.txt") + and (input_id != "Deteval_result_non_curved.txt") + ): + detections = input_reading_mod(pred_dict) + groundtruths = gt_reading_mod(gt_dir, img_id).tolist() + detections = detection_filtering( + detections, groundtruths + ) # filters detections overlapping with DC area + dc_id = [] + for i in range(len(groundtruths)): + if groundtruths[i][5] == "#": + dc_id.append(i) + cnt = 0 + for a in dc_id: + num = a - cnt + del groundtruths[num] + cnt += 1 + + local_sigma_table = np.zeros((len(groundtruths), len(detections))) + local_tau_table = np.zeros((len(groundtruths), len(detections))) + local_pred_str = {} + local_gt_str = {} + + for gt_id, gt in enumerate(groundtruths): + if len(detections) > 0: + for det_id, detection in enumerate(detections): + detection_orig = detection + detection = [float(x) for x in detection[0].split(",")] + detection = list(map(int, detection)) + pred_seq_str = detection_orig[1].strip() + det_x = detection[0::2] + det_y = detection[1::2] + gt_x = list(map(int, np.squeeze(gt[1]))) + gt_y = list(map(int, np.squeeze(gt[3]))) + gt_seq_str = str(gt[4].tolist()[0]) + + local_sigma_table[gt_id, det_id] = sigma_calculation( + det_x, det_y, gt_x, gt_y + ) + local_tau_table[gt_id, det_id] = tau_calculation( + det_x, det_y, gt_x, gt_y + ) + local_pred_str[det_id] = pred_seq_str + local_gt_str[gt_id] = gt_seq_str + + global_sigma = local_sigma_table + global_tau = local_tau_table + global_pred_str = local_pred_str + global_gt_str = local_gt_str + + single_data = {} + single_data["sigma"] = global_sigma + single_data["global_tau"] = global_tau + single_data["global_pred_str"] = global_pred_str + single_data["global_gt_str"] = global_gt_str + return single_data + + +def get_score_C(gt_label, text, pred_bboxes): + """ + get score for CentripetalText (CT) prediction. + """ + check_install("Polygon", "Polygon3") + import Polygon as plg + + def gt_reading_mod(gt_label, text): + """This helper reads groundtruths from mat files""" + groundtruths = [] + nbox = len(gt_label) + for i in range(nbox): + label = {"transcription": text[i][0], "points": gt_label[i].numpy()} + groundtruths.append(label) + + return groundtruths + + def get_union(pD, pG): + areaA = pD.area() + areaB = pG.area() + return areaA + areaB - get_intersection(pD, pG) + + def get_intersection(pD, pG): + pInt = pD & pG + if len(pInt) == 0: + return 0 + return pInt.area() + + def detection_filtering(detections, groundtruths, threshold=0.5): + for gt in groundtruths: + point_num = gt["points"].shape[1] // 2 + if gt["transcription"] == "###" and (point_num > 1): + gt_p = np.array(gt["points"]).reshape(point_num, 2).astype("int32") + gt_p = plg.Polygon(gt_p) + + for det_id, detection in enumerate(detections): + det_y = detection[0::2] + det_x = detection[1::2] + + det_p = np.concatenate((np.array(det_x), np.array(det_y))) + det_p = det_p.reshape(2, -1).transpose() + det_p = plg.Polygon(det_p) + + try: + det_gt_iou = get_intersection(det_p, gt_p) / det_p.area() + except: + print(det_x, det_y, gt_p) + if det_gt_iou > threshold: + detections[det_id] = [] + + detections[:] = [item for item in detections if item != []] + return detections + + def sigma_calculation(det_p, gt_p): + """ + sigma = inter_area / gt_area + """ + if gt_p.area() == 0.0: + return 0 + return get_intersection(det_p, gt_p) / gt_p.area() + + def tau_calculation(det_p, gt_p): + """ + tau = inter_area / det_area + """ + if det_p.area() == 0.0: + return 0 + return get_intersection(det_p, gt_p) / det_p.area() + + detections = [] + + for item in pred_bboxes: + detections.append(item[:, ::-1].reshape(-1)) + + groundtruths = gt_reading_mod(gt_label, text) + + detections = detection_filtering( + detections, groundtruths + ) # filters detections overlapping with DC area + + for idx in range(len(groundtruths) - 1, -1, -1): + # NOTE: source code use 'orin' to indicate '#', here we use 'anno', + # which may cause slight drop in fscore, about 0.12 + if groundtruths[idx]["transcription"] == "###": + groundtruths.pop(idx) + + local_sigma_table = np.zeros((len(groundtruths), len(detections))) + local_tau_table = np.zeros((len(groundtruths), len(detections))) + + for gt_id, gt in enumerate(groundtruths): + if len(detections) > 0: + for det_id, detection in enumerate(detections): + point_num = gt["points"].shape[1] // 2 + + gt_p = np.array(gt["points"]).reshape(point_num, 2).astype("int32") + gt_p = plg.Polygon(gt_p) + + det_y = detection[0::2] + det_x = detection[1::2] + + det_p = np.concatenate((np.array(det_x), np.array(det_y))) + + det_p = det_p.reshape(2, -1).transpose() + det_p = plg.Polygon(det_p) + + local_sigma_table[gt_id, det_id] = sigma_calculation(det_p, gt_p) + local_tau_table[gt_id, det_id] = tau_calculation(det_p, gt_p) + + data = {} + data["sigma"] = local_sigma_table + data["global_tau"] = local_tau_table + data["global_pred_str"] = "" + data["global_gt_str"] = "" + return data + + +def combine_results(all_data, rec_flag=True): + tr = 0.7 + tp = 0.6 + fsc_k = 0.8 + k = 2 + global_sigma = [] + global_tau = [] + global_pred_str = [] + global_gt_str = [] + + for data in all_data: + global_sigma.append(data["sigma"]) + global_tau.append(data["global_tau"]) + global_pred_str.append(data["global_pred_str"]) + global_gt_str.append(data["global_gt_str"]) + + global_accumulative_recall = 0 + global_accumulative_precision = 0 + total_num_gt = 0 + total_num_det = 0 + hit_str_count = 0 + hit_count = 0 + + def one_to_one( + local_sigma_table, + local_tau_table, + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + idy, + rec_flag, + ): + hit_str_num = 0 + for gt_id in range(num_gt): + gt_matching_qualified_sigma_candidates = np.where( + local_sigma_table[gt_id, :] > tr + ) + gt_matching_num_qualified_sigma_candidates = ( + gt_matching_qualified_sigma_candidates[0].shape[0] + ) + gt_matching_qualified_tau_candidates = np.where( + local_tau_table[gt_id, :] > tp + ) + gt_matching_num_qualified_tau_candidates = ( + gt_matching_qualified_tau_candidates[0].shape[0] + ) + + det_matching_qualified_sigma_candidates = np.where( + local_sigma_table[:, gt_matching_qualified_sigma_candidates[0]] > tr + ) + det_matching_num_qualified_sigma_candidates = ( + det_matching_qualified_sigma_candidates[0].shape[0] + ) + det_matching_qualified_tau_candidates = np.where( + local_tau_table[:, gt_matching_qualified_tau_candidates[0]] > tp + ) + det_matching_num_qualified_tau_candidates = ( + det_matching_qualified_tau_candidates[0].shape[0] + ) + + if ( + (gt_matching_num_qualified_sigma_candidates == 1) + and (gt_matching_num_qualified_tau_candidates == 1) + and (det_matching_num_qualified_sigma_candidates == 1) + and (det_matching_num_qualified_tau_candidates == 1) + ): + global_accumulative_recall = global_accumulative_recall + 1.0 + global_accumulative_precision = global_accumulative_precision + 1.0 + local_accumulative_recall = local_accumulative_recall + 1.0 + local_accumulative_precision = local_accumulative_precision + 1.0 + + gt_flag[0, gt_id] = 1 + matched_det_id = np.where(local_sigma_table[gt_id, :] > tr) + # recg start + if rec_flag: + gt_str_cur = global_gt_str[idy][gt_id] + pred_str_cur = global_pred_str[idy][matched_det_id[0].tolist()[0]] + if pred_str_cur == gt_str_cur: + hit_str_num += 1 + else: + if pred_str_cur.lower() == gt_str_cur.lower(): + hit_str_num += 1 + # recg end + det_flag[0, matched_det_id] = 1 + return ( + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + hit_str_num, + ) + + def one_to_many( + local_sigma_table, + local_tau_table, + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + idy, + rec_flag, + ): + hit_str_num = 0 + for gt_id in range(num_gt): + # skip the following if the groundtruth was matched + if gt_flag[0, gt_id] > 0: + continue + + non_zero_in_sigma = np.where(local_sigma_table[gt_id, :] > 0) + num_non_zero_in_sigma = non_zero_in_sigma[0].shape[0] + + if num_non_zero_in_sigma >= k: + ####search for all detections that overlaps with this groundtruth + qualified_tau_candidates = np.where( + (local_tau_table[gt_id, :] >= tp) & (det_flag[0, :] == 0) + ) + num_qualified_tau_candidates = qualified_tau_candidates[0].shape[0] + + if num_qualified_tau_candidates == 1: + if (local_tau_table[gt_id, qualified_tau_candidates] >= tp) and ( + local_sigma_table[gt_id, qualified_tau_candidates] >= tr + ): + # became an one-to-one case + global_accumulative_recall = global_accumulative_recall + 1.0 + global_accumulative_precision = ( + global_accumulative_precision + 1.0 + ) + local_accumulative_recall = local_accumulative_recall + 1.0 + local_accumulative_precision = ( + local_accumulative_precision + 1.0 + ) + + gt_flag[0, gt_id] = 1 + det_flag[0, qualified_tau_candidates] = 1 + # recg start + if rec_flag: + gt_str_cur = global_gt_str[idy][gt_id] + pred_str_cur = global_pred_str[idy][ + qualified_tau_candidates[0].tolist()[0] + ] + if pred_str_cur == gt_str_cur: + hit_str_num += 1 + else: + if pred_str_cur.lower() == gt_str_cur.lower(): + hit_str_num += 1 + # recg end + elif np.sum(local_sigma_table[gt_id, qualified_tau_candidates]) >= tr: + gt_flag[0, gt_id] = 1 + det_flag[0, qualified_tau_candidates] = 1 + # recg start + if rec_flag: + gt_str_cur = global_gt_str[idy][gt_id] + pred_str_cur = global_pred_str[idy][ + qualified_tau_candidates[0].tolist()[0] + ] + if pred_str_cur == gt_str_cur: + hit_str_num += 1 + else: + if pred_str_cur.lower() == gt_str_cur.lower(): + hit_str_num += 1 + # recg end + + global_accumulative_recall = global_accumulative_recall + fsc_k + global_accumulative_precision = ( + global_accumulative_precision + + num_qualified_tau_candidates * fsc_k + ) + + local_accumulative_recall = local_accumulative_recall + fsc_k + local_accumulative_precision = ( + local_accumulative_precision + + num_qualified_tau_candidates * fsc_k + ) + + return ( + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + hit_str_num, + ) + + def many_to_one( + local_sigma_table, + local_tau_table, + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + idy, + rec_flag, + ): + hit_str_num = 0 + for det_id in range(num_det): + # skip the following if the detection was matched + if det_flag[0, det_id] > 0: + continue + + non_zero_in_tau = np.where(local_tau_table[:, det_id] > 0) + num_non_zero_in_tau = non_zero_in_tau[0].shape[0] + + if num_non_zero_in_tau >= k: + ####search for all detections that overlaps with this groundtruth + qualified_sigma_candidates = np.where( + (local_sigma_table[:, det_id] >= tp) & (gt_flag[0, :] == 0) + ) + num_qualified_sigma_candidates = qualified_sigma_candidates[0].shape[0] + + if num_qualified_sigma_candidates == 1: + if (local_tau_table[qualified_sigma_candidates, det_id] >= tp) and ( + local_sigma_table[qualified_sigma_candidates, det_id] >= tr + ): + # became an one-to-one case + global_accumulative_recall = global_accumulative_recall + 1.0 + global_accumulative_precision = ( + global_accumulative_precision + 1.0 + ) + local_accumulative_recall = local_accumulative_recall + 1.0 + local_accumulative_precision = ( + local_accumulative_precision + 1.0 + ) + + gt_flag[0, qualified_sigma_candidates] = 1 + det_flag[0, det_id] = 1 + # recg start + if rec_flag: + pred_str_cur = global_pred_str[idy][det_id] + gt_len = len(qualified_sigma_candidates[0]) + for idx in range(gt_len): + ele_gt_id = qualified_sigma_candidates[0].tolist()[idx] + if ele_gt_id not in global_gt_str[idy]: + continue + gt_str_cur = global_gt_str[idy][ele_gt_id] + if pred_str_cur == gt_str_cur: + hit_str_num += 1 + break + else: + if pred_str_cur.lower() == gt_str_cur.lower(): + hit_str_num += 1 + break + # recg end + elif np.sum(local_tau_table[qualified_sigma_candidates, det_id]) >= tp: + det_flag[0, det_id] = 1 + gt_flag[0, qualified_sigma_candidates] = 1 + # recg start + if rec_flag: + pred_str_cur = global_pred_str[idy][det_id] + gt_len = len(qualified_sigma_candidates[0]) + for idx in range(gt_len): + ele_gt_id = qualified_sigma_candidates[0].tolist()[idx] + if ele_gt_id not in global_gt_str[idy]: + continue + gt_str_cur = global_gt_str[idy][ele_gt_id] + if pred_str_cur == gt_str_cur: + hit_str_num += 1 + break + else: + if pred_str_cur.lower() == gt_str_cur.lower(): + hit_str_num += 1 + break + # recg end + + global_accumulative_recall = ( + global_accumulative_recall + + num_qualified_sigma_candidates * fsc_k + ) + global_accumulative_precision = ( + global_accumulative_precision + fsc_k + ) + + local_accumulative_recall = ( + local_accumulative_recall + + num_qualified_sigma_candidates * fsc_k + ) + local_accumulative_precision = local_accumulative_precision + fsc_k + return ( + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + hit_str_num, + ) + + for idx in range(len(global_sigma)): + local_sigma_table = np.array(global_sigma[idx]) + local_tau_table = global_tau[idx] + + num_gt = local_sigma_table.shape[0] + num_det = local_sigma_table.shape[1] + + total_num_gt = total_num_gt + num_gt + total_num_det = total_num_det + num_det + + local_accumulative_recall = 0 + local_accumulative_precision = 0 + gt_flag = np.zeros((1, num_gt)) + det_flag = np.zeros((1, num_det)) + + #######first check for one-to-one case########## + ( + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + hit_str_num, + ) = one_to_one( + local_sigma_table, + local_tau_table, + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + idx, + rec_flag, + ) + + hit_str_count += hit_str_num + #######then check for one-to-many case########## + ( + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + hit_str_num, + ) = one_to_many( + local_sigma_table, + local_tau_table, + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + idx, + rec_flag, + ) + hit_str_count += hit_str_num + #######then check for many-to-one case########## + ( + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + hit_str_num, + ) = many_to_one( + local_sigma_table, + local_tau_table, + local_accumulative_recall, + local_accumulative_precision, + global_accumulative_recall, + global_accumulative_precision, + gt_flag, + det_flag, + idx, + rec_flag, + ) + hit_str_count += hit_str_num + + try: + recall = global_accumulative_recall / total_num_gt + except ZeroDivisionError: + recall = 0 + + try: + precision = global_accumulative_precision / total_num_det + except ZeroDivisionError: + precision = 0 + + try: + f_score = 2 * precision * recall / (precision + recall) + except ZeroDivisionError: + f_score = 0 + + try: + seqerr = 1 - float(hit_str_count) / global_accumulative_recall + except ZeroDivisionError: + seqerr = 1 + + try: + recall_e2e = float(hit_str_count) / total_num_gt + except ZeroDivisionError: + recall_e2e = 0 + + try: + precision_e2e = float(hit_str_count) / total_num_det + except ZeroDivisionError: + precision_e2e = 0 + + try: + f_score_e2e = 2 * precision_e2e * recall_e2e / (precision_e2e + recall_e2e) + except ZeroDivisionError: + f_score_e2e = 0 + + final = { + "total_num_gt": total_num_gt, + "total_num_det": total_num_det, + "global_accumulative_recall": global_accumulative_recall, + "hit_str_count": hit_str_count, + "recall": recall, + "precision": precision, + "f_score": f_score, + "seqerr": seqerr, + "recall_e2e": recall_e2e, + "precision_e2e": precision_e2e, + "f_score_e2e": f_score_e2e, + } + return final diff --git a/ppocr/utils/e2e_metric/polygon_fast.py b/ppocr/utils/e2e_metric/polygon_fast.py new file mode 100755 index 0000000..e2a08c6 --- /dev/null +++ b/ppocr/utils/e2e_metric/polygon_fast.py @@ -0,0 +1,84 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +from shapely.geometry import Polygon + +""" +:param det_x: [1, N] Xs of detection's vertices +:param det_y: [1, N] Ys of detection's vertices +:param gt_x: [1, N] Xs of groundtruth's vertices +:param gt_y: [1, N] Ys of groundtruth's vertices + +############## +All the calculation of 'AREA' in this script is handled by: +1) First generating a binary mask with the polygon area filled up with 1's +2) Summing up all the 1's +""" + + +def area(x, y): + polygon = Polygon(np.stack([x, y], axis=1)) + return float(polygon.area) + + +def approx_area_of_intersection(det_x, det_y, gt_x, gt_y): + """ + This helper determine if both polygons are intersecting with each others with an approximation method. + Area of intersection represented by the minimum bounding rectangular [xmin, ymin, xmax, ymax] + """ + det_ymax = np.max(det_y) + det_xmax = np.max(det_x) + det_ymin = np.min(det_y) + det_xmin = np.min(det_x) + + gt_ymax = np.max(gt_y) + gt_xmax = np.max(gt_x) + gt_ymin = np.min(gt_y) + gt_xmin = np.min(gt_x) + + all_min_ymax = np.minimum(det_ymax, gt_ymax) + all_max_ymin = np.maximum(det_ymin, gt_ymin) + + intersect_heights = np.maximum(0.0, (all_min_ymax - all_max_ymin)) + + all_min_xmax = np.minimum(det_xmax, gt_xmax) + all_max_xmin = np.maximum(det_xmin, gt_xmin) + intersect_widths = np.maximum(0.0, (all_min_xmax - all_max_xmin)) + + return intersect_heights * intersect_widths + + +def area_of_intersection(det_x, det_y, gt_x, gt_y): + p1 = Polygon(np.stack([det_x, det_y], axis=1)).buffer(0) + p2 = Polygon(np.stack([gt_x, gt_y], axis=1)).buffer(0) + return float(p1.intersection(p2).area) + + +def area_of_union(det_x, det_y, gt_x, gt_y): + p1 = Polygon(np.stack([det_x, det_y], axis=1)).buffer(0) + p2 = Polygon(np.stack([gt_x, gt_y], axis=1)).buffer(0) + return float(p1.union(p2).area) + + +def iou(det_x, det_y, gt_x, gt_y): + return area_of_intersection(det_x, det_y, gt_x, gt_y) / ( + area_of_union(det_x, det_y, gt_x, gt_y) + 1.0 + ) + + +def iod(det_x, det_y, gt_x, gt_y): + """ + This helper determine the fraction of intersection area over detection area + """ + return area_of_intersection(det_x, det_y, gt_x, gt_y) / (area(det_x, det_y) + 1.0) diff --git a/ppocr/utils/e2e_utils/extract_batchsize.py b/ppocr/utils/e2e_utils/extract_batchsize.py new file mode 100644 index 0000000..f1ab77b --- /dev/null +++ b/ppocr/utils/e2e_utils/extract_batchsize.py @@ -0,0 +1,88 @@ +import paddle +import numpy as np +import copy + + +def org_tcl_rois(batch_size, pos_lists, pos_masks, label_lists, tcl_bs): + """ """ + pos_lists_, pos_masks_, label_lists_ = [], [], [] + img_bs = batch_size + ngpu = int(batch_size / img_bs) + img_ids = np.array(pos_lists, dtype=np.int32)[:, 0, 0].copy() + pos_lists_split, pos_masks_split, label_lists_split = [], [], [] + for i in range(ngpu): + pos_lists_split.append([]) + pos_masks_split.append([]) + label_lists_split.append([]) + + for i in range(img_ids.shape[0]): + img_id = img_ids[i] + gpu_id = int(img_id / img_bs) + img_id = img_id % img_bs + pos_list = pos_lists[i].copy() + pos_list[:, 0] = img_id + pos_lists_split[gpu_id].append(pos_list) + pos_masks_split[gpu_id].append(pos_masks[i].copy()) + label_lists_split[gpu_id].append(copy.deepcopy(label_lists[i])) + # repeat or delete + for i in range(ngpu): + vp_len = len(pos_lists_split[i]) + if vp_len <= tcl_bs: + for j in range(0, tcl_bs - vp_len): + pos_list = pos_lists_split[i][j].copy() + pos_lists_split[i].append(pos_list) + pos_mask = pos_masks_split[i][j].copy() + pos_masks_split[i].append(pos_mask) + label_list = copy.deepcopy(label_lists_split[i][j]) + label_lists_split[i].append(label_list) + else: + for j in range(0, vp_len - tcl_bs): + c_len = len(pos_lists_split[i]) + pop_id = np.random.permutation(c_len)[0] + pos_lists_split[i].pop(pop_id) + pos_masks_split[i].pop(pop_id) + label_lists_split[i].pop(pop_id) + # merge + for i in range(ngpu): + pos_lists_.extend(pos_lists_split[i]) + pos_masks_.extend(pos_masks_split[i]) + label_lists_.extend(label_lists_split[i]) + return pos_lists_, pos_masks_, label_lists_ + + +def pre_process( + label_list, pos_list, pos_mask, max_text_length, max_text_nums, pad_num, tcl_bs +): + label_list = label_list.numpy() + batch, _, _, _ = label_list.shape + pos_list = pos_list.numpy() + pos_mask = pos_mask.numpy() + pos_list_t = [] + pos_mask_t = [] + label_list_t = [] + for i in range(batch): + for j in range(max_text_nums): + if pos_mask[i, j].any(): + pos_list_t.append(pos_list[i][j]) + pos_mask_t.append(pos_mask[i][j]) + label_list_t.append(label_list[i][j]) + pos_list, pos_mask, label_list = org_tcl_rois( + batch, pos_list_t, pos_mask_t, label_list_t, tcl_bs + ) + label = [] + tt = [l.tolist() for l in label_list] + for i in range(tcl_bs): + k = 0 + for j in range(max_text_length): + if tt[i][j][0] != pad_num: + k += 1 + else: + break + label.append(k) + label = paddle.to_tensor(label) + label = paddle.cast(label, dtype="int64") + pos_list = paddle.to_tensor(pos_list) + pos_mask = paddle.to_tensor(pos_mask) + label_list = paddle.squeeze(paddle.to_tensor(label_list), axis=2) + label_list = paddle.cast(label_list, dtype="int32") + return pos_list, pos_mask, label_list, label diff --git a/ppocr/utils/e2e_utils/extract_textpoint_fast.py b/ppocr/utils/e2e_utils/extract_textpoint_fast.py new file mode 100644 index 0000000..67a89e2 --- /dev/null +++ b/ppocr/utils/e2e_utils/extract_textpoint_fast.py @@ -0,0 +1,523 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Contains various CTC decoders.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import cv2 +import math + +import numpy as np +from itertools import groupby +from skimage.morphology._skeletonize import thin + + +def get_dict(character_dict_path): + character_str = "" + with open(character_dict_path, "rb") as fin: + lines = fin.readlines() + for line in lines: + line = line.decode("utf-8").strip("\n").strip("\r\n") + character_str += line + dict_character = list(character_str) + return dict_character + + +def softmax(logits): + """ + logits: N x d + """ + max_value = np.max(logits, axis=1, keepdims=True) + exp = np.exp(logits - max_value) + exp_sum = np.sum(exp, axis=1, keepdims=True) + dist = exp / exp_sum + return dist + + +def get_keep_pos_idxs(labels, remove_blank=None): + """ + Remove duplicate and get pos idxs of keep items. + The value of keep_blank should be [None, 95]. + """ + duplicate_len_list = [] + keep_pos_idx_list = [] + keep_char_idx_list = [] + for k, v_ in groupby(labels): + current_len = len(list(v_)) + if k != remove_blank: + current_idx = int(sum(duplicate_len_list) + current_len // 2) + keep_pos_idx_list.append(current_idx) + keep_char_idx_list.append(k) + duplicate_len_list.append(current_len) + return keep_char_idx_list, keep_pos_idx_list + + +def remove_blank(labels, blank=0): + new_labels = [x for x in labels if x != blank] + return new_labels + + +def insert_blank(labels, blank=0): + new_labels = [blank] + for l in labels: + new_labels += [l, blank] + return new_labels + + +def ctc_greedy_decoder(probs_seq, blank=95, keep_blank_in_idxs=True): + """ + CTC greedy (best path) decoder. + """ + raw_str = np.argmax(np.array(probs_seq), axis=1) + remove_blank_in_pos = None if keep_blank_in_idxs else blank + dedup_str, keep_idx_list = get_keep_pos_idxs( + raw_str, remove_blank=remove_blank_in_pos + ) + dst_str = remove_blank(dedup_str, blank=blank) + return dst_str, keep_idx_list + + +def instance_ctc_greedy_decoder( + gather_info, logits_map, pts_num=4, point_gather_mode=None +): + _, _, C = logits_map.shape + if point_gather_mode == "align": + insert_num = 0 + gather_info = np.array(gather_info) + length = len(gather_info) - 1 + for index in range(length): + stride_y = np.abs( + gather_info[index + insert_num][0] + - gather_info[index + 1 + insert_num][0] + ) + stride_x = np.abs( + gather_info[index + insert_num][1] + - gather_info[index + 1 + insert_num][1] + ) + max_points = int(max(stride_x, stride_y)) + stride = ( + gather_info[index + insert_num] - gather_info[index + 1 + insert_num] + ) / (max_points) + insert_num_temp = max_points - 1 + + for i in range(int(insert_num_temp)): + insert_value = gather_info[index + insert_num] - (i + 1) * stride + insert_index = index + i + 1 + insert_num + gather_info = np.insert(gather_info, insert_index, insert_value, axis=0) + insert_num += insert_num_temp + gather_info = gather_info.tolist() + else: + pass + ys, xs = zip(*gather_info) + logits_seq = logits_map[list(ys), list(xs)] + probs_seq = logits_seq + labels = np.argmax(probs_seq, axis=1) + dst_str = [k for k, v_ in groupby(labels) if k != C - 1] + detal = len(gather_info) // (pts_num - 1) + keep_idx_list = [0] + [detal * (i + 1) for i in range(pts_num - 2)] + [-1] + keep_gather_list = [gather_info[idx] for idx in keep_idx_list] + return dst_str, keep_gather_list + + +def ctc_decoder_for_image( + gather_info_list, logits_map, Lexicon_Table, pts_num=6, point_gather_mode=None +): + """ + CTC decoder using multiple processes. + """ + decoder_str = [] + decoder_xys = [] + for gather_info in gather_info_list: + if len(gather_info) < pts_num: + continue + dst_str, xys_list = instance_ctc_greedy_decoder( + gather_info, + logits_map, + pts_num=pts_num, + point_gather_mode=point_gather_mode, + ) + dst_str_readable = "".join([Lexicon_Table[idx] for idx in dst_str]) + if len(dst_str_readable) < 2: + continue + decoder_str.append(dst_str_readable) + decoder_xys.append(xys_list) + return decoder_str, decoder_xys + + +def sort_with_direction(pos_list, f_direction): + """ + f_direction: h x w x 2 + pos_list: [[y, x], [y, x], [y, x] ...] + """ + + def sort_part_with_direction(pos_list, point_direction): + pos_list = np.array(pos_list).reshape(-1, 2) + point_direction = np.array(point_direction).reshape(-1, 2) + average_direction = np.mean(point_direction, axis=0, keepdims=True) + pos_proj_leng = np.sum(pos_list * average_direction, axis=1) + sorted_list = pos_list[np.argsort(pos_proj_leng)].tolist() + sorted_direction = point_direction[np.argsort(pos_proj_leng)].tolist() + return sorted_list, sorted_direction + + pos_list = np.array(pos_list).reshape(-1, 2) + point_direction = f_direction[pos_list[:, 0], pos_list[:, 1]] # x, y + point_direction = point_direction[:, ::-1] # x, y -> y, x + sorted_point, sorted_direction = sort_part_with_direction(pos_list, point_direction) + + point_num = len(sorted_point) + if point_num >= 16: + middle_num = point_num // 2 + first_part_point = sorted_point[:middle_num] + first_point_direction = sorted_direction[:middle_num] + sorted_fist_part_point, sorted_fist_part_direction = sort_part_with_direction( + first_part_point, first_point_direction + ) + + last_part_point = sorted_point[middle_num:] + last_point_direction = sorted_direction[middle_num:] + sorted_last_part_point, sorted_last_part_direction = sort_part_with_direction( + last_part_point, last_point_direction + ) + sorted_point = sorted_fist_part_point + sorted_last_part_point + sorted_direction = sorted_fist_part_direction + sorted_last_part_direction + + return sorted_point, np.array(sorted_direction) + + +def add_id(pos_list, image_id=0): + """ + Add id for gather feature, for inference. + """ + new_list = [] + for item in pos_list: + new_list.append((image_id, item[0], item[1])) + return new_list + + +def sort_and_expand_with_direction(pos_list, f_direction): + """ + f_direction: h x w x 2 + pos_list: [[y, x], [y, x], [y, x] ...] + """ + h, w, _ = f_direction.shape + sorted_list, point_direction = sort_with_direction(pos_list, f_direction) + + point_num = len(sorted_list) + sub_direction_len = max(point_num // 3, 2) + left_direction = point_direction[:sub_direction_len, :] + right_dirction = point_direction[point_num - sub_direction_len :, :] + + left_average_direction = -np.mean(left_direction, axis=0, keepdims=True) + left_average_len = np.linalg.norm(left_average_direction) + left_start = np.array(sorted_list[0]) + left_step = left_average_direction / (left_average_len + 1e-6) + + right_average_direction = np.mean(right_dirction, axis=0, keepdims=True) + right_average_len = np.linalg.norm(right_average_direction) + right_step = right_average_direction / (right_average_len + 1e-6) + right_start = np.array(sorted_list[-1]) + + append_num = max(int((left_average_len + right_average_len) / 2.0 * 0.15), 1) + left_list = [] + right_list = [] + for i in range(append_num): + ly, lx = ( + np.round(left_start + left_step * (i + 1)) + .flatten() + .astype("int32") + .tolist() + ) + if ly < h and lx < w and (ly, lx) not in left_list: + left_list.append((ly, lx)) + ry, rx = ( + np.round(right_start + right_step * (i + 1)) + .flatten() + .astype("int32") + .tolist() + ) + if ry < h and rx < w and (ry, rx) not in right_list: + right_list.append((ry, rx)) + + all_list = left_list[::-1] + sorted_list + right_list + return all_list + + +def sort_and_expand_with_direction_v2(pos_list, f_direction, binary_tcl_map): + """ + f_direction: h x w x 2 + pos_list: [[y, x], [y, x], [y, x] ...] + binary_tcl_map: h x w + """ + h, w, _ = f_direction.shape + sorted_list, point_direction = sort_with_direction(pos_list, f_direction) + + point_num = len(sorted_list) + sub_direction_len = max(point_num // 3, 2) + left_direction = point_direction[:sub_direction_len, :] + right_dirction = point_direction[point_num - sub_direction_len :, :] + + left_average_direction = -np.mean(left_direction, axis=0, keepdims=True) + left_average_len = np.linalg.norm(left_average_direction) + left_start = np.array(sorted_list[0]) + left_step = left_average_direction / (left_average_len + 1e-6) + + right_average_direction = np.mean(right_dirction, axis=0, keepdims=True) + right_average_len = np.linalg.norm(right_average_direction) + right_step = right_average_direction / (right_average_len + 1e-6) + right_start = np.array(sorted_list[-1]) + + append_num = max(int((left_average_len + right_average_len) / 2.0 * 0.15), 1) + max_append_num = 2 * append_num + + left_list = [] + right_list = [] + for i in range(max_append_num): + ly, lx = ( + np.round(left_start + left_step * (i + 1)) + .flatten() + .astype("int32") + .tolist() + ) + if ly < h and lx < w and (ly, lx) not in left_list: + if binary_tcl_map[ly, lx] > 0.5: + left_list.append((ly, lx)) + else: + break + + for i in range(max_append_num): + ry, rx = ( + np.round(right_start + right_step * (i + 1)) + .flatten() + .astype("int32") + .tolist() + ) + if ry < h and rx < w and (ry, rx) not in right_list: + if binary_tcl_map[ry, rx] > 0.5: + right_list.append((ry, rx)) + else: + break + + all_list = left_list[::-1] + sorted_list + right_list + return all_list + + +def point_pair2poly(point_pair_list): + """ + Transfer vertical point_pairs into poly point in clockwise. + """ + point_num = len(point_pair_list) * 2 + point_list = [0] * point_num + for idx, point_pair in enumerate(point_pair_list): + point_list[idx] = point_pair[0] + point_list[point_num - 1 - idx] = point_pair[1] + return np.array(point_list).reshape(-1, 2) + + +def shrink_quad_along_width(quad, begin_width_ratio=0.0, end_width_ratio=1.0): + ratio_pair = np.array([[begin_width_ratio], [end_width_ratio]], dtype=np.float32) + p0_1 = quad[0] + (quad[1] - quad[0]) * ratio_pair + p3_2 = quad[3] + (quad[2] - quad[3]) * ratio_pair + return np.array([p0_1[0], p0_1[1], p3_2[1], p3_2[0]]) + + +def expand_poly_along_width(poly, shrink_ratio_of_width=0.3): + """ + expand poly along width. + """ + point_num = poly.shape[0] + left_quad = np.array([poly[0], poly[1], poly[-2], poly[-1]], dtype=np.float32) + left_ratio = ( + -shrink_ratio_of_width + * np.linalg.norm(left_quad[0] - left_quad[3]) + / (np.linalg.norm(left_quad[0] - left_quad[1]) + 1e-6) + ) + left_quad_expand = shrink_quad_along_width(left_quad, left_ratio, 1.0) + right_quad = np.array( + [ + poly[point_num // 2 - 2], + poly[point_num // 2 - 1], + poly[point_num // 2], + poly[point_num // 2 + 1], + ], + dtype=np.float32, + ) + right_ratio = 1.0 + shrink_ratio_of_width * np.linalg.norm( + right_quad[0] - right_quad[3] + ) / (np.linalg.norm(right_quad[0] - right_quad[1]) + 1e-6) + right_quad_expand = shrink_quad_along_width(right_quad, 0.0, right_ratio) + poly[0] = left_quad_expand[0] + poly[-1] = left_quad_expand[-1] + poly[point_num // 2 - 1] = right_quad_expand[1] + poly[point_num // 2] = right_quad_expand[2] + return poly + + +def restore_poly( + instance_yxs_list, seq_strs, p_border, ratio_w, ratio_h, src_w, src_h, valid_set +): + poly_list = [] + keep_str_list = [] + for yx_center_line, keep_str in zip(instance_yxs_list, seq_strs): + if len(keep_str) < 2: + print("--> too short, {}".format(keep_str)) + continue + + offset_expand = 1.0 + if valid_set == "totaltext": + offset_expand = 1.2 + + point_pair_list = [] + for y, x in yx_center_line: + offset = p_border[:, y, x].reshape(2, 2) * offset_expand + ori_yx = np.array([y, x], dtype=np.float32) + point_pair = ( + (ori_yx + offset)[:, ::-1] + * 4.0 + / np.array([ratio_w, ratio_h]).reshape(-1, 2) + ) + point_pair_list.append(point_pair) + + detected_poly = point_pair2poly(point_pair_list) + detected_poly = expand_poly_along_width( + detected_poly, shrink_ratio_of_width=0.2 + ) + detected_poly[:, 0] = np.clip(detected_poly[:, 0], a_min=0, a_max=src_w) + detected_poly[:, 1] = np.clip(detected_poly[:, 1], a_min=0, a_max=src_h) + + keep_str_list.append(keep_str) + if valid_set == "partvgg": + middle_point = len(detected_poly) // 2 + detected_poly = detected_poly[[0, middle_point - 1, middle_point, -1], :] + poly_list.append(detected_poly) + elif valid_set == "totaltext": + poly_list.append(detected_poly) + else: + print("--> Not supported format.") + exit(-1) + return poly_list, keep_str_list + + +def generate_pivot_list_fast( + p_score, + p_char_maps, + f_direction, + Lexicon_Table, + score_thresh=0.5, + point_gather_mode=None, +): + """ + return center point and end point of TCL instance; filter with the char maps; + """ + p_score = p_score[0] + f_direction = f_direction.transpose(1, 2, 0) + p_tcl_map = (p_score > score_thresh) * 1.0 + skeleton_map = thin(p_tcl_map.astype(np.uint8)) + instance_count, instance_label_map = cv2.connectedComponents( + skeleton_map.astype(np.uint8), connectivity=8 + ) + + # get TCL Instance + all_pos_yxs = [] + if instance_count > 0: + for instance_id in range(1, instance_count): + pos_list = [] + ys, xs = np.where(instance_label_map == instance_id) + pos_list = list(zip(ys, xs)) + + if len(pos_list) < 3: + continue + + pos_list_sorted = sort_and_expand_with_direction_v2( + pos_list, f_direction, p_tcl_map + ) + all_pos_yxs.append(pos_list_sorted) + + p_char_maps = p_char_maps.transpose([1, 2, 0]) + decoded_str, keep_yxs_list = ctc_decoder_for_image( + all_pos_yxs, + logits_map=p_char_maps, + Lexicon_Table=Lexicon_Table, + point_gather_mode=point_gather_mode, + ) + return keep_yxs_list, decoded_str + + +def extract_main_direction(pos_list, f_direction): + """ + f_direction: h x w x 2 + pos_list: [[y, x], [y, x], [y, x] ...] + """ + pos_list = np.array(pos_list) + point_direction = f_direction[pos_list[:, 0], pos_list[:, 1]] + point_direction = point_direction[:, ::-1] # x, y -> y, x + average_direction = np.mean(point_direction, axis=0, keepdims=True) + average_direction = average_direction / (np.linalg.norm(average_direction) + 1e-6) + return average_direction + + +def sort_by_direction_with_image_id_deprecated(pos_list, f_direction): + """ + f_direction: h x w x 2 + pos_list: [[id, y, x], [id, y, x], [id, y, x] ...] + """ + pos_list_full = np.array(pos_list).reshape(-1, 3) + pos_list = pos_list_full[:, 1:] + point_direction = f_direction[pos_list[:, 0], pos_list[:, 1]] # x, y + point_direction = point_direction[:, ::-1] # x, y -> y, x + average_direction = np.mean(point_direction, axis=0, keepdims=True) + pos_proj_leng = np.sum(pos_list * average_direction, axis=1) + sorted_list = pos_list_full[np.argsort(pos_proj_leng)].tolist() + return sorted_list + + +def sort_by_direction_with_image_id(pos_list, f_direction): + """ + f_direction: h x w x 2 + pos_list: [[y, x], [y, x], [y, x] ...] + """ + + def sort_part_with_direction(pos_list_full, point_direction): + pos_list_full = np.array(pos_list_full).reshape(-1, 3) + pos_list = pos_list_full[:, 1:] + point_direction = np.array(point_direction).reshape(-1, 2) + average_direction = np.mean(point_direction, axis=0, keepdims=True) + pos_proj_leng = np.sum(pos_list * average_direction, axis=1) + sorted_list = pos_list_full[np.argsort(pos_proj_leng)].tolist() + sorted_direction = point_direction[np.argsort(pos_proj_leng)].tolist() + return sorted_list, sorted_direction + + pos_list = np.array(pos_list).reshape(-1, 3) + point_direction = f_direction[pos_list[:, 1], pos_list[:, 2]] # x, y + point_direction = point_direction[:, ::-1] # x, y -> y, x + sorted_point, sorted_direction = sort_part_with_direction(pos_list, point_direction) + + point_num = len(sorted_point) + if point_num >= 16: + middle_num = point_num // 2 + first_part_point = sorted_point[:middle_num] + first_point_direction = sorted_direction[:middle_num] + sorted_fist_part_point, sorted_fist_part_direction = sort_part_with_direction( + first_part_point, first_point_direction + ) + + last_part_point = sorted_point[middle_num:] + last_point_direction = sorted_direction[middle_num:] + sorted_last_part_point, sorted_last_part_direction = sort_part_with_direction( + last_part_point, last_point_direction + ) + sorted_point = sorted_fist_part_point + sorted_last_part_point + sorted_direction = sorted_fist_part_direction + sorted_last_part_direction + + return sorted_point diff --git a/ppocr/utils/e2e_utils/extract_textpoint_slow.py b/ppocr/utils/e2e_utils/extract_textpoint_slow.py new file mode 100644 index 0000000..2beff89 --- /dev/null +++ b/ppocr/utils/e2e_utils/extract_textpoint_slow.py @@ -0,0 +1,624 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Contains various CTC decoders.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import cv2 +import math + +import numpy as np +from itertools import groupby +from skimage.morphology._skeletonize import thin + + +def get_dict(character_dict_path): + character_str = "" + with open(character_dict_path, "rb") as fin: + lines = fin.readlines() + for line in lines: + line = line.decode("utf-8").strip("\n").strip("\r\n") + character_str += line + dict_character = list(character_str) + return dict_character + + +def point_pair2poly(point_pair_list): + """ + Transfer vertical point_pairs into poly point in clockwise. + """ + pair_length_list = [] + for point_pair in point_pair_list: + pair_length = np.linalg.norm(point_pair[0] - point_pair[1]) + pair_length_list.append(pair_length) + pair_length_list = np.array(pair_length_list) + pair_info = ( + pair_length_list.max(), + pair_length_list.min(), + pair_length_list.mean(), + ) + + point_num = len(point_pair_list) * 2 + point_list = [0] * point_num + for idx, point_pair in enumerate(point_pair_list): + point_list[idx] = point_pair[0] + point_list[point_num - 1 - idx] = point_pair[1] + return np.array(point_list).reshape(-1, 2), pair_info + + +def shrink_quad_along_width(quad, begin_width_ratio=0.0, end_width_ratio=1.0): + """ + Generate shrink_quad_along_width. + """ + ratio_pair = np.array([[begin_width_ratio], [end_width_ratio]], dtype=np.float32) + p0_1 = quad[0] + (quad[1] - quad[0]) * ratio_pair + p3_2 = quad[3] + (quad[2] - quad[3]) * ratio_pair + return np.array([p0_1[0], p0_1[1], p3_2[1], p3_2[0]]) + + +def expand_poly_along_width(poly, shrink_ratio_of_width=0.3): + """ + expand poly along width. + """ + point_num = poly.shape[0] + left_quad = np.array([poly[0], poly[1], poly[-2], poly[-1]], dtype=np.float32) + left_ratio = ( + -shrink_ratio_of_width + * np.linalg.norm(left_quad[0] - left_quad[3]) + / (np.linalg.norm(left_quad[0] - left_quad[1]) + 1e-6) + ) + left_quad_expand = shrink_quad_along_width(left_quad, left_ratio, 1.0) + right_quad = np.array( + [ + poly[point_num // 2 - 2], + poly[point_num // 2 - 1], + poly[point_num // 2], + poly[point_num // 2 + 1], + ], + dtype=np.float32, + ) + right_ratio = 1.0 + shrink_ratio_of_width * np.linalg.norm( + right_quad[0] - right_quad[3] + ) / (np.linalg.norm(right_quad[0] - right_quad[1]) + 1e-6) + right_quad_expand = shrink_quad_along_width(right_quad, 0.0, right_ratio) + poly[0] = left_quad_expand[0] + poly[-1] = left_quad_expand[-1] + poly[point_num // 2 - 1] = right_quad_expand[1] + poly[point_num // 2] = right_quad_expand[2] + return poly + + +def softmax(logits): + """ + logits: N x d + """ + max_value = np.max(logits, axis=1, keepdims=True) + exp = np.exp(logits - max_value) + exp_sum = np.sum(exp, axis=1, keepdims=True) + dist = exp / exp_sum + return dist + + +def get_keep_pos_idxs(labels, remove_blank=None): + """ + Remove duplicate and get pos idxs of keep items. + The value of keep_blank should be [None, 95]. + """ + duplicate_len_list = [] + keep_pos_idx_list = [] + keep_char_idx_list = [] + for k, v_ in groupby(labels): + current_len = len(list(v_)) + if k != remove_blank: + current_idx = int(sum(duplicate_len_list) + current_len // 2) + keep_pos_idx_list.append(current_idx) + keep_char_idx_list.append(k) + duplicate_len_list.append(current_len) + return keep_char_idx_list, keep_pos_idx_list + + +def remove_blank(labels, blank=0): + new_labels = [x for x in labels if x != blank] + return new_labels + + +def insert_blank(labels, blank=0): + new_labels = [blank] + for l in labels: + new_labels += [l, blank] + return new_labels + + +def ctc_greedy_decoder(probs_seq, blank=95, keep_blank_in_idxs=True): + """ + CTC greedy (best path) decoder. + """ + raw_str = np.argmax(np.array(probs_seq), axis=1) + remove_blank_in_pos = None if keep_blank_in_idxs else blank + dedup_str, keep_idx_list = get_keep_pos_idxs( + raw_str, remove_blank=remove_blank_in_pos + ) + dst_str = remove_blank(dedup_str, blank=blank) + return dst_str, keep_idx_list + + +def instance_ctc_greedy_decoder(gather_info, logits_map, keep_blank_in_idxs=True): + """ + gather_info: [[x, y], [x, y] ...] + logits_map: H x W X (n_chars + 1) + """ + _, _, C = logits_map.shape + ys, xs = zip(*gather_info) + logits_seq = logits_map[list(ys), list(xs)] # n x 96 + probs_seq = softmax(logits_seq) + dst_str, keep_idx_list = ctc_greedy_decoder( + probs_seq, blank=C - 1, keep_blank_in_idxs=keep_blank_in_idxs + ) + keep_gather_list = [gather_info[idx] for idx in keep_idx_list] + return dst_str, keep_gather_list + + +def ctc_decoder_for_image(gather_info_list, logits_map, keep_blank_in_idxs=True): + """ + CTC decoder using multiple processes. + """ + decoder_results = [] + for gather_info in gather_info_list: + res = instance_ctc_greedy_decoder( + gather_info, logits_map, keep_blank_in_idxs=keep_blank_in_idxs + ) + decoder_results.append(res) + return decoder_results + + +def sort_with_direction(pos_list, f_direction): + """ + f_direction: h x w x 2 + pos_list: [[y, x], [y, x], [y, x] ...] + """ + + def sort_part_with_direction(pos_list, point_direction): + pos_list = np.array(pos_list).reshape(-1, 2) + point_direction = np.array(point_direction).reshape(-1, 2) + average_direction = np.mean(point_direction, axis=0, keepdims=True) + pos_proj_leng = np.sum(pos_list * average_direction, axis=1) + sorted_list = pos_list[np.argsort(pos_proj_leng)].tolist() + sorted_direction = point_direction[np.argsort(pos_proj_leng)].tolist() + return sorted_list, sorted_direction + + pos_list = np.array(pos_list).reshape(-1, 2) + point_direction = f_direction[pos_list[:, 0], pos_list[:, 1]] # x, y + point_direction = point_direction[:, ::-1] # x, y -> y, x + sorted_point, sorted_direction = sort_part_with_direction(pos_list, point_direction) + + point_num = len(sorted_point) + if point_num >= 16: + middle_num = point_num // 2 + first_part_point = sorted_point[:middle_num] + first_point_direction = sorted_direction[:middle_num] + sorted_fist_part_point, sorted_fist_part_direction = sort_part_with_direction( + first_part_point, first_point_direction + ) + + last_part_point = sorted_point[middle_num:] + last_point_direction = sorted_direction[middle_num:] + sorted_last_part_point, sorted_last_part_direction = sort_part_with_direction( + last_part_point, last_point_direction + ) + sorted_point = sorted_fist_part_point + sorted_last_part_point + sorted_direction = sorted_fist_part_direction + sorted_last_part_direction + + return sorted_point, np.array(sorted_direction) + + +def add_id(pos_list, image_id=0): + """ + Add id for gather feature, for inference. + """ + new_list = [] + for item in pos_list: + new_list.append((image_id, item[0], item[1])) + return new_list + + +def sort_and_expand_with_direction(pos_list, f_direction): + """ + f_direction: h x w x 2 + pos_list: [[y, x], [y, x], [y, x] ...] + """ + h, w, _ = f_direction.shape + sorted_list, point_direction = sort_with_direction(pos_list, f_direction) + + # expand along + point_num = len(sorted_list) + sub_direction_len = max(point_num // 3, 2) + left_direction = point_direction[:sub_direction_len, :] + right_dirction = point_direction[point_num - sub_direction_len :, :] + + left_average_direction = -np.mean(left_direction, axis=0, keepdims=True) + left_average_len = np.linalg.norm(left_average_direction) + left_start = np.array(sorted_list[0]) + left_step = left_average_direction / (left_average_len + 1e-6) + + right_average_direction = np.mean(right_dirction, axis=0, keepdims=True) + right_average_len = np.linalg.norm(right_average_direction) + right_step = right_average_direction / (right_average_len + 1e-6) + right_start = np.array(sorted_list[-1]) + + append_num = max(int((left_average_len + right_average_len) / 2.0 * 0.15), 1) + left_list = [] + right_list = [] + for i in range(append_num): + ly, lx = ( + np.round(left_start + left_step * (i + 1)) + .flatten() + .astype("int32") + .tolist() + ) + if ly < h and lx < w and (ly, lx) not in left_list: + left_list.append((ly, lx)) + ry, rx = ( + np.round(right_start + right_step * (i + 1)) + .flatten() + .astype("int32") + .tolist() + ) + if ry < h and rx < w and (ry, rx) not in right_list: + right_list.append((ry, rx)) + + all_list = left_list[::-1] + sorted_list + right_list + return all_list + + +def sort_and_expand_with_direction_v2(pos_list, f_direction, binary_tcl_map): + """ + f_direction: h x w x 2 + pos_list: [[y, x], [y, x], [y, x] ...] + binary_tcl_map: h x w + """ + h, w, _ = f_direction.shape + sorted_list, point_direction = sort_with_direction(pos_list, f_direction) + + # expand along + point_num = len(sorted_list) + sub_direction_len = max(point_num // 3, 2) + left_direction = point_direction[:sub_direction_len, :] + right_dirction = point_direction[point_num - sub_direction_len :, :] + + left_average_direction = -np.mean(left_direction, axis=0, keepdims=True) + left_average_len = np.linalg.norm(left_average_direction) + left_start = np.array(sorted_list[0]) + left_step = left_average_direction / (left_average_len + 1e-6) + + right_average_direction = np.mean(right_dirction, axis=0, keepdims=True) + right_average_len = np.linalg.norm(right_average_direction) + right_step = right_average_direction / (right_average_len + 1e-6) + right_start = np.array(sorted_list[-1]) + + append_num = max(int((left_average_len + right_average_len) / 2.0 * 0.15), 1) + max_append_num = 2 * append_num + + left_list = [] + right_list = [] + for i in range(max_append_num): + ly, lx = ( + np.round(left_start + left_step * (i + 1)) + .flatten() + .astype("int32") + .tolist() + ) + if ly < h and lx < w and (ly, lx) not in left_list: + if binary_tcl_map[ly, lx] > 0.5: + left_list.append((ly, lx)) + else: + break + + for i in range(max_append_num): + ry, rx = ( + np.round(right_start + right_step * (i + 1)) + .flatten() + .astype("int32") + .tolist() + ) + if ry < h and rx < w and (ry, rx) not in right_list: + if binary_tcl_map[ry, rx] > 0.5: + right_list.append((ry, rx)) + else: + break + + all_list = left_list[::-1] + sorted_list + right_list + return all_list + + +def generate_pivot_list_curved( + p_score, + p_char_maps, + f_direction, + score_thresh=0.5, + is_expand=True, + is_backbone=False, + image_id=0, +): + """ + return center point and end point of TCL instance; filter with the char maps; + """ + p_score = p_score[0] + f_direction = f_direction.transpose(1, 2, 0) + p_tcl_map = (p_score > score_thresh) * 1.0 + skeleton_map = thin(p_tcl_map) + instance_count, instance_label_map = cv2.connectedComponents( + skeleton_map.astype(np.uint8), connectivity=8 + ) + + # get TCL Instance + all_pos_yxs = [] + center_pos_yxs = [] + end_points_yxs = [] + instance_center_pos_yxs = [] + pred_strs = [] + if instance_count > 0: + for instance_id in range(1, instance_count): + pos_list = [] + ys, xs = np.where(instance_label_map == instance_id) + pos_list = list(zip(ys, xs)) + + ### FIX-ME, eliminate outlier + if len(pos_list) < 3: + continue + + if is_expand: + pos_list_sorted = sort_and_expand_with_direction_v2( + pos_list, f_direction, p_tcl_map + ) + else: + pos_list_sorted, _ = sort_with_direction(pos_list, f_direction) + all_pos_yxs.append(pos_list_sorted) + + # use decoder to filter background points. + p_char_maps = p_char_maps.transpose([1, 2, 0]) + decode_res = ctc_decoder_for_image( + all_pos_yxs, logits_map=p_char_maps, keep_blank_in_idxs=True + ) + for decoded_str, keep_yxs_list in decode_res: + if is_backbone: + keep_yxs_list_with_id = add_id(keep_yxs_list, image_id=image_id) + instance_center_pos_yxs.append(keep_yxs_list_with_id) + pred_strs.append(decoded_str) + else: + end_points_yxs.extend((keep_yxs_list[0], keep_yxs_list[-1])) + center_pos_yxs.extend(keep_yxs_list) + + if is_backbone: + return pred_strs, instance_center_pos_yxs + else: + return center_pos_yxs, end_points_yxs + + +def generate_pivot_list_horizontal( + p_score, p_char_maps, f_direction, score_thresh=0.5, is_backbone=False, image_id=0 +): + """ + return center point and end point of TCL instance; filter with the char maps; + """ + p_score = p_score[0] + f_direction = f_direction.transpose(1, 2, 0) + p_tcl_map_bi = (p_score > score_thresh) * 1.0 + instance_count, instance_label_map = cv2.connectedComponents( + p_tcl_map_bi.astype(np.uint8), connectivity=8 + ) + + # get TCL Instance + all_pos_yxs = [] + center_pos_yxs = [] + end_points_yxs = [] + instance_center_pos_yxs = [] + + if instance_count > 0: + for instance_id in range(1, instance_count): + pos_list = [] + ys, xs = np.where(instance_label_map == instance_id) + pos_list = list(zip(ys, xs)) + + ### FIX-ME, eliminate outlier + if len(pos_list) < 5: + continue + + # add rule here + main_direction = extract_main_direction(pos_list, f_direction) # y x + reference_directin = np.array([0, 1]).reshape([-1, 2]) # y x + is_h_angle = abs(np.sum(main_direction * reference_directin)) < math.cos( + math.pi / 180 * 70 + ) + + point_yxs = np.array(pos_list) + max_y, max_x = np.max(point_yxs, axis=0) + min_y, min_x = np.min(point_yxs, axis=0) + is_h_len = (max_y - min_y) < 1.5 * (max_x - min_x) + + pos_list_final = [] + if is_h_len: + xs = np.unique(xs) + for x in xs: + ys = instance_label_map[:, x].copy().reshape((-1,)) + y = int(np.where(ys == instance_id)[0].mean()) + pos_list_final.append((y, x)) + else: + ys = np.unique(ys) + for y in ys: + xs = instance_label_map[y, :].copy().reshape((-1,)) + x = int(np.where(xs == instance_id)[0].mean()) + pos_list_final.append((y, x)) + + pos_list_sorted, _ = sort_with_direction(pos_list_final, f_direction) + all_pos_yxs.append(pos_list_sorted) + + # use decoder to filter background points. + p_char_maps = p_char_maps.transpose([1, 2, 0]) + decode_res = ctc_decoder_for_image( + all_pos_yxs, logits_map=p_char_maps, keep_blank_in_idxs=True + ) + for decoded_str, keep_yxs_list in decode_res: + if is_backbone: + keep_yxs_list_with_id = add_id(keep_yxs_list, image_id=image_id) + instance_center_pos_yxs.append(keep_yxs_list_with_id) + else: + end_points_yxs.extend((keep_yxs_list[0], keep_yxs_list[-1])) + center_pos_yxs.extend(keep_yxs_list) + + if is_backbone: + return instance_center_pos_yxs + else: + return center_pos_yxs, end_points_yxs + + +def generate_pivot_list_slow( + p_score, + p_char_maps, + f_direction, + score_thresh=0.5, + is_backbone=False, + is_curved=True, + image_id=0, +): + """ + Warp all the function together. + """ + if is_curved: + return generate_pivot_list_curved( + p_score, + p_char_maps, + f_direction, + score_thresh=score_thresh, + is_expand=True, + is_backbone=is_backbone, + image_id=image_id, + ) + else: + return generate_pivot_list_horizontal( + p_score, + p_char_maps, + f_direction, + score_thresh=score_thresh, + is_backbone=is_backbone, + image_id=image_id, + ) + + +# for refine module +def extract_main_direction(pos_list, f_direction): + """ + f_direction: h x w x 2 + pos_list: [[y, x], [y, x], [y, x] ...] + """ + pos_list = np.array(pos_list) + point_direction = f_direction[pos_list[:, 0], pos_list[:, 1]] + point_direction = point_direction[:, ::-1] # x, y -> y, x + average_direction = np.mean(point_direction, axis=0, keepdims=True) + average_direction = average_direction / (np.linalg.norm(average_direction) + 1e-6) + return average_direction + + +def sort_by_direction_with_image_id_deprecated(pos_list, f_direction): + """ + f_direction: h x w x 2 + pos_list: [[id, y, x], [id, y, x], [id, y, x] ...] + """ + pos_list_full = np.array(pos_list).reshape(-1, 3) + pos_list = pos_list_full[:, 1:] + point_direction = f_direction[pos_list[:, 0], pos_list[:, 1]] # x, y + point_direction = point_direction[:, ::-1] # x, y -> y, x + average_direction = np.mean(point_direction, axis=0, keepdims=True) + pos_proj_leng = np.sum(pos_list * average_direction, axis=1) + sorted_list = pos_list_full[np.argsort(pos_proj_leng)].tolist() + return sorted_list + + +def sort_by_direction_with_image_id(pos_list, f_direction): + """ + f_direction: h x w x 2 + pos_list: [[y, x], [y, x], [y, x] ...] + """ + + def sort_part_with_direction(pos_list_full, point_direction): + pos_list_full = np.array(pos_list_full).reshape(-1, 3) + pos_list = pos_list_full[:, 1:] + point_direction = np.array(point_direction).reshape(-1, 2) + average_direction = np.mean(point_direction, axis=0, keepdims=True) + pos_proj_leng = np.sum(pos_list * average_direction, axis=1) + sorted_list = pos_list_full[np.argsort(pos_proj_leng)].tolist() + sorted_direction = point_direction[np.argsort(pos_proj_leng)].tolist() + return sorted_list, sorted_direction + + pos_list = np.array(pos_list).reshape(-1, 3) + point_direction = f_direction[pos_list[:, 1], pos_list[:, 2]] # x, y + point_direction = point_direction[:, ::-1] # x, y -> y, x + sorted_point, sorted_direction = sort_part_with_direction(pos_list, point_direction) + + point_num = len(sorted_point) + if point_num >= 16: + middle_num = point_num // 2 + first_part_point = sorted_point[:middle_num] + first_point_direction = sorted_direction[:middle_num] + sorted_fist_part_point, sorted_fist_part_direction = sort_part_with_direction( + first_part_point, first_point_direction + ) + + last_part_point = sorted_point[middle_num:] + last_point_direction = sorted_direction[middle_num:] + sorted_last_part_point, sorted_last_part_direction = sort_part_with_direction( + last_part_point, last_point_direction + ) + sorted_point = sorted_fist_part_point + sorted_last_part_point + sorted_direction = sorted_fist_part_direction + sorted_last_part_direction + + return sorted_point + + +def generate_pivot_list_tt_inference( + p_score, + p_char_maps, + f_direction, + score_thresh=0.5, + is_backbone=False, + is_curved=True, + image_id=0, +): + """ + return center point and end point of TCL instance; filter with the char maps; + """ + p_score = p_score[0] + f_direction = f_direction.transpose(1, 2, 0) + p_tcl_map = (p_score > score_thresh) * 1.0 + skeleton_map = thin(p_tcl_map) + instance_count, instance_label_map = cv2.connectedComponents( + skeleton_map.astype(np.uint8), connectivity=8 + ) + + # get TCL Instance + all_pos_yxs = [] + if instance_count > 0: + for instance_id in range(1, instance_count): + pos_list = [] + ys, xs = np.where(instance_label_map == instance_id) + pos_list = list(zip(ys, xs)) + ### FIX-ME, eliminate outlier + if len(pos_list) < 3: + continue + pos_list_sorted = sort_and_expand_with_direction_v2( + pos_list, f_direction, p_tcl_map + ) + pos_list_sorted_with_id = add_id(pos_list_sorted, image_id=image_id) + all_pos_yxs.append(pos_list_sorted_with_id) + return all_pos_yxs diff --git a/ppocr/utils/e2e_utils/pgnet_pp_utils.py b/ppocr/utils/e2e_utils/pgnet_pp_utils.py new file mode 100644 index 0000000..71379e5 --- /dev/null +++ b/ppocr/utils/e2e_utils/pgnet_pp_utils.py @@ -0,0 +1,179 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import paddle +import os +import sys + +__dir__ = os.path.dirname(__file__) +sys.path.append(__dir__) +sys.path.append(os.path.join(__dir__, "..")) +from extract_textpoint_slow import * +from extract_textpoint_fast import generate_pivot_list_fast, restore_poly + + +class PGNet_PostProcess(object): + # two different post-process + def __init__( + self, + character_dict_path, + valid_set, + score_thresh, + outs_dict, + shape_list, + point_gather_mode=None, + ): + self.Lexicon_Table = get_dict(character_dict_path) + self.valid_set = valid_set + self.score_thresh = score_thresh + self.outs_dict = outs_dict + self.shape_list = shape_list + self.point_gather_mode = point_gather_mode + + def pg_postprocess_fast(self): + p_score = self.outs_dict["f_score"] + p_border = self.outs_dict["f_border"] + p_char = self.outs_dict["f_char"] + p_direction = self.outs_dict["f_direction"] + if isinstance(p_score, paddle.Tensor): + p_score = p_score[0].numpy() + p_border = p_border[0].numpy() + p_direction = p_direction[0].numpy() + p_char = p_char[0].numpy() + else: + p_score = p_score[0] + p_border = p_border[0] + p_direction = p_direction[0] + p_char = p_char[0] + + src_h, src_w, ratio_h, ratio_w = self.shape_list[0] + instance_yxs_list, seq_strs = generate_pivot_list_fast( + p_score, + p_char, + p_direction, + self.Lexicon_Table, + score_thresh=self.score_thresh, + point_gather_mode=self.point_gather_mode, + ) + poly_list, keep_str_list = restore_poly( + instance_yxs_list, + seq_strs, + p_border, + ratio_w, + ratio_h, + src_w, + src_h, + self.valid_set, + ) + data = { + "points": poly_list, + "texts": keep_str_list, + } + return data + + def pg_postprocess_slow(self): + p_score = self.outs_dict["f_score"] + p_border = self.outs_dict["f_border"] + p_char = self.outs_dict["f_char"] + p_direction = self.outs_dict["f_direction"] + if isinstance(p_score, paddle.Tensor): + p_score = p_score[0].numpy() + p_border = p_border[0].numpy() + p_direction = p_direction[0].numpy() + p_char = p_char[0].numpy() + else: + p_score = p_score[0] + p_border = p_border[0] + p_direction = p_direction[0] + p_char = p_char[0] + src_h, src_w, ratio_h, ratio_w = self.shape_list[0] + is_curved = self.valid_set == "totaltext" + char_seq_idx_set, instance_yxs_list = generate_pivot_list_slow( + p_score, + p_char, + p_direction, + score_thresh=self.score_thresh, + is_backbone=True, + is_curved=is_curved, + ) + seq_strs = [] + for char_idx_set in char_seq_idx_set: + pr_str = "".join([self.Lexicon_Table[pos] for pos in char_idx_set]) + seq_strs.append(pr_str) + poly_list = [] + keep_str_list = [] + all_point_list = [] + all_point_pair_list = [] + for yx_center_line, keep_str in zip(instance_yxs_list, seq_strs): + if len(yx_center_line) == 1: + yx_center_line.append(yx_center_line[-1]) + + offset_expand = 1.0 + if self.valid_set == "totaltext": + offset_expand = 1.2 + + point_pair_list = [] + for batch_id, y, x in yx_center_line: + offset = p_border[:, y, x].reshape(2, 2) + if offset_expand != 1.0: + offset_length = np.linalg.norm(offset, axis=1, keepdims=True) + expand_length = np.clip( + offset_length * (offset_expand - 1), a_min=0.5, a_max=3.0 + ) + offset_detal = offset / offset_length * expand_length + offset = offset + offset_detal + ori_yx = np.array([y, x], dtype=np.float32) + point_pair = ( + (ori_yx + offset)[:, ::-1] + * 4.0 + / np.array([ratio_w, ratio_h]).reshape(-1, 2) + ) + point_pair_list.append(point_pair) + + all_point_list.append( + [int(round(x * 4.0 / ratio_w)), int(round(y * 4.0 / ratio_h))] + ) + all_point_pair_list.append(point_pair.round().astype(np.int32).tolist()) + + detected_poly, pair_length_info = point_pair2poly(point_pair_list) + detected_poly = expand_poly_along_width( + detected_poly, shrink_ratio_of_width=0.2 + ) + detected_poly[:, 0] = np.clip(detected_poly[:, 0], a_min=0, a_max=src_w) + detected_poly[:, 1] = np.clip(detected_poly[:, 1], a_min=0, a_max=src_h) + + if len(keep_str) < 2: + continue + + keep_str_list.append(keep_str) + detected_poly = np.round(detected_poly).astype("int32") + if self.valid_set == "partvgg": + middle_point = len(detected_poly) // 2 + detected_poly = detected_poly[ + [0, middle_point - 1, middle_point, -1], : + ] + poly_list.append(detected_poly) + elif self.valid_set == "totaltext": + poly_list.append(detected_poly) + else: + print("--> Not supported format.") + exit(-1) + data = { + "points": poly_list, + "texts": keep_str_list, + } + return data diff --git a/ppocr/utils/e2e_utils/visual.py b/ppocr/utils/e2e_utils/visual.py new file mode 100644 index 0000000..e321827 --- /dev/null +++ b/ppocr/utils/e2e_utils/visual.py @@ -0,0 +1,167 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +import cv2 +import time + + +def resize_image(im, max_side_len=512): + """ + resize image to a size multiple of max_stride which is required by the network + :param im: the resized image + :param max_side_len: limit of max image size to avoid out of memory in gpu + :return: the resized image and the resize ratio + """ + h, w, _ = im.shape + + resize_w = w + resize_h = h + + if resize_h > resize_w: + ratio = float(max_side_len) / resize_h + else: + ratio = float(max_side_len) / resize_w + + resize_h = int(resize_h * ratio) + resize_w = int(resize_w * ratio) + + max_stride = 128 + resize_h = (resize_h + max_stride - 1) // max_stride * max_stride + resize_w = (resize_w + max_stride - 1) // max_stride * max_stride + im = cv2.resize(im, (int(resize_w), int(resize_h))) + ratio_h = resize_h / float(h) + ratio_w = resize_w / float(w) + + return im, (ratio_h, ratio_w) + + +def resize_image_min(im, max_side_len=512): + """ """ + h, w, _ = im.shape + + resize_w = w + resize_h = h + + if resize_h < resize_w: + ratio = float(max_side_len) / resize_h + else: + ratio = float(max_side_len) / resize_w + + resize_h = int(resize_h * ratio) + resize_w = int(resize_w * ratio) + + max_stride = 128 + resize_h = (resize_h + max_stride - 1) // max_stride * max_stride + resize_w = (resize_w + max_stride - 1) // max_stride * max_stride + im = cv2.resize(im, (int(resize_w), int(resize_h))) + ratio_h = resize_h / float(h) + ratio_w = resize_w / float(w) + return im, (ratio_h, ratio_w) + + +def resize_image_for_totaltext(im, max_side_len=512): + """ """ + h, w, _ = im.shape + + resize_w = w + resize_h = h + ratio = 1.25 + if h * ratio > max_side_len: + ratio = float(max_side_len) / resize_h + + resize_h = int(resize_h * ratio) + resize_w = int(resize_w * ratio) + + max_stride = 128 + resize_h = (resize_h + max_stride - 1) // max_stride * max_stride + resize_w = (resize_w + max_stride - 1) // max_stride * max_stride + im = cv2.resize(im, (int(resize_w), int(resize_h))) + ratio_h = resize_h / float(h) + ratio_w = resize_w / float(w) + return im, (ratio_h, ratio_w) + + +def point_pair2poly(point_pair_list): + """ + Transfer vertical point_pairs into poly point in clockwise. + """ + pair_length_list = [] + for point_pair in point_pair_list: + pair_length = np.linalg.norm(point_pair[0] - point_pair[1]) + pair_length_list.append(pair_length) + pair_length_list = np.array(pair_length_list) + pair_info = ( + pair_length_list.max(), + pair_length_list.min(), + pair_length_list.mean(), + ) + + point_num = len(point_pair_list) * 2 + point_list = [0] * point_num + for idx, point_pair in enumerate(point_pair_list): + point_list[idx] = point_pair[0] + point_list[point_num - 1 - idx] = point_pair[1] + return np.array(point_list).reshape(-1, 2), pair_info + + +def shrink_quad_along_width(quad, begin_width_ratio=0.0, end_width_ratio=1.0): + """ + Generate shrink_quad_along_width. + """ + ratio_pair = np.array([[begin_width_ratio], [end_width_ratio]], dtype=np.float32) + p0_1 = quad[0] + (quad[1] - quad[0]) * ratio_pair + p3_2 = quad[3] + (quad[2] - quad[3]) * ratio_pair + return np.array([p0_1[0], p0_1[1], p3_2[1], p3_2[0]]) + + +def expand_poly_along_width(poly, shrink_ratio_of_width=0.3): + """ + expand poly along width. + """ + point_num = poly.shape[0] + left_quad = np.array([poly[0], poly[1], poly[-2], poly[-1]], dtype=np.float32) + left_ratio = ( + -shrink_ratio_of_width + * np.linalg.norm(left_quad[0] - left_quad[3]) + / (np.linalg.norm(left_quad[0] - left_quad[1]) + 1e-6) + ) + left_quad_expand = shrink_quad_along_width(left_quad, left_ratio, 1.0) + right_quad = np.array( + [ + poly[point_num // 2 - 2], + poly[point_num // 2 - 1], + poly[point_num // 2], + poly[point_num // 2 + 1], + ], + dtype=np.float32, + ) + right_ratio = 1.0 + shrink_ratio_of_width * np.linalg.norm( + right_quad[0] - right_quad[3] + ) / (np.linalg.norm(right_quad[0] - right_quad[1]) + 1e-6) + right_quad_expand = shrink_quad_along_width(right_quad, 0.0, right_ratio) + poly[0] = left_quad_expand[0] + poly[-1] = left_quad_expand[-1] + poly[point_num // 2 - 1] = right_quad_expand[1] + poly[point_num // 2] = right_quad_expand[2] + return poly + + +def norm2(x, axis=None): + if axis: + return np.sqrt(np.sum(x**2, axis=axis)) + return np.sqrt(np.sum(x**2)) + + +def cos(p1, p2): + return (p1 * p2).sum() / (norm2(p1) * norm2(p2)) diff --git a/ppocr/utils/ema.py b/ppocr/utils/ema.py new file mode 100644 index 0000000..b127f67 --- /dev/null +++ b/ppocr/utils/ema.py @@ -0,0 +1,123 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math + +import paddle + + +class ModelEMA: + """Exponential Moving Average for model parameters. + + Maintains shadow copies of model parameters and updates them with: + ema_param = decay * ema_param + (1 - decay) * cur_param + + Reference: PaddleDetection ppdet/optimizer/ema.py + + Args: + model (nn.Layer): The model whose parameters will be averaged. + decay (float): EMA decay coefficient. Default: 0.9998. + gamma (int): Warmup parameter for threshold/exponential decay. + Default: 2000. + ema_decay_type (str): Decay schedule type, one of + 'threshold' (default), 'exponential', 'normal'. + ema_filter_no_grad (bool): If True, parameters with + stop_gradient=True (e.g. frozen Teacher in distillation) + are excluded from EMA and pass through unchanged. + BN running stats are kept even if no-grad. Default: False. + """ + + def __init__( + self, + model, + decay=0.9998, + gamma=2000, + ema_decay_type="threshold", + ema_filter_no_grad=False, + ): + self.decay = decay + self.gamma = gamma + self.ema_decay_type = ema_decay_type + self.step = 0 + self._decay = decay + + # Build black list: frozen params (excluding BN running stats) + self.ema_black_list = set() + if ema_filter_no_grad: + bn_state_names = set() + for name, layer in model.named_sublayers(): + if isinstance(layer, (paddle.nn.BatchNorm2D, paddle.nn.BatchNorm1D)): + prefix = name + "." if name else "" + bn_state_names.add(prefix + "_mean") + bn_state_names.add(prefix + "_variance") + for n, p in model.named_parameters(): + if p.stop_gradient and n not in bn_state_names: + self.ema_black_list.add(n) + + # Initialize shadow weights + self.state_dict = {} + for k, v in model.state_dict().items(): + if k in self.ema_black_list: + self.state_dict[k] = v.clone() + else: + self.state_dict[k] = paddle.zeros_like(v).astype("float32") + + def _get_decay(self): + if self.ema_decay_type == "threshold": + return min(self.decay, (1 + self.step) / (10 + self.step)) + elif self.ema_decay_type == "exponential": + return self.decay * (1 - math.exp(-(self.step + 1) / self.gamma)) + else: # normal + return self.decay + + def update(self, model): + """Update shadow weights with current model parameters.""" + decay = self._get_decay() + self._decay = decay + model_dict = model.state_dict() + for k, v in self.state_dict.items(): + if k not in self.ema_black_list and k in model_dict: + v = decay * v + (1 - decay) * model_dict[k].astype("float32") + v.stop_gradient = True + self.state_dict[k] = v + self.step += 1 + + def apply(self): + """Return bias-corrected EMA state dict for eval/save. + + Does NOT modify internal state. + """ + if self.step == 0: + return {k: v.clone() for k, v in self.state_dict.items()} + state = {} + for k, v in self.state_dict.items(): + if k in self.ema_black_list: + state[k] = v + else: + if self.ema_decay_type != "exponential": + # threshold / normal need bias-correction + v = v / (1 - self._decay**self.step) + v = v.clone() + v.stop_gradient = True + state[k] = v + return state + + def state_dict_for_save(self): + """Return serializable dict for checkpoint.""" + return {"ema_state": self.state_dict, "step": self.step} + + def set_state_dict(self, d): + """Restore from checkpoint.""" + self.state_dict = d["ema_state"] + self.step = d["step"] diff --git a/ppocr/utils/en_dict.txt b/ppocr/utils/en_dict.txt new file mode 100644 index 0000000..7677d31 --- /dev/null +++ b/ppocr/utils/en_dict.txt @@ -0,0 +1,95 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +: +; +< += +> +? +@ +A +B +C +D +E +F +G +H +I +J +K +L +M +N +O +P +Q +R +S +T +U +V +W +X +Y +Z +[ +\ +] +^ +_ +` +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z +{ +| +} +~ +! +" +# +$ +% +& +' +( +) +* ++ +, +- +. +/ + diff --git a/ppocr/utils/export_model.py b/ppocr/utils/export_model.py new file mode 100644 index 0000000..e5edb04 --- /dev/null +++ b/ppocr/utils/export_model.py @@ -0,0 +1,547 @@ +# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import yaml +import json +import copy +import shutil +import paddle +import paddle.nn as nn +from paddle.jit import to_static + +from collections import OrderedDict +from packaging import version +from argparse import ArgumentParser, RawDescriptionHelpFormatter +from ppocr.modeling.architectures import build_model +from ppocr.postprocess import build_post_process +from ppocr.utils.save_load import load_model +from ppocr.utils.logging import get_logger + + +def represent_dictionary_order(self, dict_data): + return self.represent_mapping("tag:yaml.org,2002:map", dict_data.items()) + + +def setup_orderdict(): + yaml.add_representer(OrderedDict, represent_dictionary_order) + + +def dump_infer_config(config, path, logger): + setup_orderdict() + infer_cfg = OrderedDict() + if not os.path.exists(os.path.dirname(path)): + os.makedirs(os.path.dirname(path)) + model_name = None + if config["Global"].get("model_name", None): + model_name = config["Global"]["model_name"] + infer_cfg["Global"] = {"model_name": model_name} + if config["Global"].get("uniform_output_enabled", True): + arch_config = config["Architecture"] + if arch_config["algorithm"] in ["SVTR_LCNet", "SVTR_HGNet"]: + common_dynamic_shapes = { + "x": [[1, 3, 48, 160], [1, 3, 48, 320], [8, 3, 48, 3200]] + } + elif arch_config["model_type"] == "det": + common_dynamic_shapes = { + "x": [[1, 3, 32, 32], [1, 3, 736, 736], [1, 3, 4000, 4000]] + } + elif arch_config["algorithm"] == "SLANet": + if model_name == "SLANet_plus": + common_dynamic_shapes = { + "x": [[1, 3, 32, 32], [1, 3, 64, 448], [1, 3, 488, 488]] + } + else: + common_dynamic_shapes = { + "x": [[1, 3, 32, 32], [1, 3, 64, 448], [8, 3, 488, 488]] + } + elif arch_config["algorithm"] == "SLANeXt": + common_dynamic_shapes = { + "x": [[1, 3, 512, 512], [1, 3, 512, 512], [1, 3, 512, 512]] + } + elif arch_config["algorithm"] == "LaTeXOCR": + common_dynamic_shapes = { + "x": [[1, 1, 32, 32], [1, 1, 64, 448], [1, 1, 192, 672]] + } + elif arch_config["algorithm"] == "UniMERNet": + common_dynamic_shapes = { + "x": [[1, 1, 192, 672], [1, 1, 192, 672], [8, 1, 192, 672]] + } + elif arch_config["algorithm"] in ["PP-FormulaNet-L", "PP-FormulaNet_plus-L"]: + common_dynamic_shapes = { + "x": [[1, 1, 768, 768], [1, 1, 768, 768], [8, 1, 768, 768]] + } + elif arch_config["algorithm"] in [ + "PP-FormulaNet-S", + "PP-FormulaNet_plus-S", + "PP-FormulaNet_plus-M", + ]: + common_dynamic_shapes = { + "x": [[1, 1, 384, 384], [1, 1, 384, 384], [8, 1, 384, 384]] + } + else: + common_dynamic_shapes = None + + backend_keys = ["paddle_infer", "tensorrt"] + hpi_config = { + "backend_configs": { + key: { + ( + "dynamic_shapes" if key == "tensorrt" else "trt_dynamic_shapes" + ): common_dynamic_shapes + } + for key in backend_keys + } + } + if common_dynamic_shapes: + infer_cfg["Hpi"] = hpi_config + + infer_cfg["PreProcess"] = {"transform_ops": config["Eval"]["dataset"]["transforms"]} + postprocess = OrderedDict() + for k, v in config["PostProcess"].items(): + if config["Architecture"].get("algorithm") in [ + "LaTeXOCR", + "UniMERNet", + "PP-FormulaNet-L", + "PP-FormulaNet-S", + "PP-FormulaNet_plus-L", + "PP-FormulaNet_plus-M", + "PP-FormulaNet_plus-S", + ]: + if k != "rec_char_dict_path": + postprocess[k] = v + else: + postprocess[k] = v + + if config["Architecture"].get("algorithm") in ["LaTeXOCR"]: + tokenizer_file = config["Global"].get("rec_char_dict_path") + if tokenizer_file is not None: + with open(tokenizer_file, encoding="utf-8") as tokenizer_config_handle: + character_dict = json.load(tokenizer_config_handle) + postprocess["character_dict"] = character_dict + elif config["Architecture"].get("algorithm") in [ + "UniMERNet", + "PP-FormulaNet-L", + "PP-FormulaNet-S", + "PP-FormulaNet_plus-L", + "PP-FormulaNet_plus-M", + "PP-FormulaNet_plus-S", + ]: + tokenizer_file = config["Global"].get("rec_char_dict_path") + fast_tokenizer_file = os.path.join(tokenizer_file, "tokenizer.json") + tokenizer_config_file = os.path.join(tokenizer_file, "tokenizer_config.json") + postprocess["character_dict"] = {} + if fast_tokenizer_file is not None: + with open(fast_tokenizer_file, encoding="utf-8") as tokenizer_config_handle: + character_dict = json.load(tokenizer_config_handle) + postprocess["character_dict"]["fast_tokenizer_file"] = character_dict + if tokenizer_config_file is not None: + with open( + tokenizer_config_file, encoding="utf-8" + ) as tokenizer_config_handle: + character_dict = json.load(tokenizer_config_handle) + postprocess["character_dict"]["tokenizer_config_file"] = character_dict + else: + if config["Global"].get("character_dict_path") is not None: + with open(config["Global"]["character_dict_path"], encoding="utf-8") as f: + lines = f.readlines() + character_dict = [line.strip("\n") for line in lines] + postprocess["character_dict"] = character_dict + + infer_cfg["PostProcess"] = postprocess + + with open(path, "w", encoding="utf-8") as f: + yaml.dump(infer_cfg, f, default_flow_style=False, allow_unicode=True) + logger.info("Export inference config file to {}".format(os.path.join(path))) + + +def dynamic_to_static(model, arch_config, logger, input_shape=None): + if arch_config["algorithm"] == "SRN": + max_text_length = arch_config["Head"]["max_text_length"] + other_shape = [ + paddle.static.InputSpec(shape=[None, 1, 64, 256], dtype="float32"), + [ + paddle.static.InputSpec(shape=[None, 256, 1], dtype="int64"), + paddle.static.InputSpec( + shape=[None, max_text_length, 1], dtype="int64" + ), + paddle.static.InputSpec( + shape=[None, 8, max_text_length, max_text_length], dtype="int64" + ), + paddle.static.InputSpec( + shape=[None, 8, max_text_length, max_text_length], dtype="int64" + ), + ], + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "SAR": + other_shape = [ + paddle.static.InputSpec(shape=[None, 3, 48, 160], dtype="float32"), + [paddle.static.InputSpec(shape=[None], dtype="float32")], + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] in ["SVTR_LCNet", "SVTR_HGNet"]: + other_shape = [ + paddle.static.InputSpec(shape=[None, 3, 48, -1], dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] in ["SVTR", "CPPD"]: + other_shape = [ + paddle.static.InputSpec(shape=[None] + input_shape, dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "PREN": + other_shape = [ + paddle.static.InputSpec(shape=[None, 3, 64, 256], dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["model_type"] == "sr": + other_shape = [ + paddle.static.InputSpec(shape=[None, 3, 16, 64], dtype="float32") + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "ViTSTR": + other_shape = [ + paddle.static.InputSpec(shape=[None, 1, 224, 224], dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "ABINet": + if not input_shape: + input_shape = [3, 32, 128] + other_shape = [ + paddle.static.InputSpec(shape=[None] + input_shape, dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] in ["NRTR", "SPIN", "RFL"]: + other_shape = [ + paddle.static.InputSpec(shape=[None, 1, 32, 100], dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] in ["SATRN"]: + other_shape = [ + paddle.static.InputSpec(shape=[None, 3, 32, 100], dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "VisionLAN": + other_shape = [ + paddle.static.InputSpec(shape=[None, 3, 64, 256], dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "RobustScanner": + max_text_length = arch_config["Head"]["max_text_length"] + other_shape = [ + paddle.static.InputSpec(shape=[None, 3, 48, 160], dtype="float32"), + [ + paddle.static.InputSpec( + shape=[ + None, + ], + dtype="float32", + ), + paddle.static.InputSpec(shape=[None, max_text_length], dtype="int64"), + ], + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "CAN": + other_shape = [ + [ + paddle.static.InputSpec(shape=[None, 1, None, None], dtype="float32"), + paddle.static.InputSpec(shape=[None, 1, None, None], dtype="float32"), + paddle.static.InputSpec( + shape=[None, arch_config["Head"]["max_text_length"]], dtype="int64" + ), + ] + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "LaTeXOCR": + other_shape = [ + paddle.static.InputSpec(shape=[None, 1, None, None], dtype="float32"), + ] + model = to_static(model, input_spec=other_shape) + elif arch_config["algorithm"] == "UniMERNet": + model = paddle.jit.to_static( + model, + input_spec=[ + paddle.static.InputSpec(shape=[-1, 1, 192, 672], dtype="float32") + ], + full_graph=True, + ) + elif arch_config["algorithm"] == "SLANeXt": + model = paddle.jit.to_static( + model, + input_spec=[ + paddle.static.InputSpec(shape=[-1, 3, 512, 512], dtype="float32") + ], + full_graph=True, + ) + elif arch_config["algorithm"] in ["PP-FormulaNet-L", "PP-FormulaNet_plus-L"]: + model = paddle.jit.to_static( + model, + input_spec=[ + paddle.static.InputSpec(shape=[-1, 1, 768, 768], dtype="float32") + ], + full_graph=True, + ) + elif arch_config["algorithm"] in [ + "PP-FormulaNet-S", + "PP-FormulaNet_plus-S", + "PP-FormulaNet_plus-M", + ]: + model = paddle.jit.to_static( + model, + input_spec=[ + paddle.static.InputSpec(shape=[-1, 1, 384, 384], dtype="float32") + ], + full_graph=True, + ) + + elif arch_config["algorithm"] in ["LayoutLM", "LayoutLMv2", "LayoutXLM"]: + input_spec = [ + paddle.static.InputSpec(shape=[None, 512], dtype="int64"), # input_ids + paddle.static.InputSpec(shape=[None, 512, 4], dtype="int64"), # bbox + paddle.static.InputSpec(shape=[None, 512], dtype="int64"), # attention_mask + paddle.static.InputSpec(shape=[None, 512], dtype="int64"), # token_type_ids + paddle.static.InputSpec(shape=[None, 3, 224, 224], dtype="int64"), # image + ] + if "Re" in arch_config["Backbone"]["name"]: + input_spec.extend( + [ + paddle.static.InputSpec( + shape=[None, 512, 3], dtype="int64" + ), # entities + paddle.static.InputSpec( + shape=[None, None, 2], dtype="int64" + ), # relations + ] + ) + if model.backbone.use_visual_backbone is False: + input_spec.pop(4) + model = to_static(model, input_spec=[input_spec]) + else: + infer_shape = [3, -1, -1] + if arch_config["model_type"] == "rec": + infer_shape = [3, 32, -1] # for rec model, H must be 32 + if ( + "Transform" in arch_config + and arch_config["Transform"] is not None + and arch_config["Transform"]["name"] == "TPS" + ): + logger.info( + "When there is tps in the network, variable length input is not supported, and the input size needs to be the same as during training" + ) + infer_shape[-1] = 100 + elif arch_config["model_type"] == "table": + infer_shape = [3, 488, 488] + if arch_config["algorithm"] == "TableMaster": + infer_shape = [3, 480, 480] + if arch_config["algorithm"] == "SLANet": + infer_shape = [3, -1, -1] + model = to_static( + model, + input_spec=[ + paddle.static.InputSpec(shape=[None] + infer_shape, dtype="float32") + ], + ) + + if arch_config["model_type"] != "sr": + for layer in model.sublayers(): + if hasattr(layer, "rep") and not getattr(layer, "is_repped", False): + layer.rep() + return model + + +def export_single_model( + model, + arch_config, + save_path, + logger, + yaml_path, + config, + input_shape=None, + quanter=None, +): + + model = dynamic_to_static(model, arch_config, logger, input_shape) + + if quanter is None: + try: + import encryption # Attempt to import the encryption module for AIStudio's encryption model + except ( + ModuleNotFoundError + ): # Encryption is not needed if the module cannot be imported + print("Skipping import of the encryption module") + paddle_version = version.parse(paddle.__version__) + if config["Global"].get("export_with_pir", True): + assert ( + paddle_version >= version.parse("3.0.0b2") + or paddle_version == version.parse("0.0.0") + ) and os.environ.get("FLAGS_enable_pir_api", None) not in ["0", "False"] + paddle.jit.save(model, save_path) + else: + if paddle_version >= version.parse( + "3.0.0b2" + ) or paddle_version == version.parse("0.0.0"): + model.forward.rollback() + with paddle.pir_utils.OldIrGuard(): + model = dynamic_to_static(model, arch_config, logger, input_shape) + paddle.jit.save(model, save_path) + else: + paddle.jit.save(model, save_path) + else: + quanter.save_quantized_model(model, save_path) + logger.info("inference model is saved to {}".format(save_path)) + return + + +def convert_bn(model): + for n, m in model.named_children(): + if isinstance(m, nn.SyncBatchNorm): + bn = nn.BatchNorm2D( + m._num_features, m._momentum, m._epsilon, m._weight_attr, m._bias_attr + ) + bn.set_dict(m.state_dict()) + setattr(model, n, bn) + else: + convert_bn(m) + + +def export(config, base_model=None, save_path=None): + if paddle.distributed.get_rank() != 0: + return + logger = get_logger() + # build post process + post_process_class = build_post_process(config["PostProcess"], config["Global"]) + + # build model + # for rec algorithm + if hasattr(post_process_class, "character"): + char_num = len(getattr(post_process_class, "character")) + if config["Architecture"]["algorithm"] in [ + "Distillation", + ]: # distillation model + for key in config["Architecture"]["Models"]: + if ( + config["Architecture"]["Models"][key]["Head"]["name"] == "MultiHead" + ): # multi head + out_channels_list = {} + if config["PostProcess"]["name"] == "DistillationSARLabelDecode": + char_num = char_num - 2 + if config["PostProcess"]["name"] == "DistillationNRTRLabelDecode": + char_num = char_num - 3 + out_channels_list["CTCLabelDecode"] = char_num + out_channels_list["SARLabelDecode"] = char_num + 2 + out_channels_list["NRTRLabelDecode"] = char_num + 3 + config["Architecture"]["Models"][key]["Head"][ + "out_channels_list" + ] = out_channels_list + else: + config["Architecture"]["Models"][key]["Head"][ + "out_channels" + ] = char_num + # just one final tensor needs to exported for inference + config["Architecture"]["Models"][key]["return_all_feats"] = False + elif config["Architecture"]["Head"]["name"] == "MultiHead": # multi head + out_channels_list = {} + char_num = len(getattr(post_process_class, "character")) + if config["PostProcess"]["name"] == "SARLabelDecode": + char_num = char_num - 2 + if config["PostProcess"]["name"] == "NRTRLabelDecode": + char_num = char_num - 3 + out_channels_list["CTCLabelDecode"] = char_num + out_channels_list["SARLabelDecode"] = char_num + 2 + out_channels_list["NRTRLabelDecode"] = char_num + 3 + config["Architecture"]["Head"]["out_channels_list"] = out_channels_list + else: # base rec model + config["Architecture"]["Head"]["out_channels"] = char_num + + # for sr algorithm + if config["Architecture"]["model_type"] == "sr": + config["Architecture"]["Transform"]["infer_mode"] = True + + # for latexocr algorithm + if config["Architecture"].get("algorithm") in ["LaTeXOCR"]: + config["Architecture"]["Backbone"]["is_predict"] = True + config["Architecture"]["Backbone"]["is_export"] = True + config["Architecture"]["Head"]["is_export"] = True + if config["Architecture"].get("algorithm") in ["UniMERNet"]: + config["Architecture"]["Backbone"]["is_export"] = True + config["Architecture"]["Head"]["is_export"] = True + if config["Architecture"].get("algorithm") in [ + "PP-FormulaNet-S", + "PP-FormulaNet-L", + "PP-FormulaNet_plus-S", + "PP-FormulaNet_plus-M", + "PP-FormulaNet_plus-L", + ]: + config["Architecture"]["Head"]["is_export"] = True + if base_model is not None: + model = base_model + if isinstance(model, paddle.DataParallel): + model = copy.deepcopy(model._layers) + else: + model = copy.deepcopy(model) + else: + model = build_model(config["Architecture"]) + load_model(config, model, model_type=config["Architecture"]["model_type"]) + convert_bn(model) + model.eval() + + if not save_path: + save_path = config["Global"]["save_inference_dir"] + yaml_path = os.path.join(save_path, "inference.yml") + + arch_config = config["Architecture"] + + if ( + arch_config["algorithm"] in ["SVTR", "CPPD"] + and arch_config["Head"]["name"] != "MultiHead" + ): + input_shape = config["Eval"]["dataset"]["transforms"][-2]["SVTRRecResizeImg"][ + "image_shape" + ] + elif arch_config["algorithm"].lower() == "ABINet".lower(): + rec_rs = [ + c + for c in config["Eval"]["dataset"]["transforms"] + if "ABINetRecResizeImg" in c + ] + input_shape = rec_rs[0]["ABINetRecResizeImg"]["image_shape"] if rec_rs else None + else: + input_shape = None + dump_infer_config(config, yaml_path, logger) + if arch_config["algorithm"] in [ + "Distillation", + ]: # distillation model + archs = list(arch_config["Models"].values()) + for idx, name in enumerate(model.model_name_list): + sub_model_save_path = os.path.join(save_path, name, "inference") + export_single_model( + model.model_list[idx], + archs[idx], + sub_model_save_path, + logger, + yaml_path, + config, + ) + else: + save_path = os.path.join(save_path, "inference") + export_single_model( + model, + arch_config, + save_path, + logger, + yaml_path, + config, + input_shape=input_shape, + ) diff --git a/ppocr/utils/formula_utils/math_txt2pkl.py b/ppocr/utils/formula_utils/math_txt2pkl.py new file mode 100644 index 0000000..beafc13 --- /dev/null +++ b/ppocr/utils/formula_utils/math_txt2pkl.py @@ -0,0 +1,74 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pickle +from tqdm import tqdm +import os +import math +from paddle.utils import try_import +from collections import defaultdict +import glob +from os.path import join +import argparse + + +def txt2pickle(images, equations, save_dir): + imagesize = try_import("imagesize") + save_p = os.path.join(save_dir, "latexocr_{}.pkl".format(images.split("/")[-1])) + min_dimensions = (32, 32) + max_dimensions = (672, 192) + max_length = 512 + data = defaultdict(lambda: []) + if images is not None and equations is not None: + images_list = [ + path.replace("\\", "/") for path in glob.glob(join(images, "*.png")) + ] + indices = [int(os.path.basename(img).split(".")[0]) for img in images_list] + eqs = open(equations, "r").read().split("\n") + for i, im in tqdm(enumerate(images_list), total=len(images_list)): + width, height = imagesize.get(im) + if ( + min_dimensions[0] <= width <= max_dimensions[0] + and min_dimensions[1] <= height <= max_dimensions[1] + ): + divide_h = math.ceil(height / 16) * 16 + divide_w = math.ceil(width / 16) * 16 + im = os.path.basename(im) + data[(divide_w, divide_h)].append((eqs[indices[i]], im)) + data = dict(data) + with open(save_p, "wb") as file: + pickle.dump(data, file) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + parser.add_argument( + "--image_dir", + type=str, + default=".", + help="Input_label or input path to be converted", + ) + parser.add_argument( + "--mathtxt_path", + type=str, + default=".", + help="Input_label or input path to be converted", + ) + parser.add_argument( + "--output_dir", type=str, default="out_label.txt", help="Output file name" + ) + + args = parser.parse_args() + txt2pickle(args.image_dir, args.mathtxt_path, args.output_dir) diff --git a/ppocr/utils/formula_utils/unimernet_data_convert.py b/ppocr/utils/formula_utils/unimernet_data_convert.py new file mode 100644 index 0000000..fed8a7f --- /dev/null +++ b/ppocr/utils/formula_utils/unimernet_data_convert.py @@ -0,0 +1,107 @@ +# copyright (c) 2024 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import cv2 +import glob +import argparse +from os.path import join +from tqdm import tqdm + + +def latexocr2paddleocr_train(image_path, math_unimernet_file, math_hwe_file, save_path): + convert_f = open(save_path, "w") + sub_dir = "UniMER-1M/images" + img_sub_dir = os.path.join(image_path, sub_dir) + with open(math_unimernet_file, "r") as f: + lines = f.readlines() + formula_num = len(lines) + for i, line in tqdm(enumerate(lines), total=formula_num): + image_name = "{0:07d}.png".format(i) + math_gt = line.strip() + image_p = os.path.join(img_sub_dir, image_name) + img_name_subdir = os.path.join(sub_dir, image_name) + if os.path.exists(image_p): + convert_f.writelines("{}\t{}\n".format(img_name_subdir, math_gt)) + + sub_dir = "HME100K/train_images" + img_sub_dir = os.path.join(image_path, sub_dir) + with open(math_hwe_file, "r") as f: + lines = f.readlines() + formula_num = len(lines) + for i, line in tqdm(enumerate(lines), total=formula_num): + img_name, math_gt = line.strip().split("\t") + image_path = os.path.join(img_sub_dir, img_name) + img_name_subdir = os.path.join(sub_dir, img_name) + convert_f.writelines("{}\t{}\n".format(img_name_subdir, math_gt)) + + convert_f.close() + + +def unimernet2paddleocr_test(image_path, math_file, save_path): + convert_f = open(save_path, "w") + with open(math_file, "r") as f: + # load maths which + lines = f.readlines() + formula_num = len(lines) + for i, line in tqdm(enumerate(lines), total=formula_num): + image_name = "{0:07d}.png".format(i) + math_gt = line.strip() + image_p = os.path.join(image_path, image_name) + if os.path.exists(image_p): + convert_f.writelines("{}\t{}\n".format(image_name, math_gt)) + convert_f.close() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + parser.add_argument( + "--image_dir", + type=str, + default=".", + help="Input_label or input path to be converted", + ) + parser.add_argument( + "--unimernet_txt_path", + type=str, + default="", + help="Input_label or input path to be converted", + ) + parser.add_argument( + "--hme100k_txt_path", + type=str, + default="", + help="Input_label or input path to be converted", + ) + parser.add_argument( + "--output_path", type=str, default="out_label.txt", help="Output file name" + ) + parser.add_argument( + "--datatype", type=str, default="out_label.txt", help="datatype" + ) + args = parser.parse_args() + if args.datatype == "unimernet_train": + latexocr2paddleocr_train( + args.image_dir, + args.unimernet_txt_path, + args.hme100k_txt_path, + args.output_path, + ) + elif args.datatype == "unimernet_test": + unimernet2paddleocr_test( + args.image_dir, args.unimernet_txt_path, args.output_path + ) + else: + raise NotImplementedError("the datatype is not supported") diff --git a/ppocr/utils/gen_label.py b/ppocr/utils/gen_label.py new file mode 100644 index 0000000..1634b38 --- /dev/null +++ b/ppocr/utils/gen_label.py @@ -0,0 +1,82 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import argparse +import json + + +def gen_rec_label(input_path, out_label): + with open(out_label, "w") as out_file: + with open(input_path, "r") as f: + for line in f.readlines(): + tmp = line.strip("\n").replace(" ", "").split(",") + img_path, label = tmp[0], tmp[1] + label = label.replace('"', "") + out_file.write(img_path + "\t" + label + "\n") + + +def gen_det_label(root_path, input_dir, out_label): + with open(out_label, "w") as out_file: + for label_file in os.listdir(input_dir): + img_path = os.path.join(root_path, label_file[3:-4] + ".jpg") + label = [] + with open( + os.path.join(input_dir, label_file), "r", encoding="utf-8-sig" + ) as f: + for line in f.readlines(): + tmp = line.strip("\n\r").replace("\xef\xbb\xbf", "").split(",") + points = tmp[:8] + s = [] + for i in range(0, len(points), 2): + b = points[i : i + 2] + b = [int(t) for t in b] + s.append(b) + result = {"transcription": tmp[8], "points": s} + label.append(result) + + out_file.write( + img_path + "\t" + json.dumps(label, ensure_ascii=False) + "\n" + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--mode", + type=str, + default="rec", + help="Generate rec_label or det_label, can be set rec or det", + ) + parser.add_argument( + "--root_path", + type=str, + default=".", + help="The root directory of images.Only takes effect when mode=det ", + ) + parser.add_argument( + "--input_path", + type=str, + default=".", + help="Input_label or input path to be converted", + ) + parser.add_argument( + "--output_label", type=str, default="out_label.txt", help="Output file name" + ) + + args = parser.parse_args() + if args.mode == "rec": + print("Generate rec label") + gen_rec_label(args.input_path, args.output_label) + elif args.mode == "det": + gen_det_label(args.root_path, args.input_path, args.output_label) diff --git a/ppocr/utils/ic15_dict.txt b/ppocr/utils/ic15_dict.txt new file mode 100644 index 0000000..7104368 --- /dev/null +++ b/ppocr/utils/ic15_dict.txt @@ -0,0 +1,36 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +a +b +c +d +e +f +g +h +i +j +k +l +m +n +o +p +q +r +s +t +u +v +w +x +y +z diff --git a/ppocr/utils/iou.py b/ppocr/utils/iou.py new file mode 100644 index 0000000..cb77c34 --- /dev/null +++ b/ppocr/utils/iou.py @@ -0,0 +1,54 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/whai362/PSENet/blob/python3/models/loss/iou.py +""" + +import paddle + +EPS = 1e-6 + + +def iou_single(a, b, mask, n_class): + valid = mask == 1 + a = a.masked_select(valid) + b = b.masked_select(valid) + miou = [] + for i in range(n_class): + if a.shape == [0] and a.shape == b.shape: + inter = paddle.to_tensor(0.0) + union = paddle.to_tensor(0.0) + else: + inter = ((a == i).logical_and(b == i)).astype("float32") + union = ((a == i).logical_or(b == i)).astype("float32") + miou.append(paddle.sum(inter) / (paddle.sum(union) + EPS)) + miou = sum(miou) / len(miou) + return miou + + +def iou(a, b, mask, n_class=2, reduce=True): + batch_size = a.shape[0] + + a = a.reshape([batch_size, -1]) + b = b.reshape([batch_size, -1]) + mask = mask.reshape([batch_size, -1]) + + iou = paddle.zeros((batch_size,), dtype="float32") + for i in range(batch_size): + iou[i] = iou_single(a[i], b[i], mask[i], n_class) + + if reduce: + iou = paddle.mean(iou) + return iou diff --git a/ppocr/utils/loggers/__init__.py b/ppocr/utils/loggers/__init__.py new file mode 100644 index 0000000..a73351f --- /dev/null +++ b/ppocr/utils/loggers/__init__.py @@ -0,0 +1,2 @@ +from .wandb_logger import WandbLogger +from .loggers import Loggers diff --git a/ppocr/utils/loggers/base_logger.py b/ppocr/utils/loggers/base_logger.py new file mode 100644 index 0000000..0aa132d --- /dev/null +++ b/ppocr/utils/loggers/base_logger.py @@ -0,0 +1,16 @@ +import os +from abc import ABC, abstractmethod + + +class BaseLogger(ABC): + def __init__(self, save_dir): + self.save_dir = save_dir + os.makedirs(self.save_dir, exist_ok=True) + + @abstractmethod + def log_metrics(self, metrics, prefix=None): + pass + + @abstractmethod + def close(self): + pass diff --git a/ppocr/utils/loggers/loggers.py b/ppocr/utils/loggers/loggers.py new file mode 100644 index 0000000..a14dbcb --- /dev/null +++ b/ppocr/utils/loggers/loggers.py @@ -0,0 +1,19 @@ +from .wandb_logger import WandbLogger + + +class Loggers(object): + def __init__(self, loggers): + super().__init__() + self.loggers = loggers + + def log_metrics(self, metrics, prefix=None, step=None): + for logger in self.loggers: + logger.log_metrics(metrics, prefix=prefix, step=step) + + def log_model(self, is_best, prefix, metadata=None): + for logger in self.loggers: + logger.log_model(is_best=is_best, prefix=prefix, metadata=metadata) + + def close(self): + for logger in self.loggers: + logger.close() diff --git a/ppocr/utils/loggers/wandb_logger.py b/ppocr/utils/loggers/wandb_logger.py new file mode 100644 index 0000000..44cbfc1 --- /dev/null +++ b/ppocr/utils/loggers/wandb_logger.py @@ -0,0 +1,84 @@ +import os +from .base_logger import BaseLogger +from ppocr.utils.logging import get_logger + + +class WandbLogger(BaseLogger): + def __init__( + self, + project=None, + name=None, + id=None, + entity=None, + save_dir=None, + config=None, + **kwargs, + ): + try: + import wandb + + self.wandb = wandb + except ModuleNotFoundError: + raise ModuleNotFoundError("Please install wandb using `pip install wandb`") + + self.project = project + self.name = name + self.id = id + self.save_dir = save_dir + self.config = config + self.kwargs = kwargs + self.entity = entity + self._run = None + self._wandb_init = dict( + project=self.project, + name=self.name, + id=self.id, + entity=self.entity, + dir=self.save_dir, + resume="allow", + ) + self._wandb_init.update(**kwargs) + self.logger = get_logger() + + _ = self.run + + if self.config: + self.run.config.update(self.config) + + @property + def run(self): + if self._run is None: + if self.wandb.run is not None: + self.logger.info( + "There is a wandb run already in progress " + "and newly created instances of `WandbLogger` will reuse" + " this run. If this is not desired, call `wandb.finish()`" + "before instantiating `WandbLogger`." + ) + self._run = self.wandb.run + else: + self._run = self.wandb.init(**self._wandb_init) + return self._run + + def log_metrics(self, metrics, prefix=None, step=None): + if not prefix: + prefix = "" + updated_metrics = {prefix.lower() + "/" + k: v for k, v in metrics.items()} + + self.run.log(updated_metrics, step=step) + + def log_model(self, is_best, prefix, metadata=None): + model_path = os.path.join(self.save_dir, prefix + ".pdparams") + artifact = self.wandb.Artifact( + "model-{}".format(self.run.id), type="model", metadata=metadata + ) + artifact.add_file(model_path, name="model_ckpt.pdparams") + + aliases = [prefix] + if is_best: + aliases.append("best") + + self.run.log_artifact(artifact, aliases=aliases) + + def close(self): + self.run.finish() diff --git a/ppocr/utils/logging.py b/ppocr/utils/logging.py new file mode 100644 index 0000000..c460936 --- /dev/null +++ b/ppocr/utils/logging.py @@ -0,0 +1,78 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/WenmuZhou/PytorchOCR/blob/master/torchocr/utils/logging.py +""" + +import os +import sys +import logging +import functools +import paddle.distributed as dist + +logger_initialized = {} + + +@functools.lru_cache() +def get_logger(name="ppocr", log_file=None, log_level=logging.DEBUG, log_ranks="0"): + """Initialize and get a logger by name. + If the logger has not been initialized, this method will initialize the + logger by adding one or two handlers, otherwise the initialized logger will + be directly returned. During initialization, a StreamHandler will always be + added. If `log_file` is specified a FileHandler will also be added. + Args: + name (str): Logger name. + log_file (str | None): The log filename. If specified, a FileHandler + will be added to the logger. + log_level (int): The logger level. Note that only the process of + rank 0 is affected, and other processes will set the level to + "Error" thus be silent most of the time. + log_ranks (str): The ids of gpu to log which are separated by "," when more than 1, "0" by default. + Returns: + logging.Logger: The expected logger. + """ + logger = logging.getLogger(name) + if name in logger_initialized: + return logger + for logger_name in logger_initialized: + if name.startswith(logger_name): + return logger + + formatter = logging.Formatter( + "[%(asctime)s] %(name)s %(levelname)s: %(message)s", datefmt="%Y/%m/%d %H:%M:%S" + ) + + stream_handler = logging.StreamHandler(stream=sys.stdout) + stream_handler.setFormatter(formatter) + logger.addHandler(stream_handler) + if log_file is not None and dist.get_rank() == 0: + log_file_folder = os.path.split(log_file)[0] + os.makedirs(log_file_folder, exist_ok=True) + file_handler = logging.FileHandler(log_file, "a") + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + if isinstance(log_ranks, str): + log_ranks = [int(i) for i in log_ranks.split(",")] + elif isinstance(log_ranks, int): + log_ranks = [log_ranks] + + if dist.get_rank() in log_ranks: + logger.setLevel(log_level) + else: + logger.setLevel(logging.ERROR) + logger_initialized[name] = True + logger.propagate = False + return logger diff --git a/ppocr/utils/network.py b/ppocr/utils/network.py new file mode 100644 index 0000000..761338a --- /dev/null +++ b/ppocr/utils/network.py @@ -0,0 +1,155 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import time +import shutil +import tarfile +import requests +import os.path as osp +import paddle.distributed as dist +from tqdm import tqdm + +from ppocr.utils.logging import get_logger + +MODELS_DIR = os.path.join( + os.environ.get("PADDLE_OCR_BASE_DIR", os.path.expanduser("~/.paddleocr/")), "models" +) +DOWNLOAD_RETRY_LIMIT = 3 + + +def download_with_progressbar(url, save_path): + logger = get_logger() + if save_path and os.path.exists(save_path): + logger.info(f"Path {save_path} already exists. Skipping...") + return + else: + # Mainly used to solve the problem of downloading data from different + # machines in the case of multiple machines. Different nodes will download + # data, and the same node will only download data once. + if dist.get_rank() == 0: + _download(url, save_path) + else: + while not os.path.exists(save_path): + time.sleep(1) + + +def _download(url, save_path): + """ + Download from url, save to path. + + url (str): download url + save_path (str): download to given path + """ + logger = get_logger() + + fname = osp.split(url)[-1] + retry_cnt = 0 + + while not osp.exists(save_path): + if retry_cnt < DOWNLOAD_RETRY_LIMIT: + retry_cnt += 1 + else: + raise RuntimeError( + "Download from {} failed. " "Retry limit reached".format(url) + ) + + try: + req = requests.get(url, stream=True) + except Exception as e: # requests.exceptions.ConnectionError + logger.info( + "Downloading {} from {} failed {} times with exception {}".format( + fname, url, retry_cnt + 1, str(e) + ) + ) + time.sleep(1) + continue + + if req.status_code != 200: + raise RuntimeError( + "Downloading from {} failed with code " + "{}!".format(url, req.status_code) + ) + + # For protecting download interrupted, download to + # tmp_file firstly, move tmp_file to save_path + # after download finished + tmp_file = save_path + ".tmp" + total_size = req.headers.get("content-length") + with open(tmp_file, "wb") as f: + if total_size: + with tqdm(total=(int(total_size) + 1023) // 1024) as pbar: + for chunk in req.iter_content(chunk_size=1024): + f.write(chunk) + pbar.update(1) + else: + for chunk in req.iter_content(chunk_size=1024): + if chunk: + f.write(chunk) + shutil.move(tmp_file, save_path) + + return save_path + + +def maybe_download(model_storage_directory, url): + # using custom model + tar_file_name_list = [".pdiparams", ".pdiparams.info", ".pdmodel"] + if not os.path.exists( + os.path.join(model_storage_directory, "inference.pdiparams") + ) or not os.path.exists(os.path.join(model_storage_directory, "inference.pdmodel")): + assert url.endswith(".tar"), "Only supports tar compressed package" + tmp_path = os.path.join(model_storage_directory, url.split("/")[-1]) + print("download {} to {}".format(url, tmp_path)) + os.makedirs(model_storage_directory, exist_ok=True) + download_with_progressbar(url, tmp_path) + with tarfile.open(tmp_path, "r") as tarObj: + for member in tarObj.getmembers(): + filename = None + for tar_file_name in tar_file_name_list: + if member.name.endswith(tar_file_name): + filename = "inference" + tar_file_name + if filename is None: + continue + file = tarObj.extractfile(member) + with open(os.path.join(model_storage_directory, filename), "wb") as f: + f.write(file.read()) + os.remove(tmp_path) + + +def maybe_download_params(model_path): + if os.path.exists(model_path) or not is_link(model_path): + return model_path + else: + url = model_path + tmp_path = os.path.join(MODELS_DIR, url.split("/")[-1]) + print("download {} to {}".format(url, tmp_path)) + os.makedirs(MODELS_DIR, exist_ok=True) + download_with_progressbar(url, tmp_path) + return tmp_path + + +def is_link(s): + return s is not None and s.startswith("http") + + +def confirm_model_dir_url(model_dir, default_model_dir, default_url): + url = default_url + if model_dir is None or is_link(model_dir): + if is_link(model_dir): + url = model_dir + file_name = url.split("/")[-1][:-4] + model_dir = default_model_dir + model_dir = os.path.join(model_dir, file_name) + return model_dir, url diff --git a/ppocr/utils/poly_nms.py b/ppocr/utils/poly_nms.py new file mode 100644 index 0000000..c3a1338 --- /dev/null +++ b/ppocr/utils/poly_nms.py @@ -0,0 +1,146 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from shapely.geometry import Polygon + + +def points2polygon(points): + """Convert k points to 1 polygon. + + Args: + points (ndarray or list): A ndarray or a list of shape (2k) + that indicates k points. + + Returns: + polygon (Polygon): A polygon object. + """ + if isinstance(points, list): + points = np.array(points) + + assert isinstance(points, np.ndarray) + assert (points.size % 2 == 0) and (points.size >= 8) + + point_mat = points.reshape([-1, 2]) + return Polygon(point_mat) + + +def poly_intersection(poly_det, poly_gt, buffer=0.0001): + """Calculate the intersection area between two polygon. + + Args: + poly_det (Polygon): A polygon predicted by detector. + poly_gt (Polygon): A gt polygon. + + Returns: + intersection_area (float): The intersection area between two polygons. + """ + assert isinstance(poly_det, Polygon) + assert isinstance(poly_gt, Polygon) + + if buffer == 0: + poly_inter = poly_det & poly_gt + else: + poly_inter = poly_det.buffer(buffer) & poly_gt.buffer(buffer) + return poly_inter.area, poly_inter + + +def poly_union(poly_det, poly_gt): + """Calculate the union area between two polygon. + + Args: + poly_det (Polygon): A polygon predicted by detector. + poly_gt (Polygon): A gt polygon. + + Returns: + union_area (float): The union area between two polygons. + """ + assert isinstance(poly_det, Polygon) + assert isinstance(poly_gt, Polygon) + + area_det = poly_det.area + area_gt = poly_gt.area + area_inters, _ = poly_intersection(poly_det, poly_gt) + return area_det + area_gt - area_inters + + +def valid_boundary(x, with_score=True): + num = len(x) + if num < 8: + return False + if num % 2 == 0 and (not with_score): + return True + if num % 2 == 1 and with_score: + return True + + return False + + +def boundary_iou(src, target): + """Calculate the IOU between two boundaries. + + Args: + src (list): Source boundary. + target (list): Target boundary. + + Returns: + iou (float): The iou between two boundaries. + """ + assert valid_boundary(src, False) + assert valid_boundary(target, False) + src_poly = points2polygon(src) + target_poly = points2polygon(target) + + return poly_iou(src_poly, target_poly) + + +def poly_iou(poly_det, poly_gt): + """Calculate the IOU between two polygons. + + Args: + poly_det (Polygon): A polygon predicted by detector. + poly_gt (Polygon): A gt polygon. + + Returns: + iou (float): The IOU between two polygons. + """ + assert isinstance(poly_det, Polygon) + assert isinstance(poly_gt, Polygon) + area_inters, _ = poly_intersection(poly_det, poly_gt) + area_union = poly_union(poly_det, poly_gt) + if area_union == 0: + return 0.0 + return area_inters / area_union + + +def poly_nms(polygons, threshold): + assert isinstance(polygons, list) + + polygons = np.array(sorted(polygons, key=lambda x: x[-1])) + + keep_poly = [] + index = [i for i in range(polygons.shape[0])] + + while len(index) > 0: + keep_poly.append(polygons[index[-1]].tolist()) + A = polygons[index[-1]][:-1] + index = np.delete(index, -1) + iou_list = np.zeros((len(index),)) + for i in range(len(index)): + B = polygons[index[i]][:-1] + iou_list[i] = boundary_iou(A, B) + remove_index = np.where(iou_list > threshold) + index = np.delete(index, remove_index) + + return keep_poly diff --git a/ppocr/utils/ppocr_keys_v1.txt b/ppocr/utils/ppocr_keys_v1.txt new file mode 100644 index 0000000..b75af21 --- /dev/null +++ b/ppocr/utils/ppocr_keys_v1.txt @@ -0,0 +1,6623 @@ +' +疗 +绚 +诚 +娇 +溜 +题 +贿 +者 +廖 +更 +纳 +加 +奉 +公 +一 +就 +汴 +计 +与 +路 +房 +原 +妇 +2 +0 +8 +- +7 +其 +> +: +] +, +, +骑 +刈 +全 +消 +昏 +傈 +安 +久 +钟 +嗅 +不 +影 +处 +驽 +蜿 +资 +关 +椤 +地 +瘸 +专 +问 +忖 +票 +嫉 +炎 +韵 +要 +月 +田 +节 +陂 +鄙 +捌 +备 +拳 +伺 +眼 +网 +盎 +大 +傍 +心 +东 +愉 +汇 +蹿 +科 +每 +业 +里 +航 +晏 +字 +平 +录 +先 +1 +3 +彤 +鲶 +产 +稍 +督 +腴 +有 +象 +岳 +注 +绍 +在 +泺 +文 +定 +核 +名 +水 +过 +理 +让 +偷 +率 +等 +这 +发 +” +为 +含 +肥 +酉 +相 +鄱 +七 +编 +猥 +锛 +日 +镀 +蒂 +掰 +倒 +辆 +栾 +栗 +综 +涩 +州 +雌 +滑 +馀 +了 +机 +块 +司 +宰 +甙 +兴 +矽 +抚 +保 +用 +沧 +秩 +如 +收 +息 +滥 +页 +疑 +埠 +! +! +姥 +异 +橹 +钇 +向 +下 +跄 +的 +椴 +沫 +国 +绥 +獠 +报 +开 +民 +蜇 +何 +分 +凇 +长 +讥 +藏 +掏 +施 +羽 +中 +讲 +派 +嘟 +人 +提 +浼 +间 +世 +而 +古 +多 +倪 +唇 +饯 +控 +庚 +首 +赛 +蜓 +味 +断 +制 +觉 +技 +替 +艰 +溢 +潮 +夕 +钺 +外 +摘 +枋 +动 +双 +单 +啮 +户 +枇 +确 +锦 +曜 +杜 +或 +能 +效 +霜 +盒 +然 +侗 +电 +晁 +放 +步 +鹃 +新 +杖 +蜂 +吒 +濂 +瞬 +评 +总 +隍 +对 +独 +合 +也 +是 +府 +青 +天 +诲 +墙 +组 +滴 +级 +邀 +帘 +示 +已 +时 +骸 +仄 +泅 +和 +遨 +店 +雇 +疫 +持 +巍 +踮 +境 +只 +亨 +目 +鉴 +崤 +闲 +体 +泄 +杂 +作 +般 +轰 +化 +解 +迂 +诿 +蛭 +璀 +腾 +告 +版 +服 +省 +师 +小 +规 +程 +线 +海 +办 +引 +二 +桧 +牌 +砺 +洄 +裴 +修 +图 +痫 +胡 +许 +犊 +事 +郛 +基 +柴 +呼 +食 +研 +奶 +律 +蛋 +因 +葆 +察 +戏 +褒 +戒 +再 +李 +骁 +工 +貂 +油 +鹅 +章 +啄 +休 +场 +给 +睡 +纷 +豆 +器 +捎 +说 +敏 +学 +会 +浒 +设 +诊 +格 +廓 +查 +来 +霓 +室 +溆 +¢ +诡 +寥 +焕 +舜 +柒 +狐 +回 +戟 +砾 +厄 +实 +翩 +尿 +五 +入 +径 +惭 +喹 +股 +宇 +篝 +| +; +美 +期 +云 +九 +祺 +扮 +靠 +锝 +槌 +系 +企 +酰 +阊 +暂 +蚕 +忻 +豁 +本 +羹 +执 +条 +钦 +H +獒 +限 +进 +季 +楦 +于 +芘 +玖 +铋 +茯 +未 +答 +粘 +括 +样 +精 +欠 +矢 +甥 +帷 +嵩 +扣 +令 +仔 +风 +皈 +行 +支 +部 +蓉 +刮 +站 +蜡 +救 +钊 +汗 +松 +嫌 +成 +可 +. +鹤 +院 +从 +交 +政 +怕 +活 +调 +球 +局 +验 +髌 +第 +韫 +谗 +串 +到 +圆 +年 +米 +/ +* +友 +忿 +检 +区 +看 +自 +敢 +刃 +个 +兹 +弄 +流 +留 +同 +没 +齿 +星 +聆 +轼 +湖 +什 +三 +建 +蛔 +儿 +椋 +汕 +震 +颧 +鲤 +跟 +力 +情 +璺 +铨 +陪 +务 +指 +族 +训 +滦 +鄣 +濮 +扒 +商 +箱 +十 +召 +慷 +辗 +所 +莞 +管 +护 +臭 +横 +硒 +嗓 +接 +侦 +六 +露 +党 +馋 +驾 +剖 +高 +侬 +妪 +幂 +猗 +绺 +骐 +央 +酐 +孝 +筝 +课 +徇 +缰 +门 +男 +西 +项 +句 +谙 +瞒 +秃 +篇 +教 +碲 +罚 +声 +呐 +景 +前 +富 +嘴 +鳌 +稀 +免 +朋 +啬 +睐 +去 +赈 +鱼 +住 +肩 +愕 +速 +旁 +波 +厅 +健 +茼 +厥 +鲟 +谅 +投 +攸 +炔 +数 +方 +击 +呋 +谈 +绩 +别 +愫 +僚 +躬 +鹧 +胪 +炳 +招 +喇 +膨 +泵 +蹦 +毛 +结 +5 +4 +谱 +识 +陕 +粽 +婚 +拟 +构 +且 +搜 +任 +潘 +比 +郢 +妨 +醪 +陀 +桔 +碘 +扎 +选 +哈 +骷 +楷 +亿 +明 +缆 +脯 +监 +睫 +逻 +婵 +共 +赴 +淝 +凡 +惦 +及 +达 +揖 +谩 +澹 +减 +焰 +蛹 +番 +祁 +柏 +员 +禄 +怡 +峤 +龙 +白 +叽 +生 +闯 +起 +细 +装 +谕 +竟 +聚 +钙 +上 +导 +渊 +按 +艾 +辘 +挡 +耒 +盹 +饪 +臀 +记 +邮 +蕙 +受 +各 +医 +搂 +普 +滇 +朗 +茸 +带 +翻 +酚 +( +光 +堤 +墟 +蔷 +万 +幻 +〓 +瑙 +辈 +昧 +盏 +亘 +蛀 +吉 +铰 +请 +子 +假 +闻 +税 +井 +诩 +哨 +嫂 +好 +面 +琐 +校 +馊 +鬣 +缂 +营 +访 +炖 +占 +农 +缀 +否 +经 +钚 +棵 +趟 +张 +亟 +吏 +茶 +谨 +捻 +论 +迸 +堂 +玉 +信 +吧 +瞠 +乡 +姬 +寺 +咬 +溏 +苄 +皿 +意 +赉 +宝 +尔 +钰 +艺 +特 +唳 +踉 +都 +荣 +倚 +登 +荐 +丧 +奇 +涵 +批 +炭 +近 +符 +傩 +感 +道 +着 +菊 +虹 +仲 +众 +懈 +濯 +颞 +眺 +南 +释 +北 +缝 +标 +既 +茗 +整 +撼 +迤 +贲 +挎 +耱 +拒 +某 +妍 +卫 +哇 +英 +矶 +藩 +治 +他 +元 +领 +膜 +遮 +穗 +蛾 +飞 +荒 +棺 +劫 +么 +市 +火 +温 +拈 +棚 +洼 +转 +果 +奕 +卸 +迪 +伸 +泳 +斗 +邡 +侄 +涨 +屯 +萋 +胭 +氡 +崮 +枞 +惧 +冒 +彩 +斜 +手 +豚 +随 +旭 +淑 +妞 +形 +菌 +吲 +沱 +争 +驯 +歹 +挟 +兆 +柱 +传 +至 +包 +内 +响 +临 +红 +功 +弩 +衡 +寂 +禁 +老 +棍 +耆 +渍 +织 +害 +氵 +渑 +布 +载 +靥 +嗬 +虽 +苹 +咨 +娄 +库 +雉 +榜 +帜 +嘲 +套 +瑚 +亲 +簸 +欧 +边 +6 +腿 +旮 +抛 +吹 +瞳 +得 +镓 +梗 +厨 +继 +漾 +愣 +憨 +士 +策 +窑 +抑 +躯 +襟 +脏 +参 +贸 +言 +干 +绸 +鳄 +穷 +藜 +音 +折 +详 +) +举 +悍 +甸 +癌 +黎 +谴 +死 +罩 +迁 +寒 +驷 +袖 +媒 +蒋 +掘 +模 +纠 +恣 +观 +祖 +蛆 +碍 +位 +稿 +主 +澧 +跌 +筏 +京 +锏 +帝 +贴 +证 +糠 +才 +黄 +鲸 +略 +炯 +饱 +四 +出 +园 +犀 +牧 +容 +汉 +杆 +浈 +汰 +瑷 +造 +虫 +瘩 +怪 +驴 +济 +应 +花 +沣 +谔 +夙 +旅 +价 +矿 +以 +考 +s +u +呦 +晒 +巡 +茅 +准 +肟 +瓴 +詹 +仟 +褂 +译 +桌 +混 +宁 +怦 +郑 +抿 +些 +余 +鄂 +饴 +攒 +珑 +群 +阖 +岔 +琨 +藓 +预 +环 +洮 +岌 +宀 +杲 +瀵 +最 +常 +囡 +周 +踊 +女 +鼓 +袭 +喉 +简 +范 +薯 +遐 +疏 +粱 +黜 +禧 +法 +箔 +斤 +遥 +汝 +奥 +直 +贞 +撑 +置 +绱 +集 +她 +馅 +逗 +钧 +橱 +魉 +[ +恙 +躁 +唤 +9 +旺 +膘 +待 +脾 +惫 +购 +吗 +依 +盲 +度 +瘿 +蠖 +俾 +之 +镗 +拇 +鲵 +厝 +簧 +续 +款 +展 +啃 +表 +剔 +品 +钻 +腭 +损 +清 +锶 +统 +涌 +寸 +滨 +贪 +链 +吠 +冈 +伎 +迥 +咏 +吁 +览 +防 +迅 +失 +汾 +阔 +逵 +绀 +蔑 +列 +川 +凭 +努 +熨 +揪 +利 +俱 +绉 +抢 +鸨 +我 +即 +责 +膦 +易 +毓 +鹊 +刹 +玷 +岿 +空 +嘞 +绊 +排 +术 +估 +锷 +违 +们 +苟 +铜 +播 +肘 +件 +烫 +审 +鲂 +广 +像 +铌 +惰 +铟 +巳 +胍 +鲍 +康 +憧 +色 +恢 +想 +拷 +尤 +疳 +知 +S +Y +F +D +A +峄 +裕 +帮 +握 +搔 +氐 +氘 +难 +墒 +沮 +雨 +叁 +缥 +悴 +藐 +湫 +娟 +苑 +稠 +颛 +簇 +后 +阕 +闭 +蕤 +缚 +怎 +佞 +码 +嘤 +蔡 +痊 +舱 +螯 +帕 +赫 +昵 +升 +烬 +岫 +、 +疵 +蜻 +髁 +蕨 +隶 +烛 +械 +丑 +盂 +梁 +强 +鲛 +由 +拘 +揉 +劭 +龟 +撤 +钩 +呕 +孛 +费 +妻 +漂 +求 +阑 +崖 +秤 +甘 +通 +深 +补 +赃 +坎 +床 +啪 +承 +吼 +量 +暇 +钼 +烨 +阂 +擎 +脱 +逮 +称 +P +神 +属 +矗 +华 +届 +狍 +葑 +汹 +育 +患 +窒 +蛰 +佼 +静 +槎 +运 +鳗 +庆 +逝 +曼 +疱 +克 +代 +官 +此 +麸 +耧 +蚌 +晟 +例 +础 +榛 +副 +测 +唰 +缢 +迹 +灬 +霁 +身 +岁 +赭 +扛 +又 +菡 +乜 +雾 +板 +读 +陷 +徉 +贯 +郁 +虑 +变 +钓 +菜 +圾 +现 +琢 +式 +乐 +维 +渔 +浜 +左 +吾 +脑 +钡 +警 +T +啵 +拴 +偌 +漱 +湿 +硕 +止 +骼 +魄 +积 +燥 +联 +踢 +玛 +则 +窿 +见 +振 +畿 +送 +班 +钽 +您 +赵 +刨 +印 +讨 +踝 +籍 +谡 +舌 +崧 +汽 +蔽 +沪 +酥 +绒 +怖 +财 +帖 +肱 +私 +莎 +勋 +羔 +霸 +励 +哼 +帐 +将 +帅 +渠 +纪 +婴 +娩 +岭 +厘 +滕 +吻 +伤 +坝 +冠 +戊 +隆 +瘁 +介 +涧 +物 +黍 +并 +姗 +奢 +蹑 +掣 +垸 +锴 +命 +箍 +捉 +病 +辖 +琰 +眭 +迩 +艘 +绌 +繁 +寅 +若 +毋 +思 +诉 +类 +诈 +燮 +轲 +酮 +狂 +重 +反 +职 +筱 +县 +委 +磕 +绣 +奖 +晋 +濉 +志 +徽 +肠 +呈 +獐 +坻 +口 +片 +碰 +几 +村 +柿 +劳 +料 +获 +亩 +惕 +晕 +厌 +号 +罢 +池 +正 +鏖 +煨 +家 +棕 +复 +尝 +懋 +蜥 +锅 +岛 +扰 +队 +坠 +瘾 +钬 +@ +卧 +疣 +镇 +譬 +冰 +彷 +频 +黯 +据 +垄 +采 +八 +缪 +瘫 +型 +熹 +砰 +楠 +襁 +箐 +但 +嘶 +绳 +啤 +拍 +盥 +穆 +傲 +洗 +盯 +塘 +怔 +筛 +丿 +台 +恒 +喂 +葛 +永 +¥ +烟 +酒 +桦 +书 +砂 +蚝 +缉 +态 +瀚 +袄 +圳 +轻 +蛛 +超 +榧 +遛 +姒 +奘 +铮 +右 +荽 +望 +偻 +卡 +丶 +氰 +附 +做 +革 +索 +戚 +坨 +桷 +唁 +垅 +榻 +岐 +偎 +坛 +莨 +山 +殊 +微 +骇 +陈 +爨 +推 +嗝 +驹 +澡 +藁 +呤 +卤 +嘻 +糅 +逛 +侵 +郓 +酌 +德 +摇 +※ +鬃 +被 +慨 +殡 +羸 +昌 +泡 +戛 +鞋 +河 +宪 +沿 +玲 +鲨 +翅 +哽 +源 +铅 +语 +照 +邯 +址 +荃 +佬 +顺 +鸳 +町 +霭 +睾 +瓢 +夸 +椁 +晓 +酿 +痈 +咔 +侏 +券 +噎 +湍 +签 +嚷 +离 +午 +尚 +社 +锤 +背 +孟 +使 +浪 +缦 +潍 +鞅 +军 +姹 +驶 +笑 +鳟 +鲁 +》 +孽 +钜 +绿 +洱 +礴 +焯 +椰 +颖 +囔 +乌 +孔 +巴 +互 +性 +椽 +哞 +聘 +昨 +早 +暮 +胶 +炀 +隧 +低 +彗 +昝 +铁 +呓 +氽 +藉 +喔 +癖 +瑗 +姨 +权 +胱 +韦 +堑 +蜜 +酋 +楝 +砝 +毁 +靓 +歙 +锲 +究 +屋 +喳 +骨 +辨 +碑 +武 +鸠 +宫 +辜 +烊 +适 +坡 +殃 +培 +佩 +供 +走 +蜈 +迟 +翼 +况 +姣 +凛 +浔 +吃 +飘 +债 +犟 +金 +促 +苛 +崇 +坂 +莳 +畔 +绂 +兵 +蠕 +斋 +根 +砍 +亢 +欢 +恬 +崔 +剁 +餐 +榫 +快 +扶 +‖ +濒 +缠 +鳜 +当 +彭 +驭 +浦 +篮 +昀 +锆 +秸 +钳 +弋 +娣 +瞑 +夷 +龛 +苫 +拱 +致 +% +嵊 +障 +隐 +弑 +初 +娓 +抉 +汩 +累 +蓖 +" +唬 +助 +苓 +昙 +押 +毙 +破 +城 +郧 +逢 +嚏 +獭 +瞻 +溱 +婿 +赊 +跨 +恼 +璧 +萃 +姻 +貉 +灵 +炉 +密 +氛 +陶 +砸 +谬 +衔 +点 +琛 +沛 +枳 +层 +岱 +诺 +脍 +榈 +埂 +征 +冷 +裁 +打 +蹴 +素 +瘘 +逞 +蛐 +聊 +激 +腱 +萘 +踵 +飒 +蓟 +吆 +取 +咙 +簋 +涓 +矩 +曝 +挺 +揣 +座 +你 +史 +舵 +焱 +尘 +苏 +笈 +脚 +溉 +榨 +诵 +樊 +邓 +焊 +义 +庶 +儋 +蟋 +蒲 +赦 +呷 +杞 +诠 +豪 +还 +试 +颓 +茉 +太 +除 +紫 +逃 +痴 +草 +充 +鳕 +珉 +祗 +墨 +渭 +烩 +蘸 +慕 +璇 +镶 +穴 +嵘 +恶 +骂 +险 +绋 +幕 +碉 +肺 +戳 +刘 +潞 +秣 +纾 +潜 +銮 +洛 +须 +罘 +销 +瘪 +汞 +兮 +屉 +r +林 +厕 +质 +探 +划 +狸 +殚 +善 +煊 +烹 +〒 +锈 +逯 +宸 +辍 +泱 +柚 +袍 +远 +蹋 +嶙 +绝 +峥 +娥 +缍 +雀 +徵 +认 +镱 +谷 += +贩 +勉 +撩 +鄯 +斐 +洋 +非 +祚 +泾 +诒 +饿 +撬 +威 +晷 +搭 +芍 +锥 +笺 +蓦 +候 +琊 +档 +礁 +沼 +卵 +荠 +忑 +朝 +凹 +瑞 +头 +仪 +弧 +孵 +畏 +铆 +突 +衲 +车 +浩 +气 +茂 +悖 +厢 +枕 +酝 +戴 +湾 +邹 +飚 +攘 +锂 +写 +宵 +翁 +岷 +无 +喜 +丈 +挑 +嗟 +绛 +殉 +议 +槽 +具 +醇 +淞 +笃 +郴 +阅 +饼 +底 +壕 +砚 +弈 +询 +缕 +庹 +翟 +零 +筷 +暨 +舟 +闺 +甯 +撞 +麂 +茌 +蔼 +很 +珲 +捕 +棠 +角 +阉 +媛 +娲 +诽 +剿 +尉 +爵 +睬 +韩 +诰 +匣 +危 +糍 +镯 +立 +浏 +阳 +少 +盆 +舔 +擘 +匪 +申 +尬 +铣 +旯 +抖 +赘 +瓯 +居 +ˇ +哮 +游 +锭 +茏 +歌 +坏 +甚 +秒 +舞 +沙 +仗 +劲 +潺 +阿 +燧 +郭 +嗖 +霏 +忠 +材 +奂 +耐 +跺 +砀 +输 +岖 +媳 +氟 +极 +摆 +灿 +今 +扔 +腻 +枝 +奎 +药 +熄 +吨 +话 +q +额 +慑 +嘌 +协 +喀 +壳 +埭 +视 +著 +於 +愧 +陲 +翌 +峁 +颅 +佛 +腹 +聋 +侯 +咎 +叟 +秀 +颇 +存 +较 +罪 +哄 +岗 +扫 +栏 +钾 +羌 +己 +璨 +枭 +霉 +煌 +涸 +衿 +键 +镝 +益 +岢 +奏 +连 +夯 +睿 +冥 +均 +糖 +狞 +蹊 +稻 +爸 +刿 +胥 +煜 +丽 +肿 +璃 +掸 +跚 +灾 +垂 +樾 +濑 +乎 +莲 +窄 +犹 +撮 +战 +馄 +软 +络 +显 +鸢 +胸 +宾 +妲 +恕 +埔 +蝌 +份 +遇 +巧 +瞟 +粒 +恰 +剥 +桡 +博 +讯 +凯 +堇 +阶 +滤 +卖 +斌 +骚 +彬 +兑 +磺 +樱 +舷 +两 +娱 +福 +仃 +差 +找 +桁 +÷ +净 +把 +阴 +污 +戬 +雷 +碓 +蕲 +楚 +罡 +焖 +抽 +妫 +咒 +仑 +闱 +尽 +邑 +菁 +爱 +贷 +沥 +鞑 +牡 +嗉 +崴 +骤 +塌 +嗦 +订 +拮 +滓 +捡 +锻 +次 +坪 +杩 +臃 +箬 +融 +珂 +鹗 +宗 +枚 +降 +鸬 +妯 +阄 +堰 +盐 +毅 +必 +杨 +崃 +俺 +甬 +状 +莘 +货 +耸 +菱 +腼 +铸 +唏 +痤 +孚 +澳 +懒 +溅 +翘 +疙 +杷 +淼 +缙 +骰 +喊 +悉 +砻 +坷 +艇 +赁 +界 +谤 +纣 +宴 +晃 +茹 +归 +饭 +梢 +铡 +街 +抄 +肼 +鬟 +苯 +颂 +撷 +戈 +炒 +咆 +茭 +瘙 +负 +仰 +客 +琉 +铢 +封 +卑 +珥 +椿 +镧 +窨 +鬲 +寿 +御 +袤 +铃 +萎 +砖 +餮 +脒 +裳 +肪 +孕 +嫣 +馗 +嵇 +恳 +氯 +江 +石 +褶 +冢 +祸 +阻 +狈 +羞 +银 +靳 +透 +咳 +叼 +敷 +芷 +啥 +它 +瓤 +兰 +痘 +懊 +逑 +肌 +往 +捺 +坊 +甩 +呻 +〃 +沦 +忘 +膻 +祟 +菅 +剧 +崆 +智 +坯 +臧 +霍 +墅 +攻 +眯 +倘 +拢 +骠 +铐 +庭 +岙 +瓠 +′ +缺 +泥 +迢 +捶 +? +? +郏 +喙 +掷 +沌 +纯 +秘 +种 +听 +绘 +固 +螨 +团 +香 +盗 +妒 +埚 +蓝 +拖 +旱 +荞 +铀 +血 +遏 +汲 +辰 +叩 +拽 +幅 +硬 +惶 +桀 +漠 +措 +泼 +唑 +齐 +肾 +念 +酱 +虚 +屁 +耶 +旗 +砦 +闵 +婉 +馆 +拭 +绅 +韧 +忏 +窝 +醋 +葺 +顾 +辞 +倜 +堆 +辋 +逆 +玟 +贱 +疾 +董 +惘 +倌 +锕 +淘 +嘀 +莽 +俭 +笏 +绑 +鲷 +杈 +择 +蟀 +粥 +嗯 +驰 +逾 +案 +谪 +褓 +胫 +哩 +昕 +颚 +鲢 +绠 +躺 +鹄 +崂 +儒 +俨 +丝 +尕 +泌 +啊 +萸 +彰 +幺 +吟 +骄 +苣 +弦 +脊 +瑰 +〈 +诛 +镁 +析 +闪 +剪 +侧 +哟 +框 +螃 +守 +嬗 +燕 +狭 +铈 +缮 +概 +迳 +痧 +鲲 +俯 +售 +笼 +痣 +扉 +挖 +满 +咋 +援 +邱 +扇 +歪 +便 +玑 +绦 +峡 +蛇 +叨 +〖 +泽 +胃 +斓 +喋 +怂 +坟 +猪 +该 +蚬 +炕 +弥 +赞 +棣 +晔 +娠 +挲 +狡 +创 +疖 +铕 +镭 +稷 +挫 +弭 +啾 +翔 +粉 +履 +苘 +哦 +楼 +秕 +铂 +土 +锣 +瘟 +挣 +栉 +习 +享 +桢 +袅 +磨 +桂 +谦 +延 +坚 +蔚 +噗 +署 +谟 +猬 +钎 +恐 +嬉 +雒 +倦 +衅 +亏 +璩 +睹 +刻 +殿 +王 +算 +雕 +麻 +丘 +柯 +骆 +丸 +塍 +谚 +添 +鲈 +垓 +桎 +蚯 +芥 +予 +飕 +镦 +谌 +窗 +醚 +菀 +亮 +搪 +莺 +蒿 +羁 +足 +J +真 +轶 +悬 +衷 +靛 +翊 +掩 +哒 +炅 +掐 +冼 +妮 +l +谐 +稚 +荆 +擒 +犯 +陵 +虏 +浓 +崽 +刍 +陌 +傻 +孜 +千 +靖 +演 +矜 +钕 +煽 +杰 +酗 +渗 +伞 +栋 +俗 +泫 +戍 +罕 +沾 +疽 +灏 +煦 +芬 +磴 +叱 +阱 +榉 +湃 +蜀 +叉 +醒 +彪 +租 +郡 +篷 +屎 +良 +垢 +隗 +弱 +陨 +峪 +砷 +掴 +颁 +胎 +雯 +绵 +贬 +沐 +撵 +隘 +篙 +暖 +曹 +陡 +栓 +填 +臼 +彦 +瓶 +琪 +潼 +哪 +鸡 +摩 +啦 +俟 +锋 +域 +耻 +蔫 +疯 +纹 +撇 +毒 +绶 +痛 +酯 +忍 +爪 +赳 +歆 +嘹 +辕 +烈 +册 +朴 +钱 +吮 +毯 +癜 +娃 +谀 +邵 +厮 +炽 +璞 +邃 +丐 +追 +词 +瓒 +忆 +轧 +芫 +谯 +喷 +弟 +半 +冕 +裙 +掖 +墉 +绮 +寝 +苔 +势 +顷 +褥 +切 +衮 +君 +佳 +嫒 +蚩 +霞 +佚 +洙 +逊 +镖 +暹 +唛 +& +殒 +顶 +碗 +獗 +轭 +铺 +蛊 +废 +恹 +汨 +崩 +珍 +那 +杵 +曲 +纺 +夏 +薰 +傀 +闳 +淬 +姘 +舀 +拧 +卷 +楂 +恍 +讪 +厩 +寮 +篪 +赓 +乘 +灭 +盅 +鞣 +沟 +慎 +挂 +饺 +鼾 +杳 +树 +缨 +丛 +絮 +娌 +臻 +嗳 +篡 +侩 +述 +衰 +矛 +圈 +蚜 +匕 +筹 +匿 +濞 +晨 +叶 +骋 +郝 +挚 +蚴 +滞 +增 +侍 +描 +瓣 +吖 +嫦 +蟒 +匾 +圣 +赌 +毡 +癞 +恺 +百 +曳 +需 +篓 +肮 +庖 +帏 +卿 +驿 +遗 +蹬 +鬓 +骡 +歉 +芎 +胳 +屐 +禽 +烦 +晌 +寄 +媾 +狄 +翡 +苒 +船 +廉 +终 +痞 +殇 +々 +畦 +饶 +改 +拆 +悻 +萄 +£ +瓿 +乃 +訾 +桅 +匮 +溧 +拥 +纱 +铍 +骗 +蕃 +龋 +缬 +父 +佐 +疚 +栎 +醍 +掳 +蓄 +x +惆 +颜 +鲆 +榆 +〔 +猎 +敌 +暴 +谥 +鲫 +贾 +罗 +玻 +缄 +扦 +芪 +癣 +落 +徒 +臾 +恿 +猩 +托 +邴 +肄 +牵 +春 +陛 +耀 +刊 +拓 +蓓 +邳 +堕 +寇 +枉 +淌 +啡 +湄 +兽 +酷 +萼 +碚 +濠 +萤 +夹 +旬 +戮 +梭 +琥 +椭 +昔 +勺 +蜊 +绐 +晚 +孺 +僵 +宣 +摄 +冽 +旨 +萌 +忙 +蚤 +眉 +噼 +蟑 +付 +契 +瓜 +悼 +颡 +壁 +曾 +窕 +颢 +澎 +仿 +俑 +浑 +嵌 +浣 +乍 +碌 +褪 +乱 +蔟 +隙 +玩 +剐 +葫 +箫 +纲 +围 +伐 +决 +伙 +漩 +瑟 +刑 +肓 +镳 +缓 +蹭 +氨 +皓 +典 +畲 +坍 +铑 +檐 +塑 +洞 +倬 +储 +胴 +淳 +戾 +吐 +灼 +惺 +妙 +毕 +珐 +缈 +虱 +盖 +羰 +鸿 +磅 +谓 +髅 +娴 +苴 +唷 +蚣 +霹 +抨 +贤 +唠 +犬 +誓 +逍 +庠 +逼 +麓 +籼 +釉 +呜 +碧 +秧 +氩 +摔 +霄 +穸 +纨 +辟 +妈 +映 +完 +牛 +缴 +嗷 +炊 +恩 +荔 +茆 +掉 +紊 +慌 +莓 +羟 +阙 +萁 +磐 +另 +蕹 +辱 +鳐 +湮 +吡 +吩 +唐 +睦 +垠 +舒 +圜 +冗 +瞿 +溺 +芾 +囱 +匠 +僳 +汐 +菩 +饬 +漓 +黑 +霰 +浸 +濡 +窥 +毂 +蒡 +兢 +驻 +鹉 +芮 +诙 +迫 +雳 +厂 +忐 +臆 +猴 +鸣 +蚪 +栈 +箕 +羡 +渐 +莆 +捍 +眈 +哓 +趴 +蹼 +埕 +嚣 +骛 +宏 +淄 +斑 +噜 +严 +瑛 +垃 +椎 +诱 +压 +庾 +绞 +焘 +廿 +抡 +迄 +棘 +夫 +纬 +锹 +眨 +瞌 +侠 +脐 +竞 +瀑 +孳 +骧 +遁 +姜 +颦 +荪 +滚 +萦 +伪 +逸 +粳 +爬 +锁 +矣 +役 +趣 +洒 +颔 +诏 +逐 +奸 +甭 +惠 +攀 +蹄 +泛 +尼 +拼 +阮 +鹰 +亚 +颈 +惑 +勒 +〉 +际 +肛 +爷 +刚 +钨 +丰 +养 +冶 +鲽 +辉 +蔻 +画 +覆 +皴 +妊 +麦 +返 +醉 +皂 +擀 +〗 +酶 +凑 +粹 +悟 +诀 +硖 +港 +卜 +z +杀 +涕 +± +舍 +铠 +抵 +弛 +段 +敝 +镐 +奠 +拂 +轴 +跛 +袱 +e +t +沉 +菇 +俎 +薪 +峦 +秭 +蟹 +历 +盟 +菠 +寡 +液 +肢 +喻 +染 +裱 +悱 +抱 +氙 +赤 +捅 +猛 +跑 +氮 +谣 +仁 +尺 +辊 +窍 +烙 +衍 +架 +擦 +倏 +璐 +瑁 +币 +楞 +胖 +夔 +趸 +邛 +惴 +饕 +虔 +蝎 +§ +哉 +贝 +宽 +辫 +炮 +扩 +饲 +籽 +魏 +菟 +锰 +伍 +猝 +末 +琳 +哚 +蛎 +邂 +呀 +姿 +鄞 +却 +歧 +仙 +恸 +椐 +森 +牒 +寤 +袒 +婆 +虢 +雅 +钉 +朵 +贼 +欲 +苞 +寰 +故 +龚 +坭 +嘘 +咫 +礼 +硷 +兀 +睢 +汶 +’ +铲 +烧 +绕 +诃 +浃 +钿 +哺 +柜 +讼 +颊 +璁 +腔 +洽 +咐 +脲 +簌 +筠 +镣 +玮 +鞠 +谁 +兼 +姆 +挥 +梯 +蝴 +谘 +漕 +刷 +躏 +宦 +弼 +b +垌 +劈 +麟 +莉 +揭 +笙 +渎 +仕 +嗤 +仓 +配 +怏 +抬 +错 +泯 +镊 +孰 +猿 +邪 +仍 +秋 +鼬 +壹 +歇 +吵 +炼 +< +尧 +射 +柬 +廷 +胧 +霾 +凳 +隋 +肚 +浮 +梦 +祥 +株 +堵 +退 +L +鹫 +跎 +凶 +毽 +荟 +炫 +栩 +玳 +甜 +沂 +鹿 +顽 +伯 +爹 +赔 +蛴 +徐 +匡 +欣 +狰 +缸 +雹 +蟆 +疤 +默 +沤 +啜 +痂 +衣 +禅 +w +i +h +辽 +葳 +黝 +钗 +停 +沽 +棒 +馨 +颌 +肉 +吴 +硫 +悯 +劾 +娈 +马 +啧 +吊 +悌 +镑 +峭 +帆 +瀣 +涉 +咸 +疸 +滋 +泣 +翦 +拙 +癸 +钥 +蜒 ++ +尾 +庄 +凝 +泉 +婢 +渴 +谊 +乞 +陆 +锉 +糊 +鸦 +淮 +I +B +N +晦 +弗 +乔 +庥 +葡 +尻 +席 +橡 +傣 +渣 +拿 +惩 +麋 +斛 +缃 +矮 +蛏 +岘 +鸽 +姐 +膏 +催 +奔 +镒 +喱 +蠡 +摧 +钯 +胤 +柠 +拐 +璋 +鸥 +卢 +荡 +倾 +^ +_ +珀 +逄 +萧 +塾 +掇 +贮 +笆 +聂 +圃 +冲 +嵬 +M +滔 +笕 +值 +炙 +偶 +蜱 +搐 +梆 +汪 +蔬 +腑 +鸯 +蹇 +敞 +绯 +仨 +祯 +谆 +梧 +糗 +鑫 +啸 +豺 +囹 +猾 +巢 +柄 +瀛 +筑 +踌 +沭 +暗 +苁 +鱿 +蹉 +脂 +蘖 +牢 +热 +木 +吸 +溃 +宠 +序 +泞 +偿 +拜 +檩 +厚 +朐 +毗 +螳 +吞 +媚 +朽 +担 +蝗 +橘 +畴 +祈 +糟 +盱 +隼 +郜 +惜 +珠 +裨 +铵 +焙 +琚 +唯 +咚 +噪 +骊 +丫 +滢 +勤 +棉 +呸 +咣 +淀 +隔 +蕾 +窈 +饨 +挨 +煅 +短 +匙 +粕 +镜 +赣 +撕 +墩 +酬 +馁 +豌 +颐 +抗 +酣 +氓 +佑 +搁 +哭 +递 +耷 +涡 +桃 +贻 +碣 +截 +瘦 +昭 +镌 +蔓 +氚 +甲 +猕 +蕴 +蓬 +散 +拾 +纛 +狼 +猷 +铎 +埋 +旖 +矾 +讳 +囊 +糜 +迈 +粟 +蚂 +紧 +鲳 +瘢 +栽 +稼 +羊 +锄 +斟 +睁 +桥 +瓮 +蹙 +祉 +醺 +鼻 +昱 +剃 +跳 +篱 +跷 +蒜 +翎 +宅 +晖 +嗑 +壑 +峻 +癫 +屏 +狠 +陋 +袜 +途 +憎 +祀 +莹 +滟 +佶 +溥 +臣 +约 +盛 +峰 +磁 +慵 +婪 +拦 +莅 +朕 +鹦 +粲 +裤 +哎 +疡 +嫖 +琵 +窟 +堪 +谛 +嘉 +儡 +鳝 +斩 +郾 +驸 +酊 +妄 +胜 +贺 +徙 +傅 +噌 +钢 +栅 +庇 +恋 +匝 +巯 +邈 +尸 +锚 +粗 +佟 +蛟 +薹 +纵 +蚊 +郅 +绢 +锐 +苗 +俞 +篆 +淆 +膀 +鲜 +煎 +诶 +秽 +寻 +涮 +刺 +怀 +噶 +巨 +褰 +魅 +灶 +灌 +桉 +藕 +谜 +舸 +薄 +搀 +恽 +借 +牯 +痉 +渥 +愿 +亓 +耘 +杠 +柩 +锔 +蚶 +钣 +珈 +喘 +蹒 +幽 +赐 +稗 +晤 +莱 +泔 +扯 +肯 +菪 +裆 +腩 +豉 +疆 +骜 +腐 +倭 +珏 +唔 +粮 +亡 +润 +慰 +伽 +橄 +玄 +誉 +醐 +胆 +龊 +粼 +塬 +陇 +彼 +削 +嗣 +绾 +芽 +妗 +垭 +瘴 +爽 +薏 +寨 +龈 +泠 +弹 +赢 +漪 +猫 +嘧 +涂 +恤 +圭 +茧 +烽 +屑 +痕 +巾 +赖 +荸 +凰 +腮 +畈 +亵 +蹲 +偃 +苇 +澜 +艮 +换 +骺 +烘 +苕 +梓 +颉 +肇 +哗 +悄 +氤 +涠 +葬 +屠 +鹭 +植 +竺 +佯 +诣 +鲇 +瘀 +鲅 +邦 +移 +滁 +冯 +耕 +癔 +戌 +茬 +沁 +巩 +悠 +湘 +洪 +痹 +锟 +循 +谋 +腕 +鳃 +钠 +捞 +焉 +迎 +碱 +伫 +急 +榷 +奈 +邝 +卯 +辄 +皲 +卟 +醛 +畹 +忧 +稳 +雄 +昼 +缩 +阈 +睑 +扌 +耗 +曦 +涅 +捏 +瞧 +邕 +淖 +漉 +铝 +耦 +禹 +湛 +喽 +莼 +琅 +诸 +苎 +纂 +硅 +始 +嗨 +傥 +燃 +臂 +赅 +嘈 +呆 +贵 +屹 +壮 +肋 +亍 +蚀 +卅 +豹 +腆 +邬 +迭 +浊 +} +童 +螂 +捐 +圩 +勐 +触 +寞 +汊 +壤 +荫 +膺 +渌 +芳 +懿 +遴 +螈 +泰 +蓼 +蛤 +茜 +舅 +枫 +朔 +膝 +眙 +避 +梅 +判 +鹜 +璜 +牍 +缅 +垫 +藻 +黔 +侥 +惚 +懂 +踩 +腰 +腈 +札 +丞 +唾 +慈 +顿 +摹 +荻 +琬 +~ +斧 +沈 +滂 +胁 +胀 +幄 +莜 +Z +匀 +鄄 +掌 +绰 +茎 +焚 +赋 +萱 +谑 +汁 +铒 +瞎 +夺 +蜗 +野 +娆 +冀 +弯 +篁 +懵 +灞 +隽 +芡 +脘 +俐 +辩 +芯 +掺 +喏 +膈 +蝈 +觐 +悚 +踹 +蔗 +熠 +鼠 +呵 +抓 +橼 +峨 +畜 +缔 +禾 +崭 +弃 +熊 +摒 +凸 +拗 +穹 +蒙 +抒 +祛 +劝 +闫 +扳 +阵 +醌 +踪 +喵 +侣 +搬 +仅 +荧 +赎 +蝾 +琦 +买 +婧 +瞄 +寓 +皎 +冻 +赝 +箩 +莫 +瞰 +郊 +笫 +姝 +筒 +枪 +遣 +煸 +袋 +舆 +痱 +涛 +母 +〇 +启 +践 +耙 +绲 +盘 +遂 +昊 +搞 +槿 +诬 +纰 +泓 +惨 +檬 +亻 +越 +C +o +憩 +熵 +祷 +钒 +暧 +塔 +阗 +胰 +咄 +娶 +魔 +琶 +钞 +邻 +扬 +杉 +殴 +咽 +弓 +〆 +髻 +】 +吭 +揽 +霆 +拄 +殖 +脆 +彻 +岩 +芝 +勃 +辣 +剌 +钝 +嘎 +甄 +佘 +皖 +伦 +授 +徕 +憔 +挪 +皇 +庞 +稔 +芜 +踏 +溴 +兖 +卒 +擢 +饥 +鳞 +煲 +‰ +账 +颗 +叻 +斯 +捧 +鳍 +琮 +讹 +蛙 +纽 +谭 +酸 +兔 +莒 +睇 +伟 +觑 +羲 +嗜 +宜 +褐 +旎 +辛 +卦 +诘 +筋 +鎏 +溪 +挛 +熔 +阜 +晰 +鳅 +丢 +奚 +灸 +呱 +献 +陉 +黛 +鸪 +甾 +萨 +疮 +拯 +洲 +疹 +辑 +叙 +恻 +谒 +允 +柔 +烂 +氏 +逅 +漆 +拎 +惋 +扈 +湟 +纭 +啕 +掬 +擞 +哥 +忽 +涤 +鸵 +靡 +郗 +瓷 +扁 +廊 +怨 +雏 +钮 +敦 +E +懦 +憋 +汀 +拚 +啉 +腌 +岸 +f +痼 +瞅 +尊 +咀 +眩 +飙 +忌 +仝 +迦 +熬 +毫 +胯 +篑 +茄 +腺 +凄 +舛 +碴 +锵 +诧 +羯 +後 +漏 +汤 +宓 +仞 +蚁 +壶 +谰 +皑 +铄 +棰 +罔 +辅 +晶 +苦 +牟 +闽 +\ +烃 +饮 +聿 +丙 +蛳 +朱 +煤 +涔 +鳖 +犁 +罐 +荼 +砒 +淦 +妤 +黏 +戎 +孑 +婕 +瑾 +戢 +钵 +枣 +捋 +砥 +衩 +狙 +桠 +稣 +阎 +肃 +梏 +诫 +孪 +昶 +婊 +衫 +嗔 +侃 +塞 +蜃 +樵 +峒 +貌 +屿 +欺 +缫 +阐 +栖 +诟 +珞 +荭 +吝 +萍 +嗽 +恂 +啻 +蜴 +磬 +峋 +俸 +豫 +谎 +徊 +镍 +韬 +魇 +晴 +U +囟 +猜 +蛮 +坐 +囿 +伴 +亭 +肝 +佗 +蝠 +妃 +胞 +滩 +榴 +氖 +垩 +苋 +砣 +扪 +馏 +姓 +轩 +厉 +夥 +侈 +禀 +垒 +岑 +赏 +钛 +辐 +痔 +披 +纸 +碳 +“ +坞 +蠓 +挤 +荥 +沅 +悔 +铧 +帼 +蒌 +蝇 +a +p +y +n +g +哀 +浆 +瑶 +凿 +桶 +馈 +皮 +奴 +苜 +佤 +伶 +晗 +铱 +炬 +优 +弊 +氢 +恃 +甫 +攥 +端 +锌 +灰 +稹 +炝 +曙 +邋 +亥 +眶 +碾 +拉 +萝 +绔 +捷 +浍 +腋 +姑 +菖 +凌 +涞 +麽 +锢 +桨 +潢 +绎 +镰 +殆 +锑 +渝 +铬 +困 +绽 +觎 +匈 +糙 +暑 +裹 +鸟 +盔 +肽 +迷 +綦 +『 +亳 +佝 +俘 +钴 +觇 +骥 +仆 +疝 +跪 +婶 +郯 +瀹 +唉 +脖 +踞 +针 +晾 +忒 +扼 +瞩 +叛 +椒 +疟 +嗡 +邗 +肆 +跆 +玫 +忡 +捣 +咧 +唆 +艄 +蘑 +潦 +笛 +阚 +沸 +泻 +掊 +菽 +贫 +斥 +髂 +孢 +镂 +赂 +麝 +鸾 +屡 +衬 +苷 +恪 +叠 +希 +粤 +爻 +喝 +茫 +惬 +郸 +绻 +庸 +撅 +碟 +宄 +妹 +膛 +叮 +饵 +崛 +嗲 +椅 +冤 +搅 +咕 +敛 +尹 +垦 +闷 +蝉 +霎 +勰 +败 +蓑 +泸 +肤 +鹌 +幌 +焦 +浠 +鞍 +刁 +舰 +乙 +竿 +裔 +。 +茵 +函 +伊 +兄 +丨 +娜 +匍 +謇 +莪 +宥 +似 +蝽 +翳 +酪 +翠 +粑 +薇 +祢 +骏 +赠 +叫 +Q +噤 +噻 +竖 +芗 +莠 +潭 +俊 +羿 +耜 +O +郫 +趁 +嗪 +囚 +蹶 +芒 +洁 +笋 +鹑 +敲 +硝 +啶 +堡 +渲 +揩 +』 +携 +宿 +遒 +颍 +扭 +棱 +割 +萜 +蔸 +葵 +琴 +捂 +饰 +衙 +耿 +掠 +募 +岂 +窖 +涟 +蔺 +瘤 +柞 +瞪 +怜 +匹 +距 +楔 +炜 +哆 +秦 +缎 +幼 +茁 +绪 +痨 +恨 +楸 +娅 +瓦 +桩 +雪 +嬴 +伏 +榔 +妥 +铿 +拌 +眠 +雍 +缇 +‘ +卓 +搓 +哌 +觞 +噩 +屈 +哧 +髓 +咦 +巅 +娑 +侑 +淫 +膳 +祝 +勾 +姊 +莴 +胄 +疃 +薛 +蜷 +胛 +巷 +芙 +芋 +熙 +闰 +勿 +窃 +狱 +剩 +钏 +幢 +陟 +铛 +慧 +靴 +耍 +k +浙 +浇 +飨 +惟 +绗 +祜 +澈 +啼 +咪 +磷 +摞 +诅 +郦 +抹 +跃 +壬 +吕 +肖 +琏 +颤 +尴 +剡 +抠 +凋 +赚 +泊 +津 +宕 +殷 +倔 +氲 +漫 +邺 +涎 +怠 +$ +垮 +荬 +遵 +俏 +叹 +噢 +饽 +蜘 +孙 +筵 +疼 +鞭 +羧 +牦 +箭 +潴 +c +眸 +祭 +髯 +啖 +坳 +愁 +芩 +驮 +倡 +巽 +穰 +沃 +胚 +怒 +凤 +槛 +剂 +趵 +嫁 +v +邢 +灯 +鄢 +桐 +睽 +檗 +锯 +槟 +婷 +嵋 +圻 +诗 +蕈 +颠 +遭 +痢 +芸 +怯 +馥 +竭 +锗 +徜 +恭 +遍 +籁 +剑 +嘱 +苡 +龄 +僧 +桑 +潸 +弘 +澶 +楹 +悲 +讫 +愤 +腥 +悸 +谍 +椹 +呢 +桓 +葭 +攫 +阀 +翰 +躲 +敖 +柑 +郎 +笨 +橇 +呃 +魁 +燎 +脓 +葩 +磋 +垛 +玺 +狮 +沓 +砜 +蕊 +锺 +罹 +蕉 +翱 +虐 +闾 +巫 +旦 +茱 +嬷 +枯 +鹏 +贡 +芹 +汛 +矫 +绁 +拣 +禺 +佃 +讣 +舫 +惯 +乳 +趋 +疲 +挽 +岚 +虾 +衾 +蠹 +蹂 +飓 +氦 +铖 +孩 +稞 +瑜 +壅 +掀 +勘 +妓 +畅 +髋 +W +庐 +牲 +蓿 +榕 +练 +垣 +唱 +邸 +菲 +昆 +婺 +穿 +绡 +麒 +蚱 +掂 +愚 +泷 +涪 +漳 +妩 +娉 +榄 +讷 +觅 +旧 +藤 +煮 +呛 +柳 +腓 +叭 +庵 +烷 +阡 +罂 +蜕 +擂 +猖 +咿 +媲 +脉 +【 +沏 +貅 +黠 +熏 +哲 +烁 +坦 +酵 +兜 +× +潇 +撒 +剽 +珩 +圹 +乾 +摸 +樟 +帽 +嗒 +襄 +魂 +轿 +憬 +锡 +〕 +喃 +皆 +咖 +隅 +脸 +残 +泮 +袂 +鹂 +珊 +囤 +捆 +咤 +误 +徨 +闹 +淙 +芊 +淋 +怆 +囗 +拨 +梳 +渤 +R +G +绨 +蚓 +婀 +幡 +狩 +麾 +谢 +唢 +裸 +旌 +伉 +纶 +裂 +驳 +砼 +咛 +澄 +樨 +蹈 +宙 +澍 +倍 +貔 +操 +勇 +蟠 +摈 +砧 +虬 +够 +缁 +悦 +藿 +撸 +艹 +摁 +淹 +豇 +虎 +榭 +ˉ +吱 +d +° +喧 +荀 +踱 +侮 +奋 +偕 +饷 +犍 +惮 +坑 +璎 +徘 +宛 +妆 +袈 +倩 +窦 +昂 +荏 +乖 +K +怅 +撰 +鳙 +牙 +袁 +酞 +X +痿 +琼 +闸 +雁 +趾 +荚 +虻 +涝 +《 +杏 +韭 +偈 +烤 +绫 +鞘 +卉 +症 +遢 +蓥 +诋 +杭 +荨 +匆 +竣 +簪 +辙 +敕 +虞 +丹 +缭 +咩 +黟 +m +淤 +瑕 +咂 +铉 +硼 +茨 +嶂 +痒 +畸 +敬 +涿 +粪 +窘 +熟 +叔 +嫔 +盾 +忱 +裘 +憾 +梵 +赡 +珙 +咯 +娘 +庙 +溯 +胺 +葱 +痪 +摊 +荷 +卞 +乒 +髦 +寐 +铭 +坩 +胗 +枷 +爆 +溟 +嚼 +羚 +砬 +轨 +惊 +挠 +罄 +竽 +菏 +氧 +浅 +楣 +盼 +枢 +炸 +阆 +杯 +谏 +噬 +淇 +渺 +俪 +秆 +墓 +泪 +跻 +砌 +痰 +垡 +渡 +耽 +釜 +讶 +鳎 +煞 +呗 +韶 +舶 +绷 +鹳 +缜 +旷 +铊 +皱 +龌 +檀 +霖 +奄 +槐 +艳 +蝶 +旋 +哝 +赶 +骞 +蚧 +腊 +盈 +丁 +` +蜚 +矸 +蝙 +睨 +嚓 +僻 +鬼 +醴 +夜 +彝 +磊 +笔 +拔 +栀 +糕 +厦 +邰 +纫 +逭 +纤 +眦 +膊 +馍 +躇 +烯 +蘼 +冬 +诤 +暄 +骶 +哑 +瘠 +」 +臊 +丕 +愈 +咱 +螺 +擅 +跋 +搏 +硪 +谄 +笠 +淡 +嘿 +骅 +谧 +鼎 +皋 +姚 +歼 +蠢 +驼 +耳 +胬 +挝 +涯 +狗 +蒽 +孓 +犷 +凉 +芦 +箴 +铤 +孤 +嘛 +坤 +V +茴 +朦 +挞 +尖 +橙 +诞 +搴 +碇 +洵 +浚 +帚 +蜍 +漯 +柘 +嚎 +讽 +芭 +荤 +咻 +祠 +秉 +跖 +埃 +吓 +糯 +眷 +馒 +惹 +娼 +鲑 +嫩 +讴 +轮 +瞥 +靶 +褚 +乏 +缤 +宋 +帧 +删 +驱 +碎 +扑 +俩 +俄 +偏 +涣 +竹 +噱 +皙 +佰 +渚 +唧 +斡 +# +镉 +刀 +崎 +筐 +佣 +夭 +贰 +肴 +峙 +哔 +艿 +匐 +牺 +镛 +缘 +仡 +嫡 +劣 +枸 +堀 +梨 +簿 +鸭 +蒸 +亦 +稽 +浴 +{ +衢 +束 +槲 +j +阁 +揍 +疥 +棋 +潋 +聪 +窜 +乓 +睛 +插 +冉 +阪 +苍 +搽 +「 +蟾 +螟 +幸 +仇 +樽 +撂 +慢 +跤 +幔 +俚 +淅 +覃 +觊 +溶 +妖 +帛 +侨 +曰 +妾 +泗 +· +: +瀘 +風 +Ë +( +) +∶ +紅 +紗 +瑭 +雲 +頭 +鶏 +財 +許 +• +¥ +樂 +焗 +麗 +— +; +滙 +東 +榮 +繪 +興 +… +門 +業 +π +楊 +國 +顧 +é +盤 +寳 +Λ +龍 +鳳 +島 +誌 +緣 +結 +銭 +萬 +勝 +祎 +璟 +優 +歡 +臨 +時 +購 += +★ +藍 +昇 +鐵 +觀 +勅 +農 +聲 +畫 +兿 +術 +發 +劉 +記 +專 +耑 +園 +書 +壴 +種 +Ο +● +褀 +號 +銀 +匯 +敟 +锘 +葉 +橪 +廣 +進 +蒄 +鑽 +阝 +祙 +貢 +鍋 +豊 +夬 +喆 +團 +閣 +開 +燁 +賓 +館 +酡 +沔 +順 ++ +硚 +劵 +饸 +陽 +車 +湓 +復 +萊 +氣 +軒 +華 +堃 +迮 +纟 +戶 +馬 +學 +裡 +電 +嶽 +獨 +マ +シ +サ +ジ +燘 +袪 +環 +❤ +臺 +灣 +専 +賣 +孖 +聖 +攝 +線 +▪ +α +傢 +俬 +夢 +達 +莊 +喬 +貝 +薩 +劍 +羅 +壓 +棛 +饦 +尃 +璈 +囍 +醫 +G +I +A +# +N +鷄 +髙 +嬰 +啓 +約 +隹 +潔 +賴 +藝 +~ +寶 +籣 +麺 +  +嶺 +√ +義 +網 +峩 +長 +∧ +魚 +機 +構 +② +鳯 +偉 +L +B +㙟 +畵 +鴿 +' +詩 +溝 +嚞 +屌 +藔 +佧 +玥 +蘭 +織 +1 +3 +9 +0 +7 +點 +砭 +鴨 +鋪 +銘 +廳 +弍 +‧ +創 +湯 +坶 +℃ +卩 +骝 +& +烜 +荘 +當 +潤 +扞 +係 +懷 +碶 +钅 +蚨 +讠 +☆ +叢 +爲 +埗 +涫 +塗 +→ +楽 +現 +鯨 +愛 +瑪 +鈺 +忄 +悶 +藥 +飾 +樓 +視 +孬 +ㆍ +燚 +苪 +師 +① +丼 +锽 +│ +韓 +標 +è +兒 +閏 +匋 +張 +漢 +Ü +髪 +會 +閑 +檔 +習 +裝 +の +峯 +菘 +輝 +И +雞 +釣 +億 +浐 +K +O +R +8 +H +E +P +T +W +D +S +C +M +F +姌 +饹 +» +晞 +廰 +ä +嵯 +鷹 +負 +飲 +絲 +冚 +楗 +澤 +綫 +區 +❋ +← +質 +靑 +揚 +③ +滬 +統 +産 +協 +﹑ +乸 +畐 +經 +運 +際 +洺 +岽 +為 +粵 +諾 +崋 +豐 +碁 +ɔ +V +2 +6 +齋 +誠 +訂 +´ +勑 +雙 +陳 +無 +í +泩 +媄 +夌 +刂 +i +c +t +o +r +a +嘢 +耄 +燴 +暃 +壽 +媽 +靈 +抻 +體 +唻 +É +冮 +甹 +鎮 +錦 +ʌ +蜛 +蠄 +尓 +駕 +戀 +飬 +逹 +倫 +貴 +極 +Я +Й +寬 +磚 +嶪 +郎 +職 +| +間 +n +d +剎 +伈 +課 +飛 +橋 +瘊 +№ +譜 +骓 +圗 +滘 +縣 +粿 +咅 +養 +濤 +彳 +® +% +Ⅱ +啰 +㴪 +見 +矞 +薬 +糁 +邨 +鲮 +顔 +罱 +З +選 +話 +贏 +氪 +俵 +競 +瑩 +繡 +枱 +β +綉 +á +獅 +爾 +™ +麵 +戋 +淩 +徳 +個 +劇 +場 +務 +簡 +寵 +h +實 +膠 +轱 +圖 +築 +嘣 +樹 +㸃 +營 +耵 +孫 +饃 +鄺 +飯 +麯 +遠 +輸 +坫 +孃 +乚 +閃 +鏢 +㎡ +題 +廠 +關 +↑ +爺 +將 +軍 +連 +篦 +覌 +參 +箸 +- +窠 +棽 +寕 +夀 +爰 +歐 +呙 +閥 +頡 +熱 +雎 +垟 +裟 +凬 +勁 +帑 +馕 +夆 +疌 +枼 +馮 +貨 +蒤 +樸 +彧 +旸 +靜 +龢 +暢 +㐱 +鳥 +珺 +鏡 +灡 +爭 +堷 +廚 +Ó +騰 +診 +┅ +蘇 +褔 +凱 +頂 +豕 +亞 +帥 +嘬 +⊥ +仺 +桖 +複 +饣 +絡 +穂 +顏 +棟 +納 +▏ +濟 +親 +設 +計 +攵 +埌 +烺 +ò +頤 +燦 +蓮 +撻 +節 +講 +濱 +濃 +娽 +洳 +朿 +燈 +鈴 +護 +膚 +铔 +過 +補 +Z +U +5 +4 +坋 +闿 +䖝 +餘 +缐 +铞 +貿 +铪 +桼 +趙 +鍊 +[ +㐂 +垚 +菓 +揸 +捲 +鐘 +滏 +𣇉 +爍 +輪 +燜 +鴻 +鮮 +動 +鹞 +鷗 +丄 +慶 +鉌 +翥 +飮 +腸 +⇋ +漁 +覺 +來 +熘 +昴 +翏 +鲱 +圧 +鄉 +萭 +頔 +爐 +嫚 +г +貭 +類 +聯 +幛 +輕 +訓 +鑒 +夋 +锨 +芃 +珣 +䝉 +扙 +嵐 +銷 +處 +ㄱ +語 +誘 +苝 +歸 +儀 +燒 +楿 +內 +粢 +葒 +奧 +麥 +礻 +滿 +蠔 +穵 +瞭 +態 +鱬 +榞 +硂 +鄭 +黃 +煙 +祐 +奓 +逺 +* +瑄 +獲 +聞 +薦 +讀 +這 +樣 +決 +問 +啟 +們 +執 +説 +轉 +單 +隨 +唘 +帶 +倉 +庫 +還 +贈 +尙 +皺 +■ +餅 +產 +○ +∈ +報 +狀 +楓 +賠 +琯 +嗮 +禮 +` +傳 +> +≤ +嗞 +Φ +≥ +換 +咭 +∣ +↓ +曬 +ε +応 +寫 +″ +終 +様 +純 +費 +療 +聨 +凍 +壐 +郵 +ü +黒 +∫ +製 +塊 +調 +軽 +確 +撃 +級 +馴 +Ⅲ +涇 +繹 +數 +碼 +證 +狒 +処 +劑 +< +晧 +賀 +衆 +] +櫥 +兩 +陰 +絶 +對 +鯉 +憶 +◎ +p +e +Y +蕒 +煖 +頓 +測 +試 +鼽 +僑 +碩 +妝 +帯 +≈ +鐡 +舖 +權 +喫 +倆 +ˋ +該 +悅 +ā +俫 +. +f +s +b +m +k +g +u +j +貼 +淨 +濕 +針 +適 +備 +l +/ +給 +謢 +強 +觸 +衛 +與 +⊙ +$ +緯 +變 +⑴ +⑵ +⑶ +㎏ +殺 +∩ +幚 +─ +價 +▲ +離 +ú +ó +飄 +烏 +関 +閟 +﹝ +﹞ +邏 +輯 +鍵 +驗 +訣 +導 +歷 +屆 +層 +▼ +儱 +錄 +熳 +ē +艦 +吋 +錶 +辧 +飼 +顯 +④ +禦 +販 +気 +対 +枰 +閩 +紀 +幹 +瞓 +貊 +淚 +△ +眞 +墊 +Ω +獻 +褲 +縫 +緑 +亜 +鉅 +餠 +{ +} +◆ +蘆 +薈 +█ +◇ +溫 +彈 +晳 +粧 +犸 +穩 +訊 +崬 +凖 +熥 +П +舊 +條 +紋 +圍 +Ⅳ +筆 +尷 +難 +雜 +錯 +綁 +識 +頰 +鎖 +艶 +□ +殁 +殼 +⑧ +├ +▕ +鵬 +ǐ +ō +ǒ +糝 +綱 +▎ +μ +盜 +饅 +醬 +籤 +蓋 +釀 +鹽 +據 +à +ɡ +辦 +◥ +彐 +┌ +婦 +獸 +鲩 +伱 +ī +蒟 +蒻 +齊 +袆 +腦 +寧 +凈 +妳 +煥 +詢 +偽 +謹 +啫 +鯽 +騷 +鱸 +損 +傷 +鎻 +髮 +買 +冏 +儥 +両 +﹢ +∞ +載 +喰 +z +羙 +悵 +燙 +曉 +員 +組 +徹 +艷 +痠 +鋼 +鼙 +縮 +細 +嚒 +爯 +≠ +維 +" +鱻 +壇 +厍 +帰 +浥 +犇 +薡 +軎 +² +應 +醜 +刪 +緻 +鶴 +賜 +噁 +軌 +尨 +镔 +鷺 +槗 +彌 +葚 +濛 +請 +溇 +緹 +賢 +訪 +獴 +瑅 +資 +縤 +陣 +蕟 +栢 +韻 +祼 +恁 +伢 +謝 +劃 +涑 +總 +衖 +踺 +砋 +凉 +籃 +駿 +苼 +瘋 +昽 +紡 +驊 +腎 +﹗ +響 +杋 +剛 +嚴 +禪 +歓 +槍 +傘 +檸 +檫 +炣 +勢 +鏜 +鎢 +銑 +尐 +減 +奪 +惡 +θ +僮 +婭 +臘 +ū +ì +殻 +鉄 +∑ +蛲 +焼 +緖 +續 +紹 +懮 diff --git a/ppocr/utils/profiler.py b/ppocr/utils/profiler.py new file mode 100644 index 0000000..e4e3e05 --- /dev/null +++ b/ppocr/utils/profiler.py @@ -0,0 +1,130 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import paddle.profiler as profiler + +# A global variable to record the number of calling times for profiler +# functions. It is used to specify the tracing range of training steps. +_profiler_step_id = 0 + +# A global variable to avoid parsing from string every time. +_profiler_options = None +_prof = None + + +class ProfilerOptions(object): + """ + Use a string to initialize a ProfilerOptions. + The string should be in the format: "key1=value1;key2=value;key3=value3". + For example: + "profile_path=model.profile" + "batch_range=[50, 60]; profile_path=model.profile" + "batch_range=[50, 60]; tracer_option=OpDetail; profile_path=model.profile" + + ProfilerOptions supports following key-value pair: + batch_range - a integer list, e.g. [100, 110]. + state - a string, the optional values are 'CPU', 'GPU' or 'All'. + sorted_key - a string, the optional values are 'calls', 'total', + 'max', 'min' or 'ave. + tracer_option - a string, the optional values are 'Default', 'OpDetail', + 'AllOpDetail'. + profile_path - a string, the path to save the serialized profile data, + which can be used to generate a timeline. + exit_on_finished - a boolean. + """ + + def __init__(self, options_str): + assert isinstance(options_str, str) + + self._options = { + "batch_range": [10, 20], + "state": "All", + "sorted_key": "total", + "tracer_option": "Default", + "profile_path": "/tmp/profile", + "exit_on_finished": True, + "timer_only": True, + } + self._parse_from_string(options_str) + + def _parse_from_string(self, options_str): + for kv in options_str.replace(" ", "").split(";"): + key, value = kv.split("=") + if key == "batch_range": + value_list = value.replace("[", "").replace("]", "").split(",") + value_list = list(map(int, value_list)) + if ( + len(value_list) >= 2 + and value_list[0] >= 0 + and value_list[1] > value_list[0] + ): + self._options[key] = value_list + elif key == "exit_on_finished": + self._options[key] = value.lower() in ("yes", "true", "t", "1") + elif key in ["state", "sorted_key", "tracer_option", "profile_path"]: + self._options[key] = value + elif key == "timer_only": + self._options[key] = value + + def __getitem__(self, name): + if self._options.get(name, None) is None: + raise ValueError("ProfilerOptions does not have an option named %s." % name) + return self._options[name] + + +def add_profiler_step(options_str=None): + """ + Enable the operator-level timing using PaddlePaddle's profiler. + The profiler uses a independent variable to count the profiler steps. + One call of this function is treated as a profiler step. + Args: + profiler_options - a string to initialize the ProfilerOptions. + Default is None, and the profiler is disabled. + """ + if options_str is None: + return + + global _prof + global _profiler_step_id + global _profiler_options + + if _profiler_options is None: + _profiler_options = ProfilerOptions(options_str) + # profile : https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/performance_improving/profiling_model.html#chakanxingnengshujudetongjibiaodan + # timer_only = True only the model's throughput and time overhead are displayed + # timer_only = False calling summary can print a statistical form that presents performance data from different perspectives. + # timer_only = False the output Timeline information can be found in the profiler_log directory + if _prof is None: + _timer_only = str(_profiler_options["timer_only"]) == str(True) + _prof = profiler.Profiler( + scheduler=( + _profiler_options["batch_range"][0], + _profiler_options["batch_range"][1], + ), + on_trace_ready=profiler.export_chrome_tracing("./profiler_log"), + timer_only=_timer_only, + ) + _prof.start() + else: + _prof.step() + + if _profiler_step_id == _profiler_options["batch_range"][1]: + _prof.stop() + _prof.summary(op_detail=True, thread_sep=False, time_unit="ms") + _prof = None + if _profiler_options["exit_on_finished"]: + sys.exit(0) + + _profiler_step_id += 1 diff --git a/ppocr/utils/save_load.py b/ppocr/utils/save_load.py new file mode 100644 index 0000000..73e79bc --- /dev/null +++ b/ppocr/utils/save_load.py @@ -0,0 +1,439 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import errno +import os +import pickle +import json +from packaging import version + +import paddle + +from ppocr.utils.logging import get_logger +from ppocr.utils.network import maybe_download_params + +try: + import encryption # Attempt to import the encryption module for AIStudio's encryption model + + encrypted = encryption.is_encryption_needed() +except ImportError: + print("Skipping import of the encryption module.") + encrypted = False # Encryption is not needed if the module cannot be imported + +__all__ = ["load_model"] + + +# just to determine the inference model file format +def get_FLAGS_json_format_model(): + # json format by default + return os.environ.get("FLAGS_json_format_model", "1").lower() in ("1", "true", "t") + + +FLAGS_json_format_model = get_FLAGS_json_format_model() + + +def _mkdir_if_not_exist(path, logger): + """ + mkdir if not exists, ignore the exception when multiprocess mkdir together + """ + if not os.path.exists(path): + try: + os.makedirs(path) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(path): + logger.warning( + "be happy if some process has already created {}".format(path) + ) + else: + raise OSError("Failed to mkdir {}".format(path)) + + +def load_model(config, model, optimizer=None, model_type="det", ema=None): + """ + load model from checkpoint or pretrained_model + """ + logger = get_logger() + global_config = config["Global"] + checkpoints = global_config.get("checkpoints") + pretrained_model = global_config.get("pretrained_model") + best_model_dict = {} + is_float16 = False + is_nlp_model = model_type == "kie" and config["Architecture"]["algorithm"] not in [ + "SDMGR" + ] + + if is_nlp_model is True: + # NOTE: for kie model dsitillation, resume training is not supported now + if config["Architecture"]["algorithm"] in ["Distillation"]: + return best_model_dict + checkpoints = config["Architecture"]["Backbone"]["checkpoints"] + # load kie method metric + if checkpoints: + if os.path.exists(os.path.join(checkpoints, "metric.states")): + with open(os.path.join(checkpoints, "metric.states"), "rb") as f: + states_dict = pickle.load(f, encoding="latin1") + best_model_dict = states_dict.get("best_model_dict", {}) + if "epoch" in states_dict: + best_model_dict["start_epoch"] = states_dict["epoch"] + 1 + logger.info("resume from {}".format(checkpoints)) + + if optimizer is not None: + if checkpoints[-1] in ["/", "\\"]: + checkpoints = checkpoints[:-1] + if os.path.exists(checkpoints + ".pdopt"): + optim_dict = paddle.load(checkpoints + ".pdopt") + optimizer.set_state_dict(optim_dict) + else: + logger.warning( + "{}.pdopt is not exists, params of optimizer is not loaded".format( + checkpoints + ) + ) + + return best_model_dict + + if checkpoints: + if checkpoints.endswith(".pdparams"): + checkpoints = checkpoints.replace(".pdparams", "") + assert os.path.exists( + checkpoints + ".pdparams" + ), "The {}.pdparams does not exists!".format(checkpoints) + + # load params from trained model + params = paddle.load(checkpoints + ".pdparams") + state_dict = model.state_dict() + new_state_dict = {} + for key, value in state_dict.items(): + if key not in params: + logger.warning( + "{} not in loaded params {} !".format(key, params.keys()) + ) + continue + pre_value = params[key] + if pre_value.dtype == paddle.float16: + is_float16 = True + if pre_value.dtype != value.dtype: + pre_value = pre_value.astype(value.dtype) + if list(value.shape) == list(pre_value.shape): + new_state_dict[key] = pre_value + else: + logger.warning( + "The shape of model params {} {} not matched with loaded params shape {} !".format( + key, value.shape, pre_value.shape + ) + ) + model.set_state_dict(new_state_dict) + if is_float16: + logger.info( + "The parameter type is float16, which is converted to float32 when loading" + ) + if optimizer is not None: + if os.path.exists(checkpoints + ".pdopt"): + optim_dict = paddle.load(checkpoints + ".pdopt") + optimizer.set_state_dict(optim_dict) + else: + logger.warning( + "{}.pdopt is not exists, params of optimizer is not loaded".format( + checkpoints + ) + ) + + if os.path.exists(checkpoints + ".states"): + with open(checkpoints + ".states", "rb") as f: + states_dict = pickle.load(f, encoding="latin1") + best_model_dict = states_dict.get("best_model_dict", {}) + best_model_dict["acc"] = 0.0 + if "epoch" in states_dict: + best_model_dict["start_epoch"] = states_dict["epoch"] + 1 + logger.info("resume from {}".format(checkpoints)) + + # Restore EMA state if available + if ema is not None: + pdema_path = checkpoints + ".pdema" + if os.path.exists(pdema_path): + ema_data = paddle.load(pdema_path) + # .pdparams contains EMA weights; restore original training weights + if "train_state" in ema_data: + train_sd = ema_data["train_state"] + cur_sd = model.state_dict() + for k in cur_sd: + if k in train_sd: + if list(cur_sd[k].shape) == list(train_sd[k].shape): + cur_sd[k] = train_sd[k] + model.set_state_dict(cur_sd) + logger.info( + "EMA: restored training weights from {}".format(pdema_path) + ) + # Restore EMA shadow weights + step + if "ema_state" in ema_data and "step" in ema_data: + ema.set_state_dict(ema_data) + logger.info( + "EMA: restored shadow weights (step={}) from {}".format( + ema_data["step"], pdema_path + ) + ) + + elif pretrained_model: + is_float16 = load_pretrained_params(model, pretrained_model) + else: + logger.info("train from scratch") + best_model_dict["is_float16"] = is_float16 + return best_model_dict + + +def load_pretrained_params(model, path): + logger = get_logger() + path = maybe_download_params(path) + if path.endswith(".pdparams"): + path = path.replace(".pdparams", "") + assert os.path.exists( + path + ".pdparams" + ), "The {}.pdparams does not exists!".format(path) + + params = paddle.load(path + ".pdparams") + + state_dict = model.state_dict() + + new_state_dict = {} + is_float16 = False + + for k1 in params.keys(): + if k1 not in state_dict.keys(): + logger.warning("The pretrained params {} not in model".format(k1)) + else: + if params[k1].dtype == paddle.float16: + is_float16 = True + if params[k1].dtype != state_dict[k1].dtype: + params[k1] = params[k1].astype(state_dict[k1].dtype) + if list(state_dict[k1].shape) == list(params[k1].shape): + new_state_dict[k1] = params[k1] + else: + logger.warning( + "The shape of model params {} {} not matched with loaded params {} {} !".format( + k1, state_dict[k1].shape, k1, params[k1].shape + ) + ) + + model.set_state_dict(new_state_dict) + if is_float16: + logger.info( + "The parameter type is float16, which is converted to float32 when loading" + ) + logger.info("load pretrain successful from {}".format(path)) + return is_float16 + + +def save_model( + model, + optimizer, + model_path, + logger, + config, + is_best=False, + prefix="ppocr", + ema=None, + train_state=None, + **kwargs, +): + """ + save model to the target path + """ + _mkdir_if_not_exist(model_path, logger) + model_prefix = os.path.join(model_path, prefix) + + if prefix == "best_accuracy": + best_model_path = os.path.join(model_path, "best_model") + _mkdir_if_not_exist(best_model_path, logger) + + paddle.save(optimizer.state_dict(), model_prefix + ".pdopt") + if prefix == "best_accuracy": + paddle.save( + optimizer.state_dict(), os.path.join(best_model_path, "model.pdopt") + ) + + is_nlp_model = config["Architecture"]["model_type"] == "kie" and config[ + "Architecture" + ]["algorithm"] not in ["SDMGR"] + if is_nlp_model is not True: + paddle.save(model.state_dict(), model_prefix + ".pdparams") + metric_prefix = model_prefix + + # Save EMA state for training resumption + if ema is not None and train_state is not None: + paddle.save( + { + "train_state": train_state, + "ema_state": ema.state_dict, + "step": ema.step, + }, + model_prefix + ".pdema", + ) + + if prefix == "best_accuracy": + paddle.save( + model.state_dict(), os.path.join(best_model_path, "model.pdparams") + ) + if ema is not None and train_state is not None: + paddle.save( + { + "train_state": train_state, + "ema_state": ema.state_dict, + "step": ema.step, + }, + os.path.join(best_model_path, "model.pdema"), + ) + + else: # for kie system, we follow the save/load rules in NLP + if config["Global"]["distributed"]: + arch = model._layers + else: + arch = model + if config["Architecture"]["algorithm"] in ["Distillation"]: + arch = arch.Student + arch.backbone.model.save_pretrained(model_prefix) + metric_prefix = os.path.join(model_prefix, "metric") + + if prefix == "best_accuracy": + arch.backbone.model.save_pretrained(best_model_path) + + save_model_info = kwargs.pop("save_model_info", False) + if save_model_info: + with open(os.path.join(model_path, f"{prefix}.info.json"), "w") as f: + json.dump(kwargs, f) + logger.info("Already save model info in {}".format(model_path)) + if prefix != "latest": + done_flag = kwargs.pop("done_flag", False) + update_train_results(config, prefix, save_model_info, done_flag=done_flag) + + # save metric and config + with open(metric_prefix + ".states", "wb") as f: + pickle.dump(kwargs, f, protocol=2) + if is_best: + logger.info("save best model is to {}".format(model_prefix)) + else: + logger.info("save model in {}".format(model_prefix)) + + +def update_train_results(config, prefix, metric_info, done_flag=False, last_num=5): + if paddle.distributed.get_rank() != 0: + return + + assert last_num >= 1 + train_results_path = os.path.join( + config["Global"]["save_model_dir"], "train_result.json" + ) + save_model_tag = ["pdparams", "pdopt", "pdstates"] + paddle_version = version.parse(paddle.__version__) + if FLAGS_json_format_model or paddle_version >= version.parse("3.0.0"): + save_inference_files = { + "inference_config": "inference.yml", + "pdmodel": "inference.json", + "pdiparams": "inference.pdiparams", + } + else: + save_inference_files = { + "inference_config": "inference.yml", + "pdmodel": "inference.pdmodel", + "pdiparams": "inference.pdiparams", + "pdiparams.info": "inference.pdiparams.info", + } + if os.path.exists(train_results_path): + with open(train_results_path, "r") as fp: + train_results = json.load(fp) + else: + train_results = {} + train_results["model_name"] = config["Global"]["model_name"] + label_dict_path = config["Global"].get("character_dict_path", "") + if label_dict_path != "": + label_dict_path = os.path.abspath(label_dict_path) + if not os.path.exists(label_dict_path): + label_dict_path = "" + train_results["label_dict"] = label_dict_path + train_results["train_log"] = "train.log" + train_results["visualdl_log"] = "" + train_results["config"] = "config.yaml" + train_results["models"] = {} + for i in range(1, last_num + 1): + train_results["models"][f"last_{i}"] = {} + train_results["models"]["best"] = {} + train_results["done_flag"] = done_flag + if "best" in prefix: + if "acc" in metric_info["metric"]: + metric_score = metric_info["metric"]["acc"] + elif "precision" in metric_info["metric"]: + metric_score = metric_info["metric"]["precision"] + elif "exp_rate" in metric_info["metric"]: + metric_score = metric_info["metric"]["exp_rate"] + else: + raise ValueError("No metric score found.") + train_results["models"]["best"]["score"] = metric_score + for tag in save_model_tag: + if tag == "pdparams" and encrypted: + train_results["models"]["best"][tag] = os.path.join( + prefix, + ( + f"{prefix}.encrypted.{tag}" + if tag != "pdstates" + else f"{prefix}.states" + ), + ) + else: + train_results["models"]["best"][tag] = os.path.join( + prefix, + f"{prefix}.{tag}" if tag != "pdstates" else f"{prefix}.states", + ) + for key in save_inference_files: + train_results["models"]["best"][key] = os.path.join( + prefix, "inference", save_inference_files[key] + ) + else: + for i in range(last_num - 1, 0, -1): + train_results["models"][f"last_{i + 1}"] = train_results["models"][ + f"last_{i}" + ].copy() + if "acc" in metric_info["metric"]: + metric_score = metric_info["metric"]["acc"] + elif "precision" in metric_info["metric"]: + metric_score = metric_info["metric"]["precision"] + elif "exp_rate" in metric_info["metric"]: + metric_score = metric_info["metric"]["exp_rate"] + else: + metric_score = 0 + train_results["models"][f"last_{1}"]["score"] = metric_score + for tag in save_model_tag: + if tag == "pdparams" and encrypted: + train_results["models"][f"last_{1}"][tag] = os.path.join( + prefix, + ( + f"{prefix}.encrypted.{tag}" + if tag != "pdstates" + else f"{prefix}.states" + ), + ) + else: + train_results["models"][f"last_{1}"][tag] = os.path.join( + prefix, + f"{prefix}.{tag}" if tag != "pdstates" else f"{prefix}.states", + ) + for key in save_inference_files: + train_results["models"][f"last_{1}"][key] = os.path.join( + prefix, "inference", save_inference_files[key] + ) + + with open(train_results_path, "w") as fp: + json.dump(train_results, fp) diff --git a/ppocr/utils/stats.py b/ppocr/utils/stats.py new file mode 100755 index 0000000..6dd8c58 --- /dev/null +++ b/ppocr/utils/stats.py @@ -0,0 +1,70 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import collections +import numpy as np +import datetime + +__all__ = ["TrainingStats", "Time"] + + +class SmoothedValue(object): + """Track a series of values and provide access to smoothed values over a + window or the global series average. + """ + + def __init__(self, window_size): + self.deque = collections.deque(maxlen=window_size) + + def add_value(self, value): + self.deque.append(value) + + def get_median_value(self): + return np.median(self.deque) + + +def Time(): + return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + + +class TrainingStats(object): + def __init__(self, window_size, stats_keys): + self.window_size = window_size + self.smoothed_losses_and_metrics = { + key: SmoothedValue(window_size) for key in stats_keys + } + + def update(self, stats): + for k, v in stats.items(): + if k not in self.smoothed_losses_and_metrics: + self.smoothed_losses_and_metrics[k] = SmoothedValue(self.window_size) + self.smoothed_losses_and_metrics[k].add_value(v) + + def get(self, extras=None): + stats = collections.OrderedDict() + if extras: + for k, v in extras.items(): + stats[k] = v + for k, v in self.smoothed_losses_and_metrics.items(): + stats[k] = round(v.get_median_value(), 6) + + return stats + + def log(self, extras=None): + d = self.get(extras) + strs = [] + for k, v in d.items(): + strs.append("{}: {:x<6f}".format(k, v)) + strs = ", ".join(strs) + return strs diff --git a/ppocr/utils/utility.py b/ppocr/utils/utility.py new file mode 100644 index 0000000..6898ed4 --- /dev/null +++ b/ppocr/utils/utility.py @@ -0,0 +1,216 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +import os +import cv2 +import random +import numpy as np +import paddle +import importlib.util +import sys +import subprocess + + +def print_dict(d, logger, delimiter=0): + """ + Recursively visualize a dict and + indenting acrrording by the relationship of keys. + """ + for k, v in sorted(d.items()): + if isinstance(v, dict): + logger.info("{}{} : ".format(delimiter * " ", str(k))) + print_dict(v, logger, delimiter + 4) + elif isinstance(v, list) and len(v) >= 1 and isinstance(v[0], dict): + logger.info("{}{} : ".format(delimiter * " ", str(k))) + for value in v: + print_dict(value, logger, delimiter + 4) + else: + logger.info("{}{} : {}".format(delimiter * " ", k, v)) + + +def get_check_global_params(mode): + check_params = [ + "use_gpu", + "max_text_length", + "image_shape", + "image_shape", + "character_type", + "loss_type", + ] + if mode == "train_eval": + check_params = check_params + [ + "train_batch_size_per_card", + "test_batch_size_per_card", + ] + elif mode == "test": + check_params = check_params + ["test_batch_size_per_card"] + return check_params + + +def _check_image_file(path): + img_end = {"jpg", "bmp", "png", "jpeg", "rgb", "tif", "tiff", "gif", "pdf"} + return any([path.lower().endswith(e) for e in img_end]) + + +def get_image_file_list(img_file, infer_list=None): + imgs_lists = [] + if infer_list and not os.path.exists(infer_list): + raise Exception("not found infer list {}".format(infer_list)) + if infer_list: + with open(infer_list, "r") as f: + lines = f.readlines() + for line in lines: + image_path = line.strip().split("\t")[0] + image_path = os.path.join(img_file, image_path) + imgs_lists.append(image_path) + else: + if img_file is None or not os.path.exists(img_file): + raise Exception("not found any img file in {}".format(img_file)) + + img_end = {"jpg", "bmp", "png", "jpeg", "rgb", "tif", "tiff", "gif", "pdf"} + if os.path.isfile(img_file) and _check_image_file(img_file): + imgs_lists.append(img_file) + elif os.path.isdir(img_file): + for single_file in os.listdir(img_file): + file_path = os.path.join(img_file, single_file) + if os.path.isfile(file_path) and _check_image_file(file_path): + imgs_lists.append(file_path) + + if len(imgs_lists) == 0: + raise Exception("not found any img file in {}".format(img_file)) + imgs_lists = sorted(imgs_lists) + return imgs_lists + + +def binarize_img(img): + if len(img.shape) == 3 and img.shape[2] == 3: + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # conversion to grayscale image + # use cv2 threshold binarization + _, gray = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) + img = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR) + return img + + +def alpha_to_color(img, alpha_color=(255, 255, 255)): + if len(img.shape) == 3 and img.shape[2] == 4: + B, G, R, A = cv2.split(img) + alpha = A / 255 + + R = (alpha_color[0] * (1 - alpha) + R * alpha).astype(np.uint8) + G = (alpha_color[1] * (1 - alpha) + G * alpha).astype(np.uint8) + B = (alpha_color[2] * (1 - alpha) + B * alpha).astype(np.uint8) + + img = cv2.merge((B, G, R)) + return img + + +def check_and_read(img_path): + if os.path.basename(img_path)[-3:].lower() == "gif": + gif = cv2.VideoCapture(img_path) + ret, frame = gif.read() + if not ret: + logger = logging.getLogger("ppocr") + logger.info("Cannot read {}. This gif image maybe corrupted.") + return None, False + if len(frame.shape) == 2 or frame.shape[-1] == 1: + frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2RGB) + imgvalue = frame[:, :, ::-1] + return imgvalue, True, False + elif os.path.basename(img_path)[-3:].lower() == "pdf": + from paddle.utils import try_import + + fitz = try_import("fitz") + from PIL import Image + + imgs = [] + with fitz.open(img_path) as pdf: + for pg in range(0, pdf.page_count): + page = pdf[pg] + mat = fitz.Matrix(2, 2) + pm = page.get_pixmap(matrix=mat, alpha=False) + + # if width or height > 2000 pixels, don't enlarge the image + if pm.width > 2000 or pm.height > 2000: + pm = page.get_pixmap(matrix=fitz.Matrix(1, 1), alpha=False) + + img = Image.frombytes("RGB", [pm.width, pm.height], pm.samples) + img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) + imgs.append(img) + return imgs, False, True + return None, False, False + + +def load_vqa_bio_label_maps(label_map_path): + with open(label_map_path, "r", encoding="utf-8") as fin: + lines = fin.readlines() + old_lines = [line.strip() for line in lines] + lines = ["O"] + for line in old_lines: + # "O" has already been in lines + if line.upper() in ["OTHER", "OTHERS", "IGNORE"]: + continue + lines.append(line) + labels = ["O"] + for line in lines[1:]: + labels.append("B-" + line) + labels.append("I-" + line) + label2id_map = {label.upper(): idx for idx, label in enumerate(labels)} + id2label_map = {idx: label.upper() for idx, label in enumerate(labels)} + return label2id_map, id2label_map + + +def set_seed(seed=1024): + random.seed(seed) + np.random.seed(seed) + paddle.seed(seed) + + +def check_install(module_name, install_name): + spec = importlib.util.find_spec(module_name) + if spec is None: + print(f"Warning! The {module_name} module is NOT installed") + print( + f"Try install {module_name} module automatically. You can also try to install manually by pip install {install_name}." + ) + python = sys.executable + try: + subprocess.check_call( + [python, "-m", "pip", "install", install_name], + stdout=subprocess.DEVNULL, + ) + print(f"The {module_name} module is now installed") + except subprocess.CalledProcessError as exc: + raise Exception(f"Install {module_name} failed, please install manually") + else: + print(f"{module_name} has been installed.") + + +class AverageMeter: + def __init__(self): + self.reset() + + def reset(self): + """reset""" + self.val = 0 + self.avg = 0 + self.sum = 0 + self.count = 0 + + def update(self, val, n=1): + """update""" + self.val = val + self.sum += val * n + self.count += n + self.avg = self.sum / self.count diff --git a/ppocr/utils/visual.py b/ppocr/utils/visual.py new file mode 100644 index 0000000..1a49c31 --- /dev/null +++ b/ppocr/utils/visual.py @@ -0,0 +1,142 @@ +# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import cv2 +import os +import numpy as np +import PIL +from PIL import Image, ImageDraw, ImageFont + + +def draw_ser_results( + image, ocr_results, font_path="doc/fonts/simfang.ttf", font_size=14 +): + np.random.seed(2021) + color = ( + np.random.permutation(range(255)), + np.random.permutation(range(255)), + np.random.permutation(range(255)), + ) + color_map = { + idx: (color[0][idx], color[1][idx], color[2][idx]) for idx in range(1, 255) + } + if isinstance(image, np.ndarray): + image = Image.fromarray(image) + elif isinstance(image, str) and os.path.isfile(image): + image = Image.open(image).convert("RGB") + img_new = image.copy() + draw = ImageDraw.Draw(img_new) + + font = ImageFont.truetype(font_path, font_size, encoding="utf-8") + for ocr_info in ocr_results: + if ocr_info["pred_id"] not in color_map: + continue + color = color_map[ocr_info["pred_id"]] + text = "{}: {}".format(ocr_info["pred"], ocr_info["transcription"]) + + if "bbox" in ocr_info: + # draw with ocr engine + bbox = ocr_info["bbox"] + else: + # draw with ocr groundtruth + bbox = trans_poly_to_bbox(ocr_info["points"]) + draw_box_txt(bbox, text, draw, font, font_size, color) + + img_new = Image.blend(image, img_new, 0.7) + return np.array(img_new) + + +def draw_box_txt(bbox, text, draw, font, font_size, color): + # draw ocr results outline + bbox = ((bbox[0], bbox[1]), (bbox[2], bbox[3])) + draw.rectangle(bbox, fill=color) + + # draw ocr results + if int(PIL.__version__.split(".")[0]) < 10: + tw = font.getsize(text)[0] + th = font.getsize(text)[1] + else: + left, top, right, bottom = font.getbbox(text) + tw, th = right - left, bottom - top + + start_y = max(0, bbox[0][1] - th) + draw.rectangle( + [(bbox[0][0] + 1, start_y), (bbox[0][0] + tw + 1, start_y + th)], + fill=(0, 0, 255), + ) + draw.text((bbox[0][0] + 1, start_y), text, fill=(255, 255, 255), font=font) + + +def trans_poly_to_bbox(poly): + x1 = np.min([p[0] for p in poly]) + x2 = np.max([p[0] for p in poly]) + y1 = np.min([p[1] for p in poly]) + y2 = np.max([p[1] for p in poly]) + return [x1, y1, x2, y2] + + +def draw_re_results(image, result, font_path="doc/fonts/simfang.ttf", font_size=18): + np.random.seed(0) + if isinstance(image, np.ndarray): + image = Image.fromarray(image) + elif isinstance(image, str) and os.path.isfile(image): + image = Image.open(image).convert("RGB") + img_new = image.copy() + draw = ImageDraw.Draw(img_new) + + font = ImageFont.truetype(font_path, font_size, encoding="utf-8") + color_head = (0, 0, 255) + color_tail = (255, 0, 0) + color_line = (0, 255, 0) + + for ocr_info_head, ocr_info_tail in result: + draw_box_txt( + ocr_info_head["bbox"], + ocr_info_head["transcription"], + draw, + font, + font_size, + color_head, + ) + draw_box_txt( + ocr_info_tail["bbox"], + ocr_info_tail["transcription"], + draw, + font, + font_size, + color_tail, + ) + + center_head = ( + (ocr_info_head["bbox"][0] + ocr_info_head["bbox"][2]) // 2, + (ocr_info_head["bbox"][1] + ocr_info_head["bbox"][3]) // 2, + ) + center_tail = ( + (ocr_info_tail["bbox"][0] + ocr_info_tail["bbox"][2]) // 2, + (ocr_info_tail["bbox"][1] + ocr_info_tail["bbox"][3]) // 2, + ) + + draw.line([center_head, center_tail], fill=color_line, width=5) + + img_new = Image.blend(image, img_new, 0.5) + return np.array(img_new) + + +def draw_rectangle(img_path, boxes): + boxes = np.array(boxes) + img = cv2.imread(img_path) + img_show = img.copy() + for box in boxes.astype(int): + x1, y1, x2, y2 = box + cv2.rectangle(img_show, (x1, y1), (x2, y2), (255, 0, 0), 2) + return img_show diff --git a/ppstructure/README.md b/ppstructure/README.md new file mode 100644 index 0000000..7f169e4 --- /dev/null +++ b/ppstructure/README.md @@ -0,0 +1,16 @@ + +## PP-Structure Upgrade Considerations + +Since its launch in 2021, the PP-Structure series models have undergone three generations of updates and iterations. Currently, the latest version is the third generation (PP-StructureV3). All the code and documentation on this page are based on the second generation (PP-StructureV2). **This directory will be removed at an appropriate time in the future, and maintenance for the second generation will be discontinued.** + +The third generation, PP-StructureV3, offers more powerful document analysis capabilities, and we strongly recommend users to use this version via the integrated wheel package. For a detailed introduction to PP-StructureV3, please refer to the [PP-StructureV3 Introduction Document](https://www.paddleocr.ai/latest/en/version3.x/algorithm/PP-StructureV3/PP-StructureV3.html); for specific usage instructions, please see the [PP-StructureV3 Usage Guide](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PP-StructureV3.html). + +In addition, we will discontinue maintenance of the KIE capability provided in the second generation. We recommend that you use the PP-ChatOCRv4 model in PaddleOCR 3.x, which offers more accurate key information extraction. For a detailed introduction to PP-ChatOCRv4, please refer to the [PP-ChatOCRv4 Introduction Document](https://www.paddleocr.ai/latest/en/version3.x/algorithm/PP-ChatOCRv4/PP-ChatOCRv4.html); for specific usage instructions, please see the [PP-ChatOCRv4 Usage Document](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PP-ChatOCRv4.html). + +## PP-Structure 升级事项 + +PP-Structure 系列模型自 2021 年推出以来,已经历三代版本的更新迭代。目前,最新版本为第三代(PP-StructureV3)。本页面的所有代码与文档均基于第二代(PP-StructureV2),**后续将在适当时机删除本目录,并停止对第二代的维护。** + +第三代 PP-StructureV3 提供了更为强大的文档解析能力,我们更推荐用户通过集成 wheel 包来使用该版本。关于 PP-StructureV3 的详细介绍,请参考[PP-StructureV3 介绍文档](https://www.paddleocr.ai/latest/version3.x/algorithm/PP-StructureV3/PP-StructureV3.html);具体的使用方法可参见[PP-StructureV3 使用文档](https://www.paddleocr.ai/latest/version3.x/pipeline_usage/PP-StructureV3.html)。 + +另外,在第二代提供的 KIE 能力,我们也将停止维护,推荐大家使用 PaddleOCR 3.x 版本的 PP-ChatOCRv4 模型方案,关键信息抽取更准确。关于 PP-ChatOCRv4 的详细介绍,请参考[PP-ChatOCRv4 介绍文档](https://www.paddleocr.ai/latest/version3.x/algorithm/PP-ChatOCRv4/PP-ChatOCRv4.html);具体的使用方法可参见[PP-ChatOCRv4 使用文档](https://www.paddleocr.ai/latest/version3.x/pipeline_usage/PP-ChatOCRv4.html)。 diff --git a/ppstructure/__init__.py b/ppstructure/__init__.py new file mode 100644 index 0000000..1d11e26 --- /dev/null +++ b/ppstructure/__init__.py @@ -0,0 +1,13 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/ppstructure/kie/README.md b/ppstructure/kie/README.md new file mode 100644 index 0000000..685ca9f --- /dev/null +++ b/ppstructure/kie/README.md @@ -0,0 +1,289 @@ +English | [简体中文](README_ch.md) + +# Key Information Extraction (KIE) + +- [1. Introduction](#1-introduction) +- [2. Performance](#2-performance) +- [3. Visualization](#3-visualization) + - [3.1 SER](#31-ser) + - [3.2 RE](#32-re) +- [4. Usage](#4-usage) + - [4.1 Prepare for the environment](#41-prepare-for-the-environment) + - [4.2 Quick start](#42-quick-start) + - [4.3 More](#43-more) +- [5. Reference](#5-reference) +- [6. License](#6-license) + + +## 1. Introduction + +Key information extraction (KIE) refers to extracting key information from text or images. As downstream task of OCR, the key information extraction task of document image has many practical application scenarios, such as form recognition, ticket information extraction, ID card information extraction, etc. + +PP-Structure conducts research based on the LayoutXLM multi-modal, and proposes the VI-LayoutXLM, which gets rid of visual features when finetuning the downstream tasks. An textline sorting method is also utilized to fit in reading order. What's more, UDML knowledge distillation is used for higher accuracy. Finally, the accuracy and inference speed of VI-LayoutXLM surpass those of LayoutXLM. + +The main features of the key information extraction module in PP-Structure are as follows. + + +- Integrate multi-modal methods such as [LayoutXLM](https://arxiv.org/pdf/2104.08836.pdf), VI-LayoutXLM, and PP-OCR inference engine. +- Supports Semantic Entity Recognition (SER) and Relation Extraction (RE) tasks based on multimodal methods. Based on the SER task, the text recognition and classification in the image can be completed; based on the RE task, the relationship extraction of the text content in the image can be completed, such as judging the problem pair (pair). +- Supports custom training for SER tasks and RE tasks. +- Supports end-to-end system prediction and evaluation of OCR+SER. +- Supports end-to-end system prediction of OCR+SER+RE. +- Support SER model export and inference using PaddleInference. + + +## 2. Performance + +We evaluate the methods on the Chinese dataset of [XFUND](https://github.com/doc-analysis/XFUND), and the performance is as follows + +|Model | Backbone | Task | Config file | Hmean | Inference time (ms) | Download link| +| --- | --- | --- | --- | --- | --- | --- | +|VI-LayoutXLM| VI-LayoutXLM-base | SER | [ser_vi_layoutxlm_xfund_zh_udml.yml](../../configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh_udml.yml)|**93.19%**| 15.49|[trained model](https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_pretrained.tar)| +|LayoutXLM| LayoutXLM-base | SER | [ser_layoutxlm_xfund_zh.yml](../../configs/kie/layoutlm_series/ser_layoutxlm_xfund_zh.yml)|90.38%| 19.49 | [trained model](https://paddleocr.bj.bcebos.com/pplayout/ser_LayoutXLM_xfun_zh.tar)| +|VI-LayoutXLM| VI-LayoutXLM-base | RE | [re_vi_layoutxlm_xfund_zh_udml.yml](../../configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh_udml.yml)|**83.92%**| 15.49|[trained model](https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_pretrained.tar)| +|LayoutXLM| LayoutXLM-base | RE | [re_layoutxlm_xfund_zh.yml](../../configs/kie/layoutlm_series/re_layoutxlm_xfund_zh.yml)|74.83%| 19.49|[trained model](https://paddleocr.bj.bcebos.com/pplayout/re_LayoutXLM_xfun_zh.tar)| + + +* Note:Inference environment:V100 GPU + cuda10.2 + cudnn8.1.1 + TensorRT 7.2.3.4,tested using fp16. + +For more KIE models in PaddleOCR, please refer to [KIE model zoo](../../doc/doc_en/algorithm_overview_en.md). + + +## 3. Visualization + +There are two main solutions to the key information extraction task based on VI-LayoutXLM series model. + +(1) Text detection + text recognition + semantic entity recognition (SER) + +(2) Text detection + text recognition + semantic entity recognition (SER) + relationship extraction (RE) + + +The following images are demo results of the SER and RE models. For more detailed introduction to the above solutions, please refer to [KIE Guide](./how_to_do_kie.md). + +### 3.1 SER + +Demo results for SER task are as follows. + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + + + +**Note:** test pictures are from [xfund dataset](https://github.com/doc-analysis/XFUND), [invoice dataset](https://aistudio.baidu.com/aistudio/datasetdetail/165561) and a composite ID card dataset. + + +Boxes of different colors in the image represent different categories. + +The invoice and application form images have three categories: `request`, `answer` and `header`. The `question` and `answer` can be used to extract the relationship. + +For the ID card image, the model can directly identify the key information such as `name`, `gender`, `nationality`, so that the subsequent relationship extraction process is not required, and the key information extraction task can be completed using only one model. + +### 3.2 RE + +Demo results for RE task are as follows. + + +
    + +
    + +
    + +
    + +
    + +
    + +Red boxes are questions, blue boxes are answers. The green lines means the two connected objects are a pair. + + +## 4. Usage + +### 4.1 Prepare for the environment + + +Use the following command to install KIE dependencies. + + +```bash +git clone https://github.com/PaddlePaddle/PaddleOCR.git +cd PaddleOCR +pip install -r requirements.txt +pip install -r ppstructure/kie/requirements.txt +# 安装PaddleOCR引擎用于预测 +pip install "paddleocr<3.0" +``` + +NOTE: For KIE tasks, it is necessary to downgrade the Paddle framework version (Paddle<2.6) and the PaddleNLP version (PaddleNLP<2.6). + +The visualized results of SER are saved in the `./output` folder by default. Examples of results are as follows. + + +
    + +
    + + +### 4.2 Quick start + +Here we use XFUND dataset to quickly experience the SER model and RE model. + + +#### 4.2.1 Prepare for the dataset + +```bash +mkdir train_data +cd train_data +# download and uncompress the dataset +wget https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar && tar -xf XFUND.tar +cd .. +``` + +#### 4.2.2 Predict images using the trained model + +Use the following command to download the models. + +```bash +mkdir pretrained_model +cd pretrained_model +# download and uncompress the SER trained model +wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_pretrained.tar && tar -xf ser_vi_layoutxlm_xfund_pretrained.tar + +# download and uncompress the RE trained model +wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_pretrained.tar && tar -xf re_vi_layoutxlm_xfund_pretrained.tar +``` + + +If you want to use OCR engine to obtain end-to-end prediction results, you can use the following command to predict. + +```bash +# just predict using SER trained model +python3 tools/infer_kie_token_ser.py \ + -c configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \ + -o Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy \ + Global.infer_img=./ppstructure/docs/kie/input/zh_val_42.jpg + +# predict using SER and RE trained model at the same time +python3 ./tools/infer_kie_token_ser_re.py \ + -c configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml \ + -o Architecture.Backbone.checkpoints=./pretrained_model/re_vi_layoutxlm_xfund_pretrained/best_accuracy \ + Global.infer_img=./train_data/XFUND/zh_val/image/zh_val_42.jpg \ + -c_ser configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \ + -o_ser Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy +``` + +The visual result images and the predicted text file will be saved in the `Global.save_res_path` directory. + +If you want to use a custom ocr model, you can set it through the following fields +- `Global.kie_det_model_dir`: the detection inference model path +- `Global.kie_rec_model_dir`: the recognition inference model path + + +If you want to load the text detection and recognition results collected before, you can use the following command to predict. + +```bash +# just predict using SER trained model +python3 tools/infer_kie_token_ser.py \ + -c configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \ + -o Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy \ + Global.infer_img=./train_data/XFUND/zh_val/val.json \ + Global.infer_mode=False + +# predict using SER and RE trained model at the same time +python3 ./tools/infer_kie_token_ser_re.py \ + -c configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml \ + -o Architecture.Backbone.checkpoints=./pretrained_model/re_vi_layoutxlm_xfund_pretrained/best_accuracy \ + Global.infer_img=./train_data/XFUND/zh_val/val.json \ + Global.infer_mode=False \ + -c_ser configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \ + -o_ser Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy +``` + +#### 4.2.3 Inference using PaddleInference + +Firstly, download the inference SER inference model. + +```bash +mkdir inference +cd inference +wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_infer.tar && tar -xf ser_vi_layoutxlm_xfund_infer.tar +wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_infer.tar && tar -xf re_vi_layoutxlm_xfund_infer.tar +cd .. +``` + +- SER + +Use the following command for inference. + + +```bash +cd ppstructure +python3 kie/predict_kie_token_ser.py \ + --kie_algorithm=LayoutXLM \ + --ser_model_dir=../inference/ser_vi_layoutxlm_xfund_infer \ + --image_dir=./docs/kie/input/zh_val_42.jpg \ + --ser_dict_path=../train_data/XFUND/class_list_xfun.txt \ + --vis_font_path=../doc/fonts/simfang.ttf \ + --ocr_order_method="tb-yx" +``` + +The visual results and text file will be saved in directory `output`. + +- RE + +Use the following command for inference. + + +```bash +cd ppstructure +python3 kie/predict_kie_token_ser_re.py \ + --kie_algorithm=LayoutXLM \ + --re_model_dir=../inference/re_vi_layoutxlm_xfund_infer \ + --ser_model_dir=../inference/ser_vi_layoutxlm_xfund_infer \ + --use_visual_backbone=False \ + --image_dir=./docs/kie/input/zh_val_42.jpg \ + --ser_dict_path=../train_data/XFUND/class_list_xfun.txt \ + --vis_font_path=../doc/fonts/simfang.ttf \ + --ocr_order_method="tb-yx" +``` + +The visual results and text file will be saved in directory `output`. + +If you want to use a custom ocr model, you can set it through the following fields +- `--det_model_dir`: the detection inference model path +- `--rec_model_dir`: the recognition inference model path + +### 4.3 More + +For training, evaluation and inference tutorial for KIE models, please refer to [KIE doc](../../doc/doc_en/kie_en.md). + +For training, evaluation and inference tutorial for text detection models, please refer to [text detection doc](../../doc/doc_en/detection_en.md). + +For training, evaluation and inference tutorial for text recognition models, please refer to [text recognition doc](../../doc/doc_en/recognition_en.md). + +To complete the key information extraction task in your own scenario from data preparation to model selection, please refer to: [Guide to End-to-end KIE](./how_to_do_kie_en.md)。 + + +## 5. Reference + +- LayoutXLM: Multimodal Pre-training for Multilingual Visually-rich Document Understanding, https://arxiv.org/pdf/2104.08836.pdf +- microsoft/unilm/layoutxlm, https://github.com/microsoft/unilm/tree/master/layoutxlm +- XFUND dataset, https://github.com/doc-analysis/XFUND + +## 6. License + +The content of this project itself is licensed under the [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/) diff --git a/ppstructure/kie/README_ch.md b/ppstructure/kie/README_ch.md new file mode 100644 index 0000000..e4b1a62 --- /dev/null +++ b/ppstructure/kie/README_ch.md @@ -0,0 +1,271 @@ +[English](README.md) | 简体中文 + +# 关键信息抽取 + +- [1. 简介](#1-简介) +- [2. 精度与性能](#2-精度与性能) +- [3. 效果演示](#3-效果演示) + - [3.1 SER](#31-ser) + - [3.2 RE](#32-re) +- [4. 使用](#4-使用) + - [4.1 准备环境](#41-准备环境) + - [4.2 快速开始](#42-快速开始) + - [4.3 更多](#43-更多) +- [5. 参考链接](#5-参考链接) +- [6. License](#6-License) + + +## 1. 简介 + +关键信息抽取 (Key Information Extraction, KIE)指的是是从文本或者图像中,抽取出关键的信息。针对文档图像的关键信息抽取任务作为OCR的下游任务,存在非常多的实际应用场景,如表单识别、车票信息抽取、身份证信息抽取等。 + +PP-Structure 基于 LayoutXLM 文档多模态系列方法进行研究与优化,设计了视觉特征无关的多模态模型结构VI-LayoutXLM,同时引入符合阅读顺序的文本行排序方法以及UDML联合互学习蒸馏方法,最终在精度与速度均超越LayoutXLM。 + +PP-Structure中关键信息抽取模块的主要特性如下: + +- 集成[LayoutXLM](https://arxiv.org/pdf/2104.08836.pdf)、VI-LayoutXLM等多模态模型以及PP-OCR预测引擎。 +- 支持基于多模态方法的语义实体识别 (Semantic Entity Recognition, SER) 以及关系抽取 (Relation Extraction, RE) 任务。基于 SER 任务,可以完成对图像中的文本识别与分类;基于 RE 任务,可以完成对图象中的文本内容的关系提取,如判断问题对(pair)。 +- 支持SER任务和RE任务的自定义训练。 +- 支持OCR+SER的端到端系统预测与评估。 +- 支持OCR+SER+RE的端到端系统预测。 +- 支持SER模型的动转静导出与基于PaddleInfernece的模型推理。 + + +## 2. 精度与性能 + + +我们在 [XFUND](https://github.com/doc-analysis/XFUND) 的中文数据集上对算法进行了评估,SER与RE上的任务性能如下 + +|模型|骨干网络|任务|配置文件|hmean|预测耗时(ms)|下载链接| +| --- | --- | --- | --- | --- | --- | --- | +|VI-LayoutXLM| VI-LayoutXLM-base | SER | [ser_vi_layoutxlm_xfund_zh_udml.yml](../../configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh_udml.yml)|**93.19%**| 15.49|[训练模型](https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_pretrained.tar)| +|LayoutXLM| LayoutXLM-base | SER | [ser_layoutxlm_xfund_zh.yml](../../configs/kie/layoutlm_series/ser_layoutxlm_xfund_zh.yml)|90.38%| 19.49 | [训练模型](https://paddleocr.bj.bcebos.com/pplayout/ser_LayoutXLM_xfun_zh.tar)| +|VI-LayoutXLM| VI-LayoutXLM-base | RE | [re_vi_layoutxlm_xfund_zh_udml.yml](../../configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh_udml.yml)|**83.92%**| 15.49|[训练模型](https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_pretrained.tar)| +|LayoutXLM| LayoutXLM-base | RE | [re_layoutxlm_xfund_zh.yml](../../configs/kie/layoutlm_series/re_layoutxlm_xfund_zh.yml)|74.83%| 19.49|[训练模型](https://paddleocr.bj.bcebos.com/pplayout/re_LayoutXLM_xfun_zh.tar)| + + +* 注:预测耗时测试条件:V100 GPU + cuda10.2 + cudnn8.1.1 + TensorRT 7.2.3.4,使用FP16进行测试。 + +更多关于PaddleOCR中关键信息抽取模型的介绍,请参考[关键信息抽取模型库](../../doc/doc_ch/algorithm_overview.md)。 + + +## 3. 效果演示 + +基于多模态模型的关键信息抽取任务有2种主要的解决方案。 + +(1)文本检测 + 文本识别 + 语义实体识别(SER) +(2)文本检测 + 文本识别 + 语义实体识别(SER) + 关系抽取(RE) + +下面给出SER与RE任务的示例效果,关于上述解决方案的详细介绍,请参考[关键信息抽取全流程指南](./how_to_do_kie.md)。 + +### 3.1 SER + +对于SER任务,效果如下所示。 + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +**注意:** 测试图片来源于[XFUND数据集](https://github.com/doc-analysis/XFUND)、[发票数据集](https://aistudio.baidu.com/aistudio/datasetdetail/165561)以及合成的身份证数据集。 + + +图中不同颜色的框表示不同的类别。 + +图中的发票以及申请表图像,有`QUESTION`, `ANSWER`, `HEADER` 3种类别,识别的`QUESTION`, `ANSWER`可以用于后续的问题与答案的关系抽取。 + +图中的身份证图像,则直接识别出其中的`姓名`、`性别`、`民族`等关键信息,这样就无需后续的关系抽取过程,一个模型即可完成关键信息抽取。 + + +### 3.2 RE + +对于RE任务,效果如下所示。 + +
    + +
    + +
    + +
    + +
    + +
    + + +红色框是问题,蓝色框是答案。绿色线条表示连接的两端为一个key-value的pair。 + +## 4. 使用 + +### 4.1 准备环境 + +使用下面的命令安装运行SER与RE关键信息抽取的依赖。 + +```bash +git clone https://github.com/PaddlePaddle/PaddleOCR.git +cd PaddleOCR +pip install -r requirements.txt +pip install -r ppstructure/kie/requirements.txt +# 安装PaddleOCR引擎用于预测 +pip install "paddleocr<3.0" +``` + +NOTE: 对于KIE任务需要降低Paddle框架版本(Paddle<2.6),和PaddleNLP版本(PaddleNLP<2.6)。 + +### 4.2 快速开始 + +下面XFUND数据集,快速体验SER模型与RE模型。 + +#### 4.2.1 准备数据 + +```bash +mkdir train_data +cd train_data +# 下载与解压数据 +wget https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar && tar -xf XFUND.tar +cd .. +``` + +#### 4.2.2 基于动态图的预测 + +首先下载模型。 + +```bash +mkdir pretrained_model +cd pretrained_model +# 下载并解压SER预训练模型 +wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_pretrained.tar && tar -xf ser_vi_layoutxlm_xfund_pretrained.tar + +# 下载并解压RE预训练模型 +wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_pretrained.tar && tar -xf re_vi_layoutxlm_xfund_pretrained.tar +``` + +如果希望使用OCR引擎,获取端到端的预测结果,可以使用下面的命令进行预测。 + +```bash +# 仅预测SER模型 +python3 tools/infer_kie_token_ser.py \ + -c configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \ + -o Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy \ + Global.infer_img=./ppstructure/docs/kie/input/zh_val_42.jpg + +# SER + RE模型串联 +python3 ./tools/infer_kie_token_ser_re.py \ + -c configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml \ + -o Architecture.Backbone.checkpoints=./pretrained_model/re_vi_layoutxlm_xfund_pretrained/best_accuracy \ + Global.infer_img=./train_data/XFUND/zh_val/image/zh_val_42.jpg \ + -c_ser configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \ + -o_ser Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy +``` + +`Global.save_res_path`目录中会保存可视化的结果图像以及预测的文本文件。 + +如果想使用自定义OCR模型,可通过如下字段进行设置 +- `Global.kie_det_model_dir`: 设置检测inference模型地址 +- `Global.kie_rec_model_dir`: 设置识别inference模型地址 + + +如果希望加载标注好的文本检测与识别结果,仅预测可以使用下面的命令进行预测。 + +```bash +# 仅预测SER模型 +python3 tools/infer_kie_token_ser.py \ + -c configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \ + -o Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy \ + Global.infer_img=./train_data/XFUND/zh_val/val.json \ + Global.infer_mode=False + +# SER + RE模型串联 +python3 ./tools/infer_kie_token_ser_re.py \ + -c configs/kie/vi_layoutxlm/re_vi_layoutxlm_xfund_zh.yml \ + -o Architecture.Backbone.checkpoints=./pretrained_model/re_vi_layoutxlm_xfund_pretrained/best_accuracy \ + Global.infer_img=./train_data/XFUND/zh_val/val.json \ + Global.infer_mode=False \ + -c_ser configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml \ + -o_ser Architecture.Backbone.checkpoints=./pretrained_model/ser_vi_layoutxlm_xfund_pretrained/best_accuracy +``` + +#### 4.2.3 基于PaddleInference的预测 + +首先下载SER和RE的推理模型。 + +```bash +mkdir inference +cd inference +wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_infer.tar && tar -xf ser_vi_layoutxlm_xfund_infer.tar +wget https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/re_vi_layoutxlm_xfund_infer.tar && tar -xf re_vi_layoutxlm_xfund_infer.tar +cd .. +``` + +- SER + +执行下面的命令进行预测。 + +```bash +cd ppstructure +python3 kie/predict_kie_token_ser.py \ + --kie_algorithm=LayoutXLM \ + --ser_model_dir=../inference/ser_vi_layoutxlm_xfund_infer \ + --image_dir=./docs/kie/input/zh_val_42.jpg \ + --ser_dict_path=../train_data/XFUND/class_list_xfun.txt \ + --vis_font_path=../doc/fonts/simfang.ttf \ + --ocr_order_method="tb-yx" +``` + +可视化结果保存在`output`目录下。 + +- RE + +执行下面的命令进行预测。 + +```bash +cd ppstructure +python3 kie/predict_kie_token_ser_re.py \ + --kie_algorithm=LayoutXLM \ + --re_model_dir=../inference/re_vi_layoutxlm_xfund_infer \ + --ser_model_dir=../inference/ser_vi_layoutxlm_xfund_infer \ + --use_visual_backbone=False \ + --image_dir=./docs/kie/input/zh_val_42.jpg \ + --ser_dict_path=../train_data/XFUND/class_list_xfun.txt \ + --vis_font_path=../doc/fonts/simfang.ttf \ + --ocr_order_method="tb-yx" +``` + +可视化结果保存在`output`目录下。 + +如果想使用自定义OCR模型,可通过如下字段进行设置 +- `--det_model_dir`: 设置检测inference模型地址 +- `--rec_model_dir`: 设置识别inference模型地址 + +### 4.3 更多 + +关于KIE模型的训练评估与推理,请参考:[关键信息抽取教程](../../doc/doc_ch/kie.md)。 + +关于文本检测模型的训练评估与推理,请参考:[文本检测教程](../../doc/doc_ch/detection.md)。 + +关于文本识别模型的训练评估与推理,请参考:[文本识别教程](../../doc/doc_ch/recognition.md)。 + +关于怎样在自己的场景中完成关键信息抽取任务,请参考:[关键信息抽取全流程指南](./how_to_do_kie.md)。 + + +## 5. 参考链接 + +- LayoutXLM: Multimodal Pre-training for Multilingual Visually-rich Document Understanding, https://arxiv.org/pdf/2104.08836.pdf +- microsoft/unilm/layoutxlm, https://github.com/microsoft/unilm/tree/master/layoutxlm +- XFUND dataset, https://github.com/doc-analysis/XFUND + +## 6. License + +The content of this project itself is licensed under the [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/) diff --git a/ppstructure/kie/how_to_do_kie.md b/ppstructure/kie/how_to_do_kie.md new file mode 100644 index 0000000..b2679fb --- /dev/null +++ b/ppstructure/kie/how_to_do_kie.md @@ -0,0 +1,168 @@ + +# 怎样完成基于图像数据的信息抽取任务 + +- [1. 简介](#1-简介) + - [1.1 背景](#11-背景) + - [1.2 主流方法](#12-主流方法) +- [2. 关键信息抽取任务流程](#2-关键信息抽取任务流程) + - [2.1 训练OCR模型](#21-训练OCR模型) + - [2.2 训练KIE模型](#22-训练KIE模型) +- [3. 参考文献](#3-参考文献) + + +## 1. 简介 + +### 1.1 背景 + +关键信息抽取 (Key Information Extraction, KIE)指的是是从文本或者图像中,抽取出关键的信息。针对文档图像的关键信息抽取任务作为OCR的下游任务,存在非常多的实际应用场景,如表单识别、车票信息抽取、身份证信息抽取等。然而,使用人力从这些文档图像中提取或者收集关键信息耗时费力,怎样自动化融合图像中的视觉、布局、文字等特征并完成关键信息抽取是一个价值与挑战并存的问题。 + +对于特定场景的文档图像,其中的关键信息位置、版式等较为固定,因此在研究早期有很多基于模板匹配的方法进行关键信息的抽取,考虑到其流程较为简单,该方法仍然被广泛应用在目前的很多场景中。但是这种基于模板匹配的方法在应用到不同的场景中时,需要耗费大量精力去调整与适配模板,迁移成本较高。 + +文档图像中的KIE一般包含2个子任务,示意图如下图所示。 + +* (1)SER: 语义实体识别 (Semantic Entity Recognition),对每一个检测到的文本进行分类,如将其分为姓名,身份证。如下图中的黑色框和红色框。 +* (2)RE: 关系抽取 (Relation Extraction),对每一个检测到的文本进行分类,如将其分为问题 (key) 和答案 (value) 。然后对每一个问题找到对应的答案,相当于完成key-value的匹配过程。如下图中的红色框和黑色框分别代表问题和答案,黄色线代表问题和答案之间的对应关系。 + + +
    + +
    + + +### 1.2 基于深度学习的主流方法 + +一般的KIE方法基于命名实体识别(Named Entity Recognition,NER)来展开研究,但是此类方法仅使用了文本信息而忽略了位置与视觉特征信息,因此精度受限。近几年大多学者开始融合多个模态的输入信息,进行特征融合,并对多模态信息进行处理,从而提升KIE的精度。主要方法有以下几种 + +* (1)基于Grid的方法:此类方法主要关注图像层面多模态信息的融合,文本大多大多为字符粒度,对文本与结构结构信息的嵌入方式较为简单,如Chargrid[1]等算法。 +* (2)基于Token的方法:此类方法参考NLP中的BERT等方法,将位置、视觉等特征信息共同编码到多模态模型中,并且在大规模数据集上进行预训练,从而在下游任务中,仅需要少量的标注数据便可以获得很好的效果。如LayoutLM[2], LayoutLMv2[3], LayoutXLM[4], StrucText[5]等算法。 +* (3)基于GCN的方法:此类方法尝试学习图像、文字之间的结构信息,从而可以解决开集信息抽取的问题(训练集中没有见过的模板),如GCN[6]、SDMGR[7]等算法。 +* (4)基于End-to-end的方法:此类方法将现有的OCR文字识别以及KIE信息抽取2个任务放在一个统一的网络中进行共同学习,并在学习过程中相互加强。如Trie[8]等算法。 + +更多关于该系列算法的详细介绍,请参考“动手学OCR·十讲”课程的课节六部分:[文档分析理论与实践](https://aistudio.baidu.com/aistudio/education/group/info/25207)。 + +## 2. 关键信息抽取任务流程 + +PaddleOCR中实现了LayoutXLM等算法(基于Token),同时,在PP-StructureV2中,对LayoutXLM多模态预训练模型的网络结构进行简化,去除了其中的Visual backbone部分,设计了视觉无关的VI-LayoutXLM模型,同时引入符合人类阅读顺序的排序逻辑以及UDML知识蒸馏策略,最终同时提升了关键信息抽取模型的精度与推理速度。 + +下面介绍怎样基于PaddleOCR完成关键信息抽取任务。 + +在非End-to-end的KIE方法中,完成关键信息抽取,至少需要**2个步骤**:首先使用OCR模型,完成文字位置与内容的提取,然后使用KIE模型,根据图像、文字位置以及文字内容,提取出其中的关键信息。 + +### 2.1 训练OCR模型 + +#### 2.1.1 文本检测 + +**(1)数据** + +PaddleOCR中提供的模型大多数为通用模型,在进行文本检测的过程中,相邻文本行的检测一般是根据位置的远近进行区分,如上图,使用PP-OCRv3通用中英文检测模型进行文本检测时,容易将”民族“与“汉”这2个代表不同的字段检测到一起,从而增加后续KIE任务的难度。因此建议在做KIE任务的过程中,首先训练一个针对该文档数据集的检测模型。 + +在数据标注时,关键信息的标注需要隔开,比上图中的 “民族汉” 3个字相隔较近,此时需要将”民族“与”汉“标注为2个文本检测框,否则会增加后续KIE任务的难度。 + +对于下游任务,一般来说,`200~300`张的文本训练数据即可保证基本的训练效果,如果没有太多的先验知识,可以先标注 **`200~300`** 张图片,进行后续文本检测模型的训练。 + + +**(2)模型** + +在模型选择方面,推荐使用PP-OCRv3_det,关于更多关于检测模型的训练方法介绍,请参考:[OCR文本检测模型训练教程](../../doc/doc_ch/detection.md)与[PP-OCRv3 文本检测模型训练教程](../../doc/doc_ch/PPOCRv3_det_train.md)。 + +#### 2.1.2 文本识别 + +相对自然场景,文档图像中的文本内容识别难度一般相对较低(背景相对不太复杂),因此**优先建议**尝试PaddleOCR中提供的PP-OCRv3通用文本识别模型([PP-OCRv3模型库链接](../../doc/doc_ch/models_list.md))。 + +**(1)数据** + +然而,在部分文档场景中也会存在一些挑战,如身份证场景中存在着罕见字,在发票等场景中的字体比较特殊,这些问题都会增加文本识别的难度,此时如果希望保证或者进一步提升模型的精度,建议基于特定文档场景的文本识别数据集,加载PP-OCRv3模型进行微调。 + +在模型微调的过程中,建议准备至少`5000`张垂类场景的文本识别图像,可以保证基本的模型微调效果。如果希望提升模型的精度与泛化能力,可以合成更多与该场景类似的文本识别数据,从公开数据集中收集通用真实文本识别数据,一并添加到该场景的文本识别训练任务过程中。在训练过程中,建议每个epoch的真实垂类数据、合成数据、通用数据比例在`1:1:1`左右,这可以通过设置不同数据源的采样比例进行控制。如有3个训练文本文件,分别包含1W、2W、5W条数据,那么可以在配置文件中设置数据如下: + +```yml +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/train_list_1W.txt + - ./train_data/train_list_2W.txt + - ./train_data/train_list_5W.txt + ratio_list: [1.0, 0.5, 0.2] + ... +``` + +**(2)模型** + +在模型选择方面,推荐使用通用中英文文本识别模型PP-OCRv3_rec,关于更多关于文本识别模型的训练方法介绍,请参考:[OCR文本识别模型训练教程](../../doc/doc_ch/recognition.md)与[PP-OCRv3文本识别模型库与配置文件](../../doc/doc_ch/models_list.md)。 + +### 2.2 训练KIE模型 + +对于识别得到的文字进行关键信息抽取,有2种主要的方法。 + +(1)直接使用SER,获取关键信息的类别:如身份证场景中,将“姓名“与”张三“分别标记为`name_key`与`name_value`。最终识别得到的类别为`name_value`对应的**文本字段**即为我们所需要的关键信息。 + +(2)联合SER与RE进行使用:这种方法中,首先使用SER,获取图像文字内容中所有的key与value,然后使用RE方法,对所有的key与value进行配对,找到映射关系,从而完成关键信息的抽取。 + +#### 2.2.1 SER + +以身份证场景为例, 关键信息一般包含`姓名`、`性别`、`民族`等,我们直接将对应的字段标注为特定的类别即可,如下图所示。 + +
    + +
    + +**注意:** + +- 标注过程中,对于无关于KIE关键信息的文本内容,均需要将其标注为`other`类别,相当于背景信息。如在身份证场景中,如果我们不关注性别信息,那么可以将“性别”与“男”这2个字段的类别均标注为`other`。 +- 标注过程中,需要以**文本行**为单位进行标注,无需标注单个字符的位置信息。 + +数据量方面,一般来说,对于比较固定的场景,**50张**左右的训练图片即可达到可以接受的效果,可以使用[PPOCRLabel](https://github.com/PFCCLab/PPOCRLabel/blob/main/README_ch.md)完成KIE的标注过程。 + +模型方面,推荐使用PP-StructureV2中提出的VI-LayoutXLM模型,它基于LayoutXLM模型进行改进,去除其中的视觉特征提取模块,在精度基本无损的情况下,进一步提升了模型推理速度。更多教程请参考:[VI-LayoutXLM算法介绍](../../doc/doc_ch/algorithm_kie_vi_layoutxlm.md)与[KIE关键信息抽取使用教程](../../doc/doc_ch/kie.md)。 + + +#### 2.2.2 SER + RE + +该过程主要包含SER与RE 2个过程。SER阶段主要用于识别出文档图像中的所有key与value,RE阶段主要用于对所有的key与value进行匹配。 + +以身份证场景为例, 关键信息一般包含`姓名`、`性别`、`民族`等关键信息,在SER阶段,我们需要识别所有的question (key) 与answer (value) 。标注如下所示。每个字段的类别信息(`label`字段)可以是question、answer或者other(与待抽取的关键信息无关的字段) + +
    + +
    + + +在RE阶段,需要标注每个字段的的id与连接信息,如下图所示。 + +
    + +
    + +每个文本行字段中,需要添加`id`与`linking`字段信息,`id`记录该文本行的唯一标识,同一张图片中的不同文本内容不能重复,`linking`是一个列表,记录了不同文本之间的连接信息。如字段“出生”的id为0,字段“1996年1月11日”的id为1,那么它们均有[[0, 1]]的`linking`标注,表示该id=0与id=1的字段构成key-value的关系(姓名、性别等字段类似,此处不再一一赘述)。 + + +**注意:** + +- 标注过程中,如果value是多个字符,那么linking中可以新增一个key-value对,如`[[0, 1], [0, 2]]` + + +数据量方面,一般来说,对于比较固定的场景,**50张**左右的训练图片即可达到可以接受的效果,可以使用PPOCRLabel完成KIE的标注过程。 + +模型方面,推荐使用PP-StructureV2中提出的VI-LayoutXLM模型,它基于LayoutXLM模型进行改进,去除其中的视觉特征提取模块,在精度基本无损的情况下,进一步提升了模型推理速度。更多教程请参考:[VI-LayoutXLM算法介绍](../../doc/doc_ch/algorithm_kie_vi_layoutxlm.md)与[KIE关键信息抽取使用教程](../../doc/doc_ch/kie.md)。 + + +## 3. 参考文献 + + +[1] Katti A R, Reisswig C, Guder C, et al. Chargrid: Towards understanding 2d documents[J]. arXiv preprint arXiv:1809.08799, 2018. + +[2] Xu Y, Li M, Cui L, et al. Layoutlm: Pre-training of text and layout for document image understanding[C]//Proceedings of the 26th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. 2020: 1192-1200. + +[3] Xu Y, Xu Y, Lv T, et al. LayoutLMv2: Multi-modal pre-training for visually-rich document understanding[J]. arXiv preprint arXiv:2012.14740, 2020. + +[4]: Xu Y, Lv T, Cui L, et al. Layoutxlm: Multimodal pre-training for multilingual visually-rich document understanding[J]. arXiv preprint arXiv:2104.08836, 2021. + +[5] Li Y, Qian Y, Yu Y, et al. StrucTexT: Structured Text Understanding with Multi-Modal Transformers[C]//Proceedings of the 29th ACM International Conference on Multimedia. 2021: 1912-1920. + +[6] Liu X, Gao F, Zhang Q, et al. Graph convolution for multimodal information extraction from visually rich documents[J]. arXiv preprint arXiv:1903.11279, 2019. + +[7] Sun H, Kuang Z, Yue X, et al. Spatial Dual-Modality Graph Reasoning for Key Information Extraction[J]. arXiv preprint arXiv:2103.14470, 2021. + +[8] Zhang P, Xu Y, Cheng Z, et al. Trie: End-to-end text reading and information extraction for document understanding[C]//Proceedings of the 28th ACM International Conference on Multimedia. 2020: 1413-1422. diff --git a/ppstructure/kie/how_to_do_kie_en.md b/ppstructure/kie/how_to_do_kie_en.md new file mode 100644 index 0000000..a86a9f8 --- /dev/null +++ b/ppstructure/kie/how_to_do_kie_en.md @@ -0,0 +1,179 @@ + +# Key Information Extraction Pipeline + +- [1. Introduction](#1-Introduction) + - [1.1 Background](#11-Background) + - [1.2 Mainstream Deep-learning Solutions](#12-Mainstream-Deep-learning-Solutions) +- [2. KIE Pipeline](#2-KIE-Pipeline) + - [2.1 Train OCR Models](#21-Train-OCR-Models) + - [2.2 Train KIE Models](#22-Train-KIE-Models) +- [3. Reference](#3-Reference) + + +## 1. Introduction + +### 1.1 Background + +Key information extraction (KIE) refers to extracting key information from text or images. As the downstream task of OCR, KIE of document image has many practical application scenarios, such as form recognition, ticket information extraction, ID card information extraction, etc. However, it is time-consuming and laborious to extract key information from these document images by manpower. It's challengable but also valuable to combine multi-modal features (visual, layout, text, etc) together and complete KIE tasks. + +For the document images in a specific scene, the position and layout of the key information are relatively fixed. Therefore, in the early stage of the research, there are many methods based on template matching to extract the key information. This method is still widely used in many simple scenarios at present. However, it takes long time to adjut the template for different scenarios. + + +The KIE in the document image generally contains 2 subtasks, which is as shown follows. + +* (1) SER: semantic entity recognition, which classifies each detected textline, such as dividing it into name and ID No. As shown in the red boxes in the following figure. + +* (2) RE: relationship extraction, which matches the question and answer based on SER results. As shown in the figure below, the yellow arrows match the question and answer. + +
    + +
    + + + +### 1.2 Mainstream Deep-learning Solutions + +General KIE methods are based on Named Entity Recognition (NER), but such methods only use text information and ignore location and visual feature information, which leads to limited accuracy. In recent years, most scholars have started to combine mutil-modal features to improve the accuracy of KIE model. The main methods are as follows: + +* (1) Grid based methods. These methods mainly focus on the fusion of multi-modal information at the image level. Most texts are of character granularity. The text and structure information embedding method is simple, such as the algorithm of chargrid [1]. + +* (2) Token based methods. These methods refer to the NLP methods such as Bert, which encode the position, vision and other feature information into the multi-modal model, and conduct pre-training on large-scale datasets, so that in downstream tasks, only a small amount of annotation data is required to obtain excellent results. The representative algorithms are layoutlm [2], layoutlmv2 [3], layoutxlm [4], structext [5], etc. + +* (3) GCN based methods. These methods try to learn the structural information between images and characters, so as to solve the problem of extracting open set information (templates not seen in the training set), such as GCN [6], SDMGR [7] and other algorithms. + +* (4) End to end based methods: these methods put the existing OCR character recognition and KIE information extraction tasks into a unified network for common learning, and strengthen each other in the learning process. Such as TRIE [8]. + + +For more detailed introduction of the algorithms, please refer to Chapter 6 of [Diving into OCR](https://aistudio.baidu.com/aistudio/education/group/info/25207). + +## 2. KIE Pipeline + +Token based methods such as LayoutXLM are implemented in PaddleOCR. What's more, in PP-StructureV2, we simplify the LayoutXLM model and proposed VI-LayoutXLM, in which the visual feature extraction module is removed for speed-up. The textline sorting strategy conforming to the human reading order and UDML knowledge distillation strategy are utilized for higher model accuracy. + + +In the non end-to-end KIE method, KIE needs at least **2 steps**. Firstly, the OCR model is used to extract the text and its position. Secondly, the KIE model is used to extract the key information according to the image, text position and text content. + + +### 2.1 Train OCR Models + +#### 2.1.1 Text Detection + +**(1) Data** + +Most of the models provided in PaddleOCR are general models. In the process of text detection, the detection of adjacent text lines is generally based on the distance of the position. As shown in the figure above, when using PP-OCRv3 general English detection model for text detection, it is easy to detect the two fields representing different properties as one. Therefore, it is suggested to finetune a detection model according to your scenario firstly during the KIE task. + + +During data annotation, the different key information needs to be separated. Otherwise, it will increase the difficulty of subsequent KIE tasks. + +For downstream tasks, generally speaking, `200~300` training images can guarantee the basic training effect. If there is not too much prior knowledge, **`200~300`** images can be labeled firstly for subsequent text detection model training. + +**(2) Model** + +In terms of model selection, PP-OCRv3 detection model is recommended. For more information about the training methods of the detection model, please refer to: [Text detection tutorial](../../doc/doc_en/detection_en.md) and [PP-OCRv3 detection model tutorial](../../doc/doc_ch/PPOCRv3_det_train.md). + +#### 2.1.2 Text recognition + + +Compared with the natural scene, the text recognition in the document image is generally relatively easier (the background is not too complex), so **it is suggested to** try the PP-OCRv3 general text recognition model provided in PaddleOCR ([PP-OCRv3 model list](../../doc/doc_en/models_list_en.md)) + + +**(1) Data** + +However, there are also some challenges in some document scenarios, such as rare words in ID card scenarios and special fonts in invoice and other scenarios. These problems will increase the difficulty of text recognition. At this time, if you want to ensure or further improve the model accuracy, it is recommended to load PP-OCRv3 model based on the text recognition dataset of specific document scenarios for finetuning. + +In the process of model finetuning, it is recommended to prepare at least `5000` vertical scene text recognition images to ensure the basic model fine-tuning effect. If you want to improve the accuracy and generalization ability of the model, you can synthesize more text recognition images similar to the scene, collect general real text recognition data from the public data set, and add them to the text recognition training process. In the training process, it is suggested that the ratio of real data, synthetic data and general data of each epoch should be around `1:1:1`, which can be controlled by setting the sampling ratio of different data sources. If there are 3 training text files, including 10k, 20k and 50k pieces of data respectively, the data can be set in the configuration file as follows: + +```yml +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/train_list_10k.txt + - ./train_data/train_list_10k.txt + - ./train_data/train_list_50k.txt + ratio_list: [1.0, 0.5, 0.2] + ... +``` + +**(2) Model** + +In terms of model selection, PP-OCRv3 recognition model is recommended. For more information about the training methods of the recognition model, please refer to: [Text recognition tutorial](../../doc/doc_en/recognition_en.md) and [PP-OCRv3 model list](../../doc/doc_en/models_list_en.md). + + +### 2.2 Train KIE Models + +There are two main methods to extract the key information from the recognized texts. + +(1) Directly use SER model to obtain the key information category. For example, in the ID card scenario, we mark "name" and "Geoff Sample" as "name_key" and "name_value", respectively. The **text field** corresponding to the category "name_value" finally identified is the key information we need. + +(2) Joint use SER and RE models. For this case, we firstly use SER model to obtain all questions (keys) and questions (values) for the image text, and then use RE model to match all keys and values to find the relationship, so as to complete the extraction of key information. + +#### 2.2.1 SER + +Take the ID card scenario as an example. The key information generally includes `name`, `DOB`, etc. We can directly mark the corresponding fields as specific categories, as shown in the following figure. + +
    + +
    + +**Note:** + +- In the labeling process, text content without key information about KIE shall be labeled as`other`, which is equivalent to background information. For example, in the ID card scenario, if we do not pay attention to `DOB` information, we can mark the categories of `DOB` and `Area manager` as `other`. +- In the annotation process of, it is required to annotate the **textline** position rather than the character. + + +In terms of data, generally speaking, for relatively fixed scenes, **50** training images can achieve acceptable effects. You can refer to [PPOCRLabel](https://github.com/PFCCLab/PPOCRLabel/blob/main/README.md) for finish the labeling process. + +In terms of model, it is recommended to use the VI-layoutXLM model proposed in PP-StructureV2. It is improved based on the LayoutXLM model, removing the visual feature extraction module, and further improving the model inference speed without the significant reduction on model accuracy. For more tutorials, please refer to [VI-LayoutXLM introduction](../../doc/doc_en/algorithm_kie_vi_layoutxlm_en.md) and [KIE tutorial](../../doc/doc_en/kie_en.md). + + +#### 2.2.2 SER + RE + +The SER model is mainly used to identify all keys and values in the document image, and the RE model is mainly used to match all keys and values. + +Taking the ID card scenario as an example, the key information generally includes key information such as `name`, `DOB`, etc. in the SER stage, we need to identify all questions (keys) and answers (values). The demo annotation is as follows. All keys can be annotated as `question`, and all values can be annotated as `answer`. + + +
    + +
    + + +In the RE stage, the ID and connection information of each field need to be marked, as shown in the following figure. + +
    + +
    + +For each textline, you need to add 'ID' and 'linking' field information. The 'ID' records the unique identifier of the textline. Different text contents in the same images cannot be repeated. The 'linking' is a list that records the connection information between different texts. If the ID of the field "name" is 0 and the ID of the field "Geoff Sample" is 1, then they all have [[0, 1]] 'linking' marks, indicating that the fields with `id=0` and `id=1` form a key value relationship (the fields such as DOB and Expires are similar, and will not be repeated here). + + +**Note:** + +During annotation, if value is multiple text lines, a key-value pair can be added in linking, such as `[[0, 1], [0, 2]]`. + +In terms of data, generally speaking, for relatively fixed scenes, about **50** training images can achieve acceptable effects. + +In terms of model, it is recommended to use the VI-layoutXLM model proposed in PP-StructureV2. It is improved based on the LayoutXLM model, removing the visual feature extraction module, and further improving the model inference speed without the significant reduction on model accuracy. For more tutorials, please refer to [VI-LayoutXLM introduction](../../doc/doc_en/algorithm_kie_vi_layoutxlm_en.md) and [KIE tutorial](../../doc/doc_en/kie_en.md). + + + +## 3. Reference + + +[1] Katti A R, Reisswig C, Guder C, et al. Chargrid: Towards understanding 2d documents[J]. arXiv preprint arXiv:1809.08799, 2018. + +[2] Xu Y, Li M, Cui L, et al. Layoutlm: Pre-training of text and layout for document image understanding[C]//Proceedings of the 26th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. 2020: 1192-1200. + +[3] Xu Y, Xu Y, Lv T, et al. LayoutLMv2: Multi-modal pre-training for visually-rich document understanding[J]. arXiv preprint arXiv:2012.14740, 2020. + +[4]: Xu Y, Lv T, Cui L, et al. Layoutxlm: Multimodal pre-training for multilingual visually-rich document understanding[J]. arXiv preprint arXiv:2104.08836, 2021. + +[5] Li Y, Qian Y, Yu Y, et al. StrucTexT: Structured Text Understanding with Multi-Modal Transformers[C]//Proceedings of the 29th ACM International Conference on Multimedia. 2021: 1912-1920. + +[6] Liu X, Gao F, Zhang Q, et al. Graph convolution for multimodal information extraction from visually rich documents[J]. arXiv preprint arXiv:1903.11279, 2019. + +[7] Sun H, Kuang Z, Yue X, et al. Spatial Dual-Modality Graph Reasoning for Key Information Extraction[J]. arXiv preprint arXiv:2103.14470, 2021. + +[8] Zhang P, Xu Y, Cheng Z, et al. Trie: End-to-end text reading and information extraction for document understanding[C]//Proceedings of the 28th ACM International Conference on Multimedia. 2020: 1413-1422. diff --git a/ppstructure/kie/predict_kie_token_ser.py b/ppstructure/kie/predict_kie_token_ser.py new file mode 100644 index 0000000..9508e60 --- /dev/null +++ b/ppstructure/kie/predict_kie_token_ser.py @@ -0,0 +1,191 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import json +import numpy as np +import time + +import tools.infer.utility as utility +from ppocr.data import create_operators, transform +from ppocr.postprocess import build_post_process +from ppocr.utils.logging import get_logger +from ppocr.utils.visual import draw_ser_results +from ppocr.utils.utility import get_image_file_list, check_and_read +from ppstructure.utility import parse_args + +from paddleocr import PaddleOCR + +logger = get_logger() + + +class SerPredictor(object): + def __init__(self, args): + self.args = args + self.ocr_engine = PaddleOCR( + use_angle_cls=args.use_angle_cls, + det_model_dir=args.det_model_dir, + rec_model_dir=args.rec_model_dir, + show_log=False, + use_gpu=args.use_gpu, + ) + + pre_process_list = [ + { + "VQATokenLabelEncode": { + "algorithm": args.kie_algorithm, + "class_path": args.ser_dict_path, + "contains_re": False, + "ocr_engine": self.ocr_engine, + "order_method": args.ocr_order_method, + } + }, + {"VQATokenPad": {"max_seq_len": 512, "return_attention_mask": True}}, + {"VQASerTokenChunk": {"max_seq_len": 512, "return_attention_mask": True}}, + {"Resize": {"size": [224, 224]}}, + { + "NormalizeImage": { + "std": [58.395, 57.12, 57.375], + "mean": [123.675, 116.28, 103.53], + "scale": "1", + "order": "hwc", + } + }, + {"ToCHWImage": None}, + { + "KeepKeys": { + "keep_keys": [ + "input_ids", + "bbox", + "attention_mask", + "token_type_ids", + "image", + "labels", + "segment_offset_id", + "ocr_info", + "entities", + ] + } + }, + ] + postprocess_params = { + "name": "VQASerTokenLayoutLMPostProcess", + "class_path": args.ser_dict_path, + } + + self.preprocess_op = create_operators(pre_process_list, {"infer_mode": True}) + self.postprocess_op = build_post_process(postprocess_params) + ( + self.predictor, + self.input_tensor, + self.output_tensors, + self.config, + ) = utility.create_predictor(args, "ser", logger) + + def __call__(self, img): + ori_im = img.copy() + data = {"image": img} + data = transform(data, self.preprocess_op) + if data[0] is None: + return None, 0 + starttime = time.time() + + for idx in range(len(data)): + if isinstance(data[idx], np.ndarray): + data[idx] = np.expand_dims(data[idx], axis=0) + else: + data[idx] = [data[idx]] + if self.args.use_onnx: + input_tensor = { + name: data[idx] for idx, name in enumerate(self.input_tensor) + } + self.output_tensors = self.predictor.run(None, input_tensor) + else: + for idx in range(len(self.input_tensor)): + self.input_tensor[idx].copy_from_cpu(data[idx]) + + self.predictor.run() + + outputs = [] + for output_tensor in self.output_tensors: + output = ( + output_tensor if self.args.use_onnx else output_tensor.copy_to_cpu() + ) + outputs.append(output) + preds = outputs[0] + + post_result = self.postprocess_op( + preds, segment_offset_ids=data[6], ocr_infos=data[7] + ) + elapse = time.time() - starttime + return post_result, data, elapse + + +def main(args): + image_file_list = get_image_file_list(args.image_dir) + ser_predictor = SerPredictor(args) + count = 0 + total_time = 0 + + os.makedirs(args.output, exist_ok=True) + with open( + os.path.join(args.output, "infer.txt"), mode="w", encoding="utf-8" + ) as f_w: + for image_file in image_file_list: + img, flag, _ = check_and_read(image_file) + if not flag: + img = cv2.imread(image_file) + img = img[:, :, ::-1] + if img is None: + logger.info("error in loading image:{}".format(image_file)) + continue + ser_res, _, elapse = ser_predictor(img) + ser_res = ser_res[0] + + res_str = "{}\t{}\n".format( + image_file, + json.dumps( + { + "ocr_info": ser_res, + }, + ensure_ascii=False, + ), + ) + f_w.write(res_str) + + img_res = draw_ser_results( + image_file, + ser_res, + font_path=args.vis_font_path, + ) + + img_save_path = os.path.join(args.output, os.path.basename(image_file)) + cv2.imwrite(img_save_path, img_res) + logger.info("save vis result to {}".format(img_save_path)) + if count > 0: + total_time += elapse + count += 1 + logger.info("Predict time of {}: {}".format(image_file, elapse)) + + +if __name__ == "__main__": + main(parse_args()) diff --git a/ppstructure/kie/predict_kie_token_ser_re.py b/ppstructure/kie/predict_kie_token_ser_re.py new file mode 100644 index 0000000..ebf5988 --- /dev/null +++ b/ppstructure/kie/predict_kie_token_ser_re.py @@ -0,0 +1,144 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import json +import numpy as np +import time + +import tools.infer.utility as utility +from tools.infer_kie_token_ser_re import make_input +from ppocr.postprocess import build_post_process +from ppocr.utils.logging import get_logger +from ppocr.utils.visual import draw_ser_results, draw_re_results +from ppocr.utils.utility import get_image_file_list, check_and_read +from ppstructure.utility import parse_args +from ppstructure.kie.predict_kie_token_ser import SerPredictor + +logger = get_logger() + + +class SerRePredictor(object): + def __init__(self, args): + self.use_visual_backbone = args.use_visual_backbone + self.ser_engine = SerPredictor(args) + if args.re_model_dir is not None: + postprocess_params = {"name": "VQAReTokenLayoutLMPostProcess"} + self.postprocess_op = build_post_process(postprocess_params) + ( + self.predictor, + self.input_tensor, + self.output_tensors, + self.config, + ) = utility.create_predictor(args, "re", logger) + else: + self.predictor = None + + def __call__(self, img): + starttime = time.time() + ser_results, ser_inputs, ser_elapse = self.ser_engine(img) + if self.predictor is None: + return ser_results, ser_elapse + + re_input, entity_idx_dict_batch = make_input(ser_inputs, ser_results) + if self.use_visual_backbone == False: + re_input.pop(4) + for idx in range(len(self.input_tensor)): + self.input_tensor[idx].copy_from_cpu(re_input[idx]) + + self.predictor.run() + outputs = [] + for output_tensor in self.output_tensors: + output = output_tensor.copy_to_cpu() + outputs.append(output) + preds = dict( + loss=outputs[1], + pred_relations=outputs[2], + hidden_states=outputs[0], + ) + + post_result = self.postprocess_op( + preds, ser_results=ser_results, entity_idx_dict_batch=entity_idx_dict_batch + ) + + elapse = time.time() - starttime + return post_result, elapse + + +def main(args): + image_file_list = get_image_file_list(args.image_dir) + ser_re_predictor = SerRePredictor(args) + count = 0 + total_time = 0 + + os.makedirs(args.output, exist_ok=True) + with open( + os.path.join(args.output, "infer.txt"), mode="w", encoding="utf-8" + ) as f_w: + for image_file in image_file_list: + img, flag, _ = check_and_read(image_file) + if not flag: + img = cv2.imread(image_file) + img = img[:, :, ::-1] + if img is None: + logger.info("error in loading image:{}".format(image_file)) + continue + re_res, elapse = ser_re_predictor(img) + re_res = re_res[0] + + res_str = "{}\t{}\n".format( + image_file, + json.dumps( + { + "ocr_info": re_res, + }, + ensure_ascii=False, + ), + ) + f_w.write(res_str) + if ser_re_predictor.predictor is not None: + img_res = draw_re_results( + image_file, re_res, font_path=args.vis_font_path + ) + img_save_path = os.path.join( + args.output, + os.path.splitext(os.path.basename(image_file))[0] + "_ser_re.jpg", + ) + else: + img_res = draw_ser_results( + image_file, re_res, font_path=args.vis_font_path + ) + img_save_path = os.path.join( + args.output, + os.path.splitext(os.path.basename(image_file))[0] + "_ser.jpg", + ) + + cv2.imwrite(img_save_path, img_res) + logger.info("save vis result to {}".format(img_save_path)) + if count > 0: + total_time += elapse + count += 1 + logger.info("Predict time of {}: {}".format(image_file, elapse)) + + +if __name__ == "__main__": + main(parse_args()) diff --git a/ppstructure/kie/requirements.txt b/ppstructure/kie/requirements.txt new file mode 100644 index 0000000..500026e --- /dev/null +++ b/ppstructure/kie/requirements.txt @@ -0,0 +1,7 @@ +sentencepiece +yacs +seqeval +pypandoc +attrdict3 +python_docx +paddlenlp==2.5.2 diff --git a/ppstructure/kie/tools/eval_with_label_end2end.py b/ppstructure/kie/tools/eval_with_label_end2end.py new file mode 100644 index 0000000..f97da12 --- /dev/null +++ b/ppstructure/kie/tools/eval_with_label_end2end.py @@ -0,0 +1,259 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re +import sys +import shapely +from shapely.geometry import Polygon +import numpy as np +from collections import defaultdict +import operator +from rapidfuzz.distance import Levenshtein +import argparse +import json +import copy + + +def parse_ser_results_fp(fp, fp_type="gt", ignore_background=True): + # img/zh_val_0.jpg { + # "height": 3508, + # "width": 2480, + # "ocr_info": [ + # {"text": "Maribyrnong", "label": "other", "bbox": [1958, 144, 2184, 198]}, + # {"text": "CITYCOUNCIL", "label": "other", "bbox": [2052, 183, 2171, 214]}, + # ] + assert fp_type in ["gt", "pred"] + key = "label" if fp_type == "gt" else "pred" + res_dict = dict() + with open(fp, "r", encoding="utf-8") as fin: + lines = fin.readlines() + + for _, line in enumerate(lines): + img_path, info = line.strip().split("\t") + # get key + image_name = os.path.basename(img_path) + res_dict[image_name] = [] + # get infos + json_info = json.loads(info) + for single_ocr_info in json_info["ocr_info"]: + label = single_ocr_info[key].upper() + if label in ["O", "OTHERS", "OTHER"]: + label = "O" + if ignore_background and label == "O": + continue + single_ocr_info["label"] = label + res_dict[image_name].append(copy.deepcopy(single_ocr_info)) + return res_dict + + +def polygon_from_str(polygon_points): + """ + Create a shapely polygon object from gt or dt line. + """ + polygon_points = np.array(polygon_points).reshape(4, 2) + polygon = Polygon(polygon_points).convex_hull + return polygon + + +def polygon_iou(poly1, poly2): + """ + Intersection over union between two shapely polygons. + """ + if not poly1.intersects(poly2): # this test is fast and can accelerate calculation + iou = 0 + else: + try: + inter_area = poly1.intersection(poly2).area + union_area = poly1.area + poly2.area - inter_area + iou = float(inter_area) / union_area + except shapely.geos.TopologicalError: + # except Exception as e: + # print(e) + print("shapely.geos.TopologicalError occurred, iou set to 0") + iou = 0 + return iou + + +def ed(args, str1, str2): + if args.ignore_space: + str1 = str1.replace(" ", "") + str2 = str2.replace(" ", "") + if args.ignore_case: + str1 = str1.lower() + str2 = str2.lower() + return Levenshtein.distance(str1, str2) + + +def convert_bbox_to_polygon(bbox): + """ + bbox : [x1, y1, x2, y2] + output: [[x1, y1], [x2, y2], [x3, y3], [x4, y4]] + """ + xmin, ymin, xmax, ymax = bbox + poly = [[xmin, ymin], [xmax, ymin], [xmax, ymax], [xmin, ymax]] + return poly + + +def eval_e2e(args): + # gt + gt_results = parse_ser_results_fp(args.gt_json_path, "gt", args.ignore_background) + # pred + dt_results = parse_ser_results_fp( + args.pred_json_path, "pred", args.ignore_background + ) + iou_thresh = args.iou_thres + num_gt_chars = 0 + gt_count = 0 + dt_count = 0 + hit = 0 + ed_sum = 0 + + for img_name in dt_results: + gt_info = gt_results[img_name] + gt_count += len(gt_info) + + dt_info = dt_results[img_name] + dt_count += len(dt_info) + + dt_match = [False] * len(dt_info) + gt_match = [False] * len(gt_info) + + all_ious = defaultdict(tuple) + # gt: {text, label, bbox or poly} + for index_gt, gt in enumerate(gt_info): + if "poly" not in gt: + gt["poly"] = convert_bbox_to_polygon(gt["bbox"]) + gt_poly = polygon_from_str(gt["poly"]) + for index_dt, dt in enumerate(dt_info): + if "poly" not in dt: + dt["poly"] = convert_bbox_to_polygon(dt["bbox"]) + dt_poly = polygon_from_str(dt["poly"]) + iou = polygon_iou(dt_poly, gt_poly) + if iou >= iou_thresh: + all_ious[(index_gt, index_dt)] = iou + sorted_ious = sorted(all_ious.items(), key=operator.itemgetter(1), reverse=True) + sorted_gt_dt_pairs = [item[0] for item in sorted_ious] + + # matched gt and dt + for gt_dt_pair in sorted_gt_dt_pairs: + index_gt, index_dt = gt_dt_pair + if gt_match[index_gt] == False and dt_match[index_dt] == False: + gt_match[index_gt] = True + dt_match[index_dt] = True + # ocr rec results + gt_text = gt_info[index_gt]["text"] + dt_text = dt_info[index_dt]["text"] + + # ser results + gt_label = gt_info[index_gt]["label"] + dt_label = dt_info[index_dt]["pred"] + + if True: # ignore_masks[index_gt] == '0': + ed_sum += ed(args, gt_text, dt_text) + num_gt_chars += len(gt_text) + if gt_text == dt_text: + if args.ignore_ser_prediction or gt_label == dt_label: + hit += 1 + + # unmatched dt + for tindex, dt_match_flag in enumerate(dt_match): + if dt_match_flag == False: + dt_text = dt_info[tindex]["text"] + gt_text = "" + ed_sum += ed(args, dt_text, gt_text) + + # unmatched gt + for tindex, gt_match_flag in enumerate(gt_match): + if gt_match_flag == False: + dt_text = "" + gt_text = gt_info[tindex]["text"] + ed_sum += ed(args, gt_text, dt_text) + num_gt_chars += len(gt_text) + + eps = 1e-9 + print("config: ", args) + print("hit, dt_count, gt_count", hit, dt_count, gt_count) + precision = hit / (dt_count + eps) + recall = hit / (gt_count + eps) + fmeasure = 2.0 * precision * recall / (precision + recall + eps) + avg_edit_dist_img = ed_sum / len(gt_results) + avg_edit_dist_field = ed_sum / (gt_count + eps) + character_acc = 1 - ed_sum / (num_gt_chars + eps) + + print("character_acc: %.2f" % (character_acc * 100) + "%") + print("avg_edit_dist_field: %.2f" % (avg_edit_dist_field)) + print("avg_edit_dist_img: %.2f" % (avg_edit_dist_img)) + print("precision: %.2f" % (precision * 100) + "%") + print("recall: %.2f" % (recall * 100) + "%") + print("fmeasure: %.2f" % (fmeasure * 100) + "%") + + return + + +def parse_args(): + """ """ + + def str2bool(v): + return v.lower() in ("true", "t", "1") + + parser = argparse.ArgumentParser() + ## Required parameters + parser.add_argument( + "--gt_json_path", + default=None, + type=str, + required=True, + ) + parser.add_argument( + "--pred_json_path", + default=None, + type=str, + required=True, + ) + + parser.add_argument("--iou_thres", default=0.5, type=float) + + parser.add_argument( + "--ignore_case", + default=False, + type=str2bool, + help="whether to do lower case for the strs", + ) + + parser.add_argument( + "--ignore_space", default=True, type=str2bool, help="whether to ignore space" + ) + + parser.add_argument( + "--ignore_background", + default=True, + type=str2bool, + help="whether to ignore other label", + ) + + parser.add_argument( + "--ignore_ser_prediction", + default=False, + type=str2bool, + help="whether to ignore ocr pred results", + ) + + args = parser.parse_args() + return args + + +if __name__ == "__main__": + args = parse_args() + eval_e2e(args) diff --git a/ppstructure/kie/tools/trans_funsd_label.py b/ppstructure/kie/tools/trans_funsd_label.py new file mode 100644 index 0000000..104cb68 --- /dev/null +++ b/ppstructure/kie/tools/trans_funsd_label.py @@ -0,0 +1,166 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys +import cv2 +import numpy as np +from copy import deepcopy + + +def trans_poly_to_bbox(poly): + x1 = np.min([p[0] for p in poly]) + x2 = np.max([p[0] for p in poly]) + y1 = np.min([p[1] for p in poly]) + y2 = np.max([p[1] for p in poly]) + return [x1, y1, x2, y2] + + +def get_outer_poly(bbox_list): + x1 = min([bbox[0] for bbox in bbox_list]) + y1 = min([bbox[1] for bbox in bbox_list]) + x2 = max([bbox[2] for bbox in bbox_list]) + y2 = max([bbox[3] for bbox in bbox_list]) + return [[x1, y1], [x2, y1], [x2, y2], [x1, y2]] + + +def load_funsd_label(image_dir, anno_dir): + imgs = os.listdir(image_dir) + annos = os.listdir(anno_dir) + + imgs = [img.replace(".png", "") for img in imgs] + annos = [anno.replace(".json", "") for anno in annos] + + fn_info_map = dict() + for anno_fn in annos: + res = [] + with open(os.path.join(anno_dir, anno_fn + ".json"), "r") as fin: + infos = json.load(fin) + infos = infos["form"] + old_id2new_id_map = dict() + global_new_id = 0 + for info in infos: + if info["text"] is None: + continue + words = info["words"] + if len(words) <= 0: + continue + word_idx = 1 + curr_bboxes = [words[0]["box"]] + curr_texts = [words[0]["text"]] + while word_idx < len(words): + # switch to a new link + if words[word_idx]["box"][0] + 10 <= words[word_idx - 1]["box"][2]: + if len("".join(curr_texts[0])) > 0: + res.append( + { + "transcription": " ".join(curr_texts), + "label": info["label"], + "points": get_outer_poly(curr_bboxes), + "linking": info["linking"], + "id": global_new_id, + } + ) + if info["id"] not in old_id2new_id_map: + old_id2new_id_map[info["id"]] = [] + old_id2new_id_map[info["id"]].append(global_new_id) + global_new_id += 1 + curr_bboxes = [words[word_idx]["box"]] + curr_texts = [words[word_idx]["text"]] + else: + curr_bboxes.append(words[word_idx]["box"]) + curr_texts.append(words[word_idx]["text"]) + word_idx += 1 + if len("".join(curr_texts[0])) > 0: + res.append( + { + "transcription": " ".join(curr_texts), + "label": info["label"], + "points": get_outer_poly(curr_bboxes), + "linking": info["linking"], + "id": global_new_id, + } + ) + if info["id"] not in old_id2new_id_map: + old_id2new_id_map[info["id"]] = [] + old_id2new_id_map[info["id"]].append(global_new_id) + global_new_id += 1 + res = sorted(res, key=lambda r: (r["points"][0][1], r["points"][0][0])) + for i in range(len(res) - 1): + for j in range(i, 0, -1): + if abs( + res[j + 1]["points"][0][1] - res[j]["points"][0][1] + ) < 20 and (res[j + 1]["points"][0][0] < res[j]["points"][0][0]): + tmp = deepcopy(res[j]) + res[j] = deepcopy(res[j + 1]) + res[j + 1] = deepcopy(tmp) + else: + break + # re-generate unique ids + for idx, r in enumerate(res): + new_links = [] + for link in r["linking"]: + # illegal links will be removed + if ( + link[0] not in old_id2new_id_map + or link[1] not in old_id2new_id_map + ): + continue + for src in old_id2new_id_map[link[0]]: + for dst in old_id2new_id_map[link[1]]: + new_links.append([src, dst]) + res[idx]["linking"] = deepcopy(new_links) + + fn_info_map[anno_fn] = res + + return fn_info_map + + +def main(): + test_image_dir = "train_data/FUNSD/testing_data/images/" + test_anno_dir = "train_data/FUNSD/testing_data/annotations/" + test_output_dir = "train_data/FUNSD/test.json" + + fn_info_map = load_funsd_label(test_image_dir, test_anno_dir) + with open(test_output_dir, "w") as fout: + for fn in fn_info_map: + fout.write( + fn + + ".png" + + "\t" + + json.dumps(fn_info_map[fn], ensure_ascii=False) + + "\n" + ) + + train_image_dir = "train_data/FUNSD/training_data/images/" + train_anno_dir = "train_data/FUNSD/training_data/annotations/" + train_output_dir = "train_data/FUNSD/train.json" + + fn_info_map = load_funsd_label(train_image_dir, train_anno_dir) + with open(train_output_dir, "w") as fout: + for fn in fn_info_map: + fout.write( + fn + + ".png" + + "\t" + + json.dumps(fn_info_map[fn], ensure_ascii=False) + + "\n" + ) + print("====ok====") + return + + +if __name__ == "__main__": + main() diff --git a/ppstructure/kie/tools/trans_xfun_data.py b/ppstructure/kie/tools/trans_xfun_data.py new file mode 100644 index 0000000..4e83b5a --- /dev/null +++ b/ppstructure/kie/tools/trans_xfun_data.py @@ -0,0 +1,64 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json + + +def transfer_xfun_data(json_path=None, output_file=None): + with open(json_path, "r", encoding="utf-8") as fin: + lines = fin.readlines() + + json_info = json.loads(lines[0]) + documents = json_info["documents"] + with open(output_file, "w", encoding="utf-8") as fout: + for idx, document in enumerate(documents): + label_info = [] + img_info = document["img"] + document = document["document"] + image_path = img_info["fname"] + + for doc in document: + x1, y1, x2, y2 = doc["box"] + points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]] + label_info.append( + { + "transcription": doc["text"], + "label": doc["label"], + "points": points, + "id": doc["id"], + "linking": doc["linking"], + } + ) + + fout.write( + image_path + "\t" + json.dumps(label_info, ensure_ascii=False) + "\n" + ) + + print("===ok====") + + +def parser_args(): + import argparse + + parser = argparse.ArgumentParser(description="args for paddleserving") + parser.add_argument( + "--ori_gt_path", type=str, required=True, help="origin xfun gt path" + ) + parser.add_argument("--output_path", type=str, required=True, help="path to save") + args = parser.parse_args() + return args + + +args = parser_args() +transfer_xfun_data(args.ori_gt_path, args.output_path) diff --git a/ppstructure/layout/README.md b/ppstructure/layout/README.md new file mode 100644 index 0000000..6830f8e --- /dev/null +++ b/ppstructure/layout/README.md @@ -0,0 +1,470 @@ +English | [简体中文](README_ch.md) + +# Layout analysis + +- [1. Introduction](#1-Introduction) +- [2. Quick start](#2-Quick-start) +- [3. Install](#3-Install) + - [3.1 Install PaddlePaddle](#31-Install-paddlepaddle) + - [3.2 Install PaddleDetection](#32-Install-paddledetection) +- [4. Data preparation](#4-Data-preparation) + - [4.1 English data set](#41-English-data-set) + - [4.2 More datasets](#42-More-datasets) +- [5. Start training](#5-Start-training) + - [5.1 Train](#51-Train) + - [5.2 FGD Distillation training](#52-Fgd-distillation-training) +- [6. Model evaluation and prediction](#6-Model-evaluation-and-prediction) + - [6.1 Indicator evaluation](#61-Indicator-evaluation) + - [6.2 Test layout analysis results](#62-Test-layout-analysis-results) +- [7. Model export and inference](#7-Model-export-and-inference) + - [7.1 Model export](#71-Model-export) + - [7.2 Model inference](#72-Model-inference) + + +## 1. Introduction + +Layout analysis refers to the regional division of documents in the form of pictures and the positioning of key areas, such as text, title, table, picture, etc. The layout analysis algorithm is based on the lightweight model PP-picodet of [PaddleDetection]( https://github.com/PaddlePaddle/PaddleDetection ), including English layout analysis, Chinese layout analysis and table layout analysis models. English layout analysis models can detect document layout elements such as text, title, table, figure, list. Chinese layout analysis models can detect document layout elements such as text, figure, figure caption, table, table caption, header, footer, reference, and equation. Table layout analysis models can detect table regions. + +
    + +
    + +## 2. Quick start +PP-Structure currently provides layout analysis models in Chinese, English and table documents. For the model link, see [models_list](../docs/models_list_en.md). The whl package is also provided for quick use, see [quickstart](../docs/quickstart_en.md) for details. + +## 3. Install + +### 3.1. Install PaddlePaddle + +- **(1) Install PaddlePaddle** + +```bash +python3 -m pip install --upgrade pip + +# GPU Install +python3 -m pip install "paddlepaddle-gpu>=2.3" -i https://mirror.baidu.com/pypi/simple + +# CPU Install +python3 -m pip install "paddlepaddle>=2.3" -i https://mirror.baidu.com/pypi/simple +``` +For more requirements, please refer to the instructions in the [Install file](https://www.paddlepaddle.org.cn/install/quick)。 + +### 3.2. Install PaddleDetection + +- **(1)Download PaddleDetection Source code** + +```bash +git clone https://github.com/PaddlePaddle/PaddleDetection.git +``` + +- **(2)Install third-party libraries** + +```bash +cd PaddleDetection +python3 -m pip install -r requirements.txt +``` + +## 4. Data preparation + +If you want to experience the prediction process directly, you can skip data preparation and download the pre-training model. + +### 4.1. English data set + +Download document analysis data set [PubLayNet](https://developer.ibm.com/exchanges/data/all/publaynet/)(Dataset 96G),contains 5 classes:`{0: "Text", 1: "Title", 2: "List", 3:"Table", 4:"Figure"}` + +``` +# Download data +wget https://dax-cdn.cdn.appdomain.cloud/dax-publaynet/1.0.0/publaynet.tar.gz +# Decompress data +tar -xvf publaynet.tar.gz +``` + +Uncompressed **directory structure:** + +``` +|-publaynet + |- test + |- PMC1277013_00004.jpg + |- PMC1291385_00002.jpg + | ... + |- train.json + |- train + |- PMC1291385_00002.jpg + |- PMC1277013_00004.jpg + | ... + |- val.json + |- val + |- PMC538274_00004.jpg + |- PMC539300_00004.jpg + | ... +``` + +**data distribution:** + +| File or Folder | Description | num | +| :------------- | :------------- | ------- | +| `train/` | Training set pictures | 335,703 | +| `val/` | Verification set pictures | 11,245 | +| `test/` | Test set pictures | 11,405 | +| `train.json` | Training set annotation files | - | +| `val.json` | Validation set dimension files | - | + +**Data Annotation** + +The JSON file contains the annotations of all images, and the data is stored in a dictionary nested manner.Contains the following keys: + +- info,represents the dimension file info。 + +- licenses,represents the dimension file licenses。 + +- images,represents the list of image information in the annotation file,each element is the information of an image。The information of one of the images is as follows: + + ``` + { + 'file_name': 'PMC4055390_00006.jpg', # file_name + 'height': 601, # image height + 'width': 792, # image width + 'id': 341427 # image id + } + ``` + +- annotations, represents the list of annotation information of the target object in the annotation file,each element is the annotation information of a target object。The following is the annotation information of one of the target objects: + + ``` + { + + 'segmentation': # Segmentation annotation of objects + 'area': 60518.099043117836, # Area of object + 'iscrowd': 0, # iscrowd + 'image_id': 341427, # image id + 'bbox': [50.58, 490.86, 240.15, 252.16], # bbox [x1,y1,w,h] + 'category_id': 1, # category_id + 'id': 3322348 # image id + } + ``` + +### 4.2. More datasets + +We provide CDLA(Chinese layout analysis), TableBank(Table layout analysis)etc. data set download links,process to the JSON format of the above annotation file,that is, the training can be conducted in the same way。 + +| dataset | 简介 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [cTDaR2019_cTDaR](https://cndplab-founder.github.io/cTDaR2019/) | For form detection (TRACKA) and form identification (TRACKB).Image types include historical data sets (beginning with cTDaR_t0, such as CTDAR_T00872.jpg) and modern data sets (beginning with cTDaR_t1, CTDAR_T10482.jpg). | +| [IIIT-AR-13K](http://cvit.iiit.ac.in/usodi/iiitar13k.php) | Data sets constructed by manually annotating figures or pages from publicly available annual reports, containing 5 categories:table, figure, natural image, logo, and signature. | +| [TableBank](https://github.com/doc-analysis/TableBank) | For table detection and recognition of large datasets, including Word and Latex document formats | +| [CDLA](https://github.com/buptlihang/CDLA) | Chinese document layout analysis data set, for Chinese literature (paper) scenarios, including 10 categories:Text, Title, Figure, Figure caption, Table, Table caption, Header, Footer, Reference, Equation | +| [DocBank](https://github.com/doc-analysis/DocBank) | Large-scale dataset (500K document pages) constructed using weakly supervised methods for document layout analysis, containing 12 categories:Author, Caption, Date, Equation, Figure, Footer, List, Paragraph, Reference, Section, Table, Title | + + +## 5. Start training + +Training scripts, evaluation scripts, and prediction scripts are provided, and the PubLayNet pre-training model is used as an example in this section. + +If you do not want training and directly experience the following process of model evaluation, prediction, motion to static, and inference, you can download the provided pre-trained model (PubLayNet dataset) and skip this part. + +``` +mkdir pretrained_model +cd pretrained_model +# Download PubLayNet pre-training model(Direct experience model evaluates, predicts, and turns static) +wget https://paddleocr.bj.bcebos.com/ppstructure/models/layout/picodet_lcnet_x1_0_fgd_layout.pdparams +# Download the PubLaynet inference model(Direct experience model reasoning) +wget https://paddleocr.bj.bcebos.com/ppstructure/models/layout/picodet_lcnet_x1_0_fgd_layout_infer.tar +``` + +If the test image is Chinese, the pre-trained model of Chinese CDLA dataset can be downloaded to identify 10 types of document regions:Table, Figure, Figure caption, Table, Table caption, Header, Footer, Reference, Equation,Download the training model and inference model of Model 'picodet_lcnet_x1_0_fgd_layout_cdla' in [layout analysis model](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/ppstructure/docs/models_list.md)。If only the table area in the image is detected, you can download the pre-trained model of the table dataset, and download the training model and inference model of the 'picodet_LCnet_x1_0_FGd_layout_table' model in [Layout Analysis model](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/ppstructure/docs/models_list.md) + +### 5.1. Train + +Start training with the PaddleDetection [layout analysis profile](https://github.com/PaddlePaddle/PaddleDetection/tree/release/2.5/configs/picodet/legacy_model/application/layout_analysis) + +* Modify Profile + +If you want to train your own data set, you need to modify the data configuration and the number of categories in the configuration file. + + +Using 'configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml' as an example, the change is as follows: + +```yaml +metric: COCO +# Number of categories +num_classes: 5 + +TrainDataset: + !COCODataSet + # Modify to your own training data directory + image_dir: train + # Modify to your own training data label file + anno_path: train.json + # Modify to your own training data root directory + dataset_dir: /root/publaynet/ + data_fields: ['image', 'gt_bbox', 'gt_class', 'is_crowd'] + +EvalDataset: + !COCODataSet + # Modify to your own validation data directory + image_dir: val + # Modify to your own validation data label file + anno_path: val.json + # Modify to your own validation data root + dataset_dir: /root/publaynet/ + +TestDataset: + !ImageFolder + # Modify to your own test data label file + anno_path: /root/publaynet/val.json +``` + +* Start training. During training, PP picodet pre training model will be downloaded by default. There is no need to download in advance. + +```bash +# GPU training supports single-card and multi-card training +# The training log is automatically saved to the log directory + +# Single card training +export CUDA_VISIBLE_DEVICES=0 +python3 tools/train.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --eval + +# Multi-card training, with the -- GPUS parameter specifying the card number +export CUDA_VISIBLE_DEVICES=0,1,2,3 +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --eval +``` + +**Attention:**If the video memory is out during training, adjust Batch_size in TrainReader and base_LR in LearningRate. The published config is obtained by 8-card training. If the number of GPU cards is changed to 1, then the base_LR needs to be reduced by 8 times. + +After starting training normally, you will see the following log output: + +``` +[08/15 04:02:30] ppdet.utils.checkpoint INFO: Finish loading model weights: /root/.cache/paddle/weights/LCNet_x1_0_pretrained.pdparams +[08/15 04:02:46] ppdet.engine INFO: Epoch: [0] [ 0/1929] learning_rate: 0.040000 loss_vfl: 1.216707 loss_bbox: 1.142163 loss_dfl: 0.544196 loss: 2.903065 eta: 17 days, 13:50:26 batch_cost: 15.7452 data_cost: 2.9112 ips: 1.5243 images/s +[08/15 04:03:19] ppdet.engine INFO: Epoch: [0] [ 20/1929] learning_rate: 0.064000 loss_vfl: 1.180627 loss_bbox: 0.939552 loss_dfl: 0.442436 loss: 2.628206 eta: 2 days, 12:18:53 batch_cost: 1.5770 data_cost: 0.0008 ips: 15.2184 images/s +[08/15 04:03:47] ppdet.engine INFO: Epoch: [0] [ 40/1929] learning_rate: 0.088000 loss_vfl: 0.543321 loss_bbox: 1.071401 loss_dfl: 0.457817 loss: 2.057003 eta: 2 days, 0:07:03 batch_cost: 1.3190 data_cost: 0.0007 ips: 18.1954 images/s +[08/15 04:04:12] ppdet.engine INFO: Epoch: [0] [ 60/1929] learning_rate: 0.112000 loss_vfl: 0.630989 loss_bbox: 0.859183 loss_dfl: 0.384702 loss: 1.883143 eta: 1 day, 19:01:29 batch_cost: 1.2177 data_cost: 0.0006 ips: 19.7087 images/s +``` + +- `--eval` indicates that the best model is saved as `output/picodet_lcnet_x1_0_layout/best_accuracy` by default during the evaluation process 。 + +**Note that the configuration file for prediction / evaluation must be consistent with the training.** + +### 5.2. FGD Distillation Training + +PaddleDetection supports FGD-based [Focal and Global Knowledge Distillation for Detectors]( https://arxiv.org/abs/2111.11837v1) The training process of the target detection model of distillation, FGD distillation is divided into two parts `Focal` and `Global`. `Focal` Distillation separates the foreground and background of the image, allowing the student model to focus on the key pixels of the foreground and background features of the teacher model respectively;` Global`Distillation section reconstructs the relationships between different pixels and transfers them from the teacher to the student to compensate for the global information lost in `Focal`Distillation. + +Change the dataset and modify the data configuration and number of categories in the [TODO] configuration, referring to 4.1. Start training: + +```bash +# Single Card Training +export CUDA_VISIBLE_DEVICES=0 +python3 tools/train.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --slim_config configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x2_5_layout.yml \ + --eval +``` + +- `-c`: Specify the model configuration file. +- `--slim_config`: Specify the compression policy profile. + +## 6. Model evaluation and prediction + +### 6.1. Indicator evaluation + + Model parameters in training are saved by default in `output/picodet_ Lcnet_ X1_ 0_ Under the layout` directory. When evaluating indicators, you need to set `weights` to point to the saved parameter file.Assessment datasets can be accessed via `configs/picodet/legacy_ Model/application/layout_ Analysis/picodet_ Lcnet_ X1_ 0_ Layout. Yml` . Modify `EvalDataset` : `img_dir`,`anno_ Path`and`dataset_dir` setting. + +```bash +# GPU evaluation, weights as weights to be measured +python3 tools/eval.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + -o weights=./output/picodet_lcnet_x1_0_layout/best_model +``` + +The following information will be printed out, such as mAP, AP0.5, etc. + +```py + Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.935 + Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.979 + Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.956 + Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.404 + Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.782 + Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.969 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.539 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.938 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.949 + Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.495 + Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.818 + Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.978 +[08/15 07:07:09] ppdet.engine INFO: Total sample number: 11245, averge FPS: 24.405059207157436 +[08/15 07:07:09] ppdet.engine INFO: Best test bbox ap is 0.935. +``` + +If you use the provided pre-training model for evaluation or the FGD distillation training model, replace the `weights` model path and execute the following command for evaluation: + +``` +python3 tools/eval.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --slim_config configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x2_5_layout.yml \ + -o weights=output/picodet_lcnet_x2_5_layout/best_model +``` + +- `-c`: Specify the model configuration file. +- `--slim_config`: Specify the distillation policy profile. +- `-o weights`: Specify the model path trained by the distillation algorithm. + +### 6.2. Test Layout Analysis Results + + +The profile predicted to be used must be consistent with the training, for example, if you pass `python3 tools/train'. Py-c configs/picodet/legacy_ Model/application/layout_ Analysis/picodet_ Lcnet_ X1_ 0_ Layout. Yml` completed the training process for the model. + +With trained PaddleDetection model, you can use the following commands to make model predictions. + +```bash +python3 tools/infer.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + -o weights='output/picodet_lcnet_x1_0_layout/best_model.pdparams' \ + --infer_img='docs/images/layout.jpg' \ + --output_dir=output_dir/ \ + --draw_threshold=0.5 +``` + +- `--infer_img`: Reasoning for a single picture can also be done via `--infer_ Dir`Inform all pictures in the file. +- `--output_dir`: Specify the path to save the visualization results. +- `--draw_threshold`:Specify the NMS threshold for drawing the result box. + +If you use the provided pre-training model for prediction or the FGD distillation training model, change the `weights` model path and execute the following command to make the prediction: + +``` +python3 tools/infer.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --slim_config configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x2_5_layout.yml \ + -o weights='output/picodet_lcnet_x2_5_layout/best_model.pdparams' \ + --infer_img='docs/images/layout.jpg' \ + --output_dir=output_dir/ \ + --draw_threshold=0.5 +``` + + +## 7. Model Export and Inference + + +### 7.1 Model Export + +The inference model (the model saved by `paddle.jit.save`) is generally a solidified model saved after the model training is completed, and is mostly used to give prediction in deployment. + +The model saved during the training process is the checkpoints model, which saves the parameters of the model and is mostly used to resume training. + +Compared with the checkpoints model, the inference model will additionally save the structural information of the model. Therefore, it is easier to deploy because the model structure and model parameters are already solidified in the inference model file, and is suitable for integration with actual systems. + +Layout analysis model to inference model steps are as follows: + +```bash +python3 tools/export_model.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + -o weights=output/picodet_lcnet_x1_0_layout/best_model \ + --output_dir=output_inference/ +``` + +* If no post-export processing is required, specify:`-o export.benchmark=True`(If -o already exists, delete -o here) +* If you do not need to export NMS, specify:`-o export.nms=False` + +After successful conversion, there are three files in the directory: + +``` +output_inference/picodet_lcnet_x1_0_layout/ + ├── model.pdiparams # inference Parameter file for model + ├── model.pdiparams.info # inference Model parameter information, ignorable + └── model.pdmodel # inference Model Structure File for Model +``` + +If you change the `weights` model path using the provided pre-training model to the Inference model, or using the FGD distillation training model, the model to inference model steps are as follows: + +```bash +python3 tools/export_model.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --slim_config configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x2_5_layout.yml \ + -o weights=./output/picodet_lcnet_x2_5_layout/best_model \ + --output_dir=output_inference/ +``` + +### 7.2 Model inference + +Replace model_with the provided inference training model for inference or the FGD distillation training `model_dir`Inference model path, execute the following commands for inference: + +```bash +python3 deploy/python/infer.py \ + --model_dir=output_inference/picodet_lcnet_x1_0_layout/ \ + --image_file=docs/images/layout.jpg \ + --device=CPU +``` + +- --device:Specify the GPU or CPU device + +When model inference is complete, you will see the following log output: + +``` +------------------------------------------ +----------- Model Configuration ----------- +Model Arch: PicoDet +Transform Order: +--transform op: Resize +--transform op: NormalizeImage +--transform op: Permute +--transform op: PadStride +-------------------------------------------- +class_id:0, confidence:0.9921, left_top:[20.18,35.66],right_bottom:[341.58,600.99] +class_id:0, confidence:0.9914, left_top:[19.77,611.42],right_bottom:[341.48,901.82] +class_id:0, confidence:0.9904, left_top:[369.36,375.10],right_bottom:[691.29,600.59] +class_id:0, confidence:0.9835, left_top:[369.60,608.60],right_bottom:[691.38,736.72] +class_id:0, confidence:0.9830, left_top:[369.58,805.38],right_bottom:[690.97,901.80] +class_id:0, confidence:0.9716, left_top:[383.68,271.44],right_bottom:[688.93,335.39] +class_id:0, confidence:0.9452, left_top:[370.82,34.48],right_bottom:[688.10,63.54] +class_id:1, confidence:0.8712, left_top:[370.84,771.03],right_bottom:[519.30,789.13] +class_id:3, confidence:0.9856, left_top:[371.28,67.85],right_bottom:[685.73,267.72] +save result to: output/layout.jpg +Test iter 0 +------------------ Inference Time Info ---------------------- +total_time(ms): 2196.0, img_num: 1 +average latency time(ms): 2196.00, QPS: 0.455373 +preprocess_time(ms): 2172.50, inference_time(ms): 11.90, postprocess_time(ms): 11.60 +``` + +- Model:model structure +- Transform Order:Preprocessing operation +- class_id, confidence, left_top, right_bottom:Indicates category id, confidence level, upper left coordinate, lower right coordinate, respectively +- save result to:Save path of visual layout analysis results, default save to ./output folder +- inference time info:Inference time, where preprocess_time represents the preprocessing time, Inference_time represents the model prediction time, and postprocess_time represents the post-processing time + +The result of visualization layout is shown in the following figure + +
    + +
    + + + +## Citations + +``` +@inproceedings{zhong2019publaynet, + title={PubLayNet: largest dataset ever for document layout analysis}, + author={Zhong, Xu and Tang, Jianbin and Yepes, Antonio Jimeno}, + booktitle={2019 International Conference on Document Analysis and Recognition (ICDAR)}, + year={2019}, + volume={}, + number={}, + pages={1015-1022}, + doi={10.1109/ICDAR.2019.00166}, + ISSN={1520-5363}, + month={Sep.}, + organization={IEEE} +} + +@inproceedings{yang2022focal, + title={Focal and global knowledge distillation for detectors}, + author={Yang, Zhendong and Li, Zhe and Jiang, Xiaohu and Gong, Yuan and Yuan, Zehuan and Zhao, Danpei and Yuan, Chun}, + booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition}, + pages={4643--4652}, + year={2022} +} +``` diff --git a/ppstructure/layout/README_ch.md b/ppstructure/layout/README_ch.md new file mode 100644 index 0000000..adef46d --- /dev/null +++ b/ppstructure/layout/README_ch.md @@ -0,0 +1,469 @@ +简体中文 | [English](README.md) + +# 版面分析 + +- [1. 简介](#1-简介) +- [2. 快速开始](#2-快速开始) +- [3. 安装](#3-安装) + - [3.1 安装PaddlePaddle](#31-安装paddlepaddle) + - [3.2 安装PaddleDetection](#32-安装paddledetection) +- [4. 数据准备](#4-数据准备) + - [4.1 英文数据集](#41-英文数据集) + - [4.2 更多数据集](#42-更多数据集) +- [5. 开始训练](#5-开始训练) + - [5.1 启动训练](#51-启动训练) + - [5.2 FGD蒸馏训练](#52-fgd蒸馏训练) +- [6. 模型评估与预测](#6-模型评估与预测) + - [6.1 指标评估](#61-指标评估) + - [6.2 测试版面分析结果](#62-测试版面分析结果) +- [7 模型导出与预测](#7-模型导出与预测) + - [7.1 模型导出](#71-模型导出) + - [7.2 模型推理](#72-模型推理) + +## 1. 简介 + +版面分析指的是对图片形式的文档进行区域划分,定位其中的关键区域,如文字、标题、表格、图片等。版面分析算法基于[PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection)的轻量模型PP-PicoDet进行开发,包含英文、中文、表格版面分析3类模型。其中,英文模型支持Text、Title、Tale、Figure、List5类区域的检测,中文模型支持Text、Title、Figure、Figure caption、Table、Table caption、Header、Footer、Reference、Equation10类区域的检测,表格版面分析支持Table区域的检测,版面分析效果如下图所示: + +
    + +
    + +## 2. 快速开始 + +PP-Structure目前提供了中文、英文、表格三类文档版面分析模型,模型链接见 [models_list](../docs/models_list.md#1-版面分析模型)。也提供了whl包的形式方便快速使用,详见 [quickstart](../docs/quickstart.md)。 + + +## 3. 安装 + +### 3.1. 安装PaddlePaddle + +- **(1) 安装PaddlePaddle** + +```bash +python3 -m pip install --upgrade pip + +# GPU安装 +python3 -m pip install "paddlepaddle-gpu>=2.3" -i https://mirror.baidu.com/pypi/simple + +# CPU安装 +python3 -m pip install "paddlepaddle>=2.3" -i https://mirror.baidu.com/pypi/simple +``` +更多需求,请参照[安装文档](https://www.paddlepaddle.org.cn/install/quick)中的说明进行操作。 + +### 3.2. 安装PaddleDetection + +- **(1)下载PaddleDetection源码** + +```bash +git clone https://github.com/PaddlePaddle/PaddleDetection.git +``` + +- **(2)安装其他依赖** + +```bash +cd PaddleDetection +python3 -m pip install -r requirements.txt +``` + +## 4. 数据准备 + +如果希望直接体验预测过程,可以跳过数据准备,下载我们提供的预训练模型。 + +### 4.1. 英文数据集 + +下载文档分析数据集[PubLayNet](https://developer.ibm.com/exchanges/data/all/publaynet/)(数据集96G),包含5个类:`{0: "Text", 1: "Title", 2: "List", 3:"Table", 4:"Figure"}` + +``` +# 下载数据 +wget https://dax-cdn.cdn.appdomain.cloud/dax-publaynet/1.0.0/publaynet.tar.gz +# 解压数据 +tar -xvf publaynet.tar.gz +``` + +解压之后的**目录结构:** + +``` +|-publaynet + |- test + |- PMC1277013_00004.jpg + |- PMC1291385_00002.jpg + | ... + |- train.json + |- train + |- PMC1291385_00002.jpg + |- PMC1277013_00004.jpg + | ... + |- val.json + |- val + |- PMC538274_00004.jpg + |- PMC539300_00004.jpg + | ... +``` + +**数据分布:** + +| File or Folder | Description | num | +| :------------- | :------------- | ------- | +| `train/` | 训练集图片 | 335,703 | +| `val/` | 验证集图片 | 11,245 | +| `test/` | 测试集图片 | 11,405 | +| `train.json` | 训练集标注文件 | - | +| `val.json` | 验证集标注文件 | - | + +**标注格式:** + +json文件包含所有图像的标注,数据以字典嵌套的方式存放,包含以下key: + +- info,表示标注文件info。 + +- licenses,表示标注文件licenses。 + +- images,表示标注文件中图像信息列表,每个元素是一张图像的信息。如下为其中一张图像的信息: + + ``` + { + 'file_name': 'PMC4055390_00006.jpg', # file_name + 'height': 601, # image height + 'width': 792, # image width + 'id': 341427 # image id + } + ``` + +- annotations,表示标注文件中目标物体的标注信息列表,每个元素是一个目标物体的标注信息。如下为其中一个目标物体的标注信息: + + ``` + { + + 'segmentation': # 物体的分割标注 + 'area': 60518.099043117836, # 物体的区域面积 + 'iscrowd': 0, # iscrowd + 'image_id': 341427, # image id + 'bbox': [50.58, 490.86, 240.15, 252.16], # bbox [x1,y1,w,h] + 'category_id': 1, # category_id + 'id': 3322348 # image id + } + ``` + +### 4.2. 更多数据集 + +我们提供了CDLA(中文版面分析)、TableBank(表格版面分析)等数据集的下连接,处理为上述标注文件json格式,即可以按相同方式进行训练。 + +| dataset | 简介 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [cTDaR2019_cTDaR](https://cndplab-founder.github.io/cTDaR2019/) | 用于表格检测(TRACKA)和表格识别(TRACKB)。图片类型包含历史数据集(以cTDaR_t0开头,如cTDaR_t00872.jpg)和现代数据集(以cTDaR_t1开头,cTDaR_t10482.jpg)。 | +| [IIIT-AR-13K](http://cvit.iiit.ac.in/usodi/iiitar13k.php) | 手动注释公开的年度报告中的图形或页面而构建的数据集,包含5类:table, figure, natural image, logo, and signature | +| [CDLA](https://github.com/buptlihang/CDLA) | 中文文档版面分析数据集,面向中文文献类(论文)场景,包含10类:Text、Title、Figure、Figure caption、Table、Table caption、Header、Footer、Reference、Equation | +| [TableBank](https://github.com/doc-analysis/TableBank) | 用于表格检测和识别大型数据集,包含Word和Latex2种文档格式 | +| [DocBank](https://github.com/doc-analysis/DocBank) | 使用弱监督方法构建的大规模数据集(500K文档页面),用于文档布局分析,包含12类:Author、Caption、Date、Equation、Figure、Footer、List、Paragraph、Reference、Section、Table、Title | + + +## 5. 开始训练 + +提供了训练脚本、评估脚本和预测脚本,本节将以PubLayNet预训练模型为例进行讲解。 + +如果不希望训练,直接体验后面的模型评估、预测、动转静、推理的流程,可以下载提供的预训练模型(PubLayNet数据集),并跳过5.1和5.2。 + +``` +mkdir pretrained_model +cd pretrained_model +# 下载PubLayNet预训练模型(直接体验模型评估、预测、动转静) +wget https://paddleocr.bj.bcebos.com/ppstructure/models/layout/picodet_lcnet_x1_0_fgd_layout.pdparams +# 下载PubLaynet推理模型(直接体验模型推理) +wget https://paddleocr.bj.bcebos.com/ppstructure/models/layout/picodet_lcnet_x1_0_fgd_layout_infer.tar +``` + +如果测试图片为中文,可以下载中文CDLA数据集的预训练模型,识别10类文档区域:Table、Figure、Figure caption、Table、Table caption、Header、Footer、Reference、Equation,在[版面分析模型](../docs/models_list.md)中下载`picodet_lcnet_x1_0_fgd_layout_cdla`模型的训练模型和推理模型。如果只检测图片中的表格区域,可以下载表格数据集的预训练模型,在[版面分析模型](../docs/models_list.md)中下载`picodet_lcnet_x1_0_fgd_layout_table`模型的训练模型和推理模型。 + +### 5.1. 启动训练 + +使用PaddleDetection[版面分析配置文件](https://github.com/PaddlePaddle/PaddleDetection/tree/release/2.5/configs/picodet/legacy_model/application/layout_analysis)启动训练 + +* 修改配置文件 + +如果你希望训练自己的数据集,需要修改配置文件中的数据配置、类别数。 + + +以`configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml` 为例,修改的内容如下所示。 + +```yaml +metric: COCO +# 类别数 +num_classes: 5 + +TrainDataset: + !COCODataSet + # 修改为你自己的训练数据目录 + image_dir: train + # 修改为你自己的训练数据标签文件 + anno_path: train.json + # 修改为你自己的训练数据根目录 + dataset_dir: /root/publaynet/ + data_fields: ['image', 'gt_bbox', 'gt_class', 'is_crowd'] + +EvalDataset: + !COCODataSet + # 修改为你自己的验证数据目录 + image_dir: val + # 修改为你自己的验证数据标签文件 + anno_path: val.json + # 修改为你自己的验证数据根目录 + dataset_dir: /root/publaynet/ + +TestDataset: + !ImageFolder + # 修改为你自己的测试数据标签文件 + anno_path: /root/publaynet/val.json +``` + +* 开始训练,在训练时,会默认下载PP-PicoDet预训练模型,这里无需预先下载。 + +```bash +# GPU训练 支持单卡,多卡训练 +# 训练日志会自动保存到 log 目录中 + +# 单卡训练 +export CUDA_VISIBLE_DEVICES=0 +python3 tools/train.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --eval + +# 多卡训练,通过--gpus参数指定卡号 +export CUDA_VISIBLE_DEVICES=0,1,2,3 +python3 -m paddle.distributed.launch --gpus '0,1,2,3' tools/train.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --eval +``` + +**注意:**如果训练时显存out memory,将TrainReader中batch_size调小,同时LearningRate中base_lr等比例减小。发布的config均由8卡训练得到,如果改变GPU卡数为1,那么base_lr需要减小8倍。 + +正常启动训练后,会看到以下log输出: + +``` +[08/15 04:02:30] ppdet.utils.checkpoint INFO: Finish loading model weights: /root/.cache/paddle/weights/LCNet_x1_0_pretrained.pdparams +[08/15 04:02:46] ppdet.engine INFO: Epoch: [0] [ 0/1929] learning_rate: 0.040000 loss_vfl: 1.216707 loss_bbox: 1.142163 loss_dfl: 0.544196 loss: 2.903065 eta: 17 days, 13:50:26 batch_cost: 15.7452 data_cost: 2.9112 ips: 1.5243 images/s +[08/15 04:03:19] ppdet.engine INFO: Epoch: [0] [ 20/1929] learning_rate: 0.064000 loss_vfl: 1.180627 loss_bbox: 0.939552 loss_dfl: 0.442436 loss: 2.628206 eta: 2 days, 12:18:53 batch_cost: 1.5770 data_cost: 0.0008 ips: 15.2184 images/s +[08/15 04:03:47] ppdet.engine INFO: Epoch: [0] [ 40/1929] learning_rate: 0.088000 loss_vfl: 0.543321 loss_bbox: 1.071401 loss_dfl: 0.457817 loss: 2.057003 eta: 2 days, 0:07:03 batch_cost: 1.3190 data_cost: 0.0007 ips: 18.1954 images/s +[08/15 04:04:12] ppdet.engine INFO: Epoch: [0] [ 60/1929] learning_rate: 0.112000 loss_vfl: 0.630989 loss_bbox: 0.859183 loss_dfl: 0.384702 loss: 1.883143 eta: 1 day, 19:01:29 batch_cost: 1.2177 data_cost: 0.0006 ips: 19.7087 images/s +``` + +- `--eval`表示训练的同时,进行评估, 评估过程中默认将最佳模型,保存为 `output/picodet_lcnet_x1_0_layout/best_accuracy` 。 + +**注意,预测/评估时的配置文件请务必与训练一致。** + +### 5.2. FGD蒸馏训练 + +PaddleDetection支持了基于FGD([Focal and Global Knowledge Distillation for Detectors](https://arxiv.org/abs/2111.11837v1))蒸馏的目标检测模型训练过程,FGD蒸馏分为两个部分`Focal`和`Global`。`Focal`蒸馏分离图像的前景和背景,让学生模型分别关注教师模型的前景和背景部分特征的关键像素;`Global`蒸馏部分重建不同像素之间的关系并将其从教师转移到学生,以补偿`Focal`蒸馏中丢失的全局信息。 + +更换数据集,修改【TODO】配置中的数据配置、类别数,具体可以参考4.1。启动训练: + +```bash +# 单卡训练 +export CUDA_VISIBLE_DEVICES=0 +python3 tools/train.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --slim_config configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x2_5_layout.yml \ + --eval +``` + +- `-c`: 指定模型配置文件。 +- `--slim_config`: 指定压缩策略配置文件。 + +## 6. 模型评估与预测 + +### 6.1. 指标评估 + +训练中模型参数默认保存在`output/picodet_lcnet_x1_0_layout`目录下。在评估指标时,需要设置`weights`指向保存的参数文件。评估数据集可以通过 `configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml` 修改`EvalDataset`中的 `image_dir`、`anno_path`和`dataset_dir` 设置。 + +```bash +# GPU 评估, weights 为待测权重 +python3 tools/eval.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + -o weights=./output/picodet_lcnet_x1_0_layout/best_model +``` + +会输出以下信息,打印出mAP、AP0.5等信息。 + +```py + Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.935 + Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.979 + Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.956 + Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.404 + Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.782 + Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.969 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.539 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.938 + Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.949 + Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.495 + Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.818 + Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.978 +[08/15 07:07:09] ppdet.engine INFO: Total sample number: 11245, averge FPS: 24.405059207157436 +[08/15 07:07:09] ppdet.engine INFO: Best test bbox ap is 0.935. +``` + +若使用**提供的预训练模型进行评估**,或使用**FGD蒸馏训练的模型**,更换`weights`模型路径,执行如下命令进行评估: + +``` +python3 tools/eval.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --slim_config configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x2_5_layout.yml \ + -o weights=output/picodet_lcnet_x2_5_layout/best_model +``` + +- `-c`: 指定模型配置文件。 +- `--slim_config`: 指定蒸馏策略配置文件。 +- `-o weights`: 指定蒸馏算法训好的模型路径。 + +### 6.2 测试版面分析结果 + + +预测使用的配置文件必须与训练一致,如您通过 `python3 tools/train.py -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml` 完成了模型的训练过程。 + +使用 PaddleDetection 训练好的模型,您可以使用如下命令进行模型预测。 + +```bash +python3 tools/infer.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + -o weights='output/picodet_lcnet_x1_0_layout/best_model.pdparams' \ + --infer_img='docs/images/layout.jpg' \ + --output_dir=output_dir/ \ + --draw_threshold=0.5 +``` + +- `--infer_img`: 推理单张图片,也可以通过`--infer_dir`推理文件中的所有图片。 +- `--output_dir`: 指定可视化结果保存路径。 +- `--draw_threshold`:指定绘制结果框的NMS阈值。 + +若使用**提供的预训练模型进行预测**,或使用**FGD蒸馏训练的模型**,更换`weights`模型路径,执行如下命令进行预测: + +``` +python3 tools/infer.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --slim_config configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x2_5_layout.yml \ + -o weights='output/picodet_lcnet_x2_5_layout/best_model.pdparams' \ + --infer_img='docs/images/layout.jpg' \ + --output_dir=output_dir/ \ + --draw_threshold=0.5 +``` + + +## 7. 模型导出与预测 + + +### 7.1 模型导出 + +inference 模型(`paddle.jit.save`保存的模型) 一般是模型训练,把模型结构和模型参数保存在文件中的固化模型,多用于预测部署场景。 训练过程中保存的模型是checkpoints模型,保存的只有模型的参数,多用于恢复训练等。 与checkpoints模型相比,inference 模型会额外保存模型的结构信息,在预测部署、加速推理上性能优越,灵活方便,适合于实际系统集成。 + +版面分析模型转inference模型步骤如下: + +```bash +python3 tools/export_model.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + -o weights=output/picodet_lcnet_x1_0_layout/best_model \ + --output_dir=output_inference/ +``` + +* 如无需导出后处理,请指定:`-o export.benchmark=True`(如果-o已出现过,此处删掉-o) +* 如无需导出NMS,请指定:`-o export.nms=False` + +转换成功后,在目录下有三个文件: + +``` +output_inference/picodet_lcnet_x1_0_layout/ + ├── model.pdiparams # inference模型的参数文件 + ├── model.pdiparams.info # inference模型的参数信息,可忽略 + └── model.pdmodel # inference模型的模型结构文件 +``` + +若使用**提供的预训练模型转Inference模型**,或使用**FGD蒸馏训练的模型**,更换`weights`模型路径,模型转inference模型步骤如下: + +```bash +python3 tools/export_model.py \ + -c configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x1_0_layout.yml \ + --slim_config configs/picodet/legacy_model/application/layout_analysis/picodet_lcnet_x2_5_layout.yml \ + -o weights=./output/picodet_lcnet_x2_5_layout/best_model \ + --output_dir=output_inference/ +``` + + + +### 7.2 模型推理 + +若使用**提供的推理训练模型推理**,或使用**FGD蒸馏训练的模型**,更换`model_dir`推理模型路径,执行如下命令进行推理: + +```bash +python3 deploy/python/infer.py \ + --model_dir=output_inference/picodet_lcnet_x1_0_layout/ \ + --image_file=docs/images/layout.jpg \ + --device=CPU +``` + +- --device:指定GPU、CPU设备 + +模型推理完成,会看到以下log输出 + +``` +------------------------------------------ +----------- Model Configuration ----------- +Model Arch: PicoDet +Transform Order: +--transform op: Resize +--transform op: NormalizeImage +--transform op: Permute +--transform op: PadStride +-------------------------------------------- +class_id:0, confidence:0.9921, left_top:[20.18,35.66],right_bottom:[341.58,600.99] +class_id:0, confidence:0.9914, left_top:[19.77,611.42],right_bottom:[341.48,901.82] +class_id:0, confidence:0.9904, left_top:[369.36,375.10],right_bottom:[691.29,600.59] +class_id:0, confidence:0.9835, left_top:[369.60,608.60],right_bottom:[691.38,736.72] +class_id:0, confidence:0.9830, left_top:[369.58,805.38],right_bottom:[690.97,901.80] +class_id:0, confidence:0.9716, left_top:[383.68,271.44],right_bottom:[688.93,335.39] +class_id:0, confidence:0.9452, left_top:[370.82,34.48],right_bottom:[688.10,63.54] +class_id:1, confidence:0.8712, left_top:[370.84,771.03],right_bottom:[519.30,789.13] +class_id:3, confidence:0.9856, left_top:[371.28,67.85],right_bottom:[685.73,267.72] +save result to: output/layout.jpg +Test iter 0 +------------------ Inference Time Info ---------------------- +total_time(ms): 2196.0, img_num: 1 +average latency time(ms): 2196.00, QPS: 0.455373 +preprocess_time(ms): 2172.50, inference_time(ms): 11.90, postprocess_time(ms): 11.60 +``` + +- Model:模型结构 +- Transform Order:预处理操作 +- class_id、confidence、left_top、right_bottom:分别表示类别id、置信度、左上角坐标、右下角坐标 +- save result to:可视化版面分析结果保存路径,默认保存到`./output`文件夹 +- Inference Time Info:推理时间,其中preprocess_time表示预处理耗时,inference_time表示模型预测耗时,postprocess_time表示后处理耗时 + +可视化版面结果如下图所示 + +
    + +
    + + + +## Citations + +``` +@inproceedings{zhong2019publaynet, + title={PubLayNet: largest dataset ever for document layout analysis}, + author={Zhong, Xu and Tang, Jianbin and Yepes, Antonio Jimeno}, + booktitle={2019 International Conference on Document Analysis and Recognition (ICDAR)}, + year={2019}, + volume={}, + number={}, + pages={1015-1022}, + doi={10.1109/ICDAR.2019.00166}, + ISSN={1520-5363}, + month={Sep.}, + organization={IEEE} +} + +@inproceedings{yang2022focal, + title={Focal and global knowledge distillation for detectors}, + author={Yang, Zhendong and Li, Zhe and Jiang, Xiaohu and Gong, Yuan and Yuan, Zehuan and Zhao, Danpei and Yuan, Chun}, + booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition}, + pages={4643--4652}, + year={2022} +} +``` diff --git a/ppstructure/layout/__init__.py b/ppstructure/layout/__init__.py new file mode 100644 index 0000000..1d11e26 --- /dev/null +++ b/ppstructure/layout/__init__.py @@ -0,0 +1,13 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/ppstructure/layout/predict_layout.py b/ppstructure/layout/predict_layout.py new file mode 100755 index 0000000..10bb117 --- /dev/null +++ b/ppstructure/layout/predict_layout.py @@ -0,0 +1,143 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import numpy as np +import time + +import tools.infer.utility as utility +from ppocr.data import create_operators, transform +from ppocr.postprocess import build_post_process +from ppocr.utils.logging import get_logger +from ppocr.utils.utility import get_image_file_list, check_and_read +from ppstructure.utility import parse_args +from picodet_postprocess import PicoDetPostProcess + +logger = get_logger() + + +class LayoutPredictor(object): + def __init__(self, args): + pre_process_list = [ + {"Resize": {"size": [800, 608]}}, + { + "NormalizeImage": { + "std": [0.229, 0.224, 0.225], + "mean": [0.485, 0.456, 0.406], + "scale": "1./255.", + "order": "hwc", + } + }, + {"ToCHWImage": None}, + {"KeepKeys": {"keep_keys": ["image"]}}, + ] + postprocess_params = { + "name": "PicoDetPostProcess", + "layout_dict_path": args.layout_dict_path, + "score_threshold": args.layout_score_threshold, + "nms_threshold": args.layout_nms_threshold, + } + + self.preprocess_op = create_operators(pre_process_list) + self.postprocess_op = build_post_process(postprocess_params) + ( + self.predictor, + self.input_tensor, + self.output_tensors, + self.config, + ) = utility.create_predictor(args, "layout", logger) + self.use_onnx = args.use_onnx + + def __call__(self, img): + ori_im = img.copy() + data = {"image": img} + data = transform(data, self.preprocess_op) + img = data[0] + + if img is None: + return None, 0 + + img = np.expand_dims(img, axis=0) + img = img.copy() + + preds, elapse = 0, 1 + starttime = time.time() + + np_score_list, np_boxes_list = [], [] + if self.use_onnx: + input_dict = {} + input_dict[self.input_tensor.name] = img + outputs = self.predictor.run(self.output_tensors, input_dict) + num_outs = int(len(outputs) / 2) + for out_idx in range(num_outs): + np_score_list.append(outputs[out_idx]) + np_boxes_list.append(outputs[out_idx + num_outs]) + else: + self.input_tensor.copy_from_cpu(img) + self.predictor.run() + output_names = self.predictor.get_output_names() + num_outs = int(len(output_names) / 2) + for out_idx in range(num_outs): + np_score_list.append( + self.predictor.get_output_handle( + output_names[out_idx] + ).copy_to_cpu() + ) + np_boxes_list.append( + self.predictor.get_output_handle( + output_names[out_idx + num_outs] + ).copy_to_cpu() + ) + preds = dict(boxes=np_score_list, boxes_num=np_boxes_list) + + post_preds = self.postprocess_op(ori_im, img, preds) + elapse = time.time() - starttime + return post_preds, elapse + + +def main(args): + image_file_list = get_image_file_list(args.image_dir) + layout_predictor = LayoutPredictor(args) + count = 0 + total_time = 0 + + repeats = 50 + for image_file in image_file_list: + img, flag, _ = check_and_read(image_file) + if not flag: + img = cv2.imread(image_file) + if img is None: + logger.info("error in loading image:{}".format(image_file)) + continue + + layout_res, elapse = layout_predictor(img) + + logger.info("result: {}".format(layout_res)) + + if count > 0: + total_time += elapse + count += 1 + logger.info("Predict time of {}: {}".format(image_file, elapse)) + + +if __name__ == "__main__": + main(parse_args()) diff --git a/ppstructure/pdf2word/README.md b/ppstructure/pdf2word/README.md new file mode 100644 index 0000000..783555a --- /dev/null +++ b/ppstructure/pdf2word/README.md @@ -0,0 +1,49 @@ +# PDF2WORD + +PDF2Word是PaddleOCR社区开发者 [whjdark](https://github.com/whjdark) 基于PP-StructureV2版面分析与恢复模型实现的PDF转换Word应用程序,提供可直接安装的exe应用程序,**方便Windows用户免环境配置运行** + +## 1.使用 + +### 应用程序 + +1. 下载与安装:针对Windows用户,根据[软件下载]()一节下载软件后,运行 `pdf2word.exe` 。若您下载的是lite版本,安装过程中会在线下载环境依赖、模型等必要资源,安装时间较长,请确保网络畅通。serve版本打包了相关依赖,安装时间较短,可按需下载。 + +2. 转换:由于PP-Structure根据中英文数据分别进行适配,在转换相应文件时可**根据文档语言进行相应选择**。 + +### 脚本运行 + +3. 打开结果:点击`显示结果`,即可打开转换完成后的文件夹 + +> 注意: +> +> - 初次安装程序根据不同设备需要等待1-2分钟不等 +> - 使用Office与WPS打开的Word结果会出现不同,推荐以Office为准 +> - 本程序使用 [QPT](https://github.com/QPT-Family/QPT) 进行应用程序打包,感谢 [GT-ZhangAcer](https://github.com/GT-ZhangAcer) 对打包过程的支持 +> - 应用程序仅支持正版win10,11系统,不支持盗版Windows系统,若在安装过程中出现报错或缺少依赖,推荐直接使用 `paddleocr` whl包应用PDF2Word功能,详情可查看[链接](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.6/ppstructure/docs/quickstart.md) + +### 脚本启动界面 + +首次运行需要将切换路径到PaddleOCR文件目录 ,然后运行代码 + +``` +cd ./ppstructure/pdf2word +python pdf2word.py +``` + +### PaddleOCR whl包 + +针对Linux、Mac用户或已经拥有Python环境的用户,**推荐安装 `paddleocr` whl包直接应用PDF2Word功能**,详情可查看[链接](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.6/ppstructure/docs/quickstart.md) + + + +## 2.软件下载 + +如需获取已打包程序,可以扫描下方二维码,关注公众号填写问卷后,加入PaddleOCR官方交流群免费获取20G OCR学习大礼包,内含OCR场景应用集合(包含数码管、液晶屏、车牌、高精度SVTR模型等7个垂类模型)、《动手学OCR》电子书、课程回放视频、前沿论文等重磅资料 + +
    + +
    + +## 3.版本说明 + +v0.2版:新加入PDF解析功能,仅提供full版本,打包了所有依赖包与模型文件,尽可能避免安装失败问题。若仍然安装失败,推荐使用 `paddleocr` whl包 diff --git a/ppstructure/pdf2word/icons/chinese.png b/ppstructure/pdf2word/icons/chinese.png new file mode 100644 index 0000000..328e2ff Binary files /dev/null and b/ppstructure/pdf2word/icons/chinese.png differ diff --git a/ppstructure/pdf2word/icons/english.png b/ppstructure/pdf2word/icons/english.png new file mode 100644 index 0000000..536c4a9 Binary files /dev/null and b/ppstructure/pdf2word/icons/english.png differ diff --git a/ppstructure/pdf2word/icons/folder-open.png b/ppstructure/pdf2word/icons/folder-open.png new file mode 100644 index 0000000..ab5f55f Binary files /dev/null and b/ppstructure/pdf2word/icons/folder-open.png differ diff --git a/ppstructure/pdf2word/icons/folder-plus.png b/ppstructure/pdf2word/icons/folder-plus.png new file mode 100644 index 0000000..01ce6c1 Binary files /dev/null and b/ppstructure/pdf2word/icons/folder-plus.png differ diff --git a/ppstructure/pdf2word/pdf2word.py b/ppstructure/pdf2word/pdf2word.py new file mode 100644 index 0000000..668f04e --- /dev/null +++ b/ppstructure/pdf2word/pdf2word.py @@ -0,0 +1,538 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import tarfile +import os +import time +import datetime +import functools +import cv2 +import platform +import subprocess +import numpy as np +from paddle.utils import try_import + +fitz = try_import("fitz") +from PIL import Image +from qtpy.QtWidgets import ( + QApplication, + QWidget, + QPushButton, + QProgressBar, + QGridLayout, + QMessageBox, + QLabel, + QFileDialog, + QCheckBox, +) +from qtpy.QtCore import Signal, QThread, QObject +from qtpy.QtGui import QImage, QPixmap, QIcon + +file = os.path.dirname(os.path.abspath(__file__)) +root = os.path.abspath(os.path.join(file, "../../")) +sys.path.append(file) +sys.path.insert(0, root) + +from ppstructure.predict_system import StructureSystem, save_structure_res +from ppstructure.utility import parse_args, draw_structure_result +from ppocr.utils.network import download_with_progressbar +from ppstructure.recovery.recovery_to_doc import sorted_layout_boxes, convert_info_docx + +# from ScreenShotWidget import ScreenShotWidget + +__APPNAME__ = "pdf2word" +__VERSION__ = "0.2.2" + +URLs_EN = { + # 下载超英文轻量级PP-OCRv3模型的检测模型并解压 + "en_PP-OCRv3_det_infer": "https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_det_infer.tar", + # 下载英文轻量级PP-OCRv3模型的识别模型并解压 + "en_PP-OCRv3_rec_infer": "https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_rec_infer.tar", + # 下载超轻量级英文表格英文模型并解压 + "en_ppstructure_mobile_v2.0_SLANet_infer": "https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/paddle3.0b2/en_ppstructure_mobile_v2.0_SLANet_infer.tar", + # 英文版面分析模型 + "picodet_lcnet_x1_0_fgd_layout_infer": "https://paddleocr.bj.bcebos.com/ppstructure/models/layout/picodet_lcnet_x1_0_fgd_layout_infer.tar", +} +DICT_EN = { + "rec_char_dict_path": "en_dict.txt", + "layout_dict_path": "layout_publaynet_dict.txt", +} + +URLs_CN = { + # 下载超中文轻量级PP-OCRv3模型的检测模型并解压 + "cn_PP-OCRv3_det_infer": "https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_det_infer.tar", + # 下载中文轻量级PP-OCRv3模型的识别模型并解压 + "cn_PP-OCRv3_rec_infer": "https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar", + # 下载超轻量级英文表格英文模型并解压 + "cn_ppstructure_mobile_v2.0_SLANet_infer": "https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/paddle3.0b2/en_ppstructure_mobile_v2.0_SLANet_infer.tar", + # 中文版面分析模型 + "picodet_lcnet_x1_0_fgd_layout_cdla_infer": "https://paddleocr.bj.bcebos.com/ppstructure/models/layout/picodet_lcnet_x1_0_fgd_layout_cdla_infer.tar", +} +DICT_CN = { + "rec_char_dict_path": "ppocr_keys_v1.txt", + "layout_dict_path": "layout_cdla_dict.txt", +} + + +def QImageToCvMat(incomingImage) -> np.array: + """ + Converts a QImage into an opencv MAT format + """ + + incomingImage = incomingImage.convertToFormat(QImage.Format.Format_RGBA8888) + + width = incomingImage.width() + height = incomingImage.height() + + ptr = incomingImage.bits() + ptr.setsize(height * width * 4) + arr = np.frombuffer(ptr, np.uint8).reshape((height, width, 4)) + return arr + + +def readImage(image_file) -> list: + if os.path.basename(image_file)[-3:] == "pdf": + imgs = [] + with fitz.open(image_file) as pdf: + for pg in range(0, pdf.pageCount): + page = pdf[pg] + mat = fitz.Matrix(2, 2) + pm = page.getPixmap(matrix=mat, alpha=False) + + # if width or height > 2000 pixels, don't enlarge the image + if pm.width > 2000 or pm.height > 2000: + pm = page.getPixmap(matrix=fitz.Matrix(1, 1), alpha=False) + + img = Image.frombytes("RGB", [pm.width, pm.height], pm.samples) + img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR) + imgs.append(img) + else: + img = cv2.imread(image_file, cv2.IMREAD_COLOR) + if img is not None: + imgs = [img] + + return imgs + + +class Worker(QThread): + progressBarValue = Signal(int) + progressBarRange = Signal(int) + endsignal = Signal() + exceptedsignal = Signal(str) # 发送一个异常信号 + loopFlag = True + + def __init__(self, predictors, save_pdf, vis_font_path, use_pdf2docx_api): + super(Worker, self).__init__() + self.predictors = predictors + self.save_pdf = save_pdf + self.vis_font_path = vis_font_path + self.lang = "EN" + self.imagePaths = [] + self.use_pdf2docx_api = use_pdf2docx_api + self.outputDir = None + self.totalPageCnt = 0 + self.pageCnt = 0 + self.setStackSize(1024 * 1024) + + def setImagePath(self, imagePaths): + self.imagePaths = imagePaths + + def setLang(self, lang): + self.lang = lang + + def setOutputDir(self, outputDir): + self.outputDir = outputDir + + def setPDFParser(self, enabled): + self.use_pdf2docx_api = enabled + + def resetPageCnt(self): + self.pageCnt = 0 + + def resetTotalPageCnt(self): + self.totalPageCnt = 0 + + def ppocrPrecitor(self, imgs, img_name): + all_res = [] + # update progress bar ranges + self.totalPageCnt += len(imgs) + self.progressBarRange.emit(self.totalPageCnt) + # processing pages + for index, img in enumerate(imgs): + res, time_dict = self.predictors[self.lang](img) + + # save output + save_structure_res(res, self.outputDir, img_name) + # draw_img = draw_structure_result(img, res, self.vis_font_path) + # img_save_path = os.path.join(self.outputDir, img_name, 'show_{}.jpg'.format(index)) + # if res != []: + # cv2.imwrite(img_save_path, draw_img) + + # recovery + h, w, _ = img.shape + res = sorted_layout_boxes(res, w) + all_res += res + self.pageCnt += 1 + self.progressBarValue.emit(self.pageCnt) + + if all_res != []: + try: + convert_info_docx(imgs, all_res, self.outputDir, img_name) + except Exception as ex: + print( + "error in layout recovery image:{}, err msg: {}".format( + img_name, ex + ) + ) + print("Predict time : {:.3f}s".format(time_dict["all"])) + print("result save to {}".format(self.outputDir)) + + def run(self): + self.resetPageCnt() + self.resetTotalPageCnt() + try: + os.makedirs(self.outputDir, exist_ok=True) + for i, image_file in enumerate(self.imagePaths): + if not self.loopFlag: + break + # using use_pdf2docx_api for PDF parsing + if self.use_pdf2docx_api and os.path.basename(image_file)[-3:] == "pdf": + try_import("pdf2docx") + from pdf2docx.converter import Converter + + self.totalPageCnt += 1 + self.progressBarRange.emit(self.totalPageCnt) + print("===============using use_pdf2docx_api===============") + img_name = os.path.basename(image_file).split(".")[0] + docx_file = os.path.join(self.outputDir, "{}.docx".format(img_name)) + cv = Converter(image_file) + cv.convert(docx_file) + cv.close() + print("docx save to {}".format(docx_file)) + self.pageCnt += 1 + self.progressBarValue.emit(self.pageCnt) + else: + # using PPOCR for PDF/Image parsing + imgs = readImage(image_file) + if len(imgs) == 0: + continue + img_name = os.path.basename(image_file).split(".")[0] + os.makedirs(os.path.join(self.outputDir, img_name), exist_ok=True) + self.ppocrPrecitor(imgs, img_name) + # file processed + self.endsignal.emit() + # self.exec() + except Exception as e: + self.exceptedsignal.emit(str(e)) # 将异常发送给UI进程 + + +class APP_Image2Doc(QWidget): + def __init__(self): + super().__init__() + # self.setFixedHeight(100) + # self.setFixedWidth(520) + + # settings + self.imagePaths = [] + # self.screenShotWg = ScreenShotWidget() + self.screenShot = None + self.save_pdf = False + self.output_dir = None + self.vis_font_path = os.path.join(root, "doc", "fonts", "simfang.ttf") + self.use_pdf2docx_api = False + + # ProgressBar + self.pb = QProgressBar() + self.pb.setRange(0, 100) + self.pb.setValue(0) + + # 初始化界面 + self.setupUi() + + # 下载模型 + self.downloadModels(URLs_EN) + self.downloadModels(URLs_CN) + + # 初始化模型 + predictors = { + "EN": self.initPredictor("EN"), + "CN": self.initPredictor("CN"), + } + + # 设置工作进程 + self._thread = Worker( + predictors, self.save_pdf, self.vis_font_path, self.use_pdf2docx_api + ) + self._thread.progressBarValue.connect(self.handleProgressBarUpdateSingal) + self._thread.endsignal.connect(self.handleEndsignalSignal) + # self._thread.finished.connect(QObject.deleteLater) + self._thread.progressBarRange.connect(self.handleProgressBarRangeSingal) + self._thread.exceptedsignal.connect(self.handleThreadException) + self.time_start = 0 # save start time + + def setupUi(self): + self.setObjectName("MainWindow") + self.setWindowTitle(__APPNAME__ + " " + __VERSION__) + + layout = QGridLayout() + + self.openFileButton = QPushButton("打开文件") + self.openFileButton.setIcon(QIcon(QPixmap("./icons/folder-plus.png"))) + layout.addWidget(self.openFileButton, 0, 0, 1, 1) + self.openFileButton.clicked.connect(self.handleOpenFileSignal) + + # screenShotButton = QPushButton("截图识别") + # layout.addWidget(screenShotButton, 0, 1, 1, 1) + # screenShotButton.clicked.connect(self.screenShotSlot) + # screenShotButton.setEnabled(False) # temporarily disenble + + self.startCNButton = QPushButton("中文转换") + self.startCNButton.setIcon(QIcon(QPixmap("./icons/chinese.png"))) + layout.addWidget(self.startCNButton, 0, 1, 1, 1) + self.startCNButton.clicked.connect( + functools.partial(self.handleStartSignal, "CN", False) + ) + + self.startENButton = QPushButton("英文转换") + self.startENButton.setIcon(QIcon(QPixmap("./icons/english.png"))) + layout.addWidget(self.startENButton, 0, 2, 1, 1) + self.startENButton.clicked.connect( + functools.partial(self.handleStartSignal, "EN", False) + ) + + self.PDFParserButton = QPushButton("PDF解析", self) + layout.addWidget(self.PDFParserButton, 0, 3, 1, 1) + self.PDFParserButton.clicked.connect( + functools.partial(self.handleStartSignal, "CN", True) + ) + + self.showResultButton = QPushButton("显示结果") + self.showResultButton.setIcon(QIcon(QPixmap("./icons/folder-open.png"))) + layout.addWidget(self.showResultButton, 0, 4, 1, 1) + self.showResultButton.clicked.connect(self.handleShowResultSignal) + + # ProgressBar + layout.addWidget(self.pb, 2, 0, 1, 5) + # time estimate label + self.timeEstLabel = QLabel(("Time Left: --")) + layout.addWidget(self.timeEstLabel, 3, 0, 1, 5) + + self.setLayout(layout) + + def downloadModels(self, URLs): + # using custom model + tar_file_name_list = [ + "inference.pdiparams", + "inference.pdiparams.info", + "inference.pdmodel", + "model.pdiparams", + "model.pdiparams.info", + "model.pdmodel", + ] + model_path = os.path.join(root, "inference") + os.makedirs(model_path, exist_ok=True) + + # download and unzip models + for name in URLs.keys(): + url = URLs[name] + print("Try downloading file: {}".format(url)) + tarname = url.split("/")[-1] + tarpath = os.path.join(model_path, tarname) + if os.path.exists(tarpath): + print("File have already exist. skip") + else: + try: + download_with_progressbar(url, tarpath) + except Exception as e: + print("Error occurred when downloading file, error message:") + print(e) + + # unzip model tar + try: + with tarfile.open(tarpath, "r") as tarObj: + storage_dir = os.path.join(model_path, name) + os.makedirs(storage_dir, exist_ok=True) + for member in tarObj.getmembers(): + filename = None + for tar_file_name in tar_file_name_list: + if tar_file_name in member.name: + filename = tar_file_name + if filename is None: + continue + file = tarObj.extractfile(member) + with open(os.path.join(storage_dir, filename), "wb") as f: + f.write(file.read()) + except Exception as e: + print("Error occurred when unziping file, error message:") + print(e) + + def initPredictor(self, lang="EN"): + # init predictor args + args = parse_args() + args.table_max_len = 488 + args.ocr = True + args.recovery = True + args.save_pdf = self.save_pdf + args.table_char_dict_path = os.path.join( + root, "ppocr", "utils", "dict", "table_structure_dict.txt" + ) + if lang == "EN": + args.det_model_dir = os.path.join( + root, "inference", "en_PP-OCRv3_det_infer" # 此处从这里找到模型存放位置 + ) + args.rec_model_dir = os.path.join( + root, "inference", "en_PP-OCRv3_rec_infer" + ) + args.table_model_dir = os.path.join( + root, "inference", "en_ppstructure_mobile_v2.0_SLANet_infer" + ) + args.output = os.path.join(root, "output") # 结果保存路径 + args.layout_model_dir = os.path.join( + root, "inference", "picodet_lcnet_x1_0_fgd_layout_infer" + ) + lang_dict = DICT_EN + elif lang == "CN": + args.det_model_dir = os.path.join( + root, "inference", "cn_PP-OCRv3_det_infer" # 此处从这里找到模型存放位置 + ) + args.rec_model_dir = os.path.join( + root, "inference", "cn_PP-OCRv3_rec_infer" + ) + args.table_model_dir = os.path.join( + root, "inference", "cn_ppstructure_mobile_v2.0_SLANet_infer" + ) + args.output = os.path.join(root, "output") # 结果保存路径 + args.layout_model_dir = os.path.join( + root, "inference", "picodet_lcnet_x1_0_fgd_layout_cdla_infer" + ) + lang_dict = DICT_CN + else: + raise ValueError("Unsupported language") + args.rec_char_dict_path = os.path.join( + root, "ppocr", "utils", lang_dict["rec_char_dict_path"] + ) + args.layout_dict_path = os.path.join( + root, "ppocr", "utils", "dict", "layout_dict", lang_dict["layout_dict_path"] + ) + # init predictor + return StructureSystem(args) + + def handleOpenFileSignal(self): + """ + 可以多选图像文件 + """ + selectedFiles = QFileDialog.getOpenFileNames( + self, "多文件选择", "/", "图片文件 (*.png *.jpeg *.jpg *.bmp *.pdf)" + )[0] + if len(selectedFiles) > 0: + self.imagePaths = selectedFiles + self.screenShot = None # discard screenshot temp image + self.pb.setValue(0) + + # def screenShotSlot(self): + # ''' + # 选定图像文件和截图的转换过程只能同时进行一个 + # 截图只能同时转换一个 + # ''' + # self.screenShotWg.start() + # if self.screenShotWg.captureImage: + # self.screenShot = self.screenShotWg.captureImage + # self.imagePaths.clear() # discard openfile temp list + # self.pb.setRange(0, 1) + # self.pb.setValue(0) + + def handleStartSignal(self, lang="EN", pdfParser=False): + if self.screenShot: # for screenShot + img_name = "screenshot_" + time.strftime("%Y%m%d%H%M%S", time.localtime()) + image = QImageToCvMat(self.screenShot) + self.predictAndSave(image, img_name, lang) + # update Progress Bar + self.pb.setValue(1) + QMessageBox.information(self, "Information", "文档提取完成") + elif len(self.imagePaths) > 0: # for image file selection + # Must set image path list and language before start + self.output_dir = os.path.join( + os.path.dirname(self.imagePaths[0]), "output" + ) # output_dir should be same as imagepath + self._thread.setOutputDir(self.output_dir) + self._thread.setImagePath(self.imagePaths) + self._thread.setLang(lang) + self._thread.setPDFParser(pdfParser) + # disable buttons + self.openFileButton.setEnabled(False) + self.startCNButton.setEnabled(False) + self.startENButton.setEnabled(False) + self.PDFParserButton.setEnabled(False) + # 启动工作进程 + self._thread.start() + self.time_start = time.time() # log start time + QMessageBox.information(self, "Information", "开始转换") + else: + QMessageBox.warning(self, "Information", "请选择要识别的文件或截图") + + def handleShowResultSignal(self): + if self.output_dir is None: + return + if os.path.exists(self.output_dir): + if platform.system() == "Windows": + os.startfile(self.output_dir) + else: + subprocess.check_call(["open", os.path.normpath(self.output_dir)]) + else: + QMessageBox.information(self, "Information", "输出文件不存在") + + def handleProgressBarUpdateSingal(self, i): + self.pb.setValue(i) + # calculate time left of recognition + lenbar = self.pb.maximum() + avg_time = ( + time.time() - self.time_start + ) / i # Use average time to prevent time fluctuations + time_left = str(datetime.timedelta(seconds=avg_time * (lenbar - i))).split(".")[ + 0 + ] # Remove microseconds + self.timeEstLabel.setText(f"Time Left: {time_left}") # show time left + + def handleProgressBarRangeSingal(self, max): + self.pb.setRange(0, max) + + def handleEndsignalSignal(self): + # enable buttons + self.openFileButton.setEnabled(True) + self.startCNButton.setEnabled(True) + self.startENButton.setEnabled(True) + self.PDFParserButton.setEnabled(True) + QMessageBox.information(self, "Information", "转换结束") + + def handleCBChangeSignal(self): + self._thread.setPDFParser(self.checkBox.isChecked()) + + def handleThreadException(self, message): + self._thread.quit() + QMessageBox.information(self, "Error", message) + + +def main(): + app = QApplication(sys.argv) + + window = APP_Image2Doc() # 创建对象 + window.show() # 全屏显示窗口 + + QApplication.processEvents() + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() diff --git a/ppstructure/predict_system.py b/ppstructure/predict_system.py new file mode 100644 index 0000000..152e989 --- /dev/null +++ b/ppstructure/predict_system.py @@ -0,0 +1,415 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import subprocess + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../"))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" +import cv2 +import json +import numpy as np +import time +import logging +from copy import deepcopy + +from paddle.utils import try_import +from ppocr.utils.utility import get_image_file_list, check_and_read +from ppocr.utils.logging import get_logger +from ppocr.utils.visual import draw_ser_results, draw_re_results +from tools.infer.predict_system import TextSystem +from tools.infer.predict_rec import TextRecognizer +from ppstructure.layout.predict_layout import LayoutPredictor +from ppstructure.table.predict_table import TableSystem, to_excel +from ppstructure.utility import parse_args, draw_structure_result, cal_ocr_word_box + +logger = get_logger() + + +class StructureSystem(object): + def __init__(self, args): + self.mode = args.mode + self.recovery = args.recovery + + self.image_orientation_predictor = None + if args.image_orientation: + import paddleclas + + self.image_orientation_predictor = paddleclas.PaddleClas( + model_name="text_image_orientation" + ) + + if self.mode == "structure": + if not args.show_log: + logger.setLevel(logging.INFO) + if args.layout == False and args.ocr == True: + args.ocr = False + logger.warning( + "When args.layout is false, args.ocr is automatically set to false" + ) + # init model + self.layout_predictor = None + self.text_system = None + self.table_system = None + self.formula_system = None + if args.layout: + self.layout_predictor = LayoutPredictor(args) + if args.ocr: + self.text_system = TextSystem(args) + if args.table: + if self.text_system is not None: + self.table_system = TableSystem( + args, + self.text_system.text_detector, + self.text_system.text_recognizer, + ) + else: + self.table_system = TableSystem(args) + if args.formula: + args_formula = deepcopy(args) + args_formula.rec_algorithm = args.formula_algorithm + args_formula.rec_model_dir = args.formula_model_dir + args_formula.rec_char_dict_path = args.formula_char_dict_path + args_formula.rec_batch_num = args.formula_batch_num + self.formula_system = TextRecognizer(args_formula) + + elif self.mode == "kie": + from ppstructure.kie.predict_kie_token_ser_re import SerRePredictor + + self.kie_predictor = SerRePredictor(args) + + self.return_word_box = args.return_word_box + + def __call__(self, img, return_ocr_result_in_table=False, img_idx=0): + time_dict = { + "image_orientation": 0, + "layout": 0, + "table": 0, + "table_match": 0, + "formula": 0, + "det": 0, + "rec": 0, + "kie": 0, + "all": 0, + } + start = time.time() + + if self.image_orientation_predictor is not None: + tic = time.time() + cls_result = self.image_orientation_predictor.predict(input_data=img) + cls_res = next(cls_result) + angle = cls_res[0]["label_names"][0] + cv_rotate_code = { + "90": cv2.ROTATE_90_COUNTERCLOCKWISE, + "180": cv2.ROTATE_180, + "270": cv2.ROTATE_90_CLOCKWISE, + } + if angle in cv_rotate_code: + img = cv2.rotate(img, cv_rotate_code[angle]) + toc = time.time() + time_dict["image_orientation"] = toc - tic + + if self.mode == "structure": + ori_im = img.copy() + if self.layout_predictor is not None: + layout_res, elapse = self.layout_predictor(img) + time_dict["layout"] += elapse + else: + h, w = ori_im.shape[:2] + layout_res = [dict(bbox=None, label="table", score=0.0)] + + # As reported in issues such as #10270 and #11665, the old + # implementation, which recognizes texts from the layout regions, + # has problems with OCR recognition accuracy. + # + # To enhance the OCR recognition accuracy, we implement a patch fix + # that first use text_system to detect and recognize all text information + # and then filter out relevant texts according to the layout regions. + text_res = None + if self.text_system is not None: + text_res, ocr_time_dict = self._predict_text(img) + time_dict["det"] += ocr_time_dict["det"] + time_dict["rec"] += ocr_time_dict["rec"] + + res_list = [] + for region in layout_res: + res = "" + if region["bbox"] is not None: + x1, y1, x2, y2 = region["bbox"] + x1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2) + roi_img = ori_im[y1:y2, x1:x2, :] + else: + x1, y1, x2, y2 = 0, 0, w, h + roi_img = ori_im + bbox = [x1, y1, x2, y2] + + if region["label"] == "table": + if self.table_system is not None: + res, table_time_dict = self.table_system( + roi_img, return_ocr_result_in_table + ) + time_dict["table"] += table_time_dict["table"] + time_dict["table_match"] += table_time_dict["match"] + time_dict["det"] += table_time_dict["det"] + time_dict["rec"] += table_time_dict["rec"] + + elif region["label"] == "equation" and self.formula_system is not None: + latex_res, formula_time = self.formula_system([roi_img]) + time_dict["formula"] += formula_time + res = {"latex": latex_res[0]} + + else: + if text_res is not None: + # Filter the text results whose regions intersect with the current layout bbox. + res = self._filter_text_res(text_res, bbox) + + res_list.append( + { + "type": region["label"].lower(), + "bbox": bbox, + "img": roi_img, + "res": res, + "img_idx": img_idx, + "score": region["score"], + } + ) + + end = time.time() + time_dict["all"] = end - start + return res_list, time_dict + + elif self.mode == "kie": + re_res, elapse = self.kie_predictor(img) + time_dict["kie"] = elapse + time_dict["all"] = elapse + return re_res[0], time_dict + + return None, None + + def _predict_text(self, img): + filter_boxes, filter_rec_res, ocr_time_dict = self.text_system(img) + + # remove style char, + # when using the recognition model trained on the PubtabNet dataset, + # it will recognize the text format in the table, such as + style_token = [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ] + res = [] + for box, rec_res in zip(filter_boxes, filter_rec_res): + rec_str, rec_conf = rec_res[0], rec_res[1] + for token in style_token: + if token in rec_str: + rec_str = rec_str.replace(token, "") + if self.return_word_box: + word_box_content_list, word_box_list = cal_ocr_word_box( + rec_str, box, rec_res[2] + ) + res.append( + { + "text": rec_str, + "confidence": float(rec_conf), + "text_region": box.tolist(), + "text_word": word_box_content_list, + "text_word_region": word_box_list, + } + ) + else: + res.append( + { + "text": rec_str, + "confidence": float(rec_conf), + "text_region": box.tolist(), + } + ) + return res, ocr_time_dict + + def _filter_text_res(self, text_res, bbox): + res = [] + for r in text_res: + box = r["text_region"] + rect = box[0][0], box[0][1], box[2][0], box[2][1] + if self._has_intersection(bbox, rect): + res.append(r) + return res + + def _has_intersection(self, rect1, rect2): + x_min1, y_min1, x_max1, y_max1 = rect1 + x_min2, y_min2, x_max2, y_max2 = rect2 + if x_min1 > x_max2 or x_max1 < x_min2: + return False + if y_min1 > y_max2 or y_max1 < y_min2: + return False + return True + + +def save_structure_res(res, save_folder, img_name, img_idx=0): + excel_save_folder = os.path.join(save_folder, img_name) + os.makedirs(excel_save_folder, exist_ok=True) + res_cp = deepcopy(res) + # save res + with open( + os.path.join(excel_save_folder, "res_{}.txt".format(img_idx)), + "w", + encoding="utf8", + ) as f: + for region in res_cp: + roi_img = region.pop("img") + f.write("{}\n".format(json.dumps(region))) + + if ( + region["type"].lower() == "table" + and len(region["res"]) > 0 + and "html" in region["res"] + ): + excel_path = os.path.join( + excel_save_folder, "{}_{}.xlsx".format(region["bbox"], img_idx) + ) + to_excel(region["res"]["html"], excel_path) + elif region["type"].lower() == "figure": + img_path = os.path.join( + excel_save_folder, "{}_{}.jpg".format(region["bbox"], img_idx) + ) + cv2.imwrite(img_path, roi_img) + + +def main(args): + image_file_list = get_image_file_list(args.image_dir) + image_file_list = image_file_list + image_file_list = image_file_list[args.process_id :: args.total_process_num] + + if not args.use_pdf2docx_api: + structure_sys = StructureSystem(args) + save_folder = os.path.join(args.output, structure_sys.mode) + os.makedirs(save_folder, exist_ok=True) + img_num = len(image_file_list) + + for i, image_file in enumerate(image_file_list): + logger.info("[{}/{}] {}".format(i, img_num, image_file)) + img, flag_gif, flag_pdf = check_and_read(image_file) + img_name = os.path.basename(image_file).split(".")[0] + + if args.recovery and args.use_pdf2docx_api and flag_pdf: + try_import("pdf2docx") + from pdf2docx.converter import Converter + + os.makedirs(args.output, exist_ok=True) + docx_file = os.path.join(args.output, "{}_api.docx".format(img_name)) + cv = Converter(image_file) + cv.convert(docx_file) + cv.close() + logger.info("docx save to {}".format(docx_file)) + continue + + if not flag_gif and not flag_pdf: + img = cv2.imread(image_file) + + if not flag_pdf: + if img is None: + logger.error("error in loading image:{}".format(image_file)) + continue + imgs = [img] + else: + imgs = img + + all_res = [] + for index, img in enumerate(imgs): + res, time_dict = structure_sys(img, img_idx=index) + img_save_path = os.path.join( + save_folder, img_name, "show_{}.jpg".format(index) + ) + os.makedirs(os.path.join(save_folder, img_name), exist_ok=True) + if structure_sys.mode == "structure" and res != []: + draw_img = draw_structure_result(img, res, args.vis_font_path) + save_structure_res(res, save_folder, img_name, index) + elif structure_sys.mode == "kie": + if structure_sys.kie_predictor.predictor is not None: + draw_img = draw_re_results(img, res, font_path=args.vis_font_path) + else: + draw_img = draw_ser_results(img, res, font_path=args.vis_font_path) + + with open( + os.path.join(save_folder, img_name, "res_{}_kie.txt".format(index)), + "w", + encoding="utf8", + ) as f: + res_str = "{}\t{}\n".format( + image_file, json.dumps({"ocr_info": res}, ensure_ascii=False) + ) + f.write(res_str) + if res != []: + cv2.imwrite(img_save_path, draw_img) + logger.info("result save to {}".format(img_save_path)) + if args.recovery and res != []: + from ppstructure.recovery.recovery_to_doc import ( + sorted_layout_boxes, + convert_info_docx, + ) + from ppstructure.recovery.recovery_to_markdown import ( + convert_info_markdown, + ) + + h, w, _ = img.shape + res = sorted_layout_boxes(res, w) + all_res += res + + if args.recovery and all_res != []: + try: + convert_info_docx(img, all_res, save_folder, img_name) + if args.recovery_to_markdown: + convert_info_markdown(all_res, save_folder, img_name) + except Exception as ex: + logger.error( + "error in layout recovery image:{}, err msg: {}".format( + image_file, ex + ) + ) + continue + logger.info("Predict time : {:.3f}s".format(time_dict["all"])) + + +if __name__ == "__main__": + args = parse_args() + if args.use_mp: + p_list = [] + total_process_num = args.total_process_num + for process_id in range(total_process_num): + cmd = ( + [sys.executable, "-u"] + + sys.argv + + ["--process_id={}".format(process_id), "--use_mp={}".format(False)] + ) + p = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stdout) + p_list.append(p) + for p in p_list: + p.wait() + else: + main(args) diff --git a/ppstructure/recovery/README.md b/ppstructure/recovery/README.md new file mode 100644 index 0000000..04158b9 --- /dev/null +++ b/ppstructure/recovery/README.md @@ -0,0 +1,213 @@ +English | [简体中文](README_ch.md) + +# Layout Recovery + +- [1. Introduction](#1) +- [2. Install](#2) + - [2.1 Install PaddlePaddle](#2.1) + - [2.2 Install PaddleOCR](#2.2) +- [3. Quick Start using standard PDF parse](#3) +- [4. Quick Start using image format PDF parse ](#4) + - [4.1 Download models](#4.1) + - [4.2 Layout recovery](#4.2) +- [5. More](#5) + + + +## 1. Introduction + +The layout recovery module is used to restore the image or pdf to an +editable Word file consistent with the original image layout. + +Two layout recovery methods are provided, you can choose by PDF format: + +- **Standard PDF parse(the input is standard PDF)**: Python based PDF to word library [pdf2docx] (https://github.com/dothinking/pdf2docx) is optimized, the method extracts data from PDF with PyMuPDF, then parse layout with rule, finally, generate docx with python-docx. + +- **Image format PDF parse(the input can be standard PDF or image format PDF)**: Layout recovery combines [layout analysis](../layout/README.md)、[table recognition](../table/README.md) to better recover images, tables, titles, etc. supports input files in PDF and document image formats in Chinese and English. + +The input formats and application scenarios of the two methods are as follows: + +| method | input formats | application scenarios/problem | +| :-----: | :----------: | :----------------------------------------------------------: | +| Standard PDF parse | pdf | Advantages: Better recovery for non-paper documents, each page remains on the same page after restoration
    Disadvantages: English characters in some Chinese documents are garbled, some contents are still beyond the current page, the whole page content is restored to the table format, and the recovery effect of some pictures is not good | +| Image format PDF parse( | pdf、picture | Advantages: More suitable for paper document content recovery, OCR recognition effect is more good
    Disadvantages: Currently, the recovery is based on rules, the effect of content typesetting (spacing, fonts, etc.) need to be further improved, and the effect of layout recovery depends on layout analysis | + +The following figure shows the effect of restoring the layout of documents by using PDF parse: + +
    + +
    + +The following figures show the effect of restoring the layout of English and Chinese documents by using OCR technique: + +
    + +
    + +
    + +
    + + + + +## 2. Install + + + +### 2.1 Install PaddlePaddle + +```bash +python3 -m pip install --upgrade pip + +# If you have cuda9 or cuda10 installed on your machine, please run the following command to install +python3 -m pip install "paddlepaddle-gpu" -i https://mirror.baidu.com/pypi/simple + +# CPU installation +python3 -m pip install "paddlepaddle" -i https://mirror.baidu.com/pypi/simple +```` + +For more requirements, please refer to the instructions in [Installation Documentation](https://www.paddlepaddle.org.cn/en/install/quick?docurl=/documentation/docs/en/install/pip/macos-pip_en.html). + + + +### 2.2 Install PaddleOCR + +- **(1) Download source code** + +```bash +[Recommended] git clone https://github.com/PaddlePaddle/PaddleOCR + +# If the pull cannot be successful due to network problems, you can also choose to use the hosting on the code cloud: +git clone https://gitee.com/paddlepaddle/PaddleOCR + +# Note: Code cloud hosting code may not be able to synchronize the update of this github project in real time, there is a delay of 3 to 5 days, please use the recommended method first. +```` + +- **(2) Install recovery `requirements`** + +The layout restoration is exported as docx files, so python-docx API need to be installed, and PyMuPDF api([requires Python >= 3.7](https://pypi.org/project/PyMuPDF/)) need to be installed to process the input files in pdf format. + +Install all the libraries by running the following command: + +```bash +python3 -m pip install -r ppstructure/recovery/requirements.txt +```` + + And if using pdf parse method, we need to install pdf2docx api. + +```bash +wget https://paddleocr.bj.bcebos.com/whl/pdf2docx-0.0.0-py3-none-any.whl +pip3 install pdf2docx-0.0.0-py3-none-any.whl +``` + + + +## 3. Quick Start using standard PDF parse + +`use_pdf2docx_api` use PDF parse for layout recovery, The whl package is also provided for quick use, follow the above code, for more information please refer to [quickstart](../docs/quickstart_en.md) for details. + +```bash +# install paddleocr +pip3 install "paddleocr>=2.6" +paddleocr --image_dir=ppstructure/docs/recovery/UnrealText.pdf --type=structure --recovery=true --use_pdf2docx_api=true +``` + +Command line: + +```bash +python3 predict_system.py \ + --image_dir=ppstructure/docs/recovery/UnrealText.pdf \ + --recovery=True \ + --use_pdf2docx_api=True \ + --output=../output/ +``` + + +## 4. Quick Start using image format PDF parse + +Through layout analysis, we divided the image/PDF documents into regions, located the key regions, such as text, table, picture, etc., and recorded the location, category, and regional pixel value information of each region. Different regions are processed separately, where: + +- OCR detection and recognition is performed in the text area, and the coordinates of the OCR detection box and the text content information are added on the basis of the previous information + +- The table area identifies tables and records html and text information of tables +- Save the image directly + +We can restore the test picture through the layout information, OCR detection and recognition structure, table information, and saved pictures. + +The whl package is also provided for quick use, follow the above code, for more information please refer to [quickstart](../docs/quickstart_en.md) for details. + +```bash +paddleocr --image_dir=ppstructure/docs/table/1.png --type=structure --recovery=true --lang='en' +``` + + +### 4.1 Download models + +If input is English document, download English models: + +```bash +cd PaddleOCR/ppstructure + +# download model +mkdir inference && cd inference +# Download the detection model of the ultra-lightweight English PP-OCRv3 model and unzip it +wget https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_det_infer.tar && tar xf en_PP-OCRv3_det_infer.tar +# Download the recognition model of the ultra-lightweight English PP-OCRv3 model and unzip it +wget https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_rec_infer.tar && tar xf en_PP-OCRv3_rec_infer.tar +# Download the ultra-lightweight English table inch model and unzip it +wget https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/paddle3.0b2/en_ppstructure_mobile_v2.0_SLANet_infer.tar +tar xf en_ppstructure_mobile_v2.0_SLANet_infer.tar +# Download the layout model of publaynet dataset and unzip it +wget https://paddleocr.bj.bcebos.com/ppstructure/models/layout/picodet_lcnet_x1_0_fgd_layout_infer.tar +tar xf picodet_lcnet_x1_0_fgd_layout_infer.tar +cd .. +``` +If input is Chinese document,download Chinese models: +[Chinese and English ultra-lightweight PP-OCRv3 model](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/README.md#pp-ocr-series-model-listupdate-on-september-8th)、[table recognition model](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/ppstructure/docs/models_list.md#22-表格识别模型)、[layout analysis model](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/ppstructure/docs/models_list.md#1-版面分析模型) + + +### 4.2 Layout recovery + + +```bash +python3 predict_system.py \ + --image_dir=./docs/table/1.png \ + --det_model_dir=inference/en_PP-OCRv3_det_infer \ + --rec_model_dir=inference/en_PP-OCRv3_rec_infer \ + --rec_char_dict_path=../ppocr/utils/en_dict.txt \ + --table_model_dir=inference/en_ppstructure_mobile_v2.0_SLANet_infer \ + --table_char_dict_path=../ppocr/utils/dict/table_structure_dict.txt \ + --layout_model_dir=inference/picodet_lcnet_x1_0_fgd_layout_infer \ + --layout_dict_path=../ppocr/utils/dict/layout_dict/layout_publaynet_dict.txt \ + --vis_font_path=../doc/fonts/simfang.ttf \ + --recovery=True \ + --output=../output/ +``` + +After running, the docx of each picture will be saved in the directory specified by the output field + +Field: + +- image_dir:test file, can be picture, picture directory, pdf file, pdf file directory +- det_model_dir:OCR detection model path +- rec_model_dir:OCR recognition model path +- rec_char_dict_path:OCR recognition dict path. If the Chinese model is used, change to "../ppocr/utils/ppocr_keys_v1.txt". And if you trained the model on your own dataset, change to the trained dictionary +- table_model_dir:table recognition model path +- table_char_dict_path:table recognition dict path. If the Chinese model is used, no need to change +- layout_model_dir:layout analysis model path +- layout_dict_path:layout analysis dict path. If the Chinese model is used, change to "../ppocr/utils/dict/layout_dict/layout_cdla_dict.txt" +- recovery:whether to enable layout of recovery, default False +- output:save the recovery result path + + + +## 5. More + +For training, evaluation and inference tutorial for text detection models, please refer to [text detection doc](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/doc/doc_en/detection_en.md). + +For training, evaluation and inference tutorial for text recognition models, please refer to [text recognition doc](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/doc/doc_en/recognition_en.md). + +For training, evaluation and inference tutorial for layout analysis models, please refer to [layout analysis doc](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/ppstructure/layout/README.md) + +For training, evaluation and inference tutorial for table recognition models, please refer to [table recognition doc](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/ppstructure/table/README.md) diff --git a/ppstructure/recovery/README_ch.md b/ppstructure/recovery/README_ch.md new file mode 100644 index 0000000..01f843f --- /dev/null +++ b/ppstructure/recovery/README_ch.md @@ -0,0 +1,222 @@ +[English](README.md) | 简体中文 + +# 版面恢复 + +- [1. 简介](#1) +- [2. 安装](#2) + - [2.1 安装PaddlePaddle](#2.1) + - [2.2 安装PaddleOCR](#2.2) +- [3.使用标准PDF解析进行版面恢复](#3) +- [4. 使用图片格式PDF解析进行版面恢复](#4) + - [4.1 下载模型](#4.1) + - [4.2 版面恢复](#4.2) +- [5. 更多](#5) + + + +## 1. 简介 + +版面恢复就是将输入的图片、pdf内容仍然像原文档那样排列着,段落不变、顺序不变的输出到word文档中等。 + +提供了2种版面恢复方法,可根据输入PDF的格式进行选择: + +- **标准PDF解析(输入须为标准PDF)**:基于Python的pdf转word库[pdf2docx](https://github.com/dothinking/pdf2docx)进行优化,该方法通过PyMuPDF获取页面元素,然后利用规则解析章节、段落、表格等布局及样式,最后通过python-docx将解析的内容元素重建到word文档中。 +- **图片格式PDF解析(输入可为标准PDF或图片格式PDF)**:结合[版面分析](../layout/README_ch.md)、[表格识别](../table/README_ch.md)技术,从而更好地恢复图片、表格、标题等内容,支持中、英文pdf文档、文档图片格式的输入文件。 + +2种方法输入格式、适用场景如下: + +| 方法 | 支持输入文件 | 适用场景/存在问题 | +| :-------------: | :----------: | :----------------------------------------------------------: | +| 标准PDF解析 | pdf | 优点:非论文文档恢复效果更优、每一页内容恢复后仍在同一页
    缺点:有些中文文档中的英文乱码、仍存在内容超出当前页面的情况、整页内容恢复为表格格式、部分图片恢复效果不佳 | +| 图片格式PDF解析 | pdf、图片 | 优点:更适合论文文档正文内容的恢复、中英文文档OCR识别效果好
    缺点:目前内容恢复基于规则,内容排版效果(间距、字体等)待进一步提升、版面恢复效果依赖于版面分析效果 | + +下图展示了通过PDF解析版面恢复效果: + +
    + +
    + +下图分别展示了通过OCR技术,英文文档和中文文档版面恢复的效果: + +
    + +
    +
    + +
    + + +## 2. 安装 + + + +### 2.1 安装PaddlePaddle + +```bash +python3 -m pip install --upgrade pip + +# 您的机器安装的是CUDA9或CUDA10,请运行以下命令安装 +python3 -m pip install "paddlepaddle-gpu" -i https://mirror.baidu.com/pypi/simple + +# 您的机器是CPU,请运行以下命令安装 +python3 -m pip install "paddlepaddle" -i https://mirror.baidu.com/pypi/simple + +``` + +更多需求,请参照[安装文档](https://www.paddlepaddle.org.cn/install/quick)中的说明进行操作。 + + + +### 2.2 安装PaddleOCR + +- **(1)下载版面恢复源码** + +```bash +【推荐】git clone https://github.com/PaddlePaddle/PaddleOCR + +# 如果因为网络问题无法pull成功,也可选择使用码云上的托管: +git clone https://gitee.com/paddlepaddle/PaddleOCR + +# 注:码云托管代码可能无法实时同步本github项目更新,存在3~5天延时,请优先使用推荐方式。 +``` + +- **(2)安装recovery的`requirements`** + +版面恢复导出为docx文件,所以需要安装Python处理word文档的python-docx API,同时处理pdf格式的输入文件,需要安装PyMuPDF API([要求Python >= 3.7](https://pypi.org/project/PyMuPDF/))。 + +通过如下命令安装全部库: + +```bash +python3 -m pip install -r ppstructure/recovery/requirements.txt +``` + +使用pdf2docx库解析的方式恢复文档需要安装优化的pdf2docx。 + +```bash +wget https://paddleocr.bj.bcebos.com/whl/pdf2docx-0.0.0-py3-none-any.whl +pip3 install pdf2docx-0.0.0-py3-none-any.whl +``` + + + +## 3.使用标准PDF解析进行版面恢复 + +`use_pdf2docx_api`表示使用PDF解析的方式进行版面恢复,通过whl包的形式方便快速使用,代码如下,更多信息详见 [quickstart](../docs/quickstart.md)。 + +```bash +# 安装 paddleocr,推荐使用2.6版本 +pip3 install "paddleocr>=2.6" +paddleocr --image_dir=ppstructure/docs/recovery/UnrealText.pdf --type=structure --recovery=true --use_pdf2docx_api=true +``` + +通过命令行的方式: + +```bash +python3 predict_system.py \ + --image_dir=ppstructure/docs/recovery/UnrealText.pdf \ + --recovery=True \ + --use_pdf2docx_api=True \ + --output=../output/ +``` + + + +## 4.使用图片格式PDF解析进行版面恢复 + +我们通过版面分析对图片/pdf形式的文档进行区域划分,定位其中的关键区域,如文字、表格、图片等,记录每个区域的位置、类别、区域像素值信息。对不同的区域分别处理,其中: + +- 文字区域直接进行OCR检测和识别,在之前信息基础上增加OCR检测框坐标和文本内容信息 + +- 表格区域进行表格识别,记录表格html和文字信息 +- 图片直接保存 + +我们通过版面信息、OCR检测和识别结构、表格信息、保存的图片,对测试图片进行恢复即可。 + +提供如下代码实现版面恢复,也提供了whl包的形式方便快速使用,代码如下,更多信息详见 [quickstart](../docs/quickstart.md)。 + +```bash +# 安装 paddleocr,推荐使用2.6版本 +pip3 install "paddleocr>=2.6" +# 中文测试图 +paddleocr --image_dir=ppstructure/docs/table/1.png --type=structure --recovery=true +# 英文测试图 +paddleocr --image_dir=ppstructure/docs/table/1.png --type=structure --recovery=true --lang='en' +# pdf测试文件 +paddleocr --image_dir=ppstructure/docs/recovery/UnrealText.pdf --type=structure --recovery=true --lang='en' +``` + + + +### 4.1 下载模型 + +如果输入为英文文档类型,下载OCR检测和识别、版面分析、表格识别的英文模型 + +```bash +cd PaddleOCR/ppstructure + +# 下载模型 +mkdir inference && cd inference +# 下载英文超轻量PP-OCRv3检测模型并解压 +wget https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_det_infer.tar && tar xf en_PP-OCRv3_det_infer.tar +# 下载英文超轻量PP-OCRv3识别模型并解压 +wget https://paddleocr.bj.bcebos.com/PP-OCRv3/english/en_PP-OCRv3_rec_infer.tar && tar xf en_PP-OCRv3_rec_infer.tar +# 下载英文表格识别模型并解压 +wget https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/paddle3.0b2/en_ppstructure_mobile_v2.0_SLANet_infer.tar +tar xf en_ppstructure_mobile_v2.0_SLANet_infer.tar +# 下载英文版面分析模型 +wget https://paddleocr.bj.bcebos.com/ppstructure/models/layout/picodet_lcnet_x1_0_fgd_layout_infer.tar +tar xf picodet_lcnet_x1_0_fgd_layout_infer.tar +cd .. +``` + +如果输入为中文文档类型,在下述链接中下载中文模型即可: + +[PP-OCRv3中英文超轻量文本检测和识别模型](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/README_ch.md#pp-ocr%E7%B3%BB%E5%88%97%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8%E6%9B%B4%E6%96%B0%E4%B8%AD)、[表格识别模型](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/ppstructure/docs/models_list.md#22-表格识别模型)、[版面分析模型](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/ppstructure/docs/models_list.md#1-版面分析模型) + + + +### 4.2 版面恢复 + +使用下载的模型恢复给定文档的版面,以英文模型为例,执行如下命令: + +```bash +python3 predict_system.py \ + --image_dir=./docs/table/1.png \ + --det_model_dir=inference/en_PP-OCRv3_det_infer \ + --rec_model_dir=inference/en_PP-OCRv3_rec_infer \ + --rec_char_dict_path=../ppocr/utils/en_dict.txt \ + --table_model_dir=inference/en_ppstructure_mobile_v2.0_SLANet_infer \ + --table_char_dict_path=../ppocr/utils/dict/table_structure_dict.txt \ + --layout_model_dir=inference/picodet_lcnet_x1_0_fgd_layout_infer \ + --layout_dict_path=../ppocr/utils/dict/layout_dict/layout_publaynet_dict.txt \ + --vis_font_path=../doc/fonts/simfang.ttf \ + --recovery=True \ + --output=../output/ +``` + +运行完成后,恢复版面的docx文档会保存到`output`字段指定的目录下 + +字段含义: + +- image_dir:测试文件,可以是图片、图片目录、pdf文件、pdf文件目录 +- det_model_dir:OCR检测模型路径 +- rec_model_dir:OCR识别模型路径 +- rec_char_dict_path:OCR识别字典,如果更换为中文模型,需要更改为"../ppocr/utils/ppocr_keys_v1.txt",如果您在自己的数据集上训练的模型,则更改为训练的字典的文件 +- table_model_dir:表格识别模型路径 +- table_char_dict_path:表格识别字典,如果更换为中文模型,不需要更换字典 +- layout_model_dir:版面分析模型路径 +- layout_dict_path:版面分析字典,如果更换为中文模型,需要更改为"../ppocr/utils/dict/layout_dict/layout_cdla_dict.txt" +- recovery:是否进行版面恢复,默认False +- output:版面恢复结果保存路径 + + + +## 5. 更多 + +关于OCR检测模型的训练评估与推理,请参考:[文本检测教程](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/doc/doc_ch/detection.md) + +关于OCR识别模型的训练评估与推理,请参考:[文本识别教程](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/doc/doc_ch/recognition.md) + +关于版面分析模型的训练评估与推理,请参考:[版面分析教程](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/ppstructure/layout/README_ch.md) + +关于表格识别模型的训练评估与推理,请参考:[表格识别教程](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/ppstructure/table/README_ch.md) diff --git a/ppstructure/recovery/__init__.py b/ppstructure/recovery/__init__.py new file mode 100644 index 0000000..1d11e26 --- /dev/null +++ b/ppstructure/recovery/__init__.py @@ -0,0 +1,13 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/ppstructure/recovery/recovery_to_doc.py b/ppstructure/recovery/recovery_to_doc.py new file mode 100644 index 0000000..9ee3852 --- /dev/null +++ b/ppstructure/recovery/recovery_to_doc.py @@ -0,0 +1,155 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from copy import deepcopy + +from docx import Document +from docx import shared +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.enum.section import WD_SECTION +from docx.oxml.ns import qn +from docx.enum.table import WD_TABLE_ALIGNMENT + +from ppstructure.recovery.table_process import HtmlToDocx + +from ppocr.utils.logging import get_logger + +logger = get_logger() + + +def convert_info_docx(img, res, save_folder, img_name): + doc = Document() + doc.styles["Normal"].font.name = "Times New Roman" + doc.styles["Normal"]._element.rPr.rFonts.set(qn("w:eastAsia"), "宋体") + doc.styles["Normal"].font.size = shared.Pt(6.5) + + flag = 1 + for i, region in enumerate(res): + if not region["res"] and region["type"].lower() != "figure": + continue + img_idx = region["img_idx"] + if flag == 2 and region["layout"] == "single": + section = doc.add_section(WD_SECTION.CONTINUOUS) + section._sectPr.xpath("./w:cols")[0].set(qn("w:num"), "1") + flag = 1 + elif flag == 1 and region["layout"] == "double": + section = doc.add_section(WD_SECTION.CONTINUOUS) + section._sectPr.xpath("./w:cols")[0].set(qn("w:num"), "2") + flag = 2 + + if region["type"].lower() == "figure": + excel_save_folder = os.path.join(save_folder, img_name) + img_path = os.path.join( + excel_save_folder, "{}_{}.jpg".format(region["bbox"], img_idx) + ) + paragraph_pic = doc.add_paragraph() + paragraph_pic.alignment = WD_ALIGN_PARAGRAPH.CENTER + run = paragraph_pic.add_run("") + if flag == 1: + run.add_picture(img_path, width=shared.Inches(5)) + elif flag == 2: + run.add_picture(img_path, width=shared.Inches(2)) + elif region["type"].lower() == "title": + doc.add_heading(region["res"][0]["text"]) + elif region["type"].lower() == "table": + parser = HtmlToDocx() + parser.table_style = "TableGrid" + parser.handle_table(region["res"]["html"], doc) + elif region["type"] == "equation" and "latex" in region["res"]: + pass + else: + paragraph = doc.add_paragraph() + paragraph_format = paragraph.paragraph_format + for i, line in enumerate(region["res"]): + if i == 0: + paragraph_format.first_line_indent = shared.Inches(0.25) + text_run = paragraph.add_run(line["text"] + " ") + text_run.font.size = shared.Pt(10) + + # save to docx + docx_path = os.path.join(save_folder, "{}_ocr.docx".format(img_name)) + doc.save(docx_path) + logger.info("docx save to {}".format(docx_path)) + + +def sorted_layout_boxes(res, w): + """ + Sort text boxes in order from top to bottom, left to right + args: + res(list):ppstructure results + return: + sorted results(list) + """ + num_boxes = len(res) + if num_boxes == 1: + res[0]["layout"] = "single" + return res + + sorted_boxes = sorted(res, key=lambda x: (x["bbox"][1], x["bbox"][0])) + _boxes = list(sorted_boxes) + + new_res = [] + res_left = [] + res_right = [] + i = 0 + + while True: + if i >= num_boxes: + break + if i == num_boxes - 1: + if ( + _boxes[i]["bbox"][1] > _boxes[i - 1]["bbox"][3] + and _boxes[i]["bbox"][0] < w / 2 + and _boxes[i]["bbox"][2] > w / 2 + ): + new_res += res_left + new_res += res_right + _boxes[i]["layout"] = "single" + new_res.append(_boxes[i]) + else: + if _boxes[i]["bbox"][2] > w / 2: + _boxes[i]["layout"] = "double" + res_right.append(_boxes[i]) + new_res += res_left + new_res += res_right + elif _boxes[i]["bbox"][0] < w / 2: + _boxes[i]["layout"] = "double" + res_left.append(_boxes[i]) + new_res += res_left + new_res += res_right + res_left = [] + res_right = [] + break + elif _boxes[i]["bbox"][0] < w / 4 and _boxes[i]["bbox"][2] < 3 * w / 4: + _boxes[i]["layout"] = "double" + res_left.append(_boxes[i]) + i += 1 + elif _boxes[i]["bbox"][0] > w / 4 and _boxes[i]["bbox"][2] > w / 2: + _boxes[i]["layout"] = "double" + res_right.append(_boxes[i]) + i += 1 + else: + new_res += res_left + new_res += res_right + _boxes[i]["layout"] = "single" + new_res.append(_boxes[i]) + res_left = [] + res_right = [] + i += 1 + if res_left: + new_res += res_left + if res_right: + new_res += res_right + return new_res diff --git a/ppstructure/recovery/recovery_to_markdown.py b/ppstructure/recovery/recovery_to_markdown.py new file mode 100644 index 0000000..8ef3370 --- /dev/null +++ b/ppstructure/recovery/recovery_to_markdown.py @@ -0,0 +1,187 @@ +# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re + +from ppocr.utils.logging import get_logger + +logger = get_logger() + + +def check_merge_method(in_region): + """Select the function to merge paragraph. + + Determine the paragraph merging method based on the positional + relationship between the text bbox and the first line of text in the text bbox. + + Args: + in_region: Elements with text type in the layout result. + + Returns: + Merge the functions of paragraph, convert_text_space_head or convert_text_space_tail. + """ + text_bbox = in_region["bbox"] + text_x1 = text_bbox[0] + frist_line_box = in_region["res"][0]["text_region"] + point_1 = frist_line_box[0] + point_2 = frist_line_box[2] + frist_line_x1 = point_1[0] + frist_line_height = abs(point_2[1] - point_1[1]) + x1_distance = frist_line_x1 - text_x1 + return ( + convert_text_space_head + if x1_distance > frist_line_height + else convert_text_space_tail + ) + + +def convert_text_space_head(in_region): + """The function to merge paragraph. + + The sign of dividing paragraph is that there are two spaces at the beginning. + + Args: + in_region: Elements with text type in the layout result. + + Returns: + The text content of the current text box. + """ + text = "" + pre_x = None + frist_line = True + for i, res in enumerate(in_region["res"]): + point1 = res["text_region"][0] + point2 = res["text_region"][2] + h = point2[1] - point1[1] + + if i == 0: + text += res["text"] + pre_x = point1[0] + continue + + x1 = point1[0] + if frist_line: + if abs(pre_x - x1) < h: + text += "\n\n" + text += res["text"] + frist_line = True + else: + text += res["text"] + frist_line = False + else: + same_paragh = abs(pre_x - x1) < h + if same_paragh: + text += res["text"] + frist_line = False + else: + text += "\n\n" + text += res["text"] + frist_line = True + pre_x = x1 + return text + + +def convert_text_space_tail(in_region): + """The function to merge paragraph. + + The symbol for dividing paragraph is a space at the end. + + Args: + in_region: Elements with text type in the layout result. + + Returns: + The text content of the current text box. + """ + text = "" + frist_line = True + text_bbox = in_region["bbox"] + width = text_bbox[2] - text_bbox[0] + for i, res in enumerate(in_region["res"]): + point1 = res["text_region"][0] + point2 = res["text_region"][2] + row_width = point2[0] - point1[0] + row_height = point2[1] - point1[1] + full_row_threshold = width - row_height + is_full = row_width >= full_row_threshold + + if frist_line: + text += "\n\n" + text += res["text"] + else: + text += res["text"] + + frist_line = not is_full + return text + + +def convert_info_markdown(res, save_folder, img_name): + """Save the recognition result as a markdown file. + + Args: + res: Recognition result + save_folder: Folder to save the markdown file + img_name: PDF file or image file name + + Returns: + None + """ + + def replace_special_char(content): + special_chars = ["*", "`", "~", "$"] + for char in special_chars: + content = content.replace(char, "\\" + char) + return content + + markdown_string = [] + + for i, region in enumerate(res): + if not region["res"] and region["type"].lower() != "figure": + continue + img_idx = region["img_idx"] + + if region["type"].lower() == "figure": + img_file_name = "{}_{}.jpg".format(region["bbox"], img_idx) + markdown_string.append( + f"""
    \n\t\n
    """ + ) + elif region["type"].lower() == "title": + markdown_string.append( + f"""# {region['res'][0]['text']}""" + + "".join( + [" " + one_region["text"] for one_region in region["res"][1:]] + ) + ) + elif region["type"].lower() == "table": + markdown_string.append(region["res"]["html"]) + elif region["type"].lower() == "header" or region["type"].lower() == "footer": + pass + elif region["type"].lower() == "equation" and "latex" in region["res"]: + markdown_string.append(f"""$${region["res"]["latex"]}$$""") + elif region["type"].lower() == "text": + merge_func = check_merge_method(region) + # logger.warning(f"use merge method:{merge_func.__name__}") + markdown_string.append(replace_special_char(merge_func(region))) + else: + string = "" + for line in region["res"]: + string += line["text"] + " " + markdown_string.append(string) + + md_path = os.path.join(save_folder, "{}_ocr.md".format(img_name)) + markdown_string = "\n\n".join(markdown_string) + markdown_string = re.sub(r"\n{3,}", "\n\n", markdown_string) + with open(md_path, "w", encoding="utf-8") as f: + f.write(markdown_string) + logger.info("markdown save to {}".format(md_path)) diff --git a/ppstructure/recovery/requirements.txt b/ppstructure/recovery/requirements.txt new file mode 100644 index 0000000..86ec03e --- /dev/null +++ b/ppstructure/recovery/requirements.txt @@ -0,0 +1,4 @@ +python-docx +beautifulsoup4 +fonttools>=4.43.0 +fire>=0.3.0 diff --git a/ppstructure/recovery/table_process.py b/ppstructure/recovery/table_process.py new file mode 100644 index 0000000..f5e01e6 --- /dev/null +++ b/ppstructure/recovery/table_process.py @@ -0,0 +1,325 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: https://github.com/weizwx/html2docx/blob/master/htmldocx/h2d.py +""" + +import re +import docx +from docx import Document +from bs4 import BeautifulSoup +from html.parser import HTMLParser + + +def get_table_rows(table_soup): + table_row_selectors = [ + "table > tr", + "table > thead > tr", + "table > tbody > tr", + "table > tfoot > tr", + ] + # If there's a header, body, footer or direct child tr tags, add row dimensions from there + return table_soup.select(", ".join(table_row_selectors), recursive=False) + + +def get_table_columns(row): + # Get all columns for the specified row tag. + return row.find_all(["th", "td"], recursive=False) if row else [] + + +def get_table_dimensions(table_soup): + # Get rows for the table + rows = get_table_rows(table_soup) + # Table is either empty or has non-direct children between table and tr tags + # Thus the row dimensions and column dimensions are assumed to be 0 + + cols = get_table_columns(rows[0]) if rows else [] + # Add colspan calculation column number + col_count = 0 + for col in cols: + colspan = col.attrs.get("colspan", 1) + col_count += int(colspan) + + return rows, col_count + + +def get_cell_html(soup): + # Returns string of td element with opening and closing tags removed + # Cannot use find_all as it only finds element tags and does not find text which + # is not inside an element + return " ".join([str(i) for i in soup.contents]) + + +def delete_paragraph(paragraph): + # https://github.com/python-openxml/python-docx/issues/33#issuecomment-77661907 + p = paragraph._element + p.getparent().remove(p) + p._p = p._element = None + + +def remove_whitespace(string, leading=False, trailing=False): + """Remove white space from a string. + Args: + string(str): The string to remove white space from. + leading(bool, optional): Remove leading new lines when True. + trailing(bool, optional): Remove trailing new lines when False. + Returns: + str: The input string with new line characters removed and white space squashed. + Examples: + Single or multiple new line characters are replaced with space. + >>> remove_whitespace("abc\\ndef") + 'abc def' + >>> remove_whitespace("abc\\n\\n\\ndef") + 'abc def' + New line characters surrounded by white space are replaced with a single space. + >>> remove_whitespace("abc \\n \\n \\n def") + 'abc def' + >>> remove_whitespace("abc \\n \\n \\n def") + 'abc def' + Leading and trailing new lines are replaced with a single space. + >>> remove_whitespace("\\nabc") + ' abc' + >>> remove_whitespace(" \\n abc") + ' abc' + >>> remove_whitespace("abc\\n") + 'abc ' + >>> remove_whitespace("abc \\n ") + 'abc ' + Use ``leading=True`` to remove leading new line characters, including any surrounding + white space: + >>> remove_whitespace("\\nabc", leading=True) + 'abc' + >>> remove_whitespace(" \\n abc", leading=True) + 'abc' + Use ``trailing=True`` to remove trailing new line characters, including any surrounding + white space: + >>> remove_whitespace("abc \\n ", trailing=True) + 'abc' + """ + # Remove any leading new line characters along with any surrounding white space + if leading: + string = re.sub(r"^\s*\n+\s*", "", string) + + # Remove any trailing new line characters along with any surrounding white space + if trailing: + string = re.sub(r"\s*\n+\s*$", "", string) + + # Replace new line characters and absorb any surrounding space. + string = re.sub(r"\s*\n\s*", " ", string) + # TODO need some way to get rid of extra spaces in e.g. text text + return re.sub(r"\s+", " ", string) + + +font_styles = { + "b": "bold", + "strong": "bold", + "em": "italic", + "i": "italic", + "u": "underline", + "s": "strike", + "sup": "superscript", + "sub": "subscript", + "th": "bold", +} + +font_names = { + "code": "Courier", + "pre": "Courier", +} + + +class HtmlToDocx(HTMLParser): + def __init__(self): + super().__init__() + self.options = { + "fix-html": True, + "images": True, + "tables": True, + "styles": True, + } + self.table_row_selectors = [ + "table > tr", + "table > thead > tr", + "table > tbody > tr", + "table > tfoot > tr", + ] + self.table_style = None + self.paragraph_style = None + + def set_initial_attrs(self, document=None): + self.tags = { + "span": [], + "list": [], + } + if document: + self.doc = document + else: + self.doc = Document() + self.bs = self.options["fix-html"] # whether or not to clean with BeautifulSoup + self.document = self.doc + self.include_tables = True # TODO add this option back in? + self.include_images = self.options["images"] + self.include_styles = self.options["styles"] + self.paragraph = None + self.skip = False + self.skip_tag = None + self.instances_to_skip = 0 + + def copy_settings_from(self, other): + """Copy settings from another instance of HtmlToDocx""" + self.table_style = other.table_style + self.paragraph_style = other.paragraph_style + + def ignore_nested_tables(self, tables_soup): + """ + Returns array containing only the highest level tables + Operates on the assumption that bs4 returns child elements immediately after + the parent element in `find_all`. If this changes in the future, this method will need to be updated + :return: + """ + new_tables = [] + nest = 0 + for table in tables_soup: + if nest: + nest -= 1 + continue + new_tables.append(table) + nest = len(table.find_all("table")) + return new_tables + + def get_tables(self): + if not hasattr(self, "soup"): + self.include_tables = False + return + # find other way to do it, or require this dependency? + self.tables = self.ignore_nested_tables(self.soup.find_all("table")) + self.table_no = 0 + + def run_process(self, html): + if self.bs and BeautifulSoup: + self.soup = BeautifulSoup(html, "html.parser") + html = str(self.soup) + if self.include_tables: + self.get_tables() + self.feed(html) + + def add_html_to_cell(self, html, cell): + if not isinstance(cell, docx.table._Cell): + raise ValueError("Second argument needs to be a %s" % docx.table._Cell) + unwanted_paragraph = cell.paragraphs[0] + if unwanted_paragraph.text == "": + delete_paragraph(unwanted_paragraph) + self.set_initial_attrs(cell) + self.run_process(html) + # cells must end with a paragraph or will get message about corrupt file + # https://stackoverflow.com/a/29287121 + if not self.doc.paragraphs: + self.doc.add_paragraph("") + + def apply_paragraph_style(self, style=None): + try: + if style: + self.paragraph.style = style + elif self.paragraph_style: + self.paragraph.style = self.paragraph_style + except KeyError as e: + raise ValueError(f"Unable to apply style {self.paragraph_style}.") from e + + def handle_table(self, html, doc): + """ + To handle nested tables, we will parse tables manually as follows: + Get table soup + Create docx table + Iterate over soup and fill docx table with new instances of this parser + Tell HTMLParser to ignore any tags until the corresponding closing table tag + """ + table_soup = BeautifulSoup(html, "html.parser") + rows, cols_len = get_table_dimensions(table_soup) + table = doc.add_table(len(rows), cols_len) + table.style = doc.styles["Table Grid"] + + num_rows = len(table.rows) + num_cols = len(table.columns) + + cell_row = 0 + for index, row in enumerate(rows): + cols = get_table_columns(row) + cell_col = 0 + for col in cols: + colspan = int(col.attrs.get("colspan", 1)) + rowspan = int(col.attrs.get("rowspan", 1)) + + cell_html = get_cell_html(col) + if col.name == "th": + cell_html = "%s" % cell_html + + if cell_row >= num_rows or cell_col >= num_cols: + continue + + docx_cell = table.cell(cell_row, cell_col) + + while docx_cell.text != "": # Skip the merged cell + cell_col += 1 + docx_cell = table.cell(cell_row, cell_col) + + cell_to_merge = table.cell( + cell_row + rowspan - 1, cell_col + colspan - 1 + ) + if docx_cell != cell_to_merge: + docx_cell.merge(cell_to_merge) + + child_parser = HtmlToDocx() + child_parser.copy_settings_from(self) + child_parser.add_html_to_cell(cell_html or " ", docx_cell) + + cell_col += colspan + cell_row += 1 + + def handle_data(self, data): + if self.skip: + return + + # Only remove white space if we're not in a pre block. + if "pre" not in self.tags: + # remove leading and trailing whitespace in all instances + data = remove_whitespace(data, True, True) + + if not self.paragraph: + self.paragraph = self.doc.add_paragraph() + self.apply_paragraph_style() + + # There can only be one nested link in a valid html document + # You cannot have interactive content in an A tag, this includes links + # https://html.spec.whatwg.org/#interactive-content + link = self.tags.get("a") + if link: + self.handle_link(link["href"], data) + else: + # If there's a link, dont put the data directly in the run + self.run = self.paragraph.add_run(data) + spans = self.tags["span"] + for span in spans: + if "style" in span: + style = self.parse_dict_string(span["style"]) + self.add_styles_to_run(style) + + # add font style and name + for tag in self.tags: + if tag in font_styles: + font_style = font_styles[tag] + setattr(self.run.font, font_style, True) + + if tag in font_names: + font_name = font_names[tag] + self.run.font.name = font_name diff --git a/ppstructure/table/README.md b/ppstructure/table/README.md new file mode 100644 index 0000000..a80514f --- /dev/null +++ b/ppstructure/table/README.md @@ -0,0 +1,159 @@ +English | [简体中文](README_ch.md) + +# Table Recognition + +- [1. pipeline](#1-pipeline) +- [2. Performance](#2-performance) +- [3. Result](#3-result) +- [4. How to use](#4-how-to-use) + - [4.1 Quick start](#41-quick-start) + - [4.2 Training, Evaluation and Inference](#42-training-evaluation-and-inference) + - [4.3 Calculate TEDS](#43-calculate-teds) +- [5. Reference](#5-reference) + + +## 1. pipeline +The table recognition mainly contains three models +1. Single line text detection-DB +2. Single line text recognition-CRNN +3. Table structure and cell coordinate prediction-SLANet + +The table recognition flow chart is as follows + +![tableocr_pipeline](../docs/table/tableocr_pipeline_en.jpg) + +1. The coordinates of single-line text is detected by DB model, and then sends it to the recognition model to get the recognition result. +2. The table structure and cell coordinates is predicted by SLANet model. +3. The recognition result of the cell is combined by the coordinates, recognition result of the single line and the coordinates of the cell. +4. The cell recognition result and the table structure together construct the html string of the table. + +## 2. Performance +We evaluated the algorithm on the PubTabNet[1] eval dataset, and the performance is as follows: + +|Method|Acc|[TEDS(Tree-Edit-Distance-based Similarity)](https://github.com/ibm-aur-nlp/PubTabNet/tree/master/src)|Speed| +| --- | --- | --- | ---| +| EDD[2] |x| 88.30% |x| +| TableRec-RARE(ours) | 71.73%| 93.88% |779ms| +| SLANet(ours) | 76.31%| 95.89%|766ms| + +The performance indicators are explained as follows: +- Acc: The accuracy of the table structure in each image, a wrong token is considered an error. +- TEDS: The accuracy of the model's restoration of table information. This indicator evaluates not only the table structure, but also the text content in the table. +- Speed: The inference speed of a single image when the model runs on the CPU machine and MKL is enabled. + +## 3. Result + +![](../docs/imgs/table_ch_result1.jpg) +![](../docs/imgs/table_ch_result2.jpg) +![](../docs/imgs/table_ch_result3.jpg) + +## 4. How to use + +### 4.1 Quick start + +PP-Structure currently provides table recognition models in both Chinese and English. For the model link, see [models_list](../docs/models_list.md). The whl package is also provided for quick use, see [quickstart](../docs/quickstart_en.md) for details. + +The following takes the Chinese table recognition model as an example to introduce how to recognize a table. + +Use the following commands to quickly complete the identification of a table. + +```python +cd PaddleOCR/ppstructure + +# download model +mkdir inference && cd inference +# Download the PP-OCRv3 text detection model and unzip it +wget https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_det_infer.tar && tar xf PP-OCRv3_mobile_det_infer.tar +# Download the PP-OCRv3 text recognition model and unzip it +wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar && tar xf ch_PP-OCRv3_rec_infer.tar +# Download the PP-StructureV2 form recognition model and unzip it +wget https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/paddle3.0b2/ch_ppstructure_mobile_v2.0_SLANet_infer.tar && tar xf ch_ppstructure_mobile_v2.0_SLANet_infer.tar +cd .. +# run +python3 table/predict_table.py \ + --det_model_dir=inference/PP-OCRv3_mobile_det_infer \ + --rec_model_dir=inference/ch_PP-OCRv3_rec_infer \ + --table_model_dir=inference/ch_ppstructure_mobile_v2.0_SLANet_infer \ + --rec_char_dict_path=../ppocr/utils/ppocr_keys_v1.txt \ + --table_char_dict_path=../ppocr/utils/dict/table_structure_dict_ch.txt \ + --image_dir=docs/table/table.jpg \ + --output=../output/table + +``` + +After the operation is completed, the excel table of each image will be saved to the directory specified by the output field, and an html file will be produced in the directory to visually view the cell coordinates and the recognized table. + +**NOTE** +1. If you want to use the English table recognition model, you need to download the English text detection and recognition model and the English table recognition model in [models_list](../docs/models_list_en.md), and replace `table_structure_dict_ch.txt` with `table_structure_dict.txt`. +2. To use the TableRec-RARE model, you need to replace `table_structure_dict_ch.txt` with `table_structure_dict.txt`, and add parameter `--merge_no_span_structure=False` + +### 4.2 Training, Evaluation and Inference + +The training, evaluation and inference process of the text detection model can be referred to [detection](../../doc/doc_en/detection_en.md) + +The training, evaluation and inference process of the text recognition model can be referred to [recognition](../../doc/doc_en/recognition_en.md) + +The training, evaluation and inference process of the table recognition model can be referred to [table_recognition](../../doc/doc_en/table_recognition_en.md) + +### 4.3 Calculate TEDS + +The table uses [TEDS(Tree-Edit-Distance-based Similarity)](https://github.com/ibm-aur-nlp/PubTabNet/tree/master/src) as the evaluation metric of the model. Before the model evaluation, the three models in the pipeline need to be exported as inference models (we have provided them), and the gt for evaluation needs to be prepared. Examples of gt are as follows: +```txt +PMC5755158_010_01.png
    WeaningWeek 15Off-test
    Weaning
    Week 150.17 ± 0.080.16 ± 0.03
    Off-test0.80 ± 0.240.19 ± 0.09
    +``` +Each line in gt consists of the file name and the html string of the table. The file name and the html string of the table are separated by `\t`. + +You can also use the following command to generate an evaluation gt file from the annotation file: +```python +python3 ppstructure/table/convert_label2html.py --ori_gt_path /path/to/your_label_file --save_path /path/to/save_file +``` + +Use the following command to evaluate. After the evaluation is completed, the teds indicator will be output. +```python +python3 table/eval_table.py \ + --det_model_dir=path/to/det_model_dir \ + --rec_model_dir=path/to/rec_model_dir \ + --table_model_dir=path/to/table_model_dir \ + --image_dir=docs/table/table.jpg \ + --rec_char_dict_path=../ppocr/utils/dict/table_dict.txt \ + --table_char_dict_path=../ppocr/utils/dict/table_structure_dict.txt \ + --det_limit_side_len=736 \ + --det_limit_type=min \ + --gt_path=path/to/gt.txt +``` + +Evaluate on the PubLatNet dataset using the English model + +```bash +cd PaddleOCR/ppstructure +# Download the model +mkdir inference && cd inference +# Download the text detection model trained on the PubTabNet dataset and unzip it +wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_det_infer.tar && tar xf en_ppocr_mobile_v2.0_table_det_infer.tar +# Download the text recognition model trained on the PubTabNet dataset and unzip it +wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_rec_infer.tar && tar xf en_ppocr_mobile_v2.0_table_rec_infer.tar +# Download the table recognition model trained on the PubTabNet dataset and unzip it +wget https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/paddle3.0b2/en_ppstructure_mobile_v2.0_SLANet_infer.tar && tar xf en_ppstructure_mobile_v2.0_SLANet_infer.tar +cd .. + +python3 table/eval_table.py \ + --det_model_dir=inference/en_ppocr_mobile_v2.0_table_det_infer \ + --rec_model_dir=inference/en_ppocr_mobile_v2.0_table_rec_infer \ + --table_model_dir=inference/en_ppstructure_mobile_v2.0_SLANet_infer \ + --image_dir=train_data/table/pubtabnet/val/ \ + --rec_char_dict_path=../ppocr/utils/dict/table_dict.txt \ + --table_char_dict_path=../ppocr/utils/dict/table_structure_dict.txt \ + --det_limit_side_len=736 \ + --det_limit_type=min \ + --rec_image_shape=3,32,320 \ + --gt_path=path/to/gt.txt +``` + +output is +```bash +teds: 95.89 +``` + +## 5. Reference +1. https://github.com/ibm-aur-nlp/PubTabNet +2. https://arxiv.org/pdf/1911.10683 diff --git a/ppstructure/table/README_ch.md b/ppstructure/table/README_ch.md new file mode 100644 index 0000000..2211f79 --- /dev/null +++ b/ppstructure/table/README_ch.md @@ -0,0 +1,163 @@ +[English](README.md) | 简体中文 + +# 表格识别 + +- [1. 表格识别 pipeline](#1-表格识别-pipeline) +- [2. 性能](#2-性能) +- [3. 效果演示](#3-效果演示) +- [4. 使用](#4-使用) + - [4.1 快速开始](#41-快速开始) + - [4.2 模型训练、评估与推理](#42-模型训练评估与推理) + - [4.3 计算TEDS](#43-计算teds) +- [5. Reference](#5-reference) + + +## 1. 表格识别 pipeline + +表格识别主要包含三个模型 +1. 单行文本检测-DB +2. 单行文本识别-CRNN +3. 表格结构和cell坐标预测-SLANet + +具体流程图如下 + +![tableocr_pipeline](../docs/table/tableocr_pipeline.jpg) + +流程说明: + +1. 图片由单行文字检测模型检测到单行文字的坐标,然后送入识别模型拿到识别结果。 +2. 图片由SLANet模型拿到表格的结构信息和单元格的坐标信息。 +3. 由单行文字的坐标、识别结果和单元格的坐标一起组合出单元格的识别结果。 +4. 单元格的识别结果和表格结构一起构造表格的html字符串。 + + +## 2. 性能 + +我们在 PubTabNet[1] 评估数据集上对算法进行了评估,性能如下 + + +|算法|Acc|[TEDS(Tree-Edit-Distance-based Similarity)](https://github.com/ibm-aur-nlp/PubTabNet/tree/master/src)|Speed| +| --- | --- | --- | ---| +| EDD[2] |x| 88.30% |x| +| TableRec-RARE(ours) | 71.73%| 93.88% |779ms| +| SLANet(ours) |76.31%| 95.89%|766ms| + +性能指标解释如下: +- Acc: 模型对每张图像里表格结构的识别准确率,错一个token就算错误。 +- TEDS: 模型对表格信息还原的准确度,此指标评价内容不仅包含表格结构,还包含表格内的文字内容。 +- Speed: 模型在CPU机器上,开启MKL的情况下,单张图片的推理速度。 + +## 3. 效果演示 + +![](../docs/imgs/table_ch_result1.jpg) +![](../docs/imgs/table_ch_result2.jpg) +![](../docs/imgs/table_ch_result3.jpg) + +## 4. 使用 + +### 4.1 快速开始 + +PP-Structure目前提供了中英文两种语言的表格识别模型,模型链接见 [models_list](../docs/models_list.md)。也提供了whl包的形式方便快速使用,详见 [quickstart](../docs/quickstart.md)。 + +下面以中文表格识别模型为例,介绍如何识别一张表格。 + +使用如下命令即可快速完成一张表格的识别。 +```python +cd PaddleOCR/ppstructure + +# 下载模型 +mkdir inference && cd inference +# 下载PP-OCRv3文本检测模型并解压 +wget https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_det_infer.tar && tar xf PP-OCRv3_mobile_det_infer.tar +# 下载PP-OCRv3文本识别模型并解压 +wget https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/ch_PP-OCRv3_rec_infer.tar && tar xf ch_PP-OCRv3_rec_infer.tar +# 下载PP-StructureV2中文表格识别模型并解压 +wget https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/paddle3.0b2/ch_ppstructure_mobile_v2.0_SLANet_infer.tar && tar xf ch_ppstructure_mobile_v2.0_SLANet_infer.tar +cd .. +# 执行表格识别 +python table/predict_table.py \ + --det_model_dir=inference/PP-OCRv3_mobile_det_infer \ + --rec_model_dir=inference/ch_PP-OCRv3_rec_infer \ + --table_model_dir=inference/ch_ppstructure_mobile_v2.0_SLANet_infer \ + --rec_char_dict_path=../ppocr/utils/ppocr_keys_v1.txt \ + --table_char_dict_path=../ppocr/utils/dict/table_structure_dict_ch.txt \ + --image_dir=docs/table/table.jpg \ + --output=../output/table +``` +运行完成后,每张图片的excel表格会保存到output字段指定的目录下,同时在该目录下回生产一个html文件,用于可视化查看单元格坐标和识别的表格。 + +**NOTE** +1. 如果想使用英文模型,需要在 [models_list](../docs/models_list.md) 中下载英文文字检测识别模型和英文表格识别模型,同时替换`table_structure_dict_ch.txt`为`table_structure_dict.txt`即可。 +2. 如需使用TableRec-RARE模型,需要替换`table_structure_dict_ch.txt`为`table_structure_dict.txt`,同时参数`--merge_no_span_structure=False` + +### 4.2 模型训练、评估与推理 + +文本检测模型的训练、评估和推理流程可参考 [detection](../../doc/doc_ch/detection.md) + +文本识别模型的训练、评估和推理流程可参考 [recognition](../../doc/doc_ch/recognition.md) + +表格识别模型的训练、评估和推理流程可参考 [table_recognition](../../doc/doc_ch/table_recognition.md) + +### 4.3 计算TEDS + +表格使用 [TEDS(Tree-Edit-Distance-based Similarity)](https://github.com/ibm-aur-nlp/PubTabNet/tree/master/src) 作为模型的评估指标。在进行模型评估之前,需要将pipeline中的三个模型分别导出为inference模型(我们已经提供好),还需要准备评估的gt, gt示例如下: +```txt +PMC5755158_010_01.png
    WeaningWeek 15Off-test
    Weaning
    Week 150.17 ± 0.080.16 ± 0.03
    Off-test0.80 ± 0.240.19 ± 0.09
    +``` +gt每一行都由文件名和表格的html字符串组成,文件名和表格的html字符串之间使用`\t`分隔。 + +也可使用如下命令,由标注文件生成评估的gt文件: +```python +python3 ppstructure/table/convert_label2html.py --ori_gt_path /path/to/your_label_file --save_path /path/to/save_file +``` + +准备完成后使用如下命令进行评估,评估完成后会输出teds指标。 +```python +cd PaddleOCR/ppstructure +python3 table/eval_table.py \ + --det_model_dir=path/to/det_model_dir \ + --rec_model_dir=path/to/rec_model_dir \ + --table_model_dir=path/to/table_model_dir \ + --image_dir=docs/table/table.jpg \ + --rec_char_dict_path=../ppocr/utils/dict/table_dict.txt \ + --table_char_dict_path=../ppocr/utils/dict/table_structure_dict.txt \ + --det_limit_side_len=736 \ + --det_limit_type=min \ + --gt_path=path/to/gt.txt +``` + +如使用英文表格识别模型在PubLatNet数据集上进行评估 + +```bash +cd PaddleOCR/ppstructure +# 下载模型 +mkdir inference && cd inference +# 下载基于PubTabNet数据集训练的文本检测模型并解压 +wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_det_infer.tar && tar xf en_ppocr_mobile_v2.0_table_det_infer.tar +# 下载基于PubTabNet数据集训练的文本识别模型并解压 +wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_rec_infer.tar && tar xf en_ppocr_mobile_v2.0_table_rec_infer.tar +# 下载基于PubTabNet数据集训练的表格识别模型并解压 +wget https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/paddle3.0b2/en_ppstructure_mobile_v2.0_SLANet_infer.tar && tar xf en_ppstructure_mobile_v2.0_SLANet_infer.tar +cd .. + +python3 table/eval_table.py \ + --det_model_dir=inference/en_ppocr_mobile_v2.0_table_det_infer \ + --rec_model_dir=inference/en_ppocr_mobile_v2.0_table_rec_infer \ + --table_model_dir=inference/en_ppstructure_mobile_v2.0_SLANet_infer \ + --image_dir=train_data/table/pubtabnet/val/ \ + --rec_char_dict_path=../ppocr/utils/dict/table_dict.txt \ + --table_char_dict_path=../ppocr/utils/dict/table_structure_dict.txt \ + --det_limit_side_len=736 \ + --det_limit_type=min \ + --rec_image_shape=3,32,320 \ + --gt_path=path/to/gt.txt +``` + +将会输出 +```bash +teds: 95.89 +``` + +## 5. Reference +1. https://github.com/ibm-aur-nlp/PubTabNet +2. https://arxiv.org/pdf/1911.10683 diff --git a/ppstructure/table/__init__.py b/ppstructure/table/__init__.py new file mode 100644 index 0000000..1d11e26 --- /dev/null +++ b/ppstructure/table/__init__.py @@ -0,0 +1,13 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/ppstructure/table/convert_label2html.py b/ppstructure/table/convert_label2html.py new file mode 100644 index 0000000..91611dd --- /dev/null +++ b/ppstructure/table/convert_label2html.py @@ -0,0 +1,102 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +convert table label to html +""" + +import json +import argparse +from tqdm import tqdm + + +def save_pred_txt(key, val, tmp_file_path): + with open(tmp_file_path, "a+", encoding="utf-8") as f: + f.write("{}\t{}\n".format(key, val)) + + +def skip_char(text, sp_char_list): + """ + skip empty cell + @param text: text in cell + @param sp_char_list: style char and special code + @return: + """ + for sp_char in sp_char_list: + text = text.replace(sp_char, "") + return text + + +def gen_html(img): + """ + Formats HTML code from tokenized annotation of img + """ + html_code = img["html"]["structure"]["tokens"].copy() + to_insert = [i for i, tag in enumerate(html_code) if tag in ("", ">")] + for i, cell in zip(to_insert[::-1], img["html"]["cells"][::-1]): + if cell["tokens"]: + text = "".join(cell["tokens"]) + # skip empty text + sp_char_list = ["", "", "\u2028", " ", "", ""] + text_remove_style = skip_char(text, sp_char_list) + if len(text_remove_style) == 0: + continue + html_code.insert(i + 1, text) + html_code = "".join(html_code) + html_code = "{}
    ".format(html_code) + return html_code + + +def load_gt_data(gt_path): + """ + load gt + @param gt_path: + @return: + """ + data_list = {} + with open(gt_path, "rb") as f: + lines = f.readlines() + for line in tqdm(lines): + data_line = line.decode("utf-8").strip("\n") + info = json.loads(data_line) + data_list[info["filename"]] = info + return data_list + + +def convert(origin_gt_path, save_path): + """ + gen html from label file + @param origin_gt_path: + @param save_path: + @return: + """ + data_dict = load_gt_data(origin_gt_path) + for img_name, gt in tqdm(data_dict.items()): + html = gen_html(gt) + save_pred_txt(img_name, html, save_path) + print("convert finish") + + +def parse_args(): + parser = argparse.ArgumentParser(description="args for paddleserving") + parser.add_argument("--ori_gt_path", type=str, required=True, help="label gt path") + parser.add_argument( + "--save_path", type=str, required=True, help="path to save file" + ) + args = parser.parse_args() + return args + + +if __name__ == "__main__": + args = parse_args() + convert(args.ori_gt_path, args.save_path) diff --git a/ppstructure/table/eval_table.py b/ppstructure/table/eval_table.py new file mode 100755 index 0000000..b9e4661 --- /dev/null +++ b/ppstructure/table/eval_table.py @@ -0,0 +1,107 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +import cv2 +import pickle +import paddle +from tqdm import tqdm +from ppstructure.table.table_metric import TEDS +from ppstructure.table.predict_table import TableSystem +from ppstructure.utility import init_args +from ppocr.utils.logging import get_logger + +logger = get_logger() + + +def parse_args(): + parser = init_args() + parser.add_argument("--gt_path", type=str) + return parser.parse_args() + + +def load_txt(txt_path): + pred_html_dict = {} + if not os.path.exists(txt_path): + return pred_html_dict + with open(txt_path, encoding="utf-8") as f: + lines = f.readlines() + for line in lines: + line = line.strip().split("\t") + img_name, pred_html = line + pred_html_dict[img_name] = pred_html + return pred_html_dict + + +def load_result(path): + data = {} + if os.path.exists(path): + data = pickle.load(open(path, "rb")) + return data + + +def save_result(path, data): + old_data = load_result(path) + old_data.update(data) + with open(path, "wb") as f: + pickle.dump(old_data, f) + + +def main(gt_path, img_root, args): + os.makedirs(args.output, exist_ok=True) + # init TableSystem + text_sys = TableSystem(args) + # load gt and preds html result + gt_html_dict = load_txt(gt_path) + + ocr_result = load_result(os.path.join(args.output, "ocr.pickle")) + structure_result = load_result(os.path.join(args.output, "structure.pickle")) + + pred_htmls = [] + gt_htmls = [] + for img_name, gt_html in tqdm(gt_html_dict.items()): + img = cv2.imread(os.path.join(img_root, img_name)) + # run ocr and save result + if img_name not in ocr_result: + dt_boxes, rec_res, _, _ = text_sys._ocr(img) + ocr_result[img_name] = [dt_boxes, rec_res] + save_result(os.path.join(args.output, "ocr.pickle"), ocr_result) + # run structure and save result + if img_name not in structure_result: + structure_res, _ = text_sys._structure(img) + structure_result[img_name] = structure_res + save_result(os.path.join(args.output, "structure.pickle"), structure_result) + dt_boxes, rec_res = ocr_result[img_name] + structure_res = structure_result[img_name] + # match ocr and structure + pred_html = text_sys.match(structure_res, dt_boxes, rec_res) + + pred_htmls.append(pred_html) + gt_htmls.append(gt_html) + + # compute teds + teds = TEDS(n_jobs=16) + scores = teds.batch_evaluate_html(gt_htmls, pred_htmls) + logger.info("teds: {}".format(sum(scores) / len(scores))) + + +if __name__ == "__main__": + args = parse_args() + main(args.gt_path, args.image_dir, args) diff --git a/ppstructure/table/matcher.py b/ppstructure/table/matcher.py new file mode 100755 index 0000000..ae32b4b --- /dev/null +++ b/ppstructure/table/matcher.py @@ -0,0 +1,206 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +from ppstructure.table.table_master_match import deal_eb_token, deal_bb +import html + + +def distance(box_1, box_2): + x1, y1, x2, y2 = box_1 + x3, y3, x4, y4 = box_2 + dis = abs(x3 - x1) + abs(y3 - y1) + abs(x4 - x2) + abs(y4 - y2) + dis_2 = abs(x3 - x1) + abs(y3 - y1) + dis_3 = abs(x4 - x2) + abs(y4 - y2) + return dis + min(dis_2, dis_3) + + +def compute_iou(rec1, rec2): + """ + computing IoU + :param rec1: (y0, x0, y1, x1), which reflects + (top, left, bottom, right) + :param rec2: (y0, x0, y1, x1) + :return: scala value of IoU + """ + # computing area of each rectangles + S_rec1 = (rec1[2] - rec1[0]) * (rec1[3] - rec1[1]) + S_rec2 = (rec2[2] - rec2[0]) * (rec2[3] - rec2[1]) + + # computing the sum_area + sum_area = S_rec1 + S_rec2 + + # find the each edge of intersect rectangle + left_line = max(rec1[1], rec2[1]) + right_line = min(rec1[3], rec2[3]) + top_line = max(rec1[0], rec2[0]) + bottom_line = min(rec1[2], rec2[2]) + + # judge if there is an intersect + if left_line >= right_line or top_line >= bottom_line: + return 0.0 + else: + intersect = (right_line - left_line) * (bottom_line - top_line) + return (intersect / (sum_area - intersect)) * 1.0 + + +class TableMatch: + def __init__(self, filter_ocr_result=False, use_master=False): + self.filter_ocr_result = filter_ocr_result + self.use_master = use_master + + def __call__(self, structure_res, dt_boxes, rec_res): + pred_structures, pred_bboxes = structure_res + if self.filter_ocr_result: + dt_boxes, rec_res = self._filter_ocr_result(pred_bboxes, dt_boxes, rec_res) + matched_index = self.match_result(dt_boxes, pred_bboxes) + if self.use_master: + pred_html, pred = self.get_pred_html_master( + pred_structures, matched_index, rec_res + ) + else: + pred_html, pred = self.get_pred_html( + pred_structures, matched_index, rec_res + ) + return pred_html + + def match_result(self, dt_boxes, pred_bboxes): + matched = {} + for i, gt_box in enumerate(dt_boxes): + distances = [] + for j, pred_box in enumerate(pred_bboxes): + if len(pred_box) == 8: + pred_box = [ + np.min(pred_box[0::2]), + np.min(pred_box[1::2]), + np.max(pred_box[0::2]), + np.max(pred_box[1::2]), + ] + distances.append( + (distance(gt_box, pred_box), 1.0 - compute_iou(gt_box, pred_box)) + ) # compute iou and l1 distance + sorted_distances = distances.copy() + # select det box by iou and l1 distance + sorted_distances = sorted( + sorted_distances, key=lambda item: (item[1], item[0]) + ) + if distances.index(sorted_distances[0]) not in matched.keys(): + matched[distances.index(sorted_distances[0])] = [i] + else: + matched[distances.index(sorted_distances[0])].append(i) + return matched + + def get_pred_html(self, pred_structures, matched_index, ocr_contents): + end_html = [] + td_index = 0 + for tag in pred_structures: + if "" in tag: + if "" == tag: + end_html.extend("") + if td_index in matched_index.keys(): + b_with = False + if ( + "" in ocr_contents[matched_index[td_index][0]] + and len(matched_index[td_index]) > 1 + ): + b_with = True + end_html.extend("") + for i, td_index_index in enumerate(matched_index[td_index]): + content = ocr_contents[td_index_index][0] + if len(matched_index[td_index]) > 1: + if len(content) == 0: + continue + if content[0] == " ": + content = content[1:] + if "" in content: + content = content[3:] + if "" in content: + content = content[:-4] + if len(content) == 0: + continue + if ( + i != len(matched_index[td_index]) - 1 + and " " != content[-1] + ): + content += " " + # escape content + content = html.escape(content) + end_html.extend(content) + if b_with: + end_html.extend("") + if "" == tag: + end_html.append("") + else: + end_html.append(tag) + td_index += 1 + else: + end_html.append(tag) + return "".join(end_html), end_html + + def get_pred_html_master(self, pred_structures, matched_index, ocr_contents): + end_html = [] + td_index = 0 + for token in pred_structures: + if "" in token: + txt = "" + b_with = False + if td_index in matched_index.keys(): + if ( + "" in ocr_contents[matched_index[td_index][0]] + and len(matched_index[td_index]) > 1 + ): + b_with = True + for i, td_index_index in enumerate(matched_index[td_index]): + content = ocr_contents[td_index_index][0] + if len(matched_index[td_index]) > 1: + if len(content) == 0: + continue + if content[0] == " ": + content = content[1:] + if "" in content: + content = content[3:] + if "" in content: + content = content[:-4] + if len(content) == 0: + continue + if ( + i != len(matched_index[td_index]) - 1 + and " " != content[-1] + ): + content += " " + txt += content + if b_with: + txt = "{}".format(txt) + if "" == token: + token = "{}".format(txt) + else: + token = "{}".format(txt) + td_index += 1 + token = deal_eb_token(token) + end_html.append(token) + html = "".join(end_html) + html = deal_bb(html) + return html, end_html + + def _filter_ocr_result(self, pred_bboxes, dt_boxes, rec_res): + y1 = pred_bboxes[:, 1::2].min() + new_dt_boxes = [] + new_rec_res = [] + + for box, rec in zip(dt_boxes, rec_res): + if np.max(box[1::2]) < y1: + continue + new_dt_boxes.append(box) + new_rec_res.append(rec) + return new_dt_boxes, new_rec_res diff --git a/ppstructure/table/predict_structure.py b/ppstructure/table/predict_structure.py new file mode 100755 index 0000000..3881f9f --- /dev/null +++ b/ppstructure/table/predict_structure.py @@ -0,0 +1,207 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import numpy as np +import time +import json + +import tools.infer.utility as utility +from ppocr.data import create_operators, transform +from ppocr.postprocess import build_post_process +from ppocr.utils.logging import get_logger +from ppocr.utils.utility import get_image_file_list, check_and_read +from ppocr.utils.visual import draw_rectangle +from ppstructure.utility import parse_args + +logger = get_logger() + + +def build_pre_process_list(args): + resize_op = { + "ResizeTableImage": { + "max_len": args.table_max_len, + } + } + pad_op = {"PaddingTableImage": {"size": [args.table_max_len, args.table_max_len]}} + normalize_op = { + "NormalizeImage": { + "std": ( + [0.229, 0.224, 0.225] + if args.table_algorithm not in ["TableMaster"] + else [0.5, 0.5, 0.5] + ), + "mean": ( + [0.485, 0.456, 0.406] + if args.table_algorithm not in ["TableMaster"] + else [0.5, 0.5, 0.5] + ), + "scale": "1./255.", + "order": "hwc", + } + } + to_chw_op = {"ToCHWImage": None} + keep_keys_op = {"KeepKeys": {"keep_keys": ["image", "shape"]}} + if args.table_algorithm not in ["TableMaster"]: + pre_process_list = [resize_op, normalize_op, pad_op, to_chw_op, keep_keys_op] + else: + pre_process_list = [resize_op, pad_op, normalize_op, to_chw_op, keep_keys_op] + return pre_process_list + + +class TableStructurer(object): + def __init__(self, args): + self.args = args + self.use_onnx = args.use_onnx + pre_process_list = build_pre_process_list(args) + if args.table_algorithm not in ["TableMaster"]: + postprocess_params = { + "name": "TableLabelDecode", + "character_dict_path": args.table_char_dict_path, + "merge_no_span_structure": args.merge_no_span_structure, + } + else: + postprocess_params = { + "name": "TableMasterLabelDecode", + "character_dict_path": args.table_char_dict_path, + "box_shape": "pad", + "merge_no_span_structure": args.merge_no_span_structure, + } + + self.preprocess_op = create_operators(pre_process_list) + self.postprocess_op = build_post_process(postprocess_params) + ( + self.predictor, + self.input_tensor, + self.output_tensors, + self.config, + ) = utility.create_predictor(args, "table", logger) + + if args.benchmark: + import auto_log + + pid = os.getpid() + gpu_id = utility.get_infer_gpuid() + self.autolog = auto_log.AutoLogger( + model_name="table", + model_precision=args.precision, + batch_size=1, + data_shape="dynamic", + save_path=None, # args.save_log_path, + inference_config=self.config, + pids=pid, + process_name=None, + gpu_ids=gpu_id if args.use_gpu else None, + time_keys=["preprocess_time", "inference_time", "postprocess_time"], + warmup=0, + logger=logger, + ) + + def __call__(self, img): + starttime = time.time() + if self.args.benchmark: + self.autolog.times.start() + + ori_im = img.copy() + data = {"image": img} + data = transform(data, self.preprocess_op) + img = data[0] + if img is None: + return None, 0 + img = np.expand_dims(img, axis=0) + img = img.copy() + if self.args.benchmark: + self.autolog.times.stamp() + if self.use_onnx: + input_dict = {} + input_dict[self.input_tensor.name] = img + outputs = self.predictor.run(self.output_tensors, input_dict) + else: + self.input_tensor.copy_from_cpu(img) + self.predictor.run() + outputs = [] + for output_tensor in self.output_tensors: + output = output_tensor.copy_to_cpu() + outputs.append(output) + if self.args.benchmark: + self.autolog.times.stamp() + + preds = {} + preds["structure_probs"] = outputs[1] + preds["loc_preds"] = outputs[0] + + shape_list = np.expand_dims(data[-1], axis=0) + post_result = self.postprocess_op(preds, [shape_list]) + + structure_str_list = post_result["structure_batch_list"][0] + bbox_list = post_result["bbox_batch_list"][0] + structure_str_list = structure_str_list[0] + structure_str_list = ( + ["", "", ""] + + structure_str_list + + ["
    ", "", ""] + ) + elapse = time.time() - starttime + if self.args.benchmark: + self.autolog.times.end(stamp=True) + return (structure_str_list, bbox_list), elapse + + +def main(args): + image_file_list = get_image_file_list(args.image_dir) + table_structurer = TableStructurer(args) + count = 0 + total_time = 0 + os.makedirs(args.output, exist_ok=True) + with open( + os.path.join(args.output, "infer.txt"), mode="w", encoding="utf-8" + ) as f_w: + for image_file in image_file_list: + img, flag, _ = check_and_read(image_file) + if not flag: + img = cv2.imread(image_file) + if img is None: + logger.info("error in loading image:{}".format(image_file)) + continue + structure_res, elapse = table_structurer(img) + structure_str_list, bbox_list = structure_res + bbox_list_str = json.dumps(bbox_list.tolist()) + logger.info("result: {}, {}".format(structure_str_list, bbox_list_str)) + f_w.write("result: {}, {}\n".format(structure_str_list, bbox_list_str)) + + if len(bbox_list) > 0 and len(bbox_list[0]) == 4: + img = draw_rectangle(image_file, bbox_list) + else: + img = utility.draw_boxes(img, bbox_list) + img_save_path = os.path.join(args.output, os.path.basename(image_file)) + cv2.imwrite(img_save_path, img) + logger.info("save vis result to {}".format(img_save_path)) + if count > 0: + total_time += elapse + count += 1 + logger.info("Predict time of {}: {}".format(image_file, elapse)) + if args.benchmark: + table_structurer.autolog.report() + + +if __name__ == "__main__": + main(parse_args()) diff --git a/ppstructure/table/predict_table.py b/ppstructure/table/predict_table.py new file mode 100644 index 0000000..e1880cd --- /dev/null +++ b/ppstructure/table/predict_table.py @@ -0,0 +1,241 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" +import cv2 +import copy +import logging +import numpy as np +import time +import tools.infer.predict_rec as predict_rec +import tools.infer.predict_det as predict_det +import tools.infer.utility as utility +from tools.infer.predict_system import sorted_boxes +from ppocr.utils.utility import get_image_file_list, check_and_read +from ppocr.utils.logging import get_logger +from ppstructure.table.matcher import TableMatch +from ppstructure.table.table_master_match import TableMasterMatcher +from ppstructure.utility import parse_args +import ppstructure.table.predict_structure as predict_strture + +logger = get_logger() + + +def expand(pix, det_box, shape): + x0, y0, x1, y1 = det_box + # print(shape) + h, w, c = shape + tmp_x0 = x0 - pix + tmp_x1 = x1 + pix + tmp_y0 = y0 - pix + tmp_y1 = y1 + pix + x0_ = tmp_x0 if tmp_x0 >= 0 else 0 + x1_ = tmp_x1 if tmp_x1 <= w else w + y0_ = tmp_y0 if tmp_y0 >= 0 else 0 + y1_ = tmp_y1 if tmp_y1 <= h else h + return x0_, y0_, x1_, y1_ + + +class TableSystem(object): + def __init__(self, args, text_detector=None, text_recognizer=None): + self.args = args + if not args.show_log: + logger.setLevel(logging.INFO) + benchmark_tmp = False + if args.benchmark: + benchmark_tmp = args.benchmark + args.benchmark = False + self.text_detector = ( + predict_det.TextDetector(copy.deepcopy(args)) + if text_detector is None + else text_detector + ) + self.text_recognizer = ( + predict_rec.TextRecognizer(copy.deepcopy(args)) + if text_recognizer is None + else text_recognizer + ) + if benchmark_tmp: + args.benchmark = True + self.table_structurer = predict_strture.TableStructurer(args) + if args.table_algorithm in ["TableMaster"]: + self.match = TableMasterMatcher() + else: + self.match = TableMatch(filter_ocr_result=True) + + ( + self.predictor, + self.input_tensor, + self.output_tensors, + self.config, + ) = utility.create_predictor(args, "table", logger) + + def __call__(self, img, return_ocr_result_in_table=False): + result = dict() + time_dict = {"det": 0, "rec": 0, "table": 0, "all": 0, "match": 0} + start = time.time() + structure_res, elapse = self._structure(copy.deepcopy(img)) + result["cell_bbox"] = structure_res[1].tolist() + time_dict["table"] = elapse + + dt_boxes, rec_res, det_elapse, rec_elapse = self._ocr(copy.deepcopy(img)) + time_dict["det"] = det_elapse + time_dict["rec"] = rec_elapse + + if return_ocr_result_in_table: + result["boxes"] = [x.tolist() for x in dt_boxes] + result["rec_res"] = rec_res + + tic = time.time() + pred_html = self.match(structure_res, dt_boxes, rec_res) + toc = time.time() + time_dict["match"] = toc - tic + result["html"] = pred_html + end = time.time() + time_dict["all"] = end - start + return result, time_dict + + def _structure(self, img): + structure_res, elapse = self.table_structurer(copy.deepcopy(img)) + return structure_res, elapse + + def _ocr(self, img): + h, w = img.shape[:2] + dt_boxes, det_elapse = self.text_detector(copy.deepcopy(img)) + dt_boxes = sorted_boxes(dt_boxes) + + r_boxes = [] + for box in dt_boxes: + x_min = max(0, box[:, 0].min() - 1) + x_max = min(w, box[:, 0].max() + 1) + y_min = max(0, box[:, 1].min() - 1) + y_max = min(h, box[:, 1].max() + 1) + box = [x_min, y_min, x_max, y_max] + r_boxes.append(box) + dt_boxes = np.array(r_boxes) + logger.debug("dt_boxes num : {}, elapse : {}".format(len(dt_boxes), det_elapse)) + if dt_boxes is None: + return None, None + + img_crop_list = [] + for i in range(len(dt_boxes)): + det_box = dt_boxes[i] + x0, y0, x1, y1 = expand(2, det_box, img.shape) + text_rect = img[int(y0) : int(y1), int(x0) : int(x1), :] + img_crop_list.append(text_rect) + rec_res, rec_elapse = self.text_recognizer(img_crop_list) + logger.debug("rec_res num : {}, elapse : {}".format(len(rec_res), rec_elapse)) + return dt_boxes, rec_res, det_elapse, rec_elapse + + +def to_excel(html_table, excel_path): + from tablepyxl import tablepyxl + + tablepyxl.document_to_xl(html_table, excel_path) + + +def main(args): + image_file_list = get_image_file_list(args.image_dir) + image_file_list = image_file_list[args.process_id :: args.total_process_num] + os.makedirs(args.output, exist_ok=True) + + table_sys = TableSystem(args) + img_num = len(image_file_list) + + f_html = open(os.path.join(args.output, "show.html"), mode="w", encoding="utf-8") + f_html.write("\n\n") + f_html.write('\n') + f_html.write( + '' + ) + f_html.write("\n") + f_html.write("") + f_html.write("") + f_html.write("") + f_html.write("\n") + + for i, image_file in enumerate(image_file_list): + logger.info("[{}/{}] {}".format(i, img_num, image_file)) + img, flag, _ = check_and_read(image_file) + excel_path = os.path.join( + args.output, os.path.basename(image_file).split(".")[0] + ".xlsx" + ) + if not flag: + img = cv2.imread(image_file) + if img is None: + logger.error("error in loading image:{}".format(image_file)) + continue + starttime = time.time() + pred_res, _ = table_sys(img) + pred_html = pred_res["html"] + logger.info(pred_html) + to_excel(pred_html, excel_path) + logger.info("excel saved to {}".format(excel_path)) + elapse = time.time() - starttime + logger.info("Predict time : {:.3f}s".format(elapse)) + + if len(pred_res["cell_bbox"]) > 0 and len(pred_res["cell_bbox"][0]) == 4: + img = predict_strture.draw_rectangle(image_file, pred_res["cell_bbox"]) + else: + img = utility.draw_boxes(img, pred_res["cell_bbox"]) + img_save_path = os.path.join(args.output, os.path.basename(image_file)) + cv2.imwrite(img_save_path, img) + + f_html.write("\n") + f_html.write(f"\n') + f_html.write( + '
    img name\n") + f_html.write("ori imagetable htmlcell box
    {os.path.basename(image_file)}
    \n") + f_html.write(f'
    ' + + pred_html.replace("
    ", "").replace( + "
    ", "" + ) + + "
    \n" + ) + f_html.write(f'\n') + f_html.write("\n") + f_html.write("\n") + f_html.close() + + if args.benchmark: + table_sys.table_structurer.autolog.report() + + +if __name__ == "__main__": + args = parse_args() + if args.use_mp: + import subprocess + + p_list = [] + total_process_num = args.total_process_num + for process_id in range(total_process_num): + cmd = ( + [sys.executable, "-u"] + + sys.argv + + ["--process_id={}".format(process_id), "--use_mp={}".format(False)] + ) + p = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stdout) + p_list.append(p) + for p in p_list: + p.wait() + else: + main(args) diff --git a/ppstructure/table/table_master_match.py b/ppstructure/table/table_master_match.py new file mode 100644 index 0000000..54b4506 --- /dev/null +++ b/ppstructure/table/table_master_match.py @@ -0,0 +1,995 @@ +# copyright (c) 2022 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +This code is refer from: +https://github.com/JiaquanYe/TableMASTER-mmocr/blob/master/table_recognition/match.py +""" + +import os +import re +import cv2 +import glob +import copy +import math +import pickle +import numpy as np + +from shapely.geometry import Polygon, MultiPoint + +""" +Useful function in matching. +""" + + +def remove_empty_bboxes(bboxes): + """ + remove [0., 0., 0., 0.] in structure master bboxes. + len(bboxes.shape) must be 2. + :param bboxes: + :return: + """ + new_bboxes = [] + for bbox in bboxes: + if sum(bbox) == 0.0: + continue + new_bboxes.append(bbox) + return np.array(new_bboxes) + + +def xywh2xyxy(bboxes): + if len(bboxes.shape) == 1: + new_bboxes = np.empty_like(bboxes) + new_bboxes[0] = bboxes[0] - bboxes[2] / 2 + new_bboxes[1] = bboxes[1] - bboxes[3] / 2 + new_bboxes[2] = bboxes[0] + bboxes[2] / 2 + new_bboxes[3] = bboxes[1] + bboxes[3] / 2 + return new_bboxes + elif len(bboxes.shape) == 2: + new_bboxes = np.empty_like(bboxes) + new_bboxes[:, 0] = bboxes[:, 0] - bboxes[:, 2] / 2 + new_bboxes[:, 1] = bboxes[:, 1] - bboxes[:, 3] / 2 + new_bboxes[:, 2] = bboxes[:, 0] + bboxes[:, 2] / 2 + new_bboxes[:, 3] = bboxes[:, 1] + bboxes[:, 3] / 2 + return new_bboxes + else: + raise ValueError + + +def xyxy2xywh(bboxes): + if len(bboxes.shape) == 1: + new_bboxes = np.empty_like(bboxes) + new_bboxes[0] = bboxes[0] + (bboxes[2] - bboxes[0]) / 2 + new_bboxes[1] = bboxes[1] + (bboxes[3] - bboxes[1]) / 2 + new_bboxes[2] = bboxes[2] - bboxes[0] + new_bboxes[3] = bboxes[3] - bboxes[1] + return new_bboxes + elif len(bboxes.shape) == 2: + new_bboxes = np.empty_like(bboxes) + new_bboxes[:, 0] = bboxes[:, 0] + (bboxes[:, 2] - bboxes[:, 0]) / 2 + new_bboxes[:, 1] = bboxes[:, 1] + (bboxes[:, 3] - bboxes[:, 1]) / 2 + new_bboxes[:, 2] = bboxes[:, 2] - bboxes[:, 0] + new_bboxes[:, 3] = bboxes[:, 3] - bboxes[:, 1] + return new_bboxes + else: + raise ValueError + + +def pickle_load(path, prefix="end2end"): + if os.path.isfile(path): + data = pickle.load(open(path, "rb")) + elif os.path.isdir(path): + data = dict() + search_path = os.path.join(path, "{}_*.pkl".format(prefix)) + pkls = glob.glob(search_path) + for pkl in pkls: + this_data = pickle.load(open(pkl, "rb")) + data.update(this_data) + else: + raise ValueError + return data + + +def convert_coord(xyxy): + """ + Convert two points format to four points format. + :param xyxy: + :return: + """ + new_bbox = np.zeros([4, 2], dtype=np.float32) + new_bbox[0, 0], new_bbox[0, 1] = xyxy[0], xyxy[1] + new_bbox[1, 0], new_bbox[1, 1] = xyxy[2], xyxy[1] + new_bbox[2, 0], new_bbox[2, 1] = xyxy[2], xyxy[3] + new_bbox[3, 0], new_bbox[3, 1] = xyxy[0], xyxy[3] + return new_bbox + + +def cal_iou(bbox1, bbox2): + bbox1_poly = Polygon(bbox1).convex_hull + bbox2_poly = Polygon(bbox2).convex_hull + union_poly = np.concatenate((bbox1, bbox2)) + + if not bbox1_poly.intersects(bbox2_poly): + iou = 0 + else: + inter_area = bbox1_poly.intersection(bbox2_poly).area + union_area = MultiPoint(union_poly).convex_hull.area + if union_area == 0: + iou = 0 + else: + iou = float(inter_area) / union_area + return iou + + +def cal_distance(p1, p2): + delta_x = p1[0] - p2[0] + delta_y = p1[1] - p2[1] + d = math.sqrt((delta_x**2) + (delta_y**2)) + return d + + +def is_inside(center_point, corner_point): + """ + Find if center_point inside the bbox(corner_point) or not. + :param center_point: center point (x, y) + :param corner_point: corner point ((x1,y1),(x2,y2)) + :return: + """ + x_flag = False + y_flag = False + if (center_point[0] >= corner_point[0][0]) and ( + center_point[0] <= corner_point[1][0] + ): + x_flag = True + if (center_point[1] >= corner_point[0][1]) and ( + center_point[1] <= corner_point[1][1] + ): + y_flag = True + if x_flag and y_flag: + return True + else: + return False + + +def find_no_match(match_list, all_end2end_nums, type="end2end"): + """ + Find out no match end2end bbox in previous match list. + :param match_list: matching pairs. + :param all_end2end_nums: numbers of end2end_xywh + :param type: 'end2end' corresponding to idx 0, 'master' corresponding to idx 1. + :return: no match pse bbox index list + """ + if type == "end2end": + idx = 0 + elif type == "master": + idx = 1 + else: + raise ValueError + + no_match_indexs = [] + # m[0] is end2end index m[1] is master index + matched_bbox_indexs = [m[idx] for m in match_list] + for n in range(all_end2end_nums): + if n not in matched_bbox_indexs: + no_match_indexs.append(n) + return no_match_indexs + + +def is_abs_lower_than_threshold(this_bbox, target_bbox, threshold=3): + # only consider y axis, for grouping in row. + delta = abs(this_bbox[1] - target_bbox[1]) + if delta < threshold: + return True + else: + return False + + +def sort_line_bbox(g, bg): + """ + Sorted the bbox in the same line(group) + compare coord 'x' value, where 'y' value is closed in the same group. + :param g: index in the same group + :param bg: bbox in the same group + :return: + """ + + xs = [bg_item[0] for bg_item in bg] + xs_sorted = sorted(xs) + + g_sorted = [None] * len(xs_sorted) + bg_sorted = [None] * len(xs_sorted) + for g_item, bg_item in zip(g, bg): + idx = xs_sorted.index(bg_item[0]) + bg_sorted[idx] = bg_item + g_sorted[idx] = g_item + + return g_sorted, bg_sorted + + +def flatten(sorted_groups, sorted_bbox_groups): + idxs = [] + bboxes = [] + for group, bbox_group in zip(sorted_groups, sorted_bbox_groups): + for g, bg in zip(group, bbox_group): + idxs.append(g) + bboxes.append(bg) + return idxs, bboxes + + +def sort_bbox(end2end_xywh_bboxes, no_match_end2end_indexes): + """ + This function will group the render end2end bboxes in row. + :param end2end_xywh_bboxes: + :param no_match_end2end_indexes: + :return: + """ + groups = [] + bbox_groups = [] + for index, end2end_xywh_bbox in zip(no_match_end2end_indexes, end2end_xywh_bboxes): + this_bbox = end2end_xywh_bbox + if len(groups) == 0: + groups.append([index]) + bbox_groups.append([this_bbox]) + else: + flag = False + for g, bg in zip(groups, bbox_groups): + # this_bbox is belong to bg's row or not + if is_abs_lower_than_threshold(this_bbox, bg[0]): + g.append(index) + bg.append(this_bbox) + flag = True + break + if not flag: + # this_bbox is not belong to bg's row, create a row. + groups.append([index]) + bbox_groups.append([this_bbox]) + + # sorted bboxes in a group + tmp_groups, tmp_bbox_groups = [], [] + for g, bg in zip(groups, bbox_groups): + g_sorted, bg_sorted = sort_line_bbox(g, bg) + tmp_groups.append(g_sorted) + tmp_bbox_groups.append(bg_sorted) + + # sorted groups, sort by coord y's value. + sorted_groups = [None] * len(tmp_groups) + sorted_bbox_groups = [None] * len(tmp_bbox_groups) + ys = [bg[0][1] for bg in tmp_bbox_groups] + sorted_ys = sorted(ys) + for g, bg in zip(tmp_groups, tmp_bbox_groups): + idx = sorted_ys.index(bg[0][1]) + sorted_groups[idx] = g + sorted_bbox_groups[idx] = bg + + # flatten, get final result + end2end_sorted_idx_list, end2end_sorted_bbox_list = flatten( + sorted_groups, sorted_bbox_groups + ) + + return ( + end2end_sorted_idx_list, + end2end_sorted_bbox_list, + sorted_groups, + sorted_bbox_groups, + ) + + +def get_bboxes_list(end2end_result, structure_master_result): + """ + This function is use to convert end2end results and structure master results to + List of xyxy bbox format and List of xywh bbox format + :param end2end_result: bbox's format is xyxy + :param structure_master_result: bbox's format is xywh + :return: 4 kind list of bbox () + """ + # end2end + end2end_xyxy_list = [] + end2end_xywh_list = [] + for end2end_item in end2end_result: + src_bbox = end2end_item["bbox"] + end2end_xyxy_list.append(src_bbox) + xywh_bbox = xyxy2xywh(src_bbox) + end2end_xywh_list.append(xywh_bbox) + end2end_xyxy_bboxes = np.array(end2end_xyxy_list) + end2end_xywh_bboxes = np.array(end2end_xywh_list) + + # structure master + src_bboxes = structure_master_result["bbox"] + src_bboxes = remove_empty_bboxes(src_bboxes) + structure_master_xyxy_bboxes = src_bboxes + xywh_bbox = xyxy2xywh(src_bboxes) + structure_master_xywh_bboxes = xywh_bbox + + return ( + end2end_xyxy_bboxes, + end2end_xywh_bboxes, + structure_master_xywh_bboxes, + structure_master_xyxy_bboxes, + ) + + +def center_rule_match(end2end_xywh_bboxes, structure_master_xyxy_bboxes): + """ + Judge end2end Bbox's center point is inside structure master Bbox or not, + if end2end Bbox's center is in structure master Bbox, get matching pair. + :param end2end_xywh_bboxes: + :param structure_master_xyxy_bboxes: + :return: match pairs list, e.g. [[0,1], [1,2], ...] + """ + match_pairs_list = [] + for i, end2end_xywh in enumerate(end2end_xywh_bboxes): + for j, master_xyxy in enumerate(structure_master_xyxy_bboxes): + x_end2end, y_end2end = end2end_xywh[0], end2end_xywh[1] + x_master1, y_master1, x_master2, y_master2 = ( + master_xyxy[0], + master_xyxy[1], + master_xyxy[2], + master_xyxy[3], + ) + center_point_end2end = (x_end2end, y_end2end) + corner_point_master = ((x_master1, y_master1), (x_master2, y_master2)) + if is_inside(center_point_end2end, corner_point_master): + match_pairs_list.append([i, j]) + return match_pairs_list + + +def iou_rule_match( + end2end_xyxy_bboxes, end2end_xyxy_indexes, structure_master_xyxy_bboxes +): + """ + Use iou to find matching list. + choose max iou value bbox as match pair. + :param end2end_xyxy_bboxes: + :param end2end_xyxy_indexes: original end2end indexes. + :param structure_master_xyxy_bboxes: + :return: match pairs list, e.g. [[0,1], [1,2], ...] + """ + match_pair_list = [] + for end2end_xyxy_index, end2end_xyxy in zip( + end2end_xyxy_indexes, end2end_xyxy_bboxes + ): + max_iou = 0 + max_match = [None, None] + for j, master_xyxy in enumerate(structure_master_xyxy_bboxes): + end2end_4xy = convert_coord(end2end_xyxy) + master_4xy = convert_coord(master_xyxy) + iou = cal_iou(end2end_4xy, master_4xy) + if iou > max_iou: + max_match[0], max_match[1] = end2end_xyxy_index, j + max_iou = iou + + if max_match[0] is None: + # no match + continue + match_pair_list.append(max_match) + return match_pair_list + + +def distance_rule_match(end2end_indexes, end2end_bboxes, master_indexes, master_bboxes): + """ + Get matching between no-match end2end bboxes and no-match master bboxes. + Use min distance to match. + This rule will only run (no-match end2end nums > 0) and (no-match master nums > 0) + It will Return master_bboxes_nums match-pairs. + :param end2end_indexes: + :param end2end_bboxes: + :param master_indexes: + :param master_bboxes: + :return: match_pairs list, e.g. [[0,1], [1,2], ...] + """ + min_match_list = [] + for j, master_bbox in zip(master_indexes, master_bboxes): + min_distance = np.inf + min_match = [0, 0] # i, j + for i, end2end_bbox in zip(end2end_indexes, end2end_bboxes): + x_end2end, y_end2end = end2end_bbox[0], end2end_bbox[1] + x_master, y_master = master_bbox[0], master_bbox[1] + end2end_point = (x_end2end, y_end2end) + master_point = (x_master, y_master) + dist = cal_distance(master_point, end2end_point) + if dist < min_distance: + min_match[0], min_match[1] = i, j + min_distance = dist + min_match_list.append(min_match) + return min_match_list + + +def extra_match(no_match_end2end_indexes, master_bbox_nums): + """ + This function will create some virtual master bboxes, + and get match with the no match end2end indexes. + :param no_match_end2end_indexes: + :param master_bbox_nums: + :return: + """ + end_nums = len(no_match_end2end_indexes) + master_bbox_nums + extra_match_list = [] + for i in range(master_bbox_nums, end_nums): + end2end_index = no_match_end2end_indexes[i - master_bbox_nums] + extra_match_list.append([end2end_index, i]) + return extra_match_list + + +def get_match_dict(match_list): + """ + Convert match_list to a dict, where key is master bbox's index, value is end2end bbox index. + :param match_list: + :return: + """ + match_dict = dict() + for match_pair in match_list: + end2end_index, master_index = match_pair[0], match_pair[1] + if master_index not in match_dict.keys(): + match_dict[master_index] = [end2end_index] + else: + match_dict[master_index].append(end2end_index) + return match_dict + + +def deal_successive_space(text): + """ + deal successive space character for text + 1. Replace ' '*3 with '' which is real space is text + 2. Remove ' ', which is split token, not true space + 3. Replace '' with ' ', to get real text + :param text: + :return: + """ + text = text.replace(" " * 3, "") + text = text.replace(" ", "") + text = text.replace("", " ") + return text + + +def reduce_repeat_bb(text_list, break_token): + """ + convert ['Local', 'government', 'unit'] to ['Local government unit'] + PS: maybe style Local is also exist, too. it can be processed like this. + :param text_list: + :param break_token: + :return: + """ + count = 0 + for text in text_list: + if text.startswith(""): + count += 1 + if count == len(text_list): + new_text_list = [] + for text in text_list: + text = text.replace("", "").replace("", "") + new_text_list.append(text) + return ["" + break_token.join(new_text_list) + ""] + else: + return text_list + + +def get_match_text_dict(match_dict, end2end_info, break_token=" "): + match_text_dict = dict() + for master_index, end2end_index_list in match_dict.items(): + text_list = [ + end2end_info[end2end_index]["text"] for end2end_index in end2end_index_list + ] + text_list = reduce_repeat_bb(text_list, break_token) + text = break_token.join(text_list) + match_text_dict[master_index] = text + return match_text_dict + + +def merge_span_token(master_token_list): + """ + Merge the span style token (row span or col span). + :param master_token_list: + :return: + """ + new_master_token_list = [] + pointer = 0 + if master_token_list[-1] != "": + master_token_list.append("") + while master_token_list[pointer] != "": + try: + if master_token_list[pointer] == " + '' + '' + """ + tmp = "".join(master_token_list[pointer : pointer + 3 + 1]) + pointer += 4 + new_master_token_list.append(tmp) + + elif master_token_list[pointer + 2].startswith( + " colspan=" + ) or master_token_list[pointer + 2].startswith(" rowspan="): + """ + example: + pattern + '' + '' + """ + tmp = "".join(master_token_list[pointer : pointer + 4 + 1]) + pointer += 5 + new_master_token_list.append(tmp) + + else: + new_master_token_list.append(master_token_list[pointer]) + pointer += 1 + else: + new_master_token_list.append(master_token_list[pointer]) + pointer += 1 + except: + print("Break in merge...") + break + new_master_token_list.append("") + + return new_master_token_list + + +def deal_eb_token(master_token): + """ + post process with , , ... + emptyBboxTokenDict = { + "[]": '', + "[' ']": '', + "['', ' ', '']": '', + "['\\u2028', '\\u2028']": '', + "['', ' ', '']": '', + "['', '']": '', + "['', ' ', '']": '', + "['', '', '', '']": '', + "['', '', ' ', '', '']": '', + "['', '']": '', + "['', ' ', '\\u2028', ' ', '\\u2028', ' ', '']": '', + } + :param master_token: + :return: + """ + master_token = master_token.replace("", "") + master_token = master_token.replace("", " ") + master_token = master_token.replace("", " ") + master_token = master_token.replace("", "\u2028\u2028") + master_token = master_token.replace("", " ") + master_token = master_token.replace("", "") + master_token = master_token.replace("", " ") + master_token = master_token.replace("", "") + master_token = master_token.replace("", " ") + master_token = master_token.replace("", "") + master_token = master_token.replace( + "", " \u2028 \u2028 " + ) + return master_token + + +def insert_text_to_token(master_token_list, match_text_dict): + """ + Insert OCR text result to structure token. + :param master_token_list: + :param match_text_dict: + :return: + """ + master_token_list = merge_span_token(master_token_list) + merged_result_list = [] + text_count = 0 + for master_token in master_token_list: + if master_token.startswith(" len(match_text_dict) - 1: + text_count += 1 + continue + elif text_count not in match_text_dict.keys(): + text_count += 1 + continue + else: + master_token = master_token.replace( + "><", ">{}<".format(match_text_dict[text_count]) + ) + text_count += 1 + master_token = deal_eb_token(master_token) + merged_result_list.append(master_token) + + return "".join(merged_result_list) + + +def deal_isolate_span(thead_part): + """ + Deal with isolate span cases in this function. + It causes by wrong prediction in structure recognition model. + eg. predict to rowspan="2">. + :param thead_part: + :return: + """ + # 1. find out isolate span tokens. + isolate_pattern = ( + ' rowspan="(\d)+" colspan="(\d)+">
    |' + ' colspan="(\d)+" rowspan="(\d)+">
    |' + ' rowspan="(\d)+">|' + ' colspan="(\d)+">' + ) + isolate_iter = re.finditer(isolate_pattern, thead_part) + isolate_list = [i.group() for i in isolate_iter] + + # 2. find out span number, by step 1 results. + span_pattern = ( + ' rowspan="(\d)+" colspan="(\d)+"|' + ' colspan="(\d)+" rowspan="(\d)+"|' + ' rowspan="(\d)+"|' + ' colspan="(\d)+"' + ) + corrected_list = [] + for isolate_item in isolate_list: + span_part = re.search(span_pattern, isolate_item) + spanStr_in_isolateItem = span_part.group() + # 3. merge the span number into the span token format string. + if spanStr_in_isolateItem is not None: + corrected_item = "".format(spanStr_in_isolateItem) + corrected_list.append(corrected_item) + else: + corrected_list.append(None) + + # 4. replace original isolated token. + for corrected_item, isolate_item in zip(corrected_list, isolate_list): + if corrected_item is not None: + thead_part = thead_part.replace(isolate_item, corrected_item) + else: + pass + return thead_part + + +def deal_duplicate_bb(thead_part): + """ + Deal duplicate or after replace. + Keep one in a token. + :param thead_part: + :return: + """ + # 1. find out in . + td_pattern = ( + '(.+?)|' + '(.+?)|' + '(.+?)|' + '(.+?)|' + "(.*?)" + ) + td_iter = re.finditer(td_pattern, thead_part) + td_list = [t.group() for t in td_iter] + + # 2. is multiply in or not? + new_td_list = [] + for td_item in td_list: + if td_item.count("") > 1 or td_item.count("") > 1: + # multiply in case. + # 1. remove all + td_item = td_item.replace("", "").replace("", "") + # 2. replace -> , -> . + td_item = td_item.replace("", "").replace("", "") + new_td_list.append(td_item) + else: + new_td_list.append(td_item) + + # 3. replace original thead part. + for td_item, new_td_item in zip(td_list, new_td_list): + thead_part = thead_part.replace(td_item, new_td_item) + return thead_part + + +def deal_bb(result_token): + """ + In our opinion, always occurs in text's context. + This function will find out all tokens in and insert by manual. + :param result_token: + :return: + """ + # find out parts. + thead_pattern = "(.*?)" + if re.search(thead_pattern, result_token) is None: + return result_token + thead_part = re.search(thead_pattern, result_token).group() + origin_thead_part = copy.deepcopy(thead_part) + + # check "rowspan" or "colspan" occur in parts or not . + span_pattern = '|||' + span_iter = re.finditer(span_pattern, thead_part) + span_list = [s.group() for s in span_iter] + has_span_in_head = True if len(span_list) > 0 else False + + if not has_span_in_head: + # not include "rowspan" or "colspan" branch 1. + # 1. replace to , and to + # 2. it is possible to predict text include or by Text-line recognition, + # so we replace to , and to + thead_part = ( + thead_part.replace("", "") + .replace("", "") + .replace("", "") + .replace("", "") + ) + else: + # include "rowspan" or "colspan" branch 2. + # Firstly, we deal rowspan or colspan cases. + # 1. replace > to > + # 2. replace to + # 3. it is possible to predict text include or by Text-line recognition, + # so we replace to , and to + + # Secondly, deal ordinary cases like branch 1 + + # replace ">" to "" + replaced_span_list = [] + for sp in span_list: + replaced_span_list.append(sp.replace(">", ">")) + for sp, rsp in zip(span_list, replaced_span_list): + thead_part = thead_part.replace(sp, rsp) + + # replace "" to "" + thead_part = thead_part.replace("", "") + + # remove duplicated by re.sub + mb_pattern = "()+" + single_b_string = "" + thead_part = re.sub(mb_pattern, single_b_string, thead_part) + + mgb_pattern = "()+" + single_gb_string = "" + thead_part = re.sub(mgb_pattern, single_gb_string, thead_part) + + # ordinary cases like branch 1 + thead_part = thead_part.replace("", "").replace("", "") + + # convert back to , empty cell has no . + # but space cell( ) is suitable for + thead_part = thead_part.replace("", "") + # deal with duplicated + thead_part = deal_duplicate_bb(thead_part) + # deal with isolate span tokens, which causes by wrong predict by structure prediction. + # eg.PMC5994107_011_00.png + thead_part = deal_isolate_span(thead_part) + # replace original result with new thead part. + result_token = result_token.replace(origin_thead_part, thead_part) + return result_token + + +class Matcher: + def __init__(self, end2end_file, structure_master_file): + """ + This class process the end2end results and structure recognition results. + :param end2end_file: end2end results predict by end2end inference. + :param structure_master_file: structure recognition results predict by structure master inference. + """ + self.end2end_file = end2end_file + self.structure_master_file = structure_master_file + self.end2end_results = pickle_load(end2end_file, prefix="end2end") + self.structure_master_results = pickle_load( + structure_master_file, prefix="structure" + ) + + def match(self): + """ + Match process: + pre-process : convert end2end and structure master results to xyxy, xywh ndnarray format. + 1. Use pseBbox is inside masterBbox judge rule + 2. Use iou between pseBbox and masterBbox rule + 3. Use min distance of center point rule + :return: + """ + match_results = dict() + for idx, (file_name, end2end_result) in enumerate(self.end2end_results.items()): + match_list = [] + if file_name not in self.structure_master_results: + continue + structure_master_result = self.structure_master_results[file_name] + ( + end2end_xyxy_bboxes, + end2end_xywh_bboxes, + structure_master_xywh_bboxes, + structure_master_xyxy_bboxes, + ) = get_bboxes_list(end2end_result, structure_master_result) + + # rule 1: center rule + center_rule_match_list = center_rule_match( + end2end_xywh_bboxes, structure_master_xyxy_bboxes + ) + match_list.extend(center_rule_match_list) + + # rule 2: iou rule + # firstly, find not match index in previous step. + center_no_match_end2end_indexs = find_no_match( + match_list, len(end2end_xywh_bboxes), type="end2end" + ) + if len(center_no_match_end2end_indexs) > 0: + center_no_match_end2end_xyxy = end2end_xyxy_bboxes[ + center_no_match_end2end_indexs + ] + # secondly, iou rule match + iou_rule_match_list = iou_rule_match( + center_no_match_end2end_xyxy, + center_no_match_end2end_indexs, + structure_master_xyxy_bboxes, + ) + match_list.extend(iou_rule_match_list) + + # rule 3: distance rule + # match between no-match end2end bboxes and no-match master bboxes. + # it will return master_bboxes_nums match-pairs. + # firstly, find not match index in previous step. + centerIou_no_match_end2end_indexs = find_no_match( + match_list, len(end2end_xywh_bboxes), type="end2end" + ) + centerIou_no_match_master_indexs = find_no_match( + match_list, len(structure_master_xywh_bboxes), type="master" + ) + if ( + len(centerIou_no_match_master_indexs) > 0 + and len(centerIou_no_match_end2end_indexs) > 0 + ): + centerIou_no_match_end2end_xywh = end2end_xywh_bboxes[ + centerIou_no_match_end2end_indexs + ] + centerIou_no_match_master_xywh = structure_master_xywh_bboxes[ + centerIou_no_match_master_indexs + ] + distance_match_list = distance_rule_match( + centerIou_no_match_end2end_indexs, + centerIou_no_match_end2end_xywh, + centerIou_no_match_master_indexs, + centerIou_no_match_master_xywh, + ) + match_list.extend(distance_match_list) + + # TODO: + # The render no-match pseBbox, insert the last + # After step3 distance rule, a master bbox at least match one end2end bbox. + # But end2end bbox maybe overmuch, because numbers of master bbox will cut by max length. + # For these render end2end bboxes, we will make some virtual master bboxes, and get matching. + # The above extra insert bboxes will be further processed in "formatOutput" function. + # After this operation, it will increase TEDS score. + no_match_end2end_indexes = find_no_match( + match_list, len(end2end_xywh_bboxes), type="end2end" + ) + if len(no_match_end2end_indexes) > 0: + no_match_end2end_xywh = end2end_xywh_bboxes[no_match_end2end_indexes] + # sort the render no-match end2end bbox in row + ( + end2end_sorted_indexes_list, + end2end_sorted_bboxes_list, + sorted_groups, + sorted_bboxes_groups, + ) = sort_bbox(no_match_end2end_xywh, no_match_end2end_indexes) + # make virtual master bboxes, and get matching with the no-match end2end bboxes. + extra_match_list = extra_match( + end2end_sorted_indexes_list, len(structure_master_xywh_bboxes) + ) + match_list_add_extra_match = copy.deepcopy(match_list) + match_list_add_extra_match.extend(extra_match_list) + else: + # no no-match end2end bboxes + match_list_add_extra_match = copy.deepcopy(match_list) + sorted_groups = [] + sorted_bboxes_groups = [] + + match_result_dict = { + "match_list": match_list, + "match_list_add_extra_match": match_list_add_extra_match, + "sorted_groups": sorted_groups, + "sorted_bboxes_groups": sorted_bboxes_groups, + } + + # format output + match_result_dict = self._format(match_result_dict, file_name) + + match_results[file_name] = match_result_dict + + return match_results + + def _format(self, match_result, file_name): + """ + Extend the master token(insert virtual master token), and format matching result. + :param match_result: + :param file_name: + :return: + """ + end2end_info = self.end2end_results[file_name] + master_info = self.structure_master_results[file_name] + master_token = master_info["text"] + sorted_groups = match_result["sorted_groups"] + + # creat virtual master token + virtual_master_token_list = [] + for line_group in sorted_groups: + tmp_list = [""] + item_nums = len(line_group) + for _ in range(item_nums): + tmp_list.append("") + tmp_list.append("") + virtual_master_token_list.extend(tmp_list) + + # insert virtual master token + master_token_list = master_token.split(",") + if master_token_list[-1] == "": + # complete predict(no cut by max length) + # This situation insert virtual master token will drop TEDs score in val set. + # So we will not extend virtual token in this situation. + + # fake extend virtual + master_token_list[:-1].extend(virtual_master_token_list) + + # real extend virtual + # master_token_list = master_token_list[:-1] + # master_token_list.extend(virtual_master_token_list) + # master_token_list.append('') + + elif master_token_list[-1] == "": + master_token_list.append("") + master_token_list.extend(virtual_master_token_list) + master_token_list.append("") + else: + master_token_list.extend(virtual_master_token_list) + master_token_list.append("") + + # format output + match_result.setdefault("matched_master_token_list", master_token_list) + return match_result + + def get_merge_result(self, match_results): + """ + Merge the OCR result into structure token to get final results. + :param match_results: + :return: + """ + merged_results = dict() + + # break_token is linefeed token, when one master bbox has multiply end2end bboxes. + break_token = " " + + for idx, (file_name, match_info) in enumerate(match_results.items()): + end2end_info = self.end2end_results[file_name] + master_token_list = match_info["matched_master_token_list"] + match_list = match_info["match_list_add_extra_match"] + + match_dict = get_match_dict(match_list) + match_text_dict = get_match_text_dict(match_dict, end2end_info, break_token) + merged_result = insert_text_to_token(master_token_list, match_text_dict) + merged_result = deal_bb(merged_result) + + merged_results[file_name] = merged_result + + return merged_results + + +class TableMasterMatcher(Matcher): + def __init__(self): + pass + + def __call__(self, structure_res, dt_boxes, rec_res, img_name=1): + end2end_results = {img_name: []} + for dt_box, res in zip(dt_boxes, rec_res): + d = dict( + bbox=np.array(dt_box), + text=res[0], + ) + end2end_results[img_name].append(d) + + self.end2end_results = end2end_results + + structure_master_result_dict = {img_name: {}} + pred_structures, pred_bboxes = structure_res + pred_structures = ",".join(pred_structures[3:-3]) + structure_master_result_dict[img_name]["text"] = pred_structures + structure_master_result_dict[img_name]["bbox"] = pred_bboxes + self.structure_master_results = structure_master_result_dict + + # match + match_results = self.match() + merged_results = self.get_merge_result(match_results) + pred_html = merged_results[img_name] + pred_html = "" + pred_html + "
    " + return pred_html diff --git a/ppstructure/table/table_metric/__init__.py b/ppstructure/table/table_metric/__init__.py new file mode 100755 index 0000000..70be931 --- /dev/null +++ b/ppstructure/table/table_metric/__init__.py @@ -0,0 +1,16 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = ["TEDS"] +from .table_metric import TEDS diff --git a/ppstructure/table/table_metric/parallel.py b/ppstructure/table/table_metric/parallel.py new file mode 100755 index 0000000..f706f35 --- /dev/null +++ b/ppstructure/table/table_metric/parallel.py @@ -0,0 +1,55 @@ +from tqdm import tqdm +from concurrent.futures import ProcessPoolExecutor, as_completed + + +def parallel_process(array, function, n_jobs=16, use_kwargs=False, front_num=0): + """ + A parallel version of the map function with a progress bar. + Args: + array (array-like): An array to iterate over. + function (function): A python function to apply to the elements of array + n_jobs (int, default=16): The number of cores to use + use_kwargs (boolean, default=False): Whether to consider the elements of array as dictionaries of + keyword arguments to function + front_num (int, default=3): The number of iterations to run serially before kicking off the parallel job. + Useful for catching bugs + Returns: + [function(array[0]), function(array[1]), ...] + """ + # We run the first few iterations serially to catch bugs + if front_num > 0: + front = [ + function(**a) if use_kwargs else function(a) for a in array[:front_num] + ] + else: + front = [] + # If we set n_jobs to 1, just run a list comprehension. This is useful for benchmarking and debugging. + if n_jobs == 1: + return front + [ + function(**a) if use_kwargs else function(a) + for a in tqdm(array[front_num:]) + ] + # Assemble the workers + with ProcessPoolExecutor(max_workers=n_jobs) as pool: + # Pass the elements of array into function + if use_kwargs: + futures = [pool.submit(function, **a) for a in array[front_num:]] + else: + futures = [pool.submit(function, a) for a in array[front_num:]] + kwargs = { + "total": len(futures), + "unit": "it", + "unit_scale": True, + "leave": True, + } + # Print out the progress as tasks complete + for f in tqdm(as_completed(futures), **kwargs): + pass + out = [] + # Get the results from the futures. + for i, future in tqdm(enumerate(futures)): + try: + out.append(future.result()) + except Exception as e: + out.append(e) + return front + out diff --git a/ppstructure/table/table_metric/table_metric.py b/ppstructure/table/table_metric/table_metric.py new file mode 100755 index 0000000..636fed4 --- /dev/null +++ b/ppstructure/table/table_metric/table_metric.py @@ -0,0 +1,249 @@ +# Copyright 2020 IBM +# Author: peter.zhong@au1.ibm.com +# +# This is free software; you can redistribute it and/or modify +# it under the terms of the Apache 2.0 License. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# Apache 2.0 License for more details. + +from rapidfuzz.distance import Levenshtein +from apted import APTED, Config +from apted.helpers import Tree +from collections import deque +from .parallel import parallel_process +from tqdm import tqdm +from paddle.utils import try_import + + +class TableTree(Tree): + def __init__(self, tag, colspan=None, rowspan=None, content=None, *children): + self.tag = tag + self.colspan = colspan + self.rowspan = rowspan + self.content = content + self.children = list(children) + + def bracket(self): + """Show tree using brackets notation""" + if self.tag == "td": + result = '"tag": %s, "colspan": %d, "rowspan": %d, "text": %s' % ( + self.tag, + self.colspan, + self.rowspan, + self.content, + ) + else: + result = '"tag": %s' % self.tag + for child in self.children: + result += child.bracket() + return "{{{}}}".format(result) + + +class CustomConfig(Config): + def rename(self, node1, node2): + """Compares attributes of trees""" + # print(node1.tag) + if ( + (node1.tag != node2.tag) + or (node1.colspan != node2.colspan) + or (node1.rowspan != node2.rowspan) + ): + return 1.0 + if node1.tag == "td": + if node1.content or node2.content: + # print(node1.content, ) + return Levenshtein.normalized_distance(node1.content, node2.content) + return 0.0 + + +class CustomConfig_del_short(Config): + def rename(self, node1, node2): + """Compares attributes of trees""" + if ( + (node1.tag != node2.tag) + or (node1.colspan != node2.colspan) + or (node1.rowspan != node2.rowspan) + ): + return 1.0 + if node1.tag == "td": + if node1.content or node2.content: + # print('before') + # print(node1.content, node2.content) + # print('after') + node1_content = node1.content + node2_content = node2.content + if len(node1_content) < 3: + node1_content = ["####"] + if len(node2_content) < 3: + node2_content = ["####"] + return Levenshtein.normalized_distance(node1_content, node2_content) + return 0.0 + + +class CustomConfig_del_block(Config): + def rename(self, node1, node2): + """Compares attributes of trees""" + if ( + (node1.tag != node2.tag) + or (node1.colspan != node2.colspan) + or (node1.rowspan != node2.rowspan) + ): + return 1.0 + if node1.tag == "td": + if node1.content or node2.content: + node1_content = node1.content + node2_content = node2.content + while " " in node1_content: + print(node1_content.index(" ")) + node1_content.pop(node1_content.index(" ")) + while " " in node2_content: + print(node2_content.index(" ")) + node2_content.pop(node2_content.index(" ")) + return Levenshtein.normalized_distance(node1_content, node2_content) + return 0.0 + + +class TEDS(object): + """Tree Edit Distance basead Similarity""" + + def __init__(self, structure_only=False, n_jobs=1, ignore_nodes=None): + assert isinstance(n_jobs, int) and ( + n_jobs >= 1 + ), "n_jobs must be an integer greater than 1" + self.structure_only = structure_only + self.n_jobs = n_jobs + self.ignore_nodes = ignore_nodes + self.__tokens__ = [] + + def tokenize(self, node): + """Tokenizes table cells""" + self.__tokens__.append("<%s>" % node.tag) + if node.text is not None: + self.__tokens__ += list(node.text) + for n in node: + self.tokenize(n) + if node.tag != "unk": + self.__tokens__.append("" % node.tag) + if node.tag != "td" and node.tail is not None: + self.__tokens__ += list(node.tail) + + def load_html_tree(self, node, parent=None): + """Converts HTML tree to the format required by apted""" + global __tokens__ + if node.tag == "td": + if self.structure_only: + cell = [] + else: + self.__tokens__ = [] + self.tokenize(node) + cell = self.__tokens__[1:-1].copy() + new_node = TableTree( + node.tag, + int(node.attrib.get("colspan", "1")), + int(node.attrib.get("rowspan", "1")), + cell, + *deque(), + ) + else: + new_node = TableTree(node.tag, None, None, None, *deque()) + if parent is not None: + parent.children.append(new_node) + if node.tag != "td": + for n in node: + self.load_html_tree(n, new_node) + if parent is None: + return new_node + + def evaluate(self, pred, true): + """Computes TEDS score between the prediction and the ground truth of a + given sample + """ + try_import("lxml") + from lxml import etree, html + + if (not pred) or (not true): + return 0.0 + parser = html.HTMLParser(remove_comments=True, encoding="utf-8") + pred = html.fromstring(pred, parser=parser) + true = html.fromstring(true, parser=parser) + if pred.xpath("body/table") and true.xpath("body/table"): + pred = pred.xpath("body/table")[0] + true = true.xpath("body/table")[0] + if self.ignore_nodes: + etree.strip_tags(pred, *self.ignore_nodes) + etree.strip_tags(true, *self.ignore_nodes) + n_nodes_pred = len(pred.xpath(".//*")) + n_nodes_true = len(true.xpath(".//*")) + n_nodes = max(n_nodes_pred, n_nodes_true) + tree_pred = self.load_html_tree(pred) + tree_true = self.load_html_tree(true) + distance = APTED( + tree_pred, tree_true, CustomConfig() + ).compute_edit_distance() + return 1.0 - (float(distance) / n_nodes) + else: + return 0.0 + + def batch_evaluate(self, pred_json, true_json): + """Computes TEDS score between the prediction and the ground truth of + a batch of samples + @params pred_json: {'FILENAME': 'HTML CODE', ...} + @params true_json: {'FILENAME': {'html': 'HTML CODE'}, ...} + @output: {'FILENAME': 'TEDS SCORE', ...} + """ + samples = true_json.keys() + if self.n_jobs == 1: + scores = [ + self.evaluate(pred_json.get(filename, ""), true_json[filename]["html"]) + for filename in tqdm(samples) + ] + else: + inputs = [ + { + "pred": pred_json.get(filename, ""), + "true": true_json[filename]["html"], + } + for filename in samples + ] + scores = parallel_process( + inputs, self.evaluate, use_kwargs=True, n_jobs=self.n_jobs, front_num=1 + ) + scores = dict(zip(samples, scores)) + return scores + + def batch_evaluate_html(self, pred_htmls, true_htmls): + """Computes TEDS score between the prediction and the ground truth of + a batch of samples + """ + if self.n_jobs == 1: + scores = [ + self.evaluate(pred_html, true_html) + for (pred_html, true_html) in zip(pred_htmls, true_htmls) + ] + else: + inputs = [ + {"pred": pred_html, "true": true_html} + for (pred_html, true_html) in zip(pred_htmls, true_htmls) + ] + + scores = parallel_process( + inputs, self.evaluate, use_kwargs=True, n_jobs=self.n_jobs, front_num=1 + ) + return scores + + +if __name__ == "__main__": + import json + import pprint + + with open("sample_pred.json") as fp: + pred_json = json.load(fp) + with open("sample_gt.json") as fp: + true_json = json.load(fp) + teds = TEDS(n_jobs=4) + scores = teds.batch_evaluate(pred_json, true_json) + pp = pprint.PrettyPrinter() + pp.pprint(scores) diff --git a/ppstructure/table/tablepyxl/__init__.py b/ppstructure/table/tablepyxl/__init__.py new file mode 100644 index 0000000..1d11e26 --- /dev/null +++ b/ppstructure/table/tablepyxl/__init__.py @@ -0,0 +1,13 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/ppstructure/table/tablepyxl/style.py b/ppstructure/table/tablepyxl/style.py new file mode 100644 index 0000000..dfd0f24 --- /dev/null +++ b/ppstructure/table/tablepyxl/style.py @@ -0,0 +1,349 @@ +# This is where we handle translating css styles into openpyxl styles +# and cascading those from parent to child in the dom. + +try: + from openpyxl.cell import cell + from openpyxl.styles import ( + Font, + Alignment, + PatternFill, + NamedStyle, + Border, + Side, + Color, + ) + from openpyxl.styles.fills import FILL_SOLID + from openpyxl.styles.numbers import FORMAT_CURRENCY_USD_SIMPLE, FORMAT_PERCENTAGE + from openpyxl.styles.colors import BLACK +except: + import warnings + + warnings.warn( + "Can not import openpyxl, some functions in the ppstructure may not work. Please manually install openpyxl before using ppstructure." + ) + +FORMAT_DATE_MMDDYYYY = "mm/dd/yyyy" + + +def colormap(color): + """ + Convenience for looking up known colors + """ + cmap = {"black": BLACK} + return cmap.get(color, color) + + +def style_string_to_dict(style): + """ + Convert css style string to a python dictionary + """ + + def clean_split(string, delim): + return (s.strip() for s in string.split(delim)) + + styles = [clean_split(s, ":") for s in style.split(";") if ":" in s] + return dict(styles) + + +def get_side(style, name): + return { + "border_style": style.get("border-{}-style".format(name)), + "color": colormap(style.get("border-{}-color".format(name))), + } + + +known_styles = {} + + +def style_dict_to_named_style(style_dict, number_format=None): + """ + Change css style (stored in a python dictionary) to openpyxl NamedStyle + """ + + style_and_format_string = str( + { + "style_dict": style_dict, + "parent": style_dict.parent, + "number_format": number_format, + } + ) + + if style_and_format_string not in known_styles: + # Font + font = Font( + bold=style_dict.get("font-weight") == "bold", + color=style_dict.get_color("color", None), + size=style_dict.get("font-size"), + ) + + # Alignment + alignment = Alignment( + horizontal=style_dict.get("text-align", "general"), + vertical=style_dict.get("vertical-align"), + wrap_text=style_dict.get("white-space", "nowrap") == "normal", + ) + + # Fill + bg_color = style_dict.get_color("background-color") + fg_color = style_dict.get_color("foreground-color", Color()) + fill_type = style_dict.get("fill-type") + if bg_color and bg_color != "transparent": + fill = PatternFill( + fill_type=fill_type or FILL_SOLID, + start_color=bg_color, + end_color=fg_color, + ) + else: + fill = PatternFill() + + # Border + border = Border( + left=Side(**get_side(style_dict, "left")), + right=Side(**get_side(style_dict, "right")), + top=Side(**get_side(style_dict, "top")), + bottom=Side(**get_side(style_dict, "bottom")), + diagonal=Side(**get_side(style_dict, "diagonal")), + diagonal_direction=None, + outline=Side(**get_side(style_dict, "outline")), + vertical=None, + horizontal=None, + ) + + name = "Style {}".format(len(known_styles) + 1) + + pyxl_style = NamedStyle( + name=name, + font=font, + fill=fill, + alignment=alignment, + border=border, + number_format=number_format, + ) + + known_styles[style_and_format_string] = pyxl_style + + return known_styles[style_and_format_string] + + +class StyleDict(dict): + """ + It's like a dictionary, but it looks for items in the parent dictionary + """ + + def __init__(self, *args, **kwargs): + self.parent = kwargs.pop("parent", None) + super(StyleDict, self).__init__(*args, **kwargs) + + def __getitem__(self, item): + if item in self: + return super(StyleDict, self).__getitem__(item) + elif self.parent: + return self.parent[item] + else: + raise KeyError("{} not found".format(item)) + + def __hash__(self): + return hash(tuple([(k, self.get(k)) for k in self._keys()])) + + # Yielding the keys avoids creating unnecessary data structures + # and happily works with both python2 and python3 where the + # .keys() method is a dictionary_view in python3 and a list in python2. + def _keys(self): + yielded = set() + for k in self.keys(): + yielded.add(k) + yield k + if self.parent: + for k in self.parent._keys(): + if k not in yielded: + yielded.add(k) + yield k + + def get(self, k, d=None): + try: + return self[k] + except KeyError: + return d + + def get_color(self, k, d=None): + """ + Strip leading # off colors if necessary + """ + color = self.get(k, d) + if hasattr(color, "startswith") and color.startswith("#"): + color = color[1:] + if ( + len(color) == 3 + ): # Premailers reduces colors like #00ff00 to #0f0, openpyxl doesn't like that + color = "".join(2 * c for c in color) + return color + + +class Element(object): + """ + Our base class for representing an html element along with a cascading style. + The element is created along with a parent so that the StyleDict that we store + can point to the parent's StyleDict. + """ + + def __init__(self, element, parent=None): + self.element = element + self.number_format = None + parent_style = parent.style_dict if parent else None + self.style_dict = StyleDict( + style_string_to_dict(element.get("style", "")), parent=parent_style + ) + self._style_cache = None + + def style(self): + """ + Turn the css styles for this element into an openpyxl NamedStyle. + """ + if not self._style_cache: + self._style_cache = style_dict_to_named_style( + self.style_dict, number_format=self.number_format + ) + return self._style_cache + + def get_dimension(self, dimension_key): + """ + Extracts the dimension from the style dict of the Element and returns it as a float. + """ + dimension = self.style_dict.get(dimension_key) + if dimension: + if dimension[-2:] in ["px", "em", "pt", "in", "cm"]: + dimension = dimension[:-2] + dimension = float(dimension) + return dimension + + +class Table(Element): + """ + The concrete implementations of Elements are semantically named for the types of elements we are interested in. + This defines a very concrete tree structure for html tables that we expect to deal with. I prefer this compared to + allowing Element to have an arbitrary number of children and dealing with an abstract element tree. + """ + + def __init__(self, table): + """ + takes an html table object (from lxml) + """ + super(Table, self).__init__(table) + table_head = table.find("thead") + self.head = ( + TableHead(table_head, parent=self) if table_head is not None else None + ) + table_body = table.find("tbody") + self.body = TableBody( + table_body if table_body is not None else table, parent=self + ) + + +class TableHead(Element): + """ + This class maps to the `` element of the html table. + """ + + def __init__(self, head, parent=None): + super(TableHead, self).__init__(head, parent=parent) + self.rows = [TableRow(tr, parent=self) for tr in head.findall("tr")] + + +class TableBody(Element): + """ + This class maps to the `` element of the html table. + """ + + def __init__(self, body, parent=None): + super(TableBody, self).__init__(body, parent=parent) + self.rows = [TableRow(tr, parent=self) for tr in body.findall("tr")] + + +class TableRow(Element): + """ + This class maps to the `` element of the html table. + """ + + def __init__(self, tr, parent=None): + super(TableRow, self).__init__(tr, parent=parent) + self.cells = [ + TableCell(cell, parent=self) for cell in tr.findall("th") + tr.findall("td") + ] + + +def element_to_string(el): + return _element_to_string(el).strip() + + +def _element_to_string(el): + string = "" + + for x in el.iterchildren(): + string += "\n" + _element_to_string(x) + + text = el.text.strip() if el.text else "" + tail = el.tail.strip() if el.tail else "" + + return text + string + "\n" + tail + + +class TableCell(Element): + """ + This class maps to the `` element of the html table. + """ + + CELL_TYPES = { + "TYPE_STRING", + "TYPE_FORMULA", + "TYPE_NUMERIC", + "TYPE_BOOL", + "TYPE_CURRENCY", + "TYPE_PERCENTAGE", + "TYPE_NULL", + "TYPE_INLINE", + "TYPE_ERROR", + "TYPE_FORMULA_CACHE_STRING", + "TYPE_INTEGER", + } + + def __init__(self, cell, parent=None): + super(TableCell, self).__init__(cell, parent=parent) + self.value = element_to_string(cell) + self.number_format = self.get_number_format() + + def data_type(self): + cell_types = self.CELL_TYPES & set(self.element.get("class", "").split()) + if cell_types: + if "TYPE_FORMULA" in cell_types: + # Make sure TYPE_FORMULA takes precedence over the other classes in the set. + cell_type = "TYPE_FORMULA" + elif cell_types & {"TYPE_CURRENCY", "TYPE_INTEGER", "TYPE_PERCENTAGE"}: + cell_type = "TYPE_NUMERIC" + else: + cell_type = cell_types.pop() + else: + cell_type = "TYPE_STRING" + return getattr(cell, cell_type) + + def get_number_format(self): + if "TYPE_CURRENCY" in self.element.get("class", "").split(): + return FORMAT_CURRENCY_USD_SIMPLE + if "TYPE_INTEGER" in self.element.get("class", "").split(): + return "#,##0" + if "TYPE_PERCENTAGE" in self.element.get("class", "").split(): + return FORMAT_PERCENTAGE + if "TYPE_DATE" in self.element.get("class", "").split(): + return FORMAT_DATE_MMDDYYYY + if self.data_type() == cell.TYPE_NUMERIC: + try: + int(self.value) + except ValueError: + return "#,##0.##" + else: + return "#,##0" + + def format(self, cell): + cell.style = self.style() + data_type = self.data_type() + if data_type: + cell.data_type = data_type diff --git a/ppstructure/table/tablepyxl/tablepyxl.py b/ppstructure/table/tablepyxl/tablepyxl.py new file mode 100644 index 0000000..92f6536 --- /dev/null +++ b/ppstructure/table/tablepyxl/tablepyxl.py @@ -0,0 +1,135 @@ +# Do imports like python3 so our package works for 2 and 3 +from __future__ import absolute_import + + +from tablepyxl.style import Table +from paddle.utils import try_import + + +def string_to_int(s): + if s.isdigit(): + return int(s) + return 0 + + +def get_Tables(doc): + try_import("lxml") + from lxml import etree, html + + tree = html.fromstring(doc) + comments = tree.xpath("//comment()") + for comment in comments: + comment.drop_tag() + return [Table(table) for table in tree.xpath("//table")] + + +def write_rows(worksheet, elem, row, column=1): + """ + Writes every tr child element of elem to a row in the worksheet + returns the next row after all rows are written + """ + try_import("openpyxl") + from openpyxl.cell.cell import MergedCell + from openpyxl.utils import get_column_letter + + initial_column = column + for table_row in elem.rows: + for table_cell in table_row.cells: + cell = worksheet.cell(row=row, column=column) + while isinstance(cell, MergedCell): + column += 1 + cell = worksheet.cell(row=row, column=column) + + colspan = string_to_int(table_cell.element.get("colspan", "1")) + rowspan = string_to_int(table_cell.element.get("rowspan", "1")) + if rowspan > 1 or colspan > 1: + worksheet.merge_cells( + start_row=row, + start_column=column, + end_row=row + rowspan - 1, + end_column=column + colspan - 1, + ) + + cell.value = table_cell.value + table_cell.format(cell) + min_width = table_cell.get_dimension("min-width") + max_width = table_cell.get_dimension("max-width") + + if colspan == 1: + # Initially, when iterating for the first time through the loop, the width of all the cells is None. + # As we start filling in contents, the initial width of the cell (which can be retrieved by: + # worksheet.column_dimensions[get_column_letter(column)].width) is equal to the width of the previous + # cell in the same column (i.e. width of A2 = width of A1) + width = max( + worksheet.column_dimensions[get_column_letter(column)].width or 0, + len(table_cell.value) + 2, + ) + if max_width and width > max_width: + width = max_width + elif min_width and width < min_width: + width = min_width + worksheet.column_dimensions[get_column_letter(column)].width = width + column += colspan + row += 1 + column = initial_column + return row + + +def table_to_sheet(table, wb): + """ + Takes a table and workbook and writes the table to a new sheet. + The sheet title will be the same as the table attribute name. + """ + ws = wb.create_sheet(title=table.element.get("name")) + insert_table(table, ws, 1, 1) + + +def document_to_workbook(doc, wb=None, base_url=None): + """ + Takes a string representation of an html document and writes one sheet for + every table in the document. + The workbook is returned + """ + try_import("premailer") + try_import("openpyxl") + from premailer import Premailer + from openpyxl import Workbook + + if not wb: + wb = Workbook() + wb.remove(wb.active) + + inline_styles_doc = Premailer( + doc, base_url=base_url, remove_classes=False + ).transform() + tables = get_Tables(inline_styles_doc) + + for table in tables: + table_to_sheet(table, wb) + + return wb + + +def document_to_xl(doc, filename, base_url=None): + """ + Takes a string representation of an html document and writes one sheet for + every table in the document. The workbook is written out to a file called filename + """ + wb = document_to_workbook(doc, base_url=base_url) + wb.save(filename) + + +def insert_table(table, worksheet, column, row): + if table.head: + row = write_rows(worksheet, table.head, row, column) + if table.body: + row = write_rows(worksheet, table.body, row, column) + + +def insert_table_at_cell(table, cell): + """ + Inserts a table at the location of an openpyxl Cell object. + """ + ws = cell.parent + column, row = cell.column, cell.row + insert_table(table, ws, column, row) diff --git a/ppstructure/utility.py b/ppstructure/utility.py new file mode 100644 index 0000000..97e434d --- /dev/null +++ b/ppstructure/utility.py @@ -0,0 +1,298 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import random +import ast +import PIL +from PIL import Image, ImageDraw, ImageFont +import numpy as np +from tools.infer.utility import ( + draw_ocr_box_txt, + str2bool, + str2int_tuple, + init_args as infer_args, +) +import math + + +def init_args(): + parser = infer_args() + + # params for output + parser.add_argument("--output", type=str, default="./output") + # params for table structure + parser.add_argument("--table_max_len", type=int, default=488) + parser.add_argument("--table_algorithm", type=str, default="TableAttn") + parser.add_argument("--table_model_dir", type=str) + parser.add_argument("--merge_no_span_structure", type=str2bool, default=True) + parser.add_argument( + "--table_char_dict_path", + type=str, + default="../ppocr/utils/dict/table_structure_dict_ch.txt", + ) + # params for formula recognition + parser.add_argument("--formula_algorithm", type=str, default="LaTeXOCR") + parser.add_argument("--formula_model_dir", type=str) + parser.add_argument( + "--formula_char_dict_path", + type=str, + default="../ppocr/utils/dict/latex_ocr_tokenizer.json", + ) + parser.add_argument("--formula_batch_num", type=int, default=1) + # params for layout + parser.add_argument("--layout_model_dir", type=str) + parser.add_argument( + "--layout_dict_path", + type=str, + default="../ppocr/utils/dict/layout_dict/layout_publaynet_dict.txt", + ) + parser.add_argument( + "--layout_score_threshold", type=float, default=0.5, help="Threshold of score." + ) + parser.add_argument( + "--layout_nms_threshold", type=float, default=0.5, help="Threshold of nms." + ) + # params for kie + parser.add_argument("--kie_algorithm", type=str, default="LayoutXLM") + parser.add_argument("--ser_model_dir", type=str) + parser.add_argument("--re_model_dir", type=str) + parser.add_argument("--use_visual_backbone", type=str2bool, default=True) + parser.add_argument( + "--ser_dict_path", type=str, default="../train_data/XFUND/class_list_xfun.txt" + ) + # need to be None or tb-yx + parser.add_argument("--ocr_order_method", type=str, default=None) + # params for inference + parser.add_argument( + "--mode", + type=str, + choices=["structure", "kie"], + default="structure", + help="structure and kie is supported", + ) + parser.add_argument( + "--image_orientation", + type=bool, + default=False, + help="Whether to enable image orientation recognition", + ) + parser.add_argument( + "--layout", + type=str2bool, + default=True, + help="Whether to enable layout analysis", + ) + parser.add_argument( + "--table", + type=str2bool, + default=True, + help="In the forward, whether the table area uses table recognition", + ) + parser.add_argument( + "--formula", + type=str2bool, + default=False, + help="Whether to enable formula recognition", + ) + parser.add_argument( + "--ocr", + type=str2bool, + default=True, + help="In the forward, whether the non-table area is recognition by ocr", + ) + # param for recovery + parser.add_argument( + "--recovery", + type=str2bool, + default=False, + help="Whether to enable layout of recovery", + ) + parser.add_argument( + "--recovery_to_markdown", + type=str2bool, + default=False, + help="Whether to enable layout of recovery to markdown", + ) + parser.add_argument( + "--use_pdf2docx_api", + type=str2bool, + default=False, + help="Whether to use pdf2docx api", + ) + parser.add_argument( + "--invert", + type=str2bool, + default=False, + help="Whether to invert image before processing", + ) + parser.add_argument( + "--binarize", + type=str2bool, + default=False, + help="Whether to threshold binarize image before processing", + ) + parser.add_argument( + "--alphacolor", + type=str2int_tuple, + default=(255, 255, 255), + help="Replacement color for the alpha channel, if the latter is present; R,G,B integers", + ) + + return parser + + +def parse_args(): + parser = init_args() + return parser.parse_args() + + +def draw_structure_result(image, result, font_path): + if isinstance(image, np.ndarray): + image = Image.fromarray(image) + boxes, txts, scores = [], [], [] + + img_layout = image.copy() + draw_layout = ImageDraw.Draw(img_layout) + text_color = (255, 255, 255) + text_background_color = (80, 127, 255) + catid2color = {} + font_size = 15 + font = ImageFont.truetype(font_path, font_size, encoding="utf-8") + + for region in result: + if region["type"] not in catid2color: + box_color = ( + random.randint(0, 255), + random.randint(0, 255), + random.randint(0, 255), + ) + catid2color[region["type"]] = box_color + else: + box_color = catid2color[region["type"]] + box_layout = region["bbox"] + draw_layout.rectangle( + [(box_layout[0], box_layout[1]), (box_layout[2], box_layout[3])], + outline=box_color, + width=3, + ) + + if int(PIL.__version__.split(".")[0]) < 10: + text_w, text_h = font.getsize(region["type"]) + else: + left, top, right, bottom = font.getbbox(region["type"]) + text_w, text_h = right - left, bottom - top + + draw_layout.rectangle( + [ + (box_layout[0], box_layout[1]), + (box_layout[0] + text_w, box_layout[1] + text_h), + ], + fill=text_background_color, + ) + draw_layout.text( + (box_layout[0], box_layout[1]), region["type"], fill=text_color, font=font + ) + + if region["type"] == "table" or ( + region["type"] == "equation" and "latex" in region["res"] + ): + pass + else: + for text_result in region["res"]: + boxes.append(np.array(text_result["text_region"])) + txts.append(text_result["text"]) + scores.append(text_result["confidence"]) + + if "text_word_region" in text_result: + for word_region in text_result["text_word_region"]: + char_box = word_region + box_height = int( + math.sqrt( + (char_box[0][0] - char_box[3][0]) ** 2 + + (char_box[0][1] - char_box[3][1]) ** 2 + ) + ) + box_width = int( + math.sqrt( + (char_box[0][0] - char_box[1][0]) ** 2 + + (char_box[0][1] - char_box[1][1]) ** 2 + ) + ) + if box_height == 0 or box_width == 0: + continue + boxes.append(word_region) + txts.append("") + scores.append(1.0) + + im_show = draw_ocr_box_txt( + img_layout, boxes, txts, scores, font_path=font_path, drop_score=0 + ) + return im_show + + +def cal_ocr_word_box(rec_str, box, rec_word_info): + """Calculate the detection frame for each word based on the results of recognition and detection of ocr""" + + col_num, word_list, word_col_list, state_list = rec_word_info + box = box.tolist() + bbox_x_start = box[0][0] + bbox_x_end = box[1][0] + bbox_y_start = box[0][1] + bbox_y_end = box[2][1] + + cell_width = (bbox_x_end - bbox_x_start) / col_num + + word_box_list = [] + word_box_content_list = [] + cn_width_list = [] + cn_col_list = [] + for word, word_col, state in zip(word_list, word_col_list, state_list): + if state == "cn": + if len(word_col) != 1: + char_seq_length = (word_col[-1] - word_col[0] + 1) * cell_width + char_width = char_seq_length / (len(word_col) - 1) + cn_width_list.append(char_width) + cn_col_list += word_col + word_box_content_list += word + else: + cell_x_start = bbox_x_start + int(word_col[0] * cell_width) + cell_x_end = bbox_x_start + int((word_col[-1] + 1) * cell_width) + cell = ( + (cell_x_start, bbox_y_start), + (cell_x_end, bbox_y_start), + (cell_x_end, bbox_y_end), + (cell_x_start, bbox_y_end), + ) + word_box_list.append(cell) + word_box_content_list.append("".join(word)) + if len(cn_col_list) != 0: + if len(cn_width_list) != 0: + avg_char_width = np.mean(cn_width_list) + else: + avg_char_width = (bbox_x_end - bbox_x_start) / len(rec_str) + for center_idx in cn_col_list: + center_x = (center_idx + 0.5) * cell_width + cell_x_start = max(int(center_x - avg_char_width / 2), 0) + bbox_x_start + cell_x_end = ( + min(int(center_x + avg_char_width / 2), bbox_x_end - bbox_x_start) + + bbox_x_start + ) + cell = ( + (cell_x_start, bbox_y_start), + (cell_x_end, bbox_y_start), + (cell_x_end, bbox_y_end), + (cell_x_start, bbox_y_end), + ) + word_box_list.append(cell) + + return word_box_content_list, word_box_list diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..184dec7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,87 @@ +[build-system] +requires = ["setuptools==72.1.0", "wheel", "setuptools_scm"] +build-backend = "setuptools.build_meta" + +[project] +name = "paddleocr" +# After each version release, the version number needs to be incremented +dynamic = ["version"] +description = "Awesome multilingual OCR and document parsing toolkits based on PaddlePaddle" +authors = [ + {name = "PaddlePaddle", email = "paddleocr@baidu.com"}, +] +maintainers = [ + {name = "PaddlePaddle", email = "paddleocr@baidu.com"}, +] +readme = "README.md" +requires-python = ">=3.8" +keywords = [ + "ocr", + "textdetection", + "textrecognition", + "paddleocr", + "chineseocr", + "pp-ocr", + "pp-structure", + "document-parsing", + "pdf2markdown", + "doc2markdown", + "docx2markdown", + "kie", + "pp-chatocr", + "document-translation", + "rag" +] +license = {text = "Apache License 2.0"} +classifiers = [ + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Natural Language :: Chinese (Simplified)", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Utilities", +] +dependencies = [ + "paddlex[ocr-core]>=3.7.0,<3.8.0", + "PyYAML>=6", + "requests", + "aiohttp>=3.8.0", + "typing-extensions>=4.12", +] + +[project.urls] +homepage = "https://github.com/PaddlePaddle/PaddleOCR" +documentation = "https://github.com/PaddlePaddle/PaddleOCR/blob/main/README.md" +repository = "https://github.com/PaddlePaddle/PaddleOCR.git" +issues = "https://github.com/PaddlePaddle/PaddleOCR/issues" + +[project.scripts] +paddleocr = "paddleocr.__main__:console_entry" + +[project.optional-dependencies] +doc-parser = ["paddlex[ocr,genai-client]>=3.7.0,<3.8.0"] +ie = ["paddlex[ie]>=3.7.0,<3.8.0"] +trans = ["paddlex[trans]>=3.7.0,<3.8.0"] +doc2md = ["python-docx>=0.8.11", "python-pptx>=0.6.21", "openpyxl>=3.0.0", "pylatexenc>=2.10,<3"] +all = ["paddlex[ocr,genai-client,ie,trans]>=3.7.0,<3.8.0", "python-docx>=0.8.11", "python-pptx>=0.6.21", "openpyxl>=3.0.0"] + +[tool.setuptools.packages.find] +where = ["."] +include = ["paddleocr", "paddleocr.*"] +namespaces = false + +[tool.setuptools_scm] +version_scheme = 'release-branch-semver' +git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "v[0-9]*"] + +[tool.pytest.ini_options] +markers = [ + "resource_intensive: mark a test as resource intensive", + "py38_incompatible: test requires extras or deps that need Python 3.9+" +] +addopts = "-m 'not resource_intensive'" diff --git a/readme/README_ar.md b/readme/README_ar.md new file mode 100644 index 0000000..34c5f7e --- /dev/null +++ b/readme/README_ar.md @@ -0,0 +1,305 @@ + + +
    +

    + تاريخ النجوم +

    + + + +

    مجموعة أدوات التعرف الضوئي على الحروف (OCR) الرائدة عالمياً ومحرك الذكاء الاصطناعي للمستندات

    + +[English](../README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) | [Español](./README_es.md) | العربية + + + +[![تنزيلات PyPI](https://static.pepy.tech/badge/paddleocr)](https://pepy.tech/projects/paddleocr) +[![مُستخدَم بواسطة](https://img.shields.io/badge/Used%20by-6k%2B%20repositories-blue)](https://github.com/PaddlePaddle/PaddleOCR/network/dependents) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + +[![AI Studio](https://img.shields.io/badge/PaddleOCR-_Offiical_Website-1927BA?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAABlBMVEU2P+X///+1KuUwAAAHKklEQVR42u3dS5bjOAwEwALvf2fMavZum6IAImI7b2yYSqU+1Zb//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKCR/+fzly7rD92yVg69xh8zeLwOa5w+ZvFYHtc4ft3ykB++cOm79PAp6YO2z/Ngl4ZO5l+9+yT4QAvLqS748VF33Ylzdvzpl72f6z53YIGJ6SZdPeNHcIwOycaADdLgCSIgAIgCOAACAAykIAEAAEAAFAABCAT+WQuQVgeBqXhXQIQAAYegowLQBpbg3gZGFyAC6vgBQAMREA2/YfDPxyaDQNyTNz+3Zwn5J4ZG7PB2h0kHhi7plPCImmJwkPzO0RMa3OET0i5uGlzHFze0xcu0vE2Dq3J4U2vEPgSaHbFzPNDQAAAAAAAMBNovdw+cP/ny+uaf7w/+eYADy8kE+F4Offdjn6zZXhAXgiA78G4MNNsmnu1Xr7b3mbOL8T5Ja5bw/A35EC2LiWpzt1y9jRugBy30fLg3NvHPvnuZcC2NsCUXA/aRmA89V07Fwgt37uH8deCmBr6N44pP4UgaUATpdA7v/cMbIB8okliY65/SW5HhJ1ehPmM+8edwXgpbu4R88FayR32Y/P7oZZbOx13/Zr//ZHx27bAPnkFoyewYlbAhD3TvBobr95gaUAtr1EdNx1lgI4OcTTuR3z6+FZMEDRcu9ZCuDgGCdyGxMa4EgBRMvcjrkM7NgBZw5c0TwAUWUhZwRXA2xaya65Xa3jO2qYZ8bu2AD5w38tG5V8aZpoGN6Tz0bOfa9bceyWAciTO0jWyO1Tc5cLwJmF/JfPnXVyu3/slgHIg1n79O2O5fZv+1cHV7sC2HYqmUdHysNzX3sVkMcjUK5Gc+dMs28E5bGtm0V3gloBOP9vgZv+4sYn3RUaYFMCol5uN77g6lUApc8pWs69Zn7snS9Z9Q8G0S0AUTVUUTG3A54R1KSvo/diLAv5fKzynZeN6xogC75u93+AtBTA47OlAFSv6qY/vp3DAjD8iv2ZdFYJwKynMhTK1rInPfzaxW81LnvSgFP9KxrATaCLA3DxHpbFX31ZyNm5XRZyXG5bNkAWfP0rcrsUwOgC6NIAzgBcBiqAWwPgLrAGuGBP6jr2sifdfiJ6QQM4Bbw4AK4B3129ZSFn53ZZyA/GyFty27IBFMDFAXAG8PbyLQv5xULGPRl0K3h2AbwcgCZPhs+LD1zLnjS6AN4NwMU/DVFh7LyhASreTbvqrxdr/J4XT4Swz4FrTS+AGJ7bNbwAYkxuWzZAVljHrJfbjb9wviYXwFO/FJ8Vli4vaICsEMFyBbA3tmtsAUS0zG1c/bj4YwsZH2/+Whd0+1Nb+S7IE2sfPw4RL0XmsR8Nqvz7qFngmPHF34EqjP15AAofAkosZKPC/K6FVoeP02Ehi540NG6AK/4pYP3cLgVwXwHkDQ1QcSGb/uF4WwCmfX8u/+4vgLINcMUlQIfcLgXwXAF0+BGkpQDuuJx7/hwgpu//cWVuO3wxJOz/z8297vgYBwaIO3O7Kn+c194578ltywbIgu8fl+Z2lS+APvnLjnOv8hsgSqxjgwL4Ln9LAezaj98tgPzy7ZcC+GQzxrWxXQpgx370dm6/H7v6jaBoso5dY1swAFlwHWvfBf5pxVa93fCtdx64+1dsgCy4joWvAfPX9VoKYMs6Zse9/8Mlvv7LILlhAfKFFdsSutJXAdFkL3qlADJPrXFcXAC5KYaH586jO9mtAch9S3T0GQJ726ZWAE49kjP3rlDJuetdaL/1zeqZY9c7CRz7s0wCUPxienQBnAuAAtAAlxaAAAxfyBQABSAACkAAFIAAKAABUAACMEkKwL170oh7V8ueNLoAjgTAXWAN4BRwcABcA2oABTA4AApAAyiAwQFQABpAAQwOgALQADMWUgCuEmNyu15fSIY3gFPAiwPgFFADKIDBAVAAGkABCIACmBqAUAAaQAHMDUCMWkgBuMWw3K43F5LhDeAU8OIAuAmkARTA4AAoAA2gAARAAUwNgLvAGkABDA6Au8AaoKOJuV0vLSTDG8Ap4MUBcBNIAyiAwQFQABpAAQwOgALQAApAABTA1AC4C6wBOhqb23V+IRneAE4BLw6Aa0ANoAAGB0ABaAAFMDgACkADKAABUABTA+AusAboKATAQs4trjV+IYcfuJYCcA6gAATAQk69dFkKQANYyLkFcLIBFIDLQAVwawDsSRrAEWBwAJwCagAFMDgACkADKIDBAVAAGkABCIACmBoAzwXWAApgcADsSRrg0iNACoACEADXgAIwdCFTACykALgGFIAfl0kBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPBv/gN+IH8U6YveYgAAAABJRU5ErkJggg==&labelColor=white)](https://www.paddleocr.com) +[![اسأل DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/PaddlePaddle/PaddleOCR) +[![الرخصة](https://img.shields.io/badge/license-Apache_2.0-green)](../LICENSE) + +
    + + + + + + +**يحوّل PaddleOCR المستندات والصور إلى بيانات منظمة جاهزة للنماذج اللغوية الكبيرة (JSON/Markdown) بدقة رائدة في المجال. بأكثر من 70 ألف نجمة وثقة مشاريع رائدة مثل Dify وRAGFlow وCherry Studio، يُعد PaddleOCR الأساس المتين لبناء تطبيقات RAG والتطبيقات الوكيلية الذكية.** + + +## 🚀 الميزات الرئيسية + +### 📄 تحليل ذكي للمستندات (جاهز للنماذج اللغوية الكبيرة) +> *تحويل المرئيات المعقدة إلى بيانات منظمة لعصر النماذج اللغوية الكبيرة.* + +* **نموذج رؤية-لغة رائد للمستندات**: يتميز بنموذج **PaddleOCR-VL-1.6 (0.9B)**، النموذج خفيف الحجم الرائد في المجال للرؤية واللغة لتحليل المستندات. يحقق دقة 96.3% على OmniDocBench v1.6، ويتصدر في التعرف على النصوص والصيغ والجداول، مع تحسينات كبيرة في المستندات القديمة والأحرف النادرة والأختام والرسوم البيانية، مع مخرجات منظمة بصيغ **Markdown** و**JSON**. +* **تحويل مدرك للبنية**: بدعم من **PP-StructureV3**، يتم تحويل ملفات PDF والصور المعقدة بسلاسة إلى **Markdown** أو **JSON**. على عكس نماذج سلسلة PaddleOCR-VL، يوفر معلومات إحداثية أدق تشمل إحداثيات خلايا الجداول وإحداثيات النصوص وغيرها. +* **كفاءة جاهزة للإنتاج**: تحقيق دقة بمستوى تجاري مع حجم صغير للغاية. يتفوق على العديد من الحلول المغلقة المصدر في المعايير المرجعية العامة مع الحفاظ على كفاءة استخدام الموارد للنشر على الأجهزة الطرفية والسحابية. + +### 🔍 التعرف الشامل على النصوص (OCR للمشاهد) +> *المعيار الذهبي العالمي للكشف السريع عن النصوص متعددة اللغات.* + +* **دعم أكثر من 100 لغة**: تعرف أصلي على مكتبة عالمية واسعة. **PP-OCRv6** يدعم 50 لغة بنموذج واحد موحد (الصينية، الإنجليزية، اليابانية، و46 لغة لاتينية) — لا حاجة لتبديل النماذج. +* **إتقان العناصر المعقدة**: بالإضافة إلى التعرف القياسي على النصوص، ندعم **الكشف عن النصوص في المشاهد الطبيعية** عبر مجموعة واسعة من البيئات، بما في ذلك بطاقات الهوية، ومشاهد الشوارع، والكتب، والمكونات الصناعية. +* **قفزة في الأداء**: يحقق PP-OCRv6 تحسيناً في دقة الكشف بنسبة **4.6%** والتعرف بنسبة **5.1%** مقارنة بـ PP-OCRv5، متفوقاً على النماذج اللغوية البصرية الرئيسية. تسريع 5.2× في الاستدلال على وحدة المعالجة المركزية. + +
    +

    + هندسة PaddleOCR +

    +
    + +### 🛠️ منظومة مُركّزة على المطورين +* **تكامل سلس**: الخيار الأمثل لمنظومة الوكلاء الذكيين — متكامل بعمق مع **Dify وRAGFlow وPathway وCherry Studio**. +* **حلقة بيانات النماذج اللغوية الكبيرة**: خط أنابيب كامل لبناء مجموعات بيانات عالية الجودة، يوفر "محرك بيانات" مستداماً لضبط النماذج اللغوية الكبيرة. +* **نشر بنقرة واحدة**: دعم لمختلف منصات العتاد (وحدات معالجة الرسومات NVIDIA، معالجات Intel، وحدات Kunlunxin XPU، ومسرّعات الذكاء الاصطناعي المتنوعة). + + +## 📣 آخر التحديثات + +### 🔥 2026.06.11: إصدار PaddleOCR 3.7.0 +- أبرز مزايا PP-OCRv6: + + - **تحسين الدقة**: يحقق الطراز medium تحسيناً +4.6% في الكشف و+5.1% في التعرف مقارنة بـ PP-OCRv5_server، متفوقاً على النماذج اللغوية البصرية الرئيسية بـ 34.5M معلمة فقط. + - **دعم 50 لغة موحد**: نموذج واحد يغطي الصينية والإنجليزية واليابانية و46 لغة لاتينية — لا حاجة لتبديل النماذج. + - **سيناريوهات متخصصة**: تحسينات كبيرة في الشاشات الرقمية وأحرف المصفوفة النقطية وبصمات الإطارات والنصوص الصناعية. + - **استدلال أسرع**: تسريع 5.2× على CPU (OpenVINO)، 6.1× على Apple M4 (tiny)، 0.13 ثانية على A100 GPU. + - **ثلاثة مستويات لجميع السيناريوهات**: tiny (1.5M) / small (7.7M) / medium (34.5M) للأجهزة الطرفية والمحمولة والخوادم. + - **توفر النماذج**: جميع النماذج متاحة على [HuggingFace](https://huggingface.co/collections/PaddlePaddle/pp-ocrv6) و [ModelScope](https://www.modelscope.cn/collections/PaddlePaddle/PP-OCRv6). + +
    +2026.05.28: إصدار PaddleOCR 3.6.0 + +- أبرز مزايا PaddleOCR-VL-1.6: + + - **دقة SOTA جديدة**: 96.3% على OmniDocBench v1.6، SOTA جديد على OmniDocBench v1.5 وReal5-OmniDocBench. + - **ترقية شاملة**: تحسينات كبيرة في الجداول والمستندات القديمة والأحرف النادرة. + - **ترحيل سلس**: بنية متوافقة تماماً مع PaddleOCR-VL-1.5. + - **جرّبه الآن**: [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6) أو [الموقع الرسمي](https://www.paddleocr.com). + +
    +
    +2026.04.21: إصدار PaddleOCR 3.5.0 + +* **مرونة أكبر في تبديل واجهات الاستدلال**: يدعم التبديل السلس بين الرسم البياني الثابت في Paddle، والرسم البياني الديناميكي في Paddle، وTransformers. أصبح PaddleOCR الآن متكاملاً بعمق مع منظومة Hugging Face، كما تدعم 20 من النماذج الرئيسية استخدام Transformers كواجهة للاستدلال. +* **تحويل مستندات Office إلى Markdown**: يدعم تحويل صيغ المستندات الشائعة مثل Word وExcel وPowerPoint إلى Markdown. +* **تصدير نتائج التحليل إلى DOCX**: أصبحت سلاسل `PaddleOCR-VL` و`PP-StructureV3` و`PP-DocTranslation` تدعم الآن تصدير نتائج التحليل إلى DOCX لسهولة العرض والتحرير في Microsoft Word. +* **حزمة SDK الرسمية للاستدلال في المتصفح**: تم إصدار `PaddleOCR.js`، وهي حزمة SDK الرسمية للاستدلال في المتصفح، وتدعم تشغيل `PP-OCRv5` مباشرة داخل المتصفح. + +
    + +
    +2026.01.29: إصدار PaddleOCR 3.4.0 +* PaddleOCR-VL-1.5 (نموذج VLM رائد بحجم 0.9B): نموذجنا الرائد الأحدث لتحليل المستندات متاح الآن! + * **دقة 94.5% على OmniDocBench**: يتفوق على النماذج الكبيرة العامة الرائدة ومحللات المستندات المتخصصة. + * **متانة في العالم الواقعي**: أول من يقدم خوارزمية **PP-DocLayoutV3** لتحديد موقع الأشكال غير المنتظمة، مع إتقان 5 سيناريوهات صعبة: *الميل، التشوه، المسح الضوئي، الإضاءة، وتصوير الشاشة*. + * **توسيع القدرات**: يدعم الآن **التعرف على الأختام**، و**الكشف عن النصوص**، ويتوسع ليشمل **111 لغة** (بما في ذلك الخط التبتي الصيني والبنغالية). + * **إتقان المستندات الطويلة**: يدعم الدمج التلقائي للجداول عبر الصفحات وتحديد العناوين الهرمية. + * **جرّبه الآن**: متاح على [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.5) أو [موقعنا الرسمي](https://www.paddleocr.com). + +
    + +
    +2025.10.16: إصدار PaddleOCR 3.3.0 + +- إصدار PaddleOCR-VL: + - **تقديم النموذج**: + - **PaddleOCR-VL** هو نموذج رائد وموفر للموارد مصمم خصيصاً لتحليل المستندات. مكونه الأساسي هو PaddleOCR-VL-0.9B، وهو نموذج رؤية-لغة (VLM) صغير الحجم لكنه قوي يدمج مشفر بصري ديناميكي الدقة بأسلوب NaViT مع النموذج اللغوي ERNIE-4.5-0.3B لتمكين التعرف الدقيق على العناصر. **يدعم هذا النموذج المبتكر 109 لغات بكفاءة ويتفوق في التعرف على العناصر المعقدة (مثل النصوص والجداول والصيغ الرياضية والرسوم البيانية)، مع الحفاظ على الحد الأدنى من استهلاك الموارد**. من خلال التقييمات الشاملة على المعايير المرجعية العامة المستخدمة على نطاق واسع والمعايير الداخلية، يحقق PaddleOCR-VL أداءً رائداً في كل من تحليل المستندات على مستوى الصفحة والتعرف على العناصر. يتفوق بشكل كبير على الحلول الحالية، ويُظهر تنافسية قوية أمام نماذج VLM الرائدة، ويقدم سرعات استدلال عالية. هذه المزايا تجعله مناسباً جداً للنشر العملي في السيناريوهات الواقعية. تم إصدار النموذج على [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL). نرحب بالجميع لتنزيله واستخدامه! يمكن العثور على مزيد من المعلومات في [PaddleOCR-VL](https://www.paddleocr.ai/latest/version3.x/algorithm/PaddleOCR-VL/PaddleOCR-VL.html). + + - **الميزات الأساسية**: + - **بنية VLM صغيرة لكنها قوية**: نقدم نموذج رؤية-لغة مبتكراً مصمماً خصيصاً للاستدلال الموفر للموارد، يحقق أداءً متميزاً في التعرف على العناصر. من خلال دمج مشفر بصري ديناميكي عالي الدقة بأسلوب NaViT مع النموذج اللغوي خفيف الحجم ERNIE-4.5-0.3B، نعزز بشكل كبير قدرات التعرف وكفاءة فك التشفير. يحافظ هذا الدمج على دقة عالية مع تقليل المتطلبات الحسابية، مما يجعله مناسباً تماماً لتطبيقات معالجة المستندات الفعالة والعملية. + - **أداء رائد في تحليل المستندات**: يحقق PaddleOCR-VL أداءً رائداً في كل من تحليل المستندات على مستوى الصفحة والتعرف على العناصر. يتفوق بشكل كبير على الحلول القائمة على خطوط الأنابيب ويُظهر تنافسية قوية أمام نماذج الرؤية-اللغة (VLMs) الرائدة في تحليل المستندات. علاوة على ذلك، يتفوق في التعرف على عناصر المستندات المعقدة، مثل النصوص والجداول والصيغ الرياضية والرسوم البيانية، مما يجعله مناسباً لمجموعة واسعة من أنواع المحتوى الصعبة، بما في ذلك النصوص المكتوبة بخط اليد والمستندات التاريخية. وهذا يجعله متعدد الاستخدامات ومناسباً لمجموعة واسعة من أنواع المستندات والسيناريوهات. + - **دعم متعدد اللغات**: يدعم PaddleOCR-VL 109 لغات، تغطي اللغات العالمية الرئيسية، بما في ذلك على سبيل المثال لا الحصر الصينية والإنجليزية واليابانية واللاتينية والكورية، بالإضافة إلى لغات ذات خطوط وبنى مختلفة مثل الروسية (الخط السيريلي) والعربية والهندية (خط ديفاناغاري) والتايلاندية. تعزز هذه التغطية اللغوية الواسعة بشكل كبير من قابلية تطبيق نظامنا في سيناريوهات معالجة المستندات متعددة اللغات والعالمية. + +- إصدار نموذج التعرف متعدد اللغات PP-OCRv5: + - تحسين دقة وتغطية التعرف على الخط اللاتيني؛ إضافة دعم للأنظمة الكتابية السيريلية والعربية والديفاناغارية والتيلوغية والتاميلية وغيرها، مع تغطية التعرف على 109 لغات. يحتوي النموذج على 2 مليون معامل فقط، وقد زادت دقة بعض النماذج بأكثر من 40% مقارنة بالجيل السابق. + +
    + + +
    +2025.08.21: إصدار PaddleOCR 3.2.0 + +- **إضافات نموذجية مهمة:** + - تقديم التدريب والاستدلال والنشر لنماذج التعرف PP-OCRv5 باللغات الإنجليزية والتايلاندية واليونانية. **يقدم نموذج PP-OCRv5 الإنجليزي تحسيناً بنسبة 11% في السيناريوهات الإنجليزية مقارنة بنموذج PP-OCRv5 الرئيسي، مع تحقيق نماذج التعرف التايلاندي واليوناني دقة 82.68% و89.28% على التوالي.** + +- **ترقيات قدرات النشر:** + - **دعم كامل لإصداري إطار عمل PaddlePaddle 3.1.0 و3.1.1.** + - **ترقية شاملة لحل النشر المحلي بلغة C++ لـ PP-OCRv5، يدعم الآن كلاً من Linux وWindows، مع تكافؤ الميزات ودقة مطابقة لتنفيذ Python.** + - **يدعم الاستدلال عالي الأداء الآن CUDA 12، ويمكن إجراء الاستدلال باستخدام واجهة Paddle Inference أو ONNX Runtime.** + - **حل النشر الخدمي عالي الاستقرار أصبح الآن مفتوح المصدر بالكامل، مما يتيح للمستخدمين تخصيص صور Docker وحزم SDK حسب الحاجة.** + - يدعم حل النشر الخدمي عالي الاستقرار أيضاً الاستدعاء عبر طلبات HTTP المُنشأة يدوياً، مما يتيح تطوير الشيفرة البرمجية من جانب العميل بأي لغة برمجة. + +- **دعم المعايير المرجعية:** + - **تدعم جميع خطوط الإنتاج الآن قياس الأداء الدقيق، مما يتيح قياس زمن الاستدلال الشامل بالإضافة إلى بيانات زمن الاستجابة لكل طبقة ووحدة للمساعدة في تحليل الأداء. [إليك](docs/version3.x/pipeline_usage/instructions/benchmark.en.md) كيفية إعداد واستخدام ميزة قياس الأداء.** + - **تم تحديث الوثائق لتشمل المقاييس الرئيسية للتكوينات الشائعة الاستخدام على العتاد الرئيسي، مثل زمن الاستجابة واستخدام الذاكرة، لتوفير مراجع النشر للمستخدمين.** + +- **إصلاح الأخطاء:** + - حل مشكلة فشل حفظ السجلات أثناء تدريب النموذج. + - ترقية مكون تعزيز البيانات لنماذج الصيغ الرياضية للتوافق مع الإصدارات الأحدث من مكتبة albumentations، وإصلاح تحذيرات الجمود عند استخدام حزمة tokenizers في سيناريوهات متعددة العمليات. + - إصلاح التناقضات في سلوكيات المفاتيح (مثل `use_chart_parsing`) في ملفات تكوين PP-StructureV3 مقارنة بخطوط الأنابيب الأخرى. + +- **تحسينات أخرى:** + - **فصل التبعيات الأساسية والاختيارية. التبعيات الأساسية الدنيا فقط مطلوبة للتعرف الأساسي على النصوص؛ يمكن تثبيت التبعيات الإضافية لتحليل المستندات واستخراج المعلومات حسب الحاجة.** + - **تمكين دعم بطاقات الرسومات NVIDIA RTX من سلسلة 50 على Windows؛ يمكن للمستخدمين الرجوع إلى [دليل التثبيت](docs/version3.x/installation.en.md) لإصدارات إطار PaddlePaddle المقابلة.** + - **تدعم نماذج سلسلة PP-OCR الآن إرجاع إحداثيات الأحرف المفردة.** + - إضافة مصادر تنزيل النماذج من AIStudio وModelScope وغيرها، مما يتيح للمستخدمين تحديد مصدر تنزيل النماذج. + - إضافة دعم تحويل الرسوم البيانية إلى جداول عبر وحدة PP-Chart2Table. + - تحسين أوصاف الوثائق لتعزيز قابلية الاستخدام. +
    + + +[سجل التحديثات](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + + +## 🚀 بداية سريعة + +### الخطوة 1: جرّب عبر الإنترنت +يوفر الموقع الرسمي لـ PaddleOCR **مركز تجربة** تفاعلي و**واجهات برمجة التطبيقات (APIs)** — لا حاجة لأي إعداد، فقط انقر لتجربة الخدمة. + +👉 [زيارة الموقع الرسمي](https://www.paddleocr.com) + +### الخطوة 2: النشر المحلي +للاستخدام المحلي، يُرجى الرجوع إلى الوثائق التالية بناءً على احتياجاتك: + +- **سلسلة PP-OCR**: انظر [وثائق PP-OCR](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/OCR.html) +- **سلسلة PaddleOCR-VL**: انظر [وثائق PaddleOCR-VL](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PaddleOCR-VL.html) +- **PP-StructureV3**: انظر [وثائق PP-StructureV3](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PP-StructureV3.html) +- **المزيد من القدرات**: انظر [وثائق المزيد من القدرات](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/pipeline_overview.html) + + +## 🧩 المزيد من الميزات + +- تحويل النماذج إلى صيغة ONNX: [الحصول على نماذج ONNX](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/others/obtaining_onnx_models.html). +- تسريع الاستدلال باستخدام محركات مثل OpenVINO وONNX Runtime وTensorRT، أو إجراء الاستدلال باستخدام نماذج بصيغة ONNX: [الاستدلال عالي الأداء](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/local_inference/high_performance_inference.html). +- تسريع الاستدلال باستخدام وحدات GPU متعددة وعمليات متعددة: [الاستدلال المتوازي لخطوط الأنابيب](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/instructions/parallel_inference.html). +- دمج PaddleOCR في تطبيقات مكتوبة بلغات C++ وC# وJava وغيرها: [الخدمة](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/serving/serving.html). + +## 🔄 نظرة سريعة على نتائج التنفيذ + +### PP-OCRv5 + +
    +

    + عرض توضيحي لـ PP-OCRv5 +

    +
    + + + +### PP-StructureV3 + +
    +

    + عرض توضيحي لـ PP-StructureV3 +

    +
    + +### PaddleOCR-VL + +
    +

    + عرض توضيحي لـ PaddleOCR-VL +

    +
    + + +## ✨ تابعنا + +⭐ **قم بتمييز هذا المستودع بنجمة لمتابعة التحديثات والإصدارات الجديدة المثيرة، بما في ذلك إمكانيات التعرف الضوئي على الحروف وتحليل المستندات القوية!** ⭐ + +
    +

    + تمييز المشروع بنجمة +

    +
    + + +## 👩‍👩‍👧‍👦 المجتمع + +
    + +| حساب PaddlePaddle الرسمي على WeChat | انضم إلى مجموعة النقاش التقني | +| :---: | :---: | +| | | +
    + + +## 😃 مشاريع رائعة تستفيد من PaddleOCR +لم يكن PaddleOCR ليصل إلى ما هو عليه اليوم لولا مجتمعه المذهل! 💗 شكر جزيل لجميع شركائنا القدامى والمتعاونين الجدد وكل من بذل شغفه في PaddleOCR — سواء ذكرنا اسمه أم لا. دعمكم هو وقود حماسنا! + +
    + +| اسم المشروع | الوصف | +| ------------ | ----------- | +| [Dify](https://github.com/langgenius/dify) |منصة جاهزة للإنتاج لتطوير سير العمل الوكيلي.| +| [RAGFlow](https://github.com/infiniflow/ragflow) |محرك RAG قائم على الفهم العميق للمستندات.| +| [pathway](https://github.com/pathwaycom/pathway) |إطار عمل Python ETL لمعالجة التدفقات والتحليلات الآنية وخطوط أنابيب النماذج اللغوية الكبيرة وRAG.| +| [MinerU](https://github.com/opendatalab/MinerU) |أداة تحويل المستندات متعددة الأنواع إلى Markdown.| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |برنامج OCR مجاني، مفتوح المصدر، للمعالجة الدفعية دون اتصال بالإنترنت.| +| [cherry-studio](https://github.com/CherryHQ/cherry-studio) |تطبيق سطح مكتب يدعم مزودي نماذج لغوية كبيرة متعددين.| +| [haystack](https://github.com/deepset-ai/haystack) |إطار عمل لتنظيم الذكاء الاصطناعي لبناء تطبيقات نماذج لغوية كبيرة قابلة للتخصيص وجاهزة للإنتاج.| +| [OmniParser](https://github.com/microsoft/OmniParser) |OmniParser: أداة تحليل الشاشة لوكيل واجهة المستخدم الرسومية القائم على الرؤية البحتة.| +| [QAnything](https://github.com/netease-youdao/QAnything) |الأسئلة والأجوبة المبنية على أي شيء.| +| [تعرّف على المزيد من المشاريع](./awesome_projects.md) | [المزيد من المشاريع المبنية على PaddleOCR](./awesome_projects.md)| +
    + +## 👩‍👩‍👧‍👦 المساهمون + +
    + + + +
    + +## 🌟 النجوم + +
    +

    + تاريخ النجوم +

    +
    + + +## 📄 الرخصة +هذا المشروع مُصدر بموجب [رخصة Apache 2.0](LICENSE). + +## 🎓 الاستشهاد + +```bibtex +@misc{cui2025paddleocr30technicalreport, + title={PaddleOCR 3.0 Technical Report}, + author={Cheng Cui and Ting Sun and Manhui Lin and Tingquan Gao and Yubo Zhang and Jiaxuan Liu and Xueqing Wang and Zelun Zhang and Changda Zhou and Hongen Liu and Yue Zhang and Wenyu Lv and Kui Huang and Yichao Zhang and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2507.05595}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2507.05595}, +} + +@misc{cui2025paddleocrvlboostingmultilingualdocument, + title={PaddleOCR-VL: Boosting Multilingual Document Parsing via a 0.9B Ultra-Compact Vision-Language Model}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Handong Zheng and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2510.14528}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2510.14528}, +} + +@misc{cui2026paddleocrvl15multitask09bvlm, + title={PaddleOCR-VL-1.5: Towards a Multi-Task 0.9B VLM for Robust In-the-Wild Document Parsing}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2026}, + eprint={2601.21957}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2601.21957}, +} +``` diff --git a/readme/README_cn.md b/readme/README_cn.md new file mode 100644 index 0000000..b2f6e35 --- /dev/null +++ b/readme/README_cn.md @@ -0,0 +1,306 @@ +
    +

    + Star-history +

    + + + +

    全球领先的 OCR 工具包与文档 AI 引擎

    + +[English](../README.md) | 简体中文| [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![PyPI Downloads](https://static.pepy.tech/badge/paddleocr)](https://pepy.tech/projects/paddleocr) +[![Used by](https://img.shields.io/badge/Used%20by-6k%2B%20repositories-blue)](https://github.com/PaddlePaddle/PaddleOCR/network/dependents) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + +[![AI Studio](https://img.shields.io/badge/PaddleOCR-_Offiical_Website-1927BA?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAABlBMVEU2P+X///+1KuUwAAAHKklEQVR42u3dS5bjOAwEwALvf2fMavZum6IAImI7b2yYSqU+1Zb//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKCR/+fzly7rD92yVg69xh8zeLwOa5w+ZvFYHtc4ft3ykB++cOm79PAp6YO2z/Ngl4ZO5l+9+yT4QAvLqS748VF33Ylzdvzpl72f6z53YIGJ6SZdPeNHcIwOycaADdLgCSIgAIgCOAACAAykIAEAAEAAFAABCAT+WQuQVgeBqXhXQIQAAYegowLQBpbg3gZGFyAC6vgBQAMREA2/YfDPxyaDQNyTNz+3Zwn5J4ZG7PB2h0kHhi7plPCImmJwkPzO0RMa3OET0i5uGlzHFze0xcu0vE2Dq3J4U2vEPgSaHbFzPNDQAAAAAAAMBNovdw+cP/ny+uaf7w/+eYADy8kE+F4Offdjn6zZXhAXgiA78G4MNNsmnu1Xr7b3mbOL8T5Ja5bw/A35EC2LiWpzt1y9jRugBy30fLg3NvHPvnuZcC2NsCUXA/aRmA89V07Fwgt37uH8deCmBr6N44pP4UgaUATpdA7v/cMbIB8okliY65/SW5HhJ1ehPmM+8edwXgpbu4R88FayR32Y/P7oZZbOx13/Zr//ZHx27bAPnkFoyewYlbAhD3TvBobr95gaUAtr1EdNx1lgI4OcTTuR3z6+FZMEDRcu9ZCuDgGCdyGxMa4EgBRMvcjrkM7NgBZw5c0TwAUWUhZwRXA2xaya65Xa3jO2qYZ8bu2AD5w38tG5V8aZpoGN6Tz0bOfa9bceyWAciTO0jWyO1Tc5cLwJmF/JfPnXVyu3/slgHIg1n79O2O5fZv+1cHV7sC2HYqmUdHysNzX3sVkMcjUK5Gc+dMs28E5bGtm0V3gloBOP9vgZv+4sYn3RUaYFMCol5uN77g6lUApc8pWs69Zn7snS9Z9Q8G0S0AUTVUUTG3A54R1KSvo/diLAv5fKzynZeN6xogC75u93+AtBTA47OlAFSv6qY/vp3DAjD8iv2ZdFYJwKynMhTK1rInPfzaxW81LnvSgFP9KxrATaCLA3DxHpbFX31ZyNm5XRZyXG5bNkAWfP0rcrsUwOgC6NIAzgBcBiqAWwPgLrAGuGBP6jr2sifdfiJ6QQM4Bbw4AK4B3129ZSFn53ZZyA/GyFty27IBFMDFAXAG8PbyLQv5xULGPRl0K3h2AbwcgCZPhs+LD1zLnjS6AN4NwMU/DVFh7LyhASreTbvqrxdr/J4XT4Swz4FrTS+AGJ7bNbwAYkxuWzZAVljHrJfbjb9wviYXwFO/FJ8Vli4vaICsEMFyBbA3tmtsAUS0zG1c/bj4YwsZH2/+Whd0+1Nb+S7IE2sfPw4RL0XmsR8Nqvz7qFngmPHF34EqjP15AAofAkosZKPC/K6FVoeP02Ehi540NG6AK/4pYP3cLgVwXwHkDQ1QcSGb/uF4WwCmfX8u/+4vgLINcMUlQIfcLgXwXAF0+BGkpQDuuJx7/hwgpu//cWVuO3wxJOz/z8297vgYBwaIO3O7Kn+c194578ltywbIgu8fl+Z2lS+APvnLjnOv8hsgSqxjgwL4Ln9LAezaj98tgPzy7ZcC+GQzxrWxXQpgx370dm6/H7v6jaBoso5dY1swAFlwHWvfBf5pxVa93fCtdx64+1dsgCy4joWvAfPX9VoKYMs6Zse9/8Mlvv7LILlhAfKFFdsSutJXAdFkL3qlADJPrXFcXAC5KYaH586jO9mtAch9S3T0GQJ726ZWAE49kjP3rlDJuetdaL/1zeqZY9c7CRz7s0wCUPxienQBnAuAAtAAlxaAAAxfyBQABSAACkAAFIAAKAABUAACMEkKwL170oh7V8ueNLoAjgTAXWAN4BRwcABcA2oABTA4AApAAyiAwQFQABpAAQwOgALQADMWUgCuEmNyu15fSIY3gFPAiwPgFFADKIDBAVAAGkABCIACmBqAUAAaQAHMDUCMWkgBuMWw3K43F5LhDeAU8OIAuAmkARTA4AAoAA2gAARAAUwNgLvAGkABDA6Au8AaoKOJuV0vLSTDG8Ap4MUBcBNIAyiAwQFQABpAAQwOgALQAApAABTA1AC4C6wBOhqb23V+IRneAE4BLw6Aa0ANoAAGB0ABaAAFMDgACkADKAABUABTA+AusAboKATAQs4trjV+IYcfuJYCcA6gAATAQk69dFkKQANYyLkFcLIBFIDLQAVwawDsSRrAEWBwAJwCagAFMDgACkADKIDBAVAAGkABCIACmBoAzwXWAApgcADsSRrg0iNACoACEADXgAIwdCFTACykALgGFIAfl0kBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPBv/gN+IH8U6YveYgAAAABJRU5ErkJggg==&labelColor=white)](https://www.paddleocr.com) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/PaddlePaddle/PaddleOCR) +[![License](https://img.shields.io/badge/license-Apache_2.0-green)](../LICENSE) + +
    + + + + + + +**PaddleOCR 以业界领先的精准度,将 PDF 文档和图像转换为结构化、LLM 友好的数据格式(JSON/Markdown)。凭借 70,000+ Stars 的成绩,PaddleOCR 已获得 Dify、RAGFlow、Cherry Studio 等顶级项目的广泛信赖,是构建智能 RAG 和 Agentic 应用的核心基础组件。** + + +## 🚀 核心特性 + +### 📄 智能文档解析(面向大模型) +> *为大模型时代将杂乱的文档视觉信息转化为结构化数据。* + +* **SOTA 级文档视觉语言模型 (VLM)**: 业界领先的轻量级文档解析视觉语言模型 **PaddleOCR-VL-1.6 (0.9B)**。该模型以 96.3% 精度刷新 OmniDocBench v1.6,文本、公式、表格识别全面领先,并在古籍、生僻字、印章、图表等多场景能力显著增强,支持以 **Markdown** 和 **JSON** 格式输出结构化结果。 +* **版面结构分析**:由 **PP-StructureV3** 驱动,无缝将复杂的 PDF文档 和图像转换为 **Markdown** 或 **JSON** 格式。与 PaddleOCR-VL 系列模型不同,它提供更细粒度的坐标信息,包括表格单元格坐标、文本坐标等。 +* **生产级高效能**:以极小的模型体积实现商业级别的准确率。在公开基准测试中超越众多闭源解决方案,同时保持极高的资源利用率,完美适配边缘计算与云端部署。 + +### 🔍 通用文本识别(场景 OCR) +> *快速、精准的多语言文本检测与识别,被全球开发者广泛采用。* + +* **支持 100+ 种语言**:原生支持庞大丰富的全球语种库。**PP-OCRv6** 单模型统一支持 50 种语言(中、英、日及 46 种拉丁语系),无需切换模型即可应对多语言混合排版文档。 +* **复杂场景支持**:除了标准的文本识别,还支持在各种广泛的环境下进行**自然场景文本检测与识别**,涵盖身份证件、街景、书籍以及工业零部件等。 +* **性能提升**:PP-OCRv6 相比 PP-OCRv5 检测精度提升 **4.6%**、识别精度提升 **5.1%**,超越主流视觉语言大模型,CPU 推理加速 5.2×。 + +
    +

    + PaddleOCR Architecture +

    +
    + +### 🛠️ 以开发者为中心的生态系统 +* **无缝集成**:AI 智能体生态系统的首选——与 **Dify、RAGFlow、Pathway和Cherry Studio** 深度集成。 +* **大语言模型数据飞轮**:完整的数据流水线,用于构建高质量数据集,为微调大语言模型提供可持续的"数据引擎"。 +* **一键部署**:支持多种硬件后端(NVIDIA GPU、Intel CPU、昆仑芯 XPU 和多种 AI 加速器)。 + + +## 📣 最新动态 + +### 🔥 2026.06.11: PaddleOCR 3.7.0 发布 +- PP-OCRv6 核心亮点如下: + + - **精度全面提升**:medium 档相比 PP-OCRv5_server 检测精度提升 4.6%、识别精度提升 5.1%,以仅 34.5M 参数超越 Qwen3-VL-235B、GPT-5.5 等主流视觉语言大模型。 + - **50 种语言统一支持**:单一模型覆盖中文、英文、日文及 46 种拉丁语系语言,无需为不同语种切换模型。 + - **专业场景增强**:数码显示屏、点阵字符、轮胎印字、工业字符等传统 VLM 难以覆盖的场景识别能力大幅提升。 + - **推理速度更快**:medium 档 CPU OpenVINO 推理加速 5.2×,tiny 档 Apple M4 加速 6.1×,A100 上仅需 0.13s。 + - **三档模型覆盖全场景**:tiny(1.5M)/ small(7.7M)/ medium(34.5M)分别面向端侧/移动端/服务端部署。 + - **模型获取**:所有模型均可通过 [HuggingFace](https://huggingface.co/collections/PaddlePaddle/pp-ocrv6) 和 [ModelScope](https://www.modelscope.cn/collections/PaddlePaddle/PP-OCRv6) 获取。 + +
    +2026.05.28: PaddleOCR 3.6.0 发布 + +- PaddleOCR-VL-1.6核心亮点如下: + + - **全新SOTA精度**:OmniDocBench v1.6 突破96.3%,在OmniDocBench v1.5、Real5-OmniDocBench上同样刷新SOTA,文本、公式、表格识别全面领先开源与闭源方案。 + - **能力全面升级**:表格、古籍、生僻字识别大幅提升,印章、spotting、图表识别等多场景均有显著增强。 + - **无缝迁移**:模型结构与PaddleOCR-VL-1.5完全一致,零成本适配,即换即用。 + - **立即试用**:可在 [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6) 或 [PaddleOCR 官方网站](https://www.paddleocr.com) 使用。 + +
    + +
    +2026.04.21: PaddleOCR 3.5.0 发布 + +* **推理后端灵活切换**:支持在飞桨静态图、飞桨动态图和 Transformers 之间无缝切换。深度适配 Hugging Face 生态,20 个主要模型支持以 Transformers 作为推理后端。 +* **常见文档格式转 Markdown**:支持将 Word、Excel、Powerpoint 等常见文档格式转换为 Markdown。 +* **解析结果导出 DOCX**:`PaddleOCR-VL` 系列、`PP-StructureV3` 和 `PP-DocTranslation` 现已支持将解析结果导出为 DOCX,便于在 Microsoft Word 中查看和编辑。 +* **官方浏览器推理 SDK**:发布官方浏览器推理 SDK `PaddleOCR.js`,支持在浏览器中运行 `PP-OCRv5`。 + +
    + +
    +2026.01.29: PaddleOCR 3.4.0 发布 +* PaddleOCR-VL-1.5 (SOTA 0.9B VLM): + * **OmniDocBench 94.5%准确率**:超越顶级通用大模型和专业文档解析模型。 + * **现实5大场景文档解析的SOTA性能**:首次引入**PP-DocLayoutV3**算法进行异形框定位,可以解决5种真实场景: 倾斜、弯曲、扫描、光线变化和屏幕拍照。 + * **能力拓展**:增加**印章识别**、**文本行检测/识别**,并扩展至**111种语言**(包括中国的藏文和孟加拉语)。 + * **长文档跨页解析**:支持自动跨页表格合并和分层标题识别。 + * **立即试用**:可在[HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.5)或[PaddleOCR 官方网站](https://www.paddleocr.com)使用。 + +
    + +
    +2025.10.16: PaddleOCR 3.3.0 发布 + +- **发布PaddleOCR-VL**: + - **模型介绍**: + - **PaddleOCR-VL** 是一款先进、高效的文档解析模型,专为文档中的元素识别设计。其核心组件为 PaddleOCR-VL-0.9B,这是一种紧凑而强大的视觉语言模型(VLM),它由 NaViT 风格的动态分辨率视觉编码器与 ERNIE-4.5-0.3B 语言模型组成,能够实现精准的元素识别。**该模型支持 109 种语言,并在识别复杂元素(如文本、表格、公式和图表)方面表现出色,同时保持极低的资源消耗。通过在广泛使用的公开基准与内部基准上的全面评测,PaddleOCR-VL 在页级级文档解析与元素级识别均达到 SOTA 表现**。它显著优于现有的基于Pipeline方案和文档解析多模态方案以及先进的通用多模态大模型,并具备更快的推理速度。这些优势使其非常适合在真实场景中落地部署。模型已发布至[HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL),欢迎大家下载使用!更多介绍内容请点击[PaddleOCR-VL](https://www.paddleocr.ai/latest/version3.x/algorithm/PaddleOCR-VL/PaddleOCR-VL.html)。 + + - **特性**: + - **紧凑而强大的视觉语言模型架构**:我们提出了一种新的视觉语言模型,专为资源高效的推理而设计,在元素识别方面表现出色。通过将NaViT风格的动态高分辨率视觉编码器与轻量级的ERNIE-4.5-0.3B语言模型结合,我们显著增强了模型的识别能力和解码效率。这种集成在保持高准确率的同时降低了计算需求,使其非常适合高效且实用的文档处理应用。 + - **文档解析的SOTA性能**:PaddleOCR-VL在页面级文档解析和元素级识别中达到了最先进的性能。它显著优于现有的基于流水线的解决方案,并在文档解析中展现出与领先的视觉语言模型(VLMs)竞争的强劲实力。此外,它在识别复杂的文档元素(如文本、表格、公式和图表)方面表现出色,使其适用于包括手写文本和历史文献在内的各种具有挑战性的内容类型。这使得它具有高度的多功能性,适用于广泛的文档类型和场景。 + - **多语言支持**:PaddleOCR-VL支持109种语言,覆盖了主要的全球语言,包括但不限于中文、英文、日文、拉丁文和韩文,以及使用不同文字和结构的语言,如俄语(西里尔字母)、阿拉伯语、印地语(天城文)和泰语。这种广泛的语言覆盖大大增强了我们系统在多语言和全球化文档处理场景中的适用性。 + +- **发布PP-OCRv5小语种识别模型**: + - 优化拉丁文识别的准度和广度,新增西里尔文、阿拉伯文、天城文、泰卢固语、泰米尔语等语系,覆盖109种语言文字的识别。模型参数量仅为2M,部分模型精度较上一代提升40%以上。 + +
    + + +
    +2025.08.21: PaddleOCR 3.2.0 发布 + +- **重要模型新增:** + - 新增 PP-OCRv5 英文、泰文、希腊文识别模型的训练、推理、部署。**其中 PP-OCRv5 英文模型较 PP-OCRv5 主模型在英文场景提升 11%,泰文识别模型精度 82.68%,希腊文识别模型精度 89.28%。** + +- **部署能力升级:** + - **全面支持飞桨框架 3.1.0 和 3.1.1 版本。** + - **全面升级 PP-OCRv5 C++ 本地部署方案,支持 Linux、Windows,功能及精度效果与 Python 方案保持一致。** + - **高性能推理支持 CUDA 12,可使用 Paddle Inference、ONNX Runtime 后端推理。** + - **高稳定性服务化部署方案全面开源,支持用户根据需求对 Docker 镜像和 SDK 进行定制化修改。** + - 高稳定性服务化部署方案支持通过手动构造HTTP请求的方式调用,该方式允许客户端代码使用任意编程语言编写。 + +- **Benchmark支持**: + - **全部产线支持产线细粒度 benchmark,能够测量产线端到端推理时间以及逐层、逐模块的耗时数据,可用于辅助产线性能分析。可以参考[文档](../docs/version3.x/pipeline_usage/instructions/benchmark.md)来进行性能测试。** + - **文档中补充各产线常用配置在主流硬件上的关键指标,包括推理耗时和内存占用等,为用户部署提供参考。** + +- **Bug修复:** + - 修复模型训练时训练日志保存失败的问题。 + - 对公式模型的数据增强部分进行了版本兼容性升级,以适应新版本的 albumentations 依赖,并修复了在多进程使用 tokenizers 依赖包时出现的死锁警告。 + - 修复 PP-StructureV3 配置文件中的 `use_chart_parsing` 等开关行为与其他产线不统一的问题。 + +- **其他升级:** + - **分离必要依赖与可选依赖。使用基础文字识别功能时,仅需安装少量核心依赖;若需文档解析、信息抽取等功能,用户可按需选择安装额外依赖。** + - **支持 Windows 用户使用英伟达 50 系显卡,可根据 [安装文档](../docs/version3.x/installation.md) 安装对应版本的 paddle 框架。** + - **PP-OCR 系列模型支持返回单文字坐标。** + - 模型新增 AIStudio、ModelScope 等下载源。可指定相关下载源下载对应的模型。 + - 支持图表转表 PP-Chart2Table 单功能模块推理能力。 + - 优化部分使用文档中的描述,提升易用性。 +
    + + +[历史日志](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + + +## 🚀 快速开始 + +### 步骤 1: 在线体验 +PaddleOCR官方网站提供交互式**体验中心**和**APIs**——无需设置,一键体验。 + +👉 [访问官方网站](https://www.paddleocr.com) + +### 步骤 2: 本地部署 + +对于本地使用,请根据您的需求参考以下文档: + +- **PP-OCR系列**:查看[PP-OCR文档](https://www.paddleocr.ai/latest/version3.x/pipeline_usage/OCR.html) +- **PaddleOCR-VL系列**:查看[PaddleOCR-VL文档](https://www.paddleocr.ai/latest/version3.x/pipeline_usage/PaddleOCR-VL.html) +- **PP-StructureV3**:查看[PP-StructureV3文档](https://www.paddleocr.ai/latest/version3.x/pipeline_usage/PP-StructureV3.html) +- **更多能力**:查看[更多能力文档](https://www.paddleocr.ai/latest/version3.x/pipeline_usage/pipeline_overview.html) + + +## 🧩 更多功能 + +- 将模型转换为ONNX格式: [获取ONNX模型](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/inference_deployment/others/obtaining_onnx_models.html)。 +- 使用OpenVINO、ONNX Runtime、TensorRT等引擎加速推理,或使用ONNX格式模型进行推理: [高性能推理](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/inference_deployment/local_inference/high_performance_inference.html)。 +- 使用多GPU和多进程加速推理: [流水线并行推理](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/instructions/parallel_inference.html)。 +- 将PaddleOCR集成到C++、C#、Java等语言编写的应用程序中: [服务化部署](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/inference_deployment/serving/serving.html)。 + +## 🔄 执行结果快速预览 + +### PP-OCRv5 + +
    +

    + PP-OCRv5 Demo +

    +
    + + + +### PP-StructureV3 + +
    +

    + PP-StructureV3 Demo +

    +
    + +### PaddleOCR-VL + +
    +

    + PP-StructureV3 Demo +

    +
    + + +## ✨ 保持关注 + +⭐ **收藏本仓库,持续关注最新动态与版本发布,包括强大的 OCR 及文档解析等新功能特性。** ⭐ + +
    +

    + Star-Project +

    +
    + + +## 👩‍👩‍👧‍👦 社区 + +
    + +| PaddlePaddle 微信公众号 | 加入技术讨论群 | +| :---: | :---: | +| | | +
    + + +## 😃 使用 PaddleOCR 的优秀项目 + +
    + +PaddleOCR 的发展离不开社区贡献!💗衷心感谢所有开发者、合作伙伴与贡献者! +| 项目名称 | 简介 | +| ------------ | ----------- | +| [RAGFlow](https://github.com/infiniflow/ragflow) |基于RAG的AI工作流引擎| +| [pathway](https://github.com/pathwaycom/pathway) |用于流处理、实时分析、LLM流水线和RAG的Python ETL框架| +| [MinerU](https://github.com/opendatalab/MinerU) |多类型文档转换Markdown工具| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |开源批量离线OCR软件| +| [cherry-studio](https://github.com/CherryHQ/cherry-studio) |一个支持多个LLM提供商的桌面客户端| +| [OmniParser](https://github.com/microsoft/OmniParser) |基于纯视觉的GUI智能体屏幕解析工具| +| [QAnything](https://github.com/netease-youdao/QAnything) |基于任意内容的问答系统| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) |高效复杂PDF文档提取工具包| +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) |屏幕实时翻译工具| +| [更多项目](../awesome_projects.md) | [更多基于PaddleOCR的项目](../awesome_projects.md) | +
    + + +## 👩‍👩‍👧‍👦 贡献者 + +
    + + + +
    + +## 🌟 Star历史 + +
    +

    + Star-history +

    +
    + + +## 📄 许可证 +本项目采用[Apache 2.0许可证](LICENSE)发布。 + +## 🎓 引用 + +```bibtex +@misc{cui2025paddleocr30technicalreport, + title={PaddleOCR 3.0 Technical Report}, + author={Cheng Cui and Ting Sun and Manhui Lin and Tingquan Gao and Yubo Zhang and Jiaxuan Liu and Xueqing Wang and Zelun Zhang and Changda Zhou and Hongen Liu and Yue Zhang and Wenyu Lv and Kui Huang and Yichao Zhang and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2507.05595}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2507.05595}, +} + +@misc{cui2025paddleocrvlboostingmultilingualdocument, + title={PaddleOCR-VL: Boosting Multilingual Document Parsing via a 0.9B Ultra-Compact Vision-Language Model}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Handong Zheng and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2510.14528}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2510.14528}, +} + +@misc{cui2026paddleocrvl15multitask09bvlm, + title={PaddleOCR-VL-1.5: Towards a Multi-Task 0.9B VLM for Robust In-the-Wild Document Parsing}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2026}, + eprint={2601.21957}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2601.21957}, +} +``` diff --git a/readme/README_es.md b/readme/README_es.md new file mode 100644 index 0000000..4f21fbc --- /dev/null +++ b/readme/README_es.md @@ -0,0 +1,302 @@ +
    +

    + Star-history +

    + + + +

    Kit de OCR líder mundial y motor de IA para documentos

    + +[English](../README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) | Español | [العربية](./README_ar.md) + + + +[![PyPI Downloads](https://static.pepy.tech/badge/paddleocr)](https://pepy.tech/projects/paddleocr) +[![Used by](https://img.shields.io/badge/Used%20by-6k%2B%20repositories-blue)](https://github.com/PaddlePaddle/PaddleOCR/network/dependents) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + +[![AI Studio](https://img.shields.io/badge/PaddleOCR-_Offiical_Website-1927BA?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAABlBMVEU2P+X///+1KuUwAAAHKklEQVR42u3dS5bjOAwEwALvf2fMavZum6IAImI7b2yYSqU+1Zb//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKCR/+fzly7rD92yVg69xh8zeLwOa5w+ZvFYHtc4ft3ykB++cOm79PAp6YO2z/Ngl4ZO5l+9+yT4QAvLqS748VF33Ylzdvzpl72f6z53YIGJ6SZdPeNHcIwOycaADdLgCSIgAIgCOAACAAykIAEAAEAAFAABCAT+WQuQVgeBqXhXQIQAAYegowLQBpbg3gZGFyAC6vgBQAMREA2/YfDPxyaDQNyTNz+3Zwn5J4ZG7PB2h0kHhi7plPCImmJwkPzO0RMa3OET0i5uGlzHFze0xcu0vE2Dq3J4U2vEPgSaHbFzPNDQAAAAAAAMBNovdw+cP/ny+uaf7w/+eYADy8kE+F4Offdjn6zZXhAXgiA78G4MNNsmnu1Xr7b3mbOL8T5Ja5bw/A35EC2LiWpzt1y9jRugBy30fLg3NvHPvnuZcC2NsCUXA/aRmA89V07Fwgt37uH8deCmBr6N44pP4UgaUATpdA7v/cMbIB8okliY65/SW5HhJ1ehPmM+8edwXgpbu4R88FayR32Y/P7oZZbOx13/Zr//ZHx27bAPnkFoyewYlbAhD3TvBobr95gaUAtr1EdNx1lgI4OcTTuR3z6+FZMEDRcu9ZCuDgGCdyGxMa4EgBRMvcjrkM7NgBZw5c0TwAUWUhZwRXA2xaya65Xa3jO2qYZ8bu2AD5w38tG5V8aZpoGN6Tz0bOfa9bceyWAciTO0jWyO1Tc5cLwJmF/JfPnXVyu3/slgHIg1n79O2O5fZv+1cHV7sC2HYqmUdHysNzX3sVkMcjUK5Gc+dMs28E5bGtm0V3gloBOP9vgZv+4sYn3RUaYFMCol5uN77g6lUApc8pWs69Zn7snS9Z9Q8G0S0AUTVUUTG3A54R1KSvo/diLAv5fKzynZeN6xogC75u93+AtBTA47OlAFSv6qY/vp3DAjD8iv2ZdFYJwKynMhTK1rInPfzaxW81LnvSgFP9KxrATaCLA3DxHpbFX31ZyNm5XRZyXG5bNkAWfP0rcrsUwOgC6NIAzgBcBiqAWwPgLrAGuGBP6jr2sifdfiJ6QQM4Bbw4AK4B3129ZSFn53ZZyA/GyFty27IBFMDFAXAG8PbyLQv5xULGPRl0K3h2AbwcgCZPhs+LD1zLnjS6AN4NwMU/DVFh7LyhASreTbvqrxdr/J4XT4Swz4FrTS+AGJ7bNbwAYkxuWzZAVljHrJfbjb9wviYXwFO/FJ8Vli4vaICsEMFyBbA3tmtsAUS0zG1c/bj4YwsZH2/+Whd0+1Nb+S7IE2sfPw4RL0XmsR8Nqvz7qFngmPHF34EqjP15AAofAkosZKPC/K6FVoeP02Ehi540NG6AK/4pYP3cLgVwXwHkDQ1QcSGb/uF4WwCmfX8u/+4vgLINcMUlQIfcLgXwXAF0+BGkpQDuuJx7/hwgpu//cWVuO3wxJOz/z8297vgYBwaIO3O7Kn+c194578ltywbIgu8fl+Z2lS+APvnLjnOv8hsgSqxjgwL4Ln9LAezaj98tgPzy7ZcC+GQzxrWxXQpgx370dm6/H7v6jaBoso5dY1swAFlwHWvfBf5pxVa93fCtdx64+1dsgCy4joWvAfPX9VoKYMs6Zse9/8Mlvv7LILlhAfKFFdsSutJXAdFkL3qlADJPrXFcXAC5KYaH586jO9mtAch9S3T0GQJ726ZWAE49kjP3rlDJuetdaL/1zeqZY9c7CRz7s0wCUPxienQBnAuAAtAAlxaAAAxfyBQABSAACkAAFIAAKAABUAACMEkKwL170oh7V8ueNLoAjgTAXWAN4BRwcABcA2oABTA4AApAAyiAwQFQABpAAQwOgALQADMWUgCuEmNyu15fSIY3gFPAiwPgFFADKIDBAVAAGkABCIACmBqAUAAaQAHMDUCMWkgBuMWw3K43F5LhDeAU8OIAuAmkARTA4AAoAA2gAARAAUwNgLvAGkABDA6Au8AaoKOJuV0vLSTDG8Ap4MUBcBNIAyiAwQFQABpAAQwOgALQAApAABTA1AC4C6wBOhqb23V+IRneAE4BLw6Aa0ANoAAGB0ABaAAFMDgACkADKAABUABTA+AusAboKATAQs4trjV+IYcfuJYCcA6gAATAQk69dFkKQANYyLkFcLIBFIDLQAVwawDsSRrAEWBwAJwCagAFMDgACkADKIDBAVAAGkABCIACmBoAzwXWAApgcADsSRrg0iNACoACEADXgAIwdCFTACykALgGFIAfl0kBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPBv/gN+IH8U6YveYgAAAABJRU5ErkJggg==&labelColor=white)](https://www.paddleocr.com) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/PaddlePaddle/PaddleOCR) +[![License](https://img.shields.io/badge/license-Apache_2.0-green)](../LICENSE) + +
    + + + + + + +**PaddleOCR convierte documentos e imágenes en datos estructurados y listos para LLM (JSON/Markdown) con una precisión líder en la industria. Con más de 70 000 estrellas y la confianza de proyectos de primer nivel como Dify, RAGFlow y Cherry Studio, PaddleOCR es la base para construir aplicaciones inteligentes de RAG y Agentic.** + + +## 🚀 Características principales + +### 📄 Análisis inteligente de documentos (listo para LLM) +> *Transformando contenido visual complejo en datos estructurados para la era de los LLM.* + +* **SOTA Document VLM**: Featuring **PaddleOCR-VL-1.6 (0.9B)**, the industry's leading lightweight vision-language model for document parsing. It achieves 96.3% accuracy on OmniDocBench v1.6, leads in text, formula, and table recognition, and shows significantly enhanced capabilities in ancient documents, rare characters, seals, and charts, with structured outputs in **Markdown** and **JSON** formats. +* **Conversión con reconocimiento de estructura**: Impulsado por **PP-StructureV3**, convierte sin problemas PDFs e imágenes complejas en **Markdown** o **JSON**. A diferencia de los modelos de la serie PaddleOCR-VL, proporciona información de coordenadas más detallada, incluyendo coordenadas de celdas de tablas, coordenadas de texto y más. +* **Eficiencia lista para producción**: Logra precisión de nivel comercial con una huella ultrapequeña. Supera a numerosas soluciones de código cerrado en benchmarks públicos, manteniéndose eficiente en recursos para despliegue en el borde o en la nube. + +### 🔍 Reconocimiento universal de texto (Scene OCR) +> *El estándar de oro mundial para la detección de texto multilingüe de alta velocidad.* + +* **Compatibilidad con más de 100 idiomas**: Reconocimiento nativo de una amplia biblioteca global. **PP-OCRv6** soporta 50 idiomas con un único modelo unificado (chino, inglés, japonés y 46 idiomas latinos) — sin necesidad de cambiar modelos. +* **Dominio de elementos complejos**: Más allá del reconocimiento de texto estándar, admitimos la **detección de texto en escenas naturales** en una amplia gama de entornos, incluyendo documentos de identidad, vistas de calles, libros y componentes industriales. +* **Salto en rendimiento**: PP-OCRv6 logra **+4.6% en detección** y **+5.1% en reconocimiento** sobre PP-OCRv5, superando a los principales modelos de lenguaje visual. Aceleración 5.2× en inferencia CPU end-to-end. + +
    +

    + PaddleOCR Architecture +

    +
    + +### 🛠️ Ecosistema centrado en el desarrollador +* **Integración perfecta**: La opción preferida para el ecosistema de agentes de IA, con integración profunda en **Dify, RAGFlow, Pathway y Cherry Studio**. +* **Motor de datos para LLM**: Un pipeline completo para construir conjuntos de datos de alta calidad, proporcionando un "Motor de Datos" sostenible para el ajuste fino de modelos de lenguaje grandes. +* **Despliegue en un clic**: Compatible con diversos backends de hardware (GPU NVIDIA, CPU Intel, XPU Kunlunxin y diversos aceleradores de IA). + + +## 📣 Actualizaciones recientes + +### 🔥 2026.06.11: Lanzamiento de PaddleOCR 3.7.0 +- Aspectos destacados de PP-OCRv6: + + - **Mejora de precisión**: El nivel medium logra +4.6% en detección y +5.1% en reconocimiento sobre PP-OCRv5_server, superando a los principales VLMs (Qwen3-VL-235B, GPT-5.5) con solo 34.5M parámetros. + - **50 idiomas unificados**: Un solo modelo cubre chino, inglés, japonés y 46 idiomas latinos — sin necesidad de cambiar modelos. + - **Escenarios especializados**: Mejoras significativas en pantallas digitales, caracteres de matriz de puntos, impresiones de neumáticos y texto industrial. + - **Inferencia más rápida**: Aceleración 5.2× en CPU (OpenVINO), 6.1× en Apple M4 (tiny), 0.13s en A100 GPU. + - **Tres niveles para todos los escenarios**: tiny (1.5M) / small (7.7M) / medium (34.5M) para despliegue en edge, móvil y servidor. + - **Disponibilidad de modelos**: Todos los modelos están disponibles en [HuggingFace](https://huggingface.co/collections/PaddlePaddle/pp-ocrv6) y [ModelScope](https://www.modelscope.cn/collections/PaddlePaddle/PP-OCRv6). + +
    +2026.05.28: Lanzamiento de PaddleOCR 3.6.0 + +- Aspectos destacados de PaddleOCR-VL-1.6: + + - **Nueva precisión SOTA**: 96.3% en OmniDocBench v1.6, nuevo SOTA en OmniDocBench v1.5 y Real5-OmniDocBench. + - **Mejora integral**: mejoras significativas en tablas, documentos antiguos y caracteres raros. + - **Migración sin fisuras**: arquitectura totalmente compatible con PaddleOCR-VL-1.5. + - **Pruébalo**: [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6) o [sitio oficial](https://www.paddleocr.com). + +
    +
    +2026.04.21: Lanzamiento de PaddleOCR 3.5.0 + +* **Backends de inferencia más flexibles**: cambia sin problemas entre grafo estático de Paddle, grafo dinámico de Paddle y Transformers. PaddleOCR está ahora profundamente integrado con el ecosistema de Hugging Face, y 20 modelos principales admiten Transformers como backend de inferencia. +* **Documentos de Office a Markdown**: convierte formatos de documentos comunes como Word, Excel y PowerPoint a Markdown. +* **Exportación de resultados a DOCX**: las series `PaddleOCR-VL`, `PP-StructureV3` y `PP-DocTranslation` ahora admiten exportar los resultados de análisis a DOCX para verlos y editarlos cómodamente en Microsoft Word. +* **SDK oficial de inferencia en navegador**: se lanzó `PaddleOCR.js`, el SDK oficial de inferencia en navegador, que permite ejecutar `PP-OCRv5` directamente en el navegador. + +
    + +
    +2026.01.29: Lanzamiento de PaddleOCR 3.4.0 +* PaddleOCR-VL-1.5 (SOTA 0,9B VLM): ¡Nuestro último modelo insignia para el análisis de documentos ya está disponible! + * **94,5% de precisión en OmniDocBench**: Superando a los mejores modelos generales de gran escala y a los analizadores de documentos especializados. + * **Robustez en el mundo real**: El primero en introducir el algoritmo **PP-DocLayoutV3** para el posicionamiento de formas irregulares, dominando 5 escenarios difíciles: *inclinación, deformación, escaneo, iluminación y fotografía de pantalla*. + * **Expansión de capacidades**: Ahora admite **reconocimiento de sellos**, **detección de texto** y se expande a **111 idiomas** (incluyendo el tibetano de China y el bengalí). + * **Dominio de documentos largos**: Admite la fusión automática de tablas entre páginas e identificación jerárquica de encabezados. + * **Pruébalo ahora**: Disponible en [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.5) o en nuestro [Sitio web oficial](https://www.paddleocr.com). + +
    + +
    +2025.10.16: Lanzamiento de PaddleOCR 3.3.0 + +- Lanzamiento de PaddleOCR-VL: + - **Introducción al modelo**: + - **PaddleOCR-VL** es un modelo SOTA eficiente en recursos diseñado específicamente para el análisis de documentos. Su componente principal es PaddleOCR-VL-0.9B, un modelo de visión y lenguaje (VLM) compacto pero potente que integra un codificador visual de resolución dinámica al estilo NaViT con el modelo de lenguaje ERNIE-4.5-0.3B para permitir un reconocimiento preciso de elementos. **Este innovador modelo admite eficientemente 109 idiomas y sobresale en el reconocimiento de elementos complejos (p. ej., texto, tablas, fórmulas y gráficos), mientras mantiene un consumo mínimo de recursos**. A través de evaluaciones exhaustivas en benchmarks públicos ampliamente utilizados y benchmarks internos, PaddleOCR-VL logra un rendimiento SOTA tanto en el análisis de documentos a nivel de página como en el reconocimiento a nivel de elemento. Supera significativamente a las soluciones existentes, exhibe una fuerte competitividad frente a los VLM de primer nivel y ofrece velocidades de inferencia rápidas. Estas fortalezas lo hacen altamente adecuado para el despliegue práctico en escenarios del mundo real. El modelo ha sido publicado en [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL). ¡Todos son bienvenidos a descargarlo y usarlo! Más información de introducción se puede encontrar en [PaddleOCR-VL](https://www.paddleocr.ai/latest/version3.x/algorithm/PaddleOCR-VL/PaddleOCR-VL.html). + + - **Características principales**: + - **Arquitectura VLM compacta pero potente**: Presentamos un novedoso modelo de visión y lenguaje diseñado específicamente para una inferencia eficiente en recursos, logrando un rendimiento sobresaliente en el reconocimiento de elementos. Al integrar un codificador visual dinámico de alta resolución al estilo NaViT con el modelo de lenguaje ligero ERNIE-4.5-0.3B, mejoramos significativamente las capacidades de reconocimiento y la eficiencia de decodificación del modelo. Esta integración mantiene una alta precisión mientras reduce las demandas computacionales, lo que lo hace adecuado para aplicaciones de procesamiento de documentos eficientes y prácticas. + - **Rendimiento SOTA en análisis de documentos**: PaddleOCR-VL logra un rendimiento de vanguardia tanto en el análisis de documentos a nivel de página como en el reconocimiento a nivel de elemento. Supera significativamente a las soluciones basadas en pipeline existentes y exhibe una fuerte competitividad frente a los modelos de visión y lenguaje (VLM) líderes en el análisis de documentos. Además, sobresale en el reconocimiento de elementos de documentos complejos, como texto, tablas, fórmulas y gráficos, lo que lo hace adecuado para una amplia gama de tipos de contenido desafiantes, incluyendo texto manuscrito y documentos históricos. Esto lo hace altamente versátil y adecuado para una amplia gama de tipos de documentos y escenarios. + - **Soporte multilingüe**: PaddleOCR-VL admite 109 idiomas, cubriendo los principales idiomas globales, incluyendo, entre otros, chino, inglés, japonés, latín y coreano, así como idiomas con diferentes escrituras y estructuras, como el ruso (escritura cirílica), el árabe, el hindi (escritura devanagari) y el tailandés. Esta amplia cobertura de idiomas mejora sustancialmente la aplicabilidad de nuestro sistema a escenarios de procesamiento de documentos multilingües y globalizados. + +- Lanzamiento del modelo de reconocimiento multilingüe PP-OCRv5: + - Se mejoró la precisión y cobertura del reconocimiento de escritura latina; se añadió compatibilidad con sistemas de escritura cirílico, árabe, devanagari, telugu, tamil y otros, cubriendo el reconocimiento de 109 idiomas. El modelo tiene solo 2M de parámetros, y la precisión de algunos modelos ha aumentado más de un 40% en comparación con la generación anterior. + +
    + + +
    +2025.08.21: Lanzamiento de PaddleOCR 3.2.0 + +- **Adiciones significativas de modelos:** + - Se introdujeron entrenamiento, inferencia y despliegue para modelos de reconocimiento PP-OCRv5 en inglés, tailandés y griego. **El modelo PP-OCRv5 en inglés ofrece una mejora del 11% en escenarios en inglés en comparación con el modelo principal PP-OCRv5, con los modelos de reconocimiento en tailandés y griego alcanzando precisiones del 82,68% y 89,28%, respectivamente.** + +- **Mejoras en las capacidades de despliegue:** + - **Compatibilidad total con las versiones 3.1.0 y 3.1.1 del framework PaddlePaddle.** + - **Actualización integral de la solución de despliegue local en C++ de PP-OCRv5, que ahora admite tanto Linux como Windows, con paridad de características y precisión idéntica a la implementación en Python.** + - **La inferencia de alto rendimiento ahora admite CUDA 12, y la inferencia puede realizarse utilizando los backends Paddle Inference u ONNX Runtime.** + - **La solución de despliegue orientado a servicios de alta estabilidad ahora es completamente de código abierto, lo que permite a los usuarios personalizar imágenes Docker y SDKs según sea necesario.** + - La solución de despliegue orientado a servicios de alta estabilidad también admite la invocación mediante solicitudes HTTP construidas manualmente, lo que permite el desarrollo de código del lado del cliente en cualquier lenguaje de programación. + +- **Soporte de benchmarks:** + - **Todas las líneas de producción ahora admiten benchmarking detallado, permitiendo medir el tiempo de inferencia de extremo a extremo, así como datos de latencia por capa y por módulo para ayudar en el análisis de rendimiento. [Aquí](docs/version3.x/pipeline_usage/instructions/benchmark.en.md) se explica cómo configurar y usar la función de benchmark.** + - **La documentación se ha actualizado para incluir métricas clave para configuraciones de uso común en hardware convencional, como la latencia de inferencia y el uso de memoria, proporcionando referencias de despliegue para los usuarios.** + +- **Corrección de errores:** + - Se resolvió el problema del guardado fallido de registros durante el entrenamiento del modelo. + - Se actualizó el componente de aumento de datos para modelos de fórmulas para compatibilidad con versiones más recientes de la dependencia albumentations, y se corrigieron advertencias de bloqueo al usar el paquete tokenizers en escenarios multiproceso. + - Se corrigieron inconsistencias en los comportamientos de los interruptores (p. ej., `use_chart_parsing`) en los archivos de configuración de PP-StructureV3 en comparación con otros pipelines. + +- **Otras mejoras:** + - **Se separaron las dependencias principales y opcionales. Solo se requieren dependencias principales mínimas para el reconocimiento de texto básico; las dependencias adicionales para el análisis de documentos y la extracción de información se pueden instalar según sea necesario.** + - **Se habilitó la compatibilidad con tarjetas gráficas NVIDIA RTX de la serie 50 en Windows; los usuarios pueden consultar la [guía de instalación](docs/version3.x/installation.en.md) para conocer las versiones correspondientes del framework PaddlePaddle.** + - **Los modelos de la serie PP-OCR ahora admiten la devolución de coordenadas de caracteres individuales.** + - Se añadieron fuentes de descarga de modelos de AIStudio, ModelScope y otras, permitiendo a los usuarios especificar la fuente para las descargas de modelos. + - Se añadió compatibilidad con la conversión de gráficos a tablas a través del módulo PP-Chart2Table. + - Se optimizaron las descripciones de la documentación para mejorar la usabilidad. +
    + + +[Historial de cambios](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + + +## 🚀 Inicio rápido + +### Paso 1: Pruébalo en línea +El sitio web oficial de PaddleOCR ofrece un **Centro de experiencia** interactivo y **APIs** — sin necesidad de configuración, solo un clic para experimentar. + +👉 [Visitar el sitio web oficial](https://www.paddleocr.com) + +### Paso 2: Despliegue local +Para uso local, consulte la siguiente documentación según sus necesidades: + +- **Serie PP-OCR**: Consulte la [Documentación de PP-OCR](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/OCR.html) +- **Serie PaddleOCR-VL**: Consulte la [Documentación de PaddleOCR-VL](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PaddleOCR-VL.html) +- **PP-StructureV3**: Consulte la [Documentación de PP-StructureV3](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PP-StructureV3.html) +- **Más capacidades**: Consulte la [Documentación de más capacidades](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/pipeline_overview.html) + + +## 🧩 Más características + +- Convertir modelos al formato ONNX: [Obtención de modelos ONNX](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/others/obtaining_onnx_models.html). +- Acelerar la inferencia usando motores como OpenVINO, ONNX Runtime, TensorRT, o realizar inferencia usando modelos en formato ONNX: [Inferencia de alto rendimiento](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/local_inference/high_performance_inference.html). +- Acelerar la inferencia usando múltiples GPU y múltiples procesos: [Inferencia paralela para pipelines](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/instructions/parallel_inference.html). +- Integrar PaddleOCR en aplicaciones escritas en C++, C#, Java, etc.: [Serving](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/serving/serving.html). + +## 🔄 Resumen rápido de los resultados de ejecución + +### PP-OCRv5 + +
    +

    + PP-OCRv5 Demo +

    +
    + + + +### PP-StructureV3 + +
    +

    + PP-StructureV3 Demo +

    +
    + +### PaddleOCR-VL + +
    +

    + PP-StructureV3 Demo +

    +
    + + +## ✨ Mantente al día + +⭐ **¡Dale una estrella a este repositorio para estar al tanto de emocionantes actualizaciones y nuevos lanzamientos, incluyendo potentes capacidades de OCR y análisis de documentos!** ⭐ + +
    +

    + Star-Project +

    +
    + + +## 👩‍👩‍👧‍👦 Comunidad + +
    + +| Cuenta oficial de PaddlePaddle en WeChat | Únete al grupo de discusión técnica | +| :---: | :---: | +| | | +
    + + +## 😃 Proyectos destacados que utilizan PaddleOCR +¡PaddleOCR no estaría donde está hoy sin su increíble comunidad! 💗 Un enorme agradecimiento a todos nuestros socios de larga data, nuevos colaboradores y a todos los que han volcado su pasión en PaddleOCR — los hayamos mencionado o no. ¡Su apoyo alimenta nuestro fuego! + +
    + +| Nombre del proyecto | Descripción | +| ------------ | ----------- | +| [Dify](https://github.com/langgenius/dify) |Plataforma lista para producción para el desarrollo de flujos de trabajo agénticos.| +| [RAGFlow](https://github.com/infiniflow/ragflow) |Motor RAG basado en la comprensión profunda de documentos.| +| [pathway](https://github.com/pathwaycom/pathway) |Framework ETL de Python para procesamiento de flujos, análisis en tiempo real, pipelines de LLM y RAG.| +| [MinerU](https://github.com/opendatalab/MinerU) |Herramienta de conversión de documentos de múltiples tipos a Markdown.| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |Software de OCR offline por lotes, gratuito y de código abierto.| +| [cherry-studio](https://github.com/CherryHQ/cherry-studio) |Cliente de escritorio compatible con múltiples proveedores de LLM.| +| [haystack](https://github.com/deepset-ai/haystack) |Framework de orquestación de IA para construir aplicaciones LLM personalizables y listas para producción.| +| [OmniParser](https://github.com/microsoft/OmniParser) |OmniParser: herramienta de análisis de pantalla para agentes GUI basados únicamente en visión.| +| [QAnything](https://github.com/netease-youdao/QAnything) |Preguntas y respuestas basadas en cualquier cosa.| +| [Ver más proyectos](./awesome_projects.md) | [Más proyectos basados en PaddleOCR](./awesome_projects.md)| +
    + +## 👩‍👩‍👧‍👦 Colaboradores + +
    + + + +
    + +## 🌟 Estrellas + +
    +

    + Star-history +

    +
    + + +## 📄 Licencia +Este proyecto se publica bajo la [licencia Apache 2.0](LICENSE). + +## 🎓 Cita + +```bibtex +@misc{cui2025paddleocr30technicalreport, + title={PaddleOCR 3.0 Technical Report}, + author={Cheng Cui and Ting Sun and Manhui Lin and Tingquan Gao and Yubo Zhang and Jiaxuan Liu and Xueqing Wang and Zelun Zhang and Changda Zhou and Hongen Liu and Yue Zhang and Wenyu Lv and Kui Huang and Yichao Zhang and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2507.05595}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2507.05595}, +} + +@misc{cui2025paddleocrvlboostingmultilingualdocument, + title={PaddleOCR-VL: Boosting Multilingual Document Parsing via a 0.9B Ultra-Compact Vision-Language Model}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Handong Zheng and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2510.14528}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2510.14528}, +} + +@misc{cui2026paddleocrvl15multitask09bvlm, + title={PaddleOCR-VL-1.5: Towards a Multi-Task 0.9B VLM for Robust In-the-Wild Document Parsing}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2026}, + eprint={2601.21957}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2601.21957}, +} diff --git a/readme/README_fr.md b/readme/README_fr.md new file mode 100644 index 0000000..29815f3 --- /dev/null +++ b/readme/README_fr.md @@ -0,0 +1,302 @@ +
    +

    + Star-history +

    + + + +

    Boîte à outils OCR de pointe mondiale & Moteur d'IA documentaire

    + +[English](../README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | Français | [Русский](./README_ru.md) | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![PyPI Downloads](https://static.pepy.tech/badge/paddleocr)](https://pepy.tech/projects/paddleocr) +[![Used by](https://img.shields.io/badge/Used%20by-6k%2B%20repositories-blue)](https://github.com/PaddlePaddle/PaddleOCR/network/dependents) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + +[![AI Studio](https://img.shields.io/badge/PaddleOCR-_Site_Officiel-1927BA?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAABlBMVEU2P+X///+1KuUwAAAHKklEQVR42u3dS5bjOAwEwALvf2fMavZum6IAImI7b2yYSqU+1Zb//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKCR/+fzly7rD92yVg69xh8zeLwOa5w+ZvFYHtc4ft3ykB++cOm79PAp6YO2z/Ngl4ZO5l+9+yT4QAvLqS748VF33Ylzdvzpl72f6z53YIGJ6SZdPeNHcIwOycaADdLgCSIgAIgCOAACAAykIAEAAEAAFAABCAT+WQuQVgeBqXhXQIQAAYegowLQBpbg3gZGFyAC6vgBQAMREA2/YfDPxyaDQNyTNz+3Zwn5J4ZG7PB2h0kHhi7plPCImmJwkPzO0RMa3OET0i5uGlzHFze0xcu0vE2Dq3J4U2vEPgSaHbFzPNDQAAAAAAAMBNovdw+cP/ny+uaf7w/+eYADy8kE+F4Offdjn6zZXhAXgiA78G4MNNsmnu1Xr7b3mbOL8T5Ja5bw/A35EC2LiWpzt1y9jRugBy30fLg3NvHPvnuZcC2NsCUXA/aRmA89V07Fwgt37uH8deCmBr6N44pP4UgaUATpdA7v/cMbIB8okliY65/SW5HhJ1ehPmM+8edwXgpbu4R88FayR32Y/P7oZZbOx13/Zr//ZHx27bAPnkFoyewYlbAhD3TvBobr95gaUAtr1EdNx1lgI4OcTTuR3z6+FZMEDRcu9ZCuDgGCdyGxMa4EgBRMvcjrkM7NgBZw5c0TwAUWUhZwRXA2xaya65Xa3jO2qYZ8bu2AD5w38tG5V8aZpoGN6Tz0bOfa9bceyWAciTO0jWyO1Tc5cLwJmF/JfPnXVyu3/slgHIg1n79O2O5fZv+1cHV7sC2HYqmUdHysNzX3sVkMcjUK5Gc+dMs28E5bGtm0V3gloBOP9vgZv+4sYn3RUaYFMCol5uN77g6lUApc8pWs69Zn7snS9Z9Q8G0S0AUTVUUTG3A54R1KSvo/diLAv5fKzynZeN6xogC75u93+AtBTA47OlAFSv6qY/vp3DAjD8iv2ZdFYJwKynMhTK1rInPfzaxW81LnvSgFP9KxrATaCLA3DxHpbFX31ZyNm5XRZyXG5bNkAWfP0rcrsUwOgC6NIAzgBcBiqAWwPgLrAGuGBP6jr2sifdfiJ6QQM4Bbw4AK4B3129ZSFn53ZZyA/GyFty27IBFMDFAXAG8PbyLQv5xULGPRl0K3h2AbwcgCZPhs+LD1zLnjS6AN4NwMU/DVFh7LyhASreTbvqrxdr/J4XT4Swz4FrTS+AGJ7bNbwAYkxuWzZAVljHrJfbjb9wviYXwFO/FJ8Vli4vaICsEMFyBbA3tmtsAUS0zG1c/bj4YwsZH2/+Whd0+1Nb+S7IE2sfPw4RL0XmsR8Nqvz7qFngmPHF34EqjP15AAofAkosZKPC/K6FVoeP02Ehi540NG6AK/4pYP3cLgVwXwHkDQ1QcSGb/uF4WwCmfX8u/+4vgLINcMUlQIfcLgXwXAF0+BGkpQDuuJx7/hwgpu//cWVuO3wxJOz/z8297vgYBwaIO3O7Kn+c194578ltywbIgu8fl+Z2lS+APvnLjnOv8hsgSqxjgwL4Ln9LAezaj98tgPzy7ZcC+GQzxrWxXQpgx370dm6/H7v6jaBoso5dY1swAFlwHWvfBf5pxVa93fCtdx64+1dsgCy4joWvAfPX9VoKYMs6Zse9/8Mlvv7LILlhAfKFFdsSutJXAdFkL3qlADJPrXFcXAC5KYaH586jO9mtAch9S3T0GQJ726ZWAE49kjP3rlDJuetdaL/1zeqZY9c7CRz7s0wCUPxienQBnAuAAtAAlxaAAAxfyBQABSAACkAAFIAAKAABUAACMEkKwL170oh7V8ueNLoAjgTAXWAN4BRwcABcA2oABTA4AApAAyiAwQFQABpAAQwOgALQADMWUgCuEmNyu15fSIY3gFPAiwPgFFADKIDBAVAAGkABCIACmBqAUAAaQAHMDUCMWkgBuMWw3K43F5LhDeAU8OIAuAmkARTA4AAoAA2gAARAAUwNgLvAGkABDA6Au8AaoKOJuV0vLSTDG8Ap4MUBcBNIAyiAwQFQABpAAQwOgALQAApAABTA1AC4C6wBOhqb23V+IRneAE4BLw6Aa0ANoAAGB0ABaAAFMDgACkADKAABUABTA+AusAboKATAQs4trjV+IYcfuJYCcA6gAATAQk69dFkKQANYyLkFcLIBFIDLQAVwawDsSRrAEWBwAJwCagAFMDgACkADKIDBAVAAGkABCIACmBoAzwXWAApgcADsSRrg0iNACoACEADXgAIwdCFTACykALgGFIAfl0kBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPBv/gN+IH8U6YveYgAAAABJRU5ErkJggg==&labelColor=white)](https://www.paddleocr.com) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/PaddlePaddle/PaddleOCR) +[![License](https://img.shields.io/badge/license-Apache_2.0-green)](../LICENSE) + +
    + + + + + + +**PaddleOCR convertit des documents et des images en données structurées prêtes pour les LLM (JSON/Markdown) avec une précision de pointe dans l'industrie. Avec plus de 70k étoiles et la confiance de projets de premier plan tels que Dify, RAGFlow et Cherry Studio, PaddleOCR est le socle fondamental pour construire des applications RAG intelligentes et des applications Agentiques.** + + +## 🚀 Fonctionnalités clés + +### 📄 Analyse intelligente de documents (prêt pour les LLM) +> *Transformer des visuels désordonnés en données structurées pour l'ère des LLM.* + +* **VLM documentaire de pointe** : Avec **PaddleOCR-VL-1.6 (0,9 milliard de paramètres)**, le modèle vision-langage léger de pointe de l'industrie pour l'analyse de documents. Il atteint 96,3 % de précision sur OmniDocBench v1.6, mène l'industrie en reconnaissance de texte, formules et tableaux, et améliore significativement les capacités pour les documents anciens, les caractères rares, les sceaux et les graphiques, avec des sorties structurées aux formats **Markdown** et **JSON**. +* **Conversion avec conscience de la structure** : Propulsé par **PP-StructureV3**, convertissez sans effort des PDF et images complexes en **Markdown** ou **JSON**. Contrairement aux modèles de la série PaddleOCR-VL, il fournit des informations de coordonnées plus fines, incluant les coordonnées des cellules de tableau, les coordonnées du texte, et bien plus encore. +* **Efficacité prête pour la production** : Atteignez une précision de niveau commercial avec une empreinte ultra-réduite. Surpasse de nombreuses solutions propriétaires sur les benchmarks publics tout en restant économe en ressources pour le déploiement en périphérie ou dans le cloud. + +### 🔍 Reconnaissance de texte universelle (OCR de scène) +> *L'étalon-or mondial pour la détection de texte multilingue à haute vitesse.* + +* **Plus de 100 langues supportées** : Reconnaissance native pour une vaste bibliothèque mondiale. **PP-OCRv6** supporte 50 langues avec un seul modèle unifié (chinois, anglais, japonais et 46 langues latines) — sans changement de modèle. +* **Maîtrise des éléments complexes** : Au-delà de la reconnaissance de texte standard, nous prenons en charge la **détection de texte en scène naturelle** dans une large gamme d'environnements, y compris les pièces d'identité, les vues de rue, les livres et les composants industriels. +* **Bond en performance** : PP-OCRv6 atteint **+4.6% en détection** et **+5.1% en reconnaissance** par rapport à PP-OCRv5, surpassant les principaux modèles de langage visuel. Accélération 5.2× en inférence CPU de bout en bout. + +
    +

    + Architecture PaddleOCR +

    +
    + +### 🛠️ Écosystème centré sur les développeurs +* **Intégration transparente** : Le premier choix pour l'écosystème des agents IA — profondément intégré avec **Dify, RAGFlow, Pathway et Cherry Studio**. +* **Volant de données pour LLM** : Un pipeline complet pour construire des jeux de données de haute qualité, fournissant un « Moteur de données » durable pour l'affinage des grands modèles de langage. +* **Déploiement en un clic** : Prend en charge divers backends matériels (GPU NVIDIA, CPU Intel, XPU Kunlunxin et divers accélérateurs IA). + + +## 📣 Mises à jour récentes + +### 🔥 2026.06.11 : Publication de PaddleOCR 3.7.0 +- Points forts de PP-OCRv6 : + + - **Amélioration de la précision** : Le niveau medium atteint +4.6% en détection et +5.1% en reconnaissance par rapport à PP-OCRv5_server, surpassant les principaux VLMs (Qwen3-VL-235B, GPT-5.5) avec seulement 34.5M paramètres. + - **50 langues unifiées** : Un seul modèle couvre le chinois, l'anglais, le japonais et 46 langues latines — sans changement de modèle. + - **Scénarios spécialisés** : Améliorations majeures pour les écrans numériques, caractères matriciels, empreintes de pneus et texte industriel. + - **Inférence plus rapide** : Accélération 5.2× CPU (OpenVINO), 6.1× sur Apple M4 (tiny), 0.13s sur A100 GPU. + - **Trois niveaux pour tous les scénarios** : tiny (1.5M) / small (7.7M) / medium (34.5M) pour le déploiement edge, mobile et serveur. + - **Disponibilité des modèles** : Tous les modèles sont disponibles sur [HuggingFace](https://huggingface.co/collections/PaddlePaddle/pp-ocrv6) et [ModelScope](https://www.modelscope.cn/collections/PaddlePaddle/PP-OCRv6). + +
    +2026.05.28 : Publication de PaddleOCR 3.6.0 + +- Points forts de PaddleOCR-VL-1.6 : + + - **Nouvelle précision SOTA** : 96.3% sur OmniDocBench v1.6, nouveau SOTA sur OmniDocBench v1.5 et Real5-OmniDocBench. + - **Amélioration complète** : améliorations significatives des tableaux, documents anciens et caractères rares. + - **Migration transparente** : architecture entièrement compatible avec PaddleOCR-VL-1.5. + - **Essayez** : [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6) ou [site officiel](https://www.paddleocr.com). + +
    +
    +2026.04.21 : Publication de PaddleOCR 3.5.0 + +* **Backends d'inférence plus flexibles** : basculez en toute transparence entre les graphes statiques Paddle, les graphes dynamiques Paddle et Transformers. PaddleOCR est désormais profondément intégré à l'écosystème Hugging Face, et 20 modèles majeurs prennent en charge Transformers comme backend d'inférence. +* **Conversion des documents bureautiques en Markdown** : conversion des formats courants comme Word, Excel et PowerPoint vers Markdown. +* **Export DOCX des résultats d'analyse** : les séries `PaddleOCR-VL`, `PP-StructureV3` et `PP-DocTranslation` prennent désormais en charge l'export des résultats d'analyse au format DOCX pour une consultation et une édition pratiques dans Microsoft Word. +* **SDK officiel d'inférence dans le navigateur** : sortie du SDK officiel d'inférence dans le navigateur `PaddleOCR.js`, qui permet d'exécuter `PP-OCRv5` directement dans le navigateur. + +
    + +
    +2026.01.29 : Publication de PaddleOCR 3.4.0 +* PaddleOCR-VL-1.5 (VLM 0,9 milliard de paramètres, état de l'art) : Notre dernier modèle phare pour l'analyse de documents est désormais disponible ! + * **94,5 % de précision sur OmniDocBench** : Surpasse les grands modèles généralistes de premier rang et les analyseurs de documents spécialisés. + * **Robustesse dans le monde réel** : Premier à introduire l'algorithme **PP-DocLayoutV3** pour le positionnement de formes irrégulières, maîtrisant 5 scénarios difficiles : *Inclinaison, Déformation, Numérisation, Éclairage et Photographie d'écran*. + * **Extension des capacités** : Prend désormais en charge la **Reconnaissance de sceaux**, la **Détection de texte**, et s'étend à **111 langues** (incluant le tibétain et le bengali). + * **Maîtrise des longs documents** : Prend en charge la fusion automatique de tableaux sur plusieurs pages et l'identification hiérarchique des titres. + * **Essayez-le maintenant** : Disponible sur [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.5) ou sur notre [Site officiel](https://www.paddleocr.com). + +
    + +
    +2025.10.16 : Publication de PaddleOCR 3.3.0 + +- Publication de PaddleOCR-VL : + - **Présentation du modèle** : + - **PaddleOCR-VL** est un modèle de pointe et économe en ressources, spécialement conçu pour l'analyse de documents. Son composant principal est PaddleOCR-VL-0.9B, un modèle vision-langage (VLM) compact mais puissant qui intègre un encodeur visuel à résolution dynamique de style NaViT avec le modèle de langage ERNIE-4.5-0.3B pour permettre une reconnaissance précise des éléments. **Ce modèle innovant prend en charge efficacement 109 langues et excelle dans la reconnaissance d'éléments complexes (par exemple, texte, tableaux, formules et graphiques), tout en maintenant une consommation minimale de ressources**. Grâce à des évaluations complètes sur des benchmarks publics largement utilisés et des benchmarks internes, PaddleOCR-VL atteint des performances de pointe à la fois dans l'analyse de documents au niveau de la page et dans la reconnaissance au niveau des éléments. Il surpasse significativement les solutions existantes, présente une forte compétitivité face aux VLM de premier plan, et offre des vitesses d'inférence rapides. Ces atouts le rendent très adapté au déploiement pratique dans des scénarios du monde réel. Le modèle a été publié sur [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL). Tout le monde est invité à le télécharger et à l'utiliser ! Plus d'informations d'introduction sont disponibles dans [PaddleOCR-VL](https://www.paddleocr.ai/latest/version3.x/algorithm/PaddleOCR-VL/PaddleOCR-VL.html). + + - **Fonctionnalités principales** : + - **Architecture VLM compacte mais puissante** : Nous présentons un nouveau modèle vision-langage spécialement conçu pour une inférence économe en ressources, atteignant des performances remarquables dans la reconnaissance d'éléments. En intégrant un encodeur visuel haute résolution dynamique de style NaViT avec le modèle de langage léger ERNIE-4.5-0.3B, nous améliorons considérablement les capacités de reconnaissance et l'efficacité du décodage du modèle. Cette intégration maintient une haute précision tout en réduisant les besoins de calcul, ce qui le rend bien adapté aux applications de traitement de documents efficaces et pratiques. + - **Performances de pointe en analyse de documents** : PaddleOCR-VL atteint des performances à l'état de l'art à la fois dans l'analyse de documents au niveau de la page et dans la reconnaissance au niveau des éléments. Il surpasse significativement les solutions existantes basées sur des pipelines et présente une forte compétitivité face aux principaux modèles vision-langage (VLM) en analyse de documents. De plus, il excelle dans la reconnaissance d'éléments documentaires complexes, tels que le texte, les tableaux, les formules et les graphiques, ce qui le rend adapté à une large gamme de types de contenu difficiles, y compris le texte manuscrit et les documents historiques. Cela le rend très polyvalent et adapté à une large gamme de types de documents et de scénarios. + - **Support multilingue** : PaddleOCR-VL prend en charge 109 langues, couvrant les principales langues mondiales, notamment le chinois, l'anglais, le japonais, le latin et le coréen, ainsi que les langues avec des scripts et des structures différents, tels que le russe (script cyrillique), l'arabe, l'hindi (script devanagari) et le thaï. Cette large couverture linguistique améliore considérablement l'applicabilité de notre système aux scénarios de traitement de documents multilingues et mondialisés. + +- Publication du modèle de reconnaissance multilingue PP-OCRv5 : + - Amélioration de la précision et de la couverture de la reconnaissance des scripts latins ; ajout de la prise en charge des systèmes cyrillique, arabe, devanagari, télougou, tamoul et d'autres systèmes linguistiques, couvrant la reconnaissance de 109 langues. Le modèle ne compte que 2 millions de paramètres, et la précision de certains modèles a augmenté de plus de 40 % par rapport à la génération précédente. + +
    + + +
    +2025.08.21 : Publication de PaddleOCR 3.2.0 + +- **Ajouts significatifs de modèles :** + - Introduction de l'entraînement, de l'inférence et du déploiement pour les modèles de reconnaissance PP-OCRv5 en anglais, thaï et grec. **Le modèle PP-OCRv5 anglais apporte une amélioration de 11 % dans les scénarios en anglais par rapport au modèle principal PP-OCRv5, avec les modèles de reconnaissance thaï et grec atteignant des précisions de 82,68 % et 89,28 %, respectivement.** + +- **Améliorations des capacités de déploiement :** + - **Prise en charge complète des versions 3.1.0 et 3.1.1 du framework PaddlePaddle.** + - **Mise à niveau complète de la solution de déploiement local C++ de PP-OCRv5, prenant désormais en charge Linux et Windows, avec une parité de fonctionnalités et une précision identique à l'implémentation Python.** + - **L'inférence haute performance prend désormais en charge CUDA 12, et l'inférence peut être effectuée en utilisant le backend Paddle Inference ou ONNX Runtime.** + - **La solution de déploiement orientée services à haute stabilité est désormais entièrement open-source, permettant aux utilisateurs de personnaliser les images Docker et les SDK selon leurs besoins.** + - La solution de déploiement orientée services à haute stabilité prend également en charge l'invocation via des requêtes HTTP construites manuellement, permettant le développement de code client dans n'importe quel langage de programmation. + +- **Support des benchmarks :** + - **Toutes les lignes de production prennent désormais en charge des benchmarks granulaires, permettant la mesure du temps d'inférence de bout en bout ainsi que les données de latence par couche et par module pour faciliter l'analyse des performances. [Voici](docs/version3.x/pipeline_usage/instructions/benchmark.en.md) comment configurer et utiliser la fonctionnalité de benchmark.** + - **La documentation a été mise à jour pour inclure les métriques clés pour les configurations couramment utilisées sur le matériel grand public, telles que la latence d'inférence et l'utilisation de la mémoire, fournissant des références de déploiement pour les utilisateurs.** + +- **Corrections de bugs :** + - Résolution du problème de l'échec de sauvegarde des journaux lors de l'entraînement du modèle. + - Mise à niveau du composant d'augmentation de données pour les modèles de formules pour la compatibilité avec les nouvelles versions de la dépendance albumentations, et correction des avertissements de blocage lors de l'utilisation du package tokenizers dans des scénarios multi-processus. + - Correction des incohérences dans les comportements des commutateurs (par exemple, `use_chart_parsing`) dans les fichiers de configuration PP-StructureV3 par rapport aux autres pipelines. + +- **Autres améliorations :** + - **Séparation des dépendances principales et optionnelles. Seules les dépendances principales minimales sont requises pour la reconnaissance de texte de base ; des dépendances supplémentaires pour l'analyse de documents et l'extraction d'informations peuvent être installées selon les besoins.** + - **Activation de la prise en charge des cartes graphiques NVIDIA RTX série 50 sous Windows ; les utilisateurs peuvent consulter le [guide d'installation](docs/version3.x/installation.en.md) pour les versions correspondantes du framework PaddlePaddle.** + - **Les modèles de la série PP-OCR prennent désormais en charge le retour des coordonnées de chaque caractère.** + - Ajout de sources de téléchargement de modèles AIStudio, ModelScope et autres, permettant aux utilisateurs de spécifier la source pour les téléchargements de modèles. + - Ajout de la prise en charge de la conversion graphique en tableau via le module PP-Chart2Table. + - Optimisation des descriptions de documentation pour améliorer la facilité d'utilisation. +
    + + +[Journal des modifications](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + + +## 🚀 Démarrage rapide + +### Étape 1 : Essayer en ligne +Le site officiel de PaddleOCR propose un **Centre d'expérience** interactif et des **API** — aucune configuration requise, un seul clic pour découvrir. + +👉 [Visiter le site officiel](https://www.paddleocr.com) + +### Étape 2 : Déploiement local +Pour une utilisation locale, veuillez consulter la documentation suivante en fonction de vos besoins : + +- **Série PP-OCR** : Voir la [documentation PP-OCR](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/OCR.html) +- **Série PaddleOCR-VL** : Voir la [documentation PaddleOCR-VL](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PaddleOCR-VL.html) +- **PP-StructureV3** : Voir la [documentation PP-StructureV3](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PP-StructureV3.html) +- **Autres capacités** : Voir la [documentation sur les autres capacités](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/pipeline_overview.html) + + +## 🧩 Plus de fonctionnalités + +- Convertir des modèles au format ONNX : [Obtenir des modèles ONNX](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/others/obtaining_onnx_models.html). +- Accélérer l'inférence à l'aide de moteurs tels qu'OpenVINO, ONNX Runtime, TensorRT, ou effectuer une inférence à l'aide de modèles au format ONNX : [Inférence haute performance](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/local_inference/high_performance_inference.html). +- Accélérer l'inférence à l'aide de plusieurs GPU et plusieurs processus : [Inférence parallèle pour les pipelines](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/instructions/parallel_inference.html). +- Intégrer PaddleOCR dans des applications écrites en C++, C#, Java, etc. : [Services](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/serving/serving.html). + +## 🔄 Aperçu rapide des résultats d'exécution + +### PP-OCRv5 + +
    +

    + Démo PP-OCRv5 +

    +
    + + + +### PP-StructureV3 + +
    +

    + Démo PP-StructureV3 +

    +
    + +### PaddleOCR-VL + +
    +

    + Démo PP-StructureV3 +

    +
    + + +## ✨ Restez informé + +⭐ **Mettez une étoile à ce dépôt pour suivre les mises à jour passionnantes et les nouvelles versions, y compris les puissantes capacités d'OCR et d'analyse de documents !** ⭐ + +
    +

    + Star-Project +

    +
    + + +## 👩‍👩‍👧‍👦 Communauté + +
    + +| Compte officiel WeChat de PaddlePaddle | Rejoindre le groupe de discussion technique | +| :---: | :---: | +| | | +
    + + +## 😃 Projets remarquables utilisant PaddleOCR +PaddleOCR n'en serait pas là aujourd'hui sans son incroyable communauté ! 💗 Un immense merci à tous nos partenaires de longue date, aux nouveaux collaborateurs et à tous ceux qui ont mis leur passion dans PaddleOCR — que nous vous ayons cités ou non. Votre soutien alimente notre feu ! + +
    + +| Nom du projet | Description | +| ------------ | ----------- | +| [Dify](https://github.com/langgenius/dify) |Plateforme prête pour la production pour le développement de flux de travail agentiques.| +| [RAGFlow](https://github.com/infiniflow/ragflow) |Moteur RAG basé sur la compréhension approfondie des documents.| +| [pathway](https://github.com/pathwaycom/pathway) |Framework Python ETL pour le traitement de flux, l'analytique en temps réel, les pipelines LLM et le RAG.| +| [MinerU](https://github.com/opendatalab/MinerU) |Outil de conversion de documents multi-types en Markdown.| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |Logiciel OCR hors ligne par lots, gratuit et open-source.| +| [cherry-studio](https://github.com/CherryHQ/cherry-studio) |Un client de bureau prenant en charge plusieurs fournisseurs de LLM.| +| [haystack](https://github.com/deepset-ai/haystack) |Framework d'orchestration IA pour construire des applications LLM personnalisables et prêtes pour la production.| +| [OmniParser](https://github.com/microsoft/OmniParser) |OmniParser : Outil d'analyse d'écran pour agent GUI basé sur la vision pure.| +| [QAnything](https://github.com/netease-youdao/QAnything) |Questions et réponses basées sur n'importe quoi.| +| [En savoir plus sur les projets](./awesome_projects.md) | [Plus de projets basés sur PaddleOCR](./awesome_projects.md)| +
    + +## 👩‍👩‍👧‍👦 Contributeurs + +
    + + + +
    + +## 🌟 Étoiles + +
    +

    + Star-history +

    +
    + + +## 📄 Licence +Ce projet est publié sous la [licence Apache 2.0](LICENSE). + +## 🎓 Citation + +```bibtex +@misc{cui2025paddleocr30technicalreport, + title={PaddleOCR 3.0 Technical Report}, + author={Cheng Cui and Ting Sun and Manhui Lin and Tingquan Gao and Yubo Zhang and Jiaxuan Liu and Xueqing Wang and Zelun Zhang and Changda Zhou and Hongen Liu and Yue Zhang and Wenyu Lv and Kui Huang and Yichao Zhang and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2507.05595}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2507.05595}, +} + +@misc{cui2025paddleocrvlboostingmultilingualdocument, + title={PaddleOCR-VL: Boosting Multilingual Document Parsing via a 0.9B Ultra-Compact Vision-Language Model}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Handong Zheng and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2510.14528}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2510.14528}, +} + +@misc{cui2026paddleocrvl15multitask09bvlm, + title={PaddleOCR-VL-1.5: Towards a Multi-Task 0.9B VLM for Robust In-the-Wild Document Parsing}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2026}, + eprint={2601.21957}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2601.21957}, +} diff --git a/readme/README_ja.md b/readme/README_ja.md new file mode 100644 index 0000000..4ff6c92 --- /dev/null +++ b/readme/README_ja.md @@ -0,0 +1,304 @@ + +
    +

    + Star履歴 +

    + + + +

    世界をリードするOCRツールキット & ドキュメントAIエンジン

    + +[English](../README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | 日本語 | [한국어](./README_ko.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![PyPI Downloads](https://static.pepy.tech/badge/paddleocr)](https://pepy.tech/projects/paddleocr) +[![Used by](https://img.shields.io/badge/Used%20by-6k%2B%20repositories-blue)](https://github.com/PaddlePaddle/PaddleOCR/network/dependents) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + +[![AI Studio](https://img.shields.io/badge/PaddleOCR-_Offiical_Website-1927BA?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAABlBMVEU2P+X///+1KuUwAAAHKklEQVR42u3dS5bjOAwEwALvf2fMavZum6IAImI7b2yYSqU+1Zb//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKCR/+fzly7rD92yVg69xh8zeLwOa5w+ZvFYHtc4ft3ykB++cOm79PAp6YO2z/Ngl4ZO5l+9+yT4QAvLqS748VF33Ylzdvzpl72f6z53YIGJ6SZdPeNHcIwOycaADdLgCSIgAIgCOAACAAykIAEAAEAAFAABCAT+WQuQVgeBqXhXQIQAAYegowLQBpbg3gZGFyAC6vgBQAMREA2/YfDPxyaDQNyTNz+3Zwn5J4ZG7PB2h0kHhi7plPCImmJwkPzO0RMa3OET0i5uGlzHFze0xcu0vE2Dq3J4U2vEPgSaHbFzPNDQAAAAAAAMBNovdw+cP/ny+uaf7w/+eYADy8kE+F4Offdjn6zZXhAXgiA78G4MNNsmnu1Xr7b3mbOL8T5Ja5bw/A35EC2LiWpzt1y9jRugBy30fLg3NvHPvnuZcC2NsCUXA/aRmA89V07Fwgt37uH8deCmBr6N44pP4UgaUATpdA7v/cMbIB8okliY65/SW5HhJ1ehPmM+8edwXgpbu4R88FayR32Y/P7oZZbOx13/Zr//ZHx27bAPnkFoyewYlbAhD3TvBobr95gaUAtr1EdNx1lgI4OcTTuR3z6+FZMEDRcu9ZCuDgGCdyGxMa4EgBRMvcjrkM7NgBZw5c0TwAUWUhZwRXA2xaya65Xa3jO2qYZ8bu2AD5w38tG5V8aZpoGN6Tz0bOfa9bceyWAciTO0jWyO1Tc5cLwJmF/JfPnXVyu3/slgHIg1n79O2O5fZv+1cHV7sC2HYqmUdHysNzX3sVkMcjUK5Gc+dMs28E5bGtm0V3gloBOP9vgZv+4sYn3RUaYFMCol5uN77g6lUApc8pWs69Zn7snS9Z9Q8G0S0AUTVUUTG3A54R1KSvo/diLAv5fKzynZeN6xogC75u93+AtBTA47OlAFSv6qY/vp3DAjD8iv2ZdFYJwKynMhTK1rInPfzaxW81LnvSgFP9KxrATaCLA3DxHpbFX31ZyNm5XRZyXG5bNkAWfP0rcrsUwOgC6NIAzgBcBiqAWwPgLrAGuGBP6jr2sifdfiJ6QQM4Bbw4AK4B3129ZSFn53ZZyA/GyFty27IBFMDFAXAG8PbyLQv5xULGPRl0K3h2AbwcgCZPhs+LD1zLnjS6AN4NwMU/DVFh7LyhASreTbvqrxdr/J4XT4Swz4FrTS+AGJ7bNbwAYkxuWzZAVljHrJfbjb9wviYXwFO/FJ8Vli4vaICsEMFyBbA3tmtsAUS0zG1c/bj4YwsZH2/+Whd0+1Nb+S7IE2sfPw4RL0XmsR8Nqvz7qFngmPHF34EqjP15AAofAkosZKPC/K6FVoeP02Ehi540NG6AK/4pYP3cLgVwXwHkDQ1QcSGb/uF4WwCmfX8u/+4vgLINcMUlQIfcLgXwXAF0+BGkpQDuuJx7/hwgpu//cWVuO3wxJOz/z8297vgYBwaIO3O7Kn+c194578ltywbIgu8fl+Z2lS+APvnLjnOv8hsgSqxjgwL4Ln9LAezaj98tgPzy7ZcC+GQzxrWxXQpgx370dm6/H7v6jaBoso5dY1swAFlwHWvfBf5pxVa93fCtdx64+1dsgCy4joWvAfPX9VoKYMs6Zse9/8Mlvv7LILlhAfKFFdsSutJXAdFkL3qlADJPrXFcXAC5KYaH586jO9mtAch9S3T0GQJ726ZWAE49kjP3rlDJuetdaL/1zeqZY9c7CRz7s0wCUPxienQBnAuAAtAAlxaAAAxfyBQABSAACkAAFIAAKAABUAACMEkKwL170oh7V8ueNLoAjgTAXWAN4BRwcABcA2oABTA4AApAAyiAwQFQABpAAQwOgALQADMWUgCuEmNyu15fSIY3gFPAiwPgFFADKIDBAVAAGkABCIACmBqAUAAaQAHMDUCMWkgBuMWw3K43F5LhDeAU8OIAuAmkARTA4AAoAA2gAARAAUwNgLvAGkABDA6Au8AaoKOJuV0vLSTDG8Ap4MUBcBNIAyiAwQFQABpAAQwOgALQAApAABTA1AC4C6wBOhqb23V+IRneAE4BLw6Aa0ANoAAGB0ABaAAFMDgACkADKAABUABTA+AusAboKATAQs4trjV+IYcfuJYCcA6gAATAQk69dFkKQANYyLkFcLIBFIDLQAVwawDsSRrAEWBwAJwCagAFMDgACkADKIDBAVAAGkABCIACmBoAzwXWAApgcADsSRrg0iNACoACEADXgAIwdCFTACykALgGFIAfl0kBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPBv/gN+IH8U6YveYgAAAABJRU5ErkJggg==&labelColor=white)](https://www.paddleocr.com) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/PaddlePaddle/PaddleOCR) +[![License](https://img.shields.io/badge/license-Apache_2.0-green)](../LICENSE) + +
    + + + + + + +**PaddleOCRは、ドキュメントや画像を業界最高水準の精度で構造化されたLLM対応データ(JSON/Markdown)に変換します。70,000以上のStarを獲得し、Dify、RAGFlow、Cherry Studioなどの一流プロジェクトで採用されているPaddleOCRは、インテリジェントなRAGおよびエージェントアプリケーション構築の基盤です。** + + +## 🚀 主な機能 + +### 📄 インテリジェントドキュメント解析(LLM対応) +> *LLM時代に向けて、雑然とした視覚データを構造化データに変換* + +* **最先端のドキュメントVLM**: 業界をリードする軽量視覚言語モデル **PaddleOCR-VL-1.6(0.9B)** を搭載。OmniDocBench v1.6 で 96.3% の精度を達成し、テキスト、数式、表の認識で業界をリード。古文書、稀な文字、印鑑、チャートなど多シーンの能力も大幅に強化され、**Markdown**および**JSON**形式の構造化出力に対応しています。 +* **構造認識型変換**: **PP-StructureV3**を活用し、複雑なPDFや画像を**Markdown**または**JSON**にシームレスに変換します。PaddleOCR-VLシリーズモデルとは異なり、テーブルセル座標、テキスト座標などのより詳細な座標情報を提供します。 +* **本番環境対応の効率性**: 超小型フットプリントで商用レベルの精度を実現。公開ベンチマークで多くのクローズドソースソリューションを凌駕しつつ、エッジ/クラウドデプロイメントに対してリソース効率を維持します。 + +### 🔍 汎用テキスト認識(シーンOCR) +> *高速・多言語テキスト検出のグローバルスタンダード* + +* **100以上の言語をサポート**: 広範なグローバル言語ライブラリのネイティブ認識。**PP-OCRv6**は単一モデルで50言語(中国語、英語、日本語、46のラテン語系言語)を統一サポートします。 +* **複雑な要素への対応力**: 標準的なテキスト認識を超え、身分証明書、街頭風景、書籍、産業部品など、幅広い環境での**自然シーンテキスト検出**をサポートします。 +* **性能の飛躍的向上**: PP-OCRv6はPP-OCRv5と比較して検出精度**+4.6%**、認識精度**+5.1%**を達成し、主要な視覚言語モデルを上回ります。CPU推論5.2×高速化。 + +
    +

    + PaddleOCR アーキテクチャ +

    +
    + +### 🛠️ 開発者中心のエコシステム +* **シームレスな統合**: AIエージェントエコシステムの最良の選択肢 ── **Dify、RAGFlow、Pathway、Cherry Studio**と深く統合されています。 +* **LLMデータフライホイール**: 高品質データセットを構築する完全なパイプラインを提供し、大規模言語モデルのファインチューニングのための持続可能な「データエンジン」を実現します。 +* **ワンクリックデプロイ**: さまざまなハードウェアバックエンド(NVIDIA GPU、Intel CPU、Kunlunxin XPU、各種AIアクセラレータ)をサポートします。 + + +## 📣 最新情報 + +### 🔥 2026.06.11: PaddleOCR 3.7.0リリース +- PP-OCRv6の主な特徴: + + - **精度向上**: mediumティアはPP-OCRv5_serverと比較して検出+4.6%、認識+5.1%を達成、34.5Mパラメータのみで主要VLM(Qwen3-VL-235B、GPT-5.5)を上回る。 + - **50言語統一サポート**: 単一モデルで中国語、英語、日本語、46のラテン語系言語をカバー — モデル切替不要。 + - **専門シナリオ強化**: デジタルディスプレイ、ドットマトリックス文字、タイヤ印字、工業文字の認識が大幅向上。 + - **高速推論**: CPU 5.2×高速化(OpenVINO)、Apple M4 6.1×(tiny)、A100 GPUで0.13s。 + - **全シナリオ対応3ティア**: tiny(1.5M)/ small(7.7M)/ medium(34.5M)でエッジからサーバーまで対応。 + - **モデル入手**: 全モデルは [HuggingFace](https://huggingface.co/collections/PaddlePaddle/pp-ocrv6) および [ModelScope](https://www.modelscope.cn/collections/PaddlePaddle/PP-OCRv6) で公開中。 + +
    +2026.05.28: PaddleOCR 3.6.0リリース + +- PaddleOCR-VL-1.6の主な特徴: + + - **新SOTA精度**: OmniDocBench v1.6で96.3%、OmniDocBench v1.5とReal5-OmniDocBenchでも新SOTA達成。 + - **総合的な強化**: テーブル、古文書、希少文字の認識が大幅向上。 + - **シームレスな移行**: PaddleOCR-VL-1.5と完全互換のアーキテクチャ。 + - **今すぐ試す**: [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6)または[公式サイト](https://www.paddleocr.com)。 + +
    +
    +2026.04.21: PaddleOCR 3.5.0リリース + +* **柔軟な推論バックエンド**: Paddleの静的グラフ、動的グラフ、Transformersをシームレスに切り替え可能。Hugging Face エコシステムに深く対応し、主要20モデルがTransformersを推論バックエンドとしてサポート。 +* **Office文書をMarkdownに変換**: Word、Excel、PowerPoint などの一般的な文書形式を Markdown に変換可能。 +* **解析結果の DOCX 出力**: `PaddleOCR-VL` シリーズ、`PP-StructureV3`、`PP-DocTranslation` で、解析結果を DOCX として出力できるようになり、Microsoft Word での閲覧・編集が容易に。 +* **公式ブラウザ推論 SDK**: 公式ブラウザ推論 SDK `PaddleOCR.js` を公開し、ブラウザ上で `PP-OCRv5` を実行可能。 + +
    + +
    +2026.01.29: PaddleOCR 3.4.0リリース +* PaddleOCR-VL-1.5(最先端の0.9B VLM): ドキュメント解析のための最新フラッグシップモデルが公開されました! + * **OmniDocBenchで94.5%の精度**: トップクラスの汎用大規模モデルや専門ドキュメントパーサーを凌駕。 + * **実環境でのロバスト性**: 非定型形状位置決定のための**PP-DocLayoutV3**アルゴリズムを初めて導入し、*傾き、歪み、スキャン、照明、画面撮影*の5つの困難なシナリオに対応。 + * **機能拡張**: **印鑑認識**、**テキスト検出**をサポートし、**111言語**(中国のチベット文字やベンガル文字を含む)に対応拡大。 + * **長文ドキュメントへの対応**: ページをまたがるテーブルの自動結合および階層的な見出し識別をサポート。 + * **今すぐ試す**: [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.5)または[公式ウェブサイト](https://www.paddleocr.com)で利用可能です。 + +
    + +
    +2025.10.16: PaddleOCR 3.3.0リリース + +- PaddleOCR-VLをリリース: + - **モデル紹介**: + - **PaddleOCR-VL**はドキュメント解析に特化した最先端かつリソース効率の高いモデルです。コアコンポーネントであるPaddleOCR-VL-0.9Bは、NaViTスタイルの動的解像度ビジュアルエンコーダとERNIE-4.5-0.3B言語モデルを統合したコンパクトながらも強力な視覚言語モデル(VLM)であり、正確な要素認識を実現します。**この革新的なモデルは109言語を効率的にサポートし、複雑な要素(テキスト、テーブル、数式、チャートなど)の認識に優れつつ、リソース消費を最小限に抑えます**。広く使用されている公開ベンチマークおよび社内ベンチマークでの包括的な評価を通じて、PaddleOCR-VLはページレベルのドキュメント解析と要素レベルの認識の両方で最先端の性能を達成しています。既存のソリューションを大幅に上回り、トップクラスのVLMに対して高い競争力を示し、高速な推論速度を提供します。これらの強みにより、実世界のシナリオへの実践的なデプロイメントに非常に適しています。モデルは[HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL)で公開されています。ぜひダウンロードしてお使いください!詳細情報は[PaddleOCR-VL](https://www.paddleocr.ai/latest/version3.x/algorithm/PaddleOCR-VL/PaddleOCR-VL.html)をご覧ください。 + + - **主要機能**: + - **コンパクトかつ強力なVLMアーキテクチャ**: リソース効率の高い推論に特化した新しい視覚言語モデルを提案し、要素認識において卓越した性能を実現しました。NaViTスタイルの動的高解像度ビジュアルエンコーダと軽量なERNIE-4.5-0.3B言語モデルを統合することで、モデルの認識能力とデコード効率を大幅に向上させました。この統合により、計算負荷を削減しつつ高い精度を維持し、効率的で実用的なドキュメント処理アプリケーションに適しています。 + - **ドキュメント解析における最先端性能**: PaddleOCR-VLはページレベルのドキュメント解析と要素レベルの認識の両方で最先端の性能を達成しています。既存のパイプラインベースのソリューションを大幅に上回り、ドキュメント解析における主要な視覚言語モデル(VLM)に対して高い競争力を示しています。さらに、テキスト、テーブル、数式、チャートなどの複雑なドキュメント要素の認識に優れ、手書きテキストや歴史文書を含む幅広い種類のコンテンツに対応可能です。これにより、多様なドキュメントタイプやシナリオに対して高い汎用性を発揮します。 + - **多言語サポート**: PaddleOCR-VLは109言語をサポートし、中国語、英語、日本語、ラテン語、韓国語をはじめ、ロシア語(キリル文字)、アラビア語、ヒンディー語(デーヴァナーガリー文字)、タイ語など、異なる文字体系や構造を持つ言語を含む主要なグローバル言語をカバーしています。この幅広い言語対応により、多言語およびグローバルなドキュメント処理シナリオへの適用性が大幅に向上しています。 + +- PP-OCRv5多言語認識モデルをリリース: + - ラテン文字認識の精度とカバレッジを改善。キリル文字、アラビア文字、デーヴァナーガリー文字、テルグ文字、タミル文字など他の文字体系のサポートを追加し、109言語の認識をカバー。モデルのパラメータ数はわずか2Mで、一部のモデルの精度は前世代と比較して40%以上向上しています。 + +
    + + +
    +2025.08.21: PaddleOCR 3.2.0リリース + +- **モデルの大幅な追加:** + - PP-OCRv5認識モデルの英語、タイ語、ギリシャ語の学習、推論、デプロイメントを導入。**PP-OCRv5英語モデルは、主要なPP-OCRv5モデルと比較して英語シナリオで11%の改善を達成し、タイ語およびギリシャ語の認識モデルはそれぞれ82.68%および89.28%の精度を実現しています。** + +- **デプロイメント機能のアップグレード:** + - **PaddlePaddleフレームワークバージョン3.1.0および3.1.1を完全サポート。** + - **PP-OCRv5 C++ローカルデプロイメントソリューションを全面アップグレードし、LinuxとWindowsの両方をサポート。Python実装と同等の機能および精度を実現。** + - **高性能推論がCUDA 12をサポートし、Paddle InferenceまたはONNX Runtimeバックエンドを使用した推論が可能。** + - **高安定性サービス指向デプロイメントソリューションが完全にオープンソース化され、ユーザーが必要に応じてDockerイメージやSDKをカスタマイズ可能。** + - 高安定性サービス指向デプロイメントソリューションは、手動でHTTPリクエストを構築して呼び出すこともサポートしており、任意のプログラミング言語でクライアント側コードの開発が可能です。 + +- **ベンチマークサポート:** + - **全プロダクションラインがきめ細かいベンチマークをサポートし、エンドツーエンドの推論時間だけでなく、レイヤーごとおよびモジュールごとのレイテンシデータの測定が可能となり、性能分析を支援します。ベンチマーク機能のセットアップと使用方法は[こちら](docs/version3.x/pipeline_usage/instructions/benchmark.en.md)をご覧ください。** + - **主要なハードウェアでよく使用される構成の推論レイテンシやメモリ使用量などの重要な指標を含むようドキュメントを更新し、ユーザーにデプロイメントの参考情報を提供。** + +- **バグ修正:** + - モデル学習時のログ保存失敗の問題を解決。 + - 数式モデルのデータ拡張コンポーネントをアップグレードし、albumentations依存関係の新しいバージョンとの互換性を確保。マルチプロセスシナリオでtokenizersパッケージ使用時のデッドロック警告を修正。 + - PP-StructureV3設定ファイルにおけるスイッチ動作(例:`use_chart_parsing`)の他のパイプラインとの不整合を修正。 + +- **その他の改善:** + - **コア依存関係とオプション依存関係を分離。基本的なテキスト認識には最小限のコア依存関係のみが必要で、ドキュメント解析や情報抽出のための追加依存関係は必要に応じてインストール可能。** + - **WindowsでNVIDIA RTX 50シリーズグラフィックスカードのサポートを有効化。対応するPaddlePaddleフレームワークバージョンについては[インストールガイド](docs/version3.x/installation.en.md)を参照してください。** + - **PP-OCRシリーズモデルが単一文字座標の返却をサポート。** + - AIStudio、ModelScopeなどのモデルダウンロードソースを追加し、ユーザーがモデルダウンロードのソースを指定可能に。 + - PP-Chart2Tableモジュールによるチャートからテーブルへの変換サポートを追加。 + - ドキュメントの説明を最適化し、使いやすさを向上。 +
    + + +[更新履歴](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + + +## 🚀 クイックスタート + +### ステップ1:オンラインで試す +PaddleOCR公式ウェブサイトでは、インタラクティブな**体験センター**と**API**を提供しています。セットアップ不要、ワンクリックで体験できます。 + +👉 [公式ウェブサイトへアクセス](https://www.paddleocr.com) + +### ステップ2:ローカルデプロイメント +ローカルでの使用については、ニーズに応じて以下のドキュメントを参照してください: + +- **PP-OCRシリーズ**: [PP-OCRドキュメント](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/OCR.html)を参照 +- **PaddleOCR-VLシリーズ**: [PaddleOCR-VLドキュメント](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PaddleOCR-VL.html)を参照 +- **PP-StructureV3**: [PP-StructureV3ドキュメント](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PP-StructureV3.html)を参照 +- **その他の機能**: [その他の機能ドキュメント](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/pipeline_overview.html)を参照 + + +## 🧩 その他の機能 + +- モデルをONNX形式に変換: [ONNXモデルの取得](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/others/obtaining_onnx_models.html) +- OpenVINO、ONNX Runtime、TensorRTなどのエンジンを使用した推論高速化、またはONNX形式モデルによる推論: [高性能推論](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/local_inference/high_performance_inference.html) +- マルチGPUおよびマルチプロセスによる推論高速化: [パイプラインの並列推論](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/instructions/parallel_inference.html) +- PaddleOCRをC++、C#、Javaなどで書かれたアプリケーションに統合: [サービスデプロイメント](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/serving/serving.html) + +## 🔄 実行結果の概要 + +### PP-OCRv5 + +
    +

    + PP-OCRv5 デモ +

    +
    + + + +### PP-StructureV3 + +
    +

    + PP-StructureV3 デモ +

    +
    + +### PaddleOCR-VL + +
    +

    + PaddleOCR-VL デモ +

    +
    + + +## ✨ 最新情報をチェック + +⭐ **このリポジトリにStarを付けて、強力なOCRおよびドキュメント解析機能を含むエキサイティングなアップデートや新リリースを見逃さないようにしましょう!** ⭐ + +
    +

    + プロジェクトにStarを付ける +

    +
    + + +## 👩‍👩‍👧‍👦 コミュニティ + +
    + +| PaddlePaddle WeChat公式アカウント | 技術ディスカッショングループに参加 | +| :---: | :---: | +| | | +
    + + +## 😃 PaddleOCRを活用した素晴らしいプロジェクト +PaddleOCRが今日あるのは、素晴らしいコミュニティのおかげです!💗 長年のパートナー、新たな協力者、そしてPaddleOCRに情熱を注いでくださったすべての皆様に心から感謝いたします。名前を挙げきれなかった方も含めて、皆様のサポートが私たちの原動力です! + +
    + +| プロジェクト名 | 説明 | +| ------------ | ----------- | +| [Dify](https://github.com/langgenius/dify) |エージェントワークフロー開発のためのプロダクション対応プラットフォーム。| +| [RAGFlow](https://github.com/infiniflow/ragflow) |深いドキュメント理解に基づくRAGエンジン。| +| [pathway](https://github.com/pathwaycom/pathway) |ストリーム処理、リアルタイム分析、LLMパイプライン、RAG向けのPython ETLフレームワーク。| +| [MinerU](https://github.com/opendatalab/MinerU) |マルチタイプドキュメントからMarkdownへの変換ツール。| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |無料・オープンソースのバッチオフラインOCRソフトウェア。| +| [cherry-studio](https://github.com/CherryHQ/cherry-studio) |複数のLLMプロバイダーをサポートするデスクトップクライアント。| +| [haystack](https://github.com/deepset-ai/haystack) |カスタマイズ可能なプロダクション対応LLMアプリケーションを構築するためのAIオーケストレーションフレームワーク。| +| [OmniParser](https://github.com/microsoft/OmniParser) |純粋なビジョンベースのGUIエージェント向け画面解析ツール。| +| [QAnything](https://github.com/netease-youdao/QAnything) |あらゆるものに基づく質問応答。| +| [その他のプロジェクトを見る](./awesome_projects.md) | [PaddleOCRに基づくその他のプロジェクト](./awesome_projects.md)| +
    + +## 👩‍👩‍👧‍👦 コントリビューター + +
    + + + +
    + +## 🌟 Star + +
    +

    + Star履歴 +

    +
    + + +## 📄 ライセンス +このプロジェクトは[Apache 2.0ライセンス](LICENSE)の下で公開されています。 + +## 🎓 引用 + +```bibtex +@misc{cui2025paddleocr30technicalreport, + title={PaddleOCR 3.0 Technical Report}, + author={Cheng Cui and Ting Sun and Manhui Lin and Tingquan Gao and Yubo Zhang and Jiaxuan Liu and Xueqing Wang and Zelun Zhang and Changda Zhou and Hongen Liu and Yue Zhang and Wenyu Lv and Kui Huang and Yichao Zhang and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2507.05595}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2507.05595}, +} + +@misc{cui2025paddleocrvlboostingmultilingualdocument, + title={PaddleOCR-VL: Boosting Multilingual Document Parsing via a 0.9B Ultra-Compact Vision-Language Model}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Handong Zheng and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2510.14528}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2510.14528}, +} + +@misc{cui2026paddleocrvl15multitask09bvlm, + title={PaddleOCR-VL-1.5: Towards a Multi-Task 0.9B VLM for Robust In-the-Wild Document Parsing}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2026}, + eprint={2601.21957}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2601.21957}, +} +``` diff --git a/readme/README_ko.md b/readme/README_ko.md new file mode 100644 index 0000000..9c5025c --- /dev/null +++ b/readme/README_ko.md @@ -0,0 +1,304 @@ + +
    +

    + 스타 히스토리 +

    + + + +

    세계 최고의 OCR 툴킷 & 문서 AI 엔진

    + +[English](../README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | 한국어 | [Français](./README_fr.md) | [Русский](./README_ru.md) | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![PyPI Downloads](https://static.pepy.tech/badge/paddleocr)](https://pepy.tech/projects/paddleocr) +[![Used by](https://img.shields.io/badge/Used%20by-6k%2B%20repositories-blue)](https://github.com/PaddlePaddle/PaddleOCR/network/dependents) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + +[![AI Studio](https://img.shields.io/badge/PaddleOCR-_Offiical_Website-1927BA?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAABlBMVEU2P+X///+1KuUwAAAHKklEQVR42u3dS5bjOAwEwALvf2fMavZum6IAImI7b2yYSqU+1Zb//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKCR/+fzly7rD92yVg69xh8zeLwOa5w+ZvFYHtc4ft3ykB++cOm79PAp6YO2z/Ngl4ZO5l+9+yT4QAvLqS748VF33Ylzdvzpl72f6z53YIGJ6SZdPeNHcIwOycaADdLgCSIgAIgCOAACAAykIAEAAEAAFAABCAT+WQuQVgeBqXhXQIQAAYegowLQBpbg3gZGFyAC6vgBQAMREA2/YfDPxyaDQNyTNz+3Zwn5J4ZG7PB2h0kHhi7plPCImmJwkPzO0RMa3OET0i5uGlzHFze0xcu0vE2Dq3J4U2vEPgSaHbFzPNDQAAAAAAAMBNovdw+cP/ny+uaf7w/+eYADy8kE+F4Offdjn6zZXhAXgiA78G4MNNsmnu1Xr7b3mbOL8T5Ja5bw/A35EC2LiWpzt1y9jRugBy30fLg3NvHPvnuZcC2NsCUXA/aRmA89V07Fwgt37uH8deCmBr6N44pP4UgaUATpdA7v/cMbIB8okliY65/SW5HhJ1ehPmM+8edwXgpbu4R88FayR32Y/P7oZZbOx13/Zr//ZHx27bAPnkFoyewYlbAhD3TvBobr95gaUAtr1EdNx1lgI4OcTTuR3z6+FZMEDRcu9ZCuDgGCdyGxMa4EgBRMvcjrkM7NgBZw5c0TwAUWUhZwRXA2xaya65Xa3jO2qYZ8bu2AD5w38tG5V8aZpoGN6Tz0bOfa9bceyWAciTO0jWyO1Tc5cLwJmF/JfPnXVyu3/slgHIg1n79O2O5fZv+1cHV7sC2HYqmUdHysNzX3sVkMcjUK5Gc+dMs28E5bGtm0V3gloBOP9vgZv+4sYn3RUaYFMCol5uN77g6lUApc8pWs69Zn7snS9Z9Q8G0S0AUTVUUTG3A54R1KSvo/diLAv5fKzynZeN6xogC75u93+AtBTA47OlAFSv6qY/vp3DAjD8iv2ZdFYJwKynMhTK1rInPfzaxW81LnvSgFP9KxrATaCLA3DxHpbFX31ZyNm5XRZyXG5bNkAWfP0rcrsUwOgC6NIAzgBcBiqAWwPgLrAGuGBP6jr2sifdfiJ6QQM4Bbw4AK4B3129ZSFn53ZZyA/GyFty27IBFMDFAXAG8PbyLQv5xULGPRl0K3h2AbwcgCZPhs+LD1zLnjS6AN4NwMU/DVFh7LyhASreTbvqrxdr/J4XT4Swz4FrTS+AGJ7bNbwAYkxuWzZAVljHrJfbjb9wviYXwFO/FJ8Vli4vaICsEMFyBbA3tmtsAUS0zG1c/bj4YwsZH2/+Whd0+1Nb+S7IE2sfPw4RL0XmsR8Nqvz7qFngmPHF34EqjP15AAofAkosZKPC/K6FVoeP02Ehi540NG6AK/4pYP3cLgVwXwHkDQ1QcSGb/uF4WwCmfX8u/+4vgLINcMUlQIfcLgXwXAF0+BGkpQDuuJx7/hwgpu//cWVuO3wxJOz/z8297vgYBwaIO3O7Kn+c194578ltywbIgu8fl+Z2lS+APvnLjnOv8hsgSqxjgwL4Ln9LAezaj98tgPzy7ZcC+GQzxrWxXQpgx370dm6/H7v6jaBoso5dY1swAFlwHWvfBf5pxVa93fCtdx64+1dsgCy4joWvAfPX9VoKYMs6Zse9/8Mlvv7LILlhAfKFFdsSutJXAdFkL3qlADJPrXFcXAC5KYaH586jO9mtAch9S3T0GQJ726ZWAE49kjP3rlDJuetdaL/1zeqZY9c7CRz7s0wCUPxienQBnAuAAtAAlxaAAAxfyBQABSAACkAAFIAAKAABUAACMEkKwL170oh7V8ueNLoAjgTAXWAN4BRwcABcA2oABTA4AApAAyiAwQFQABpAAQwOgALQADMWUgCuEmNyu15fSIY3gFPAiwPgFFADKIDBAVAAGkABCIACmBqAUAAaQAHMDUCMWkgBuMWw3K43F5LhDeAU8OIAuAmkARTA4AAoAA2gAARAAUwNgLvAGkABDA6Au8AaoKOJuV0vLSTDG8Ap4MUBcBNIAyiAwQFQABpAAQwOgALQAApAABTA1AC4C6wBOhqb23V+IRneAE4BLw6Aa0ANoAAGB0ABaAAFMDgACkADKAABUABTA+AusAboKATAQs4trjV+IYcfuJYCcA6gAATAQk69dFkKQANYyLkFcLIBFIDLQAVwawDsSRrAEWBwAJwCagAFMDgACkADKIDBAVAAGkABCIACmBoAzwXWAApgcADsSRrg0iNACoACEADXgAIwdCFTACykALgGFIAfl0kBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPBv/gN+IH8U6YveYgAAAABJRU5ErkJggg==&labelColor=white)](https://www.paddleocr.com) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/PaddlePaddle/PaddleOCR) +[![License](https://img.shields.io/badge/license-Apache_2.0-green)](../LICENSE) + +
    + + + + + + +**PaddleOCR는 문서와 이미지를 업계 최고 수준의 정확도로 구조화된 LLM 지원 데이터(JSON/Markdown)로 변환합니다. 70,000개 이상의 Star와 Dify, RAGFlow, Cherry Studio 등 최상위 프로젝트의 신뢰를 받는 PaddleOCR는 지능형 RAG 및 에이전트 기반 애플리케이션 구축의 핵심 기반입니다.** + + +## 🚀 주요 기능 + +### 📄 지능형 문서 파싱 (LLM 지원) +> *복잡한 시각 자료를 LLM 시대에 맞는 구조화된 데이터로 변환합니다.* + +* **최첨단 문서 VLM**: 문서 파싱을 위한 업계 최고의 경량 비전-언어 모델인 **PaddleOCR-VL-1.6 (0.9B)**를 탑재하였습니다. OmniDocBench v1.6에서 96.3% 정확도를 달성했으며, 텍스트·수식·표 인식에서 업계를 선도합니다. 고문서, 희귀 문자, 인장, 차트 등 다양한 시나리오에서의 성능도 크게 향상되었으며, **Markdown** 및 **JSON** 형식의 구조화된 출력을 지원합니다. +* **구조 인식 변환**: **PP-StructureV3**를 기반으로 복잡한 PDF와 이미지를 **Markdown** 또는 **JSON**으로 원활하게 변환합니다. PaddleOCR-VL 시리즈 모델과 달리 표 셀 좌표, 텍스트 좌표 등 더욱 세밀한 좌표 정보를 제공합니다. +* **상용 수준의 효율성**: 초소형 모델로 상용 등급의 정확도를 달성합니다. 공개 벤치마크에서 다수의 비공개 솔루션을 능가하면서도 엣지/클라우드 배포에 적합한 자원 효율성을 유지합니다. + +### 🔍 범용 텍스트 인식 (장면 OCR) +> *고속 다국어 텍스트 탐지의 글로벌 표준.* + +* **100개 이상의 언어 지원**: 방대한 글로벌 언어 라이브러리를 기본 지원합니다. **PP-OCRv6**는 단일 모델로 50개 언어(중국어, 영어, 일본어 및 46개 라틴 언어)를 통합 지원합니다. +* **복잡한 요소 처리**: 표준 텍스트 인식을 넘어 신분증, 거리 풍경, 도서, 산업 부품 등 다양한 환경에서의 **자연 장면 텍스트 탐지**를 지원합니다. +* **성능 도약**: PP-OCRv6는 PP-OCRv5 대비 검출 정확도 **+4.6%**, 인식 정확도 **+5.1%** 향상을 달성하며, 주류 비전 언어 모델을 능가합니다. CPU 추론 5.2배 가속. + +
    +

    + PaddleOCR 아키텍처 +

    +
    + +### 🛠️ 개발자 중심 생태계 +* **원활한 통합**: AI 에이전트 생태계의 최고 선택 - **Dify, RAGFlow, Pathway, Cherry Studio**와 깊이 통합되어 있습니다. +* **LLM 데이터 플라이휠**: 고품질 데이터셋 구축을 위한 완전한 파이프라인으로, 대규모 언어 모델 파인튜닝을 위한 지속 가능한 "데이터 엔진"을 제공합니다. +* **원클릭 배포**: 다양한 하드웨어 백엔드(NVIDIA GPU, Intel CPU, Kunlunxin XPU 및 다양한 AI 가속기)를 지원합니다. + + +## 📣 최근 업데이트 + +### 🔥 2026.06.11: PaddleOCR 3.7.0 출시 +- PP-OCRv6 주요 특징: + + - **정확도 향상**: medium 티어가 PP-OCRv5_server 대비 검출 +4.6%, 인식 +5.1% 달성, 34.5M 파라미터만으로 주류 VLM(Qwen3-VL-235B, GPT-5.5)을 능가. + - **50개 언어 통합 지원**: 단일 모델로 중국어, 영어, 일본어 및 46개 라틴 언어를 지원 — 모델 전환 불필요. + - **전문 시나리오 강화**: 디지털 디스플레이, 도트 매트릭스 문자, 타이어 인쇄, 산업용 문자 인식 대폭 향상. + - **빠른 추론**: CPU 5.2배 가속(OpenVINO), Apple M4 6.1배(tiny), A100 GPU 0.13초. + - **전 시나리오 3단계 모델**: tiny(1.5M) / small(7.7M) / medium(34.5M)으로 엣지부터 서버까지 대응. + - **모델 다운로드**: 모든 모델은 [HuggingFace](https://huggingface.co/collections/PaddlePaddle/pp-ocrv6) 및 [ModelScope](https://www.modelscope.cn/collections/PaddlePaddle/PP-OCRv6)에서 제공됩니다. + +
    +2026.05.28: PaddleOCR 3.6.0 출시 + +- PaddleOCR-VL-1.6 주요 특징: + + - **새로운 SOTA 정확도**: OmniDocBench v1.6에서 96.3%, OmniDocBench v1.5 및 Real5-OmniDocBench에서도 새로운 SOTA. + - **종합 강화**: 테이블, 고문서, 희귀 문자 인식 대폭 향상. + - **원활한 마이그레이션**: PaddleOCR-VL-1.5와 완전 호환 아키텍처. + - **지금 사용해보기**: [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6) 또는 [공식 사이트](https://www.paddleocr.com). + +
    +
    +2026.04.21: PaddleOCR 3.5.0 출시 + +* **유연한 추론 백엔드 전환**: Paddle 정적 그래프, Paddle 동적 그래프, Transformers 사이를 원활하게 전환할 수 있습니다. Hugging Face 생태계에 깊이 통합되었으며, 주요 20개 모델이 Transformers를 추론 백엔드로 지원합니다. +* **Office 문서를 Markdown으로 변환**: Word, Excel, PowerPoint 등 일반적인 문서 형식을 Markdown으로 변환할 수 있습니다. +* **분석 결과 DOCX 내보내기**: `PaddleOCR-VL` 시리즈, `PP-StructureV3`, `PP-DocTranslation`이 이제 분석 결과를 DOCX로 내보내 Microsoft Word에서 편리하게 확인하고 편집할 수 있습니다. +* **공식 브라우저 추론 SDK**: 공식 브라우저 추론 SDK `PaddleOCR.js`를 출시하여 브라우저에서 `PP-OCRv5`를 실행할 수 있습니다. + +
    + +
    +2026.01.29: PaddleOCR 3.4.0 출시 +* PaddleOCR-VL-1.5 (최첨단 0.9B VLM): 문서 파싱을 위한 최신 플래그십 모델이 출시되었습니다! + * **OmniDocBench에서 94.5% 정확도**: 최상위 범용 대규모 모델 및 전문 문서 파서를 능가합니다. + * **실제 환경 강건성**: 비정형 형태 위치 지정을 위한 **PP-DocLayoutV3** 알고리즘을 최초 도입하여 *기울기, 왜곡, 스캔, 조명, 화면 촬영*이라는 5가지 까다로운 시나리오를 정복합니다. + * **기능 확장**: **인감 인식**, **텍스트 탐지**를 지원하며, **111개 언어**(중국 티베트 문자 및 벵골어 포함)로 확장되었습니다. + * **장문서 처리**: 자동 교차 페이지 표 병합 및 계층적 제목 식별을 지원합니다. + * **지금 사용해 보세요**: [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.5) 또는 [공식 웹사이트](https://www.paddleocr.com)에서 이용 가능합니다. + +
    + +
    +2025.10.16: PaddleOCR 3.3.0 출시 + +- PaddleOCR-VL 출시: + - **모델 소개**: + - **PaddleOCR-VL**은 문서 파싱에 특화된 최첨단 자원 효율적 모델입니다. 핵심 구성 요소인 PaddleOCR-VL-0.9B는 NaViT 스타일의 동적 해상도 비주얼 인코더와 ERNIE-4.5-0.3B 언어 모델을 통합하여 정확한 요소 인식을 가능하게 하는 컴팩트하면서도 강력한 비전-언어 모델(VLM)입니다. **이 혁신적인 모델은 109개 언어를 효율적으로 지원하며, 최소한의 자원 소비를 유지하면서 복잡한 요소(텍스트, 표, 수식, 차트 등) 인식에 탁월합니다**. 널리 사용되는 공개 벤치마크와 자체 벤치마크를 통한 종합 평가에서 PaddleOCR-VL은 페이지 수준 문서 파싱과 요소 수준 인식 모두에서 최첨단 성능을 달성합니다. 기존 솔루션을 크게 능가하고, 최상위 VLM과의 경쟁에서도 강한 경쟁력을 보이며, 빠른 추론 속도를 제공합니다. 이러한 강점은 실제 시나리오에서의 실용적 배포에 매우 적합합니다. 모델은 [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL)에 공개되었습니다. 누구나 다운로드하여 사용할 수 있습니다! 더 자세한 소개는 [PaddleOCR-VL](https://www.paddleocr.ai/latest/version3.x/algorithm/PaddleOCR-VL/PaddleOCR-VL.html)에서 확인할 수 있습니다. + + - **핵심 기능**: + - **컴팩트하면서도 강력한 VLM 아키텍처**: 자원 효율적 추론에 특화 설계된 새로운 비전-언어 모델을 제시하며, 요소 인식에서 탁월한 성능을 달성합니다. NaViT 스타일의 동적 고해상도 비주얼 인코더와 경량 ERNIE-4.5-0.3B 언어 모델을 통합하여 모델의 인식 능력과 디코딩 효율성을 크게 향상시킵니다. 이러한 통합은 높은 정확도를 유지하면서 연산 요구량을 줄여 효율적이고 실용적인 문서 처리 애플리케이션에 적합합니다. + - **문서 파싱 최첨단 성능**: PaddleOCR-VL은 페이지 수준 문서 파싱과 요소 수준 인식 모두에서 최첨단 성능을 달성합니다. 기존 파이프라인 기반 솔루션을 크게 능가하며, 문서 파싱에서 선도적인 비전-언어 모델(VLM)과 강한 경쟁력을 보입니다. 또한 텍스트, 표, 수식, 차트 등 복잡한 문서 요소 인식에 탁월하여 필기체 텍스트 및 역사 문서를 포함한 다양하고 까다로운 콘텐츠 유형에 적합합니다. 이로 인해 다양한 문서 유형과 시나리오에 폭넓게 활용할 수 있습니다. + - **다국어 지원**: PaddleOCR-VL은 109개 언어를 지원하며, 중국어, 영어, 일본어, 라틴어, 한국어를 포함한(이에 국한되지 않는) 주요 글로벌 언어와 러시아어(키릴 문자), 아랍어, 힌디어(데바나가리 문자), 태국어 등 다양한 문자 체계와 구조를 가진 언어를 포괄합니다. 이러한 광범위한 언어 지원은 다국어 및 글로벌 문서 처리 시나리오에서의 시스템 적용성을 크게 향상시킵니다. + +- PP-OCRv5 다국어 인식 모델 출시: + - 라틴 문자 인식의 정확도와 범위를 개선하였습니다. 키릴 문자, 아랍 문자, 데바나가리 문자, 텔루구 문자, 타밀 문자 및 기타 언어 체계 지원을 추가하여 109개 언어 인식을 지원합니다. 모델 파라미터는 2M에 불과하며, 일부 모델의 정확도는 이전 세대 대비 40% 이상 향상되었습니다. + +
    + + +
    +2025.08.21: PaddleOCR 3.2.0 출시 + +- **주요 모델 추가:** + - PP-OCRv5 인식 모델의 영어, 태국어, 그리스어 학습, 추론 및 배포를 도입하였습니다. **PP-OCRv5 영어 모델은 기존 PP-OCRv5 메인 모델 대비 영어 시나리오에서 11% 향상을 달성했으며, 태국어 및 그리스어 인식 모델은 각각 82.68%와 89.28%의 정확도를 기록하였습니다.** + +- **배포 기능 업그레이드:** + - **PaddlePaddle 프레임워크 버전 3.1.0 및 3.1.1 완전 지원.** + - **PP-OCRv5 C++ 로컬 배포 솔루션 종합 업그레이드, Linux 및 Windows 모두 지원하며 Python 구현과 동일한 기능 및 정확도를 제공합니다.** + - **고성능 추론이 CUDA 12를 지원하며, Paddle Inference 또는 ONNX Runtime 백엔드를 사용하여 추론을 수행할 수 있습니다.** + - **고안정성 서비스 지향 배포 솔루션이 완전히 오픈소스화되어, 사용자가 필요에 따라 Docker 이미지와 SDK를 커스터마이징할 수 있습니다.** + - 고안정성 서비스 지향 배포 솔루션은 수동으로 구성한 HTTP 요청을 통한 호출도 지원하여, 모든 프로그래밍 언어에서 클라이언트 측 코드 개발이 가능합니다. + +- **벤치마크 지원:** + - **모든 프로덕션 라인이 세분화된 벤치마킹을 지원하여, 엔드투엔드 추론 시간은 물론 레이어별 및 모듈별 지연 시간 데이터를 측정하여 성능 분석을 지원합니다. 벤치마크 기능의 설정 및 사용 방법은 [여기](docs/version3.x/pipeline_usage/instructions/benchmark.en.md)를 참조하세요.** + - **문서에 주류 하드웨어에서의 일반 구성에 대한 주요 지표(추론 지연 시간 및 메모리 사용량 등)가 업데이트되어, 사용자에게 배포 참고 자료를 제공합니다.** + +- **버그 수정:** + - 모델 학습 중 로그 저장 실패 문제를 해결하였습니다. + - 수식 모델의 데이터 증강 구성 요소를 업그레이드하여 최신 버전의 albumentations 종속성과의 호환성을 확보하고, 멀티프로세스 시나리오에서 tokenizers 패키지 사용 시 발생하는 데드락 경고를 수정하였습니다. + - PP-StructureV3 구성 파일에서 스위치 동작(예: `use_chart_parsing`)이 다른 파이프라인과 불일치하는 문제를 수정하였습니다. + +- **기타 개선 사항:** + - **핵심 종속성과 선택적 종속성을 분리하였습니다. 기본 텍스트 인식에는 최소한의 핵심 종속성만 필요하며, 문서 파싱 및 정보 추출을 위한 추가 종속성은 필요에 따라 설치할 수 있습니다.** + - **Windows에서 NVIDIA RTX 50 시리즈 그래픽 카드 지원을 활성화하였습니다. 해당 PaddlePaddle 프레임워크 버전은 [설치 가이드](docs/version3.x/installation.en.md)를 참조하세요.** + - **PP-OCR 시리즈 모델이 단일 문자 좌표 반환을 지원합니다.** + - AIStudio, ModelScope 등 모델 다운로드 소스를 추가하여, 사용자가 모델 다운로드 소스를 지정할 수 있습니다. + - PP-Chart2Table 모듈을 통한 차트-표 변환 지원을 추가하였습니다. + - 사용성 향상을 위해 문서 설명을 최적화하였습니다. +
    + + +[변경 이력](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + + +## 🚀 빠른 시작 + +### 1단계: 온라인 체험 +PaddleOCR 공식 웹사이트에서는 별도 설정 없이 클릭 한 번으로 체험할 수 있는 인터랙티브 **체험 센터**와 **API**를 제공합니다. + +👉 [공식 웹사이트 방문](https://www.paddleocr.com) + +### 2단계: 로컬 배포 +로컬 사용을 위해 필요에 따라 다음 문서를 참조하세요: + +- **PP-OCR 시리즈**: [PP-OCR 문서](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/OCR.html) 참조 +- **PaddleOCR-VL 시리즈**: [PaddleOCR-VL 문서](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PaddleOCR-VL.html) 참조 +- **PP-StructureV3**: [PP-StructureV3 문서](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PP-StructureV3.html) 참조 +- **추가 기능**: [추가 기능 문서](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/pipeline_overview.html) 참조 + + +## 🧩 추가 기능 + +- 모델을 ONNX 형식으로 변환: [ONNX 모델 획득](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/others/obtaining_onnx_models.html). +- OpenVINO, ONNX Runtime, TensorRT 등의 엔진을 사용한 추론 가속 또는 ONNX 형식 모델을 사용한 추론: [고성능 추론](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/local_inference/high_performance_inference.html). +- 다중 GPU 및 다중 프로세스를 사용한 추론 가속: [파이프라인 병렬 추론](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/instructions/parallel_inference.html). +- PaddleOCR를 C++, C#, Java 등으로 작성된 애플리케이션에 통합: [서빙](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/serving/serving.html). + +## 🔄 실행 결과 빠른 미리보기 + +### PP-OCRv5 + +
    +

    + PP-OCRv5 데모 +

    +
    + + + +### PP-StructureV3 + +
    +

    + PP-StructureV3 데모 +

    +
    + +### PaddleOCR-VL + +
    +

    + PaddleOCR-VL 데모 +

    +
    + + +## ✨ 소식을 받아보세요 + +⭐ **이 저장소에 Star를 눌러 강력한 OCR 및 문서 파싱 기능을 포함한 흥미로운 업데이트와 새로운 릴리스를 확인하세요!** ⭐ + +
    +

    + 프로젝트에 Star 누르기 +

    +
    + + +## 👩‍👩‍👧‍👦 커뮤니티 + +
    + +| PaddlePaddle 위챗 공식 계정 | 기술 토론 그룹 참여 | +| :---: | :---: | +| | | +
    + + +## 😃 PaddleOCR를 활용한 멋진 프로젝트 +PaddleOCR가 오늘날의 모습을 갖추기까지 놀라운 커뮤니티의 힘이 있었습니다! 💗 오랜 파트너, 새로운 협력자, 그리고 PaddleOCR에 열정을 쏟아주신 모든 분들께 진심으로 감사드립니다 - 여기에 이름이 언급되지 않은 분들까지 포함하여. 여러분의 지원이 우리의 원동력입니다! + +
    + +| 프로젝트 이름 | 설명 | +| ------------ | ----------- | +| [Dify](https://github.com/langgenius/dify) |에이전트 워크플로우 개발을 위한 프로덕션 지원 플랫폼.| +| [RAGFlow](https://github.com/infiniflow/ragflow) |심층 문서 이해 기반 RAG 엔진.| +| [pathway](https://github.com/pathwaycom/pathway) |스트림 처리, 실시간 분석, LLM 파이프라인 및 RAG를 위한 Python ETL 프레임워크.| +| [MinerU](https://github.com/opendatalab/MinerU) |다양한 유형의 문서를 Markdown으로 변환하는 도구.| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |무료, 오픈소스, 일괄 오프라인 OCR 소프트웨어.| +| [cherry-studio](https://github.com/CherryHQ/cherry-studio) |여러 LLM 제공업체를 지원하는 데스크톱 클라이언트.| +| [haystack](https://github.com/deepset-ai/haystack) |커스터마이징 가능한 프로덕션 지원 LLM 애플리케이션 구축을 위한 AI 오케스트레이션 프레임워크.| +| [OmniParser](https://github.com/microsoft/OmniParser) |순수 비전 기반 GUI 에이전트를 위한 화면 파싱 도구.| +| [QAnything](https://github.com/netease-youdao/QAnything) |모든 것에 기반한 질의응답.| +| [더 많은 프로젝트 보기](./awesome_projects.md) | [PaddleOCR 기반 추가 프로젝트](./awesome_projects.md)| +
    + +## 👩‍👩‍👧‍👦 기여자 + +
    + + + +
    + +## 🌟 Star + +
    +

    + 스타 히스토리 +

    +
    + + +## 📄 라이선스 +이 프로젝트는 [Apache 2.0 라이선스](LICENSE)로 배포됩니다. + +## 🎓 인용 + +```bibtex +@misc{cui2025paddleocr30technicalreport, + title={PaddleOCR 3.0 Technical Report}, + author={Cheng Cui and Ting Sun and Manhui Lin and Tingquan Gao and Yubo Zhang and Jiaxuan Liu and Xueqing Wang and Zelun Zhang and Changda Zhou and Hongen Liu and Yue Zhang and Wenyu Lv and Kui Huang and Yichao Zhang and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2507.05595}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2507.05595}, +} + +@misc{cui2025paddleocrvlboostingmultilingualdocument, + title={PaddleOCR-VL: Boosting Multilingual Document Parsing via a 0.9B Ultra-Compact Vision-Language Model}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Handong Zheng and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2510.14528}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2510.14528}, +} + +@misc{cui2026paddleocrvl15multitask09bvlm, + title={PaddleOCR-VL-1.5: Towards a Multi-Task 0.9B VLM for Robust In-the-Wild Document Parsing}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2026}, + eprint={2601.21957}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2601.21957}, +} +``` diff --git a/readme/README_ru.md b/readme/README_ru.md new file mode 100644 index 0000000..b53f168 --- /dev/null +++ b/readme/README_ru.md @@ -0,0 +1,303 @@ +
    +

    + Star-history +

    + + + +

    Ведущий в мире инструментарий OCR и движок Document AI

    + +[English](../README.md) | [简体中文](./README_cn.md) | [繁體中文](./README_tcn.md) | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Français](./README_fr.md) | Русский | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![PyPI Downloads](https://static.pepy.tech/badge/paddleocr)](https://pepy.tech/projects/paddleocr) +[![Used by](https://img.shields.io/badge/Used%20by-6k%2B%20repositories-blue)](https://github.com/PaddlePaddle/PaddleOCR/network/dependents) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + +[![AI Studio](https://img.shields.io/badge/PaddleOCR-_Offiical_Website-1927BA?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAABlBMVEU2P+X///+1KuUwAAAHKklEQVR42u3dS5bjOAwEwALvf2fMavZum6IAImI7b2yYSqU+1Zb//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKCR/+fzly7rD92yVg69xh8zeLwOa5w+ZvFYHtc4ft3ykB++cOm79PAp6YO2z/Ngl4ZO5l+9+yT4QAvLqS748VF33Ylzdvzpl72f6z53YIGJ6SZdPeNHcIwOycaADdLgCSIgAIgCOAACAAykIAEAAEAAFAABCAT+WQuQVgeBqXhXQIQAAYegowLQBpbg3gZGFyAC6vgBQAMREA2/YfDPxyaDQNyTNz+3Zwn5J4ZG7PB2h0kHhi7plPCImmJwkPzO0RMa3OET0i5uGlzHFze0xcu0vE2Dq3J4U2vEPgSaHbFzPNDQAAAAAAAMBNovdw+cP/ny+uaf7w/+eYADy8kE+F4Offdjn6zZXhAXgiA78G4MNNsmnu1Xr7b3mbOL8T5Ja5bw/A35EC2LiWpzt1y9jRugBy30fLg3NvHPvnuZcC2NsCUXA/aRmA89V07Fwgt37uH8deCmBr6N44pP4UgaUATpdA7v/cMbIB8okliY65/SW5HhJ1ehPmM+8edwXgpbu4R88FayR32Y/P7oZZbOx13/Zr//ZHx27bAPnkFoyewYlbAhD3TvBobr95gaUAtr1EdNx1lgI4OcTTuR3z6+FZMEDRcu9ZCuDgGCdyGxMa4EgBRMvcjrkM7NgBZw5c0TwAUWUhZwRXA2xaya65Xa3jO2qYZ8bu2AD5w38tG5V8aZpoGN6Tz0bOfa9bceyWAciTO0jWyO1Tc5cLwJmF/JfPnXVyu3/slgHIg1n79O2O5fZv+1cHV7sC2HYqmUdHysNzX3sVkMcjUK5Gc+dMs28E5bGtm0V3gloBOP9vgZv+4sYn3RUaYFMCol5uN77g6lUApc8pWs69Zn7snS9Z9Q8G0S0AUTVUUTG3A54R1KSvo/diLAv5fKzynZeN6xogC75u93+AtBTA47OlAFSv6qY/vp3DAjD8iv2ZdFYJwKynMhTK1rInPfzaxW81LnvSgFP9KxrATaCLA3DxHpbFX31ZyNm5XRZyXG5bNkAWfP0rcrsUwOgC6NIAzgBcBiqAWwPgLrAGuGBP6jr2sifdfiJ6QQM4Bbw4AK4B3129ZSFn53ZZyA/GyFty27IBFMDFAXAG8PbyLQv5xULGPRl0K3h2AbwcgCZPhs+LD1zLnjS6AN4NwMU/DVFh7LyhASreTbvqrxdr/J4XT4Swz4FrTS+AGJ7bNbwAYkxuWzZAVljHrJfbjb9wviYXwFO/FJ8Vli4vaICsEMFyBbA3tmtsAUS0zG1c/bj4YwsZH2/+Whd0+1Nb+S7IE2sfPw4RL0XmsR8Nqvz7qFngmPHF34EqjP15AAofAkosZKPC/K6FVoeP02Ehi540NG6AK/4pYP3cLgVwXwHkDQ1QcSGb/uF4WwCmfX8u/+4vgLINcMUlQIfcLgXwXAF0+BGkpQDuuJx7/hwgpu//cWVuO3wxJOz/z8297vgYBwaIO3O7Kn+c194578ltywbIgu8fl+Z2lS+APvnLjnOv8hsgSqxjgwL4Ln9LAezaj98tgPzy7ZcC+GQzxrWxXQpgx370dm6/H7v6jaBoso5dY1swAFlwHWvfBf5pxVa93fCtdx64+1dsgCy4joWvAfPX9VoKYMs6Zse9/8Mlvv7LILlhAfKFFdsSutJXAdFkL3qlADJPrXFcXAC5KYaH586jO9mtAch9S3T0GQJ726ZWAE49kjP3rlDJuetdaL/1zeqZY9c7CRz7s0wCUPxienQBnAuAAtAAlxaAAAxfyBQABSAACkAAFIAAKAABUAACMEkKwL170oh7V8ueNLoAjgTAXWAN4BRwcABcA2oABTA4AApAAyiAwQFQABpAAQwOgALQADMWUgCuEmNyu15fSIY3gFPAiwPgFFADKIDBAVAAGkABCIACmBqAUAAaQAHMDUCMWkgBuMWw3K43F5LhDeAU8OIAuAmkARTA4AAoAA2gAARAAUwNgLvAGkABDA6Au8AaoKOJuV0vLSTDG8Ap4MUBcBNIAyiAwQFQABpAAQwOgALQAApAABTA1AC4C6wBOhqb23V+IRneAE4BLw6Aa0ANoAAGB0ABaAAFMDgACkADKAABUABTA+AusAboKATAQs4trjV+IYcfuJYCcA6gAATAQk69dFkKQANYyLkFcLIBFIDLQAVwawDsSRrAEWBwAJwCagAFMDgACkADKIDBAVAAGkABCIACmBoAzwXWAApgcADsSRrg0iNACoACEADXgAIwdCFTACykALgGFIAfl0kBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPBv/gN+IH8U6YveYgAAAABJRU5ErkJggg==&labelColor=white)](https://www.paddleocr.com) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/PaddlePaddle/PaddleOCR) +[![License](https://img.shields.io/badge/license-Apache_2.0-green)](../LICENSE) + +
    + + + + + + +**PaddleOCR преобразует документы и изображения в структурированные данные, готовые для использования с LLM (JSON/Markdown), с точностью мирового уровня. Имея более 70 тысяч звёзд и доверие таких ведущих проектов, как Dify, RAGFlow и Cherry Studio, PaddleOCR является основой для создания интеллектуальных приложений RAG и Agentic.** + + +## 🚀 Ключевые возможности + +### 📄 Интеллектуальный разбор документов (готово для LLM) +> *Преобразование сложных визуальных данных в структурированные данные для эпохи LLM.* + +* **SOTA Document VLM**: Featuring **PaddleOCR-VL-1.6 (0.9B)**, the industry's leading lightweight vision-language model for document parsing. It achieves 96.3% accuracy on OmniDocBench v1.6, leads in text, formula, and table recognition, and shows significantly enhanced capabilities in ancient documents, rare characters, seals, and charts, with structured outputs in **Markdown** and **JSON** formats. +* **Конвертация с учётом структуры**: На основе **PP-StructureV3** — бесшовное преобразование сложных PDF-файлов и изображений в **Markdown** или **JSON**. В отличие от моделей серии PaddleOCR-VL, предоставляет более детальную координатную информацию, включая координаты ячеек таблиц, координаты текста и многое другое. +* **Эффективность промышленного уровня**: Коммерческая точность при минимальном объёме ресурсов. Превосходит многочисленные закрытые решения в публичных тестах, оставаясь ресурсоэффективным для развёртывания на периферийных устройствах и в облаке. + +### 🔍 Универсальное распознавание текста (Scene OCR) +> *Мировой золотой стандарт высокоскоростного многоязычного обнаружения текста.* + +* **Поддержка 100+ языков**: Нативное распознавание обширной глобальной библиотеки. **PP-OCRv6** поддерживает 50 языков единой моделью (китайский, английский, японский и 46 латинских языков) — без переключения моделей. +* **Мастерство работы со сложными элементами**: Помимо стандартного распознавания текста, поддерживается **обнаружение текста в естественных сценах** в широком диапазоне условий, включая удостоверения личности, уличные виды, книги и промышленные компоненты. +* **Скачок производительности**: PP-OCRv6 достигает **+4.6% детекции** и **+5.1% распознавания** по сравнению с PP-OCRv5, превосходя ведущие визуально-языковые модели. Ускорение CPU-инференса в 5.2×. + +
    +

    + PaddleOCR Architecture +

    +
    + +### 🛠️ Экосистема, ориентированная на разработчиков +* **Бесшовная интеграция**: Первый выбор для экосистемы AI Agent — глубокая интеграция с **Dify, RAGFlow, Pathway и Cherry Studio**. +* **Маховик данных для LLM**: Полный конвейер для создания высококачественных наборов данных, обеспечивающий устойчивый «Data Engine» для тонкой настройки больших языковых моделей. +* **Развёртывание в один клик**: Поддержка различных аппаратных бэкендов (NVIDIA GPU, Intel CPU, Kunlunxin XPU и разнообразные AI-ускорители). + + +## 📣 Последние обновления + +### 🔥 2026.06.11: Выпуск PaddleOCR 3.7.0 +- Ключевые особенности PP-OCRv6: + + - **Повышение точности**: уровень medium достигает +4.6% детекции и +5.1% распознавания по сравнению с PP-OCRv5_server, превосходя ведущие VLM (Qwen3-VL-235B, GPT-5.5) с всего 34.5M параметрами. + - **50 языков в одной модели**: единая модель покрывает китайский, английский, японский и 46 латинских языков — без переключения моделей. + - **Специализированные сценарии**: значительные улучшения в распознавании цифровых дисплеев, матричных символов, шинных отпечатков и промышленного текста. + - **Быстрый инференс**: ускорение CPU 5.2× (OpenVINO), 6.1× на Apple M4 (tiny), 0.13с на A100 GPU. + - **Три уровня для всех сценариев**: tiny (1.5M) / small (7.7M) / medium (34.5M) для периферии, мобильных устройств и серверов. + - **Доступность моделей**: Все модели доступны на [HuggingFace](https://huggingface.co/collections/PaddlePaddle/pp-ocrv6) и [ModelScope](https://www.modelscope.cn/collections/PaddlePaddle/PP-OCRv6). + +
    +2026.05.28: Выпуск PaddleOCR 3.6.0 + +- Ключевые особенности PaddleOCR-VL-1.6: + + - **Новая точность SOTA**: 96.3% на OmniDocBench v1.6, новый SOTA на OmniDocBench v1.5 и Real5-OmniDocBench. + - **Всестороннее улучшение**: значительные улучшения в таблицах, древних документах и редких символах. + - **Бесшовная миграция**: архитектура полностью совместима с PaddleOCR-VL-1.5. + - **Попробуйте**: доступно на [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6) или [официальном сайте](https://www.paddleocr.com). + +
    +
    +2026.04.21: Выпуск PaddleOCR 3.5.0 + +* **Гибкое переключение бэкендов инференса**: поддерживается бесшовное переключение между статическим графом Paddle, динамическим графом Paddle и Transformers. PaddleOCR теперь глубоко интегрирован с экосистемой Hugging Face, а 20 ключевых моделей поддерживают Transformers в качестве бэкенда инференса. +* **Преобразование офисных документов в Markdown**: поддерживается преобразование распространённых форматов документов, таких как Word, Excel и PowerPoint, в Markdown. +* **Экспорт результатов разбора в DOCX**: серии `PaddleOCR-VL`, `PP-StructureV3` и `PP-DocTranslation` теперь поддерживают экспорт результатов разбора в формат DOCX для удобного просмотра и редактирования в Microsoft Word. +* **Официальный браузерный SDK инференса**: выпущен официальный браузерный SDK инференса `PaddleOCR.js`, который поддерживает запуск `PP-OCRv5` прямо в браузере. + +
    + +
    +2026.01.29: Выпуск PaddleOCR 3.4.0 +* PaddleOCR-VL-1.5 (SOTA 0.9B VLM): Наша новейшая флагманская модель для разбора документов уже доступна! + * **94,5% точность на OmniDocBench**: Превосходит ведущие универсальные большие модели и специализированные парсеры документов. + * **Устойчивость к реальным условиям**: Первая реализация алгоритма **PP-DocLayoutV3** для позиционирования нестандартных форм, освоившая 5 сложных сценариев: *перекос, деформация, сканирование, неравномерное освещение и фотосъёмка экрана*. + * **Расширение возможностей**: Теперь поддерживается **распознавание печатей**, **обнаружение текста** и расширение до **111 языков** (включая тибетское письмо Китая и бенгальский язык). + * **Работа с длинными документами**: Поддержка автоматического объединения таблиц на нескольких страницах и иерархической идентификации заголовков. + * **Попробуйте сейчас**: Доступно на [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.5) или на нашем [официальном сайте](https://www.paddleocr.com). + +
    + +
    +2025.10.16: Выпуск PaddleOCR 3.3.0 + +- Выпуск PaddleOCR-VL: + - **Описание модели**: + - **PaddleOCR-VL** — это SOTA-модель с эффективным использованием ресурсов, разработанная специально для разбора документов. Её ключевым компонентом является PaddleOCR-VL-0.9B — компактная, но мощная визуально-языковая модель (VLM), объединяющая динамический визуальный энкодер с переменным разрешением в стиле NaViT с языковой моделью ERNIE-4.5-0.3B для точного распознавания элементов. **Эта инновационная модель эффективно поддерживает 109 языков и превосходно справляется с распознаванием сложных элементов (например, текста, таблиц, формул и диаграмм), сохраняя минимальное потребление ресурсов**. По результатам комплексных оценок на широко используемых публичных тестах и внутренних тестах PaddleOCR-VL достигает SOTA-производительности как в разборе документов на уровне страниц, так и в распознавании элементов. Она значительно превосходит существующие решения, демонстрирует высокую конкурентоспособность по сравнению с ведущими VLM и обеспечивает высокую скорость вывода. Эти преимущества делают её высокопригодной для практического развёртывания в реальных сценариях. Модель опубликована на [HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL). Приглашаем всех скачать и использовать! Дополнительная информация доступна в разделе [PaddleOCR-VL](https://www.paddleocr.ai/latest/version3.x/algorithm/PaddleOCR-VL/PaddleOCR-VL.html). + + - **Основные возможности**: + - **Компактная, но мощная архитектура VLM**: Представлена новая визуально-языковая модель, специально разработанная для ресурсоэффективного вывода, достигающая выдающейся производительности в распознавании элементов. Благодаря интеграции динамического высокоразрешающего визуального энкодера в стиле NaViT с лёгкой языковой моделью ERNIE-4.5-0.3B мы значительно повысили возможности распознавания и эффективность декодирования модели. Эта интеграция сохраняет высокую точность при снижении вычислительных требований, что делает её хорошо подходящей для эффективной и практической обработки документов. + - **SOTA-производительность в разборе документов**: PaddleOCR-VL достигает передовой производительности как в разборе документов на уровне страниц, так и в распознавании элементов. Она значительно превосходит существующие конвейерные решения и демонстрирует высокую конкурентоспособность по сравнению с ведущими визуально-языковыми моделями (VLM) в разборе документов. Кроме того, она превосходно справляется с распознаванием сложных элементов документов, таких как текст, таблицы, формулы и диаграммы, что делает её пригодной для широкого спектра сложных типов контента, включая рукописный текст и исторические документы. Это делает её высоко универсальной и подходящей для широкого спектра типов и сценариев документов. + - **Многоязычная поддержка**: PaddleOCR-VL поддерживает 109 языков, охватывая основные мировые языки, включая, но не ограничиваясь китайским, английским, японским, латинским и корейским, а также языки с различными системами письма и структурами, такие как русский (кириллица), арабский, хинди (письмо деванагари) и тайский. Широкий охват языков существенно повышает применимость нашей системы к многоязычным и глобализированным сценариям обработки документов. + +- Выпуск PP-OCRv5 — многоязычной модели распознавания: + - Улучшена точность и охват распознавания латинского письма; добавлена поддержка кириллицы, арабского, деванагари, телугу, тамильского и других языковых систем, охватывающих распознавание 109 языков. Модель имеет всего 2 МБ параметров, а точность некоторых моделей выросла более чем на 40% по сравнению с предыдущим поколением. + +
    + + +
    +2025.08.21: Выпуск PaddleOCR 3.2.0 + +- **Значительные дополнения моделей:** + - Введены обучение, вывод и развёртывание моделей распознавания PP-OCRv5 для английского, тайского и греческого языков. **Модель PP-OCRv5 для английского языка обеспечивает улучшение на 11% в английских сценариях по сравнению с основной моделью PP-OCRv5, при этом модели распознавания тайского и греческого языков достигают точности 82,68% и 89,28% соответственно.** + +- **Улучшения возможностей развёртывания:** + - **Полная поддержка версий фреймворка PaddlePaddle 3.1.0 и 3.1.1.** + - **Комплексное обновление решения для локального развёртывания PP-OCRv5 на C++, теперь поддерживающего как Linux, так и Windows, с полным соответствием функций и идентичной точностью реализации на Python.** + - **Высокопроизводительный вывод теперь поддерживает CUDA 12, а вывод может выполняться с использованием бэкендов Paddle Inference или ONNX Runtime.** + - **Решение для высоконадёжного сервисного развёртывания теперь полностью открыто, позволяя пользователям при необходимости настраивать образы Docker и SDK.** + - Решение для высоконадёжного сервисного развёртывания также поддерживает вызов через вручную сформированные HTTP-запросы, что позволяет разрабатывать клиентский код на любом языке программирования. + +- **Поддержка бенчмарков:** + - **Все производственные конвейеры теперь поддерживают детализированное бенчмаркирование, позволяя измерять сквозное время вывода, а также задержки на уровне отдельных слоёв и модулей для анализа производительности. [Здесь](docs/version3.x/pipeline_usage/instructions/benchmark.en.md) описано, как настроить и использовать функцию бенчмарка.** + - **Документация обновлена и включает ключевые метрики для часто используемых конфигураций на основном оборудовании, такие как задержка вывода и использование памяти, предоставляя справочные данные для развёртывания.** + +- **Исправления ошибок:** + - Устранена проблема с неудачным сохранением журналов во время обучения модели. + - Обновлён компонент аугментации данных для моделей формул для совместимости с более новыми версиями зависимости albumentations, а также исправлены предупреждения о взаимоблокировке при использовании пакета tokenizers в многопроцессорных сценариях. + - Исправлены несоответствия в поведении переключателей (например, `use_chart_parsing`) в файлах конфигурации PP-StructureV3 по сравнению с другими конвейерами. + +- **Прочие улучшения:** + - **Разделены основные и дополнительные зависимости. Для базового распознавания текста требуются только минимальные основные зависимости; дополнительные зависимости для разбора документов и извлечения информации могут быть установлены по мере необходимости.** + - **Включена поддержка видеокарт NVIDIA RTX серии 50 на Windows; пользователи могут обратиться к [руководству по установке](docs/version3.x/installation.en.md) для получения информации о соответствующих версиях фреймворка PaddlePaddle.** + - **Модели серии PP-OCR теперь поддерживают возврат координат отдельных символов.** + - Добавлены источники загрузки моделей AIStudio, ModelScope и другие, позволяющие пользователям указывать источник для загрузки моделей. + - Добавлена поддержка преобразования диаграмм в таблицы через модуль PP-Chart2Table. + - Оптимизированы описания в документации для улучшения удобства использования. +
    + + +[История изменений](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + + +## 🚀 Быстрый старт + +### Шаг 1: Попробуйте онлайн +Официальный сайт PaddleOCR предоставляет интерактивный **Центр опыта** и **API** — без необходимости настройки, просто один клик для ознакомления. + +👉 [Посетить официальный сайт](https://www.paddleocr.com) + +### Шаг 2: Локальное развёртывание +Для локального использования обратитесь к следующей документации в соответствии с вашими потребностями: + +- **Серия PP-OCR**: См. [Документацию PP-OCR](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/OCR.html) +- **Серия PaddleOCR-VL**: См. [Документацию PaddleOCR-VL](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PaddleOCR-VL.html) +- **PP-StructureV3**: См. [Документацию PP-StructureV3](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/PP-StructureV3.html) +- **Дополнительные возможности**: См. [Документацию по дополнительным возможностям](https://www.paddleocr.ai/latest/en/version3.x/pipeline_usage/pipeline_overview.html) + + +## 🧩 Дополнительные возможности + +- Конвертация моделей в формат ONNX: [Получение моделей ONNX](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/others/obtaining_onnx_models.html). +- Ускорение вывода с использованием движков OpenVINO, ONNX Runtime, TensorRT или выполнение вывода с использованием моделей в формате ONNX: [Высокопроизводительный вывод](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/local_inference/high_performance_inference.html). +- Ускорение вывода с использованием нескольких GPU и многопроцессорной обработки: [Параллельный вывод для конвейеров](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/pipeline_usage/instructions/parallel_inference.html). +- Интеграция PaddleOCR в приложения, написанные на C++, C#, Java и др.: [Сервисное развёртывание](https://paddlepaddle.github.io/PaddleOCR/latest/en/version3.x/inference_deployment/serving/serving.html). + +## 🔄 Краткий обзор результатов выполнения + +### PP-OCRv5 + +
    +

    + PP-OCRv5 Demo +

    +
    + + + +### PP-StructureV3 + +
    +

    + PP-StructureV3 Demo +

    +
    + +### PaddleOCR-VL + +
    +

    + PP-StructureV3 Demo +

    +
    + + +## ✨ Следите за обновлениями + +⭐ **Добавьте этот репозиторий в избранное, чтобы быть в курсе захватывающих обновлений и новых выпусков, включая мощные возможности OCR и разбора документов!** ⭐ + +
    +

    + Star-Project +

    +
    + + +## 👩‍👩‍👧‍👦 Сообщество + +
    + +| Официальный аккаунт PaddlePaddle в WeChat | Присоединиться к группе технических обсуждений | +| :---: | :---: | +| | | +
    + + +## 😃 Замечательные проекты, использующие PaddleOCR +PaddleOCR не достиг бы своего нынешнего уровня без своего невероятного сообщества! 💗 Огромная благодарность всем нашим давним партнёрам, новым соавторам и всем, кто вложил свою душу в PaddleOCR — независимо от того, упомянуты вы здесь или нет. Ваша поддержка питает наш огонь! + +
    + +| Название проекта | Описание | +| ------------ | ----------- | +| [Dify](https://github.com/langgenius/dify) |Готовая к производству платформа для разработки агентных рабочих процессов.| +| [RAGFlow](https://github.com/infiniflow/ragflow) |RAG-движок на основе глубокого понимания документов.| +| [pathway](https://github.com/pathwaycom/pathway) |Python ETL-фреймворк для потоковой обработки, аналитики в реальном времени, конвейеров LLM и RAG.| +| [MinerU](https://github.com/opendatalab/MinerU) |Инструмент для конвертации документов различных типов в Markdown.| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |Бесплатное программное обеспечение для пакетного офлайн-OCR с открытым исходным кодом.| +| [cherry-studio](https://github.com/CherryHQ/cherry-studio) |Настольный клиент с поддержкой нескольких провайдеров LLM.| +| [haystack](https://github.com/deepset-ai/haystack) |Фреймворк оркестрации AI для создания настраиваемых, готовых к производству приложений LLM.| +| [OmniParser](https://github.com/microsoft/OmniParser) |OmniParser: инструмент разбора экрана для агента GUI на основе чистого зрения.| +| [QAnything](https://github.com/netease-youdao/QAnything) |Вопросы и ответы на основе чего угодно.| +| [Узнать о других проектах](./awesome_projects.md) | [Другие проекты на основе PaddleOCR](./awesome_projects.md)| +
    + +## 👩‍👩‍👧‍👦 Участники + +
    + + + +
    + +## 🌟 Звёзды + +
    +

    + Star-history +

    +
    + + +## 📄 Лицензия +Этот проект выпущен под лицензией [Apache 2.0](LICENSE). + +## 🎓 Цитирование + +```bibtex +@misc{cui2025paddleocr30technicalreport, + title={PaddleOCR 3.0 Technical Report}, + author={Cheng Cui and Ting Sun and Manhui Lin and Tingquan Gao and Yubo Zhang and Jiaxuan Liu and Xueqing Wang and Zelun Zhang and Changda Zhou and Hongen Liu and Yue Zhang and Wenyu Lv and Kui Huang and Yichao Zhang and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2507.05595}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2507.05595}, +} + +@misc{cui2025paddleocrvlboostingmultilingualdocument, + title={PaddleOCR-VL: Boosting Multilingual Document Parsing via a 0.9B Ultra-Compact Vision-Language Model}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Handong Zheng and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2510.14528}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2510.14528}, +} + +@misc{cui2026paddleocrvl15multitask09bvlm, + title={PaddleOCR-VL-1.5: Towards a Multi-Task 0.9B VLM for Robust In-the-Wild Document Parsing}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2026}, + eprint={2601.21957}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2601.21957}, +} +``` diff --git a/readme/README_tcn.md b/readme/README_tcn.md new file mode 100644 index 0000000..2a1e37e --- /dev/null +++ b/readme/README_tcn.md @@ -0,0 +1,303 @@ +
    +

    + Star-history +

    + + + +

    全球領先的 OCR 工具包與文檔 AI 引擎

    + + +[English](../README.md) | [简体中文](./README_cn.md)| 繁體中文 | [日本語](./README_ja.md) | [한국어](./README_ko.md) | [Français](./README_fr.md) | [Русский](./README_ru.md) | [Español](./README_es.md) | [العربية](./README_ar.md) + + + +[![PyPI Downloads](https://static.pepy.tech/badge/paddleocr)](https://pepy.tech/projects/paddleocr) +[![Used by](https://img.shields.io/badge/Used%20by-6k%2B%20repositories-blue)](https://github.com/PaddlePaddle/PaddleOCR/network/dependents) +![python](https://img.shields.io/badge/python-3.8~3.12-aff.svg) +![os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-pink.svg) +![hardware](https://img.shields.io/badge/hardware-cpu%2C%20gpu%2C%20xpu%2C%20npu-yellow.svg) + +[![AI Studio](https://img.shields.io/badge/PaddleOCR-_Offiical_Website-1927BA?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAMAAADDpiTIAAAABlBMVEU2P+X///+1KuUwAAAHKklEQVR42u3dS5bjOAwEwALvf2fMavZum6IAImI7b2yYSqU+1Zb//gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKCR/+fzly7rD92yVg69xh8zeLwOa5w+ZvFYHtc4ft3ykB++cOm79PAp6YO2z/Ngl4ZO5l+9+yT4QAvLqS748VF33Ylzdvzpl72f6z53YIGJ6SZdPeNHcIwOycaADdLgCSIgAIgCOAACAAykIAEAAEAAFAABCAT+WQuQVgeBqXhXQIQAAYegowLQBpbg3gZGFyAC6vgBQAMREA2/YfDPxyaDQNyTNz+3Zwn5J4ZG7PB2h0kHhi7plPCImmJwkPzO0RMa3OET0i5uGlzHFze0xcu0vE2Dq3J4U2vEPgSaHbFzPNDQAAAAAAAMBNovdw+cP/ny+uaf7w/+eYADy8kE+F4Offdjn6zZXhAXgiA78G4MNNsmnu1Xr7b3mbOL8T5Ja5bw/A35EC2LiWpzt1y9jRugBy30fLg3NvHPvnuZcC2NsCUXA/aRmA89V07Fwgt37uH8deCmBr6N44pP4UgaUATpdA7v/cMbIB8okliY65/SW5HhJ1ehPmM+8edwXgpbu4R88FayR32Y/P7oZZbOx13/Zr//ZHx27bAPnkFoyewYlbAhD3TvBobr95gaUAtr1EdNx1lgI4OcTTuR3z6+FZMEDRcu9ZCuDgGCdyGxMa4EgBRMvcjrkM7NgBZw5c0TwAUWUhZwRXA2xaya65Xa3jO2qYZ8bu2AD5w38tG5V8aZpoGN6Tz0bOfa9bceyWAciTO0jWyO1Tc5cLwJmF/JfPnXVyu3/slgHIg1n79O2O5fZv+1cHV7sC2HYqmUdHysNzX3sVkMcjUK5Gc+dMs28E5bGtm0V3gloBOP9vgZv+4sYn3RUaYFMCol5uN77g6lUApc8pWs69Zn7snS9Z9Q8G0S0AUTVUUTG3A54R1KSvo/diLAv5fKzynZeN6xogC75u93+AtBTA47OlAFSv6qY/vp3DAjD8iv2ZdFYJwKynMhTK1rInPfzaxW81LnvSgFP9KxrATaCLA3DxHpbFX31ZyNm5XRZyXG5bNkAWfP0rcrsUwOgC6NIAzgBcBiqAWwPgLrAGuGBP6jr2sifdfiJ6QQM4Bbw4AK4B3129ZSFn53ZZyA/GyFty27IBFMDFAXAG8PbyLQv5xULGPRl0K3h2AbwcgCZPhs+LD1zLnjS6AN4NwMU/DVFh7LyhASreTbvqrxdr/J4XT4Swz4FrTS+AGJ7bNbwAYkxuWzZAVljHrJfbjb9wviYXwFO/FJ8Vli4vaICsEMFyBbA3tmtsAUS0zG1c/bj4YwsZH2/+Whd0+1Nb+S7IE2sfPw4RL0XmsR8Nqvz7qFngmPHF34EqjP15AAofAkosZKPC/K6FVoeP02Ehi540NG6AK/4pYP3cLgVwXwHkDQ1QcSGb/uF4WwCmfX8u/+4vgLINcMUlQIfcLgXwXAF0+BGkpQDuuJx7/hwgpu//cWVuO3wxJOz/z8297vgYBwaIO3O7Kn+c194578ltywbIgu8fl+Z2lS+APvnLjnOv8hsgSqxjgwL4Ln9LAezaj98tgPzy7ZcC+GQzxrWxXQpgx370dm6/H7v6jaBoso5dY1swAFlwHWvfBf5pxVa93fCtdx64+1dsgCy4joWvAfPX9VoKYMs6Zse9/8Mlvv7LILlhAfKFFdsSutJXAdFkL3qlADJPrXFcXAC5KYaH586jO9mtAch9S3T0GQJ726ZWAE49kjP3rlDJuetdaL/1zeqZY9c7CRz7s0wCUPxienQBnAuAAtAAlxaAAAxfyBQABSAACkAAFIAAKAABUAACMEkKwL170oh7V8ueNLoAjgTAXWAN4BRwcABcA2oABTA4AApAAyiAwQFQABpAAQwOgALQADMWUgCuEmNyu15fSIY3gFPAiwPgFFADKIDBAVAAGkABCIACmBqAUAAaQAHMDUCMWkgBuMWw3K43F5LhDeAU8OIAuAmkARTA4AAoAA2gAARAAUwNgLvAGkABDA6Au8AaoKOJuV0vLSTDG8Ap4MUBcBNIAyiAwQFQABpAAQwOgALQAApAABTA1AC4C6wBOhqb23V+IRneAE4BLw6Aa0ANoAAGB0ABaAAFMDgACkADKAABUABTA+AusAboKATAQs4trjV+IYcfuJYCcA6gAATAQk69dFkKQANYyLkFcLIBFIDLQAVwawDsSRrAEWBwAJwCagAFMDgACkADKIDBAVAAGkABCIACmBoAzwXWAApgcADsSRrg0iNACoACEADXgAIwdCFTACykALgGFIAfl0kBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPBv/gN+IH8U6YveYgAAAABJRU5ErkJggg==&labelColor=white)](https://www.paddleocr.com) +[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/PaddlePaddle/PaddleOCR) +[![License](https://img.shields.io/badge/license-Apache_2.0-green)](../LICENSE) + +
    + + + + +**PaddleOCR 以業界領先的精準度,將 PDF 文件和圖像轉換為結構化、LLM 友好的資料格式(JSON/Markdown)。憑藉 70,000+ Stars 的成績,PaddleOCR 已獲得 Dify、RAGFlow、Cherry Studio 等頂級專案的廣泛信賴,是建構智慧 RAG 和 Agentic 應用的核心基礎元件。** + + +## 🚀 核心特性 + +### 📄 智能文檔解析(面向大模型) +> *為大模型時代將雜亂的文檔視覺信息轉化為結構化數據。* + +* **SOTA 級文檔視覺語言模型 (VLM)**: 業界領先的輕量級文檔解析視覺語言模型 **PaddleOCR-VL-1.6 (0.9B)**。該模型以 96.3% 精度刷新 OmniDocBench v1.6,文本、公式、表格識別全面領先,並在古籍、生僻字、印章、圖表等多場景能力顯著增強,支持以 **Markdown** 和 **JSON** 格式輸出結構化結果。 +* **版面結構分析**:由**PP-StructureV3**驅動,無縫將複雜的PDF和圖像轉換為**Markdown**或**JSON**格式。與PaddleOCR-VL系列模型不同,它提供更細粒度的坐標信息,包括表格單元格坐標、文本坐標等, +* **生產級高效能**:以極小的模型體積實現商業級別的準確率。在公開基準測試中超越眾多閉源解決方案,同時保持極高的資源利用率,完美適配邊緣計算與雲端部署。 + +### 🔍 通用文本識別(場景 OCR) +> *快速、多語言文本檢測與識別的全球黃金標準。* + +* **支持 100+ 種語言**:原生支持龐大豐富的全球語種庫。**PP-OCRv6** 單模型統一支持 50 種語言(中、英、日及 46 種拉丁語系),無需切換模型即可應對多語言混合排版文檔。 +* **複雜場景支持**:除了標準的文本識別,我們還支持在各種廣泛的環境下進行**自然場景文本檢測與識別**,涵蓋身份證件、街景、書籍以及工業零部件等。 +* **性能提升**:PP-OCRv6 相比 PP-OCRv5 檢測精度提升 **4.6%**、識別精度提升 **5.1%**,超越主流視覺語言大模型,CPU 推理加速 5.2×。 + +
    +

    + PaddleOCR Architecture +

    +
    + +### 🛠️ 以開發者為中心的生態系統 +* **無縫集成**:AI智能體生態系統的首選——與**Dify、RAGFlow、Pathway和Cherry Studio**深度集成。 +* **大語言模型數據飛輪**:完整的數據流水線,用於構建高質量數據集,為微調大語言模型提供可持續的"數據引擎"。 +* **一鍵部署**:支持多種硬件後端(NVIDIA GPU、Intel CPU、昆侖芯XPU和多種AI加速器)。 + + +## 📣 最新動態 + +### 🔥 2026.06.11: PaddleOCR 3.7.0 發布 +- PP-OCRv6 核心亮點如下: + + - **精度全面提升**:medium 檔相比 PP-OCRv5_server 檢測精度提升 4.6%、識別精度提升 5.1%,以僅 34.5M 參數超越 Qwen3-VL-235B、GPT-5.5 等主流視覺語言大模型。 + - **50 種語言統一支持**:單一模型覆蓋中文、英文、日文及 46 種拉丁語系語言,無需為不同語種切換模型。 + - **專業場景增強**:數碼顯示屏、點陣字符、輪胎印字、工業字符等傳統 VLM 難以覆蓋的場景識別能力大幅提升。 + - **推理速度更快**:medium 檔 CPU OpenVINO 推理加速 5.2×,tiny 檔 Apple M4 加速 6.1×,A100 上僅需 0.13s。 + - **三檔模型覆蓋全場景**:tiny(1.5M)/ small(7.7M)/ medium(34.5M)分別面向端側/移動端/服務端部署。 + - **模型取得**:所有模型均可透過 [HuggingFace](https://huggingface.co/collections/PaddlePaddle/pp-ocrv6) 和 [ModelScope](https://www.modelscope.cn/collections/PaddlePaddle/PP-OCRv6) 取得。 + +
    +2026.05.28: PaddleOCR 3.6.0 發布 + +- PaddleOCR-VL-1.6 核心亮點: + + - **全新 SOTA 精度**:OmniDocBench v1.6 突破 96.3%,OmniDocBench v1.5 和 Real5-OmniDocBench 同步刷新 SOTA。 + - **能力全面升級**:表格、古籍、生僻字識別大幅提升。 + - **無縫遷移**:模型結構與 PaddleOCR-VL-1.5 完全一致,零成本適配。 + - **立即試用**:[HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.6) 或[官方網站](https://www.paddleocr.com)。 + +
    +
    +2026.04.21: PaddleOCR 3.5.0 發布 + +* **推理後端靈活切換**:支持在飛槳靜態圖、飛槳動態圖和 Transformers 之間無縫切換。深度適配 Hugging Face 生態,20 個主要模型支持以 Transformers 作為推理後端。 +* **常見文檔格式轉 Markdown**:支持將 Word、Excel、PowerPoint 等常見文檔格式轉換為 Markdown。 +* **解析結果導出 DOCX**:`PaddleOCR-VL` 系列、`PP-StructureV3` 和 `PP-DocTranslation` 現已支持將解析結果導出為 DOCX,便於在 Microsoft Word 中查看和編輯。 +* **官方瀏覽器推理 SDK**:發布官方瀏覽器推理 SDK `PaddleOCR.js`,支持在瀏覽器中運行 `PP-OCRv5`。 + +
    + +
    +2026.01.29: PaddleOCR 3.4.0 發布 +* PaddleOCR-VL-1.5 (SOTA 0.9B VLM):我們最新的旗艦文檔解析模型現已上線! + * **OmniDocBench 94.5%準確率**:超越頂級通用大模型和專業文檔解析模型。 + * **現實5大場景文檔解析的SOTA性能**:首次引入**PP-DocLayoutV3**算法進行不規則形狀定位,掌控5種艱難場景:傾斜、彎曲、掃描、光照和屏幕拍照。 + * **能力拓展**:現已支持**印章識別**、**文本識別**,並擴展至**111種語言**(包括中國的藏文和孟加拉語)。 + * **長文檔跨頁解析**:支持自動跨頁表格合併和分層標題識別。 + * **立即試用**:可在[HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL-1.5)或我們的[官方網站](https://www.paddleocr.com)使用。 + +
    + +
    +2025.10.16: PaddleOCR 3.3.0 發布 + +- **發布PaddleOCR-VL**: + - **模型介紹**: + - **PaddleOCR-VL** 是一款先進、高效的文檔解析模型,專為文檔中的元素識別設計。其核心組件為 PaddleOCR-VL-0.9B,這是一種緊湊而強大的視覺語言模型(VLM),它由 NaViT 風格的動態分辨率視覺編碼器與 ERNIE-4.5-0.3B 語言模型組成,能夠實現精準的元素識別。**該模型支持 109 種語言,並在識別複雜元素(如文本、表格、公式和圖表)方面表現出色,同時保持極低的資源消耗。通過在廣泛使用的公開基準與內部基準上的全面評測,PaddleOCR-VL 在頁級級文檔解析與元素級識別均達到 SOTA 表現**。它顯著優於現有的基於Pipeline方案和文檔解析多模態方案以及先進的通用多模態大模型,並具備更快的推理速度。這些優勢使其非常適合在真實場景中落地部署。模型已發布至[HuggingFace](https://huggingface.co/PaddlePaddle/PaddleOCR-VL),歡迎大家下載使用!更多介紹內容請點擊[PaddleOCR-VL](https://www.paddleocr.ai/latest/version3.x/algorithm/PaddleOCR-VL/PaddleOCR-VL.html)。 + + - **特性**: + - **緊湊而強大的視覺語言模型架構**:我們提出了一種新的視覺語言模型,專為資源高效的推理而設計,在元素識別方面表現出色。通過將NaViT風格的動態高分辨率視覺編碼器與輕量級的ERNIE-4.5-0.3B語言模型結合,我們顯著增強了模型的識別能力和解碼效率。這種集成在保持高準確率的同時降低了計算需求,使其非常適合高效且實用的文檔處理應用。 + - **文檔解析的SOTA性能**:PaddleOCR-VL在頁面級文檔解析和元素級識別中達到了最先進的性能。它顯著優於現有的基於流水線的解決方案,並在文檔解析中展現出與領先的視覺語言模型(VLMs)競爭的強勁實力。此外,它在識別複雜的文檔元素(如文本、表格、公式和圖表)方面表現出色,使其適用於包括手寫文本和歷史文獻在內的各種具有挑戰性的內容類型。這使得它具有高度的多功能性,適用於廣泛的文檔類型和場景。 + - **多語言支持**:PaddleOCR-VL支持109種語言,覆蓋了主要的全球語言,包括但不限於中文、英文、日文、拉丁文和韓文,以及使用不同文字和結構的語言,如俄語(西里爾字母)、阿拉伯語、印地語(天城文)和泰語。這種廣泛的語言覆蓋大大增強了我們系統在多語言和全球化文檔處理場景中的適用性。 + +- **發布PP-OCRv5小語種識別模型**: + - 優化拉丁文識別的準度和廣度,新增西里爾文、阿拉伯文、天城文、泰盧固語、泰米爾語等語系,覆蓋109種語言文字的識別。模型參數量僅為2M,部分模型精度較上一代提升40%以上。 + +
    + + +
    +2025.08.21: PaddleOCR 3.2.0 發布 + +- **重要模型新增:** + - 新增 PP-OCRv5 英文、泰文、希臘文識別模型的訓練、推理、部署。**其中 PP-OCRv5 英文模型較 PP-OCRv5 主模型在英文場景提升 11%,泰文識別模型精度 82.68%,希臘文識別模型精度 89.28%。** + +- **部署能力升級:** + - **全面支持飛槳框架 3.1.0 和 3.1.1 版本。** + - **全面升級 PP-OCRv5 C++ 本地部署方案,支持 Linux、Windows,功能及精度效果與 Python 方案保持一致。** + - **高性能推理支持 CUDA 12,可使用 Paddle Inference、ONNX Runtime 後端推理。** + - **高穩定性服務化部署方案全面開源,支持用戶根據需求對 Docker 鏡像和 SDK 進行定制化修改。** + - 高穩定性服務化部署方案支持通過手動構造HTTP請求的方式調用,該方式允許客戶端代碼使用任意編程語言編寫。 + +- **Benchmark支持**: + - **全部產線支持產線細粒度 benchmark,能夠測量產線端到端推理時間以及逐層、逐模塊的耗時數據,可用於輔助產線性能分析。可以參考[文檔](../docs/version3.x/pipeline_usage/instructions/benchmark.md)來進行性能測試。** + - **文檔中補充各產線常用配置在主流硬件上的關鍵指標,包括推理耗時和內存佔用等,為用戶部署提供參考。** + +- **Bug修復:** + - 修復模型訓練時訓練日誌保存失敗的問題。 + - 對公式模型的數據增強部分進行了版本兼容性升級,以適應新版本的 albumentations 依賴,並修復了在多進程使用 tokenizers 依賴包時出現的死鎖警告。 + - 修復 PP-StructureV3 配置文件中的 `use_chart_parsing` 等開關行為與其他產線不統一的問題。 + +- **其他升級:** + - **分離必要依賴與可選依賴。使用基礎文字識別功能時,僅需安裝少量核心依賴;若需文檔解析、信息抽取等功能,用戶可按需選擇安裝額外依賴。** + - **支持 Windows 用戶使用英偉達 50 系顯卡,可根據 [安裝文檔](../docs/version3.x/installation.md) 安裝對應版本的 paddle 框架。** + - **PP-OCR 系列模型支持返回單文字坐標。** + - 模型新增 AIStudio、ModelScope 等下載源。可指定相關下載源下載對應的模型。 + - 支持圖表轉表 PP-Chart2Table 單功能模塊推理能力。 + - 優化部分使用文檔中的描述,提升易用性。 +
    + + +[歷史日誌](https://paddlepaddle.github.io/PaddleOCR/latest/en/update/update.html) + + +## 🚀 快速開始 + +### 步驟 1: 在線體驗 +PaddleOCR官方網站提供交互式**體驗中心**和**APIs**——無需設置,一鍵體驗。 + +👉 [訪問官方網站](https://www.paddleocr.com) + +### 步驟 2: 本地部署 +對於本地使用,請根據您的需求參考以下文檔: + +- **PP-OCR系列**:查看[PP-OCR文檔](https://www.paddleocr.ai/latest/version3.x/pipeline_usage/OCR.html) +- **PaddleOCR-VL系列**:查看[PaddleOCR-VL文檔](https://www.paddleocr.ai/latest/version3.x/pipeline_usage/PaddleOCR-VL.html) +- **PP-StructureV3**:查看[PP-StructureV3文檔](https://www.paddleocr.ai/latest/version3.x/pipeline_usage/PP-StructureV3.html) +- **更多能力**:查看[更多能力文檔](https://www.paddleocr.ai/latest/version3.x/pipeline_usage/pipeline_overview.html) + + +## 🧩 更多功能 + +- 將模型轉換為ONNX格式: [獲取ONNX模型](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/inference_deployment/others/obtaining_onnx_models.html)。 +- 使用OpenVINO、ONNX Runtime、TensorRT等引擎加速推理,或使用ONNX格式模型進行推理: [高性能推理](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/inference_deployment/local_inference/high_performance_inference.html)。 +- 使用多GPU和多進程加速推理: [流水線並行推理](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/pipeline_usage/instructions/parallel_inference.html)。 +- 將PaddleOCR集成到C++、C#、Java等語言編寫的應用程序中: [服務化部署](https://paddlepaddle.github.io/PaddleOCR/latest/version3.x/inference_deployment/serving/serving.html)。 + +## 🔄 執行結果快速預覽 + +### PP-OCRv5 + +
    +

    + PP-OCRv5 Demo +

    +
    + + + +### PP-StructureV3 + +
    +

    + PP-StructureV3 Demo +

    +
    + +### PaddleOCR-VL + +
    +

    + PP-StructureV3 Demo +

    +
    + + +## ✨ 保持關注 + +⭐ **收藏本倉庫,持續關注最新動態與版本發布,包括強大的 OCR 及文檔解析等新功能特性。** ⭐ + +
    +

    + Star-Project +

    +
    + + +## 👩‍👩‍👧‍👦 社區 + +
    + +| PaddlePaddle 微信公眾號 | 加入技術討論群 | +| :---: | :---: | +| | | +
    + + +## 😃 使用 PaddleOCR 的優秀項目 + +
    + +PaddleOCR 的發展離不開社區貢獻!💗衷心感謝所有開發者、合作夥伴與貢獻者! +| 項目名稱 | 簡介 | +| ------------ | ----------- | +| [RAGFlow](https://github.com/infiniflow/ragflow) |基於RAG的AI工作流引擎| +| [pathway](https://github.com/pathwaycom/pathway) |用於流處理、實時分析、LLM流水線和RAG的Python ETL框架| +| [MinerU](https://github.com/opendatalab/MinerU) |多類型文檔轉換Markdown工具| +| [Umi-OCR](https://github.com/hiroi-sora/Umi-OCR) |開源批量離線OCR軟件| +| [cherry-studio](https://github.com/CherryHQ/cherry-studio) |一個支持多個LLM提供商的桌面客戶端| +| [OmniParser](https://github.com/microsoft/OmniParser) |基於純視覺的GUI智能體屏幕解析工具| +| [QAnything](https://github.com/netease-youdao/QAnything) |基於任意內容的問答系統| +| [PDF-Extract-Kit](https://github.com/opendatalab/PDF-Extract-Kit) |高效複雜PDF文檔提取工具包| +| [Dango-Translator](https://github.com/PantsuDango/Dango-Translator) |屏幕實時翻譯工具| +| [更多項目](../awesome_projects.md) | [更多基於PaddleOCR的項目](../awesome_projects.md) | +
    + + +## 👩‍👩‍👧‍👦 貢獻者 + +
    + + + +
    + +## 🌟 Star歷史 + +
    +

    + Star-history +

    +
    + + +## 📄 許可證 +本項目採用[Apache 2.0許可證](LICENSE)發布。 + +## 🎓 引用 + +```bibtex +@misc{cui2025paddleocr30technicalreport, + title={PaddleOCR 3.0 Technical Report}, + author={Cheng Cui and Ting Sun and Manhui Lin and Tingquan Gao and Yubo Zhang and Jiaxuan Liu and Xueqing Wang and Zelun Zhang and Changda Zhou and Hongen Liu and Yue Zhang and Wenyu Lv and Kui Huang and Yichao Zhang and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2507.05595}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2507.05595}, +} + +@misc{cui2025paddleocrvlboostingmultilingualdocument, + title={PaddleOCR-VL: Boosting Multilingual Document Parsing via a 0.9B Ultra-Compact Vision-Language Model}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Handong Zheng and Jing Zhang and Jun Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2025}, + eprint={2510.14528}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2510.14528}, +} + +@misc{cui2026paddleocrvl15multitask09bvlm, + title={PaddleOCR-VL-1.5: Towards a Multi-Task 0.9B VLM for Robust In-the-Wild Document Parsing}, + author={Cheng Cui and Ting Sun and Suyin Liang and Tingquan Gao and Zelun Zhang and Jiaxuan Liu and Xueqing Wang and Changda Zhou and Hongen Liu and Manhui Lin and Yue Zhang and Yubo Zhang and Yi Liu and Dianhai Yu and Yanjun Ma}, + year={2026}, + eprint={2601.21957}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2601.21957}, +} +``` diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5d4f463 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,18 @@ +shapely +scikit-image +pyclipper +lmdb; python_version >= "3.9" +lmdb<1.5; python_version < "3.9" +tqdm +numpy +rapidfuzz +opencv-python +opencv-contrib-python +cython +Pillow +pyyaml +requests +albumentations +# to be compatible with albumentations +albucore +packaging diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f1e3f1b --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from setuptools import setup + + +setup() diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 0000000..8b9729f --- /dev/null +++ b/skills/README.md @@ -0,0 +1,19 @@ +# PaddleOCR Agent Skills + +English | [中文](./README_cn.md) + +This directory contains the official PaddleOCR Agent Skills. + +## Included Skills + +- `paddleocr-text-recognition`: text recognition for images and PDF files +- `paddleocr-doc-parsing`: document parsing and structured output + +## Documentation + +For installation, configuration, usage examples, and local verification, see the [documentation](../docs/version3.x/integrations/skills.en.md). + +## Directory Layout + +- `paddleocr-text-recognition/`: text recognition skill +- `paddleocr-doc-parsing/`: document parsing skill diff --git a/skills/README_cn.md b/skills/README_cn.md new file mode 100644 index 0000000..8b373e8 --- /dev/null +++ b/skills/README_cn.md @@ -0,0 +1,19 @@ +# PaddleOCR Agent Skills + +中文 | [English](./README.md) + +本目录包含 PaddleOCR 官方提供的 Agent Skills。 + +## Skills 列表 + +- `paddleocr-text-recognition`:用于图片和 PDF 文字识别。 +- `paddleocr-doc-parsing`:用于文档解析与结构化输出。 + +## 详细文档 + +安装、配置、使用示例和本地验证说明,请参考 [文档](../docs/version3.x/integrations/skills.md)。 + +## 目录说明 + +- `paddleocr-text-recognition/`:文字识别 Skill +- `paddleocr-doc-parsing/`:文档解析 Skill diff --git a/skills/paddleocr-doc-parsing/SKILL.md b/skills/paddleocr-doc-parsing/SKILL.md new file mode 100644 index 0000000..66e8cba --- /dev/null +++ b/skills/paddleocr-doc-parsing/SKILL.md @@ -0,0 +1,138 @@ +--- +name: paddleocr-doc-parsing +description: >- + Use this skill to extract structured Markdown/JSON from PDFs and document images—tables with + cell-level precision, formulas as LaTeX, figures, seals, charts, headers/footers, multi-column + layout and correct reading order. + Trigger terms: 文档解析, 版面分析, 版面还原, 表格提取, 公式识别, 多栏排版, 扫描件结构化, + 发票, 财报, 复杂 PDF, PDF转Markdown, 图表, 阅读顺序; reading order, formula, LaTeX, + layout parsing, structure extraction, PP-StructureV3, PaddleOCR-VL. +license: Apache-2.0 +metadata: + openclaw: + requires: + env: + - PADDLEOCR_ACCESS_TOKEN + bins: + - paddleocr + primaryEnv: PADDLEOCR_ACCESS_TOKEN + emoji: "📄" + install: + - kind: uv + package: paddleocr + bins: [paddleocr] +--- + +# PaddleOCR Document Parsing + +## When to Use This Skill + +**Use this skill for**: + +- Documents with tables (invoices, financial reports, spreadsheets) +- Documents with mathematical formulas (academic papers, scientific documents) +- Documents with charts and diagrams +- Multi-column layouts (newspapers, magazines, brochures) +- Complex document structures requiring layout analysis + +## Usage + +### Basic Document Parsing + +From URL: + +```bash +paddleocr api \ + --model_type doc_parsing \ + --file_url "https://example.com/report.pdf" +``` + +From local file: + +```bash +paddleocr api \ + --model_type doc_parsing \ + --file_path "./document.pdf" +``` + +### Common Options + +```bash +# With specific model +paddleocr api \ + --model_type doc_parsing \ + --model PP-StructureV3 \ + --file_path "./report.pdf" + +# Disable preprocessing (faster, for flat/well-oriented images) +paddleocr api \ + --model_type doc_parsing \ + --file_path "./document.pdf" \ + --use_doc_unwarping False \ + --use_doc_orientation_classify False + +# With page ranges +paddleocr api \ + --model_type doc_parsing \ + --file_path "./large.pdf" \ + --page_ranges "1-5,10,15-20" + +# Save result and resources +paddleocr api \ + --model_type doc_parsing \ + --file_url "https://..." \ + --output result.json \ + --save_resources ./resources + +# Prettify markdown output +paddleocr api \ + --model_type doc_parsing \ + --file_path "./document.pdf" \ + --prettify_markdown True +``` + +### Output Format + +```json +{ + "jobId": "job-xxx", + "pages": [ + { + "markdownText": "# Title\n\nContent...", + "markdownImages": { + "img1": "https://...", + "img2": "https://..." + }, + "outputImages": { + "layout1": "https://..." + } + } + ] +} +``` + +## Important Notes + +**Preprocessing options**: For flat, well-oriented images (screenshots, properly scanned documents), you can disable preprocessing for faster results: + +```bash +paddleocr api --model_type doc_parsing --file_path "./document.pdf" --use_doc_unwarping False --use_doc_orientation_classify False +``` + +Keep preprocessing enabled when: +- The input is a photo of a curved or folded document +- The document has significant perspective distortion +- Orientation is uncertain (rotated 90/180/270 degrees) + +**Display complete results**: Always show the full extracted content to users. Do not truncate with "..." unless content exceeds 10,000 characters. When multiple pages are processed, summarize if needed but provide complete results when explicitly requested. + +**Handle errors gracefully**: When the CLI returns an error, inform the user of the specific issue rather than silently failing. Common errors: +- Authentication: `PADDLEOCR_ACCESS_TOKEN` invalid or missing +- Quota: API rate limit exceeded +- No content detected: Document may be blank or contain no extractable text + +## CLI Reference + +Run `paddleocr api --help` for all options. + +For full documentation, see: [PaddleOCR Official Documentation](https://www.paddleocr.ai/latest/en/version3.x/inference_deployment/serving/paddleocr_official_api/cli.html) diff --git a/skills/paddleocr-text-recognition/SKILL.md b/skills/paddleocr-text-recognition/SKILL.md new file mode 100644 index 0000000..f9f792c --- /dev/null +++ b/skills/paddleocr-text-recognition/SKILL.md @@ -0,0 +1,130 @@ +--- +name: paddleocr-text-recognition +description: >- + Use this skill whenever the user wants text extracted from images, photos, scans, screenshots, + or scanned PDFs. Returns exact machine-readable strings with line-level text and optional bbox + coordinates. Strong accuracy for CJK, small print, and handwritten text. + Trigger terms: OCR, 文字识别, 图片转文字, 截图识字, 提取图中文字, 扫描识字, 识字, 纯文字, + plain text extraction, 坐标, 检测框, bbox, bounding box, image to text, screenshot, photo scan, + recognize text. +license: Apache-2.0 +metadata: + openclaw: + requires: + env: + - PADDLEOCR_ACCESS_TOKEN + bins: + - paddleocr + primaryEnv: PADDLEOCR_ACCESS_TOKEN + emoji: "🔤" + install: + - kind: uv + package: paddleocr + bins: [paddleocr] +--- + +# PaddleOCR Text Recognition + +## When to Use This Skill + +**Use this skill for**: + +- Extract text from images (screenshots, photos, scans) +- Extract text from PDFs or document images when the goal is **line/box-level text** +- Extract text from URLs or local files that point to images/PDFs + +**Do not use for**: + +- Documents with tables, formulas, charts, or complex layouts — use Document Parsing instead + +## Usage + +### Basic OCR + +From URL: + +```bash +paddleocr api \ + --model_type ocr \ + --file_url "https://example.com/image.png" +``` + +From local file: + +```bash +paddleocr api \ + --model_type ocr \ + --file_path "./document.pdf" +``` + +### Common Options + +```bash +# With specific model +paddleocr api \ + --model_type ocr \ + --model PP-OCRv5 \ + --file_path "./report.pdf" + +# Disable preprocessing (faster, for flat/well-oriented images) +paddleocr api \ + --model_type ocr \ + --file_path "./document.pdf" \ + --use_doc_unwarping False \ + --use_doc_orientation_classify False + +# Save result to file +paddleocr api \ + --model_type ocr \ + --file_url "https://..." \ + --output result.json + +# Page ranges +paddleocr api \ + --model_type ocr \ + --file_path "./large.pdf" \ + --page_ranges "1-5,10,15-20" +``` + +### Output Format + +```json +{ + "jobId": "job-xxx", + "pages": [ + { + "prunedResult": { + "rec_texts": ["Line 1", "Line 2"], + "rec_scores": [0.98, 0.95] + }, + "ocrImageUrl": "https://..." + } + ] +} +``` + +## Important Notes + +**Preprocessing options**: By default, the API enables document preprocessing (unwarping and orientation classification). For flat, well-oriented images (screenshots, properly scanned documents), you can disable preprocessing for faster results: + +```bash +paddleocr api --model_type ocr --file_path "./document.pdf" --use_doc_unwarping False --use_doc_orientation_classify False +``` + +Keep preprocessing enabled when: +- The input is a photo of a curved or folded document +- The document has significant perspective distortion +- Orientation is uncertain (rotated 90/180/270 degrees) + +**Display complete results**: Always show the full extracted content to users. Do not truncate with "..." unless content exceeds 10,000 characters. When multiple pages are processed, summarize if needed but provide complete results when explicitly requested. + +**Handle errors gracefully**: When the CLI returns an error, inform the user of the specific issue rather than silently failing or falling back to your own vision capabilities. Common errors: +- Authentication: `PADDLEOCR_ACCESS_TOKEN` invalid or missing +- Quota: API rate limit exceeded +- No content detected: Image may be blank or contain no text + +## CLI Reference + +Run `paddleocr api --help` for all options. + +For full documentation, see: [PaddleOCR Official Documentation](https://www.paddleocr.ai/latest/en/version3.x/inference_deployment/serving/paddleocr_official_api/cli.html) diff --git a/test_tipc/benchmark_train.sh b/test_tipc/benchmark_train.sh new file mode 100644 index 0000000..1668e41 --- /dev/null +++ b/test_tipc/benchmark_train.sh @@ -0,0 +1,294 @@ +#!/bin/bash +source test_tipc/common_func.sh + +# run benchmark sh +# Usage: +# bash run_benchmark_train.sh config.txt params +# or +# bash run_benchmark_train.sh config.txt + +function func_parser_params(){ + strs=$1 + IFS="=" + array=(${strs}) + tmp=${array[1]} + echo ${tmp} +} + +function set_dynamic_epoch(){ + string=$1 + num=$2 + _str=${string:1:6} + IFS="C" + arr=(${_str}) + M=${arr[0]} + P=${arr[1]} + ep=`expr $num \* $M \* $P` + echo $ep +} + +function func_sed_params(){ + filename=$1 + line=$2 + param_value=$3 + params=`sed -n "${line}p" $filename` + IFS=":" + array=(${params}) + key=${array[0]} + value=${array[1]} + + new_params="${key}:${param_value}" + IFS=";" + cmd="sed -i '${line}s/.*/${new_params}/' '${filename}'" + eval $cmd +} + +function set_gpu_id(){ + string=$1 + _str=${string:1:6} + IFS="C" + arr=(${_str}) + M=${arr[0]} + P=${arr[1]} + gn=`expr $P - 1` + gpu_num=`expr $gn / $M` + seq=`seq -s "," 0 $gpu_num` + echo $seq +} + +function get_repo_name(){ + IFS=";" + cur_dir=$(pwd) + IFS="/" + arr=(${cur_dir}) + echo ${arr[-1]} +} + +FILENAME=$1 +# copy FILENAME as new +new_filename="./test_tipc/benchmark_train.txt" +cmd=`yes|cp $FILENAME $new_filename` +FILENAME=$new_filename +# MODE must be one of ['benchmark_train'] +MODE=$2 +PARAMS=$3 + +to_static="" +# parse "to_static" options and modify trainer into "to_static_trainer" +if [[ $PARAMS =~ "dynamicTostatic" ]] ;then + to_static="d2sT_" + sed -i 's/trainer:norm_train/trainer:to_static_train/g' $FILENAME + # clear PARAM contents + if [ $PARAMS = "to_static" ] ;then + PARAMS="" + fi +fi +# bash test_tipc/benchmark_train.sh test_tipc/configs/det_mv3_db_v2_0/train_benchmark.txt benchmark_train dynamic_bs8_fp32_DP_N1C8 +# bash test_tipc/benchmark_train.sh test_tipc/configs/det_mv3_db_v2_0/train_benchmark.txt benchmark_train dynamicTostatic_bs8_fp32_DP_N1C8 +# bash test_tipc/benchmark_train.sh test_tipc/configs/det_mv3_db_v2_0/train_benchmark.txt benchmark_train dynamic_bs8_null_DP_N1C1 +IFS=$'\n' +# parser params from train_benchmark.txt +dataline=`cat $FILENAME` +# parser params +IFS=$'\n' +lines=(${dataline}) +model_name=$(func_parser_value "${lines[1]}") +python_name=$(func_parser_value "${lines[2]}") + +# set env +python=python +export str_tmp=$(echo `pip list|grep paddlepaddle-gpu|awk -F ' ' '{print $2}'`) +export frame_version=${str_tmp%%.post*} +export frame_commit=$(echo `${python} -c "import paddle;print(paddle.version.commit)"`) + +# 获取benchmark_params所在的行数 +line_num=`grep -n -w "train_benchmark_params" $FILENAME | cut -d ":" -f 1` +# for train log parser +batch_size=$(func_parser_value "${lines[line_num]}") +line_num=`expr $line_num + 1` +fp_items=$(func_parser_value "${lines[line_num]}") +line_num=`expr $line_num + 1` +epoch=$(func_parser_value "${lines[line_num]}") + +line_num=`expr $line_num + 1` +profile_option_key=$(func_parser_key "${lines[line_num]}") +profile_option_params=$(func_parser_value "${lines[line_num]}") +profile_option="${profile_option_key}:${profile_option_params}" + +line_num=`expr $line_num + 1` +flags_value=$(func_parser_value "${lines[line_num]}") +# set flags +IFS=";" +flags_list=(${flags_value}) +for _flag in ${flags_list[*]}; do + cmd="export ${_flag}" + eval $cmd +done + +# set log_name +repo_name=$(get_repo_name ) +SAVE_LOG=${BENCHMARK_LOG_DIR:-$(pwd)} # */benchmark_log +mkdir -p "${SAVE_LOG}/benchmark_log/" +status_log="${SAVE_LOG}/benchmark_log/results.log" +# get benchmark profiling params : PROFILING_TIMER_ONLY=no|True|False +PROFILING_TIMER_ONLY=${PROFILING_TIMER_ONLY:-"True"} +# The number of lines in which train params can be replaced. +line_python=3 +line_gpuid=4 +line_precision=6 +line_epoch=7 +line_batchsize=9 +line_profile=13 +line_eval_py=24 +line_export_py=30 + +func_sed_params "$FILENAME" "${line_eval_py}" "null" +func_sed_params "$FILENAME" "${line_export_py}" "null" +func_sed_params "$FILENAME" "${line_python}" "$python" + +# if params +if [ ! -n "$PARAMS" ] ;then + # PARAMS input is not a word. + IFS="|" + batch_size_list=(${batch_size}) + fp_items_list=(${fp_items}) + device_num_list=(N1C4) + run_mode="DP" +elif [[ ${PARAMS} = "dynamicTostatic" ]];then + IFS="|" + model_type=$PARAMS + batch_size_list=(${batch_size}) + fp_items_list=(${fp_items}) + device_num_list=(N1C4) + run_mode="DP" +else + # parser params from input: modeltype_bs${bs_item}_${fp_item}_${run_mode}_${device_num} + IFS="_" + params_list=(${PARAMS}) + model_type=${params_list[0]} + batch_size=${params_list[1]} + batch_size=`echo ${batch_size} | tr -cd "[0-9]" ` + precision=${params_list[2]} + run_mode=${params_list[3]} + device_num=${params_list[4]} + IFS=";" + + if [ ${precision} = "fp16" ];then + precision="amp" + fi + + epoch=$(set_dynamic_epoch $device_num $epoch) + fp_items_list=($precision) + batch_size_list=($batch_size) + device_num_list=($device_num) +fi + +IFS="|" +for batch_size in ${batch_size_list[*]}; do + for train_precision in ${fp_items_list[*]}; do + for device_num in ${device_num_list[*]}; do + # sed batchsize and precision + if [ ${train_precision} = "amp" ];then + precision="fp16" + else + precision="fp32" + fi + + func_sed_params "$FILENAME" "${line_precision}" "$train_precision" + func_sed_params "$FILENAME" "${line_batchsize}" "$MODE=$batch_size" + func_sed_params "$FILENAME" "${line_epoch}" "$MODE=$epoch" + gpu_id=$(set_gpu_id $device_num) + + if [ ${#gpu_id} -le 1 ];then + func_sed_params "$FILENAME" "${line_gpuid}" "0" # sed used gpu_id + if [[ ${PROFILING_TIMER_ONLY} != "no" ]];then + echo "run profile" + # The default value of profile_option's timer_only parameter is True + if [[ ${PROFILING_TIMER_ONLY} = "False" ]];then + profile_option="${profile_option};timer_only=False" + fi + log_path="$SAVE_LOG/profiling_log" + mkdir -p $log_path + log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}profiling" + # set profile_option params + tmp=`sed -i "${line_profile}s/.*/\"${profile_option}\"/" "${FILENAME}"` + # run test_train_inference_python.sh + cmd="timeout 5m bash test_tipc/test_train_inference_python.sh ${FILENAME} benchmark_train > ${log_path}/${log_name} 2>&1 " + echo $cmd + eval ${cmd} + eval "cat ${log_path}/${log_name}" + fi + echo "run without profile" + # without profile + log_path="$SAVE_LOG/train_log" + speed_log_path="$SAVE_LOG/index" + mkdir -p $log_path + mkdir -p $speed_log_path + log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}log" + speed_log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}speed" + func_sed_params "$FILENAME" "${line_profile}" "null" # sed profile_id as null + cmd="bash test_tipc/test_train_inference_python.sh ${FILENAME} benchmark_train > ${log_path}/${log_name} 2>&1 " + echo $cmd + job_bt=`date '+%Y%m%d%H%M%S'` + eval $cmd + job_et=`date '+%Y%m%d%H%M%S'` + export model_run_time=$((${job_et}-${job_bt})) + eval "cat ${log_path}/${log_name}" + + # parser log + _model_name="${model_name}_bs${batch_size}_${precision}_${run_mode}" + cmd="${python} ${BENCHMARK_ROOT}/scripts/analysis.py --filename ${log_path}/${log_name} \ + --speed_log_file '${speed_log_path}/${speed_log_name}' \ + --model_name ${_model_name} \ + --base_batch_size ${batch_size} \ + --run_mode ${run_mode} \ + --fp_item ${precision} \ + --keyword ips: \ + --skip_steps 2 \ + --device_num ${device_num} \ + --speed_unit samples/s \ + --convergence_key loss: " + echo $cmd + eval $cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${cmd}" "${status_log}" + else + IFS=";" + unset_env=`unset CUDA_VISIBLE_DEVICES` + log_path="$SAVE_LOG/train_log" + speed_log_path="$SAVE_LOG/index" + mkdir -p $log_path + mkdir -p $speed_log_path + log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}log" + speed_log_name="${repo_name}_${model_name}_bs${batch_size}_${precision}_${run_mode}_${device_num}_${to_static}speed" + func_sed_params "$FILENAME" "${line_gpuid}" "$gpu_id" # sed used gpu_id + func_sed_params "$FILENAME" "${line_profile}" "null" # sed --profile_option as null + cmd="bash test_tipc/test_train_inference_python.sh ${FILENAME} benchmark_train > ${log_path}/${log_name} 2>&1 " + echo $cmd + job_bt=`date '+%Y%m%d%H%M%S'` + eval $cmd + job_et=`date '+%Y%m%d%H%M%S'` + export model_run_time=$((${job_et}-${job_bt})) + eval "cat ${log_path}/${log_name}" + # parser log + _model_name="${model_name}_bs${batch_size}_${precision}_${run_mode}" + + cmd="${python} ${BENCHMARK_ROOT}/scripts/analysis.py --filename ${log_path}/${log_name} \ + --speed_log_file '${speed_log_path}/${speed_log_name}' \ + --model_name ${_model_name} \ + --base_batch_size ${batch_size} \ + --run_mode ${run_mode} \ + --fp_item ${precision} \ + --keyword ips: \ + --skip_steps 2 \ + --device_num ${device_num} \ + --speed_unit images/s \ + --convergence_key loss: " + echo $cmd + eval $cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${cmd}" "${status_log}" + fi + done + done +done diff --git a/test_tipc/build_server.sh b/test_tipc/build_server.sh new file mode 100644 index 0000000..36d9789 --- /dev/null +++ b/test_tipc/build_server.sh @@ -0,0 +1,69 @@ +#使用镜像: +#registry.baidubce.com/paddlepaddle/paddle:latest-dev-cuda10.1-cudnn7-gcc82 + +#编译Serving Server: + +#client和app可以直接使用release版本 + +#server因为加入了自定义OP,需要重新编译 + +apt-get update +apt install -y libcurl4-openssl-dev libbz2-dev +wget https://paddle-serving.bj.bcebos.com/others/centos_ssl.tar && tar xf centos_ssl.tar && rm -rf centos_ssl.tar && mv libcrypto.so.1.0.2k /usr/lib/libcrypto.so.1.0.2k && mv libssl.so.1.0.2k /usr/lib/libssl.so.1.0.2k && ln -sf /usr/lib/libcrypto.so.1.0.2k /usr/lib/libcrypto.so.10 && ln -sf /usr/lib/libssl.so.1.0.2k /usr/lib/libssl.so.10 && ln -sf /usr/lib/libcrypto.so.10 /usr/lib/libcrypto.so && ln -sf /usr/lib/libssl.so.10 /usr/lib/libssl.so + +# 安装go依赖 +rm -rf /usr/local/go +wget -qO- https://paddle-ci.cdn.bcebos.com/go1.17.2.linux-amd64.tar.gz | tar -xz -C /usr/local +export GOROOT=/usr/local/go +export GOPATH=/root/gopath +export PATH=$PATH:$GOPATH/bin:$GOROOT/bin +go env -w GO111MODULE=on +go env -w GOPROXY=https://goproxy.cn,direct +go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v1.15.2 +go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger@v1.15.2 +go install github.com/golang/protobuf/protoc-gen-go@v1.4.3 +go install google.golang.org/grpc@v1.33.0 +go env -w GO111MODULE=auto + +# 下载opencv库 +wget https://paddle-qa.bj.bcebos.com/PaddleServing/opencv3.tar.gz && tar -xvf opencv3.tar.gz && rm -rf opencv3.tar.gz +export OPENCV_DIR=$PWD/opencv3 + +# clone Serving +git clone https://github.com/PaddlePaddle/Serving.git -b develop --depth=1 +cd Serving +export Serving_repo_path=$PWD +git submodule update --init --recursive +python -m pip install -r python/requirements.txt + + +export PYTHON_INCLUDE_DIR=$(python -c "import sysconfig; print(sysconfig.get_path('include'))") +export PYTHON_LIBRARIES=$(python -c "import sysconfig; print(sysconfig.get_config_var('LIBDIR'))") +export PYTHON_EXECUTABLE=`which python` + +export CUDA_PATH='/usr/local/cuda' +export CUDNN_LIBRARY='/usr/local/cuda/lib64/' +export CUDA_CUDART_LIBRARY='/usr/local/cuda/lib64/' +export TENSORRT_LIBRARY_PATH='/usr/local/TensorRT6-cuda10.1-cudnn7/targets/x86_64-linux-gnu/' + +# cp 自定义OP代码 +cp -rf ../deploy/pdserving/general_detection_op.cpp ${Serving_repo_path}/core/general-server/op + +# 编译Server, export SERVING_BIN +mkdir server-build-gpu-opencv && cd server-build-gpu-opencv +cmake -DPYTHON_INCLUDE_DIR=$PYTHON_INCLUDE_DIR \ + -DPYTHON_LIBRARIES=$PYTHON_LIBRARIES \ + -DPYTHON_EXECUTABLE=$PYTHON_EXECUTABLE \ + -DCUDA_TOOLKIT_ROOT_DIR=${CUDA_PATH} \ + -DCUDNN_LIBRARY=${CUDNN_LIBRARY} \ + -DCUDA_CUDART_LIBRARY=${CUDA_CUDART_LIBRARY} \ + -DTENSORRT_ROOT=${TENSORRT_LIBRARY_PATH} \ + -DOPENCV_DIR=${OPENCV_DIR} \ + -DWITH_OPENCV=ON \ + -DSERVER=ON \ + -DWITH_GPU=ON .. +make -j32 + +python -m pip install python/dist/paddle* +export SERVING_BIN=$PWD/core/general-server/serving +cd ../../ diff --git a/test_tipc/common_func.sh b/test_tipc/common_func.sh new file mode 100644 index 0000000..9ec22f0 --- /dev/null +++ b/test_tipc/common_func.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +function func_parser_key(){ + strs=$1 + IFS=":" + array=(${strs}) + tmp=${array[0]} + echo ${tmp} +} + +function func_parser_value(){ + strs=$1 + IFS=":" + array=(${strs}) + tmp=${array[1]} + echo ${tmp} +} + +function func_set_params(){ + key=$1 + value=$2 + if [ ${key}x = "null"x ];then + echo " " + elif [[ ${value} = "null" ]] || [[ ${value} = " " ]] || [ ${#value} -le 0 ];then + echo " " + else + echo "${key}=${value}" + fi +} + +function func_parser_params(){ + strs=$1 + MODE=$2 + IFS=":" + array=(${strs}) + key=${array[0]} + tmp=${array[1]} + IFS="|" + res="" + for _params in ${tmp[*]}; do + IFS="=" + array=(${_params}) + mode=${array[0]} + value=${array[1]} + if [[ ${mode} = ${MODE} ]]; then + IFS="|" + #echo $(func_set_params "${mode}" "${value}") + echo $value + break + fi + IFS="|" + done + echo ${res} +} + +function status_check(){ + last_status=$1 # the exit code + run_command=$2 + run_log=$3 + model_name=$4 + log_path=$5 + if [ $last_status -eq 0 ]; then + echo -e "\033[33m Run successfully with command - ${model_name} - ${run_command} - ${log_path} \033[0m" | tee -a ${run_log} + else + echo -e "\033[33m Run failed with command - ${model_name} - ${run_command} - ${log_path} \033[0m" | tee -a ${run_log} + fi +} diff --git a/test_tipc/compare_results.py b/test_tipc/compare_results.py new file mode 100644 index 0000000..96a1f20 --- /dev/null +++ b/test_tipc/compare_results.py @@ -0,0 +1,145 @@ +import numpy as np +import os +import subprocess +import json +import argparse +import glob + + +def init_args(): + parser = argparse.ArgumentParser() + # params for testing assert allclose + parser.add_argument("--atol", type=float, default=1e-3) + parser.add_argument("--rtol", type=float, default=1e-3) + parser.add_argument("--gt_file", type=str, default="") + parser.add_argument("--log_file", type=str, default="") + parser.add_argument("--precision", type=str, default="fp32") + return parser + + +def parse_args(): + parser = init_args() + return parser.parse_args() + + +def run_shell_command(cmd): + p = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True + ) + out, err = p.communicate() + + if p.returncode == 0: + return out.decode("utf-8") + else: + return None + + +def parser_results_from_log_by_name(log_path, names_list): + if not os.path.exists(log_path): + raise ValueError("The log file {} does not exists!".format(log_path)) + + if names_list is None or len(names_list) < 1: + return [] + + parser_results = {} + for name in names_list: + cmd = "grep {} {}".format(name, log_path) + outs = run_shell_command(cmd) + outs = outs.split("\n")[0] + result = outs.split("{}".format(name))[-1] + try: + result = json.loads(result) + except: + result = np.array([int(r) for r in result.split()]).reshape(-1, 4) + parser_results[name] = result + return parser_results + + +def load_gt_from_file(gt_file): + if not os.path.exists(gt_file): + raise ValueError("The log file {} does not exists!".format(gt_file)) + with open(gt_file, "r") as f: + data = f.readlines() + f.close() + parser_gt = {} + for line in data: + image_name, result = line.strip("\n").split("\t") + image_name = image_name.split("/")[-1] + try: + result = json.loads(result) + except: + result = np.array([int(r) for r in result.split()]).reshape(-1, 4) + parser_gt[image_name] = result + return parser_gt + + +def load_gt_from_txts(gt_file): + gt_list = glob.glob(gt_file) + gt_collection = {} + for gt_f in gt_list: + gt_dict = load_gt_from_file(gt_f) + basename = os.path.basename(gt_f) + if "fp32" in basename: + gt_collection["fp32"] = [gt_dict, gt_f] + elif "fp16" in basename: + gt_collection["fp16"] = [gt_dict, gt_f] + elif "int8" in basename: + gt_collection["int8"] = [gt_dict, gt_f] + else: + continue + return gt_collection + + +def collect_predict_from_logs(log_path, key_list): + log_list = glob.glob(log_path) + pred_collection = {} + for log_f in log_list: + pred_dict = parser_results_from_log_by_name(log_f, key_list) + key = os.path.basename(log_f) + pred_collection[key] = pred_dict + + return pred_collection + + +def testing_assert_allclose(dict_x, dict_y, atol=1e-7, rtol=1e-7): + for k in dict_x: + np.testing.assert_allclose( + np.array(dict_x[k]), np.array(dict_y[k]), atol=atol, rtol=rtol + ) + + +if __name__ == "__main__": + # Usage: + # python3.7 tests/compare_results.py --gt_file=./tests/results/*.txt --log_file=./tests/output/infer_*.log + + args = parse_args() + + gt_collection = load_gt_from_txts(args.gt_file) + key_list = gt_collection["fp32"][0].keys() + + pred_collection = collect_predict_from_logs(args.log_file, key_list) + for filename in pred_collection.keys(): + if "fp32" in filename: + gt_dict, gt_filename = gt_collection["fp32"] + elif "fp16" in filename: + gt_dict, gt_filename = gt_collection["fp16"] + elif "int8" in filename: + gt_dict, gt_filename = gt_collection["int8"] + else: + continue + pred_dict = pred_collection[filename] + + try: + testing_assert_allclose(gt_dict, pred_dict, atol=args.atol, rtol=args.rtol) + print( + "Assert allclose passed! The results of {} and {} are consistent!".format( + filename, gt_filename + ) + ) + except Exception as E: + print(E) + raise ValueError( + "The results of {} and the results of {} are inconsistent!".format( + filename, gt_filename + ) + ) diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml b/test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml new file mode 100644 index 0000000..6336213 --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml @@ -0,0 +1,206 @@ +Global: + debug: false + use_gpu: true + epoch_num: 800 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_ppocr_v3_distillation + save_epoch_step: 3 + eval_batch_step: [0, 2000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: &max_text_length 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_ppocrv3_distillation.txt + d2s_train_image_shape: [3, 48, -1] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs : [700] + values : [0.0005, 0.00005] + warmup_epoch: 5 + regularizer: + name: L2 + factor: 3.0e-05 + + +Architecture: + model_type: &model_type "rec" + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: SVTR_LCNet + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + last_conv_stride: [1, 2] + last_pool_type: avg + Head: + name: MultiHead + head_list: + - CTCHead: + Neck: + name: svtr + dims: 64 + depth: 2 + hidden_dims: 120 + use_guide: True + Head: + fc_decay: 0.00001 + - SARHead: + enc_dim: 512 + max_text_length: *max_text_length +Loss: + name: CombinedLoss + loss_config_list: + - DistillationDMLLoss: + weight: 1.0 + act: "softmax" + use_log: true + model_name_pairs: + - ["Student", "Teacher"] + key: head_out + multi_head: True + dis_head: ctc + name: dml_ctc + - DistillationDMLLoss: + weight: 0.5 + act: "softmax" + use_log: true + model_name_pairs: + - ["Student", "Teacher"] + key: head_out + multi_head: True + dis_head: sar + name: dml_sar + - DistillationDistanceLoss: + weight: 1.0 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: backbone_out + - DistillationCTCLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: head_out + multi_head: True + - DistillationSARLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: head_out + multi_head: True + +PostProcess: + name: DistillationCTCLabelDecode + model_name: ["Student", "Teacher"] + key: head_out + multi_head: True + +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: "Student" + ignore_space: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + ext_op_transform_idx: 1 + label_file_list: + - ./train_data/ic15_data/rec_gt_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecConAug: + prob: 0.5 + ext_data_num: 2 + image_shape: [48, 320, 3] + - RecAug: + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_workers: 4 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: + - ./train_data/ic15_data/rec_gt_test.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - MultiLabelEncode: + - RecResizeImg: + image_shape: [3, 48, 320] + - KeepKeys: + keep_keys: + - image + - label_ctc + - label_sar + - length + - valid_ratio + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 4 diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..4071cbe --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:PP-OCRv3_mobile_rec +use_opencv:True +infer_model:./inference/PP-OCRv3_mobile_rec_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_img_h=48 --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference/ +null:null +--benchmark:True +--det:False +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..1b946f3 --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:PP-OCRv3_mobile_rec +python:python3.7 +2onnx: paddle2onnx +--det_model_dir: +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file: +--rec_model_dir:./inference/PP-OCRv3_mobile_rec_infer/ +--rec_save_file:./inference/rec_v3_onnx/model.onnx +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/rec_inference/ diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..a22da5d --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:PP-OCRv3_mobile_rec +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:null +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:null +--det_serving_client:null +--rec_dirname:./inference/PP-OCRv3_mobile_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v3_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v3_client/ +serving_dir:./deploy/pdserving +web_service:web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py --det=False +--image_dir:../../inference/rec_inference diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec/train_infer_python.txt b/test_tipc/configs/PP-OCRv3_mobile_rec/train_infer_python.txt new file mode 100644 index 0000000..7899862 --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:PP-OCRv3_mobile_rec +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o Global.print_batch_step=1 Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/PP-OCRv3_mobile_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,320]}] +===========================train_benchmark_params========================== +batch_size:64 +fp_items:fp32|fp16 +epoch:1 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================to_static_train_benchmark_params=========================== +to_static_train:Global.to_static=true diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..4b7e7cb --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:PP-OCRv3_mobile_rec +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/PP-OCRv3_mobile_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,320]}] diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..8e7feef --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:PP-OCRv3_mobile_rec +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/PP-OCRv3_mobile_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,320]}] diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec/train_pact_infer_python.txt b/test_tipc/configs/PP-OCRv3_mobile_rec/train_pact_infer_python.txt new file mode 100644 index 0000000..6239c95 --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec/train_pact_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:PP-OCRv3_mobile_rec_PACT +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:pretrain_models/PP-OCRv3_mobile_rec_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:pact_train +norm_train:null +pact_train:deploy/slim/quantization/quant.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:deploy/slim/quantization/export_model.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o +fpgm_export: null +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/PP-OCRv3_mobile_rec_slim_infer +infer_export:null +infer_quant:True +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,320]}] diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec/train_ptq_infer_python.txt b/test_tipc/configs/PP-OCRv3_mobile_rec/train_ptq_infer_python.txt new file mode 100644 index 0000000..3aa42cb --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec/train_ptq_infer_python.txt @@ -0,0 +1,21 @@ +===========================kl_quant_params=========================== +model_name:PP-OCRv3_mobile_rec_KL +python:python3.7 +Global.pretrained_model: +Global.save_inference_dir:null +infer_model:./inference/PP-OCRv3_mobile_rec_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o +infer_quant:True +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:False|True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:int8 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..9ae0023 --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:PP-OCRv3_mobile_rec_KL +use_opencv:True +infer_model:./inference/PP-OCRv3_mobile_rec_klquant_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_img_h=48 --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference/ +null:null +--benchmark:True +--det:False +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..d418f49 --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:PP-OCRv3_mobile_rec_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv3_det_klquant_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v3_kl_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v3_kl_client/ +--rec_dirname:./inference/PP-OCRv3_mobile_rec_klquant_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v3_kl_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v3_kl_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..92546b1 --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:PP-OCRv3_mobile_rec_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:null +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:null +--det_serving_client:null +--rec_dirname:./inference/PP-OCRv3_mobile_rec_klquant_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v3_kl_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v3_kl_client/ +serving_dir:./deploy/pdserving +web_service:web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py --det=False +--image_dir:../../inference/rec_inference diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..f325dd3 --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:PP-OCRv3_mobile_rec_PACT +use_opencv:True +infer_model:./inference/PP-OCRv3_mobile_rec_pact_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_img_h=48 --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference/ +null:null +--benchmark:True +--det:False +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..1108d59 --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:PP-OCRv3_mobile_rec_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv3_det_pact_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v3_pact_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v3_pact_client/ +--rec_dirname:./inference/PP-OCRv3_mobile_rec_pact_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v3_pact_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v3_pact_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..948eac8 --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:PP-OCRv3_mobile_rec_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:null +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:null +--det_serving_client:null +--rec_dirname:./inference/PP-OCRv3_mobile_rec_pact_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v3_pact_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v3_pact_client/ +serving_dir:./deploy/pdserving +web_service:web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py --det=False +--image_dir:../../inference/rec_inference diff --git a/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..eda0833 --- /dev/null +++ b/test_tipc/configs/PP-OCRv3_mobile_rec_PACT/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:PP-OCRv3_mobile_rec_PACT +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:pretrain_models/PP-OCRv3_mobile_rec_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:pact_train +norm_train:null +pact_train:deploy/slim/quantization/quant.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:deploy/slim/quantization/export_model.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o +fpgm_export: null +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/PP-OCRv3_mobile_rec_slim_quant_infer +infer_export:null +infer_quant:True +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,320]}] diff --git a/test_tipc/configs/PP-OCRv4_mobile_rec/train_infer_python.txt b/test_tipc/configs/PP-OCRv4_mobile_rec/train_infer_python.txt new file mode 100644 index 0000000..3730a5e --- /dev/null +++ b/test_tipc/configs/PP-OCRv4_mobile_rec/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:PP-OCRv4_mobile_rec +python:python +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml -o Global.cal_metric_during_train=False Global.print_batch_step=1 Train.loader.shuffle=false Train.dataset.data_dir=./train_data/ic15_data Train.dataset.label_file_list=[./train_data/ic15_data/rec_gt_train.txt] Eval.dataset.data_dir=./train_data/ic15_data Eval.dataset.label_file_list=[./train_data/ic15_data/rec_gt_test.txt] Train.loader.num_workers=16 Eval.loader.num_workers=16 +pact_train:null +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/PP-OCRv4_mobile_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,320]}] +===========================train_benchmark_params========================== +batch_size:128 +fp_items:fp32|fp16 +epoch:1 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/PP-OCRv4_mobile_rec_ampO2_ultra/train_infer_python.txt b/test_tipc/configs/PP-OCRv4_mobile_rec_ampO2_ultra/train_infer_python.txt new file mode 100644 index 0000000..909f390 --- /dev/null +++ b/test_tipc/configs/PP-OCRv4_mobile_rec_ampO2_ultra/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:PP-OCRv4_mobile_rec +python:python +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_ampO2_ultra.yml -o Global.cal_metric_during_train=False Global.print_batch_step=1 Train.loader.shuffle=false Train.dataset.data_dir=./train_data/ic15_data Train.dataset.label_file_list=[./train_data/ic15_data/rec_gt_train.txt] Eval.dataset.data_dir=./train_data/ic15_data Eval.dataset.label_file_list=[./train_data/ic15_data/rec_gt_test.txt] +pact_train:null +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/PP-OCRv4_mobile_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,320]}] +===========================train_benchmark_params========================== +batch_size:384 +fp_items:fp16 +epoch:1 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================disable_to_static_train_benchmark=========================== +to_static_train:Global.to_static=False diff --git a/test_tipc/configs/PP-OCRv4_mobile_rec_fp32_ultra/train_infer_python.txt b/test_tipc/configs/PP-OCRv4_mobile_rec_fp32_ultra/train_infer_python.txt new file mode 100644 index 0000000..eda3409 --- /dev/null +++ b/test_tipc/configs/PP-OCRv4_mobile_rec_fp32_ultra/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:PP-OCRv4_mobile_rec +python:python +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec_fp32_ultra.yml -o Global.cal_metric_during_train=False Global.print_batch_step=1 Train.loader.shuffle=false Train.dataset.data_dir=./train_data/ic15_data Train.dataset.label_file_list=[./train_data/ic15_data/rec_gt_train.txt] Eval.dataset.data_dir=./train_data/ic15_data Eval.dataset.label_file_list=[./train_data/ic15_data/rec_gt_test.txt] +pact_train:null +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/PP-OCRv4_mobile_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,320]}] +===========================train_benchmark_params========================== +batch_size:192 +fp_items:fp32 +epoch:1 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================disable_to_static_train_benchmark=========================== +to_static_train:Global.to_static=False diff --git a/test_tipc/configs/PP-OCRv4_server_rec/train_infer_python.txt b/test_tipc/configs/PP-OCRv4_server_rec/train_infer_python.txt new file mode 100644 index 0000000..8f12c5d --- /dev/null +++ b/test_tipc/configs/PP-OCRv4_server_rec/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:PP-OCRv4_server_rec +python:python +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/rec/PP-OCRv4/PP-OCRv4_server_rec.yml -o Global.cal_metric_during_train=False Global.print_batch_step=1 Train.loader.shuffle=false Train.dataset.data_dir=./train_data/ic15_data Train.dataset.label_file_list=[./train_data/ic15_data/rec_gt_train.txt] Eval.dataset.data_dir=./train_data/ic15_data Eval.dataset.label_file_list=[./train_data/ic15_data/rec_gt_test.txt] Train.loader.num_workers=16 Eval.loader.num_workers=16 +pact_train:null +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/rec/PP-OCRv4/PP-OCRv4_server_rec.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/PP-OCRv4_mobile_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,320]}] +===========================train_benchmark_params========================== +batch_size:128 +fp_items:fp32|fp16 +epoch:1 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/PP-OCRv4_server_rec_ampO2_ultra/train_infer_python.txt b/test_tipc/configs/PP-OCRv4_server_rec_ampO2_ultra/train_infer_python.txt new file mode 100644 index 0000000..0789305 --- /dev/null +++ b/test_tipc/configs/PP-OCRv4_server_rec_ampO2_ultra/train_infer_python.txt @@ -0,0 +1,60 @@ +===========================train_params=========================== +model_name:PP-OCRv4_server_rec +python:python +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/rec/PP-OCRv4/PP-OCRv4_server_rec_ampO2_ultra.yml -o Global.cal_metric_during_train=False Global.print_batch_step=1 Train.loader.shuffle=false Train.dataset.data_dir=./train_data/ic15_data Train.dataset.label_file_list=[./train_data/ic15_data/rec_gt_train.txt] Eval.dataset.data_dir=./train_data/ic15_data Eval.dataset.label_file_list=[./train_data/ic15_data/rec_gt_test.txt] +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/PP-OCRv4_mobile_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,320]}] +===========================train_benchmark_params========================== +batch_size:256 +fp_items:fp16 +epoch:1 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================disable_to_static_train_benchmark=========================== +to_static_train:Global.to_static=False diff --git a/test_tipc/configs/PP-OCRv4_server_rec_fp32_ultra/train_infer_python.txt b/test_tipc/configs/PP-OCRv4_server_rec_fp32_ultra/train_infer_python.txt new file mode 100644 index 0000000..7221d78 --- /dev/null +++ b/test_tipc/configs/PP-OCRv4_server_rec_fp32_ultra/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:PP-OCRv4_server_rec +python:python +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/rec/PP-OCRv4/PP-OCRv4_server_rec_fp32_ultra.yml -o Global.cal_metric_during_train=False Global.print_batch_step=1 Train.loader.shuffle=false Train.dataset.data_dir=./train_data/ic15_data Train.dataset.label_file_list=[./train_data/ic15_data/rec_gt_train.txt] Eval.dataset.data_dir=./train_data/ic15_data Eval.dataset.label_file_list=[./train_data/ic15_data/rec_gt_test.txt] +pact_train:null +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/rec/PP-OCRv4/PP-OCRv4_mobile_rec.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/PP-OCRv4_mobile_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,320]}] +===========================train_benchmark_params========================== +batch_size:256 +fp_items:fp32 +epoch:1 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================disable_to_static_train_benchmark=========================== +to_static_train:Global.to_static=False diff --git a/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..2a432b3 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_PP-OCRv2 +use_opencv:True +infer_model:./inference/ch_PP-OCRv2_det_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--rec_model_dir:./inference/ch_PP-OCRv2_rec_infer/ +--benchmark:True +--det:True +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..32b290a --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================ch_PP-OCRv2=========================== +model_name:ch_PP-OCRv2 +python:python3.7 +infer_model:./inference/ch_PP-OCRv2_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_system.py +--use_gpu:False|True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--rec_model_dir:./inference/ch_PP-OCRv2_rec_infer/ +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt new file mode 100644 index 0000000..d03ee1e --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn system +runtime_device:ARM_CPU +det_infer_model:ch_PP-OCRv2_det_infer|ch_PP-OCRv2_det_slim_quant_infer +rec_infer_model:ch_PP-OCRv2_rec_infer|ch_PP-OCRv2_rec_slim_quant_infer +cls_infer_model:ch_ppocr_mobile_v2.0_cls_infer|ch_ppocr_mobile_v2.0_cls_slim_infer +--cpu_threads:1|4 +--det_batch_size:1 +--rec_batch_size:1 +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +--rec_dict_dir:./ppocr_keys_v1.txt +--benchmark:True diff --git a/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt new file mode 100644 index 0000000..a9fcafb --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn system +runtime_device:ARM_GPU_OPENCL +det_infer_model:ch_PP-OCRv2_det_infer|ch_PP-OCRv2_det_slim_quant_infer +rec_infer_model:ch_PP-OCRv2_rec_infer|ch_PP-OCRv2_rec_slim_quant_infer +cls_infer_model:ch_ppocr_mobile_v2.0_cls_infer|ch_ppocr_mobile_v2.0_cls_slim_infer +--cpu_threads:1|4 +--det_batch_size:1 +--rec_batch_size:1 +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +--rec_dict_dir:./ppocr_keys_v1.txt +--benchmark:True diff --git a/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..6a85f9f --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:ch_PP-OCRv2 +python:python3.7 +2onnx: paddle2onnx +--det_model_dir:./inference/ch_PP-OCRv2_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file:./inference/det_v2_onnx/model.onnx +--rec_model_dir:./inference/ch_PP-OCRv2_rec_infer/ +--rec_save_file:./inference/rec_v2_onnx/model.onnx +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_system.py --rec_image_shape="3,32,320" +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/00008790.jpg diff --git a/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..f0456b5 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2 +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv2_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v2_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v2_client/ +--rec_dirname:./inference/ch_PP-OCRv2_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v2_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v2_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..4ad64db --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2 +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv2_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v2_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v2_client/ +--rec_dirname:./inference/ch_PP-OCRv2_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v2_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v2_client/ +serving_dir:./deploy/pdserving +web_service:web_service.py --config=config.yml --opt op.det.concurrency="1" op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..a723fff --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_PP-OCRv2_det +use_opencv:True +infer_model:./inference/ch_PP-OCRv2_det_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +--det:True +--rec:False +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt new file mode 100644 index 0000000..003d776 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn det +runtime_device:ARM_CPU +det_infer_model:ch_PP-OCRv2_det_infer|ch_PP-OCRv2_det_slim_quant_infer +null:null +null:null +--cpu_threads:1|4 +--det_batch_size:1 +null:null +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +null:null +--benchmark:True diff --git a/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt b/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt new file mode 100644 index 0000000..a0b5569 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn det +runtime_device:ARM_GPU_OPENCL +det_infer_model:ch_PP-OCRv2_det_infer|ch_PP-OCRv2_det_slim_quant_infer +null:null +null:null +--cpu_threads:1|4 +--det_batch_size:1 +null:null +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +null:null +--benchmark:True diff --git a/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..834aef4 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:ch_PP-OCRv2_det +python:python3.7 +2onnx: paddle2onnx +--det_model_dir:./inference/ch_PP-OCRv2_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file:./inference/det_v2_onnx/model.onnx +--rec_model_dir: +--rec_save_file: +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_det.py +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ diff --git a/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..587a7d7 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2_det +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv2_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v2_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v2_client/ +--rec_dirname:null +--rec_serving_server:null +--rec_serving_client:null +serving_dir:./deploy/pdserving +web_service:web_service_det.py --config=config.yml --opt op.det.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv2_det/train_infer_python.txt b/test_tipc/configs/ch_PP-OCRv2_det/train_infer_python.txt new file mode 100644 index 0000000..7a20df7 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv2_det +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml -o Global.print_batch_step=1 Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml -o +quant_export:null +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/ch_PP-OCRv2_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/ch_PP-OCRv2_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..91a6288 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv2_det +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml -o +quant_export:null +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/ch_PP-OCRv2_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_PP-OCRv2_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..85b0ebc --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv2_det +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml -o +quant_export:null +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/ch_PP-OCRv2_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_PP-OCRv2_det/train_pact_infer_python.txt b/test_tipc/configs/ch_PP-OCRv2_det/train_pact_infer_python.txt new file mode 100644 index 0000000..1a20f97 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det/train_pact_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv2_det_PACT +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=1|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:pact_train +norm_train:null +pact_train:deploy/slim/quantization/quant.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml -o +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:deploy/slim/quantization/export_model.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml -o +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/ch_PP-OCRv2_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_PP-OCRv2_det/train_ptq_infer_python.txt b/test_tipc/configs/ch_PP-OCRv2_det/train_ptq_infer_python.txt new file mode 100644 index 0000000..ccc9e5c --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det/train_ptq_infer_python.txt @@ -0,0 +1,21 @@ +===========================kl_quant_params=========================== +model_name:ch_PP-OCRv2_det_KL +python:python3.7 +Global.pretrained_model:null +Global.save_inference_dir:null +infer_model:./inference/ch_PP-OCRv2_det_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml -o +infer_quant:True +inference:tools/infer/predict_det.py +--use_gpu:False|True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:int8 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/ch_PP-OCRv2_det_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..038223a --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_PP-OCRv2_det_KL +use_opencv:True +infer_model:./inference/ch_PP-OCRv2_det_klquant_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +--det:True +--rec:False +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_PP-OCRv2_det_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..e306b0a --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2_det_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv2_det_klquant_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v2_kl_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v2_kl_client/ +--rec_dirname:./inference/ch_PP-OCRv2_rec_klquant_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v2_kl_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v2_kl_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv2_det_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..2c96d2b --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2_det_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv2_det_klquant_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v2_kl_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v2_kl_client/ +--rec_dirname:null +--rec_serving_server:null +--rec_serving_client:null +serving_dir:./deploy/pdserving +web_service:web_service_det.py --config=config.yml --opt op.det.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv2_det_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..2106672 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_PP-OCRv2_det_PACT +use_opencv:True +infer_model:./inference/ch_PP-OCRv2_det_pact_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +--det:True +--rec:False +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_PP-OCRv2_det_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..b2d929b --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2_det_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv2_det_pact_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v2_pact_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v2_pact_client/ +--rec_dirname:./inference/ch_PP-OCRv2_rec_pact_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v2_pact_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v2_pact_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv2_det_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_det_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..d5d99ab --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_det_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2_det_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv2_det_pact_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v2_pact_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v2_pact_client/ +--rec_dirname:null +--rec_serving_server:null +--rec_serving_client:null +serving_dir:./deploy/pdserving +web_service:web_service_det.py --config=config.yml --opt op.det.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml b/test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml new file mode 100644 index 0000000..43e14b8 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml @@ -0,0 +1,159 @@ +Global: + debug: false + use_gpu: true + epoch_num: 800 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_pp-OCRv2_distillation + save_epoch_step: 3 + eval_batch_step: [0, 200000] + cal_metric_during_train: true + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: false + infer_img: doc/imgs_words/ch/word_1.jpg + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: false + use_space_char: true + distributed: true + save_res_path: ./output/rec/predicts_pp-OCRv2_distillation.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs : [700] + values : [0.001, 0.0001] + warmup_epoch: 5 + regularizer: + name: L2 + factor: 2.0e-05 + +Architecture: + model_type: &model_type "rec" + name: DistillationModel + algorithm: Distillation + Models: + Teacher: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 64 + Head: + name: CTCHead + mid_channels: 96 + fc_decay: 0.00002 + Student: + pretrained: + freeze_params: false + return_all_feats: true + model_type: *model_type + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV1Enhance + scale: 0.5 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 64 + Head: + name: CTCHead + mid_channels: 96 + fc_decay: 0.00002 + + +Loss: + name: CombinedLoss + loss_config_list: + - DistillationCTCLoss: + weight: 1.0 + model_name_list: ["Student", "Teacher"] + key: head_out + - DistillationDMLLoss: + weight: 1.0 + act: "softmax" + use_log: true + model_name_pairs: + - ["Student", "Teacher"] + key: head_out + - DistillationDistanceLoss: + weight: 1.0 + mode: "l2" + model_name_pairs: + - ["Student", "Teacher"] + key: backbone_out + +PostProcess: + name: DistillationCTCLabelDecode + model_name: ["Student", "Teacher"] + key: head_out + +Metric: + name: DistillationMetric + base_metric_name: RecMetric + main_indicator: acc + key: "Student" + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: + - ./train_data/ic15_data/rec_gt_train.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - RecAug: + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: true + batch_size_per_card: 128 + drop_last: true + num_sections: 1 + num_workers: 8 +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: + - ./train_data/ic15_data/rec_gt_test.txt + transforms: + - DecodeImage: + img_mode: BGR + channel_first: false + - CTCLabelEncode: + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: + - image + - label + - length + loader: + shuffle: false + drop_last: false + batch_size_per_card: 128 + num_workers: 8 diff --git a/test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..c616e41 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_PP-OCRv2_rec +use_opencv:True +infer_model:./inference/ch_PP-OCRv2_rec_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference/ +null:null +--benchmark:True +--det:False +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..003d593 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:ch_PP-OCRv2_rec +python:python3.7 +2onnx: paddle2onnx +--det_model_dir: +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file: +--rec_model_dir:./inference/ch_PP-OCRv2_rec_infer/ +--rec_save_file:./inference/rec_v2_onnx/model.onnx +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_rec.py --rec_image_shape="3,32,320" +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/rec_inference/ diff --git a/test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..e9e90d3 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2_rec +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:null +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:null +--det_serving_client:null +--rec_dirname:./inference/ch_PP-OCRv2_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v2_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v2_client/ +serving_dir:./deploy/pdserving +web_service:web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py --det=False +--image_dir:../../inference/rec_inference diff --git a/test_tipc/configs/ch_PP-OCRv2_rec/train_infer_python.txt b/test_tipc/configs/ch_PP-OCRv2_rec/train_infer_python.txt new file mode 100644 index 0000000..6d20b2d --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv2_rec +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml -o Global.print_batch_step=4 Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/ch_PP-OCRv2_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,320]}] +===========================train_benchmark_params========================== +batch_size:64 +fp_items:fp32|fp16 +epoch:1 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/ch_PP-OCRv2_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..5795bc2 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv2_rec +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/ch_PP-OCRv2_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,320]}] diff --git a/test_tipc/configs/ch_PP-OCRv2_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..1b8800f --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv2_rec +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/ch_PP-OCRv2_rec_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,320]}] diff --git a/test_tipc/configs/ch_PP-OCRv2_rec/train_pact_infer_python.txt b/test_tipc/configs/ch_PP-OCRv2_rec/train_pact_infer_python.txt new file mode 100644 index 0000000..0ac75ef --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec/train_pact_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv2_rec_PACT +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:pretrain_models/ch_PP-OCRv2_rec_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:pact_train +norm_train:null +pact_train:deploy/slim/quantization/quant.py -c test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml -o +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:deploy/slim/quantization/export_model.py -c test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml -o +fpgm_export: null +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/ch_PP-OCRv2_rec_slim_quant_infer +infer_export:null +infer_quant:True +inference:tools/infer/predict_rec.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,320]}] diff --git a/test_tipc/configs/ch_PP-OCRv2_rec/train_ptq_infer_python.txt b/test_tipc/configs/ch_PP-OCRv2_rec/train_ptq_infer_python.txt new file mode 100644 index 0000000..c30e085 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec/train_ptq_infer_python.txt @@ -0,0 +1,21 @@ +===========================kl_quant_params=========================== +model_name:ch_PP-OCRv2_rec_KL +python:python3.7 +Global.pretrained_model:null +Global.save_inference_dir:null +infer_model:./inference/ch_PP-OCRv2_rec_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c test_tipc/configs/ch_PP-OCRv2_rec/ch_PP-OCRv2_rec_distillation.yml -o +infer_quant:True +inference:tools/infer/predict_rec.py --rec_image_shape="3,32,320" +--use_gpu:False|True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:int8 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/ch_PP-OCRv2_rec_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_rec_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..cdf6cb3 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_PP-OCRv2_rec_KL +use_opencv:True +infer_model:./inference/ch_PP-OCRv2_rec_klquant_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference/ +null:null +--benchmark:True +--det:False +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_PP-OCRv2_rec_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_rec_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..34d4007 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2_rec_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv2_det_klquant_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v2_kl_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v2_kl_client/ +--rec_dirname:./inference/ch_PP-OCRv2_rec_klquant_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v2_kl_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v2_kl_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv2_rec_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_rec_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..3405f2b --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2_rec_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:null +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:null +--det_serving_client:null +--rec_dirname:./inference/ch_PP-OCRv2_rec_klquant_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v2_kl_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v2_kl_client/ +serving_dir:./deploy/pdserving +web_service:web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py --det=False +--image_dir:../../inference/rec_inference diff --git a/test_tipc/configs/ch_PP-OCRv2_rec_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_rec_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..73943d4 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_PP-OCRv2_rec_PACT +use_opencv:True +infer_model:./inference/ch_PP-OCRv2_rec_pact_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference/ +null:null +--benchmark:True +--det:False +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_PP-OCRv2_rec_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_rec_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..2a174b9 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2_rec_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv2_det_pact_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v2_pact_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v2_pact_client/ +--rec_dirname:./inference/ch_PP-OCRv2_rec_pact_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v2_pact_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v2_pact_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv2_rec_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv2_rec_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..2b7ed81 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv2_rec_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv2_rec_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:null +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:null +--det_serving_client:null +--rec_dirname:./inference/ch_PP-OCRv2_rec_pact_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v2_pact_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v2_pact_client/ +serving_dir:./deploy/pdserving +web_service:web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py --det=False +--image_dir:../../inference/rec_inference diff --git a/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..97ee8d7 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_PP-OCRv3 +use_opencv:True +infer_model:./inference/PP-OCRv3_mobile_det_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_img_h=48 --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--rec_model_dir:./inference/PP-OCRv3_mobile_rec_infer/ +--benchmark:True +--det:True +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..29b20a6 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================ch_PP-OCRv2=========================== +model_name:ch_PP-OCRv3 +python:python3.7 +infer_model:./inference/PP-OCRv3_mobile_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_system.py --rec_image_shape="3,48,320" +--use_gpu:False|True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--rec_model_dir:./inference/PP-OCRv3_mobile_rec_infer/ +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt new file mode 100644 index 0000000..946ec08 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn system +runtime_device:ARM_CPU +det_infer_model:PP-OCRv3_mobile_det_infer|ch_PP-OCRv3_det_slim_quant_infer +rec_infer_model:PP-OCRv3_mobile_rec_infer|PP-OCRv3_mobile_rec_slim_quant_infer +cls_infer_model:ch_ppocr_mobile_v2.0_cls_infer|ch_ppocr_mobile_v2.0_cls_slim_infer +--cpu_threads:1|4 +--det_batch_size:1 +--rec_batch_size:1 +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +--rec_dict_dir:./ppocr_keys_v1.txt +--benchmark:True diff --git a/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt new file mode 100644 index 0000000..61822d9 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn system +runtime_device:ARM_GPU_OPENCL +det_infer_model:PP-OCRv3_mobile_det_infer|ch_PP-OCRv3_det_slim_quant_infer +rec_infer_model:PP-OCRv3_mobile_rec_infer|PP-OCRv3_mobile_rec_slim_quant_infer +cls_infer_model:ch_ppocr_mobile_v2.0_cls_infer|ch_ppocr_mobile_v2.0_cls_slim_infer +--cpu_threads:1|4 +--det_batch_size:1 +--rec_batch_size:1 +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +--rec_dict_dir:./ppocr_keys_v1.txt +--benchmark:True diff --git a/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..0f4cef3 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:ch_PP-OCRv3 +python:python3.7 +2onnx: paddle2onnx +--det_model_dir:./inference/PP-OCRv3_mobile_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file:./inference/det_v3_onnx/model.onnx +--rec_model_dir:./inference/PP-OCRv3_mobile_rec_infer/ +--rec_save_file:./inference/rec_v3_onnx/model.onnx +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_system.py --rec_image_shape="3,48,320" +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/00008790.jpg diff --git a/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..d5979ad --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv3 +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/PP-OCRv3_mobile_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v3_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v3_client/ +--rec_dirname:./inference/PP-OCRv3_mobile_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v3_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v3_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..c3cee90 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv3 +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/PP-OCRv3_mobile_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v3_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v3_client/ +--rec_dirname:./inference/PP-OCRv3_mobile_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v3_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v3_client/ +serving_dir:./deploy/pdserving +web_service:web_service.py --config=config.yml --opt op.det.concurrency="1" op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..e266f2a --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_PP-OCRv3_det +use_opencv:True +infer_model:./inference/PP-OCRv3_mobile_det_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +--det:True +--rec:False +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt new file mode 100644 index 0000000..c0cc839 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn det +runtime_device:ARM_CPU +det_infer_model:PP-OCRv3_mobile_det_infer|ch_PP-OCRv3_det_slim_quant_infer +null:null +null:null +--cpu_threads:1|4 +--det_batch_size:1 +null:null +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +null:null +--benchmark:True diff --git a/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt b/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt new file mode 100644 index 0000000..f8adb28 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn det +runtime_device:ARM_GPU_OPENCL +det_infer_model:PP-OCRv3_mobile_det_infer|ch_PP-OCRv3_det_slim_quant_infer +null:null +null:null +--cpu_threads:1|4 +--det_batch_size:1 +null:null +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +null:null +--benchmark:True diff --git a/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..7dae93e --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:ch_PP-OCRv3_det +python:python3.7 +2onnx: paddle2onnx +--det_model_dir:./inference/PP-OCRv3_mobile_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file:./inference/det_v3_onnx/model.onnx +--rec_model_dir: +--rec_save_file: +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_det.py +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ diff --git a/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..60111cb --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv3_det +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/PP-OCRv3_mobile_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v3_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v3_client/ +--rec_dirname:null +--rec_serving_server:null +--rec_serving_client:null +serving_dir:./deploy/pdserving +web_service:web_service_det.py --config=config.yml --opt op.det.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv3_det/train_infer_python.txt b/test_tipc/configs/ch_PP-OCRv3_det/train_infer_python.txt new file mode 100644 index 0000000..91d05df --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv3_det +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml -o Global.print_batch_step=1 Train.loader.shuffle=false Global.eval_batch_step=[4000,400] +pact_train:null +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml -o +quant_export:null +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/PP-OCRv3_mobile_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================to_static_train_benchmark_params=========================== +to_static_train:Global.to_static=true diff --git a/test_tipc/configs/ch_PP-OCRv3_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..40a44ff --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv3_det +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml -o +quant_export:null +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/PP-OCRv3_mobile_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_PP-OCRv3_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..5b2b080 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv3_det +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml -o +quant_export:null +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/PP-OCRv3_mobile_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_PP-OCRv3_det/train_pact_infer_python.txt b/test_tipc/configs/ch_PP-OCRv3_det/train_pact_infer_python.txt new file mode 100644 index 0000000..76d2148 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det/train_pact_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_PP-OCRv3_det_PACT +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=1|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:pact_train +norm_train:null +pact_train:deploy/slim/quantization/quant.py -c configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml -o +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:deploy/slim/quantization/export_model.py -c configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml -o +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/PP-OCRv3_mobile_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_PP-OCRv3_det/train_ptq_infer_python.txt b/test_tipc/configs/ch_PP-OCRv3_det/train_ptq_infer_python.txt new file mode 100644 index 0000000..c193039 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det/train_ptq_infer_python.txt @@ -0,0 +1,21 @@ +===========================kl_quant_params=========================== +model_name:ch_PP-OCRv3_det_KL +python:python3.7 +Global.pretrained_model:null +Global.save_inference_dir:null +infer_model:./inference/PP-OCRv3_mobile_det_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c configs/det/PP-OCRv3/PP-OCRv3_det_cml.yml -o +infer_quant:True +inference:tools/infer/predict_det.py +--use_gpu:False|True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:int8 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/ch_PP-OCRv3_det_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..afaa015 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_PP-OCRv3_det_KL +use_opencv:True +infer_model:./inference/ch_PP-OCRv3_det_klquant_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +--det:True +--rec:False +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_PP-OCRv3_det_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..5376f85 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv3_det_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv3_det_klquant_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v3_kl_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v3_kl_client/ +--rec_dirname:./inference/PP-OCRv3_mobile_rec_klquant_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v3_kl_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v3_kl_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv3_det_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..23dbc49 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv3_det_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv3_det_klquant_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v3_kl_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v3_kl_client/ +--rec_dirname:null +--rec_serving_server:null +--rec_serving_client:null +serving_dir:./deploy/pdserving +web_service:web_service_det.py --config=config.yml --opt op.det.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv3_det_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..794e979 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_PP-OCRv3_det_PACT +use_opencv:True +infer_model:./inference/ch_PP-OCRv3_det_pact_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +--det:True +--rec:False +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_PP-OCRv3_det_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..86f9c06 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv3_det_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv3_det_pact_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v3_pact_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v3_pact_client/ +--rec_dirname:./inference/PP-OCRv3_mobile_rec_pact_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_v3_pact_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_v3_pact_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv3_det_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_PP-OCRv3_det_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..4546644 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv3_det_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_PP-OCRv3_det_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_PP-OCRv3_det_pact_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_v3_pact_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_v3_pact_client/ +--rec_dirname:null +--rec_serving_server:null +--rec_serving_client:null +serving_dir:./deploy/pdserving +web_service:web_service_det.py --config=config.yml --opt op.det.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_PP-OCRv4_mobile_det/train_infer_python.txt b/test_tipc/configs/ch_PP-OCRv4_mobile_det/train_infer_python.txt new file mode 100644 index 0000000..a52f9d1 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv4_mobile_det/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:PP-OCRv4_mobile_det +python:python +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:pretrain_models/PP-OCRv4_mobile_det_pretrained.pdparams +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/PP-OCRv4/PP-OCRv4_mobile_det.yml -o Global.print_batch_step=1 Train.loader.shuffle=false Global.eval_batch_step=[4000,400] +pact_train:null +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/PP-OCRv4/PP-OCRv4_mobile_det.yml -o +quant_export:null +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/PP-OCRv3_mobile_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================to_static_train_benchmark_params=========================== +to_static_train:Global.to_static=true diff --git a/test_tipc/configs/ch_PP-OCRv4_server_det/train_infer_python.txt b/test_tipc/configs/ch_PP-OCRv4_server_det/train_infer_python.txt new file mode 100644 index 0000000..3974509 --- /dev/null +++ b/test_tipc/configs/ch_PP-OCRv4_server_det/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:PP-OCRv4_server_det +python:python +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:pretrain_models/PP-OCRv4_server_det_pretrained.pdparams +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/PP-OCRv4/PP-OCRv4_server_det.yml -o Global.print_batch_step=1 Train.loader.shuffle=false Global.eval_batch_step=[4000,400] +pact_train:null +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/PP-OCRv4/PP-OCRv4_server_det.yml -o +quant_export:null +fpgm_export: +distill_export:null +export1:null +export2:null +inference_dir:Student +infer_model:./inference/PP-OCRv3_mobile_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:4 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================to_static_train_benchmark_params=========================== +to_static_train:Global.to_static=true diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..65312a7 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_ppocr_mobile_v2_0 +use_opencv:True +infer_model:./inference/ch_ppocr_mobile_v2.0_det_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--rec_model_dir:./inference/ch_ppocr_mobile_v2.0_rec_infer/ +--benchmark:True +--det:True +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..00373b6 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================ch_ppocr_mobile_v2.0=========================== +model_name:ch_ppocr_mobile_v2_0 +python:python3.7 +infer_model:./inference/ch_ppocr_mobile_v2.0_det_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_system.py +--use_gpu:False|True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--rec_model_dir:./inference/ch_ppocr_mobile_v2.0_rec_infer/ +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt new file mode 100644 index 0000000..9594368 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn system +runtime_device:ARM_CPU +det_infer_model:ch_ppocr_mobile_v2.0_det_infer|ch_ppocr_db_mobile_v2.0_det_quant_infer +rec_infer_model:ch_ppocr_mobile_v2.0_rec_infer|ch_ppocr_mobile_v2.0_rec_slim_infer +cls_infer_model:ch_ppocr_mobile_v2.0_cls_infer|ch_ppocr_mobile_v2.0_cls_slim_infer +--cpu_threads:1|4 +--det_batch_size:1 +--rec_batch_size:1 +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +--rec_dict_dir:./ppocr_keys_v1.txt +--benchmark:True diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt new file mode 100644 index 0000000..c13b9be --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn system +runtime_device:ARM_GPU_OPENCL +det_infer_model:ch_ppocr_mobile_v2.0_det_infer|ch_ppocr_db_mobile_v2.0_det_quant_infer +rec_infer_model:ch_ppocr_mobile_v2.0_rec_infer|ch_ppocr_mobile_v2.0_rec_slim_infer +cls_infer_model:ch_ppocr_mobile_v2.0_cls_infer|ch_ppocr_mobile_v2.0_cls_slim_infer +--cpu_threads:1|4 +--det_batch_size:1 +--rec_batch_size:1 +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +--rec_dict_dir:./ppocr_keys_v1.txt +--benchmark:True diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..ec6135f --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:ch_ppocr_mobile_v2_0 +python:python3.7 +2onnx: paddle2onnx +--det_model_dir:./inference/ch_ppocr_mobile_v2.0_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file:./inference/det_mobile_onnx/model.onnx +--rec_model_dir:./inference/ch_ppocr_mobile_v2.0_rec_infer/ +--rec_save_file:./inference/rec_mobile_onnx/model.onnx +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_system.py --rec_image_shape="3,32,320" +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..305882a --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0 +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_mobile_v2.0_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_mobile_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_mobile_client/ +--rec_dirname:./inference/ch_ppocr_mobile_v2.0_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_mobile_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_mobile_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..0c366b0 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0 +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_mobile_v2.0_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_mobile_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_mobile_client/ +--rec_dirname:./inference/ch_ppocr_mobile_v2.0_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_mobile_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_mobile_client/ +serving_dir:./deploy/pdserving +web_service:web_service.py --config=config.yml --opt op.det.concurrency="1" op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..8dd7718 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_ppocr_mobile_v2_0_det +use_opencv:True +infer_model:./inference/ch_ppocr_mobile_v2.0_det_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +--det:True +--rec:False +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_infer_python_jetson.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_infer_python_jetson.txt new file mode 100644 index 0000000..5f9dfa5 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_infer_python_jetson.txt @@ -0,0 +1,18 @@ +===========================infer_params=========================== +model_name:ch_ppocr_mobile_v2_0_det +python:python +infer_model:./inference/ch_ppocr_mobile_v2.0_det_infer +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt new file mode 100644 index 0000000..e83534b --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn det +runtime_device:ARM_CPU +det_infer_model:ch_ppocr_mobile_v2.0_det_infer|ch_ppocr_db_mobile_v2.0_det_quant_infer +null:null +null:null +--cpu_threads:1|4 +--det_batch_size:1 +null:null +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +null:null +--benchmark:True diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt new file mode 100644 index 0000000..2ce8aec --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt @@ -0,0 +1,13 @@ +===========================lite_params=========================== +inference:./ocr_db_crnn det +runtime_device:ARM_GPU_OPENCL +det_infer_model:ch_ppocr_mobile_v2.0_det_infer|ch_ppocr_db_mobile_v2.0_det_quant_infer +null:null +null:null +--cpu_threads:1|4 +--det_batch_size:1 +null:null +--image_dir:./test_data/icdar2015_lite/text_localization/ch4_test_images/ +--config_dir:./config.txt +null:null +--benchmark:True diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..37ceec3 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:ch_ppocr_mobile_v2_0_det +python:python3.7 +2onnx: paddle2onnx +--det_model_dir:./inference/ch_ppocr_mobile_v2.0_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file:./inference/det_mobile_onnx/model.onnx +--rec_model_dir: +--rec_save_file: +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_det.py +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..6dfd7e7 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0_det +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_mobile_v2.0_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_mobile_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_mobile_client/ +--rec_dirname:null +--rec_serving_server:null +--rec_serving_client:null +serving_dir:./deploy/pdserving +web_service:web_service_det.py --config=config.yml --opt op.det.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt new file mode 100644 index 0000000..4112e64 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_det +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=100|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained Global.print_batch_step=2 Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:./inference/ch_ppocr_mobile_v2.0_det_train/best_accuracy +infer_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_linux_dcu_normal_normal_infer_python_dcu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_linux_dcu_normal_normal_infer_python_dcu.txt new file mode 100644 index 0000000..e69de29 diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..a3bcbe1 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_det +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=100|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:./inference/ch_ppocr_mobile_v2.0_det_train/best_accuracy +infer_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..537d15e --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_det +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=100|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:./inference/ch_ppocr_mobile_v2.0_det_train/best_accuracy +infer_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt new file mode 100644 index 0000000..3f321a1 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:ocr_det +python:python +gpu_list:-1 +Global.use_gpu:False +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.pretrained_model: +norm_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:./inference/ch_ppocr_mobile_v2.0_det_train/best_accuracy +infer_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:1|6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_pact_infer_python.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_pact_infer_python.txt new file mode 100644 index 0000000..ba880d1 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_pact_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_det_PACT +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:pact_train +norm_train:null +pact_train:deploy/slim/quantization/quant.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:deploy/slim/quantization/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:./inference/ch_ppocr_mobile_v2.0_det_prune_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_ptq_infer_python.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_ptq_infer_python.txt new file mode 100644 index 0000000..45c4fd1 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_ptq_infer_python.txt @@ -0,0 +1,21 @@ +===========================kl_quant_params=========================== +model_name:ch_ppocr_mobile_v2_0_det_KL +python:python3.7 +Global.pretrained_model:null +Global.save_inference_dir:null +infer_model:./inference/ch_ppocr_mobile_v2.0_det_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +infer_quant:True +inference:tools/infer/predict_det.py +--use_gpu:False|True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:int8 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt new file mode 100644 index 0000000..f338e02 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt @@ -0,0 +1,51 @@ +===========================train_params=========================== +model_name:ocr_det +python:python +gpu_list:0 +Global.use_gpu:True +Global.auto_cast:fp32|amp +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.pretrained_model: +norm_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:./inference/ch_ppocr_mobile_v2.0_det_train/best_accuracy +infer_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:1|6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32|fp16|int8 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det_FPGM/train_infer_python.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_FPGM/train_infer_python.txt new file mode 100644 index 0000000..8b6dfbe --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_FPGM/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_det_FPGM +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:fpgm_train +norm_train:null +pact_train:null +fpgm_train:deploy/slim/prune/sensitivity_anal.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o Global.pretrained_model=./pretrain_models/det_mv3_db_v2.0_train/best_accuracy +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:null +fpgm_export:deploy/slim/prune/export_prune_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:null +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det_FPGM/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_FPGM/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..279b428 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_FPGM/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_det_FPGM +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:fpgm_train +norm_train:null +pact_train:null +fpgm_train:deploy/slim/prune/sensitivity_anal.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o Global.pretrained_model=./pretrain_models/det_mv3_db_v2.0_train/best_accuracy +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:null +fpgm_export:deploy/slim/prune/export_prune_model.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:null +infer_export:null +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..6ee80f6 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_ppocr_mobile_v2_0_det_KL +use_opencv:True +infer_model:./inference/ch_ppocr_mobile_v2.0_det_klquant_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +--det:True +--rec:False +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_infer_python_mac_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_infer_python_mac_cpu.txt new file mode 100644 index 0000000..ea74b7f --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_infer_python_mac_cpu.txt @@ -0,0 +1,17 @@ +===========================kl_quant_params=========================== +infer_model:./inference/ch_ppocr_mobile_v2.0_det_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +infer_quant:True +inference:tools/infer/predict_det.py +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:1|6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:int8 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_infer_python_windows_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_infer_python_windows_gpu_cpu.txt new file mode 100644 index 0000000..ea74b7f --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_infer_python_windows_gpu_cpu.txt @@ -0,0 +1,17 @@ +===========================kl_quant_params=========================== +infer_model:./inference/ch_ppocr_mobile_v2.0_det_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c configs/det/ch_ppocr_v2.0/ch_det_mv3_db_v2.0.yml -o +infer_quant:True +inference:tools/infer/predict_det.py +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:1|6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:int8 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..c5dc525 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0_det_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_mobile_v2.0_det_klquant_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_mobile_kl_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_mobile_kl_client/ +--rec_dirname:./inference/ch_ppocr_mobile_v2.0_rec_klquant_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_mobile_kl_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_mobile_kl_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..82d4db3 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0_det_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_mobile_v2.0_det_klquant_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_mobile_kl_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_mobile_kl_client/ +--rec_dirname:null +--rec_serving_server:null +--rec_serving_client:null +serving_dir:./deploy/pdserving +web_service:web_service_det.py --config=config.yml --opt op.det.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..a8066dd --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_ppocr_mobile_v2_0_det_PACT +use_opencv:True +infer_model:./inference/ch_ppocr_mobile_v2.0_det_pact_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +--det:True +--rec:False +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..3be5395 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0_det_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_mobile_v2.0_det_pact_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_mobile_pact_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_mobile_pact_client/ +--rec_dirname:./inference/ch_ppocr_mobile_v2.0_rec_pact_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_mobile_pact_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_mobile_pact_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_det_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..63e7f8f --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_det_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0_det_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_mobile_v2.0_det_pact_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_mobile_pact_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_mobile_pact_client/ +--rec_dirname:null +--rec_serving_server:null +--rec_serving_client:null +serving_dir:./deploy/pdserving +web_service:web_service_det.py --config=config.yml --opt op.det.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..a8342c5 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec +use_opencv:True +infer_model:./inference/ch_ppocr_mobile_v2.0_rec_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference/ +null:null +--benchmark:True +--det:False +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..79da03e --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec +python:python3.7 +2onnx: paddle2onnx +--det_model_dir: +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file: +--rec_model_dir:./inference/ch_ppocr_mobile_v2.0_rec_infer/ +--rec_save_file:./inference/rec_mobile_onnx/model.onnx +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_rec.py --rec_image_shape="3,32,320" +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/rec_inference/ diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..5c60903 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:null +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:null +--det_serving_client:null +--rec_dirname:./inference/ch_ppocr_mobile_v2.0_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_mobile_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_mobile_client/ +serving_dir:./deploy/pdserving +web_service:web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py --det=False +--image_dir:../../inference/rec_inference diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_infer_python.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_infer_python.txt new file mode 100644 index 0000000..dc490cd --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/rec/rec_icdar15_train.yml -o Global.print_batch_step=4 Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c configs/rec/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/rec/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/ch_ppocr_mobile_v2.0_rec_train/best_accuracy +infer_export:tools/export_model.py -c configs/rec/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] +===========================train_benchmark_params========================== +batch_size:256 +fp_items:fp32|fp16 +epoch:3 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..2f919d1 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/rec/rec_icdar15_train.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c configs/rec/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/rec/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/ch_ppocr_mobile_v2.0_rec_train/best_accuracy +infer_export:tools/export_model.py -c configs/rec/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..f60e279 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/rec/rec_icdar15_train.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c configs/rec/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/rec/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/ch_ppocr_mobile_v2.0_rec_train/best_accuracy +infer_export:tools/export_model.py -c configs/rec/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_pact_infer_python.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_pact_infer_python.txt new file mode 100644 index 0000000..9c1223f --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_pact_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec_PACT +python:python3.7 +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.checkpoints:null +train_model_name:latest +train_infer_img_dir:./train_data/ic15_data/test/word_1.png +null:null +## +trainer:pact_train +norm_train:null +pact_train:deploy/slim/quantization/quant.py -c test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/rec_chinese_lite_train_v2.0.yml -o +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:deploy/slim/quantization/export_model.py -c test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/rec_chinese_lite_train_v2.0.yml -o +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +infer_model:./inference/ch_ppocr_mobile_v2.0_rec_slim_infer/ +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_image_shape="3,32,100" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,320]}] diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_ptq_infer_python.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_ptq_infer_python.txt new file mode 100644 index 0000000..df47f32 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec/train_ptq_infer_python.txt @@ -0,0 +1,21 @@ +===========================kl_quant_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec_KL +python:python3.7 +Global.pretrained_model:null +Global.save_inference_dir:null +infer_model:./inference/ch_ppocr_mobile_v2.0_rec_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/rec_chinese_lite_train_v2.0.yml -o +infer_quant:True +inference:tools/infer/predict_rec.py --rec_image_shape="3,32,320" +--use_gpu:False|True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:int8 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/rec_chinese_lite_train_v2.0.yml b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/rec_chinese_lite_train_v2.0.yml new file mode 100644 index 0000000..ee42dbf --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/rec_chinese_lite_train_v2.0.yml @@ -0,0 +1,102 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_chinese_lite_v2.0 + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: False + use_space_char: True + save_res_path: ./output/rec/predicts_chinese_lite_v2.0.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + disable_se: True + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ic15_data + label_file_list: ["train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ic15_data + label_file_list: ["train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/train_infer_python.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/train_infer_python.txt new file mode 100644 index 0000000..d99ad00 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec_FPGM +python:python3.7 +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/ic15_data/test/word_1.png +null:null +## +trainer:fpgm_train +norm_train:null +pact_train:null +fpgm_train:deploy/slim/prune/sensitivity_anal.py -c test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/rec_chinese_lite_train_v2.0.yml -o Global.pretrained_model=./pretrain_models/ch_ppocr_mobile_v2.0_rec_train/best_accuracy +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:null +fpgm_export:deploy/slim/prune/export_prune_model.py -c test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/rec_chinese_lite_train_v2.0.yml -o +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:null +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,320]}] diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..6754f73 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec_FPGM +python:python3.7 +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/ic15_data/test/word_1.png +null:null +## +trainer:fpgm_train +norm_train:null +pact_train:null +fpgm_train:deploy/slim/prune/sensitivity_anal.py -c test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/rec_chinese_lite_train_v2.0.yml -o Global.pretrained_model=./pretrain_models/ch_ppocr_mobile_v2.0_rec_train/best_accuracy +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:null +fpgm_export:deploy/slim/prune/export_prune_model.py -c test_tipc/configs/ch_ppocr_mobile_v2_0_rec_FPGM/rec_chinese_lite_train_v2.0.yml -o +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:null +infer_export:null +infer_quant:False +inference:tools/infer/predict_rec.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,320]}] diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..7669b44 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec_KL +use_opencv:True +infer_model:./inference/ch_ppocr_mobile_v2.0_rec_klquant_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference/ +null:null +--benchmark:True +--det:False +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..d904e22 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_mobile_v2.0_det_klquant_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_mobile_kl_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_mobile_kl_client/ +--rec_dirname:./inference/ch_ppocr_mobile_v2.0_rec_klquant_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_mobile_kl_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_mobile_kl_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..de4f7ed --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec_KL +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:null +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:null +--det_serving_client:null +--rec_dirname:./inference/ch_ppocr_mobile_v2.0_rec_klquant_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_mobile_kl_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_mobile_kl_client/ +serving_dir:./deploy/pdserving +web_service:web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py --det=False +--image_dir:../../inference/rec_inference diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/rec_chinese_lite_train_v2.0.yml b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/rec_chinese_lite_train_v2.0.yml new file mode 100644 index 0000000..b06dafe --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_KL/rec_chinese_lite_train_v2.0.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_chinese_lite_v2.0 + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: False + use_space_char: True + save_res_path: ./output/rec/predicts_chinese_lite_v2.0.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ic15_data + label_file_list: ["train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ic15_data + label_file_list: ["train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..d1f7c48 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec_PACT +use_opencv:True +infer_model:./inference/ch_ppocr_mobile_v2.0_rec_pact_infer +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference/ +null:null +--benchmark:True +--det:False +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..5a30474 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_mobile_v2.0_det_pact_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_mobile_pact_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_mobile_pact_client/ +--rec_dirname:./inference/ch_ppocr_mobile_v2.0_rec_pact_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_mobile_pact_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_mobile_pact_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..5871199 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_ppocr_mobile_v2_0_rec_PACT +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:null +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:null +--det_serving_client:null +--rec_dirname:./inference/ch_ppocr_mobile_v2.0_rec_pact_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_mobile_pact_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_mobile_pact_client/ +serving_dir:./deploy/pdserving +web_service:web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py --det=False +--image_dir:../../inference/rec_inference diff --git a/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/rec_chinese_lite_train_v2.0.yml b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/rec_chinese_lite_train_v2.0.yml new file mode 100644 index 0000000..b06dafe --- /dev/null +++ b/test_tipc/configs/ch_ppocr_mobile_v2_0_rec_PACT/rec_chinese_lite_train_v2.0.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: true + epoch_num: 500 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec_chinese_lite_v2.0 + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: ppocr/utils/ppocr_keys_v1.txt + max_text_length: 25 + infer_mode: False + use_space_char: True + save_res_path: ./output/rec/predicts_chinese_lite_v2.0.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Cosine + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: small + small_stride: [1, 2, 2, 2] + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 48 + Head: + name: CTCHead + fc_decay: 0.00001 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/ic15_data + label_file_list: ["train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RecAug: + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/ic15_data + label_file_list: ["train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 320] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..f742892 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_ppocr_server_v2_0 +use_opencv:True +infer_model:./inference/ch_ppocr_server_v2.0_det_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--rec_model_dir:./inference/ch_ppocr_server_v2.0_rec_infer/ +--benchmark:True +--det:True +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..53f8ab0 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================ch_ppocr_server_v2.0=========================== +model_name:ch_ppocr_server_v2_0 +python:python3.7 +infer_model:./inference/ch_ppocr_server_v2.0_det_infer/ +infer_export:null +infer_quant:True +inference:tools/infer/predict_system.py +--use_gpu:False|True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--rec_model_dir:./inference/ch_ppocr_server_v2.0_rec_infer/ +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..cd2d857 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:ch_ppocr_server_v2_0 +python:python3.7 +2onnx: paddle2onnx +--det_model_dir:./inference/ch_ppocr_server_v2.0_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file:./inference/det_server_onnx/model.onnx +--rec_model_dir:./inference/ch_ppocr_server_v2.0_rec_infer/ +--rec_save_file:./inference/rec_server_onnx/model.onnx +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_system.py --rec_image_shape="3,32,320" +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/00008790.jpg diff --git a/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..55b27e0 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt @@ -0,0 +1,19 @@ +===========================serving_params=========================== +model_name:ch_ppocr_server_v2_0 +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_server_v2.0_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_server_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_server_client/ +--rec_dirname:./inference/ch_ppocr_server_v2.0_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_server_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_server_client/ +serving_dir:./deploy/pdserving +web_service:-m paddle_serving_server.serve +--op:GeneralDetectionOp GeneralInferOp +--port:8181 +--gpu_id:"0"|null +cpp_client:ocr_cpp_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..21b8c9a --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_ppocr_server_v2_0 +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_server_v2.0_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_server_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_server_client/ +--rec_dirname:./inference/ch_ppocr_server_v2.0_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_server_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_server_client/ +serving_dir:./deploy/pdserving +web_service:web_service.py --config=config.yml --opt op.det.concurrency="1" op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml b/test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml new file mode 100644 index 0000000..6728703 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml @@ -0,0 +1,124 @@ +Global: + use_gpu: false + epoch_num: 5 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/db_mv3/ + save_epoch_step: 1200 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 30000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_db/predicts_db.txt + +Architecture: + model_type: det + algorithm: DB + Transform: + Backbone: + name: ResNet #MobileNetV3 + layers: 50 + Neck: + name: DBFPN + out_channels: 256 + Head: + name: DBHead + k: 50 + +Loss: + name: DBLoss + balance_loss: true + main_loss_type: DiceLoss + alpha: 5 #5 + beta: 10 #10 + ohem_ratio: 3 + +Optimizer: + name: Adam #Momentum + #momentum: 0.9 + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.001 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: DBPostProcess + thresh: 0.3 + box_thresh: 0.6 + max_candidates: 1000 + unclip_ratio: 1.5 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - Resize: + # size: [640, 640] + - MakeBorderMap: + shrink_ratio: 0.4 + thresh_min: 0.3 + thresh_max: 0.7 + - MakeShrinkMap: + shrink_ratio: 0.4 + min_text_size: 8 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'threshold_map', 'threshold_mask', 'shrink_map', 'shrink_mask'] # the order of the dataloader list + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 + num_workers: 0 + use_shared_memory: False + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + image_shape: [736, 1280] + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 0 + use_shared_memory: False diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..53e6ab1 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_det/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_ppocr_server_v2_0_det +use_opencv:True +infer_model:./inference/ch_ppocr_server_v2.0_det_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +--det:True +--rec:False +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..2c8a9b3 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:ch_ppocr_server_v2_0_det +python:python3.7 +2onnx: paddle2onnx +--det_model_dir:./inference/ch_ppocr_server_v2.0_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file:./inference/det_server_onnx/model.onnx +--rec_model_dir: +--rec_save_file: +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_det.py +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/00008790.jpg diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..4d4f067 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_det/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_ppocr_server_v2_0_det +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:./inference/ch_ppocr_server_v2.0_det_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:./deploy/pdserving/ppocr_det_server_serving/ +--det_serving_client:./deploy/pdserving/ppocr_det_server_client/ +--rec_dirname:null +--rec_serving_server:null +--rec_serving_client:null +serving_dir:./deploy/pdserving +web_service:web_service_det.py --config=config.yml --opt op.det.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py +--image_dir:../../doc/imgs/1.jpg diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_det/train_infer_python.txt b/test_tipc/configs/ch_ppocr_server_v2_0_det/train_infer_python.txt new file mode 100644 index 0000000..90ed29f --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_det/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:ch_ppocr_server_v2_0_det +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_lite_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml -o +quant_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/ch_ppocr_server_v2.0_det_train/best_accuracy +infer_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_res18_db_v2.0.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--save_log_path:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..e4e2f2e --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_det/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_server_v2_0_det +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_lite_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml -o +quant_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/ch_ppocr_server_v2.0_det_train/best_accuracy +infer_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_res18_db_v2.0.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--save_log_path:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..c09c163 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_server_v2_0_det +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_lite_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml -o +quant_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/ch_ppocr_server_v2_0_det/det_r50_vd_db.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/ch_ppocr_server_v2.0_det_train/best_accuracy +infer_export:tools/export_model.py -c configs/det/ch_ppocr_v2.0/ch_det_res18_db_v2.0.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--save_log_path:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..f1f0a17 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:ch_ppocr_server_v2_0_rec +use_opencv:True +infer_model:./inference/ch_ppocr_server_v2.0_rec_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference/ +null:null +--benchmark:True +--det:False +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..227b6fd --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_rec/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:ch_ppocr_server_v2_0_rec +python:python3.7 +2onnx: paddle2onnx +--det_model_dir: +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file: +--rec_model_dir:./inference/ch_ppocr_server_v2.0_rec_infer/ +--rec_save_file:./inference/rec_server_onnx/model.onnx +--opset_version:10 +--enable_onnx_checker:True +inference:tools/infer/predict_rec.py --rec_image_shape="3,32,320" +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./inference/rec_inference/ diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..4133e96 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_rec/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt @@ -0,0 +1,23 @@ +===========================serving_params=========================== +model_name:ch_ppocr_server_v2_0_rec +python:python3.7 +trans_model:-m paddle_serving_client.convert +--det_dirname:null +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_serving_server:null +--det_serving_client:null +--rec_dirname:./inference/ch_ppocr_server_v2.0_rec_infer/ +--rec_serving_server:./deploy/pdserving/ppocr_rec_server_serving/ +--rec_serving_client:./deploy/pdserving/ppocr_rec_server_client/ +serving_dir:./deploy/pdserving +web_service:web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" +op.det.local_service_conf.devices:gpu|null +op.det.local_service_conf.use_mkldnn:False +op.det.local_service_conf.thread_num:6 +op.det.local_service_conf.use_trt:False +op.det.local_service_conf.precision:fp32 +op.det.local_service_conf.model_config: +op.rec.local_service_conf.model_config: +pipline:pipeline_http_client.py --det=False +--image_dir:../../inference/rec_inference diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml b/test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml new file mode 100644 index 0000000..5825c3e --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml @@ -0,0 +1,99 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/ic15/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: ./ + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/en_dict.txt + character_type: EN + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_ic15.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + use_shared_memory: False + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_rec/train_infer_python.txt b/test_tipc/configs/ch_ppocr_server_v2_0_rec/train_infer_python.txt new file mode 100644 index 0000000..85741f9 --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_rec/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:ch_ppocr_server_v2_0_rec +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=100 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o Global.print_batch_step=4 Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/ch_ppocr_server_v2.0_rec_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] +===========================train_benchmark_params========================== +batch_size:256 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..d5f57ae --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_server_v2_0_rec +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=100 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/ch_ppocr_server_v2.0_rec_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/ch_ppocr_server_v2_0_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/ch_ppocr_server_v2_0_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..20eb10b --- /dev/null +++ b/test_tipc/configs/ch_ppocr_server_v2_0_rec/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:ch_ppocr_server_v2_0_rec +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=100 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/ch_ppocr_server_v2.0_rec_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/ch_ppocr_server_v2_0_rec/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1|6 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/det_mv3_db_v2_0/train_infer_python.txt b/test_tipc/configs/det_mv3_db_v2_0/train_infer_python.txt new file mode 100644 index 0000000..f6ee9df --- /dev/null +++ b/test_tipc/configs/det_mv3_db_v2_0/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:det_mv3_db_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/det_mv3_db.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained Global.print_batch_step=1 Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/det_mv3_db.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:./inference/det_mv3_db_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c configs/det/det_mv3_db.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:16 +fp_items:fp32|fp16 +epoch:4 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/det_mv3_east_v2_0/det_mv3_east.yml b/test_tipc/configs/det_mv3_east_v2_0/det_mv3_east.yml new file mode 100644 index 0000000..461179e --- /dev/null +++ b/test_tipc/configs/det_mv3_east_v2_0/det_mv3_east.yml @@ -0,0 +1,109 @@ +Global: + use_gpu: true + epoch_num: 10000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/east_mv3/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/det_east/predicts_east.txt + +Architecture: + model_type: det + algorithm: EAST + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: EASTFPN + model_name: small + Head: + name: EASTHead + model_name: small + +Loss: + name: EASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: EASTPostProcess + score_thresh: 0.8 + cover_thresh: 0.1 + nms_thresh: 0.2 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - EASTProcessTrain: + image_shape: [512, 512] + background_ratio: 0.125 + min_crop_side_ratio: 0.1 + min_text_size: 10 + - KeepKeys: + keep_keys: ['image', 'score_map', 'geo_map', 'training_mask'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 16 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 2400 + limit_type: max + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/test_tipc/configs/det_mv3_east_v2_0/train_infer_python.txt b/test_tipc/configs/det_mv3_east_v2_0/train_infer_python.txt new file mode 100644 index 0000000..9c6d966 --- /dev/null +++ b/test_tipc/configs/det_mv3_east_v2_0/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:det_mv3_east_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=500 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:./pretrain_models/det_mv3_east_v2.0_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/det_mv3_east_v2_0/det_mv3_east.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/det_mv3_east_v2_0/det_mv3_east.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/det_mv3_east_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/det_mv3_east_v2_0/det_mv3_east.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--save_log_path:null +--benchmark:True +--det_algorithm:EAST +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/det_mv3_pse_v2_0/det_mv3_pse.yml b/test_tipc/configs/det_mv3_pse_v2_0/det_mv3_pse.yml new file mode 100644 index 0000000..27ec3eb --- /dev/null +++ b/test_tipc/configs/det_mv3_pse_v2_0/det_mv3_pse.yml @@ -0,0 +1,135 @@ +Global: + use_gpu: true + epoch_num: 600 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_mv3_pse/ + save_epoch_step: 600 + # evaluation is run every 63 iterations + eval_batch_step: [ 0,1000 ] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/MobileNetV3_large_x0_5_pretrained + checkpoints: #./output/det_r50_vd_pse_batch8_ColorJitter/best_accuracy + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_pse/predicts_pse.txt + +Architecture: + model_type: det + algorithm: PSE + Transform: null + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: FPN + out_channels: 96 + Head: + name: PSEHead + hidden_dim: 96 + out_channels: 7 + +Loss: + name: PSELoss + alpha: 0.7 + ohem_ratio: 3 + kernel_sample_mask: pred + reduction: none + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Step + learning_rate: 0.001 + step_size: 200 + gamma: 0.1 + regularizer: + name: 'L2' + factor: 0.0005 + +PostProcess: + name: PSEPostProcess + thresh: 0 + box_thresh: 0.85 + min_area: 16 + box_type: quad # 'quad' or 'poly' + scale: 1 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - ColorJitter: + brightness: 0.12549019607843137 + saturation: 0.5 + - IaaAugment: + augmenter_args: + - { 'type': Resize, 'args': { 'size': [ 0.5, 3 ] } } + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [ -10, 10 ] } } + - MakePseGt: + kernel_num: 7 + min_shrink_ratio: 0.4 + size: 640 + - RandomCropImgMask: + size: [ 640,640 ] + main_key: gt_text + crop_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 16 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 736 + limit_type: min + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'shape', 'polys', 'ignore_tags' ] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 8 diff --git a/test_tipc/configs/det_mv3_pse_v2_0/train_infer_python.txt b/test_tipc/configs/det_mv3_pse_v2_0/train_infer_python.txt new file mode 100644 index 0000000..525fdc7 --- /dev/null +++ b/test_tipc/configs/det_mv3_pse_v2_0/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:det_mv3_pse_v2_0 +python:python3.7 +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=500 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/det_mv3_pse_v2_0/det_mv3_pse.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/det_mv3_pse_v2_0/det_mv3_pse.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/det_mv3_pse_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/det_mv3_pse_v2_0/det_mv3_pse.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--save_log_path:null +--benchmark:True +--det_algorithm:PSE +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/det_r18_ct/train_infer_python.txt b/test_tipc/configs/det_r18_ct/train_infer_python.txt new file mode 100644 index 0000000..e025312 --- /dev/null +++ b/test_tipc/configs/det_r18_ct/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:det_r18_ct +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_lite_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/total_text/test/rgb/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/det_r18_vd_ct.yml -o Global.print_batch_step=1 Train.loader.shuffle=false +quant_export:null +fpgm_export:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c configs/det/det_r18_vd_ct.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/det_r18_vd_ct.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/det_r18_vd_ct/best_accuracy +infer_export:tools/export_model.py -c configs/det/det_r18_vd_ct.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--save_log_path:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/det_r50_db_plusplus/train_infer_python.txt b/test_tipc/configs/det_r50_db_plusplus/train_infer_python.txt new file mode 100644 index 0000000..110b7f9 --- /dev/null +++ b/test_tipc/configs/det_r50_db_plusplus/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:det_r50_db_plusplus +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/det_r50_db++_icdar15.yml -o Global.pretrained_model=./pretrain_models/ResNet50_dcn_asf_synthtext_pretrained +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/det_r50_db++_icdar15.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:./inference/det_r50_db++_train/best_accuracy +infer_export:tools/export_model.py -c configs/det/det_r50_db++_icdar15.yml -o +infer_quant:False +inference:tools/infer/predict_det.py --det_algorithm="DB++" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/det_r50_db_v2_0/train_infer_python.txt b/test_tipc/configs/det_r50_db_v2_0/train_infer_python.txt new file mode 100644 index 0000000..e5c1dd8 --- /dev/null +++ b/test_tipc/configs/det_r50_db_v2_0/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:det_r50_db_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_lite_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/det/det_r50_vd_db.yml -o Global.print_batch_step=1 Train.loader.shuffle=false +quant_export:null +fpgm_export:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c configs/det/det_r50_vd_db.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/det/det_r50_vd_db.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/det_r50_vd_db_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c configs/det/det_r50_vd_db.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--save_log_path:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/det_r50_dcn_fce_ctw_v2_0/det_r50_vd_dcn_fce_ctw.yml b/test_tipc/configs/det_r50_dcn_fce_ctw_v2_0/det_r50_vd_dcn_fce_ctw.yml new file mode 100644 index 0000000..cd355f1 --- /dev/null +++ b/test_tipc/configs/det_r50_dcn_fce_ctw_v2_0/det_r50_vd_dcn_fce_ctw.yml @@ -0,0 +1,139 @@ +Global: + use_gpu: true + epoch_num: 1500 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/det_r50_dcn_fce_ctw/ + save_epoch_step: 100 + # evaluation is run every 835 iterations + eval_batch_step: [0, 4000] + cal_metric_during_train: False + pretrained_model: pretrain_models/det_r50_dcn_fce_ctw_v2.0_train/best_accuracy.pdparams + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_fce/predicts_fce.txt + + +Architecture: + model_type: det + algorithm: FCE + Transform: + Backbone: + name: ResNet_vd + layers: 50 + dcn_stage: [False, True, True, True] + out_indices: [1,2,3] + Neck: + name: FCEFPN + out_channels: 256 + has_extra_convs: False + extra_stage: 0 + Head: + name: FCEHead + fourier_degree: 5 +Loss: + name: FCELoss + fourier_degree: 5 + num_sample: 50 + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0001 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: FCEPostProcess + scales: [8, 16, 32] + alpha: 1.0 + beta: 1.0 + fourier_degree: 5 + box_type: 'poly' + +Metric: + name: DetFCEMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + ignore_orientation: True + - DetLabelEncode: # Class handling label + - ColorJitter: + brightness: 0.142 + saturation: 0.5 + contrast: 0.5 + - RandomScaling: + - RandomCropFlip: + crop_ratio: 0.5 + - RandomCropPolyInstances: + crop_ratio: 0.8 + min_side_ratio: 0.3 + - RandomRotatePolyInstances: + rotate_ratio: 0.5 + max_angle: 30 + pad_with_fixed_color: False + - SquareResizePad: + target_size: 800 + pad_ratio: 0.6 + - IaaAugment: + augmenter_args: + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - FCENetTargets: + fourier_degree: 5 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'p3_maps', 'p4_maps', 'p5_maps'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 6 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + ignore_orientation: True + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_type: 'min' + limit_side_len: 736 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - Pad: + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/test_tipc/configs/det_r50_dcn_fce_ctw_v2_0/train_infer_python.txt b/test_tipc/configs/det_r50_dcn_fce_ctw_v2_0/train_infer_python.txt new file mode 100644 index 0000000..cc6080f --- /dev/null +++ b/test_tipc/configs/det_r50_dcn_fce_ctw_v2_0/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:det_r50_dcn_fce_ctw_v2_0 +python:python3.7 +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=500 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/det_r50_dcn_fce_ctw_v2_0/det_r50_vd_dcn_fce_ctw.yml -o Global.print_batch_step=1 Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/det_r50_dcn_fce_ctw_v2_0/det_r50_vd_dcn_fce_ctw.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/det_r50_dcn_fce_ctw_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/det_r50_dcn_fce_ctw_v2_0/det_r50_vd_dcn_fce_ctw.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--save_log_path:null +--benchmark:True +--det_algorithm:FCE +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:6 +fp_items:fp32|fp16 +epoch:1 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/det_r50_vd_east_v2_0/det_r50_vd_east.yml b/test_tipc/configs/det_r50_vd_east_v2_0/det_r50_vd_east.yml new file mode 100644 index 0000000..ae7bd94 --- /dev/null +++ b/test_tipc/configs/det_r50_vd_east_v2_0/det_r50_vd_east.yml @@ -0,0 +1,108 @@ +Global: + use_gpu: true + epoch_num: 10000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/east_r50_vd/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/det_east/predicts_east.txt + +Architecture: + model_type: det + algorithm: EAST + Transform: + Backbone: + name: ResNet_vd + layers: 50 + Neck: + name: EASTFPN + model_name: large + Head: + name: EASTHead + model_name: large + +Loss: + name: EASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: EASTPostProcess + score_thresh: 0.8 + cover_thresh: 0.1 + nms_thresh: 0.2 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - EASTProcessTrain: + image_shape: [512, 512] + background_ratio: 0.125 + min_crop_side_ratio: 0.1 + min_text_size: 10 + - KeepKeys: + keep_keys: ['image', 'score_map', 'geo_map', 'training_mask'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + Fa: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 2400 + limit_type: max + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/test_tipc/configs/det_r50_vd_east_v2_0/train_infer_python.txt b/test_tipc/configs/det_r50_vd_east_v2_0/train_infer_python.txt new file mode 100644 index 0000000..e3ef8c6 --- /dev/null +++ b/test_tipc/configs/det_r50_vd_east_v2_0/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:det_r50_vd_east_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=500 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:./pretrain_models/det_r50_vd_east_v2.0_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/det_r50_vd_east_v2_0/det_r50_vd_east.yml -o Global.pretrained_model=pretrain_models/det_r50_vd_east_v2.0_train/best_accuracy.pdparams Global.print_batch_step=1 Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/det_r50_vd_east_v2_0/det_r50_vd_east.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/det_r50_vd_east_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/det_r50_vd_east_v2_0/det_r50_vd_east.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--save_log_path:null +--benchmark:True +--det_algorithm:EAST +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/det_r50_vd_pse_v2_0/det_r50_vd_pse.yml b/test_tipc/configs/det_r50_vd_pse_v2_0/det_r50_vd_pse.yml new file mode 100644 index 0000000..c069d1f --- /dev/null +++ b/test_tipc/configs/det_r50_vd_pse_v2_0/det_r50_vd_pse.yml @@ -0,0 +1,134 @@ +Global: + use_gpu: true + epoch_num: 600 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/det_r50_vd_pse/ + save_epoch_step: 600 + # evaluation is run every 125 iterations + eval_batch_step: [ 0,1000 ] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: #./output/det_r50_vd_pse_batch8_ColorJitter/best_accuracy + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_en/img_10.jpg + save_res_path: ./output/det_pse/predicts_pse.txt + +Architecture: + model_type: det + algorithm: PSE + Transform: + Backbone: + name: ResNet_vd + layers: 50 + Neck: + name: FPN + out_channels: 256 + Head: + name: PSEHead + hidden_dim: 256 + out_channels: 7 + +Loss: + name: PSELoss + alpha: 0.7 + ohem_ratio: 3 + kernel_sample_mask: pred + reduction: none + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Step + learning_rate: 0.0001 + step_size: 200 + gamma: 0.1 + regularizer: + name: 'L2' + factor: 0.0005 + +PostProcess: + name: PSEPostProcess + thresh: 0 + box_thresh: 0.85 + min_area: 16 + box_type: quad # 'quad' or 'poly' + scale: 1 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - ColorJitter: + brightness: 0.12549019607843137 + saturation: 0.5 + - IaaAugment: + augmenter_args: + - { 'type': Resize, 'args': { 'size': [ 0.5, 3 ] } } + - { 'type': Fliplr, 'args': { 'p': 0.5 } } + - { 'type': Affine, 'args': { 'rotate': [ -10, 10 ] } } + - MakePseGt: + kernel_num: 7 + min_shrink_ratio: 0.4 + size: 640 + - RandomCropImgMask: + size: [ 640,640 ] + main_key: gt_text + crop_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'gt_text', 'gt_kernels', 'mask' ] # the order of the dataloader list + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + limit_side_len: 736 + limit_type: min + - NormalizeImage: + scale: 1./255. + mean: [ 0.485, 0.456, 0.406 ] + std: [ 0.229, 0.224, 0.225 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'image', 'shape', 'polys', 'ignore_tags' ] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 8 diff --git a/test_tipc/configs/det_r50_vd_pse_v2_0/train_infer_python.txt b/test_tipc/configs/det_r50_vd_pse_v2_0/train_infer_python.txt new file mode 100644 index 0000000..6658905 --- /dev/null +++ b/test_tipc/configs/det_r50_vd_pse_v2_0/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:det_r50_vd_pse_v2_0 +python:python3.7 +gpu_list:0 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=500 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/det_r50_vd_pse_v2_0/det_r50_vd_pse.yml -o Global.print_batch_step=1 Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/det_r50_vd_pse_v2_0/det_r50_vd_pse.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/det_r50_vd_pse_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/det_r50_vd_pse_v2_0/det_r50_vd_pse.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +--save_log_path:null +--benchmark:True +--det_algorithm:PSE +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/det_r50_vd_sast_icdar15_v2_0/det_r50_vd_sast_icdar2015.yml b/test_tipc/configs/det_r50_vd_sast_icdar15_v2_0/det_r50_vd_sast_icdar2015.yml new file mode 100644 index 0000000..8055884 --- /dev/null +++ b/test_tipc/configs/det_r50_vd_sast_icdar15_v2_0/det_r50_vd_sast_icdar2015.yml @@ -0,0 +1,110 @@ +Global: + use_gpu: true + epoch_num: 5000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/sast_r50_vd_ic15/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/sast_r50_vd_ic15/predicts_sast.txt + + +Architecture: + model_type: det + algorithm: SAST + Transform: + Backbone: + name: ResNet_SAST + layers: 50 + Neck: + name: SASTFPN + with_cab: True + Head: + name: SASTHead + +Loss: + name: SASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: SASTPostProcess + score_thresh: 0.5 + sample_pts_num: 2 + nms_thresh: 0.2 + expand_scale: 1.0 + shrink_ratio_of_width: 0.3 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/train_icdar2015_label.txt + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - SASTProcessTrain: + image_shape: [512, 512] + min_crop_side_ratio: 0.3 + min_crop_size: 24 + min_text_size: 4 + max_text_size: 512 + - KeepKeys: + keep_keys: ['image', 'score_map', 'border_map', 'training_mask', 'tvo_map', 'tco_map'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/icdar2015/text_localization/ + label_file_list: + - ./train_data/icdar2015/text_localization/test_icdar2015_label.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + resize_long: 1536 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/test_tipc/configs/det_r50_vd_sast_icdar15_v2_0/train_infer_python.txt b/test_tipc/configs/det_r50_vd_sast_icdar15_v2_0/train_infer_python.txt new file mode 100644 index 0000000..b01f192 --- /dev/null +++ b/test_tipc/configs/det_r50_vd_sast_icdar15_v2_0/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:det_r50_vd_sast_icdar15_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=500 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:./pretrain_models/det_r50_vd_sast_icdar15_v2.0_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/det_r50_vd_sast_icdar15_v2_0/det_r50_vd_sast_icdar2015.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/det_r50_vd_sast_icdar15_v2_0/det_r50_vd_sast_icdar2015.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:./inference/det_r50_vd_sast_icdar15_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/det_r50_vd_sast_icdar15_v2_0/det_r50_vd_sast_icdar2015.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/00008790.jpg +null:null +--benchmark:True +--det_algorithm:SAST +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/det_r50_vd_sast_totaltext_v2_0/det_r50_vd_sast_totaltext.yml b/test_tipc/configs/det_r50_vd_sast_totaltext_v2_0/det_r50_vd_sast_totaltext.yml new file mode 100644 index 0000000..6c6a9f1 --- /dev/null +++ b/test_tipc/configs/det_r50_vd_sast_totaltext_v2_0/det_r50_vd_sast_totaltext.yml @@ -0,0 +1,108 @@ +Global: + use_gpu: true + epoch_num: 5000 + log_smooth_window: 20 + print_batch_step: 2 + save_model_dir: ./output/sast_r50_vd_tt/ + save_epoch_step: 1000 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [4000, 5000] + cal_metric_during_train: False + pretrained_model: ./pretrain_models/ResNet50_vd_ssld_pretrained + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + save_res_path: ./output/sast_r50_vd_tt/predicts_sast.txt + +Architecture: + model_type: det + algorithm: SAST + Transform: + Backbone: + name: ResNet_SAST + layers: 50 + Neck: + name: SASTFPN + with_cab: True + Head: + name: SASTHead + +Loss: + name: SASTLoss + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + # name: Cosine + learning_rate: 0.001 + # warmup_epoch: 0 + regularizer: + name: 'L2' + factor: 0 + +PostProcess: + name: SASTPostProcess + score_thresh: 0.5 + sample_pts_num: 6 + nms_thresh: 0.2 + expand_scale: 1.2 + shrink_ratio_of_width: 0.2 + +Metric: + name: DetMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/total_text/train + label_file_list: [./train_data/total_text/train/train.txt] + ratio_list: [1.0] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - SASTProcessTrain: + image_shape: [512, 512] + min_crop_side_ratio: 0.3 + min_crop_size: 24 + min_text_size: 4 + max_text_size: 512 + - KeepKeys: + keep_keys: ['image', 'score_map', 'border_map', 'training_mask', 'tvo_map', 'tco_map'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 4 + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ + label_file_list: + - ./train_data/total_text/test/test.txt + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - DetLabelEncode: # Class handling label + - DetResizeForTest: + resize_long: 768 + - NormalizeImage: + scale: 1./255. + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: ['image', 'shape', 'polys', 'ignore_tags'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 # must be 1 + num_workers: 2 diff --git a/test_tipc/configs/det_r50_vd_sast_totaltext_v2_0/train_infer_python.txt b/test_tipc/configs/det_r50_vd_sast_totaltext_v2_0/train_infer_python.txt new file mode 100644 index 0000000..a47ad68 --- /dev/null +++ b/test_tipc/configs/det_r50_vd_sast_totaltext_v2_0/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:det_r50_vd_sast_totaltext_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=5000 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/icdar2015/text_localization/ch4_test_images/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/det_r50_vd_sast_totaltext_v2_0/det_r50_vd_sast_totaltext.yml -o Global.pretrained_model=./pretrain_models/ResNet50_vd_ssld_pretrained +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/det_r50_vd_sast_totaltext_v2_0/det_r50_vd_sast_totaltext.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:./inference/det_r50_vd_sast_totaltext_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/det_r50_vd_sast_totaltext_v2_0/det_r50_vd_sast_totaltext.yml -o +infer_quant:False +inference:tools/infer/predict_det.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--det_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +--det_algorithm:SAST +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/en_server_pgnetA/train_infer_python.txt b/test_tipc/configs/en_server_pgnetA/train_infer_python.txt new file mode 100644 index 0000000..a9dd4e6 --- /dev/null +++ b/test_tipc/configs/en_server_pgnetA/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:en_server_pgnetA +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=500 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=14 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./train_data/total_text/test/rgb/ +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/e2e/e2e_r50_vd_pg.yml -o Global.pretrained_model=./pretrain_models/en_server_pgnetA/best_accuracy +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/e2e/e2e_r50_vd_pg.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +inference_dir:null +train_model:./inference/en_server_pgnetA/best_accuracy +infer_export:tools/export_model.py -c configs/e2e/e2e_r50_vd_pg.yml -o +infer_quant:False +inference:tools/infer/predict_e2e.py +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--e2e_model_dir: +--image_dir:./inference/ch_det_data_50/all-sum-510/ +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,640,640]}];[{float32,[3,960,960]}] diff --git a/test_tipc/configs/en_table_structure/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/en_table_structure/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..4dd41e3 --- /dev/null +++ b/test_tipc/configs/en_table_structure/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:en_table_structure +use_opencv:True +infer_model:./inference/en_ppocr_mobile_v2.0_table_structure_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_img_h=32 --det_model_dir=./inference/en_ppocr_mobile_v2.0_table_det_infer --rec_model_dir=./inference/en_ppocr_mobile_v2.0_table_rec_infer --rec_char_dict_path=./ppocr/utils/dict/table_dict.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --limit_side_len=736 --limit_type=min --output=./output/table --merge_no_span_structure=False --type=structure --table=True +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:True +--det:True +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/en_table_structure/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/en_table_structure/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..e81bae8 --- /dev/null +++ b/test_tipc/configs/en_table_structure/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:en_table_structure +python:python3.7 +2onnx: paddle2onnx +--det_model_dir:./inference/en_ppocr_mobile_v2.0_table_structure_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file:./inference/en_ppocr_mobile_v2.0_table_structure_infer/model.onnx +--rec_model_dir: +--rec_save_file: +--opset_version:10 +--enable_onnx_checker:True +inference:ppstructure/table/predict_structure.py --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg diff --git a/test_tipc/configs/en_table_structure/train_infer_python.txt b/test_tipc/configs/en_table_structure/train_infer_python.txt new file mode 100644 index 0000000..8861ea8 --- /dev/null +++ b/test_tipc/configs/en_table_structure/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:en_table_structure +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:./pretrain_models/en_ppocr_mobile_v2.0_table_structure_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./ppstructure/docs/table/table.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/table/table_mv3.yml -o Global.print_batch_step=1 Train.loader.shuffle=false Train.dataset.data_dir=./train_data/pubtabnet/train Train.dataset.label_file_list=[./train_data/pubtabnet/train.jsonl] Eval.dataset.data_dir=./train_data/pubtabnet/test Eval.dataset.label_file_list=[./train_data/pubtabnet/test.jsonl] +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/table/table_mv3.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/en_ppocr_mobile_v2.0_table_structure_infer +infer_export:null +infer_quant:False +inference:ppstructure/table/predict_table.py --det_model_dir=./inference/en_ppocr_mobile_v2.0_table_det_infer --rec_model_dir=./inference/en_ppocr_mobile_v2.0_table_rec_infer --rec_char_dict_path=./ppocr/utils/dict/table_dict.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --image_dir=./ppstructure/docs/table/table.jpg --det_limit_side_len=736 --det_limit_type=min --output ./output/table +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,488,488]}] +===========================train_benchmark_params========================== +batch_size:32 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/en_table_structure/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/en_table_structure/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..8e25b9d --- /dev/null +++ b/test_tipc/configs/en_table_structure/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:en_table_structure +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:./pretrain_models/en_ppocr_mobile_v2.0_table_structure_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./ppstructure/docs/table/table.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/table/table_mv3.yml -o Train.dataset.data_dir=./train_data/pubtabnet/train Train.dataset.label_file_list=[./train_data/pubtabnet/train.jsonl] Eval.dataset.data_dir=./train_data/pubtabnet/test Eval.dataset.label_file_list=[./train_data/pubtabnet/test.jsonl] +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/table/table_mv3.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/en_ppocr_mobile_v2.0_table_structure_infer +infer_export:null +infer_quant:False +inference:ppstructure/table/predict_table.py --det_model_dir=./inference/en_ppocr_mobile_v2.0_table_det_infer --rec_model_dir=./inference/en_ppocr_mobile_v2.0_table_rec_infer --rec_char_dict_path=./ppocr/utils/dict/table_dict.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --image_dir=./ppstructure/docs/table/table.jpg --det_limit_side_len=736 --det_limit_type=min --output ./output/table +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,488,488]}] diff --git a/test_tipc/configs/en_table_structure/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/en_table_structure/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..a399e35 --- /dev/null +++ b/test_tipc/configs/en_table_structure/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:en_table_structure +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:./pretrain_models/en_ppocr_mobile_v2.0_table_structure_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./ppstructure/docs/table/table.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/table/table_mv3.yml -o Train.dataset.data_dir=./train_data/pubtabnet/train Train.dataset.label_file_list=[./train_data/pubtabnet/train.jsonl] Eval.dataset.data_dir=./train_data/pubtabnet/test Eval.dataset.label_file_list=[./train_data/pubtabnet/test.jsonl] +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/table/table_mv3.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/en_ppocr_mobile_v2.0_table_structure_infer +infer_export:null +infer_quant:False +inference:ppstructure/table/predict_table.py --det_model_dir=./inference/en_ppocr_mobile_v2.0_table_det_infer --rec_model_dir=./inference/en_ppocr_mobile_v2.0_table_rec_infer --rec_char_dict_path=./ppocr/utils/dict/table_dict.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --image_dir=./ppstructure/docs/table/table.jpg --det_limit_side_len=736 --det_limit_type=min --output ./output/table +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,488,488]}] diff --git a/test_tipc/configs/en_table_structure/train_pact_infer_python.txt b/test_tipc/configs/en_table_structure/train_pact_infer_python.txt new file mode 100644 index 0000000..0bb04c4 --- /dev/null +++ b/test_tipc/configs/en_table_structure/train_pact_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:en_table_structure_PACT +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=2 +Global.pretrained_model:./pretrain_models/en_ppocr_mobile_v2.0_table_structure_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./ppstructure/docs/table/table.jpg +null:null +## +trainer:pact_train +norm_train:null +pact_train:deploy/slim/quantization/quant.py -c configs/table/table_mv3.yml -o Train.dataset.data_dir=./train_data/pubtabnet/train Train.dataset.label_file_list=[./train_data/pubtabnet/train.jsonl] Eval.dataset.data_dir=./train_data/pubtabnet/test Eval.dataset.label_file_list=[./train_data/pubtabnet/test.jsonl] +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:deploy/slim/quantization/export_model.py -c configs/table/table_mv3.yml -o +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/en_ppocr_mobile_v2.0_table_structure_infer +infer_export:null +infer_quant:True +inference:ppstructure/table/predict_table.py --det_model_dir=./inference/en_ppocr_mobile_v2.0_table_det_infer --rec_model_dir=./inference/en_ppocr_mobile_v2.0_table_rec_infer --rec_char_dict_path=./ppocr/utils/dict/table_dict.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --image_dir=./ppstructure/docs/table/table.jpg --det_limit_side_len=736 --det_limit_type=min --output ./output/table +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,488,488]}] diff --git a/test_tipc/configs/en_table_structure/train_ptq_infer_python.txt b/test_tipc/configs/en_table_structure/train_ptq_infer_python.txt new file mode 100644 index 0000000..aae0895 --- /dev/null +++ b/test_tipc/configs/en_table_structure/train_ptq_infer_python.txt @@ -0,0 +1,21 @@ +===========================train_params=========================== +model_name:en_table_structure_KL +python:python3.7 +Global.pretrained_model: +Global.save_inference_dir:null +infer_model:./inference/en_ppocr_mobile_v2.0_table_structure_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c configs/table/table_mv3.yml -o +infer_quant:True +inference:ppstructure/table/predict_table.py --det_model_dir=./inference/en_ppocr_mobile_v2.0_table_det_infer --rec_model_dir=./inference/en_ppocr_mobile_v2.0_table_rec_infer --rec_char_dict_path=./ppocr/utils/dict/table_dict.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --image_dir=./ppstructure/docs/table/table.jpg --det_limit_side_len=736 --det_limit_type=min --output ./output/table +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:int8 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:False +null:null +null:null diff --git a/test_tipc/configs/en_table_structure_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/en_table_structure_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..2f457dc --- /dev/null +++ b/test_tipc/configs/en_table_structure_PACT/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:en_table_structure_PACT +use_opencv:True +infer_model:./inference/en_ppocr_mobile_v2.0_table_structure_slim_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --rec_img_h=32 --det_model_dir=./inference/en_ppocr_mobile_v2.0_table_det_infer --rec_model_dir=./inference/en_ppocr_mobile_v2.0_table_rec_infer --rec_char_dict_path=./ppocr/utils/dict/table_dict.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --limit_side_len=736 --limit_type=min --output=./output/table --merge_no_span_structure=False --type=structure --table=True +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:True +--det:True +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml b/test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml new file mode 100644 index 0000000..31e0ed4 --- /dev/null +++ b/test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml @@ -0,0 +1,122 @@ +Global: + use_gpu: True + epoch_num: &epoch_num 200 + log_smooth_window: 10 + print_batch_step: 10 + save_model_dir: ./output/ser_layoutxlm_xfund_zh + save_epoch_step: 2000 + # evaluation is run every 10 iterations after the 0th iteration + eval_batch_step: [ 0, 187 ] + cal_metric_during_train: False + save_inference_dir: + use_visualdl: False + seed: 2022 + infer_img: ppstructure/docs/kie/input/zh_val_42.jpg + save_res_path: ./output/ser_layoutxlm_xfund_zh/res + +Architecture: + model_type: kie + algorithm: &algorithm "LayoutXLM" + Transform: + Backbone: + name: LayoutXLMForSer + pretrained: True + checkpoints: + num_classes: &num_classes 7 + +Loss: + name: VQASerTokenLayoutLMLoss + num_classes: *num_classes + key: "backbone_out" + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Linear + learning_rate: 0.00005 + epochs: *epoch_num + warmup_epoch: 2 + regularizer: + name: L2 + factor: 0.00000 + +PostProcess: + name: VQASerTokenLayoutLMPostProcess + class_path: &class_path train_data/XFUND/class_list_xfun.txt + +Metric: + name: VQASerTokenMetric + main_indicator: hmean + +Train: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_train/image + label_file_list: + - train_data/XFUND/zh_train/train.json + ratio_list: [ 1.0 ] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: &max_seq_len 512 + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: True + drop_last: False + batch_size_per_card: 8 + num_workers: 16 + +Eval: + dataset: + name: SimpleDataSet + data_dir: train_data/XFUND/zh_val/image + label_file_list: + - train_data/XFUND/zh_val/val.json + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VQATokenLabelEncode: # Class handling label + contains_re: False + algorithm: *algorithm + class_path: *class_path + - VQATokenPad: + max_seq_len: *max_seq_len + return_attention_mask: True + - VQASerTokenChunk: + max_seq_len: *max_seq_len + - Resize: + size: [224,224] + - NormalizeImage: + scale: 1 + mean: [ 123.675, 116.28, 103.53 ] + std: [ 58.395, 57.12, 57.375 ] + order: 'hwc' + - ToCHWImage: + - KeepKeys: + keep_keys: [ 'input_ids', 'bbox', 'attention_mask', 'token_type_ids', 'image', 'labels'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 8 + num_workers: 4 diff --git a/test_tipc/configs/layoutxlm_ser/train_infer_python.txt b/test_tipc/configs/layoutxlm_ser/train_infer_python.txt new file mode 100644 index 0000000..d07daa9 --- /dev/null +++ b/test_tipc/configs/layoutxlm_ser/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:layoutxlm_ser +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=17 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=4|whole_train_whole_infer=8 +Architecture.Backbone.checkpoints:null +train_model_name:latest +train_infer_img_dir:ppstructure/docs/kie/input/zh_val_42.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml -o Global.print_batch_step=1 Global.eval_batch_step=[1000,1000] Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Architecture.Backbone.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:null +infer_export:null +infer_quant:False +inference:ppstructure/kie/predict_kie_token_ser.py --kie_algorithm=LayoutXLM --ser_dict_path=train_data/XFUND/class_list_xfun.txt --output=output +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--ser_model_dir: +--image_dir:./ppstructure/docs/kie/input/zh_val_42.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,224,224]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:3 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98 diff --git a/test_tipc/configs/layoutxlm_ser/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/layoutxlm_ser/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..96b43ce --- /dev/null +++ b/test_tipc/configs/layoutxlm_ser/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:layoutxlm_ser +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=17 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=4|whole_train_whole_infer=8 +Architecture.Backbone.checkpoints:null +train_model_name:latest +train_infer_img_dir:ppstructure/docs/kie/input/zh_val_42.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Architecture.Backbone.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:null +infer_export:null +infer_quant:False +inference:ppstructure/kie/predict_kie_token_ser.py --kie_algorithm=LayoutXLM --ser_dict_path=train_data/XFUND/class_list_xfun.txt --output=output +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--ser_model_dir: +--image_dir:./ppstructure/docs/kie/input/zh_val_42.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,224,224]}] diff --git a/test_tipc/configs/layoutxlm_ser/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/layoutxlm_ser/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..8353957 --- /dev/null +++ b/test_tipc/configs/layoutxlm_ser/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:layoutxlm_ser +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=17 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=4|whole_train_whole_infer=8 +Architecture.Backbone.checkpoints:null +train_model_name:latest +train_infer_img_dir:ppstructure/docs/kie/input/zh_val_42.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml -o Global.print_batch_step=1 Global.eval_batch_step=[1000,1000] Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Architecture.Backbone.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:null +infer_export:null +infer_quant:False +inference:ppstructure/kie/predict_kie_token_ser.py --kie_algorithm=LayoutXLM --ser_dict_path=train_data/XFUND/class_list_xfun.txt --output=output +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--ser_model_dir: +--image_dir:./ppstructure/docs/kie/input/zh_val_42.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,224,224]}] diff --git a/test_tipc/configs/layoutxlm_ser/train_pact_infer_python.txt b/test_tipc/configs/layoutxlm_ser/train_pact_infer_python.txt new file mode 100644 index 0000000..c19b4b7 --- /dev/null +++ b/test_tipc/configs/layoutxlm_ser/train_pact_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:layoutxlm_ser_PACT +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=17 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=4|whole_train_whole_infer=8 +Architecture.Backbone.pretrained:pretrain_models/ser_LayoutXLM_xfun_zh +train_model_name:latest +train_infer_img_dir:ppstructure/docs/kie/input/zh_val_42.jpg +null:null +## +trainer:pact_train +norm_train:null +pact_train:deploy/slim/quantization/quant.py -c test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml -o Global.eval_batch_step=[2000,10] +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Architecture.Backbone.checkpoints: +norm_export:null +quant_export:deploy/slim/quantization/export_model.py -c test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml -o +fpgm_export: null +distill_export:null +export1:null +export2:null +## +infer_model:null +infer_export:null +infer_quant:False +inference:ppstructure/kie/predict_kie_token_ser.py --kie_algorithm=LayoutXLM --ser_dict_path=train_data/XFUND/class_list_xfun.txt --output=output +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--ser_model_dir: +--image_dir:./ppstructure/docs/kie/input/zh_val_42.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,224,224]}] diff --git a/test_tipc/configs/layoutxlm_ser/train_ptq_infer_python.txt b/test_tipc/configs/layoutxlm_ser/train_ptq_infer_python.txt new file mode 100644 index 0000000..47e1e70 --- /dev/null +++ b/test_tipc/configs/layoutxlm_ser/train_ptq_infer_python.txt @@ -0,0 +1,21 @@ +===========================train_params=========================== +model_name:layoutxlm_ser_KL +python:python3.7 +Global.pretrained_model: +Global.save_inference_dir:null +infer_model:./inference/ser_LayoutXLM_xfun_zh_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c test_tipc/configs/layoutxlm_ser/ser_layoutxlm_xfund_zh.yml -o Train.loader.batch_size_per_card=1 Eval.loader.batch_size_per_card=1 +infer_quant:True +inference:ppstructure/kie/predict_kie_token_ser.py --kie_algorithm=LayoutXLM --ser_dict_path=./train_data/XFUND/class_list_xfun.txt +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:int8 +--ser_model_dir: +--image_dir:./ppstructure/docs/kie/input/zh_val_42.jpg +null:null +--benchmark:False +null:null +null:null diff --git a/test_tipc/configs/rec_d28_can/rec_d28_can.yml b/test_tipc/configs/rec_d28_can/rec_d28_can.yml new file mode 100644 index 0000000..5501865 --- /dev/null +++ b/test_tipc/configs/rec_d28_can/rec_d28_can.yml @@ -0,0 +1,122 @@ +Global: + use_gpu: True + epoch_num: 240 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/can/ + save_epoch_step: 1 + # evaluation is run every 1105 iterations (1 epoch)(batch_size = 8) + eval_batch_step: [0, 1105] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/datasets/crohme_demo/hme_00.jpg + # for data or label process + character_dict_path: ppocr/utils/dict/latex_symbol_dict.txt + max_text_length: 36 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_can.txt + +Optimizer: + name: Momentum + momentum: 0.9 + clip_norm_global: 100.0 + lr: + name: TwoStepCosine + learning_rate: 0.01 + warmup_epoch: 1 + weight_decay: 0.0001 + +Architecture: + model_type: rec + algorithm: CAN + in_channels: 1 + Transform: + Backbone: + name: DenseNet + growthRate: 24 + reduction: 0.5 + bottleneck: True + use_dropout: True + input_channel: 1 + Head: + name: CANHead + in_channel: 684 + out_channel: 111 + max_text_length: 36 + ratio: 16 + attdecoder: + is_train: True + input_size: 256 + hidden_size: 256 + encoder_out_channel: 684 + dropout: True + dropout_ratio: 0.5 + word_num: 111 + counting_decoder_out_channel: 111 + attention: + attention_dim: 512 + word_conv_kernel: 1 + +Loss: + name: CANLoss + +PostProcess: + name: CANLabelDecode + +Metric: + name: CANMetric + main_indicator: exp_rate + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/CROHME_lite/training/images/ + label_file_list: ["./train_data/CROHME_lite/training/labels.txt"] + transforms: + - DecodeImage: + channel_first: False + - NormalizeImage: + mean: [0,0,0] + std: [1,1,1] + order: 'hwc' + - GrayImageChannelFormat: + inverse: True + - CANLabelEncode: + lower: False + - KeepKeys: + keep_keys: ['image', 'label'] + loader: + shuffle: True + batch_size_per_card: 8 + drop_last: False + num_workers: 4 + collate_fn: DyMaskCollator + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/CROHME_lite/evaluation/images/ + label_file_list: ["./train_data/CROHME_lite/evaluation/labels.txt"] + transforms: + - DecodeImage: + channel_first: False + - NormalizeImage: + mean: [0,0,0] + std: [1,1,1] + order: 'hwc' + - GrayImageChannelFormat: + inverse: True + - CANLabelEncode: + lower: False + - KeepKeys: + keep_keys: ['image', 'label'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 + num_workers: 4 + collate_fn: DyMaskCollator diff --git a/test_tipc/configs/rec_d28_can/train_infer_python.txt b/test_tipc/configs/rec_d28_can/train_infer_python.txt new file mode 100644 index 0000000..731d327 --- /dev/null +++ b/test_tipc/configs/rec_d28_can/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_d28_can +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=240 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=8 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./doc/datasets/crohme_demo +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_d28_can/rec_d28_can.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_d28_can/rec_d28_can.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_d28_can/rec_d28_can.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_d28_can_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_d28_can/rec_d28_can.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/dict/latex_symbol_dict.txt --rec_algorithm="CAN" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./doc/datasets/crohme_demo +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[1,100,100]}] diff --git a/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml b/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml new file mode 100644 index 0000000..8118d58 --- /dev/null +++ b/test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml @@ -0,0 +1,103 @@ +Global: + use_gpu: True + epoch_num: 21 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/nrtr/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_nrtr.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.99 + clip_norm: 5.0 + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 + regularizer: + name: 'L2' + factor: 0. + +Architecture: + model_type: rec + algorithm: NRTR + in_channels: 1 + Transform: + Backbone: + name: MTB + cnn_num: 2 + Head: + name: Transformer + d_model: 512 + num_encoder_layers: 6 + beam_size: -1 # When Beam size is greater than 0, it means to use beam search when evaluation. + + +Loss: + name: CELoss + smoothing: True + +PostProcess: + name: NRTRLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - NRTRLabelEncode: # Class handling label + - GrayRecResizeImg: + image_shape: [100, 32] + resize_type: PIL # PIL or OpenCV + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 512 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - NRTRLabelEncode: # Class handling label + - GrayRecResizeImg: + image_shape: [100, 32] + resize_type: PIL # PIL or OpenCV + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/test_tipc/configs/rec_mtb_nrtr/train_infer_python.txt b/test_tipc/configs/rec_mtb_nrtr/train_infer_python.txt new file mode 100644 index 0000000..4a8fda0 --- /dev/null +++ b/test_tipc/configs/rec_mtb_nrtr/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_mtb_nrtr +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_mtb_nrtr_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_mtb_nrtr/rec_mtb_nrtr.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="1,32,100" --rec_algorithm="NRTR" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[1,32,100]}] diff --git a/test_tipc/configs/rec_mv3_none_bilstm_ctc_v2_0/rec_icdar15_train.yml b/test_tipc/configs/rec_mv3_none_bilstm_ctc_v2_0/rec_icdar15_train.yml new file mode 100644 index 0000000..b0ba615 --- /dev/null +++ b/test_tipc/configs/rec_mv3_none_bilstm_ctc_v2_0/rec_icdar15_train.yml @@ -0,0 +1,97 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/mv3_none_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 20000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_none_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 96 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/test_tipc/configs/rec_mv3_none_bilstm_ctc_v2_0/train_infer_python.txt b/test_tipc/configs/rec_mv3_none_bilstm_ctc_v2_0/train_infer_python.txt new file mode 100644 index 0000000..22c29c9 --- /dev/null +++ b/test_tipc/configs/rec_mv3_none_bilstm_ctc_v2_0/train_infer_python.txt @@ -0,0 +1,59 @@ +===========================train_params=========================== +model_name:rec_mv3_none_bilstm_ctc_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=100 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_mv3_none_bilstm_ctc_v2_0/rec_icdar15_train.yml -o Global.print_batch_step=4 Train.loader.shuffle=false +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_mv3_none_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_mv3_none_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_mv3_none_bilstm_ctc_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_mv3_none_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,100" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] +===========================train_benchmark_params========================== +batch_size:256 +fp_items:fp32|fp16 +epoch:4 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 diff --git a/test_tipc/configs/rec_mv3_none_none_ctc_v2_0/rec_icdar15_train.yml b/test_tipc/configs/rec_mv3_none_none_ctc_v2_0/rec_icdar15_train.yml new file mode 100644 index 0000000..06a2b71 --- /dev/null +++ b/test_tipc/configs/rec_mv3_none_none_ctc_v2_0/rec_icdar15_train.yml @@ -0,0 +1,96 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/mv3_none_none_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_none_none_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: Rosetta + Transform: + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + fc_decay: 0.0004 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/test_tipc/configs/rec_mv3_none_none_ctc_v2_0/train_infer_python.txt b/test_tipc/configs/rec_mv3_none_none_ctc_v2_0/train_infer_python.txt new file mode 100644 index 0000000..0b66a97 --- /dev/null +++ b/test_tipc/configs/rec_mv3_none_none_ctc_v2_0/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_mv3_none_none_ctc_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=100 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_mv3_none_none_ctc_v2_0/rec_icdar15_train.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_mv3_none_none_ctc_v2_0/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_mv3_none_none_ctc_v2_0/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_mv3_none_none_ctc_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_mv3_none_none_ctc_v2_0/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,100" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/rec_mv3_tps_bilstm_att_v2_0/rec_mv3_tps_bilstm_att.yml b/test_tipc/configs/rec_mv3_tps_bilstm_att_v2_0/rec_mv3_tps_bilstm_att.yml new file mode 100644 index 0000000..2b14c04 --- /dev/null +++ b/test_tipc/configs/rec_mv3_tps_bilstm_att_v2_0/rec_mv3_tps_bilstm_att.yml @@ -0,0 +1,103 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/rec_mv3_tps_bilstm_att/ + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_tps_bilstm_att.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0.00001 + +Architecture: + model_type: rec + algorithm: RARE + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: small + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 96 + Head: + name: AttentionHead + hidden_size: 96 + + +Loss: + name: AttentionLoss + +PostProcess: + name: AttnLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 1 diff --git a/test_tipc/configs/rec_mv3_tps_bilstm_att_v2_0/train_infer_python.txt b/test_tipc/configs/rec_mv3_tps_bilstm_att_v2_0/train_infer_python.txt new file mode 100644 index 0000000..77dc79c --- /dev/null +++ b/test_tipc/configs/rec_mv3_tps_bilstm_att_v2_0/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_mv3_tps_bilstm_att_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_mv3_tps_bilstm_att_v2_0/rec_mv3_tps_bilstm_att.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_mv3_tps_bilstm_att_v2_0/rec_mv3_tps_bilstm_att.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_mv3_tps_bilstm_att_v2_0/rec_mv3_tps_bilstm_att.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_mv3_tps_bilstm_att_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_mv3_tps_bilstm_att_v2_0/rec_mv3_tps_bilstm_att.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,100" --rec_algorithm="RARE" --min_subgraph_size=5 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/rec_mv3_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml b/test_tipc/configs/rec_mv3_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml new file mode 100644 index 0000000..e20ea00 --- /dev/null +++ b/test_tipc/configs/rec_mv3_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml @@ -0,0 +1,101 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/mv3_tps_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_mv3_tps_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: STARNet + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: small + Backbone: + name: MobileNetV3 + scale: 0.5 + model_name: large + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 96 + Head: + name: CTCHead + fc_decay: 0.0004 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/test_tipc/configs/rec_mv3_tps_bilstm_ctc_v2_0/train_infer_python.txt b/test_tipc/configs/rec_mv3_tps_bilstm_ctc_v2_0/train_infer_python.txt new file mode 100644 index 0000000..f38c8d8 --- /dev/null +++ b/test_tipc/configs/rec_mv3_tps_bilstm_ctc_v2_0/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_mv3_tps_bilstm_ctc_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=100 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_mv3_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_mv3_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_mv3_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_mv3_tps_bilstm_ctc_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_mv3_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,100" --rec_algorithm="StarNet" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/rec_parseq/rec_vit_parseq.yml b/test_tipc/configs/rec_parseq/rec_vit_parseq.yml new file mode 100644 index 0000000..fc283ba --- /dev/null +++ b/test_tipc/configs/rec_parseq/rec_vit_parseq.yml @@ -0,0 +1,118 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/rec/parseq + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 500] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + num_heads: 8 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_parseq.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: OneCycle + max_lr: 0.0007 + +Architecture: + model_type: rec + algorithm: ParseQ + in_channels: 3 + Transform: + Backbone: + name: ViTParseQ + img_size: [32, 128] + patch_size: [4, 8] + embed_dim: 384 + depth: 12 + num_heads: 6 + mlp_ratio: 4 + in_channels: 3 + Head: + name: ParseQHead + # Architecture + max_text_length: 25 + embed_dim: 384 + dec_num_heads: 12 + dec_mlp_ratio: 4 + dec_depth: 1 + # Training + perm_num: 6 + perm_forward: true + perm_mirrored: true + dropout: 0.1 + # Decoding mode (test) + decode_ar: true + refine_iters: 1 + +Loss: + name: ParseQLoss + +PostProcess: + name: ParseQLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + is_filter: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ParseQRecAug: + aug_type: 0 # or 1 + - ParseQLabelEncode: + - SVTRRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 192 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ParseQLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 384 + num_workers: 4 diff --git a/test_tipc/configs/rec_parseq/train_infer_python.txt b/test_tipc/configs/rec_parseq/train_infer_python.txt new file mode 100644 index 0000000..2e441fc --- /dev/null +++ b/test_tipc/configs/rec_parseq/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:rec_parseq +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_parseq/rec_vit_parseq.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_parseq/rec_vit_parseq.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_parseq/rec_vit_parseq.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_parseq_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_parseq/rec_vit_parseq.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,128" --rec_algorithm="ParseQ" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,128]}] +===========================train_benchmark_params========================== +batch_size:512 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================to_static_train_benchmark_params=========================== +to_static_train:Global.to_static=true diff --git a/test_tipc/configs/rec_r31_robustscanner/rec_r31_robustscanner.yml b/test_tipc/configs/rec_r31_robustscanner/rec_r31_robustscanner.yml new file mode 100644 index 0000000..b5466d4 --- /dev/null +++ b/test_tipc/configs/rec_r31_robustscanner/rec_r31_robustscanner.yml @@ -0,0 +1,111 @@ +Global: + use_gpu: true + epoch_num: 5 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./output/rec/rec_r31_robustscanner/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: ./inference/rec_inference + # for data or label process + character_dict_path: ppocr/utils/dict90.txt + max_text_length: &max_text_length 40 + infer_mode: False + use_space_char: False + rm_symbol: True + save_res_path: ./output/rec/predicts_robustscanner.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs: [3, 4] + values: [0.001, 0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: RobustScanner + Transform: + Backbone: + name: ResNet31 + init_type: KaimingNormal + Head: + name: RobustScannerHead + enc_outchannles: 128 + hybrid_dec_rnn_layers: 2 + hybrid_dec_dropout: 0 + position_dec_rnn_layers: 2 + start_idx: 91 + mask: True + padding_idx: 92 + encode_value: False + max_text_length: *max_text_length + +Loss: + name: SARLoss + +PostProcess: + name: SARLabelDecode + +Metric: + name: RecMetric + is_filter: True + + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - RobustScannerRecResizeImg: + image_shape: [3, 48, 48, 160] # h:48 w:[48,160] + width_downsample_ratio: 0.25 + max_text_length: *max_text_length + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio', 'word_positons'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 16 + drop_last: True + num_workers: 0 + use_shared_memory: False + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - RobustScannerRecResizeImg: + image_shape: [3, 48, 48, 160] + max_text_length: *max_text_length + width_downsample_ratio: 0.25 + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio', 'word_positons'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 16 + num_workers: 0 + use_shared_memory: False + diff --git a/test_tipc/configs/rec_r31_robustscanner/train_infer_python.txt b/test_tipc/configs/rec_r31_robustscanner/train_infer_python.txt new file mode 100644 index 0000000..c2e2f2b --- /dev/null +++ b/test_tipc/configs/rec_r31_robustscanner/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_r31_robustscanner +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=5 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r31_robustscanner/rec_r31_robustscanner.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r31_robustscanner/rec_r31_robustscanner.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r31_robustscanner/rec_r31_robustscanner.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r31_robustscanner/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r31_robustscanner/rec_r31_robustscanner.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/dict90.txt --rec_image_shape="3,48,48,160" --use_space_char=False --rec_algorithm="RobustScanner" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,160]}] diff --git a/test_tipc/configs/rec_r31_sar/rec_r31_sar.yml b/test_tipc/configs/rec_r31_sar/rec_r31_sar.yml new file mode 100644 index 0000000..36bc3c5 --- /dev/null +++ b/test_tipc/configs/rec_r31_sar/rec_r31_sar.yml @@ -0,0 +1,98 @@ +Global: + use_gpu: true + epoch_num: 5 + log_smooth_window: 20 + print_batch_step: 20 + save_model_dir: ./sar_rec + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: + # for data or label process + character_dict_path: ppocr/utils/dict90.txt + max_text_length: 30 + infer_mode: False + use_space_char: False + rm_symbol: True + save_res_path: ./output/rec/predicts_sar.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs: [3, 4] + values: [0.001, 0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: SAR + Transform: + Backbone: + name: ResNet31 + Head: + name: SARHead + +Loss: + name: SARLoss + +PostProcess: + name: SARLabelDecode + +Metric: + name: RecMetric + + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - SARRecResizeImg: + image_shape: [3, 48, 48, 160] # h:48 w:[48,160] + width_downsample_ratio: 0.25 + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 64 + drop_last: True + num_workers: 8 + use_shared_memory: False + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SARLabelEncode: # Class handling label + - SARRecResizeImg: + image_shape: [3, 48, 48, 160] + width_downsample_ratio: 0.25 + - KeepKeys: + keep_keys: ['image', 'label', 'valid_ratio'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 64 + num_workers: 4 + use_shared_memory: False + diff --git a/test_tipc/configs/rec_r31_sar/train_infer_python.txt b/test_tipc/configs/rec_r31_sar/train_infer_python.txt new file mode 100644 index 0000000..4acc622 --- /dev/null +++ b/test_tipc/configs/rec_r31_sar/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_r31_sar +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r31_sar/rec_r31_sar.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r31_sar/rec_r31_sar.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r31_sar/rec_r31_sar.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r31_sar_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r31_sar/rec_r31_sar.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/dict90.txt --rec_image_shape="3,48,48,160" --rec_algorithm="SAR" +--use_gpu:True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,48,160]}] diff --git a/test_tipc/configs/rec_r32_gaspin_bilstm_att/rec_r32_gaspin_bilstm_att.yml b/test_tipc/configs/rec_r32_gaspin_bilstm_att/rec_r32_gaspin_bilstm_att.yml new file mode 100644 index 0000000..21d56b6 --- /dev/null +++ b/test_tipc/configs/rec_r32_gaspin_bilstm_att/rec_r32_gaspin_bilstm_att.yml @@ -0,0 +1,117 @@ +Global: + use_gpu: True + epoch_num: 6 + log_smooth_window: 50 + print_batch_step: 50 + save_model_dir: ./output/rec/rec_r32_gaspin_bilstm_att/ + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: pretrain_models/rec_r32_gaspin_bilstm_att_train/best_accuracy + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: ./ppocr/utils/dict/spin_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r32_gaspin_bilstm_att.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + lr: + name: Piecewise + decay_epochs: [3, 4, 5] + values: [0.001, 0.0003, 0.00009, 0.000027] + + clip_norm: 5 + +Architecture: + model_type: rec + algorithm: SPIN + in_channels: 1 + Transform: + name: GA_SPIN + offsets: True + default_type: 6 + loc_lr: 0.1 + stn: True + Backbone: + name: ResNet32 + out_channels: 512 + Neck: + name: SequenceEncoder + encoder_type: cascadernn + hidden_size: 256 + out_channels: [256, 512] + with_linear: True + Head: + name: SPINAttentionHead + hidden_size: 256 + + +Loss: + name: SPINAttentionLoss + ignore_index: 0 + +PostProcess: + name: SPINLabelDecode + use_space_char: False + + +Metric: + name: RecMetric + main_indicator: acc + is_filter: True + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SPINLabelEncode: # Class handling label + - SPINRecResizeImg: + image_shape: [100, 32] + interpolation : 2 + mean: [127.5] + std: [127.5] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 128 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SPINLabelEncode: # Class handling label + - SPINRecResizeImg: + image_shape: [100, 32] + interpolation : 2 + mean: [127.5] + std: [127.5] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 1 + num_workers: 1 diff --git a/test_tipc/configs/rec_r32_gaspin_bilstm_att/train_infer_python.txt b/test_tipc/configs/rec_r32_gaspin_bilstm_att/train_infer_python.txt new file mode 100644 index 0000000..ac378b3 --- /dev/null +++ b/test_tipc/configs/rec_r32_gaspin_bilstm_att/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_r32_gaspin_bilstm_att +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r32_gaspin_bilstm_att/rec_r32_gaspin_bilstm_att.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r32_gaspin_bilstm_att/rec_r32_gaspin_bilstm_att.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r32_gaspin_bilstm_att/rec_r32_gaspin_bilstm_att.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r32_gaspin_bilstm_att/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r32_gaspin_bilstm_att/rec_r32_gaspin_bilstm_att.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/dict/spin_dict.txt --use_space_char=False --rec_image_shape="3,32,100" --rec_algorithm="SPIN" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/rec_r34_vd_none_bilstm_ctc_v2_0/rec_icdar15_train.yml b/test_tipc/configs/rec_r34_vd_none_bilstm_ctc_v2_0/rec_icdar15_train.yml new file mode 100644 index 0000000..3e9ea48 --- /dev/null +++ b/test_tipc/configs/rec_r34_vd_none_bilstm_ctc_v2_0/rec_icdar15_train.yml @@ -0,0 +1,96 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r34_vd_none_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r34_vd_none_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: CRNN + Transform: + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/test_tipc/configs/rec_r34_vd_none_bilstm_ctc_v2_0/train_infer_python.txt b/test_tipc/configs/rec_r34_vd_none_bilstm_ctc_v2_0/train_infer_python.txt new file mode 100644 index 0000000..b9c6b6f --- /dev/null +++ b/test_tipc/configs/rec_r34_vd_none_bilstm_ctc_v2_0/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_r34_vd_none_bilstm_ctc_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=100 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r34_vd_none_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r34_vd_none_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r34_vd_none_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r34_vd_none_bilstm_ctc_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r34_vd_none_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,100" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/rec_r34_vd_none_none_ctc_v2_0/rec_icdar15_train.yml b/test_tipc/configs/rec_r34_vd_none_none_ctc_v2_0/rec_icdar15_train.yml new file mode 100644 index 0000000..ce15e27 --- /dev/null +++ b/test_tipc/configs/rec_r34_vd_none_none_ctc_v2_0/rec_icdar15_train.yml @@ -0,0 +1,94 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r34_vd_none_none_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r34_vd_none_none_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: Rosetta + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + fc_decay: 0.0004 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/test_tipc/configs/rec_r34_vd_none_none_ctc_v2_0/train_infer_python.txt b/test_tipc/configs/rec_r34_vd_none_none_ctc_v2_0/train_infer_python.txt new file mode 100644 index 0000000..cd336f0 --- /dev/null +++ b/test_tipc/configs/rec_r34_vd_none_none_ctc_v2_0/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_r34_vd_none_none_ctc_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=100 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r34_vd_none_none_ctc_v2_0/rec_icdar15_train.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r34_vd_none_none_ctc_v2_0/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r34_vd_none_none_ctc_v2_0/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r34_vd_none_none_ctc_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r34_vd_none_none_ctc_v2_0/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,100" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/rec_r34_vd_tps_bilstm_att_v2_0/rec_r34_vd_tps_bilstm_att.yml b/test_tipc/configs/rec_r34_vd_tps_bilstm_att_v2_0/rec_r34_vd_tps_bilstm_att.yml new file mode 100644 index 0000000..5dd797b --- /dev/null +++ b/test_tipc/configs/rec_r34_vd_tps_bilstm_att_v2_0/rec_r34_vd_tps_bilstm_att.yml @@ -0,0 +1,102 @@ +Global: + use_gpu: True + epoch_num: 400 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/b3_rare_r34_none_gru/ + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_b3_rare_r34_none_gru.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0.00000 + +Architecture: + model_type: rec + algorithm: RARE + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: large + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 #96 + Head: + name: AttentionHead # AttentionHead + hidden_size: 256 # + l2_decay: 0.00001 + +Loss: + name: AttentionLoss + +PostProcess: + name: AttnLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - AttnLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/test_tipc/configs/rec_r34_vd_tps_bilstm_att_v2_0/train_infer_python.txt b/test_tipc/configs/rec_r34_vd_tps_bilstm_att_v2_0/train_infer_python.txt new file mode 100644 index 0000000..0910ff8 --- /dev/null +++ b/test_tipc/configs/rec_r34_vd_tps_bilstm_att_v2_0/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_r34_vd_tps_bilstm_att_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r34_vd_tps_bilstm_att_v2_0/rec_r34_vd_tps_bilstm_att.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r34_vd_tps_bilstm_att_v2_0/rec_r34_vd_tps_bilstm_att.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r34_vd_tps_bilstm_att_v2_0/rec_r34_vd_tps_bilstm_att.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r34_vd_tps_bilstm_att_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r34_vd_tps_bilstm_att_v2_0/rec_r34_vd_tps_bilstm_att.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,100" --rec_algorithm="RARE" --min_subgraph_size=5 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/rec_r34_vd_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml b/test_tipc/configs/rec_r34_vd_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml new file mode 100644 index 0000000..2dcba81 --- /dev/null +++ b/test_tipc/configs/rec_r34_vd_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml @@ -0,0 +1,100 @@ +Global: + use_gpu: true + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r34_vd_tps_bilstm_ctc/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_r34_vd_tps_bilstm_ctc.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + learning_rate: 0.0005 + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: STARNet + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 0.1 + model_name: large + Backbone: + name: ResNet + layers: 34 + Neck: + name: SequenceEncoder + encoder_type: rnn + hidden_size: 256 + Head: + name: CTCHead + fc_decay: 0 + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 256 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - RecResizeImg: + image_shape: [3, 32, 100] + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 diff --git a/test_tipc/configs/rec_r34_vd_tps_bilstm_ctc_v2_0/train_infer_python.txt b/test_tipc/configs/rec_r34_vd_tps_bilstm_ctc_v2_0/train_infer_python.txt new file mode 100644 index 0000000..33144e6 --- /dev/null +++ b/test_tipc/configs/rec_r34_vd_tps_bilstm_ctc_v2_0/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_r34_vd_tps_bilstm_ctc_v2_0 +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=5|whole_train_whole_infer=100 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=128|whole_train_whole_infer=128 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r34_vd_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r34_vd_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r34_vd_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r34_vd_tps_bilstm_ctc_v2.0_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r34_vd_tps_bilstm_ctc_v2_0/rec_icdar15_train.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,100" --rec_algorithm="StarNet" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,100]}] diff --git a/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml b/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml new file mode 100644 index 0000000..5b5890e --- /dev/null +++ b/test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml @@ -0,0 +1,106 @@ +Global: + use_gpu: True + epoch_num: 10 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/r45_abinet/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_abinet.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.99 + clip_norm: 20.0 + lr: + name: Piecewise + decay_epochs: [6] + values: [0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0. + +Architecture: + model_type: rec + algorithm: ABINet + in_channels: 3 + Transform: + Backbone: + name: ResNet45 + + Head: + name: ABINetHead + use_lang: True + iter_size: 3 + + +Loss: + name: CELoss + ignore_index: &ignore_index 100 # Must be greater than the number of character classes + +PostProcess: + name: ABINetLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetRecAug: + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 96 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetLabelEncode: # Class handling label + ignore_index: *ignore_index + - ABINetRecResizeImg: + image_shape: [3, 32, 128] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 4 + use_shared_memory: False diff --git a/test_tipc/configs/rec_r45_abinet/train_infer_python.txt b/test_tipc/configs/rec_r45_abinet/train_infer_python.txt new file mode 100644 index 0000000..04fc188 --- /dev/null +++ b/test_tipc/configs/rec_r45_abinet/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_abinet +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r45_abinet_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r45_abinet/rec_r45_abinet.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,32,128" --rec_algorithm="ABINet" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,32,128]}] diff --git a/test_tipc/configs/rec_r45_visionlan/rec_r45_visionlan.yml b/test_tipc/configs/rec_r45_visionlan/rec_r45_visionlan.yml new file mode 100644 index 0000000..860e4f5 --- /dev/null +++ b/test_tipc/configs/rec_r45_visionlan/rec_r45_visionlan.yml @@ -0,0 +1,108 @@ +Global: + use_gpu: true + epoch_num: 8 + log_smooth_window: 200 + print_batch_step: 200 + save_model_dir: ./output/rec/r45_visionlan + save_epoch_step: 1 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/en/word_2.png + # for data or label process + character_dict_path: + max_text_length: &max_text_length 25 + training_step: &training_step LA + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_visionlan.txt + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 20.0 + group_lr: true + training_step: *training_step + lr: + name: Piecewise + decay_epochs: [6] + values: [0.0001, 0.00001] + regularizer: + name: 'L2' + factor: 0 + +Architecture: + model_type: rec + algorithm: VisionLAN + Transform: + Backbone: + name: ResNet45 + strides: [2, 2, 2, 1, 1] + Head: + name: VLHead + n_layers: 3 + n_position: 256 + n_dim: 512 + max_text_length: *max_text_length + training_step: *training_step + +Loss: + name: VLLoss + mode: *training_step + weight_res: 0.5 + weight_mas: 0.5 + +PostProcess: + name: VLLabelDecode + +Metric: + name: RecMetric + is_filter: true + + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - ABINetRecAug: + - VLLabelEncode: # Class handling label + - VLRecResizeImg: + image_shape: [3, 64, 256] + - KeepKeys: + keep_keys: ['image', 'label', 'label_res', 'label_sub', 'label_id', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 220 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: RGB + channel_first: False + - VLLabelEncode: # Class handling label + - VLRecResizeImg: + image_shape: [3, 64, 256] + - KeepKeys: + keep_keys: ['image', 'label', 'label_res', 'label_sub', 'label_id', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 64 + num_workers: 4 + diff --git a/test_tipc/configs/rec_r45_visionlan/train_infer_python.txt b/test_tipc/configs/rec_r45_visionlan/train_infer_python.txt new file mode 100644 index 0000000..79618ed --- /dev/null +++ b/test_tipc/configs/rec_r45_visionlan/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_r45_visionlan +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=32|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r45_visionlan/rec_r45_visionlan.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r45_visionlan/rec_r45_visionlan.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r45_visionlan/rec_r45_visionlan.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r45_visionlan_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r45_visionlan/rec_r45_visionlan.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,64,256" --rec_algorithm="VisionLAN" --use_space_char=False +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,64,256]}] diff --git a/test_tipc/configs/rec_r50_fpn_vd_none_srn/rec_r50_fpn_srn.yml b/test_tipc/configs/rec_r50_fpn_vd_none_srn/rec_r50_fpn_srn.yml new file mode 100644 index 0000000..41e5252 --- /dev/null +++ b/test_tipc/configs/rec_r50_fpn_vd_none_srn/rec_r50_fpn_srn.yml @@ -0,0 +1,108 @@ +Global: + use_gpu: True + epoch_num: 72 + log_smooth_window: 20 + print_batch_step: 5 + save_model_dir: ./output/rec/srn_new + save_epoch_step: 3 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 5000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words/ch/word_1.jpg + # for data or label process + character_dict_path: + max_text_length: 25 + num_heads: 8 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_srn.txt + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + clip_norm: 10.0 + lr: + learning_rate: 0.0001 + +Architecture: + model_type: rec + algorithm: SRN + in_channels: 1 + Transform: + Backbone: + name: ResNetFPN + Head: + name: SRNHead + max_text_length: 25 + num_heads: 8 + num_encoder_TUs: 2 + num_decoder_TUs: 4 + hidden_dims: 512 + +Loss: + name: SRNLoss + +PostProcess: + name: SRNLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SRNLabelEncode: # Class handling label + - SRNRecResizeImg: + image_shape: [1, 64, 256] + - KeepKeys: + keep_keys: ['image', + 'label', + 'length', + 'encoder_word_pos', + 'gsrm_word_pos', + 'gsrm_slf_attn_bias1', + 'gsrm_slf_attn_bias2'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 64 + drop_last: False + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - SRNLabelEncode: # Class handling label + - SRNRecResizeImg: + image_shape: [1, 64, 256] + - KeepKeys: + keep_keys: ['image', + 'label', + 'length', + 'encoder_word_pos', + 'gsrm_word_pos', + 'gsrm_slf_attn_bias1', + 'gsrm_slf_attn_bias2'] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 32 + num_workers: 4 diff --git a/test_tipc/configs/rec_r50_fpn_vd_none_srn/train_infer_python.txt b/test_tipc/configs/rec_r50_fpn_vd_none_srn/train_infer_python.txt new file mode 100644 index 0000000..c1cfd1f --- /dev/null +++ b/test_tipc/configs/rec_r50_fpn_vd_none_srn/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_r50_fpn_vd_none_srn +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_r50_fpn_vd_none_srn/rec_r50_fpn_srn.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_r50_fpn_vd_none_srn/rec_r50_fpn_srn.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_r50_fpn_vd_none_srn/rec_r50_fpn_srn.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_r50_vd_srn_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_r50_fpn_vd_none_srn/rec_r50_fpn_srn.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="1,64,256" --rec_algorithm="SRN" --use_space_char=False --min_subgraph_size=3 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[1,64,256]}] diff --git a/test_tipc/configs/rec_resnet_rfl/rec_resnet_rfl.yml b/test_tipc/configs/rec_resnet_rfl/rec_resnet_rfl.yml new file mode 100644 index 0000000..b4f18f5 --- /dev/null +++ b/test_tipc/configs/rec_resnet_rfl/rec_resnet_rfl.yml @@ -0,0 +1,111 @@ +Global: + use_gpu: True + epoch_num: 6 + log_smooth_window: 20 + print_batch_step: 50 + save_model_dir: ./output/rec/rec_resnet_rfl/ + save_epoch_step: 1 + # evaluation is run every 5000 iterations after the 4000th iteration + eval_batch_step: [0, 5000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/rec_resnet_rfl.txt + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.999 + weight_decay: 0.0 + clip_norm_global: 5.0 + lr: + name: Piecewise + decay_epochs : [3, 4, 5] + values : [0.001, 0.0003, 0.00009, 0.000027] + +Architecture: + model_type: rec + algorithm: RFL + in_channels: 1 + Transform: + name: TPS + num_fiducial: 20 + loc_lr: 1.0 + model_name: large + Backbone: + name: ResNetRFL + use_cnt: True + use_seq: True + Neck: + name: RFAdaptor + use_v2s: True + use_s2v: True + Head: + name: RFLHead + in_channels: 512 + hidden_size: 256 + batch_max_legnth: 25 + out_channels: 38 + use_cnt: True + use_seq: True + +Loss: + name: RFLLoss + +PostProcess: + name: RFLLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RFLLabelEncode: # Class handling label + - RFLRecResizeImg: + image_shape: [1, 32, 100] + interpolation: 2 + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'cnt_label'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 64 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - RFLLabelEncode: # Class handling label + - RFLRecResizeImg: + image_shape: [1, 32, 100] + interpolation: 2 + - KeepKeys: + keep_keys: ['image', 'label', 'length', 'cnt_label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 8 diff --git a/test_tipc/configs/rec_resnet_rfl/train_infer_python.txt b/test_tipc/configs/rec_resnet_rfl/train_infer_python.txt new file mode 100644 index 0000000..091e962 --- /dev/null +++ b/test_tipc/configs/rec_resnet_rfl/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_resnet_rfl +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_resnet_rfl/rec_resnet_rfl.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_resnet_rfl/rec_resnet_rfl.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_resnet_rfl/rec_resnet_rfl.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_resnet_rfl_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_resnet_rfl/rec_resnet_rfl.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_image_shape="1,32,100" --rec_algorithm="RFL" --min_subgraph_size=5 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[1,32,100]}] diff --git a/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml b/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml new file mode 100644 index 0000000..6e22bc7 --- /dev/null +++ b/test_tipc/configs/rec_svtrnet/rec_svtrnet.yml @@ -0,0 +1,118 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/svtr/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: + character_type: en + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_svtr_tiny.txt + d2s_train_image_shape: [3, 64, 256] + + +Optimizer: + name: AdamW + beta1: 0.9 + beta2: 0.99 + epsilon: 8.e-8 + weight_decay: 0.05 + no_weight_decay_name: norm pos_embed + one_dim_param_no_weight_decay: true + lr: + name: Cosine + learning_rate: 0.0005 + warmup_epoch: 2 + +Architecture: + model_type: rec + algorithm: SVTR + Transform: + name: STN_ON + tps_inputsize: [32, 64] + tps_outputsize: [32, 100] + num_control_points: 20 + tps_margins: [0.05,0.05] + stn_activation: none + Backbone: + name: SVTRNet + img_size: [32, 100] + out_char_num: 25 + out_channels: 192 + patch_merging: 'Conv' + embed_dim: [64, 128, 256] + depth: [3, 6, 3] + num_heads: [2, 4, 8] + mixer: ['Local','Local','Local','Local','Local','Local','Global','Global','Global','Global','Global','Global'] + local_mixer: [[7, 11], [7, 11], [7, 11]] + last_stage: True + prenorm: false + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CTCLoss + +PostProcess: + name: CTCLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 512 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - CTCLabelEncode: # Class handling label + - SVTRRecResizeImg: + image_shape: [3, 64, 256] + padding: False + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/test_tipc/configs/rec_svtrnet/train_infer_python.txt b/test_tipc/configs/rec_svtrnet/train_infer_python.txt new file mode 100644 index 0000000..04ac03a --- /dev/null +++ b/test_tipc/configs/rec_svtrnet/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:rec_svtrnet +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_svtrnet_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_svtrnet/rec_svtrnet.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/ic15_dict.txt --rec_image_shape="3,64,256" --rec_algorithm="SVTR" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,64,256]}] +===========================train_benchmark_params========================== +batch_size:512 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================to_static_train_benchmark_params=========================== +to_static_train:Global.to_static=true diff --git a/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml b/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml new file mode 100644 index 0000000..a0aed48 --- /dev/null +++ b/test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml @@ -0,0 +1,104 @@ +Global: + use_gpu: True + epoch_num: 20 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/rec/vitstr_none_ce/ + save_epoch_step: 1 + # evaluation is run every 2000 iterations after the 0th iteration# + eval_batch_step: [0, 2000] + cal_metric_during_train: True + pretrained_model: + checkpoints: + save_inference_dir: + use_visualdl: False + infer_img: doc/imgs_words_en/word_10.png + # for data or label process + character_dict_path: ppocr/utils/EN_symbol_dict.txt + max_text_length: 25 + infer_mode: False + use_space_char: False + save_res_path: ./output/rec/predicts_vitstr.txt + + +Optimizer: + name: Adadelta + epsilon: 1.e-8 + rho: 0.95 + clip_norm: 5.0 + lr: + learning_rate: 1.0 + +Architecture: + model_type: rec + algorithm: ViTSTR + in_channels: 1 + Transform: + Backbone: + name: ViTSTR + Neck: + name: SequenceEncoder + encoder_type: reshape + Head: + name: CTCHead + +Loss: + name: CELoss + smoothing: False + with_all: True + ignore_index: &ignore_index 0 # Must be zero or greater than the number of character classes + +PostProcess: + name: ViTSTRLabelDecode + +Metric: + name: RecMetric + main_indicator: acc + +Train: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data/ + label_file_list: ["./train_data/ic15_data/rec_gt_train.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: True + batch_size_per_card: 48 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: SimpleDataSet + data_dir: ./train_data/ic15_data + label_file_list: ["./train_data/ic15_data/rec_gt_test.txt"] + transforms: + - DecodeImage: # load image + img_mode: BGR + channel_first: False + - ViTSTRLabelEncode: # Class handling label + ignore_index: *ignore_index + - GrayRecResizeImg: + image_shape: [224, 224] # W H + resize_type: PIL # PIL or OpenCV + inter_type: 'Image.BICUBIC' + scale: false + - KeepKeys: + keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 256 + num_workers: 2 diff --git a/test_tipc/configs/rec_vitstr_none_ce/train_infer_python.txt b/test_tipc/configs/rec_vitstr_none_ce/train_infer_python.txt new file mode 100644 index 0000000..187c115 --- /dev/null +++ b/test_tipc/configs/rec_vitstr_none_ce/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:rec_vitstr +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=2|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=64 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:tools/eval.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/rec_vitstr_none_ce_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/rec_vitstr_none_ce/rec_vitstr_none_ce.yml -o +infer_quant:False +inference:tools/infer/predict_rec.py --rec_char_dict_path=./ppocr/utils/EN_symbol_dict.txt --rec_image_shape="1,224,224" --rec_algorithm="ViTSTR" +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--rec_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[1,224,224]}] diff --git a/test_tipc/configs/slanet/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt b/test_tipc/configs/slanet/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt new file mode 100644 index 0000000..1829b21 --- /dev/null +++ b/test_tipc/configs/slanet/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt @@ -0,0 +1,20 @@ +===========================cpp_infer_params=========================== +model_name:slanet +use_opencv:True +infer_model:./inference/ch_ppstructure_mobile_v2.0_SLANet_infer/ +infer_quant:False +inference:./deploy/cpp_infer/build/ppocr --det_model_dir=./inference/PP-OCRv3_mobile_det_infer --rec_model_dir=./inference/PP-OCRv3_mobile_rec_infer --output=./output/table --type=structure --table=True --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict_ch.txt +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:6 +--use_tensorrt:False +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:True +--det:True +--rec:True +--cls:False +--use_angle_cls:False diff --git a/test_tipc/configs/slanet/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt b/test_tipc/configs/slanet/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt new file mode 100644 index 0000000..99062b0 --- /dev/null +++ b/test_tipc/configs/slanet/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt @@ -0,0 +1,17 @@ +===========================paddle2onnx_params=========================== +model_name:slanet +python:python3.7 +2onnx: paddle2onnx +--det_model_dir:./inference/ch_ppstructure_mobile_v2.0_SLANet_infer/ +--model_filename:inference.pdmodel +--params_filename:inference.pdiparams +--det_save_file:./inference/ch_ppstructure_mobile_v2.0_SLANet_infer/model.onnx +--rec_model_dir: +--rec_save_file: +--opset_version:10 +--enable_onnx_checker:True +inference:ppstructure/table/predict_structure.py --table_char_dict_path=./ppocr/utils/dict/table_structure_dict_ch.txt +--use_gpu:True|False +--det_model_dir: +--rec_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg diff --git a/test_tipc/configs/slanet/train_infer_python.txt b/test_tipc/configs/slanet/train_infer_python.txt new file mode 100644 index 0000000..e596e44 --- /dev/null +++ b/test_tipc/configs/slanet/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:slanet +python:python +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:./pretrain_models/ch_ppstructure_mobile_v2.0_SLANet_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./ppstructure/docs/table/table.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c configs/table/SLANet_ch.yml -o Global.cal_metric_during_train=False Global.print_batch_step=1 Train.loader.shuffle=false Train.dataset.data_dir=./train_data/pubtabnet Train.dataset.label_file_list=[./train_data/pubtabnet/train.txt] Eval.dataset.data_dir=./train_data/pubtabnet Eval.dataset.label_file_list=[./train_data/pubtabnet/val.txt] Global.d2s_train_image_shape='[3, -1, -1]' +pact_train:null +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c configs/table/SLANet.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/en_ppstructure_mobile_v2.0_SLANet_train +infer_export:null +infer_quant:False +inference:ppstructure/table/predict_table.py --det_model_dir=./inference/en_ppocr_mobile_v2.0_table_det_infer --rec_model_dir=./inference/en_ppocr_mobile_v2.0_table_rec_infer --rec_char_dict_path=./ppocr/utils/dict/table_dict.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --image_dir=./ppstructure/docs/table/table.jpg --det_limit_side_len=736 --det_limit_type=min --output ./output/table +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,488,488]}] +===========================train_benchmark_params========================== +batch_size:64 +fp_items:fp32|fp16 +epoch:1 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================to_static_train_benchmark_params=========================== +to_static_train:Global.to_static=true diff --git a/test_tipc/configs/slanet/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/slanet/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..4c9d8d6 --- /dev/null +++ b/test_tipc/configs/slanet/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:slanet +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:./pretrain_models/en_ppstructure_mobile_v2.0_SLANet_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./ppstructure/docs/table/table.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/slanet/SLANet.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/slanet/SLANet.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/en_ppstructure_mobile_v2.0_SLANet_train +infer_export:null +infer_quant:False +inference:ppstructure/table/predict_table.py --det_model_dir=./inference/en_ppocr_mobile_v2.0_table_det_infer --rec_model_dir=./inference/en_ppocr_mobile_v2.0_table_rec_infer --rec_char_dict_path=./ppocr/utils/dict/table_dict.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --image_dir=./ppstructure/docs/table/table.jpg --det_limit_side_len=736 --det_limit_type=min --output ./output/table +--use_gpu:False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,488,488]}] diff --git a/test_tipc/configs/slanet/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/slanet/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..f8d3eff --- /dev/null +++ b/test_tipc/configs/slanet/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:slanet +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=3|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=128 +Global.pretrained_model:./pretrain_models/en_ppstructure_mobile_v2.0_SLANet_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./ppstructure/docs/table/table.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/slanet/SLANet.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/slanet/SLANet.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/en_ppstructure_mobile_v2.0_SLANet_train +infer_export:null +infer_quant:False +inference:ppstructure/table/predict_table.py --det_model_dir=./inference/en_ppocr_mobile_v2.0_table_det_infer --rec_model_dir=./inference/en_ppocr_mobile_v2.0_table_rec_infer --rec_char_dict_path=./ppocr/utils/dict/table_dict.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --image_dir=./ppstructure/docs/table/table.jpg --det_limit_side_len=736 --det_limit_type=min --output ./output/table +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:True +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,488,488]}] diff --git a/test_tipc/configs/slanet/train_pact_infer_python.txt b/test_tipc/configs/slanet/train_pact_infer_python.txt new file mode 100644 index 0000000..98546af --- /dev/null +++ b/test_tipc/configs/slanet/train_pact_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:slanet_PACT +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=50 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=2 +Global.pretrained_model:./pretrain_models/en_ppstructure_mobile_v2.0_SLANet_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./ppstructure/docs/table/table.jpg +null:null +## +trainer:pact_train +norm_train:null +pact_train:deploy/slim/quantization/quant.py -c test_tipc/configs/slanet/SLANet.yml -o +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:null +quant_export:deploy/slim/quantization/export_model.py -c test_tipc/configs/slanet/SLANet.yml -o +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:./inference/en_ppstructure_mobile_v2.0_SLANet_infer +infer_export:null +infer_quant:True +inference:ppstructure/table/predict_table.py --det_model_dir=./inference/en_ppocr_mobile_v2.0_table_det_infer --rec_model_dir=./inference/en_ppocr_mobile_v2.0_table_rec_infer --rec_char_dict_path=./ppocr/utils/dict/table_dict.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --image_dir=./ppstructure/docs/table/table.jpg --det_limit_side_len=736 --det_limit_type=min --output ./output/table +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,488,488]}] diff --git a/test_tipc/configs/slanet/train_ptq_infer_python.txt b/test_tipc/configs/slanet/train_ptq_infer_python.txt new file mode 100644 index 0000000..05d148e --- /dev/null +++ b/test_tipc/configs/slanet/train_ptq_infer_python.txt @@ -0,0 +1,21 @@ +===========================train_params=========================== +model_name:slanet_KL +python:python3.7 +Global.pretrained_model: +Global.save_inference_dir:null +infer_model:./inference/en_ppstructure_mobile_v2.0_SLANet_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c test_tipc/configs/slanet/SLANet.yml -o +infer_quant:True +inference:ppstructure/table/predict_table.py --det_model_dir=./inference/PP-OCRv3_mobile_det_infer --rec_model_dir=./inference/PP-OCRv3_mobile_rec_infer --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --table_char_dict_path=./ppocr/utils/dict/table_structure_dict.txt --image_dir=./ppstructure/docs/table/table.jpg --det_limit_side_len=736 --det_limit_type=min --output ./output/table +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:int8 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:True +null:null +null:null diff --git a/test_tipc/configs/sr_telescope/sr_telescope.yml b/test_tipc/configs/sr_telescope/sr_telescope.yml new file mode 100644 index 0000000..774c19d --- /dev/null +++ b/test_tipc/configs/sr_telescope/sr_telescope.yml @@ -0,0 +1,83 @@ +Global: + use_gpu: true + epoch_num: 2 + log_smooth_window: 20 + print_batch_step: 10 + save_model_dir: ./output/sr/sr_telescope/ + save_epoch_step: 3 + # evaluation is run every 2000 iterations + eval_batch_step: [0, 1000] + cal_metric_during_train: False + pretrained_model: + checkpoints: + save_inference_dir: ./output/sr/sr_telescope/infer + use_visualdl: False + infer_img: doc/imgs_words_en/word_52.png + # for data or label process + character_dict_path: + max_text_length: 100 + infer_mode: False + use_space_char: False + save_res_path: ./output/sr/predicts_telescope.txt + +Optimizer: + name: Adam + beta1: 0.5 + beta2: 0.999 + clip_norm: 0.25 + lr: + learning_rate: 0.0001 + +Architecture: + model_type: sr + algorithm: Telescope + Transform: + name: TBSRN + STN: True + infer_mode: False + +Loss: + name: TelescopeLoss + confuse_dict_path: ./ppocr/utils/dict/confuse.pkl + + +PostProcess: + name: None + +Metric: + name: SRMetric + main_indicator: all + +Train: + dataset: + name: LMDBDataSetSR + data_dir: ./train_data/TextZoom/test + transforms: + - SRResize: + imgH: 32 + imgW: 128 + down_sample_scale: 2 + - KeepKeys: + keep_keys: ['img_lr', 'img_hr', 'label'] # dataloader will return list in this order + loader: + shuffle: False + batch_size_per_card: 16 + drop_last: True + num_workers: 4 + +Eval: + dataset: + name: LMDBDataSetSR + data_dir: ./train_data/TextZoom/test + transforms: + - SRResize: + imgH: 32 + imgW: 128 + down_sample_scale: 2 + - KeepKeys: + keep_keys: ['img_lr', 'img_hr', 'label'] # dataloader will return list in this order + loader: + shuffle: False + drop_last: False + batch_size_per_card: 16 + num_workers: 4 diff --git a/test_tipc/configs/sr_telescope/train_infer_python.txt b/test_tipc/configs/sr_telescope/train_infer_python.txt new file mode 100644 index 0000000..7235f07 --- /dev/null +++ b/test_tipc/configs/sr_telescope/train_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:sr_telescope +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:null +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=300 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=16|whole_train_whole_infer=16 +Global.pretrained_model:null +train_model_name:latest +train_infer_img_dir:./inference/rec_inference +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/sr_telescope/sr_telescope.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/sr_telescope/sr_telescope.yml -o +quant_export:null +fpgm_export:null +distill_export:null +export1:null +export2:null +## +train_model:./inference/sr_telescope_train/best_accuracy +infer_export:tools/export_model.py -c test_tipc/configs/sr_telescope/sr_telescope.yml -o +infer_quant:False +inference:tools/infer/predict_sr.py --sr_image_shape="1,32,128" --rec_algorithm="Telescope" --min_subgraph_size=5 +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--sr_model_dir: +--image_dir:./inference/rec_inference +--save_log_path:./test/output/ +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[1,32,128]}] diff --git a/test_tipc/configs/table_master/table_master.yml b/test_tipc/configs/table_master/table_master.yml new file mode 100644 index 0000000..cc96fbc --- /dev/null +++ b/test_tipc/configs/table_master/table_master.yml @@ -0,0 +1,133 @@ +Global: + use_gpu: true + epoch_num: 17 + log_smooth_window: 20 + print_batch_step: 100 + save_model_dir: ./output/table_master/ + save_epoch_step: 17 + eval_batch_step: [0, 6259] + cal_metric_during_train: false + pretrained_model: null + checkpoints: + save_inference_dir: output/table_master/infer + use_visualdl: false + infer_img: ppstructure/docs/table/table.jpg + save_res_path: ./output/table_master + character_dict_path: ppocr/utils/dict/table_master_structure_dict.txt + infer_mode: false + max_text_length: 500 + d2s_train_image_shape: [3, 480, 480] + + +Optimizer: + name: Adam + beta1: 0.9 + beta2: 0.999 + lr: + name: MultiStepDecay + learning_rate: 0.001 + milestones: [12, 15] + gamma: 0.1 + warmup_epoch: 0.02 + regularizer: + name: L2 + factor: 0.0 + +Architecture: + model_type: table + algorithm: TableMaster + Backbone: + name: TableResNetExtra + gcb_config: + ratio: 0.0625 + headers: 1 + att_scale: False + fusion_type: channel_add + layers: [False, True, True, True] + layers: [1,2,5,3] + Head: + name: TableMasterHead + hidden_size: 512 + headers: 8 + dropout: 0 + d_ff: 2024 + max_text_length: 500 + +Loss: + name: TableMasterLoss + ignore_index: 42 # set to len of dict + 3 + +PostProcess: + name: TableMasterLabelDecode + box_shape: pad + +Metric: + name: TableMetric + main_indicator: acc + compute_bbox_metric: False + +Train: + dataset: + name: LMDBDataSetTableMaster + data_dir: train_data/StructureLabel_val_500/ + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - TableMasterLabelEncode: + learn_empty_box: False + merge_no_span_structure: False + replace_empty_cell_token: True + - ResizeTableImage: + max_len: 480 + resize_bboxes: True + - PaddingTableImage: + size: [480, 480] + - TableBoxEncode: + box_format: 'xywh' + - NormalizeImage: + scale: 1./255. + mean: [0.5, 0.5, 0.5] + std: [0.5, 0.5, 0.5] + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: [image, structure, bboxes, bbox_masks, shape] + loader: + shuffle: True + batch_size_per_card: 10 + drop_last: True + num_workers: 8 + +Eval: + dataset: + name: LMDBDataSetTableMaster + data_dir: train_data/StructureLabel_val_500/ + transforms: + - DecodeImage: + img_mode: BGR + channel_first: False + - TableMasterLabelEncode: + learn_empty_box: False + merge_no_span_structure: False + replace_empty_cell_token: True + - ResizeTableImage: + max_len: 480 + resize_bboxes: True + - PaddingTableImage: + size: [480, 480] + - TableBoxEncode: + box_format: 'xywh' + - NormalizeImage: + scale: 1./255. + mean: [0.5, 0.5, 0.5] + std: [0.5, 0.5, 0.5] + order: hwc + - ToCHWImage: null + - KeepKeys: + keep_keys: [image, structure, bboxes, bbox_masks, shape] + loader: + shuffle: False + drop_last: False + batch_size_per_card: 10 + num_workers: 8 diff --git a/test_tipc/configs/table_master/train_infer_python.txt b/test_tipc/configs/table_master/train_infer_python.txt new file mode 100644 index 0000000..a83a031 --- /dev/null +++ b/test_tipc/configs/table_master/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:table_master +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=17 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=2|whole_train_whole_infer=4 +Global.pretrained_model:./pretrain_models/table_structure_tablemaster_train/best_accuracy +train_model_name:latest +train_infer_img_dir:./ppstructure/docs/table/table.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c test_tipc/configs/table_master/table_master.yml -o Global.print_batch_step=1 +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Global.checkpoints: +norm_export:tools/export_model.py -c test_tipc/configs/table_master/table_master.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:null +infer_export:null +infer_quant:False +inference:ppstructure/table/predict_structure.py --table_char_dict_path=./ppocr/utils/dict/table_master_structure_dict.txt --output ./output/table --table_algorithm=TableMaster --table_max_len=480 +--use_gpu:True +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--table_model_dir: +--image_dir:./ppstructure/docs/table/table.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,480,480]}] +===========================train_benchmark_params========================== +batch_size:10 +fp_items:fp32|fp16 +epoch:2 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98;FLAGS_conv_workspace_size_limit=4096 +===========================to_static_train_benchmark_params=========================== +to_static_train:Global.to_static=true diff --git a/test_tipc/configs/vi_layoutxlm_ser/train_infer_python.txt b/test_tipc/configs/vi_layoutxlm_ser/train_infer_python.txt new file mode 100644 index 0000000..e304519 --- /dev/null +++ b/test_tipc/configs/vi_layoutxlm_ser/train_infer_python.txt @@ -0,0 +1,61 @@ +===========================train_params=========================== +model_name:vi_layoutxlm_ser +python:python +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=17 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=4|whole_train_whole_infer=8 +Architecture.Backbone.checkpoints:null +train_model_name:latest +train_infer_img_dir:ppstructure/docs/kie/input/zh_val_42.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ./configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml -o Global.print_batch_step=1 Global.eval_batch_step=[1000,1000] Train.loader.shuffle=false Train.loader.num_workers=32 Eval.loader.num_workers=32 +pact_train:null +fpgm_train:null +distill_train:null +to_static_train:Global.to_static=true +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Architecture.Backbone.checkpoints: +norm_export:tools/export_model.py -c ./configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:null +infer_export:null +infer_quant:False +inference:ppstructure/kie/predict_kie_token_ser.py --kie_algorithm=LayoutXLM --ser_dict_path=train_data/XFUND/class_list_xfun.txt --output=output --ocr_order_method=tb-yx +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--ser_model_dir: +--image_dir:./ppstructure/docs/kie/input/zh_val_42.jpg +null:null +--benchmark:False +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,224,224]}] +===========================train_benchmark_params========================== +batch_size:8 +fp_items:fp32|fp16 +epoch:3 +--profiler_options:batch_range=[10,20];state=GPU;tracer_option=Default;profile_path=model.profile +flags:FLAGS_eager_delete_tensor_gb=0.0;FLAGS_fraction_of_gpu_memory_to_use=0.98 +===========================to_static_train_benchmark_params=========================== +to_static_train:Global.to_static=true diff --git a/test_tipc/configs/vi_layoutxlm_ser/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/vi_layoutxlm_ser/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..74953ec --- /dev/null +++ b/test_tipc/configs/vi_layoutxlm_ser/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:vi_layoutxlm_ser +python:python3.7 +gpu_list:192.168.0.1,192.168.0.2;0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=17 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=4|whole_train_whole_infer=8 +Architecture.Backbone.checkpoints:null +train_model_name:latest +train_infer_img_dir:ppstructure/docs/kie/input/zh_val_42.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ./configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Architecture.Backbone.checkpoints: +norm_export:tools/export_model.py -c ./configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:null +infer_export:null +infer_quant:False +inference:ppstructure/kie/predict_kie_token_ser.py --kie_algorithm=LayoutXLM --ser_dict_path=train_data/XFUND/class_list_xfun.txt --output=output --ocr_order_method=tb-yx +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--ser_model_dir: +--image_dir:./ppstructure/docs/kie/input/zh_val_42.jpg +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,224,224]}] diff --git a/test_tipc/configs/vi_layoutxlm_ser/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt b/test_tipc/configs/vi_layoutxlm_ser/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt new file mode 100644 index 0000000..95daa02 --- /dev/null +++ b/test_tipc/configs/vi_layoutxlm_ser/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:vi_layoutxlm_ser +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:amp +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=17 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=4|whole_train_whole_infer=8 +Architecture.Backbone.checkpoints:null +train_model_name:latest +train_infer_img_dir:ppstructure/docs/kie/input/zh_val_42.jpg +null:null +## +trainer:norm_train +norm_train:tools/train.py -c ./configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml -o +pact_train:null +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Architecture.Backbone.checkpoints: +norm_export:tools/export_model.py -c ./configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml -o +quant_export: +fpgm_export: +distill_export:null +export1:null +export2:null +## +infer_model:null +infer_export:null +infer_quant:False +inference:ppstructure/kie/predict_kie_token_ser.py --kie_algorithm=LayoutXLM --ser_dict_path=train_data/XFUND/class_list_xfun.txt --output=output --ocr_order_method=tb-yx +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--ser_model_dir: +--image_dir:./ppstructure/docs/kie/input/zh_val_42.jpg +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,224,224]}] diff --git a/test_tipc/configs/vi_layoutxlm_ser/train_pact_infer_python.txt b/test_tipc/configs/vi_layoutxlm_ser/train_pact_infer_python.txt new file mode 100644 index 0000000..c1fc48c --- /dev/null +++ b/test_tipc/configs/vi_layoutxlm_ser/train_pact_infer_python.txt @@ -0,0 +1,53 @@ +===========================train_params=========================== +model_name:vi_layoutxlm_ser_PACT +python:python3.7 +gpu_list:0|0,1 +Global.use_gpu:True|True +Global.auto_cast:fp32 +Global.epoch_num:lite_train_lite_infer=1|whole_train_whole_infer=17 +Global.save_model_dir:./output/ +Train.loader.batch_size_per_card:lite_train_lite_infer=4|whole_train_whole_infer=8 +Architecture.Backbone.pretrained:./pretrain_models/ser_vi_layoutxlm_xfund_pretrained/best_accuracy +train_model_name:latest +train_infer_img_dir:ppstructure/docs/kie/input/zh_val_42.jpg +null:null +## +trainer:pact_train +norm_train:null +pact_train:deploy/slim/quantization/quant.py -c ./configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml -o Global.eval_batch_step=[2000,10] +fpgm_train:null +distill_train:null +null:null +null:null +## +===========================eval_params=========================== +eval:null +null:null +## +===========================infer_params=========================== +Global.save_inference_dir:./output/ +Architecture.Backbone.checkpoints: +norm_export:null +quant_export:deploy/slim/quantization/export_model.py -c ./configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml -o +fpgm_export: null +distill_export:null +export1:null +export2:null +## +infer_model:null +infer_export:null +infer_quant:False +inference:ppstructure/kie/predict_kie_token_ser.py --kie_algorithm=LayoutXLM --ser_dict_path=train_data/XFUND/class_list_xfun.txt --output=output --ocr_order_method=tb-yx +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:fp32 +--ser_model_dir: +--image_dir:./ppstructure/docs/kie/input/zh_val_42.jpg +null:null +--benchmark:True +null:null +===========================infer_benchmark_params========================== +random_infer_input:[{float32,[3,224,224]}] diff --git a/test_tipc/configs/vi_layoutxlm_ser/train_ptq_infer_python.txt b/test_tipc/configs/vi_layoutxlm_ser/train_ptq_infer_python.txt new file mode 100644 index 0000000..faae871 --- /dev/null +++ b/test_tipc/configs/vi_layoutxlm_ser/train_ptq_infer_python.txt @@ -0,0 +1,21 @@ +===========================train_params=========================== +model_name:vi_layoutxlm_ser_KL +python:python3.7 +Global.pretrained_model: +Global.save_inference_dir:null +infer_model:./inference/ser_vi_layoutxlm_xfund_infer/ +infer_export:deploy/slim/quantization/quant_kl.py -c ./configs/kie/vi_layoutxlm/ser_vi_layoutxlm_xfund_zh.yml -o Train.loader.batch_size_per_card=1 Eval.loader.batch_size_per_card=1 +infer_quant:True +inference:ppstructure/kie/predict_kie_token_ser.py --kie_algorithm=LayoutXLM --ser_dict_path=train_data/XFUND/class_list_xfun.txt --output=output --ocr_order_method=tb-yx +--use_gpu:True|False +--enable_mkldnn:False +--cpu_threads:6 +--rec_batch_num:1 +--use_tensorrt:False +--precision:int8 +--ser_model_dir: +--image_dir:./ppstructure/docs/kie/input/zh_val_42.jpg +null:null +--benchmark:True +null:null +null:null diff --git a/test_tipc/docs/benchmark_train.md b/test_tipc/docs/benchmark_train.md new file mode 100644 index 0000000..3c22e42 --- /dev/null +++ b/test_tipc/docs/benchmark_train.md @@ -0,0 +1,76 @@ + +# TIPC Linux端Benchmark测试文档 + +该文档为Benchmark测试说明,Benchmark预测功能测试的主程序为`benchmark_train.sh`,用于验证监控模型训练的性能。 + +# 1. 测试流程 +## 1.1 准备数据和环境安装 +运行`test_tipc/prepare.sh`,完成训练数据准备和安装环境流程。 + +```shell +# 运行格式:bash test_tipc/prepare.sh train_benchmark.txt mode +bash test_tipc/prepare.sh test_tipc/configs/det_mv3_db_v2_0/train_infer_python.txt benchmark_train +``` + +## 1.2 功能测试 +执行`test_tipc/benchmark_train.sh`,完成模型训练和日志解析 + +```shell +# 运行格式:bash test_tipc/benchmark_train.sh train_benchmark.txt mode +bash test_tipc/benchmark_train.sh test_tipc/configs/det_mv3_db_v2_0/train_infer_python.txt benchmark_train + +``` + +`test_tipc/benchmark_train.sh`支持根据传入的第三个参数实现只运行某一个训练配置,如下: +```shell +# 运行格式:bash test_tipc/benchmark_train.sh train_benchmark.txt mode +bash test_tipc/benchmark_train.sh test_tipc/configs/det_mv3_db_v2_0/train_infer_python.txt benchmark_train dynamic_bs8_fp32_DP_N1C1 +``` +dynamic_bs8_fp32_DP_N1C1为test_tipc/benchmark_train.sh传入的参数,格式如下: +`${modeltype}_${batch_size}_${fp_item}_${run_mode}_${device_num}` +包含的信息有:模型类型、batchsize大小、训练精度如fp32,fp16等、分布式运行模式以及分布式训练使用的机器信息如单机单卡(N1C1)。 + + +## 2. 日志输出 + +运行后将保存模型的训练日志和解析日志,使用 `test_tipc/configs/det_mv3_db_v2_0/train_infer_python.txt` 参数文件的训练日志解析结果是: + +``` +{"model_branch": "dygaph", "model_commit": "7c39a1996b19087737c05d883fd346d2f39dbcc0", "model_name": "det_mv3_db_v2_0_bs8_fp32_SingleP_DP", "batch_size": 8, "fp_item": "fp32", "run_process_type": "SingleP", "run_mode": "DP", "convergence_value": "5.413110", "convergence_key": "loss:", "ips": 19.333, "speed_unit": "samples/s", "device_num": "N1C1", "model_run_time": "0", "frame_commit": "8cc09552473b842c651ead3b9848d41827a3dbab", "frame_version": "0.0.0"} +``` + +训练日志和日志解析结果保存在benchmark_log目录下,文件组织格式如下: +``` +train_log/ +├── index +│   ├── PaddleOCR_det_mv3_db_v2_0_bs8_fp32_SingleP_DP_N1C1_speed +│   └── PaddleOCR_det_mv3_db_v2_0_bs8_fp32_SingleP_DP_N1C4_speed +├── profiling_log +│   └── PaddleOCR_det_mv3_db_v2_0_bs8_fp32_SingleP_DP_N1C1_profiling +└── train_log + ├── PaddleOCR_det_mv3_db_v2_0_bs8_fp32_SingleP_DP_N1C1_log + └── PaddleOCR_det_mv3_db_v2_0_bs8_fp32_SingleP_DP_N1C4_log +``` +## 3. 各模型单卡性能数据一览 + +*注:本节中的速度指标均使用单卡(1块Nvidia V100 16G GPU)测得。通常情况下。 + + +|模型名称|配置文件|大数据集 float32 fps |小数据集 float32 fps |diff |大数据集 float16 fps|小数据集 float16 fps| diff | 大数据集大小 | 小数据集大小 | +|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| +| ch_ppocr_mobile_v2.0_det |[config](../configs/ch_ppocr_mobile_v2.0_det/train_infer_python.txt) | 53.836 | 53.343 / 53.914 / 52.785 |0.020940758 | 45.574 | 45.57 / 46.292 / 46.213 | 0.015596647 | 10,000| 2,000| +| ch_ppocr_mobile_v2.0_rec |[config](../configs/ch_ppocr_mobile_v2.0_rec/train_infer_python.txt) | 2083.311 | 2043.194 / 2066.372 / 2093.317 |0.023944295 | 2153.261 | 2167.561 / 2165.726 / 2155.614| 0.005511725 | 600,000| 160,000| +| ch_ppocr_server_v2.0_det |[config](../configs/ch_ppocr_server_v2.0_det/train_infer_python.txt) | 20.716 | 20.739 / 20.807 / 20.755 |0.003268131 | 20.592 | 20.498 / 20.993 / 20.75| 0.023579288 | 10,000| 2,000| +| ch_ppocr_server_v2.0_rec |[config](../configs/ch_ppocr_server_v2.0_rec/train_infer_python.txt) | 528.56 | 528.386 / 528.991 / 528.391 |0.001143687 | 1189.788 | 1190.007 / 1176.332 / 1192.084| 0.013213834 | 600,000| 160,000| +| ch_PP-OCRv2_det |[config](../configs/ch_PP-OCRv2_det/train_infer_python.txt) | 13.87 | 13.386 / 13.529 / 13.428 |0.010569887 | 17.847 | 17.746 / 17.908 / 17.96| 0.011915367 | 10,000| 2,000| +| ch_PP-OCRv2_rec |[config](../configs/ch_PP-OCRv2_rec/train_infer_python.txt) | 109.248 | 106.32 / 106.318 / 108.587 |0.020895687 | 117.491 | 117.62 / 117.757 / 117.726| 0.001163413 | 140,000| 40,000| +| det_mv3_db_v2.0 |[config](../configs/det_mv3_db_v2_0/train_infer_python.txt) | 61.802 | 62.078 / 61.802 / 62.008 |0.00444602 | 82.947 | 84.294 / 84.457 / 84.005| 0.005351836 | 10,000| 2,000| +| det_r50_vd_db_v2.0 |[config](../configs/det_r50_vd_db_v2.0/train_infer_python.txt) | 29.955 | 29.092 / 29.31 / 28.844 |0.015899011 | 51.097 |50.367 / 50.879 / 50.227| 0.012814717 | 10,000| 2,000| +| det_r50_vd_east_v2.0 |[config](../configs/det_r50_vd_east_v2.0/train_infer_python.txt) | 42.485 | 42.624 / 42.663 / 42.561 |0.00239083 | 67.61 |67.825/ 68.299/ 68.51| 0.00999854 | 10,000| 2,000| +| det_r50_vd_pse_v2.0 |[config](../configs/det_r50_vd_pse_v2.0/train_infer_python.txt) | 16.455 | 16.517 / 16.555 / 16.353 |0.012201752 | 27.02 |27.288 / 27.152 / 27.408| 0.009340339 | 10,000| 2,000| +| rec_mv3_none_bilstm_ctc_v2.0 |[config](../configs/rec_mv3_none_bilstm_ctc_v2.0/train_infer_python.txt) | 2288.358 | 2291.906 / 2293.725 / 2290.05 |0.001602197 | 2336.17 |2327.042 / 2328.093 / 2344.915| 0.007622025 | 600,000| 160,000| +| layoutxlm_ser |[config](../configs/layoutxlm/train_infer_python.txt) | 18.001 | 18.114 / 18.107 / 18.307 |0.010924783 | 21.982 | 21.507 / 21.116 / 21.406| 0.018180127 | 1490 | 1490| +| PP-Structure-table |[config](../configs/en_table_structure/train_infer_python.txt) | 14.151 | 14.077 / 14.23 / 14.25 |0.012140351 | 16.285 | 16.595 / 16.878 / 16.531 | 0.020559308 | 20,000| 5,000| +| det_r50_dcn_fce_ctw_v2.0 |[config](../configs/det_r50_dcn_fce_ctw_v2.0/train_infer_python.txt) | 14.057 | 14.029 / 14.02 / 14.014 |0.001069214 | 18.298 |18.411 / 18.376 / 18.331| 0.004345228 | 10,000| 2,000| +| ch_PP-OCRv3_det |[config](../configs/ch_PP-OCRv3_det/train_infer_python.txt) | 8.622 | 8.431 / 8.423 / 8.479|0.006604552 | 14.203 |14.346 14.468 14.23| 0.016450097 | 10,000| 2,000| +| PP-OCRv3_mobile_rec |[config](../configs/PP-OCRv3_mobile_rec/train_infer_python.txt) | 90.239 | 90.077 / 91.513 / 91.325|0.01569176 | | | | 160,000| 40,000| diff --git a/test_tipc/docs/compare_cpp_right.png b/test_tipc/docs/compare_cpp_right.png new file mode 100644 index 0000000..f9d0ba8 Binary files /dev/null and b/test_tipc/docs/compare_cpp_right.png differ diff --git a/test_tipc/docs/compare_cpp_wrong.png b/test_tipc/docs/compare_cpp_wrong.png new file mode 100644 index 0000000..621d446 Binary files /dev/null and b/test_tipc/docs/compare_cpp_wrong.png differ diff --git a/test_tipc/docs/compare_right.png b/test_tipc/docs/compare_right.png new file mode 100644 index 0000000..3d74ef1 Binary files /dev/null and b/test_tipc/docs/compare_right.png differ diff --git a/test_tipc/docs/compare_wrong.png b/test_tipc/docs/compare_wrong.png new file mode 100644 index 0000000..26ad576 Binary files /dev/null and b/test_tipc/docs/compare_wrong.png differ diff --git a/test_tipc/docs/guide.png b/test_tipc/docs/guide.png new file mode 100644 index 0000000..319ac81 Binary files /dev/null and b/test_tipc/docs/guide.png differ diff --git a/test_tipc/docs/install.md b/test_tipc/docs/install.md new file mode 100644 index 0000000..d67637a --- /dev/null +++ b/test_tipc/docs/install.md @@ -0,0 +1,116 @@ +## 1. 环境准备 + +本教程适用于test_tipc目录下基础功能测试的运行环境搭建。 + +推荐环境: +- CUDA 10.1/10.2 +- CUDNN 7.6/cudnn8.1 +- TensorRT 6.1.0.5 / 7.1 / 7.2 + +环境配置可以选择docker镜像安装,或者在本地环境Python搭建环境。推荐使用docker镜像安装,避免不必要的环境配置。 + +## 2. Docker 镜像安装 + +推荐docker镜像安装,按照如下命令创建镜像,当前目录映射到镜像中的`/paddle`目录下 +``` +nvidia-docker run --name paddle -it -v $PWD:/paddle paddlepaddle/paddle:latest-dev-cuda10.1-cudnn7-gcc82 /bin/bash +cd /paddle + +# 安装带TRT的paddle +pip3.7 install https://paddle-wheel.bj.bcebos.com/with-trt/2.1.3/linux-gpu-cuda10.1-cudnn7-mkl-gcc8.2-trt6-avx/paddlepaddle_gpu-2.1.3.post101-cp37-cp37m-linux_x86_64.whl +``` + +## 3 Python 环境构建 + +非docker环境下,环境配置比较灵活,推荐环境组合配置: +- CUDA10.1 + CUDNN7.6 + TensorRT 6 +- CUDA10.2 + CUDNN8.1 + TensorRT 7 +- CUDA11.1 + CUDNN8.1 + TensorRT 7 + +下面以 CUDA10.2 + CUDNN8.1 + TensorRT 7 配置为例,介绍环境配置的流程。 + +### 3.1 安装CUDNN + +如果当前环境满足CUDNN版本的要求,可以跳过此步骤。 + +以CUDNN8.1 安装安装为例,安装步骤如下,首先下载CUDNN,从[Nvidia官网](https://developer.nvidia.com/rdp/cudnn-archive)下载CUDNN8.1版本,下载符合当前系统版本的三个deb文件,分别是: +- cuDNN Runtime Library ,如:libcudnn8_8.1.0.77-1+cuda10.2_amd64.deb +- cuDNN Developer Library ,如:libcudnn8-dev_8.1.0.77-1+cuda10.2_amd64.deb +- cuDNN Code Samples,如:libcudnn8-samples_8.1.0.77-1+cuda10.2_amd64.deb + +deb安装可以参考[官方文档](https://docs.nvidia.com/deeplearning/cudnn/install-guide/index.html#installlinux-deb),安装方式如下 +``` +# x.x.x表示下载的版本号 +# $HOME为工作目录 +sudo dpkg -i libcudnn8_x.x.x-1+cudax.x_arm64.deb +sudo dpkg -i libcudnn8-dev_8.x.x.x-1+cudax.x_arm64.deb +sudo dpkg -i libcudnn8-samples_8.x.x.x-1+cudax.x_arm64.deb + +# 验证是否正确安装 +cp -r /usr/src/cudnn_samples_v8/ $HOME +cd $HOME/cudnn_samples_v8/mnistCUDNN + +# 编译 +make clean && make +./mnistCUDNN +``` +如果运行mnistCUDNN完后提示运行成功,则表示安装成功。如果运行后出现freeimage相关的报错,需要按照提示安装freeimage库: +``` +sudo apt-get install libfreeimage-dev +sudo apt-get install libfreeimage +``` + +### 3.2 安装TensorRT + +首先,从[Nvidia官网TensorRT板块](https://developer.nvidia.com/tensorrt-getting-started)下载TensorRT,这里选择7.1.3.4版本的TensorRT,注意选择适合自己系统版本和CUDA版本的TensorRT,另外建议下载TAR package的安装包。 + +以Ubuntu16.04+CUDA10.2为例,下载并解压后可以参考[官方文档](https://docs.nvidia.com/deeplearning/tensorrt/archives/tensorrt-713/install-guide/index.html#installing-tar)的安装步骤,按照如下步骤安装: +``` +# 以下安装命令中 '${version}' 为下载的TensorRT版本,如7.1.3.4 +# 设置环境变量, 为解压后的TensorRT的lib目录 +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH: + +# 安装TensorRT +cd TensorRT-${version}/python +pip3.7 install tensorrt-*-cp3x-none-linux_x86_64.whl + +# 安装graphsurgeon +cd TensorRT-${version}/graphsurgeon +``` + + +### 3.3 安装PaddlePaddle + +下载[链接](https://www.paddlepaddle.org.cn/install/quick) + + +## 4. 安装PaddleOCR依赖 +``` +# 安装AutoLog +git clone https://github.com/LDOUBLEV/AutoLog +cd AutoLog +pip3.7 install -r requirements.txt +python3.7 setup.py bdist_wheel +pip3.7 install ./dist/auto_log-1.0.0-py3-none-any.whl + +# 下载OCR代码 +cd ../ +git clone https://github.com/PaddlePaddle/PaddleOCR + +``` + +安装PaddleOCR依赖: +``` +cd PaddleOCR +pip3.7 install -r requirements.txt +``` + +## FAQ : +Q. You are using Paddle compiled with TensorRT, but TensorRT dynamic library is not found. Ignore this if TensorRT is not needed. + +A. 问题一般是当前安装paddle版本带TRT,但是本地环境找不到TensorRT的预测库,需要下载TensorRT库,解压后设置环境变量LD_LIBRARY_PATH; +如: +``` +export LD_LIBRARY_PATH=/usr/local/python3.7.0/lib:/usr/local/nvidia/lib:/usr/local/nvidia/lib64:/paddle/package/TensorRT-6.0.1.5/lib +``` +或者问题是下载的TensorRT版本和当前paddle中编译的TRT版本不匹配,需要下载版本相符的TensorRT重新安装。 diff --git a/test_tipc/docs/lite_auto_log.png b/test_tipc/docs/lite_auto_log.png new file mode 100644 index 0000000..d5b6b45 Binary files /dev/null and b/test_tipc/docs/lite_auto_log.png differ diff --git a/test_tipc/docs/lite_log.png b/test_tipc/docs/lite_log.png new file mode 100644 index 0000000..2b3e40b Binary files /dev/null and b/test_tipc/docs/lite_log.png differ diff --git a/test_tipc/docs/mac_test_train_inference_python.md b/test_tipc/docs/mac_test_train_inference_python.md new file mode 100644 index 0000000..f3ece93 --- /dev/null +++ b/test_tipc/docs/mac_test_train_inference_python.md @@ -0,0 +1,143 @@ +# Mac端基础训练预测功能测试 + +Mac端基础训练预测功能测试的主程序为`test_train_inference_python.sh`,可以测试基于Python的模型CPU训练,包括裁剪、PACT在线量化、蒸馏训练,以及评估、CPU推理等基本功能。 + +注:Mac端测试用法同linux端测试方法类似,但是无需测试需要在GPU上运行的测试。 + +## 1. 测试结论汇总 + +- 训练相关: + +| 算法名称 | 模型名称 | 单机单卡(CPU) | 单机多卡 | 多机多卡 | 模型压缩(CPU) | +| :---- | :---- | :---- | :---- | :---- | :---- | +| DB | ch_ppocr_mobile_v2.0_det| 正常训练 | - | - | 正常训练:FPGM裁剪、PACT量化 | + + +- 预测相关:基于训练是否使用量化,可以将训练产出的模型可以分为`正常模型`和`量化模型`,这两类模型对应的预测功能汇总如下, + +| 模型类型 |device | batchsize | tensorrt | mkldnn | cpu多线程 | +| ---- | ---- | ---- | :----: | :----: | :----: | +| 正常模型 | CPU | 1/6 | - | fp32 | 支持 | +| 量化模型 | CPU | 1/6 | - | int8 | 支持 | + + +## 2. 测试流程 + +Mac端无GPU,环境准备只需要Python环境即可,安装PaddlePaddle等依赖参考下述文档。 + +### 2.1 安装依赖 +- 安装PaddlePaddle >= 2.3 +- 安装PaddleOCR依赖 + ``` + pip install -r ../requirements.txt + ``` +- 安装autolog(规范化日志输出工具) + ``` + pip install https://paddleocr.bj.bcebos.com/libs/auto_log-1.2.0-py3-none-any.whl + ``` +- 安装PaddleSlim (可选) + ``` + # 如果要测试量化、裁剪等功能,需要安装PaddleSlim + pip install paddleslim + ``` + + +### 2.2 功能测试 + +先运行`prepare.sh`准备数据和模型,然后运行`test_train_inference_python.sh`进行测试,最终在```test_tipc/output```目录下生成`,model_name/lite_train_lite_infer/*.log`格式的日志文件。 + +`test_train_inference_python.sh`包含基础链条的4种运行模式,每种模式的运行数据不同,分别用于测试速度和精度,分别是: + +- 模式1:lite_train_lite_infer,使用少量数据训练,用于快速验证训练到预测的走通流程,不验证精度和速度; +```shell +# 同linux端运行不同的是,Mac端测试使用新的配置文件mac_ppocr_det_mobile_params.txt, +# 配置文件中默认去掉了GPU和mkldnn相关的测试链条 +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt 'lite_train_lite_infer' +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt 'lite_train_lite_infer' +``` + +- 模式2:lite_train_whole_infer,使用少量数据训练,一定量数据预测,用于验证训练后的模型执行预测,预测速度是否合理; +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt 'lite_train_whole_infer' +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt 'lite_train_whole_infer' +``` + +- 模式3:whole_infer,不训练,全量数据预测,走通开源模型评估、动转静,检查inference model预测时间和精度; +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt 'whole_infer' +# 用法1: +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt 'whole_infer' +# 用法2: 指定GPU卡预测,第三个传入参数为GPU卡号 +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt 'whole_infer' '1' +``` + +- 模式4:whole_train_whole_infer,CE: 全量数据训练,全量数据预测,验证模型训练精度,预测精度,预测速度;(Mac端不建议运行此模式) +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt 'whole_train_whole_infer' +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_mac_cpu_normal_normal_infer_python_mac_cpu.txt 'whole_train_whole_infer' +``` + +运行相应指令后,在`test_tipc/output`文件夹下自动会保存运行日志。如`lite_train_lite_infer`模式下,会运行训练+inference的链条,因此,在`test_tipc/output`文件夹有以下文件: +``` +test_tipc/output/model_name/lite_train_lite_infer/ +|- results_python.log # 运行指令状态的日志 +|- norm_train_gpus_-1_autocast_null/ # CPU上正常训练的训练日志和模型保存文件夹 +...... +|- python_infer_cpu_usemkldnn_False_threads_1_precision_fp32_batchsize_1.log # CPU上关闭Mkldnn线程数设置为1,测试batch_size=1条件下的fp32精度预测运行日志 +...... +``` + +其中`results_python.log`中包含了每条指令的运行状态,如果运行成功会输出: +``` +Run successfully with command - python3.7 tools/train.py -c tests/configs/det_mv3_db.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained Global.use_gpu=False Global.save_model_dir=./tests/output/norm_train_gpus_-1_autocast_null Global.epoch_num=1 Train.loader.batch_size_per_card=2 ! +Run successfully with command - python3.7 tools/export_model.py -c tests/configs/det_mv3_db.yml -o Global.pretrained_model=./tests/output/norm_train_gpus_-1_autocast_null/latest Global.save_inference_dir=./tests/output/norm_train_gpus_-1_autocast_null! +...... +``` +如果运行失败,会输出: +``` +Run failed with command - python3.7 tools/train.py -c tests/configs/det_mv3_db.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained Global.use_gpu=Faslse Global.save_model_dir=./tests/output/norm_train_gpus_-1_autocast_null Global.epoch_num=1 Train.loader.batch_size_per_card=2 ! +Run failed with command - python3.7 tools/export_model.py -c tests/configs/det_mv3_db.yml -o Global.pretrained_model=./tests/output/norm_train_gpus_0_autocast_null/latest Global.save_inference_dir=./tests/output/norm_train_gpus_-1_autocast_null! +...... +``` +可以很方便的根据`results_python.log`中的内容判定哪一个指令运行错误。 + +### 2.3 精度测试 + +使用compare_results.py脚本比较模型预测的结果是否符合预期,主要步骤包括: +- 提取日志中的预测坐标; +- 从本地文件中提取保存好的坐标结果; +- 比较上述两个结果是否符合精度预期,误差大于设置阈值时会报错。 + +#### 使用方式 +运行命令: +```shell +python test_tipc/compare_results.py --gt_file=./test_tipc/results/python_*.txt --log_file=./test_tipc/output/python_*.log --atol=1e-3 --rtol=1e-3 +``` + +参数介绍: +- gt_file: 指向事先保存好的预测结果路径,支持*.txt 结尾,会自动索引*.txt格式的文件,文件默认保存在test_tipc/result/ 文件夹下 +- log_file: 指向运行test_tipc/test_train_inference_python.sh 脚本的infer模式保存的预测日志,预测日志中打印的有预测结果,比如:文本框,预测文本,类别等等,同样支持python_infer_*.log格式传入 +- atol: 设置的绝对误差 +- rtol: 设置的相对误差 + +#### 运行结果 + +正常运行效果如下: +``` +Assert allclose passed! The results of python_infer_cpu_usemkldnn_False_threads_1_batchsize_1.log and ./test_tipc/results/python_ppocr_det_mobile_results_fp32.txt are consistent! +``` + +出现不一致结果时的运行输出: +``` +...... +Traceback (most recent call last): + File "test_tipc/compare_results.py", line 140, in + format(filename, gt_filename)) +ValueError: The results of python_infer_cpu_usemkldnn_False_threads_1_batchsize_1.log and the results of ./test_tipc/results/python_ppocr_det_mobile_results_fp32.txt are inconsistent! +``` + + +## 3. 更多教程 +本文档为功能测试用,更丰富的训练预测使用教程请参考: +[模型训练](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/doc/doc_ch/training.md) +[基于Python预测引擎推理](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/doc/doc_ch/inference_ppocr.md) diff --git a/test_tipc/docs/ssh_termux_ls.png b/test_tipc/docs/ssh_termux_ls.png new file mode 100644 index 0000000..2df7802 Binary files /dev/null and b/test_tipc/docs/ssh_termux_ls.png differ diff --git a/test_tipc/docs/termux.jpg b/test_tipc/docs/termux.jpg new file mode 100644 index 0000000..cb87c4c Binary files /dev/null and b/test_tipc/docs/termux.jpg differ diff --git a/test_tipc/docs/termux_for_android.md b/test_tipc/docs/termux_for_android.md new file mode 100644 index 0000000..3ae9d18 --- /dev/null +++ b/test_tipc/docs/termux_for_android.md @@ -0,0 +1,127 @@ +# 安卓手机通过Termux连接电脑 + +由于通过adb方式连接手机后,很多linux命令无法运行,自动化测试受阻,所以此处特此介绍另外一种通过Termux的连接方式,不仅可以运行大部分linux命令,方便开发者在手机上在线调试,甚至还可以多实现台机器同时连接手机。Termux不是真实的Linux环境,但是Termux可以安装真实的Linux,而且不会损失性能,与此同时,Termux不需要root。在配置Termux之前,请确保电脑已经安装adb工具,安装方式请参考[Lite端部署](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/deploy/lite/readme.md) 。在运行以下命令后确保可以显示安卓设备信息。 + +``` +adb devices +``` +连接成功信息提示: +``` +List of devices attached +744be294 device +``` + +## 1.安卓手机安装termux app + +### 1.1 下载termux apk文件 + +由于目前该app目前各大商城暂无,所以可以直接下载如下apk文件。 + +打开电脑终端,执行以下命令: + +``` +wget http://10.12.121.133:8911/cuicheng01/fullchain/termux-v1.0.3.apk +``` + +### 1.2 安装termux到手机上 + +在手机端的开发者模式下,允许USB调试,允许USB安装。在电脑终端,执行如下命令,将termux app安装到手机上: + +``` +adb install termux-v1.0.3.apk +``` + +此处需要手机端确认安装,点击确认。 + +### 1.3 验证是否安装成功 + +打开手机,检验termux是否安装成功,如果没有,重新执行1.2,如果有相应的app,点击进入,会有如下显示。 + + + +接下来的配置环境需要在手机上此终端运行相关命令。 + +## 2.手机端配置termux + +首先将手机联网,最好可以连接外网,部分的配置需要外网。打开Termux终端,执行以下命令安装基础件`proot`,并使用`termux-chroot`命令可以模拟 root 环境与标准的 Linux 目录结构。 + +``` +pkg i -y proot +termux-chroot +``` + +Termux 默认只能访问自身内部的数据,如果要访问手机中其它的数据,输入下面的命令后,手机弹出对请求权限的窗口,允许即可(方便对部分运行出的结果在手机端可视化)。 + +``` +termux-setup-storage +``` + +### 2.1 配置SSH + +作为 Linux 终端或者服务器,必须有SSH。不管你是 SSH 连接到 Termux还是使用Termux去连其它主机,都需要先安装openssh。如果安装失败,请重复执行命令。 + +``` +pkg i -y openssh +``` + +启动 SSH 服务端,默认端口号为8022 + +``` +sshd +``` + + +### 2.2 电脑通过SSH方式连接手机 + +1.保证手机和电脑处于同一局域网下 +手机端分别输入以下命令获得ip地址和当前用户: + +``` +# 获取ip地址 +ifconfig + +# 获取当前用户 +whoami +``` + +如获取到的ip地址和当前用户分别是`172.24.162.117`和`u0_a374`。 + +2.电脑端通过SSH连接手机 + +``` +#默认端口号为8022 +ssh u0_a374@172.24.162.117 -p 8022 +``` + +3.运行ls命令后,会有如下显示: + +``` +ls +``` + + + + +### 2.3 通过scp传输数据 + +1.在当前目录上新建test目录 + +``` +mkdir test +``` + +2.测试scp功能 + +将电脑中的某个文件拷贝到手机上: +``` +scp -P 8022 test.txt u0_a374@172.24.162.117:/home/storage/test +``` + +3.手机端查看 + +打开手机终端,在`/home/storage/test`下查看是否存在`test.txt` + + +## 3. 更多教程 + +本教程可以完成Termux基本配置,更多关于Termux的用法,请参考:[Termux高级终端安装使用配置教程](https://www.sqlsec.com/2018/05/termux.html)。 diff --git a/test_tipc/docs/test.png b/test_tipc/docs/test.png new file mode 100644 index 0000000..f99f23d Binary files /dev/null and b/test_tipc/docs/test.png differ diff --git a/test_tipc/docs/test_inference_cpp.md b/test_tipc/docs/test_inference_cpp.md new file mode 100644 index 0000000..5e9e852 --- /dev/null +++ b/test_tipc/docs/test_inference_cpp.md @@ -0,0 +1,86 @@ +# C++预测功能测试 + +C++预测功能测试的主程序为`test_inference_cpp.sh`,可以测试基于C++预测库的模型推理功能。 + +## 1. 测试结论汇总 + +基于训练是否使用量化,进行本测试的模型可以分为`正常模型`和`量化模型`,这两类模型对应的C++预测功能汇总如下: + +| 模型类型 |device | batchsize | tensorrt | mkldnn | cpu多线程 | +| ---- | ---- | ---- | :----: | :----: | :----: | +| 正常模型 | GPU | 1/6 | fp32/fp16 | - | - | +| 正常模型 | CPU | 1/6 | - | fp32 | 支持 | +| 量化模型 | GPU | 1/6 | int8 | - | - | +| 量化模型 | CPU | 1/6 | - | int8 | 支持 | + +## 2. 测试流程 +运行环境配置请参考[文档](./install.md)的内容配置TIPC的运行环境。 + +### 2.1 功能测试 +先运行`prepare.sh`准备数据和模型,然后运行`test_inference_cpp.sh`进行测试,最终在```test_tipc/output/{model_name}/cpp_infer```目录下生成`cpp_infer_*.log`后缀的日志文件。 + +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt "cpp_infer" + +# 用法1: +bash test_tipc/test_inference_cpp.sh test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt +# 用法2: 指定GPU卡预测,第三个传入参数为GPU卡号 +bash test_tipc/test_inference_cpp.sh test_tipc/configs/ch_PP-OCRv2_rec/model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt '1' +``` + +运行预测指令后,在`test_tipc/output`文件夹下自动会保存运行日志,包括以下文件: + +```shell +test_tipc/output/ +|- results_cpp.log # 运行指令状态的日志 +|- cpp_infer_cpu_usemkldnn_False_threads_6_precision_fp32_batchsize_6.log # CPU上不开启Mkldnn,线程数设置为6,测试batch_size=6条件下的预测运行日志 +|- cpp_infer_gpu_usetrt_False_precision_fp32_batchsize_6.log # GPU上不开启TensorRT,测试batch_size=6的fp32精度预测日志 +...... +``` +其中results_cpp.log中包含了每条指令的运行状态,如果运行成功会输出: + +``` +[33m Run successfully with command - ch_PP-OCRv2_rec - ./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 --use_gpu=True --use_tensorrt=False --precision=fp32 --rec_model_dir=./inference/ch_PP-OCRv2_rec_infer/ --rec_batch_num=6 --image_dir=./inference/rec_inference/ --benchmark=True --det=False --rec=True --cls=False --use_angle_cls=False > ./test_tipc/output/ch_PP-OCRv2_rec/cpp_infer/cpp_infer_gpu_usetrt_False_precision_fp32_batchsize_6.log 2>&1 !  + Run successfully with command - ch_PP-OCRv2_rec - ./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 --use_gpu=False --enable_mkldnn=False --cpu_threads=6 --rec_model_dir=./inference/ch_PP-OCRv2_rec_infer/ --rec_batch_num=6 --image_dir=./inference/rec_inference/ --benchmark=True --det=False --rec=True --cls=False --use_angle_cls=False > ./test_tipc/output/ch_PP-OCRv2_rec/cpp_infer/cpp_infer_cpu_usemkldnn_False_threads_6_precision_fp32_batchsize_6.log 2>&1 !  +...... +``` +如果运行失败,会输出: +``` +Run failed with command - ch_PP-OCRv2_rec - ./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 --use_gpu=True --use_tensorrt=False --precision=fp32 --rec_model_dir=./inference/ch_PP-OCRv2_rec_infer/ --rec_batch_num=6 --image_dir=./inference/rec_inference/ --benchmark=True --det=False --rec=True --cls=False --use_angle_cls=False > ./test_tipc/output/ch_PP-OCRv2_rec/cpp_infer/cpp_infer_gpu_usetrt_False_precision_fp32_batchsize_6.log 2>&1 ! +Run failed with command - ch_PP-OCRv2_rec - ./deploy/cpp_infer/build/ppocr --rec_char_dict_path=./ppocr/utils/ppocr_keys_v1.txt --rec_img_h=32 --use_gpu=False --enable_mkldnn=False --cpu_threads=6 --rec_model_dir=./inference/ch_PP-OCRv2_rec_infer/ --rec_batch_num=6 --image_dir=./inference/rec_inference/ --benchmark=True --det=False --rec=True --cls=False --use_angle_cls=False > ./test_tipc/output/ch_PP-OCRv2_rec/cpp_infer/cpp_infer_cpu_usemkldnn_False_threads_6_precision_fp32_batchsize_6.log 2>&1 ! +...... +``` +可以很方便的根据results_cpp.log中的内容判定哪一个指令运行错误。 + + +### 2.2 精度测试 + +使用compare_results.py脚本比较模型预测的结果是否符合预期,主要步骤包括: +- 提取日志中的预测坐标; +- 从本地文件中提取保存好的坐标结果; +- 比较上述两个结果是否符合精度预期,误差大于设置阈值时会报错。 + +#### 使用方式 +运行命令: +```shell +python3.7 test_tipc/compare_results.py --gt_file=./test_tipc/results/cpp_*.txt --log_file=./test_tipc/output/cpp_*.log --atol=1e-3 --rtol=1e-3 +``` + +参数介绍: +- gt_file: 指向事先保存好的预测结果路径,支持*.txt 结尾,会自动索引*.txt格式的文件,文件默认保存在test_tipc/result/ 文件夹下 +- log_file: 指向运行test_tipc/test_inference_cpp.sh 脚本的infer模式保存的预测日志,预测日志中打印的有预测结果,比如:文本框,预测文本,类别等等,同样支持cpp_infer_*.log格式传入 +- atol: 设置的绝对误差 +- rtol: 设置的相对误差 + +#### 运行结果 + +正常运行效果如下图: + + +出现不一致结果时的运行输出: + + + +## 3. 更多教程 + +本文档为功能测试用,更详细的c++预测使用教程请参考:[服务器端C++预测](https://github.com/PaddlePaddle/PaddleOCR/tree/dygraph/deploy/cpp_infer) diff --git a/test_tipc/docs/test_lite_arm_cpp.md b/test_tipc/docs/test_lite_arm_cpp.md new file mode 100644 index 0000000..b84e069 --- /dev/null +++ b/test_tipc/docs/test_lite_arm_cpp.md @@ -0,0 +1,95 @@ +# Lite\_arm\_cpp预测功能测试 + +Lite\_arm\_cpp预测功能测试的主程序为`test_lite_arm_cpp.sh`,可以在ARM上基于Lite预测库测试模型的C++推理功能。 + +## 1. 测试结论汇总 + +目前Lite端的样本间支持以方式的组合: + +**字段说明:** +- 模型类型:包括正常模型(FP32)和量化模型(INT8) +- batch-size:包括1和4 +- threads:包括1和4 +- predictor数量:包括单predictor预测和多predictor预测 +- 预测库来源:包括下载方式和编译方式 +- 测试硬件:ARM\_CPU/ARM\_GPU_OPENCL + +| 模型类型 | batch-size | threads | predictor数量 | 预测库来源 | 测试硬件 | +| :----: | :----: | :----: | :----: | :----: | :----: | +| 正常模型/量化模型 | 1 | 1/4 | 单/多 | 下载方式/编译方式 | ARM\_CPU/ARM\_GPU_OPENCL | + + +## 2. 测试流程 +运行环境配置请参考[文档](./install.md)的内容配置TIPC的运行环境。 + +### 2.1 功能测试 + +先运行`prepare_lite_cpp.sh`,运行后会在当前路径下生成`test_lite.tar`,其中包含了测试数据、测试模型和用于预测的可执行文件。将`test_lite.tar`上传到被测试的手机上,在手机的终端解压该文件,进入`test_lite`目录中,然后运行`test_lite_arm_cpp.sh`进行测试,最终在`test_lite/output`目录下生成`lite_*.log`后缀的日志文件。 + +#### 2.1.1 基于ARM\_CPU测试 + +```shell + +# 数据、模型、Paddle-Lite预测库准备 +#预测库为下载方式 +bash test_tipc/prepare_lite_cpp.sh ./test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt download +#预测库为编译方式 +bash test_tipc/prepare_lite_cpp.sh ./test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt compile + +# 手机端测试: +bash test_lite_arm_cpp.sh model_linux_gpu_normal_normal_lite_cpp_arm_cpu.txt + +``` + +#### 2.1.2 基于ARM\_GPU\_OPENCL测试 + +```shell + +# 数据、模型、Paddle-Lite预测库准备 +#预测库下载方式 +bash test_tipc/prepare_lite_cpp.sh ./test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt download +#预测库编译方式 +bash test_tipc/prepare_lite_cpp.sh ./test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt compile + +# 手机端测试: +bash test_lite_arm_cpp.sh model_linux_gpu_normal_normal_lite_cpp_arm_gpu_opencl.txt + +``` + + +**注意**: + +由于运行该项目需要bash等命令,传统的adb方式不能很好的安装。所以此处推荐通在手机上开启虚拟终端的方式连接电脑,连接方式可以参考[安卓手机termux连接电脑](./termux_for_android.md)。 + +### 2.2 运行结果 + +各测试的运行情况会打印在 `./output/` 中: +运行成功时会输出: + +``` +Run successfully with command - ./ocr_db_crnn det ch_PP-OCRv2_det_infer_opt.nb ARM_CPU FP32 1 1 ./test_data/icdar2015_lite/text_localization/ch4_test_images/ ./config.txt True > ./output/lite_ch_PP-OCRv2_det_infer_opt.nb_runtime_device_ARM_CPU_precision_FP32_batchsize_1_threads_1.log 2>&1! +Run successfully with command xxx +... +``` + +运行失败时会输出: + +``` +Run failed with command - ./ocr_db_crnn det ch_PP-OCRv2_det_infer_opt.nb ARM_CPU FP32 1 1 ./test_data/icdar2015_lite/text_localization/ch4_test_images/ ./config.txt True > ./output/lite_ch_PP-OCRv2_det_infer_opt.nb_runtime_device_ARM_CPU_precision_FP32_batchsize_1_threads_1.log 2>&1! +Run failed with command xxx +... +``` + +在./output/文件夹下,会存在如下日志,每一个日志都是不同配置下的log结果: + + + +在每一个log中,都会调用autolog打印如下信息: + + + + + +## 3. 更多教程 + +本文档为功能测试用,更详细的Lite端预测使用教程请参考:[Lite端部署](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/deploy/lite/readme.md)。 diff --git a/test_tipc/docs/test_paddle2onnx.md b/test_tipc/docs/test_paddle2onnx.md new file mode 100644 index 0000000..9393388 --- /dev/null +++ b/test_tipc/docs/test_paddle2onnx.md @@ -0,0 +1,48 @@ +# Paddle2onnx预测功能测试 + +PaddleServing预测功能测试的主程序为`test_paddle2onnx.sh`,可以测试Paddle2ONNX的模型转化功能,并验证正确性。 + +## 1. 测试结论汇总 + +基于训练是否使用量化,进行本测试的模型可以分为`正常模型`和`量化模型`,这两类模型对应的Paddle2ONNX预测功能汇总如下: + +| 模型类型 |device | +| ---- | ---- | +| 正常模型 | GPU | +| 正常模型 | CPU | +| 量化模型 | GPU | +| 量化模型 | CPU | + +## 2. 测试流程 +### 2.1 功能测试 +先运行`prepare.sh`准备数据和模型,然后运行`test_paddle2onnx.sh`进行测试,最终在```test_tipc/output/{model_name}/paddle2onnx```目录下生成`paddle2onnx_infer_*.log`后缀的日志文件。 + +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt "paddle2onnx_infer" + +# 用法: +bash test_tipc/test_paddle2onnx.sh ./test_tipc/configs/ch_PP-OCRv2_det/model_linux_gpu_normal_normal_paddle2onnx_python_linux_cpu.txt +``` + +#### 运行结果 + +各测试的运行情况会打印在 `test_tipc/output/{model_name}/paddle2onnx/results_paddle2onnx.log` 中: +运行成功时会输出: + +``` +Run successfully with command - ch_PP-OCRv2_det - paddle2onnx --model_dir=./inference/ch_PP-OCRv2_det_infer/ --model_filename=inference.pdmodel --params_filename=inference.pdiparams --save_file=./inference/det_v2_onnx/model.onnx --opset_version=10 --enable_onnx_checker=True! +Run successfully with command - ch_PP-OCRv2_det - python3.7 tools/infer/predict_det.py --use_gpu=True --image_dir=./inference/ch_det_data_50/all-sum-510/ --det_model_dir=./inference/det_v2_onnx/model.onnx --use_onnx=True > ./test_tipc/output/ch_PP-OCRv2_det/paddle2onnx/paddle2onnx_infer_gpu.log 2>&1 ! +Run successfully with command - ch_PP-OCRv2_det - python3.7 tools/infer/predict_det.py --use_gpu=False --image_dir=./inference/ch_det_data_50/all-sum-510/ --det_model_dir=./inference/det_v2_onnx/model.onnx --use_onnx=True > ./test_tipc/output/ch_PP-OCRv2_det/paddle2onnx/paddle2onnx_infer_cpu.log 2>&1 ! +``` + +运行失败时会输出: + +``` +Run failed with command - ch_PP-OCRv2_det - paddle2onnx --model_dir=./inference/ch_PP-OCRv2_det_infer/ --model_filename=inference.pdmodel --params_filename=inference.pdiparams --save_file=./inference/det_v2_onnx/model.onnx --opset_version=10 --enable_onnx_checker=True! +... +``` + + +## 3. 更多教程 + +本文档为功能测试用,更详细的Paddle2onnx预测使用教程请参考:[Paddle2ONNX](https://github.com/PaddlePaddle/Paddle2ONNX) diff --git a/test_tipc/docs/test_ptq_inference_python.md b/test_tipc/docs/test_ptq_inference_python.md new file mode 100644 index 0000000..b118b66 --- /dev/null +++ b/test_tipc/docs/test_ptq_inference_python.md @@ -0,0 +1,51 @@ +# Linux GPU/CPU KL离线量化训练推理测试 + +Linux GPU/CPU KL离线量化训练推理测试的主程序为`test_ptq_inference_python.sh`,可以测试基于Python的模型训练、评估、推理等基本功能。 + +## 1. 测试结论汇总 +- 训练相关: + +| 算法名称 | 模型名称 | 单机单卡 | +| :----: | :----: | :----: | +| | model_name | KL离线量化训练 | + +- 推理相关: + +| 算法名称 | 模型名称 | device_CPU | device_GPU | batchsize | +| :----: | :----: | :----: | :----: | :----: | +| | model_name | 支持 | 支持 | 1 | + +## 2. 测试流程 + +### 2.1 准备数据和模型 + +先运行`prepare.sh`准备数据和模型,然后运行`test_ptq_inference_python.sh`进行测试,最终在```test_tipc/output/{model_name}/whole_infer```目录下生成`python_infer_*.log`后缀的日志文件。 + +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_PP-OCRv2_det/train_ptq_infer_python.txt "whole_infer" + +# 用法: +bash test_tipc/test_ptq_inference_python.sh ./test_tipc/configs/ch_PP-OCRv2_det/train_ptq_infer_python.txt "whole_infer" +``` + +#### 运行结果 + +各测试的运行情况会打印在 `test_tipc/output/{model_name}/paddle2onnx/results_paddle2onnx.log` 中: +运行成功时会输出: + +``` +Run successfully with command - ch_PP-OCRv2_det_KL - python3.7 deploy/slim/quantization/quant_kl.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml -o Global.pretrained_model=./inference/ch_PP-OCRv2_det_infer/ Global.save_inference_dir=./inference/ch_PP-OCRv2_det_infer/_klquant > ./test_tipc/output/ch_PP-OCRv2_det_KL/whole_infer/whole_infer_export_0.log 2>&1 ! +Run successfully with command - ch_PP-OCRv2_det_KL - python3.7 tools/infer/predict_det.py --use_gpu=False --enable_mkldnn=False --cpu_threads=6 --det_model_dir=./inference/ch_PP-OCRv2_det_infer/_klquant --rec_batch_num=1 --image_dir=./inference/ch_det_data_50/all-sum-510/ --precision=int8 > ./test_tipc/output/ch_PP-OCRv2_det_KL/whole_infer/python_infer_cpu_usemkldnn_False_threads_6_precision_int8_batchsize_1.log 2>&1 ! +Run successfully with command - ch_PP-OCRv2_det_KL - python3.7 tools/infer/predict_det.py --use_gpu=True --use_tensorrt=False --precision=int8 --det_model_dir=./inference/ch_PP-OCRv2_det_infer/_klquant --rec_batch_num=1 --image_dir=./inference/ch_det_data_50/all-sum-510/ > ./test_tipc/output/ch_PP-OCRv2_det_KL/whole_infer/python_infer_gpu_usetrt_False_precision_int8_batchsize_1.log 2>&1 ! +``` + +运行失败时会输出: + +``` +Run failed with command - ch_PP-OCRv2_det_KL - python3.7 deploy/slim/quantization/quant_kl.py -c configs/det/ch_PP-OCRv2/ch_PP-OCRv2_det_cml.yml -o Global.pretrained_model=./inference/ch_PP-OCRv2_det_infer/ Global.save_inference_dir=./inference/ch_PP-OCRv2_det_infer/_klquant > ./test_tipc/output/ch_PP-OCRv2_det_KL/whole_infer/whole_infer_export_0.log 2>&1 ! +... +``` + +## 3. 更多教程 + +本文档为功能测试用,更详细的量化使用教程请参考:[量化](../../deploy/slim/quantization/README.md) diff --git a/test_tipc/docs/test_serving.md b/test_tipc/docs/test_serving.md new file mode 100644 index 0000000..1be7fa2 --- /dev/null +++ b/test_tipc/docs/test_serving.md @@ -0,0 +1,63 @@ +# PaddleServing预测功能测试 + +PaddleServing预测功能测试的主程序为`test_serving_infer_python.sh`和`test_serving_infer_cpp.sh`,可以测试基于PaddleServing的部署功能。 + +## 1. 测试结论汇总 + +基于训练是否使用量化,进行本测试的模型可以分为`正常模型`和`量化模型`,这两类模型对应的Serving预测功能汇总如下: + +| 模型类型 |device | batchsize | tensorrt | mkldnn | cpu多线程 | +| ---- | ---- | ---- | :----: | :----: | :----: | +| 正常模型 | GPU | 1/6 | fp32/fp16 | - | - | +| 正常模型 | CPU | 1/6 | - | fp32 | 支持 | +| 量化模型 | GPU | 1/6 | int8 | - | - | +| 量化模型 | CPU | 1/6 | - | int8 | 支持 | + +## 2. 测试流程 +运行环境配置请参考[文档](./install.md)的内容配置TIPC的运行环境。 + +### 2.1 功能测试 +**python serving** +先运行`prepare.sh`准备数据和模型,然后运行`test_serving_infer_python.sh`进行测试,最终在```test_tipc/output/{model_name}/serving_infer/python```目录下生成`python_*.log`后缀的日志文件。 + +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt "serving_infer" + +# 用法: +bash test_tipc/test_serving_infer_python.sh ./test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_serving_python_linux_gpu_cpu.txt "serving_infer" +``` +**cpp serving** +先运行`prepare.sh`准备数据和模型,然后运行`test_serving_infer_cpp.sh`进行测试,最终在```test_tipc/output/{model_name}/serving_infer/cpp```目录下生成`cpp_*.log`后缀的日志文件。 + +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt "serving_infer" + +# 用法: +bash test_tipc/test_serving_infer_cpp.sh ./test_tipc/configs/ch_PP-OCRv2/model_linux_gpu_normal_normal_serving_cpp_linux_gpu_cpu.txt "serving_infer" +``` + +#### 运行结果 + +各测试的运行情况会打印在 `test_tipc/output/{model_name}/serving_infer/python(cpp)/results_python(cpp)_serving.log` 中: +运行成功时会输出: + +``` +Run successfully with command - ch_PP-OCRv2_rec - nohup python3.7 web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" op.det.local_service_conf.devices= op.det.local_service_conf.use_mkldnn=False op.det.local_service_conf.thread_num=6 op.rec.local_service_conf.model_config=ppocr_rec_v2_serving > ./test_tipc/output/ch_PP-OCRv2_rec/serving_infer/python/python_server_cpu_usemkldnn_False_threads_6.log 2>&1 &! +Run successfully with command - ch_PP-OCRv2_rec - python3.7 pipeline_http_client.py --det=False --image_dir=../../inference/rec_inference > ./test_tipc/output/ch_PP-OCRv2_rec/serving_infer/python/python_client_cpu_pipeline_http_usemkldnn_False_threads_6_batchsize_1.log 2>&1 ! +... +``` + +运行失败时会输出: + +``` +Run failed with command - ch_PP-OCRv2_rec - nohup python3.7 web_service_rec.py --config=config.yml --opt op.rec.concurrency="1" op.det.local_service_conf.devices= op.det.local_service_conf.use_mkldnn=False op.det.local_service_conf.thread_num=6 op.rec.local_service_conf.model_config=ppocr_rec_v2_serving > ./test_tipc/output/ch_PP-OCRv2_rec/serving_infer/python/python_server_cpu_usemkldnn_False_threads_6.log 2>&1 &! +Run failed with command - ch_PP-OCRv2_rec - python3.7 pipeline_http_client.py --det=False --image_dir=../../inference/rec_inference > ./test_tipc/output/ch_PP-OCRv2_rec/serving_infer/python/python_client_cpu_pipeline_http_usemkldnn_False_threads_6_batchsize_1.log 2>&1 ! +... +``` + +详细的预测结果会存在 test_tipc/output/{model_name}/serving_infer/python(cpp)/ 文件夹下 + + +## 3. 更多教程 + +本文档为功能测试用,更详细的Serving预测使用教程请参考:[PPOCR 服务化部署](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/deploy/pdserving/README_CN.md) diff --git a/test_tipc/docs/test_train_fleet_inference_python.md b/test_tipc/docs/test_train_fleet_inference_python.md new file mode 100644 index 0000000..af1e7e8 --- /dev/null +++ b/test_tipc/docs/test_train_fleet_inference_python.md @@ -0,0 +1,107 @@ +# Linux GPU/CPU 多机多卡训练推理测试 + +Linux GPU/CPU 多机多卡训练推理测试的主程序为`test_train_inference_python.sh`,可以测试基于Python的模型训练、评估、推理等基本功能。 + +## 1. 测试结论汇总 + +- 训练相关: + +| 算法名称 | 模型名称 | 多机多卡 | +| :----: | :----: | :----: | +| PP-OCRv3 | PP-OCRv3_mobile_rec | 分布式训练 | + + +- 推理相关: + +| 算法名称 | 模型名称 | device_CPU | device_GPU | batchsize | +| :----: | :----: | :----: | :----: | :----: | +| PP-OCRv3 | PP-OCRv3_mobile_rec | 支持 | - | 1/6 | + + +## 2. 测试流程 + +运行环境配置请参考[文档](./install.md)的内容配置TIPC的运行环境。 + +### 2.1 功能测试 + +#### 2.1.1 修改配置文件 + +首先,修改配置文件中的`ip`设置: 假设两台机器的`ip`地址分别为`192.168.0.1`和`192.168.0.2`,则对应的配置文件`gpu_list`字段需要修改为`gpu_list:192.168.0.1,192.168.0.2;0,1`; `ip`地址查看命令为`ifconfig`。 + + +#### 2.1.2 准备数据 + +运行`prepare.sh`准备数据和模型,以配置文件`test_tipc/configs/PP-OCRv3_mobile_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt`为例,数据准备命令如下所示。 + +```shell +bash test_tipc/prepare.sh test_tipc/configs/PP-OCRv3_mobile_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt lite_train_lite_infer +``` + +**注意:** 由于是多机训练,这里需要在所有的节点上均运行启动上述命令,准备数据。 + +#### 2.1.3 修改起始端口并开始测试 + +在多机的节点上使用下面的命令设置分布式的起始端口(否则后面运行的时候会由于无法找到运行端口而hang住),一般建议设置在`10000~20000`之间。 + +```shell +export FLAGS_START_PORT=17000 +``` + +以配置文件`test_tipc/configs/PP-OCRv3_mobile_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt`为例,测试方法如下所示。 + +```shell +bash test_tipc/test_train_inference_python.sh test_tipc/configs/PP-OCRv3_mobile_rec/train_linux_gpu_fleet_normal_infer_python_linux_gpu_cpu.txt lite_train_lite_infer +``` + +**注意:** 由于是多机训练,这里需要在所有的节点上均运行启动上述命令进行测试。 + + +#### 2.1.4 输出结果 + +输出结果如下,表示命令运行成功。 + +```bash + Run successfully with command - PP-OCRv3_mobile_rec - python3.7 -m paddle.distributed.launch --ips=192.168.0.1,192.168.0.2 --gpus=0,1 tools/train.py -c test_tipc/configs/PP-OCRv3_mobile_rec/PP-OCRv3_mobile_rec_distillation.yml -o Global.use_gpu=True Global.save_model_dir=./test_tipc/output/PP-OCRv3_mobile_rec/lite_train_lite_infer/norm_train_gpus_0,1_autocast_fp32_nodes_2 Global.epoch_num=3 Global.auto_cast=fp32 Train.loader.batch_size_per_card=16 ! + ...... + Run successfully with command - PP-OCRv3_mobile_rec - python3.7 tools/infer/predict_rec.py --rec_image_shape="3,48,320" --use_gpu=False --enable_mkldnn=False --cpu_threads=6 --rec_model_dir=./test_tipc/output/PP-OCRv3_mobile_rec/lite_train_lite_infer/norm_train_gpus_0,1_autocast_fp32_nodes_2/Student --rec_batch_num=1 --image_dir=./inference/rec_inference --benchmark=True --precision=fp32 > ./test_tipc/output/PP-OCRv3_mobile_rec/lite_train_lite_infer/python_infer_cpu_usemkldnn_False_threads_6_precision_fp32_batchsize_1.log 2>&1 ! +``` + +在开启benchmark参数时,可以得到测试的详细数据,包含运行环境信息(系统版本、CUDA版本、CUDNN版本、驱动版本),Paddle版本信息,参数设置信息(运行设备、线程数、是否开启内存优化等),模型信息(模型名称、精度),数据信息(batchsize、是否为动态shape等),性能信息(CPU,GPU的占用、运行耗时、预处理耗时、推理耗时、后处理耗时),内容如下所示: + +``` +[2022/06/02 22:53:35] ppocr INFO: + +[2022/06/02 22:53:35] ppocr INFO: ---------------------- Env info ---------------------- +[2022/06/02 22:53:35] ppocr INFO: OS_version: Ubuntu 16.04 +[2022/06/02 22:53:35] ppocr INFO: CUDA_version: 10.1.243 +[2022/06/02 22:53:35] ppocr INFO: CUDNN_version: 7.6.5 +[2022/06/02 22:53:35] ppocr INFO: drivier_version: 460.32.03 +[2022/06/02 22:53:35] ppocr INFO: ---------------------- Paddle info ---------------------- +[2022/06/02 22:53:35] ppocr INFO: paddle_version: 2.3.0-rc0 +[2022/06/02 22:53:35] ppocr INFO: paddle_commit: 5d4980c052583fec022812d9c29460aff7cdc18b +[2022/06/02 22:53:35] ppocr INFO: log_api_version: 1.0 +[2022/06/02 22:53:35] ppocr INFO: ----------------------- Conf info ----------------------- +[2022/06/02 22:53:35] ppocr INFO: runtime_device: cpu +[2022/06/02 22:53:35] ppocr INFO: ir_optim: True +[2022/06/02 22:53:35] ppocr INFO: enable_memory_optim: True +[2022/06/02 22:53:35] ppocr INFO: enable_tensorrt: False +[2022/06/02 22:53:35] ppocr INFO: enable_mkldnn: False +[2022/06/02 22:53:35] ppocr INFO: cpu_math_library_num_threads: 6 +[2022/06/02 22:53:35] ppocr INFO: ----------------------- Model info ---------------------- +[2022/06/02 22:53:35] ppocr INFO: model_name: rec +[2022/06/02 22:53:35] ppocr INFO: precision: fp32 +[2022/06/02 22:53:35] ppocr INFO: ----------------------- Data info ----------------------- +[2022/06/02 22:53:35] ppocr INFO: batch_size: 1 +[2022/06/02 22:53:35] ppocr INFO: input_shape: dynamic +[2022/06/02 22:53:35] ppocr INFO: data_num: 6 +[2022/06/02 22:53:35] ppocr INFO: ----------------------- Perf info ----------------------- +[2022/06/02 22:53:35] ppocr INFO: cpu_rss(MB): 288.957, gpu_rss(MB): None, gpu_util: None% +[2022/06/02 22:53:35] ppocr INFO: total time spent(s): 0.4824 +[2022/06/02 22:53:35] ppocr INFO: preprocess_time(ms): 0.1136, inference_time(ms): 79.5877, postprocess_time(ms): 0.6945 +``` + +该信息可以在运行log中查看,以上面的`PP-OCRv3_mobile_rec`为例,log位置在`./test_tipc/output/PP-OCRv3_mobile_rec/lite_train_lite_infer/results_python.log`。 + +如果运行失败,也会在终端中输出运行失败的日志信息以及对应的运行命令。可以基于该命令,分析运行失败的原因。 + +**注意:** 由于分布式训练时,仅在`trainer_id=0`所在的节点中保存模型,因此其他的节点中在运行模型导出与推理时会报错,为正常现象。 diff --git a/test_tipc/docs/test_train_inference_python.md b/test_tipc/docs/test_train_inference_python.md new file mode 100644 index 0000000..4578057 --- /dev/null +++ b/test_tipc/docs/test_train_inference_python.md @@ -0,0 +1,165 @@ +# Linux端基础训练预测功能测试 + +Linux端基础训练预测功能测试的主程序为`test_train_inference_python.sh`,可以测试基于Python的模型训练、评估、推理等基本功能,包括PACT在线量化。 + +- Mac端基础训练预测功能测试参考[链接](./mac_test_train_inference_python.md) +- Windows端基础训练预测功能测试参考[链接](./win_test_train_inference_python.md) + +## 1. 测试结论汇总 + +- 训练相关: + +| 算法名称 | 模型名称 | 单机单卡 | 单机多卡 | 多机多卡 | 模型压缩(单机多卡) | +| :---- | :---- | :---- | :---- | :---- | :---- | +| DB | ch_ppocr_mobile_v2_0_det| 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练:FPGM裁剪、PACT量化 | +| DB | ch_ppocr_server_v2_0_det| 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练:FPGM裁剪、PACT量化 | +| CRNN | ch_ppocr_mobile_v2_0_rec| 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练:PACT量化 | +| CRNN | ch_ppocr_server_v2_0_rec| 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练:PACT量化 | +|PP-OCR| ch_ppocr_mobile_v2_0| 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练
    混合精度 | - | +|PP-OCR| ch_ppocr_server_v2_0| 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练
    混合精度 | - | +|PP-OCRv2| ch_PP-OCRv2 | 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练
    混合精度 | - | +|PP-OCRv3| ch_PP-OCRv3 | 正常训练
    混合精度 | 正常训练
    混合精度 | 正常训练
    混合精度 | - | + + +- 预测相关:基于训练是否使用量化,可以将训练产出的模型可以分为`正常模型`和`量化模型`,这两类模型对应的预测功能汇总如下, + +| 模型类型 |device | batchsize | tensorrt | mkldnn | cpu多线程 | +| ---- | ---- | ---- | :----: | :----: | :----: | +| 正常模型 | GPU | 1/6 | fp32/fp16 | - | - | +| 正常模型 | CPU | 1/6 | - | fp32/fp16 | 支持 | +| 量化模型 | GPU | 1/6 | int8 | - | - | +| 量化模型 | CPU | 1/6 | - | int8 | 支持 | + + +## 2. 测试流程 + +运行环境配置请参考[文档](./install.md)的内容配置TIPC的运行环境。 + +### 2.1 安装依赖 +- 安装PaddlePaddle >= 2.3 +- 安装PaddleOCR依赖 + ``` + pip3 install -r ../requirements.txt + ``` +- 安装autolog(规范化日志输出工具) + ``` + pip3 install https://paddleocr.bj.bcebos.com/libs/auto_log-1.2.0-py3-none-any.whl + ``` +- 安装PaddleSlim (可选) + ``` + # 如果要测试量化、裁剪等功能,需要安装PaddleSlim + pip3 install paddleslim + ``` + + +### 2.2 功能测试 +#### 2.2.1 基础训练推理链条 +先运行`prepare.sh`准备数据和模型,然后运行`test_train_inference_python.sh`进行测试,最终在```test_tipc/output```目录下生成`,model_name/lite_train_lite_infer/*.log`格式的日志文件。 + + +`test_train_inference_python.sh`包含基础链条的4种运行模式,每种模式的运行数据不同,分别用于测试速度和精度,分别是: + +- 模式1:lite_train_lite_infer,使用少量数据训练,用于快速验证训练到预测的走通流程,不验证精度和速度; +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt 'lite_train_lite_infer' +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt 'lite_train_lite_infer' +``` + +- 模式2:lite_train_whole_infer,使用少量数据训练,一定量数据预测,用于验证训练后的模型执行预测,预测速度是否合理; +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt 'lite_train_whole_infer' +bash test_tipc/test_train_inference_python.sh ../test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt 'lite_train_whole_infer' +``` + +- 模式3:whole_infer,不训练,全量数据预测,走通开源模型评估、动转静,检查inference model预测时间和精度; +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt 'whole_infer' +# 用法1: +bash test_tipc/test_train_inference_python.sh ../test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt 'whole_infer' +# 用法2: 指定GPU卡预测,第三个传入参数为GPU卡号 +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt 'whole_infer' '1' +``` + +- 模式4:whole_train_whole_infer,CE: 全量数据训练,全量数据预测,验证模型训练精度,预测精度,预测速度; +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt 'whole_train_whole_infer' +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_infer_python.txt 'whole_train_whole_infer' +``` + +运行相应指令后,在`test_tipc/output`文件夹下自动会保存运行日志。如'lite_train_lite_infer'模式下,会运行训练+inference的链条,因此,在`test_tipc/output`文件夹有以下文件: +``` +test_tipc/output/model_name/lite_train_lite_infer/ +|- results_python.log # 运行指令状态的日志 +|- norm_train_gpus_0_autocast_null/ # GPU 0号卡上正常单机单卡训练的训练日志和模型保存文件夹 +|- norm_train_gpus_0,1_autocast_null/ # GPU 0,1号卡上正常单机多卡训练的训练日志和模型保存文件夹 +...... +|- python_infer_cpu_usemkldnn_False_threads_6_precision_fp32_batchsize_1.log # CPU上关闭Mkldnn线程数设置为6,测试batch_size=1条件下的fp32精度预测运行日志 +|- python_infer_gpu_usetrt_False_precision_fp32_batchsize_1.log # GPU上关闭TensorRT,测试batch_size=1的fp32精度预测日志 +...... +``` + +其中`results_python.log`中包含了每条指令的运行状态,如果运行成功会输出: +``` +[33m Run successfully with command - ch_ppocr_mobile_v2_0_det - python3.7 tools/train.py -c configs/det/ch_ppocr_v2_0/ch_det_mv3_db_v2_0.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained Global.use_gpu=True Global.save_model_dir=./test_tipc/output/ch_ppocr_mobile_v2_0_det/lite_train_lite_infer/norm_train_gpus_0_autocast_null Global.epoch_num=100 Train.loader.batch_size_per_card=2 !  + Run successfully with command - ch_ppocr_mobile_v2_0_det - python3.7 tools/export_model.py -c configs/det/ch_ppocr_v2_0/ch_det_mv3_db_v2_0.yml -o Global.checkpoints=./test_tipc/output/ch_ppocr_mobile_v2_0_det/lite_train_lite_infer/norm_train_gpus_0_autocast_null/latest Global.save_inference_dir=./test_tipc/output/ch_ppocr_mobile_v2_0_det/lite_train_lite_infer/norm_train_gpus_0_autocast_null > ./test_tipc/output/ch_ppocr_mobile_v2_0_det/lite_train_lite_infer/norm_train_gpus_0_autocast_null_nodes_1_export.log 2>&1 !  + Run successfully with command - ch_ppocr_mobile_v2_0_det - python3.7 tools/infer/predict_det.py --use_gpu=True --use_tensorrt=False --precision=fp32 --det_model_dir=./test_tipc/output/ch_ppocr_mobile_v2_0_det/lite_train_lite_infer/norm_train_gpus_0_autocast_null --rec_batch_num=1 --image_dir=./train_data/icdar2015/text_localization/ch4_test_images/ --benchmark=True > ./test_tipc/output/ch_ppocr_mobile_v2_0_det/lite_train_lite_infer/python_infer_gpu_usetrt_False_precision_fp32_batchsize_1.log 2>&1 !  + Run successfully with command - ch_ppocr_mobile_v2_0_det - python3.7 tools/infer/predict_det.py --use_gpu=False --enable_mkldnn=False --cpu_threads=6 --det_model_dir=./test_tipc/output/ch_ppocr_mobile_v2_0_det/lite_train_lite_infer/norm_train_gpus_0_autocast_null --rec_batch_num=1 --image_dir=./train_data/icdar2015/text_localization/ch4_test_images/ --benchmark=True --precision=fp32 > ./test_tipc/output/ch_ppocr_mobile_v2_0_det/lite_train_lite_infer/python_infer_cpu_usemkldnn_False_threads_6_precision_fp32_batchsize_1.log 2>&1 !  +...... +``` +如果运行失败,会输出: +``` +Run failed with command - python3.7 tools/train.py -c tests/configs/det_mv3_db.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained Global.use_gpu=True Global.save_model_dir=./tests/output/norm_train_gpus_0_autocast_null Global.epoch_num=1 Train.loader.batch_size_per_card=2 ! +Run failed with command - python3.7 tools/export_model.py -c tests/configs/det_mv3_db.yml -o Global.pretrained_model=./tests/output/norm_train_gpus_0_autocast_null/latest Global.save_inference_dir=./tests/output/norm_train_gpus_0_autocast_null! +...... +``` +可以很方便的根据`results_python.log`中的内容判定哪一个指令运行错误。 + +#### 2.2.2 PACT在线量化链条 +此外,`test_train_inference_python.sh`还包含PACT在线量化模式,命令如下: +以ch_PP-OCRv2_det为例,如需测试其他模型更换配置即可。 + +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_PP-OCRv2_det/train_pact_infer_python.txt 'lite_train_lite_infer' +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_PP-OCRv2_det/train_pact_infer_python.txt 'lite_train_lite_infer' +``` +#### 2.2.3 混合精度训练链条 +此外,`test_train_inference_python.sh`还包含混合精度训练模式,命令如下: +以ch_PP-OCRv2_det为例,如需测试其他模型更换配置即可。 + +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_PP-OCRv2_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt 'lite_train_lite_infer' +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_PP-OCRv2_det/train_linux_gpu_normal_amp_infer_python_linux_gpu_cpu.txt 'lite_train_lite_infer' +``` + +### 2.3 精度测试 + +使用compare_results.py脚本比较模型预测的结果是否符合预期,主要步骤包括: +- 提取日志中的预测坐标; +- 从本地文件中提取保存好的坐标结果; +- 比较上述两个结果是否符合精度预期,误差大于设置阈值时会报错。 + +#### 使用方式 +运行命令: +```shell +python3.7 test_tipc/compare_results.py --gt_file=./test_tipc/results/python_*.txt --log_file=./test_tipc/output/python_*.log --atol=1e-3 --rtol=1e-3 +``` + +参数介绍: +- gt_file: 指向事先保存好的预测结果路径,支持*.txt 结尾,会自动索引*.txt格式的文件,文件默认保存在test_tipc/result/ 文件夹下 +- log_file: 指向运行test_tipc/test_train_inference_python.sh 脚本的infer模式保存的预测日志,预测日志中打印的有预测结果,比如:文本框,预测文本,类别等等,同样支持python_infer_*.log格式传入 +- atol: 设置的绝对误差 +- rtol: 设置的相对误差 + +#### 运行结果 + +正常运行效果如下图: + + +出现不一致结果时的运行输出: + + + +## 3. 更多教程 +本文档为功能测试用,更丰富的训练预测使用教程请参考: +[模型训练](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/doc/doc_ch/training.md) +[基于Python预测引擎推理](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/doc/doc_ch/inference_ppocr.md) diff --git a/test_tipc/docs/win_test_train_inference_python.md b/test_tipc/docs/win_test_train_inference_python.md new file mode 100644 index 0000000..5f80df1 --- /dev/null +++ b/test_tipc/docs/win_test_train_inference_python.md @@ -0,0 +1,146 @@ +# Windows端基础训练预测功能测试 + +Windows端基础训练预测功能测试的主程序为`test_train_inference_python.sh`,可以测试基于Python的模型训练、评估、推理等基本功能,包括裁剪、量化、蒸馏。 + +## 1. 测试结论汇总 + +- 训练相关: + +| 算法名称 | 模型名称 | 单机单卡 | 单机多卡 | 多机多卡 | 模型压缩(单机多卡) | +| :---- | :---- | :---- | :---- | :---- | :---- | +| DB | ch_ppocr_mobile_v2_0_det| 正常训练
    混合精度 | - | - | 正常训练:FPGM裁剪、PACT量化 | + + +- 预测相关:基于训练是否使用量化,可以将训练产出的模型可以分为`正常模型`和`量化模型`,这两类模型对应的预测功能汇总如下: + +| 模型类型 |device | batchsize | tensorrt | mkldnn | cpu多线程 | +| ---- | ---- | ---- | :----: | :----: | :----: | +| 正常模型 | GPU | 1/6 | fp32/fp16 | - | - | +| 正常模型 | CPU | 1/6 | - | fp32/fp16 | 支持 | +| 量化模型 | GPU | 1/6 | int8 | - | - | +| 量化模型 | CPU | 1/6 | - | int8 | 支持 | + + +## 2. 测试流程 + +运行环境配置请参考[文档](./install.md)的内容配置TIPC的运行环境。 + +另外,由于Windows上和linux的路径管理方式不同,可以在win上安装gitbash终端,在gitbash中执行指令的方式和在linux端执行指令方式相同,更方便tipc测试。gitbash[下载链接](https://git-scm.com/download/win)。 + + +### 2.1 安装依赖 +- 安装PaddlePaddle >= 2.3 +- 安装PaddleOCR依赖 + ``` + pip install -r ../requirements.txt + ``` +- 安装autolog(规范化日志输出工具) + ``` + pip install https://paddleocr.bj.bcebos.com/libs/auto_log-1.2.0-py3-none-any.whl + ``` +- 安装PaddleSlim (可选) + ``` + # 如果要测试量化、裁剪等功能,需要安装PaddleSlim + pip install paddleslim + ``` + + +### 2.2 功能测试 +先运行`prepare.sh`准备数据和模型,然后运行`test_train_inference_python.sh`进行测试,最终在```test_tipc/output```目录下生成`,model_name/lite_train_lite_infer/*.log`格式的日志文件。 + + +`test_train_inference_python.sh`包含基础链条的4种运行模式,每种模式的运行数据不同,分别用于测试速度和精度,分别是: + +- 模式1:lite_train_lite_infer,使用少量数据训练,用于快速验证训练到预测的走通流程,不验证精度和速度; +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt 'lite_train_lite_infer' +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt 'lite_train_lite_infer' +``` + +- 模式2:lite_train_whole_infer,使用少量数据训练,一定量数据预测,用于验证训练后的模型执行预测,预测速度是否合理; +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt 'lite_train_whole_infer' +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt 'lite_train_whole_infer' +``` + +- 模式3:whole_infer,不训练,全量数据预测,走通开源模型评估、动转静,检查inference model预测时间和精度; +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt 'whole_infer' +# 用法1: +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt 'whole_infer' +# 用法2: 指定GPU卡预测,第三个传入参数为GPU卡号 +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt 'whole_infer' '1' +``` + +- 模式4:whole_train_whole_infer,CE: 全量数据训练,全量数据预测,验证模型训练精度,预测精度,预测速度; +```shell +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt 'whole_train_whole_infer' +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2_0_det/train_windows_gpu_normal_normal_infer_python_windows_cpu_gpu.txt 'whole_train_whole_infer' +``` + +运行相应指令后,在`test_tipc/output`文件夹下自动会保存运行日志。如'lite_train_lite_infer'模式下,会运行训练+inference的链条,因此,在`test_tipc/output`文件夹有以下文件: +``` +test_tipc/output/model_name/lite_train_lite_infer/ +|- results_python.log # 运行指令状态的日志 +|- norm_train_gpus_0_autocast_null/ # GPU 0号卡上正常训练的训练日志和模型保存文件夹 +...... +|- python_infer_cpu_usemkldnn_False_threads_6_precision_fp32_batchsize_1.log # CPU上关闭Mkldnn线程数设置为6,测试batch_size=1条件下的fp32精度预测运行日志 +|- python_infer_gpu_usetrt_False_precision_fp32_batchsize_1.log # GPU上关闭TensorRT,测试batch_size=1的fp32精度预测日志 +...... +``` + +其中`results_python.log`中包含了每条指令的运行状态,如果运行成功会输出: +``` +Run successfully with command - python3.7 tools/train.py -c tests/configs/det_mv3_db.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained Global.use_gpu=True Global.save_model_dir=./tests/output/norm_train_gpus_0_autocast_null Global.epoch_num=1 Train.loader.batch_size_per_card=2 ! +Run successfully with command - python3.7 tools/export_model.py -c tests/configs/det_mv3_db.yml -o Global.pretrained_model=./tests/output/norm_train_gpus_0_autocast_null/latest Global.save_inference_dir=./tests/output/norm_train_gpus_0_autocast_null! +...... +``` +如果运行失败,会输出: +``` +Run failed with command - python3.7 tools/train.py -c tests/configs/det_mv3_db.yml -o Global.pretrained_model=./pretrain_models/MobileNetV3_large_x0_5_pretrained Global.use_gpu=True Global.save_model_dir=./tests/output/norm_train_gpus_0_autocast_null Global.epoch_num=1 Train.loader.batch_size_per_card=2 ! +Run failed with command - python3.7 tools/export_model.py -c tests/configs/det_mv3_db.yml -o Global.pretrained_model=./tests/output/norm_train_gpus_0_autocast_null/latest Global.save_inference_dir=./tests/output/norm_train_gpus_0_autocast_null! +...... +``` +可以很方便的根据`results_python.log`中的内容判定哪一个指令运行错误。 + + +### 2.3 精度测试 + +使用compare_results.py脚本比较模型预测的结果是否符合预期,主要步骤包括: +- 提取日志中的预测坐标; +- 从本地文件中提取保存好的坐标结果; +- 比较上述两个结果是否符合精度预期,误差大于设置阈值时会报错。 + +#### 使用方式 +运行命令: +```shell +python test_tipc/compare_results.py --gt_file=./test_tipc/results/python_*.txt --log_file=./test_tipc/output/python_*.log --atol=1e-3 --rtol=1e-3 +``` + +参数介绍: +- gt_file: 指向事先保存好的预测结果路径,支持*.txt 结尾,会自动索引*.txt格式的文件,文件默认保存在test_tipc/result/ 文件夹下 +- log_file: 指向运行test_tipc/test_train_inference_python.sh 脚本的infer模式保存的预测日志,预测日志中打印的有预测结果,比如:文本框,预测文本,类别等等,同样支持python_infer_*.log格式传入 +- atol: 设置的绝对误差 +- rtol: 设置的相对误差 + +#### 运行结果 + +正常运行效果如下: +``` +Assert allclose passed! The results of python_infer_cpu_usemkldnn_False_threads_1_batchsize_1.log and ./test_tipc/results/python_ppocr_det_mobile_results_fp32.txt are consistent! +``` + +出现不一致结果时的运行输出: +``` +...... +Traceback (most recent call last): + File "test_tipc/compare_results.py", line 140, in + format(filename, gt_filename)) +ValueError: The results of python_infer_cpu_usemkldnn_False_threads_1_batchsize_1.log and the results of ./test_tipc/results/python_ppocr_det_mobile_results_fp32.txt are inconsistent! +``` + + +## 3. 更多教程 +本文档为功能测试用,更丰富的训练预测使用教程请参考: +[模型训练](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/doc/doc_ch/training.md) +[基于Python预测引擎推理](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/doc/doc_ch/inference_ppocr.md) diff --git a/test_tipc/prepare.sh b/test_tipc/prepare.sh new file mode 100644 index 0000000..7e58741 --- /dev/null +++ b/test_tipc/prepare.sh @@ -0,0 +1,912 @@ +#!/bin/bash +source test_tipc/common_func.sh + +FILENAME=$1 + +# MODE be one of ['lite_train_lite_infer' 'lite_train_whole_infer' 'whole_train_whole_infer', +# 'whole_infer', 'klquant_whole_infer', +# 'cpp_infer', 'serving_infer'] + +MODE=$2 + +dataline=$(cat ${FILENAME}) + +# parser params +IFS=$'\n' +lines=(${dataline}) + +# The training params +model_name=$(func_parser_value "${lines[1]}") + +trainer_list=$(func_parser_value "${lines[14]}") + +if [ ${MODE} = "benchmark_train" ];then + python_name_list=$(func_parser_value "${lines[2]}") + array=(${python_name_list}) + python_name=python + ${python_name} -m pip install -r requirements.txt + if [[ ${model_name} =~ "ch_ppocr_mobile_v2_0_det" || ${model_name} =~ "det_mv3_db_v2_0" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/pretrained/MobileNetV3_large_x0_5_pretrained.pdparams --no-check-certificate + rm -rf ./train_data/icdar2015 + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/icdar2015_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015_benckmark.tar + ln -s ./icdar2015_benckmark ./icdar2015 + cd ../ + if [[ ${model_name} =~ "ch_ppocr_mobile_v2_0_det" ]];then + # expand gt.txt 2 times + cd ./train_data/icdar2015/text_localization + for i in `seq 2`;do cp train_icdar2015_label.txt dup$i.txt;done + cat dup* > train_icdar2015_label.txt && rm -rf dup* + cd ../../../ + fi + fi + if [[ ${model_name} =~ "PP-OCRv4_mobile_det" ]];then + wget -nc -P ./pretrain_models/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/PP-OCRv4_mobile_det_pretrained.pdparams + --no-check-certificate + rm -rf ./train_data/icdar2015 + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/icdar2015_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015_benckmark.tar + ln -s ./icdar2015_benckmark ./icdar2015 + cd ../ + fi + if [[ ${model_name} =~ "PP-OCRv4_server_det" ]];then + wget -nc -P ./pretrain_models/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/PP-OCRv4_server_det_pretrained.pdparams --no-check-certificate + rm -rf ./train_data/icdar2015 + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/icdar2015_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015_benckmark.tar + ln -s ./icdar2015_benckmark ./icdar2015 + cd ../ + fi + if [[ ${model_name} =~ "PP-OCRv4_mobile_rec" ]];then + rm -rf ./train_data/ic15_data + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/ic15_data_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf ic15_data_benckmark.tar + ln -s ./ic15_data_benckmark ./ic15_data + cd ic15_data + mv rec_gt_train4w.txt rec_gt_train.txt + cd ../ + cd ../ + fi + if [[ ${model_name} =~ "PP-OCRv4_server_rec" ]];then + rm -rf ./train_data/ic15_data + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/ic15_data_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf ic15_data_benckmark.tar + ln -s ./ic15_data_benckmark ./ic15_data + cd ic15_data + mv rec_gt_train4w.txt rec_gt_train.txt + cd ../ + cd ../ + fi + if [[ ${model_name} =~ "ch_ppocr_server_v2_0_det" || ${model_name} =~ "ch_PP-OCRv3_det" ]];then + rm -rf ./train_data/icdar2015 + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/icdar2015_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015_benckmark.tar + ln -s ./icdar2015_benckmark ./icdar2015 + cd ../ + fi + if [[ ${model_name} =~ "ch_PP-OCRv2_det" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_det_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf ch_ppocr_server_v2.0_det_train.tar && cd ../ + rm -rf ./train_data/icdar2015 + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/icdar2015_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015_benckmark.tar + ln -s ./icdar2015_benckmark ./icdar2015 + cd ../ + fi + if [[ ${model_name} =~ "det_r50_vd_east_v2_0" ]]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_r50_vd_east_v2.0_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf det_r50_vd_east_v2.0_train.tar && cd ../ + rm -rf ./train_data/icdar2015 + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/icdar2015_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015_benckmark.tar + ln -s ./icdar2015_benckmark ./icdar2015 + cd ../ + fi + if [[ ${model_name} =~ "det_r50_db_v2_0" || ${model_name} =~ "det_r50_vd_pse_v2_0" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/pretrained/ResNet50_vd_ssld_pretrained.pdparams --no-check-certificate + rm -rf ./train_data/icdar2015 + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/icdar2015_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015_benckmark.tar + ln -s ./icdar2015_benckmark ./icdar2015 + cd ../ + fi + if [[ ${model_name} =~ "det_r18_db_v2_0" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/pretrained/ResNet18_vd_pretrained.pdparams --no-check-certificate + rm -rf ./train_data/icdar2015 + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/icdar2015_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015_benckmark.tar + ln -s ./icdar2015_benckmark ./icdar2015 + cd ../ + fi + if [[ ${model_name} =~ "ch_ppocr_mobile_v2_0_rec" || ${model_name} =~ "ch_ppocr_server_v2_0_rec" || ${model_name} =~ "ch_PP-OCRv2_rec" || ${model_name} =~ "rec_mv3_none_bilstm_ctc_v2_0" || ${model_name} =~ "PP-OCRv3_mobile_rec" ]];then + rm -rf ./train_data/ic15_data + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/ic15_data_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf ic15_data_benckmark.tar + ln -s ./ic15_data_benckmark ./ic15_data + cd ../ + fi + if [[ ${model_name} =~ "ch_PP-OCRv2_rec" || ${model_name} =~ "PP-OCRv3_mobile_rec" || ${model_name} =~ "PP-OCRv4_mobile_rec" || ${model_name} =~ "PP-OCRv4_server_rec" ]];then + rm -rf ./train_data/ic15_data + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/ic15_data_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf ic15_data_benckmark.tar + ln -s ./ic15_data_benckmark ./ic15_data + cd ic15_data + mv rec_gt_train4w.txt rec_gt_train.txt + cd ../ + cd ../ + fi + if [[ ${model_name} == "en_table_structure" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.1/table/en_ppocr_mobile_v2.0_table_structure_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf en_ppocr_mobile_v2.0_table_structure_train.tar && cd ../ + rm -rf ./train_data/pubtabnet + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/pubtabnet_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf pubtabnet_benckmark.tar + ln -s ./pubtabnet_benckmark ./pubtabnet + cd ../ + fi + if [[ ${model_name} == "slanet" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/ch_ppstructure_mobile_v2.0_SLANet_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf ch_ppstructure_mobile_v2.0_SLANet_train.tar && cd ../ + rm -rf ./train_data/pubtabnet + wget -nc -P ./train_data/ https://paddle-model-ecology.bj.bcebos.com/uapi/data/table_rec_dataset_examples.tar --no-check-certificate + cd ./train_data/ && tar xf table_rec_dataset_examples.tar + ln -s ./table_rec_dataset_examples ./pubtabnet + cd pubtabnet + for i in `seq 10`;do cp train.txt dup$i.txt;done + cat dup* > train.txt && rm -rf dup* + cd ../ + cd ../ + fi + if [[ ${model_name} == "det_r50_dcn_fce_ctw_v2_0" ]]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/contribution/det_r50_dcn_fce_ctw_v2.0_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf det_r50_dcn_fce_ctw_v2.0_train.tar && cd ../ + rm -rf ./train_data/icdar2015 + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/icdar2015_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015_benckmark.tar + ln -s ./icdar2015_benckmark ./icdar2015 + cd ../ + fi + if [ ${model_name} == "layoutxlm_ser" ] || [ ${model_name} == "vi_layoutxlm_ser" ]; then + ${python_name} -m pip install -r ppstructure/kie/requirements.txt + ${python_name} -m pip install opencv-python -U + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar --no-check-certificate + cd ./train_data/ && tar xf XFUND.tar + # expand gt.txt 10 times + cd XFUND/zh_train + for i in `seq 10`;do cp train.json dup$i.txt;done + cat dup* > train.json && rm -rf dup* + cd ../../ + + cd ../ + fi + if [ ${model_name} == "table_master" ];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/ppstructure/models/tablemaster/table_structure_tablemaster_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf table_structure_tablemaster_train.tar && cd ../ + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/StructureLabel_val_500.tar --no-check-certificate + cd ./train_data/ && tar xf StructureLabel_val_500.tar + cd ../ + fi + if [ ${model_name} == "rec_svtrnet" ]; then + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/ic15_data_benckmark.tar --no-check-certificate + cd ./train_data/ && tar xf ic15_data_benckmark.tar + ln -s ./ic15_data_benckmark ./ic15_data + cd ic15_data + mv rec_gt_train4w.txt rec_gt_train.txt + + for i in `seq 10`;do cp rec_gt_train.txt dup$i.txt;done + cat dup* > rec_gt_train.txt && rm -rf dup* + + cd ../ + cd ../ + fi +fi + +if [ ${MODE} = "lite_train_lite_infer" ];then + python_name_list=$(func_parser_value "${lines[2]}") + array=(${python_name_list}) + python_name=${array[0]} + ${python_name} -m pip install -r requirements.txt + ${python_name} -m pip install https://paddleocr.bj.bcebos.com/libs/auto_log-1.2.0-py3-none-any.whl + ${python_name} -m pip install paddleslim + # pretrain lite train data + wget -nc -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_large_x0_5_pretrained.pdparams --no-check-certificate + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_mv3_db_v2.0_train.tar --no-check-certificate + cd ./pretrain_models/ + tar xf det_mv3_db_v2.0_train.tar + cd ../ + if [[ ${model_name} =~ "ch_PP-OCRv2_det" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_distill_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf ch_PP-OCRv2_det_distill_train.tar && cd ../ + fi + if [[ ${model_name} =~ "PP-OCRv3_det" ]];then + wget -nc -P ./pretrain_models/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/PP-OCRv3_mobile_det_pretrained.pdparams --no-check-certificate + fi + if [[ ${model_name} =~ "PP-OCRv4_mobile_det" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/pretrained/PPLCNetV3_x0_75_ocr_det.pdparams --no-check-certificate + fi + if [[ ${model_name} =~ "PP-OCRv4_server_det" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/pretrained/PPHGNet_small_ocr_det.pdparams --no-check-certificate + fi + if [ ${model_name} == "en_table_structure" ] || [ ${model_name} == "en_table_structure_PACT" ];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.1/table/en_ppocr_mobile_v2.0_table_structure_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf en_ppocr_mobile_v2.0_table_structure_train.tar && cd ../ + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_det_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_rec_infer.tar --no-check-certificate + cd ./inference/ && tar xf en_ppocr_mobile_v2.0_table_det_infer.tar && tar xf en_ppocr_mobile_v2.0_table_rec_infer.tar && cd ../ + fi + if [[ ${model_name} =~ "slanet" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/en_ppstructure_mobile_v2.0_SLANet_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf en_ppstructure_mobile_v2.0_SLANet_train.tar && cd ../ + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_det_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_rec_infer.tar --no-check-certificate + cd ./inference/ && tar xf en_ppocr_mobile_v2.0_table_det_infer.tar && tar xf en_ppocr_mobile_v2.0_table_rec_infer.tar && cd ../ + fi + if [[ ${model_name} =~ "det_r50_db_plusplus" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.1/en_det/ResNet50_dcn_asf_synthtext_pretrained.pdparams --no-check-certificate + fi + if [ ${model_name} == "table_master" ];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/ppstructure/models/tablemaster/table_structure_tablemaster_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf table_structure_tablemaster_train.tar && cd ../ + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/StructureLabel_val_500.tar --no-check-certificate + cd ./train_data/ && tar xf StructureLabel_val_500.tar && cd ../ + fi + rm -rf ./train_data/icdar2015 + rm -rf ./train_data/ic15_data + rm -rf ./train_data/pubtabnet + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/icdar2015_lite.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ic15_data.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/pubtabnet.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./deploy/slim/prune https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/sen.pickle --no-check-certificate + + cd ./train_data/ && tar xf icdar2015_lite.tar && tar xf ic15_data.tar && tar xf pubtabnet.tar + ln -s ./icdar2015_lite ./icdar2015 + wget -nc -P ./ic15_data/ https://paddleocr.bj.bcebos.com/dataset/rec_gt_train_lite.txt --no-check-certificate + wget -nc -P ./ic15_data/ https://paddleocr.bj.bcebos.com/dataset/rec_gt_test_lite.txt --no-check-certificate + mv ic15_data/rec_gt_train_lite.txt ic15_data/rec_gt_train.txt + mv ic15_data/rec_gt_test_lite.txt ic15_data/rec_gt_test.txt + cd ../ + cd ./inference && tar xf rec_inference.tar && cd ../ + if [ ${model_name} == "ch_PP-OCRv2_det" ] || [ ${model_name} == "ch_PP-OCRv2_det_PACT" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_det_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf ch_ppocr_server_v2.0_det_train.tar && cd ../ + fi + if [ ${model_name} == "ch_PP-OCRv2_rec" ] || [ ${model_name} == "ch_PP-OCRv2_rec_PACT" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf ch_PP-OCRv2_rec_train.tar && cd ../ + fi + if [ ${model_name} == "PP-OCRv3_mobile_rec" ] || [ ${model_name} == "PP-OCRv3_mobile_rec_PACT" ]; then + wget -nc -P ./pretrain_models/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_rec_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf PP-OCRv3_mobile_rec_train.tar && cd ../ + fi + if [ ${model_name} == "det_r18_db_v2_0" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/pretrained/ResNet18_vd_pretrained.pdparams --no-check-certificate + fi + if [ ${model_name} == "en_server_pgnetA" ]; then + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/total_text_lite.tar --no-check-certificate + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/pgnet/en_server_pgnetA.tar --no-check-certificate + cd ./pretrain_models/ && tar xf en_server_pgnetA.tar && cd ../ + cd ./train_data && tar xf total_text_lite.tar && ln -s total_text_lite total_text && cd ../ + fi + if [ ${model_name} == "det_r50_vd_sast_icdar15_v2_0" ] || [ ${model_name} == "det_r50_vd_sast_totaltext_v2_0" ]; then + wget -nc -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet50_vd_ssld_pretrained.pdparams --no-check-certificate + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_r50_vd_sast_icdar15_v2.0_train.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/total_text_lite.tar --no-check-certificate + cd ./train_data && tar xf total_text_lite.tar && ln -s total_text_lite total_text && cd ../ + cd ./pretrain_models && tar xf det_r50_vd_sast_icdar15_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "det_mv3_db_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_mv3_db_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf det_mv3_db_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "det_r50_db_v2_0" ]; then + wget -nc -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet50_vd_ssld_pretrained.pdparams --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_r50_vd_db_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf det_r50_vd_db_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "ch_ppocr_mobile_v2_0_rec_FPGM" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf ch_ppocr_mobile_v2.0_rec_train.tar && cd ../ + ${python_name} -m pip install paddleslim + fi + if [ ${model_name} == "ch_ppocr_mobile_v2_0_det_FPGM" ]; then + ${python_name} -m pip install paddleslim + fi + if [ ${model_name} == "det_r50_vd_pse_v2_0" ]; then + wget -nc -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet50_vd_ssld_pretrained.pdparams --no-check-certificate + fi + if [ ${model_name} == "det_mv3_east_v2_0" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_mv3_east_v2.0_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf det_mv3_east_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "det_r50_vd_east_v2_0" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_r50_vd_east_v2.0_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf det_r50_vd_east_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "det_r50_dcn_fce_ctw_v2_0" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/contribution/det_r50_dcn_fce_ctw_v2.0_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf det_r50_dcn_fce_ctw_v2.0_train.tar & cd ../ + fi + if [ ${model_name} == "rec_r32_gaspin_bilstm_att" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/rec_r32_gaspin_bilstm_att_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf rec_r32_gaspin_bilstm_att_train.tar && cd ../ + fi + if [[ ${model_name} =~ "layoutxlm_ser" ]]; then + ${python_name} -m pip install -r ppstructure/kie/requirements.txt + ${python_name} -m pip install opencv-python -U + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar --no-check-certificate + cd ./train_data/ && tar xf XFUND.tar + cd ../ + + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/pplayout/ser_LayoutXLM_xfun_zh.tar --no-check-certificate + cd ./pretrain_models/ && tar xf ser_LayoutXLM_xfun_zh.tar && cd ../ + fi + if [[ ${model_name} =~ "vi_layoutxlm_ser" ]]; then + ${python_name} -m pip install -r ppstructure/kie/requirements.txt + ${python_name} -m pip install opencv-python -U + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar --no-check-certificate + cd ./train_data/ && tar xf XFUND.tar + cd ../ + if [ ${model_name} == "vi_layoutxlm_ser_PACT" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_pretrained.tar --no-check-certificate + cd ./pretrain_models/ && tar xf ser_vi_layoutxlm_xfund_pretrained.tar && cd ../ + fi + fi + if [ ${model_name} == "det_r18_ct" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/pretrained/ResNet18_vd_pretrained.pdparams --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/ct_tipc/total_text_lite2.tar --no-check-certificate + cd ./train_data && tar xf total_text_lite2.tar && ln -s total_text_lite2 total_text && cd ../ + fi + if [ ${model_name} == "sr_telescope" ]; then + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/TextZoom.tar --no-check-certificate + cd ./train_data/ && tar xf TextZoom.tar && cd ../ + fi + if [ ${model_name} == "rec_d28_can" ]; then + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/CROHME_lite.tar --no-check-certificate + cd ./train_data/ && tar xf CROHME_lite.tar && cd ../ + fi + +elif [ ${MODE} = "whole_train_whole_infer" ];then + wget -nc -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_large_x0_5_pretrained.pdparams --no-check-certificate + rm -rf ./train_data/icdar2015 + rm -rf ./train_data/ic15_data + rm -rf ./train_data/pubtabnet + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/icdar2015.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ic15_data.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/pubtabnet.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015.tar && tar xf ic15_data.tar && tar xf pubtabnet.tar + wget -nc -P ./ic15_data/ https://paddleocr.bj.bcebos.com/dataset/rec_gt_train_lite.txt --no-check-certificate + wget -nc -P ./ic15_data/ https://paddleocr.bj.bcebos.com/dataset/rec_gt_test_lite.txt --no-check-certificate + cd ../ + if [ ${model_name} == "ch_PP-OCRv2_det" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_distill_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf ch_PP-OCRv2_det_distill_train.tar && cd ../ + fi + if [ ${model_name} == "PP-OCRv3_det" ]; then + wget -nc -P ./pretrain_models/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/PP-OCRv3_mobile_det_pretrained.pdparams --no-check-certificate + fi + if [ ${model_name} == "en_server_pgnetA" ]; then + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/total_text_lite.tar --no-check-certificate + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/pgnet/en_server_pgnetA.tar --no-check-certificate + cd ./pretrain_models/ && tar xf en_server_pgnetA.tar && cd ../ + cd ./train_data && tar xf total_text.tar && ln -s total_text_lite total_text && cd ../ + fi + if [ ${model_name} == "det_r50_vd_sast_totaltext_v2_0" ]; then + wget -nc -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet50_vd_ssld_pretrained.pdparams --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/total_text_lite.tar --no-check-certificate + cd ./train_data && tar xf total_text.tar && ln -s total_text_lite total_text && cd ../ + fi + if [[ ${model_name} =~ "en_table_structure" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.1/table/en_ppocr_mobile_v2.0_table_structure_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf en_ppocr_mobile_v2.0_table_structure_train.tar && cd ../ + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_det_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_rec_infer.tar --no-check-certificate + cd ./inference/ && tar xf en_ppocr_mobile_v2.0_table_det_infer.tar && tar xf en_ppocr_mobile_v2.0_table_rec_infer.tar && cd ../ + fi +elif [ ${MODE} = "lite_train_whole_infer" ];then + wget -nc -P ./pretrain_models/ https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/MobileNetV3_large_x0_5_pretrained.pdparams --no-check-certificate + rm -rf ./train_data/icdar2015 + rm -rf ./train_data/ic15_data + rm -rf ./train_data/pubtabnet + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/icdar2015_infer.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ic15_data.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/pubtabnet.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015_infer.tar && tar xf ic15_data.tar && tar xf pubtabnet.tar + ln -s ./icdar2015_infer ./icdar2015 + wget -nc -P ./ic15_data/ https://paddleocr.bj.bcebos.com/dataset/rec_gt_train_lite.txt --no-check-certificate + wget -nc -P ./ic15_data/ https://paddleocr.bj.bcebos.com/dataset/rec_gt_test_lite.txt --no-check-certificate + cd ../ + if [ ${model_name} == "ch_PP-OCRv2_det" ]; then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_distill_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf ch_PP-OCRv2_det_distill_train.tar && cd ../ + fi + if [ ${model_name} == "PP-OCRv3_det" ]; then + wget -nc -P ./pretrain_models/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_pretrained_model/PP-OCRv3_mobile_det_pretrained.pdparams --no-check-certificate + fi + if [[ ${model_name} =~ "en_table_structure" ]];then + wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.1/table/en_ppocr_mobile_v2.0_table_structure_train.tar --no-check-certificate + cd ./pretrain_models/ && tar xf en_ppocr_mobile_v2.0_table_structure_train.tar && cd ../ + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_det_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_rec_infer.tar --no-check-certificate + cd ./inference/ && tar xf en_ppocr_mobile_v2.0_table_det_infer.tar && tar xf en_ppocr_mobile_v2.0_table_rec_infer.tar && cd ../ + fi +elif [ ${MODE} = "whole_infer" ];then + python_name_list=$(func_parser_value "${lines[2]}") + array=(${python_name_list}) + python_name=${array[0]} + ${python_name} -m pip install paddleslim + ${python_name} -m pip install -r requirements.txt + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + cd ./inference && tar xf rec_inference.tar && tar xf ch_det_data_50.tar && cd ../ + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/pubtabnet.tar --no-check-certificate + cd ./train_data/ && tar xf XFUND.tar && tar xf pubtabnet.tar && cd ../ + head -n 2 train_data/XFUND/zh_val/val.json > train_data/XFUND/zh_val/val_lite.json + mv train_data/XFUND/zh_val/val_lite.json train_data/XFUND/zh_val/val.json + if [ ${model_name} = "ch_ppocr_mobile_v2_0_det" ]; then + eval_model_name="ch_ppocr_mobile_v2.0_det_train" + rm -rf ./train_data/icdar2015 + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_train.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_infer.tar --no-check-certificate + cd ./inference && tar xf ${eval_model_name}.tar && tar xf ch_det_data_50.tar && tar xf ch_ppocr_mobile_v2.0_det_infer.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_mobile_v2_0_det_PACT" ]; then + eval_model_name="ch_ppocr_mobile_v2.0_det_prune_infer" + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/slim/ch_ppocr_mobile_v2.0_det_prune_infer.tar --no-check-certificate + cd ./inference && tar xf ${eval_model_name}.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_server_v2_0_det" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_det_train.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_server_v2.0_det_train.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_mobile_v2_0" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_det_infer.tar && tar xf ch_ppocr_mobile_v2.0_rec_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_server_v2_0" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_server_v2.0_det_infer.tar && tar xf ch_ppocr_server_v2.0_rec_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_mobile_v2_0_rec_PACT" ]; then + eval_model_name="ch_ppocr_mobile_v2.0_rec_slim_infer" + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_slim_infer.tar --no-check-certificate + cd ./inference && tar xf ${eval_model_name}.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_mobile_v2_0_rec_FPGM" ]; then + eval_model_name="ch_PP-OCRv2_rec_infer" + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ${eval_model_name}.tar && cd ../ + fi + if [[ ${model_name} =~ "ch_PP-OCRv2" ]]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_det_infer.tar && tar xf ch_PP-OCRv2_rec_infer.tar && tar xf ch_det_data_50.tar && cd ../ + fi + if [[ ${model_name} =~ "ch_PP-OCRv3" ]]; then + wget -nc -P ./inference/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_det_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_rec_infer.tar --no-check-certificate + cd ./inference && tar xf PP-OCRv3_mobile_det_infer.tar && tar xf PP-OCRv3_mobile_rec_infer.tar && tar xf ch_det_data_50.tar && cd ../ + fi + if [[ ${model_name} =~ "ch_PP-OCRv2_det" ]]; then + eval_model_name="ch_PP-OCRv2_det_infer" + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar --no-check-certificate + cd ./inference && tar xf ${eval_model_name}.tar && tar xf ch_det_data_50.tar && cd ../ + fi + if [[ ${model_name} =~ "ch_PP-OCRv3_det" ]]; then + eval_model_name="PP-OCRv3_mobile_det_infer" + wget -nc -P ./inference/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_det_infer.tar --no-check-certificate + cd ./inference && tar xf ${eval_model_name}.tar && tar xf ch_det_data_50.tar && cd ../ + fi + if [[ ${model_name} =~ "ch_PP-OCRv2_rec" ]]; then + eval_model_name="ch_PP-OCRv2_rec_infer" + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_slim_quant_infer.tar --no-check-certificate + cd ./inference && tar xf ${eval_model_name}.tar && tar xf ch_PP-OCRv2_rec_slim_quant_infer.tar && cd ../ + fi + if [[ ${model_name} =~ "PP-OCRv3_mobile_rec" ]]; then + eval_model_name="PP-OCRv3_mobile_rec_infer" + wget -nc -P ./inference/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_rec_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/PP-OCRv3_mobile_rec_slim_infer.tar --no-check-certificate + cd ./inference && tar xf ${eval_model_name}.tar && tar xf PP-OCRv3_mobile_rec_slim_infer.tar && cd ../ + fi + if [[ ${model_name} == "PP-OCRv3_mobile_rec_PACT" ]]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/PP-OCRv3_mobile_rec_slim_infer.tar --no-check-certificate + cd ./inference && tar xf PP-OCRv3_mobile_rec_slim_infer.tar && cd ../ + fi + if [ ${model_name} == "en_server_pgnetA" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/pgnet/en_server_pgnetA.tar --no-check-certificate + cd ./inference && tar xf en_server_pgnetA.tar && tar xf ch_det_data_50.tar && cd ../ + fi + if [ ${model_name} == "det_r50_vd_sast_icdar15_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_r50_vd_sast_icdar15_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf det_r50_vd_sast_icdar15_v2.0_train.tar && tar xf ch_det_data_50.tar && cd ../ + fi + if [ ${model_name} == "rec_mv3_none_none_ctc_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mv3_none_none_ctc_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf rec_mv3_none_none_ctc_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "rec_r34_vd_none_none_ctc_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_r34_vd_none_none_ctc_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf rec_r34_vd_none_none_ctc_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "rec_mv3_none_bilstm_ctc_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mv3_none_bilstm_ctc_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf rec_mv3_none_bilstm_ctc_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "rec_r34_vd_none_bilstm_ctc_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_r34_vd_none_bilstm_ctc_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf rec_r34_vd_none_bilstm_ctc_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "rec_mv3_tps_bilstm_ctc_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mv3_tps_bilstm_ctc_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf rec_mv3_tps_bilstm_ctc_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "rec_r34_vd_tps_bilstm_ctc_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_r34_vd_tps_bilstm_ctc_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf rec_r34_vd_tps_bilstm_ctc_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "ch_ppocr_server_v2_0_rec" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_rec_train.tar --no-check-certificate + cd ./inference/ && tar xf ch_ppocr_server_v2.0_rec_train.tar && cd ../ + fi + if [ ${model_name} == "ch_ppocr_mobile_v2_0_rec" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_train.tar --no-check-certificate + cd ./inference/ && tar xf ch_ppocr_mobile_v2.0_rec_train.tar && cd ../ + fi + if [ ${model_name} == "rec_mtb_nrtr" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mtb_nrtr_train.tar --no-check-certificate + cd ./inference/ && tar xf rec_mtb_nrtr_train.tar && cd ../ + fi + if [ ${model_name} == "rec_mv3_tps_bilstm_att_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mv3_tps_bilstm_att_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf rec_mv3_tps_bilstm_att_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "rec_r34_vd_tps_bilstm_att_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_r34_vd_tps_bilstm_att_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf rec_r34_vd_tps_bilstm_att_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "rec_r31_sar" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.1/rec/rec_r31_sar_train.tar --no-check-certificate + cd ./inference/ && tar xf rec_r31_sar_train.tar && cd ../ + fi + if [ ${model_name} == "rec_r50_fpn_vd_none_srn" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_r50_vd_srn_train.tar --no-check-certificate + cd ./inference/ && tar xf rec_r50_vd_srn_train.tar && cd ../ + fi + + if [ ${model_name} == "det_r50_vd_sast_totaltext_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_r50_vd_sast_totaltext_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf det_r50_vd_sast_totaltext_v2.0_train.tar && cd ../ + fi + if [ ${model_name} == "det_mv3_db_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_mv3_db_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf det_mv3_db_v2.0_train.tar && tar xf ch_det_data_50.tar && cd ../ + fi + if [ ${model_name} == "det_r50_db_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_r50_vd_db_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf det_r50_vd_db_v2.0_train.tar && tar xf ch_det_data_50.tar && cd ../ + fi + if [ ${model_name} == "det_mv3_pse_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.1/en_det/det_mv3_pse_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf det_mv3_pse_v2.0_train.tar & cd ../ + fi + if [ ${model_name} == "det_r50_vd_pse_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.1/en_det/det_r50_vd_pse_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf det_r50_vd_pse_v2.0_train.tar & cd ../ + fi + if [ ${model_name} == "det_mv3_east_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_mv3_east_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf det_mv3_east_v2.0_train.tar & cd ../ + fi + if [ ${model_name} == "det_r50_vd_east_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/det_r50_vd_east_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf det_r50_vd_east_v2.0_train.tar & cd ../ + fi + if [ ${model_name} == "det_r50_dcn_fce_ctw_v2_0" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/contribution/det_r50_dcn_fce_ctw_v2.0_train.tar --no-check-certificate + cd ./inference/ && tar xf det_r50_dcn_fce_ctw_v2.0_train.tar & cd ../ + fi + if [[ ${model_name} =~ "en_table_structure" ]];then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_det_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_rec_infer.tar --no-check-certificate + + cd ./inference/ && tar xf en_ppocr_mobile_v2.0_table_det_infer.tar && tar xf en_ppocr_mobile_v2.0_table_rec_infer.tar + if [ ${model_name} == "en_table_structure" ]; then + wget -nc https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_structure_infer.tar --no-check-certificate + tar xf en_ppocr_mobile_v2.0_table_structure_infer.tar + elif [ ${model_name} == "en_table_structure_PACT" ]; then + wget -nc https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_structure_slim_infer.tar --no-check-certificate + tar xf en_ppocr_mobile_v2.0_table_structure_slim_infer.tar + fi + cd ../ + fi + if [[ ${model_name} =~ "slanet" ]];then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/paddle3.0b2/en_ppstructure_mobile_v2.0_SLANet_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_det_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_rec_infer.tar --no-check-certificate + cd ./inference/ && tar xf en_ppstructure_mobile_v2.0_SLANet_infer.tar && tar xf PP-OCRv3_mobile_det_infer.tar && tar xf PP-OCRv3_mobile_rec_infer.tar && cd ../ + fi + if [[ ${model_name} =~ "vi_layoutxlm_ser" ]]; then + ${python_name} -m pip install -r ppstructure/kie/requirements.txt + ${python_name} -m pip install opencv-python -U + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/ppstructure/models/vi_layoutxlm/ser_vi_layoutxlm_xfund_infer.tar --no-check-certificate + cd ./inference/ && tar xf ser_vi_layoutxlm_xfund_infer.tar & cd ../ + fi + if [[ ${model_name} =~ "layoutxlm_ser" ]]; then + ${python_name} -m pip install -r ppstructure/kie/requirements.txt + ${python_name} -m pip install opencv-python -U + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/pplayout/ser_LayoutXLM_xfun_zh_infer.tar --no-check-certificate + cd ./inference/ && tar xf ser_LayoutXLM_xfun_zh_infer.tar & cd ../ + fi +fi + +if [[ ${model_name} =~ "KL" ]]; then + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/icdar2015_lite.tar --no-check-certificate + cd ./train_data/ && tar xf icdar2015_lite.tar && rm -rf ./icdar2015 && ln -s ./icdar2015_lite ./icdar2015 && cd ../ + if [ ${model_name} = "ch_ppocr_mobile_v2_0_det_KL" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_det_infer.tar && tar xf ch_det_data_50.tar && cd ../ + fi + if [ ${model_name} = "ch_PP-OCRv2_rec_KL" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ic15_data.tar --no-check-certificate + cd ./train_data/ && tar xf ic15_data.tar && cd ../ + cd ./inference && tar xf rec_inference.tar && tar xf ch_PP-OCRv2_rec_infer.tar && cd ../ + fi + if [ ${model_name} = "PP-OCRv3_mobile_rec_KL" ]; then + wget -nc -P ./inference https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_rec_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ic15_data.tar --no-check-certificate + cd ./train_data/ && tar xf ic15_data.tar + wget -nc -P ./ic15_data/ https://paddleocr.bj.bcebos.com/dataset/rec_gt_train_lite.txt --no-check-certificate + wget -nc -P ./ic15_data/ https://paddleocr.bj.bcebos.com/dataset/rec_gt_test_lite.txt --no-check-certificate + cd ../ + cd ./inference && tar xf rec_inference.tar && tar xf PP-OCRv3_mobile_rec_infer.tar && cd ../ + fi + if [ ${model_name} = "ch_PP-OCRv2_det_KL" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_det_infer.tar && tar xf ch_det_data_50.tar && cd ../ + fi + if [ ${model_name} = "ch_PP-OCRv3_det_KL" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_det_infer.tar --no-check-certificate + cd ./inference && tar xf PP-OCRv3_mobile_det_infer.tar && tar xf ch_det_data_50.tar && cd ../ + fi + if [ ${model_name} = "ch_ppocr_mobile_v2_0_rec_KL" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ic15_data.tar --no-check-certificate + cd ./train_data/ && tar xf ic15_data.tar && cd ../ + cd ./inference && tar xf ch_ppocr_mobile_v2.0_rec_infer.tar && tar xf rec_inference.tar && cd ../ + fi + if [ ${model_name} = "en_table_structure_KL" ];then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_structure_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_det_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_rec_infer.tar --no-check-certificate + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/dataset/pubtabnet.tar --no-check-certificate + cd ./inference/ && tar xf en_ppocr_mobile_v2.0_table_structure_infer.tar && tar xf en_ppocr_mobile_v2.0_table_det_infer.tar && tar xf en_ppocr_mobile_v2.0_table_rec_infer.tar && cd ../ + cd ./train_data/ && tar xf pubtabnet.tar && cd ../ + fi + if [[ ${model_name} =~ "layoutxlm_ser_KL" ]]; then + wget -nc -P ./train_data/ https://paddleocr.bj.bcebos.com/ppstructure/dataset/XFUND.tar --no-check-certificate + cd ./train_data/ && tar xf XFUND.tar && cd ../ + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/pplayout/ser_LayoutXLM_xfun_zh_infer.tar --no-check-certificate + cd ./inference/ && tar xf ser_LayoutXLM_xfun_zh_infer.tar & cd ../ + fi +fi + +if [ ${MODE} = "cpp_infer" ];then + if [ ${model_name} = "ch_ppocr_mobile_v2_0_det" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_det_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_mobile_v2_0_det_KL" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_ppocr_mobile_v2.0_det_klquant_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_det_klquant_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_mobile_v2_0_det_PACT" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_ppocr_mobile_v2.0_det_pact_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_det_pact_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_mobile_v2_0_rec" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_rec_infer.tar && tar xf rec_inference.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_mobile_v2_0_rec_KL" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_ppocr_mobile_v2.0_rec_klquant_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_rec_klquant_infer.tar && tar xf rec_inference.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_mobile_v2_0_rec_PACT" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_ppocr_mobile_v2.0_rec_pact_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_rec_pact_infer.tar && tar xf rec_inference.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_server_v2_0_det" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_det_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_server_v2.0_det_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_server_v2_0_rec" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_server_v2.0_rec_infer.tar && tar xf rec_inference.tar && cd ../ + elif [ ${model_name} = "ch_PP-OCRv2_det" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_det_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_PP-OCRv2_det_KL" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv2_det_klquant_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_det_klquant_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_PP-OCRv2_det_PACT" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv2_det_pact_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_det_pact_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_PP-OCRv2_rec" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_rec_infer.tar && tar xf rec_inference.tar && cd ../ + elif [ ${model_name} = "ch_PP-OCRv2_rec_KL" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv2_rec_klquant_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_rec_klquant_infer.tar && tar xf rec_inference.tar && cd ../ + elif [ ${model_name} = "ch_PP-OCRv2_rec_PACT" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv2_rec_pact_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_rec_pact_infer.tar && tar xf rec_inference.tar && cd ../ + elif [ ${model_name} = "ch_PP-OCRv3_det" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_det_infer.tar --no-check-certificate + cd ./inference && tar xf PP-OCRv3_mobile_det_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_PP-OCRv3_det_KL" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv3_det_klquant_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv3_det_klquant_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_PP-OCRv3_det_PACT" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv3_det_pact_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv3_det_pact_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "PP-OCRv3_mobile_rec" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./inference https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_rec_infer.tar --no-check-certificate + cd ./inference && tar xf PP-OCRv3_mobile_rec_infer.tar && tar xf rec_inference.tar && cd ../ + elif [ ${model_name} = "PP-OCRv3_mobile_rec_KL" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/PP-OCRv3_mobile_rec_klquant_infer.tar --no-check-certificate + cd ./inference && tar xf PP-OCRv3_mobile_rec_klquant_infer.tar && tar xf rec_inference.tar && cd ../ + elif [ ${model_name} = "PP-OCRv3_mobile_rec_PACT" ]; then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/PP-OCRv3_mobile_rec_pact_infer.tar --no-check-certificate + cd ./inference && tar xf PP-OCRv3_mobile_rec_pact_infer.tar && tar xf rec_inference.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_mobile_v2_0" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_det_infer.tar && tar xf ch_ppocr_mobile_v2.0_rec_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_ppocr_server_v2_0" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_server_v2.0_det_infer.tar && tar xf ch_ppocr_server_v2.0_rec_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_PP-OCRv2" ]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_det_infer.tar && tar xf ch_PP-OCRv2_rec_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [ ${model_name} = "ch_PP-OCRv3" ]; then + wget -nc -P ./inference https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_rec_infer.tar --no-check-certificate + cd ./inference && tar xf PP-OCRv3_mobile_det_infer.tar && tar xf PP-OCRv3_mobile_rec_infer.tar && tar xf ch_det_data_50.tar && cd ../ + elif [[ ${model_name} =~ "en_table_structure" ]];then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_det_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_rec_infer.tar --no-check-certificate + + cd ./inference/ && tar xf en_ppocr_mobile_v2.0_table_det_infer.tar && tar xf en_ppocr_mobile_v2.0_table_rec_infer.tar + if [ ${model_name} == "en_table_structure" ]; then + wget -nc https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_structure_infer.tar --no-check-certificate + tar xf en_ppocr_mobile_v2.0_table_structure_infer.tar + elif [ ${model_name} == "en_table_structure_PACT" ]; then + wget -nc https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_structure_slim_infer.tar --no-check-certificate + tar xf en_ppocr_mobile_v2.0_table_structure_slim_infer.tar + fi + cd ../ + elif [[ ${model_name} =~ "slanet" ]];then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/paddle3.0b2/ch_ppstructure_mobile_v2.0_SLANet_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_det_infer.tar --no-check-certificate + wget -nc -P ./inference/ https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_rec_infer.tar --no-check-certificate + cd ./inference/ && tar xf ch_ppstructure_mobile_v2.0_SLANet_infer.tar && tar xf PP-OCRv3_mobile_det_infer.tar && tar xf PP-OCRv3_mobile_rec_infer.tar && cd ../ + fi +fi + +if [ ${MODE} = "serving_infer" ];then + # prepare serving env + python_name_list=$(func_parser_value "${lines[2]}") + IFS='|' + array=(${python_name_list}) + python_name=${array[0]} + ${python_name} -m pip install paddle-serving-server-gpu + ${python_name} -m pip install paddle_serving_client + ${python_name} -m pip install paddle-serving-app + ${python_name} -m pip install https://paddleocr.bj.bcebos.com/libs/auto_log-1.2.0-py3-none-any.whl + # wget model + if [ ${model_name} == "ch_ppocr_mobile_v2_0_det_KL" ] || [ ${model_name} == "ch_ppocr_mobile_v2.0_rec_KL" ] ; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_ppocr_mobile_v2.0_det_klquant_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_ppocr_mobile_v2.0_rec_klquant_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_det_klquant_infer.tar && tar xf ch_ppocr_mobile_v2.0_rec_klquant_infer.tar && cd ../ + elif [ ${model_name} == "ch_PP-OCRv2_det_KL" ] || [ ${model_name} == "ch_PP-OCRv2_rec_KL" ] ; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv2_det_klquant_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv2_rec_klquant_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_det_klquant_infer.tar && tar xf ch_PP-OCRv2_rec_klquant_infer.tar && cd ../ + elif [ ${model_name} == "ch_PP-OCRv3_det_KL" ] || [ ${model_name} == "PP-OCRv3_mobile_rec_KL" ] ; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv3_det_klquant_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/PP-OCRv3_mobile_rec_klquant_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv3_det_klquant_infer.tar && tar xf PP-OCRv3_mobile_rec_klquant_infer.tar && cd ../ + elif [ ${model_name} == "ch_ppocr_mobile_v2_0_det_PACT" ] || [ ${model_name} == "ch_ppocr_mobile_v2.0_rec_PACT" ] ; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_ppocr_mobile_v2.0_det_pact_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_ppocr_mobile_v2.0_rec_pact_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_det_pact_infer.tar && tar xf ch_ppocr_mobile_v2.0_rec_pact_infer.tar && cd ../ + elif [ ${model_name} == "ch_PP-OCRv2_det_PACT" ] || [ ${model_name} == "ch_PP-OCRv2_rec_PACT" ] ; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv2_det_pact_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv2_rec_pact_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_det_pact_infer.tar && tar xf ch_PP-OCRv2_rec_pact_infer.tar && cd ../ + elif [ ${model_name} == "ch_PP-OCRv3_det_PACT" ] || [ ${model_name} == "PP-OCRv3_mobile_rec_PACT" ] ; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/ch_PP-OCRv3_det_pact_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/tipc_fake_model/PP-OCRv3_mobile_rec_pact_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv3_det_pact_infer.tar && tar xf PP-OCRv3_mobile_rec_pact_infer.tar && cd ../ + elif [[ ${model_name} =~ "ch_ppocr_mobile_v2_0" ]]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_det_infer.tar && tar xf ch_ppocr_mobile_v2.0_rec_infer.tar && cd ../ + elif [[ ${model_name} =~ "ch_ppocr_server_v2_0" ]]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_server_v2.0_det_infer.tar && tar xf ch_ppocr_server_v2.0_rec_infer.tar && cd ../ + elif [[ ${model_name} =~ "ch_PP-OCRv2" ]]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_det_infer.tar && tar xf ch_PP-OCRv2_rec_infer.tar && cd ../ + elif [[ ${model_name} =~ "ch_PP-OCRv3" ]]; then + wget -nc -P ./inference https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_rec_infer.tar --no-check-certificate + cd ./inference && tar xf PP-OCRv3_mobile_det_infer.tar && tar xf PP-OCRv3_mobile_rec_infer.tar && cd ../ + fi + # wget data + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar --no-check-certificate + cd ./inference && tar xf ch_det_data_50.tar && tar xf rec_inference.tar && cd ../ +fi + +if [ ${MODE} = "paddle2onnx_infer" ];then + # prepare serving env + python_name=$(func_parser_value "${lines[2]}") + ${python_name} -m pip install paddle2onnx onnxruntime onnx + # wget model + if [[ ${model_name} =~ "ch_ppocr_mobile_v2_0" ]]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_mobile_v2.0_det_infer.tar && tar xf ch_ppocr_mobile_v2.0_rec_infer.tar && cd ../ + elif [[ ${model_name} =~ "ch_ppocr_server_v2_0" ]]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_server_v2.0_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_ppocr_server_v2.0_det_infer.tar && tar xf ch_ppocr_server_v2.0_rec_infer.tar && cd ../ + elif [[ ${model_name} =~ "ch_PP-OCRv2" ]]; then + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar --no-check-certificate + cd ./inference && tar xf ch_PP-OCRv2_det_infer.tar && tar xf ch_PP-OCRv2_rec_infer.tar && cd ../ + elif [[ ${model_name} =~ "ch_PP-OCRv3" ]]; then + wget -nc -P ./inference https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_det_infer.tar --no-check-certificate + wget -nc -P ./inference https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/PP-OCRv3_mobile_rec_infer.tar --no-check-certificate + cd ./inference && tar xf PP-OCRv3_mobile_det_infer.tar && tar xf PP-OCRv3_mobile_rec_infer.tar && cd ../ + elif [[ ${model_name} =~ "slanet" ]];then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/ppstructure/models/slanet/paddle3.0b2/ch_ppstructure_mobile_v2.0_SLANet_infer.tar --no-check-certificate + cd ./inference/ && tar xf ch_ppstructure_mobile_v2.0_SLANet_infer.tar && cd ../ + elif [[ ${model_name} =~ "en_table_structure" ]];then + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/table/en_ppocr_mobile_v2.0_table_structure_infer.tar --no-check-certificate + cd ./inference/ && tar xf en_ppocr_mobile_v2.0_table_structure_infer.tar && cd ../ + fi + + # wget data + wget -nc -P ./inference https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/ch_det_data_50.tar + wget -nc -P ./inference/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/rec_inference.tar + cd ./inference && tar xf ch_det_data_50.tar && tar xf rec_inference.tar && cd ../ + +fi diff --git a/test_tipc/prepare_lite_cpp.sh b/test_tipc/prepare_lite_cpp.sh new file mode 100644 index 0000000..0d3a5ca --- /dev/null +++ b/test_tipc/prepare_lite_cpp.sh @@ -0,0 +1,109 @@ +#!/bin/bash +source ./test_tipc/common_func.sh +FILENAME=$1 +dataline=$(cat ${FILENAME}) +# parser params +IFS=$'\n' +lines=(${dataline}) +IFS=$'\n' +paddlelite_library_source=$2 + +inference_cmd=$(func_parser_value "${lines[1]}") +DEVICE=$(func_parser_value "${lines[2]}") +det_lite_model_list=$(func_parser_value "${lines[3]}") +rec_lite_model_list=$(func_parser_value "${lines[4]}") +cls_lite_model_list=$(func_parser_value "${lines[5]}") + +if [[ $inference_cmd =~ "det" ]]; then + lite_model_list=${det_lite_model_list} +elif [[ $inference_cmd =~ "rec" ]]; then + lite_model_list=(${rec_lite_model_list[*]} ${cls_lite_model_list[*]}) +elif [[ $inference_cmd =~ "system" ]]; then + lite_model_list=(${det_lite_model_list[*]} ${rec_lite_model_list[*]} ${cls_lite_model_list[*]}) +else + echo "inference_cmd is wrong, please check." + exit 1 +fi + +if [ ${DEVICE} = "ARM_CPU" ]; then + valid_targets="arm" + paddlelite_library_url="https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10-rc/inference_lite_lib.android.armv8.gcc.c++_shared.with_extra.with_cv.tar.gz" + end_index="66" + compile_with_opencl="OFF" +elif [ ${DEVICE} = "ARM_GPU_OPENCL" ]; then + valid_targets="opencl" + paddlelite_library_url="https://github.com/PaddlePaddle/Paddle-Lite/releases/download/v2.10-rc/inference_lite_lib.armv8.clang.with_exception.with_extra.with_cv.opencl.tar.gz" + end_index="71" + compile_with_opencl="ON" +else + echo "DEVICE only support ARM_CPU, ARM_GPU_OPENCL." + exit 2 +fi + +# prepare paddlelite model +pip install paddlelite==2.10-rc +current_dir=${PWD} +IFS="|" +model_path=./inference_models + +for model in ${lite_model_list[*]}; do + if [[ $model =~ "PP-OCRv2" ]]; then + inference_model_url=https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/${model}.tar + elif [[ $model =~ "v2_0" ]]; then + inference_model_url=https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/${model}.tar + elif [[ $model =~ "PP-OCRv3" ]]; then + inference_model_url=https://paddleocr.bj.bcebos.com/PP-OCRv3/chinese/${model}.tar + else + echo "Model is wrong, please check." + exit 3 + fi + inference_model=${inference_model_url##*/} + wget -nc -P ${model_path} ${inference_model_url} + cd ${model_path} && tar -xf ${inference_model} && cd ../ + model_dir=${model_path}/${inference_model%.*} + model_file=${model_dir}/inference.pdmodel + param_file=${model_dir}/inference.pdiparams + paddle_lite_opt --model_dir=${model_dir} --model_file=${model_file} --param_file=${param_file} --valid_targets=${valid_targets} --optimize_out=${model_dir}_opt +done + +# prepare test data +data_url=https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/icdar2015_lite.tar +data_file=${data_url##*/} +wget -nc -P ./test_data ${data_url} +cd ./test_data && tar -xf ${data_file} && rm ${data_file} && cd ../ + +# prepare paddlelite predict library +if [[ ${paddlelite_library_source} = "download" ]]; then + paddlelite_library_zipfile=$(echo $paddlelite_library_url | awk -F "/" '{print $NF}') + paddlelite_library_file=${paddlelite_library_zipfile:0:${end_index}} + wget ${paddlelite_library_url} && tar -xf ${paddlelite_library_zipfile} + cd ${paddlelite_library_zipfile} +elif [[ ${paddlelite_library_source} = "compile" ]]; then + git clone -b release/v2.10 https://github.com/PaddlePaddle/Paddle-Lite.git + cd Paddle-Lite + ./lite/tools/build_android.sh --arch=armv8 --with_cv=ON --with_extra=ON --toolchain=clang --with_opencl=${compile_with_opencl} + cd ../ + cp -r Paddle-Lite/build.lite.android.armv8.clang/inference_lite_lib.android.armv8/ . + paddlelite_library_file=inference_lite_lib.android.armv8 +else + echo "paddlelite_library_source only support 'download' and 'compile'" + exit 3 +fi + +# organize the required files +mkdir -p ${paddlelite_library_file}/demo/cxx/ocr/test_lite +cp -r ${model_path}/*_opt.nb test_data ${paddlelite_library_file}/demo/cxx/ocr/test_lite +cp ppocr/utils/ppocr_keys_v1.txt deploy/lite/config.txt ${paddlelite_library_file}/demo/cxx/ocr/test_lite +cp -r ./deploy/lite/* ${paddlelite_library_file}/demo/cxx/ocr/ +cp ${paddlelite_library_file}/cxx/lib/libpaddle_light_api_shared.so ${paddlelite_library_file}/demo/cxx/ocr/test_lite +cp ${FILENAME} test_tipc/test_lite_arm_cpp.sh test_tipc/common_func.sh ${paddlelite_library_file}/demo/cxx/ocr/test_lite +cd ${paddlelite_library_file}/demo/cxx/ocr/ +git clone https://github.com/cuicheng01/AutoLog.git + +# compile and do some postprocess +make -j +sleep 1 +make -j +cp ocr_db_crnn test_lite && cp test_lite/libpaddle_light_api_shared.so test_lite/libc++_shared.so +tar -cf test_lite.tar ./test_lite && cp test_lite.tar ${current_dir} && cd ${current_dir} +rm -rf ${paddlelite_library_file}* && rm -rf ${model_path} diff --git a/test_tipc/readme.md b/test_tipc/readme.md new file mode 100644 index 0000000..540d9d9 --- /dev/null +++ b/test_tipc/readme.md @@ -0,0 +1,148 @@ + +# 飞桨训推一体全流程(TIPC) + +## 1. 简介 + +飞桨除了基本的模型训练和预测,还提供了支持多端多平台的高性能推理部署工具。本文档提供了PaddleOCR中所有模型的飞桨训推一体全流程(Training and Inference Pipeline Criterion(TIPC))信息和测试工具,方便用户查阅每种模型的训练推理部署打通情况,并可以进行一键测试。 + +
    + +
    + +## 2. 汇总信息 + +打通情况汇总如下,已填写的部分表示可以使用本工具进行一键测试,未填写的表示正在支持中。 + +**字段说明:** +- 基础训练预测:包括模型训练、Paddle Inference Python预测。 +- 更多训练方式:包括多机多卡、混合精度。 +- 模型压缩:包括裁剪、离线/在线量化、蒸馏。 +- 其他预测部署:包括Paddle Inference C++预测、Paddle Serving部署、Paddle-Lite部署等。 + +更详细的mkldnn、Tensorrt等预测加速相关功能的支持情况可以查看各测试工具的[更多教程](#more)。 + +| 算法论文 | 模型名称 | 模型类型 | 基础
    训练预测 | 更多
    训练方式 | 模型压缩 | 其他预测部署 | +| :--- | :--- | :----: | :--------: | :---- | :---- | :---- | +| DB |ch_ppocr_mobile_v2.0_det | 检测 | 支持 | 多机多卡
    混合精度 | - | Paddle Inference: C++
    Paddle Serving: Python, C++
    Paddle-Lite:
    (1) ARM CPU(C++) | +| DB |ch_ppocr_mobile_v2.0_det_FPGM | 检测 | 支持 | 多机多卡
    混合精度 | FPGM裁剪 | Paddle Inference: C++
    Paddle Serving: Python, C++
    Paddle-Lite:
    (1) ARM CPU(C++) | +| DB |ch_ppocr_mobile_v2.0_det_PACT | 检测 | 支持 | 多机多卡
    混合精度 | PACT量化 | Paddle Inference: C++
    Paddle Serving: Python, C++
    Paddle-Lite:
    (1) ARM CPU(C++) | +| DB |ch_ppocr_mobile_v2.0_det_KL | 检测 | 支持 | 多机多卡
    混合精度 | 离线量化| Paddle Inference: C++
    Paddle Serving: Python, C++
    Paddle-Lite:
    (1) ARM CPU(C++) | +| DB |ch_ppocr_server_v2.0_det | 检测 | 支持 | 多机多卡
    混合精度 | - | Paddle Inference: C++
    Paddle Serving: Python, C++ | +| DB |ch_PP-OCRv2_det | 检测 | 支持 | 多机多卡
    混合精度 | - | Paddle Inference: C++
    Paddle Serving: Python, C++ | +| CRNN |ch_ppocr_mobile_v2.0_rec | 识别 | 支持 | 多机多卡
    混合精度 | - | Paddle Inference: C++
    Paddle Serving: Python, C++
    Paddle-Lite:
    (1) ARM CPU(C++) | +| CRNN |ch_ppocr_server_v2.0_rec | 识别 | 支持 | 多机多卡
    混合精度 | - | Paddle Inference: C++
    Paddle Serving: Python, C++ | +| CRNN |ch_PP-OCRv2_rec | 识别 | 支持 | 多机多卡
    混合精度 | - | Paddle Inference: C++
    Paddle Serving: Python, C++ | +| PP-OCR |ch_ppocr_mobile_v2.0 | 检测+识别 | 支持 | 多机多卡
    混合精度 | - | Paddle Inference: C++
    Paddle Serving: Python, C++
    Paddle-Lite:
    (1) ARM CPU(C++) | +| PP-OCR |ch_ppocr_server_v2.0 | 检测+识别 | 支持 | 多机多卡
    混合精度 | - | Paddle Inference: C++
    Paddle Serving: Python, C++ | +|PP-OCRv2|ch_PP-OCRv2 | 检测+识别 | 支持 | 多机多卡
    混合精度 | - | Paddle Inference: C++
    Paddle Serving: Python, C++ | +| DB |det_mv3_db_v2.0 | 检测 | 支持 | 多机多卡
    混合精度 | - | - | +| DB |det_r50_vd_db_v2.0 | 检测 | 支持 | 多机多卡
    混合精度 | - | - | +| EAST |det_mv3_east_v2.0 | 检测 | 支持 | 多机多卡
    混合精度 | - | - | +| EAST |det_r50_vd_east_v2.0 | 检测 | 支持 | 多机多卡
    混合精度 | - | - | +| PSENet |det_mv3_pse_v2.0 | 检测 | 支持 | 多机多卡
    混合精度 | - | - | +| PSENet |det_r50_vd_pse_v2.0 | 检测 | 支持 | 多机多卡
    混合精度 | - | - | +| SAST |det_r50_vd_sast_totaltext_v2.0 | 检测 | 支持 | 多机多卡
    混合精度 | - | - | +| Rosetta|rec_mv3_none_none_ctc_v2.0 | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| Rosetta|rec_r34_vd_none_none_ctc_v2.0 | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| CAN |rec_d28_can | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| CRNN |rec_mv3_none_bilstm_ctc_v2.0 | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| CRNN |rec_r34_vd_none_bilstm_ctc_v2.0| 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| StarNet|rec_mv3_tps_bilstm_ctc_v2.0 | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| StarNet|rec_r34_vd_tps_bilstm_ctc_v2.0 | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| RARE |rec_mv3_tps_bilstm_att_v2.0 | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| RARE |rec_r34_vd_tps_bilstm_att_v2.0 | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| SRN |rec_r50fpn_vd_none_srn | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| NRTR |rec_mtb_nrtr | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| SAR |rec_r31_sar | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| SPIN |rec_r32_gaspin_bilstm_att | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| RobustScanner |rec_r31_robustscanner | 识别 | 支持 | 多机多卡
    混合精度 | - | - | +| PGNet |rec_r34_vd_none_none_ctc_v2.0 | 端到端| 支持 | 多机多卡
    混合精度 | - | - | +| TableMaster |table_structure_tablemaster_train | 表格识别| 支持 | 多机多卡
    混合精度 | - | - | + + + +## 3. 测试工具简介 +### 目录介绍 + +```shell +test_tipc/ +├── configs/ # 配置文件目录 + ├── ch_ppocr_mobile_v2.0_det # ch_ppocr_mobile_v2.0_det模型的测试配置文件目录 + ├── train_infer_python.txt # 测试Linux上python训练预测(基础训练预测)的配置文件 + ├── model_linux_gpu_normal_normal_infer_cpp_linux_gpu_cpu.txt # 测试Linux上c++预测的配置文件 + ├── train_linux_gpu_fleet_amp_infer_python_linux_gpu_cpu.txt # 测试Linux上多机多卡、混合精度训练和python预测的配置文件 + ├── ... + ├── ch_ppocr_server_v2.0_det # ch_ppocr_server_v2.0_det模型的测试配置文件目录 + ├── ... + ├── ch_ppocr_mobile_v2.0_rec # ch_ppocr_mobile_v2.0_rec模型的测试配置文件目录 + ├── ... + ├── ch_ppocr_server_v2.0_det # ch_ppocr_server_v2.0_det模型的测试配置文件目录 + ├── ... + ├── ... +├── results/ # 预先保存的预测结果,用于和实际预测结果进行精读比对 + ├── python_ppocr_det_mobile_results_fp32.txt # 预存的mobile版ppocr检测模型python预测fp32精度的结果 + ├── python_ppocr_det_mobile_results_fp16.txt # 预存的mobile版ppocr检测模型python预测fp16精度的结果 + ├── cpp_ppocr_det_mobile_results_fp32.txt # 预存的mobile版ppocr检测模型c++预测的fp32精度的结果 + ├── cpp_ppocr_det_mobile_results_fp16.txt # 预存的mobile版ppocr检测模型c++预测的fp16精度的结果 + ├── ... +├── prepare.sh # 完成test_*.sh运行所需要的数据和模型下载 +├── test_train_inference_python.sh # 测试python训练预测的主程序 +├── test_inference_cpp.sh # 测试c++预测的主程序 +├── test_serving.sh # 测试serving部署预测的主程序 +├── test_lite_arm_cpu_cpp.sh # 测试lite在arm_cpu上部署的C++预测的主程序 +├── compare_results.py # 用于对比log中的预测结果与results中的预存结果精度误差是否在限定范围内 +└── readme.md # 使用文档 +``` + +### 测试流程概述 + +使用本工具,可以测试不同功能的支持情况,以及预测结果是否对齐,测试流程概括如下: +
    + +
    + +1. 运行prepare.sh准备测试所需数据和模型; +2. 运行要测试的功能对应的测试脚本`test_*.sh`,产出log,由log可以看到不同配置是否运行成功; +3. 用`compare_results.py`对比log中的预测结果和预存在results目录下的结果,判断预测精度是否符合预期(在误差范围内)。 + +测试单项功能仅需两行命令,**如需测试不同模型/功能,替换配置文件即可**,命令格式如下: +```shell +# 功能:准备数据 +# 格式:bash + 运行脚本 + 参数1: 配置文件选择 + 参数2: 模式选择 +bash test_tipc/prepare.sh configs/[model_name]/[params_file_name] [Mode] + +# 功能:运行测试 +# 格式:bash + 运行脚本 + 参数1: 配置文件选择 + 参数2: 模式选择 +bash test_tipc/test_train_inference_python.sh configs/[model_name]/[params_file_name] [Mode] +``` + +例如,测试基本训练预测功能的`lite_train_lite_infer`模式,运行: +```shell +# 准备数据 +bash test_tipc/prepare.sh ./test_tipc/configs/ch_ppocr_mobile_v2.0_det/train_infer_python.txt 'lite_train_lite_infer' +# 运行测试 +bash test_tipc/test_train_inference_python.sh ./test_tipc/configs/ch_ppocr_mobile_v2.0_det/train_infer_python.txt 'lite_train_lite_infer' +``` +关于本示例命令的更多信息可查看[基础训练预测使用文档](https://github.com/PaddlePaddle/PaddleOCR/blob/dygraph/test_tipc/docs/test_train_inference_python.md#22-%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95)。 + +### 配置文件命名规范 +在`configs`目录下,**按模型名称划分为子目录**,子目录中存放所有该模型测试需要用到的配置文件,配置文件的命名遵循如下规范: + +1. 基础训练预测配置简单命名为:`train_infer_python.txt`,表示**Linux环境下单机、不使用混合精度训练+python预测**,其完整命名对应`train_linux_gpu_normal_normal_infer_python_linux_gpu_cpu.txt`,由于本配置文件使用频率较高,这里进行了名称简化。 + +2. 其他带训练配置命名格式为:`train_训练硬件环境(linux_gpu/linux_dcu/…)_是否多机(fleet/normal)_是否混合精度(amp/normal)_预测模式(infer/lite/serving)_语言(cpp/python/java)_预测硬件环境(linux_gpu/mac/opencl_arm_gpu/...).txt`。如,linux gpu下多机多卡+混合精度链条测试对应配置 `train_linux_gpu_fleet_amp_infer_python_linux_gpu_cpu.txt`,linux dcu下基础训练预测对应配置 `train_linux_dcu_normal_normal_infer_python_linux_dcu.txt`。 + +3. 仅预测的配置(如serving、lite等)命名格式:`model_训练硬件环境(linux_gpu/linux_dcu/…)_是否多机(fleet/normal)_是否混合精度(amp/normal)_(infer/lite/serving)_语言(cpp/python/java)_预测硬件环境(linux_gpu/mac/opencl_arm_gpu/...).txt`,即,与2相比,仅第一个字段从train换为model,测试时模型直接下载获取,这里的“训练硬件环境”表示所测试的模型是在哪种环境下训练得到的。 + +**根据上述命名规范,可以直接从子目录名称和配置文件名找到需要测试的场景和功能对应的配置文件。** + + + +## 4. 开始测试 +各功能测试中涉及混合精度、裁剪、量化等训练相关,及mkldnn、Tensorrt等多种预测相关参数配置,请点击下方相应链接了解更多细节和使用教程: +- [test_train_inference_python 使用](docs/test_train_inference_python.md) :测试基于Python的模型训练、评估、推理等基本功能,包括裁剪、量化、蒸馏。 +- [test_train_fleet_inference_python 使用](./docs/test_train_fleet_inference_python.md):测试基于Python的多机多卡训练与推理等基本功能。 +- [test_inference_cpp 使用](docs/test_inference_cpp.md):测试基于C++的模型推理。 +- [test_serving 使用](docs/test_serving.md):测试基于Paddle Serving的服务化部署功能。 +- [test_lite_arm_cpp 使用](docs/test_lite_arm_cpp.md):测试基于Paddle-Lite的ARM CPU端c++预测部署功能。 +- [test_paddle2onnx 使用](docs/test_paddle2onnx.md):测试Paddle2ONNX的模型转化功能,并验证正确性。 diff --git a/test_tipc/results/cpp_ppocr_det_mobile_results_fp16.txt b/test_tipc/results/cpp_ppocr_det_mobile_results_fp16.txt new file mode 100644 index 0000000..34cde25 --- /dev/null +++ b/test_tipc/results/cpp_ppocr_det_mobile_results_fp16.txt @@ -0,0 +1,50 @@ +../../inference/ch_det_data_50/all-sum-510/00008790.jpg 208 404 282 404 282 421 208 421 58 396 107 396 107 413 58 413 197 387 296 387 296 403 197 403 161 389 174 389 174 402 161 402 34 378 134 378 134 394 34 394 323 377 329 377 329 382 323 382 199 370 292 370 292 383 199 383 216 309 274 309 274 325 216 325 161 304 173 304 173 315 161 315 370 301 437 301 437 317 370 317 30 301 135 300 135 316 30 317 221 291 270 291 270 308 221 308 58 224 106 224 106 238 58 238 216 222 274 222 274 239 216 239 161 217 174 217 174 229 161 229 33 205 133 205 133 221 33 221 221 204 270 204 270 221 221 221 73 145 385 145 385 162 73 162 52 119 119 119 119 135 52 135 72 50 296 50 296 66 72 66 54 15 118 15 118 32 54 32 +../../inference/ch_det_data_50/all-sum-510/00018946.jpg 439 327 476 327 476 341 439 341 85 284 142 284 142 308 85 308 300 278 380 278 380 299 300 299 195 262 287 275 284 299 192 286 196 196 454 218 452 244 194 222 343 182 376 182 376 193 343 193 198 162 341 169 340 195 197 188 176 130 381 145 380 165 175 150 176 100 417 118 415 148 174 130 +../../inference/ch_det_data_50/all-sum-510/00034387.jpg 263 459 741 459 741 485 263 485 346 415 421 415 421 444 346 444 544 418 568 418 568 442 544 442 684 415 712 415 712 444 684 444 173 413 228 413 228 444 173 444 872 412 910 412 910 447 872 447 55 415 76 415 76 443 55 443 855 371 927 371 927 401 855 401 347 371 420 371 420 400 347 400 672 370 725 370 725 402 672 402 537 371 571 371 571 401 537 401 136 364 230 367 229 403 135 400 55 370 76 370 76 399 55 399 856 328 927 328 927 358 856 358 350 328 420 328 420 358 350 358 672 326 725 326 725 358 672 358 539 327 571 327 571 359 539 359 170 326 229 323 231 357 171 359 56 328 76 328 76 358 56 358 297 326 316 326 316 334 297 334 854 284 927 284 927 314 854 314 672 284 725 284 725 315 672 315 344 284 431 282 432 315 345 317 537 283 570 283 570 314 537 314 170 281 228 281 228 315 170 315 55 285 75 285 75 314 55 314 856 241 927 241 927 270 856 270 346 240 464 240 464 271 346 271 154 241 228 241 228 271 154 271 672 240 726 240 726 271 672 271 530 240 573 240 573 272 530 272 55 241 76 241 76 270 55 270 854 196 927 198 926 228 853 225 672 197 728 197 728 228 672 228 342 199 439 194 441 224 344 230 175 196 229 196 229 226 175 226 55 199 75 199 75 228 55 228 526 193 578 193 578 228 526 228 347 154 420 154 420 182 347 182 853 153 927 153 927 181 853 181 175 153 228 153 228 184 175 184 668 152 725 152 725 182 668 182 536 153 572 153 572 183 536 183 55 155 76 155 76 183 55 183 347 109 420 109 420 138 347 138 172 109 229 109 229 140 172 140 544 111 565 111 565 138 544 138 51 110 77 110 77 140 51 140 639 105 729 105 729 141 639 141 815 101 929 109 927 141 813 133 812 65 953 65 953 93 812 93 305 64 447 66 447 94 305 92 671 65 725 65 725 95 671 95 173 64 229 66 228 96 172 94 37 64 91 66 90 98 36 96 527 63 581 63 581 95 527 95 333 18 671 18 671 45 333 45 +../../inference/ch_det_data_50/all-sum-510/00037951.jpg 432 973 552 977 552 994 432 991 431 931 554 931 554 970 431 970 29 520 101 520 101 546 29 546 29 441 146 441 146 465 29 465 233 333 328 331 328 356 233 358 121 250 439 250 439 287 121 287 180 205 380 205 380 229 180 229 255 104 323 121 307 184 239 166 35 57 147 57 147 82 35 82 +../../inference/ch_det_data_50/all-sum-510/00044782.jpg 222 214 247 214 247 230 222 230 162 214 183 214 183 231 162 231 122 190 216 190 216 203 122 203 90 82 252 82 252 100 90 100 70 61 279 61 279 78 70 78 103 14 244 14 244 46 103 46 +../../inference/ch_det_data_50/all-sum-510/00067516.jpg 139 806 596 807 596 824 139 823 46 782 699 782 699 800 46 800 577 749 669 749 669 766 577 766 353 748 397 748 397 769 353 769 220 749 261 749 261 767 220 767 475 748 502 748 502 769 475 769 68 746 134 749 133 766 67 763 574 680 670 680 670 700 574 700 474 680 519 680 519 701 474 701 352 680 397 680 397 701 352 701 68 679 134 682 133 700 67 697 219 678 245 681 242 702 216 698 575 614 669 614 669 633 575 633 66 612 135 614 135 633 66 631 474 613 501 613 501 633 474 633 353 613 379 613 379 634 353 634 219 612 245 612 245 633 219 633 576 546 669 546 669 566 576 566 474 545 519 545 519 566 474 566 351 544 381 544 381 567 351 567 219 545 245 545 245 566 219 566 67 541 134 544 133 565 66 562 67 477 134 480 133 501 66 498 584 479 666 479 666 499 584 499 474 478 519 478 519 500 474 500 352 478 397 478 397 500 352 500 218 477 246 477 246 502 218 502 579 424 666 427 665 451 578 448 344 428 410 428 410 449 344 449 66 425 151 427 151 451 66 449 473 427 515 427 515 450 473 450 218 427 259 427 259 450 218 450 282 396 479 397 479 420 282 419 83 316 667 316 667 335 83 335 64 277 666 277 666 292 64 292 456 209 585 209 585 226 456 226 311 208 373 208 373 227 311 227 163 208 227 208 227 227 163 227 504 150 541 150 541 168 504 168 264 47 485 47 485 69 264 69 +../../inference/ch_det_data_50/all-sum-510/00088568.jpg 57 443 119 443 119 456 57 456 309 413 744 413 744 430 309 430 309 375 737 375 737 392 309 392 415 337 559 337 559 351 415 351 307 322 674 321 674 338 307 339 275 292 348 294 348 313 275 311 52 285 210 285 210 301 52 301 273 262 421 262 421 279 273 279 55 262 249 262 249 279 55 279 669 247 697 247 697 262 669 262 601 247 629 247 629 262 601 262 531 247 559 247 559 262 531 262 461 247 489 247 489 262 461 262 277 247 310 247 310 261 277 261 55 240 142 240 142 254 55 254 276 230 400 230 400 246 276 246 741 227 749 237 741 246 732 237 665 230 701 230 701 245 665 245 598 230 631 230 631 245 598 245 527 230 563 230 563 245 527 245 458 230 493 230 493 245 458 245 52 213 212 215 212 233 52 231 732 214 747 214 747 227 732 227 662 212 706 212 706 230 662 230 594 213 638 213 638 227 594 227 522 213 570 213 570 227 522 227 453 213 497 213 497 227 453 227 278 213 352 213 352 227 278 227 734 198 748 198 748 210 734 210 667 196 702 196 702 210 667 210 599 196 633 196 633 211 599 211 527 196 564 196 564 210 527 210 459 196 493 196 493 210 459 210 276 194 418 195 418 212 276 211 54 190 241 190 241 207 54 207 664 179 705 179 705 194 664 194 278 178 352 180 352 195 278 193 733 179 747 179 747 194 733 194 596 178 635 178 635 193 596 193 523 177 567 177 567 195 523 195 456 178 495 178 495 193 456 193 55 170 142 170 142 184 55 184 733 164 748 164 748 176 733 176 664 162 705 162 705 176 664 176 597 162 635 162 635 176 597 176 525 162 566 162 566 176 525 176 456 162 494 162 494 176 456 176 277 160 399 160 399 176 277 176 54 146 149 146 149 161 54 161 452 145 497 145 497 160 452 160 729 144 748 144 748 162 729 162 662 143 706 143 706 161 662 161 595 144 636 144 636 159 595 159 521 143 566 141 567 159 522 161 277 143 310 143 310 159 277 159 275 120 430 120 430 140 275 140 50 119 234 120 234 140 50 139 402 90 703 90 703 107 402 107 46 78 282 78 282 98 46 98 324 67 745 68 745 86 324 85 667 47 744 47 744 64 667 64 295 47 435 47 435 63 295 63 64 30 232 27 233 65 65 68 +../../inference/ch_det_data_50/all-sum-510/00091741.jpg 46 335 87 335 87 360 46 360 98 209 258 209 258 232 98 232 101 189 258 190 258 206 101 205 87 99 268 97 269 184 88 186 92 45 266 53 263 117 89 109 89 10 258 12 258 38 89 36 +../../inference/ch_det_data_50/all-sum-510/00105313.jpg 289 261 407 261 407 277 289 277 152 260 265 260 265 276 152 276 10 257 74 259 74 276 10 274 32 230 134 230 134 245 32 245 34 215 218 215 218 228 34 228 32 199 148 199 148 214 32 214 31 181 217 182 217 199 31 198 34 169 107 169 107 182 34 182 34 153 126 153 126 166 34 166 33 136 144 137 144 150 33 149 34 122 177 122 177 135 34 135 32 104 178 104 178 120 32 120 32 91 102 91 102 104 32 104 33 75 121 75 121 88 33 88 32 60 121 60 121 73 32 73 34 44 121 44 121 57 34 57 31 28 144 28 144 43 31 43 177 20 415 15 416 51 178 56 24 10 152 10 152 26 24 26 +../../inference/ch_det_data_50/all-sum-510/00134770.jpg 386 645 457 645 457 658 386 658 406 618 486 616 486 634 406 636 111 533 272 530 272 550 111 553 110 501 445 496 445 516 110 521 110 469 445 465 445 485 110 489 110 438 446 433 446 453 110 458 109 407 445 403 445 423 109 427 151 375 443 372 443 392 151 395 183 336 371 334 371 358 183 360 73 96 517 101 516 220 72 215 +../../inference/ch_det_data_50/all-sum-510/00145943.jpg 390 243 751 274 735 454 375 423 88 90 302 90 302 121 88 121 43 40 329 37 329 78 43 81 +../../inference/ch_det_data_50/all-sum-510/00147605.jpg 800 613 878 613 878 627 800 627 514 605 786 604 786 629 514 630 116 521 226 521 226 561 116 561 252 522 309 522 309 558 252 558 713 500 902 503 902 539 713 536 254 501 296 501 296 519 254 519 345 479 475 479 475 517 345 517 251 483 296 483 296 501 251 501 350 456 447 456 447 471 350 471 143 442 203 442 203 469 143 469 727 370 880 370 880 422 727 422 526 369 684 369 684 421 526 421 140 367 490 367 490 423 140 423 742 313 872 313 872 338 742 338 798 155 888 155 888 192 798 192 272 140 457 140 457 161 272 161 737 114 895 118 894 158 736 155 107 110 206 110 206 131 107 131 268 92 464 94 464 134 268 131 +../../inference/ch_det_data_50/all-sum-510/00150341.jpg 98 640 300 640 300 664 98 664 113 615 289 615 289 633 113 633 82 591 320 590 320 611 82 612 30 563 315 561 315 582 30 584 30 513 169 513 169 531 30 531 32 488 111 488 111 506 32 506 357 458 465 461 464 486 356 483 26 458 271 459 271 483 26 482 338 438 423 442 422 461 337 457 64 437 145 437 145 455 64 455 205 414 293 414 293 436 205 436 318 407 442 411 441 439 317 435 42 404 176 407 176 435 42 432 28 381 137 381 137 405 28 405 +../../inference/ch_det_data_50/all-sum-510/00150669.jpg 647 698 683 698 683 718 647 718 515 684 551 684 551 721 515 721 650 687 680 687 680 702 650 702 920 673 938 673 938 686 920 686 518 670 548 670 548 690 518 690 785 670 808 670 808 688 785 688 590 670 608 670 608 688 590 688 732 665 745 679 732 692 718 679 652 668 680 668 680 689 652 689 271 665 423 665 423 690 271 690 45 666 110 666 110 688 45 688 130 664 205 664 205 690 130 690 781 628 812 628 812 663 781 663 643 626 687 626 687 666 643 666 514 627 550 627 550 665 514 665 654 617 673 617 673 629 654 629 521 617 541 617 541 629 521 629 858 617 868 617 868 628 858 628 727 617 736 617 736 628 727 628 920 614 940 614 940 631 920 631 785 614 807 614 807 631 785 631 371 603 421 603 421 620 371 620 83 600 216 603 216 624 83 620 46 602 72 602 72 623 46 623 780 569 817 573 813 610 776 606 922 559 936 559 936 575 922 575 856 559 869 559 869 575 856 575 61 552 411 552 411 569 61 569 61 531 117 533 117 547 61 545 859 527 868 527 868 539 859 539 923 525 936 525 936 542 923 542 787 524 807 524 807 540 787 540 526 526 536 526 536 536 526 536 261 511 396 511 396 528 261 528 120 512 246 512 246 526 120 526 47 512 120 512 120 527 47 527 753 491 829 491 829 508 753 508 636 491 712 491 712 508 636 508 517 491 593 491 593 508 517 508 84 448 125 448 125 463 84 463 221 448 238 448 238 462 221 462 682 444 869 444 869 461 682 461 561 444 667 444 667 461 561 461 489 445 545 445 545 459 489 459 183 437 209 437 209 459 183 459 52 429 73 437 64 464 42 456 222 430 278 430 278 445 222 445 86 430 145 430 145 445 86 445 505 382 617 381 617 398 505 399 701 380 758 380 758 398 701 398 307 371 365 371 365 386 307 386 90 371 168 371 168 386 90 386 686 334 821 334 821 352 686 352 496 333 659 333 659 350 496 350 207 314 245 314 245 333 207 333 497 287 642 287 642 304 497 304 670 286 804 286 804 304 670 304 668 239 817 239 817 257 668 257 495 239 644 239 644 257 495 257 668 193 816 193 816 209 668 209 496 192 644 192 644 208 496 208 668 144 816 144 816 161 668 161 497 144 646 144 646 161 497 161 488 102 546 102 546 121 488 121 845 21 900 21 900 43 845 43 25 18 702 18 702 39 25 39 896 10 997 14 996 46 895 42 +../../inference/ch_det_data_50/all-sum-510/00152568.jpg 2 250 285 252 285 281 2 279 195 231 255 231 255 241 195 241 198 158 282 164 277 230 193 224 177 148 251 148 251 161 177 161 +../../inference/ch_det_data_50/all-sum-510/00155628.jpg 147 898 506 901 506 925 147 922 519 892 562 894 561 912 518 910 59 884 83 884 83 895 59 895 148 877 505 881 505 902 148 897 523 833 641 837 640 858 522 854 68 832 187 834 187 855 68 853 245 554 468 554 468 570 245 570 307 506 405 508 405 526 307 523 243 481 460 483 460 504 243 502 250 420 460 422 460 454 250 452 193 377 518 379 518 410 193 408 473 194 625 194 625 212 473 212 70 127 643 129 643 163 70 161 478 39 599 35 602 101 481 105 67 23 136 14 140 44 71 54 +../../inference/ch_det_data_50/all-sum-510/00173364.jpg 7 176 59 176 59 201 7 201 135 118 196 118 196 135 135 135 38 75 87 75 87 105 38 105 249 19 313 19 313 38 249 38 19 15 105 15 105 40 19 40 +../../inference/ch_det_data_50/all-sum-510/00175503.jpg 39 256 503 252 504 362 40 366 49 198 351 175 357 253 55 276 +../../inference/ch_det_data_50/all-sum-510/00193218.jpg 282 373 411 373 411 389 282 389 170 373 223 373 223 390 170 390 108 373 162 373 162 390 108 390 276 357 358 357 358 371 276 371 169 357 222 357 222 371 169 371 106 356 175 356 175 373 106 373 408 356 493 356 493 370 408 370 24 185 64 185 64 203 24 203 500 184 558 184 558 201 500 201 379 185 421 183 422 200 380 202 283 184 311 184 311 202 283 202 173 185 197 185 197 201 173 201 498 163 544 163 544 177 498 177 379 162 412 162 412 177 379 177 261 161 303 161 303 178 261 178 174 161 231 161 231 178 174 178 24 161 80 161 80 178 24 178 385 139 489 139 489 155 385 155 26 137 133 137 133 153 26 153 442 115 538 117 538 134 442 132 345 117 406 117 406 131 345 131 259 117 303 117 303 131 259 131 28 112 229 114 229 132 28 130 130 90 395 93 395 110 130 107 560 81 585 81 585 109 560 109 +../../inference/ch_det_data_50/all-sum-510/00195033.jpg 221 302 240 302 240 309 221 309 487 262 534 264 533 282 486 280 125 249 194 249 194 285 125 285 336 248 364 248 364 268 336 268 317 221 381 223 381 240 317 238 431 224 450 224 450 236 431 236 360 202 539 202 539 218 360 218 87 199 148 201 148 218 87 216 371 181 450 181 450 195 371 195 327 180 354 180 354 194 327 194 94 178 241 178 241 195 94 195 431 159 559 159 559 175 431 175 128 148 289 149 289 166 128 165 35 145 75 148 74 163 34 160 487 146 501 146 501 153 487 153 100 143 122 143 122 154 100 154 370 127 505 126 505 140 370 141 98 125 194 125 194 139 98 139 320 125 338 125 338 136 320 136 35 121 78 121 78 135 35 135 322 104 338 104 338 116 322 116 371 101 503 101 503 117 371 117 348 103 362 103 362 115 348 115 37 101 81 101 81 114 37 114 97 98 207 99 207 116 97 115 305 89 317 89 317 97 305 97 346 86 364 86 364 97 346 97 319 85 342 85 342 100 319 100 357 82 515 80 515 96 357 98 40 81 90 81 90 94 40 94 92 77 242 78 242 95 92 94 312 65 394 65 394 79 312 79 240 64 290 64 290 78 240 78 183 52 222 52 222 66 183 66 468 47 547 47 547 61 468 61 422 34 438 34 438 55 422 55 464 29 551 29 551 43 464 43 206 19 330 21 330 42 206 40 +../../inference/ch_det_data_50/all-sum-510/00208502.jpg 556 535 630 535 630 569 556 569 204 537 284 537 284 552 204 552 142 512 191 512 191 526 142 526 248 511 309 511 309 525 248 525 41 499 118 499 118 520 41 520 465 490 558 490 558 510 465 510 666 489 680 493 677 505 662 501 724 490 739 490 739 503 724 503 40 450 118 448 118 469 40 471 173 448 237 448 237 465 173 465 93 403 121 403 121 424 93 424 38 403 63 403 63 424 38 424 214 392 232 405 220 422 203 409 39 357 58 357 58 375 39 375 92 355 121 355 121 375 92 375 187 339 248 337 249 363 188 365 458 319 551 317 551 338 458 340 457 271 553 271 553 292 457 292 562 271 737 267 737 288 562 292 516 225 548 225 548 245 516 245 620 185 675 185 675 202 620 202 456 130 550 128 550 149 456 151 571 104 789 98 789 121 571 127 121 46 291 46 291 99 121 99 536 36 710 36 710 92 536 92 +../../inference/ch_det_data_50/all-sum-510/00224225.jpg 135 426 157 426 157 449 135 449 199 402 480 408 479 461 198 455 200 225 474 225 474 394 200 394 130 264 174 264 174 281 130 281 343 205 458 205 458 232 343 232 197 186 349 194 346 242 194 234 7 41 160 39 161 115 8 117 +../../inference/ch_det_data_50/all-sum-510/00227746.jpg 142 230 210 230 210 240 142 240 71 230 130 230 130 240 71 240 215 228 386 228 386 240 215 240 290 208 347 208 347 224 290 224 142 179 165 181 162 209 139 208 172 179 250 179 250 195 172 195 171 152 347 152 347 167 171 167 143 110 279 112 279 135 143 132 202 53 387 53 387 69 202 69 141 47 193 47 193 64 141 64 +../../inference/ch_det_data_50/all-sum-510/00229605.jpg 742 528 882 528 882 545 742 545 232 497 590 496 590 524 232 525 5 496 229 496 229 524 5 524 733 494 884 497 884 522 733 519 605 493 718 488 719 517 606 522 2 242 865 227 866 291 3 305 477 26 884 26 884 77 477 77 +../../inference/ch_det_data_50/all-sum-510/00233011.jpg 61 225 293 225 293 243 61 243 11 218 43 218 43 252 11 252 60 177 120 177 120 196 60 196 11 169 44 169 44 204 11 204 59 127 149 129 149 148 59 146 11 123 45 123 45 156 11 156 124 86 239 86 239 104 124 104 147 49 218 49 218 67 147 67 257 44 354 47 353 71 256 68 8 47 54 47 54 69 8 69 275 10 346 10 346 32 275 32 26 9 75 9 75 32 26 32 +../../inference/ch_det_data_50/all-sum-510/00233625.jpg 370 395 635 397 635 445 370 443 67 210 935 204 936 325 68 331 +../../inference/ch_det_data_50/all-sum-510/00233634.jpg 213 637 264 637 264 706 213 706 522 634 572 634 572 697 522 697 641 522 684 522 684 570 641 570 95 514 155 514 155 592 95 592 754 394 762 394 762 403 754 403 677 362 730 360 733 432 679 433 53 360 109 360 109 436 53 436 77 207 157 207 157 282 77 282 642 204 695 204 695 274 642 274 208 88 262 85 266 165 212 168 362 47 428 44 432 117 366 120 +../../inference/ch_det_data_50/all-sum-510/00234400.jpg 156 419 739 419 739 439 156 439 157 393 653 393 653 412 157 412 38 390 129 390 129 413 38 413 156 339 307 342 307 365 156 362 36 342 125 342 125 363 36 363 519 293 705 293 705 316 519 316 393 290 485 288 485 316 393 318 156 291 271 291 271 315 156 315 35 291 127 291 127 315 35 315 155 242 360 242 360 269 155 269 34 242 83 242 83 270 34 270 27 150 159 150 159 177 27 177 280 96 507 96 507 113 280 113 313 44 477 47 476 90 312 87 516 50 664 52 664 68 516 67 485 17 708 15 708 45 485 47 +../../inference/ch_det_data_50/all-sum-510/00234883.jpg 64 122 318 117 319 193 65 197 71 118 122 118 122 132 71 132 381 62 506 61 506 75 381 76 57 26 368 26 368 116 57 116 385 26 503 23 503 47 385 50 +../../inference/ch_det_data_50/all-sum-510/test_add_0.jpg 311 521 391 521 391 534 311 534 277 499 426 499 426 516 277 516 259 445 438 445 438 461 259 461 210 426 487 426 487 443 210 443 244 385 460 385 460 411 244 411 220 327 476 327 476 373 220 373 205 204 494 208 493 279 204 275 264 163 423 165 423 198 264 196 15 17 203 15 203 45 15 47 +../../inference/ch_det_data_50/all-sum-510/test_add_1.png +../../inference/ch_det_data_50/all-sum-510/test_add_10.png 155 123 187 123 187 174 155 174 160 105 184 105 184 131 160 131 116 45 155 44 158 176 119 176 63 30 102 31 99 172 60 171 +../../inference/ch_det_data_50/all-sum-510/test_add_11.jpg 1388 755 1486 755 1486 794 1388 794 1011 752 1210 752 1210 802 1011 802 681 752 879 752 879 801 681 801 355 750 568 745 570 796 356 801 76 748 266 743 268 796 78 801 600 645 1155 645 1155 706 600 706 600 562 1151 553 1151 614 600 622 596 478 1070 470 1070 529 596 537 595 390 1095 385 1095 444 595 448 600 303 1061 303 1061 362 600 362 353 180 1521 180 1521 265 353 265 59 40 261 40 261 91 59 91 1303 39 1495 39 1495 90 1303 90 971 37 1173 32 1175 83 973 88 668 37 864 32 866 83 670 88 361 32 561 32 561 88 361 88 +../../inference/ch_det_data_50/all-sum-510/test_add_12.jpg 9 590 140 592 140 615 9 613 107 520 908 524 908 571 107 566 632 448 905 445 905 481 632 484 110 445 468 447 468 487 110 485 580 303 682 301 683 351 581 353 368 257 568 262 565 361 364 355 61 83 856 85 856 164 61 162 +../../inference/ch_det_data_50/all-sum-510/test_add_13.jpg 68 94 117 97 116 115 67 112 +../../inference/ch_det_data_50/all-sum-510/test_add_14.jpg 28 94 238 92 238 130 28 132 27 50 241 48 241 88 27 90 +../../inference/ch_det_data_50/all-sum-510/test_add_15.jpg 140 251 354 251 354 268 140 268 203 212 407 217 407 234 203 229 104 210 194 212 194 229 104 227 153 155 287 159 287 175 153 172 143 134 307 140 307 157 143 150 106 136 147 136 147 149 106 149 106 101 278 107 277 126 105 119 106 70 247 77 246 97 105 90 106 37 211 40 210 64 105 61 +../../inference/ch_det_data_50/all-sum-510/test_add_16.jpg 380 740 750 740 750 780 380 780 360 700 472 700 472 728 360 728 1550 698 1580 698 1580 750 1550 750 1256 694 1444 694 1444 722 1256 722 1242 659 1452 659 1452 690 1242 690 384 643 672 643 672 682 384 682 1226 623 1474 621 1474 655 1226 657 356 599 582 599 582 631 356 631 1198 587 1496 587 1496 619 1198 619 1164 553 1534 553 1534 585 1164 585 378 549 642 549 642 589 378 589 354 500 520 500 520 540 354 540 772 258 1128 258 1128 303 772 303 372 208 508 208 508 303 372 303 774 208 1092 214 1092 260 774 254 +../../inference/ch_det_data_50/all-sum-510/test_add_17.jpg 319 255 394 257 394 271 319 269 306 236 407 238 407 257 306 255 306 221 413 226 412 243 305 237 93 134 387 140 386 210 92 204 69 92 401 100 401 127 69 118 66 74 225 77 225 95 66 92 64 58 227 60 227 77 64 75 +../../inference/ch_det_data_50/all-sum-510/test_add_18.jpg 153 908 616 914 616 935 153 930 464 786 718 788 718 816 464 813 552 750 666 755 665 792 551 788 117 538 190 538 190 572 117 572 115 472 676 484 675 530 114 518 119 427 670 439 670 471 119 459 119 374 676 379 676 411 119 406 555 261 677 262 677 280 555 279 164 258 336 258 336 275 164 275 342 194 457 196 457 221 342 219 307 172 490 172 490 190 307 190 252 125 540 129 540 171 252 168 345 90 488 92 488 110 345 108 283 40 569 48 567 84 282 76 235 30 268 30 268 64 235 64 +../../inference/ch_det_data_50/all-sum-510/test_add_19.jpg 22 293 44 293 44 304 22 304 62 291 106 291 106 305 62 305 61 279 107 279 107 291 61 291 218 278 247 278 247 292 218 292 176 278 210 278 210 291 176 291 141 275 166 275 166 307 141 307 7 266 20 266 20 278 7 278 219 264 245 264 245 279 219 279 60 263 133 263 133 279 60 279 22 264 49 264 49 279 22 279 218 251 250 251 250 266 218 266 63 251 133 251 133 264 63 264 22 250 45 250 45 265 22 265 7 251 20 251 20 263 7 263 8 240 18 240 18 249 8 249 61 236 115 236 115 252 61 252 23 234 49 237 47 253 21 250 210 235 246 235 246 252 210 252 143 236 166 236 166 252 143 252 493 224 533 224 533 241 493 241 334 224 355 224 355 239 334 239 287 224 315 224 315 239 287 239 61 224 114 224 114 238 61 238 7 226 18 226 18 235 7 235 219 223 250 223 250 237 219 237 141 224 167 221 169 235 143 238 23 223 49 223 49 239 23 239 494 212 526 212 526 225 494 225 418 211 439 211 439 226 418 226 335 211 400 211 400 224 335 224 291 211 322 211 322 224 291 224 220 211 251 211 251 224 220 224 144 212 167 212 167 223 144 223 60 211 115 209 115 222 60 224 24 210 50 210 50 224 24 224 336 197 384 197 384 211 336 211 63 198 89 198 89 209 63 209 492 195 542 195 542 213 492 213 443 201 456 194 464 207 451 215 219 195 257 195 257 213 219 213 177 196 207 196 207 210 177 210 144 197 158 197 158 210 144 210 23 196 44 196 44 212 23 212 416 193 440 193 440 213 416 213 63 185 134 185 134 197 63 197 335 184 400 184 400 197 335 197 455 180 466 191 456 201 444 190 289 187 309 180 315 194 295 202 219 183 256 183 256 197 219 197 140 183 160 183 160 198 140 198 493 182 519 182 519 197 493 197 426 178 441 191 426 204 412 190 32 177 46 189 32 202 19 189 176 180 193 180 193 197 176 197 335 170 402 170 402 186 335 186 491 169 521 169 521 186 491 186 426 163 441 176 426 191 412 179 292 170 315 170 315 186 292 186 219 170 252 170 252 185 219 185 177 171 189 171 189 185 177 185 62 170 127 168 127 182 62 184 454 167 464 177 455 186 445 176 142 169 164 169 164 185 142 185 492 158 525 158 525 172 492 172 399 159 436 159 436 169 399 169 334 157 403 157 403 170 334 170 295 157 327 157 327 171 295 171 219 156 253 156 253 170 219 170 143 156 164 156 164 171 143 171 60 157 127 155 127 169 60 171 491 142 543 142 543 158 491 158 449 143 480 143 480 157 449 157 334 142 441 142 441 157 334 157 294 143 328 143 328 157 294 157 219 143 254 143 254 157 219 157 61 143 105 143 105 156 61 156 142 141 164 141 164 157 142 157 17 150 31 136 45 149 30 162 285 133 293 133 293 141 285 141 177 132 193 132 193 145 177 145 335 130 389 130 389 143 335 143 491 129 528 129 528 143 491 143 449 129 479 129 479 143 449 143 417 130 437 130 437 142 417 142 291 129 323 129 323 143 291 143 217 130 256 128 257 143 218 145 61 129 97 129 97 143 61 143 143 128 161 128 161 145 143 145 29 123 45 132 34 149 18 139 492 117 537 117 537 130 492 130 335 117 389 117 389 130 335 130 218 118 256 118 256 128 218 128 450 116 480 116 480 130 450 130 417 116 440 116 440 131 417 131 177 116 210 116 210 130 177 130 143 116 164 116 164 131 143 131 60 115 90 115 90 132 60 132 17 121 32 110 45 124 29 136 490 105 527 105 527 115 490 115 448 105 479 105 479 115 448 115 419 106 436 106 436 114 419 114 292 105 321 105 321 116 292 116 218 105 244 105 244 115 218 115 175 105 205 105 205 115 175 115 143 105 163 105 163 116 143 116 334 104 373 104 373 115 334 115 61 104 88 104 88 115 61 115 483 89 523 89 523 99 483 99 330 87 381 87 381 100 330 100 274 87 336 87 336 100 274 100 213 87 248 87 248 100 213 100 5 85 103 85 103 101 5 101 414 64 464 64 464 78 414 78 287 64 335 64 335 78 287 78 155 62 208 62 208 79 155 79 414 47 525 48 525 64 414 63 287 48 377 48 377 64 287 64 157 48 270 48 270 63 157 63 415 34 483 34 483 48 415 48 287 33 338 33 338 50 287 50 26 34 45 34 45 52 26 52 155 32 207 32 207 49 155 49 55 32 115 31 116 51 56 53 411 2 529 2 529 19 411 19 144 2 346 0 346 17 144 19 +../../inference/ch_det_data_50/all-sum-510/test_add_2.jpg 251 404 535 404 535 430 251 430 302 339 483 339 483 385 302 385 302 303 482 303 482 326 302 326 573 217 693 217 693 240 573 240 331 216 455 214 455 240 331 242 108 212 182 214 181 244 107 242 313 98 672 99 672 121 313 120 311 60 585 61 585 87 311 86 +../../inference/ch_det_data_50/all-sum-510/test_add_20.jpg 30 345 607 345 607 372 30 372 216 292 512 292 512 323 216 323 472 270 527 270 527 287 472 287 216 266 292 266 292 287 216 287 218 238 486 238 486 265 218 265 220 215 305 215 305 236 220 236 399 190 419 190 419 207 399 207 221 185 343 185 343 209 221 209 220 160 289 160 289 182 220 182 374 120 477 122 477 147 374 145 221 122 367 120 367 145 221 147 217 80 354 82 354 117 217 115 439 33 607 33 607 60 439 60 67 15 400 15 400 46 67 46 +../../inference/ch_det_data_50/all-sum-510/test_add_3.jpg 168 326 339 324 339 341 168 343 169 286 309 288 309 314 169 312 169 219 324 219 324 235 169 235 339 219 451 216 451 232 339 235 168 200 373 200 373 216 168 216 168 180 418 180 418 197 168 197 169 147 417 147 417 165 169 165 170 117 419 117 419 141 170 141 325 62 480 62 480 93 325 93 170 62 310 59 311 91 171 94 +../../inference/ch_det_data_50/all-sum-510/test_add_4.png +../../inference/ch_det_data_50/all-sum-510/test_add_5.png 47 162 109 162 109 176 47 176 51 119 170 119 170 136 51 136 49 100 166 100 166 119 49 119 51 83 166 83 166 102 51 102 50 66 169 66 169 85 50 85 49 47 149 46 149 68 49 69 5 9 81 9 81 43 5 43 +../../inference/ch_det_data_50/all-sum-510/test_add_6.jpg 122 222 220 226 219 253 121 249 160 176 185 180 182 200 157 196 +../../inference/ch_det_data_50/all-sum-510/test_add_7.jpg 47 937 175 933 176 964 48 967 224 870 632 873 632 955 224 952 53 743 640 743 640 793 53 793 148 673 546 676 546 723 148 720 71 502 636 502 636 604 71 604 54 264 660 274 657 446 51 436 59 173 534 173 534 241 59 241 502 173 646 173 646 239 502 239 +../../inference/ch_det_data_50/all-sum-510/test_add_8.jpg 249 584 455 578 456 608 250 614 106 531 458 524 458 561 107 568 334 492 385 492 385 509 334 509 26 306 356 296 357 321 27 331 21 258 447 250 447 275 21 283 77 208 447 204 447 226 77 230 158 20 322 28 319 82 155 74 +../../inference/ch_det_data_50/all-sum-510/test_add_9.png 264 684 486 684 486 697 264 697 194 666 556 666 556 682 194 682 152 595 600 595 600 608 152 608 211 577 542 577 542 590 211 590 131 558 616 558 616 571 131 571 84 540 665 540 665 553 84 553 95 521 654 521 654 536 95 536 361 448 390 448 390 461 361 461 236 375 515 375 515 391 236 391 174 353 575 353 575 369 174 369 342 279 409 281 409 298 342 296 254 203 493 203 493 220 254 220 diff --git a/test_tipc/results/cpp_ppocr_det_mobile_results_fp32.txt b/test_tipc/results/cpp_ppocr_det_mobile_results_fp32.txt new file mode 100644 index 0000000..fb33ce1 --- /dev/null +++ b/test_tipc/results/cpp_ppocr_det_mobile_results_fp32.txt @@ -0,0 +1,50 @@ +../../inference/ch_det_data_50/all-sum-510/00008790.jpg 208 404 282 404 282 421 208 421 58 396 107 396 107 413 58 413 197 387 296 387 296 403 197 403 161 389 174 389 174 402 161 402 34 378 134 378 134 394 34 394 323 377 329 377 329 382 323 382 199 370 292 370 292 383 199 383 216 309 274 309 274 325 216 325 161 304 173 304 173 315 161 315 370 301 437 301 437 317 370 317 30 301 135 300 135 316 30 317 221 291 270 291 270 308 221 308 58 224 106 224 106 238 58 238 216 222 274 222 274 239 216 239 161 217 174 217 174 229 161 229 33 205 133 205 133 221 33 221 221 204 270 204 270 221 221 221 73 145 385 145 385 162 73 162 52 119 119 119 119 135 52 135 72 50 296 50 296 66 72 66 54 15 118 15 118 32 54 32 +../../inference/ch_det_data_50/all-sum-510/00018946.jpg 439 327 476 327 476 341 439 341 85 284 142 284 142 308 85 308 300 278 380 278 380 299 300 299 195 262 287 275 284 299 192 286 196 196 454 218 452 244 194 222 343 182 376 182 376 193 343 193 198 162 341 169 340 195 197 188 176 130 381 145 380 165 175 150 176 100 417 118 415 148 174 130 +../../inference/ch_det_data_50/all-sum-510/00034387.jpg 263 459 741 459 741 485 263 485 346 415 421 415 421 444 346 444 544 418 568 418 568 442 544 442 684 415 712 415 712 444 684 444 173 413 228 413 228 444 173 444 872 412 910 412 910 447 872 447 55 415 76 415 76 443 55 443 855 371 927 371 927 401 855 401 347 371 420 371 420 400 347 400 672 370 725 370 725 402 672 402 537 371 571 371 571 401 537 401 136 364 230 367 229 403 135 400 55 370 76 370 76 399 55 399 856 328 927 328 927 358 856 358 350 328 420 328 420 358 350 358 672 326 725 326 725 358 672 358 539 327 571 327 571 359 539 359 170 326 229 323 231 357 171 359 56 328 76 328 76 358 56 358 297 326 316 326 316 334 297 334 854 284 927 284 927 314 854 314 672 284 725 284 725 315 672 315 344 284 431 282 432 315 345 317 537 283 570 283 570 314 537 314 170 281 228 281 228 315 170 315 55 285 75 285 75 314 55 314 856 241 927 241 927 270 856 270 346 240 464 240 464 271 346 271 154 241 228 241 228 271 154 271 672 240 726 240 726 271 672 271 530 240 573 240 573 272 530 272 55 241 76 241 76 270 55 270 854 196 927 198 926 228 853 225 672 197 728 197 728 228 672 228 342 199 439 194 441 224 344 230 175 196 229 196 229 226 175 226 55 199 75 199 75 228 55 228 526 193 578 193 578 228 526 228 347 154 420 154 420 182 347 182 853 153 927 153 927 181 853 181 175 153 228 153 228 184 175 184 668 152 725 152 725 182 668 182 536 153 572 153 572 183 536 183 55 155 76 155 76 183 55 183 347 109 420 109 420 138 347 138 172 109 229 109 229 140 172 140 544 111 565 111 565 138 544 138 51 110 77 110 77 140 51 140 639 105 730 105 730 141 639 141 815 101 929 109 927 141 813 133 812 65 953 65 953 93 812 93 305 64 447 66 447 94 305 92 671 65 725 65 725 95 671 95 173 64 229 66 228 96 172 94 37 64 91 66 90 98 36 96 527 63 581 63 581 95 527 95 333 18 671 18 671 45 333 45 +../../inference/ch_det_data_50/all-sum-510/00037951.jpg 432 973 552 977 552 994 432 991 431 931 554 931 554 970 431 970 29 520 101 520 101 546 29 546 29 441 146 441 146 465 29 465 233 333 328 331 328 356 233 358 121 250 439 250 439 287 121 287 180 205 380 205 380 229 180 229 257 103 323 121 305 184 239 165 35 57 147 57 147 82 35 82 +../../inference/ch_det_data_50/all-sum-510/00044782.jpg 222 214 247 214 247 230 222 230 162 214 183 214 183 231 162 231 122 190 216 190 216 203 122 203 90 82 252 82 252 100 90 100 70 61 279 61 279 78 70 78 103 14 244 14 244 46 103 46 +../../inference/ch_det_data_50/all-sum-510/00067516.jpg 139 806 596 807 596 824 139 823 46 782 699 782 699 800 46 800 577 749 669 749 669 766 577 766 353 748 397 748 397 769 353 769 220 749 261 749 261 767 220 767 475 748 502 748 502 769 475 769 68 746 134 749 133 766 67 763 574 680 670 680 670 700 574 700 474 680 519 680 519 701 474 701 352 680 397 680 397 701 352 701 68 679 134 682 133 700 67 697 219 678 245 681 242 702 216 698 575 614 669 614 669 633 575 633 68 611 134 614 133 633 67 630 474 613 501 613 501 633 474 633 353 613 379 613 379 634 353 634 219 612 245 612 245 633 219 633 576 546 669 546 669 566 576 566 474 545 519 545 519 566 474 566 351 544 381 544 381 567 351 567 219 545 245 545 245 566 219 566 67 541 134 544 133 565 66 562 67 477 134 480 133 501 66 498 584 479 666 479 666 499 584 499 474 478 519 478 519 500 474 500 352 478 397 478 397 500 352 500 218 477 246 477 246 502 218 502 579 424 666 427 665 451 578 448 345 428 411 428 411 449 345 449 66 425 151 427 151 451 66 449 473 427 515 427 515 450 473 450 218 427 259 427 259 450 218 450 282 396 479 397 479 420 282 419 83 316 667 316 667 335 83 335 64 277 666 277 666 292 64 292 456 209 585 209 585 226 456 226 311 208 373 208 373 227 311 227 163 208 227 208 227 227 163 227 504 150 541 150 541 168 504 168 264 47 485 47 485 69 264 69 +../../inference/ch_det_data_50/all-sum-510/00088568.jpg 57 443 119 443 119 456 57 456 309 413 744 413 744 430 309 430 309 375 737 375 737 392 309 392 415 337 559 337 559 351 415 351 307 322 674 321 674 338 307 339 275 292 349 294 349 313 275 311 52 285 210 285 210 301 52 301 273 262 420 262 420 279 273 279 55 262 249 262 249 279 55 279 669 247 697 247 697 262 669 262 601 247 629 247 629 262 601 262 531 247 559 247 559 262 531 262 461 247 489 247 489 262 461 262 277 247 310 247 310 261 277 261 55 240 142 240 142 254 55 254 276 230 400 230 400 246 276 246 741 227 749 237 741 246 732 237 665 230 701 230 701 245 665 245 598 230 631 230 631 245 598 245 527 230 563 230 563 245 527 245 458 230 493 230 493 245 458 245 52 213 212 215 212 233 52 231 732 214 747 214 747 227 732 227 662 212 706 212 706 230 662 230 594 213 638 213 638 227 594 227 522 213 570 213 570 227 522 227 453 213 497 213 497 227 453 227 278 213 352 213 352 227 278 227 734 198 748 198 748 210 734 210 667 196 702 196 702 210 667 210 599 196 633 196 633 211 599 211 527 196 564 196 564 210 527 210 459 196 493 196 493 210 459 210 276 194 418 195 418 212 276 211 54 190 241 190 241 207 54 207 664 179 705 179 705 194 664 194 278 178 352 180 352 195 278 193 733 179 747 179 747 194 733 194 596 178 635 178 635 193 596 193 523 177 567 177 567 195 523 195 456 178 495 178 495 193 456 193 55 170 142 170 142 184 55 184 733 164 748 164 748 176 733 176 664 162 705 162 705 176 664 176 597 162 635 162 635 176 597 176 525 162 566 162 566 176 525 176 456 162 494 162 494 176 456 176 277 160 399 160 399 176 277 176 54 146 149 146 149 161 54 161 452 145 497 145 497 160 452 160 729 144 748 144 748 162 729 162 662 143 706 143 706 161 662 161 595 144 636 144 636 159 595 159 521 143 566 141 567 159 522 161 277 143 310 143 310 159 277 159 275 120 430 120 430 140 275 140 50 119 234 120 234 140 50 139 402 90 703 90 703 107 402 107 46 78 282 78 282 98 46 98 324 67 745 68 745 86 324 85 666 47 743 47 743 64 666 64 295 47 435 47 435 63 295 63 64 30 232 27 233 65 65 68 +../../inference/ch_det_data_50/all-sum-510/00091741.jpg 46 335 87 335 87 360 46 360 98 209 258 209 258 232 98 232 101 189 258 190 258 206 101 205 87 99 268 97 269 184 88 186 92 45 266 53 263 117 89 109 89 10 258 12 258 38 89 36 +../../inference/ch_det_data_50/all-sum-510/00105313.jpg 289 261 407 261 407 277 289 277 152 260 265 260 265 276 152 276 10 257 74 259 74 276 10 274 32 230 134 230 134 245 32 245 34 215 218 215 218 228 34 228 32 199 148 199 148 214 32 214 31 181 217 182 217 199 31 198 34 169 107 169 107 182 34 182 34 153 126 153 126 166 34 166 33 136 144 137 144 150 33 149 34 122 177 122 177 135 34 135 32 104 178 104 178 120 32 120 32 91 102 91 102 104 32 104 33 75 121 75 121 88 33 88 32 60 121 60 121 73 32 73 34 44 121 44 121 57 34 57 31 28 144 28 144 43 31 43 177 20 415 15 416 51 178 56 24 10 152 10 152 26 24 26 +../../inference/ch_det_data_50/all-sum-510/00134770.jpg 386 645 457 645 457 658 386 658 406 618 486 617 486 635 406 636 111 533 272 530 272 550 111 553 110 501 445 496 445 516 110 521 110 469 445 465 445 485 110 489 110 438 446 433 446 453 110 458 109 407 445 403 445 423 109 427 151 375 443 372 443 392 151 395 183 336 371 334 371 358 183 360 224 307 272 308 272 318 224 317 73 96 517 101 516 220 72 215 +../../inference/ch_det_data_50/all-sum-510/00145943.jpg 390 243 751 274 735 454 375 423 88 90 302 90 302 121 88 121 43 40 329 37 329 78 43 81 +../../inference/ch_det_data_50/all-sum-510/00147605.jpg 514 605 786 604 786 629 514 630 116 521 226 521 226 561 116 561 252 522 309 522 309 558 252 558 713 500 902 503 902 539 713 536 254 501 296 501 296 519 254 519 345 479 475 479 475 517 345 517 251 483 296 483 296 501 251 501 350 456 447 456 447 471 350 471 143 442 203 442 203 469 143 469 727 370 880 370 880 422 727 422 526 369 684 369 684 421 526 421 140 367 490 367 490 423 140 423 742 313 872 313 872 338 742 338 798 155 888 155 888 192 798 192 272 140 457 140 457 161 272 161 737 114 895 118 894 158 736 155 107 110 206 110 206 131 107 131 268 92 464 94 464 134 268 131 +../../inference/ch_det_data_50/all-sum-510/00150341.jpg 99 643 300 643 300 664 99 664 113 615 289 615 289 633 113 633 82 591 320 590 320 611 82 612 30 563 315 561 315 582 30 584 30 513 169 513 169 531 30 531 32 488 111 488 111 506 32 506 357 458 465 461 464 486 356 483 26 458 271 459 271 483 26 482 338 438 423 442 422 461 337 457 64 437 145 437 145 455 64 455 205 414 293 414 293 436 205 436 318 407 442 411 441 439 317 435 42 404 176 407 176 435 42 432 28 381 137 381 137 405 28 405 +../../inference/ch_det_data_50/all-sum-510/00150669.jpg 647 698 683 698 683 718 647 718 515 684 551 684 551 721 515 721 650 687 680 687 680 702 650 702 920 673 938 673 938 686 920 686 518 670 548 670 548 690 518 690 785 670 808 670 808 688 785 688 590 670 608 670 608 688 590 688 732 665 745 679 732 692 718 679 652 668 680 668 680 689 652 689 271 665 423 665 423 690 271 690 130 664 205 664 205 690 130 690 44 664 111 664 111 689 44 689 781 628 812 628 812 663 781 663 643 626 687 626 687 666 643 666 514 627 550 627 550 665 514 665 654 617 673 617 673 629 654 629 858 617 868 617 868 628 858 628 727 617 736 617 736 628 727 628 920 614 940 614 940 631 920 631 785 614 807 614 807 631 785 631 371 603 421 603 421 620 371 620 83 600 216 603 216 624 83 620 46 602 72 602 72 624 46 624 780 569 817 573 813 610 776 606 922 559 936 559 936 575 922 575 856 559 869 559 869 575 856 575 61 552 411 552 411 569 61 569 61 531 117 533 117 547 61 545 859 527 868 527 868 539 859 539 923 525 936 525 936 542 923 542 787 524 807 524 807 540 787 540 526 526 536 526 536 536 526 536 261 511 396 511 396 528 261 528 120 512 246 512 246 526 120 526 47 512 120 512 120 527 47 527 753 491 829 491 829 508 753 508 636 491 712 491 712 508 636 508 517 491 593 491 593 508 517 508 84 448 125 448 125 463 84 463 221 448 238 448 238 462 221 462 682 444 869 444 869 461 682 461 561 444 667 444 667 461 561 461 489 445 545 445 545 459 489 459 183 437 209 437 209 459 183 459 52 429 73 437 64 464 42 456 222 430 278 430 278 445 222 445 86 430 145 430 145 445 86 445 505 382 617 381 617 398 505 399 701 380 758 380 758 398 701 398 307 371 365 371 365 386 307 386 90 371 168 371 168 386 90 386 686 334 821 334 821 352 686 352 496 333 659 333 659 350 496 350 207 314 245 314 245 333 207 333 497 287 642 287 642 304 497 304 670 286 804 286 804 304 670 304 668 239 817 239 817 257 668 257 495 239 644 239 644 257 495 257 668 193 816 193 816 209 668 209 496 193 644 193 644 209 496 209 668 144 816 144 816 161 668 161 497 144 646 144 646 161 497 161 488 102 546 102 546 121 488 121 845 21 900 21 900 43 845 43 25 18 702 18 702 39 25 39 896 10 997 14 996 46 895 42 +../../inference/ch_det_data_50/all-sum-510/00152568.jpg 2 250 285 252 285 281 2 279 195 231 255 231 255 241 195 241 198 158 282 164 277 230 193 224 177 148 251 148 251 161 177 161 +../../inference/ch_det_data_50/all-sum-510/00155628.jpg 147 898 506 901 506 925 147 922 519 892 562 894 561 912 518 910 59 884 83 884 83 895 59 895 148 877 505 881 505 902 148 897 523 833 641 837 640 858 522 854 68 832 187 834 187 855 68 853 245 554 468 554 468 570 245 570 307 506 405 508 405 526 307 523 243 481 460 483 460 504 243 502 250 420 460 422 460 454 250 452 193 377 518 379 518 410 193 408 473 194 625 194 625 212 473 212 70 127 643 129 643 163 70 161 478 39 599 35 602 101 481 105 67 23 136 14 140 44 71 54 +../../inference/ch_det_data_50/all-sum-510/00173364.jpg 7 176 58 176 58 201 7 201 135 118 196 118 196 135 135 135 38 75 87 75 87 105 38 105 249 19 313 19 313 38 249 38 19 15 105 15 105 40 19 40 +../../inference/ch_det_data_50/all-sum-510/00175503.jpg 39 256 503 252 504 362 40 366 49 198 351 175 357 253 55 276 +../../inference/ch_det_data_50/all-sum-510/00193218.jpg 282 373 411 373 411 389 282 389 170 373 223 373 223 390 170 390 108 373 162 373 162 390 108 390 276 357 358 357 358 371 276 371 169 357 222 357 222 371 169 371 106 356 175 356 175 373 106 373 408 356 493 356 493 370 408 370 24 185 64 185 64 203 24 203 500 184 558 184 558 201 500 201 379 185 421 183 422 200 380 202 283 184 311 184 311 202 283 202 173 185 197 185 197 201 173 201 498 163 544 163 544 177 498 177 379 162 412 162 412 177 379 177 261 161 303 161 303 178 261 178 174 161 231 161 231 178 174 178 24 161 80 161 80 178 24 178 385 139 489 139 489 155 385 155 26 137 133 137 133 153 26 153 442 115 538 117 538 134 442 132 345 117 406 117 406 131 345 131 259 117 303 117 303 131 259 131 28 112 229 114 229 132 28 130 130 90 395 93 395 110 130 107 560 81 585 81 585 109 560 109 +../../inference/ch_det_data_50/all-sum-510/00195033.jpg 221 302 240 302 240 309 221 309 487 262 534 264 533 282 486 280 125 249 194 249 194 285 125 285 336 248 364 248 364 268 336 268 317 221 381 223 381 240 317 238 431 224 450 224 450 236 431 236 360 202 539 202 539 218 360 218 87 199 148 201 148 218 87 216 371 181 450 181 450 195 371 195 327 180 354 180 354 194 327 194 94 178 241 178 241 195 94 195 431 159 559 159 559 175 431 175 128 148 289 149 289 166 128 165 35 145 75 148 74 163 34 160 487 146 501 146 501 153 487 153 100 143 122 143 122 154 100 154 370 127 505 126 505 140 370 141 98 125 194 125 194 139 98 139 320 125 338 125 338 136 320 136 35 121 78 121 78 135 35 135 322 104 338 104 338 116 322 116 371 101 503 101 503 117 371 117 348 103 362 103 362 115 348 115 37 101 81 101 81 114 37 114 97 98 207 99 207 116 97 115 305 89 317 89 317 97 305 97 346 86 364 86 364 97 346 97 319 85 342 85 342 100 319 100 357 82 515 80 515 96 357 98 40 81 90 81 90 94 40 94 92 77 242 78 242 95 92 94 312 65 394 65 394 79 312 79 240 64 290 64 290 78 240 78 183 52 222 52 222 66 183 66 468 47 547 47 547 61 468 61 422 34 438 34 438 55 422 55 464 29 551 29 551 43 464 43 206 19 330 21 330 42 206 40 +../../inference/ch_det_data_50/all-sum-510/00208502.jpg 556 535 630 535 630 569 556 569 204 537 284 537 284 552 204 552 142 512 191 512 191 526 142 526 248 511 309 511 309 525 248 525 41 499 118 499 118 520 41 520 465 490 558 490 558 510 465 510 666 489 680 493 677 505 662 501 724 490 739 490 739 503 724 503 40 450 118 448 118 469 40 471 173 448 237 448 237 465 173 465 93 403 121 403 121 424 93 424 38 403 63 403 63 424 38 424 214 392 232 405 220 422 203 409 39 357 58 357 58 375 39 375 92 355 121 355 121 375 92 375 187 339 248 337 249 363 188 365 458 319 551 317 551 338 458 340 457 271 553 271 553 292 457 292 562 271 737 267 737 288 562 292 516 225 548 225 548 245 516 245 620 185 675 185 675 202 620 202 456 130 550 128 550 149 456 151 571 104 789 98 789 121 571 127 121 46 291 46 291 99 121 99 536 36 710 36 710 92 536 92 +../../inference/ch_det_data_50/all-sum-510/00224225.jpg 135 426 157 426 157 449 135 449 199 402 480 408 479 461 198 455 200 225 474 225 474 394 200 394 130 264 174 264 174 281 130 281 343 205 458 205 458 232 343 232 197 186 349 194 346 242 194 234 7 41 160 39 161 115 8 117 +../../inference/ch_det_data_50/all-sum-510/00227746.jpg 142 230 210 230 210 240 142 240 72 230 130 230 130 240 72 240 215 228 386 228 386 240 215 240 290 208 347 208 347 224 290 224 142 179 165 181 162 209 139 208 171 152 347 152 347 167 171 167 143 110 279 112 279 135 143 132 202 53 387 53 387 69 202 69 141 47 193 47 193 64 141 64 +../../inference/ch_det_data_50/all-sum-510/00229605.jpg 742 528 882 528 882 545 742 545 232 497 590 496 590 524 232 525 5 496 229 496 229 524 5 524 734 494 884 497 884 522 734 519 605 493 718 488 719 517 606 522 2 242 865 227 866 291 3 305 477 26 884 26 884 77 477 77 +../../inference/ch_det_data_50/all-sum-510/00233011.jpg 61 225 293 225 293 243 61 243 11 218 43 218 43 252 11 252 60 177 120 177 120 196 60 196 11 169 44 169 44 204 11 204 59 127 149 129 149 148 59 146 11 123 45 123 45 156 11 156 124 87 239 87 239 105 124 105 147 49 218 49 218 67 147 67 257 44 354 47 353 71 256 68 8 47 54 47 54 69 8 69 275 10 346 10 346 32 275 32 26 9 75 9 75 32 26 32 +../../inference/ch_det_data_50/all-sum-510/00233625.jpg 370 395 635 397 635 445 370 443 67 210 935 204 936 325 68 331 +../../inference/ch_det_data_50/all-sum-510/00233634.jpg 213 637 264 637 264 706 213 706 522 634 572 634 572 697 522 697 641 522 684 522 684 570 641 570 95 514 155 514 155 592 95 592 754 394 762 394 762 403 754 403 677 362 730 360 733 432 679 433 53 360 109 360 109 436 53 436 77 207 157 207 157 282 77 282 642 204 695 204 695 274 642 274 208 88 262 85 266 165 212 168 362 47 428 44 432 117 366 120 +../../inference/ch_det_data_50/all-sum-510/00234400.jpg 156 419 739 419 739 439 156 439 157 393 653 393 653 412 157 412 38 390 129 390 129 413 38 413 156 339 307 342 307 365 156 362 36 342 125 342 125 363 36 363 519 293 705 293 705 316 519 316 393 290 485 288 485 316 393 318 156 291 271 291 271 315 156 315 35 291 127 291 127 315 35 315 155 242 360 242 360 269 155 269 34 242 83 242 83 270 34 270 27 150 159 150 159 177 27 177 280 96 507 96 507 113 280 113 313 44 477 47 476 90 312 87 516 50 664 52 664 68 516 67 485 17 708 15 708 45 485 47 +../../inference/ch_det_data_50/all-sum-510/00234883.jpg 64 122 318 117 319 193 65 197 71 118 122 118 122 132 71 132 381 62 506 61 506 75 381 76 54 25 369 23 369 116 54 118 385 26 503 23 503 47 385 50 +../../inference/ch_det_data_50/all-sum-510/test_add_0.jpg 311 521 391 521 391 534 311 534 277 499 426 499 426 516 277 516 259 445 438 445 438 461 259 461 210 426 487 426 487 443 210 443 244 385 460 385 460 411 244 411 220 327 476 327 476 373 220 373 205 204 494 208 493 279 204 275 264 163 423 165 423 198 264 196 15 17 203 15 203 45 15 47 +../../inference/ch_det_data_50/all-sum-510/test_add_1.png +../../inference/ch_det_data_50/all-sum-510/test_add_10.png 155 123 187 123 187 174 155 174 160 105 184 105 184 131 160 131 116 45 155 44 158 176 119 176 63 30 102 31 99 172 60 171 +../../inference/ch_det_data_50/all-sum-510/test_add_11.jpg 1388 755 1486 755 1486 794 1388 794 1011 752 1210 752 1210 802 1011 802 681 752 879 752 879 801 681 801 355 750 568 745 570 796 356 801 76 748 266 743 268 796 78 801 600 645 1155 645 1155 706 600 706 600 562 1151 553 1151 614 600 622 596 478 1070 470 1070 529 596 537 595 390 1095 385 1095 444 595 448 600 303 1061 303 1061 362 600 362 353 180 1521 180 1521 265 353 265 59 40 261 40 261 91 59 91 1303 39 1495 39 1495 90 1303 90 971 37 1173 32 1175 83 973 88 668 37 864 32 866 83 670 88 361 32 561 32 561 88 361 88 +../../inference/ch_det_data_50/all-sum-510/test_add_12.jpg 9 590 140 592 140 615 9 613 107 520 908 524 908 571 107 566 632 448 905 445 905 481 632 484 110 445 468 447 468 487 110 485 580 303 682 301 683 351 581 353 368 257 568 262 565 361 364 355 61 83 856 85 856 164 61 162 +../../inference/ch_det_data_50/all-sum-510/test_add_13.jpg 68 93 118 96 116 116 66 113 +../../inference/ch_det_data_50/all-sum-510/test_add_14.jpg 28 94 238 92 238 130 28 132 27 50 241 48 241 88 27 90 +../../inference/ch_det_data_50/all-sum-510/test_add_15.jpg 140 251 354 251 354 268 140 268 203 212 407 217 407 234 203 229 104 210 194 212 194 229 104 227 153 155 287 159 287 175 153 172 143 134 307 140 307 157 143 150 106 136 147 136 147 149 106 149 106 101 278 107 277 126 105 119 106 70 247 77 246 97 105 90 106 37 211 40 210 64 105 61 +../../inference/ch_det_data_50/all-sum-510/test_add_16.jpg 380 740 750 740 750 780 380 780 360 700 472 700 472 728 360 728 1550 698 1580 698 1580 750 1550 750 1256 694 1444 694 1444 722 1256 722 1242 659 1452 659 1452 690 1242 690 384 643 672 643 672 682 384 682 1226 623 1474 621 1474 655 1226 657 356 599 582 599 582 631 356 631 1198 587 1496 587 1496 619 1198 619 1164 553 1534 553 1534 585 1164 585 378 549 642 549 642 589 378 589 354 500 520 500 520 540 354 540 772 258 1128 258 1128 303 772 303 372 208 508 208 508 303 372 303 774 208 1092 214 1092 260 774 254 +../../inference/ch_det_data_50/all-sum-510/test_add_17.jpg 319 255 394 257 394 271 319 269 306 236 407 238 407 257 306 255 306 221 413 226 412 243 305 237 93 135 387 140 386 209 92 204 69 92 401 100 401 127 69 118 66 74 225 77 225 95 66 92 64 58 227 60 227 77 64 75 +../../inference/ch_det_data_50/all-sum-510/test_add_18.jpg 153 908 616 914 616 935 153 930 464 786 718 788 718 816 464 813 552 750 666 755 665 792 551 788 117 538 190 538 190 572 117 572 115 472 676 484 675 530 114 518 119 427 670 439 670 471 119 459 119 374 676 380 676 411 119 405 555 261 677 262 677 280 555 279 164 258 336 258 336 275 164 275 342 194 457 196 457 221 342 219 307 172 490 172 490 190 307 190 252 125 540 129 540 171 252 168 345 90 488 92 488 110 345 108 283 40 569 48 567 84 282 76 235 30 268 30 268 64 235 64 +../../inference/ch_det_data_50/all-sum-510/test_add_19.jpg 22 293 44 293 44 304 22 304 62 291 106 291 106 305 62 305 61 279 107 279 107 291 61 291 218 278 247 278 247 292 218 292 176 278 210 278 210 291 176 291 141 275 166 275 166 307 141 307 7 266 20 266 20 278 7 278 219 264 245 264 245 279 219 279 60 263 133 263 133 279 60 279 22 264 49 264 49 279 22 279 218 251 250 251 250 266 218 266 63 251 133 251 133 264 63 264 22 250 45 250 45 265 22 265 7 251 20 251 20 263 7 263 8 240 18 240 18 249 8 249 61 236 115 236 115 252 61 252 23 234 49 237 47 253 21 250 210 235 246 235 246 252 210 252 143 236 166 236 166 252 143 252 493 224 533 224 533 241 493 241 334 224 355 224 355 239 334 239 287 224 315 224 315 239 287 239 61 224 114 224 114 238 61 238 7 226 18 226 18 235 7 235 219 223 250 223 250 237 219 237 141 224 167 221 169 235 143 238 23 223 49 223 49 239 23 239 494 212 526 212 526 225 494 225 418 211 439 211 439 226 418 226 335 211 400 211 400 224 335 224 291 211 322 211 322 224 291 224 220 211 251 211 251 224 220 224 144 212 167 212 167 223 144 223 60 211 115 209 115 222 60 224 24 210 50 210 50 224 24 224 336 197 384 197 384 211 336 211 63 198 89 198 89 209 63 209 492 195 542 195 542 213 492 213 219 195 257 195 257 213 219 213 177 196 207 196 207 210 177 210 144 197 158 197 158 210 144 210 23 196 44 196 44 212 23 212 416 193 440 193 440 213 416 213 63 185 134 185 134 197 63 197 335 184 400 184 400 197 335 197 455 180 466 191 456 201 444 190 289 187 309 180 315 194 295 202 219 183 256 183 256 197 219 197 140 183 160 183 160 198 140 198 493 182 519 182 519 197 493 197 426 178 441 191 426 204 412 190 32 177 46 189 32 202 19 189 176 180 193 180 193 197 176 197 335 170 402 170 402 186 335 186 491 169 521 169 521 186 491 186 426 163 441 176 426 191 412 179 292 170 315 170 315 186 292 186 219 170 252 170 252 185 219 185 177 171 189 171 189 185 177 185 62 170 127 168 127 182 62 184 454 167 464 177 455 186 445 176 142 169 164 169 164 185 142 185 492 158 525 158 525 172 492 172 399 159 436 159 436 169 399 169 334 157 403 157 403 170 334 170 295 157 327 157 327 171 295 171 219 156 253 156 253 170 219 170 143 156 164 156 164 171 143 171 60 157 127 155 127 169 60 171 491 142 543 142 543 158 491 158 449 143 480 143 480 157 449 157 334 142 441 142 441 157 334 157 294 143 328 143 328 157 294 157 219 143 254 143 254 157 219 157 61 143 105 143 105 156 61 156 142 141 164 141 164 157 142 157 17 150 31 136 45 149 30 162 285 133 293 133 293 141 285 141 177 132 193 132 193 145 177 145 335 130 389 130 389 143 335 143 491 129 528 129 528 143 491 143 449 129 479 129 479 143 449 143 291 129 323 129 323 143 291 143 217 130 256 128 257 143 218 145 61 129 97 129 97 143 61 143 416 128 439 128 439 143 416 143 143 128 161 128 161 145 143 145 29 123 45 132 34 149 18 139 492 117 537 117 537 130 492 130 335 117 389 117 389 130 335 130 218 118 256 118 256 128 218 128 450 116 480 116 480 130 450 130 417 116 440 116 440 131 417 131 177 116 210 116 210 130 177 130 143 116 164 116 164 131 143 131 60 115 90 115 90 132 60 132 17 121 32 110 45 124 29 136 490 105 527 105 527 115 490 115 448 105 479 105 479 115 448 115 419 106 436 106 436 114 419 114 292 105 321 105 321 116 292 116 218 105 244 105 244 115 218 115 175 105 205 105 205 115 175 115 143 105 163 105 163 116 143 116 334 104 373 104 373 115 334 115 61 104 88 104 88 115 61 115 483 89 523 89 523 99 483 99 330 87 381 87 381 100 330 100 274 87 336 87 336 100 274 100 213 87 248 87 248 100 213 100 5 85 103 85 103 101 5 101 414 64 464 64 464 78 414 78 287 64 335 64 335 78 287 78 155 62 208 62 208 79 155 79 414 47 525 48 525 64 414 63 287 48 377 48 377 64 287 64 157 48 270 48 270 63 157 63 415 34 483 34 483 48 415 48 287 33 338 33 338 50 287 50 26 34 45 34 45 52 26 52 155 32 207 32 207 49 155 49 55 32 115 31 116 51 56 53 411 2 529 2 529 19 411 19 144 2 346 0 346 17 144 19 +../../inference/ch_det_data_50/all-sum-510/test_add_2.jpg 251 404 535 404 535 430 251 430 302 339 483 339 483 385 302 385 302 303 482 303 482 326 302 326 573 217 693 217 693 240 573 240 331 216 455 214 455 240 331 242 108 212 182 214 181 244 107 242 313 98 672 99 672 121 313 120 311 60 585 61 585 87 311 86 +../../inference/ch_det_data_50/all-sum-510/test_add_20.jpg 30 345 607 345 607 372 30 372 216 292 512 292 512 323 216 323 472 270 527 270 527 287 472 287 216 266 292 266 292 287 216 287 218 238 486 238 486 265 218 265 220 215 305 215 305 236 220 236 221 185 343 185 343 209 221 209 220 160 289 160 289 182 220 182 374 120 477 122 477 147 374 145 221 122 367 120 367 145 221 147 217 80 354 82 354 117 217 115 439 33 607 33 607 60 439 60 67 15 400 15 400 46 67 46 +../../inference/ch_det_data_50/all-sum-510/test_add_3.jpg 168 326 339 324 339 341 168 343 169 286 309 288 309 314 169 312 169 219 324 219 324 235 169 235 339 219 451 216 451 232 339 235 168 200 373 200 373 216 168 216 168 180 418 180 418 197 168 197 169 147 417 147 417 165 169 165 170 117 419 117 419 141 170 141 325 62 480 62 480 93 325 93 170 62 310 59 311 91 171 94 +../../inference/ch_det_data_50/all-sum-510/test_add_4.png +../../inference/ch_det_data_50/all-sum-510/test_add_5.png 47 162 109 162 109 176 47 176 51 119 170 119 170 136 51 136 49 100 166 100 166 119 49 119 51 83 166 83 166 102 51 102 50 66 169 66 169 85 50 85 49 47 149 46 149 68 49 69 5 9 81 9 81 43 5 43 +../../inference/ch_det_data_50/all-sum-510/test_add_6.jpg 122 222 220 226 219 253 121 249 160 176 185 180 182 200 157 196 +../../inference/ch_det_data_50/all-sum-510/test_add_7.jpg 47 937 175 933 176 964 48 967 224 870 632 873 632 955 224 952 53 743 640 743 640 793 53 793 148 673 546 676 546 723 148 720 71 502 636 502 636 604 71 604 54 264 660 274 657 446 51 436 59 173 534 173 534 241 59 241 502 173 646 173 646 239 502 239 +../../inference/ch_det_data_50/all-sum-510/test_add_8.jpg 249 584 455 578 456 608 250 614 106 531 458 524 458 561 107 568 334 492 385 492 385 509 334 509 26 306 356 296 357 321 27 331 21 258 447 250 447 275 21 283 77 208 447 204 447 226 77 230 158 20 322 28 319 82 155 74 +../../inference/ch_det_data_50/all-sum-510/test_add_9.png 264 684 486 684 486 697 264 697 194 666 556 666 556 682 194 682 152 595 600 595 600 608 152 608 211 577 543 577 543 590 211 590 131 559 617 558 617 572 131 573 84 540 665 540 665 553 84 553 95 521 654 521 654 536 95 536 361 448 390 448 390 461 361 461 236 375 515 375 515 391 236 391 174 353 575 353 575 369 174 369 342 279 409 281 409 298 342 296 254 203 493 203 493 220 254 220 diff --git a/test_tipc/results/python_ppocr_det_mobile_results_fp16.txt b/test_tipc/results/python_ppocr_det_mobile_results_fp16.txt new file mode 100644 index 0000000..191bdaf --- /dev/null +++ b/test_tipc/results/python_ppocr_det_mobile_results_fp16.txt @@ -0,0 +1,49 @@ +00008790.jpg [[[209, 406], [280, 406], [280, 419], [209, 419]], [[60, 398], [105, 398], [105, 411], [60, 411]], [[198, 389], [291, 389], [291, 402], [198, 402]], [[162, 391], [173, 391], [173, 401], [162, 401]], [[35, 380], [133, 380], [133, 393], [35, 393]], [[199, 371], [292, 371], [292, 384], [199, 384]], [[218, 310], [272, 310], [272, 324], [218, 324]], [[162, 305], [172, 305], [172, 314], [162, 314]], [[371, 302], [436, 302], [436, 316], [371, 316]], [[31, 302], [134, 301], [134, 315], [31, 316]], [[223, 292], [269, 292], [269, 306], [223, 306]], [[60, 225], [104, 225], [104, 236], [60, 236]], [[218, 223], [272, 223], [272, 237], [218, 237]], [[162, 219], [173, 219], [173, 227], [162, 227]], [[33, 207], [131, 207], [131, 220], [33, 220]], [[223, 206], [269, 206], [269, 220], [223, 220]], [[74, 146], [383, 146], [383, 159], [74, 159]], [[54, 120], [117, 120], [117, 134], [54, 134]], [[74, 51], [296, 51], [296, 65], [74, 65]], [[56, 18], [116, 18], [116, 32], [56, 32]]] +00018946.jpg [[[441, 328], [474, 328], [474, 339], [441, 339]], [[86, 284], [141, 286], [140, 307], [85, 305]], [[302, 279], [377, 279], [377, 297], [302, 297]], [[197, 265], [281, 274], [279, 293], [195, 284]], [[198, 197], [452, 219], [450, 242], [196, 220]], [[343, 182], [376, 182], [376, 192], [343, 192]], [[199, 164], [340, 171], [339, 192], [198, 185]], [[177, 101], [415, 118], [413, 145], [175, 128]]] +00034387.jpg [[[265, 460], [740, 460], [740, 484], [265, 484]], [[348, 417], [420, 417], [420, 443], [348, 443]], [[545, 418], [568, 418], [568, 442], [545, 442]], [[685, 417], [710, 417], [710, 443], [685, 443]], [[175, 415], [226, 415], [226, 443], [175, 443]], [[874, 414], [908, 414], [908, 446], [874, 446]], [[56, 417], [74, 417], [74, 442], [56, 442]], [[856, 373], [925, 373], [925, 400], [856, 400]], [[348, 372], [418, 372], [418, 397], [348, 397]], [[674, 372], [723, 372], [723, 401], [674, 401]], [[539, 373], [570, 373], [570, 400], [539, 400]], [[151, 365], [228, 369], [226, 402], [149, 398]], [[56, 372], [74, 372], [74, 397], [56, 397]], [[857, 329], [925, 329], [925, 355], [857, 355]], [[351, 330], [419, 330], [419, 356], [351, 356]], [[674, 328], [723, 328], [723, 356], [674, 356]], [[541, 329], [570, 329], [570, 357], [541, 357]], [[171, 327], [227, 324], [229, 355], [173, 358]], [[57, 330], [74, 330], [74, 356], [57, 356]], [[298, 327], [316, 327], [316, 334], [298, 334]], [[855, 286], [925, 286], [925, 312], [855, 312]], [[674, 286], [723, 286], [723, 313], [674, 313]], [[346, 286], [426, 283], [427, 313], [347, 316]], [[540, 285], [569, 285], [569, 312], [540, 312]], [[172, 282], [226, 282], [226, 313], [172, 313]], [[56, 287], [73, 287], [73, 312], [56, 312]], [[857, 242], [925, 242], [925, 268], [857, 268]], [[348, 242], [460, 242], [460, 268], [348, 268]], [[156, 242], [227, 242], [227, 269], [156, 269]], [[674, 241], [724, 241], [724, 269], [674, 269]], [[531, 241], [572, 241], [572, 270], [531, 270]], [[56, 242], [74, 242], [74, 268], [56, 268]], [[855, 197], [925, 200], [924, 226], [854, 224]], [[674, 198], [726, 198], [726, 226], [674, 226]], [[344, 200], [430, 195], [432, 224], [346, 230]], [[176, 197], [227, 197], [227, 225], [176, 225]], [[56, 200], [73, 200], [73, 226], [56, 226]], [[527, 194], [576, 194], [576, 226], [527, 226]], [[349, 155], [419, 155], [419, 181], [349, 181]], [[854, 154], [925, 154], [925, 180], [854, 180]], [[176, 154], [226, 154], [226, 183], [176, 183]], [[670, 153], [723, 153], [723, 181], [670, 181]], [[538, 154], [571, 154], [571, 182], [538, 182]], [[56, 156], [74, 156], [74, 182], [56, 182]], [[349, 111], [419, 111], [419, 137], [349, 137]], [[174, 111], [227, 111], [227, 139], [174, 139]], [[546, 113], [564, 113], [564, 137], [546, 137]], [[52, 112], [75, 112], [75, 139], [52, 139]], [[639, 108], [727, 105], [728, 138], [640, 141]], [[817, 103], [927, 110], [925, 139], [815, 132]], [[814, 68], [951, 68], [951, 92], [814, 92]], [[307, 66], [446, 68], [446, 93], [306, 90]], [[673, 67], [723, 67], [723, 93], [673, 93]], [[175, 65], [228, 68], [226, 95], [174, 92]], [[39, 65], [90, 68], [88, 97], [37, 94]], [[528, 65], [580, 65], [580, 94], [528, 94]], [[334, 20], [670, 20], [670, 43], [334, 43]]] +00037951.jpg [[[434, 976], [551, 978], [550, 993], [434, 991]], [[433, 932], [553, 932], [553, 969], [433, 969]], [[30, 522], [98, 522], [98, 545], [30, 545]], [[31, 443], [145, 443], [145, 464], [31, 464]], [[234, 335], [326, 332], [327, 354], [235, 356]], [[124, 252], [436, 252], [436, 284], [124, 284]], [[182, 206], [378, 206], [378, 227], [182, 227]], [[258, 106], [320, 123], [304, 181], [242, 163]], [[28, 65], [33, 65], [33, 71], [28, 71]], [[37, 58], [147, 58], [147, 80], [37, 80]]] +00044782.jpg [[[104, 218], [115, 218], [115, 227], [104, 227]], [[223, 216], [246, 216], [246, 228], [223, 228]], [[163, 216], [182, 216], [182, 229], [163, 229]], [[124, 191], [164, 191], [164, 202], [124, 202]], [[91, 84], [251, 84], [251, 98], [91, 98]], [[73, 63], [278, 63], [278, 78], [73, 78]], [[104, 15], [243, 15], [243, 44], [104, 44]]] +00067516.jpg [[[141, 808], [594, 809], [594, 822], [141, 821]], [[49, 784], [696, 784], [696, 798], [49, 798]], [[579, 751], [667, 751], [667, 764], [579, 764]], [[355, 750], [395, 750], [395, 767], [355, 767]], [[221, 751], [260, 751], [260, 765], [221, 765]], [[477, 750], [501, 750], [501, 768], [477, 768]], [[69, 748], [133, 751], [132, 765], [68, 761]], [[576, 682], [668, 682], [668, 699], [576, 699]], [[476, 682], [518, 682], [518, 700], [476, 700]], [[354, 682], [395, 682], [395, 700], [354, 700]], [[69, 681], [133, 684], [132, 699], [68, 695]], [[220, 679], [243, 682], [241, 700], [218, 697]], [[577, 615], [667, 615], [667, 632], [577, 632]], [[68, 612], [134, 615], [133, 632], [67, 629]], [[476, 614], [500, 614], [500, 633], [476, 633]], [[354, 613], [378, 613], [378, 634], [354, 634]], [[219, 612], [245, 612], [245, 633], [219, 633]], [[578, 547], [667, 547], [667, 564], [578, 564]], [[476, 546], [518, 546], [518, 565], [476, 565]], [[353, 545], [379, 545], [379, 566], [353, 566]], [[219, 545], [245, 545], [245, 566], [219, 566]], [[68, 542], [133, 546], [132, 563], [67, 560]], [[68, 478], [133, 482], [132, 499], [67, 496]], [[586, 481], [664, 481], [664, 497], [586, 497]], [[476, 480], [518, 480], [518, 498], [476, 498]], [[354, 480], [395, 480], [395, 498], [354, 498]], [[219, 479], [245, 479], [245, 500], [219, 500]], [[580, 425], [665, 429], [664, 449], [580, 446]], [[346, 429], [410, 429], [410, 447], [346, 447]], [[68, 426], [150, 429], [149, 449], [67, 447]], [[474, 427], [515, 427], [515, 449], [474, 449]], [[218, 427], [259, 427], [259, 449], [218, 449]], [[283, 398], [478, 399], [478, 419], [283, 418]], [[86, 318], [664, 318], [664, 332], [86, 332]], [[65, 279], [665, 279], [665, 292], [65, 292]], [[458, 210], [584, 210], [584, 224], [458, 224]], [[313, 209], [372, 209], [372, 226], [313, 226]], [[164, 209], [225, 209], [225, 226], [164, 226]], [[505, 151], [539, 151], [539, 166], [505, 166]], [[266, 48], [483, 48], [483, 68], [266, 68]]] +00088568.jpg [[[341, 446], [371, 446], [371, 453], [341, 453]], [[58, 445], [117, 445], [117, 455], [58, 455]], [[552, 433], [571, 433], [571, 440], [552, 440]], [[583, 431], [740, 431], [740, 442], [583, 442]], [[311, 415], [743, 415], [743, 428], [311, 428]], [[311, 377], [736, 377], [736, 390], [311, 390]], [[425, 340], [551, 340], [551, 350], [425, 350]], [[287, 324], [294, 332], [289, 337], [281, 330]], [[276, 294], [348, 296], [347, 311], [276, 309]], [[54, 288], [210, 288], [210, 301], [54, 301]], [[275, 265], [421, 265], [421, 278], [275, 278]], [[56, 264], [248, 264], [248, 277], [56, 277]], [[671, 248], [695, 248], [695, 261], [671, 261]], [[602, 248], [628, 248], [628, 261], [602, 261]], [[533, 248], [557, 248], [557, 261], [533, 261]], [[463, 248], [487, 248], [487, 261], [463, 261]], [[278, 248], [309, 248], [309, 260], [278, 260]], [[55, 240], [142, 240], [142, 254], [55, 254]], [[277, 231], [398, 231], [398, 244], [277, 244]], [[741, 228], [749, 237], [742, 245], [733, 236]], [[665, 230], [700, 230], [700, 244], [665, 244]], [[598, 230], [631, 230], [631, 244], [598, 244]], [[528, 230], [562, 230], [562, 244], [528, 244]], [[459, 230], [492, 230], [492, 244], [459, 244]], [[54, 215], [211, 217], [211, 231], [54, 229]], [[739, 211], [749, 221], [740, 229], [731, 220]], [[663, 214], [704, 214], [704, 228], [663, 228]], [[595, 215], [637, 215], [637, 226], [595, 226]], [[524, 215], [568, 215], [568, 226], [524, 226]], [[454, 215], [495, 215], [495, 226], [454, 226]], [[279, 215], [351, 215], [351, 226], [279, 226]], [[736, 199], [747, 199], [747, 208], [736, 208]], [[668, 197], [700, 197], [700, 208], [668, 208]], [[599, 196], [633, 196], [633, 210], [599, 210]], [[529, 197], [562, 197], [562, 208], [529, 208]], [[461, 197], [491, 197], [491, 208], [461, 208]], [[277, 195], [417, 196], [417, 211], [277, 209]], [[55, 192], [239, 192], [239, 205], [55, 205]], [[665, 181], [703, 181], [703, 192], [665, 192]], [[279, 180], [351, 181], [350, 192], [279, 191]], [[734, 180], [747, 180], [747, 193], [734, 193]], [[597, 180], [634, 180], [634, 191], [597, 191]], [[525, 179], [566, 179], [566, 193], [525, 193]], [[458, 180], [493, 180], [493, 191], [458, 191]], [[55, 170], [142, 170], [142, 184], [55, 184]], [[735, 165], [747, 165], [747, 175], [735, 175]], [[665, 163], [703, 163], [703, 175], [665, 175]], [[598, 163], [634, 163], [634, 175], [598, 175]], [[527, 163], [565, 163], [565, 175], [527, 175]], [[458, 163], [492, 163], [492, 175], [458, 175]], [[279, 162], [398, 162], [398, 176], [279, 176]], [[54, 146], [148, 146], [148, 159], [54, 159]], [[453, 147], [495, 147], [495, 158], [453, 158]], [[731, 143], [748, 146], [745, 161], [728, 158]], [[663, 145], [704, 145], [704, 159], [663, 159]], [[596, 146], [635, 146], [635, 157], [596, 157]], [[522, 145], [566, 142], [567, 157], [523, 159]], [[277, 144], [310, 144], [310, 158], [277, 158]], [[276, 121], [428, 121], [428, 139], [276, 139]], [[52, 120], [232, 121], [232, 139], [52, 138]], [[404, 91], [701, 91], [701, 106], [404, 106]], [[48, 79], [280, 79], [280, 97], [48, 97]], [[325, 69], [744, 70], [744, 84], [325, 83]], [[668, 48], [743, 48], [743, 63], [668, 63]], [[297, 48], [433, 48], [433, 62], [297, 62]]] +00091741.jpg [[[47, 336], [83, 336], [83, 358], [47, 358]], [[98, 211], [257, 209], [257, 229], [98, 231]], [[103, 190], [257, 191], [257, 205], [103, 204]], [[89, 101], [266, 99], [267, 181], [90, 184]], [[94, 48], [262, 55], [260, 114], [91, 107]], [[91, 12], [257, 14], [257, 37], [90, 35]]] +00105313.jpg [[[291, 262], [406, 262], [406, 275], [291, 275]], [[153, 262], [264, 262], [264, 274], [153, 274]], [[11, 258], [73, 261], [72, 274], [11, 272]], [[33, 231], [132, 231], [132, 244], [33, 244]], [[35, 217], [216, 217], [216, 227], [35, 227]], [[33, 200], [146, 200], [146, 213], [33, 213]], [[32, 183], [215, 184], [215, 197], [32, 196]], [[35, 170], [105, 170], [105, 181], [35, 181]], [[35, 155], [124, 155], [124, 164], [35, 164]], [[34, 137], [142, 138], [142, 149], [34, 148]], [[35, 123], [176, 123], [176, 133], [35, 133]], [[33, 106], [176, 106], [176, 119], [33, 119]], [[34, 92], [102, 92], [102, 102], [34, 102]], [[34, 77], [119, 77], [119, 87], [34, 87]], [[32, 60], [120, 60], [120, 73], [32, 73]], [[35, 46], [119, 46], [119, 55], [35, 55]], [[32, 29], [142, 29], [142, 42], [32, 42]], [[25, 12], [147, 12], [147, 24], [25, 24]]] +00134770.jpg [[[388, 646], [456, 646], [456, 656], [388, 656]], [[407, 620], [484, 619], [485, 633], [408, 634]], [[112, 534], [270, 531], [270, 549], [113, 551]], [[111, 502], [443, 497], [443, 514], [112, 519]], [[111, 471], [443, 467], [443, 484], [112, 488]], [[111, 439], [444, 434], [444, 451], [112, 457]], [[111, 409], [442, 405], [442, 421], [112, 425]], [[153, 376], [441, 373], [441, 390], [153, 394]], [[184, 338], [369, 336], [369, 356], [185, 358]], [[75, 98], [515, 104], [513, 218], [74, 212]]] +00145943.jpg [[[394, 248], [746, 279], [731, 449], [379, 418]], [[90, 92], [300, 92], [300, 119], [90, 119]], [[46, 41], [326, 39], [326, 75], [46, 77]]] +00147605.jpg [[[805, 616], [874, 616], [874, 627], [805, 627]], [[516, 607], [784, 605], [784, 628], [516, 629]], [[118, 522], [224, 522], [224, 560], [118, 560]], [[253, 524], [307, 524], [307, 557], [253, 557]], [[715, 501], [900, 505], [900, 538], [714, 534]], [[255, 502], [295, 502], [295, 517], [255, 517]], [[347, 481], [473, 481], [473, 515], [347, 515]], [[252, 484], [295, 484], [295, 499], [252, 499]], [[350, 456], [447, 456], [447, 470], [350, 470]], [[145, 444], [201, 444], [201, 467], [145, 467]], [[728, 371], [878, 371], [878, 420], [728, 420]], [[528, 369], [681, 369], [681, 418], [528, 418]], [[143, 369], [488, 369], [488, 420], [143, 420]], [[744, 315], [871, 315], [871, 336], [744, 336]], [[799, 157], [886, 154], [887, 188], [800, 191]], [[274, 142], [455, 142], [455, 160], [274, 160]], [[738, 116], [894, 119], [893, 157], [737, 153]], [[108, 112], [204, 112], [204, 130], [108, 130]], [[270, 92], [463, 96], [462, 132], [270, 129]]] +00150341.jpg [[[100, 644], [297, 644], [297, 661], [100, 661]], [[115, 617], [288, 617], [288, 631], [115, 631]], [[84, 593], [319, 592], [319, 609], [84, 610]], [[31, 565], [313, 562], [314, 580], [31, 582]], [[444, 560], [461, 560], [461, 569], [444, 569]], [[390, 557], [446, 557], [446, 572], [390, 572]], [[31, 515], [168, 515], [168, 529], [31, 529]], [[33, 490], [110, 490], [110, 504], [33, 504]], [[358, 459], [464, 463], [463, 485], [357, 481]], [[28, 459], [268, 460], [268, 481], [28, 480]], [[339, 439], [421, 444], [421, 460], [338, 455]], [[65, 439], [143, 439], [143, 453], [65, 453]], [[207, 416], [292, 416], [292, 434], [207, 434]], [[319, 408], [441, 413], [440, 438], [318, 433]], [[44, 405], [175, 409], [174, 434], [43, 430]], [[31, 383], [137, 383], [137, 404], [31, 404]]] +00150669.jpg [[[649, 700], [681, 700], [681, 716], [649, 716]], [[517, 685], [549, 685], [549, 720], [517, 720]], [[651, 688], [678, 688], [678, 701], [651, 701]], [[862, 687], [876, 687], [876, 695], [862, 695]], [[922, 675], [938, 675], [938, 685], [922, 685]], [[785, 671], [807, 671], [807, 687], [785, 687]], [[592, 672], [606, 672], [606, 686], [592, 686]], [[722, 679], [732, 669], [742, 678], [731, 688]], [[651, 680], [667, 665], [681, 679], [666, 695]], [[273, 667], [422, 667], [422, 688], [273, 688]], [[47, 668], [108, 668], [108, 686], [47, 686]], [[136, 666], [203, 666], [203, 688], [136, 688]], [[782, 629], [810, 629], [810, 661], [782, 661]], [[645, 627], [685, 627], [685, 665], [645, 665]], [[516, 628], [548, 628], [548, 664], [516, 664]], [[655, 619], [672, 619], [672, 627], [655, 627]], [[598, 617], [605, 624], [599, 629], [592, 622]], [[523, 619], [540, 619], [540, 627], [523, 627]], [[858, 618], [868, 618], [868, 627], [858, 627]], [[727, 618], [735, 618], [735, 627], [727, 627]], [[919, 620], [932, 611], [942, 624], [929, 633]], [[786, 616], [805, 616], [805, 629], [786, 629]], [[373, 604], [420, 604], [420, 619], [373, 619]], [[85, 603], [215, 605], [214, 621], [84, 619]], [[48, 603], [71, 603], [71, 622], [48, 622]], [[788, 561], [806, 561], [806, 572], [788, 572]], [[923, 560], [935, 560], [935, 574], [923, 574]], [[856, 560], [869, 560], [869, 574], [856, 574]], [[62, 554], [410, 554], [410, 568], [62, 568]], [[63, 532], [116, 535], [115, 545], [62, 543]], [[859, 527], [868, 527], [868, 539], [859, 539]], [[925, 526], [934, 526], [934, 540], [925, 540]], [[794, 520], [807, 529], [798, 542], [785, 533]], [[526, 526], [535, 526], [535, 536], [526, 536]], [[262, 513], [395, 513], [395, 526], [262, 526]], [[122, 514], [245, 514], [245, 524], [122, 524]], [[49, 514], [119, 514], [119, 525], [49, 525]], [[755, 492], [828, 492], [828, 507], [755, 507]], [[638, 492], [710, 492], [710, 507], [638, 507]], [[519, 492], [592, 492], [592, 507], [519, 507]], [[85, 450], [123, 450], [123, 461], [85, 461]], [[220, 450], [236, 447], [238, 459], [223, 462]], [[683, 445], [868, 445], [868, 459], [683, 459]], [[562, 445], [666, 445], [666, 459], [562, 459]], [[491, 446], [544, 446], [544, 458], [491, 458]], [[183, 437], [208, 437], [208, 459], [183, 459]], [[52, 431], [72, 438], [64, 462], [44, 455]], [[224, 432], [276, 432], [276, 443], [224, 443]], [[88, 432], [144, 432], [144, 443], [88, 443]], [[506, 383], [616, 382], [616, 397], [506, 398]], [[702, 381], [758, 381], [758, 399], [702, 399]], [[308, 373], [364, 373], [364, 384], [308, 384]], [[92, 373], [167, 373], [167, 384], [92, 384]], [[688, 335], [820, 335], [820, 350], [688, 350]], [[498, 335], [657, 335], [657, 350], [498, 350]], [[208, 316], [244, 316], [244, 331], [208, 331]], [[499, 289], [641, 289], [641, 302], [499, 302]], [[671, 287], [801, 287], [801, 301], [671, 301]], [[670, 241], [816, 241], [816, 255], [670, 255]], [[497, 241], [643, 241], [643, 255], [497, 255]], [[670, 194], [815, 194], [815, 208], [670, 208]], [[498, 194], [643, 194], [643, 208], [498, 208]], [[670, 145], [815, 145], [815, 160], [670, 160]], [[499, 145], [645, 145], [645, 160], [499, 160]], [[489, 103], [546, 103], [546, 120], [489, 120]], [[56, 89], [95, 89], [95, 97], [56, 97]], [[845, 26], [887, 20], [889, 39], [848, 44]], [[26, 20], [700, 20], [700, 37], [26, 37]], [[898, 11], [996, 16], [995, 45], [896, 40]]] +00152568.jpg [[[3, 252], [284, 254], [284, 280], [3, 278]], [[196, 233], [254, 233], [254, 240], [196, 240]], [[49, 229], [90, 229], [90, 240], [49, 240]], [[200, 159], [281, 165], [276, 229], [195, 222]]] +00155628.jpg [[[149, 901], [503, 903], [503, 922], [149, 920]], [[520, 893], [561, 896], [560, 911], [519, 908]], [[61, 885], [81, 885], [81, 894], [61, 894]], [[150, 878], [503, 882], [503, 900], [149, 896]], [[524, 834], [640, 839], [639, 856], [524, 852]], [[70, 834], [185, 835], [185, 853], [69, 852]], [[246, 555], [466, 555], [466, 569], [246, 569]], [[308, 507], [403, 509], [403, 524], [308, 522]], [[244, 482], [459, 484], [459, 502], [244, 500]], [[252, 422], [459, 424], [458, 452], [251, 450]], [[195, 378], [517, 380], [516, 408], [195, 406]], [[474, 194], [624, 196], [624, 210], [473, 208]], [[73, 129], [641, 131], [641, 160], [73, 158]], [[483, 41], [597, 37], [599, 98], [486, 102]], [[68, 25], [135, 16], [139, 43], [72, 52]]] +00173364.jpg [[[8, 178], [56, 178], [56, 200], [8, 200]], [[137, 120], [194, 120], [194, 133], [137, 133]], [[39, 76], [86, 76], [86, 105], [39, 105]], [[249, 20], [310, 20], [310, 36], [249, 36]], [[21, 16], [104, 16], [104, 39], [21, 39]]] +00175503.jpg [[[43, 260], [500, 255], [501, 358], [44, 363]], [[52, 200], [349, 178], [354, 251], [58, 273]]] +00193218.jpg [[[283, 375], [410, 375], [410, 388], [283, 388]], [[172, 375], [221, 375], [221, 389], [172, 389]], [[110, 375], [161, 375], [161, 389], [110, 389]], [[276, 358], [357, 358], [357, 371], [276, 371]], [[171, 359], [220, 359], [220, 370], [171, 370]], [[409, 357], [492, 357], [492, 370], [409, 370]], [[26, 187], [62, 187], [62, 202], [26, 202]], [[501, 185], [557, 185], [557, 199], [501, 199]], [[381, 187], [420, 185], [421, 199], [382, 201]], [[284, 186], [310, 186], [310, 201], [284, 201]], [[174, 186], [196, 186], [196, 201], [174, 201]], [[499, 165], [540, 165], [540, 176], [499, 176]], [[381, 164], [409, 164], [409, 177], [381, 177]], [[262, 163], [302, 163], [302, 177], [262, 177]], [[176, 163], [230, 163], [230, 177], [176, 177]], [[26, 163], [79, 163], [79, 177], [26, 177]], [[387, 140], [488, 140], [488, 153], [387, 153]], [[28, 139], [131, 139], [131, 152], [28, 152]], [[443, 117], [537, 119], [537, 133], [443, 132]], [[346, 119], [405, 119], [405, 130], [346, 130]], [[261, 119], [303, 119], [303, 130], [261, 130]], [[30, 113], [228, 116], [228, 131], [30, 129]], [[131, 91], [394, 94], [394, 109], [131, 105]], [[562, 82], [583, 82], [583, 107], [562, 107]]] +00195033.jpg [[[488, 263], [533, 265], [532, 280], [487, 278]], [[126, 250], [192, 250], [192, 283], [126, 283]], [[338, 249], [362, 249], [362, 266], [338, 266]], [[319, 222], [380, 225], [380, 238], [319, 236]], [[431, 224], [450, 224], [450, 235], [431, 235]], [[365, 203], [538, 203], [538, 216], [365, 216]], [[89, 200], [146, 203], [146, 217], [89, 214]], [[329, 201], [354, 201], [354, 212], [329, 212]], [[371, 181], [449, 181], [449, 194], [371, 194]], [[329, 181], [352, 181], [352, 192], [329, 192]], [[96, 179], [240, 179], [240, 193], [96, 193]], [[456, 162], [555, 162], [555, 175], [456, 175]], [[129, 150], [287, 151], [287, 165], [129, 164]], [[36, 145], [73, 149], [72, 163], [35, 159]], [[527, 146], [552, 146], [552, 155], [527, 155]], [[102, 145], [120, 145], [120, 153], [102, 153]], [[371, 129], [503, 128], [503, 139], [371, 140]], [[99, 126], [193, 126], [193, 139], [99, 139]], [[322, 127], [337, 127], [337, 135], [322, 135]], [[37, 123], [77, 123], [77, 134], [37, 134]], [[324, 106], [337, 106], [337, 115], [324, 115]], [[309, 107], [315, 107], [315, 112], [309, 112]], [[372, 103], [501, 103], [501, 116], [372, 116]], [[349, 105], [360, 105], [360, 114], [349, 114]], [[38, 103], [80, 103], [80, 113], [38, 113]], [[99, 100], [205, 101], [205, 115], [99, 114]], [[306, 90], [317, 90], [317, 97], [306, 97]], [[347, 88], [362, 88], [362, 96], [347, 96]], [[321, 87], [340, 87], [340, 99], [321, 99]], [[358, 84], [513, 82], [513, 95], [358, 97]], [[41, 83], [89, 83], [89, 93], [41, 93]], [[94, 79], [241, 80], [241, 94], [94, 93]], [[313, 66], [394, 66], [394, 79], [313, 79]], [[242, 66], [288, 66], [288, 77], [242, 77]], [[185, 54], [220, 54], [220, 65], [185, 65]], [[469, 48], [547, 48], [547, 61], [469, 61]], [[423, 36], [436, 36], [436, 54], [423, 54]], [[465, 30], [551, 30], [551, 43], [465, 43]], [[207, 21], [329, 23], [328, 41], [207, 39]]] +00208502.jpg [[[247, 566], [282, 566], [282, 573], [247, 573]], [[558, 534], [629, 539], [627, 570], [556, 565]], [[205, 540], [284, 540], [284, 552], [205, 552]], [[143, 513], [189, 513], [189, 525], [143, 525]], [[249, 512], [307, 512], [307, 524], [249, 524]], [[44, 500], [118, 500], [118, 519], [44, 519]], [[467, 491], [556, 491], [556, 508], [467, 508]], [[667, 490], [678, 494], [675, 503], [664, 499]], [[788, 489], [794, 495], [789, 499], [783, 494]], [[726, 491], [737, 491], [737, 501], [726, 501]], [[42, 452], [117, 450], [117, 469], [42, 470]], [[175, 450], [236, 450], [236, 464], [175, 464]], [[614, 407], [638, 407], [638, 422], [614, 422]], [[95, 405], [119, 405], [119, 422], [95, 422]], [[49, 399], [64, 414], [50, 427], [36, 413]], [[209, 401], [226, 401], [226, 415], [209, 415]], [[40, 357], [58, 357], [58, 374], [40, 374]], [[94, 356], [119, 356], [119, 373], [94, 373]], [[188, 341], [246, 339], [247, 361], [189, 364]], [[459, 321], [549, 319], [549, 337], [460, 339]], [[459, 273], [551, 273], [551, 290], [459, 290]], [[563, 272], [735, 269], [735, 286], [564, 289]], [[517, 225], [547, 225], [547, 245], [517, 245]], [[459, 226], [480, 226], [480, 244], [459, 244]], [[621, 187], [673, 187], [673, 201], [621, 201]], [[457, 132], [548, 130], [548, 147], [458, 149]], [[572, 106], [787, 99], [787, 120], [573, 126]], [[122, 48], [290, 48], [290, 97], [122, 97]], [[539, 39], [708, 39], [708, 89], [539, 89]]] +00224225.jpg [[[134, 429], [153, 426], [157, 445], [138, 448]], [[202, 404], [478, 411], [476, 459], [201, 452]], [[205, 230], [469, 230], [469, 390], [205, 390]], [[131, 265], [172, 265], [172, 279], [131, 279]], [[345, 207], [456, 207], [456, 231], [345, 231]], [[199, 189], [346, 196], [344, 239], [197, 232]], [[10, 44], [157, 41], [158, 112], [11, 115]]] +00227746.jpg [[[190, 232], [258, 232], [258, 238], [190, 238]], [[160, 232], [183, 232], [183, 238], [160, 238]], [[123, 232], [150, 232], [150, 238], [123, 238]], [[290, 208], [345, 206], [346, 222], [291, 224]], [[172, 181], [249, 181], [249, 194], [172, 194]], [[143, 178], [165, 180], [162, 208], [140, 206]], [[142, 164], [157, 162], [160, 177], [145, 180]], [[173, 157], [203, 157], [203, 164], [173, 164]], [[200, 154], [347, 154], [347, 167], [200, 167]], [[144, 111], [277, 114], [277, 134], [144, 131]], [[201, 52], [387, 53], [386, 69], [201, 68]], [[141, 46], [192, 46], [192, 63], [141, 63]], [[40, 26], [61, 26], [61, 42], [40, 42]]] +00229605.jpg [[[743, 529], [881, 529], [881, 544], [743, 544]], [[236, 499], [589, 498], [589, 522], [236, 523]], [[6, 498], [227, 498], [227, 522], [6, 522]], [[735, 496], [883, 499], [883, 520], [734, 517]], [[606, 495], [716, 489], [718, 515], [608, 521]], [[4, 245], [863, 230], [864, 288], [5, 303]], [[478, 28], [883, 28], [883, 76], [478, 76]]] +00233011.jpg [[[63, 227], [291, 227], [291, 242], [63, 242]], [[12, 219], [41, 219], [41, 250], [12, 250]], [[61, 177], [119, 177], [119, 195], [61, 195]], [[11, 173], [40, 169], [44, 200], [14, 203]], [[61, 129], [147, 131], [147, 147], [61, 144]], [[12, 124], [43, 124], [43, 154], [12, 154]], [[125, 89], [238, 89], [238, 103], [125, 103]], [[148, 51], [216, 51], [216, 65], [148, 65]], [[258, 46], [353, 50], [352, 69], [257, 65]], [[9, 49], [52, 49], [52, 68], [9, 68]], [[277, 12], [345, 12], [345, 31], [277, 31]], [[28, 11], [74, 11], [74, 31], [28, 31]]] +00233625.jpg [[[375, 397], [632, 399], [632, 443], [375, 440]], [[71, 214], [932, 207], [933, 321], [71, 328]]] +00233634.jpg [[[215, 639], [261, 639], [261, 703], [215, 703]], [[523, 635], [570, 635], [570, 695], [523, 695]], [[643, 523], [682, 523], [682, 568], [643, 568]], [[97, 516], [152, 516], [152, 589], [97, 589]], [[755, 395], [760, 395], [760, 401], [755, 401]], [[26, 395], [32, 395], [32, 400], [26, 400]], [[678, 364], [728, 362], [731, 430], [681, 432]], [[54, 361], [107, 361], [107, 434], [54, 434]], [[78, 208], [155, 208], [155, 280], [78, 280]], [[643, 205], [693, 205], [693, 272], [643, 272]], [[210, 88], [260, 86], [263, 164], [213, 166]], [[363, 48], [426, 45], [430, 115], [367, 118]]] +00234400.jpg [[[446, 421], [738, 421], [738, 438], [446, 438]], [[157, 421], [454, 421], [454, 438], [157, 438]], [[158, 394], [652, 394], [652, 411], [158, 411]], [[40, 391], [127, 391], [127, 412], [40, 412]], [[158, 342], [304, 345], [304, 363], [158, 360]], [[38, 344], [123, 344], [123, 362], [38, 362]], [[520, 295], [703, 295], [703, 314], [520, 314]], [[394, 292], [483, 290], [484, 314], [394, 317]], [[157, 293], [270, 293], [270, 313], [157, 313]], [[37, 293], [125, 293], [125, 313], [37, 313]], [[156, 243], [358, 243], [358, 267], [156, 267]], [[36, 243], [82, 243], [82, 269], [36, 269]], [[29, 152], [158, 152], [158, 175], [29, 175]], [[282, 98], [507, 98], [507, 111], [282, 111]], [[315, 46], [475, 50], [474, 88], [314, 85]], [[518, 51], [663, 53], [662, 67], [517, 65]], [[487, 19], [706, 17], [706, 43], [487, 45]]] +00234883.jpg [[[344, 145], [355, 145], [355, 153], [344, 153]], [[66, 125], [316, 120], [317, 190], [67, 195]], [[79, 138], [109, 141], [108, 152], [78, 148]], [[72, 120], [120, 120], [120, 130], [72, 130]], [[383, 63], [504, 62], [504, 74], [383, 75]], [[58, 29], [365, 26], [366, 112], [59, 115]], [[387, 28], [501, 26], [501, 45], [387, 47]]] +test_add_0.jpg [[[311, 521], [391, 521], [391, 534], [311, 534]], [[277, 500], [424, 500], [424, 514], [277, 514]], [[261, 446], [437, 446], [437, 459], [261, 459]], [[212, 428], [485, 428], [485, 441], [212, 441]], [[247, 388], [457, 388], [457, 409], [247, 409]], [[222, 328], [474, 328], [474, 372], [222, 372]], [[208, 207], [492, 211], [490, 277], [207, 272]], [[266, 164], [422, 166], [421, 197], [265, 195]], [[18, 20], [201, 18], [201, 43], [18, 45]]] +test_add_1.png [] +test_add_10.png [[[157, 124], [186, 124], [186, 172], [157, 172]], [[65, 117], [95, 117], [95, 168], [65, 168]], [[161, 106], [183, 106], [183, 127], [161, 127]], [[69, 100], [94, 100], [94, 128], [69, 128]], [[117, 46], [154, 45], [157, 174], [121, 175]], [[66, 34], [97, 34], [97, 112], [66, 112]]] +test_add_11.jpg [[[1525, 773], [1564, 756], [1575, 780], [1536, 798]], [[1390, 757], [1483, 757], [1483, 791], [1390, 791]], [[1013, 754], [1207, 754], [1207, 800], [1013, 800]], [[685, 755], [875, 755], [875, 796], [685, 796]], [[356, 753], [566, 747], [567, 793], [358, 798]], [[78, 751], [264, 745], [265, 793], [79, 798]], [[602, 647], [1152, 647], [1152, 703], [602, 703]], [[601, 564], [1148, 555], [1149, 611], [602, 620]], [[598, 480], [1066, 472], [1067, 526], [599, 535]], [[598, 393], [1090, 388], [1090, 439], [599, 444]], [[603, 306], [1057, 306], [1057, 357], [603, 357]], [[357, 184], [1517, 184], [1517, 261], [357, 261]], [[60, 43], [257, 37], [259, 83], [61, 89]], [[1305, 41], [1492, 41], [1492, 87], [1305, 87]], [[973, 40], [1171, 34], [1172, 80], [974, 86]], [[670, 40], [862, 34], [864, 80], [671, 86]], [[363, 34], [558, 34], [558, 85], [363, 85]]] +test_add_12.jpg [[[11, 592], [136, 594], [135, 613], [11, 611]], [[109, 521], [907, 526], [907, 569], [109, 565]], [[635, 451], [902, 448], [903, 478], [635, 481]], [[112, 447], [466, 449], [466, 486], [112, 483]], [[582, 306], [680, 304], [681, 348], [583, 351]], [[369, 261], [565, 266], [563, 357], [367, 353]], [[64, 85], [853, 88], [853, 161], [64, 159]]] +test_add_13.jpg [[[68, 94], [117, 97], [116, 114], [67, 111]]] +test_add_14.jpg [[[30, 97], [235, 95], [236, 127], [31, 129]], [[30, 52], [239, 50], [239, 86], [30, 87]]] +test_add_15.jpg [[[141, 253], [353, 253], [353, 266], [141, 266]], [[205, 214], [406, 219], [406, 232], [204, 227]], [[106, 212], [193, 213], [193, 227], [106, 226]], [[154, 156], [286, 161], [286, 174], [154, 170]], [[148, 136], [305, 142], [305, 156], [147, 150]], [[108, 137], [144, 137], [144, 148], [108, 148]], [[108, 102], [275, 109], [275, 125], [107, 117]], [[107, 72], [245, 79], [245, 96], [106, 88]], [[107, 39], [209, 42], [209, 62], [106, 59]]] +test_add_16.jpg [[[398, 842], [408, 842], [408, 852], [398, 852]], [[382, 742], [746, 742], [746, 776], [382, 776]], [[362, 703], [468, 703], [468, 725], [362, 725]], [[1552, 701], [1576, 701], [1576, 746], [1552, 746]], [[1256, 695], [1442, 695], [1442, 721], [1256, 721]], [[1244, 661], [1448, 661], [1448, 687], [1244, 687]], [[386, 645], [668, 645], [668, 679], [386, 679]], [[1228, 625], [1470, 623], [1470, 651], [1228, 653]], [[360, 604], [580, 604], [580, 629], [360, 629]], [[1202, 592], [1494, 592], [1494, 617], [1202, 617]], [[1166, 556], [1530, 556], [1530, 582], [1166, 582]], [[380, 552], [638, 552], [638, 586], [380, 586]], [[356, 502], [516, 502], [516, 536], [356, 536]], [[774, 260], [1124, 260], [1124, 300], [774, 300]], [[374, 210], [504, 210], [504, 300], [374, 300]], [[776, 212], [1088, 217], [1088, 252], [776, 248]]] +test_add_17.jpg [[[321, 255], [393, 258], [392, 271], [320, 269]], [[307, 222], [411, 228], [411, 241], [306, 236]], [[96, 136], [385, 143], [384, 208], [94, 201]], [[72, 95], [399, 103], [398, 124], [71, 117]], [[68, 76], [224, 79], [223, 93], [67, 90]], [[66, 59], [226, 62], [225, 76], [65, 74]]] +test_add_18.jpg [[[466, 788], [715, 790], [715, 813], [466, 811]], [[553, 752], [665, 757], [663, 791], [552, 786]], [[119, 539], [189, 539], [189, 570], [119, 570]], [[116, 473], [674, 486], [673, 528], [115, 516]], [[121, 429], [669, 441], [668, 470], [121, 457]], [[121, 375], [674, 381], [674, 410], [121, 404]], [[556, 262], [675, 264], [675, 278], [556, 277]], [[164, 259], [334, 259], [334, 273], [164, 273]], [[344, 195], [456, 197], [455, 220], [343, 217]], [[309, 175], [490, 175], [490, 190], [309, 190]], [[255, 128], [537, 131], [537, 169], [254, 165]], [[347, 92], [486, 94], [486, 109], [346, 107]], [[285, 41], [567, 49], [566, 82], [284, 74]], [[236, 32], [266, 32], [266, 60], [236, 60]]] +test_add_19.jpg [[[24, 294], [42, 294], [42, 302], [24, 302]], [[64, 293], [105, 293], [105, 303], [64, 303]], [[145, 287], [163, 287], [163, 304], [145, 304]], [[63, 280], [106, 280], [106, 290], [63, 290]], [[9, 281], [26, 281], [26, 288], [9, 288]], [[220, 279], [245, 279], [245, 291], [220, 291]], [[177, 279], [208, 279], [208, 290], [177, 290]], [[23, 279], [51, 279], [51, 290], [23, 290]], [[145, 278], [162, 278], [162, 292], [145, 292]], [[8, 267], [18, 267], [18, 276], [8, 276]], [[221, 265], [243, 265], [243, 277], [221, 277]], [[24, 265], [47, 265], [47, 277], [24, 277]], [[142, 263], [163, 263], [163, 279], [142, 279]], [[218, 252], [249, 252], [249, 265], [218, 265]], [[65, 253], [131, 253], [131, 263], [65, 263]], [[24, 252], [43, 252], [43, 264], [24, 264]], [[8, 253], [18, 253], [18, 262], [8, 262]], [[8, 240], [17, 240], [17, 249], [8, 249]], [[63, 237], [114, 237], [114, 251], [63, 251]], [[25, 236], [47, 239], [45, 251], [23, 249]], [[144, 234], [166, 237], [163, 253], [142, 249]], [[494, 226], [531, 226], [531, 239], [494, 239]], [[335, 226], [354, 226], [354, 237], [335, 237]], [[288, 226], [314, 226], [314, 237], [288, 237]], [[63, 226], [113, 226], [113, 236], [63, 236]], [[7, 227], [17, 227], [17, 234], [7, 234]], [[221, 225], [248, 225], [248, 235], [221, 235]], [[143, 225], [165, 222], [167, 234], [145, 237]], [[24, 224], [48, 224], [48, 238], [24, 238]], [[495, 213], [524, 213], [524, 224], [495, 224]], [[420, 212], [437, 212], [437, 225], [420, 225]], [[336, 212], [398, 212], [398, 223], [336, 223]], [[292, 212], [320, 212], [320, 223], [292, 223]], [[222, 212], [249, 212], [249, 223], [222, 223]], [[145, 212], [166, 212], [166, 223], [145, 223]], [[61, 211], [113, 209], [114, 222], [62, 224]], [[26, 211], [48, 211], [48, 223], [26, 223]], [[337, 199], [383, 199], [383, 209], [337, 209]], [[65, 200], [87, 200], [87, 207], [65, 207]], [[493, 197], [541, 197], [541, 211], [493, 211]], [[445, 202], [455, 196], [462, 206], [452, 212]], [[178, 198], [205, 198], [205, 208], [178, 208]], [[146, 199], [157, 199], [157, 208], [146, 208]], [[32, 194], [43, 204], [33, 214], [22, 203]], [[422, 193], [440, 201], [432, 215], [415, 207]], [[65, 186], [132, 186], [132, 196], [65, 196]], [[337, 185], [399, 185], [399, 196], [337, 196]], [[445, 190], [456, 182], [465, 191], [454, 200]], [[292, 188], [308, 182], [313, 193], [297, 200]], [[220, 183], [255, 183], [255, 197], [220, 197]], [[142, 184], [158, 184], [158, 197], [142, 197]], [[493, 182], [518, 182], [518, 197], [493, 197]], [[425, 180], [437, 191], [427, 202], [414, 190]], [[32, 179], [42, 189], [32, 199], [22, 189]], [[182, 179], [195, 185], [188, 198], [175, 192]], [[335, 172], [400, 169], [400, 183], [336, 185]], [[492, 170], [519, 170], [519, 185], [492, 185]], [[412, 177], [428, 164], [440, 178], [425, 190]], [[293, 171], [315, 171], [315, 185], [293, 185]], [[220, 170], [251, 170], [251, 184], [220, 184]], [[178, 172], [188, 172], [188, 183], [178, 183]], [[64, 172], [125, 170], [125, 181], [64, 182]], [[454, 168], [464, 176], [454, 185], [445, 176]], [[142, 172], [159, 168], [163, 180], [145, 185]], [[30, 165], [43, 174], [34, 186], [20, 177]], [[493, 160], [523, 160], [523, 170], [493, 170]], [[402, 161], [435, 161], [435, 168], [402, 168]], [[335, 159], [401, 159], [401, 169], [335, 169]], [[296, 159], [325, 159], [325, 170], [296, 170]], [[221, 158], [251, 158], [251, 169], [221, 169]], [[174, 161], [183, 156], [190, 167], [181, 172]], [[145, 158], [162, 158], [162, 170], [145, 170]], [[61, 158], [125, 157], [125, 168], [62, 169]], [[20, 161], [33, 154], [40, 167], [28, 174]], [[492, 143], [542, 143], [542, 157], [492, 157]], [[450, 144], [479, 144], [479, 157], [450, 157]], [[335, 143], [439, 143], [439, 156], [335, 156]], [[294, 143], [327, 143], [327, 157], [294, 157]], [[220, 143], [253, 143], [253, 157], [220, 157]], [[178, 145], [187, 145], [187, 156], [178, 156]], [[63, 144], [104, 144], [104, 155], [63, 155]], [[144, 140], [164, 145], [160, 159], [141, 154]], [[31, 137], [44, 149], [31, 162], [17, 149]], [[286, 135], [291, 135], [291, 140], [286, 140]], [[177, 133], [193, 133], [193, 144], [177, 144]], [[336, 132], [388, 132], [388, 141], [336, 141]], [[492, 131], [525, 131], [525, 141], [492, 141]], [[450, 131], [477, 131], [477, 141], [450, 141]], [[292, 131], [321, 131], [321, 141], [292, 141]], [[218, 132], [255, 130], [256, 141], [219, 144]], [[63, 131], [95, 131], [95, 141], [63, 141]], [[417, 130], [437, 130], [437, 141], [417, 141]], [[145, 130], [159, 130], [159, 143], [145, 143]], [[30, 124], [43, 133], [32, 147], [19, 138]], [[493, 118], [535, 118], [535, 129], [493, 129]], [[336, 118], [388, 118], [388, 129], [336, 129]], [[218, 118], [255, 118], [255, 128], [218, 128]], [[451, 117], [478, 117], [478, 129], [451, 129]], [[418, 117], [438, 117], [438, 130], [418, 130]], [[177, 116], [209, 116], [209, 130], [177, 130]], [[145, 117], [162, 117], [162, 130], [145, 130]], [[62, 116], [88, 116], [88, 131], [62, 131]], [[19, 121], [33, 111], [43, 124], [29, 134]], [[491, 107], [523, 107], [523, 113], [491, 113]], [[449, 107], [477, 107], [477, 113], [449, 113]], [[420, 107], [436, 107], [436, 113], [420, 113]], [[295, 107], [319, 107], [319, 114], [295, 114]], [[220, 107], [242, 107], [242, 113], [220, 113]], [[176, 107], [203, 107], [203, 113], [176, 113]], [[145, 107], [161, 107], [161, 114], [145, 114]], [[334, 105], [372, 105], [372, 114], [334, 114]], [[63, 106], [86, 106], [86, 113], [63, 113]], [[483, 89], [522, 89], [522, 99], [483, 99]], [[331, 88], [380, 88], [380, 99], [331, 99]], [[276, 88], [325, 88], [325, 99], [276, 99]], [[214, 88], [246, 88], [246, 99], [214, 99]], [[411, 86], [474, 86], [474, 100], [411, 100]], [[6, 86], [102, 86], [102, 100], [6, 100]], [[415, 66], [461, 66], [461, 77], [415, 77]], [[288, 66], [333, 66], [333, 77], [288, 77]], [[157, 64], [206, 64], [206, 78], [157, 78]], [[416, 48], [523, 49], [523, 63], [415, 62]], [[288, 49], [375, 49], [375, 63], [288, 63]], [[159, 49], [269, 49], [269, 62], [159, 62]], [[24, 53], [36, 46], [45, 59], [33, 67]], [[416, 36], [481, 36], [481, 46], [416, 46]], [[25, 38], [39, 32], [46, 46], [33, 52]], [[157, 34], [205, 34], [205, 47], [157, 47]], [[412, 4], [527, 4], [527, 17], [412, 17]], [[146, 4], [345, 2], [345, 15], [146, 17]]] +test_add_20.jpg [[[31, 346], [605, 346], [605, 370], [31, 370]], [[217, 294], [510, 294], [510, 322], [217, 322]], [[473, 271], [525, 271], [525, 286], [473, 286]], [[220, 267], [287, 267], [287, 286], [220, 286]], [[219, 239], [484, 239], [484, 263], [219, 263]], [[221, 217], [303, 217], [303, 234], [221, 234]], [[402, 192], [417, 192], [417, 205], [402, 205]], [[222, 187], [341, 187], [341, 207], [222, 207]], [[221, 162], [287, 162], [287, 180], [221, 180]], [[375, 122], [475, 124], [475, 146], [375, 143]], [[222, 124], [356, 122], [356, 143], [222, 146]], [[218, 81], [352, 84], [352, 116], [218, 113]], [[440, 35], [605, 35], [605, 60], [440, 60]], [[72, 16], [398, 16], [398, 44], [72, 44]]] +test_add_3.jpg [[[169, 327], [337, 326], [337, 341], [169, 342]], [[170, 288], [307, 290], [307, 312], [170, 310]], [[171, 221], [323, 221], [323, 234], [171, 234]], [[340, 221], [449, 217], [449, 231], [341, 234]], [[169, 201], [372, 201], [372, 214], [169, 214]], [[170, 183], [418, 183], [418, 196], [170, 196]], [[170, 149], [416, 149], [416, 163], [170, 163]], [[171, 119], [418, 119], [418, 140], [171, 140]], [[326, 64], [478, 64], [478, 91], [326, 91]], [[173, 64], [306, 60], [306, 89], [174, 93]]] +test_add_4.png [] +test_add_5.png [[[48, 164], [108, 164], [108, 174], [48, 174]], [[52, 121], [169, 121], [169, 134], [52, 134]], [[50, 102], [165, 102], [165, 118], [50, 118]], [[52, 83], [164, 83], [164, 100], [52, 100]], [[51, 68], [166, 68], [166, 84], [51, 84]], [[51, 50], [145, 47], [145, 64], [52, 67]]] +test_add_6.jpg [[[123, 223], [219, 227], [218, 251], [122, 247]], [[172, 172], [186, 186], [172, 200], [158, 186]]] +test_add_7.jpg [[[48, 938], [174, 936], [174, 962], [48, 964]], [[227, 873], [629, 876], [628, 953], [226, 949]], [[56, 745], [638, 745], [638, 790], [56, 790]], [[150, 674], [545, 678], [544, 721], [150, 718]], [[73, 504], [633, 504], [633, 601], [73, 601]], [[59, 270], [655, 279], [652, 441], [56, 432]], [[513, 193], [553, 193], [553, 223], [513, 223]], [[61, 175], [532, 175], [532, 239], [61, 239]], [[533, 178], [642, 178], [642, 236], [533, 236]]] +test_add_8.jpg [[[251, 586], [454, 580], [454, 606], [252, 613]], [[107, 533], [457, 527], [457, 560], [108, 566]], [[336, 494], [384, 494], [384, 507], [336, 507]], [[27, 307], [355, 297], [356, 320], [28, 330]], [[22, 259], [445, 251], [445, 274], [23, 282]], [[78, 209], [445, 205], [445, 225], [78, 229]], [[160, 23], [319, 30], [317, 79], [158, 72]]] +test_add_9.png [[[266, 687], [486, 687], [486, 696], [266, 696]], [[196, 668], [554, 668], [554, 681], [196, 681]], [[154, 596], [597, 596], [597, 606], [154, 606]], [[215, 578], [541, 578], [541, 588], [215, 588]], [[134, 560], [615, 560], [615, 570], [134, 570]], [[85, 543], [665, 543], [665, 553], [85, 553]], [[96, 522], [653, 522], [653, 535], [96, 535]], [[362, 449], [389, 449], [389, 460], [362, 460]], [[238, 376], [513, 376], [513, 389], [238, 389]], [[177, 356], [574, 356], [574, 368], [177, 368]], [[344, 281], [408, 283], [407, 297], [343, 294]], [[257, 205], [493, 205], [493, 219], [257, 219]]] diff --git a/test_tipc/results/python_ppocr_det_mobile_results_fp32.txt b/test_tipc/results/python_ppocr_det_mobile_results_fp32.txt new file mode 100644 index 0000000..e370b49 --- /dev/null +++ b/test_tipc/results/python_ppocr_det_mobile_results_fp32.txt @@ -0,0 +1,49 @@ +00008790.jpg [[[209, 406], [280, 406], [280, 419], [209, 419]], [[60, 398], [105, 398], [105, 411], [60, 411]], [[198, 389], [291, 389], [291, 402], [198, 402]], [[162, 391], [173, 391], [173, 401], [162, 401]], [[35, 380], [133, 380], [133, 393], [35, 393]], [[199, 371], [292, 371], [292, 384], [199, 384]], [[218, 310], [272, 310], [272, 324], [218, 324]], [[162, 305], [172, 305], [172, 314], [162, 314]], [[371, 302], [436, 302], [436, 316], [371, 316]], [[31, 302], [134, 301], [134, 315], [31, 316]], [[223, 292], [269, 292], [269, 306], [223, 306]], [[60, 225], [104, 225], [104, 236], [60, 236]], [[218, 223], [272, 223], [272, 237], [218, 237]], [[162, 219], [173, 219], [173, 227], [162, 227]], [[33, 207], [131, 207], [131, 220], [33, 220]], [[223, 206], [269, 206], [269, 220], [223, 220]], [[74, 146], [383, 146], [383, 159], [74, 159]], [[54, 120], [117, 120], [117, 134], [54, 134]], [[74, 51], [296, 51], [296, 65], [74, 65]], [[56, 17], [116, 17], [116, 31], [56, 31]]] +00018946.jpg [[[441, 328], [474, 328], [474, 339], [441, 339]], [[86, 284], [141, 286], [140, 307], [85, 305]], [[302, 279], [377, 279], [377, 297], [302, 297]], [[197, 265], [281, 274], [279, 293], [195, 284]], [[198, 197], [452, 219], [450, 242], [196, 220]], [[343, 182], [376, 182], [376, 192], [343, 192]], [[199, 164], [340, 171], [339, 192], [198, 185]], [[177, 101], [415, 118], [413, 145], [175, 128]]] +00034387.jpg [[[265, 460], [740, 460], [740, 484], [265, 484]], [[348, 417], [420, 417], [420, 443], [348, 443]], [[545, 418], [568, 418], [568, 442], [545, 442]], [[685, 417], [710, 417], [710, 443], [685, 443]], [[175, 415], [226, 415], [226, 443], [175, 443]], [[874, 414], [908, 414], [908, 446], [874, 446]], [[56, 417], [74, 417], [74, 442], [56, 442]], [[856, 373], [925, 373], [925, 400], [856, 400]], [[348, 372], [418, 372], [418, 397], [348, 397]], [[674, 372], [723, 372], [723, 401], [674, 401]], [[539, 373], [570, 373], [570, 400], [539, 400]], [[151, 365], [228, 369], [226, 402], [149, 398]], [[56, 372], [74, 372], [74, 397], [56, 397]], [[857, 329], [925, 329], [925, 355], [857, 355]], [[351, 330], [419, 330], [419, 356], [351, 356]], [[674, 328], [723, 328], [723, 356], [674, 356]], [[541, 329], [570, 329], [570, 357], [541, 357]], [[171, 327], [227, 324], [229, 355], [173, 358]], [[57, 330], [74, 330], [74, 356], [57, 356]], [[298, 327], [316, 327], [316, 334], [298, 334]], [[855, 286], [925, 286], [925, 312], [855, 312]], [[674, 286], [723, 286], [723, 313], [674, 313]], [[346, 286], [426, 283], [427, 313], [347, 316]], [[540, 285], [569, 285], [569, 312], [540, 312]], [[172, 282], [226, 282], [226, 313], [172, 313]], [[56, 287], [73, 287], [73, 312], [56, 312]], [[857, 242], [925, 242], [925, 268], [857, 268]], [[348, 242], [460, 242], [460, 268], [348, 268]], [[156, 242], [227, 242], [227, 269], [156, 269]], [[674, 241], [724, 241], [724, 269], [674, 269]], [[531, 241], [572, 241], [572, 270], [531, 270]], [[56, 242], [74, 242], [74, 268], [56, 268]], [[855, 197], [925, 200], [924, 226], [854, 224]], [[674, 198], [726, 198], [726, 226], [674, 226]], [[344, 200], [434, 195], [436, 223], [346, 228]], [[176, 197], [227, 197], [227, 225], [176, 225]], [[56, 200], [73, 200], [73, 226], [56, 226]], [[527, 194], [576, 194], [576, 226], [527, 226]], [[349, 155], [419, 155], [419, 181], [349, 181]], [[854, 154], [925, 154], [925, 180], [854, 180]], [[176, 154], [226, 154], [226, 183], [176, 183]], [[670, 153], [723, 153], [723, 181], [670, 181]], [[538, 154], [571, 154], [571, 182], [538, 182]], [[56, 156], [74, 156], [74, 182], [56, 182]], [[349, 111], [419, 111], [419, 137], [349, 137]], [[174, 111], [227, 111], [227, 139], [174, 139]], [[546, 113], [564, 113], [564, 137], [546, 137]], [[52, 112], [75, 112], [75, 139], [52, 139]], [[639, 108], [727, 105], [728, 138], [640, 141]], [[817, 103], [927, 110], [925, 139], [815, 132]], [[814, 68], [951, 68], [951, 92], [814, 92]], [[307, 66], [446, 68], [446, 93], [306, 90]], [[673, 67], [723, 67], [723, 93], [673, 93]], [[175, 65], [228, 68], [226, 95], [174, 92]], [[39, 65], [90, 68], [88, 97], [37, 94]], [[528, 65], [580, 65], [580, 94], [528, 94]], [[334, 20], [670, 20], [670, 43], [334, 43]]] +00037951.jpg [[[434, 976], [551, 978], [550, 993], [434, 991]], [[433, 932], [553, 932], [553, 969], [433, 969]], [[30, 522], [98, 522], [98, 545], [30, 545]], [[31, 443], [145, 443], [145, 464], [31, 464]], [[234, 335], [326, 332], [327, 354], [235, 356]], [[124, 252], [436, 252], [436, 284], [124, 284]], [[182, 206], [378, 206], [378, 227], [182, 227]], [[258, 106], [320, 123], [304, 181], [242, 163]], [[28, 65], [33, 65], [33, 71], [28, 71]], [[37, 58], [147, 58], [147, 80], [37, 80]]] +00044782.jpg [[[104, 218], [115, 218], [115, 227], [104, 227]], [[223, 216], [246, 216], [246, 228], [223, 228]], [[163, 216], [182, 216], [182, 229], [163, 229]], [[124, 191], [164, 191], [164, 202], [124, 202]], [[91, 84], [251, 84], [251, 98], [91, 98]], [[73, 63], [278, 63], [278, 78], [73, 78]], [[104, 15], [243, 15], [243, 44], [104, 44]]] +00067516.jpg [[[141, 808], [594, 809], [594, 822], [141, 821]], [[49, 784], [695, 784], [695, 798], [49, 798]], [[579, 751], [667, 751], [667, 764], [579, 764]], [[355, 750], [395, 750], [395, 767], [355, 767]], [[221, 751], [260, 751], [260, 765], [221, 765]], [[477, 750], [501, 750], [501, 768], [477, 768]], [[69, 748], [133, 751], [132, 765], [68, 761]], [[576, 682], [668, 682], [668, 699], [576, 699]], [[476, 682], [518, 682], [518, 700], [476, 700]], [[354, 682], [395, 682], [395, 700], [354, 700]], [[69, 681], [133, 684], [132, 699], [68, 695]], [[220, 679], [243, 682], [241, 700], [218, 697]], [[577, 615], [667, 615], [667, 632], [577, 632]], [[68, 612], [134, 615], [133, 632], [67, 629]], [[476, 614], [500, 614], [500, 633], [476, 633]], [[354, 613], [378, 613], [378, 634], [354, 634]], [[219, 612], [245, 612], [245, 633], [219, 633]], [[578, 547], [667, 547], [667, 564], [578, 564]], [[476, 546], [518, 546], [518, 565], [476, 565]], [[353, 545], [379, 545], [379, 566], [353, 566]], [[219, 545], [245, 545], [245, 566], [219, 566]], [[68, 542], [133, 546], [132, 563], [67, 560]], [[68, 478], [133, 482], [132, 499], [67, 496]], [[586, 481], [664, 481], [664, 497], [586, 497]], [[476, 480], [518, 480], [518, 498], [476, 498]], [[354, 480], [395, 480], [395, 498], [354, 498]], [[219, 479], [245, 479], [245, 500], [219, 500]], [[580, 425], [665, 429], [664, 449], [580, 446]], [[346, 429], [410, 429], [410, 447], [346, 447]], [[68, 426], [150, 429], [149, 449], [67, 447]], [[474, 427], [515, 427], [515, 449], [474, 449]], [[218, 427], [259, 427], [259, 449], [218, 449]], [[283, 398], [478, 399], [478, 419], [283, 418]], [[86, 318], [664, 318], [664, 332], [86, 332]], [[65, 279], [665, 279], [665, 292], [65, 292]], [[458, 210], [584, 210], [584, 224], [458, 224]], [[312, 209], [371, 209], [371, 226], [312, 226]], [[164, 209], [225, 209], [225, 226], [164, 226]], [[505, 151], [539, 151], [539, 166], [505, 166]], [[266, 48], [483, 48], [483, 68], [266, 68]]] +00088568.jpg [[[341, 446], [371, 446], [371, 453], [341, 453]], [[58, 445], [117, 445], [117, 455], [58, 455]], [[552, 433], [571, 433], [571, 440], [552, 440]], [[583, 431], [740, 431], [740, 442], [583, 442]], [[311, 415], [743, 415], [743, 428], [311, 428]], [[310, 376], [735, 376], [735, 389], [310, 389]], [[425, 340], [551, 340], [551, 350], [425, 350]], [[287, 324], [294, 332], [289, 337], [281, 330]], [[276, 294], [348, 296], [347, 311], [276, 309]], [[54, 288], [210, 288], [210, 301], [54, 301]], [[275, 265], [421, 265], [421, 278], [275, 278]], [[56, 264], [248, 264], [248, 277], [56, 277]], [[671, 248], [695, 248], [695, 261], [671, 261]], [[602, 248], [628, 248], [628, 261], [602, 261]], [[533, 248], [557, 248], [557, 261], [533, 261]], [[463, 248], [487, 248], [487, 261], [463, 261]], [[278, 248], [309, 248], [309, 260], [278, 260]], [[55, 240], [142, 240], [142, 254], [55, 254]], [[277, 231], [398, 231], [398, 244], [277, 244]], [[741, 228], [749, 237], [742, 245], [733, 236]], [[665, 230], [700, 230], [700, 244], [665, 244]], [[598, 230], [631, 230], [631, 244], [598, 244]], [[528, 230], [562, 230], [562, 244], [528, 244]], [[459, 230], [492, 230], [492, 244], [459, 244]], [[54, 215], [211, 217], [211, 231], [54, 229]], [[739, 211], [749, 221], [740, 229], [731, 220]], [[663, 214], [704, 214], [704, 228], [663, 228]], [[595, 215], [637, 215], [637, 226], [595, 226]], [[524, 215], [569, 215], [569, 226], [524, 226]], [[454, 215], [495, 215], [495, 226], [454, 226]], [[279, 215], [351, 215], [351, 226], [279, 226]], [[736, 199], [747, 199], [747, 208], [736, 208]], [[668, 197], [700, 197], [700, 208], [668, 208]], [[599, 196], [633, 196], [633, 210], [599, 210]], [[529, 197], [562, 197], [562, 208], [529, 208]], [[461, 197], [491, 197], [491, 208], [461, 208]], [[277, 195], [417, 196], [417, 211], [277, 209]], [[55, 192], [239, 192], [239, 205], [55, 205]], [[665, 181], [703, 181], [703, 192], [665, 192]], [[279, 180], [351, 181], [350, 192], [279, 191]], [[734, 180], [747, 180], [747, 193], [734, 193]], [[597, 180], [634, 180], [634, 191], [597, 191]], [[525, 179], [566, 179], [566, 193], [525, 193]], [[458, 180], [493, 180], [493, 191], [458, 191]], [[55, 170], [142, 170], [142, 184], [55, 184]], [[735, 165], [747, 165], [747, 175], [735, 175]], [[665, 163], [703, 163], [703, 175], [665, 175]], [[598, 163], [634, 163], [634, 175], [598, 175]], [[527, 163], [565, 163], [565, 175], [527, 175]], [[458, 163], [492, 163], [492, 175], [458, 175]], [[279, 162], [398, 162], [398, 176], [279, 176]], [[54, 146], [148, 146], [148, 159], [54, 159]], [[453, 147], [495, 147], [495, 158], [453, 158]], [[731, 143], [748, 146], [745, 161], [728, 158]], [[663, 145], [704, 145], [704, 159], [663, 159]], [[596, 146], [635, 146], [635, 157], [596, 157]], [[522, 145], [566, 142], [567, 157], [523, 159]], [[277, 144], [310, 144], [310, 158], [277, 158]], [[276, 121], [428, 121], [428, 139], [276, 139]], [[52, 120], [232, 121], [232, 139], [52, 138]], [[404, 91], [701, 91], [701, 106], [404, 106]], [[48, 79], [280, 79], [280, 97], [48, 97]], [[325, 69], [744, 70], [744, 84], [325, 83]], [[668, 48], [743, 48], [743, 63], [668, 63]], [[297, 48], [433, 48], [433, 62], [297, 62]]] +00091741.jpg [[[47, 336], [83, 336], [83, 358], [47, 358]], [[99, 211], [257, 211], [257, 230], [99, 230]], [[103, 190], [257, 191], [257, 205], [103, 204]], [[89, 101], [266, 99], [267, 181], [90, 184]], [[94, 48], [262, 55], [260, 114], [91, 107]], [[91, 12], [257, 14], [257, 37], [90, 35]]] +00105313.jpg [[[291, 262], [406, 262], [406, 275], [291, 275]], [[153, 262], [264, 262], [264, 274], [153, 274]], [[11, 258], [73, 261], [72, 274], [11, 272]], [[33, 231], [132, 231], [132, 244], [33, 244]], [[35, 217], [216, 217], [216, 227], [35, 227]], [[32, 200], [145, 200], [145, 213], [32, 213]], [[32, 183], [215, 184], [215, 197], [32, 196]], [[35, 170], [105, 170], [105, 181], [35, 181]], [[35, 155], [124, 155], [124, 164], [35, 164]], [[34, 137], [142, 138], [142, 149], [34, 148]], [[35, 123], [176, 123], [176, 133], [35, 133]], [[33, 106], [176, 106], [176, 119], [33, 119]], [[34, 92], [102, 92], [102, 102], [34, 102]], [[34, 77], [119, 77], [119, 87], [34, 87]], [[32, 60], [120, 60], [120, 73], [32, 73]], [[35, 46], [119, 46], [119, 55], [35, 55]], [[32, 29], [142, 29], [142, 42], [32, 42]], [[25, 12], [147, 12], [147, 24], [25, 24]]] +00134770.jpg [[[388, 645], [456, 645], [456, 655], [388, 655]], [[407, 620], [484, 619], [485, 633], [408, 634]], [[112, 534], [270, 531], [270, 549], [113, 551]], [[111, 502], [443, 497], [443, 514], [112, 519]], [[111, 471], [443, 467], [443, 484], [112, 488]], [[111, 439], [444, 434], [444, 451], [112, 457]], [[111, 409], [442, 405], [442, 421], [112, 425]], [[153, 376], [441, 373], [441, 390], [153, 394]], [[184, 338], [369, 336], [369, 356], [185, 358]], [[75, 98], [515, 104], [513, 218], [74, 212]]] +00145943.jpg [[[394, 248], [746, 279], [731, 449], [379, 418]], [[90, 92], [300, 92], [300, 119], [90, 119]], [[46, 41], [326, 39], [326, 75], [46, 77]]] +00147605.jpg [[[804, 615], [874, 616], [874, 627], [804, 626]], [[516, 607], [784, 605], [784, 628], [516, 629]], [[118, 522], [224, 522], [224, 560], [118, 560]], [[253, 524], [307, 524], [307, 557], [253, 557]], [[715, 501], [900, 505], [900, 538], [714, 534]], [[255, 502], [295, 502], [295, 517], [255, 517]], [[347, 481], [473, 481], [473, 515], [347, 515]], [[252, 484], [295, 484], [295, 499], [252, 499]], [[350, 456], [447, 456], [447, 470], [350, 470]], [[145, 444], [201, 444], [201, 467], [145, 467]], [[728, 371], [878, 371], [878, 420], [728, 420]], [[528, 369], [681, 369], [681, 418], [528, 418]], [[142, 368], [486, 368], [486, 419], [142, 419]], [[744, 315], [871, 315], [871, 336], [744, 336]], [[799, 157], [886, 154], [887, 188], [800, 191]], [[274, 142], [455, 142], [455, 160], [274, 160]], [[738, 116], [894, 119], [893, 157], [737, 153]], [[108, 112], [204, 112], [204, 130], [108, 130]], [[270, 92], [463, 96], [462, 132], [270, 129]]] +00150341.jpg [[[100, 645], [298, 645], [298, 662], [100, 662]], [[115, 617], [288, 617], [288, 631], [115, 631]], [[84, 593], [319, 592], [319, 609], [84, 610]], [[31, 565], [313, 562], [314, 580], [31, 582]], [[444, 560], [461, 560], [461, 569], [444, 569]], [[390, 557], [446, 557], [446, 572], [390, 572]], [[31, 515], [168, 515], [168, 529], [31, 529]], [[33, 490], [110, 490], [110, 504], [33, 504]], [[358, 459], [464, 463], [463, 485], [357, 481]], [[28, 459], [268, 460], [268, 481], [28, 480]], [[339, 439], [421, 444], [421, 460], [338, 455]], [[64, 439], [143, 439], [143, 453], [64, 453]], [[207, 416], [292, 416], [292, 434], [207, 434]], [[319, 408], [441, 413], [440, 438], [318, 433]], [[44, 405], [175, 409], [174, 434], [43, 430]], [[31, 383], [137, 383], [137, 404], [31, 404]]] +00150669.jpg [[[649, 700], [681, 700], [681, 716], [649, 716]], [[517, 685], [549, 685], [549, 720], [517, 720]], [[651, 688], [678, 688], [678, 701], [651, 701]], [[862, 687], [876, 687], [876, 695], [862, 695]], [[922, 675], [938, 675], [938, 685], [922, 685]], [[785, 671], [807, 671], [807, 687], [785, 687]], [[592, 672], [606, 672], [606, 686], [592, 686]], [[722, 679], [732, 669], [742, 678], [731, 688]], [[651, 680], [667, 665], [681, 679], [666, 695]], [[273, 667], [422, 667], [422, 688], [273, 688]], [[136, 666], [203, 666], [203, 688], [136, 688]], [[46, 666], [109, 666], [109, 687], [46, 687]], [[782, 629], [810, 629], [810, 661], [782, 661]], [[645, 627], [685, 627], [685, 665], [645, 665]], [[516, 628], [548, 628], [548, 664], [516, 664]], [[655, 619], [672, 619], [672, 627], [655, 627]], [[598, 617], [605, 624], [599, 629], [592, 622]], [[523, 619], [540, 619], [540, 627], [523, 627]], [[858, 618], [868, 618], [868, 627], [858, 627]], [[727, 618], [735, 618], [735, 627], [727, 627]], [[918, 621], [932, 611], [942, 624], [928, 634]], [[786, 616], [805, 616], [805, 629], [786, 629]], [[373, 604], [420, 604], [420, 619], [373, 619]], [[85, 603], [215, 605], [214, 621], [84, 619]], [[48, 603], [71, 603], [71, 622], [48, 622]], [[788, 561], [806, 561], [806, 572], [788, 572]], [[923, 560], [935, 560], [935, 574], [923, 574]], [[856, 560], [869, 560], [869, 574], [856, 574]], [[62, 553], [409, 553], [409, 567], [62, 567]], [[63, 532], [116, 535], [115, 545], [62, 543]], [[859, 527], [868, 527], [868, 539], [859, 539]], [[925, 526], [934, 526], [934, 540], [925, 540]], [[794, 520], [807, 529], [798, 542], [785, 533]], [[526, 526], [535, 526], [535, 536], [526, 536]], [[262, 513], [395, 513], [395, 526], [262, 526]], [[122, 514], [245, 514], [245, 524], [122, 524]], [[49, 514], [119, 514], [119, 525], [49, 525]], [[755, 492], [828, 492], [828, 507], [755, 507]], [[638, 492], [710, 492], [710, 507], [638, 507]], [[519, 492], [592, 492], [592, 507], [519, 507]], [[85, 450], [123, 450], [123, 461], [85, 461]], [[220, 450], [236, 447], [238, 459], [223, 462]], [[682, 445], [867, 445], [867, 459], [682, 459]], [[562, 445], [666, 445], [666, 459], [562, 459]], [[491, 446], [544, 446], [544, 458], [491, 458]], [[183, 437], [208, 437], [208, 459], [183, 459]], [[52, 431], [72, 438], [64, 462], [44, 455]], [[224, 432], [276, 432], [276, 443], [224, 443]], [[88, 432], [144, 432], [144, 443], [88, 443]], [[506, 383], [616, 382], [616, 397], [506, 398]], [[702, 381], [758, 381], [758, 399], [702, 399]], [[308, 373], [364, 373], [364, 384], [308, 384]], [[91, 372], [166, 372], [166, 383], [91, 383]], [[688, 335], [820, 335], [820, 350], [688, 350]], [[498, 335], [657, 335], [657, 350], [498, 350]], [[208, 316], [244, 316], [244, 331], [208, 331]], [[499, 289], [641, 289], [641, 302], [499, 302]], [[671, 287], [801, 287], [801, 301], [671, 301]], [[670, 241], [816, 241], [816, 255], [670, 255]], [[497, 241], [643, 241], [643, 255], [497, 255]], [[669, 193], [814, 193], [814, 207], [669, 207]], [[498, 193], [643, 193], [643, 207], [498, 207]], [[670, 145], [815, 145], [815, 160], [670, 160]], [[499, 145], [645, 145], [645, 160], [499, 160]], [[489, 103], [546, 103], [546, 120], [489, 120]], [[56, 89], [95, 89], [95, 97], [56, 97]], [[845, 26], [887, 20], [889, 39], [848, 44]], [[26, 20], [700, 20], [700, 37], [26, 37]], [[898, 11], [996, 16], [995, 45], [896, 40]]] +00152568.jpg [[[3, 252], [284, 254], [284, 280], [3, 278]], [[196, 233], [254, 233], [254, 240], [196, 240]], [[49, 229], [90, 229], [90, 240], [49, 240]], [[200, 159], [281, 165], [276, 229], [195, 222]]] +00155628.jpg [[[149, 901], [503, 903], [503, 922], [149, 920]], [[520, 893], [561, 896], [560, 911], [519, 908]], [[61, 885], [81, 885], [81, 894], [61, 894]], [[150, 878], [503, 882], [503, 900], [149, 896]], [[524, 834], [640, 839], [639, 856], [524, 852]], [[70, 834], [185, 835], [185, 853], [69, 852]], [[246, 555], [466, 555], [466, 569], [246, 569]], [[308, 507], [403, 509], [403, 524], [308, 522]], [[244, 482], [459, 484], [459, 502], [244, 500]], [[252, 422], [459, 424], [458, 452], [251, 450]], [[195, 378], [517, 380], [516, 408], [195, 406]], [[474, 194], [624, 196], [624, 210], [473, 208]], [[73, 129], [641, 131], [641, 160], [73, 158]], [[483, 41], [597, 37], [599, 98], [486, 102]], [[68, 25], [135, 16], [139, 43], [72, 52]]] +00173364.jpg [[[8, 178], [57, 178], [57, 200], [8, 200]], [[137, 120], [194, 120], [194, 133], [137, 133]], [[39, 76], [86, 76], [86, 105], [39, 105]], [[250, 20], [311, 20], [311, 36], [250, 36]], [[21, 16], [104, 16], [104, 39], [21, 39]]] +00175503.jpg [[[43, 260], [500, 255], [501, 358], [44, 363]], [[52, 200], [349, 178], [354, 251], [58, 273]]] +00193218.jpg [[[283, 375], [410, 375], [410, 388], [283, 388]], [[172, 375], [221, 375], [221, 389], [172, 389]], [[110, 375], [161, 375], [161, 389], [110, 389]], [[276, 358], [357, 358], [357, 371], [276, 371]], [[171, 359], [220, 359], [220, 370], [171, 370]], [[409, 357], [492, 357], [492, 370], [409, 370]], [[26, 187], [62, 187], [62, 202], [26, 202]], [[501, 185], [557, 185], [557, 199], [501, 199]], [[381, 187], [420, 185], [421, 199], [382, 201]], [[284, 186], [310, 186], [310, 201], [284, 201]], [[174, 186], [196, 186], [196, 201], [174, 201]], [[499, 165], [540, 165], [540, 176], [499, 176]], [[381, 164], [409, 164], [409, 177], [381, 177]], [[262, 163], [302, 163], [302, 177], [262, 177]], [[176, 163], [230, 163], [230, 177], [176, 177]], [[26, 163], [79, 163], [79, 177], [26, 177]], [[387, 140], [488, 140], [488, 153], [387, 153]], [[28, 139], [131, 139], [131, 152], [28, 152]], [[443, 117], [537, 119], [537, 133], [443, 132]], [[346, 119], [405, 119], [405, 130], [346, 130]], [[261, 119], [302, 119], [302, 130], [261, 130]], [[30, 113], [228, 116], [228, 131], [30, 129]], [[131, 91], [394, 94], [394, 109], [131, 105]], [[562, 82], [583, 82], [583, 107], [562, 107]]] +00195033.jpg [[[488, 263], [533, 265], [532, 280], [487, 278]], [[126, 250], [192, 250], [192, 283], [126, 283]], [[338, 249], [362, 249], [362, 266], [338, 266]], [[319, 222], [380, 225], [380, 238], [319, 236]], [[431, 224], [450, 224], [450, 235], [431, 235]], [[365, 203], [538, 203], [538, 216], [365, 216]], [[89, 200], [146, 203], [146, 217], [89, 214]], [[329, 201], [354, 201], [354, 212], [329, 212]], [[371, 181], [449, 181], [449, 194], [371, 194]], [[329, 181], [352, 181], [352, 192], [329, 192]], [[96, 179], [240, 179], [240, 193], [96, 193]], [[456, 161], [555, 161], [555, 174], [456, 174]], [[129, 150], [287, 151], [287, 165], [129, 164]], [[36, 145], [73, 149], [72, 163], [35, 159]], [[527, 146], [552, 146], [552, 155], [527, 155]], [[102, 145], [120, 145], [120, 153], [102, 153]], [[371, 129], [503, 128], [503, 139], [371, 140]], [[99, 126], [193, 126], [193, 139], [99, 139]], [[322, 127], [337, 127], [337, 135], [322, 135]], [[37, 123], [77, 123], [77, 134], [37, 134]], [[324, 106], [337, 106], [337, 115], [324, 115]], [[309, 107], [315, 107], [315, 112], [309, 112]], [[372, 103], [501, 103], [501, 116], [372, 116]], [[349, 105], [360, 105], [360, 114], [349, 114]], [[38, 103], [80, 103], [80, 113], [38, 113]], [[99, 100], [205, 101], [205, 115], [99, 114]], [[306, 90], [317, 90], [317, 97], [306, 97]], [[347, 88], [362, 88], [362, 96], [347, 96]], [[321, 87], [340, 87], [340, 99], [321, 99]], [[358, 84], [513, 82], [513, 95], [358, 97]], [[41, 83], [89, 83], [89, 93], [41, 93]], [[94, 79], [241, 80], [241, 94], [94, 93]], [[313, 66], [394, 66], [394, 79], [313, 79]], [[242, 66], [288, 66], [288, 77], [242, 77]], [[185, 54], [220, 54], [220, 65], [185, 65]], [[469, 48], [547, 48], [547, 61], [469, 61]], [[423, 36], [436, 36], [436, 54], [423, 54]], [[465, 30], [551, 30], [551, 43], [465, 43]], [[207, 21], [329, 23], [328, 41], [207, 39]]] +00208502.jpg [[[247, 566], [282, 566], [282, 573], [247, 573]], [[558, 534], [629, 539], [627, 570], [556, 565]], [[205, 540], [284, 540], [284, 552], [205, 552]], [[143, 513], [189, 513], [189, 525], [143, 525]], [[249, 512], [307, 512], [307, 524], [249, 524]], [[44, 500], [118, 500], [118, 519], [44, 519]], [[467, 491], [556, 491], [556, 508], [467, 508]], [[667, 490], [678, 494], [675, 503], [664, 499]], [[788, 489], [794, 495], [789, 499], [783, 494]], [[726, 491], [737, 491], [737, 501], [726, 501]], [[42, 452], [117, 450], [117, 469], [42, 470]], [[175, 450], [236, 450], [236, 464], [175, 464]], [[614, 407], [638, 407], [638, 422], [614, 422]], [[95, 405], [119, 405], [119, 422], [95, 422]], [[49, 399], [63, 413], [49, 427], [35, 414]], [[209, 401], [226, 401], [226, 415], [209, 415]], [[40, 357], [58, 357], [58, 374], [40, 374]], [[94, 356], [119, 356], [119, 373], [94, 373]], [[188, 341], [246, 339], [247, 361], [189, 364]], [[459, 321], [549, 319], [549, 337], [460, 339]], [[459, 273], [551, 273], [551, 290], [459, 290]], [[563, 272], [735, 269], [735, 286], [564, 289]], [[517, 225], [547, 225], [547, 245], [517, 245]], [[459, 226], [480, 226], [480, 244], [459, 244]], [[621, 187], [673, 187], [673, 201], [621, 201]], [[457, 132], [548, 130], [548, 147], [458, 149]], [[572, 106], [787, 99], [787, 120], [573, 126]], [[122, 48], [290, 48], [290, 97], [122, 97]], [[539, 39], [708, 39], [708, 89], [539, 89]]] +00224225.jpg [[[134, 429], [153, 426], [157, 445], [138, 448]], [[202, 404], [478, 411], [476, 459], [201, 452]], [[205, 230], [469, 230], [469, 390], [205, 390]], [[131, 265], [172, 265], [172, 279], [131, 279]], [[344, 207], [455, 207], [455, 231], [344, 231]], [[199, 189], [346, 196], [344, 239], [197, 232]], [[10, 44], [157, 41], [158, 112], [11, 115]]] +00227746.jpg [[[190, 232], [258, 232], [258, 238], [190, 238]], [[160, 232], [183, 232], [183, 238], [160, 238]], [[123, 232], [150, 232], [150, 238], [123, 238]], [[290, 209], [345, 207], [346, 221], [291, 223]], [[172, 181], [249, 181], [249, 194], [172, 194]], [[143, 178], [165, 180], [162, 208], [140, 206]], [[142, 164], [157, 162], [160, 177], [145, 180]], [[173, 157], [203, 157], [203, 164], [173, 164]], [[200, 154], [347, 154], [347, 167], [200, 167]], [[144, 111], [277, 114], [277, 134], [144, 131]], [[201, 52], [387, 53], [386, 69], [201, 68]], [[139, 47], [191, 45], [192, 62], [140, 64]], [[40, 26], [61, 26], [61, 42], [40, 42]]] +00229605.jpg [[[743, 529], [881, 529], [881, 544], [743, 544]], [[236, 499], [589, 498], [589, 522], [236, 523]], [[6, 498], [227, 498], [227, 522], [6, 522]], [[736, 496], [883, 499], [883, 520], [735, 517]], [[606, 495], [716, 489], [718, 515], [608, 521]], [[4, 245], [863, 230], [864, 288], [5, 303]], [[478, 28], [883, 28], [883, 76], [478, 76]]] +00233011.jpg [[[63, 227], [291, 227], [291, 242], [63, 242]], [[12, 219], [41, 219], [41, 250], [12, 250]], [[61, 177], [119, 177], [119, 195], [61, 195]], [[11, 173], [40, 169], [44, 200], [14, 203]], [[61, 129], [147, 131], [147, 147], [61, 144]], [[12, 124], [43, 124], [43, 154], [12, 154]], [[125, 89], [238, 89], [238, 103], [125, 103]], [[148, 51], [216, 51], [216, 65], [148, 65]], [[258, 46], [353, 50], [352, 69], [257, 65]], [[9, 49], [52, 49], [52, 68], [9, 68]], [[277, 12], [345, 12], [345, 31], [277, 31]], [[28, 11], [73, 11], [73, 31], [28, 31]]] +00233625.jpg [[[375, 397], [632, 399], [632, 443], [375, 440]], [[71, 214], [932, 207], [933, 321], [71, 328]]] +00233634.jpg [[[215, 639], [261, 639], [261, 703], [215, 703]], [[523, 635], [570, 635], [570, 695], [523, 695]], [[643, 523], [682, 523], [682, 568], [643, 568]], [[97, 516], [152, 516], [152, 589], [97, 589]], [[755, 395], [760, 395], [760, 401], [755, 401]], [[26, 395], [32, 395], [32, 400], [26, 400]], [[678, 364], [728, 362], [731, 430], [681, 432]], [[54, 361], [107, 361], [107, 434], [54, 434]], [[78, 208], [155, 208], [155, 280], [78, 280]], [[643, 205], [693, 205], [693, 272], [643, 272]], [[210, 88], [260, 86], [263, 164], [213, 166]], [[363, 48], [426, 45], [430, 115], [367, 118]]] +00234400.jpg [[[446, 421], [738, 421], [738, 438], [446, 438]], [[157, 421], [454, 421], [454, 438], [157, 438]], [[158, 394], [652, 394], [652, 411], [158, 411]], [[40, 391], [127, 391], [127, 412], [40, 412]], [[158, 342], [304, 345], [304, 363], [158, 360]], [[38, 344], [123, 344], [123, 362], [38, 362]], [[520, 295], [703, 295], [703, 314], [520, 314]], [[394, 292], [483, 290], [484, 314], [394, 317]], [[157, 293], [270, 293], [270, 313], [157, 313]], [[37, 293], [125, 293], [125, 313], [37, 313]], [[156, 243], [358, 243], [358, 267], [156, 267]], [[36, 243], [82, 243], [82, 269], [36, 269]], [[28, 151], [157, 151], [157, 174], [28, 174]], [[282, 98], [507, 98], [507, 111], [282, 111]], [[315, 46], [475, 50], [474, 88], [314, 85]], [[518, 51], [663, 53], [662, 67], [517, 65]], [[487, 19], [706, 17], [706, 43], [487, 45]]] +00234883.jpg [[[66, 125], [316, 120], [317, 190], [67, 195]], [[79, 138], [109, 140], [108, 151], [78, 148]], [[72, 120], [120, 120], [120, 130], [72, 130]], [[383, 63], [504, 62], [504, 74], [383, 75]], [[58, 29], [365, 26], [366, 112], [59, 115]], [[387, 28], [501, 26], [501, 45], [387, 47]]] +test_add_0.jpg [[[311, 521], [391, 521], [391, 534], [311, 534]], [[277, 500], [424, 500], [424, 514], [277, 514]], [[261, 446], [437, 446], [437, 459], [261, 459]], [[212, 428], [485, 428], [485, 441], [212, 441]], [[247, 388], [457, 388], [457, 409], [247, 409]], [[222, 328], [474, 328], [474, 372], [222, 372]], [[208, 207], [492, 211], [490, 277], [207, 272]], [[266, 164], [422, 166], [421, 197], [265, 195]], [[18, 20], [201, 18], [201, 43], [18, 45]]] +test_add_1.png [] +test_add_10.png [[[157, 124], [186, 124], [186, 172], [157, 172]], [[65, 117], [95, 117], [95, 168], [65, 168]], [[161, 106], [183, 106], [183, 127], [161, 127]], [[69, 100], [94, 100], [94, 128], [69, 128]], [[117, 46], [154, 45], [157, 174], [121, 175]], [[66, 34], [97, 34], [97, 112], [66, 112]]] +test_add_11.jpg [[[1525, 773], [1564, 756], [1575, 780], [1536, 798]], [[1390, 757], [1483, 757], [1483, 791], [1390, 791]], [[1013, 754], [1207, 754], [1207, 800], [1013, 800]], [[685, 755], [875, 755], [875, 796], [685, 796]], [[356, 753], [566, 747], [567, 793], [358, 798]], [[78, 751], [264, 745], [265, 793], [79, 798]], [[602, 647], [1152, 647], [1152, 703], [602, 703]], [[601, 564], [1148, 555], [1149, 611], [602, 620]], [[598, 480], [1066, 472], [1067, 526], [599, 535]], [[598, 393], [1090, 388], [1090, 439], [599, 444]], [[603, 306], [1057, 306], [1057, 357], [603, 357]], [[357, 184], [1515, 184], [1515, 261], [357, 261]], [[60, 43], [257, 37], [259, 83], [61, 89]], [[1305, 41], [1492, 41], [1492, 87], [1305, 87]], [[973, 40], [1171, 34], [1172, 80], [974, 86]], [[670, 40], [862, 34], [864, 80], [671, 86]], [[363, 34], [558, 34], [558, 85], [363, 85]]] +test_add_12.jpg [[[11, 592], [136, 594], [135, 613], [11, 611]], [[109, 521], [907, 526], [907, 569], [109, 565]], [[635, 451], [902, 448], [903, 478], [635, 481]], [[112, 447], [466, 449], [466, 486], [112, 483]], [[582, 306], [680, 304], [681, 348], [583, 351]], [[369, 261], [565, 266], [563, 357], [367, 353]], [[64, 85], [853, 88], [853, 161], [64, 159]]] +test_add_13.jpg [[[68, 94], [117, 97], [116, 114], [67, 111]]] +test_add_14.jpg [[[30, 97], [235, 95], [236, 127], [31, 129]], [[30, 52], [239, 50], [239, 86], [30, 87]]] +test_add_15.jpg [[[141, 253], [353, 253], [353, 266], [141, 266]], [[205, 214], [406, 219], [406, 232], [204, 227]], [[106, 212], [193, 213], [193, 227], [106, 226]], [[154, 156], [286, 161], [286, 174], [154, 170]], [[148, 136], [305, 142], [305, 156], [147, 150]], [[108, 137], [145, 137], [145, 148], [108, 148]], [[108, 102], [275, 109], [275, 125], [107, 117]], [[107, 72], [245, 79], [245, 96], [106, 88]], [[107, 39], [209, 42], [209, 62], [106, 59]]] +test_add_16.jpg [[[398, 842], [408, 842], [408, 852], [398, 852]], [[382, 741], [746, 741], [746, 774], [382, 774]], [[362, 703], [468, 703], [468, 725], [362, 725]], [[1552, 701], [1576, 701], [1576, 746], [1552, 746]], [[1256, 695], [1442, 695], [1442, 721], [1256, 721]], [[1244, 661], [1448, 661], [1448, 687], [1244, 687]], [[386, 645], [668, 645], [668, 679], [386, 679]], [[1226, 625], [1470, 623], [1470, 651], [1226, 653]], [[360, 604], [580, 604], [580, 629], [360, 629]], [[1202, 592], [1494, 592], [1494, 617], [1202, 617]], [[1166, 556], [1530, 556], [1530, 582], [1166, 582]], [[380, 552], [638, 552], [638, 586], [380, 586]], [[356, 502], [516, 502], [516, 536], [356, 536]], [[774, 260], [1124, 260], [1124, 300], [774, 300]], [[374, 210], [504, 210], [504, 300], [374, 300]], [[776, 212], [1088, 217], [1088, 252], [776, 248]]] +test_add_17.jpg [[[321, 255], [393, 258], [392, 271], [320, 269]], [[307, 222], [411, 228], [411, 241], [306, 236]], [[96, 137], [385, 143], [384, 206], [94, 201]], [[72, 95], [399, 103], [398, 124], [71, 117]], [[68, 76], [224, 79], [223, 93], [67, 90]], [[66, 59], [226, 62], [225, 76], [65, 74]]] +test_add_18.jpg [[[466, 788], [715, 790], [715, 813], [466, 811]], [[553, 752], [665, 757], [663, 791], [552, 786]], [[119, 539], [189, 539], [189, 570], [119, 570]], [[116, 473], [674, 486], [673, 528], [115, 516]], [[121, 429], [669, 441], [668, 470], [121, 457]], [[122, 376], [673, 383], [673, 409], [122, 402]], [[556, 262], [675, 264], [675, 278], [556, 277]], [[165, 259], [335, 259], [335, 273], [165, 273]], [[344, 195], [456, 197], [455, 220], [343, 217]], [[309, 175], [490, 175], [490, 190], [309, 190]], [[255, 128], [537, 131], [537, 169], [254, 165]], [[347, 92], [486, 94], [486, 109], [346, 107]], [[285, 41], [567, 49], [566, 82], [284, 74]], [[236, 32], [266, 32], [266, 60], [236, 60]]] +test_add_19.jpg [[[24, 294], [42, 294], [42, 302], [24, 302]], [[64, 293], [105, 293], [105, 303], [64, 303]], [[145, 287], [163, 287], [163, 304], [145, 304]], [[63, 280], [106, 280], [106, 290], [63, 290]], [[9, 281], [26, 281], [26, 288], [9, 288]], [[220, 279], [245, 279], [245, 291], [220, 291]], [[177, 279], [208, 279], [208, 290], [177, 290]], [[23, 279], [51, 279], [51, 290], [23, 290]], [[145, 278], [162, 278], [162, 292], [145, 292]], [[8, 267], [18, 267], [18, 276], [8, 276]], [[221, 265], [243, 265], [243, 277], [221, 277]], [[24, 265], [47, 265], [47, 277], [24, 277]], [[142, 263], [163, 263], [163, 279], [142, 279]], [[218, 252], [249, 252], [249, 265], [218, 265]], [[65, 253], [131, 253], [131, 263], [65, 263]], [[24, 252], [43, 252], [43, 264], [24, 264]], [[8, 253], [18, 253], [18, 262], [8, 262]], [[8, 240], [17, 240], [17, 249], [8, 249]], [[63, 237], [114, 237], [114, 251], [63, 251]], [[25, 236], [47, 239], [45, 251], [23, 249]], [[144, 234], [166, 237], [163, 253], [142, 249]], [[494, 226], [531, 226], [531, 239], [494, 239]], [[335, 226], [354, 226], [354, 237], [335, 237]], [[288, 226], [314, 226], [314, 237], [288, 237]], [[63, 226], [113, 226], [113, 236], [63, 236]], [[7, 227], [17, 227], [17, 234], [7, 234]], [[221, 225], [248, 225], [248, 235], [221, 235]], [[143, 225], [165, 222], [167, 234], [145, 237]], [[24, 224], [48, 224], [48, 238], [24, 238]], [[495, 213], [524, 213], [524, 224], [495, 224]], [[420, 212], [437, 212], [437, 225], [420, 225]], [[336, 212], [398, 212], [398, 223], [336, 223]], [[292, 212], [320, 212], [320, 223], [292, 223]], [[222, 212], [249, 212], [249, 223], [222, 223]], [[145, 212], [166, 212], [166, 223], [145, 223]], [[61, 211], [113, 209], [114, 222], [62, 224]], [[26, 211], [48, 211], [48, 223], [26, 223]], [[337, 199], [383, 199], [383, 209], [337, 209]], [[65, 200], [87, 200], [87, 207], [65, 207]], [[493, 197], [541, 197], [541, 211], [493, 211]], [[445, 202], [455, 196], [462, 206], [452, 212]], [[178, 198], [205, 198], [205, 208], [178, 208]], [[146, 199], [157, 199], [157, 208], [146, 208]], [[32, 194], [43, 204], [33, 214], [22, 203]], [[422, 193], [440, 201], [432, 215], [415, 207]], [[64, 186], [131, 186], [131, 196], [64, 196]], [[337, 185], [399, 185], [399, 196], [337, 196]], [[445, 190], [456, 182], [465, 191], [454, 200]], [[292, 188], [308, 182], [313, 193], [297, 200]], [[220, 183], [255, 183], [255, 197], [220, 197]], [[142, 184], [158, 184], [158, 197], [142, 197]], [[493, 182], [518, 182], [518, 197], [493, 197]], [[425, 180], [437, 191], [427, 202], [414, 190]], [[32, 179], [42, 189], [32, 199], [22, 189]], [[182, 179], [196, 185], [188, 199], [175, 193]], [[335, 172], [400, 169], [400, 183], [336, 185]], [[492, 170], [519, 170], [519, 185], [492, 185]], [[412, 177], [428, 164], [440, 178], [425, 190]], [[293, 171], [315, 171], [315, 185], [293, 185]], [[220, 170], [251, 170], [251, 184], [220, 184]], [[178, 172], [188, 172], [188, 183], [178, 183]], [[64, 172], [125, 170], [125, 181], [64, 182]], [[454, 168], [464, 176], [454, 185], [445, 176]], [[145, 168], [163, 172], [159, 185], [142, 180]], [[30, 165], [43, 174], [34, 186], [20, 177]], [[493, 160], [523, 160], [523, 170], [493, 170]], [[402, 161], [435, 161], [435, 168], [402, 168]], [[335, 159], [401, 159], [401, 169], [335, 169]], [[296, 159], [325, 159], [325, 170], [296, 170]], [[221, 158], [251, 158], [251, 169], [221, 169]], [[174, 161], [183, 156], [190, 167], [181, 172]], [[145, 158], [162, 158], [162, 170], [145, 170]], [[61, 158], [125, 157], [125, 168], [62, 169]], [[20, 161], [33, 154], [40, 167], [28, 174]], [[492, 143], [542, 143], [542, 157], [492, 157]], [[450, 144], [479, 144], [479, 157], [450, 157]], [[335, 143], [439, 143], [439, 156], [335, 156]], [[294, 143], [327, 143], [327, 157], [294, 157]], [[220, 143], [253, 143], [253, 157], [220, 157]], [[178, 145], [187, 145], [187, 156], [178, 156]], [[63, 144], [104, 144], [104, 155], [63, 155]], [[144, 140], [164, 145], [160, 159], [141, 154]], [[31, 137], [44, 149], [31, 162], [17, 149]], [[286, 135], [291, 135], [291, 140], [286, 140]], [[177, 133], [193, 133], [193, 144], [177, 144]], [[336, 132], [388, 132], [388, 141], [336, 141]], [[492, 131], [525, 131], [525, 141], [492, 141]], [[450, 131], [477, 131], [477, 141], [450, 141]], [[292, 131], [321, 131], [321, 141], [292, 141]], [[218, 132], [255, 130], [256, 141], [219, 144]], [[63, 131], [95, 131], [95, 141], [63, 141]], [[417, 130], [437, 130], [437, 141], [417, 141]], [[145, 130], [159, 130], [159, 143], [145, 143]], [[30, 124], [43, 133], [32, 147], [19, 138]], [[493, 118], [535, 118], [535, 129], [493, 129]], [[335, 118], [387, 118], [387, 129], [335, 129]], [[218, 118], [255, 118], [255, 128], [218, 128]], [[451, 117], [478, 117], [478, 129], [451, 129]], [[418, 117], [438, 117], [438, 130], [418, 130]], [[177, 116], [209, 116], [209, 130], [177, 130]], [[145, 117], [162, 117], [162, 130], [145, 130]], [[62, 116], [88, 116], [88, 131], [62, 131]], [[19, 121], [33, 111], [43, 124], [29, 134]], [[491, 107], [523, 107], [523, 113], [491, 113]], [[449, 107], [477, 107], [477, 113], [449, 113]], [[420, 107], [436, 107], [436, 113], [420, 113]], [[295, 107], [319, 107], [319, 114], [295, 114]], [[220, 107], [242, 107], [242, 113], [220, 113]], [[176, 107], [203, 107], [203, 113], [176, 113]], [[145, 107], [161, 107], [161, 114], [145, 114]], [[334, 105], [372, 105], [372, 114], [334, 114]], [[63, 106], [86, 106], [86, 113], [63, 113]], [[483, 89], [522, 89], [522, 99], [483, 99]], [[331, 88], [380, 88], [380, 99], [331, 99]], [[276, 88], [325, 88], [325, 99], [276, 99]], [[214, 88], [246, 88], [246, 99], [214, 99]], [[411, 86], [474, 86], [474, 100], [411, 100]], [[6, 86], [102, 86], [102, 100], [6, 100]], [[415, 66], [461, 66], [461, 77], [415, 77]], [[288, 66], [333, 66], [333, 77], [288, 77]], [[157, 64], [206, 64], [206, 78], [157, 78]], [[33, 61], [43, 66], [37, 77], [27, 72]], [[416, 48], [523, 49], [523, 63], [415, 62]], [[288, 49], [375, 49], [375, 63], [288, 63]], [[159, 49], [269, 49], [269, 62], [159, 62]], [[24, 53], [36, 46], [45, 59], [33, 67]], [[416, 36], [481, 36], [481, 46], [416, 46]], [[25, 38], [39, 32], [46, 45], [32, 52]], [[157, 34], [205, 34], [205, 47], [157, 47]], [[412, 4], [527, 4], [527, 17], [412, 17]], [[146, 4], [345, 2], [345, 15], [146, 17]]] +test_add_20.jpg [[[31, 346], [605, 346], [605, 370], [31, 370]], [[217, 294], [510, 294], [510, 322], [217, 322]], [[473, 271], [525, 271], [525, 286], [473, 286]], [[220, 267], [287, 267], [287, 286], [220, 286]], [[219, 239], [484, 239], [484, 263], [219, 263]], [[221, 217], [303, 217], [303, 234], [221, 234]], [[402, 192], [417, 192], [417, 205], [402, 205]], [[222, 187], [341, 187], [341, 207], [222, 207]], [[221, 162], [287, 162], [287, 180], [221, 180]], [[375, 122], [475, 124], [475, 146], [375, 143]], [[222, 124], [356, 122], [356, 143], [222, 146]], [[218, 81], [352, 84], [352, 116], [218, 113]], [[440, 34], [605, 34], [605, 59], [440, 59]], [[72, 16], [398, 16], [398, 44], [72, 44]]] +test_add_3.jpg [[[169, 327], [337, 326], [337, 341], [169, 342]], [[170, 288], [307, 290], [307, 312], [170, 310]], [[171, 221], [323, 221], [323, 234], [171, 234]], [[340, 221], [449, 217], [449, 231], [341, 234]], [[169, 201], [372, 201], [372, 214], [169, 214]], [[170, 183], [418, 183], [418, 196], [170, 196]], [[170, 149], [416, 149], [416, 163], [170, 163]], [[171, 119], [418, 119], [418, 140], [171, 140]], [[326, 64], [478, 64], [478, 91], [326, 91]], [[173, 64], [306, 60], [306, 89], [174, 93]]] +test_add_4.png [] +test_add_5.png [[[48, 164], [108, 164], [108, 174], [48, 174]], [[52, 121], [169, 121], [169, 134], [52, 134]], [[50, 102], [165, 102], [165, 118], [50, 118]], [[52, 83], [164, 83], [164, 100], [52, 100]], [[51, 68], [166, 68], [166, 84], [51, 84]], [[51, 50], [145, 47], [145, 64], [52, 67]]] +test_add_6.jpg [[[123, 223], [219, 227], [218, 251], [122, 247]], [[172, 172], [186, 186], [172, 200], [158, 186]]] +test_add_7.jpg [[[48, 938], [174, 936], [174, 962], [48, 964]], [[227, 873], [629, 876], [628, 953], [226, 949]], [[56, 744], [637, 744], [637, 789], [56, 789]], [[150, 674], [545, 678], [544, 721], [150, 718]], [[73, 504], [633, 504], [633, 601], [73, 601]], [[59, 270], [655, 279], [652, 441], [56, 432]], [[513, 193], [553, 193], [553, 223], [513, 223]], [[61, 175], [532, 175], [532, 239], [61, 239]], [[533, 178], [642, 178], [642, 236], [533, 236]]] +test_add_8.jpg [[[251, 586], [454, 580], [454, 606], [252, 613]], [[107, 533], [457, 527], [457, 560], [108, 566]], [[336, 494], [384, 494], [384, 507], [336, 507]], [[27, 307], [355, 297], [356, 320], [28, 330]], [[22, 259], [445, 251], [445, 274], [23, 282]], [[78, 209], [445, 205], [445, 225], [78, 229]], [[160, 23], [319, 30], [317, 79], [158, 72]]] +test_add_9.png [[[266, 687], [486, 687], [486, 696], [266, 696]], [[196, 668], [554, 668], [554, 681], [196, 681]], [[153, 596], [597, 596], [597, 606], [153, 606]], [[215, 578], [541, 578], [541, 588], [215, 588]], [[85, 542], [664, 542], [664, 552], [85, 552]], [[96, 522], [653, 522], [653, 535], [96, 535]], [[362, 449], [389, 449], [389, 460], [362, 460]], [[238, 376], [513, 376], [513, 389], [238, 389]], [[177, 356], [574, 356], [574, 368], [177, 368]], [[344, 281], [408, 283], [407, 297], [343, 294]], [[256, 205], [492, 205], [492, 219], [256, 219]]] diff --git a/test_tipc/supplementary/__init__.py b/test_tipc/supplementary/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_tipc/supplementary/config.py b/test_tipc/supplementary/config.py new file mode 100644 index 0000000..635a2b3 --- /dev/null +++ b/test_tipc/supplementary/config.py @@ -0,0 +1,138 @@ +import numpy as np +import os +import sys +import platform +import yaml +import time +import shutil +import paddle +import paddle.distributed as dist +from tqdm import tqdm +from argparse import ArgumentParser, RawDescriptionHelpFormatter +from utils import get_logger, print_dict + + +class ArgsParser(ArgumentParser): + def __init__(self): + super(ArgsParser, self).__init__(formatter_class=RawDescriptionHelpFormatter) + self.add_argument("-c", "--config", help="configuration file to use") + self.add_argument("-o", "--opt", nargs="+", help="set configuration options") + self.add_argument( + "-p", + "--profiler_options", + type=str, + default=None, + help='The option of profiler, which should be in format "key1=value1;key2=value2;key3=value3".', + ) + + def parse_args(self, argv=None): + args = super(ArgsParser, self).parse_args(argv) + assert args.config is not None, "Please specify --config=configure_file_path." + args.opt = self._parse_opt(args.opt) + return args + + def _parse_opt(self, opts): + config = {} + if not opts: + return config + for s in opts: + s = s.strip() + k, v = s.split("=") + config[k] = yaml.load(v, Loader=yaml.SafeLoader) + return config + + +class AttrDict(dict): + """Single level attribute dict, NOT recursive""" + + def __init__(self, **kwargs): + super(AttrDict, self).__init__() + super(AttrDict, self).update(kwargs) + + def __getattr__(self, key): + if key in self: + return self[key] + raise AttributeError("object has no attribute '{}'".format(key)) + + +global_config = AttrDict() + +default_config = { + "Global": { + "debug": False, + } +} + + +def load_config(file_path): + """ + Load config from yml/yaml file. + Args: + file_path (str): Path of the config file to be loaded. + Returns: global config + """ + merge_config(default_config) + _, ext = os.path.splitext(file_path) + assert ext in [".yml", ".yaml"], "only support yaml files for now" + merge_config(yaml.load(open(file_path, "rb"), Loader=yaml.SafeLoader)) + return global_config + + +def merge_config(config): + """ + Merge config into global config. + Args: + config (dict): Config to be merged. + Returns: global config + """ + for key, value in config.items(): + if "." not in key: + if isinstance(value, dict) and key in global_config: + global_config[key].update(value) + else: + global_config[key] = value + else: + sub_keys = key.split(".") + assert ( + sub_keys[0] in global_config + ), "the sub_keys can only be one of global_config: {}, but get: {}, please check your running command".format( + global_config.keys(), sub_keys[0] + ) + cur = global_config[sub_keys[0]] + for idx, sub_key in enumerate(sub_keys[1:]): + if idx == len(sub_keys) - 2: + cur[sub_key] = value + else: + cur = cur[sub_key] + + +def preprocess(is_train=False): + FLAGS = ArgsParser().parse_args() + profiler_options = FLAGS.profiler_options + config = load_config(FLAGS.config) + merge_config(FLAGS.opt) + profile_dic = {"profiler_options": FLAGS.profiler_options} + merge_config(profile_dic) + + if is_train: + # save_config + save_model_dir = config["save_model_dir"] + os.makedirs(save_model_dir, exist_ok=True) + with open(os.path.join(save_model_dir, "config.yml"), "w") as f: + yaml.dump(dict(config), f, default_flow_style=False, sort_keys=False) + log_file = "{}/train.log".format(save_model_dir) + else: + log_file = None + logger = get_logger(log_file=log_file) + + # check if set use_gpu=True in paddlepaddle cpu version + use_gpu = config["use_gpu"] + + print_dict(config, logger) + + return config, logger + + +if __name__ == "__main__": + config, logger = preprocess(is_train=False) + # print(config) diff --git a/test_tipc/supplementary/custom_op/custom_relu_op.cc b/test_tipc/supplementary/custom_op/custom_relu_op.cc new file mode 100644 index 0000000..6c48da7 --- /dev/null +++ b/test_tipc/supplementary/custom_op/custom_relu_op.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// reference from : +// https://github.com/PaddlePaddle/Paddle-Inference-Demo/blob/master/python/custom-operator/custom_relu_op.cc +#include +#include + +#include "paddle/extension.h" + +template +void relu_cpu_forward_kernel(const data_t *x_data, data_t *out_data, + int64_t x_numel) { + for (int i = 0; i < x_numel; ++i) { + out_data[i] = std::max(static_cast(0.), x_data[i]); + } +} + +template +void relu_cpu_backward_kernel(const data_t *grad_out_data, + const data_t *out_data, data_t *grad_x_data, + int64_t out_numel) { + for (int i = 0; i < out_numel; ++i) { + grad_x_data[i] = + grad_out_data[i] * (out_data[i] > static_cast(0) ? 1. : 0.); + } +} + +std::vector relu_cpu_forward(const paddle::Tensor &x) { + auto out = paddle::Tensor(paddle::PlaceType::kCPU); + + out.reshape(x.shape()); + PD_DISPATCH_FLOATING_TYPES( + x.type(), "relu_cpu_forward", ([&] { + relu_cpu_forward_kernel( + x.data(), out.mutable_data(x.place()), x.size()); + })); + + return {out}; +} + +std::vector relu_cpu_backward(const paddle::Tensor &x, + const paddle::Tensor &out, + const paddle::Tensor &grad_out) { + auto grad_x = paddle::Tensor(paddle::PlaceType::kCPU); + grad_x.reshape(x.shape()); + + PD_DISPATCH_FLOATING_TYPES(out.type(), "relu_cpu_backward", ([&] { + relu_cpu_backward_kernel( + grad_out.data(), out.data(), + grad_x.mutable_data(x.place()), + out.size()); + })); + + return {grad_x}; +} + +std::vector relu_cuda_forward(const paddle::Tensor &x); +std::vector relu_cuda_backward(const paddle::Tensor &x, + const paddle::Tensor &out, + const paddle::Tensor &grad_out); + +std::vector ReluForward(const paddle::Tensor &x) { + // TODO(chenweihang): Check Input + if (x.place() == paddle::PlaceType::kCPU) { + return relu_cpu_forward(x); + } else if (x.place() == paddle::PlaceType::kGPU) { + return relu_cuda_forward(x); + } else { + throw std::runtime_error("Not implemented."); + } +} + +std::vector ReluBackward(const paddle::Tensor &x, + const paddle::Tensor &out, + const paddle::Tensor &grad_out) { + // TODO(chenweihang): Check Input + if (x.place() == paddle::PlaceType::kCPU) { + return relu_cpu_backward(x, out, grad_out); + } else if (x.place() == paddle::PlaceType::kGPU) { + return relu_cuda_backward(x, out, grad_out); + } else { + throw std::runtime_error("Not implemented."); + } +} + +PD_BUILD_OP(custom_relu) + .Inputs({"X"}) + .Outputs({"Out"}) + .SetKernelFn(PD_KERNEL(ReluForward)); + +PD_BUILD_GRAD_OP(custom_relu) + .Inputs({"X", "Out", paddle::Grad("Out")}) + .Outputs({paddle::Grad("X")}) + .SetKernelFn(PD_KERNEL(ReluBackward)); diff --git a/test_tipc/supplementary/custom_op/custom_relu_op.cu b/test_tipc/supplementary/custom_op/custom_relu_op.cu new file mode 100644 index 0000000..fc2792e --- /dev/null +++ b/test_tipc/supplementary/custom_op/custom_relu_op.cu @@ -0,0 +1,71 @@ +// Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// reference +// https://github.com/PaddlePaddle/Paddle-Inference-Demo/blob/master/python/custom-operator/custom_relu_op.cu + +#include "paddle/extension.h" + +template +__global__ void relu_cuda_forward_kernel(const data_t *x, data_t *y, + const int num) { + int gid = blockIdx.x * blockDim.x + threadIdx.x; + for (int i = gid; i < num; i += blockDim.x * gridDim.x) { + y[i] = max(x[i], static_cast(0.)); + } +} + +template +__global__ void relu_cuda_backward_kernel(const data_t *dy, const data_t *y, + data_t *dx, const int num) { + int gid = blockIdx.x * blockDim.x + threadIdx.x; + for (int i = gid; i < num; i += blockDim.x * gridDim.x) { + dx[i] = dy[i] * (y[i] > 0 ? 1. : 0.); + } +} + +std::vector relu_cuda_forward(const paddle::Tensor &x) { + auto out = paddle::Tensor(paddle::PlaceType::kGPU); + + out.reshape(x.shape()); + int numel = x.size(); + int block = 512; + int grid = (numel + block - 1) / block; + PD_DISPATCH_FLOATING_TYPES( + x.type(), "relu_cuda_forward_kernel", ([&] { + relu_cuda_forward_kernel<<>>( + x.data(), out.mutable_data(x.place()), numel); + })); + + return {out}; +} + +std::vector relu_cuda_backward(const paddle::Tensor &x, + const paddle::Tensor &out, + const paddle::Tensor &grad_out) { + auto grad_x = paddle::Tensor(paddle::PlaceType::kGPU); + grad_x.reshape(x.shape()); + + int numel = out.size(); + int block = 512; + int grid = (numel + block - 1) / block; + PD_DISPATCH_FLOATING_TYPES( + out.type(), "relu_cuda_backward_kernel", ([&] { + relu_cuda_backward_kernel<<>>( + grad_out.data(), out.data(), + grad_x.mutable_data(x.place()), numel); + })); + + return {grad_x}; +} diff --git a/test_tipc/supplementary/custom_op/test.py b/test_tipc/supplementary/custom_op/test.py new file mode 100644 index 0000000..df8d939 --- /dev/null +++ b/test_tipc/supplementary/custom_op/test.py @@ -0,0 +1,77 @@ +import paddle +import paddle.nn as nn +from paddle.vision.transforms import Compose, Normalize +from paddle.utils.cpp_extension import load +from paddle.inference import Config +from paddle.inference import create_predictor +import numpy as np + +EPOCH_NUM = 4 +BATCH_SIZE = 64 + +# jit compile custom op +custom_ops = load( + name="custom_jit_ops", sources=["custom_relu_op.cc", "custom_relu_op.cu"] +) + + +class LeNet(nn.Layer): + def __init__(self): + super(LeNet, self).__init__() + self.conv1 = nn.Conv2D( + in_channels=1, out_channels=6, kernel_size=5, stride=1, padding=2 + ) + self.max_pool1 = nn.MaxPool2D(kernel_size=2, stride=2) + self.conv2 = nn.Conv2D(in_channels=6, out_channels=16, kernel_size=5, stride=1) + self.max_pool2 = nn.MaxPool2D(kernel_size=2, stride=2) + self.linear1 = nn.Linear(in_features=16 * 5 * 5, out_features=120) + self.linear2 = nn.Linear(in_features=120, out_features=84) + self.linear3 = nn.Linear(in_features=84, out_features=10) + + def forward(self, x): + x = self.conv1(x) + x = custom_ops.custom_relu(x) + x = self.max_pool1(x) + x = custom_ops.custom_relu(x) + x = self.conv2(x) + x = self.max_pool2(x) + x = paddle.flatten(x, start_axis=1, stop_axis=-1) + x = self.linear1(x) + x = custom_ops.custom_relu(x) + x = self.linear2(x) + x = custom_ops.custom_relu(x) + x = self.linear3(x) + return x + + +# set device +paddle.set_device("gpu") + +# model +net = LeNet() +loss_fn = nn.CrossEntropyLoss() +opt = paddle.optimizer.Adam(learning_rate=0.001, parameters=net.parameters()) + +# data loader +transform = Compose([Normalize(mean=[127.5], std=[127.5], data_format="CHW")]) +train_dataset = paddle.vision.datasets.MNIST(mode="train", transform=transform) +train_loader = paddle.io.DataLoader( + train_dataset, batch_size=BATCH_SIZE, shuffle=True, drop_last=True, num_workers=2 +) + +# train +for epoch_id in range(EPOCH_NUM): + for batch_id, (image, label) in enumerate(train_loader()): + out = net(image) + loss = loss_fn(out, label) + loss.backward() + + if batch_id % 300 == 0: + print( + "Epoch {} batch {}: loss = {}".format( + epoch_id, batch_id, np.mean(loss.numpy()) + ) + ) + + opt.step() + opt.clear_grad() diff --git a/test_tipc/supplementary/data.py b/test_tipc/supplementary/data.py new file mode 100644 index 0000000..02c814d --- /dev/null +++ b/test_tipc/supplementary/data.py @@ -0,0 +1,133 @@ +import numpy as np +import paddle +import os +import cv2 +import glob + + +def transform(data, ops=None): + """transform""" + if ops is None: + ops = [] + for op in ops: + data = op(data) + if data is None: + return None + return data + + +def create_operators(op_param_list, global_config=None): + """ + create operators based on the config + Args: + params(list): a dict list, used to create some operators + """ + assert isinstance(op_param_list, list), "operator config should be a list" + ops = [] + for operator in op_param_list: + assert isinstance(operator, dict) and len(operator) == 1, "yaml format error" + op_name = list(operator)[0] + param = {} if operator[op_name] is None else operator[op_name] + if global_config is not None: + param.update(global_config) + op = eval(op_name)(**param) + ops.append(op) + return ops + + +class DecodeImage(object): + """decode image""" + + def __init__(self, img_mode="RGB", channel_first=False, **kwargs): + self.img_mode = img_mode + self.channel_first = channel_first + + def __call__(self, data): + img = data["image"] + assert type(img) is bytes and len(img) > 0, "invalid input 'img' in DecodeImage" + img = np.frombuffer(img, dtype="uint8") + img = cv2.imdecode(img, 1) + if img is None: + return None + if self.img_mode == "GRAY": + img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) + elif self.img_mode == "RGB": + assert img.shape[2] == 3, "invalid shape of image[%s]" % (img.shape) + img = img[:, :, ::-1] + + if self.channel_first: + img = img.transpose((2, 0, 1)) + + data["image"] = img + data["src_image"] = img + return data + + +class NormalizeImage(object): + """normalize image such as subtract mean, divide std""" + + def __init__(self, scale=None, mean=None, std=None, order="chw", **kwargs): + if isinstance(scale, str): + scale = eval(scale) + self.scale = np.float32(scale if scale is not None else 1.0 / 255.0) + mean = mean if mean is not None else [0.485, 0.456, 0.406] + std = std if std is not None else [0.229, 0.224, 0.225] + + shape = (3, 1, 1) if order == "chw" else (1, 1, 3) + self.mean = np.array(mean).reshape(shape).astype("float32") + self.std = np.array(std).reshape(shape).astype("float32") + + def __call__(self, data): + img = data["image"] + from PIL import Image + + if isinstance(img, Image.Image): + img = np.array(img) + assert isinstance(img, np.ndarray), "invalid input 'img' in NormalizeImage" + data["image"] = (img.astype("float32") * self.scale - self.mean) / self.std + return data + + +class ToCHWImage(object): + """convert hwc image to chw image""" + + def __init__(self, **kwargs): + pass + + def __call__(self, data): + img = data["image"] + from PIL import Image + + if isinstance(img, Image.Image): + img = np.array(img) + data["image"] = img.transpose((2, 0, 1)) + + src_img = data["src_image"] + from PIL import Image + + if isinstance(img, Image.Image): + src_img = np.array(src_img) + data["src_image"] = img.transpose((2, 0, 1)) + + return data + + +class SimpleDataset(nn.Dataset): + def __init__(self, config, mode, logger, seed=None): + self.logger = logger + self.mode = mode.lower() + + data_dir = config["Train"]["data_dir"] + + imgs_list = self.get_image_list(data_dir) + + self.ops = create_operators(cfg["transforms"], None) + + def get_image_list(self, img_dir): + imgs = glob.glob(os.path.join(img_dir, "*.png")) + if len(imgs) == 0: + raise ValueError(f"not any images founded in {img_dir}") + return imgs + + def __getitem__(self, idx): + return None diff --git a/test_tipc/supplementary/data_loader.py b/test_tipc/supplementary/data_loader.py new file mode 100644 index 0000000..2e40662 --- /dev/null +++ b/test_tipc/supplementary/data_loader.py @@ -0,0 +1,60 @@ +import numpy as np +from paddle.vision.datasets import Cifar100 +from paddle.vision.transforms import Normalize +import signal +import os +from paddle.io import Dataset, DataLoader, DistributedBatchSampler + + +def term_mp(sig_num, frame): + """kill all child processes""" + pid = os.getpid() + pgid = os.getpgid(os.getpid()) + print("main proc {} exit, kill process group " "{}".format(pid, pgid)) + os.killpg(pgid, signal.SIGKILL) + return + + +def build_dataloader(mode, batch_size=4, seed=None, num_workers=0, device="gpu:0"): + normalize = Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], data_format="HWC") + + if mode.lower() == "train": + dataset = Cifar100(mode=mode, transform=normalize) + elif mode.lower() in ["test", "valid", "eval"]: + dataset = Cifar100(mode="test", transform=normalize) + else: + raise ValueError(f"{mode} should be one of ['train', 'test']") + + # define batch sampler + batch_sampler = DistributedBatchSampler( + dataset=dataset, batch_size=batch_size, shuffle=False, drop_last=True + ) + + data_loader = DataLoader( + dataset=dataset, + batch_sampler=batch_sampler, + places=device, + num_workers=num_workers, + return_list=True, + use_shared_memory=False, + ) + + # support exit using ctrl+c + signal.signal(signal.SIGINT, term_mp) + signal.signal(signal.SIGTERM, term_mp) + + return data_loader + + +# cifar100 = Cifar100(mode='train', transform=normalize) + +# data = cifar100[0] + +# image, label = data + +# reader = build_dataloader('train') + +# for idx, data in enumerate(reader): +# print(idx, data[0].shape, data[1].shape) +# if idx >= 10: +# break diff --git a/test_tipc/supplementary/load_cifar.py b/test_tipc/supplementary/load_cifar.py new file mode 100644 index 0000000..35d0ffa --- /dev/null +++ b/test_tipc/supplementary/load_cifar.py @@ -0,0 +1,40 @@ +import pickle as p +import numpy as np +from PIL import Image + + +def load_CIFAR_batch(filename): + """load single batch of cifar""" + with open(filename, "rb") as f: + datadict = p.load(f, encoding="bytes") + # 以字典的形式取出数据 + X = datadict[b"data"] + Y = datadict[b"fine_labels"] + try: + X = X.reshape(10000, 3, 32, 32) + except: + X = X.reshape(50000, 3, 32, 32) + Y = np.array(Y) + print(Y.shape) + return X, Y + + +if __name__ == "__main__": + mode = "train" + imgX, imgY = load_CIFAR_batch(f"./cifar-100-python/{mode}") + with open(f"./cifar-100-python/{mode}_imgs/img_label.txt", "a+") as f: + for i in range(imgY.shape[0]): + f.write("img" + str(i) + " " + str(imgY[i]) + "\n") + + for i in range(imgX.shape[0]): + imgs = imgX[i] + img0 = imgs[0] + img1 = imgs[1] + img2 = imgs[2] + i0 = Image.fromarray(img0) + i1 = Image.fromarray(img1) + i2 = Image.fromarray(img2) + img = Image.merge("RGB", (i0, i1, i2)) + name = "img" + str(i) + ".png" + img.save(f"./cifar-100-python/{mode}_imgs/" + name, "png") + print("save successfully!") diff --git a/test_tipc/supplementary/loss.py b/test_tipc/supplementary/loss.py new file mode 100644 index 0000000..f139a71 --- /dev/null +++ b/test_tipc/supplementary/loss.py @@ -0,0 +1,131 @@ +import paddle +import paddle.nn.functional as F + + +class Loss(object): + """ + Loss + """ + + def __init__(self, class_dim=1000, epsilon=None): + assert class_dim > 1, "class_dim=%d is not larger than 1" % (class_dim) + self._class_dim = class_dim + if epsilon is not None and epsilon >= 0.0 and epsilon <= 1.0: + self._epsilon = epsilon + self._label_smoothing = True + else: + self._epsilon = None + self._label_smoothing = False + + def _labelsmoothing(self, target): + if target.shape[-1] != self._class_dim: + one_hot_target = F.one_hot(target, self._class_dim) + else: + one_hot_target = target + soft_target = F.label_smooth(one_hot_target, epsilon=self._epsilon) + soft_target = paddle.reshape(soft_target, shape=[-1, self._class_dim]) + return soft_target + + def _crossentropy(self, input, target, use_pure_fp16=False): + if self._label_smoothing: + target = self._labelsmoothing(target) + input = -F.log_softmax(input, axis=-1) + cost = paddle.sum(target * input, axis=-1) + else: + cost = F.cross_entropy(input=input, label=target) + if use_pure_fp16: + avg_cost = paddle.sum(cost) + else: + avg_cost = paddle.mean(cost) + return avg_cost + + def __call__(self, input, target): + return self._crossentropy(input, target) + + +def build_loss(config, epsilon=None): + class_dim = config["class_dim"] + loss_func = Loss(class_dim=class_dim, epsilon=epsilon) + return loss_func + + +class LossDistill(Loss): + def __init__(self, model_name_list, class_dim=1000, epsilon=None): + assert class_dim > 1, "class_dim=%d is not larger than 1" % (class_dim) + self._class_dim = class_dim + if epsilon is not None and epsilon >= 0.0 and epsilon <= 1.0: + self._epsilon = epsilon + self._label_smoothing = True + else: + self._epsilon = None + self._label_smoothing = False + + self.model_name_list = model_name_list + assert len(self.model_name_list) > 1, "error" + + def __call__(self, input, target): + losses = {} + for k in self.model_name_list: + inp = input[k] + losses[k] = self._crossentropy(inp, target) + return losses + + +class KLJSLoss(object): + def __init__(self, mode="kl"): + assert mode in [ + "kl", + "js", + "KL", + "JS", + ], "mode can only be one of ['kl', 'js', 'KL', 'JS']" + self.mode = mode + + def __call__(self, p1, p2, reduction="mean"): + p1 = F.softmax(p1, axis=-1) + p2 = F.softmax(p2, axis=-1) + + loss = paddle.multiply(p2, paddle.log((p2 + 1e-5) / (p1 + 1e-5) + 1e-5)) + + if self.mode.lower() == "js": + loss += paddle.multiply(p1, paddle.log((p1 + 1e-5) / (p2 + 1e-5) + 1e-5)) + loss *= 0.5 + if reduction == "mean": + loss = paddle.mean(loss) + elif reduction == "none" or reduction is None: + return loss + else: + loss = paddle.sum(loss) + return loss + + +class DMLLoss(object): + def __init__(self, model_name_pairs, mode="js"): + self.model_name_pairs = self._check_model_name_pairs(model_name_pairs) + self.kljs_loss = KLJSLoss(mode=mode) + + def _check_model_name_pairs(self, model_name_pairs): + if not isinstance(model_name_pairs, list): + return [] + elif isinstance(model_name_pairs[0], list) and isinstance( + model_name_pairs[0][0], str + ): + return model_name_pairs + else: + return [model_name_pairs] + + def __call__(self, predicts, target=None): + loss_dict = dict() + for pairs in self.model_name_pairs: + p1 = predicts[pairs[0]] + p2 = predicts[pairs[1]] + + loss_dict[pairs[0] + "_" + pairs[1]] = self.kljs_loss(p1, p2) + + return loss_dict + + +# def build_distill_loss(config, epsilon=None): +# class_dim = config['class_dim'] +# loss = LossDistill(model_name_list=['student', 'student1'], ) +# return loss_func diff --git a/test_tipc/supplementary/metric.py b/test_tipc/supplementary/metric.py new file mode 100644 index 0000000..4851afc --- /dev/null +++ b/test_tipc/supplementary/metric.py @@ -0,0 +1,60 @@ +import paddle +import paddle.nn.functional as F +from collections import OrderedDict + + +def create_metric( + out, + label, + architecture=None, + topk=5, + classes_num=1000, + use_distillation=False, + mode="train", +): + """ + Create measures of model accuracy, such as top1 and top5 + + Args: + out(variable): model output variable + feeds(dict): dict of model input variables(included label) + topk(int): usually top5 + classes_num(int): num of classes + use_distillation(bool): whether to use distillation training + mode(str): mode, train/valid + + Returns: + fetches(dict): dict of measures + """ + # if architecture["name"] == "GoogLeNet": + # assert len(out) == 3, "GoogLeNet should have 3 outputs" + # out = out[0] + # else: + # # just need student label to get metrics + # if use_distillation: + # out = out[1] + softmax_out = F.softmax(out) + + fetches = OrderedDict() + # set top1 to fetches + top1 = paddle.metric.accuracy(softmax_out, label=label, k=1) + # set topk to fetches + k = min(topk, classes_num) + topk = paddle.metric.accuracy(softmax_out, label=label, k=k) + + # multi cards' eval + if mode != "train" and paddle.distributed.get_world_size() > 1: + top1 = ( + paddle.distributed.all_reduce(top1, op=paddle.distributed.ReduceOp.SUM) + / paddle.distributed.get_world_size() + ) + topk = ( + paddle.distributed.all_reduce(topk, op=paddle.distributed.ReduceOp.SUM) + / paddle.distributed.get_world_size() + ) + + fetches["top1"] = top1 + topk_name = "top{}".format(k) + fetches[topk_name] = topk + + return fetches diff --git a/test_tipc/supplementary/mv3.py b/test_tipc/supplementary/mv3.py new file mode 100644 index 0000000..f891b86 --- /dev/null +++ b/test_tipc/supplementary/mv3.py @@ -0,0 +1,518 @@ +# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import paddle +from paddle import ParamAttr +import paddle.nn as nn +import paddle.nn.functional as F +from paddle.nn.functional import hardswish, hardsigmoid +from paddle.nn import Conv2D, BatchNorm, Linear, Dropout +from paddle.nn import AdaptiveAvgPool2D, MaxPool2D, AvgPool2D +from paddle.regularizer import L2Decay +import math + +from paddle.utils.cpp_extension import load + +# jit compile custom op +custom_ops = load( + name="custom_jit_ops", + sources=["./custom_op/custom_relu_op.cc", "./custom_op/custom_relu_op.cu"], +) + + +def make_divisible(v, divisor=8, min_value=None): + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + if new_v < 0.9 * v: + new_v += divisor + return new_v + + +class MobileNetV3(nn.Layer): + def __init__( + self, + scale=1.0, + model_name="small", + dropout_prob=0.2, + class_dim=1000, + use_custom_relu=False, + ): + super(MobileNetV3, self).__init__() + self.use_custom_relu = use_custom_relu + + inplanes = 16 + if model_name == "large": + self.cfg = [ + # k, exp, c, se, nl, s, + [3, 16, 16, False, "relu", 1], + [3, 64, 24, False, "relu", 2], + [3, 72, 24, False, "relu", 1], + [5, 72, 40, True, "relu", 2], + [5, 120, 40, True, "relu", 1], + [5, 120, 40, True, "relu", 1], + [3, 240, 80, False, "hardswish", 2], + [3, 200, 80, False, "hardswish", 1], + [3, 184, 80, False, "hardswish", 1], + [3, 184, 80, False, "hardswish", 1], + [3, 480, 112, True, "hardswish", 1], + [3, 672, 112, True, "hardswish", 1], + [5, 672, 160, True, "hardswish", 2], + [5, 960, 160, True, "hardswish", 1], + [5, 960, 160, True, "hardswish", 1], + ] + self.cls_ch_squeeze = 960 + self.cls_ch_expand = 1280 + elif model_name == "small": + self.cfg = [ + # k, exp, c, se, nl, s, + [3, 16, 16, True, "relu", 2], + [3, 72, 24, False, "relu", 2], + [3, 88, 24, False, "relu", 1], + [5, 96, 40, True, "hardswish", 2], + [5, 240, 40, True, "hardswish", 1], + [5, 240, 40, True, "hardswish", 1], + [5, 120, 48, True, "hardswish", 1], + [5, 144, 48, True, "hardswish", 1], + [5, 288, 96, True, "hardswish", 2], + [5, 576, 96, True, "hardswish", 1], + [5, 576, 96, True, "hardswish", 1], + ] + self.cls_ch_squeeze = 576 + self.cls_ch_expand = 1280 + else: + raise NotImplementedError( + "mode[{}_model] is not implemented!".format(model_name) + ) + + self.conv1 = ConvBNLayer( + in_c=3, + out_c=make_divisible(inplanes * scale), + filter_size=3, + stride=2, + padding=1, + num_groups=1, + if_act=True, + act="hardswish", + name="conv1", + use_custom_relu=self.use_custom_relu, + ) + + self.block_list = [] + i = 0 + inplanes = make_divisible(inplanes * scale) + for k, exp, c, se, nl, s in self.cfg: + block = self.add_sublayer( + "conv" + str(i + 2), + ResidualUnit( + in_c=inplanes, + mid_c=make_divisible(scale * exp), + out_c=make_divisible(scale * c), + filter_size=k, + stride=s, + use_se=se, + act=nl, + name="conv" + str(i + 2), + use_custom_relu=self.use_custom_relu, + ), + ) + self.block_list.append(block) + inplanes = make_divisible(scale * c) + i += 1 + + self.last_second_conv = ConvBNLayer( + in_c=inplanes, + out_c=make_divisible(scale * self.cls_ch_squeeze), + filter_size=1, + stride=1, + padding=0, + num_groups=1, + if_act=True, + act="hardswish", + name="conv_last", + use_custom_relu=self.use_custom_relu, + ) + + self.pool = AdaptiveAvgPool2D(1) + + self.last_conv = Conv2D( + in_channels=make_divisible(scale * self.cls_ch_squeeze), + out_channels=self.cls_ch_expand, + kernel_size=1, + stride=1, + padding=0, + weight_attr=ParamAttr(), + bias_attr=False, + ) + + self.dropout = Dropout(p=dropout_prob, mode="downscale_in_infer") + + self.out = Linear( + self.cls_ch_expand, + class_dim, + weight_attr=ParamAttr(), + bias_attr=ParamAttr(), + ) + + def forward(self, inputs, label=None): + x = self.conv1(inputs) + + for block in self.block_list: + x = block(x) + + x = self.last_second_conv(x) + x = self.pool(x) + + x = self.last_conv(x) + x = hardswish(x) + x = self.dropout(x) + x = paddle.flatten(x, start_axis=1, stop_axis=-1) + x = self.out(x) + return x + + +class ConvBNLayer(nn.Layer): + def __init__( + self, + in_c, + out_c, + filter_size, + stride, + padding, + num_groups=1, + if_act=True, + act=None, + use_cudnn=True, + name="", + use_custom_relu=False, + ): + super(ConvBNLayer, self).__init__() + self.if_act = if_act + self.act = act + self.conv = Conv2D( + in_channels=in_c, + out_channels=out_c, + kernel_size=filter_size, + stride=stride, + padding=padding, + groups=num_groups, + weight_attr=ParamAttr(), + bias_attr=False, + ) + self.bn = BatchNorm( + num_channels=out_c, + act=None, + param_attr=ParamAttr(regularizer=L2Decay(0.0)), + bias_attr=ParamAttr(regularizer=L2Decay(0.0)), + ) + # moving_mean_name=name + "_bn_mean", + # moving_variance_name=name + "_bn_variance") + + self.use_custom_relu = use_custom_relu + + def forward(self, x): + x = self.conv(x) + x = self.bn(x) + if self.if_act: + if self.act == "relu": + if self.use_custom_relu: + x = custom_ops.custom_relu(x) + else: + x = F.relu(x) + elif self.act == "hardswish": + x = hardswish(x) + else: + print("The activation function is selected incorrectly.") + exit() + return x + + +class ResidualUnit(nn.Layer): + def __init__( + self, + in_c, + mid_c, + out_c, + filter_size, + stride, + use_se, + act=None, + name="", + use_custom_relu=False, + ): + super(ResidualUnit, self).__init__() + self.if_shortcut = stride == 1 and in_c == out_c + self.if_se = use_se + + self.use_custom_relu = use_custom_relu + + self.expand_conv = ConvBNLayer( + in_c=in_c, + out_c=mid_c, + filter_size=1, + stride=1, + padding=0, + if_act=True, + act=act, + name=name + "_expand", + use_custom_relu=self.use_custom_relu, + ) + self.bottleneck_conv = ConvBNLayer( + in_c=mid_c, + out_c=mid_c, + filter_size=filter_size, + stride=stride, + padding=int((filter_size - 1) // 2), + num_groups=mid_c, + if_act=True, + act=act, + name=name + "_depthwise", + use_custom_relu=self.use_custom_relu, + ) + if self.if_se: + self.mid_se = SEModule(mid_c, name=name + "_se") + self.linear_conv = ConvBNLayer( + in_c=mid_c, + out_c=out_c, + filter_size=1, + stride=1, + padding=0, + if_act=False, + act=None, + name=name + "_linear", + use_custom_relu=self.use_custom_relu, + ) + + def forward(self, inputs): + x = self.expand_conv(inputs) + x = self.bottleneck_conv(x) + if self.if_se: + x = self.mid_se(x) + x = self.linear_conv(x) + if self.if_shortcut: + x = paddle.add(inputs, x) + return x + + +class SEModule(nn.Layer): + def __init__(self, channel, reduction=4, name=""): + super(SEModule, self).__init__() + self.avg_pool = AdaptiveAvgPool2D(1) + self.conv1 = Conv2D( + in_channels=channel, + out_channels=channel // reduction, + kernel_size=1, + stride=1, + padding=0, + weight_attr=ParamAttr(), + bias_attr=ParamAttr(), + ) + self.conv2 = Conv2D( + in_channels=channel // reduction, + out_channels=channel, + kernel_size=1, + stride=1, + padding=0, + weight_attr=ParamAttr(), + bias_attr=ParamAttr(), + ) + + def forward(self, inputs): + outputs = self.avg_pool(inputs) + outputs = self.conv1(outputs) + outputs = F.relu(outputs) + outputs = self.conv2(outputs) + outputs = hardsigmoid(outputs, slope=0.2, offset=0.5) + return paddle.multiply(x=inputs, y=outputs) + + +def MobileNetV3_small_x0_35(**args): + model = MobileNetV3(model_name="small", scale=0.35, **args) + return model + + +def MobileNetV3_small_x0_5(**args): + model = MobileNetV3(model_name="small", scale=0.5, **args) + return model + + +def MobileNetV3_small_x0_75(**args): + model = MobileNetV3(model_name="small", scale=0.75, **args) + return model + + +def MobileNetV3_small_x1_0(**args): + model = MobileNetV3(model_name="small", scale=1.0, **args) + return model + + +def MobileNetV3_small_x1_25(**args): + model = MobileNetV3(model_name="small", scale=1.25, **args) + return model + + +def MobileNetV3_large_x0_35(**args): + model = MobileNetV3(model_name="large", scale=0.35, **args) + return model + + +def MobileNetV3_large_x0_5(**args): + model = MobileNetV3(model_name="large", scale=0.5, **args) + return model + + +def MobileNetV3_large_x0_75(**args): + model = MobileNetV3(model_name="large", scale=0.75, **args) + return model + + +def MobileNetV3_large_x1_0(**args): + model = MobileNetV3(model_name="large", scale=1.0, **args) + return model + + +def MobileNetV3_large_x1_25(**args): + model = MobileNetV3(model_name="large", scale=1.25, **args) + return + + +class DistillMV3(nn.Layer): + def __init__( + self, + scale=1.0, + model_name="small", + dropout_prob=0.2, + class_dim=1000, + args=None, + use_custom_relu=False, + ): + super(DistillMV3, self).__init__() + + self.student = MobileNetV3( + model_name=model_name, + scale=scale, + class_dim=class_dim, + use_custom_relu=use_custom_relu, + ) + + self.student1 = MobileNetV3( + model_name=model_name, + scale=scale, + class_dim=class_dim, + use_custom_relu=use_custom_relu, + ) + + def forward(self, inputs, label=None): + predicts = dict() + predicts["student"] = self.student(inputs, label) + predicts["student1"] = self.student1(inputs, label) + return predicts + + +def distillmv3_large_x0_5(**args): + model = DistillMV3(model_name="large", scale=0.5, **args) + return model + + +class SiameseMV3(nn.Layer): + def __init__( + self, + scale=1.0, + model_name="small", + dropout_prob=0.2, + class_dim=1000, + args=None, + use_custom_relu=False, + ): + super(SiameseMV3, self).__init__() + + self.net = MobileNetV3( + model_name=model_name, + scale=scale, + class_dim=class_dim, + use_custom_relu=use_custom_relu, + ) + self.net1 = MobileNetV3( + model_name=model_name, + scale=scale, + class_dim=class_dim, + use_custom_relu=use_custom_relu, + ) + + def forward(self, inputs, label=None): + # net + x = self.net.conv1(inputs) + for block in self.net.block_list: + x = block(x) + + # net1 + x1 = self.net1.conv1(inputs) + for block in self.net1.block_list: + x1 = block(x1) + # add + x = x + x1 + + x = self.net.last_second_conv(x) + x = self.net.pool(x) + + x = self.net.last_conv(x) + x = hardswish(x) + x = self.net.dropout(x) + x = paddle.flatten(x, start_axis=1, stop_axis=-1) + x = self.net.out(x) + return x + + +def siamese_mv3(class_dim, use_custom_relu): + model = SiameseMV3( + scale=0.5, + model_name="large", + class_dim=class_dim, + use_custom_relu=use_custom_relu, + ) + return model + + +def build_model(config): + model_type = config["model_type"] + if model_type == "cls": + class_dim = config["MODEL"]["class_dim"] + use_custom_relu = config["MODEL"]["use_custom_relu"] + if "siamese" in config["MODEL"] and config["MODEL"]["siamese"] is True: + model = siamese_mv3(class_dim=class_dim, use_custom_relu=use_custom_relu) + else: + model = MobileNetV3_large_x0_5( + class_dim=class_dim, use_custom_relu=use_custom_relu + ) + + elif model_type == "cls_distill": + class_dim = config["MODEL"]["class_dim"] + use_custom_relu = config["MODEL"]["use_custom_relu"] + model = distillmv3_large_x0_5( + class_dim=class_dim, use_custom_relu=use_custom_relu + ) + + elif model_type == "cls_distill_multiopt": + class_dim = config["MODEL"]["class_dim"] + use_custom_relu = config["MODEL"]["use_custom_relu"] + model = distillmv3_large_x0_5(class_dim=100, use_custom_relu=use_custom_relu) + else: + raise ValueError("model_type should be one of ['']") + + return model diff --git a/test_tipc/supplementary/mv3_distill.yml b/test_tipc/supplementary/mv3_distill.yml new file mode 100644 index 0000000..57b8130 --- /dev/null +++ b/test_tipc/supplementary/mv3_distill.yml @@ -0,0 +1,30 @@ + +class_dim: 100 +total_images: 50000 +epochs: 1000 +topk: 5 +save_model_dir: ./output/ +use_gpu: True +model_type: cls_distill + +LEARNING_RATE: + function: 'Cosine' + params: + lr: 0.001 + warmup_epoch: 5 + +OPTIMIZER: + function: 'Momentum' + params: + momentum: 0.9 + regularizer: + function: 'L2' + factor: 0.00002 + +TRAIN: + batch_size: 1280 + num_workers: 4 + +VALID: + batch_size: 64 + num_workers: 4 diff --git a/test_tipc/supplementary/mv3_large_x0_5.yml b/test_tipc/supplementary/mv3_large_x0_5.yml new file mode 100644 index 0000000..fd7d308 --- /dev/null +++ b/test_tipc/supplementary/mv3_large_x0_5.yml @@ -0,0 +1,48 @@ + +class_dim: 100 +total_images: 50000 +epoch: 1000 +topk: 5 +save_model_dir: ./output/ +use_gpu: True +model_type: cls +use_custom_relu: false +pretrained_model: +checkpoints: +save_model_dir: ./output/cls/ + +# slim +quant_train: false +prune_train: false + +MODEL: + class_dim: 100 + use_custom_relu: False + siamese: False + +AMP: + use_amp: False + scale_loss: 1024.0 + use_dynamic_loss_scale: True + +LEARNING_RATE: + function: 'Cosine' + params: + lr: 0.001 + warmup_epoch: 5 + +OPTIMIZER: + function: 'Momentum' + params: + momentum: 0.9 + regularizer: + function: 'L2' + factor: 0.00002 + +TRAIN: + batch_size: 1280 + num_workers: 4 + +VALID: + batch_size: 64 + num_workers: 4 diff --git a/test_tipc/supplementary/optimizer.py b/test_tipc/supplementary/optimizer.py new file mode 100644 index 0000000..cb7ca3a --- /dev/null +++ b/test_tipc/supplementary/optimizer.py @@ -0,0 +1,330 @@ +import sys +import math +from paddle.optimizer.lr import LinearWarmup +from paddle.optimizer.lr import PiecewiseDecay +from paddle.optimizer.lr import CosineAnnealingDecay +from paddle.optimizer.lr import ExponentialDecay +import paddle +import paddle.regularizer as regularizer +from copy import deepcopy + + +class Cosine(CosineAnnealingDecay): + """ + Cosine learning rate decay + lr = 0.05 * (math.cos(epoch * (math.pi / epochs)) + 1) + Args: + lr(float): initial learning rate + step_each_epoch(int): steps each epoch + epochs(int): total training epochs + """ + + def __init__(self, lr, step_each_epoch, epochs, **kwargs): + super(Cosine, self).__init__( + learning_rate=lr, + T_max=step_each_epoch * epochs, + ) + + self.update_specified = False + + +class Piecewise(PiecewiseDecay): + """ + Piecewise learning rate decay + Args: + lr(float): initial learning rate + step_each_epoch(int): steps each epoch + decay_epochs(list): piecewise decay epochs + gamma(float): decay factor + """ + + def __init__(self, lr, step_each_epoch, decay_epochs, gamma=0.1, **kwargs): + boundaries = [step_each_epoch * e for e in decay_epochs] + lr_values = [lr * (gamma**i) for i in range(len(boundaries) + 1)] + super(Piecewise, self).__init__(boundaries=boundaries, values=lr_values) + + self.update_specified = False + + +class CosineWarmup(LinearWarmup): + """ + Cosine learning rate decay with warmup + [0, warmup_epoch): linear warmup + [warmup_epoch, epochs): cosine decay + Args: + lr(float): initial learning rate + step_each_epoch(int): steps each epoch + epochs(int): total training epochs + warmup_epoch(int): epoch num of warmup + """ + + def __init__(self, lr, step_each_epoch, epochs, warmup_epoch=5, **kwargs): + assert ( + epochs > warmup_epoch + ), "total epoch({}) should be larger than warmup_epoch({}) in CosineWarmup.".format( + epochs, warmup_epoch + ) + warmup_step = warmup_epoch * step_each_epoch + start_lr = 0.0 + end_lr = lr + lr_sch = Cosine(lr, step_each_epoch, epochs - warmup_epoch) + + super(CosineWarmup, self).__init__( + learning_rate=lr_sch, + warmup_steps=warmup_step, + start_lr=start_lr, + end_lr=end_lr, + ) + + self.update_specified = False + + +class ExponentialWarmup(LinearWarmup): + """ + Exponential learning rate decay with warmup + [0, warmup_epoch): linear warmup + [warmup_epoch, epochs): Exponential decay + Args: + lr(float): initial learning rate + step_each_epoch(int): steps each epoch + decay_epochs(float): decay epochs + decay_rate(float): decay rate + warmup_epoch(int): epoch num of warmup + """ + + def __init__( + self, + lr, + step_each_epoch, + decay_epochs=2.4, + decay_rate=0.97, + warmup_epoch=5, + **kwargs, + ): + warmup_step = warmup_epoch * step_each_epoch + start_lr = 0.0 + end_lr = lr + lr_sch = ExponentialDecay(lr, decay_rate) + + super(ExponentialWarmup, self).__init__( + learning_rate=lr_sch, + warmup_steps=warmup_step, + start_lr=start_lr, + end_lr=end_lr, + ) + + # NOTE: hac method to update exponential lr scheduler + self.update_specified = True + self.update_start_step = warmup_step + self.update_step_interval = int(decay_epochs * step_each_epoch) + self.step_each_epoch = step_each_epoch + + +class LearningRateBuilder: + """ + Build learning rate variable + https://www.paddlepaddle.org.cn/documentation/docs/zh/api_cn/layers_cn.html + Args: + function(str): class name of learning rate + params(dict): parameters used for init the class + """ + + def __init__( + self, function="Linear", params={"lr": 0.1, "steps": 100, "end_lr": 0.0} + ): + self.function = function + self.params = params + + def __call__(self): + mod = sys.modules[__name__] + lr = getattr(mod, self.function)(**self.params) + return lr + + +class L1Decay(object): + """ + L1 Weight Decay Regularization, which encourages the weights to be sparse. + Args: + factor(float): regularization coeff. Default:0.0. + """ + + def __init__(self, factor=0.0): + super(L1Decay, self).__init__() + self.factor = factor + + def __call__(self): + reg = regularizer.L1Decay(self.factor) + return reg + + +class L2Decay(object): + """ + L2 Weight Decay Regularization, which encourages the weights to be sparse. + Args: + factor(float): regularization coeff. Default:0.0. + """ + + def __init__(self, factor=0.0): + super(L2Decay, self).__init__() + self.factor = factor + + def __call__(self): + reg = regularizer.L2Decay(self.factor) + return reg + + +class Momentum(object): + """ + Simple Momentum optimizer with velocity state. + Args: + learning_rate (float|Variable) - The learning rate used to update parameters. + Can be a float value or a Variable with one float value as data element. + momentum (float) - Momentum factor. + regularization (WeightDecayRegularizer, optional) - The strategy of regularization. + """ + + def __init__( + self, learning_rate, momentum, parameter_list=None, regularization=None, **args + ): + super(Momentum, self).__init__() + self.learning_rate = learning_rate + self.momentum = momentum + self.parameter_list = parameter_list + self.regularization = regularization + + def __call__(self): + opt = paddle.optimizer.Momentum( + learning_rate=self.learning_rate, + momentum=self.momentum, + parameters=self.parameter_list, + weight_decay=self.regularization, + ) + return opt + + +class RMSProp(object): + """ + Root Mean Squared Propagation (RMSProp) is an unpublished, adaptive learning rate method. + Args: + learning_rate (float|Variable) - The learning rate used to update parameters. + Can be a float value or a Variable with one float value as data element. + momentum (float) - Momentum factor. + rho (float) - rho value in equation. + epsilon (float) - avoid division by zero, default is 1e-6. + regularization (WeightDecayRegularizer, optional) - The strategy of regularization. + """ + + def __init__( + self, + learning_rate, + momentum, + rho=0.95, + epsilon=1e-6, + parameter_list=None, + regularization=None, + **args, + ): + super(RMSProp, self).__init__() + self.learning_rate = learning_rate + self.momentum = momentum + self.rho = rho + self.epsilon = epsilon + self.parameter_list = parameter_list + self.regularization = regularization + + def __call__(self): + opt = paddle.optimizer.RMSProp( + learning_rate=self.learning_rate, + momentum=self.momentum, + rho=self.rho, + epsilon=self.epsilon, + parameters=self.parameter_list, + weight_decay=self.regularization, + ) + return opt + + +class OptimizerBuilder(object): + """ + Build optimizer + Args: + function(str): optimizer name of learning rate + params(dict): parameters used for init the class + regularizer (dict): parameters used for create regularization + """ + + def __init__(self, function="Momentum", params={"momentum": 0.9}, regularizer=None): + self.function = function + self.params = params + # create regularizer + if regularizer is not None: + mod = sys.modules[__name__] + reg_func = regularizer["function"] + "Decay" + del regularizer["function"] + reg = getattr(mod, reg_func)(**regularizer)() + self.params["regularization"] = reg + + def __call__(self, learning_rate, parameter_list=None): + mod = sys.modules[__name__] + opt = getattr(mod, self.function) + return opt( + learning_rate=learning_rate, parameter_list=parameter_list, **self.params + )() + + +def create_optimizer(config, parameter_list=None): + """ + Create an optimizer using config, usually including + learning rate and regularization. + + Args: + config(dict): such as + { + 'LEARNING_RATE': + {'function': 'Cosine', + 'params': {'lr': 0.1} + }, + 'OPTIMIZER': + {'function': 'Momentum', + 'params':{'momentum': 0.9}, + 'regularizer': + {'function': 'L2', 'factor': 0.0001} + } + } + + Returns: + an optimizer instance + """ + # create learning_rate instance + lr_config = config["LEARNING_RATE"] + lr_config["params"].update( + { + "epochs": config["epoch"], + "step_each_epoch": config["total_images"] // config["TRAIN"]["batch_size"], + } + ) + lr = LearningRateBuilder(**lr_config)() + + # create optimizer instance + opt_config = deepcopy(config["OPTIMIZER"]) + + opt = OptimizerBuilder(**opt_config) + return opt(lr, parameter_list), lr + + +def create_multi_optimizer(config, parameter_list=None): + """ """ + # create learning_rate instance + lr_config = config["LEARNING_RATE"] + lr_config["params"].update( + { + "epochs": config["epoch"], + "step_each_epoch": config["total_images"] // config["TRAIN"]["batch_size"], + } + ) + lr = LearningRateBuilder(**lr_config)() + + # create optimizer instance + opt_config = deepcopy.copy(config["OPTIMIZER"]) + opt = OptimizerBuilder(**opt_config) + return opt(lr, parameter_list), lr diff --git a/test_tipc/supplementary/readme.md b/test_tipc/supplementary/readme.md new file mode 100644 index 0000000..8e822fa --- /dev/null +++ b/test_tipc/supplementary/readme.md @@ -0,0 +1,74 @@ + +# TIPC Linux端补充训练功能测试 + +Linux端基础训练预测功能测试的主程序为test_train_python.sh,可以测试基于Python的模型训练、评估等基本功能,包括裁剪、量化、蒸馏训练。 + +![](./test_tipc/tipc_train.png) + +测试链条如上图所示,主要测试内容有带共享权重,自定义OP的模型的正常训练和slim相关功能训练流程是否正常。 + + +# 2. 测试流程 + +本节介绍补充链条的测试流程 + +## 2.1 安装依赖 + +- 安装PaddlePaddle >= 2.2 +- 安装其他依赖 + +``` +pip3 install -r requirements.txt +``` + +## 2.2 功能测试 + +`test_train_python.sh`包含2种运行模式,每种模式的运行数据不同,分别用于测试训练是否正常,分别是: + +- 模式1:lite_train_lite_infer,使用少量数据训练,用于快速验证训练到预测的走通流程,不验证精度和速度; + +``` +bash test_tipc/test_train_python.sh ./test_tipc/train_infer_python.txt 'lite_train_lite_infer' +``` + +- 模式2:whole_train_whole_infer,使用全量数据训练,用于快速验证训练到预测的走通流程,验证模型最终训练精度; + +``` +bash test_tipc/test_train_python.sh ./test_tipc/train_infer_python.txt 'whole_train_whole_infer' +``` + +如果是运行量化裁剪等训练方式,需要使用不同的配置文件。量化训练的测试指令如下: +``` +bash test_tipc/test_train_python.sh ./test_tipc/train_infer_python_PACT.txt 'lite_train_lite_infer' +``` + +同理,FPGM裁剪的运行方式如下: +``` +bash test_tipc/test_train_python.sh ./test_tipc/train_infer_python_FPGM.txt 'lite_train_lite_infer' +``` + +多机多卡的运行配置文件分别为 `train_infer_python_fleet.txt`, `train_infer_python_FPGM_fleet.txt` 和 `train_infer_python_PACT_fleet.txt`。 +运行时,需要修改配置文件中的 `gpu_list:xx.xx.xx.xx,yy.yy.yy.yy;0,1`。 将 `xx.xx.xx.xx` 替换为具体的 `ip` 地址,各个`ip`地址之间用`,`分隔。 另外,和单机训练 +不同,启动多机多卡训练需要在多机的每个节点上分别运行命令。以多机多卡量化训练为例,指令如下: +``` +bash test_tipc/test_train_python.sh ./test_tipc/train_infer_python_PACT_fleet.txt 'lite_train_lite_infer' +``` + +运行相应指令后,在`test_tipc/output`文件夹下自动会保存运行日志。如'lite_train_lite_infer'模式运行后,在test_tipc/extra_output文件夹有以下文件: + +``` +test_tipc/output/ +|- results_python.log # 运行指令状态的日志 +``` + +其中results_python.log中包含了每条指令的运行状态,如果运行成功会输出: + +``` +Run successfully with command - python3.7 train.py -c mv3_large_x0_5.yml -o use_gpu=True epoch=20 AMP.use_amp=True TRAIN.batch_size=1280 use_custom_relu=False model_type=cls MODEL.siamese=False ! +Run successfully with command - python3.7 train.py -c mv3_large_x0_5.yml -o use_gpu=True epoch=2 AMP.use_amp=True TRAIN.batch_size=1280 use_custom_relu=False model_type=cls MODEL.siamese=False ! +Run successfully with command - python3.7 train.py -c mv3_large_x0_5.yml -o use_gpu=True epoch=2 AMP.use_amp=True TRAIN.batch_size=1280 use_custom_relu=False model_type=cls MODEL.siamese=True ! +Run successfully with command - python3.7 train.py -c mv3_large_x0_5.yml -o use_gpu=True epoch=2 AMP.use_amp=True TRAIN.batch_size=1280 use_custom_relu=False model_type=cls_distill MODEL.siamese=False ! +Run successfully with command - python3.7 train.py -c mv3_large_x0_5.yml -o use_gpu=True epoch=2 AMP.use_amp=True TRAIN.batch_size=1280 use_custom_relu=False model_type=cls_distill MODEL.siamese=True ! +Run successfully with command - python3.7 train.py -c mv3_large_x0_5.yml -o use_gpu=True epoch=2 AMP.use_amp=True TRAIN.batch_size=1280 use_custom_relu=False model_type=cls_distill_multiopt MODEL.siamese=False ! + +``` diff --git a/test_tipc/supplementary/requirements.txt b/test_tipc/supplementary/requirements.txt new file mode 100644 index 0000000..c55500a --- /dev/null +++ b/test_tipc/supplementary/requirements.txt @@ -0,0 +1 @@ +paddleslim==2.2.1 diff --git a/test_tipc/supplementary/slim/__init__.py b/test_tipc/supplementary/slim/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_tipc/supplementary/slim/slim_fpgm.py b/test_tipc/supplementary/slim/slim_fpgm.py new file mode 100644 index 0000000..0304744 --- /dev/null +++ b/test_tipc/supplementary/slim/slim_fpgm.py @@ -0,0 +1,21 @@ +import paddleslim +import paddle +import numpy as np + +from paddleslim.dygraph import FPGMFilterPruner + + +def prune_model(model, input_shape, prune_ratio=0.1): + flops = paddle.flops(model, input_shape) + pruner = FPGMFilterPruner(model, input_shape) + + params_sensitive = {} + for param in model.parameters(): + if "transpose" not in param.name and "linear" not in param.name: + # set prune ratio as 10%. The larger the value, the more convolution weights will be cropped + params_sensitive[param.name] = prune_ratio + + plan = pruner.prune_vars(params_sensitive, [0]) + + flops = paddle.flops(model, input_shape) + return model diff --git a/test_tipc/supplementary/slim/slim_quant.py b/test_tipc/supplementary/slim/slim_quant.py new file mode 100644 index 0000000..41db3eb --- /dev/null +++ b/test_tipc/supplementary/slim/slim_quant.py @@ -0,0 +1,48 @@ +import paddle +import numpy as np +import os +import paddle.nn as nn +import paddleslim + + +class PACT(paddle.nn.Layer): + def __init__(self): + super(PACT, self).__init__() + alpha_attr = paddle.ParamAttr( + name=self.full_name() + ".pact", + initializer=paddle.nn.initializer.Constant(value=20), + learning_rate=1.0, + regularizer=paddle.regularizer.L2Decay(2e-5), + ) + + self.alpha = self.create_parameter(shape=[1], attr=alpha_attr, dtype="float32") + + def forward(self, x): + out_left = paddle.nn.functional.relu(x - self.alpha) + out_right = paddle.nn.functional.relu(-self.alpha - x) + x = x - out_left + out_right + return x + + +quant_config = { + # weight preprocess type, default is None and no preprocessing is performed. + "weight_preprocess_type": None, + # activation preprocess type, default is None and no preprocessing is performed. + "activation_preprocess_type": None, + # weight quantize type, default is 'channel_wise_abs_max' + "weight_quantize_type": "channel_wise_abs_max", + # activation quantize type, default is 'moving_average_abs_max' + "activation_quantize_type": "moving_average_abs_max", + # weight quantize bit num, default is 8 + "weight_bits": 8, + # activation quantize bit num, default is 8 + "activation_bits": 8, + # data type after quantization, such as 'uint8', 'int8', etc. default is 'int8' + "dtype": "int8", + # window size for 'range_abs_max' quantization. default is 10000 + "window_size": 10000, + # The decay coefficient of moving average, default is 0.9 + "moving_rate": 0.9, + # for dygraph quantization, layers of type in quantizable_layer_type will be quantized + "quantizable_layer_type": ["Conv2D", "Linear"], +} diff --git a/test_tipc/supplementary/test_tipc/common_func.sh b/test_tipc/supplementary/test_tipc/common_func.sh new file mode 100644 index 0000000..844536a --- /dev/null +++ b/test_tipc/supplementary/test_tipc/common_func.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +function func_parser_key(){ + strs=$1 + IFS=":" + array=(${strs}) + tmp=${array[0]} + echo ${tmp} +} + +function func_parser_value(){ + strs=$1 + IFS=":" + array=(${strs}) + tmp=${array[1]} + echo ${tmp} +} + +function func_set_params(){ + key=$1 + value=$2 + if [ ${key}x = "null"x ];then + echo " " + elif [[ ${value} = "null" ]] || [[ ${value} = " " ]] || [ ${#value} -le 0 ];then + echo " " + else + echo "${key}=${value}" + fi +} + +function func_parser_params(){ + strs=$1 + MODE=$2 + IFS=":" + array=(${strs}) + key=${array[0]} + tmp=${array[1]} + IFS="|" + res="" + for _params in ${tmp[*]}; do + IFS="=" + array=(${_params}) + mode=${array[0]} + value=${array[1]} + if [[ ${mode} = ${MODE} ]]; then + IFS="|" + #echo $(func_set_params "${mode}" "${value}") + echo $value + break + fi + IFS="|" + done + echo ${res} +} + +function status_check(){ + last_status=$1 # the exit code + run_command=$2 + run_log=$3 + if [ $last_status -eq 0 ]; then + echo -e "\033[33m Run successfully with command - ${run_command}! \033[0m" | tee -a ${run_log} + else + echo -e "\033[33m Run failed with command - ${run_command}! \033[0m" | tee -a ${run_log} + fi +} diff --git a/test_tipc/supplementary/test_tipc/test_train_python.sh b/test_tipc/supplementary/test_tipc/test_train_python.sh new file mode 100644 index 0000000..53e1cc9 --- /dev/null +++ b/test_tipc/supplementary/test_tipc/test_train_python.sh @@ -0,0 +1,114 @@ +#!/bin/bash +source test_tipc/common_func.sh + +FILENAME=$1 +# MODE be one of ['lite_train_lite_infer' 'lite_train_whole_infer'] +MODE=$2 + +dataline=$(awk 'NR==1, NR==51{print}' $FILENAME) + +# parser params +IFS=$'\n' +lines=(${dataline}) + +model_name=$(func_parser_value "${lines[1]}") +python=$(func_parser_value "${lines[2]}") +gpu_list=$(func_parser_value "${lines[3]}") +train_use_gpu_key=$(func_parser_key "${lines[4]}") +train_use_gpu_value=$(func_parser_value "${lines[4]}") +autocast_list=$(func_parser_value "${lines[5]}") +autocast_key=$(func_parser_key "${lines[5]}") +epoch_key=$(func_parser_key "${lines[6]}") +epoch_num=$(func_parser_params "${lines[6]}" "${MODE}") +save_model_key=$(func_parser_key "${lines[7]}") +train_batch_key=$(func_parser_key "${lines[8]}") +train_batch_value=$(func_parser_params "${lines[8]}" "${MODE}") +pretrain_model_key=$(func_parser_key "${lines[9]}") +pretrain_model_value=$(func_parser_value "${lines[9]}") +checkpoints_key=$(func_parser_key "${lines[10]}") +checkpoints_value=$(func_parser_value "${lines[10]}") +use_custom_key=$(func_parser_key "${lines[11]}") +use_custom_list=$(func_parser_value "${lines[11]}") +model_type_key=$(func_parser_key "${lines[12]}") +model_type_list=$(func_parser_value "${lines[12]}") +use_share_conv_key=$(func_parser_key "${lines[13]}") +use_share_conv_list=$(func_parser_value "${lines[13]}") +run_train_py=$(func_parser_value "${lines[14]}") + +LOG_PATH="./test_tipc/extra_output" +mkdir -p ${LOG_PATH} +status_log="${LOG_PATH}/results_python.log" + +if [ ${MODE} = "lite_train_lite_infer" ] || [ ${MODE} = "whole_train_whole_infer" ]; then + IFS="|" + export Count=0 + USE_GPU_KEY=(${train_use_gpu_value}) + # select cpu\gpu\distribute training + for gpu in ${gpu_list[*]}; do + train_use_gpu=${USE_GPU_KEY[Count]} + Count=$(($Count + 1)) + ips="" + if [ ${gpu} = "-1" ];then + env="" + elif [ ${#gpu} -le 1 ];then + env="export CUDA_VISIBLE_DEVICES=${gpu}" + eval ${env} + elif [ ${#gpu} -le 15 ];then + IFS="," + array=(${gpu}) + env="export CUDA_VISIBLE_DEVICES=${array[0]}" + IFS="|" + else + IFS=";" + array=(${gpu}) + ips=${array[0]} + gpu=${array[1]} + IFS="|" + env=" " + fi + for autocast in ${autocast_list[*]}; do + # set amp + if [ ${autocast} = "amp" ]; then + set_amp_config="AMP.use_amp=True" + else + set_amp_config=" " + fi + + if [ ${run_train_py} = "null" ]; then + continue + fi + + set_autocast=$(func_set_params "${autocast_key}" "${autocast}") + set_epoch=$(func_set_params "${epoch_key}" "${epoch_num}") + set_pretrain=$(func_set_params "${pretrain_model_key}" "${pretrain_model_value}") + set_checkpoints=$(func_set_params "${checkpoints_key}" "${checkpoints_value}") + set_batchsize=$(func_set_params "${train_batch_key}" "${train_batch_value}") + set_use_gpu=$(func_set_params "${train_use_gpu_key}" "${train_use_gpu}") + + for custom_op in ${use_custom_list[*]}; do + for model_type in ${model_type_list[*]}; do + for share_conv in ${use_share_conv_list[*]}; do + set_use_custom_op=$(func_set_params "${use_custom_key}" "${custom_op}") + set_model_type=$(func_set_params "${model_type_key}" "${model_type}") + set_use_share_conv=$(func_set_params "${use_share_conv_key}" "${share_conv}") + + set_save_model=$(func_set_params "${save_model_key}" "${save_log}") + if [ ${#gpu} -le 2 ];then # train with cpu or single gpu + cmd="${python} ${run_train_py} ${set_use_gpu} ${set_save_model} ${set_epoch} ${set_pretrain} ${set_checkpoints} ${set_autocast} ${set_batchsize} ${set_use_custom_op} ${set_model_type} ${set_use_share_conv} ${set_amp_config}" + elif [ ${#ips} -le 26 ];then # train with multi-gpu + cmd="${python} -m paddle.distributed.launch --gpus=${gpu} ${run_train_py} ${set_use_gpu} ${set_save_model} ${set_epoch} ${set_pretrain} ${set_checkpoints} ${set_autocast} ${set_batchsize} ${set_use_custom_op} ${set_model_type} ${set_use_share_conv} ${set_amp_config}" + else + cmd="${python} -m paddle.distributed.launch --ips=${ips} --gpus=${gpu} ${run_train_py} ${set_use_gpu} ${set_save_model} ${set_epoch} ${set_pretrain} ${set_checkpoints} ${set_autocast} ${set_batchsize} ${set_use_custom_op} ${set_model_type} ${set_use_share_conv} ${set_amp_config}" + fi + + # run train + eval "unset CUDA_VISIBLE_DEVICES" + # echo $cmd + eval $cmd + status_check $? "${cmd}" "${status_log}" + done + done + done + done + done +fi diff --git a/test_tipc/supplementary/test_tipc/tipc_train.png b/test_tipc/supplementary/test_tipc/tipc_train.png new file mode 100644 index 0000000..9ca124e Binary files /dev/null and b/test_tipc/supplementary/test_tipc/tipc_train.png differ diff --git a/test_tipc/supplementary/test_tipc/train_infer_python.txt b/test_tipc/supplementary/test_tipc/train_infer_python.txt new file mode 100644 index 0000000..99028c0 --- /dev/null +++ b/test_tipc/supplementary/test_tipc/train_infer_python.txt @@ -0,0 +1,17 @@ +===========================train_params=========================== +model_name:ch_PPOCRv2_det +python:python3.7 +gpu_list:0|0,1 +use_gpu:True|True +AMP.use_amp:True|False +epoch:lite_train_lite_infer=2|whole_train_whole_infer=1000 +save_model_dir:./output/ +TRAIN.batch_size:lite_train_lite_infer=1280|whole_train_whole_infer=1280 +pretrained_model:null +checkpoints:null +use_custom_relu:False|True +model_type:cls|cls_distill|cls_distill_multiopt +MODEL.siamese:False|True +norm_train:train.py -c mv3_large_x0_5.yml -o +quant_train:False +prune_train:False diff --git a/test_tipc/supplementary/test_tipc/train_infer_python_FPGM.txt b/test_tipc/supplementary/test_tipc/train_infer_python_FPGM.txt new file mode 100644 index 0000000..ccbd27f --- /dev/null +++ b/test_tipc/supplementary/test_tipc/train_infer_python_FPGM.txt @@ -0,0 +1,17 @@ +===========================train_params=========================== +model_name:ch_PPOCRv2_det +python:python3.7 +gpu_list:0|0,1 +use_gpu:True|True +AMP.use_amp:True|False +epoch:lite_train_lite_infer=2|whole_train_whole_infer=1000 +save_model_dir:./output/ +TRAIN.batch_size:lite_train_lite_infer=1280|whole_train_whole_infer=1280 +pretrained_model:null +checkpoints:null +use_custom_relu:False|True +model_type:cls|cls_distill|cls_distill_multiopt +MODEL.siamese:False|True +norm_train:train.py -c mv3_large_x0_5.yml -o prune_train=True +quant_train:False +prune_train:False diff --git a/test_tipc/supplementary/test_tipc/train_infer_python_FPGM_fleet.txt b/test_tipc/supplementary/test_tipc/train_infer_python_FPGM_fleet.txt new file mode 100644 index 0000000..be2b211 --- /dev/null +++ b/test_tipc/supplementary/test_tipc/train_infer_python_FPGM_fleet.txt @@ -0,0 +1,17 @@ +===========================train_params=========================== +model_name:ch_PPOCRv2_det +python:python3.7 +gpu_list:xx.xx.xx.xx,yy.yy.yy.yy;0,1 +use_gpu:True +AMP.use_amp:True|False +epoch:lite_train_lite_infer=2|whole_train_whole_infer=1000 +save_model_dir:./output/ +TRAIN.batch_size:lite_train_lite_infer=1280|whole_train_whole_infer=1280 +pretrained_model:null +checkpoints:null +use_custom_relu:False|True +model_type:cls|cls_distill|cls_distill_multiopt +MODEL.siamese:False|True +norm_train:train.py -c mv3_large_x0_5.yml -o prune_train=True +quant_train:False +prune_train:False diff --git a/test_tipc/supplementary/test_tipc/train_infer_python_PACT.txt b/test_tipc/supplementary/test_tipc/train_infer_python_PACT.txt new file mode 100644 index 0000000..24d291b --- /dev/null +++ b/test_tipc/supplementary/test_tipc/train_infer_python_PACT.txt @@ -0,0 +1,17 @@ +===========================train_params=========================== +model_name:ch_PPOCRv2_det +python:python3.7 +gpu_list:0|0,1 +use_gpu:True|True +AMP.use_amp:True|False +epoch:lite_train_lite_infer=2|whole_train_whole_infer=1000 +save_model_dir:./output/ +TRAIN.batch_size:lite_train_lite_infer=1280|whole_train_whole_infer=1280 +pretrained_model:null +checkpoints:null +use_custom_relu:False|True +model_type:cls|cls_distill|cls_distill_multiopt +MODEL.siamese:False|True +norm_train:train.py -c mv3_large_x0_5.yml -o quant_train=True +quant_train:False +prune_train:False diff --git a/test_tipc/supplementary/test_tipc/train_infer_python_PACT_fleet.txt b/test_tipc/supplementary/test_tipc/train_infer_python_PACT_fleet.txt new file mode 100644 index 0000000..93f06d7 --- /dev/null +++ b/test_tipc/supplementary/test_tipc/train_infer_python_PACT_fleet.txt @@ -0,0 +1,17 @@ +===========================train_params=========================== +model_name:ch_PPOCRv2_det +python:python3.7 +gpu_list:xx.xx.xx.xx,yy.yy.yy.yy;0,1 +use_gpu:True +AMP.use_amp:True|False +epoch:lite_train_lite_infer=2|whole_train_whole_infer=1000 +save_model_dir:./output/ +TRAIN.batch_size:lite_train_lite_infer=1280|whole_train_whole_infer=1280 +pretrained_model:null +checkpoints:null +use_custom_relu:False|True +model_type:cls|cls_distill|cls_distill_multiopt +MODEL.siamese:False|True +norm_train:train.py -c mv3_large_x0_5.yml -o quant_train=True +quant_train:False +prune_train:False diff --git a/test_tipc/supplementary/test_tipc/train_infer_python_fleet.txt b/test_tipc/supplementary/test_tipc/train_infer_python_fleet.txt new file mode 100644 index 0000000..00b9e82 --- /dev/null +++ b/test_tipc/supplementary/test_tipc/train_infer_python_fleet.txt @@ -0,0 +1,17 @@ +===========================train_params=========================== +model_name:ch_PPOCRv2_det +python:python3.7 +gpu_list:xx.xx.xx.xx,yy.yy.yy.yy;0,1 +use_gpu:True +AMP.use_amp:True|False +epoch:lite_train_lite_infer=2|whole_train_whole_infer=1000 +save_model_dir:./output/ +TRAIN.batch_size:lite_train_lite_infer=1280|whole_train_whole_infer=1280 +pretrained_model:null +checkpoints:null +use_custom_relu:False|True +model_type:cls|cls_distill|cls_distill_multiopt +MODEL.siamese:False|True +norm_train: train.py -c mv3_large_x0_5.yml -o +quant_train:False +prune_train:False diff --git a/test_tipc/supplementary/train.py b/test_tipc/supplementary/train.py new file mode 100644 index 0000000..ece8d5b --- /dev/null +++ b/test_tipc/supplementary/train.py @@ -0,0 +1,494 @@ +import paddle +import numpy as np +import os +import paddle.nn as nn +import paddle.distributed as dist + +dist.get_world_size() +dist.init_parallel_env() + +from loss import build_loss, LossDistill, DMLLoss, KLJSLoss +from optimizer import create_optimizer +from data_loader import build_dataloader +from metric import create_metric +from mv3 import MobileNetV3_large_x0_5, distillmv3_large_x0_5, build_model +from config import preprocess +import time + +from paddleslim.dygraph.quant import QAT +from slim.slim_quant import PACT, quant_config +from slim.slim_fpgm import prune_model +from utils import load_model + + +def _mkdir_if_not_exist(path, logger): + """ + mkdir if not exists, ignore the exception when multiprocess mkdir together + """ + if not os.path.exists(path): + try: + os.makedirs(path) + except OSError as e: + if e.errno == errno.EEXIST and os.path.isdir(path): + logger.warning( + "be happy if some process has already created {}".format(path) + ) + else: + raise OSError("Failed to mkdir {}".format(path)) + + +def save_model( + model, optimizer, model_path, logger, is_best=False, prefix="ppocr", **kwargs +): + """ + save model to the target path + """ + _mkdir_if_not_exist(model_path, logger) + model_prefix = os.path.join(model_path, prefix) + paddle.save(model.state_dict(), model_prefix + ".pdparams") + if type(optimizer) is list: + paddle.save(optimizer[0].state_dict(), model_prefix + ".pdopt") + paddle.save(optimizer[1].state_dict(), model_prefix + "_1" + ".pdopt") + + else: + paddle.save(optimizer.state_dict(), model_prefix + ".pdopt") + + # # save metric and config + # with open(model_prefix + '.states', 'wb') as f: + # pickle.dump(kwargs, f, protocol=2) + if is_best: + logger.info("save best model is to {}".format(model_prefix)) + else: + logger.info("save model in {}".format(model_prefix)) + + +def amp_scaler(config): + if "AMP" in config and config["AMP"]["use_amp"] is True: + AMP_RELATED_FLAGS_SETTING = { + "FLAGS_cudnn_batchnorm_spatial_persistent": 1, + } + paddle.set_flags(AMP_RELATED_FLAGS_SETTING) + scale_loss = config["AMP"].get("scale_loss", 1.0) + use_dynamic_loss_scaling = config["AMP"].get("use_dynamic_loss_scaling", False) + scaler = paddle.amp.GradScaler( + init_loss_scaling=scale_loss, + use_dynamic_loss_scaling=use_dynamic_loss_scaling, + ) + return scaler + else: + return None + + +def set_seed(seed): + paddle.seed(seed) + np.random.seed(seed) + + +def train(config, scaler=None): + EPOCH = config["epoch"] + topk = config["topk"] + + batch_size = config["TRAIN"]["batch_size"] + num_workers = config["TRAIN"]["num_workers"] + train_loader = build_dataloader( + "train", batch_size=batch_size, num_workers=num_workers + ) + + # build metric + metric_func = create_metric + + # build model + # model = MobileNetV3_large_x0_5(class_dim=100) + model = build_model(config) + + # build_optimizer + optimizer, lr_scheduler = create_optimizer( + config, parameter_list=model.parameters() + ) + + # load model + pre_best_model_dict = load_model(config, model, optimizer) + if len(pre_best_model_dict) > 0: + pre_str = "The metric of loaded metric as follows {}".format( + ", ".join(["{}: {}".format(k, v) for k, v in pre_best_model_dict.items()]) + ) + logger.info(pre_str) + + # about slim prune and quant + if "quant_train" in config and config["quant_train"] is True: + quanter = QAT(config=quant_config, act_preprocess=PACT) + quanter.quantize(model) + elif "prune_train" in config and config["prune_train"] is True: + model = prune_model(model, [1, 3, 32, 32], 0.1) + else: + pass + + # distribution + model.train() + model = paddle.DataParallel(model) + # build loss function + loss_func = build_loss(config) + + data_num = len(train_loader) + + best_acc = {} + for epoch in range(EPOCH): + st = time.time() + for idx, data in enumerate(train_loader): + img_batch, label = data + img_batch = paddle.transpose(img_batch, [0, 3, 1, 2]) + label = paddle.unsqueeze(label, -1) + + if scaler is not None: + with paddle.amp.auto_cast(): + outs = model(img_batch) + else: + outs = model(img_batch) + + # cal metric + acc = metric_func(outs, label) + + # cal loss + avg_loss = loss_func(outs, label) + + if scaler is None: + # backward + avg_loss.backward() + optimizer.step() + optimizer.clear_grad() + else: + scaled_avg_loss = scaler.scale(avg_loss) + scaled_avg_loss.backward() + scaler.minimize(optimizer, scaled_avg_loss) + + if not isinstance(lr_scheduler, float): + lr_scheduler.step() + + if idx % 10 == 0: + et = time.time() + strs = f"epoch: [{epoch}/{EPOCH}], iter: [{idx}/{data_num}], " + strs += f"loss: {float(avg_loss)}" + strs += ( + f", acc_topk1: {float(acc['top1'])}, acc_top5: {float(acc['top5'])}" + ) + strs += f", batch_time: {round(et-st, 4)} s" + logger.info(strs) + st = time.time() + + if epoch % 10 == 0: + acc = eval(config, model) + if len(best_acc) < 1 or float(acc["top5"]) > best_acc["top5"]: + best_acc = acc + best_acc["epoch"] = epoch + is_best = True + else: + is_best = False + logger.info( + f"The best acc: acc_topk1: {float(best_acc['top1'])}, acc_top5: {float(best_acc['top5'])}, best_epoch: {best_acc['epoch']}" + ) + save_model( + model, + optimizer, + config["save_model_dir"], + logger, + is_best, + prefix="cls", + ) + + +def train_distill(config, scaler=None): + EPOCH = config["epoch"] + topk = config["topk"] + + batch_size = config["TRAIN"]["batch_size"] + num_workers = config["TRAIN"]["num_workers"] + train_loader = build_dataloader( + "train", batch_size=batch_size, num_workers=num_workers + ) + + # build metric + metric_func = create_metric + + # model = distillmv3_large_x0_5(class_dim=100) + model = build_model(config) + + # pact quant train + if "quant_train" in config and config["quant_train"] is True: + quanter = QAT(config=quant_config, act_preprocess=PACT) + quanter.quantize(model) + elif "prune_train" in config and config["prune_train"] is True: + model = prune_model(model, [1, 3, 32, 32], 0.1) + else: + pass + + # build_optimizer + optimizer, lr_scheduler = create_optimizer( + config, parameter_list=model.parameters() + ) + + # load model + pre_best_model_dict = load_model(config, model, optimizer) + if len(pre_best_model_dict) > 0: + pre_str = "The metric of loaded metric as follows {}".format( + ", ".join(["{}: {}".format(k, v) for k, v in pre_best_model_dict.items()]) + ) + logger.info(pre_str) + + model.train() + model = paddle.DataParallel(model) + + # build loss function + loss_func_distill = LossDistill(model_name_list=["student", "student1"]) + loss_func_dml = DMLLoss(model_name_pairs=["student", "student1"]) + loss_func_js = KLJSLoss(mode="js") + + data_num = len(train_loader) + + best_acc = {} + for epoch in range(EPOCH): + st = time.time() + for idx, data in enumerate(train_loader): + img_batch, label = data + img_batch = paddle.transpose(img_batch, [0, 3, 1, 2]) + label = paddle.unsqueeze(label, -1) + if scaler is not None: + with paddle.amp.auto_cast(): + outs = model(img_batch) + else: + outs = model(img_batch) + + # cal metric + acc = metric_func(outs["student"], label) + + # cal loss + avg_loss = ( + loss_func_distill(outs, label)["student"] + + loss_func_distill(outs, label)["student1"] + + loss_func_dml(outs, label)["student_student1"] + ) + + # backward + if scaler is None: + avg_loss.backward() + optimizer.step() + optimizer.clear_grad() + else: + scaled_avg_loss = scaler.scale(avg_loss) + scaled_avg_loss.backward() + scaler.minimize(optimizer, scaled_avg_loss) + + if not isinstance(lr_scheduler, float): + lr_scheduler.step() + + if idx % 10 == 0: + et = time.time() + strs = f"epoch: [{epoch}/{EPOCH}], iter: [{idx}/{data_num}], " + strs += f"loss: {float(avg_loss)}" + strs += ( + f", acc_topk1: {float(acc['top1'])}, acc_top5: {float(acc['top5'])}" + ) + strs += f", batch_time: {round(et-st, 4)} s" + logger.info(strs) + st = time.time() + + if epoch % 10 == 0: + acc = eval(config, model._layers.student) + if len(best_acc) < 1 or float(acc["top5"]) > best_acc["top5"]: + best_acc = acc + best_acc["epoch"] = epoch + is_best = True + else: + is_best = False + logger.info( + f"The best acc: acc_topk1: {float(best_acc['top1'])}, acc_top5: {float(best_acc['top5'])}, best_epoch: {best_acc['epoch']}" + ) + + save_model( + model, + optimizer, + config["save_model_dir"], + logger, + is_best, + prefix="cls_distill", + ) + + +def train_distill_multiopt(config, scaler=None): + EPOCH = config["epoch"] + topk = config["topk"] + + batch_size = config["TRAIN"]["batch_size"] + num_workers = config["TRAIN"]["num_workers"] + train_loader = build_dataloader( + "train", batch_size=batch_size, num_workers=num_workers + ) + + # build metric + metric_func = create_metric + + # model = distillmv3_large_x0_5(class_dim=100) + model = build_model(config) + + # build_optimizer + optimizer, lr_scheduler = create_optimizer( + config, parameter_list=model.student.parameters() + ) + optimizer1, lr_scheduler1 = create_optimizer( + config, parameter_list=model.student1.parameters() + ) + + # load model + pre_best_model_dict = load_model(config, model, optimizer) + if len(pre_best_model_dict) > 0: + pre_str = "The metric of loaded metric as follows {}".format( + ", ".join(["{}: {}".format(k, v) for k, v in pre_best_model_dict.items()]) + ) + logger.info(pre_str) + + # quant train + if "quant_train" in config and config["quant_train"] is True: + quanter = QAT(config=quant_config, act_preprocess=PACT) + quanter.quantize(model) + elif "prune_train" in config and config["prune_train"] is True: + model = prune_model(model, [1, 3, 32, 32], 0.1) + else: + pass + + model.train() + + model = paddle.DataParallel(model) + + # build loss function + loss_func_distill = LossDistill(model_name_list=["student", "student1"]) + loss_func_dml = DMLLoss(model_name_pairs=["student", "student1"]) + loss_func_js = KLJSLoss(mode="js") + + data_num = len(train_loader) + best_acc = {} + for epoch in range(EPOCH): + st = time.time() + for idx, data in enumerate(train_loader): + img_batch, label = data + img_batch = paddle.transpose(img_batch, [0, 3, 1, 2]) + label = paddle.unsqueeze(label, -1) + + if scaler is not None: + with paddle.amp.auto_cast(): + outs = model(img_batch) + else: + outs = model(img_batch) + + # cal metric + acc = metric_func(outs["student"], label) + + # cal loss + avg_loss = ( + loss_func_distill(outs, label)["student"] + + loss_func_dml(outs, label)["student_student1"] + ) + avg_loss1 = ( + loss_func_distill(outs, label)["student1"] + + loss_func_dml(outs, label)["student_student1"] + ) + + if scaler is None: + # backward + avg_loss.backward(retain_graph=True) + optimizer.step() + optimizer.clear_grad() + + avg_loss1.backward() + optimizer1.step() + optimizer1.clear_grad() + else: + scaled_avg_loss = scaler.scale(avg_loss) + scaled_avg_loss.backward() + scaler.minimize(optimizer, scaled_avg_loss) + + scaled_avg_loss = scaler.scale(avg_loss1) + scaled_avg_loss.backward() + scaler.minimize(optimizer1, scaled_avg_loss) + + if not isinstance(lr_scheduler, float): + lr_scheduler.step() + if not isinstance(lr_scheduler1, float): + lr_scheduler1.step() + + if idx % 10 == 0: + et = time.time() + strs = f"epoch: [{epoch}/{EPOCH}], iter: [{idx}/{data_num}], " + strs += f"loss: {float(avg_loss)}, loss1: {float(avg_loss1)}" + strs += ( + f", acc_topk1: {float(acc['top1'])}, acc_top5: {float(acc['top5'])}" + ) + strs += f", batch_time: {round(et-st, 4)} s" + logger.info(strs) + st = time.time() + + if epoch % 10 == 0: + acc = eval(config, model._layers.student) + if len(best_acc) < 1 or float(acc["top5"]) > best_acc["top5"]: + best_acc = acc + best_acc["epoch"] = epoch + is_best = True + else: + is_best = False + logger.info( + f"The best acc: acc_topk1: {float(best_acc['top1'])}, acc_top5: {float(best_acc['top5'])}, best_epoch: {best_acc['epoch']}" + ) + save_model( + model, + [optimizer, optimizer1], + config["save_model_dir"], + logger, + is_best, + prefix="cls_distill_multiopt", + ) + + +def eval(config, model): + batch_size = config["VALID"]["batch_size"] + num_workers = config["VALID"]["num_workers"] + valid_loader = build_dataloader( + "test", batch_size=batch_size, num_workers=num_workers + ) + + # build metric + metric_func = create_metric + + outs = [] + labels = [] + for idx, data in enumerate(valid_loader): + img_batch, label = data + img_batch = paddle.transpose(img_batch, [0, 3, 1, 2]) + label = paddle.unsqueeze(label, -1) + out = model(img_batch) + + outs.append(out) + labels.append(label) + + outs = paddle.concat(outs, axis=0) + labels = paddle.concat(labels, axis=0) + acc = metric_func(outs, labels) + + strs = f"The metric are as follows: acc_topk1: {float(acc['top1'])}, acc_top5: {float(acc['top5'])}" + logger.info(strs) + return acc + + +if __name__ == "__main__": + config, logger = preprocess(is_train=False) + + # AMP scaler + scaler = amp_scaler(config) + + model_type = config["model_type"] + + if model_type == "cls": + train(config) + elif model_type == "cls_distill": + train_distill(config) + elif model_type == "cls_distill_multiopt": + train_distill_multiopt(config) + else: + raise ValueError("model_type should be one of ['']") diff --git a/test_tipc/supplementary/train.sh b/test_tipc/supplementary/train.sh new file mode 100644 index 0000000..a2c7c90 --- /dev/null +++ b/test_tipc/supplementary/train.sh @@ -0,0 +1,5 @@ +# single GPU +python3.7 train.py -c mv3_large_x0_5.yml + +# distribute training +python3.7 -m paddle.distributed.launch --log_dir=./debug/ --gpus '0,1' train.py -c mv3_large_x0_5.yml diff --git a/test_tipc/supplementary/utils.py b/test_tipc/supplementary/utils.py new file mode 100644 index 0000000..ffd61fa --- /dev/null +++ b/test_tipc/supplementary/utils.py @@ -0,0 +1,172 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import logging +import functools +import paddle.distributed as dist + +logger_initialized = {} + + +def print_dict(d, logger, delimiter=0): + """ + Recursively visualize a dict and + indenting acrrording by the relationship of keys. + """ + for k, v in sorted(d.items()): + if isinstance(v, dict): + logger.info("{}{} : ".format(delimiter * " ", str(k))) + print_dict(v, logger, delimiter + 4) + elif isinstance(v, list) and len(v) >= 1 and isinstance(v[0], dict): + logger.info("{}{} : ".format(delimiter * " ", str(k))) + for value in v: + print_dict(value, logger, delimiter + 4) + else: + logger.info("{}{} : {}".format(delimiter * " ", k, v)) + + +@functools.lru_cache() +def get_logger(name="root", log_file=None, log_level=logging.DEBUG): + """Initialize and get a logger by name. + If the logger has not been initialized, this method will initialize the + logger by adding one or two handlers, otherwise the initialized logger will + be directly returned. During initialization, a StreamHandler will always be + added. If `log_file` is specified a FileHandler will also be added. + Args: + name (str): Logger name. + log_file (str | None): The log filename. If specified, a FileHandler + will be added to the logger. + log_level (int): The logger level. Note that only the process of + rank 0 is affected, and other processes will set the level to + "Error" thus be silent most of the time. + Returns: + logging.Logger: The expected logger. + """ + logger = logging.getLogger(name) + if name in logger_initialized: + return logger + for logger_name in logger_initialized: + if name.startswith(logger_name): + return logger + + formatter = logging.Formatter( + "[%(asctime)s] %(name)s %(levelname)s: %(message)s", datefmt="%Y/%m/%d %H:%M:%S" + ) + + stream_handler = logging.StreamHandler(stream=sys.stdout) + stream_handler.setFormatter(formatter) + logger.addHandler(stream_handler) + if log_file is not None and dist.get_rank() == 0: + log_file_folder = os.path.split(log_file)[0] + os.makedirs(log_file_folder, exist_ok=True) + file_handler = logging.FileHandler(log_file, "a") + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + if dist.get_rank() == 0: + logger.setLevel(log_level) + else: + logger.setLevel(logging.ERROR) + logger_initialized[name] = True + return logger + + +def load_model(config, model, optimizer=None): + """ + load model from checkpoint or pretrained_model + """ + logger = get_logger() + checkpoints = config.get("checkpoints") + pretrained_model = config.get("pretrained_model") + best_model_dict = {} + if checkpoints: + if checkpoints.endswith(".pdparams"): + checkpoints = checkpoints.replace(".pdparams", "") + assert os.path.exists( + checkpoints + ".pdparams" + ), "The {}.pdparams does not exists!".format(checkpoints) + + # load params from trained model + params = paddle.load(checkpoints + ".pdparams") + state_dict = model.state_dict() + new_state_dict = {} + for key, value in state_dict.items(): + if key not in params: + logger.warning( + "{} not in loaded params {} !".format(key, params.keys()) + ) + continue + pre_value = params[key] + if list(value.shape) == list(pre_value.shape): + new_state_dict[key] = pre_value + else: + logger.warning( + "The shape of model params {} {} not matched with loaded params shape {} !".format( + key, value.shape, pre_value.shape + ) + ) + model.set_state_dict(new_state_dict) + + if optimizer is not None: + if os.path.exists(checkpoints + ".pdopt"): + optim_dict = paddle.load(checkpoints + ".pdopt") + optimizer.set_state_dict(optim_dict) + else: + logger.warning( + "{}.pdopt is not exists, params of optimizer is not loaded".format( + checkpoints + ) + ) + + if os.path.exists(checkpoints + ".states"): + with open(checkpoints + ".states", "rb") as f: + states_dict = pickle.load(f, encoding="latin1") + best_model_dict = states_dict.get("best_model_dict", {}) + if "epoch" in states_dict: + best_model_dict["start_epoch"] = states_dict["epoch"] + 1 + logger.info("resume from {}".format(checkpoints)) + elif pretrained_model: + load_pretrained_params(model, pretrained_model) + else: + logger.info("train from scratch") + return best_model_dict + + +def load_pretrained_params(model, path): + logger = get_logger() + if path.endswith(".pdparams"): + path = path.replace(".pdparams", "") + assert os.path.exists( + path + ".pdparams" + ), "The {}.pdparams does not exists!".format(path) + + params = paddle.load(path + ".pdparams") + state_dict = model.state_dict() + new_state_dict = {} + for k1 in params.keys(): + if k1 not in state_dict.keys(): + logger.warning("The pretrained params {} not in model".format(k1)) + else: + if list(state_dict[k1].shape) == list(params[k1].shape): + new_state_dict[k1] = params[k1] + else: + logger.warning( + "The shape of model params {} {} not matched with loaded params {} {} !".format( + k1, state_dict[k1].shape, k1, params[k1].shape + ) + ) + model.set_state_dict(new_state_dict) + logger.info("load pretrain successful from {}".format(path)) + return model diff --git a/test_tipc/test_inference_cpp.sh b/test_tipc/test_inference_cpp.sh new file mode 100644 index 0000000..c84f225 --- /dev/null +++ b/test_tipc/test_inference_cpp.sh @@ -0,0 +1,240 @@ +#!/bin/bash +source test_tipc/common_func.sh + +FILENAME=$1 +dataline=$(awk 'NR==1, NR==20{print}' $FILENAME) + +# parser params +IFS=$'\n' +lines=(${dataline}) + +# parser cpp inference model +model_name=$(func_parser_value "${lines[1]}") +use_opencv=$(func_parser_value "${lines[2]}") +cpp_infer_model_dir_list=$(func_parser_value "${lines[3]}") +cpp_infer_is_quant=$(func_parser_value "${lines[4]}") +# parser cpp inference +inference_cmd=$(func_parser_value "${lines[5]}") +cpp_use_gpu_key=$(func_parser_key "${lines[6]}") +cpp_use_gpu_list=$(func_parser_value "${lines[6]}") +cpp_use_mkldnn_key=$(func_parser_key "${lines[7]}") +cpp_use_mkldnn_list=$(func_parser_value "${lines[7]}") +cpp_cpu_threads_key=$(func_parser_key "${lines[8]}") +cpp_cpu_threads_list=$(func_parser_value "${lines[8]}") +cpp_batch_size_key=$(func_parser_key "${lines[9]}") +cpp_batch_size_list=$(func_parser_value "${lines[9]}") +cpp_use_trt_key=$(func_parser_key "${lines[10]}") +cpp_use_trt_list=$(func_parser_value "${lines[10]}") +cpp_precision_key=$(func_parser_key "${lines[11]}") +cpp_precision_list=$(func_parser_value "${lines[11]}") +cpp_infer_model_key=$(func_parser_key "${lines[12]}") +cpp_image_dir_key=$(func_parser_key "${lines[13]}") +cpp_infer_img_dir=$(func_parser_value "${lines[13]}") +cpp_infer_key1=$(func_parser_key "${lines[14]}") +cpp_infer_value1=$(func_parser_value "${lines[14]}") +cpp_benchmark_key=$(func_parser_key "${lines[15]}") +cpp_benchmark_value=$(func_parser_value "${lines[15]}") +cpp_det_key=$(func_parser_key "${lines[16]}") +cpp_det_value=$(func_parser_value "${lines[16]}") +cpp_rec_key=$(func_parser_key "${lines[17]}") +cpp_rec_value=$(func_parser_value "${lines[17]}") +cpp_cls_key=$(func_parser_key "${lines[18]}") +cpp_cls_value=$(func_parser_value "${lines[18]}") +cpp_use_angle_cls_key=$(func_parser_key "${lines[19]}") +cpp_use_angle_cls_value=$(func_parser_value "${lines[19]}") + +LOG_PATH="./test_tipc/output/${model_name}/cpp_infer" +mkdir -p ${LOG_PATH} +status_log="${LOG_PATH}/results_cpp.log" + + +function func_cpp_inference(){ + IFS='|' + _script=$1 + _model_dir=$2 + _log_path=$3 + _img_dir=$4 + _flag_quant=$5 + # inference + for use_gpu in ${cpp_use_gpu_list[*]}; do + if [ ${use_gpu} = "False" ] || [ ${use_gpu} = "cpu" ]; then + for use_mkldnn in ${cpp_use_mkldnn_list[*]}; do + if [ ${use_mkldnn} = "False" ] && [ ${_flag_quant} = "True" ]; then + continue + fi + for threads in ${cpp_cpu_threads_list[*]}; do + for batch_size in ${cpp_batch_size_list[*]}; do + precision="fp32" + if [ ${use_mkldnn} = "False" ] && [ ${_flag_quant} = "True" ]; then + precision="int8" + fi + _save_log_path="${_log_path}/cpp_infer_cpu_usemkldnn_${use_mkldnn}_threads_${threads}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${cpp_image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${cpp_benchmark_key}" "${cpp_benchmark_value}") + set_batchsize=$(func_set_params "${cpp_batch_size_key}" "${batch_size}") + set_mkldnn=$(func_set_params "${cpp_use_mkldnn_key}" "${use_mkldnn}") + set_cpu_threads=$(func_set_params "${cpp_cpu_threads_key}" "${threads}") + set_model_dir=$(func_set_params "${cpp_infer_model_key}" "${_model_dir}") + set_infer_params1=$(func_set_params "${cpp_infer_key1}" "${cpp_infer_value1}") + set_det=$(func_set_params "${cpp_det_key}" "${cpp_det_value}") + set_rec=$(func_set_params "${cpp_rec_key}" "${cpp_rec_value}") + set_cls=$(func_set_params "${cpp_cls_key}" "${cpp_cls_value}") + set_use_angle_cls=$(func_set_params "${cpp_use_angle_cls_key}" "${cpp_use_angle_cls_value}") + command="${_script} ${cpp_use_gpu_key}=${use_gpu} ${set_mkldnn} ${set_cpu_threads} ${set_model_dir} ${set_batchsize} ${set_infer_data} ${set_benchmark} ${set_det} ${set_rec} ${set_cls} ${set_use_angle_cls} ${set_infer_params1} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + done + done + done + elif [ ${use_gpu} = "True" ] || [ ${use_gpu} = "gpu" ]; then + for use_trt in ${cpp_use_trt_list[*]}; do + for precision in ${cpp_precision_list[*]}; do + if [[ ${_flag_quant} = "False" ]] && [[ ${precision} =~ "int8" ]]; then + continue + fi + if [[ ${precision} =~ "fp16" || ${precision} =~ "int8" ]] && [ ${use_trt} = "False" ]; then + continue + fi + if [[ ${use_trt} = "False" || ${precision} =~ "int8" ]] && [ ${_flag_quant} = "True" ]; then + continue + fi + for batch_size in ${cpp_batch_size_list[*]}; do + _save_log_path="${_log_path}/cpp_infer_gpu_usetrt_${use_trt}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${cpp_image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${cpp_benchmark_key}" "${cpp_benchmark_value}") + set_batchsize=$(func_set_params "${cpp_batch_size_key}" "${batch_size}") + set_tensorrt=$(func_set_params "${cpp_use_trt_key}" "${use_trt}") + set_precision=$(func_set_params "${cpp_precision_key}" "${precision}") + set_model_dir=$(func_set_params "${cpp_infer_model_key}" "${_model_dir}") + set_infer_params1=$(func_set_params "${cpp_infer_key1}" "${cpp_infer_value1}") + set_det=$(func_set_params "${cpp_det_key}" "${cpp_det_value}") + set_rec=$(func_set_params "${cpp_rec_key}" "${cpp_rec_value}") + set_cls=$(func_set_params "${cpp_cls_key}" "${cpp_cls_value}") + set_use_angle_cls=$(func_set_params "${cpp_use_angle_cls_key}" "${cpp_use_angle_cls_value}") + command="${_script} ${cpp_use_gpu_key}=${use_gpu} ${set_tensorrt} ${set_precision} ${set_model_dir} ${set_batchsize} ${set_infer_data} ${set_benchmark} ${set_det} ${set_rec} ${set_cls} ${set_use_angle_cls} ${set_infer_params1} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + + done + done + done + else + echo "Does not support hardware other than CPU and GPU Currently!" + fi + done +} + + +cd deploy/cpp_infer +if [ ${use_opencv} = "True" ]; then + if [ -d "opencv-3.4.7/opencv3/" ] && [ $(md5sum opencv-3.4.7.tar.gz | awk -F ' ' '{print $1}') = "faa2b5950f8bee3f03118e600c74746a" ];then + echo "################### build opencv skipped ###################" + else + echo "################### build opencv ###################" + rm -rf opencv-3.4.7.tar.gz opencv-3.4.7/ + wget https://paddleocr.bj.bcebos.com/dygraph_v2.0/test/opencv-3.4.7.tar.gz + tar -xf opencv-3.4.7.tar.gz + + cd opencv-3.4.7/ + install_path=$(pwd)/opencv3 + + rm -rf build + mkdir build + cd build + + cmake .. \ + -DCMAKE_INSTALL_PREFIX=${install_path} \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DWITH_IPP=OFF \ + -DBUILD_IPP_IW=OFF \ + -DWITH_LAPACK=OFF \ + -DWITH_EIGEN=OFF \ + -DCMAKE_INSTALL_LIBDIR=lib64 \ + -DWITH_ZLIB=ON \ + -DBUILD_ZLIB=ON \ + -DWITH_JPEG=ON \ + -DBUILD_JPEG=ON \ + -DWITH_PNG=ON \ + -DBUILD_PNG=ON \ + -DWITH_TIFF=ON \ + -DBUILD_TIFF=ON + + make -j + make install + cd ../.. + echo "################### build opencv finished ###################" + fi +fi + + +echo "################### build PaddleOCR demo ####################" +if [ ${use_opencv} = "True" ]; then + OPENCV_DIR=$(pwd)/opencv-3.4.7/opencv3/ +else + OPENCV_DIR='' +fi +if [ -d "paddle_inference/" ] ;then + echo "################### download paddle inference skipped ###################" +else + echo "################### download paddle inference ###################" + PADDLEInfer=$3 + if [ "" = "$PADDLEInfer" ];then + wget -nc https://paddle-inference-lib.bj.bcebos.com/2.3.0/cxx_c/Linux/GPU/x86-64_gcc8.2_avx_mkl_cuda10.1_cudnn7.6.5_trt6.0.1.5/paddle_inference.tgz --no-check-certificate + else + wget -nc $PADDLEInfer --no-check-certificate + fi + tar zxf paddle_inference.tgz + if [ ! -d "paddle_inference" ]; then + ln -s paddle_inference_install_dir paddle_inference + fi + echo "################### download paddle inference finished ###################" +fi +LIB_DIR=$(pwd)/paddle_inference/ +CUDA_LIB_DIR=$(dirname `find /usr -name libcudart.so`) +CUDNN_LIB_DIR=$(dirname `find /usr -name libcudnn.so`) + +BUILD_DIR=build +rm -rf ${BUILD_DIR} +mkdir ${BUILD_DIR} +cd ${BUILD_DIR} +cmake .. \ + -DPADDLE_LIB=${LIB_DIR} \ + -DWITH_MKL=ON \ + -DWITH_GPU=OFF \ + -DWITH_STATIC_LIB=OFF \ + -DWITH_TENSORRT=OFF \ + -DOPENCV_DIR=${OPENCV_DIR} \ + -DCUDNN_LIB=${CUDNN_LIB_DIR} \ + -DCUDA_LIB=${CUDA_LIB_DIR} \ + -DTENSORRT_DIR=${TENSORRT_DIR} \ + +make -j +cd ../../../ +echo "################### build PaddleOCR demo finished ###################" + + +# set cuda device +GPUID=$2 +if [ ${#GPUID} -le 0 ];then + env="export CUDA_VISIBLE_DEVICES=0" +else + env="export CUDA_VISIBLE_DEVICES=${GPUID}" +fi +eval $env + + +echo "################### run test ###################" +export Count=0 +IFS="|" +infer_quant_flag=(${cpp_infer_is_quant}) +for infer_model in ${cpp_infer_model_dir_list[*]}; do + #run inference + is_quant=${infer_quant_flag[Count]} + func_cpp_inference "${inference_cmd}" "${infer_model}" "${LOG_PATH}" "${cpp_infer_img_dir}" ${is_quant} + Count=$(($Count + 1)) +done diff --git a/test_tipc/test_inference_python.sh b/test_tipc/test_inference_python.sh new file mode 100644 index 0000000..a127358 --- /dev/null +++ b/test_tipc/test_inference_python.sh @@ -0,0 +1,169 @@ +#!/bin/bash +source test_tipc/common_func.sh +#source test_tipc/test_train_inference_python.sh + +FILENAME=$1 +# MODE be one of ['whole_infer'] +MODE=$2 + +dataline=$(awk 'NR==1, NR==20{print}' $FILENAME) + +# parser params +IFS=$'\n' +lines=(${dataline}) + +model_name=$(func_parser_value "${lines[1]}") +python=$(func_parser_value "${lines[2]}") + +infer_model_dir_list=$(func_parser_value "${lines[3]}") +infer_export_list=$(func_parser_value "${lines[4]}") +infer_is_quant=$(func_parser_value "${lines[5]}") +# parser inference +inference_py=$(func_parser_value "${lines[6]}") +use_gpu_key=$(func_parser_key "${lines[7]}") +use_gpu_list=$(func_parser_value "${lines[7]}") +use_mkldnn_key=$(func_parser_key "${lines[8]}") +use_mkldnn_list=$(func_parser_value "${lines[8]}") +cpu_threads_key=$(func_parser_key "${lines[9]}") +cpu_threads_list=$(func_parser_value "${lines[9]}") +batch_size_key=$(func_parser_key "${lines[10]}") +batch_size_list=$(func_parser_value "${lines[10]}") +use_trt_key=$(func_parser_key "${lines[11]}") +use_trt_list=$(func_parser_value "${lines[11]}") +precision_key=$(func_parser_key "${lines[12]}") +precision_list=$(func_parser_value "${lines[12]}") +infer_model_key=$(func_parser_key "${lines[13]}") +image_dir_key=$(func_parser_key "${lines[14]}") +infer_img_dir=$(func_parser_value "${lines[14]}") +rec_model_key=$(func_parser_key "${lines[15]}") +rec_model_value=$(func_parser_value "${lines[15]}") +benchmark_key=$(func_parser_key "${lines[16]}") +benchmark_value=$(func_parser_value "${lines[16]}") +infer_key1=$(func_parser_key "${lines[17]}") +infer_value1=$(func_parser_value "${lines[17]}") + + + +LOG_PATH="./test_tipc/output/${model_name}/${MODE}" +mkdir -p ${LOG_PATH} +status_log="${LOG_PATH}/results_python.log" + + +function func_inference(){ + IFS='|' + _python=$1 + _script=$2 + _model_dir=$3 + _log_path=$4 + _img_dir=$5 + _flag_quant=$6 + # inference + for use_gpu in ${use_gpu_list[*]}; do + if [ ${use_gpu} = "False" ] || [ ${use_gpu} = "cpu" ]; then + for use_mkldnn in ${use_mkldnn_list[*]}; do + if [ ${use_mkldnn} = "False" ] && [ ${_flag_quant} = "True" ]; then + continue + fi + for threads in ${cpu_threads_list[*]}; do + for batch_size in ${batch_size_list[*]}; do + for precision in ${precision_list[*]}; do + if [ ${use_mkldnn} = "False" ] && [ ${precision} = "fp16" ]; then + continue + fi # skip when enable fp16 but disable mkldnn + if [ ${_flag_quant} = "True" ] && [ ${precision} != "int8" ]; then + continue + fi # skip when quant model inference but precision is not int8 + set_precision=$(func_set_params "${precision_key}" "${precision}") + + _save_log_path="${_log_path}/python_infer_cpu_usemkldnn_${use_mkldnn}_threads_${threads}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${benchmark_key}" "${benchmark_value}") + set_batchsize=$(func_set_params "${batch_size_key}" "${batch_size}") + set_mkldnn=$(func_set_params "${use_mkldnn_key}" "${use_mkldnn}") + set_cpu_threads=$(func_set_params "${cpu_threads_key}" "${threads}") + set_model_dir=$(func_set_params "${infer_model_key}" "${_model_dir}") + set_infer_params0=$(func_set_params "${rec_model_key}" "${rec_model_value}") + set_infer_params1=$(func_set_params "${infer_key1}" "${infer_value1}") + command="${_python} ${_script} ${use_gpu_key}=${use_gpu} ${set_mkldnn} ${set_cpu_threads} ${set_model_dir} ${set_batchsize} ${set_infer_params0} ${set_infer_data} ${set_benchmark} ${set_precision} ${set_infer_params1} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + done + done + done + done + elif [ ${use_gpu} = "True" ] || [ ${use_gpu} = "gpu" ]; then + for use_trt in ${use_trt_list[*]}; do + for precision in ${precision_list[*]}; do + if [[ ${_flag_quant} = "False" ]] && [[ ${precision} =~ "int8" ]]; then + continue + fi + if [[ ${precision} =~ "fp16" || ${precision} =~ "int8" ]] && [ ${use_trt} = "False" ]; then + continue + fi + if [[ ${use_trt} = "False" || ${precision} =~ "int8" ]] && [ ${_flag_quant} = "True" ]; then + continue + fi + for batch_size in ${batch_size_list[*]}; do + _save_log_path="${_log_path}/python_infer_gpu_usetrt_${use_trt}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${benchmark_key}" "${benchmark_value}") + set_batchsize=$(func_set_params "${batch_size_key}" "${batch_size}") + set_tensorrt=$(func_set_params "${use_trt_key}" "${use_trt}") + set_precision=$(func_set_params "${precision_key}" "${precision}") + set_model_dir=$(func_set_params "${infer_model_key}" "${_model_dir}") + set_infer_params0=$(func_set_params "${rec_model_key}" "${rec_model_value}") + set_infer_params1=$(func_set_params "${infer_key1}" "${infer_value1}") + command="${_python} ${_script} ${use_gpu_key}=${use_gpu} ${set_tensorrt} ${set_precision} ${set_model_dir} ${set_batchsize} ${set_infer_data} ${set_benchmark} ${set_infer_params1} ${set_infer_params0} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + + done + done + done + else + echo "Does not support hardware other than CPU and GPU Currently!" + fi + done +} + +if [ ${MODE} = "whole_infer" ]; then + GPUID=$3 + if [ ${#GPUID} -le 0 ];then + env=" " + else + env="export CUDA_VISIBLE_DEVICES=${GPUID}" + fi + # set CUDA_VISIBLE_DEVICES + eval $env + export Count=0 + IFS="|" + infer_run_exports=(${infer_export_list}) + infer_quant_flag=(${infer_is_quant}) + for infer_model in ${infer_model_dir_list[*]}; do + # run export + if [ ${infer_run_exports[Count]} != "null" ];then + _save_log_path="${_log_path}/python_infer_gpu_usetrt_${use_trt}_precision_${precision}_batchsize_${batch_size}_infermodel_${infer_model}.log" + save_infer_dir=$(dirname $infer_model) + set_export_weight=$(func_set_params "${export_weight}" "${infer_model}") + set_save_infer_key=$(func_set_params "${save_infer_key}" "${save_infer_dir}") + export_cmd="${python} ${infer_run_exports[Count]} ${set_export_weight} ${set_save_infer_key} > ${_save_log_path} 2>&1 " + echo ${infer_run_exports[Count]} + eval $export_cmd + status_export=$? + status_check $status_export "${export_cmd}" "${status_log}" "${model_name}" "${_save_log_path}" + else + save_infer_dir=${infer_model} + fi + #run inference + is_quant=${infer_quant_flag[Count]} + if [ ${MODE} = "klquant_infer" ]; then + is_quant="True" + fi + func_inference "${python}" "${inference_py}" "${save_infer_dir}" "${LOG_PATH}" "${infer_img_dir}" ${is_quant} + Count=$(($Count + 1)) + done +fi diff --git a/test_tipc/test_lite_arm_cpp.sh b/test_tipc/test_lite_arm_cpp.sh new file mode 100644 index 0000000..c071a23 --- /dev/null +++ b/test_tipc/test_lite_arm_cpp.sh @@ -0,0 +1,159 @@ +#!/bin/bash +source ./common_func.sh +export LD_LIBRARY_PATH=${PWD}:$LD_LIBRARY_PATH + +FILENAME=$1 +dataline=$(cat $FILENAME) +# parser params +IFS=$'\n' +lines=(${dataline}) + +# parser lite inference +inference_cmd=$(func_parser_value "${lines[1]}") +runtime_device=$(func_parser_value "${lines[2]}") +det_model_list=$(func_parser_value "${lines[3]}") +rec_model_list=$(func_parser_value "${lines[4]}") +cls_model_list=$(func_parser_value "${lines[5]}") +cpu_threads_list=$(func_parser_value "${lines[6]}") +det_batch_size_list=$(func_parser_value "${lines[7]}") +rec_batch_size_list=$(func_parser_value "${lines[8]}") +infer_img_dir_list=$(func_parser_value "${lines[9]}") +config_dir=$(func_parser_value "${lines[10]}") +rec_dict_dir=$(func_parser_value "${lines[11]}") +benchmark_value=$(func_parser_value "${lines[12]}") + +if [[ $inference_cmd =~ "det" ]]; then + lite_model_list=${det_lite_model_list} +elif [[ $inference_cmd =~ "rec" ]]; then + lite_model_list=(${rec_lite_model_list[*]} ${cls_lite_model_list[*]}) +elif [[ $inference_cmd =~ "system" ]]; then + lite_model_list=(${det_lite_model_list[*]} ${rec_lite_model_list[*]} ${cls_lite_model_list[*]}) +else + echo "inference_cmd is wrong, please check." + exit 1 +fi + +LOG_PATH="./output" +mkdir -p ${LOG_PATH} +status_log="${LOG_PATH}/results.log" + + +function func_test_det(){ + IFS='|' + _script=$1 + _det_model=$2 + _log_path=$3 + _img_dir=$4 + _config=$5 + if [[ $_det_model =~ "slim" ]]; then + precision="INT8" + else + precision="FP32" + fi + + # lite inference + for num_threads in ${cpu_threads_list[*]}; do + for det_batchsize in ${det_batch_size_list[*]}; do + _save_log_path="${_log_path}/lite_${_det_model}_runtime_device_${runtime_device}_precision_${precision}_det_batchsize_${det_batchsize}_threads_${num_threads}.log" + command="${_script} ${_det_model} ${runtime_device} ${precision} ${num_threads} ${det_batchsize} ${_img_dir} ${_config} ${benchmark_value} > ${_save_log_path} 2>&1" + eval ${command} + status_check $? "${command}" "${status_log}" + done + done +} + +function func_test_rec(){ + IFS='|' + _script=$1 + _rec_model=$2 + _cls_model=$3 + _log_path=$4 + _img_dir=$5 + _config=$6 + _rec_dict_dir=$7 + + if [[ $_det_model =~ "slim" ]]; then + _precision="INT8" + else + _precision="FP32" + fi + + # lite inference + for num_threads in ${cpu_threads_list[*]}; do + for rec_batchsize in ${rec_batch_size_list[*]}; do + _save_log_path="${_log_path}/lite_${_rec_model}_${cls_model}_runtime_device_${runtime_device}_precision_${_precision}_rec_batchsize_${rec_batchsize}_threads_${num_threads}.log" + command="${_script} ${_rec_model} ${_cls_model} ${runtime_device} ${_precision} ${num_threads} ${rec_batchsize} ${_img_dir} ${_config} ${_rec_dict_dir} ${benchmark_value} > ${_save_log_path} 2>&1" + eval ${command} + status_check $? "${command}" "${status_log}" + done + done +} + +function func_test_system(){ + IFS='|' + _script=$1 + _det_model=$2 + _rec_model=$3 + _cls_model=$4 + _log_path=$5 + _img_dir=$6 + _config=$7 + _rec_dict_dir=$8 + if [[ $_det_model =~ "slim" ]]; then + _precision="INT8" + else + _precision="FP32" + fi + + # lite inference + for num_threads in ${cpu_threads_list[*]}; do + for det_batchsize in ${det_batch_size_list[*]}; do + for rec_batchsize in ${rec_batch_size_list[*]}; do + _save_log_path="${_log_path}/lite_${_det_model}_${_rec_model}_${_cls_model}_runtime_device_${runtime_device}_precision_${_precision}_det_batchsize_${det_batchsize}_rec_batchsize_${rec_batchsize}_threads_${num_threads}.log" + command="${_script} ${_det_model} ${_rec_model} ${_cls_model} ${runtime_device} ${_precision} ${num_threads} ${det_batchsize} ${_img_dir} ${_config} ${_rec_dict_dir} ${benchmark_value} > ${_save_log_path} 2>&1" + eval ${command} + status_check $? "${command}" "${status_log}" + done + done + done +} + + +echo "################### run test ###################" + +if [[ $inference_cmd =~ "det" ]]; then + IFS="|" + det_model_list=(${det_model_list[*]}) + + for i in {0..1}; do + #run lite inference + for img_dir in ${infer_img_dir_list[*]}; do + func_test_det "${inference_cmd}" "${det_model_list[i]}_opt.nb" "${LOG_PATH}" "${img_dir}" "${config_dir}" + done + done + +elif [[ $inference_cmd =~ "rec" ]]; then + IFS="|" + rec_model_list=(${rec_model_list[*]}) + cls_model_list=(${cls_model_list[*]}) + + for i in {0..1}; do + #run lite inference + for img_dir in ${infer_img_dir_list[*]}; do + func_test_rec "${inference_cmd}" "${rec_model}_opt.nb" "${cls_model_list[i]}_opt.nb" "${LOG_PATH}" "${img_dir}" "${rec_dict_dir}" "${config_dir}" + done + done + +elif [[ $inference_cmd =~ "system" ]]; then + IFS="|" + det_model_list=(${det_model_list[*]}) + rec_model_list=(${rec_model_list[*]}) + cls_model_list=(${cls_model_list[*]}) + + for i in {0..1}; do + #run lite inference + for img_dir in ${infer_img_dir_list[*]}; do + func_test_system "${inference_cmd}" "${det_model_list[i]}_opt.nb" "${rec_model_list[i]}_opt.nb" "${cls_model_list[i]}_opt.nb" "${LOG_PATH}" "${img_dir}" "${config_dir}" "${rec_dict_dir}" + done + done +fi diff --git a/test_tipc/test_paddle2onnx.sh b/test_tipc/test_paddle2onnx.sh new file mode 100644 index 0000000..f035e6b --- /dev/null +++ b/test_tipc/test_paddle2onnx.sh @@ -0,0 +1,174 @@ +#!/bin/bash +source test_tipc/common_func.sh + +FILENAME=$1 + +dataline=$(cat ${FILENAME}) +lines=(${dataline}) +# common params +model_name=$(func_parser_value "${lines[1]}") +python=$(func_parser_value "${lines[2]}") + + +# parser params +dataline=$(awk 'NR==1, NR==17{print}' $FILENAME) +IFS=$'\n' +lines=(${dataline}) + +# parser paddle2onnx +model_name=$(func_parser_value "${lines[1]}") +python=$(func_parser_value "${lines[2]}") +padlle2onnx_cmd=$(func_parser_value "${lines[3]}") +det_infer_model_dir_key=$(func_parser_key "${lines[4]}") +det_infer_model_dir_value=$(func_parser_value "${lines[4]}") +model_filename_key=$(func_parser_key "${lines[5]}") +model_filename_value=$(func_parser_value "${lines[5]}") +params_filename_key=$(func_parser_key "${lines[6]}") +params_filename_value=$(func_parser_value "${lines[6]}") +det_save_file_key=$(func_parser_key "${lines[7]}") +det_save_file_value=$(func_parser_value "${lines[7]}") +rec_infer_model_dir_key=$(func_parser_key "${lines[8]}") +rec_infer_model_dir_value=$(func_parser_value "${lines[8]}") +rec_save_file_key=$(func_parser_key "${lines[9]}") +rec_save_file_value=$(func_parser_value "${lines[9]}") +opset_version_key=$(func_parser_key "${lines[10]}") +opset_version_value=$(func_parser_value "${lines[10]}") +enable_onnx_checker_key=$(func_parser_key "${lines[11]}") +enable_onnx_checker_value=$(func_parser_value "${lines[11]}") +# parser onnx inference +inference_py=$(func_parser_value "${lines[12]}") +use_gpu_key=$(func_parser_key "${lines[13]}") +use_gpu_list=$(func_parser_value "${lines[13]}") +det_model_key=$(func_parser_key "${lines[14]}") +rec_model_key=$(func_parser_key "${lines[15]}") +image_dir_key=$(func_parser_key "${lines[16]}") +image_dir_value=$(func_parser_value "${lines[16]}") + +LOG_PATH="./test_tipc/output/${model_name}/paddle2onnx" +mkdir -p ${LOG_PATH} +status_log="${LOG_PATH}/results_paddle2onnx.log" + + +function func_paddle2onnx(){ + IFS='|' + _script=$1 + + # paddle2onnx + if [ ${model_name} = "ch_PP-OCRv2" ] || [ ${model_name} = "ch_PP-OCRv3" ] || [ ${model_name} = "ch_ppocr_mobile_v2_0" ] || [ ${model_name} = "ch_ppocr_server_v2_0" ]; then + # trans det + set_dirname=$(func_set_params "--model_dir" "${det_infer_model_dir_value}") + set_model_filename=$(func_set_params "${model_filename_key}" "${model_filename_value}") + set_params_filename=$(func_set_params "${params_filename_key}" "${params_filename_value}") + set_save_model=$(func_set_params "--save_file" "${det_save_file_value}") + set_opset_version=$(func_set_params "${opset_version_key}" "${opset_version_value}") + set_enable_onnx_checker=$(func_set_params "${enable_onnx_checker_key}" "${enable_onnx_checker_value}") + trans_det_log="${LOG_PATH}/trans_model_det.log" + trans_model_cmd="${padlle2onnx_cmd} ${set_dirname} ${set_model_filename} ${set_params_filename} ${set_save_model} ${set_opset_version} ${set_enable_onnx_checker} --enable_dev_version=False > ${trans_det_log} 2>&1 " + eval $trans_model_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${trans_model_cmd}" "${status_log}" "${model_name}" "${trans_det_log}" + # trans rec + set_dirname=$(func_set_params "--model_dir" "${rec_infer_model_dir_value}") + set_model_filename=$(func_set_params "${model_filename_key}" "${model_filename_value}") + set_params_filename=$(func_set_params "${params_filename_key}" "${params_filename_value}") + set_save_model=$(func_set_params "--save_file" "${rec_save_file_value}") + set_opset_version=$(func_set_params "${opset_version_key}" "${opset_version_value}") + set_enable_onnx_checker=$(func_set_params "${enable_onnx_checker_key}" "${enable_onnx_checker_value}") + trans_rec_log="${LOG_PATH}/trans_model_rec.log" + trans_model_cmd="${padlle2onnx_cmd} ${set_dirname} ${set_model_filename} ${set_params_filename} ${set_save_model} ${set_opset_version} ${set_enable_onnx_checker} --enable_dev_version=False > ${trans_rec_log} 2>&1 " + eval $trans_model_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${trans_model_cmd}" "${status_log}" "${model_name}" "${trans_rec_log}" + elif [[ ${model_name} =~ "det" ]]; then + # trans det + set_dirname=$(func_set_params "--model_dir" "${det_infer_model_dir_value}") + set_model_filename=$(func_set_params "${model_filename_key}" "${model_filename_value}") + set_params_filename=$(func_set_params "${params_filename_key}" "${params_filename_value}") + set_save_model=$(func_set_params "--save_file" "${det_save_file_value}") + set_opset_version=$(func_set_params "${opset_version_key}" "${opset_version_value}") + set_enable_onnx_checker=$(func_set_params "${enable_onnx_checker_key}" "${enable_onnx_checker_value}") + trans_det_log="${LOG_PATH}/trans_model_det.log" + trans_model_cmd="${padlle2onnx_cmd} ${set_dirname} ${set_model_filename} ${set_params_filename} ${set_save_model} ${set_opset_version} ${set_enable_onnx_checker} --enable_dev_version=False > ${trans_det_log} 2>&1 " + eval $trans_model_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${trans_model_cmd}" "${status_log}" "${model_name}" "${trans_det_log}" + elif [[ ${model_name} =~ "rec" ]]; then + # trans rec + set_dirname=$(func_set_params "--model_dir" "${rec_infer_model_dir_value}") + set_model_filename=$(func_set_params "${model_filename_key}" "${model_filename_value}") + set_params_filename=$(func_set_params "${params_filename_key}" "${params_filename_value}") + set_save_model=$(func_set_params "--save_file" "${rec_save_file_value}") + set_opset_version=$(func_set_params "${opset_version_key}" "${opset_version_value}") + set_enable_onnx_checker=$(func_set_params "${enable_onnx_checker_key}" "${enable_onnx_checker_value}") + trans_rec_log="${LOG_PATH}/trans_model_rec.log" + trans_model_cmd="${padlle2onnx_cmd} ${set_dirname} ${set_model_filename} ${set_params_filename} ${set_save_model} ${set_opset_version} ${set_enable_onnx_checker} --enable_dev_version=False > ${trans_rec_log} 2>&1 " + eval $trans_model_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${trans_model_cmd}" "${status_log}" "${model_name}" "${trans_rec_log}" + elif [ ${model_name} = "slanet" ] || [ ${model_name} = "en_table_structure" ]; then + # trans det + set_dirname=$(func_set_params "--model_dir" "${det_infer_model_dir_value}") + set_model_filename=$(func_set_params "${model_filename_key}" "${model_filename_value}") + set_params_filename=$(func_set_params "${params_filename_key}" "${params_filename_value}") + set_save_model=$(func_set_params "--save_file" "${det_save_file_value}") + set_opset_version=$(func_set_params "${opset_version_key}" "${opset_version_value}") + set_enable_onnx_checker=$(func_set_params "${enable_onnx_checker_key}" "${enable_onnx_checker_value}") + trans_det_log="${LOG_PATH}/trans_model_det.log" + trans_model_cmd="${padlle2onnx_cmd} ${set_dirname} ${set_model_filename} ${set_params_filename} ${set_save_model} ${set_opset_version} ${set_enable_onnx_checker} --enable_dev_version=True > ${trans_det_log} 2>&1 " + eval $trans_model_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${trans_model_cmd}" "${status_log}" "${model_name}" "${trans_det_log}" + fi + + # python inference + for use_gpu in ${use_gpu_list[*]}; do + if [ ${use_gpu} = "False" ] || [ ${use_gpu} = "cpu" ]; then + _save_log_path="${LOG_PATH}/paddle2onnx_infer_cpu.log" + set_gpu=$(func_set_params "${use_gpu_key}" "${use_gpu}") + set_img_dir=$(func_set_params "${image_dir_key}" "${image_dir_value}") + if [ ${model_name} = "ch_PP-OCRv2" ] || [ ${model_name} = "ch_PP-OCRv3" ] || [ ${model_name} = "ch_ppocr_mobile_v2_0" ] || [ ${model_name} = "ch_ppocr_server_v2_0" ]; then + set_det_model_dir=$(func_set_params "${det_model_key}" "${det_save_file_value}") + set_rec_model_dir=$(func_set_params "${rec_model_key}" "${rec_save_file_value}") + infer_model_cmd="${python} ${inference_py} ${set_gpu} ${set_img_dir} ${set_det_model_dir} ${set_rec_model_dir} --use_onnx=True > ${_save_log_path} 2>&1 " + elif [[ ${model_name} =~ "det" ]] || [ ${model_name} = "slanet" ] || [ ${model_name} = "en_table_structure" ]; then + set_det_model_dir=$(func_set_params "${det_model_key}" "${det_save_file_value}") + infer_model_cmd="${python} ${inference_py} ${set_gpu} ${set_img_dir} ${set_det_model_dir} --use_onnx=True > ${_save_log_path} 2>&1 " + elif [[ ${model_name} =~ "rec" ]]; then + set_rec_model_dir=$(func_set_params "${rec_model_key}" "${rec_save_file_value}") + infer_model_cmd="${python} ${inference_py} ${set_gpu} ${set_img_dir} ${set_rec_model_dir} --use_onnx=True > ${_save_log_path} 2>&1 " + fi + eval $infer_model_cmd + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${infer_model_cmd}" "${status_log}" "${model_name}" "${_save_log_path}" + elif [ ${use_gpu} = "True" ] || [ ${use_gpu} = "gpu" ]; then + _save_log_path="${LOG_PATH}/paddle2onnx_infer_gpu.log" + set_gpu=$(func_set_params "${use_gpu_key}" "${use_gpu}") + set_img_dir=$(func_set_params "${image_dir_key}" "${image_dir_value}") + if [ ${model_name} = "ch_PP-OCRv2" ] || [ ${model_name} = "ch_PP-OCRv3" ] || [ ${model_name} = "ch_ppocr_mobile_v2_0" ] || [ ${model_name} = "ch_ppocr_server_v2_0" ]; then + set_det_model_dir=$(func_set_params "${det_model_key}" "${det_save_file_value}") + set_rec_model_dir=$(func_set_params "${rec_model_key}" "${rec_save_file_value}") + infer_model_cmd="${python} ${inference_py} ${set_gpu} ${set_img_dir} ${set_det_model_dir} ${set_rec_model_dir} --use_onnx=True > ${_save_log_path} 2>&1 " + elif [[ ${model_name} =~ "det" ]]|| [ ${model_name} = "slanet" ] || [ ${model_name} = "en_table_structure" ]; then + set_det_model_dir=$(func_set_params "${det_model_key}" "${det_save_file_value}") + infer_model_cmd="${python} ${inference_py} ${set_gpu} ${set_img_dir} ${set_det_model_dir} --use_onnx=True > ${_save_log_path} 2>&1 " + elif [[ ${model_name} =~ "rec" ]]; then + set_rec_model_dir=$(func_set_params "${rec_model_key}" "${rec_save_file_value}") + infer_model_cmd="${python} ${inference_py} ${set_gpu} ${set_img_dir} ${set_rec_model_dir} --use_onnx=True > ${_save_log_path} 2>&1 " + fi + eval $infer_model_cmd + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${infer_model_cmd}" "${status_log}" "${model_name}" "${_save_log_path}" + else + echo "Does not support hardware other than CPU and GPU Currently!" + fi + done +} + + +echo "################### run test ###################" + +export Count=0 +IFS="|" +func_paddle2onnx diff --git a/test_tipc/test_ptq_inference_python.sh b/test_tipc/test_ptq_inference_python.sh new file mode 100644 index 0000000..f9cd829 --- /dev/null +++ b/test_tipc/test_ptq_inference_python.sh @@ -0,0 +1,157 @@ +#!/bin/bash +source test_tipc/common_func.sh + +FILENAME=$1 +# MODE be one of [''whole_infer'] +MODE=$2 + +IFS=$'\n' +# parser klquant_infer params + +dataline=$(awk 'NR==1, NR==17{print}' $FILENAME) +lines=(${dataline}) +model_name=$(func_parser_value "${lines[1]}") +python=$(func_parser_value "${lines[2]}") +export_weight=$(func_parser_key "${lines[3]}") +save_infer_key=$(func_parser_key "${lines[4]}") +# parser inference model +infer_model_dir_list=$(func_parser_value "${lines[5]}") +infer_export_list=$(func_parser_value "${lines[6]}") +infer_is_quant=$(func_parser_value "${lines[7]}") +# parser inference +inference_py=$(func_parser_value "${lines[8]}") +use_gpu_key=$(func_parser_key "${lines[9]}") +use_gpu_list=$(func_parser_value "${lines[9]}") +use_mkldnn_key=$(func_parser_key "${lines[10]}") +use_mkldnn_list=$(func_parser_value "${lines[10]}") +cpu_threads_key=$(func_parser_key "${lines[11]}") +cpu_threads_list=$(func_parser_value "${lines[11]}") +batch_size_key=$(func_parser_key "${lines[12]}") +batch_size_list=$(func_parser_value "${lines[12]}") +use_trt_key=$(func_parser_key "${lines[13]}") +use_trt_list=$(func_parser_value "${lines[13]}") +precision_key=$(func_parser_key "${lines[14]}") +precision_list=$(func_parser_value "${lines[14]}") +infer_model_key=$(func_parser_key "${lines[15]}") +image_dir_key=$(func_parser_key "${lines[16]}") +infer_img_dir=$(func_parser_value "${lines[16]}") +save_log_key=$(func_parser_key "${lines[17]}") +save_log_value=$(func_parser_value "${lines[17]}") +benchmark_key=$(func_parser_key "${lines[18]}") +benchmark_value=$(func_parser_value "${lines[18]}") +infer_key1=$(func_parser_key "${lines[19]}") +infer_value1=$(func_parser_value "${lines[19]}") + + +LOG_PATH="./test_tipc/output/${model_name}/${MODE}" +mkdir -p ${LOG_PATH} +status_log="${LOG_PATH}/results_python.log" + + +function func_inference(){ + IFS='|' + _python=$1 + _script=$2 + _model_dir=$3 + _log_path=$4 + _img_dir=$5 + _flag_quant=$6 + # inference + for use_gpu in ${use_gpu_list[*]}; do + if [ ${use_gpu} = "False" ] || [ ${use_gpu} = "cpu" ]; then + for use_mkldnn in ${use_mkldnn_list[*]}; do + for threads in ${cpu_threads_list[*]}; do + for batch_size in ${batch_size_list[*]}; do + for precision in ${precision_list[*]}; do + if [ ${use_mkldnn} = "False" ] && [ ${precision} = "fp16" ]; then + continue + fi # skip when enable fp16 but disable mkldnn + if [ ${_flag_quant} = "True" ] && [ ${precision} != "int8" ]; then + continue + fi # skip when quant model inference but precision is not int8 + set_precision=$(func_set_params "${precision_key}" "${precision}") + + _save_log_path="${_log_path}/python_infer_cpu_usemkldnn_${use_mkldnn}_threads_${threads}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${benchmark_key}" "${benchmark_value}") + set_batchsize=$(func_set_params "${batch_size_key}" "${batch_size}") + set_mkldnn=$(func_set_params "${use_mkldnn_key}" "${use_mkldnn}") + set_cpu_threads=$(func_set_params "${cpu_threads_key}" "${threads}") + set_model_dir=$(func_set_params "${infer_model_key}" "${_model_dir}") + set_infer_params0=$(func_set_params "${save_log_key}" "${save_log_value}") + set_infer_params1=$(func_set_params "${infer_key1}" "${infer_value1}") + command="${_python} ${_script} ${use_gpu_key}=${use_gpu} ${set_mkldnn} ${set_cpu_threads} ${set_model_dir} ${set_batchsize} ${set_infer_params0} ${set_infer_data} ${set_benchmark} ${set_precision} ${set_infer_params1} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + done + done + done + done + elif [ ${use_gpu} = "True" ] || [ ${use_gpu} = "gpu" ]; then + for use_trt in ${use_trt_list[*]}; do + for precision in ${precision_list[*]}; do + if [ ${_flag_quant} = "True" ] && [ ${precision} != "int8" ]; then + continue + fi # skip when quant model inference but precision is not int8 + for batch_size in ${batch_size_list[*]}; do + _save_log_path="${_log_path}/python_infer_gpu_usetrt_${use_trt}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${benchmark_key}" "${benchmark_value}") + set_batchsize=$(func_set_params "${batch_size_key}" "${batch_size}") + set_tensorrt=$(func_set_params "${use_trt_key}" "${use_trt}") + set_precision=$(func_set_params "${precision_key}" "${precision}") + set_model_dir=$(func_set_params "${infer_model_key}" "${_model_dir}") + set_infer_params0=$(func_set_params "${save_log_key}" "${save_log_value}") + set_infer_params1=$(func_set_params "${infer_key1}" "${infer_value1}") + command="${_python} ${_script} ${use_gpu_key}=${use_gpu} ${set_tensorrt} ${set_precision} ${set_model_dir} ${set_batchsize} ${set_infer_data} ${set_benchmark} ${set_infer_params1} ${set_infer_params0} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + + done + done + done + else + echo "Does not support hardware other than CPU and GPU Currently!" + fi + done +} + +if [ ${MODE} = "whole_infer" ]; then + GPUID=$3 + if [ ${#GPUID} -le 0 ];then + env=" " + else + env="export CUDA_VISIBLE_DEVICES=${GPUID}" + fi + # set CUDA_VISIBLE_DEVICES + eval $env + export Count=0 + IFS="|" + infer_run_exports=(${infer_export_list}) + infer_quant_flag=(${infer_is_quant}) + for infer_model in ${infer_model_dir_list[*]}; do + # run export + if [ ${infer_run_exports[Count]} != "null" ];then + save_infer_dir="${infer_model}_klquant" + set_export_weight=$(func_set_params "${export_weight}" "${infer_model}") + set_save_infer_key=$(func_set_params "${save_infer_key}" "${save_infer_dir}") + export_log_path="${LOG_PATH}/${MODE}_export_${Count}.log" + export_cmd="${python} ${infer_run_exports[Count]} ${set_export_weight} ${set_save_infer_key} > ${export_log_path} 2>&1 " + echo ${infer_run_exports[Count]} + echo $export_cmd + eval $export_cmd + status_export=$? + status_check $status_export "${export_cmd}" "${status_log}" "${model_name}" "${export_log_path}" + else + save_infer_dir=${infer_model} + fi + #run inference + is_quant="True" + func_inference "${python}" "${inference_py}" "${save_infer_dir}" "${LOG_PATH}" "${infer_img_dir}" ${is_quant} + Count=$(($Count + 1)) + done +fi diff --git a/test_tipc/test_serving_infer_cpp.sh b/test_tipc/test_serving_infer_cpp.sh new file mode 100644 index 0000000..6de6856 --- /dev/null +++ b/test_tipc/test_serving_infer_cpp.sh @@ -0,0 +1,142 @@ +#!/bin/bash +source test_tipc/common_func.sh + +function func_parser_model_config(){ + strs=$1 + IFS="/" + array=(${strs}) + tmp=${array[-1]} + echo ${tmp} +} + +FILENAME=$1 +dataline=$(awk 'NR==1, NR==19{print}' $FILENAME) +MODE=$2 + +# parser params +IFS=$'\n' +lines=(${dataline}) + +# parser serving +model_name=$(func_parser_value "${lines[1]}") +python_list=$(func_parser_value "${lines[2]}") +trans_model_py=$(func_parser_value "${lines[3]}") +det_infer_model_dir_key=$(func_parser_key "${lines[4]}") +det_infer_model_dir_value=$(func_parser_value "${lines[4]}") +model_filename_key=$(func_parser_key "${lines[5]}") +model_filename_value=$(func_parser_value "${lines[5]}") +params_filename_key=$(func_parser_key "${lines[6]}") +params_filename_value=$(func_parser_value "${lines[6]}") +det_serving_server_key=$(func_parser_key "${lines[7]}") +det_serving_server_value=$(func_parser_value "${lines[7]}") +det_serving_client_key=$(func_parser_key "${lines[8]}") +det_serving_client_value=$(func_parser_value "${lines[8]}") +rec_infer_model_dir_key=$(func_parser_key "${lines[9]}") +rec_infer_model_dir_value=$(func_parser_value "${lines[9]}") +rec_serving_server_key=$(func_parser_key "${lines[10]}") +rec_serving_server_value=$(func_parser_value "${lines[10]}") +rec_serving_client_key=$(func_parser_key "${lines[11]}") +rec_serving_client_value=$(func_parser_value "${lines[11]}") +det_server_value=$(func_parser_model_config "${lines[7]}") +det_client_value=$(func_parser_model_config "${lines[8]}") +rec_server_value=$(func_parser_model_config "${lines[10]}") +rec_client_value=$(func_parser_model_config "${lines[11]}") +serving_dir_value=$(func_parser_value "${lines[12]}") +web_service_py=$(func_parser_value "${lines[13]}") +op_key=$(func_parser_key "${lines[14]}") +op_value=$(func_parser_value "${lines[14]}") +port_key=$(func_parser_key "${lines[15]}") +port_value=$(func_parser_value "${lines[15]}") +gpu_key=$(func_parser_key "${lines[16]}") +gpu_value=$(func_parser_value "${lines[16]}") +cpp_client_py=$(func_parser_value "${lines[17]}") +image_dir_key=$(func_parser_key "${lines[18]}") +image_dir_value=$(func_parser_value "${lines[18]}") + +LOG_PATH="$(pwd)/test_tipc/output/${model_name}/${MODE}/cpp" +mkdir -p ${LOG_PATH} +status_log="${LOG_PATH}/results_cpp_serving.log" + +function func_serving(){ + IFS='|' + _python=$1 + _script=$2 + _model_dir=$3 + # pdserving + set_model_filename=$(func_set_params "${model_filename_key}" "${model_filename_value}") + set_params_filename=$(func_set_params "${params_filename_key}" "${params_filename_value}") + # trans det + set_dirname=$(func_set_params "--dirname" "${det_infer_model_dir_value}") + set_serving_server=$(func_set_params "--serving_server" "${det_serving_server_value}") + set_serving_client=$(func_set_params "--serving_client" "${det_serving_client_value}") + python_list=(${python_list}) + trans_det_log="${LOG_PATH}/cpp_trans_model_det.log" + trans_model_cmd="${python_list[0]} ${trans_model_py} ${set_dirname} ${set_model_filename} ${set_params_filename} ${set_serving_server} ${set_serving_client} > ${trans_det_log} 2>&1 " + eval $trans_model_cmd + cp "deploy/pdserving/serving_client_conf.prototxt" ${det_serving_client_value} + # trans rec + set_dirname=$(func_set_params "--dirname" "${rec_infer_model_dir_value}") + set_serving_server=$(func_set_params "--serving_server" "${rec_serving_server_value}") + set_serving_client=$(func_set_params "--serving_client" "${rec_serving_client_value}") + python_list=(${python_list}) + trans_rec_log="${LOG_PATH}/cpp_trans_model_rec.log" + trans_model_cmd="${python_list[0]} ${trans_model_py} ${set_dirname} ${set_model_filename} ${set_params_filename} ${set_serving_server} ${set_serving_client} > ${trans_rec_log} 2>&1 " + eval $trans_model_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${trans_model_cmd}" "${status_log}" "${model_name}" "${trans_rec_log}" + set_image_dir=$(func_set_params "${image_dir_key}" "${image_dir_value}") + python_list=(${python_list}) + cd ${serving_dir_value} + + # cpp serving + for gpu_id in ${gpu_value[*]}; do + if [ ${gpu_id} = "null" ]; then + server_log_path="${LOG_PATH}/cpp_server_cpu.log" + web_service_cpp_cmd="nohup ${python_list[0]} ${web_service_py} --model ${det_server_value} ${rec_server_value} ${op_key} ${op_value} ${port_key} ${port_value} > ${server_log_path} 2>&1 &" + eval $web_service_cpp_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${web_service_cpp_cmd}" "${status_log}" "${model_name}" "${server_log_path}" + sleep 5s + _save_log_path="${LOG_PATH}/cpp_client_cpu.log" + cpp_client_cmd="${python_list[0]} ${cpp_client_py} ${det_client_value} ${rec_client_value} > ${_save_log_path} 2>&1" + eval $cpp_client_cmd + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${cpp_client_cmd}" "${status_log}" "${model_name}" "${_save_log_path}" + #ps ux | grep -i ${port_value} | awk '{print $2}' | xargs kill -s 9 + ${python_list[0]} ${web_service_py} stop + sleep 5s + else + server_log_path="${LOG_PATH}/cpp_server_gpu.log" + web_service_cpp_cmd="nohup ${python_list[0]} ${web_service_py} --model ${det_server_value} ${rec_server_value} ${op_key} ${op_value} ${port_key} ${port_value} ${gpu_key} ${gpu_id} > ${server_log_path} 2>&1 &" + eval $web_service_cpp_cmd + sleep 5s + _save_log_path="${LOG_PATH}/cpp_client_gpu.log" + cpp_client_cmd="${python_list[0]} ${cpp_client_py} ${det_client_value} ${rec_client_value} > ${_save_log_path} 2>&1" + eval $cpp_client_cmd + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${cpp_client_cmd}" "${status_log}" "${model_name}" "${_save_log_path}" + #ps ux | grep -i ${port_value} | awk '{print $2}' | xargs kill -s 9 + ${python_list[0]} ${web_service_py} stop + fi + done +} + + +#set cuda device +GPUID=$3 +if [ ${#GPUID} -le 0 ];then + env="export CUDA_VISIBLE_DEVICES=0" +else + env="export CUDA_VISIBLE_DEVICES=${GPUID}" +fi +eval $env +echo $env + + +echo "################### run test ###################" + +export Count=0 +IFS="|" +func_serving "${web_service_cpp_cmd}" diff --git a/test_tipc/test_serving_infer_python.sh b/test_tipc/test_serving_infer_python.sh new file mode 100644 index 0000000..c7d305d --- /dev/null +++ b/test_tipc/test_serving_infer_python.sh @@ -0,0 +1,229 @@ +#!/bin/bash +source test_tipc/common_func.sh + +function func_parser_model_config(){ + strs=$1 + IFS="/" + array=(${strs}) + tmp=${array[-1]} + echo ${tmp} +} + +FILENAME=$1 +dataline=$(awk 'NR==1, NR==23{print}' $FILENAME) +MODE=$2 + +# parser params +IFS=$'\n' +lines=(${dataline}) + +# parser serving +model_name=$(func_parser_value "${lines[1]}") +python_list=$(func_parser_value "${lines[2]}") +trans_model_py=$(func_parser_value "${lines[3]}") +det_infer_model_dir_key=$(func_parser_key "${lines[4]}") +det_infer_model_dir_value=$(func_parser_value "${lines[4]}") +model_filename_key=$(func_parser_key "${lines[5]}") +model_filename_value=$(func_parser_value "${lines[5]}") +params_filename_key=$(func_parser_key "${lines[6]}") +params_filename_value=$(func_parser_value "${lines[6]}") +det_serving_server_key=$(func_parser_key "${lines[7]}") +det_serving_server_value=$(func_parser_value "${lines[7]}") +det_serving_client_key=$(func_parser_key "${lines[8]}") +det_serving_client_value=$(func_parser_value "${lines[8]}") +rec_infer_model_dir_key=$(func_parser_key "${lines[9]}") +rec_infer_model_dir_value=$(func_parser_value "${lines[9]}") +rec_serving_server_key=$(func_parser_key "${lines[10]}") +rec_serving_server_value=$(func_parser_value "${lines[10]}") +rec_serving_client_key=$(func_parser_key "${lines[11]}") +rec_serving_client_value=$(func_parser_value "${lines[11]}") +serving_dir_value=$(func_parser_value "${lines[12]}") +web_service_py=$(func_parser_value "${lines[13]}") +web_use_gpu_key=$(func_parser_key "${lines[14]}") +web_use_gpu_list=$(func_parser_value "${lines[14]}") +web_use_mkldnn_key=$(func_parser_key "${lines[15]}") +web_use_mkldnn_list=$(func_parser_value "${lines[15]}") +web_cpu_threads_key=$(func_parser_key "${lines[16]}") +web_cpu_threads_list=$(func_parser_value "${lines[16]}") +web_use_trt_key=$(func_parser_key "${lines[17]}") +web_use_trt_list=$(func_parser_value "${lines[17]}") +web_precision_key=$(func_parser_key "${lines[18]}") +web_precision_list=$(func_parser_value "${lines[18]}") +det_server_key=$(func_parser_key "${lines[19]}") +det_server_value=$(func_parser_model_config "${lines[7]}") +det_client_value=$(func_parser_model_config "${lines[8]}") +rec_server_key=$(func_parser_key "${lines[20]}") +rec_server_value=$(func_parser_model_config "${lines[10]}") +rec_client_value=$(func_parser_model_config "${lines[11]}") +pipeline_py=$(func_parser_value "${lines[21]}") +image_dir_key=$(func_parser_key "${lines[22]}") +image_dir_value=$(func_parser_value "${lines[22]}") + +LOG_PATH="$(pwd)/test_tipc/output/${model_name}/${MODE}/python" +mkdir -p ${LOG_PATH} +status_log="${LOG_PATH}/results_python_serving.log" + +function func_serving(){ + IFS='|' + _python=$1 + _script=$2 + _model_dir=$3 + # pdserving + set_model_filename=$(func_set_params "${model_filename_key}" "${model_filename_value}") + set_params_filename=$(func_set_params "${params_filename_key}" "${params_filename_value}") + if [ ${model_name} = "ch_PP-OCRv2" ] || [ ${model_name} = "ch_PP-OCRv3" ] || [ ${model_name} = "ch_ppocr_mobile_v2_0" ] || [ ${model_name} = "ch_ppocr_server_v2_0" ]; then + # trans det + set_dirname=$(func_set_params "--dirname" "${det_infer_model_dir_value}") + set_serving_server=$(func_set_params "--serving_server" "${det_serving_server_value}") + set_serving_client=$(func_set_params "--serving_client" "${det_serving_client_value}") + python_list=(${python_list}) + trans_det_log="${LOG_PATH}/python_trans_model_det.log" + trans_model_cmd="${python_list[0]} ${trans_model_py} ${set_dirname} ${set_model_filename} ${set_params_filename} ${set_serving_server} ${set_serving_client} > ${trans_det_log} 2>&1 " + eval $trans_model_cmd + # trans rec + set_dirname=$(func_set_params "--dirname" "${rec_infer_model_dir_value}") + set_serving_server=$(func_set_params "--serving_server" "${rec_serving_server_value}") + set_serving_client=$(func_set_params "--serving_client" "${rec_serving_client_value}") + python_list=(${python_list}) + trans_rec_log="${LOG_PATH}/python_trans_model_rec.log" + trans_model_cmd="${python_list[0]} ${trans_model_py} ${set_dirname} ${set_model_filename} ${set_params_filename} ${set_serving_server} ${set_serving_client} > ${trans_rec_log} 2>&1 " + eval $trans_model_cmd + elif [[ ${model_name} =~ "det" ]]; then + # trans det + set_dirname=$(func_set_params "--dirname" "${det_infer_model_dir_value}") + set_serving_server=$(func_set_params "--serving_server" "${det_serving_server_value}") + set_serving_client=$(func_set_params "--serving_client" "${det_serving_client_value}") + python_list=(${python_list}) + trans_det_log="${LOG_PATH}/python_trans_model_det.log" + trans_model_cmd="${python_list[0]} ${trans_model_py} ${set_dirname} ${set_model_filename} ${set_params_filename} ${set_serving_server} ${set_serving_client} > ${trans_det_log} 2>&1 " + eval $trans_model_cmd + elif [[ ${model_name} =~ "rec" ]]; then + # trans rec + set_dirname=$(func_set_params "--dirname" "${rec_infer_model_dir_value}") + set_serving_server=$(func_set_params "--serving_server" "${rec_serving_server_value}") + set_serving_client=$(func_set_params "--serving_client" "${rec_serving_client_value}") + python_list=(${python_list}) + trans_rec_log="${LOG_PATH}/python_trans_model_rec.log" + trans_model_cmd="${python_list[0]} ${trans_model_py} ${set_dirname} ${set_model_filename} ${set_params_filename} ${set_serving_server} ${set_serving_client} > ${trans_rec_log} 2>&1 " + eval $trans_model_cmd + fi + set_image_dir=$(func_set_params "${image_dir_key}" "${image_dir_value}") + python_list=(${python_list}) + + cd ${serving_dir_value} + python=${python_list[0]} + + # python serving + for use_gpu in ${web_use_gpu_list[*]}; do + if [ ${use_gpu} = "null" ]; then + for use_mkldnn in ${web_use_mkldnn_list[*]}; do + for threads in ${web_cpu_threads_list[*]}; do + set_cpu_threads=$(func_set_params "${web_cpu_threads_key}" "${threads}") + server_log_path="${LOG_PATH}/python_server_cpu_usemkldnn_${use_mkldnn}_threads_${threads}.log" + if [ ${model_name} = "ch_PP-OCRv2" ] || [ ${model_name} = "ch_PP-OCRv3" ] || [ ${model_name} = "ch_ppocr_mobile_v2_0" ] || [ ${model_name} = "ch_ppocr_server_v2_0" ]; then + set_det_model_config=$(func_set_params "${det_server_key}" "${det_server_value}") + set_rec_model_config=$(func_set_params "${rec_server_key}" "${rec_server_value}") + web_service_cmd="nohup ${python} ${web_service_py} ${web_use_gpu_key}="" ${web_use_mkldnn_key}=${use_mkldnn} ${set_cpu_threads} ${set_det_model_config} ${set_rec_model_config} > ${server_log_path} 2>&1 &" + eval $web_service_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" "${server_log_path}" + elif [[ ${model_name} =~ "det" ]]; then + set_det_model_config=$(func_set_params "${det_server_key}" "${det_server_value}") + web_service_cmd="nohup ${python} ${web_service_py} ${web_use_gpu_key}="" ${web_use_mkldnn_key}=${use_mkldnn} ${set_cpu_threads} ${set_det_model_config} > ${server_log_path} 2>&1 &" + eval $web_service_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" "${server_log_path}" + elif [[ ${model_name} =~ "rec" ]]; then + set_rec_model_config=$(func_set_params "${rec_server_key}" "${rec_server_value}") + web_service_cmd="nohup ${python} ${web_service_py} ${web_use_gpu_key}="" ${web_use_mkldnn_key}=${use_mkldnn} ${set_cpu_threads} ${set_rec_model_config} > ${server_log_path} 2>&1 &" + eval $web_service_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" "${server_log_path}" + fi + sleep 2s + for pipeline in ${pipeline_py[*]}; do + _save_log_path="${LOG_PATH}/python_client_cpu_${pipeline%_client*}_usemkldnn_${use_mkldnn}_threads_${threads}_batchsize_1.log" + pipeline_cmd="${python} ${pipeline} ${set_image_dir} > ${_save_log_path} 2>&1 " + eval $pipeline_cmd + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${pipeline_cmd}" "${status_log}" "${model_name}" "${_save_log_path}" + sleep 2s + done + ps ux | grep -E 'web_service' | awk '{print $2}' | xargs kill -s 9 + done + done + elif [ ${use_gpu} = "gpu" ]; then + for use_trt in ${web_use_trt_list[*]}; do + for precision in ${web_precision_list[*]}; do + server_log_path="${LOG_PATH}/python_server_gpu_usetrt_${use_trt}_precision_${precision}.log" + if [[ ${_flag_quant} = "False" ]] && [[ ${precision} =~ "int8" ]]; then + continue + fi + if [[ ${precision} =~ "fp16" || ${precision} =~ "int8" ]] && [ ${use_trt} = "False" ]; then + continue + fi + if [[ ${use_trt} = "False" || ${precision} =~ "int8" ]] && [[ ${_flag_quant} = "True" ]]; then + continue + fi + set_tensorrt=$(func_set_params "${web_use_trt_key}" "${use_trt}") + if [ ${use_trt} = True ]; then + device_type=2 + fi + set_precision=$(func_set_params "${web_precision_key}" "${precision}") + if [ ${model_name} = "ch_PP-OCRv2" ] || [ ${model_name} = "ch_PP-OCRv3" ] || [ ${model_name} = "ch_ppocr_mobile_v2_0" ] || [ ${model_name} = "ch_ppocr_server_v2_0" ]; then + set_det_model_config=$(func_set_params "${det_server_key}" "${det_server_value}") + set_rec_model_config=$(func_set_params "${rec_server_key}" "${rec_server_value}") + web_service_cmd="nohup ${python} ${web_service_py} ${set_tensorrt} ${set_precision} ${set_det_model_config} ${set_rec_model_config} > ${server_log_path} 2>&1 &" + eval $web_service_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" "${server_log_path}" + elif [[ ${model_name} =~ "det" ]]; then + set_det_model_config=$(func_set_params "${det_server_key}" "${det_server_value}") + web_service_cmd="nohup ${python} ${web_service_py} ${set_tensorrt} ${set_precision} ${set_det_model_config} > ${server_log_path} 2>&1 &" + eval $web_service_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" "${server_log_path}" + elif [[ ${model_name} =~ "rec" ]]; then + set_rec_model_config=$(func_set_params "${rec_server_key}" "${rec_server_value}") + web_service_cmd="nohup ${python} ${web_service_py} ${set_tensorrt} ${set_precision} ${set_rec_model_config} > ${server_log_path} 2>&1 &" + eval $web_service_cmd + last_status=${PIPESTATUS[0]} + status_check $last_status "${web_service_cmd}" "${status_log}" "${model_name}" "${server_log_path}" + fi + sleep 2s + for pipeline in ${pipeline_py[*]}; do + _save_log_path="${LOG_PATH}/python_client_gpu_${pipeline%_client*}_usetrt_${use_trt}_precision_${precision}_batchsize_1.log" + pipeline_cmd="${python} ${pipeline} ${set_image_dir}> ${_save_log_path} 2>&1" + eval $pipeline_cmd + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${pipeline_cmd}" "${status_log}" "${model_name}" "${_save_log_path}" + sleep 2s + done + ps ux | grep -E 'web_service' | awk '{print $2}' | xargs kill -s 9 + done + done + else + echo "Does not support hardware other than CPU and GPU Currently!" + fi + done +} + + +#set cuda device +GPUID=$3 +if [ ${#GPUID} -le 0 ];then + env="export CUDA_VISIBLE_DEVICES=0" +else + env="export CUDA_VISIBLE_DEVICES=${GPUID}" +fi +eval $env +echo $env + + +echo "################### run test ###################" + +export Count=0 +IFS="|" +func_serving "${web_service_cmd}" diff --git a/test_tipc/test_train_inference_python.sh b/test_tipc/test_train_inference_python.sh new file mode 100644 index 0000000..95a6bd8 --- /dev/null +++ b/test_tipc/test_train_inference_python.sh @@ -0,0 +1,343 @@ +#!/bin/bash +source test_tipc/common_func.sh + +FILENAME=$1 +# MODE be one of ['lite_train_lite_infer' 'lite_train_whole_infer' 'whole_train_whole_infer', 'whole_infer'] +MODE=$2 + +dataline=$(awk 'NR>=1{print}' $FILENAME) + +# parser params +IFS=$'\n' +lines=(${dataline}) + +# The training params +model_name=$(func_parser_value "${lines[1]}") +python=$(func_parser_value "${lines[2]}") +gpu_list=$(func_parser_value "${lines[3]}") +train_use_gpu_key=$(func_parser_key "${lines[4]}") +train_use_gpu_value=$(func_parser_value "${lines[4]}") +autocast_list=$(func_parser_value "${lines[5]}") +autocast_key=$(func_parser_key "${lines[5]}") +epoch_key=$(func_parser_key "${lines[6]}") +epoch_num=$(func_parser_params "${lines[6]}" "${MODE}") +save_model_key=$(func_parser_key "${lines[7]}") +train_batch_key=$(func_parser_key "${lines[8]}") +train_batch_value=$(func_parser_params "${lines[8]}" "${MODE}") +pretrain_model_key=$(func_parser_key "${lines[9]}") +pretrain_model_value=$(func_parser_value "${lines[9]}") +train_model_name=$(func_parser_value "${lines[10]}") +train_infer_img_dir=$(func_parser_value "${lines[11]}") +train_param_key1=$(func_parser_key "${lines[12]}") +train_param_value1=$(func_parser_value "${lines[12]}") + +trainer_list=$(func_parser_value "${lines[14]}") +trainer_norm=$(func_parser_key "${lines[15]}") +norm_trainer=$(func_parser_value "${lines[15]}") +pact_key=$(func_parser_key "${lines[16]}") +pact_trainer=$(func_parser_value "${lines[16]}") +fpgm_key=$(func_parser_key "${lines[17]}") +fpgm_trainer=$(func_parser_value "${lines[17]}") +distill_key=$(func_parser_key "${lines[18]}") +distill_trainer=$(func_parser_value "${lines[18]}") +trainer_key1=$(func_parser_key "${lines[19]}") +trainer_value1=$(func_parser_value "${lines[19]}") +trainer_key2=$(func_parser_key "${lines[20]}") +trainer_value2=$(func_parser_value "${lines[20]}") + +eval_py=$(func_parser_value "${lines[23]}") +eval_key1=$(func_parser_key "${lines[24]}") +eval_value1=$(func_parser_value "${lines[24]}") + +save_infer_key=$(func_parser_key "${lines[27]}") +export_weight=$(func_parser_key "${lines[28]}") +norm_export=$(func_parser_value "${lines[29]}") +pact_export=$(func_parser_value "${lines[30]}") +fpgm_export=$(func_parser_value "${lines[31]}") +distill_export=$(func_parser_value "${lines[32]}") +export_key1=$(func_parser_key "${lines[33]}") +export_value1=$(func_parser_value "${lines[33]}") +export_key2=$(func_parser_key "${lines[34]}") +export_value2=$(func_parser_value "${lines[34]}") +inference_dir=$(func_parser_value "${lines[35]}") + +# parser inference model +infer_model_dir_list=$(func_parser_value "${lines[36]}") +infer_export_list=$(func_parser_value "${lines[37]}") +infer_is_quant=$(func_parser_value "${lines[38]}") +# parser inference +inference_py=$(func_parser_value "${lines[39]}") +use_gpu_key=$(func_parser_key "${lines[40]}") +use_gpu_list=$(func_parser_value "${lines[40]}") +use_mkldnn_key=$(func_parser_key "${lines[41]}") +use_mkldnn_list=$(func_parser_value "${lines[41]}") +cpu_threads_key=$(func_parser_key "${lines[42]}") +cpu_threads_list=$(func_parser_value "${lines[42]}") +batch_size_key=$(func_parser_key "${lines[43]}") +batch_size_list=$(func_parser_value "${lines[43]}") +use_trt_key=$(func_parser_key "${lines[44]}") +use_trt_list=$(func_parser_value "${lines[44]}") +precision_key=$(func_parser_key "${lines[45]}") +precision_list=$(func_parser_value "${lines[45]}") +infer_model_key=$(func_parser_key "${lines[46]}") +image_dir_key=$(func_parser_key "${lines[47]}") +infer_img_dir=$(func_parser_value "${lines[47]}") +save_log_key=$(func_parser_key "${lines[48]}") +benchmark_key=$(func_parser_key "${lines[49]}") +benchmark_value=$(func_parser_value "${lines[49]}") +infer_key1=$(func_parser_key "${lines[50]}") +infer_value1=$(func_parser_value "${lines[50]}") + +line_num=`grep -n -w "to_static_train_benchmark_params" $FILENAME | cut -d ":" -f 1` +to_static_key=$(func_parser_key "${lines[line_num]}") +to_static_trainer=$(func_parser_value "${lines[line_num]}") + +LOG_PATH="./test_tipc/output/${model_name}/${MODE}" +mkdir -p ${LOG_PATH} +status_log="${LOG_PATH}/results_python.log" + +function func_inference(){ + IFS='|' + _python=$1 + _script=$2 + _model_dir=$3 + _log_path=$4 + _img_dir=$5 + _flag_quant=$6 + _gpu=$7 + # inference + for use_gpu in ${use_gpu_list[*]}; do + if [ ${use_gpu} = "False" ] || [ ${use_gpu} = "cpu" ]; then + for use_mkldnn in ${use_mkldnn_list[*]}; do + # if [ ${use_mkldnn} = "False" ] && [ ${_flag_quant} = "True" ]; then + # continue + # fi + for threads in ${cpu_threads_list[*]}; do + for batch_size in ${batch_size_list[*]}; do + for precision in ${precision_list[*]}; do + if [ ${use_mkldnn} = "False" ] && [ ${precision} = "fp16" ]; then + continue + fi # skip when enable fp16 but disable mkldnn + if [ ${_flag_quant} = "True" ] && [ ${precision} != "int8" ]; then + continue + fi # skip when quant model inference but precision is not int8 + set_precision=$(func_set_params "${precision_key}" "${precision}") + + _save_log_path="${_log_path}/python_infer_cpu_gpus_${_gpu}_usemkldnn_${use_mkldnn}_threads_${threads}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${benchmark_key}" "${benchmark_value}") + set_batchsize=$(func_set_params "${batch_size_key}" "${batch_size}") + set_mkldnn=$(func_set_params "${use_mkldnn_key}" "${use_mkldnn}") + set_cpu_threads=$(func_set_params "${cpu_threads_key}" "${threads}") + set_model_dir=$(func_set_params "${infer_model_key}" "${_model_dir}") + set_infer_params0=$(func_set_params "${save_log_key}" "${save_log_value}") + set_infer_params1=$(func_set_params "${infer_key1}" "${infer_value1}") + command="${_python} ${_script} ${use_gpu_key}=${use_gpu} ${set_mkldnn} ${set_cpu_threads} ${set_model_dir} ${set_batchsize} ${set_infer_params0} ${set_infer_data} ${set_benchmark} ${set_precision} ${set_infer_params1} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + done + done + done + done + elif [ ${use_gpu} = "True" ] || [ ${use_gpu} = "gpu" ]; then + for use_trt in ${use_trt_list[*]}; do + for precision in ${precision_list[*]}; do + if [[ ${_flag_quant} = "False" ]] && [[ ${precision} =~ "int8" ]]; then + continue + fi + if [[ ${precision} =~ "fp16" || ${precision} =~ "int8" ]] && [ ${use_trt} = "False" ]; then + continue + fi + if [[ ${use_trt} = "False" && ${precision} =~ "int8" ]] && [ ${_flag_quant} = "True" ]; then + continue + fi + for batch_size in ${batch_size_list[*]}; do + _save_log_path="${_log_path}/python_infer_gpu_gpus_${_gpu}_usetrt_${use_trt}_precision_${precision}_batchsize_${batch_size}.log" + set_infer_data=$(func_set_params "${image_dir_key}" "${_img_dir}") + set_benchmark=$(func_set_params "${benchmark_key}" "${benchmark_value}") + set_batchsize=$(func_set_params "${batch_size_key}" "${batch_size}") + set_tensorrt=$(func_set_params "${use_trt_key}" "${use_trt}") + set_precision=$(func_set_params "${precision_key}" "${precision}") + set_model_dir=$(func_set_params "${infer_model_key}" "${_model_dir}") + set_infer_params0=$(func_set_params "${save_log_key}" "${save_log_value}") + set_infer_params1=$(func_set_params "${infer_key1}" "${infer_value1}") + command="${_python} ${_script} ${use_gpu_key}=${use_gpu} ${set_tensorrt} ${set_precision} ${set_model_dir} ${set_batchsize} ${set_infer_data} ${set_benchmark} ${set_infer_params1} ${set_infer_params0} > ${_save_log_path} 2>&1 " + eval $command + last_status=${PIPESTATUS[0]} + eval "cat ${_save_log_path}" + status_check $last_status "${command}" "${status_log}" "${model_name}" "${_save_log_path}" + + done + done + done + else + echo "Does not support hardware other than CPU and GPU Currently!" + fi + done +} + +if [ ${MODE} = "whole_infer" ]; then + GPUID=$3 + if [ ${#GPUID} -le 0 ];then + env=" " + else + env="export CUDA_VISIBLE_DEVICES=${GPUID}" + fi + # set CUDA_VISIBLE_DEVICES + eval $env + export Count=0 + gpu=0 + IFS="|" + infer_run_exports=(${infer_export_list}) + infer_quant_flag=(${infer_is_quant}) + for infer_model in ${infer_model_dir_list[*]}; do + # run export + if [ ${infer_run_exports[Count]} != "null" ];then + save_infer_dir="${infer_model}" + set_export_weight=$(func_set_params "${export_weight}" "${infer_model}") + set_save_infer_key=$(func_set_params "${save_infer_key}" "${save_infer_dir}") + export_log_path="${LOG_PATH}_export_${Count}.log" + export_cmd="${python} ${infer_run_exports[Count]} ${set_export_weight} ${set_save_infer_key} > ${export_log_path} 2>&1 " + echo ${infer_run_exports[Count]} + echo $export_cmd + eval $export_cmd + status_export=$? + status_check $status_export "${export_cmd}" "${status_log}" "${model_name}" "${export_log_path}" + else + save_infer_dir=${infer_model} + fi + #run inference + is_quant=${infer_quant_flag[Count]} + func_inference "${python}" "${inference_py}" "${save_infer_dir}" "${LOG_PATH}" "${infer_img_dir}" ${is_quant} "${gpu}" + Count=$(($Count + 1)) + done +else + IFS="|" + export Count=0 + USE_GPU_KEY=(${train_use_gpu_value}) + for gpu in ${gpu_list[*]}; do + train_use_gpu=${USE_GPU_KEY[Count]} + Count=$(($Count + 1)) + ips="" + if [ ${gpu} = "-1" ];then + env="" + elif [ ${#gpu} -le 1 ];then + env="export CUDA_VISIBLE_DEVICES=${gpu}" + elif [ ${#gpu} -le 15 ];then + IFS="," + array=(${gpu}) + env="export CUDA_VISIBLE_DEVICES=${array[0]}" + IFS="|" + else + IFS=";" + array=(${gpu}) + ips=${array[0]} + gpu=${array[1]} + IFS="|" + env=" " + fi + for autocast in ${autocast_list[*]}; do + if [ ${autocast} = "amp" ]; then + set_amp_config="Global.use_amp=True Global.scale_loss=1024.0 Global.use_dynamic_loss_scaling=True" + else + set_amp_config=" " + fi + for trainer in ${trainer_list[*]}; do + flag_quant=False + if [ ${trainer} = ${pact_key} ]; then + run_train=${pact_trainer} + run_export=${pact_export} + flag_quant=True + elif [ ${trainer} = "${fpgm_key}" ]; then + run_train=${fpgm_trainer} + run_export=${fpgm_export} + elif [ ${trainer} = "${distill_key}" ]; then + run_train=${distill_trainer} + run_export=${distill_export} + elif [ ${trainer} = "${to_static_key}" ]; then + run_train="${norm_trainer} ${to_static_trainer}" + run_export=${norm_export} + elif [[ ${trainer} = ${trainer_key2} ]]; then + run_train=${trainer_value2} + run_export=${export_value2} + else + run_train=${norm_trainer} + run_export=${norm_export} + fi + + if [ ${run_train} = "null" ]; then + continue + fi + + set_epoch=$(func_set_params "${epoch_key}" "${epoch_num}") + set_pretrain=$(func_set_params "${pretrain_model_key}" "${pretrain_model_value}") + set_batchsize=$(func_set_params "${train_batch_key}" "${train_batch_value}") + set_train_params1=$(func_set_params "${train_param_key1}" "${train_param_value1}") + set_use_gpu=$(func_set_params "${train_use_gpu_key}" "${train_use_gpu}") + # if length of ips >= 15, then it is seen as multi-machine + # 15 is the min length of ips info for multi-machine: 0.0.0.0,0.0.0.0 + if [ ${#ips} -le 15 ];then + save_log="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}" + nodes=1 + else + IFS="," + ips_array=(${ips}) + IFS="|" + nodes=${#ips_array[@]} + save_log="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}_nodes_${nodes}" + fi + + + set_save_model=$(func_set_params "${save_model_key}" "${save_log}") + if [ ${#gpu} -le 2 ];then # train with cpu or single gpu + cmd="${python} ${run_train} ${set_use_gpu} ${set_save_model} ${set_epoch} ${set_pretrain} ${set_batchsize} ${set_amp_config} ${set_train_params1}" + elif [ ${#ips} -le 15 ];then # train with multi-gpu + cmd="${python} -m paddle.distributed.launch --gpus=${gpu} ${run_train} ${set_use_gpu} ${set_save_model} ${set_epoch} ${set_pretrain} ${set_batchsize} ${set_amp_config} ${set_train_params1}" + else # train with multi-machine + cmd="${python} -m paddle.distributed.launch --ips=${ips} --gpus=${gpu} ${run_train} ${set_use_gpu} ${set_save_model} ${set_pretrain} ${set_epoch} ${set_batchsize} ${set_amp_config} ${set_train_params1}" + fi + # run train + eval $cmd + eval "cat ${save_log}/train.log >> ${save_log}.log" + status_check $? "${cmd}" "${status_log}" "${model_name}" "${save_log}.log" + + set_eval_pretrain=$(func_set_params "${pretrain_model_key}" "${save_log}/${train_model_name}") + + # run eval + if [ ${eval_py} != "null" ]; then + eval ${env} + set_eval_params1=$(func_set_params "${eval_key1}" "${eval_value1}") + eval_log_path="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}_nodes_${nodes}_eval.log" + eval_cmd="${python} ${eval_py} ${set_eval_pretrain} ${set_use_gpu} ${set_eval_params1} > ${eval_log_path} 2>&1 " + eval $eval_cmd + status_check $? "${eval_cmd}" "${status_log}" "${model_name}" "${eval_log_path}" + fi + # run export model + if [ ${run_export} != "null" ]; then + # run export model + save_infer_path="${save_log}" + export_log_path="${LOG_PATH}/${trainer}_gpus_${gpu}_autocast_${autocast}_nodes_${nodes}_export.log" + set_export_weight=$(func_set_params "${export_weight}" "${save_log}/${train_model_name}") + set_save_infer_key=$(func_set_params "${save_infer_key}" "${save_infer_path}") + export_cmd="${python} ${run_export} ${set_export_weight} ${set_save_infer_key} > ${export_log_path} 2>&1 " + eval $export_cmd + status_check $? "${export_cmd}" "${status_log}" "${model_name}" "${export_log_path}" + + #run inference + eval $env + save_infer_path="${save_log}" + if [[ ${inference_dir} != "null" ]] && [[ ${inference_dir} != '##' ]]; then + infer_model_dir="${save_infer_path}/${inference_dir}" + else + infer_model_dir=${save_infer_path} + fi + func_inference "${python}" "${inference_py}" "${infer_model_dir}" "${LOG_PATH}" "${train_infer_img_dir}" "${flag_quant}" "${gpu}" + + eval "unset CUDA_VISIBLE_DEVICES" + fi + done # done with: for trainer in ${trainer_list[*]}; do + done # done with: for autocast in ${autocast_list[*]}; do + done # done with: for gpu in ${gpu_list[*]}; do +fi # end if [ ${MODE} = "infer" ]; then diff --git a/test_tipc/test_train_inference_python_npu.sh b/test_tipc/test_train_inference_python_npu.sh new file mode 100644 index 0000000..4b122b0 --- /dev/null +++ b/test_tipc/test_train_inference_python_npu.sh @@ -0,0 +1,66 @@ +#!/bin/bash +source test_tipc/common_func.sh + +function readlinkf() { + perl -MCwd -e 'print Cwd::abs_path shift' "$1"; +} + +function func_parser_config() { + strs=$1 + IFS=" " + array=(${strs}) + tmp=${array[2]} + echo ${tmp} +} + +BASEDIR=$(dirname "$0") +REPO_ROOT_PATH=$(readlinkf ${BASEDIR}/../) + +FILENAME=$1 + +# disable mkldnn on non x86_64 env +arch=$(uname -i) +if [ $arch != 'x86_64' ]; then + sed -i 's/--enable_mkldnn:True|False/--enable_mkldnn:False/g' $FILENAME + sed -i 's/--enable_mkldnn:True/--enable_mkldnn:False/g' $FILENAME +fi + +# change gpu to npu in tipc txt configs +sed -i 's/use_gpu/use_npu/g' $FILENAME +# disable benchmark as AutoLog required nvidia-smi command +sed -i 's/--benchmark:True/--benchmark:False/g' $FILENAME +# python has been updated to version 3.9 for npu backend +sed -i "s/python3.7/python3.9/g" $FILENAME +dataline=`cat $FILENAME` + +# parser params +IFS=$'\n' +lines=(${dataline}) + +modelname=$(echo ${lines[1]} | cut -d ":" -f2) +if [ $modelname == "rec_r31_sar" ] || [ $modelname == "rec_mtb_nrtr" ]; then + sed -i "s/Global.epoch_num:lite_train_lite_infer=2/Global.epoch_num:lite_train_lite_infer=1/g" $FILENAME + sed -i "s/gpu_list:0|0,1/gpu_list:0,1/g" $FILENAME + sed -i "s/Global.use_npu:True|True/Global.use_npu:True/g" $FILENAME +fi +if [ $modelname == "ch_ppocr_mobile_v2_0_rec_FPGM" ]; then + sed -i '18s/$/ -o Global.use_gpu=False/' $FILENAME + sed -i '32s/$/ Global.use_gpu=False/' $FILENAME +fi + +# replace training config file +grep -n 'tools/.*yml' $FILENAME | cut -d ":" -f 1 \ +| while read line_num ; do + train_cmd=$(func_parser_value "${lines[line_num-1]}") + trainer_config=$(func_parser_config ${train_cmd}) + sed -i 's/use_gpu/use_npu/g' "$REPO_ROOT_PATH/$trainer_config" + sed -i 's/use_sync_bn: True/use_sync_bn: False/g' "$REPO_ROOT_PATH/$trainer_config" +done + +# change gpu to npu in execution script +sed -i 's/\"gpu\"/\"npu\"/g' test_tipc/test_train_inference_python.sh + +# pass parameters to test_train_inference_python.sh +cmd='bash test_tipc/test_train_inference_python.sh ${FILENAME} $2' +echo -e '\033[1;32m Started to run command: ${cmd}! \033[0m' +eval $cmd diff --git a/test_tipc/test_train_inference_python_xpu.sh b/test_tipc/test_train_inference_python_xpu.sh new file mode 100644 index 0000000..88015e4 --- /dev/null +++ b/test_tipc/test_train_inference_python_xpu.sh @@ -0,0 +1,66 @@ +#!/bin/bash +source test_tipc/common_func.sh + +function readlinkf() { + perl -MCwd -e 'print Cwd::abs_path shift' "$1"; +} + +function func_parser_config() { + strs=$1 + IFS=" " + array=(${strs}) + tmp=${array[2]} + echo ${tmp} +} + +BASEDIR=$(dirname "$0") +REPO_ROOT_PATH=$(readlinkf ${BASEDIR}/../) + +FILENAME=$1 + +# disable mkldnn on non x86_64 env +arch=$(uname -i) +if [ $arch != 'x86_64' ]; then + sed -i 's/--enable_mkldnn:True|False/--enable_mkldnn:False/g' $FILENAME + sed -i 's/--enable_mkldnn:True/--enable_mkldnn:False/g' $FILENAME +fi + +# change gpu to xpu in tipc txt configs +sed -i 's/use_gpu/use_xpu/g' $FILENAME +# disable benchmark as AutoLog required nvidia-smi command +sed -i 's/--benchmark:True/--benchmark:False/g' $FILENAME +# python has been updated to version 3.9 for xpu backend +sed -i "s/python3.7/python3.9/g" $FILENAME +dataline=`cat $FILENAME` + +# parser params +IFS=$'\n' +lines=(${dataline}) + +modelname=$(echo ${lines[1]} | cut -d ":" -f2) +if [ $modelname == "rec_r31_sar" ] || [ $modelname == "rec_mtb_nrtr" ]; then + sed -i "s/Global.epoch_num:lite_train_lite_infer=2/Global.epoch_num:lite_train_lite_infer=1/g" $FILENAME + sed -i "s/gpu_list:0|0,1/gpu_list:0,1/g" $FILENAME + sed -i "s/Global.use_xpu:True|True/Global.use_xpu:True/g" $FILENAME +fi +if [ $modelname == "ch_ppocr_mobile_v2_0_rec_FPGM" ]; then + sed -i '18s/$/ -o Global.use_gpu=False/' $FILENAME + sed -i '32s/$/ Global.use_gpu=False/' $FILENAME +fi + +# replace training config file +grep -n 'tools/.*yml' $FILENAME | cut -d ":" -f 1 \ +| while read line_num ; do + train_cmd=$(func_parser_value "${lines[line_num-1]}") + trainer_config=$(func_parser_config ${train_cmd}) + sed -i 's/use_gpu/use_xpu/g' "$REPO_ROOT_PATH/$trainer_config" + sed -i 's/use_sync_bn: True/use_sync_bn: False/g' "$REPO_ROOT_PATH/$trainer_config" +done + +# change gpu to xpu in execution script +sed -i 's/\"gpu\"/\"xpu\"/g' test_tipc/test_train_inference_python.sh + +# pass parameters to test_train_inference_python.sh +cmd='bash test_tipc/test_train_inference_python.sh ${FILENAME} $2' +echo -e '\033[1;32m Started to run command: ${cmd}! \033[0m' +eval $cmd diff --git a/test_tipc/web/expect.json b/test_tipc/web/expect.json new file mode 100644 index 0000000..a60c80a --- /dev/null +++ b/test_tipc/web/expect.json @@ -0,0 +1,20 @@ +{ + "text": [ + "纯臻营养护发素", + "产品信息/参数", + "(45元/每公斤,100公斤起订)", + "每瓶22元,1000瓶起订)", + "【品牌】:代加工方式/OEMODM", + "【品名】:纯臻营养护发素", + "【产品编号】:YM-X-3011", + "ODMOEM", + "【净含量】:220ml", + "【适用人群】:适合所有肤质", + "【主要成分】:鲸蜡硬脂醇、燕麦β-葡聚", + "糖、椰油酰胺丙基甜菜碱、泛醌", + "(成品包材)", + "【主要功能】:可紧致头发磷层,从而达到", + "即时持久改善头发光泽的效果,给干燥的头", + "发足够的滋养" + ] +} diff --git a/test_tipc/web/index.html b/test_tipc/web/index.html new file mode 100644 index 0000000..2111155 --- /dev/null +++ b/test_tipc/web/index.html @@ -0,0 +1,13 @@ + + + + + + + ocr test + + + + + + diff --git a/test_tipc/web/index.test.js b/test_tipc/web/index.test.js new file mode 100644 index 0000000..e07aed8 --- /dev/null +++ b/test_tipc/web/index.test.js @@ -0,0 +1,82 @@ +const expectData = require('./expect.json'); + +describe('e2e test ocr model', () => { + + beforeAll(async () => { + await page.goto(PATH); + }); + + it('ocr infer and diff test', async () => { + page.on('console', msg => console.log('PAGE LOG:', msg.text())); + + const text = await page.evaluate(async () => { + const $ocr = document.querySelector('#ocr'); + const ocr = paddlejs['ocr']; + await ocr.init('./models/ch_PP-OCRv2_det_infer', './models/ch_PP-OCRv2_rec_infer'); + const res = await ocr.recognize($ocr); + return res.text; + }); + // 模型文字识别结果与预期结果diff的字符数 + let diffNum = 0; + // 文本框字符串相似度 + let similarity = 0; + // 预期字符diff数 + const expectedDiffNum = 10; + // 预期文本框字符串相似度 + const expecteSimilarity = 0.9; + // 预期文本内容 + const expectResult = expectData.text; + + expectResult && expectResult.forEach((item, index) => { + const word = text[index]; + // 逐字符对比 + for(let i = 0; i < item.length; i++) { + if (item[i] !== word[i]) { + console.log('expect: ', item[i], ' word: ', word[i]); + diffNum++; + } + } + // 文本框字符串相似度对比 + const s = similar(item, word); + similarity += s; + }); + + similarity = similarity / expectResult.length; + + expect(diffNum).toBeLessThanOrEqual(expectedDiffNum); + + expect(similarity).toBeGreaterThanOrEqual(expecteSimilarity); + + function similar(string, expect) { + if (!string || !expect) { + return 0; + } + const length = string.length > expect.length ? string.length : expect.length; + const n = string.length; + const m = expect.length; + let data = []; + const min = (a, b, c) => { + return a < b ? (a < c ? a : c) : (b < c ? b : c); + }; + let i, j, si, ej, cost; + if (n === 0) return m; + if (m === 0) return n; + for (i = 0; i <= n; i++) { + data[i] = []; + [i][0] = i + } + for (j = 0; j <= m; j++) { + data[0][j] = j; + } + for (i = 1; i <= n; i++) { + si = string.charAt(i - 1); + for (j = 1; j <= m; j++) { + ej = expect.charAt(j - 1); + cost = si === ej ? 0 : 1; + data[i][j] = min(data[i - 1][j] + 1, data[i][j - 1] + 1, data[i - 1][j - 1] + cost); + } + } + return (1 - data[n][m] / length); + } + }); +}); diff --git a/test_tipc/web/jest-puppeteer.config.js b/test_tipc/web/jest-puppeteer.config.js new file mode 100644 index 0000000..ac60eea --- /dev/null +++ b/test_tipc/web/jest-puppeteer.config.js @@ -0,0 +1,14 @@ +// jest-puppeteer.config.js +module.exports = { + launch: { + headless: false, + product: 'chrome' + }, + browserContext: 'default', + server: { + command: 'python3 -m http.server 9811', + port: 9811, + launchTimeout: 10000, + debug: true + } +}; diff --git a/test_tipc/web/jest.config.js b/test_tipc/web/jest.config.js new file mode 100644 index 0000000..aed1573 --- /dev/null +++ b/test_tipc/web/jest.config.js @@ -0,0 +1,111 @@ +// For a detailed explanation regarding each configuration property and type check, visit: +// https://jestjs.io/docs/en/configuration.html + +module.exports = { + preset: 'jest-puppeteer', + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Automatically clear mock calls and instances between every test + clearMocks: true, + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A set of global variables that need to be available in all test environments + globals: { + PATH: 'http://localhost:9811' + }, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + moduleFileExtensions: [ + 'js', + 'json', + 'jsx', + 'ts', + 'tsx', + 'node' + ], + + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + roots: [ + '' + ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + // testEnvironment: 'jsdom', + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + testPathIgnorePatterns: [ + '/node_modules/' + ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + testRegex: '.(.+)\\.test\\.(js|ts)$', + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href + testURL: 'http://localhost:9898/', + + // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" + // timers: "real", + + // A map from regular expressions to paths to transformers + transform: { + '^.+\\.js$': 'babel-jest' + }, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + transformIgnorePatterns: [ + '/node_modules/', + '\\.pnp\\.[^\\/]+$' + ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + verbose: true, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, + testTimeout: 50000 +}; diff --git a/test_tipc/web/test.jpg b/test_tipc/web/test.jpg new file mode 100644 index 0000000..60682be Binary files /dev/null and b/test_tipc/web/test.jpg differ diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..290f972 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tests/api_client/test_cli.py b/tests/api_client/test_cli.py new file mode 100644 index 0000000..225fe67 --- /dev/null +++ b/tests/api_client/test_cli.py @@ -0,0 +1,82 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +from pathlib import Path + +from paddleocr._api_client import cli +from paddleocr._api_client.results import OCRPage, OCRResult + + +def test_cli_save_resources_uses_result_specific_helper(tmp_path, monkeypatch, capsys): + class FakeClient: + def __init__(self, **kwargs): + self.kwargs = kwargs + + def ocr(self, **kwargs): + return OCRResult( + job_id="job-1", + pages=[ + OCRPage( + pruned_result={}, ocr_image_url="https://example.test/a.png" + ) + ], + ) + + def save_ocr_result_resources(self, result, destination, overwrite=False): + assert result.job_id == "job-1" + assert destination == str(tmp_path) + assert overwrite is True + return [str(Path(destination) / "ocr-page-1.png")] + + def close(self): + return None + + monkeypatch.setattr(cli, "PaddleOCRClient", FakeClient) + + args = argparse.Namespace( + token="token", + client_platform=None, + base_url=None, + request_timeout=300.0, + poll_timeout=600.0, + model=None, + model_type="ocr", + file_url="https://example.test/input.pdf", + file_path=None, + output=None, + save_resources=str(tmp_path), + overwrite_resources=True, + page_ranges=None, + batch_id=None, + use_doc_orientation_classify=None, + use_doc_unwarping=None, + use_textline_orientation=None, + use_chart_recognition=None, + use_seal_recognition=None, + use_table_recognition=None, + use_formula_recognition=None, + use_layout_detection=None, + text_det_limit_side_len=None, + text_det_limit_type=None, + text_rec_score_thresh=None, + prettify_markdown=None, + visualize=None, + ) + + cli._execute_api(args) + + captured = capsys.readouterr() + assert '"jobId": "job-1"' in captured.out + assert "Resources saved to:" in captured.err diff --git a/tests/api_client/test_core.py b/tests/api_client/test_core.py new file mode 100644 index 0000000..913f668 --- /dev/null +++ b/tests/api_client/test_core.py @@ -0,0 +1,174 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from paddleocr._api_client._core import ( + default_payload, + job_id_for_task, + resolve_document_model, + resolve_document_options, + resolve_ocr_model, + validate_input_source, +) +from paddleocr._api_client.errors import InvalidRequestError +from paddleocr._api_client.models import ( + Model, + OCROptions, + PaddleOCRVLOptions, + PPStructureV3Options, +) +from paddleocr._utils.naming import snake_to_camel +from paddleocr._api_client._poller import parse_doc_parsing_result, parse_ocr_result +from paddleocr._api_client.results import Job + + +def test_ppstructurev3_options_to_payload_uses_official_camel_case_keys(): + assert snake_to_camel("top_p") == "topP" + assert snake_to_camel("use_e2e_wired_table_rec_model") == "useE2eWiredTableRecModel" + + options = PPStructureV3Options( + use_e2e_wired_table_rec_model=True, + format_block_content=True, + markdown_ignore_labels=["image"], + text_det_limit_side_len=1280, + extra_options={"futureOption": "enabled"}, + ) + assert options.to_payload() == { + "useE2eWiredTableRecModel": True, + "formatBlockContent": True, + "markdownIgnoreLabels": ["image"], + "textDetLimitSideLen": 1280, + "futureOption": "enabled", + } + + +def test_paddleocr_vl_options_include_service_parameters_and_extra_options(): + options = PaddleOCRVLOptions( + use_ocr_for_image_block=True, + format_block_content=True, + markdown_ignore_labels=["image"], + vlm_extra_args={"temperature": 0.1}, + return_markdown_images=False, + output_formats=["docx"], + extra_options={"futureOption": "enabled"}, + ) + + assert options.to_payload() == { + "useOcrForImageBlock": True, + "formatBlockContent": True, + "markdownIgnoreLabels": ["image"], + "vlmExtraArgs": {"temperature": 0.1}, + "returnMarkdownImages": False, + "outputFormats": ["docx"], + "futureOption": "enabled", + } + + +def test_result_parsers_preserve_raw_fields_and_data_info(): + ocr_line = { + "result": { + "dataInfo": {"numPages": 1}, + "ocrResults": [ + { + "prunedResult": {"rec_texts": ["hello"]}, + "ocrImage": "ocr.png", + "docPreprocessingImage": "pre.png", + "inputImage": "input.png", + } + ], + } + } + ocr_result = parse_ocr_result("job-ocr", [ocr_line]) + assert ocr_result.data_info == {"numPages": 1} + assert ocr_result.pages[0].doc_preprocessing_image_url == "pre.png" + assert ocr_result.pages[0].input_image_url == "input.png" + assert ocr_result.pages[0].raw["ocrImage"] == "ocr.png" + + doc_page = { + "prunedResult": {"blocks": [{"label": "text", "content": "hello"}]}, + "markdown": { + "text": "hello", + "images": {"figure.png": "figure-url"}, + "isStart": True, + }, + "outputImages": {"page.png": "page-url"}, + "inputImage": "input.png", + "exports": {"docx": "docx-url"}, + } + doc_line = { + "result": { + "dataInfo": {"numPages": 1}, + "layoutParsingResults": [doc_page], + } + } + doc_result = parse_doc_parsing_result("job-doc", [doc_line]) + assert doc_result.data_info == {"numPages": 1} + assert doc_result.pages[0].pruned_result == doc_page["prunedResult"] + assert doc_result.pages[0].markdown == doc_page["markdown"] + assert doc_result.pages[0].exports == {"docx": "docx-url"} + assert doc_result.pages[0].raw == doc_page + + +def test_core_resolves_models_and_default_payloads(): + assert resolve_ocr_model("PP-OCRv5") is Model.PP_OCRV5 + assert resolve_ocr_model("PP-OCRv5-latin") is Model.PP_OCRV5_LATIN + assert resolve_ocr_model("PP-OCRv6") is Model.PP_OCRV6 + assert resolve_document_model("PaddleOCR-VL-1.6") is Model.PADDLE_OCR_VL_16 + + assert default_payload(Model.PP_OCRV5) == OCROptions().to_payload() + assert default_payload(Model.PP_OCRV5_LATIN) == OCROptions().to_payload() + assert default_payload(Model.PP_OCRV6) == OCROptions().to_payload() + assert default_payload(Model.PADDLE_OCR_VL_16) == PaddleOCRVLOptions().to_payload() + + with pytest.raises(InvalidRequestError): + resolve_ocr_model("PP-StructureV3") + + with pytest.raises(InvalidRequestError): + resolve_document_model("unknown-model") + + +def test_core_selects_document_options_by_model(): + assert isinstance( + resolve_document_options(Model.PP_STRUCTURE_V3, None), + PPStructureV3Options, + ) + assert isinstance( + resolve_document_options(Model.PADDLE_OCR_VL_16, None), + PaddleOCRVLOptions, + ) + + with pytest.raises(InvalidRequestError): + resolve_document_options(Model.PP_STRUCTURE_V3, PaddleOCRVLOptions()) + + with pytest.raises(InvalidRequestError): + resolve_document_options(Model.PADDLE_OCR_VL_16, PPStructureV3Options()) + + +def test_core_validates_input_source_and_job_task(): + validate_input_source("https://example.test/file.pdf", None) + validate_input_source(None, "/tmp/file.pdf") + + with pytest.raises(InvalidRequestError): + validate_input_source(None, None) + + with pytest.raises(InvalidRequestError): + validate_input_source("https://example.test/file.pdf", "/tmp/file.pdf") + + job = Job(job_id="job-1", model=Model.PP_OCRV5.value, task="ocr") + assert job_id_for_task(job, "ocr") == "job-1" + assert job_id_for_task("job-2", "ocr") == "job-2" + + with pytest.raises(InvalidRequestError): + job_id_for_task(job, "document_parsing") diff --git a/tests/api_client/test_http.py b/tests/api_client/test_http.py new file mode 100644 index 0000000..8d3f38d --- /dev/null +++ b/tests/api_client/test_http.py @@ -0,0 +1,264 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +from http.server import BaseHTTPRequestHandler, HTTPServer +from threading import Thread + +import pytest + +from paddleocr._api_client._http import API_PATH, DEFAULT_BASE_URL, HTTPClient +from paddleocr._api_client.client import PaddleOCRClient +from paddleocr._api_client.errors import ( + APIError, + AuthError, + InvalidRequestError, + NetworkError, + RateLimitError, + RequestTimeoutError, + ServiceUnavailableError, +) +from paddleocr._api_client.models import Model + + +class _MockHandler(BaseHTTPRequestHandler): + """Records requests and responds with canned JSON.""" + + requests_log = [] + response_body = {"code": 0, "data": {"jobId": "job-1"}} + response_status = 200 + + def do_POST(self): + content_length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(content_length) if content_length else b"" + self.__class__.requests_log.append( + { + "method": "POST", + "path": self.path, + "headers": dict(self.headers), + "body": body, + } + ) + self.send_response(self.__class__.response_status) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(json.dumps(self.__class__.response_body).encode()) + + def do_GET(self): + self.__class__.requests_log.append( + {"method": "GET", "path": self.path, "headers": dict(self.headers)} + ) + self.send_response(self.__class__.response_status) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(json.dumps(self.__class__.response_body).encode()) + + def log_message(self, format, *args): + pass + + +@pytest.fixture() +def mock_server(): + _MockHandler.requests_log = [] + _MockHandler.response_body = {"code": 0, "data": {"jobId": "job-1"}} + _MockHandler.response_status = 200 + + server = HTTPServer(("127.0.0.1", 0), _MockHandler) + port = server.server_address[1] + thread = Thread(target=server.serve_forever, daemon=True) + thread.start() + yield f"http://127.0.0.1:{port}", _MockHandler + server.shutdown() + + +def test_base_url_appends_api_path(mock_server): + base_url, handler = mock_server + client = HTTPClient("test-token", base_url, timeout=5.0) + + client.submit_url("PP-OCRv5", "https://example.test/file.pdf", {}) + + assert len(handler.requests_log) == 1 + assert handler.requests_log[0]["path"] == API_PATH + + +def test_get_job_status_url(mock_server): + base_url, handler = mock_server + handler.response_body = {"code": 0, "data": {"jobId": "job-1", "state": "running"}} + client = HTTPClient("test-token", base_url, timeout=5.0) + + client.get_job_status("job-123") + + assert handler.requests_log[0]["path"] == f"{API_PATH}/job-123" + + +def test_get_batch_status_url(mock_server): + base_url, handler = mock_server + handler.response_body = {"code": 0, "data": {"batchId": "b-1", "jobs": []}} + client = HTTPClient("test-token", base_url, timeout=5.0) + + client.get_batch_status("batch-abc") + + assert handler.requests_log[0]["path"] == f"{API_PATH}/batch/batch-abc" + + +def test_base_url_trailing_slashes_normalized(mock_server): + base_url, handler = mock_server + client = HTTPClient("test-token", base_url + "///", timeout=5.0) + + client.submit_url("PP-OCRv5", "https://example.test/file.pdf", {}) + + assert handler.requests_log[0]["path"] == API_PATH + + +def test_authorization_header_sent(mock_server): + base_url, handler = mock_server + client = HTTPClient("my-secret-token", base_url, timeout=5.0) + + client.submit_url("PP-OCRv5", "https://example.test/file.pdf", {}) + + assert ( + handler.requests_log[0]["headers"]["Authorization"] == "Bearer my-secret-token" + ) + + +def test_client_platform_header(mock_server): + base_url, handler = mock_server + client = HTTPClient("token", base_url, timeout=5.0, client_platform="sdk-test") + + client.submit_url("PP-OCRv5", "https://example.test/file.pdf", {}) + + assert handler.requests_log[0]["headers"]["Client-Platform"] == "sdk-test" + + +def test_submit_url_posts_correct_body(mock_server): + base_url, handler = mock_server + client = HTTPClient("token", base_url, timeout=5.0) + + job_id = client.submit_url( + "PP-OCRv5", + "https://example.test/file.pdf", + {"visualize": True}, + page_ranges="1-3", + batch_id="batch-1", + ) + + assert job_id == "job-1" + body = json.loads(handler.requests_log[0]["body"]) + assert body == { + "fileUrl": "https://example.test/file.pdf", + "model": "PP-OCRv5", + "optionalPayload": {"visualize": True}, + "pageRanges": "1-3", + "batchId": "batch-1", + } + + +def test_error_mapping_401(mock_server): + base_url, handler = mock_server + handler.response_status = 401 + handler.response_body = {"code": 401, "msg": "Unauthorized"} + client = HTTPClient("bad-token", base_url, timeout=5.0) + + with pytest.raises(AuthError): + client.submit_url("PP-OCRv5", "https://example.test/file.pdf", {}) + + +def test_error_mapping_400(mock_server): + base_url, handler = mock_server + handler.response_status = 400 + handler.response_body = {"code": 400, "msg": "Bad request"} + client = HTTPClient("token", base_url, timeout=5.0) + + with pytest.raises(InvalidRequestError): + client.submit_url("PP-OCRv5", "https://example.test/file.pdf", {}) + + +def test_error_mapping_429(mock_server): + base_url, handler = mock_server + handler.response_status = 429 + handler.response_body = {"code": 429, "msg": "Rate limited"} + client = HTTPClient("token", base_url, timeout=5.0) + + with pytest.raises(RateLimitError): + client.submit_url("PP-OCRv5", "https://example.test/file.pdf", {}) + + +def test_error_mapping_503(mock_server): + base_url, handler = mock_server + handler.response_status = 503 + handler.response_body = {"code": 503, "msg": "Service unavailable"} + client = HTTPClient("token", base_url, timeout=5.0) + + with pytest.raises(ServiceUnavailableError): + client.submit_url("PP-OCRv5", "https://example.test/file.pdf", {}) + + +def test_error_mapping_500(mock_server): + base_url, handler = mock_server + handler.response_status = 500 + handler.response_body = {"code": 500, "msg": "Internal error"} + client = HTTPClient("token", base_url, timeout=5.0) + + with pytest.raises(APIError): + client.submit_url("PP-OCRv5", "https://example.test/file.pdf", {}) + + +def test_paddleocr_client_requires_token(monkeypatch): + monkeypatch.delenv("PADDLEOCR_ACCESS_TOKEN", raising=False) + with pytest.raises(AuthError): + PaddleOCRClient() + + +def test_paddleocr_client_reads_env_token(monkeypatch): + monkeypatch.setenv("PADDLEOCR_ACCESS_TOKEN", "env-token") + client = PaddleOCRClient() + client.close() + + +def test_default_base_url_is_host_only(): + assert "/api/" not in DEFAULT_BASE_URL + assert DEFAULT_BASE_URL == "https://paddleocr.aistudio-app.com" + + +def test_paddleocr_client_client_platform_header(monkeypatch): + class FakeResponse: + status_code = 200 + text = "" + + def json(self): + return {"code": 0, "data": {"jobId": "job-1"}} + + class FakeSession: + def __init__(self): + self.headers = {} + self.captured_headers = None + + def post(self, *args, **kwargs): + self.captured_headers = dict(self.headers) + return FakeResponse() + + def close(self): + return None + + fake_session = FakeSession() + monkeypatch.setattr( + "paddleocr._api_client._http.requests.Session", + lambda: fake_session, + ) + + client = PaddleOCRClient(token="token", client_platform="my-app") + job = client.submit_ocr(file_url="https://example.test/input.pdf") + + assert job.job_id == "job-1" + assert fake_session.captured_headers["Client-Platform"] == "my-app" diff --git a/tests/api_client/test_resources.py b/tests/api_client/test_resources.py new file mode 100644 index 0000000..a4ab8f7 --- /dev/null +++ b/tests/api_client/test_resources.py @@ -0,0 +1,133 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path + +import pytest + +from paddleocr._api_client.client import PaddleOCRClient +from paddleocr._api_client.errors import InvalidRequestError +from paddleocr._api_client.results import ( + DocParsingPage, + DocParsingResult, + OCRPage, + OCRResult, +) + + +class _Response: + def __init__(self, content: bytes): + self.content = content + + def raise_for_status(self): + return None + + +def test_save_resource_writes_single_url(tmp_path, monkeypatch): + calls = [] + + def fake_get(url, timeout): + calls.append((url, timeout)) + return _Response(b"content") + + monkeypatch.setattr("paddleocr._api_client._resources.requests.get", fake_get) + + client = PaddleOCRClient(token="token", request_timeout=12.0) + saved_path = client.save_resource( + "https://example.test/path/image.png", str(tmp_path) + ) + + assert saved_path == str(tmp_path / "image.png") + assert (tmp_path / "image.png").read_bytes() == b"content" + assert calls == [("https://example.test/path/image.png", 12.0)] + + +def test_save_ocr_result_resources_uses_stable_page_names(tmp_path, monkeypatch): + contents = [b"one", b"two"] + + def fake_get(url, timeout): + return _Response(contents.pop(0)) + + monkeypatch.setattr("paddleocr._api_client._resources.requests.get", fake_get) + + client = PaddleOCRClient(token="token") + result = OCRResult( + job_id="job-1", + pages=[ + OCRPage(pruned_result={}, ocr_image_url="https://example.test/a.png?sig=1"), + OCRPage(pruned_result={}, ocr_image_url="https://example.test/b.jpg"), + ], + ) + + saved_paths = client.save_ocr_result_resources(result, str(tmp_path)) + + assert saved_paths == [ + str(tmp_path / "ocr-page-1.png"), + str(tmp_path / "ocr-page-2.jpg"), + ] + assert (tmp_path / "ocr-page-1.png").read_bytes() == b"one" + assert (tmp_path / "ocr-page-2.jpg").read_bytes() == b"two" + + +def test_save_document_parsing_result_resources_sorts_and_validates_keys( + tmp_path, monkeypatch +): + calls = [] + + def fake_get(url, timeout): + calls.append(url) + return _Response(url.encode()) + + monkeypatch.setattr("paddleocr._api_client._resources.requests.get", fake_get) + + client = PaddleOCRClient(token="token") + result = DocParsingResult( + job_id="job-1", + pages=[ + DocParsingPage( + markdown_text="", + markdown_images={ + "b.png": "https://example.test/b", + "a.png": "https://example.test/a", + }, + output_images={"rendered.jpg": "https://example.test/rendered"}, + ) + ], + ) + + saved_paths = client.save_document_parsing_result_resources(result, str(tmp_path)) + + assert saved_paths == [ + str(tmp_path / "a.png"), + str(tmp_path / "b.png"), + str(tmp_path / "rendered.jpg"), + ] + assert calls == [ + "https://example.test/a", + "https://example.test/b", + "https://example.test/rendered", + ] + + for unsafe_key in ("../escape.png", "nested\\escape.png"): + bad_result = DocParsingResult( + job_id="job-2", + pages=[ + DocParsingPage( + markdown_text="", + markdown_images={unsafe_key: "https://example.test/escape"}, + ) + ], + ) + with pytest.raises(InvalidRequestError): + client.save_document_parsing_result_resources(bad_result, str(tmp_path)) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..22d5161 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,28 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Shared pytest configuration for the PaddleOCR repository test suite.""" + +from __future__ import annotations + +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +MCP_SERVER_ROOT = REPO_ROOT / "mcp_server" + +if str(REPO_ROOT) not in sys.path: + sys.path.insert(0, str(REPO_ROOT)) +if str(MCP_SERVER_ROOT) not in sys.path: + sys.path.insert(0, str(MCP_SERVER_ROOT)) diff --git a/tests/models/__init__.py b/tests/models/__init__.py new file mode 100644 index 0000000..290f972 --- /dev/null +++ b/tests/models/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tests/models/image_classification_common.py b/tests/models/image_classification_common.py new file mode 100644 index 0000000..c547bea --- /dev/null +++ b/tests/models/image_classification_common.py @@ -0,0 +1,24 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def check_result_item_keys(result_item): + assert result_item.keys() == { + "input_path", + "page_index", + "input_img", + "class_ids", + "scores", + "label_names", + } diff --git a/tests/models/object_detection_common.py b/tests/models/object_detection_common.py new file mode 100644 index 0000000..7e59eaa --- /dev/null +++ b/tests/models/object_detection_common.py @@ -0,0 +1,22 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +def check_result_item_keys(result_item): + assert result_item.keys() == { + "input_path", + "page_index", + "input_img", + "boxes", + } diff --git a/tests/models/test_doc_img_orientation_classification.py b/tests/models/test_doc_img_orientation_classification.py new file mode 100644 index 0000000..7a7a4da --- /dev/null +++ b/tests/models/test_doc_img_orientation_classification.py @@ -0,0 +1,37 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from paddleocr import DocImgOrientationClassification +from ..testing_utils import TEST_DATA_DIR, check_simple_inference_result +from .image_classification_common import check_result_item_keys + + +@pytest.fixture(scope="module") +def doc_img_orientation_classification_predictor(): + return DocImgOrientationClassification() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "book_rot180.jpg", + ], +) +def test_predict(doc_img_orientation_classification_predictor, image_path): + result = doc_img_orientation_classification_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + check_result_item_keys(result[0]) diff --git a/tests/models/test_doc_vlm.py b/tests/models/test_doc_vlm.py new file mode 100644 index 0000000..303653a --- /dev/null +++ b/tests/models/test_doc_vlm.py @@ -0,0 +1,69 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import DocVLM +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def doc_vlm_predictor(): + return DocVLM() + + +@pytest.mark.resource_intensive +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "medal_table.png", + ], +) +def test_predict(doc_vlm_predictor, image_path): + result = doc_vlm_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + assert result[0].keys() == { + "input_path", + "page_index", + "input_img", + "result", + } + + +@pytest.mark.resource_intensive +@pytest.mark.parametrize( + "params", + [ + {}, + ], +) +def test_predict_params( + monkeypatch, + doc_vlm_predictor, + params, +): + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + doc_vlm_predictor, + "paddlex_predictor", + "dummy_path", + params, + ) diff --git a/tests/models/test_formula_recognition.py b/tests/models/test_formula_recognition.py new file mode 100644 index 0000000..1ffa4bd --- /dev/null +++ b/tests/models/test_formula_recognition.py @@ -0,0 +1,67 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import FormulaRecognition +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def formula_recognition_predictor(): + return FormulaRecognition() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "formula.png", + ], +) +def test_predict(formula_recognition_predictor, image_path): + result = formula_recognition_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + assert result[0].keys() == { + "input_path", + "page_index", + "input_img", + "rec_formula", + } + + +@pytest.mark.parametrize( + "params", + [ + {}, + ], +) +def test_predict_params( + monkeypatch, + formula_recognition_predictor, + params, +): + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + formula_recognition_predictor, + "paddlex_predictor", + "dummy_path", + params, + ) diff --git a/tests/models/test_layout_detection.py b/tests/models/test_layout_detection.py new file mode 100644 index 0000000..7fd03d5 --- /dev/null +++ b/tests/models/test_layout_detection.py @@ -0,0 +1,67 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import LayoutDetection +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) +from .object_detection_common import check_result_item_keys + + +@pytest.fixture(scope="module") +def layout_detection_predictor(): + return LayoutDetection() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "doc_with_formula.png", + ], +) +def test_predict(layout_detection_predictor, image_path): + result = layout_detection_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + check_result_item_keys(result[0]) + + +@pytest.mark.parametrize( + "params", + [ + {"img_size": 640}, + {"threshold": 0.5}, + {"layout_nms": True}, + {"layout_unclip_ratio": True}, + {"layout_merge_bboxes_mode": True}, + ], +) +def test_predict_params( + monkeypatch, + layout_detection_predictor, + params, +): + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + layout_detection_predictor, + "paddlex_predictor", + "dummy_path", + params, + ) diff --git a/tests/models/test_seal_text_detection.py b/tests/models/test_seal_text_detection.py new file mode 100644 index 0000000..6b8687d --- /dev/null +++ b/tests/models/test_seal_text_detection.py @@ -0,0 +1,71 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import SealTextDetection +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def seal_text_detection_predictor(): + return SealTextDetection() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "seal.png", + ], +) +def test_predict(seal_text_detection_predictor, image_path): + result = seal_text_detection_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + assert result[0].keys() == { + "input_path", + "page_index", + "input_img", + "dt_polys", + "dt_scores", + } + + +@pytest.mark.parametrize( + "params", + [ + {"limit_side_len": 640, "limit_type": "min"}, + {"thresh": 0.5}, + {"box_thresh": 0.3}, + {"unclip_ratio": 3.0}, + ], +) +def test_predict_params( + monkeypatch, + seal_text_detection_predictor, + params, +): + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + seal_text_detection_predictor, + "paddlex_predictor", + "dummy_path", + params, + ) diff --git a/tests/models/test_table_cells_detection.py b/tests/models/test_table_cells_detection.py new file mode 100644 index 0000000..b7e8f88 --- /dev/null +++ b/tests/models/test_table_cells_detection.py @@ -0,0 +1,64 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import TableCellsDetection +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) +from .object_detection_common import check_result_item_keys + + +@pytest.fixture(scope="module") +def table_cells_detection_predictor(): + return TableCellsDetection() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "table.jpg", + ], +) +def test_predict(table_cells_detection_predictor, image_path): + result = table_cells_detection_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + check_result_item_keys(result[0]) + + +@pytest.mark.parametrize( + "params", + [ + {"img_size": 640}, + {"threshold": 0.5}, + ], +) +def test_predict_params( + monkeypatch, + table_cells_detection_predictor, + params, +): + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + table_cells_detection_predictor, + "paddlex_predictor", + "dummy_path", + params, + ) diff --git a/tests/models/test_table_classification.py b/tests/models/test_table_classification.py new file mode 100644 index 0000000..f7dc4c1 --- /dev/null +++ b/tests/models/test_table_classification.py @@ -0,0 +1,39 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import TableClassification +from ..testing_utils import TEST_DATA_DIR, check_simple_inference_result +from .image_classification_common import check_result_item_keys + + +@pytest.fixture(scope="module") +def table_classification_predictor(): + return TableClassification() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "table.jpg", + ], +) +def test_predict(table_classification_predictor, image_path): + result = table_classification_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + check_result_item_keys(result[0]) diff --git a/tests/models/test_table_structure_recognition.py b/tests/models/test_table_structure_recognition.py new file mode 100644 index 0000000..9a7c3bd --- /dev/null +++ b/tests/models/test_table_structure_recognition.py @@ -0,0 +1,69 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import TableStructureRecognition +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def table_structure_recognition_predictor(): + return TableStructureRecognition() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "table.jpg", + ], +) +def test_predict(table_structure_recognition_predictor, image_path): + result = table_structure_recognition_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + assert result[0].keys() == { + "input_path", + "page_index", + "input_img", + "bbox", + "structure", + "structure_score", + } + + +@pytest.mark.parametrize( + "params", + [ + {}, + ], +) +def test_predict_params( + monkeypatch, + table_structure_recognition_predictor, + params, +): + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + table_structure_recognition_predictor, + "paddlex_predictor", + "dummy_path", + params, + ) diff --git a/tests/models/test_text_detection.py b/tests/models/test_text_detection.py new file mode 100644 index 0000000..21ea8c0 --- /dev/null +++ b/tests/models/test_text_detection.py @@ -0,0 +1,72 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from paddleocr import TextDetection +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +pytestmark = pytest.mark.py38_incompatible + + +@pytest.fixture(scope="module") +def text_detection_predictor(): + return TextDetection() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "table.jpg", + ], +) +def test_predict(text_detection_predictor, image_path): + result = text_detection_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + assert result[0].keys() == { + "input_path", + "page_index", + "input_img", + "dt_polys", + "dt_scores", + } + + +@pytest.mark.parametrize( + "params", + [ + {"limit_side_len": 640, "limit_type": "min"}, + {"thresh": 0.5}, + {"box_thresh": 0.3}, + {"unclip_ratio": 3.0}, + ], +) +def test_predict_params( + monkeypatch, + text_detection_predictor, + params, +): + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + text_detection_predictor, + "paddlex_predictor", + "dummy_path", + params, + ) diff --git a/tests/models/test_text_image_unwarping.py b/tests/models/test_text_image_unwarping.py new file mode 100644 index 0000000..ab6ada5 --- /dev/null +++ b/tests/models/test_text_image_unwarping.py @@ -0,0 +1,65 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from paddleocr import TextImageUnwarping +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def text_image_unwarping_predictor(): + return TextImageUnwarping() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "book.jpg", + ], +) +def test_predict(text_image_unwarping_predictor, image_path): + result = text_image_unwarping_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + assert result[0].keys() == { + "input_path", + "page_index", + "input_img", + "doctr_img", + } + + +@pytest.mark.parametrize( + "params", + [ + {}, + ], +) +def test_predict_params( + monkeypatch, + text_image_unwarping_predictor, + params, +): + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + text_image_unwarping_predictor, + "paddlex_predictor", + "dummy_path", + params, + ) diff --git a/tests/models/test_text_recognition.py b/tests/models/test_text_recognition.py new file mode 100644 index 0000000..b47bd43 --- /dev/null +++ b/tests/models/test_text_recognition.py @@ -0,0 +1,46 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from paddleocr import TextRecognition +from ..testing_utils import TEST_DATA_DIR, check_simple_inference_result + + +pytestmark = pytest.mark.py38_incompatible + + +@pytest.fixture(scope="module") +def text_recognition_predictor(): + return TextRecognition() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "textline.png", + ], +) +def test_predict(text_recognition_predictor, image_path): + result = text_recognition_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + assert result[0].keys() == { + "input_path", + "page_index", + "input_img", + "rec_text", + "rec_score", + "vis_font", + } diff --git a/tests/models/test_textline_orientation_classification.py b/tests/models/test_textline_orientation_classification.py new file mode 100644 index 0000000..ca4c5e7 --- /dev/null +++ b/tests/models/test_textline_orientation_classification.py @@ -0,0 +1,37 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from paddleocr import TextLineOrientationClassification +from ..testing_utils import TEST_DATA_DIR, check_simple_inference_result +from .image_classification_common import check_result_item_keys + + +@pytest.fixture(scope="module") +def textline_orientation_classification_predictor(): + return TextLineOrientationClassification() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "textline_rot180.jpg", + ], +) +def test_predict(textline_orientation_classification_predictor, image_path): + result = textline_orientation_classification_predictor.predict(str(image_path)) + + check_simple_inference_result(result) + check_result_item_keys(result[0]) diff --git a/tests/pipelines/__init__.py b/tests/pipelines/__init__.py new file mode 100644 index 0000000..290f972 --- /dev/null +++ b/tests/pipelines/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tests/pipelines/test_doc_preprocessor.py b/tests/pipelines/test_doc_preprocessor.py new file mode 100644 index 0000000..d6b44d6 --- /dev/null +++ b/tests/pipelines/test_doc_preprocessor.py @@ -0,0 +1,71 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from paddleocr import DocPreprocessor +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def ocr_engine() -> DocPreprocessor: + return DocPreprocessor() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "book_rot180.jpg", + ], +) +def test_predict(ocr_engine: DocPreprocessor, image_path: str) -> None: + """ + Test PaddleOCR's doc preprocessor functionality. + + Args: + ocr_engine: An instance of `DocPreprocessor`. + image_path: Path to the image to be processed. + """ + result = ocr_engine.predict(str(image_path)) + + check_simple_inference_result(result) + res = result[0] + assert res["angle"] in {0, 90, 180, 270, -1} + assert res["rot_img"] is not None + assert res["output_img"] is not None + + +@pytest.mark.parametrize( + "params", + [ + {"use_doc_orientation_classify": False}, + {"use_doc_unwarping": False}, + ], +) +def test_predict_params( + monkeypatch, + ocr_engine: DocPreprocessor, + params: dict, +) -> None: + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + ocr_engine, + "paddlex_pipeline", + "dummy_path", + params, + ) diff --git a/tests/pipelines/test_doc_understanding.py b/tests/pipelines/test_doc_understanding.py new file mode 100644 index 0000000..a02ce6a --- /dev/null +++ b/tests/pipelines/test_doc_understanding.py @@ -0,0 +1,59 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import DocUnderstanding +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def ocr_engine() -> DocUnderstanding: + return DocUnderstanding() + + +@pytest.mark.resource_intensive +@pytest.mark.parametrize( + "input", + [ + { + "image": str(TEST_DATA_DIR / "medal_table.png"), + "query": "识别这份表格的内容", + }, + { + "image": str(TEST_DATA_DIR / "table.jpg"), + "query": "识别这份表格的内容", + }, + ], +) +def test_predict(ocr_engine: DocUnderstanding, input: dict) -> None: + """ + Test PaddleOCR's doc understanding functionality. + + Args: + ocr_engine: An instance of `DocUnderstanding`. + input: Input dict to be processed. + """ + result = ocr_engine.predict(input) + + check_simple_inference_result(result) + res = result[0] + assert res["result"] is not None + assert isinstance(res["result"], str) diff --git a/tests/pipelines/test_formula_recognition.py b/tests/pipelines/test_formula_recognition.py new file mode 100644 index 0000000..389477c --- /dev/null +++ b/tests/pipelines/test_formula_recognition.py @@ -0,0 +1,84 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import FormulaRecognitionPipeline +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def formula_recognition_engine() -> FormulaRecognitionPipeline: + return FormulaRecognitionPipeline() + + +# TODO: Should we separate unit tests and integration tests? +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "doc_with_formula.png", + ], +) +def test_predict( + formula_recognition_engine: FormulaRecognitionPipeline, image_path: str +) -> None: + """ + Test FormulaRecognitionPipeline's formula_recognition functionality. + + Args: + formula_recognition_engine: An instance of `FormulaRecognitionPipeline`. + image_path: Path to the image to be processed. + """ + result = formula_recognition_engine.predict(str(image_path)) + + check_simple_inference_result(result) + res = result[0] + assert isinstance(res["formula_res_list"], list) + assert len(res["formula_res_list"]) > 0 + + +# TODO: Also check passing `None` +@pytest.mark.parametrize( + "params", + [ + {"use_doc_orientation_classify": False}, + {"use_doc_unwarping": False}, + {"use_layout_detection": False}, + {"layout_threshold": 0.5}, + {"layout_nms": True}, + {"layout_unclip_ratio": 1.5}, + {"layout_merge_bboxes_mode": "large"}, + ], +) +def test_predict_params( + monkeypatch, + formula_recognition_engine: FormulaRecognitionPipeline, + params: dict, +) -> None: + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + formula_recognition_engine, + "paddlex_pipeline", + "dummy_path", + params, + ) + + +# TODO: Test init params diff --git a/tests/pipelines/test_ocr.py b/tests/pipelines/test_ocr.py new file mode 100644 index 0000000..4a0a675 --- /dev/null +++ b/tests/pipelines/test_ocr.py @@ -0,0 +1,188 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from paddleocr import PaddleOCR +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def ocr_engine() -> PaddleOCR: + return PaddleOCR( + text_detection_model_name="PP-OCRv5_server_det", + text_recognition_model_name="PP-OCRv5_server_rec", + ) + + +# TODO: Should we separate unit tests and integration tests? +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "table.jpg", + ], +) +def test_predict(ocr_engine: PaddleOCR, image_path: str) -> None: + """ + Test PaddleOCR's OCR functionality. + + Args: + ocr_engine: An instance of `PaddleOCR`. + image_path: Path to the image to be processed. + """ + result = ocr_engine.predict(str(image_path)) + + check_simple_inference_result(result) + res = result[0] + assert len(res["dt_polys"]) > 0 + assert isinstance(res["rec_texts"], list) + assert len(res["rec_texts"]) > 0 + for text in res["rec_texts"]: + assert isinstance(text, str) + + +# TODO: Also check passing `None` +@pytest.mark.parametrize( + "params", + [ + {"use_doc_orientation_classify": False}, + {"use_doc_unwarping": False}, + {"use_textline_orientation": False}, + {"text_det_limit_side_len": 640, "text_det_limit_type": "min"}, + {"text_det_thresh": 0.5}, + {"text_det_box_thresh": 0.3}, + {"text_det_unclip_ratio": 3.0}, + {"text_rec_score_thresh": 0.5}, + ], +) +def test_predict_params( + monkeypatch, + ocr_engine: PaddleOCR, + params: dict, +) -> None: + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + ocr_engine, + "paddlex_pipeline", + "dummy_path", + params, + ) + + +# TODO: Test init params + + +def test_lang_and_ocr_version(): + ocr_engine = PaddleOCR(lang="ch", ocr_version="PP-OCRv5") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv5_server_det" + assert ocr_engine._params["text_recognition_model_name"] == "PP-OCRv5_server_rec" + ocr_engine = PaddleOCR(lang="chinese_cht", ocr_version="PP-OCRv5") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv5_server_det" + assert ocr_engine._params["text_recognition_model_name"] == "PP-OCRv5_server_rec" + ocr_engine = PaddleOCR(lang="en", ocr_version="PP-OCRv5") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv5_server_det" + assert ocr_engine._params["text_recognition_model_name"] == "en_PP-OCRv5_mobile_rec" + ocr_engine = PaddleOCR(lang="japan", ocr_version="PP-OCRv5") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv5_server_det" + assert ocr_engine._params["text_recognition_model_name"] == "PP-OCRv5_server_rec" + ocr_engine = PaddleOCR(lang="ch", ocr_version="PP-OCRv4") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv4_mobile_det" + assert ocr_engine._params["text_recognition_model_name"] == "PP-OCRv4_mobile_rec" + ocr_engine = PaddleOCR(lang="en", ocr_version="PP-OCRv4") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv4_mobile_det" + assert ocr_engine._params["text_recognition_model_name"] == "en_PP-OCRv4_mobile_rec" + ocr_engine = PaddleOCR(lang="ch", ocr_version="PP-OCRv3") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv3_mobile_det" + assert ocr_engine._params["text_recognition_model_name"] == "PP-OCRv3_mobile_rec" + ocr_engine = PaddleOCR(lang="en", ocr_version="PP-OCRv3") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv3_mobile_det" + assert ocr_engine._params["text_recognition_model_name"] == "en_PP-OCRv3_mobile_rec" + ocr_engine = PaddleOCR(lang="fr", ocr_version="PP-OCRv3") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv3_mobile_det" + assert ( + ocr_engine._params["text_recognition_model_name"] == "latin_PP-OCRv3_mobile_rec" + ) + ocr_engine = PaddleOCR(lang="ar", ocr_version="PP-OCRv3") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv3_mobile_det" + assert ( + ocr_engine._params["text_recognition_model_name"] + == "arabic_PP-OCRv3_mobile_rec" + ) + ocr_engine = PaddleOCR(lang="ru", ocr_version="PP-OCRv3") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv3_mobile_det" + assert ( + ocr_engine._params["text_recognition_model_name"] + == "cyrillic_PP-OCRv3_mobile_rec" + ) + ocr_engine = PaddleOCR(lang="hi", ocr_version="PP-OCRv3") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv3_mobile_det" + assert ( + ocr_engine._params["text_recognition_model_name"] + == "devanagari_PP-OCRv3_mobile_rec" + ) + ocr_engine = PaddleOCR(lang="japan", ocr_version="PP-OCRv3") + assert ocr_engine._params["text_detection_model_name"] == "PP-OCRv3_mobile_det" + assert ( + ocr_engine._params["text_recognition_model_name"] == "japan_PP-OCRv3_mobile_rec" + ) + + +def test_pp_ocrv6_model_names(): + engine = object.__new__(PaddleOCR) + for lang in ("ch", "chinese_cht", "en", "japan", "fr", "de", "vi", "ku", "az"): + det, rec = engine._get_ocr_model_names(lang, "PP-OCRv6") + assert det == "PP-OCRv6_medium_det" + assert rec == "PP-OCRv6_medium_rec" + det, rec = engine._get_ocr_model_names("pi", "PP-OCRv6") + assert det is None + assert rec is None + det, rec = engine._get_ocr_model_names("ru", "PP-OCRv6") + assert det is None + assert rec is None + + +def test_default_ocr_model_names(): + engine = object.__new__(PaddleOCR) + det, rec = engine._get_ocr_model_names(None, None) + assert det == "PP-OCRv6_medium_det" + assert rec == "PP-OCRv6_medium_rec" + det, rec = engine._get_ocr_model_names("ch", None) + assert det == "PP-OCRv6_medium_det" + assert rec == "PP-OCRv6_medium_rec" + det, rec = engine._get_ocr_model_names("fr", None) + assert det == "PP-OCRv6_medium_det" + assert rec == "PP-OCRv6_medium_rec" + det, rec = engine._get_ocr_model_names("ru", None) + assert det == "PP-OCRv5_server_det" + assert rec == "eslav_PP-OCRv5_mobile_rec" + det, rec = engine._get_ocr_model_names("az", None) + assert det == "PP-OCRv6_medium_det" + assert rec == "PP-OCRv6_medium_rec" + det, rec = engine._get_ocr_model_names("ku", None) + assert det == "PP-OCRv6_medium_det" + assert rec == "PP-OCRv6_medium_rec" + det, rec = engine._get_ocr_model_names("pi", None) + assert det == "PP-OCRv5_server_det" + assert rec == "latin_PP-OCRv5_mobile_rec" + + +def test_unsupported_lang_version_raises(): + with pytest.raises(ValueError, match="No models are available"): + PaddleOCR(lang="pi", ocr_version="PP-OCRv6") + with pytest.raises(ValueError, match="No models are available"): + PaddleOCR(lang="ru", ocr_version="PP-OCRv6") diff --git a/tests/pipelines/test_pp_chatocrv4_doc.py b/tests/pipelines/test_pp_chatocrv4_doc.py new file mode 100644 index 0000000..2f4b70a --- /dev/null +++ b/tests/pipelines/test_pp_chatocrv4_doc.py @@ -0,0 +1,96 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import PPChatOCRv4Doc +from ..testing_utils import TEST_DATA_DIR + + +@pytest.fixture(scope="module") +def pp_chatocrv4_doc_pipeline(): + return PPChatOCRv4Doc() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "doc_with_formula.png", + ], +) +def test_visual_predict(pp_chatocrv4_doc_pipeline, image_path): + result = pp_chatocrv4_doc_pipeline.visual_predict(str(image_path)) + + assert result is not None + assert isinstance(result, list) + assert len(result) == 1 + res = result[0] + assert isinstance(res, dict) + assert res.keys() == {"visual_info", "layout_parsing_result"} + assert isinstance(res["visual_info"], dict) + assert isinstance(res["layout_parsing_result"], dict) + + +@pytest.mark.parametrize( + "params", + [ + {"use_doc_orientation_classify": False}, + {"use_doc_unwarping": False}, + {"use_table_recognition": False}, + {"layout_threshold": 0.88}, + {"layout_threshold": [0.45, 0.4]}, + {"layout_threshold": {0: 0.45, 2: 0.48, 7: 0.4}}, + {"layout_nms": False}, + {"layout_unclip_ratio": 1.1}, + {"layout_unclip_ratio": [1.2, 1.5]}, + {"layout_unclip_ratio": {0: 1.2, 2: 1.5, 7: 1.8}}, + {"layout_merge_bboxes_mode": "large"}, + {"layout_merge_bboxes_mode": {0: "large", 2: "small", 7: "union"}}, + {"text_det_limit_side_len": 640, "text_det_limit_type": "min"}, + {"text_det_thresh": 0.5}, + {"text_det_box_thresh": 0.3}, + {"text_det_unclip_ratio": 3.0}, + {"text_rec_score_thresh": 0.5}, + ], +) +def test_visual_predict_params( + monkeypatch, + pp_chatocrv4_doc_pipeline, + params, +): + def _dummy_visual_predict(input, **params): + yield {"visual_info": {}, "layout_parsing_result": params} + + monkeypatch.setattr( + pp_chatocrv4_doc_pipeline.paddlex_pipeline, + "visual_predict", + _dummy_visual_predict, + ) + + result = pp_chatocrv4_doc_pipeline.visual_predict( + input, + **params, + ) + + assert isinstance(result, list) + assert len(result) == 1 + res = result[0] + res = res["layout_parsing_result"] + for k, v in params.items(): + assert res[k] == v + + +# TODO: Test constructor and other methods diff --git a/tests/pipelines/test_pp_doctranslation.py b/tests/pipelines/test_pp_doctranslation.py new file mode 100644 index 0000000..1ba060e --- /dev/null +++ b/tests/pipelines/test_pp_doctranslation.py @@ -0,0 +1,96 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import PPDocTranslation +from ..testing_utils import TEST_DATA_DIR + + +@pytest.fixture(scope="module") +def pp_doctranslation_pipeline(): + return PPDocTranslation() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "book.jpg", + ], +) +def test_visual_predict(pp_doctranslation_pipeline, image_path): + result = pp_doctranslation_pipeline.visual_predict(str(image_path)) + + assert result is not None + assert isinstance(result, list) + assert len(result) == 1 + res = result[0] + assert isinstance(res, dict) + assert res.keys() == {"layout_parsing_result"} + assert isinstance(res["layout_parsing_result"], dict) + + +@pytest.mark.parametrize( + "params", + [ + {"use_doc_orientation_classify": False}, + {"use_doc_unwarping": False}, + {"use_table_recognition": False}, + {"use_formula_recognition": False}, + {"layout_threshold": 0.88}, + {"layout_threshold": [0.45, 0.4]}, + {"layout_threshold": {0: 0.45, 2: 0.48, 7: 0.4}}, + {"layout_nms": False}, + {"layout_unclip_ratio": 1.1}, + {"layout_unclip_ratio": [1.2, 1.5]}, + {"layout_unclip_ratio": {0: 1.2, 2: 1.5, 7: 1.8}}, + {"layout_merge_bboxes_mode": "large"}, + {"layout_merge_bboxes_mode": {0: "large", 2: "small", 7: "union"}}, + {"text_det_limit_side_len": 640, "text_det_limit_type": "min"}, + {"text_det_thresh": 0.5}, + {"text_det_box_thresh": 0.3}, + {"text_det_unclip_ratio": 3.0}, + {"text_rec_score_thresh": 0.5}, + ], +) +def test_visual_predict_params( + monkeypatch, + pp_doctranslation_pipeline, + params, +): + def _dummy_visual_predict(input, **params): + yield {"layout_parsing_result": params} + + monkeypatch.setattr( + pp_doctranslation_pipeline.paddlex_pipeline, + "visual_predict", + _dummy_visual_predict, + ) + + result = pp_doctranslation_pipeline.visual_predict( + input, + **params, + ) + + assert isinstance(result, list) + assert len(result) == 1 + res = result[0] + res = res["layout_parsing_result"] + for k, v in params.items(): + assert res[k] == v + + +# TODO: Test constructor and other methods diff --git a/tests/pipelines/test_pp_structurev3.py b/tests/pipelines/test_pp_structurev3.py new file mode 100644 index 0000000..c85b81e --- /dev/null +++ b/tests/pipelines/test_pp_structurev3.py @@ -0,0 +1,88 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import PPStructureV3 +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def pp_structurev3_pipeline(): + return PPStructureV3() + + +@pytest.mark.resource_intensive +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "doc_with_formula.png", + ], +) +def test_predict(pp_structurev3_pipeline, image_path): + result = pp_structurev3_pipeline.predict(str(image_path)) + + check_simple_inference_result(result) + res = result[0] + overall_ocr_res = res["overall_ocr_res"] + assert len(overall_ocr_res["dt_polys"]) > 0 + assert len(overall_ocr_res["rec_texts"]) > 0 + assert len(overall_ocr_res["rec_polys"]) > 0 + assert len(overall_ocr_res["rec_boxes"]) > 0 + + +@pytest.mark.parametrize( + "params", + [ + {"use_doc_orientation_classify": False}, + {"use_doc_unwarping": False}, + {"use_table_recognition": False}, + {"use_formula_recognition": False}, + {"layout_threshold": 0.88}, + {"layout_threshold": [0.45, 0.4]}, + {"layout_threshold": {0: 0.45, 2: 0.48, 7: 0.4}}, + {"layout_nms": False}, + {"layout_unclip_ratio": 1.1}, + {"layout_unclip_ratio": [1.2, 1.5]}, + {"layout_unclip_ratio": {0: 1.2, 2: 1.5, 7: 1.8}}, + {"layout_merge_bboxes_mode": "large"}, + {"layout_merge_bboxes_mode": {0: "large", 2: "small", 7: "union"}}, + {"text_det_limit_side_len": 640, "text_det_limit_type": "min"}, + {"text_det_thresh": 0.5}, + {"text_det_box_thresh": 0.3}, + {"text_det_unclip_ratio": 3.0}, + {"text_rec_score_thresh": 0.5}, + ], +) +def test_predict_params( + monkeypatch, + pp_structurev3_pipeline, + params, +): + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + pp_structurev3_pipeline, + "paddlex_pipeline", + "dummy_path", + params, + ) + + +# TODO: Test constructor and other methods diff --git a/tests/pipelines/test_seal_rec.py b/tests/pipelines/test_seal_rec.py new file mode 100644 index 0000000..145fdd9 --- /dev/null +++ b/tests/pipelines/test_seal_rec.py @@ -0,0 +1,86 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import SealRecognition +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def ocr_engine() -> SealRecognition: + return SealRecognition() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "seal.png", + ], +) +def test_predict(ocr_engine: SealRecognition, image_path: str) -> None: + """ + Test PaddleOCR's seal recognition functionality. + + Args: + ocr_engine: An instance of `SealRecognition`. + image_path: Path to the image to be processed. + """ + result = ocr_engine.predict(str(image_path)) + + check_simple_inference_result(result) + res = result[0]["seal_res_list"][0] + assert len(res["dt_polys"]) > 0 + assert isinstance(res["rec_texts"], list) + assert len(res["rec_texts"]) > 0 + for text in res["rec_texts"]: + assert isinstance(text, str) + + +@pytest.mark.parametrize( + "params", + [ + {"use_doc_orientation_classify": False, "use_doc_unwarping": False}, + {"use_layout_detection": False}, + {"layout_det_res": None}, + {"layout_threshold": 0.5}, + {"layout_nms": False}, + {"layout_unclip_ratio": 1.0}, + {"layout_merge_bboxes_mode": "large"}, + {"seal_det_limit_side_len": 736}, + {"seal_det_limit_type": "min"}, + {"seal_det_thresh": 0.5}, + {"seal_det_box_thresh": 0.6}, + {"seal_det_unclip_ratio": 0.5}, + {"seal_rec_score_thresh": 0.05}, + ], +) +def test_predict_params( + monkeypatch, + ocr_engine: SealRecognition, + params: dict, +) -> None: + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + ocr_engine, + "paddlex_pipeline", + "dummy_path", + params, + ) diff --git a/tests/pipelines/test_table_recognition_v2.py b/tests/pipelines/test_table_recognition_v2.py new file mode 100644 index 0000000..58fdbda --- /dev/null +++ b/tests/pipelines/test_table_recognition_v2.py @@ -0,0 +1,80 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +pytestmark = pytest.mark.py38_incompatible + +from paddleocr import TableRecognitionPipelineV2 +from ..testing_utils import ( + TEST_DATA_DIR, + check_simple_inference_result, + check_wrapper_simple_inference_param_forwarding, +) + + +@pytest.fixture(scope="module") +def table_recognition_v2_pipeline(): + return TableRecognitionPipelineV2() + + +@pytest.mark.parametrize( + "image_path", + [ + TEST_DATA_DIR / "table.jpg", + ], +) +def test_predict(table_recognition_v2_pipeline, image_path): + result = table_recognition_v2_pipeline.predict( + str(image_path), use_doc_orientation_classify=False, use_doc_unwarping=False + ) + + check_simple_inference_result(result) + res = result[0] + assert len(res["table_res_list"]) > 0 + assert isinstance(res["table_res_list"][0], dict) + assert len(res["table_res_list"][0]["cell_box_list"]) > 0 + assert isinstance(res["table_res_list"][0]["pred_html"], str) + assert isinstance(res["table_res_list"][0]["table_ocr_pred"], dict) + + +@pytest.mark.parametrize( + "params", + [ + {"use_doc_orientation_classify": False}, + {"use_doc_unwarping": False}, + {"use_layout_detection": False}, + {"use_ocr_model": False}, + {"text_det_limit_side_len": 640, "text_det_limit_type": "min"}, + {"text_det_thresh": 0.5}, + {"text_det_box_thresh": 0.3}, + {"text_det_unclip_ratio": 3.0}, + {"text_rec_score_thresh": 0.5}, + ], +) +def test_predict_params( + monkeypatch, + table_recognition_v2_pipeline, + params, +): + check_wrapper_simple_inference_param_forwarding( + monkeypatch, + table_recognition_v2_pipeline, + "paddlex_pipeline", + "dummy_path", + params, + ) + + +# TODO: Test constructor and other methods diff --git a/tests/ppocr/test_cls_postprocess.py b/tests/ppocr/test_cls_postprocess.py new file mode 100644 index 0000000..f65039c --- /dev/null +++ b/tests/ppocr/test_cls_postprocess.py @@ -0,0 +1,72 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +import paddle +import pytest + +from ppocr.postprocess.cls_postprocess import ClsPostProcess + + +# Fixtures for common test inputs +@pytest.fixture +def preds_tensor(): + return paddle.to_tensor(np.array([[0.1, 0.7, 0.2], [0.3, 0.3, 0.4]])) + + +@pytest.fixture +def label_list(): + return {0: "class0", 1: "class1", 2: "class2"} + + +# Parameterize tests to cover multiple scenarios +@pytest.mark.parametrize( + "label_list, expected", + [ + ({0: "class0", 1: "class1", 2: "class2"}, [("class1", 0.7), ("class2", 0.4)]), + (None, [(1, 0.7), (2, 0.4)]), + ], +) +def test_cls_post_process_with_and_without_label_list( + preds_tensor, label_list, expected +): + post_process = ClsPostProcess(label_list=label_list) + result = post_process(preds_tensor) + assert isinstance(result, list), "Result should be a list" + assert result == expected, f"Expected {expected}, got {result}" + + +# Test with a key in the prediction dictionary +def test_cls_post_process_with_key(preds_tensor, label_list): + preds_dict = {"key": preds_tensor} + post_process = ClsPostProcess(label_list=label_list, key="key") + result = post_process(preds_dict) + expected = [("class1", 0.7), ("class2", 0.4)] + assert isinstance(result, list), "Result should be a list" + assert result == expected, f"Expected {expected}, got {result}" + + +# Test with label input +def test_cls_post_process_with_label(preds_tensor, label_list): + labels = [2, 0] + post_process = ClsPostProcess(label_list=label_list) + result, label_result = post_process(preds_tensor, labels) + expected_result = [("class1", 0.7), ("class2", 0.4)] + expected_label_result = [("class2", 1.0), ("class0", 1.0)] + assert isinstance(result, list), "Result should be a list" + assert result == expected_result, f"Expected {expected_result}, got {result}" + assert isinstance(label_result, list), "Label result should be a list" + assert ( + label_result == expected_label_result + ), f"Expected {expected_label_result}, got {label_result}" diff --git a/tests/ppocr/test_formula_model.py b/tests/ppocr/test_formula_model.py new file mode 100644 index 0000000..8705573 --- /dev/null +++ b/tests/ppocr/test_formula_model.py @@ -0,0 +1,202 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle +import pytest + +from ppocr.modeling.backbones.rec_donut_swin import DonutSwinModel, DonutSwinModelOutput +from ppocr.modeling.backbones.rec_pphgnetv2 import PPHGNetV2_B4_Formula +from ppocr.modeling.backbones.rec_vary_vit import Vary_VIT_B_Formula +from ppocr.modeling.heads.rec_unimernet_head import UniMERNetHead +from ppocr.modeling.heads.rec_ppformulanet_head import PPFormulaNet_Head + + +@pytest.fixture +def sample_image(): + return paddle.randn([1, 1, 192, 672]) + + +@pytest.fixture +def sample_image_ppformulanet_s(): + return paddle.randn([1, 1, 384, 384]) + + +@pytest.fixture +def sample_image_ppformulanet_l(): + return paddle.randn([1, 1, 768, 768]) + + +@pytest.fixture +def encoder_feat(): + encoded_feat = paddle.randn([1, 126, 1024]) + return DonutSwinModelOutput( + last_hidden_state=encoded_feat, + ) + + +@pytest.fixture +def encoder_feat_ppformulanet_s(): + encoded_feat = paddle.randn([1, 144, 2048]) + return DonutSwinModelOutput( + last_hidden_state=encoded_feat, + ) + + +@pytest.fixture +def encoder_feat_ppformulanet_l(): + encoded_feat = paddle.randn([1, 144, 1024]) + return DonutSwinModelOutput( + last_hidden_state=encoded_feat, + ) + + +def test_unimernet_backbone(sample_image): + """ + Test UniMERNet backbone. + + Args: + sample_image: sample image to be processed. + """ + backbone = DonutSwinModel( + hidden_size=1024, + num_layers=4, + num_heads=[4, 8, 16, 32], + add_pooling_layer=True, + use_mask_token=False, + ) + backbone.eval() + with paddle.no_grad(): + result = backbone(sample_image) + encoder_feat = result[0] + assert encoder_feat.shape == [1, 126, 1024] + + +def test_unimernet_head(encoder_feat): + """ + Test UniMERNet head. + + Args: + encoder_feat: encoder feature from unimernet backbone. + """ + head = UniMERNetHead( + max_new_tokens=5, + decoder_start_token_id=0, + temperature=0.2, + do_sample=False, + top_p=0.95, + encoder_hidden_size=1024, + is_export=False, + length_aware=True, + ) + + head.eval() + with paddle.no_grad(): + result = head(encoder_feat) + assert result.shape == [1, 6] + + +def test_ppformulanet_s_backbone(sample_image_ppformulanet_s): + """ + Test PP-FormulaNet-S backbone. + + Args: + sample_image_ppformulanet_s: sample image to be processed. + """ + backbone = PPHGNetV2_B4_Formula( + class_num=1024, + ) + backbone.eval() + with paddle.no_grad(): + result = backbone(sample_image_ppformulanet_s) + encoder_feat = result[0] + assert encoder_feat.shape == [1, 144, 2048] + + +def test_ppformulanet_s_head(encoder_feat_ppformulanet_s): + """ + Test PP-FormulaNet-S head. + + Args: + encoder_feat_ppformulanet_s: encoder feature from PP-FormulaNet-S backbone. + """ + head = PPFormulaNet_Head( + max_new_tokens=6, + decoder_start_token_id=0, + decoder_ffn_dim=1536, + decoder_hidden_size=384, + decoder_layers=2, + temperature=0.2, + do_sample=False, + top_p=0.95, + encoder_hidden_size=2048, + is_export=False, + length_aware=True, + use_parallel=True, + parallel_step=3, + ) + + head.eval() + with paddle.no_grad(): + result = head(encoder_feat_ppformulanet_s) + assert result.shape == [1, 9] + + +def test_ppformulanet_l_backbone(sample_image_ppformulanet_l): + """ + Test PP-FormulaNet-L backbone. + + Args: + sample_image_ppformulanet_l: sample image to be processed. + """ + backbone = Vary_VIT_B_Formula( + image_size=768, + encoder_embed_dim=768, + encoder_depth=12, + encoder_num_heads=12, + encoder_global_attn_indexes=[2, 5, 8, 11], + ) + backbone.eval() + with paddle.no_grad(): + result = backbone(sample_image_ppformulanet_l) + encoder_feat = result[0] + assert encoder_feat.shape == [1, 144, 1024] + + +def test_ppformulanet_l_head(encoder_feat_ppformulanet_l): + """ + Test PP-FormulaNet-L head. + + Args: + encoder_feat_ppformulanet_l: encoder feature from PP-FormulaNet-L Head. + """ + head = PPFormulaNet_Head( + max_new_tokens=6, + decoder_start_token_id=0, + decoder_ffn_dim=2048, + decoder_hidden_size=512, + decoder_layers=8, + temperature=0.2, + do_sample=False, + top_p=0.95, + encoder_hidden_size=1024, + is_export=False, + length_aware=False, + use_parallel=False, + parallel_step=0, + ) + + head.eval() + with paddle.no_grad(): + result = head(encoder_feat_ppformulanet_l) + assert result.shape == [1, 7] diff --git a/tests/ppocr/test_iaa_augment.py b/tests/ppocr/test_iaa_augment.py new file mode 100644 index 0000000..250da1a --- /dev/null +++ b/tests/ppocr/test_iaa_augment.py @@ -0,0 +1,211 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import random +import sys + +import numpy as np +import pytest + +pytestmark = pytest.mark.py38_incompatible +# py3.8 lmdb wheel exposes an undefined Py_SET_REFCNT symbol and fails to +# load. Skip collection only on py3.8; on py3.9+ a lmdb import failure is +# a real regression and must surface rather than be silently skipped. +if sys.version_info < (3, 9): + pytest.importorskip("lmdb") + +from ppocr.data.imaug.iaa_augment import IaaAugment + +# Set a fixed random seed to ensure test reproducibility +np.random.seed(42) +random.seed(42) + + +# Fixture to provide a sample image for tests +@pytest.fixture +def sample_image(): + # Create a 100x100 pixel dummy image with 3 color channels (RGB) + return np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8) + + +# Fixture to provide sample polygons for tests +@pytest.fixture +def sample_polys(): + # Create dummy polygons as sample data + polys = [ + np.array([[10, 10], [20, 10], [20, 20], [10, 20]], dtype=np.float32), + np.array([[30, 30], [40, 30], [40, 40], [30, 40]], dtype=np.float32), + ] + return polys + + +# Helper function to create a data dictionary for testing +def create_data(sample_image, sample_polys): + return { + "image": sample_image.copy(), + "polys": [poly.copy() for poly in sample_polys], + } + + +# Test the default behavior of the augmenter (without specified arguments) +def test_iaa_augment_default(sample_image, sample_polys): + data = create_data(sample_image, sample_polys) + augmenter = IaaAugment() + transformed_data = augmenter(data) + + # Check the data types and structure of the transformed image and polygons + assert isinstance( + transformed_data["image"], np.ndarray + ), "Image should be a numpy array" + assert isinstance( + transformed_data["polys"], np.ndarray + ), "Polys should be a numpy array" + assert transformed_data["image"].ndim == 3, "Image should be 3-dimensional" + + # Verify that the polygons have been transformed + polys_changed = any( + not np.allclose(orig_poly, trans_poly) + for orig_poly, trans_poly in zip(sample_polys, transformed_data["polys"]) + ) + assert polys_changed, "Polygons should have been transformed" + + +# Test the augmenter with empty arguments, meaning no transformations should occur +def test_iaa_augment_none(sample_image, sample_polys): + data = create_data(sample_image, sample_polys) + augmenter = IaaAugment(augmenter_args=[]) + transformed_data = augmenter(data) + + # Check that the image and polygons remain unchanged + assert np.array_equal( + data["image"], transformed_data["image"] + ), "Image should be unchanged" + for orig_poly, transformed_poly in zip(data["polys"], transformed_data["polys"]): + assert np.array_equal( + orig_poly, transformed_poly + ), "Polygons should be unchanged" + + +# Parameterized test to check various augmenter arguments and expected image shapes +@pytest.mark.parametrize( + "augmenter_args, expected_shape", + [ + ([], (100, 100, 3)), + ([{"type": "Resize", "args": {"size": [0.5, 0.5]}}], (50, 50, 3)), + ([{"type": "Resize", "args": {"size": [2.0, 2.0]}}], (200, 200, 3)), + ], +) +def test_iaa_augment_resize(sample_image, sample_polys, augmenter_args, expected_shape): + data = create_data(sample_image, sample_polys) + augmenter = IaaAugment(augmenter_args=augmenter_args) + transformed_data = augmenter(data) + + # Verify that the transformed image has the expected shape + assert ( + transformed_data["image"].shape == expected_shape + ), f"Expected image shape {expected_shape}, got {transformed_data['image'].shape}" + + +# Test custom augmenter arguments with specific transformations +def test_iaa_augment_custom(sample_image, sample_polys): + data = create_data(sample_image, sample_polys) + augmenter_args = [ + {"type": "Affine", "args": {"rotate": [45, 45]}}, # Apply 45-degree rotation + {"type": "Resize", "args": {"size": [0.5, 0.5]}}, + ] + augmenter = IaaAugment(augmenter_args=augmenter_args) + transformed_data = augmenter(data) + + # Check the expected image dimensions after resizing + expected_height = int(sample_image.shape[0] * 0.5) + expected_width = int(sample_image.shape[1] * 0.5) + assert ( + transformed_data["image"].shape[0] == expected_height + ), "Image height should be scaled by 0.5" + assert ( + transformed_data["image"].shape[1] == expected_width + ), "Image width should be scaled by 0.5" + + # Verify that the polygons have been transformed + polys_changed = any( + not np.allclose(orig_poly, trans_poly) + for orig_poly, trans_poly in zip(sample_polys, transformed_data["polys"]) + ) + assert polys_changed, "Polygons should have been transformed" + + +def test_iaa_augment_affine_fit_output(sample_image, sample_polys): + data = create_data(sample_image, sample_polys) + augmenter_args = [ + {"type": "Affine", "args": {"rotate": [45, 45], "fit_output": True}}, + ] + augmenter = IaaAugment(augmenter_args=augmenter_args) + transformed_data = augmenter(data) + + assert transformed_data["image"].shape[0] > sample_image.shape[0] + assert transformed_data["image"].shape[1] > sample_image.shape[1] + + +# Test that an unknown transformation type raises an AttributeError +def test_iaa_augment_unknown_transform(): + augmenter_args = [{"type": "UnknownTransform", "args": {}}] + with pytest.raises(AttributeError): + IaaAugment(augmenter_args=augmenter_args) + + +# Test that an invalid resize size parameter raises a ValueError +def test_iaa_augment_invalid_resize_size(sample_image, sample_polys): + augmenter_args = [{"type": "Resize", "args": {"size": "invalid_size"}}] + with pytest.raises(ValueError) as exc_info: + IaaAugment(augmenter_args=augmenter_args) + assert "'size' must be a list or tuple of two numbers" in str(exc_info.value) + + +# Test that polygons are transformed as expected +def test_iaa_augment_polys_transformation(sample_image, sample_polys): + data = create_data(sample_image, sample_polys) + augmenter_args = [ + {"type": "Affine", "args": {"rotate": [90, 90]}}, # Apply 90-degree rotation + ] + augmenter = IaaAugment(augmenter_args=augmenter_args) + transformed_data = augmenter(data) + + # Verify that the polygons have been transformed + polys_changed = any( + not np.allclose(orig_poly, trans_poly) + for orig_poly, trans_poly in zip(sample_polys, transformed_data["polys"]) + ) + assert polys_changed, "Polygons should have been transformed" + + +# Test multiple transformations applied to the augmenter +def test_iaa_augment_multiple_transforms(sample_image, sample_polys): + augmenter_args = [ + {"type": "Fliplr", "args": {"p": 1.0}}, # Always apply horizontal flip + {"type": "Affine", "args": {"shear": 10}}, + ] + data = create_data(sample_image, sample_polys) + augmenter = IaaAugment(augmenter_args=augmenter_args) + transformed_data = augmenter(data) + + # Ensure the image has been transformed + images_different = not np.array_equal(transformed_data["image"], sample_image) + assert images_different, "Image should be transformed" + + # Ensure the polygons have been transformed + polys_changed = any( + not np.allclose(orig_poly, trans_poly) + for orig_poly, trans_poly in zip(sample_polys, transformed_data["polys"]) + ) + assert polys_changed, "Polygons should have been transformed" diff --git a/tests/ppocr/test_rec_postprocess.py b/tests/ppocr/test_rec_postprocess.py new file mode 100644 index 0000000..41b951e --- /dev/null +++ b/tests/ppocr/test_rec_postprocess.py @@ -0,0 +1,149 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib.util +from pathlib import Path + +import numpy as np +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def _load_base_rec_label_decode(): + spec = importlib.util.spec_from_file_location( + "ppocr.postprocess.rec_postprocess", + REPO_ROOT / "ppocr" / "postprocess" / "rec_postprocess.py", + ) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + return module.BaseRecLabelDecode + + +BaseRecLabelDecode = _load_base_rec_label_decode() + + +class TestBaseRecLabelDecode: + """Tests for BaseRecLabelDecode.get_word_info().""" + + @pytest.fixture + def decoder(self): + return BaseRecLabelDecode() + + def test_get_word_info_with_german_accented_chars(self, decoder): + text = "Grüßen" + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert len(word_list) == 1 + assert "".join(word_list[0]) == "Grüßen" + assert state_list[0] == "en&num" + + def test_get_word_info_with_longer_german_word(self, decoder): + text = "ungewöhnlichen" + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert len(word_list) == 1 + assert "".join(word_list[0]) == "ungewöhnlichen" + assert state_list[0] == "en&num" + + def test_get_word_info_with_french_accented_chars(self, decoder): + text = "café" + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert len(word_list) == 1 + assert "".join(word_list[0]) == "café" + + def test_get_word_info_underscore_as_splitter(self, decoder): + text = "hello_world" + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert len(word_list) == 2 + assert "".join(word_list[0]) == "hello" + assert "".join(word_list[1]) == "world" + + def test_get_word_info_with_mixed_content(self, decoder): + text = "Grüßen Sie" + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert len(word_list) == 2 + assert "".join(word_list[0]) == "Grüßen" + assert "".join(word_list[1]) == "Sie" + + def test_get_word_info_with_french_apostrophe(self, decoder): + text = "n'êtes" + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert len(word_list) == 1 + assert "".join(word_list[0]) == "n'êtes" + + @pytest.mark.parametrize( + "text,expected_word_count,expected_joined_words", + [ + ("été", 1, ["été"]), + ("français", 1, ["français"]), + ("élève", 1, ["élève"]), + ("à demain", 2, ["à", "demain"]), + ], + ) + def test_get_word_info_french_accented_words( + self, decoder, text, expected_word_count, expected_joined_words + ): + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert len(word_list) == expected_word_count + assert ["".join(word) for word in word_list] == expected_joined_words + assert all(state == "en&num" for state in state_list) + + def test_get_word_info_french_complex_sentence(self, decoder): + text = "C'était très français" + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert ["".join(word) for word in word_list] == [ + "C'était", + "très", + "français", + ] + assert state_list == ["en&num", "en&num", "en&num"] + + def test_get_word_info_with_ascii_only(self, decoder): + text = "hello world" + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert len(word_list) == 2 + assert "".join(word_list[0]) == "hello" + assert "".join(word_list[1]) == "world" + + def test_get_word_info_with_numbers(self, decoder): + text = "VGG-16" + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert len(word_list) == 1 + assert "".join(word_list[0]) == "VGG-16" + + def test_get_word_info_with_floating_point(self, decoder): + text = "price 3.14" + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert len(word_list) == 2 + assert "".join(word_list[0]) == "price" + assert "".join(word_list[1]) == "3.14" + + def test_get_word_info_with_chinese(self, decoder): + text = "你好啊" + selection = np.ones(len(text), dtype=bool) + word_list, _, state_list = decoder.get_word_info(text, selection) + assert len(word_list) == 1 + assert "".join(word_list[0]) == "你好啊" + assert state_list[0] == "cn" diff --git a/tests/security/test_latexocr_pickle.py b/tests/security/test_latexocr_pickle.py new file mode 100644 index 0000000..59fe54a --- /dev/null +++ b/tests/security/test_latexocr_pickle.py @@ -0,0 +1,138 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib.util +import io +import pickle +import sys +import types +from pathlib import Path + +import numpy as np +import pytest + + +def _load_latexocr_module(monkeypatch): + module_name = "ppocr.data.latexocr_dataset" + module_path = ( + Path(__file__).resolve().parents[2] / "ppocr" / "data" / "latexocr_dataset.py" + ) + + ppocr_pkg = types.ModuleType("ppocr") + ppocr_pkg.__path__ = [] + data_pkg = types.ModuleType("ppocr.data") + data_pkg.__path__ = [] + imaug_pkg = types.ModuleType("ppocr.data.imaug") + imaug_pkg.__path__ = [] + label_ops_mod = types.ModuleType("ppocr.data.imaug.label_ops") + + class _Tokenizer: + def __init__(self, *_args, **_kwargs): + pass + + label_ops_mod.LatexOCRLabelEncode = _Tokenizer + imaug_pkg.transform = lambda data, ops: data + imaug_pkg.create_operators = lambda transforms, global_config: [] + + paddle_mod = types.ModuleType("paddle") + paddle_io_mod = types.ModuleType("paddle.io") + + class _Dataset: + pass + + paddle_io_mod.Dataset = _Dataset + paddle_mod.io = paddle_io_mod + paddle_mod.arange = lambda n: list(range(n)) + paddle_mod.randperm = lambda n: list(range(n)) + + for name, module in { + "ppocr": ppocr_pkg, + "ppocr.data": data_pkg, + "ppocr.data.imaug": imaug_pkg, + "ppocr.data.imaug.label_ops": label_ops_mod, + "paddle": paddle_mod, + "paddle.io": paddle_io_mod, + "cv2": types.ModuleType("cv2"), + }.items(): + monkeypatch.setitem(sys.modules, name, module) + + spec = importlib.util.spec_from_file_location(module_name, module_path) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + monkeypatch.setitem(sys.modules, module_name, module) + spec.loader.exec_module(module) + return module + + +class _Payload: + def __init__(self, marker_path: Path): + self.marker_path = marker_path + + def __reduce__(self): + expression = ( + f"(__import__('pathlib').Path({self.marker_path.as_posix()!r}).write_text('pwned'), " + "{(32, 32): [('eq', 'img.png')]})[1]" + ) + return (eval, (expression,)) + + +@pytest.fixture +def latexocr_config(tmp_path): + return { + "Global": { + "max_seq_len": 16, + "rec_char_dict_path": str(tmp_path / "dict.txt"), + }, + "Train": { + "dataset": { + "data": str(tmp_path / "dataset.pkl"), + "data_dir": str(tmp_path), + "min_dimensions": [1, 1], + "max_dimensions": [128, 128], + "batch_size_per_pair": 1, + "keep_smaller_batches": True, + "transforms": [], + }, + "loader": {"shuffle": False}, + }, + } + + +def test_latexocr_dataset_rejects_executable_pickle_payload( + tmp_path, latexocr_config, monkeypatch +): + module = _load_latexocr_module(monkeypatch) + marker_path = tmp_path / "marker.txt" + payload_path = Path(latexocr_config["Train"]["dataset"]["data"]) + payload_path.write_bytes(pickle.dumps(_Payload(marker_path))) + + with pytest.raises(pickle.UnpicklingError): + module.LaTeXOCRDataSet(latexocr_config, "Train", logger=types.SimpleNamespace()) + + assert not marker_path.exists() + + +def test_latexocr_dataset_accepts_basic_dict_payload( + tmp_path, latexocr_config, monkeypatch +): + module = _load_latexocr_module(monkeypatch) + payload_path = Path(latexocr_config["Train"]["dataset"]["data"]) + payload_path.write_bytes(pickle.dumps({(32, 32): [("eq", "img.png")]})) + + dataset = module.LaTeXOCRDataSet( + latexocr_config, "Train", logger=types.SimpleNamespace() + ) + + assert dataset.size == 1 + assert dataset.data == {(32, 32): [("eq", "img.png")]} diff --git a/tests/security/test_lmdb_pickle.py b/tests/security/test_lmdb_pickle.py new file mode 100644 index 0000000..de5231d --- /dev/null +++ b/tests/security/test_lmdb_pickle.py @@ -0,0 +1,150 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib.util +import os +import pickle +import sys +import types +from pathlib import Path + +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def _stub_module(monkeypatch, name, **attrs): + module = types.ModuleType(name) + for key, value in attrs.items(): + setattr(module, key, value) + monkeypatch.setitem(sys.modules, name, module) + return module + + +def _load_lmdb_dataset_module(monkeypatch, lmdb_open): + _stub_module(monkeypatch, "cv2") + _stub_module( + monkeypatch, "numpy", random=types.SimpleNamespace(randint=lambda upper: 0) + ) + _stub_module(monkeypatch, "lmdb", open=lmdb_open) + _stub_module(monkeypatch, "PIL") + _stub_module(monkeypatch, "PIL.Image") + + paddle_module = _stub_module(monkeypatch, "paddle") + paddle_io_module = _stub_module( + monkeypatch, "paddle.io", Dataset=type("Dataset", (), {}) + ) + paddle_module.io = paddle_io_module + + ppocr_module = _stub_module(monkeypatch, "ppocr") + ppocr_data_module = _stub_module(monkeypatch, "ppocr.data") + ppocr_data_module.__path__ = [] + ppocr_module.data = ppocr_data_module + _stub_module( + monkeypatch, + "ppocr.data.imaug", + transform=lambda data, ops: data, + create_operators=lambda *args, **kwargs: [], + ) + + spec = importlib.util.spec_from_file_location( + "ppocr.data.lmdb_dataset", REPO_ROOT / "ppocr" / "data" / "lmdb_dataset.py" + ) + module = importlib.util.module_from_spec(spec) + monkeypatch.setitem(sys.modules, "ppocr.data.lmdb_dataset", module) + spec.loader.exec_module(module) + return module + + +class _Exploit: + def __reduce__(self): + return (os.system, ("echo SHOULD_NOT_RUN > /tmp/paddleocr_lmdb_pickle_test",)) + + +class _Txn: + def __init__(self, values): + self._values = values + + def get(self, key): + return self._values.get(key) + + +class _Env: + def __init__(self, txn): + self._txn = txn + + def begin(self, write=False): + return self._txn + + +@pytest.fixture(autouse=True) +def _cleanup_marker(): + marker = Path("/tmp/paddleocr_lmdb_pickle_test") + marker.unlink(missing_ok=True) + yield + marker.unlink(missing_ok=True) + + +def test_tablemaster_sample_info_rejects_pickle_rce_payload(monkeypatch): + module = _load_lmdb_dataset_module( + monkeypatch, lmdb_open=lambda *args, **kwargs: None + ) + payload = pickle.dumps(_Exploit()) + txn = _Txn({b"1": payload}) + dataset = module.LMDBDataSetTableMaster.__new__(module.LMDBDataSetTableMaster) + + assert dataset.get_lmdb_sample_info(txn, 1) is None + assert not Path("/tmp/paddleocr_lmdb_pickle_test").exists() + + +def test_tablemaster_sample_info_accepts_expected_basic_pickle_data(monkeypatch): + module = _load_lmdb_dataset_module( + monkeypatch, lmdb_open=lambda *args, **kwargs: None + ) + safe_payload = pickle.dumps(("sample.png", b"img-bytes", "raw-name\ntext\n1,2,3,4")) + txn = _Txn({b"1": safe_payload}) + dataset = module.LMDBDataSetTableMaster.__new__(module.LMDBDataSetTableMaster) + + sample = dataset.get_lmdb_sample_info(txn, 1) + + assert sample["file_name"] == "sample.png" + assert sample["image"] == b"img-bytes" + assert sample["structure"] == ["text"] + assert sample["cells"] == [{"bbox": [1, 2, 3, 4], "tokens": ["1", "2"]}] + + +def test_tablemaster_length_metadata_rejects_pickle_rce_payload(monkeypatch): + payload = pickle.dumps(_Exploit()) + txn = _Txn({b"__len__": payload}) + module = _load_lmdb_dataset_module( + monkeypatch, lmdb_open=lambda *args, **kwargs: _Env(txn) + ) + dataset = module.LMDBDataSetTableMaster.__new__(module.LMDBDataSetTableMaster) + + with pytest.raises(pickle.UnpicklingError): + dataset.load_hierarchical_lmdb_dataset("/tmp/ignored") + + assert not Path("/tmp/paddleocr_lmdb_pickle_test").exists() + + +def test_tablemaster_length_metadata_accepts_expected_integer_pickle(monkeypatch): + txn = _Txn({b"__len__": pickle.dumps(7)}) + module = _load_lmdb_dataset_module( + monkeypatch, lmdb_open=lambda *args, **kwargs: _Env(txn) + ) + dataset = module.LMDBDataSetTableMaster.__new__(module.LMDBDataSetTableMaster) + + lmdb_sets = dataset.load_hierarchical_lmdb_dataset("/tmp/ignored") + + assert lmdb_sets[0]["num_samples"] == 7 diff --git a/tests/test_files/book.jpg b/tests/test_files/book.jpg new file mode 100644 index 0000000..05897ee Binary files /dev/null and b/tests/test_files/book.jpg differ diff --git a/tests/test_files/book_rot180.jpg b/tests/test_files/book_rot180.jpg new file mode 100644 index 0000000..64f8af1 Binary files /dev/null and b/tests/test_files/book_rot180.jpg differ diff --git a/tests/test_files/doc_with_formula.png b/tests/test_files/doc_with_formula.png new file mode 100644 index 0000000..a7e7f22 Binary files /dev/null and b/tests/test_files/doc_with_formula.png differ diff --git a/tests/test_files/formula.png b/tests/test_files/formula.png new file mode 100644 index 0000000..26a271a Binary files /dev/null and b/tests/test_files/formula.png differ diff --git a/tests/test_files/medal_table.png b/tests/test_files/medal_table.png new file mode 100644 index 0000000..d4f6b3f Binary files /dev/null and b/tests/test_files/medal_table.png differ diff --git a/tests/test_files/seal.png b/tests/test_files/seal.png new file mode 100644 index 0000000..b814e41 Binary files /dev/null and b/tests/test_files/seal.png differ diff --git a/tests/test_files/table.jpg b/tests/test_files/table.jpg new file mode 100644 index 0000000..07059ca Binary files /dev/null and b/tests/test_files/table.jpg differ diff --git a/tests/test_files/textline.png b/tests/test_files/textline.png new file mode 100644 index 0000000..6e4efd2 Binary files /dev/null and b/tests/test_files/textline.png differ diff --git a/tests/test_files/textline_rot180.jpg b/tests/test_files/textline_rot180.jpg new file mode 100644 index 0000000..c28fafa Binary files /dev/null and b/tests/test_files/textline_rot180.jpg differ diff --git a/tests/test_ppstructure.py b/tests/test_ppstructure.py new file mode 100644 index 0000000..290f972 --- /dev/null +++ b/tests/test_ppstructure.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tests/testing_utils.py b/tests/testing_utils.py new file mode 100644 index 0000000..9e6683a --- /dev/null +++ b/tests/testing_utils.py @@ -0,0 +1,50 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path + +TEST_DATA_DIR = Path(__file__).parent / "test_files" + + +def check_simple_inference_result(result, *, expected_length=1): + assert result is not None + assert isinstance(result, list) + assert len(result) == expected_length + for res in result: + assert isinstance(res, dict) + + +def check_wrapper_simple_inference_param_forwarding( + monkeypatch, + wrapper, + wrapped_obj_attr_name, + input, + params, +): + def _dummy_predict(input, **params): + yield params + + monkeypatch.setattr( + getattr(wrapper, wrapped_obj_attr_name), "predict", _dummy_predict + ) + + result = getattr(wrapper, "predict")( + input, + **params, + ) + + assert isinstance(result, list) + assert len(result) == 1 + for k, v in params.items(): + assert result[0][k] == v diff --git a/tests/tools/test_docs_github_links.py b/tests/tools/test_docs_github_links.py new file mode 100644 index 0000000..2ebf10d --- /dev/null +++ b/tests/tools/test_docs_github_links.py @@ -0,0 +1,91 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib.util +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def _load_tool(name): + spec = importlib.util.spec_from_file_location( + name, REPO_ROOT / "tools" / f"{name}.py" + ) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def test_check_docs_github_links_rejects_moving_source_refs(tmp_path): + checker = _load_tool("check_docs_github_links") + doc_path = tmp_path / "doc.md" + doc_path.write_text( + "[config](https://github.com/PaddlePaddle/PaddleOCR/blob/main/deploy/config.yaml)\n", + encoding="utf-8", + ) + + violations = checker.find_forbidden_links( + tmp_path, + repo_slug="PaddlePaddle/PaddleOCR", + forbidden_refs={"main", "master"}, + ) + + assert len(violations) == 1 + assert violations[0].path == doc_path + assert violations[0].ref == "main" + assert violations[0].line_number == 1 + + +def test_check_docs_github_links_allows_versioned_and_placeholder_refs(tmp_path): + checker = _load_tool("check_docs_github_links") + doc_path = tmp_path / "doc.md" + doc_path.write_text( + "\n".join( + [ + "[release](https://github.com/PaddlePaddle/PaddleOCR/blob/release/3.5/deploy/config.yaml)", + "[placeholder](https://github.com/PaddlePaddle/PaddleOCR/blob/{{PADDLEOCR_GITHUB_REF}}/deploy/config.yaml)", + ] + ), + encoding="utf-8", + ) + + violations = checker.find_forbidden_links( + tmp_path, + repo_slug="PaddlePaddle/PaddleOCR", + forbidden_refs={"main", "master"}, + ) + + assert violations == [] + + +def test_resolve_doc_github_refs_replaces_placeholders(tmp_path): + resolver = _load_tool("resolve_doc_github_refs") + doc_path = tmp_path / "doc.md" + doc_path.write_text( + "[config](https://github.com/PaddlePaddle/PaddleOCR/blob/{{PADDLEOCR_GITHUB_REF}}/deploy/config.yaml)\n", + encoding="utf-8", + ) + + changed = resolver.resolve_placeholders( + tmp_path, + placeholder="{{PADDLEOCR_GITHUB_REF}}", + source_ref="release/3.5", + ) + + assert changed == [doc_path] + assert ( + doc_path.read_text(encoding="utf-8") + == "[config](https://github.com/PaddlePaddle/PaddleOCR/blob/release/3.5/deploy/config.yaml)\n" + ) diff --git a/tests/tools/test_program_safe_yaml.py b/tests/tools/test_program_safe_yaml.py new file mode 100644 index 0000000..6adf3e4 --- /dev/null +++ b/tests/tools/test_program_safe_yaml.py @@ -0,0 +1,95 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib.util +import sys +import types +from pathlib import Path + +import pytest +import yaml + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +def _stub_module(monkeypatch, name, **attrs): + module = types.ModuleType(name) + for key, value in attrs.items(): + setattr(module, key, value) + monkeypatch.setitem(sys.modules, name, module) + return module + + +def _load_tools_program_module(monkeypatch): + _stub_module(monkeypatch, "paddle") + _stub_module(monkeypatch, "paddle.distributed") + _stub_module(monkeypatch, "cv2") + _stub_module(monkeypatch, "numpy") + _stub_module(monkeypatch, "tqdm", tqdm=lambda *args, **kwargs: None) + _stub_module(monkeypatch, "ppocr") + _stub_module(monkeypatch, "ppocr.utils") + _stub_module(monkeypatch, "ppocr.utils.stats", TrainingStats=object) + _stub_module( + monkeypatch, "ppocr.utils.save_load", save_model=lambda *args, **kwargs: None + ) + _stub_module( + monkeypatch, + "ppocr.utils.utility", + print_dict=lambda *args, **kwargs: None, + AverageMeter=object, + ) + _stub_module( + monkeypatch, "ppocr.utils.logging", get_logger=lambda *args, **kwargs: None + ) + _stub_module(monkeypatch, "ppocr.utils.loggers", WandbLogger=object, Loggers=object) + _stub_module(monkeypatch, "ppocr.utils.profiler") + _stub_module( + monkeypatch, "ppocr.data", build_dataloader=lambda *args, **kwargs: None + ) + _stub_module( + monkeypatch, "ppocr.utils.export_model", export=lambda *args, **kwargs: None + ) + + module_name = "paddleocr_tools_program" + spec = importlib.util.spec_from_file_location( + module_name, REPO_ROOT / "tools" / "program.py" + ) + module = importlib.util.module_from_spec(spec) + monkeypatch.setitem(sys.modules, module_name, module) + spec.loader.exec_module(module) + return module + + +def test_tools_program_load_config_rejects_python_object_tags(tmp_path, monkeypatch): + module = _load_tools_program_module(monkeypatch) + payload = ( + '!!python/object/apply:os.system ["echo SHOULD_NOT_RUN > ' + '/tmp/paddleocr_tools_program_test"]\n' + ) + config_path = tmp_path / "malicious.yml" + config_path.write_text(payload, encoding="utf-8") + + with pytest.raises(yaml.constructor.ConstructorError): + module.load_config(str(config_path)) + + +def test_tools_program_parse_opt_rejects_python_object_tags(monkeypatch): + parser = _load_tools_program_module(monkeypatch).ArgsParser() + malicious_opt = [ + 'Global.debug=!!python/object/apply:os.system ["echo SHOULD_NOT_RUN > ' + '/tmp/paddleocr_opt_test"]' + ] + + with pytest.raises(yaml.constructor.ConstructorError): + parser._parse_opt(malicious_opt) diff --git a/tests/unit/test_patch_layout_parsing.py b/tests/unit/test_patch_layout_parsing.py new file mode 100644 index 0000000..cfd383b --- /dev/null +++ b/tests/unit/test_patch_layout_parsing.py @@ -0,0 +1,151 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for the layout parsing patches (issue #17503). + +These tests verify the fixed functions directly without importing the +full paddleocr package (which requires paddlex). +""" + +from pathlib import Path + +import importlib.util +import numpy as np +import pytest + +REPO_ROOT = Path(__file__).resolve().parents[2] + + +# Import the patch module directly to avoid triggering the full paddleocr +# import chain which requires paddlex +def _import_patch_module(): + """Import _patch_layout_parsing without triggering paddleocr.__init__.""" + spec = importlib.util.spec_from_file_location( + "paddleocr._pipelines._patch_layout_parsing", + REPO_ROOT / "paddleocr" / "_pipelines" / "_patch_layout_parsing.py", + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + +_patch_mod = _import_patch_module() +_fixed_calculate_overlap_ratio = _patch_mod._fixed_calculate_overlap_ratio +_fixed_calculate_minimum_enclosing_bbox = ( + _patch_mod._fixed_calculate_minimum_enclosing_bbox +) + + +class TestFixedCalculateOverlapRatio: + """Tests for the overflow-safe calculate_overlap_ratio.""" + + def test_normal_overlap(self): + bbox1 = [0, 0, 100, 100] + bbox2 = [50, 50, 150, 150] + ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="union") + # Intersection: 50x50 = 2500 + # Union: 10000 + 10000 - 2500 = 17500 + expected = 2500.0 / 17500.0 + assert abs(ratio - expected) < 1e-6 + + def test_no_overlap(self): + bbox1 = [0, 0, 50, 50] + bbox2 = [100, 100, 200, 200] + ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="union") + assert ratio == 0.0 + + def test_complete_overlap(self): + bbox1 = [0, 0, 100, 100] + bbox2 = [0, 0, 100, 100] + ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="union") + assert abs(ratio - 1.0) < 1e-6 + + def test_large_coordinates_no_overflow(self): + """Verify no integer overflow with large coordinate values. + + This is the primary bug from issue #17503. With int32 arithmetic, + inter_width * inter_height would overflow for large images + (e.g. after document unwarping). + """ + # Coordinates large enough to cause int32 overflow when multiplied + bbox1 = np.array([0, 0, 50000, 50000], dtype=np.int32) + bbox2 = np.array([0, 0, 50000, 50000], dtype=np.int32) + ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="union") + # Should be 1.0 (identical boxes), not corrupted by overflow + assert abs(ratio - 1.0) < 1e-6 + + def test_large_coordinates_partial_overlap(self): + """Verify correct overlap ratio with large coordinates.""" + bbox1 = np.array([0, 0, 60000, 60000], dtype=np.int32) + bbox2 = np.array([30000, 30000, 90000, 90000], dtype=np.int32) + ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="small") + # Intersection: 30000x30000 = 900_000_000 + # Small box area: 60000*60000 = 3_600_000_000 + expected = 900_000_000.0 / 3_600_000_000.0 + assert abs(ratio - expected) < 1e-6 + + def test_mode_small(self): + bbox1 = [0, 0, 100, 100] + bbox2 = [0, 0, 200, 200] + ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="small") + # Intersection = 100*100 = 10000, small area = 10000 + assert abs(ratio - 1.0) < 1e-6 + + def test_mode_large(self): + bbox1 = [0, 0, 100, 100] + bbox2 = [0, 0, 200, 200] + ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="large") + # Intersection = 100*100 = 10000, large area = 40000 + expected = 10000.0 / 40000.0 + assert abs(ratio - expected) < 1e-6 + + def test_zero_area_bbox(self): + bbox1 = [0, 0, 0, 0] + bbox2 = [0, 0, 100, 100] + ratio = _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="union") + assert ratio == 0.0 + + def test_invalid_mode_raises(self): + bbox1 = [0, 0, 100, 100] + bbox2 = [50, 50, 150, 150] + with pytest.raises(ValueError, match="Invalid mode"): + _fixed_calculate_overlap_ratio(bbox1, bbox2, mode="invalid") + + +class TestFixedCalculateMinimumEnclosingBbox: + """Tests for the empty-safe calculate_minimum_enclosing_bbox.""" + + def test_single_bbox(self): + result = _fixed_calculate_minimum_enclosing_bbox([[10, 20, 30, 40]]) + np.testing.assert_array_equal(result, [10, 20, 30, 40]) + + def test_multiple_bboxes(self): + bboxes = [[10, 20, 30, 40], [5, 15, 35, 45]] + result = _fixed_calculate_minimum_enclosing_bbox(bboxes) + np.testing.assert_array_equal(result, [5, 15, 35, 45]) + + def test_empty_list_returns_degenerate_bbox(self): + """Verify empty list returns a degenerate bbox instead of raising. + + This is the secondary fix from issue #17503. The original code + raises ValueError("The list of bounding boxes is empty."). + """ + result = _fixed_calculate_minimum_enclosing_bbox([]) + assert result is not None + np.testing.assert_array_equal(result, [0, 0, 0, 0]) + + def test_none_input_returns_degenerate_bbox(self): + result = _fixed_calculate_minimum_enclosing_bbox(None) + assert result is not None + np.testing.assert_array_equal(result, [0, 0, 0, 0]) diff --git a/tests/utils/test_naming.py b/tests/utils/test_naming.py new file mode 100644 index 0000000..7a2d225 --- /dev/null +++ b/tests/utils/test_naming.py @@ -0,0 +1,33 @@ +# Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from paddleocr._utils.naming import snake_keys_to_camel, snake_to_camel + + +def test_snake_to_camel(): + assert snake_to_camel("visualize") == "visualize" + assert snake_to_camel("top_p") == "topP" + assert snake_to_camel("use_e2e_wired_table_rec_model") == "useE2eWiredTableRecModel" + assert ( + snake_to_camel("use_ocr_results_with_table_cells") + == "useOcrResultsWithTableCells" + ) + assert snake_to_camel("") == "" + + +def test_snake_keys_to_camel(): + assert snake_keys_to_camel({"top_p": 1, "visualize": True}) == { + "topP": 1, + "visualize": True, + } diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000..d56c9db --- /dev/null +++ b/tools/__init__.py @@ -0,0 +1,14 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# Copyright 2018 The Google AI Language Team Authors and The HuggingFace Inc. team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tools/check_docs_github_links.py b/tools/check_docs_github_links.py new file mode 100644 index 0000000..306ef0a --- /dev/null +++ b/tools/check_docs_github_links.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +import argparse +import re +from pathlib import Path +from typing import NamedTuple + + +class Violation(NamedTuple): + path: Path + line_number: int + ref: str + url: str + + +def _compile_link_pattern(repo_slug): + escaped_repo = re.escape(repo_slug) + return re.compile( + rf"https://github\.com/{escaped_repo}/(?:blob|tree)/(?P[^/\s)\]\"'<>]+)" + rf"(?P/[^\s)\]\"'<>]*)?" + ) + + +def find_forbidden_links(root, repo_slug, forbidden_refs): + root = Path(root) + forbidden_refs = set(forbidden_refs) + link_pattern = _compile_link_pattern(repo_slug) + violations = [] + + for path in sorted(root.rglob("*.md")): + if not path.is_file(): + continue + for line_number, line in enumerate( + path.read_text(encoding="utf-8").splitlines(), 1 + ): + for match in link_pattern.finditer(line): + ref = match.group("ref") + if ref in forbidden_refs: + violations.append( + Violation( + path=path, + line_number=line_number, + ref=ref, + url=match.group(0), + ) + ) + return violations + + +def main(argv=None): + parser = argparse.ArgumentParser( + description="Reject docs links that point to moving GitHub source refs." + ) + parser.add_argument("--root", default="docs", help="Directory to scan.") + parser.add_argument( + "--repo-slug", required=True, help="Example: PaddlePaddle/PaddleOCR" + ) + parser.add_argument( + "--forbidden-ref", + action="append", + required=True, + help="Moving source ref to reject. Can be passed multiple times.", + ) + args = parser.parse_args(argv) + + violations = find_forbidden_links( + args.root, + repo_slug=args.repo_slug, + forbidden_refs=set(args.forbidden_ref), + ) + if violations: + for violation in violations: + print( + f"{violation.path}:{violation.line_number}: " + f"forbidden GitHub ref '{violation.ref}' in {violation.url}" + ) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/end2end/convert_ppocr_label.py b/tools/end2end/convert_ppocr_label.py new file mode 100644 index 0000000..08cea77 --- /dev/null +++ b/tools/end2end/convert_ppocr_label.py @@ -0,0 +1,103 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import numpy as np +import json +import os + + +def poly_to_string(poly): + if len(poly.shape) > 1: + poly = np.array(poly).flatten() + + string = "\t".join(str(i) for i in poly) + return string + + +def convert_label(label_dir, mode="gt", save_dir="./save_results/"): + if not os.path.exists(label_dir): + raise ValueError(f"The file {label_dir} does not exist!") + + assert label_dir != save_dir, "hahahhaha" + + label_file = open(label_dir, "r") + data = label_file.readlines() + + gt_dict = {} + + for line in data: + try: + tmp = line.split("\t") + assert len(tmp) == 2, "" + except: + tmp = line.strip().split(" ") + + gt_lists = [] + + if tmp[0].split("/")[0] is not None: + img_path = tmp[0] + anno = json.loads(tmp[1]) + gt_collect = [] + for dic in anno: + # txt = dic['transcription'].replace(' ', '') # ignore blank + txt = dic["transcription"] + if "score" in dic and float(dic["score"]) < 0.5: + continue + if "\u3000" in txt: + txt = txt.replace("\u3000", " ") + # while ' ' in txt: + # txt = txt.replace(' ', '') + poly = np.array(dic["points"]).flatten() + if txt == "###": + txt_tag = 1 ## ignore 1 + else: + txt_tag = 0 + if mode == "gt": + gt_label = ( + poly_to_string(poly) + "\t" + str(txt_tag) + "\t" + txt + "\n" + ) + else: + gt_label = poly_to_string(poly) + "\t" + txt + "\n" + + gt_lists.append(gt_label) + + gt_dict[img_path] = gt_lists + else: + continue + + if not os.path.exists(save_dir): + os.makedirs(save_dir) + + for img_name in gt_dict.keys(): + save_name = img_name.split("/")[-1] + save_file = os.path.join(save_dir, save_name + ".txt") + with open(save_file, "w") as f: + f.writelines(gt_dict[img_name]) + + print("The convert label saved in {}".format(save_dir)) + + +def parse_args(): + import argparse + + parser = argparse.ArgumentParser(description="args") + parser.add_argument("--label_path", type=str, required=True) + parser.add_argument("--save_folder", type=str, required=True) + parser.add_argument("--mode", type=str, default=False) + args = parser.parse_args() + return args + + +if __name__ == "__main__": + args = parse_args() + convert_label(args.label_path, args.mode, args.save_folder) diff --git a/tools/end2end/draw_html.py b/tools/end2end/draw_html.py new file mode 100644 index 0000000..c894f44 --- /dev/null +++ b/tools/end2end/draw_html.py @@ -0,0 +1,72 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import argparse + + +def str2bool(v): + return v.lower() in ("true", "t", "1") + + +def init_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--image_dir", type=str, default="") + parser.add_argument("--save_html_path", type=str, default="./default.html") + parser.add_argument("--width", type=int, default=640) + return parser + + +def parse_args(): + parser = init_args() + return parser.parse_args() + + +def draw_debug_img(args): + html_path = args.save_html_path + + err_cnt = 0 + with open(html_path, "w") as html: + html.write("\n\n") + html.write('\n') + html.write( + '' + ) + image_list = [] + path = args.image_dir + for i, filename in enumerate(sorted(os.listdir(path))): + if filename.endswith("txt"): + continue + # The image path + base = "{}/{}".format(path, filename) + html.write("\n") + html.write(f"') + + html.write("\n") + html.write("\n") + html.write("
    {filename}\n GT") + html.write(f'GT\n
    \n") + html.write("\n\n") + print(f"The html file saved in {html_path}") + return + + +if __name__ == "__main__": + args = parse_args() + + draw_debug_img(args) diff --git a/tools/end2end/eval_end2end.py b/tools/end2end/eval_end2end.py new file mode 100644 index 0000000..3795c3c --- /dev/null +++ b/tools/end2end/eval_end2end.py @@ -0,0 +1,191 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re +import sys +import shapely +from shapely.geometry import Polygon +import numpy as np +from collections import defaultdict +import operator +import editdistance + + +def strQ2B(ustring): + rstring = "" + for uchar in ustring: + inside_code = ord(uchar) + if inside_code == 12288: + inside_code = 32 + elif inside_code >= 65281 and inside_code <= 65374: + inside_code -= 65248 + rstring += chr(inside_code) + return rstring + + +def polygon_from_str(polygon_points): + """ + Create a shapely polygon object from gt or dt line. + """ + polygon_points = np.array(polygon_points).reshape(4, 2) + polygon = Polygon(polygon_points).convex_hull + return polygon + + +def polygon_iou(poly1, poly2): + """ + Intersection over union between two shapely polygons. + """ + if not poly1.intersects(poly2): # this test is fast and can accelerate calculation + iou = 0 + else: + try: + inter_area = poly1.intersection(poly2).area + union_area = poly1.area + poly2.area - inter_area + iou = float(inter_area) / union_area + except shapely.geos.TopologicalError: + # except Exception as e: + # print(e) + print("shapely.geos.TopologicalError occurred, iou set to 0") + iou = 0 + return iou + + +def ed(str1, str2): + return editdistance.eval(str1, str2) + + +def e2e_eval(gt_dir, res_dir, ignore_blank=False): + print("start testing...") + iou_thresh = 0.5 + val_names = os.listdir(gt_dir) + num_gt_chars = 0 + gt_count = 0 + dt_count = 0 + hit = 0 + ed_sum = 0 + + for i, val_name in enumerate(val_names): + with open(os.path.join(gt_dir, val_name), encoding="utf-8") as f: + gt_lines = [o.strip() for o in f.readlines()] + gts = [] + ignore_masks = [] + for line in gt_lines: + parts = line.strip().split("\t") + # ignore illegal data + if len(parts) < 9: + continue + assert len(parts) < 11 + if len(parts) == 9: + gts.append(parts[:8] + [""]) + else: + gts.append(parts[:8] + [parts[-1]]) + + ignore_masks.append(parts[8]) + + val_path = os.path.join(res_dir, val_name) + if not os.path.exists(val_path): + dt_lines = [] + else: + with open(val_path, encoding="utf-8") as f: + dt_lines = [o.strip() for o in f.readlines()] + dts = [] + for line in dt_lines: + # print(line) + parts = line.strip().split("\t") + assert len(parts) < 10, "line error: {}".format(line) + if len(parts) == 8: + dts.append(parts + [""]) + else: + dts.append(parts) + + dt_match = [False] * len(dts) + gt_match = [False] * len(gts) + all_ious = defaultdict(tuple) + for index_gt, gt in enumerate(gts): + gt_coors = [float(gt_coor) for gt_coor in gt[0:8]] + gt_poly = polygon_from_str(gt_coors) + for index_dt, dt in enumerate(dts): + dt_coors = [float(dt_coor) for dt_coor in dt[0:8]] + dt_poly = polygon_from_str(dt_coors) + iou = polygon_iou(dt_poly, gt_poly) + if iou >= iou_thresh: + all_ious[(index_gt, index_dt)] = iou + sorted_ious = sorted(all_ious.items(), key=operator.itemgetter(1), reverse=True) + sorted_gt_dt_pairs = [item[0] for item in sorted_ious] + + # matched gt and dt + for gt_dt_pair in sorted_gt_dt_pairs: + index_gt, index_dt = gt_dt_pair + if gt_match[index_gt] == False and dt_match[index_dt] == False: + gt_match[index_gt] = True + dt_match[index_dt] = True + if ignore_blank: + gt_str = strQ2B(gts[index_gt][8]).replace(" ", "") + dt_str = strQ2B(dts[index_dt][8]).replace(" ", "") + else: + gt_str = strQ2B(gts[index_gt][8]) + dt_str = strQ2B(dts[index_dt][8]) + if ignore_masks[index_gt] == "0": + ed_sum += ed(gt_str, dt_str) + num_gt_chars += len(gt_str) + if gt_str == dt_str: + hit += 1 + gt_count += 1 + dt_count += 1 + + # unmatched dt + for tindex, dt_match_flag in enumerate(dt_match): + if dt_match_flag == False: + dt_str = dts[tindex][8] + gt_str = "" + ed_sum += ed(dt_str, gt_str) + dt_count += 1 + + # unmatched gt + for tindex, gt_match_flag in enumerate(gt_match): + if gt_match_flag == False and ignore_masks[tindex] == "0": + dt_str = "" + gt_str = gts[tindex][8] + ed_sum += ed(gt_str, dt_str) + num_gt_chars += len(gt_str) + gt_count += 1 + + eps = 1e-9 + print("hit, dt_count, gt_count", hit, dt_count, gt_count) + precision = hit / (dt_count + eps) + recall = hit / (gt_count + eps) + fmeasure = 2.0 * precision * recall / (precision + recall + eps) + avg_edit_dist_img = ed_sum / len(val_names) + avg_edit_dist_field = ed_sum / (gt_count + eps) + character_acc = 1 - ed_sum / (num_gt_chars + eps) + + print("character_acc: %.2f" % (character_acc * 100) + "%") + print("avg_edit_dist_field: %.2f" % (avg_edit_dist_field)) + print("avg_edit_dist_img: %.2f" % (avg_edit_dist_img)) + print("precision: %.2f" % (precision * 100) + "%") + print("recall: %.2f" % (recall * 100) + "%") + print("fmeasure: %.2f" % (fmeasure * 100) + "%") + + +if __name__ == "__main__": + # if len(sys.argv) != 3: + # print("python3 ocr_e2e_eval.py gt_dir res_dir") + # exit(-1) + # gt_folder = sys.argv[1] + # pred_folder = sys.argv[2] + gt_folder = sys.argv[1] + pred_folder = sys.argv[2] + e2e_eval(gt_folder, pred_folder) diff --git a/tools/end2end/readme.md b/tools/end2end/readme.md new file mode 100644 index 0000000..636ee76 --- /dev/null +++ b/tools/end2end/readme.md @@ -0,0 +1,63 @@ + +# 简介 + +`tools/end2end`目录下存放了文本检测+文本识别pipeline串联预测的指标评测代码以及可视化工具。本节介绍文本检测+文本识别的端对端指标评估方式。 + + +## 端对端评测步骤 + +**步骤一:** + +运行`tools/infer/predict_system.py`,得到保存的结果: + +``` +python3 tools/infer/predict_system.py --det_model_dir=./ch_PP-OCRv2_det_infer/ --rec_model_dir=./ch_PP-OCRv2_rec_infer/ --image_dir=./datasets/img_dir/ --draw_img_save_dir=./ch_PP-OCRv2_results/ --is_visualize=True +``` + +文本检测识别可视化图默认保存在`./ch_PP-OCRv2_results/`目录下,预测结果默认保存在`./ch_PP-OCRv2_results/system_results.txt`中,格式如下: +``` +all-sum-510/00224225.jpg [{"transcription": "超赞", "points": [[8.0, 48.0], [157.0, 44.0], [159.0, 115.0], [10.0, 119.0]], "score": "0.99396634"}, {"transcription": "中", "points": [[202.0, 152.0], [230.0, 152.0], [230.0, 163.0], [202.0, 163.0]], "score": "0.09310734"}, {"transcription": "58.0m", "points": [[196.0, 192.0], [444.0, 192.0], [444.0, 240.0], [196.0, 240.0]], "score": "0.44041982"}, {"transcription": "汽配", "points": [[55.0, 263.0], [95.0, 263.0], [95.0, 281.0], [55.0, 281.0]], "score": "0.9986651"}, {"transcription": "成总店", "points": [[120.0, 262.0], [176.0, 262.0], [176.0, 283.0], [120.0, 283.0]], "score": "0.9929402"}, {"transcription": "K", "points": [[237.0, 286.0], [311.0, 286.0], [311.0, 345.0], [237.0, 345.0]], "score": "0.6074794"}, {"transcription": "88:-8", "points": [[203.0, 405.0], [477.0, 414.0], [475.0, 459.0], [201.0, 450.0]], "score": "0.7106863"}] +``` + + +**步骤二:** + +将步骤一保存的数据转换为端对端评测需要的数据格式: + +修改 `tools/end2end/convert_ppocr_label.py`中的代码,convert_label函数中设置输入标签路径,Mode,保存标签路径等,对预测数据的GTlabel和预测结果的label格式进行转换。 + +``` +python3 tools/end2end/convert_ppocr_label.py --mode=gt --label_path=path/to/label_txt --save_folder=save_gt_label + +python3 tools/end2end/convert_ppocr_label.py --mode=pred --label_path=path/to/pred_txt --save_folder=save_PPOCRV2_infer +``` + +得到如下结果: +``` +├── ./save_gt_label/ +├── ./save_PPOCRV2_infer/ +``` + +**步骤三:** + +执行端对端评测,运行`tools/eval_end2end.py`计算端对端指标,运行方式如下: + +``` +python3 tools/eval_end2end.py "gt_label_dir" "predict_label_dir" +``` + +比如: + +``` +python3 tools/eval_end2end.py ./save_gt_label/ ./save_PPOCRV2_infer/ +``` +将得到如下结果,fmeasure为主要关注的指标: +``` +hit, dt_count, gt_count 1557 2693 3283 +character_acc: 61.77% +avg_edit_dist_field: 3.08 +avg_edit_dist_img: 51.82 +precision: 57.82% +recall: 47.43% +fmeasure: 52.11% +``` diff --git a/tools/eval.py b/tools/eval.py new file mode 100755 index 0000000..4a59f2c --- /dev/null +++ b/tools/eval.py @@ -0,0 +1,181 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, __dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +import paddle +from ppocr.data import build_dataloader, set_signal_handlers +from ppocr.modeling.architectures import build_model +from ppocr.postprocess import build_post_process +from ppocr.metrics import build_metric +from ppocr.utils.save_load import load_model +import tools.program as program + + +def main(): + global_config = config["Global"] + # build dataloader + set_signal_handlers() + valid_dataloader = build_dataloader(config, "Eval", device, logger) + + # build post process + post_process_class = build_post_process(config["PostProcess"], global_config) + + # build model + # for rec algorithm + if hasattr(post_process_class, "character"): + char_num = len(getattr(post_process_class, "character")) + if config["Architecture"]["algorithm"] in [ + "Distillation", + ]: # distillation model + for key in config["Architecture"]["Models"]: + if ( + config["Architecture"]["Models"][key]["Head"]["name"] == "MultiHead" + ): # for multi head + out_channels_list = {} + if config["PostProcess"]["name"] == "DistillationSARLabelDecode": + char_num = char_num - 2 + if config["PostProcess"]["name"] == "DistillationNRTRLabelDecode": + char_num = char_num - 3 + out_channels_list["CTCLabelDecode"] = char_num + out_channels_list["SARLabelDecode"] = char_num + 2 + out_channels_list["NRTRLabelDecode"] = char_num + 3 + config["Architecture"]["Models"][key]["Head"][ + "out_channels_list" + ] = out_channels_list + else: + config["Architecture"]["Models"][key]["Head"][ + "out_channels" + ] = char_num + elif config["Architecture"]["Head"]["name"] == "MultiHead": # for multi head + out_channels_list = {} + if config["PostProcess"]["name"] == "SARLabelDecode": + char_num = char_num - 2 + if config["PostProcess"]["name"] == "NRTRLabelDecode": + char_num = char_num - 3 + out_channels_list["CTCLabelDecode"] = char_num + out_channels_list["SARLabelDecode"] = char_num + 2 + out_channels_list["NRTRLabelDecode"] = char_num + 3 + config["Architecture"]["Head"]["out_channels_list"] = out_channels_list + else: # base rec model + config["Architecture"]["Head"]["out_channels"] = char_num + + model = build_model(config["Architecture"]) + extra_input_models = [ + "SRN", + "NRTR", + "SAR", + "SEED", + "SVTR", + "SVTR_LCNet", + "VisionLAN", + "RobustScanner", + "SVTR_HGNet", + ] + extra_input = False + if config["Architecture"]["algorithm"] == "Distillation": + for key in config["Architecture"]["Models"]: + extra_input = ( + extra_input + or config["Architecture"]["Models"][key]["algorithm"] + in extra_input_models + ) + else: + extra_input = config["Architecture"]["algorithm"] in extra_input_models + if "model_type" in config["Architecture"].keys(): + if config["Architecture"]["algorithm"] == "CAN": + model_type = "can" + elif config["Architecture"]["algorithm"] == "LaTeXOCR": + model_type = "latexocr" + config["Metric"]["cal_bleu_score"] = True + elif config["Architecture"]["algorithm"] == "UniMERNet": + model_type = "unimernet" + config["Metric"]["cal_bleu_score"] = True + elif config["Architecture"]["algorithm"] in [ + "PP-FormulaNet-S", + "PP-FormulaNet-L", + "PP-FormulaNet_plus-S", + "PP-FormulaNet_plus-M", + "PP-FormulaNet_plus-L", + ]: + model_type = "pp_formulanet" + config["Metric"]["cal_bleu_score"] = True + else: + model_type = config["Architecture"]["model_type"] + else: + model_type = None + + # build metric + eval_class = build_metric(config["Metric"]) + # amp + use_amp = config["Global"].get("use_amp", False) + amp_level = config["Global"].get("amp_level", "O2") + amp_custom_black_list = config["Global"].get("amp_custom_black_list", []) + if use_amp: + AMP_RELATED_FLAGS_SETTING = { + "FLAGS_cudnn_batchnorm_spatial_persistent": 1, + } + paddle.set_flags(AMP_RELATED_FLAGS_SETTING) + scale_loss = config["Global"].get("scale_loss", 1.0) + use_dynamic_loss_scaling = config["Global"].get( + "use_dynamic_loss_scaling", False + ) + scaler = paddle.amp.GradScaler( + init_loss_scaling=scale_loss, + use_dynamic_loss_scaling=use_dynamic_loss_scaling, + ) + if amp_level == "O2": + model = paddle.amp.decorate( + models=model, level=amp_level, master_weight=True + ) + else: + scaler = None + + best_model_dict = load_model( + config, model, model_type=config["Architecture"]["model_type"] + ) + if len(best_model_dict): + logger.info("metric in ckpt ***************") + for k, v in best_model_dict.items(): + logger.info("{}:{}".format(k, v)) + + # start eval + metric = program.eval( + model, + valid_dataloader, + post_process_class, + eval_class, + model_type, + extra_input, + scaler, + amp_level, + amp_custom_black_list, + ) + logger.info("metric eval ***************") + for k, v in metric.items(): + logger.info("{}:{}".format(k, v)) + + +if __name__ == "__main__": + config, device, logger, vdl_writer = program.preprocess() + main() diff --git a/tools/export_center.py b/tools/export_center.py new file mode 100644 index 0000000..e79c2b8 --- /dev/null +++ b/tools/export_center.py @@ -0,0 +1,77 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys +import pickle + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.append(os.path.abspath(os.path.join(__dir__, ".."))) + +from ppocr.data import build_dataloader, set_signal_handlers +from ppocr.modeling.architectures import build_model +from ppocr.postprocess import build_post_process +from ppocr.utils.save_load import load_model +from ppocr.utils.utility import print_dict +import tools.program as program + + +def main(): + global_config = config["Global"] + # build dataloader + config["Eval"]["dataset"]["name"] = config["Train"]["dataset"]["name"] + config["Eval"]["dataset"]["data_dir"] = config["Train"]["dataset"]["data_dir"] + config["Eval"]["dataset"]["label_file_list"] = config["Train"]["dataset"][ + "label_file_list" + ] + set_signal_handlers() + eval_dataloader = build_dataloader(config, "Eval", device, logger) + + # build post process + post_process_class = build_post_process(config["PostProcess"], global_config) + + # build model + # for rec algorithm + if hasattr(post_process_class, "character"): + char_num = len(getattr(post_process_class, "character")) + config["Architecture"]["Head"]["out_channels"] = char_num + + # set return_features = True + config["Architecture"]["Head"]["return_feats"] = True + + model = build_model(config["Architecture"]) + + best_model_dict = load_model(config, model) + if len(best_model_dict): + logger.info("metric in ckpt ***************") + for k, v in best_model_dict.items(): + logger.info("{}:{}".format(k, v)) + + # get features from train data + char_center = program.get_center(model, eval_dataloader, post_process_class) + + # serialize to disk + with open("train_center.pkl", "wb") as f: + pickle.dump(char_center, f) + return + + +if __name__ == "__main__": + config, device, logger, vdl_writer = program.preprocess() + main() diff --git a/tools/export_model.py b/tools/export_model.py new file mode 100755 index 0000000..da1bcbb --- /dev/null +++ b/tools/export_model.py @@ -0,0 +1,37 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +import argparse + +from tools.program import load_config, merge_config, ArgsParser +from ppocr.utils.export_model import export + + +def main(): + FLAGS = ArgsParser().parse_args() + config = load_config(FLAGS.config) + config = merge_config(config, FLAGS.opt) + # export model + export(config) + + +if __name__ == "__main__": + main() diff --git a/tools/infer/predict_cls.py b/tools/infer/predict_cls.py new file mode 100755 index 0000000..043e645 --- /dev/null +++ b/tools/infer/predict_cls.py @@ -0,0 +1,167 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import copy +import numpy as np +import math +import time +import traceback + +import tools.infer.utility as utility +from ppocr.postprocess import build_post_process +from ppocr.utils.logging import get_logger +from ppocr.utils.utility import get_image_file_list, check_and_read + +logger = get_logger() + + +class TextClassifier(object): + def __init__(self, args): + if os.path.exists(f"{args.cls_model_dir}/inference.yml"): + model_config = utility.load_config(f"{args.cls_model_dir}/inference.yml") + model_name = model_config.get("Global", {}).get("model_name", "") + if model_name and model_name not in [ + "PP-LCNet_x1_0_textline_ori", + "PP-LCNet_x0_25_textline_ori", + ]: + raise ValueError( + f"{model_name} is not supported. Please check if the model is supported by the PaddleOCR wheel." + ) + + self.cls_image_shape = [int(v) for v in args.cls_image_shape.split(",")] + self.cls_batch_num = args.cls_batch_num + self.cls_thresh = args.cls_thresh + postprocess_params = { + "name": "ClsPostProcess", + "label_list": args.label_list, + } + self.postprocess_op = build_post_process(postprocess_params) + ( + self.predictor, + self.input_tensor, + self.output_tensors, + _, + ) = utility.create_predictor(args, "cls", logger) + self.use_onnx = args.use_onnx + + def resize_norm_img(self, img): + imgC, imgH, imgW = self.cls_image_shape + h = img.shape[0] + w = img.shape[1] + ratio = w / float(h) + if math.ceil(imgH * ratio) > imgW: + resized_w = imgW + else: + resized_w = int(math.ceil(imgH * ratio)) + resized_image = cv2.resize(img, (resized_w, imgH)) + resized_image = resized_image.astype("float32") + if self.cls_image_shape[0] == 1: + resized_image = resized_image / 255 + resized_image = resized_image[np.newaxis, :] + else: + resized_image = resized_image.transpose((2, 0, 1)) / 255 + resized_image -= 0.5 + resized_image /= 0.5 + padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) + padding_im[:, :, 0:resized_w] = resized_image + return padding_im + + def __call__(self, img_list): + img_list = copy.deepcopy(img_list) + img_num = len(img_list) + # Calculate the aspect ratio of all text bars + width_list = [] + for img in img_list: + width_list.append(img.shape[1] / float(img.shape[0])) + # Sorting can speed up the cls process + indices = np.argsort(np.array(width_list)) + + cls_res = [["", 0.0]] * img_num + batch_num = self.cls_batch_num + elapse = 0 + for beg_img_no in range(0, img_num, batch_num): + end_img_no = min(img_num, beg_img_no + batch_num) + norm_img_batch = [] + max_wh_ratio = 0 + starttime = time.time() + for ino in range(beg_img_no, end_img_no): + h, w = img_list[indices[ino]].shape[0:2] + wh_ratio = w * 1.0 / h + max_wh_ratio = max(max_wh_ratio, wh_ratio) + for ino in range(beg_img_no, end_img_no): + norm_img = self.resize_norm_img(img_list[indices[ino]]) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) + norm_img_batch = np.concatenate(norm_img_batch) + norm_img_batch = norm_img_batch.copy() + + if self.use_onnx: + input_dict = {} + input_dict[self.input_tensor.name] = norm_img_batch + outputs = self.predictor.run(self.output_tensors, input_dict) + prob_out = outputs[0] + else: + self.input_tensor.copy_from_cpu(norm_img_batch) + self.predictor.run() + prob_out = self.output_tensors[0].copy_to_cpu() + self.predictor.try_shrink_memory() + cls_result = self.postprocess_op(prob_out) + elapse += time.time() - starttime + for rno in range(len(cls_result)): + label, score = cls_result[rno] + cls_res[indices[beg_img_no + rno]] = [label, score] + if "180" in label and score > self.cls_thresh: + img_list[indices[beg_img_no + rno]] = cv2.rotate( + img_list[indices[beg_img_no + rno]], 1 + ) + return img_list, cls_res, elapse + + +def main(args): + image_file_list = get_image_file_list(args.image_dir) + text_classifier = TextClassifier(args) + valid_image_file_list = [] + img_list = [] + for image_file in image_file_list: + img, flag, _ = check_and_read(image_file) + if not flag: + img = cv2.imread(image_file) + if img is None: + logger.info("error in loading image:{}".format(image_file)) + continue + valid_image_file_list.append(image_file) + img_list.append(img) + try: + img_list, cls_res, predict_time = text_classifier(img_list) + except Exception as E: + logger.info(traceback.format_exc()) + logger.info(E) + exit() + for ino in range(len(img_list)): + logger.info( + "Predicts of {}:{}".format(valid_image_file_list[ino], cls_res[ino]) + ) + + +if __name__ == "__main__": + main(utility.parse_args()) diff --git a/tools/infer/predict_det.py b/tools/infer/predict_det.py new file mode 100755 index 0000000..3d9e59b --- /dev/null +++ b/tools/infer/predict_det.py @@ -0,0 +1,504 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import numpy as np +import time +import sys + +import tools.infer.utility as utility +from ppocr.utils.logging import get_logger +from ppocr.utils.utility import get_image_file_list, check_and_read +from ppocr.data import create_operators, transform +from ppocr.postprocess import build_post_process +import json + + +class TextDetector(object): + def __init__(self, args, logger=None): + if os.path.exists(f"{args.det_model_dir}/inference.yml"): + model_config = utility.load_config(f"{args.det_model_dir}/inference.yml") + model_name = model_config.get("Global", {}).get("model_name", "") + if model_name and model_name not in [ + "PP-OCRv5_mobile_det", + "PP-OCRv5_server_det", + "PP-OCRv6_tiny_det", + "PP-OCRv6_small_det", + "PP-OCRv6_medium_det", + ]: + raise ValueError( + f"{model_name} is not supported. Please check if the model is supported by the PaddleOCR wheel." + ) + + if logger is None: + logger = get_logger() + self.args = args + self.det_algorithm = args.det_algorithm + self.use_onnx = args.use_onnx + pre_process_list = [ + { + "DetResizeForTest": { + "limit_side_len": args.det_limit_side_len, + "limit_type": args.det_limit_type, + } + }, + { + "NormalizeImage": { + "std": [0.229, 0.224, 0.225], + "mean": [0.485, 0.456, 0.406], + "scale": "1./255.", + "order": "hwc", + } + }, + {"ToCHWImage": None}, + {"KeepKeys": {"keep_keys": ["image", "shape"]}}, + ] + postprocess_params = {} + if self.det_algorithm == "DB": + postprocess_params["name"] = "DBPostProcess" + postprocess_params["thresh"] = args.det_db_thresh + postprocess_params["box_thresh"] = args.det_db_box_thresh + postprocess_params["max_candidates"] = 1000 + postprocess_params["unclip_ratio"] = args.det_db_unclip_ratio + postprocess_params["use_dilation"] = args.use_dilation + postprocess_params["score_mode"] = args.det_db_score_mode + postprocess_params["box_type"] = args.det_box_type + elif self.det_algorithm == "DB++": + postprocess_params["name"] = "DBPostProcess" + postprocess_params["thresh"] = args.det_db_thresh + postprocess_params["box_thresh"] = args.det_db_box_thresh + postprocess_params["max_candidates"] = 1000 + postprocess_params["unclip_ratio"] = args.det_db_unclip_ratio + postprocess_params["use_dilation"] = args.use_dilation + postprocess_params["score_mode"] = args.det_db_score_mode + postprocess_params["box_type"] = args.det_box_type + pre_process_list[1] = { + "NormalizeImage": { + "std": [1.0, 1.0, 1.0], + "mean": [0.48109378172549, 0.45752457890196, 0.40787054090196], + "scale": "1./255.", + "order": "hwc", + } + } + elif self.det_algorithm == "EAST": + postprocess_params["name"] = "EASTPostProcess" + postprocess_params["score_thresh"] = args.det_east_score_thresh + postprocess_params["cover_thresh"] = args.det_east_cover_thresh + postprocess_params["nms_thresh"] = args.det_east_nms_thresh + elif self.det_algorithm == "SAST": + pre_process_list[0] = { + "DetResizeForTest": {"resize_long": args.det_limit_side_len} + } + postprocess_params["name"] = "SASTPostProcess" + postprocess_params["score_thresh"] = args.det_sast_score_thresh + postprocess_params["nms_thresh"] = args.det_sast_nms_thresh + + if args.det_box_type == "poly": + postprocess_params["sample_pts_num"] = 6 + postprocess_params["expand_scale"] = 1.2 + postprocess_params["shrink_ratio_of_width"] = 0.2 + else: + postprocess_params["sample_pts_num"] = 2 + postprocess_params["expand_scale"] = 1.0 + postprocess_params["shrink_ratio_of_width"] = 0.3 + + elif self.det_algorithm == "PSE": + postprocess_params["name"] = "PSEPostProcess" + postprocess_params["thresh"] = args.det_pse_thresh + postprocess_params["box_thresh"] = args.det_pse_box_thresh + postprocess_params["min_area"] = args.det_pse_min_area + postprocess_params["box_type"] = args.det_box_type + postprocess_params["scale"] = args.det_pse_scale + elif self.det_algorithm == "FCE": + pre_process_list[0] = {"DetResizeForTest": {"rescale_img": [1080, 736]}} + postprocess_params["name"] = "FCEPostProcess" + postprocess_params["scales"] = args.scales + postprocess_params["alpha"] = args.alpha + postprocess_params["beta"] = args.beta + postprocess_params["fourier_degree"] = args.fourier_degree + postprocess_params["box_type"] = args.det_box_type + elif self.det_algorithm == "CT": + pre_process_list[0] = {"ScaleAlignedShort": {"short_size": 640}} + postprocess_params["name"] = "CTPostProcess" + else: + logger.info("unknown det_algorithm:{}".format(self.det_algorithm)) + sys.exit(0) + + self.preprocess_op = create_operators(pre_process_list) + self.postprocess_op = build_post_process(postprocess_params) + ( + self.predictor, + self.input_tensor, + self.output_tensors, + self.config, + ) = utility.create_predictor(args, "det", logger) + + if self.use_onnx: + img_h, img_w = self.input_tensor.shape[2:] + if isinstance(img_h, str) or isinstance(img_w, str): + pass + elif img_h is not None and img_w is not None and img_h > 0 and img_w > 0: + pre_process_list[0] = { + "DetResizeForTest": {"image_shape": [img_h, img_w]} + } + self.preprocess_op = create_operators(pre_process_list) + + if args.benchmark: + import auto_log + + pid = os.getpid() + gpu_id = utility.get_infer_gpuid() + self.autolog = auto_log.AutoLogger( + model_name="det", + model_precision=args.precision, + batch_size=1, + data_shape="dynamic", + save_path=None, # not used if logger is not None + inference_config=self.config, + pids=pid, + process_name=None, + gpu_ids=gpu_id if args.use_gpu else None, + time_keys=["preprocess_time", "inference_time", "postprocess_time"], + warmup=2, + logger=logger, + ) + + def order_points_clockwise(self, pts): + rect = np.zeros((4, 2), dtype="float32") + s = pts.sum(axis=1) + rect[0] = pts[np.argmin(s)] + rect[2] = pts[np.argmax(s)] + tmp = np.delete(pts, (np.argmin(s), np.argmax(s)), axis=0) + diff = np.diff(np.array(tmp), axis=1) + rect[1] = tmp[np.argmin(diff)] + rect[3] = tmp[np.argmax(diff)] + return rect + + def pad_polygons(self, polygon, max_points): + padding_size = max_points - len(polygon) + if padding_size == 0: + return polygon + last_point = polygon[-1] + padding = np.repeat([last_point], padding_size, axis=0) + return np.vstack([polygon, padding]) + + def clip_det_res(self, points, img_height, img_width): + for pno in range(points.shape[0]): + points[pno, 0] = int(min(max(points[pno, 0], 0), img_width - 1)) + points[pno, 1] = int(min(max(points[pno, 1], 0), img_height - 1)) + return points + + def filter_tag_det_res(self, dt_boxes, image_shape): + img_height, img_width = image_shape[0:2] + dt_boxes_new = [] + for box in dt_boxes: + if type(box) is list: + box = np.array(box) + box = self.order_points_clockwise(box) + box = self.clip_det_res(box, img_height, img_width) + rect_width = int(np.linalg.norm(box[0] - box[1])) + rect_height = int(np.linalg.norm(box[0] - box[3])) + if rect_width <= 3 or rect_height <= 3: + continue + dt_boxes_new.append(box) + dt_boxes = np.array(dt_boxes_new) + return dt_boxes + + def filter_tag_det_res_only_clip(self, dt_boxes, image_shape): + img_height, img_width = image_shape[0:2] + dt_boxes_new = [] + for box in dt_boxes: + if type(box) is list: + box = np.array(box) + box = self.clip_det_res(box, img_height, img_width) + dt_boxes_new.append(box) + + if len(dt_boxes_new) > 0: + max_points = max(len(polygon) for polygon in dt_boxes_new) + dt_boxes_new = [ + self.pad_polygons(polygon, max_points) for polygon in dt_boxes_new + ] + + dt_boxes = np.array(dt_boxes_new) + return dt_boxes + + def predict(self, img): + ori_im = img.copy() + data = {"image": img} + + st = time.time() + + if self.args.benchmark: + self.autolog.times.start() + + data = transform(data, self.preprocess_op) + img, shape_list = data + if img is None: + return None, 0 + img = np.expand_dims(img, axis=0) + shape_list = np.expand_dims(shape_list, axis=0) + img = img.copy() + + if self.args.benchmark: + self.autolog.times.stamp() + if self.use_onnx: + input_dict = {} + input_dict[self.input_tensor.name] = img + outputs = self.predictor.run(self.output_tensors, input_dict) + else: + self.input_tensor.copy_from_cpu(img) + self.predictor.run() + outputs = [] + for output_tensor in self.output_tensors: + output = output_tensor.copy_to_cpu() + outputs.append(output) + if self.args.benchmark: + self.autolog.times.stamp() + + preds = {} + if self.det_algorithm == "EAST": + preds["f_geo"] = outputs[0] + preds["f_score"] = outputs[1] + elif self.det_algorithm == "SAST": + preds["f_border"] = outputs[0] + preds["f_score"] = outputs[1] + preds["f_tco"] = outputs[2] + preds["f_tvo"] = outputs[3] + elif self.det_algorithm in ["DB", "PSE", "DB++"]: + preds["maps"] = outputs[0] + elif self.det_algorithm == "FCE": + for i, output in enumerate(outputs): + preds["level_{}".format(i)] = output + elif self.det_algorithm == "CT": + preds["maps"] = outputs[0] + preds["score"] = outputs[1] + else: + raise NotImplementedError + + post_result = self.postprocess_op(preds, shape_list) + dt_boxes = post_result[0]["points"] + + if self.args.det_box_type == "poly": + dt_boxes = self.filter_tag_det_res_only_clip(dt_boxes, ori_im.shape) + else: + dt_boxes = self.filter_tag_det_res(dt_boxes, ori_im.shape) + + if self.args.benchmark: + self.autolog.times.end(stamp=True) + et = time.time() + return dt_boxes, et - st + + def __call__(self, img, use_slice=False): + # For image like poster with one side much greater than the other side, + # splitting recursively and processing with overlap to enhance performance. + MIN_BOUND_DISTANCE = 50 + dt_boxes = np.zeros((0, 4, 2), dtype=np.float32) + elapse = 0 + if ( + img.shape[0] / img.shape[1] > 2 + and img.shape[0] > self.args.det_limit_side_len + and use_slice + ): + start_h = 0 + end_h = 0 + while end_h <= img.shape[0]: + end_h = start_h + img.shape[1] * 3 // 4 + subimg = img[start_h:end_h, :] + if len(subimg) == 0: + break + sub_dt_boxes, sub_elapse = self.predict(subimg) + offset = start_h + # To prevent text blocks from being cut off, roll back a certain buffer area. + if ( + len(sub_dt_boxes) == 0 + or img.shape[1] - max([x[-1][1] for x in sub_dt_boxes]) + > MIN_BOUND_DISTANCE + ): + start_h = end_h + else: + sorted_indices = np.argsort(sub_dt_boxes[:, 2, 1]) + sub_dt_boxes = sub_dt_boxes[sorted_indices] + bottom_line = ( + 0 + if len(sub_dt_boxes) <= 1 + else int(np.max(sub_dt_boxes[:-1, 2, 1])) + ) + if bottom_line > 0: + start_h += bottom_line + sub_dt_boxes = sub_dt_boxes[ + sub_dt_boxes[:, 2, 1] <= bottom_line + ] + else: + start_h = end_h + if len(sub_dt_boxes) > 0: + if dt_boxes.shape[0] == 0: + dt_boxes = sub_dt_boxes + np.array( + [0, offset], dtype=np.float32 + ) + else: + dt_boxes = np.append( + dt_boxes, + sub_dt_boxes + np.array([0, offset], dtype=np.float32), + axis=0, + ) + elapse += sub_elapse + elif ( + img.shape[1] / img.shape[0] > 3 + and img.shape[1] > self.args.det_limit_side_len * 3 + and use_slice + ): + start_w = 0 + end_w = 0 + while end_w <= img.shape[1]: + end_w = start_w + img.shape[0] * 3 // 4 + subimg = img[:, start_w:end_w] + if len(subimg) == 0: + break + sub_dt_boxes, sub_elapse = self.predict(subimg) + offset = start_w + if ( + len(sub_dt_boxes) == 0 + or img.shape[0] - max([x[-1][0] for x in sub_dt_boxes]) + > MIN_BOUND_DISTANCE + ): + start_w = end_w + else: + sorted_indices = np.argsort(sub_dt_boxes[:, 2, 0]) + sub_dt_boxes = sub_dt_boxes[sorted_indices] + right_line = ( + 0 + if len(sub_dt_boxes) <= 1 + else int(np.max(sub_dt_boxes[:-1, 1, 0])) + ) + if right_line > 0: + start_w += right_line + sub_dt_boxes = sub_dt_boxes[sub_dt_boxes[:, 1, 0] <= right_line] + else: + start_w = end_w + if len(sub_dt_boxes) > 0: + if dt_boxes.shape[0] == 0: + dt_boxes = sub_dt_boxes + np.array( + [offset, 0], dtype=np.float32 + ) + else: + dt_boxes = np.append( + dt_boxes, + sub_dt_boxes + np.array([offset, 0], dtype=np.float32), + axis=0, + ) + elapse += sub_elapse + else: + dt_boxes, elapse = self.predict(img) + return dt_boxes, elapse + + +if __name__ == "__main__": + args = utility.parse_args() + image_file_list = get_image_file_list(args.image_dir) + total_time = 0 + draw_img_save_dir = args.draw_img_save_dir + os.makedirs(draw_img_save_dir, exist_ok=True) + + # logger + log_file = args.save_log_path + if os.path.isdir(args.save_log_path) or ( + not os.path.exists(args.save_log_path) and args.save_log_path.endswith("/") + ): + log_file = os.path.join(log_file, "benchmark_detection.log") + logger = get_logger(log_file=log_file) + + # create text detector + text_detector = TextDetector(args, logger) + + if args.warmup: + img = np.random.uniform(0, 255, [640, 640, 3]).astype(np.uint8) + for i in range(2): + res = text_detector(img) + + save_results = [] + for idx, image_file in enumerate(image_file_list): + img, flag_gif, flag_pdf = check_and_read(image_file) + if not flag_gif and not flag_pdf: + img = cv2.imread(image_file) + if not flag_pdf: + if img is None: + logger.debug("error in loading image:{}".format(image_file)) + continue + imgs = [img] + else: + page_num = args.page_num + if page_num > len(img) or page_num == 0: + page_num = len(img) + imgs = img[:page_num] + for index, img in enumerate(imgs): + st = time.time() + dt_boxes, _ = text_detector(img) + elapse = time.time() - st + total_time += elapse + if len(imgs) > 1: + save_pred = ( + os.path.basename(image_file) + + "_" + + str(index) + + "\t" + + str(json.dumps([x.tolist() for x in dt_boxes])) + + "\n" + ) + else: + save_pred = ( + os.path.basename(image_file) + + "\t" + + str(json.dumps([x.tolist() for x in dt_boxes])) + + "\n" + ) + save_results.append(save_pred) + logger.info(save_pred) + if len(imgs) > 1: + logger.info( + "{}_{} The predict time of {}: {}".format( + idx, index, image_file, elapse + ) + ) + else: + logger.info( + "{} The predict time of {}: {}".format(idx, image_file, elapse) + ) + + src_im = utility.draw_text_det_res(dt_boxes, img) + + if flag_gif: + save_file = image_file[:-3] + "png" + elif flag_pdf: + save_file = image_file.replace(".pdf", "_" + str(index) + ".png") + else: + save_file = image_file + img_path = os.path.join( + draw_img_save_dir, "det_res_{}".format(os.path.basename(save_file)) + ) + cv2.imwrite(img_path, src_im) + logger.info("The visualized image saved in {}".format(img_path)) + + with open(os.path.join(draw_img_save_dir, "det_results.txt"), "w") as f: + f.writelines(save_results) + f.close() + if args.benchmark: + text_detector.autolog.report() diff --git a/tools/infer/predict_e2e.py b/tools/infer/predict_e2e.py new file mode 100755 index 0000000..95eb792 --- /dev/null +++ b/tools/infer/predict_e2e.py @@ -0,0 +1,178 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import numpy as np +import time +import sys + +import tools.infer.utility as utility +from ppocr.utils.logging import get_logger +from ppocr.utils.utility import get_image_file_list, check_and_read +from ppocr.data import create_operators, transform +from ppocr.postprocess import build_post_process + +logger = get_logger() + + +class TextE2E(object): + def __init__(self, args): + if os.path.exists(f"{args.e2e_model_dir}/inference.yml"): + model_config = utility.load_config(f"{args.e2e_model_dir}/inference.yml") + model_name = model_config.get("Global", {}).get("model_name", "") + if model_name: + raise ValueError( + f"{model_name} is not supported. Please check if the model is supported by the PaddleOCR wheel." + ) + + self.args = args + self.e2e_algorithm = args.e2e_algorithm + self.use_onnx = args.use_onnx + pre_process_list = [ + {"E2EResizeForTest": {}}, + { + "NormalizeImage": { + "std": [0.229, 0.224, 0.225], + "mean": [0.485, 0.456, 0.406], + "scale": "1./255.", + "order": "hwc", + } + }, + {"ToCHWImage": None}, + {"KeepKeys": {"keep_keys": ["image", "shape"]}}, + ] + postprocess_params = {} + if self.e2e_algorithm == "PGNet": + pre_process_list[0] = { + "E2EResizeForTest": { + "max_side_len": args.e2e_limit_side_len, + "valid_set": "totaltext", + } + } + postprocess_params["name"] = "PGPostProcess" + postprocess_params["score_thresh"] = args.e2e_pgnet_score_thresh + postprocess_params["character_dict_path"] = args.e2e_char_dict_path + postprocess_params["valid_set"] = args.e2e_pgnet_valid_set + postprocess_params["mode"] = args.e2e_pgnet_mode + else: + logger.info("unknown e2e_algorithm:{}".format(self.e2e_algorithm)) + sys.exit(0) + + self.preprocess_op = create_operators(pre_process_list) + self.postprocess_op = build_post_process(postprocess_params) + ( + self.predictor, + self.input_tensor, + self.output_tensors, + _, + ) = utility.create_predictor( + args, "e2e", logger + ) # paddle.jit.load(args.det_model_dir) + # self.predictor.eval() + + def clip_det_res(self, points, img_height, img_width): + for pno in range(points.shape[0]): + points[pno, 0] = int(min(max(points[pno, 0], 0), img_width - 1)) + points[pno, 1] = int(min(max(points[pno, 1], 0), img_height - 1)) + return points + + def filter_tag_det_res_only_clip(self, dt_boxes, image_shape): + img_height, img_width = image_shape[0:2] + dt_boxes_new = [] + for box in dt_boxes: + box = self.clip_det_res(box, img_height, img_width) + dt_boxes_new.append(box) + dt_boxes = np.array(dt_boxes_new) + return dt_boxes + + def __call__(self, img): + ori_im = img.copy() + data = {"image": img} + data = transform(data, self.preprocess_op) + img, shape_list = data + if img is None: + return None, 0 + img = np.expand_dims(img, axis=0) + shape_list = np.expand_dims(shape_list, axis=0) + img = img.copy() + starttime = time.time() + + if self.use_onnx: + input_dict = {} + input_dict[self.input_tensor.name] = img + outputs = self.predictor.run(self.output_tensors, input_dict) + preds = {} + preds["f_border"] = outputs[0] + preds["f_char"] = outputs[1] + preds["f_direction"] = outputs[2] + preds["f_score"] = outputs[3] + else: + self.input_tensor.copy_from_cpu(img) + self.predictor.run() + outputs = [] + for output_tensor in self.output_tensors: + output = output_tensor.copy_to_cpu() + outputs.append(output) + + preds = {} + if self.e2e_algorithm == "PGNet": + preds["f_border"] = outputs[0] + preds["f_char"] = outputs[1] + preds["f_direction"] = outputs[2] + preds["f_score"] = outputs[3] + else: + raise NotImplementedError + post_result = self.postprocess_op(preds, shape_list) + points, strs = post_result["points"], post_result["texts"] + dt_boxes = self.filter_tag_det_res_only_clip(points, ori_im.shape) + elapse = time.time() - starttime + return dt_boxes, strs, elapse + + +if __name__ == "__main__": + args = utility.parse_args() + image_file_list = get_image_file_list(args.image_dir) + text_detector = TextE2E(args) + count = 0 + total_time = 0 + draw_img_save = "./inference_results" + if not os.path.exists(draw_img_save): + os.makedirs(draw_img_save) + for image_file in image_file_list: + img, flag, _ = check_and_read(image_file) + if not flag: + img = cv2.imread(image_file) + if img is None: + logger.info("error in loading image:{}".format(image_file)) + continue + points, strs, elapse = text_detector(img) + if count > 0: + total_time += elapse + count += 1 + logger.info("Predict time of {}: {}".format(image_file, elapse)) + src_im = utility.draw_e2e_res(points, strs, image_file) + img_name_pure = os.path.split(image_file)[-1] + img_path = os.path.join(draw_img_save, "e2e_res_{}".format(img_name_pure)) + cv2.imwrite(img_path, src_im) + logger.info("The visualized image saved in {}".format(img_path)) + if count > 1: + logger.info("Avg Time: {}".format(total_time / (count - 1))) diff --git a/tools/infer/predict_rec.py b/tools/infer/predict_rec.py new file mode 100755 index 0000000..b53f58d --- /dev/null +++ b/tools/infer/predict_rec.py @@ -0,0 +1,916 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import sys +from PIL import Image + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import numpy as np +import math +import time +import traceback +import paddle + +import tools.infer.utility as utility +from ppocr.postprocess import build_post_process +from ppocr.utils.logging import get_logger +from ppocr.utils.utility import get_image_file_list, check_and_read + +logger = get_logger() + + +class TextRecognizer(object): + def __init__(self, args, logger=None): + if os.path.exists(f"{args.rec_model_dir}/inference.yml"): + model_config = utility.load_config(f"{args.rec_model_dir}/inference.yml") + model_name = model_config.get("Global", {}).get("model_name", "") + if model_name and model_name not in [ + "PP-OCRv5_mobile_rec", + "PP-OCRv5_server_rec", + "korean_PP-OCRv5_mobile_rec", + "eslav_PP-OCRv5_mobile_rec", + "latin_PP-OCRv5_mobile_rec", + "en_PP-OCRv5_mobile_rec", + "th_PP-OCRv5_mobile_rec", + "el_PP-OCRv5_mobile_rec", + "PP-OCRv6_tiny_rec", + "PP-OCRv6_small_rec", + "PP-OCRv6_medium_rec", + ]: + raise ValueError( + f"{model_name} is not supported. Please check if the model is supported by the PaddleOCR wheel." + ) + + if args.rec_char_dict_path == "./ppocr/utils/ppocr_keys_v1.txt": + rec_char_list = model_config.get("PostProcess", {}).get( + "character_dict", [] + ) + if rec_char_list: + new_rec_char_dict_path = f"{args.rec_model_dir}/ppocr_keys.txt" + with open(new_rec_char_dict_path, "w", encoding="utf-8") as f: + f.writelines([char + "\n" for char in rec_char_list]) + args.rec_char_dict_path = new_rec_char_dict_path + + if logger is None: + logger = get_logger() + self.rec_image_shape = [int(v) for v in args.rec_image_shape.split(",")] + self.rec_batch_num = args.rec_batch_num + self.rec_algorithm = args.rec_algorithm + postprocess_params = { + "name": "CTCLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + } + if self.rec_algorithm == "SRN": + postprocess_params = { + "name": "SRNLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + } + elif self.rec_algorithm == "RARE": + postprocess_params = { + "name": "AttnLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + } + elif self.rec_algorithm == "NRTR": + postprocess_params = { + "name": "NRTRLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + } + elif self.rec_algorithm == "SAR": + postprocess_params = { + "name": "SARLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + } + elif self.rec_algorithm == "VisionLAN": + postprocess_params = { + "name": "VLLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + "max_text_length": args.max_text_length, + } + elif self.rec_algorithm == "ViTSTR": + postprocess_params = { + "name": "ViTSTRLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + } + elif self.rec_algorithm == "ABINet": + postprocess_params = { + "name": "ABINetLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + } + elif self.rec_algorithm == "SPIN": + postprocess_params = { + "name": "SPINLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + } + elif self.rec_algorithm == "RobustScanner": + postprocess_params = { + "name": "SARLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + "rm_symbol": True, + } + elif self.rec_algorithm == "RFL": + postprocess_params = { + "name": "RFLLabelDecode", + "character_dict_path": None, + "use_space_char": args.use_space_char, + } + elif self.rec_algorithm == "SATRN": + postprocess_params = { + "name": "SATRNLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + "rm_symbol": True, + } + elif self.rec_algorithm in ["CPPD", "CPPDPadding"]: + postprocess_params = { + "name": "CPPDLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + "rm_symbol": True, + } + elif self.rec_algorithm == "PREN": + postprocess_params = {"name": "PRENLabelDecode"} + elif self.rec_algorithm == "CAN": + self.inverse = args.rec_image_inverse + postprocess_params = { + "name": "CANLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + } + elif self.rec_algorithm == "LaTeXOCR": + postprocess_params = { + "name": "LaTeXOCRDecode", + "rec_char_dict_path": args.rec_char_dict_path, + } + elif self.rec_algorithm == "ParseQ": + postprocess_params = { + "name": "ParseQLabelDecode", + "character_dict_path": args.rec_char_dict_path, + "use_space_char": args.use_space_char, + } + self.postprocess_op = build_post_process(postprocess_params) + self.postprocess_params = postprocess_params + ( + self.predictor, + self.input_tensor, + self.output_tensors, + self.config, + ) = utility.create_predictor(args, "rec", logger) + self.benchmark = args.benchmark + self.use_onnx = args.use_onnx + if args.benchmark: + import auto_log + + pid = os.getpid() + gpu_id = utility.get_infer_gpuid() + self.autolog = auto_log.AutoLogger( + model_name="rec", + model_precision=args.precision, + batch_size=args.rec_batch_num, + data_shape="dynamic", + save_path=None, # not used if logger is not None + inference_config=self.config, + pids=pid, + process_name=None, + gpu_ids=gpu_id if args.use_gpu else None, + time_keys=["preprocess_time", "inference_time", "postprocess_time"], + warmup=0, + logger=logger, + ) + self.return_word_box = args.return_word_box + + def resize_norm_img(self, img, max_wh_ratio): + imgC, imgH, imgW = self.rec_image_shape + if self.rec_algorithm == "NRTR" or self.rec_algorithm == "ViTSTR": + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + # return padding_im + image_pil = Image.fromarray(np.uint8(img)) + if self.rec_algorithm == "ViTSTR": + img = image_pil.resize([imgW, imgH], Image.BICUBIC) + else: + img = image_pil.resize([imgW, imgH], Image.Resampling.LANCZOS) + img = np.array(img) + norm_img = np.expand_dims(img, -1) + norm_img = norm_img.transpose((2, 0, 1)) + if self.rec_algorithm == "ViTSTR": + norm_img = norm_img.astype(np.float32) / 255.0 + else: + norm_img = norm_img.astype(np.float32) / 128.0 - 1.0 + return norm_img + elif self.rec_algorithm == "RFL": + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + resized_image = cv2.resize(img, (imgW, imgH), interpolation=cv2.INTER_CUBIC) + resized_image = resized_image.astype("float32") + resized_image = resized_image / 255 + resized_image = resized_image[np.newaxis, :] + resized_image -= 0.5 + resized_image /= 0.5 + return resized_image + + assert imgC == img.shape[2] + imgW = int((imgH * max_wh_ratio)) + if self.use_onnx: + w = self.input_tensor.shape[3:][0] + if isinstance(w, str): + pass + elif w is not None and w > 0: + imgW = w + h, w = img.shape[:2] + ratio = w / float(h) + if math.ceil(imgH * ratio) > imgW: + resized_w = imgW + else: + resized_w = int(math.ceil(imgH * ratio)) + if self.rec_algorithm == "RARE": + if resized_w > self.rec_image_shape[2]: + resized_w = self.rec_image_shape[2] + imgW = self.rec_image_shape[2] + resized_image = cv2.resize(img, (resized_w, imgH)) + resized_image = resized_image.astype("float32") + resized_image = resized_image.transpose((2, 0, 1)) / 255 + resized_image -= 0.5 + resized_image /= 0.5 + padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) + padding_im[:, :, 0:resized_w] = resized_image + return padding_im + + def resize_norm_img_vl(self, img, image_shape): + imgC, imgH, imgW = image_shape + img = img[:, :, ::-1] # bgr2rgb + resized_image = cv2.resize(img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) + resized_image = resized_image.astype("float32") + resized_image = resized_image.transpose((2, 0, 1)) / 255 + return resized_image + + def resize_norm_img_srn(self, img, image_shape): + imgC, imgH, imgW = image_shape + + img_black = np.zeros((imgH, imgW)) + im_hei = img.shape[0] + im_wid = img.shape[1] + + if im_wid <= im_hei * 1: + img_new = cv2.resize(img, (imgH * 1, imgH)) + elif im_wid <= im_hei * 2: + img_new = cv2.resize(img, (imgH * 2, imgH)) + elif im_wid <= im_hei * 3: + img_new = cv2.resize(img, (imgH * 3, imgH)) + else: + img_new = cv2.resize(img, (imgW, imgH)) + + img_np = np.asarray(img_new) + img_np = cv2.cvtColor(img_np, cv2.COLOR_BGR2GRAY) + img_black[:, 0 : img_np.shape[1]] = img_np + img_black = img_black[:, :, np.newaxis] + + row, col, c = img_black.shape + c = 1 + + return np.reshape(img_black, (c, row, col)).astype(np.float32) + + def srn_other_inputs(self, image_shape, num_heads, max_text_length): + imgC, imgH, imgW = image_shape + feature_dim = int((imgH / 8) * (imgW / 8)) + + encoder_word_pos = ( + np.array(range(0, feature_dim)).reshape((feature_dim, 1)).astype("int64") + ) + gsrm_word_pos = ( + np.array(range(0, max_text_length)) + .reshape((max_text_length, 1)) + .astype("int64") + ) + + gsrm_attn_bias_data = np.ones((1, max_text_length, max_text_length)) + gsrm_slf_attn_bias1 = np.triu(gsrm_attn_bias_data, 1).reshape( + [-1, 1, max_text_length, max_text_length] + ) + gsrm_slf_attn_bias1 = np.tile(gsrm_slf_attn_bias1, [1, num_heads, 1, 1]).astype( + "float32" + ) * [-1e9] + + gsrm_slf_attn_bias2 = np.tril(gsrm_attn_bias_data, -1).reshape( + [-1, 1, max_text_length, max_text_length] + ) + gsrm_slf_attn_bias2 = np.tile(gsrm_slf_attn_bias2, [1, num_heads, 1, 1]).astype( + "float32" + ) * [-1e9] + + encoder_word_pos = encoder_word_pos[np.newaxis, :] + gsrm_word_pos = gsrm_word_pos[np.newaxis, :] + + return [ + encoder_word_pos, + gsrm_word_pos, + gsrm_slf_attn_bias1, + gsrm_slf_attn_bias2, + ] + + def process_image_srn(self, img, image_shape, num_heads, max_text_length): + norm_img = self.resize_norm_img_srn(img, image_shape) + norm_img = norm_img[np.newaxis, :] + + [ + encoder_word_pos, + gsrm_word_pos, + gsrm_slf_attn_bias1, + gsrm_slf_attn_bias2, + ] = self.srn_other_inputs(image_shape, num_heads, max_text_length) + + gsrm_slf_attn_bias1 = gsrm_slf_attn_bias1.astype(np.float32) + gsrm_slf_attn_bias2 = gsrm_slf_attn_bias2.astype(np.float32) + encoder_word_pos = encoder_word_pos.astype(np.int64) + gsrm_word_pos = gsrm_word_pos.astype(np.int64) + + return ( + norm_img, + encoder_word_pos, + gsrm_word_pos, + gsrm_slf_attn_bias1, + gsrm_slf_attn_bias2, + ) + + def resize_norm_img_sar(self, img, image_shape, width_downsample_ratio=0.25): + imgC, imgH, imgW_min, imgW_max = image_shape + h = img.shape[0] + w = img.shape[1] + valid_ratio = 1.0 + # make sure new_width is an integral multiple of width_divisor. + width_divisor = int(1 / width_downsample_ratio) + # resize + ratio = w / float(h) + resize_w = math.ceil(imgH * ratio) + if resize_w % width_divisor != 0: + resize_w = round(resize_w / width_divisor) * width_divisor + if imgW_min is not None: + resize_w = max(imgW_min, resize_w) + if imgW_max is not None: + valid_ratio = min(1.0, 1.0 * resize_w / imgW_max) + resize_w = min(imgW_max, resize_w) + resized_image = cv2.resize(img, (resize_w, imgH)) + resized_image = resized_image.astype("float32") + # norm + if image_shape[0] == 1: + resized_image = resized_image / 255 + resized_image = resized_image[np.newaxis, :] + else: + resized_image = resized_image.transpose((2, 0, 1)) / 255 + resized_image -= 0.5 + resized_image /= 0.5 + resize_shape = resized_image.shape + padding_im = -1.0 * np.ones((imgC, imgH, imgW_max), dtype=np.float32) + padding_im[:, :, 0:resize_w] = resized_image + pad_shape = padding_im.shape + + return padding_im, resize_shape, pad_shape, valid_ratio + + def resize_norm_img_spin(self, img): + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + # return padding_im + img = cv2.resize(img, tuple([100, 32]), cv2.INTER_CUBIC) + img = np.array(img, np.float32) + img = np.expand_dims(img, -1) + img = img.transpose((2, 0, 1)) + mean = [127.5] + std = [127.5] + mean = np.array(mean, dtype=np.float32) + std = np.array(std, dtype=np.float32) + mean = np.float32(mean.reshape(1, -1)) + stdinv = 1 / np.float32(std.reshape(1, -1)) + img -= mean + img *= stdinv + return img + + def resize_norm_img_svtr(self, img, image_shape): + imgC, imgH, imgW = image_shape + max_wh_ratio = imgW * 1.0 / imgH + h, w = img.shape[0], img.shape[1] + ratio = w * 1.0 / h + max_wh_ratio = min(max(max_wh_ratio, ratio), max_wh_ratio) + imgW = int(imgH * max_wh_ratio) + if math.ceil(imgH * ratio) > imgW: + resized_w = imgW + else: + resized_w = int(math.ceil(imgH * ratio)) + resized_image = cv2.resize(img, (resized_w, imgH)) + resized_image = resized_image.astype("float32") + resized_image = resized_image.transpose((2, 0, 1)) / 255 + resized_image -= 0.5 + resized_image /= 0.5 + padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) + padding_im[:, :, 0:resized_w] = resized_image + return padding_im + + def resize_norm_img_cppd_padding( + self, img, image_shape, padding=True, interpolation=cv2.INTER_LINEAR + ): + imgC, imgH, imgW = image_shape + h = img.shape[0] + w = img.shape[1] + if not padding: + resized_image = cv2.resize(img, (imgW, imgH), interpolation=interpolation) + resized_w = imgW + else: + ratio = w / float(h) + if math.ceil(imgH * ratio) > imgW: + resized_w = imgW + else: + resized_w = int(math.ceil(imgH * ratio)) + resized_image = cv2.resize(img, (resized_w, imgH)) + resized_image = resized_image.astype("float32") + if image_shape[0] == 1: + resized_image = resized_image / 255 + resized_image = resized_image[np.newaxis, :] + else: + resized_image = resized_image.transpose((2, 0, 1)) / 255 + resized_image -= 0.5 + resized_image /= 0.5 + padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32) + padding_im[:, :, 0:resized_w] = resized_image + + return padding_im + + def resize_norm_img_abinet(self, img, image_shape): + imgC, imgH, imgW = image_shape + + resized_image = cv2.resize(img, (imgW, imgH), interpolation=cv2.INTER_LINEAR) + resized_image = resized_image.astype("float32") + resized_image = resized_image / 255.0 + + mean = np.array([0.485, 0.456, 0.406]) + std = np.array([0.229, 0.224, 0.225]) + resized_image = (resized_image - mean[None, None, ...]) / std[None, None, ...] + resized_image = resized_image.transpose((2, 0, 1)) + resized_image = resized_image.astype("float32") + + return resized_image + + def norm_img_can(self, img, image_shape): + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # CAN only predict gray scale image + + if self.inverse: + img = 255 - img + + if self.rec_image_shape[0] == 1: + h, w = img.shape + _, imgH, imgW = self.rec_image_shape + if h < imgH or w < imgW: + padding_h = max(imgH - h, 0) + padding_w = max(imgW - w, 0) + img_padded = np.pad( + img, + ((0, padding_h), (0, padding_w)), + "constant", + constant_values=(255), + ) + img = img_padded + + img = np.expand_dims(img, 0) / 255.0 # h,w,c -> c,h,w + img = img.astype("float32") + + return img + + def pad_(self, img, divable=32): + threshold = 128 + data = np.array(img.convert("LA")) + if data[..., -1].var() == 0: + data = (data[..., 0]).astype(np.uint8) + else: + data = (255 - data[..., -1]).astype(np.uint8) + data = (data - data.min()) / (data.max() - data.min()) * 255 + if data.mean() > threshold: + # To invert the text to white + gray = 255 * (data < threshold).astype(np.uint8) + else: + gray = 255 * (data > threshold).astype(np.uint8) + data = 255 - data + + coords = cv2.findNonZero(gray) # Find all non-zero points (text) + a, b, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box + rect = data[b : b + h, a : a + w] + im = Image.fromarray(rect).convert("L") + dims = [] + for x in [w, h]: + div, mod = divmod(x, divable) + dims.append(divable * (div + (1 if mod > 0 else 0))) + padded = Image.new("L", dims, 255) + padded.paste(im, (0, 0, im.size[0], im.size[1])) + return padded + + def minmax_size_( + self, + img, + max_dimensions, + min_dimensions, + ): + if max_dimensions is not None: + ratios = [a / b for a, b in zip(img.size, max_dimensions)] + if any([r > 1 for r in ratios]): + size = np.array(img.size) // max(ratios) + img = img.resize(tuple(size.astype(int)), Image.BILINEAR) + if min_dimensions is not None: + # hypothesis: there is a dim in img smaller than min_dimensions, and return a proper dim >= min_dimensions + padded_size = [ + max(img_dim, min_dim) + for img_dim, min_dim in zip(img.size, min_dimensions) + ] + if padded_size != list(img.size): # assert hypothesis + padded_im = Image.new("L", padded_size, 255) + padded_im.paste(img, img.getbbox()) + img = padded_im + return img + + def norm_img_latexocr(self, img): + # CAN only predict gray scale image + shape = (1, 1, 3) + mean = [0.7931, 0.7931, 0.7931] + std = [0.1738, 0.1738, 0.1738] + scale = np.float32(1.0 / 255.0) + min_dimensions = [32, 32] + max_dimensions = [672, 192] + mean = np.array(mean).reshape(shape).astype("float32") + std = np.array(std).reshape(shape).astype("float32") + + im_h, im_w = img.shape[:2] + if ( + min_dimensions[0] <= im_w <= max_dimensions[0] + and min_dimensions[1] <= im_h <= max_dimensions[1] + ): + pass + else: + img = Image.fromarray(np.uint8(img)) + img = self.minmax_size_(self.pad_(img), max_dimensions, min_dimensions) + img = np.array(img) + im_h, im_w = img.shape[:2] + img = np.dstack([img, img, img]) + img = (img.astype("float32") * scale - mean) / std + img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + divide_h = math.ceil(im_h / 16) * 16 + divide_w = math.ceil(im_w / 16) * 16 + img = np.pad( + img, ((0, divide_h - im_h), (0, divide_w - im_w)), constant_values=(1, 1) + ) + img = img[:, :, np.newaxis].transpose(2, 0, 1) + img = img.astype("float32") + return img + + def __call__(self, img_list): + img_num = len(img_list) + # Calculate the aspect ratio of all text bars + width_list = [] + for img in img_list: + width_list.append(img.shape[1] / float(img.shape[0])) + # Sorting can speed up the recognition process + indices = np.argsort(np.array(width_list)) + rec_res = [["", 0.0]] * img_num + batch_num = self.rec_batch_num + st = time.time() + if self.benchmark: + self.autolog.times.start() + for beg_img_no in range(0, img_num, batch_num): + end_img_no = min(img_num, beg_img_no + batch_num) + norm_img_batch = [] + if self.rec_algorithm == "SRN": + encoder_word_pos_list = [] + gsrm_word_pos_list = [] + gsrm_slf_attn_bias1_list = [] + gsrm_slf_attn_bias2_list = [] + if self.rec_algorithm == "SAR": + valid_ratios = [] + imgC, imgH, imgW = self.rec_image_shape[:3] + max_wh_ratio = imgW / imgH + wh_ratio_list = [] + for ino in range(beg_img_no, end_img_no): + h, w = img_list[indices[ino]].shape[0:2] + wh_ratio = w * 1.0 / h + max_wh_ratio = max(max_wh_ratio, wh_ratio) + wh_ratio_list.append(wh_ratio) + for ino in range(beg_img_no, end_img_no): + if self.rec_algorithm == "SAR": + norm_img, _, _, valid_ratio = self.resize_norm_img_sar( + img_list[indices[ino]], self.rec_image_shape + ) + norm_img = norm_img[np.newaxis, :] + valid_ratio = np.expand_dims(valid_ratio, axis=0) + valid_ratios.append(valid_ratio) + norm_img_batch.append(norm_img) + elif self.rec_algorithm == "SRN": + norm_img = self.process_image_srn( + img_list[indices[ino]], self.rec_image_shape, 8, 25 + ) + encoder_word_pos_list.append(norm_img[1]) + gsrm_word_pos_list.append(norm_img[2]) + gsrm_slf_attn_bias1_list.append(norm_img[3]) + gsrm_slf_attn_bias2_list.append(norm_img[4]) + norm_img_batch.append(norm_img[0]) + elif self.rec_algorithm in ["SVTR", "SATRN", "ParseQ", "CPPD"]: + norm_img = self.resize_norm_img_svtr( + img_list[indices[ino]], self.rec_image_shape + ) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) + elif self.rec_algorithm in ["CPPDPadding"]: + norm_img = self.resize_norm_img_cppd_padding( + img_list[indices[ino]], self.rec_image_shape + ) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) + elif self.rec_algorithm in ["VisionLAN", "PREN"]: + norm_img = self.resize_norm_img_vl( + img_list[indices[ino]], self.rec_image_shape + ) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) + elif self.rec_algorithm == "SPIN": + norm_img = self.resize_norm_img_spin(img_list[indices[ino]]) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) + elif self.rec_algorithm == "ABINet": + norm_img = self.resize_norm_img_abinet( + img_list[indices[ino]], self.rec_image_shape + ) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) + elif self.rec_algorithm == "RobustScanner": + norm_img, _, _, valid_ratio = self.resize_norm_img_sar( + img_list[indices[ino]], + self.rec_image_shape, + width_downsample_ratio=0.25, + ) + norm_img = norm_img[np.newaxis, :] + valid_ratio = np.expand_dims(valid_ratio, axis=0) + valid_ratios = [] + valid_ratios.append(valid_ratio) + norm_img_batch.append(norm_img) + word_positions_list = [] + word_positions = np.array(range(0, 40)).astype("int64") + word_positions = np.expand_dims(word_positions, axis=0) + word_positions_list.append(word_positions) + elif self.rec_algorithm == "CAN": + norm_img = self.norm_img_can(img_list[indices[ino]], max_wh_ratio) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) + norm_image_mask = np.ones(norm_img.shape, dtype="float32") + word_label = np.ones([1, 36], dtype="int64") + norm_img_mask_batch = [] + word_label_list = [] + norm_img_mask_batch.append(norm_image_mask) + word_label_list.append(word_label) + elif self.rec_algorithm == "LaTeXOCR": + norm_img = self.norm_img_latexocr(img_list[indices[ino]]) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) + else: + norm_img = self.resize_norm_img( + img_list[indices[ino]], max_wh_ratio + ) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) + norm_img_batch = np.concatenate(norm_img_batch) + norm_img_batch = norm_img_batch.copy() + if self.benchmark: + self.autolog.times.stamp() + + if self.rec_algorithm == "SRN": + encoder_word_pos_list = np.concatenate(encoder_word_pos_list) + gsrm_word_pos_list = np.concatenate(gsrm_word_pos_list) + gsrm_slf_attn_bias1_list = np.concatenate(gsrm_slf_attn_bias1_list) + gsrm_slf_attn_bias2_list = np.concatenate(gsrm_slf_attn_bias2_list) + + inputs = [ + norm_img_batch, + encoder_word_pos_list, + gsrm_word_pos_list, + gsrm_slf_attn_bias1_list, + gsrm_slf_attn_bias2_list, + ] + if self.use_onnx: + input_dict = {} + input_dict[self.input_tensor.name] = norm_img_batch + outputs = self.predictor.run(self.output_tensors, input_dict) + preds = {"predict": outputs[2]} + else: + input_names = self.predictor.get_input_names() + for i in range(len(input_names)): + input_tensor = self.predictor.get_input_handle(input_names[i]) + input_tensor.copy_from_cpu(inputs[i]) + self.predictor.run() + outputs = [] + for output_tensor in self.output_tensors: + output = output_tensor.copy_to_cpu() + outputs.append(output) + if self.benchmark: + self.autolog.times.stamp() + preds = {"predict": outputs[2]} + elif self.rec_algorithm == "SAR": + valid_ratios = np.concatenate(valid_ratios) + inputs = [ + norm_img_batch, + np.array([valid_ratios], dtype=np.float32).T, + ] + if self.use_onnx: + input_dict = {} + input_dict[self.input_tensor.name] = norm_img_batch + outputs = self.predictor.run(self.output_tensors, input_dict) + preds = outputs[0] + else: + input_names = self.predictor.get_input_names() + for i in range(len(input_names)): + input_tensor = self.predictor.get_input_handle(input_names[i]) + input_tensor.copy_from_cpu(inputs[i]) + self.predictor.run() + outputs = [] + for output_tensor in self.output_tensors: + output = output_tensor.copy_to_cpu() + outputs.append(output) + if self.benchmark: + self.autolog.times.stamp() + preds = outputs[0] + elif self.rec_algorithm == "RobustScanner": + valid_ratios = np.concatenate(valid_ratios) + word_positions_list = np.concatenate(word_positions_list) + inputs = [norm_img_batch, valid_ratios, word_positions_list] + + if self.use_onnx: + input_dict = {} + input_dict[self.input_tensor.name] = norm_img_batch + outputs = self.predictor.run(self.output_tensors, input_dict) + preds = outputs[0] + else: + input_names = self.predictor.get_input_names() + for i in range(len(input_names)): + input_tensor = self.predictor.get_input_handle(input_names[i]) + input_tensor.copy_from_cpu(inputs[i]) + self.predictor.run() + outputs = [] + for output_tensor in self.output_tensors: + output = output_tensor.copy_to_cpu() + outputs.append(output) + if self.benchmark: + self.autolog.times.stamp() + preds = outputs[0] + elif self.rec_algorithm == "CAN": + norm_img_mask_batch = np.concatenate(norm_img_mask_batch) + word_label_list = np.concatenate(word_label_list) + inputs = [norm_img_batch, norm_img_mask_batch, word_label_list] + if self.use_onnx: + input_dict = {} + input_dict[self.input_tensor.name] = norm_img_batch + outputs = self.predictor.run(self.output_tensors, input_dict) + preds = outputs + else: + input_names = self.predictor.get_input_names() + input_tensor = [] + for i in range(len(input_names)): + input_tensor_i = self.predictor.get_input_handle(input_names[i]) + input_tensor_i.copy_from_cpu(inputs[i]) + input_tensor.append(input_tensor_i) + self.input_tensor = input_tensor + self.predictor.run() + outputs = [] + for output_tensor in self.output_tensors: + output = output_tensor.copy_to_cpu() + outputs.append(output) + if self.benchmark: + self.autolog.times.stamp() + preds = outputs + elif self.rec_algorithm == "LaTeXOCR": + inputs = [norm_img_batch] + if self.use_onnx: + input_dict = {} + input_dict[self.input_tensor.name] = norm_img_batch + outputs = self.predictor.run(self.output_tensors, input_dict) + preds = outputs + else: + input_names = self.predictor.get_input_names() + input_tensor = [] + for i in range(len(input_names)): + input_tensor_i = self.predictor.get_input_handle(input_names[i]) + input_tensor_i.copy_from_cpu(inputs[i]) + input_tensor.append(input_tensor_i) + self.input_tensor = input_tensor + self.predictor.run() + outputs = [] + for output_tensor in self.output_tensors: + output = output_tensor.copy_to_cpu() + outputs.append(output) + if self.benchmark: + self.autolog.times.stamp() + preds = outputs + else: + if self.use_onnx: + input_dict = {} + input_dict[self.input_tensor.name] = norm_img_batch + outputs = self.predictor.run(self.output_tensors, input_dict) + preds = outputs[0] + else: + self.input_tensor.copy_from_cpu(norm_img_batch) + self.predictor.run() + outputs = [] + for output_tensor in self.output_tensors: + output = output_tensor.copy_to_cpu() + outputs.append(output) + if self.benchmark: + self.autolog.times.stamp() + if len(outputs) != 1: + preds = outputs + else: + preds = outputs[0] + if self.postprocess_params["name"] == "CTCLabelDecode": + rec_result = self.postprocess_op( + preds, + return_word_box=self.return_word_box, + wh_ratio_list=wh_ratio_list, + max_wh_ratio=max_wh_ratio, + ) + elif self.postprocess_params["name"] == "LaTeXOCRDecode": + preds = [p.reshape([-1]) for p in preds] + rec_result = self.postprocess_op(preds) + else: + rec_result = self.postprocess_op(preds) + for rno in range(len(rec_result)): + rec_res[indices[beg_img_no + rno]] = rec_result[rno] + if self.benchmark: + self.autolog.times.end(stamp=True) + return rec_res, time.time() - st + + +def main(args): + image_file_list = get_image_file_list(args.image_dir) + valid_image_file_list = [] + img_list = [] + + # logger + log_file = args.save_log_path + if os.path.isdir(args.save_log_path) or ( + not os.path.exists(args.save_log_path) and args.save_log_path.endswith("/") + ): + log_file = os.path.join(log_file, "benchmark_recognition.log") + logger = get_logger(log_file=log_file) + + # create text recognizer + text_recognizer = TextRecognizer(args) + + logger.info( + "In PP-OCRv3, rec_image_shape parameter defaults to '3, 48, 320', " + "if you are using recognition model with PP-OCRv2 or an older version, please set --rec_image_shape='3,32,320" + ) + + # warmup 2 times + if args.warmup: + img = np.random.uniform(0, 255, [48, 320, 3]).astype(np.uint8) + for i in range(2): + res = text_recognizer([img] * int(args.rec_batch_num)) + + for image_file in image_file_list: + img, flag, _ = check_and_read(image_file) + if not flag: + img = cv2.imread(image_file) + if img is None: + logger.info("error in loading image:{}".format(image_file)) + continue + valid_image_file_list.append(image_file) + img_list.append(img) + try: + rec_res, _ = text_recognizer(img_list) + + except Exception as E: + logger.info(traceback.format_exc()) + logger.info(E) + exit() + for ino in range(len(img_list)): + logger.info( + "Predicts of {}:{}".format(valid_image_file_list[ino], rec_res[ino]) + ) + if args.benchmark: + text_recognizer.autolog.report() + + +if __name__ == "__main__": + main(utility.parse_args()) diff --git a/tools/infer/predict_sr.py b/tools/infer/predict_sr.py new file mode 100755 index 0000000..389fc24 --- /dev/null +++ b/tools/infer/predict_sr.py @@ -0,0 +1,173 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import sys +from PIL import Image + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, __dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import numpy as np +import math +import time +import traceback +import paddle + +import tools.infer.utility as utility +from ppocr.postprocess import build_post_process +from ppocr.utils.logging import get_logger +from ppocr.utils.utility import get_image_file_list, check_and_read + +logger = get_logger() + + +class TextSR(object): + def __init__(self, args): + if os.path.exists(f"{args.sr_model_dir}/inference.yml"): + model_config = utility.load_config(f"{args.sr_model_dir}/inference.yml") + model_name = model_config.get("Global", {}).get("model_name", "") + if model_name: + raise ValueError( + f"{model_name} is not supported. Please check if the model is supported by the PaddleOCR wheel." + ) + + self.sr_image_shape = [int(v) for v in args.sr_image_shape.split(",")] + self.sr_batch_num = args.sr_batch_num + + ( + self.predictor, + self.input_tensor, + self.output_tensors, + self.config, + ) = utility.create_predictor(args, "sr", logger) + self.benchmark = args.benchmark + if args.benchmark: + import auto_log + + pid = os.getpid() + gpu_id = utility.get_infer_gpuid() + self.autolog = auto_log.AutoLogger( + model_name="sr", + model_precision=args.precision, + batch_size=args.sr_batch_num, + data_shape="dynamic", + save_path=None, # args.save_log_path, + inference_config=self.config, + pids=pid, + process_name=None, + gpu_ids=gpu_id if args.use_gpu else None, + time_keys=["preprocess_time", "inference_time", "postprocess_time"], + warmup=0, + logger=logger, + ) + + def resize_norm_img(self, img): + imgC, imgH, imgW = self.sr_image_shape + img = img.resize((imgW // 2, imgH // 2), Image.BICUBIC) + img_numpy = np.array(img).astype("float32") + img_numpy = img_numpy.transpose((2, 0, 1)) / 255 + return img_numpy + + def __call__(self, img_list): + img_num = len(img_list) + batch_num = self.sr_batch_num + st = time.time() + st = time.time() + all_result = [] * img_num + if self.benchmark: + self.autolog.times.start() + for beg_img_no in range(0, img_num, batch_num): + end_img_no = min(img_num, beg_img_no + batch_num) + norm_img_batch = [] + imgC, imgH, imgW = self.sr_image_shape + for ino in range(beg_img_no, end_img_no): + norm_img = self.resize_norm_img(img_list[ino]) + norm_img = norm_img[np.newaxis, :] + norm_img_batch.append(norm_img) + + norm_img_batch = np.concatenate(norm_img_batch) + norm_img_batch = norm_img_batch.copy() + if self.benchmark: + self.autolog.times.stamp() + self.input_tensor.copy_from_cpu(norm_img_batch) + self.predictor.run() + outputs = [] + for output_tensor in self.output_tensors: + output = output_tensor.copy_to_cpu() + outputs.append(output) + if len(outputs) != 1: + preds = outputs + else: + preds = outputs[0] + all_result.append(outputs) + if self.benchmark: + self.autolog.times.end(stamp=True) + return all_result, time.time() - st + + +def main(args): + image_file_list = get_image_file_list(args.image_dir) + text_recognizer = TextSR(args) + valid_image_file_list = [] + img_list = [] + + # warmup 2 times + if args.warmup: + img = np.random.uniform(0, 255, [16, 64, 3]).astype(np.uint8) + for i in range(2): + res = text_recognizer([img] * int(args.sr_batch_num)) + + for image_file in image_file_list: + img, flag, _ = check_and_read(image_file) + if not flag: + img = Image.open(image_file).convert("RGB") + if img is None: + logger.info("error in loading image:{}".format(image_file)) + continue + valid_image_file_list.append(image_file) + img_list.append(img) + try: + preds, _ = text_recognizer(img_list) + for beg_no in range(len(preds)): + sr_img = preds[beg_no][1] + lr_img = preds[beg_no][0] + for i in range(sr_img.shape[0]): + fm_sr = (sr_img[i] * 255).transpose(1, 2, 0).astype(np.uint8) + fm_lr = (lr_img[i] * 255).transpose(1, 2, 0).astype(np.uint8) + img_name_pure = os.path.split( + valid_image_file_list[beg_no * args.sr_batch_num + i] + )[-1] + cv2.imwrite( + "infer_result/sr_{}".format(img_name_pure), fm_sr[:, :, ::-1] + ) + logger.info( + "The visualized image saved in infer_result/sr_{}".format( + img_name_pure + ) + ) + + except Exception as E: + logger.info(traceback.format_exc()) + logger.info(E) + exit() + if args.benchmark: + text_recognizer.autolog.report() + + +if __name__ == "__main__": + main(utility.parse_args()) diff --git a/tools/infer/predict_system.py b/tools/infer/predict_system.py new file mode 100755 index 0000000..bcb0758 --- /dev/null +++ b/tools/infer/predict_system.py @@ -0,0 +1,326 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import sys +import subprocess + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, "../.."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import copy +import numpy as np +import json +import time +import logging +from PIL import Image +import tools.infer.utility as utility +import tools.infer.predict_rec as predict_rec +import tools.infer.predict_det as predict_det +import tools.infer.predict_cls as predict_cls +from ppocr.utils.utility import get_image_file_list, check_and_read +from ppocr.utils.logging import get_logger +from tools.infer.utility import ( + draw_ocr_box_txt, + get_rotate_crop_image, + get_minarea_rect_crop, + slice_generator, + merge_fragmented, +) + +logger = get_logger() + + +class TextSystem(object): + def __init__(self, args): + if not args.show_log: + logger.setLevel(logging.INFO) + + self.text_detector = predict_det.TextDetector(args) + self.text_recognizer = predict_rec.TextRecognizer(args) + self.use_angle_cls = args.use_angle_cls + self.drop_score = args.drop_score + if self.use_angle_cls: + self.text_classifier = predict_cls.TextClassifier(args) + + self.args = args + self.crop_image_res_index = 0 + + def draw_crop_rec_res(self, output_dir, img_crop_list, rec_res): + os.makedirs(output_dir, exist_ok=True) + bbox_num = len(img_crop_list) + for bno in range(bbox_num): + cv2.imwrite( + os.path.join( + output_dir, f"mg_crop_{bno+self.crop_image_res_index}.jpg" + ), + img_crop_list[bno], + ) + logger.debug(f"{bno}, {rec_res[bno]}") + self.crop_image_res_index += bbox_num + + def __call__(self, img, cls=True, slice={}): + time_dict = {"det": 0, "rec": 0, "cls": 0, "all": 0} + + if img is None: + logger.debug("no valid image provided") + return None, None, time_dict + + start = time.time() + ori_im = img.copy() + if slice: + slice_gen = slice_generator( + img, + horizontal_stride=slice["horizontal_stride"], + vertical_stride=slice["vertical_stride"], + ) + elapsed = [] + dt_slice_boxes = [] + for slice_crop, v_start, h_start in slice_gen: + dt_boxes, elapse = self.text_detector(slice_crop, use_slice=True) + if dt_boxes.size: + dt_boxes[:, :, 0] += h_start + dt_boxes[:, :, 1] += v_start + dt_slice_boxes.append(dt_boxes) + elapsed.append(elapse) + dt_boxes = np.concatenate(dt_slice_boxes) + + dt_boxes = merge_fragmented( + boxes=dt_boxes, + x_threshold=slice["merge_x_thres"], + y_threshold=slice["merge_y_thres"], + ) + elapse = sum(elapsed) + else: + dt_boxes, elapse = self.text_detector(img) + + time_dict["det"] = elapse + + if dt_boxes is None: + logger.debug("no dt_boxes found, elapsed : {}".format(elapse)) + end = time.time() + time_dict["all"] = end - start + return None, None, time_dict + else: + logger.debug( + "dt_boxes num : {}, elapsed : {}".format(len(dt_boxes), elapse) + ) + img_crop_list = [] + + dt_boxes = sorted_boxes(dt_boxes) + + for bno in range(len(dt_boxes)): + tmp_box = copy.deepcopy(dt_boxes[bno]) + if self.args.det_box_type == "quad": + img_crop = get_rotate_crop_image(ori_im, tmp_box) + else: + img_crop = get_minarea_rect_crop(ori_im, tmp_box) + img_crop_list.append(img_crop) + if self.use_angle_cls and cls: + img_crop_list, angle_list, elapse = self.text_classifier(img_crop_list) + time_dict["cls"] = elapse + logger.debug( + "cls num : {}, elapsed : {}".format(len(img_crop_list), elapse) + ) + if len(img_crop_list) > 1000: + logger.debug( + f"rec crops num: {len(img_crop_list)}, time and memory cost may be large." + ) + + rec_res, elapse = self.text_recognizer(img_crop_list) + time_dict["rec"] = elapse + logger.debug("rec_res num : {}, elapsed : {}".format(len(rec_res), elapse)) + if self.args.save_crop_res: + self.draw_crop_rec_res(self.args.crop_res_save_dir, img_crop_list, rec_res) + filter_boxes, filter_rec_res = [], [] + for box, rec_result in zip(dt_boxes, rec_res): + text, score = rec_result[0], rec_result[1] + if score >= self.drop_score: + filter_boxes.append(box) + filter_rec_res.append(rec_result) + end = time.time() + time_dict["all"] = end - start + return filter_boxes, filter_rec_res, time_dict + + +def sorted_boxes(dt_boxes): + """ + Sort text boxes in order from top to bottom, left to right + args: + dt_boxes(array):detected text boxes with shape [4, 2] + return: + sorted boxes(array) with shape [4, 2] + """ + num_boxes = dt_boxes.shape[0] + sorted_boxes = sorted(dt_boxes, key=lambda x: (x[0][1], x[0][0])) + _boxes = list(sorted_boxes) + + for i in range(num_boxes - 1): + for j in range(i, -1, -1): + if abs(_boxes[j + 1][0][1] - _boxes[j][0][1]) < 10 and ( + _boxes[j + 1][0][0] < _boxes[j][0][0] + ): + tmp = _boxes[j] + _boxes[j] = _boxes[j + 1] + _boxes[j + 1] = tmp + else: + break + return _boxes + + +def main(args): + image_file_list = get_image_file_list(args.image_dir) + image_file_list = image_file_list[args.process_id :: args.total_process_num] + text_sys = TextSystem(args) + is_visualize = True + font_path = args.vis_font_path + drop_score = args.drop_score + draw_img_save_dir = args.draw_img_save_dir + os.makedirs(draw_img_save_dir, exist_ok=True) + save_results = [] + + logger.info( + "In PP-OCRv3, rec_image_shape parameter defaults to '3, 48, 320', " + "if you are using recognition model with PP-OCRv2 or an older version, please set --rec_image_shape='3,32,320" + ) + + # warm up 10 times + if args.warmup: + img = np.random.uniform(0, 255, [640, 640, 3]).astype(np.uint8) + for i in range(10): + res = text_sys(img) + + total_time = 0 + cpu_mem, gpu_mem, gpu_util = 0, 0, 0 + _st = time.time() + count = 0 + for idx, image_file in enumerate(image_file_list): + img, flag_gif, flag_pdf = check_and_read(image_file) + if not flag_gif and not flag_pdf: + img = cv2.imread(image_file) + if not flag_pdf: + if img is None: + logger.debug("error in loading image:{}".format(image_file)) + continue + imgs = [img] + else: + page_num = args.page_num + if page_num > len(img) or page_num == 0: + page_num = len(img) + imgs = img[:page_num] + for index, img in enumerate(imgs): + starttime = time.time() + dt_boxes, rec_res, time_dict = text_sys(img) + elapse = time.time() - starttime + total_time += elapse + if len(imgs) > 1: + logger.debug( + str(idx) + + "_" + + str(index) + + " Predict time of %s: %.3fs" % (image_file, elapse) + ) + else: + logger.debug( + str(idx) + " Predict time of %s: %.3fs" % (image_file, elapse) + ) + for text, score in rec_res: + logger.debug("{}, {:.3f}".format(text, score)) + + res = [ + { + "transcription": rec_res[i][0], + "points": np.array(dt_boxes[i]).astype(np.int32).tolist(), + } + for i in range(len(dt_boxes)) + ] + if len(imgs) > 1: + save_pred = ( + os.path.basename(image_file) + + "_" + + str(index) + + "\t" + + json.dumps(res, ensure_ascii=False) + + "\n" + ) + else: + save_pred = ( + os.path.basename(image_file) + + "\t" + + json.dumps(res, ensure_ascii=False) + + "\n" + ) + save_results.append(save_pred) + + if is_visualize: + image = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) + boxes = dt_boxes + txts = [rec_res[i][0] for i in range(len(rec_res))] + scores = [rec_res[i][1] for i in range(len(rec_res))] + + draw_img = draw_ocr_box_txt( + image, + boxes, + txts, + scores, + drop_score=drop_score, + font_path=font_path, + ) + if flag_gif: + save_file = image_file[:-3] + "png" + elif flag_pdf: + save_file = image_file.replace(".pdf", "_" + str(index) + ".png") + else: + save_file = image_file + cv2.imwrite( + os.path.join(draw_img_save_dir, os.path.basename(save_file)), + draw_img[:, :, ::-1], + ) + logger.debug( + "The visualized image saved in {}".format( + os.path.join(draw_img_save_dir, os.path.basename(save_file)) + ) + ) + + logger.info("The predict total time is {}".format(time.time() - _st)) + if args.benchmark: + text_sys.text_detector.autolog.report() + text_sys.text_recognizer.autolog.report() + + with open( + os.path.join(draw_img_save_dir, "system_results.txt"), "w", encoding="utf-8" + ) as f: + f.writelines(save_results) + + +if __name__ == "__main__": + args = utility.parse_args() + if args.use_mp: + p_list = [] + total_process_num = args.total_process_num + for process_id in range(total_process_num): + cmd = ( + [sys.executable, "-u"] + + sys.argv + + ["--process_id={}".format(process_id), "--use_mp={}".format(False)] + ) + p = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stdout) + p_list.append(p) + for p in p_list: + p.wait() + else: + main(args) diff --git a/tools/infer/utility.py b/tools/infer/utility.py new file mode 100644 index 0000000..fb48334 --- /dev/null +++ b/tools/infer/utility.py @@ -0,0 +1,1042 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import os +import sys +import cv2 +import numpy as np +import paddle +import PIL +from PIL import Image, ImageDraw, ImageFont +import math +from paddle import inference +import random +import yaml +from ppocr.utils.logging import get_logger + + +def str2bool(v): + return v.lower() in ("true", "yes", "t", "y", "1") + + +def str2int_tuple(v): + return tuple([int(i.strip()) for i in v.split(",")]) + + +def init_args(): + parser = argparse.ArgumentParser() + # params for prediction engine + parser.add_argument("--use_gpu", type=str2bool, default=True) + parser.add_argument("--use_xpu", type=str2bool, default=False) + parser.add_argument("--use_npu", type=str2bool, default=False) + parser.add_argument("--use_mlu", type=str2bool, default=False) + parser.add_argument("--use_metax_gpu", type=str2bool, default=False) + parser.add_argument( + "--use_gcu", + type=str2bool, + default=False, + help="Use Enflame GCU(General Compute Unit)", + ) + parser.add_argument("--ir_optim", type=str2bool, default=True) + parser.add_argument("--use_tensorrt", type=str2bool, default=False) + parser.add_argument("--min_subgraph_size", type=int, default=15) + parser.add_argument("--precision", type=str, default="fp32") + parser.add_argument("--gpu_mem", type=int, default=500) + parser.add_argument("--gpu_id", type=int, default=0) + + # params for text detector + parser.add_argument("--image_dir", type=str) + parser.add_argument("--page_num", type=int, default=0) + parser.add_argument("--det_algorithm", type=str, default="DB") + parser.add_argument("--det_model_dir", type=str) + parser.add_argument("--det_limit_side_len", type=float, default=960) + parser.add_argument("--det_limit_type", type=str, default="max") + parser.add_argument("--det_box_type", type=str, default="quad") + + # DB params + parser.add_argument("--det_db_thresh", type=float, default=0.3) + parser.add_argument("--det_db_box_thresh", type=float, default=0.6) + parser.add_argument("--det_db_unclip_ratio", type=float, default=1.5) + parser.add_argument("--max_batch_size", type=int, default=10) + parser.add_argument("--use_dilation", type=str2bool, default=False) + parser.add_argument("--det_db_score_mode", type=str, default="fast") + + # EAST params + parser.add_argument("--det_east_score_thresh", type=float, default=0.8) + parser.add_argument("--det_east_cover_thresh", type=float, default=0.1) + parser.add_argument("--det_east_nms_thresh", type=float, default=0.2) + + # SAST params + parser.add_argument("--det_sast_score_thresh", type=float, default=0.5) + parser.add_argument("--det_sast_nms_thresh", type=float, default=0.2) + + # PSE params + parser.add_argument("--det_pse_thresh", type=float, default=0) + parser.add_argument("--det_pse_box_thresh", type=float, default=0.85) + parser.add_argument("--det_pse_min_area", type=float, default=16) + parser.add_argument("--det_pse_scale", type=int, default=1) + + # FCE params + parser.add_argument("--scales", type=list, default=[8, 16, 32]) + parser.add_argument("--alpha", type=float, default=1.0) + parser.add_argument("--beta", type=float, default=1.0) + parser.add_argument("--fourier_degree", type=int, default=5) + + # params for text recognizer + parser.add_argument("--rec_algorithm", type=str, default="SVTR_LCNet") + parser.add_argument("--rec_model_dir", type=str) + parser.add_argument("--rec_image_inverse", type=str2bool, default=True) + parser.add_argument("--rec_image_shape", type=str, default="3, 48, 320") + parser.add_argument("--rec_batch_num", type=int, default=6) + parser.add_argument("--max_text_length", type=int, default=25) + parser.add_argument( + "--rec_char_dict_path", type=str, default="./ppocr/utils/ppocr_keys_v1.txt" + ) + parser.add_argument("--use_space_char", type=str2bool, default=True) + parser.add_argument("--vis_font_path", type=str, default="./doc/fonts/simfang.ttf") + parser.add_argument("--drop_score", type=float, default=0.5) + + # params for e2e + parser.add_argument("--e2e_algorithm", type=str, default="PGNet") + parser.add_argument("--e2e_model_dir", type=str) + parser.add_argument("--e2e_limit_side_len", type=float, default=768) + parser.add_argument("--e2e_limit_type", type=str, default="max") + + # PGNet params + parser.add_argument("--e2e_pgnet_score_thresh", type=float, default=0.5) + parser.add_argument( + "--e2e_char_dict_path", type=str, default="./ppocr/utils/ic15_dict.txt" + ) + parser.add_argument("--e2e_pgnet_valid_set", type=str, default="totaltext") + parser.add_argument("--e2e_pgnet_mode", type=str, default="fast") + + # params for text classifier + parser.add_argument("--use_angle_cls", type=str2bool, default=False) + parser.add_argument("--cls_model_dir", type=str) + parser.add_argument("--cls_image_shape", type=str, default="3, 48, 192") + parser.add_argument("--label_list", type=list, default=["0", "180"]) + parser.add_argument("--cls_batch_num", type=int, default=6) + parser.add_argument("--cls_thresh", type=float, default=0.9) + + parser.add_argument("--enable_mkldnn", type=str2bool, default=None) + parser.add_argument("--cpu_threads", type=int, default=10) + parser.add_argument("--use_pdserving", type=str2bool, default=False) + parser.add_argument("--warmup", type=str2bool, default=False) + + # SR params + parser.add_argument("--sr_model_dir", type=str) + parser.add_argument("--sr_image_shape", type=str, default="3, 32, 128") + parser.add_argument("--sr_batch_num", type=int, default=1) + + # + parser.add_argument("--draw_img_save_dir", type=str, default="./inference_results") + parser.add_argument("--save_crop_res", type=str2bool, default=False) + parser.add_argument("--crop_res_save_dir", type=str, default="./output") + + # multi-process + parser.add_argument("--use_mp", type=str2bool, default=False) + parser.add_argument("--total_process_num", type=int, default=1) + parser.add_argument("--process_id", type=int, default=0) + + parser.add_argument("--benchmark", type=str2bool, default=False) + parser.add_argument("--save_log_path", type=str, default="./log_output/") + + parser.add_argument("--show_log", type=str2bool, default=True) + parser.add_argument("--use_onnx", type=str2bool, default=False) + parser.add_argument("--onnx_providers", nargs="+", type=str, default=False) + parser.add_argument("--onnx_sess_options", type=list, default=False) + + # extended function + parser.add_argument( + "--return_word_box", + type=str2bool, + default=False, + help="Whether return the bbox of each word (split by space) or chinese character. Only used in ppstructure for layout recovery", + ) + + return parser + + +def parse_args(): + parser = init_args() + return parser.parse_args() + + +def create_predictor(args, mode, logger): + if mode == "det": + model_dir = args.det_model_dir + elif mode == "cls": + model_dir = args.cls_model_dir + elif mode == "rec": + model_dir = args.rec_model_dir + elif mode == "table": + model_dir = args.table_model_dir + elif mode == "ser": + model_dir = args.ser_model_dir + elif mode == "re": + model_dir = args.re_model_dir + elif mode == "sr": + model_dir = args.sr_model_dir + elif mode == "layout": + model_dir = args.layout_model_dir + else: + model_dir = args.e2e_model_dir + + if model_dir is None: + logger.info("not find {} model file path {}".format(mode, model_dir)) + sys.exit(0) + if args.use_onnx: + import onnxruntime as ort + + model_file_path = model_dir + if not os.path.exists(model_file_path): + raise ValueError("not find model file path {}".format(model_file_path)) + + sess_options = args.onnx_sess_options or None + + if args.onnx_providers and len(args.onnx_providers) > 0: + sess = ort.InferenceSession( + model_file_path, + providers=args.onnx_providers, + sess_options=sess_options, + ) + elif args.use_gpu: + sess = ort.InferenceSession( + model_file_path, + providers=[ + ( + "CUDAExecutionProvider", + { + "device_id": args.gpu_id, + "cudnn_conv_algo_search": "HEURISTIC", + }, + ) + ], + sess_options=sess_options, + ) + else: + sess = ort.InferenceSession( + model_file_path, + providers=["CPUExecutionProvider"], + sess_options=sess_options, + ) + inputs = sess.get_inputs() + return ( + sess, + inputs[0] if len(inputs) == 1 else [vo.name for vo in inputs], + None, + None, + ) + + else: + file_names = ["model", "inference"] + for file_name in file_names: + params_file_path = f"{model_dir}/{file_name}.pdiparams" + if os.path.exists(params_file_path): + break + + if not os.path.exists(params_file_path): + raise ValueError(f"not find {file_name}.pdiparams in {model_dir}") + + if not ( + os.path.exists(f"{model_dir}/{file_name}.pdmodel") + or os.path.exists(f"{model_dir}/{file_name}.json") + ): + raise ValueError( + f"neither {file_name}.json nor {file_name}.pdmodel was found in {model_dir}." + ) + + if os.path.exists(f"{model_dir}/{file_name}.json"): + model_file_path = f"{model_dir}/{file_name}.json" + else: + model_file_path = f"{model_dir}/{file_name}.pdmodel" + + config = inference.Config(model_file_path, params_file_path) + + if hasattr(args, "precision"): + if args.precision == "fp16" and args.use_tensorrt: + precision = inference.PrecisionType.Half + elif args.precision == "int8": + precision = inference.PrecisionType.Int8 + else: + precision = inference.PrecisionType.Float32 + else: + precision = inference.PrecisionType.Float32 + + if args.use_gpu: + gpu_id = get_infer_gpuid() + if gpu_id is None: + logger.warning( + "GPU is not found in current device by nvidia-smi. Please check your device or ignore it if run on jetson." + ) + config.enable_use_gpu(args.gpu_mem, args.gpu_id) + if args.use_tensorrt: + if ".json" in model_file_path: + trt_dynamic_shapes = {} + trt_dynamic_shape_input_data = {} + if os.path.exists(f"{model_dir}/inference.yml"): + model_config = load_config(f"{model_dir}/inference.yml") + trt_dynamic_shapes = ( + model_config.get("Hpi", {}) + .get("backend_configs", {}) + .get("paddle_infer", {}) + .get("trt_dynamic_shapes", {}) + ) + trt_dynamic_shape_input_data = ( + model_config.get("Hpi", {}) + .get("backend_configs", {}) + .get("paddle_infer", {}) + .get("trt_dynamic_shapes_input_data", {}) + ) + + if not trt_dynamic_shapes: + raise RuntimeError( + "Configuration Error: 'trt_dynamic_shapes' must be defined in 'inference.yml' for Paddle Inference TensorRT." + ) + + trt_save_path = f"{model_dir}/.cache/trt/{file_name}" + trt_model_file_path = trt_save_path + ".json" + trt_params_file_path = trt_save_path + ".pdiparams" + if not os.path.exists(trt_model_file_path) or not os.path.exists( + trt_params_file_path + ): + _convert_trt( + {}, + model_file_path, + params_file_path, + trt_save_path, + args.gpu_id, + trt_dynamic_shapes, + trt_dynamic_shape_input_data, + ) + config = inference.Config(model_file_path, params_file_path) + config.exp_disable_mixed_precision_ops({"feed", "fetch"}) + config.enable_use_gpu(args.gpu_mem, args.gpu_id) + else: + config.enable_tensorrt_engine( + workspace_size=1 << 30, + precision_mode=precision, + max_batch_size=args.max_batch_size, + min_subgraph_size=args.min_subgraph_size, # skip the minimum trt subgraph + use_calib_mode=False, + ) + + # collect shape + trt_shape_f = os.path.join( + model_dir, f"{mode}_trt_dynamic_shape.txt" + ) + + if not os.path.exists(trt_shape_f): + config.collect_shape_range_info(trt_shape_f) + logger.info(f"collect dynamic shape info into : {trt_shape_f}") + try: + config.enable_tuned_tensorrt_dynamic_shape(trt_shape_f, True) + except Exception as E: + logger.info(E) + logger.info("Please keep your paddlepaddle-gpu >= 2.3.0!") + + elif args.use_npu: + config.enable_custom_device("npu") + elif args.use_mlu: + config.enable_custom_device("mlu") + elif args.use_metax_gpu: + if args.precision == "fp16": + config.enable_custom_device( + "metax_gpu", 0, paddle.inference.PrecisionType.Half + ) + + else: + config.enable_custom_device("metax_gpu") + elif args.use_xpu: + config.enable_xpu(10 * 1024 * 1024) + elif args.use_gcu: # for Enflame GCU(General Compute Unit) + assert paddle.device.is_compiled_with_custom_device("gcu"), ( + "Args use_gcu cannot be set as True while your paddle " + "is not compiled with gcu! \nPlease try: \n" + "\t1. Install paddle-custom-gcu to run model on GCU. \n" + "\t2. Set use_gcu as False in args to run model on CPU." + ) + import paddle_custom_device.gcu.passes as gcu_passes + + gcu_passes.setUp() + if args.precision == "fp16": + config.enable_custom_device( + "gcu", 0, paddle.inference.PrecisionType.Half + ) + gcu_passes.set_exp_enable_mixed_precision_ops(config) + else: + config.enable_custom_device("gcu") + + if paddle.framework.use_pir_api(): + config.enable_new_ir(True) + config.enable_new_executor(True) + else: + pass_builder = config.pass_builder() + gcu_passes.append_passes_for_legacy_ir(pass_builder, "PaddleOCR") + else: + config.disable_gpu() + if args.enable_mkldnn is not None: + if args.enable_mkldnn: + # cache 10 different shapes for mkldnn to avoid memory leak + config.set_mkldnn_cache_capacity(10) + config.enable_mkldnn() + if args.precision == "fp16": + config.enable_mkldnn_bfloat16() + else: + if hasattr(config, "disable_mkldnn"): + config.disable_mkldnn() + + if hasattr(args, "cpu_threads"): + config.set_cpu_math_library_num_threads(args.cpu_threads) + else: + # default cpu threads as 10 + config.set_cpu_math_library_num_threads(10) + + if hasattr(config, "enable_new_ir"): + config.enable_new_ir() + if hasattr(config, "enable_new_executor"): + config.enable_new_executor() + + # enable memory optim + config.enable_memory_optim() + config.disable_glog_info() + if not args.use_gcu: # for Enflame GCU(General Compute Unit) + config.delete_pass("conv_transpose_eltwiseadd_bn_fuse_pass") + config.delete_pass("matmul_transpose_reshape_fuse_pass") + if mode == "rec" and args.rec_algorithm == "SRN": + config.delete_pass("gpu_cpu_map_matmul_v2_to_matmul_pass") + if mode == "re": + config.delete_pass("simplify_with_basic_ops_pass") + if mode == "table": + config.delete_pass("fc_fuse_pass") # not supported for table + config.switch_use_feed_fetch_ops(False) + config.switch_ir_optim(True) + + # create predictor + predictor = inference.create_predictor(config) + input_names = predictor.get_input_names() + if mode in ["ser", "re"]: + input_tensor = [] + for name in input_names: + input_tensor.append(predictor.get_input_handle(name)) + else: + for name in input_names: + input_tensor = predictor.get_input_handle(name) + output_tensors = get_output_tensors(args, mode, predictor) + return predictor, input_tensor, output_tensors, config + + +def _convert_trt( + trt_cfg_setting, + pp_model_file, + pp_params_file, + trt_save_path, + device_id, + dynamic_shapes, + dynamic_shape_input_data, +): + from paddle.tensorrt.export import Input, TensorRTConfig, convert + + def _set_trt_config(): + for attr_name in trt_cfg_setting: + assert hasattr( + trt_config, attr_name + ), f"The `{type(trt_config)}` don't have the attribute `{attr_name}`!" + setattr(trt_config, attr_name, trt_cfg_setting[attr_name]) + + def _get_predictor(model_file, params_file): + # HACK + config = inference.Config(str(model_file), str(params_file)) + config.enable_use_gpu(100, device_id) + # NOTE: Disable oneDNN to circumvent a bug in Paddle Inference + config.disable_mkldnn() + config.disable_glog_info() + return inference.create_predictor(config) + + dynamic_shape_input_data = dynamic_shape_input_data or {} + + predictor = _get_predictor(pp_model_file, pp_params_file) + input_names = predictor.get_input_names() + for name in dynamic_shapes: + if name not in input_names: + raise ValueError( + f"Invalid input name {repr(name)} found in `dynamic_shapes`" + ) + for name in input_names: + if name not in dynamic_shapes: + raise ValueError(f"Input name {repr(name)} not found in `dynamic_shapes`") + for name in dynamic_shape_input_data: + if name not in input_names: + raise ValueError( + f"Invalid input name {repr(name)} found in `dynamic_shape_input_data`" + ) + + trt_inputs = [] + for name, candidate_shapes in dynamic_shapes.items(): + # XXX: Currently we have no way to get the data type of the tensor + # without creating an input handle. + handle = predictor.get_input_handle(name) + dtype = _pd_dtype_to_np_dtype(handle.type()) + min_shape, opt_shape, max_shape = candidate_shapes + if name in dynamic_shape_input_data: + min_arr = np.array(dynamic_shape_input_data[name][0], dtype=dtype).reshape( + min_shape + ) + opt_arr = np.array(dynamic_shape_input_data[name][1], dtype=dtype).reshape( + opt_shape + ) + max_arr = np.array(dynamic_shape_input_data[name][2], dtype=dtype).reshape( + max_shape + ) + else: + min_arr = np.ones(min_shape, dtype=dtype) + opt_arr = np.ones(opt_shape, dtype=dtype) + max_arr = np.ones(max_shape, dtype=dtype) + + # refer to: https://github.com/PolaKuma/Paddle/blob/3347f225bc09f2ec09802a2090432dd5cb5b6739/test/tensorrt/test_converter_model_resnet50.py + trt_input = Input((min_arr, opt_arr, max_arr)) + trt_inputs.append(trt_input) + + # Create TensorRTConfig + trt_config = TensorRTConfig(inputs=trt_inputs) + _set_trt_config() + trt_config.save_model_dir = trt_save_path + pp_model_path = pp_model_file.split(".")[0] + convert(pp_model_path, trt_config) + + +def _pd_dtype_to_np_dtype(pd_dtype): + if pd_dtype == inference.DataType.FLOAT64: + return np.float64 + elif pd_dtype == inference.DataType.FLOAT32: + return np.float32 + elif pd_dtype == inference.DataType.INT64: + return np.int64 + elif pd_dtype == inference.DataType.INT32: + return np.int32 + elif pd_dtype == inference.DataType.UINT8: + return np.uint8 + elif pd_dtype == inference.DataType.INT8: + return np.int8 + else: + raise TypeError(f"Unsupported data type: {pd_dtype}") + + +def load_config(file_path): + _, ext = os.path.splitext(file_path) + if ext not in [".yml", ".yaml"]: + raise ValueError(f"only support yaml files for now, got {file_path}") + with open(file_path, "rb") as file: + config = yaml.load(file, Loader=yaml.SafeLoader) + return config + + +def get_output_tensors(args, mode, predictor): + output_names = predictor.get_output_names() + output_tensors = [] + if mode == "rec" and args.rec_algorithm in ["CRNN", "SVTR_LCNet", "SVTR_HGNet"]: + output_name = "softmax_0.tmp_0" + if output_name in output_names: + return [predictor.get_output_handle(output_name)] + else: + for output_name in output_names: + output_tensor = predictor.get_output_handle(output_name) + output_tensors.append(output_tensor) + else: + for output_name in output_names: + output_tensor = predictor.get_output_handle(output_name) + output_tensors.append(output_tensor) + return output_tensors + + +def get_infer_gpuid(): + """ + Get the GPU ID to be used for inference. + + Returns: + int: The GPU ID to be used for inference. + """ + logger = get_logger() + if not paddle.device.is_compiled_with_rocm: + gpu_id_str = os.environ.get("CUDA_VISIBLE_DEVICES", "0") + else: + gpu_id_str = os.environ.get("HIP_VISIBLE_DEVICES", "0") + + gpu_ids = gpu_id_str.split(",") + logger.warning( + "The first GPU is used for inference by default, GPU ID: {}".format(gpu_ids[0]) + ) + return int(gpu_ids[0]) + + +def draw_e2e_res(dt_boxes, strs, img_path): + src_im = cv2.imread(img_path) + for box, str in zip(dt_boxes, strs): + box = box.astype(np.int32).reshape((-1, 1, 2)) + cv2.polylines(src_im, [box], True, color=(255, 255, 0), thickness=2) + cv2.putText( + src_im, + str, + org=(int(box[0, 0, 0]), int(box[0, 0, 1])), + fontFace=cv2.FONT_HERSHEY_COMPLEX, + fontScale=0.7, + color=(0, 255, 0), + thickness=1, + ) + return src_im + + +def draw_text_det_res(dt_boxes, img): + for box in dt_boxes: + box = np.array(box).astype(np.int32).reshape(-1, 2) + cv2.polylines(img, [box], True, color=(255, 255, 0), thickness=2) + return img + + +def resize_img(img, input_size=600): + """ + resize img and limit the longest side of the image to input_size + """ + img = np.array(img) + im_shape = img.shape + im_size_max = np.max(im_shape[0:2]) + im_scale = float(input_size) / float(im_size_max) + img = cv2.resize(img, None, None, fx=im_scale, fy=im_scale) + return img + + +def draw_ocr( + image, + boxes, + txts=None, + scores=None, + drop_score=0.5, + font_path="./doc/fonts/simfang.ttf", +): + """ + Visualize the results of OCR detection and recognition + args: + image(Image|array): RGB image + boxes(list): boxes with shape(N, 4, 2) + txts(list): the texts + scores(list): txxs corresponding scores + drop_score(float): only scores greater than drop_threshold will be visualized + font_path: the path of font which is used to draw text + return(array): + the visualized img + """ + if scores is None: + scores = [1] * len(boxes) + box_num = len(boxes) + for i in range(box_num): + if scores is not None and (scores[i] < drop_score or math.isnan(scores[i])): + continue + box = np.reshape(np.array(boxes[i]), [-1, 1, 2]).astype(np.int64) + image = cv2.polylines(np.array(image), [box], True, (255, 0, 0), 2) + if txts is not None: + img = np.array(resize_img(image, input_size=600)) + txt_img = text_visual( + txts, + scores, + img_h=img.shape[0], + img_w=600, + threshold=drop_score, + font_path=font_path, + ) + img = np.concatenate([np.array(img), np.array(txt_img)], axis=1) + return img + return image + + +def draw_ocr_box_txt( + image, + boxes, + txts=None, + scores=None, + drop_score=0.5, + font_path="./doc/fonts/simfang.ttf", +): + h, w = image.height, image.width + img_left = image.copy() + img_right = np.ones((h, w, 3), dtype=np.uint8) * 255 + random.seed(0) + + draw_left = ImageDraw.Draw(img_left) + if txts is None or len(txts) != len(boxes): + txts = [None] * len(boxes) + for idx, (box, txt) in enumerate(zip(boxes, txts)): + if scores is not None and scores[idx] < drop_score: + continue + color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + draw_left.polygon(box, fill=color) + img_right_text = draw_box_txt_fine((w, h), box, txt, font_path) + pts = np.array(box, np.int32).reshape((-1, 1, 2)) + cv2.polylines(img_right_text, [pts], True, color, 1) + img_right = cv2.bitwise_and(img_right, img_right_text) + img_left = Image.blend(image, img_left, 0.5) + img_show = Image.new("RGB", (w * 2, h), (255, 255, 255)) + img_show.paste(img_left, (0, 0, w, h)) + img_show.paste(Image.fromarray(img_right), (w, 0, w * 2, h)) + return np.array(img_show) + + +def draw_box_txt_fine(img_size, box, txt, font_path="./doc/fonts/simfang.ttf"): + box_height = int( + math.sqrt((box[0][0] - box[3][0]) ** 2 + (box[0][1] - box[3][1]) ** 2) + ) + box_width = int( + math.sqrt((box[0][0] - box[1][0]) ** 2 + (box[0][1] - box[1][1]) ** 2) + ) + + if box_height > 2 * box_width and box_height > 30: + img_text = Image.new("RGB", (box_height, box_width), (255, 255, 255)) + draw_text = ImageDraw.Draw(img_text) + if txt: + font = create_font(txt, (box_height, box_width), font_path) + draw_text.text([0, 0], txt, fill=(0, 0, 0), font=font) + img_text = img_text.transpose(Image.ROTATE_270) + else: + img_text = Image.new("RGB", (box_width, box_height), (255, 255, 255)) + draw_text = ImageDraw.Draw(img_text) + if txt: + font = create_font(txt, (box_width, box_height), font_path) + draw_text.text([0, 0], txt, fill=(0, 0, 0), font=font) + + pts1 = np.float32( + [[0, 0], [box_width, 0], [box_width, box_height], [0, box_height]] + ) + pts2 = np.array(box, dtype=np.float32) + M = cv2.getPerspectiveTransform(pts1, pts2) + + img_text = np.array(img_text, dtype=np.uint8) + img_right_text = cv2.warpPerspective( + img_text, + M, + img_size, + flags=cv2.INTER_NEAREST, + borderMode=cv2.BORDER_CONSTANT, + borderValue=(255, 255, 255), + ) + return img_right_text + + +def create_font(txt, sz, font_path="./doc/fonts/simfang.ttf"): + font_size = int(sz[1] * 0.99) + font = ImageFont.truetype(font_path, font_size, encoding="utf-8") + if int(PIL.__version__.split(".")[0]) < 10: + length = font.getsize(txt)[0] + else: + length = font.getlength(txt) + + if length > sz[0]: + font_size = int(font_size * sz[0] / length) + font = ImageFont.truetype(font_path, font_size, encoding="utf-8") + return font + + +def str_count(s): + """ + Count the number of Chinese characters, + a single English character and a single number + equal to half the length of Chinese characters. + args: + s(string): the input of string + return(int): + the number of Chinese characters + """ + import string + + count_zh = count_pu = 0 + s_len = len(s) + en_dg_count = 0 + for c in s: + if c in string.ascii_letters or c.isdigit() or c.isspace(): + en_dg_count += 1 + elif c.isalpha(): + count_zh += 1 + else: + count_pu += 1 + return s_len - math.ceil(en_dg_count / 2) + + +def text_visual( + texts, scores, img_h=400, img_w=600, threshold=0.0, font_path="./doc/simfang.ttf" +): + """ + create new blank img and draw txt on it + args: + texts(list): the text will be draw + scores(list|None): corresponding score of each txt + img_h(int): the height of blank img + img_w(int): the width of blank img + font_path: the path of font which is used to draw text + return(array): + """ + if scores is not None: + assert len(texts) == len( + scores + ), "The number of txts and corresponding scores must match" + + def create_blank_img(): + blank_img = np.ones(shape=[img_h, img_w], dtype=np.uint8) * 255 + blank_img[:, img_w - 1 :] = 0 + blank_img = Image.fromarray(blank_img).convert("RGB") + draw_txt = ImageDraw.Draw(blank_img) + return blank_img, draw_txt + + blank_img, draw_txt = create_blank_img() + + font_size = 20 + txt_color = (0, 0, 0) + font = ImageFont.truetype(font_path, font_size, encoding="utf-8") + + gap = font_size + 5 + txt_img_list = [] + count, index = 1, 0 + for idx, txt in enumerate(texts): + index += 1 + if scores[idx] < threshold or math.isnan(scores[idx]): + index -= 1 + continue + first_line = True + while str_count(txt) >= img_w // font_size - 4: + tmp = txt + txt = tmp[: img_w // font_size - 4] + if first_line: + new_txt = str(index) + ": " + txt + first_line = False + else: + new_txt = " " + txt + draw_txt.text((0, gap * count), new_txt, txt_color, font=font) + txt = tmp[img_w // font_size - 4 :] + if count >= img_h // gap - 1: + txt_img_list.append(np.array(blank_img)) + blank_img, draw_txt = create_blank_img() + count = 0 + count += 1 + if first_line: + new_txt = str(index) + ": " + txt + " " + "%.3f" % (scores[idx]) + else: + new_txt = " " + txt + " " + "%.3f" % (scores[idx]) + draw_txt.text((0, gap * count), new_txt, txt_color, font=font) + # whether add new blank img or not + if count >= img_h // gap - 1 and idx + 1 < len(texts): + txt_img_list.append(np.array(blank_img)) + blank_img, draw_txt = create_blank_img() + count = 0 + count += 1 + txt_img_list.append(np.array(blank_img)) + if len(txt_img_list) == 1: + blank_img = np.array(txt_img_list[0]) + else: + blank_img = np.concatenate(txt_img_list, axis=1) + return np.array(blank_img) + + +def base64_to_cv2(b64str): + import base64 + + data = base64.b64decode(b64str.encode("utf8")) + data = np.frombuffer(data, np.uint8) + data = cv2.imdecode(data, cv2.IMREAD_COLOR) + return data + + +def draw_boxes(image, boxes, scores=None, drop_score=0.5): + if scores is None: + scores = [1] * len(boxes) + for box, score in zip(boxes, scores): + if score < drop_score: + continue + box = np.reshape(np.array(box), [-1, 1, 2]).astype(np.int64) + image = cv2.polylines(np.array(image), [box], True, (255, 0, 0), 2) + return image + + +def get_rotate_crop_image(img, points): + """ + img_height, img_width = img.shape[0:2] + left = int(np.min(points[:, 0])) + right = int(np.max(points[:, 0])) + top = int(np.min(points[:, 1])) + bottom = int(np.max(points[:, 1])) + img_crop = img[top:bottom, left:right, :].copy() + points[:, 0] = points[:, 0] - left + points[:, 1] = points[:, 1] - top + """ + assert len(points) == 4, "shape of points must be 4*2" + img_crop_width = int( + max( + np.linalg.norm(points[0] - points[1]), np.linalg.norm(points[2] - points[3]) + ) + ) + img_crop_height = int( + max( + np.linalg.norm(points[0] - points[3]), np.linalg.norm(points[1] - points[2]) + ) + ) + pts_std = np.float32( + [ + [0, 0], + [img_crop_width, 0], + [img_crop_width, img_crop_height], + [0, img_crop_height], + ] + ) + M = cv2.getPerspectiveTransform(points, pts_std) + dst_img = cv2.warpPerspective( + img, + M, + (img_crop_width, img_crop_height), + borderMode=cv2.BORDER_REPLICATE, + flags=cv2.INTER_CUBIC, + ) + dst_img_height, dst_img_width = dst_img.shape[0:2] + if dst_img_height * 1.0 / dst_img_width >= 1.5: + dst_img = np.rot90(dst_img) + return dst_img + + +def get_minarea_rect_crop(img, points): + bounding_box = cv2.minAreaRect(np.array(points).astype(np.int32)) + points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0]) + + index_a, index_b, index_c, index_d = 0, 1, 2, 3 + if points[1][1] > points[0][1]: + index_a = 0 + index_d = 1 + else: + index_a = 1 + index_d = 0 + if points[3][1] > points[2][1]: + index_b = 2 + index_c = 3 + else: + index_b = 3 + index_c = 2 + + box = [points[index_a], points[index_b], points[index_c], points[index_d]] + crop_img = get_rotate_crop_image(img, np.array(box)) + return crop_img + + +def slice_generator(image, horizontal_stride, vertical_stride, maximum_slices=500): + if not isinstance(image, np.ndarray): + image = np.array(image) + + image_h, image_w = image.shape[:2] + vertical_num_slices = (image_h + vertical_stride - 1) // vertical_stride + horizontal_num_slices = (image_w + horizontal_stride - 1) // horizontal_stride + + assert ( + vertical_num_slices > 0 + ), f"Invalid number ({vertical_num_slices}) of vertical slices" + + assert ( + horizontal_num_slices > 0 + ), f"Invalid number ({horizontal_num_slices}) of horizontal slices" + + if vertical_num_slices >= maximum_slices: + recommended_vertical_stride = max(1, image_h // maximum_slices) + 1 + assert ( + False + ), f"Too computationally expensive with {vertical_num_slices} slices, try a higher vertical stride (recommended minimum: {recommended_vertical_stride})" + + if horizontal_num_slices >= maximum_slices: + recommended_horizontal_stride = max(1, image_w // maximum_slices) + 1 + assert ( + False + ), f"Too computationally expensive with {horizontal_num_slices} slices, try a higher horizontal stride (recommended minimum: {recommended_horizontal_stride})" + + for v_slice_idx in range(vertical_num_slices): + v_start = max(0, (v_slice_idx * vertical_stride)) + v_end = min(((v_slice_idx + 1) * vertical_stride), image_h) + vertical_slice = image[v_start:v_end, :] + for h_slice_idx in range(horizontal_num_slices): + h_start = max(0, (h_slice_idx * horizontal_stride)) + h_end = min(((h_slice_idx + 1) * horizontal_stride), image_w) + horizontal_slice = vertical_slice[:, h_start:h_end] + + yield (horizontal_slice, v_start, h_start) + + +def calculate_box_extents(box): + min_x = box[0][0] + max_x = box[1][0] + min_y = box[0][1] + max_y = box[2][1] + return min_x, max_x, min_y, max_y + + +def merge_boxes(box1, box2, x_threshold, y_threshold): + min_x1, max_x1, min_y1, max_y1 = calculate_box_extents(box1) + min_x2, max_x2, min_y2, max_y2 = calculate_box_extents(box2) + + if ( + abs(min_y1 - min_y2) <= y_threshold + and abs(max_y1 - max_y2) <= y_threshold + and abs(max_x1 - min_x2) <= x_threshold + ): + new_xmin = min(min_x1, min_x2) + new_xmax = max(max_x1, max_x2) + new_ymin = min(min_y1, min_y2) + new_ymax = max(max_y1, max_y2) + return [ + [new_xmin, new_ymin], + [new_xmax, new_ymin], + [new_xmax, new_ymax], + [new_xmin, new_ymax], + ] + else: + return None + + +def merge_fragmented(boxes, x_threshold=10, y_threshold=10): + merged_boxes = [] + visited = set() + + for i, box1 in enumerate(boxes): + if i in visited: + continue + + merged_box = [point[:] for point in box1] + + for j, box2 in enumerate(boxes[i + 1 :], start=i + 1): + if j not in visited: + merged_result = merge_boxes( + merged_box, box2, x_threshold=x_threshold, y_threshold=y_threshold + ) + if merged_result: + merged_box = merged_result + visited.add(j) + + merged_boxes.append(merged_box) + + if len(merged_boxes) == len(boxes): + return np.array(merged_boxes) + else: + return merge_fragmented(merged_boxes, x_threshold, y_threshold) + + +def check_gpu(use_gpu): + if use_gpu and ( + not paddle.is_compiled_with_cuda() or paddle.device.get_device() == "cpu" + ): + use_gpu = False + return use_gpu + + +if __name__ == "__main__": + pass diff --git a/tools/infer_cls.py b/tools/infer_cls.py new file mode 100755 index 0000000..6c26ff4 --- /dev/null +++ b/tools/infer_cls.py @@ -0,0 +1,84 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import paddle + +from ppocr.data import create_operators, transform +from ppocr.modeling.architectures import build_model +from ppocr.postprocess import build_post_process +from ppocr.utils.save_load import load_model +from ppocr.utils.utility import get_image_file_list +import tools.program as program + + +def main(): + global_config = config["Global"] + + # build post process + post_process_class = build_post_process(config["PostProcess"], global_config) + + # build model + model = build_model(config["Architecture"]) + + load_model(config, model) + + # create data ops + transforms = [] + for op in config["Eval"]["dataset"]["transforms"]: + op_name = list(op)[0] + if "Label" in op_name: + continue + elif op_name == "KeepKeys": + op[op_name]["keep_keys"] = ["image"] + elif op_name == "SSLRotateResize": + op[op_name]["mode"] = "test" + transforms.append(op) + global_config["infer_mode"] = True + ops = create_operators(transforms, global_config) + + model.eval() + for file in get_image_file_list(config["Global"]["infer_img"]): + logger.info("infer_img: {}".format(file)) + with open(file, "rb") as f: + img = f.read() + data = {"image": img} + batch = transform(data, ops) + + images = np.expand_dims(batch[0], axis=0) + images = paddle.to_tensor(images) + preds = model(images) + post_result = post_process_class(preds) + for rec_result in post_result: + logger.info("\t result: {}".format(rec_result)) + logger.info("success!") + + +if __name__ == "__main__": + config, device, logger, vdl_writer = program.preprocess() + main() diff --git a/tools/infer_det.py b/tools/infer_det.py new file mode 100755 index 0000000..3c001c5 --- /dev/null +++ b/tools/infer_det.py @@ -0,0 +1,143 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import json +import paddle + +from ppocr.data import create_operators, transform +from ppocr.modeling.architectures import build_model +from ppocr.postprocess import build_post_process +from ppocr.utils.save_load import load_model +from ppocr.utils.utility import get_image_file_list +import tools.program as program + + +def draw_det_res(dt_boxes, config, img, img_name, save_path): + import cv2 + + src_im = img + for box in dt_boxes: + box = np.array(box).astype(np.int32).reshape((-1, 1, 2)) + cv2.polylines(src_im, [box], True, color=(255, 255, 0), thickness=2) + if not os.path.exists(save_path): + os.makedirs(save_path) + save_path = os.path.join(save_path, os.path.basename(img_name)) + cv2.imwrite(save_path, src_im) + logger.info("The detected Image saved in {}".format(save_path)) + + +@paddle.no_grad() +def main(): + global_config = config["Global"] + + # build model + model = build_model(config["Architecture"]) + + load_model(config, model) + # build post process + post_process_class = build_post_process(config["PostProcess"]) + + # create data ops + transforms = [] + for op in config["Eval"]["dataset"]["transforms"]: + op_name = list(op)[0] + if "Label" in op_name: + continue + elif op_name == "KeepKeys": + op[op_name]["keep_keys"] = ["image", "shape"] + transforms.append(op) + + ops = create_operators(transforms, global_config) + + save_res_path = config["Global"]["save_res_path"] + if not os.path.exists(os.path.dirname(save_res_path)): + os.makedirs(os.path.dirname(save_res_path)) + + model.eval() + infer_img = config["Global"]["infer_img"] + # Support txt file containing one image path per line + if infer_img.endswith(".txt") and os.path.isfile(infer_img): + with open(infer_img, "r") as f: + img_file_list = [line.strip() for line in f if line.strip()] + else: + img_file_list = get_image_file_list(infer_img) + with open(save_res_path, "wb") as fout: + for file in img_file_list: + logger.info("infer_img: {}".format(file)) + with open(file, "rb") as f: + img = f.read() + data = {"image": img} + batch = transform(data, ops) + + images = np.expand_dims(batch[0], axis=0) + shape_list = np.expand_dims(batch[1], axis=0) + images = paddle.to_tensor(images) + preds = model(images) + post_result = post_process_class(preds, shape_list) + + src_img = cv2.imread(file) + + dt_boxes_json = [] + # parser boxes if post_result is dict + if isinstance(post_result, dict): + det_box_json = {} + for k in post_result.keys(): + boxes = post_result[k][0]["points"] + dt_boxes_list = [] + for box in boxes: + tmp_json = {"transcription": ""} + tmp_json["points"] = np.array(box).tolist() + dt_boxes_list.append(tmp_json) + det_box_json[k] = dt_boxes_list + save_det_path = os.path.dirname( + config["Global"]["save_res_path"] + ) + "/det_results_{}/".format(k) + draw_det_res(boxes, config, src_img, file, save_det_path) + else: + boxes = post_result[0]["points"] + dt_boxes_json = [] + # write result + for box in boxes: + tmp_json = {"transcription": ""} + tmp_json["points"] = np.array(box).tolist() + dt_boxes_json.append(tmp_json) + save_det_path = ( + os.path.dirname(config["Global"]["save_res_path"]) + "/det_results/" + ) + draw_det_res(boxes, config, src_img, file, save_det_path) + otstr = file + "\t" + json.dumps(dt_boxes_json) + "\n" + fout.write(otstr.encode()) + + logger.info("success!") + + +if __name__ == "__main__": + config, device, logger, vdl_writer = program.preprocess() + main() diff --git a/tools/infer_e2e.py b/tools/infer_e2e.py new file mode 100755 index 0000000..02cd68a --- /dev/null +++ b/tools/infer_e2e.py @@ -0,0 +1,170 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import json +import paddle + +from ppocr.data import create_operators, transform +from ppocr.modeling.architectures import build_model +from ppocr.postprocess import build_post_process +from ppocr.utils.save_load import load_model +from ppocr.utils.utility import get_image_file_list +import tools.program as program +from PIL import Image, ImageDraw, ImageFont +import math + + +def draw_e2e_res_for_chinese( + image, boxes, txts, config, img_name, font_path="./doc/simfang.ttf" +): + h, w = image.height, image.width + img_left = image.copy() + img_right = Image.new("RGB", (w, h), (255, 255, 255)) + + import random + + random.seed(0) + draw_left = ImageDraw.Draw(img_left) + draw_right = ImageDraw.Draw(img_right) + for idx, (box, txt) in enumerate(zip(boxes, txts)): + box = np.array(box) + box = [tuple(x) for x in box] + color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) + draw_left.polygon(box, fill=color) + draw_right.polygon(box, outline=color) + font = ImageFont.truetype(font_path, 15, encoding="utf-8") + draw_right.text([box[0][0], box[0][1]], txt, fill=(0, 0, 0), font=font) + img_left = Image.blend(image, img_left, 0.5) + img_show = Image.new("RGB", (w * 2, h), (255, 255, 255)) + img_show.paste(img_left, (0, 0, w, h)) + img_show.paste(img_right, (w, 0, w * 2, h)) + + save_e2e_path = os.path.dirname(config["Global"]["save_res_path"]) + "/e2e_results/" + if not os.path.exists(save_e2e_path): + os.makedirs(save_e2e_path) + save_path = os.path.join(save_e2e_path, os.path.basename(img_name)) + cv2.imwrite(save_path, np.array(img_show)[:, :, ::-1]) + logger.info("The e2e Image saved in {}".format(save_path)) + + +def draw_e2e_res(dt_boxes, strs, config, img, img_name): + if len(dt_boxes) > 0: + src_im = img + for box, str in zip(dt_boxes, strs): + box = box.astype(np.int32).reshape((-1, 1, 2)) + cv2.polylines(src_im, [box], True, color=(255, 255, 0), thickness=2) + cv2.putText( + src_im, + str, + org=(int(box[0, 0, 0]), int(box[0, 0, 1])), + fontFace=cv2.FONT_HERSHEY_COMPLEX, + fontScale=0.7, + color=(0, 255, 0), + thickness=1, + ) + save_det_path = ( + os.path.dirname(config["Global"]["save_res_path"]) + "/e2e_results/" + ) + if not os.path.exists(save_det_path): + os.makedirs(save_det_path) + save_path = os.path.join(save_det_path, os.path.basename(img_name)) + cv2.imwrite(save_path, src_im) + logger.info("The e2e Image saved in {}".format(save_path)) + + +def main(): + global_config = config["Global"] + + # build model + model = build_model(config["Architecture"]) + + load_model(config, model) + + # build post process + post_process_class = build_post_process(config["PostProcess"], global_config) + + # create data ops + transforms = [] + for op in config["Eval"]["dataset"]["transforms"]: + op_name = list(op)[0] + if "Label" in op_name: + continue + elif op_name == "KeepKeys": + op[op_name]["keep_keys"] = ["image", "shape"] + transforms.append(op) + + ops = create_operators(transforms, global_config) + + save_res_path = config["Global"]["save_res_path"] + if not os.path.exists(os.path.dirname(save_res_path)): + os.makedirs(os.path.dirname(save_res_path)) + + model.eval() + with open(save_res_path, "wb") as fout: + for file in get_image_file_list(config["Global"]["infer_img"]): + logger.info("infer_img: {}".format(file)) + with open(file, "rb") as f: + img = f.read() + data = {"image": img} + batch = transform(data, ops) + images = np.expand_dims(batch[0], axis=0) + shape_list = np.expand_dims(batch[1], axis=0) + images = paddle.to_tensor(images) + preds = model(images) + post_result = post_process_class(preds, shape_list) + points, strs = post_result["points"], post_result["texts"] + # write result + dt_boxes_json = [] + for poly, str in zip(points, strs): + tmp_json = {"transcription": str} + tmp_json["points"] = poly.tolist() + dt_boxes_json.append(tmp_json) + otstr = file + "\t" + json.dumps(dt_boxes_json) + "\n" + fout.write(otstr.encode()) + src_img = cv2.imread(file) + if global_config["infer_visual_type"] == "EN": + draw_e2e_res(points, strs, config, src_img, file) + elif global_config["infer_visual_type"] == "CN": + src_img = Image.fromarray(cv2.cvtColor(src_img, cv2.COLOR_BGR2RGB)) + draw_e2e_res_for_chinese( + src_img, + points, + strs, + config, + file, + font_path="./doc/fonts/simfang.ttf", + ) + + logger.info("success!") + + +if __name__ == "__main__": + config, device, logger, vdl_writer = program.preprocess() + main() diff --git a/tools/infer_kie.py b/tools/infer_kie.py new file mode 100755 index 0000000..f3efaf9 --- /dev/null +++ b/tools/infer_kie.py @@ -0,0 +1,186 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np +import paddle.nn.functional as F + +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import cv2 +import paddle + +from ppocr.data import create_operators, transform +from ppocr.modeling.architectures import build_model +from ppocr.utils.save_load import load_model +import tools.program as program +import time + + +def read_class_list(filepath): + ret = {} + with open(filepath, "r") as f: + lines = f.readlines() + for idx, line in enumerate(lines): + ret[idx] = line.strip("\n") + return ret + + +def draw_kie_result(batch, node, idx_to_cls, count): + img = batch[6].copy() + boxes = batch[7] + h, w = img.shape[:2] + pred_img = np.ones((h, w * 2, 3), dtype=np.uint8) * 255 + max_value, max_idx = paddle.max(node, -1), paddle.argmax(node, -1) + node_pred_label = max_idx.numpy().tolist() + node_pred_score = max_value.numpy().tolist() + + for i, box in enumerate(boxes): + if i >= len(node_pred_label): + break + new_box = [ + [box[0], box[1]], + [box[2], box[1]], + [box[2], box[3]], + [box[0], box[3]], + ] + Pts = np.array([new_box], np.int32) + cv2.polylines( + img, [Pts.reshape((-1, 1, 2))], True, color=(255, 255, 0), thickness=1 + ) + x_min = int(min([point[0] for point in new_box])) + y_min = int(min([point[1] for point in new_box])) + + pred_label = node_pred_label[i] + if pred_label in idx_to_cls: + pred_label = idx_to_cls[pred_label] + pred_score = "{:.2f}".format(node_pred_score[i]) + text = pred_label + "(" + pred_score + ")" + cv2.putText( + pred_img, + text, + (x_min * 2, y_min), + cv2.FONT_HERSHEY_SIMPLEX, + 0.5, + (255, 0, 0), + 1, + ) + vis_img = np.ones((h, w * 3, 3), dtype=np.uint8) * 255 + vis_img[:, :w] = img + vis_img[:, w:] = pred_img + save_kie_path = os.path.dirname(config["Global"]["save_res_path"]) + "/kie_results/" + if not os.path.exists(save_kie_path): + os.makedirs(save_kie_path) + save_path = os.path.join(save_kie_path, str(count) + ".png") + cv2.imwrite(save_path, vis_img) + logger.info("The Kie Image saved in {}".format(save_path)) + + +def write_kie_result(fout, node, data): + """ + Write infer result to output file, sorted by the predict label of each line. + The format keeps the same as the input with additional score attribute. + """ + import json + + label = data["label"] + annotations = json.loads(label) + max_value, max_idx = paddle.max(node, -1), paddle.argmax(node, -1) + node_pred_label = max_idx.numpy().tolist() + node_pred_score = max_value.numpy().tolist() + res = [] + for i, label in enumerate(node_pred_label): + pred_score = "{:.2f}".format(node_pred_score[i]) + pred_res = { + "label": label, + "transcription": annotations[i]["transcription"], + "score": pred_score, + "points": annotations[i]["points"], + } + res.append(pred_res) + res.sort(key=lambda x: x["label"]) + fout.writelines([json.dumps(res, ensure_ascii=False) + "\n"]) + + +def main(): + global_config = config["Global"] + + # build model + model = build_model(config["Architecture"]) + load_model(config, model) + + # create data ops + transforms = [] + for op in config["Eval"]["dataset"]["transforms"]: + transforms.append(op) + + data_dir = config["Eval"]["dataset"]["data_dir"] + + ops = create_operators(transforms, global_config) + + save_res_path = config["Global"]["save_res_path"] + class_path = config["Global"]["class_path"] + idx_to_cls = read_class_list(class_path) + os.makedirs(os.path.dirname(save_res_path), exist_ok=True) + + model.eval() + + warmup_times = 0 + count_t = [] + with open(save_res_path, "w") as fout: + with open(config["Global"]["infer_img"], "rb") as f: + lines = f.readlines() + for index, data_line in enumerate(lines): + if index == 10: + warmup_t = time.time() + data_line = data_line.decode("utf-8") + substr = data_line.strip("\n").split("\t") + img_path, label = data_dir + "/" + substr[0], substr[1] + data = {"img_path": img_path, "label": label} + with open(data["img_path"], "rb") as f: + img = f.read() + data["image"] = img + st = time.time() + batch = transform(data, ops) + batch_pred = [0] * len(batch) + for i in range(len(batch)): + batch_pred[i] = paddle.to_tensor(np.expand_dims(batch[i], axis=0)) + st = time.time() + node, edge = model(batch_pred) + node = F.softmax(node, -1) + count_t.append(time.time() - st) + draw_kie_result(batch, node, idx_to_cls, index) + write_kie_result(fout, node, data) + fout.close() + logger.info("success!") + logger.info( + "It took {} s for predict {} images.".format(np.sum(count_t), len(count_t)) + ) + ips = len(count_t[warmup_times:]) / np.sum(count_t[warmup_times:]) + logger.info("The ips is {} images/s".format(ips)) + + +if __name__ == "__main__": + config, device, logger, vdl_writer = program.preprocess() + main() diff --git a/tools/infer_kie_token_ser.py b/tools/infer_kie_token_ser.py new file mode 100755 index 0000000..8fe25ac --- /dev/null +++ b/tools/infer_kie_token_ser.py @@ -0,0 +1,178 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" +import cv2 +import json +import paddle + +from ppocr.data import create_operators, transform +from ppocr.modeling.architectures import build_model +from ppocr.postprocess import build_post_process +from ppocr.utils.save_load import load_model +from ppocr.utils.visual import draw_ser_results +from ppocr.utils.utility import get_image_file_list, load_vqa_bio_label_maps +import tools.program as program + + +def to_tensor(data): + import numbers + from collections import defaultdict + + data_dict = defaultdict(list) + to_tensor_idxs = [] + + for idx, v in enumerate(data): + if isinstance(v, (np.ndarray, paddle.Tensor, numbers.Number)): + if idx not in to_tensor_idxs: + to_tensor_idxs.append(idx) + data_dict[idx].append(v) + for idx in to_tensor_idxs: + data_dict[idx] = paddle.to_tensor(data_dict[idx]) + return list(data_dict.values()) + + +class SerPredictor(object): + def __init__(self, config): + global_config = config["Global"] + self.algorithm = config["Architecture"]["algorithm"] + + # build post process + self.post_process_class = build_post_process( + config["PostProcess"], global_config + ) + + # build model + self.model = build_model(config["Architecture"]) + + load_model(config, self.model, model_type=config["Architecture"]["model_type"]) + + from paddleocr import PaddleOCR + + self.ocr_engine = PaddleOCR( + use_angle_cls=False, + show_log=False, + rec_model_dir=global_config.get("kie_rec_model_dir", None), + det_model_dir=global_config.get("kie_det_model_dir", None), + use_gpu=global_config["use_gpu"], + ) + + # create data ops + transforms = [] + for op in config["Eval"]["dataset"]["transforms"]: + op_name = list(op)[0] + if "Label" in op_name: + op[op_name]["ocr_engine"] = self.ocr_engine + elif op_name == "KeepKeys": + op[op_name]["keep_keys"] = [ + "input_ids", + "bbox", + "attention_mask", + "token_type_ids", + "image", + "labels", + "segment_offset_id", + "ocr_info", + "entities", + ] + + transforms.append(op) + if config["Global"].get("infer_mode", None) is None: + global_config["infer_mode"] = True + self.ops = create_operators( + config["Eval"]["dataset"]["transforms"], global_config + ) + self.model.eval() + + def __call__(self, data): + with open(data["img_path"], "rb") as f: + img = f.read() + data["image"] = img + batch = transform(data, self.ops) + batch = to_tensor(batch) + preds = self.model(batch) + + post_result = self.post_process_class( + preds, segment_offset_ids=batch[6], ocr_infos=batch[7] + ) + return post_result, batch + + +if __name__ == "__main__": + config, device, logger, vdl_writer = program.preprocess() + os.makedirs(config["Global"]["save_res_path"], exist_ok=True) + + ser_engine = SerPredictor(config) + + if config["Global"].get("infer_mode", None) is False: + data_dir = config["Eval"]["dataset"]["data_dir"] + with open(config["Global"]["infer_img"], "rb") as f: + infer_imgs = f.readlines() + else: + infer_imgs = get_image_file_list(config["Global"]["infer_img"]) + + with open( + os.path.join(config["Global"]["save_res_path"], "infer_results.txt"), + "w", + encoding="utf-8", + ) as fout: + for idx, info in enumerate(infer_imgs): + if config["Global"].get("infer_mode", None) is False: + data_line = info.decode("utf-8") + substr = data_line.strip("\n").split("\t") + img_path = os.path.join(data_dir, substr[0]) + data = {"img_path": img_path, "label": substr[1]} + else: + img_path = info + data = {"img_path": img_path} + + save_img_path = os.path.join( + config["Global"]["save_res_path"], + os.path.splitext(os.path.basename(img_path))[0] + "_ser.jpg", + ) + + result, _ = ser_engine(data) + result = result[0] + fout.write( + img_path + + "\t" + + json.dumps( + { + "ocr_info": result, + }, + ensure_ascii=False, + ) + + "\n" + ) + img_res = draw_ser_results(img_path, result) + cv2.imwrite(save_img_path, img_res) + + logger.info( + "process: [{}/{}], save result to {}".format( + idx, len(infer_imgs), save_img_path + ) + ) diff --git a/tools/infer_kie_token_ser_re.py b/tools/infer_kie_token_ser_re.py new file mode 100755 index 0000000..a9589ca --- /dev/null +++ b/tools/infer_kie_token_ser_re.py @@ -0,0 +1,226 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" +import cv2 +import json +import paddle +import paddle.distributed as dist + +from ppocr.data import create_operators, transform +from ppocr.modeling.architectures import build_model +from ppocr.postprocess import build_post_process +from ppocr.utils.save_load import load_model +from ppocr.utils.visual import draw_re_results +from ppocr.utils.logging import get_logger +from ppocr.utils.utility import get_image_file_list, load_vqa_bio_label_maps, print_dict +from tools.program import ArgsParser, load_config, merge_config +from tools.infer_kie_token_ser import SerPredictor + + +class ReArgsParser(ArgsParser): + def __init__(self): + super(ReArgsParser, self).__init__() + self.add_argument( + "-c_ser", "--config_ser", help="ser configuration file to use" + ) + self.add_argument( + "-o_ser", "--opt_ser", nargs="+", help="set ser configuration options " + ) + + def parse_args(self, argv=None): + args = super(ReArgsParser, self).parse_args(argv) + assert ( + args.config_ser is not None + ), "Please specify --config_ser=ser_configure_file_path." + args.opt_ser = self._parse_opt(args.opt_ser) + return args + + +def make_input(ser_inputs, ser_results): + entities_labels = {"HEADER": 0, "QUESTION": 1, "ANSWER": 2} + batch_size, max_seq_len = ser_inputs[0].shape[:2] + entities = ser_inputs[8][0] + ser_results = ser_results[0] + assert len(entities) == len(ser_results) + + # entities + start = [] + end = [] + label = [] + entity_idx_dict = {} + for i, (res, entity) in enumerate(zip(ser_results, entities)): + if res["pred"] == "O": + continue + entity_idx_dict[len(start)] = i + start.append(entity["start"]) + end.append(entity["end"]) + label.append(entities_labels[res["pred"]]) + + entities = np.full([max_seq_len + 1, 3], fill_value=-1, dtype=np.int64) + entities[0, 0] = len(start) + entities[1 : len(start) + 1, 0] = start + entities[0, 1] = len(end) + entities[1 : len(end) + 1, 1] = end + entities[0, 2] = len(label) + entities[1 : len(label) + 1, 2] = label + + # relations + head = [] + tail = [] + for i in range(len(label)): + for j in range(len(label)): + if label[i] == 1 and label[j] == 2: + head.append(i) + tail.append(j) + + relations = np.full([len(head) + 1, 2], fill_value=-1, dtype=np.int64) + relations[0, 0] = len(head) + relations[1 : len(head) + 1, 0] = head + relations[0, 1] = len(tail) + relations[1 : len(tail) + 1, 1] = tail + + entities = np.expand_dims(entities, axis=0) + entities = np.repeat(entities, batch_size, axis=0) + relations = np.expand_dims(relations, axis=0) + relations = np.repeat(relations, batch_size, axis=0) + + # remove ocr_info segment_offset_id and label in ser input + if isinstance(ser_inputs[0], paddle.Tensor): + entities = paddle.to_tensor(entities) + relations = paddle.to_tensor(relations) + ser_inputs = ser_inputs[:5] + [entities, relations] + + entity_idx_dict_batch = [] + for b in range(batch_size): + entity_idx_dict_batch.append(entity_idx_dict) + return ser_inputs, entity_idx_dict_batch + + +class SerRePredictor(object): + def __init__(self, config, ser_config): + global_config = config["Global"] + if "infer_mode" in global_config: + ser_config["Global"]["infer_mode"] = global_config["infer_mode"] + + self.ser_engine = SerPredictor(ser_config) + + # init re model + + # build post process + self.post_process_class = build_post_process( + config["PostProcess"], global_config + ) + + # build model + self.model = build_model(config["Architecture"]) + + load_model(config, self.model, model_type=config["Architecture"]["model_type"]) + + self.model.eval() + + def __call__(self, data): + ser_results, ser_inputs = self.ser_engine(data) + re_input, entity_idx_dict_batch = make_input(ser_inputs, ser_results) + if self.model.backbone.use_visual_backbone is False: + re_input.pop(4) + preds = self.model(re_input) + post_result = self.post_process_class( + preds, ser_results=ser_results, entity_idx_dict_batch=entity_idx_dict_batch + ) + return post_result + + +def preprocess(): + FLAGS = ReArgsParser().parse_args() + config = load_config(FLAGS.config) + config = merge_config(config, FLAGS.opt) + + ser_config = load_config(FLAGS.config_ser) + ser_config = merge_config(ser_config, FLAGS.opt_ser) + + logger = get_logger() + + # check if set use_gpu=True in paddlepaddle cpu version + use_gpu = config["Global"]["use_gpu"] + + device = "gpu:{}".format(dist.ParallelEnv().dev_id) if use_gpu else "cpu" + device = paddle.set_device(device) + + logger.info("{} re config {}".format("*" * 10, "*" * 10)) + print_dict(config, logger) + logger.info("\n") + logger.info("{} ser config {}".format("*" * 10, "*" * 10)) + print_dict(ser_config, logger) + logger.info("train with paddle {} and device {}".format(paddle.__version__, device)) + return config, ser_config, device, logger + + +if __name__ == "__main__": + config, ser_config, device, logger = preprocess() + os.makedirs(config["Global"]["save_res_path"], exist_ok=True) + + ser_re_engine = SerRePredictor(config, ser_config) + + if config["Global"].get("infer_mode", None) is False: + data_dir = config["Eval"]["dataset"]["data_dir"] + with open(config["Global"]["infer_img"], "rb") as f: + infer_imgs = f.readlines() + else: + infer_imgs = get_image_file_list(config["Global"]["infer_img"]) + + with open( + os.path.join(config["Global"]["save_res_path"], "infer_results.txt"), + "w", + encoding="utf-8", + ) as fout: + for idx, info in enumerate(infer_imgs): + if config["Global"].get("infer_mode", None) is False: + data_line = info.decode("utf-8") + substr = data_line.strip("\n").split("\t") + img_path = os.path.join(data_dir, substr[0]) + data = {"img_path": img_path, "label": substr[1]} + else: + img_path = info + data = {"img_path": img_path} + + save_img_path = os.path.join( + config["Global"]["save_res_path"], + os.path.splitext(os.path.basename(img_path))[0] + "_ser_re.jpg", + ) + + result = ser_re_engine(data) + result = result[0] + fout.write(img_path + "\t" + json.dumps(result, ensure_ascii=False) + "\n") + img_res = draw_re_results(img_path, result) + cv2.imwrite(save_img_path, img_res) + + logger.info( + "process: [{}/{}], save result to {}".format( + idx, len(infer_imgs), save_img_path + ) + ) diff --git a/tools/infer_rec.py b/tools/infer_rec.py new file mode 100755 index 0000000..be00880 --- /dev/null +++ b/tools/infer_rec.py @@ -0,0 +1,232 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +import os +import sys +import json + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import paddle + +from ppocr.data import create_operators, transform +from ppocr.modeling.architectures import build_model +from ppocr.postprocess import build_post_process +from ppocr.utils.save_load import load_model +from ppocr.utils.utility import get_image_file_list +import tools.program as program + + +def main(): + global_config = config["Global"] + if config["Architecture"].get("algorithm") in [ + "UniMERNet", + "PP-FormulaNet-S", + "PP-FormulaNet-L", + "PP-FormulaNet_plus-S", + "PP-FormulaNet_plus-M", + "PP-FormulaNet_plus-L", + ]: + config["PostProcess"]["is_infer"] = True + # build post process + post_process_class = build_post_process(config["PostProcess"], global_config) + + # build model + if hasattr(post_process_class, "character"): + char_num = len(getattr(post_process_class, "character")) + if config["Architecture"]["algorithm"] in [ + "Distillation", + ]: # distillation model + for key in config["Architecture"]["Models"]: + if ( + config["Architecture"]["Models"][key]["Head"]["name"] == "MultiHead" + ): # multi head + out_channels_list = {} + if config["PostProcess"]["name"] == "DistillationSARLabelDecode": + char_num = char_num - 2 + if config["PostProcess"]["name"] == "DistillationNRTRLabelDecode": + char_num = char_num - 3 + out_channels_list["CTCLabelDecode"] = char_num + out_channels_list["SARLabelDecode"] = char_num + 2 + out_channels_list["NRTRLabelDecode"] = char_num + 3 + config["Architecture"]["Models"][key]["Head"][ + "out_channels_list" + ] = out_channels_list + else: + config["Architecture"]["Models"][key]["Head"][ + "out_channels" + ] = char_num + elif config["Architecture"]["Head"]["name"] == "MultiHead": # multi head + out_channels_list = {} + char_num = len(getattr(post_process_class, "character")) + if config["PostProcess"]["name"] == "SARLabelDecode": + char_num = char_num - 2 + if config["PostProcess"]["name"] == "NRTRLabelDecode": + char_num = char_num - 3 + out_channels_list["CTCLabelDecode"] = char_num + out_channels_list["SARLabelDecode"] = char_num + 2 + out_channels_list["NRTRLabelDecode"] = char_num + 3 + config["Architecture"]["Head"]["out_channels_list"] = out_channels_list + else: # base rec model + config["Architecture"]["Head"]["out_channels"] = char_num + + if config["Architecture"].get("algorithm") in ["LaTeXOCR"]: + config["Architecture"]["Backbone"]["is_predict"] = True + config["Architecture"]["Backbone"]["is_export"] = True + config["Architecture"]["Head"]["is_export"] = True + + model = build_model(config["Architecture"]) + + load_model(config, model) + + # create data ops + transforms = [] + for op in config["Eval"]["dataset"]["transforms"]: + op_name = list(op)[0] + if "Label" in op_name: + continue + elif op_name in ["RecResizeImg"]: + op[op_name]["infer_mode"] = True + elif op_name == "KeepKeys": + if config["Architecture"]["algorithm"] == "SRN": + op[op_name]["keep_keys"] = [ + "image", + "encoder_word_pos", + "gsrm_word_pos", + "gsrm_slf_attn_bias1", + "gsrm_slf_attn_bias2", + ] + elif config["Architecture"]["algorithm"] == "SAR": + op[op_name]["keep_keys"] = ["image", "valid_ratio"] + elif config["Architecture"]["algorithm"] == "RobustScanner": + op[op_name]["keep_keys"] = ["image", "valid_ratio", "word_positions"] + else: + op[op_name]["keep_keys"] = ["image"] + transforms.append(op) + global_config["infer_mode"] = True + ops = create_operators(transforms, global_config) + + save_res_path = config["Global"].get( + "save_res_path", "./output/rec/predicts_rec.txt" + ) + if not os.path.exists(os.path.dirname(save_res_path)): + os.makedirs(os.path.dirname(save_res_path)) + + model.eval() + + infer_imgs = config["Global"]["infer_img"] + infer_list = config["Global"].get("infer_list", None) + with open(save_res_path, "w") as fout: + for file in get_image_file_list(infer_imgs, infer_list=infer_list): + logger.info("infer_img: {}".format(file)) + with open(file, "rb") as f: + img = f.read() + if config["Architecture"]["algorithm"] in [ + "UniMERNet", + "PP-FormulaNet-S", + "PP-FormulaNet-L", + "PP-FormulaNet_plus-S", + "PP-FormulaNet_plus-M", + "PP-FormulaNet_plus-L", + ]: + data = {"image": img, "filename": file} + else: + data = {"image": img} + batch = transform(data, ops) + if config["Architecture"]["algorithm"] == "SRN": + encoder_word_pos_list = np.expand_dims(batch[1], axis=0) + gsrm_word_pos_list = np.expand_dims(batch[2], axis=0) + gsrm_slf_attn_bias1_list = np.expand_dims(batch[3], axis=0) + gsrm_slf_attn_bias2_list = np.expand_dims(batch[4], axis=0) + + others = [ + paddle.to_tensor(encoder_word_pos_list), + paddle.to_tensor(gsrm_word_pos_list), + paddle.to_tensor(gsrm_slf_attn_bias1_list), + paddle.to_tensor(gsrm_slf_attn_bias2_list), + ] + if config["Architecture"]["algorithm"] == "SAR": + valid_ratio = np.expand_dims(batch[-1], axis=0) + img_metas = [paddle.to_tensor(valid_ratio)] + if config["Architecture"]["algorithm"] == "RobustScanner": + valid_ratio = np.expand_dims(batch[1], axis=0) + word_positions = np.expand_dims(batch[2], axis=0) + img_metas = [ + paddle.to_tensor(valid_ratio), + paddle.to_tensor(word_positions), + ] + if config["Architecture"]["algorithm"] == "CAN": + image_mask = paddle.ones( + (np.expand_dims(batch[0], axis=0).shape), dtype="float32" + ) + label = paddle.ones((1, 36), dtype="int64") + images = np.expand_dims(batch[0], axis=0) + images = paddle.to_tensor(images) + if config["Architecture"]["algorithm"] == "SRN": + preds = model(images, others) + elif config["Architecture"]["algorithm"] == "SAR": + preds = model(images, img_metas) + elif config["Architecture"]["algorithm"] == "RobustScanner": + preds = model(images, img_metas) + elif config["Architecture"]["algorithm"] == "CAN": + preds = model([images, image_mask, label]) + else: + preds = model(images) + post_result = post_process_class(preds) + info = None + if isinstance(post_result, dict): + rec_info = dict() + for key in post_result: + if len(post_result[key][0]) >= 2: + rec_info[key] = { + "label": post_result[key][0][0], + "score": float(post_result[key][0][1]), + } + info = json.dumps(rec_info, ensure_ascii=False) + elif isinstance(post_result, list) and isinstance(post_result[0], int): + # for RFLearning CNT branch + info = str(post_result[0]) + elif config["Architecture"]["algorithm"] in [ + "LaTeXOCR", + "UniMERNet", + "PP-FormulaNet-S", + "PP-FormulaNet-L", + "PP-FormulaNet_plus-S", + "PP-FormulaNet_plus-M", + "PP-FormulaNet_plus-L", + ]: + info = str(post_result[0]) + else: + if len(post_result[0]) >= 2: + info = post_result[0][0] + "\t" + str(post_result[0][1]) + + if info is not None: + logger.info("\t result: {}".format(info)) + fout.write(file + "\t" + info + "\n") + logger.info("success!") + + +if __name__ == "__main__": + config, device, logger, vdl_writer = program.preprocess() + main() diff --git a/tools/infer_sr.py b/tools/infer_sr.py new file mode 100755 index 0000000..4285625 --- /dev/null +++ b/tools/infer_sr.py @@ -0,0 +1,101 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +import os +import sys +import json +from PIL import Image +import cv2 + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, __dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import paddle + +from ppocr.data import create_operators, transform +from ppocr.modeling.architectures import build_model +from ppocr.postprocess import build_post_process +from ppocr.utils.save_load import load_model +from ppocr.utils.utility import get_image_file_list +import tools.program as program + + +def main(): + global_config = config["Global"] + + # build post process + post_process_class = build_post_process(config["PostProcess"], global_config) + + # sr transform + config["Architecture"]["Transform"]["infer_mode"] = True + + model = build_model(config["Architecture"]) + + load_model(config, model) + + # create data ops + transforms = [] + for op in config["Eval"]["dataset"]["transforms"]: + op_name = list(op)[0] + if "Label" in op_name: + continue + elif op_name in ["SRResize"]: + op[op_name]["infer_mode"] = True + elif op_name == "KeepKeys": + op[op_name]["keep_keys"] = ["img_lr"] + transforms.append(op) + global_config["infer_mode"] = True + ops = create_operators(transforms, global_config) + + save_visual_path = config["Global"].get("save_visual", "infer_result/") + if not os.path.exists(os.path.dirname(save_visual_path)): + os.makedirs(os.path.dirname(save_visual_path)) + + model.eval() + for file in get_image_file_list(config["Global"]["infer_img"]): + logger.info("infer_img: {}".format(file)) + img = Image.open(file).convert("RGB") + data = {"image_lr": img} + batch = transform(data, ops) + images = np.expand_dims(batch[0], axis=0) + images = paddle.to_tensor(images) + + preds = model(images) + sr_img = preds["sr_img"][0] + lr_img = preds["lr_img"][0] + fm_sr = (sr_img.numpy() * 255).transpose(1, 2, 0).astype(np.uint8) + fm_lr = (lr_img.numpy() * 255).transpose(1, 2, 0).astype(np.uint8) + img_name_pure = os.path.split(file)[-1] + cv2.imwrite( + "{}/sr_{}".format(save_visual_path, img_name_pure), fm_sr[:, :, ::-1] + ) + logger.info( + "The visualized image saved in infer_result/sr_{}".format(img_name_pure) + ) + + logger.info("success!") + + +if __name__ == "__main__": + config, device, logger, vdl_writer = program.preprocess() + main() diff --git a/tools/infer_table.py b/tools/infer_table.py new file mode 100644 index 0000000..c386cef --- /dev/null +++ b/tools/infer_table.py @@ -0,0 +1,120 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numpy as np + +import os +import sys +import json + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +os.environ["FLAGS_allocator_strategy"] = "auto_growth" + +import paddle +from paddle.jit import to_static + +from ppocr.data import create_operators, transform +from ppocr.modeling.architectures import build_model +from ppocr.postprocess import build_post_process +from ppocr.utils.save_load import load_model +from ppocr.utils.utility import get_image_file_list +from ppocr.utils.visual import draw_rectangle +from tools.infer.utility import draw_boxes +import tools.program as program +import cv2 + + +@paddle.no_grad() +def main(config, device, logger, vdl_writer): + global_config = config["Global"] + + # build post process + post_process_class = build_post_process(config["PostProcess"], global_config) + + # build model + if hasattr(post_process_class, "character"): + config["Architecture"]["Head"]["out_channels"] = len( + getattr(post_process_class, "character") + ) + + model = build_model(config["Architecture"]) + algorithm = config["Architecture"]["algorithm"] + + load_model(config, model) + + # create data ops + transforms = [] + for op in config["Eval"]["dataset"]["transforms"]: + op_name = list(op)[0] + if "Encode" in op_name: + continue + if op_name == "KeepKeys": + op[op_name]["keep_keys"] = ["image", "shape"] + transforms.append(op) + + global_config["infer_mode"] = True + ops = create_operators(transforms, global_config) + + save_res_path = config["Global"]["save_res_path"] + os.makedirs(save_res_path, exist_ok=True) + + model.eval() + with open( + os.path.join(save_res_path, "infer.txt"), mode="w", encoding="utf-8" + ) as f_w: + for file in get_image_file_list(config["Global"]["infer_img"]): + logger.info("infer_img: {}".format(file)) + with open(file, "rb") as f: + img = f.read() + data = {"image": img} + batch = transform(data, ops) + images = np.expand_dims(batch[0], axis=0) + shape_list = np.expand_dims(batch[1], axis=0) + + images = paddle.to_tensor(images) + preds = model(images) + post_result = post_process_class(preds, [shape_list]) + + structure_str_list = post_result["structure_batch_list"][0] + bbox_list = post_result["bbox_batch_list"][0] + structure_str_list = structure_str_list[0] + structure_str_list = ( + ["", "", ""] + + structure_str_list + + ["
    ", "", ""] + ) + bbox_list_str = json.dumps(bbox_list.tolist()) + + logger.info("result: {}, {}".format(structure_str_list, bbox_list_str)) + f_w.write("result: {}, {}\n".format(structure_str_list, bbox_list_str)) + + if len(bbox_list) > 0 and len(bbox_list[0]) == 4: + img = draw_rectangle(file, bbox_list) + else: + img = draw_boxes(cv2.imread(file), bbox_list) + cv2.imwrite(os.path.join(save_res_path, os.path.basename(file)), img) + logger.info("save result to {}".format(save_res_path)) + logger.info("success!") + + +if __name__ == "__main__": + config, device, logger, vdl_writer = program.preprocess() + main(config, device, logger, vdl_writer) diff --git a/tools/naive_sync_bn.py b/tools/naive_sync_bn.py new file mode 100644 index 0000000..50da685 --- /dev/null +++ b/tools/naive_sync_bn.py @@ -0,0 +1,120 @@ +# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import paddle.distributed as dist +import math +import paddle +import paddle.nn as nn + + +class _AllReduce(paddle.autograd.PyLayer): + @staticmethod + def forward(ctx, input): + input_list = [paddle.zeros_like(input) for k in range(dist.get_world_size())] + # Use allgather instead of allreduce since I don't trust in-place operations .. + dist.all_gather(input_list, input, sync_op=True) + inputs = paddle.stack(input_list, axis=0) + return paddle.sum(inputs, axis=0) + + @staticmethod + def backward(ctx, grad_output): + dist.all_reduce(grad_output, sync_op=True) + return grad_output + + +def differentiable_all_reduce(input): + """ + Differentiable counterpart of `dist.all_reduce`. + """ + if ( + not dist.is_available() + or not dist.is_initialized() + or dist.get_world_size() == 1 + ): + return input + return _AllReduce.apply(input) + + +class NaiveSyncBatchNorm(nn.BatchNorm2D): + def __init__(self, *args, stats_mode="", **kwargs): + super().__init__(*args, **kwargs) + assert stats_mode in ["", "N"] + self._stats_mode = stats_mode + + def forward(self, input): + if dist.get_world_size() == 1 or not self.training: + return super().forward(input) + + B, C = input.shape[0], input.shape[1] + + mean = paddle.mean(input, axis=[0, 2, 3]) + meansqr = paddle.mean(input * input, axis=[0, 2, 3]) + + if self._stats_mode == "": + assert ( + B > 0 + ), 'SyncBatchNorm(stats_mode="") does not support zero batch size.' + vec = paddle.concat([mean, meansqr], axis=0) + vec = differentiable_all_reduce(vec) * (1.0 / dist.get_world_size()) + mean, meansqr = paddle.split(vec, [C, C]) + momentum = ( + 1 - self._momentum + ) # NOTE: paddle has reverse momentum definition + else: + if B == 0: + vec = paddle.zeros([2 * C + 1], dtype=mean.dtype) + vec = vec + input.sum() # make sure there is gradient w.r.t input + else: + vec = paddle.concat( + [ + mean, + meansqr, + paddle.ones([1], dtype=mean.dtype), + ], + axis=0, + ) + vec = differentiable_all_reduce(vec * B) + + total_batch = vec[-1].detach() + momentum = total_batch.clip(max=1) * ( + 1 - self._momentum + ) # no update if total_batch is 0 + mean, meansqr, _ = paddle.split( + vec / total_batch.clip(min=1), [C, C, int(vec.shape[0] - 2 * C)] + ) # avoid div-by-zero + + var = meansqr - mean * mean + invstd = paddle.rsqrt(var + self._epsilon) + scale = self.weight * invstd + bias = self.bias - mean * scale + scale = scale.reshape([1, -1, 1, 1]) + bias = bias.reshape([1, -1, 1, 1]) + + tmp_mean = self._mean + momentum * (mean.detach() - self._mean) + self._mean.set_value(tmp_mean) + tmp_variance = self._variance + (momentum * (var.detach() - self._variance)) + self._variance.set_value(tmp_variance) + ret = input * scale + bias + return ret + + +def convert_syncbn(model): + for n, m in model.named_children(): + if isinstance(m, nn.layer.norm._BatchNormBase): + syncbn = NaiveSyncBatchNorm( + m._num_features, m._momentum, m._epsilon, m._weight_attr, m._bias_attr + ) + setattr(model, n, syncbn) + else: + convert_syncbn(m) diff --git a/tools/program.py b/tools/program.py new file mode 100755 index 0000000..5db1ddd --- /dev/null +++ b/tools/program.py @@ -0,0 +1,1010 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import gc +import sys +import platform +import yaml +import time +import datetime +import paddle +import paddle.distributed as dist +from tqdm import tqdm +import cv2 +import numpy as np +import copy +from argparse import ArgumentParser, RawDescriptionHelpFormatter + +from ppocr.utils.stats import TrainingStats +from ppocr.utils.save_load import save_model +from ppocr.utils.utility import print_dict, AverageMeter +from ppocr.utils.logging import get_logger +from ppocr.utils.loggers import WandbLogger, Loggers +from ppocr.utils import profiler +from ppocr.data import build_dataloader +from ppocr.utils.export_model import export + + +class ArgsParser(ArgumentParser): + def __init__(self): + super(ArgsParser, self).__init__(formatter_class=RawDescriptionHelpFormatter) + self.add_argument("-c", "--config", help="configuration file to use") + self.add_argument("-o", "--opt", nargs="+", help="set configuration options") + self.add_argument( + "-p", + "--profiler_options", + type=str, + default=None, + help="The option of profiler, which should be in format " + '"key1=value1;key2=value2;key3=value3".', + ) + + def parse_args(self, argv=None): + args = super(ArgsParser, self).parse_args(argv) + assert args.config is not None, "Please specify --config=configure_file_path." + args.opt = self._parse_opt(args.opt) + return args + + def _parse_opt(self, opts): + config = {} + if not opts: + return config + for s in opts: + s = s.strip() + k, v = s.split("=") + config[k] = yaml.load(v, Loader=yaml.SafeLoader) + return config + + +def load_config(file_path): + """ + Load config from yml/yaml file. + Args: + file_path (str): Path of the config file to be loaded. + Returns: global config + """ + _, ext = os.path.splitext(file_path) + assert ext in [".yml", ".yaml"], "only support yaml files for now" + config = yaml.load(open(file_path, "rb"), Loader=yaml.SafeLoader) + return config + + +def merge_config(config, opts): + """ + Merge config into global config. + Args: + config (dict): Config to be merged. + Returns: global config + """ + for key, value in opts.items(): + if "." not in key: + if isinstance(value, dict) and key in config: + config[key].update(value) + else: + config[key] = value + else: + sub_keys = key.split(".") + assert sub_keys[0] in config, ( + "the sub_keys can only be one of global_config: {}, but get: " + "{}, please check your running command".format( + config.keys(), sub_keys[0] + ) + ) + cur = config[sub_keys[0]] + for idx, sub_key in enumerate(sub_keys[1:]): + if idx == len(sub_keys) - 2: + cur[sub_key] = value + else: + cur = cur[sub_key] + return config + + +def check_device( + use_gpu, + use_xpu=False, + use_npu=False, + use_mlu=False, + use_gcu=False, + use_iluvatar_gpu=False, + use_metax_gpu=False, +): + """ + Log error and exit when set use_gpu=true in paddlepaddle + cpu version. + """ + err = ( + "Config {} cannot be set as true while your paddle " + "is not compiled with {} ! \nPlease try: \n" + "\t1. Install paddlepaddle to run model on {} \n" + "\t2. Set {} as false in config file to run " + "model on CPU" + ) + + try: + if use_gpu and use_xpu: + print("use_xpu and use_gpu can not both be true.") + if use_gpu and not paddle.is_compiled_with_cuda(): + print(err.format("use_gpu", "cuda", "gpu", "use_gpu")) + sys.exit(1) + if use_xpu and not paddle.device.is_compiled_with_xpu(): + print(err.format("use_xpu", "xpu", "xpu", "use_xpu")) + sys.exit(1) + if use_npu: + if ( + int(paddle.version.major) != 0 + and int(paddle.version.major) <= 2 + and int(paddle.version.minor) <= 4 + ): + if not paddle.device.is_compiled_with_npu(): + print(err.format("use_npu", "npu", "npu", "use_npu")) + sys.exit(1) + # is_compiled_with_npu() has been updated after paddle-2.4 + else: + if not paddle.device.is_compiled_with_custom_device("npu"): + print(err.format("use_npu", "npu", "npu", "use_npu")) + sys.exit(1) + if use_mlu and not paddle.device.is_compiled_with_mlu(): + print(err.format("use_mlu", "mlu", "mlu", "use_mlu")) + sys.exit(1) + if use_gcu and not paddle.device.is_compiled_with_custom_device("gcu"): + print(err.format("use_gcu", "gcu", "gcu", "use_gcu")) + sys.exit(1) + if use_metax_gpu and not paddle.device.is_compiled_with_custom_device( + "metax_gpu" + ): + print( + err.format("use_metax_gpu", "metax_gpu", "metax_gpu", "use_metax_gpu") + ) + sys.exit(1) + + except Exception as e: + pass + + +def to_float32(preds): + if isinstance(preds, dict): + for k in preds: + if isinstance(preds[k], dict) or isinstance(preds[k], list): + preds[k] = to_float32(preds[k]) + elif isinstance(preds[k], paddle.Tensor): + preds[k] = preds[k].astype(paddle.float32) + elif isinstance(preds, list): + for k in range(len(preds)): + if isinstance(preds[k], dict): + preds[k] = to_float32(preds[k]) + elif isinstance(preds[k], list): + preds[k] = to_float32(preds[k]) + elif isinstance(preds[k], paddle.Tensor): + preds[k] = preds[k].astype(paddle.float32) + elif isinstance(preds, paddle.Tensor): + preds = preds.astype(paddle.float32) + return preds + + +def train( + config, + train_dataloader, + valid_dataloader, + device, + model, + loss_class, + optimizer, + lr_scheduler, + post_process_class, + eval_class, + pre_best_model_dict, + logger, + step_pre_epoch, + log_writer=None, + scaler=None, + amp_level="O2", + amp_custom_black_list=[], + amp_custom_white_list=[], + amp_dtype="float16", + wd_scheduler=None, + ema=None, +): + cal_metric_during_train = config["Global"].get("cal_metric_during_train", False) + calc_epoch_interval = config["Global"].get("calc_epoch_interval", 1) + log_smooth_window = config["Global"]["log_smooth_window"] + epoch_num = config["Global"]["epoch_num"] + print_batch_step = config["Global"]["print_batch_step"] + eval_batch_step = config["Global"]["eval_batch_step"] + eval_batch_epoch = config["Global"].get("eval_batch_epoch", None) + profiler_options = config["profiler_options"] + print_mem_info = config["Global"].get("print_mem_info", True) + uniform_output_enabled = config["Global"].get("uniform_output_enabled", False) + + global_step = 0 + if "global_step" in pre_best_model_dict: + global_step = pre_best_model_dict["global_step"] + start_eval_step = 0 + if isinstance(eval_batch_step, list) and len(eval_batch_step) >= 2: + start_eval_step = eval_batch_step[0] if not eval_batch_epoch else 0 + eval_batch_step = ( + eval_batch_step[1] + if not eval_batch_epoch + else step_pre_epoch * eval_batch_epoch + ) + if len(valid_dataloader) == 0: + logger.info( + "No Images in eval dataset, evaluation during training " + "will be disabled" + ) + start_eval_step = 1e111 + logger.info( + "During the training process, after the {}th iteration, " + "an evaluation is run every {} iterations".format( + start_eval_step, eval_batch_step + ) + ) + save_epoch_step = config["Global"]["save_epoch_step"] + save_model_dir = config["Global"]["save_model_dir"] + if not os.path.exists(save_model_dir): + os.makedirs(save_model_dir) + main_indicator = eval_class.main_indicator + best_model_dict = {main_indicator: 0} + best_model_dict.update(pre_best_model_dict) + train_stats = TrainingStats(log_smooth_window, ["lr"]) + model_average = False + model.train() + + use_srn = config["Architecture"]["algorithm"] == "SRN" + extra_input_models = [ + "SRN", + "NRTR", + "SAR", + "SEED", + "SVTR", + "SVTR_LCNet", + "SPIN", + "VisionLAN", + "RobustScanner", + "RFL", + "DRRG", + "SATRN", + "SVTR_HGNet", + "ParseQ", + "CPPD", + ] + extra_input = False + if config["Architecture"]["algorithm"] == "Distillation": + for key in config["Architecture"]["Models"]: + extra_input = ( + extra_input + or config["Architecture"]["Models"][key]["algorithm"] + in extra_input_models + ) + else: + extra_input = config["Architecture"]["algorithm"] in extra_input_models + try: + model_type = config["Architecture"]["model_type"] + except: + model_type = None + + algorithm = config["Architecture"]["algorithm"] + + start_epoch = ( + best_model_dict["start_epoch"] if "start_epoch" in best_model_dict else 1 + ) + + total_samples = 0 + train_reader_cost = 0.0 + train_batch_cost = 0.0 + reader_start = time.time() + eta_meter = AverageMeter() + + max_iter = ( + len(train_dataloader) - 1 + if platform.system() == "Windows" + else len(train_dataloader) + ) + + for epoch in range(start_epoch, epoch_num + 1): + if train_dataloader.dataset.need_reset: + # Update index mapping + shared epoch (no disk I/O, no worker restart) + train_dataloader.dataset.reset_data_lines(seed=epoch, epoch=epoch) + max_iter = ( + len(train_dataloader) - 1 + if platform.system() == "Windows" + else len(train_dataloader) + ) + # Match original behavior: fresh DistributedBatchSampler always + # starts with self.epoch=0 for its internal np shuffle seed + if hasattr(train_dataloader.batch_sampler, "set_epoch"): + train_dataloader.batch_sampler.set_epoch(0) + + for idx, batch in enumerate(train_dataloader): + model.train() + profiler.add_profiler_step(profiler_options) + train_reader_cost += time.time() - reader_start + if idx >= max_iter: + break + lr = optimizer.get_lr() + images = batch[0] + if use_srn: + model_average = True + # use amp + if scaler: + with paddle.amp.auto_cast( + level=amp_level, + custom_black_list=amp_custom_black_list, + custom_white_list=amp_custom_white_list, + dtype=amp_dtype, + ): + if model_type == "table" or extra_input: + preds = model(images, data=batch[1:]) + elif model_type in ["kie"]: + preds = model(batch) + elif algorithm in ["CAN"]: + preds = model(batch[:3]) + elif algorithm in [ + "LaTeXOCR", + "UniMERNet", + "PP-FormulaNet-S", + "PP-FormulaNet-L", + "PP-FormulaNet_plus-S", + "PP-FormulaNet_plus-M", + "PP-FormulaNet_plus-L", + ]: + preds = model(batch) + else: + preds = model(images) + preds = to_float32(preds) + loss = loss_class(preds, batch) + avg_loss = loss["loss"] + scaled_avg_loss = scaler.scale(avg_loss) + scaled_avg_loss.backward() + scaler.minimize(optimizer, scaled_avg_loss) + else: + if model_type == "table" or extra_input: + preds = model(images, data=batch[1:]) + elif model_type in ["kie", "sr"]: + preds = model(batch) + elif algorithm in ["CAN"]: + preds = model(batch[:3]) + elif algorithm in [ + "LaTeXOCR", + "UniMERNet", + "PP-FormulaNet-S", + "PP-FormulaNet-L", + "PP-FormulaNet_plus-S", + "PP-FormulaNet_plus-M", + "PP-FormulaNet_plus-L", + ]: + preds = model(batch) + else: + preds = model(images) + loss = loss_class(preds, batch) + avg_loss = loss["loss"] + avg_loss.backward() + optimizer.step() + + optimizer.clear_grad() + + if ema is not None: + ema.update(model) + + if ( + cal_metric_during_train and epoch % calc_epoch_interval == 0 + ): # only rec and cls need + batch = [item.numpy() for item in batch] + if model_type in ["kie", "sr"]: + eval_class(preds, batch) + elif model_type in ["table"]: + post_result = post_process_class(preds, batch) + eval_class(post_result, batch) + elif algorithm in ["CAN"]: + model_type = "can" + eval_class(preds[0], batch[2:], epoch_reset=(idx == 0)) + elif algorithm in ["LaTeXOCR"]: + model_type = "latexocr" + post_result = post_process_class(preds, batch[1], mode="train") + eval_class(post_result[0], post_result[1], epoch_reset=(idx == 0)) + elif algorithm in ["UniMERNet"]: + model_type = "unimernet" + post_result = post_process_class(preds[0], batch[1], mode="train") + eval_class(post_result[0], post_result[1], epoch_reset=(idx == 0)) + elif algorithm in [ + "PP-FormulaNet-S", + "PP-FormulaNet-L", + "PP-FormulaNet_plus-S", + "PP-FormulaNet_plus-M", + "PP-FormulaNet_plus-L", + ]: + model_type = "pp_formulanet" + post_result = post_process_class(preds[0], batch[1], mode="train") + eval_class(post_result[0], post_result[1], epoch_reset=(idx == 0)) + else: + if config["Loss"]["name"] in [ + "MultiLoss", + "MultiLoss_v2", + ]: # for multi head loss + post_result = post_process_class( + preds["ctc"], batch[1] + ) # for CTC head out + elif config["Loss"]["name"] in ["VLLoss"]: + post_result = post_process_class(preds, batch[1], batch[-1]) + else: + post_result = post_process_class(preds, batch[1]) + eval_class(post_result, batch) + metric = eval_class.get_metric() + train_stats.update(metric) + + train_batch_time = time.time() - reader_start + train_batch_cost += train_batch_time + eta_meter.update(train_batch_time) + global_step += 1 + total_samples += len(images) + + if not isinstance(lr_scheduler, float): + lr_scheduler.step() + + if wd_scheduler is not None: + wd_scheduler.step() + + # logger and visualdl + stats = { + k: float(v) if v.shape == [] else v.numpy().mean() + for k, v in loss.items() + } + stats["lr"] = lr + if wd_scheduler is not None: + stats["wd"] = wd_scheduler.get_wd() + train_stats.update(stats) + + if log_writer is not None and dist.get_rank() == 0: + log_writer.log_metrics( + metrics=train_stats.get(), prefix="TRAIN", step=global_step + ) + + if (global_step > 0 and global_step % print_batch_step == 0) or ( + idx >= len(train_dataloader) - 1 + ): + logs = train_stats.log() + + eta_sec = ( + (epoch_num + 1 - epoch) * len(train_dataloader) - idx - 1 + ) * eta_meter.avg + eta_sec_format = str(datetime.timedelta(seconds=int(eta_sec))) + max_mem_reserved_str = "" + max_mem_allocated_str = "" + if paddle.device.is_compiled_with_cuda() and print_mem_info: + max_mem_reserved_str = f", max_mem_reserved: {paddle.device.cuda.max_memory_reserved() // (1024 ** 2)} MB," + max_mem_allocated_str = f" max_mem_allocated: {paddle.device.cuda.max_memory_allocated() // (1024 ** 2)} MB" + strs = ( + "epoch: [{}/{}], global_step: {}, {}, avg_reader_cost: " + "{:.5f} s, avg_batch_cost: {:.5f} s, avg_samples: {}, " + "ips: {:.5f} samples/s, eta: {}{}{}".format( + epoch, + epoch_num, + global_step, + logs, + train_reader_cost / print_batch_step, + train_batch_cost / print_batch_step, + total_samples / print_batch_step, + total_samples / train_batch_cost, + eta_sec_format, + max_mem_reserved_str, + max_mem_allocated_str, + ) + ) + logger.info(strs) + + total_samples = 0 + train_reader_cost = 0.0 + train_batch_cost = 0.0 + # eval + if ( + global_step > start_eval_step + and (global_step - start_eval_step) % eval_batch_step == 0 + and dist.get_rank() == 0 + ): + if model_average: + Model_Average = paddle.incubate.ModelAverage( + 0.15, + parameters=model.parameters(), + min_average_window=10000, + max_average_window=15625, + ) + Model_Average.apply() + # Apply EMA weights for eval and save + _ema_train_state = None + if ema is not None: + _ema_train_state = copy.deepcopy(model.state_dict()) + model.set_state_dict(ema.apply()) + cur_metric = eval( + model, + valid_dataloader, + post_process_class, + eval_class, + model_type, + extra_input=extra_input, + scaler=scaler, + amp_level=amp_level, + amp_custom_black_list=amp_custom_black_list, + amp_custom_white_list=amp_custom_white_list, + amp_dtype=amp_dtype, + ) + cur_metric_str = "cur metric, {}".format( + ", ".join(["{}: {}".format(k, v) for k, v in cur_metric.items()]) + ) + logger.info(cur_metric_str) + + # logger metric + if log_writer is not None: + log_writer.log_metrics( + metrics=cur_metric, prefix="EVAL", step=global_step + ) + + if cur_metric[main_indicator] >= best_model_dict[main_indicator]: + best_model_dict.update(cur_metric) + best_model_dict["best_epoch"] = epoch + prefix = "best_accuracy" + if uniform_output_enabled: + export( + config, + model, + os.path.join(save_model_dir, prefix, "inference"), + ) + gc.collect() + model_info = {"epoch": epoch, "metric": best_model_dict} + else: + model_info = None + save_model( + model, + optimizer, + ( + os.path.join(save_model_dir, prefix) + if uniform_output_enabled + else save_model_dir + ), + logger, + config, + is_best=True, + prefix=prefix, + ema=ema, + train_state=_ema_train_state, + save_model_info=model_info, + best_model_dict=best_model_dict, + epoch=epoch, + global_step=global_step, + ) + best_str = "best metric, {}".format( + ", ".join( + ["{}: {}".format(k, v) for k, v in best_model_dict.items()] + ) + ) + logger.info(best_str) + # logger best metric + if log_writer is not None: + log_writer.log_metrics( + metrics={ + "best_{}".format(main_indicator): best_model_dict[ + main_indicator + ] + }, + prefix="EVAL", + step=global_step, + ) + + log_writer.log_model( + is_best=True, prefix="best_accuracy", metadata=best_model_dict + ) + + # Restore training weights after eval/save + if _ema_train_state is not None: + model.set_state_dict(_ema_train_state) + + reader_start = time.time() + if dist.get_rank() == 0: + prefix = "latest" + # Apply EMA weights for save + _ema_train_state_latest = None + if ema is not None: + _ema_train_state_latest = copy.deepcopy(model.state_dict()) + model.set_state_dict(ema.apply()) + if uniform_output_enabled: + export(config, model, os.path.join(save_model_dir, prefix, "inference")) + gc.collect() + model_info = {"epoch": epoch, "metric": best_model_dict} + else: + model_info = None + save_model( + model, + optimizer, + ( + os.path.join(save_model_dir, prefix) + if uniform_output_enabled + else save_model_dir + ), + logger, + config, + is_best=False, + prefix=prefix, + ema=ema, + train_state=_ema_train_state_latest, + save_model_info=model_info, + best_model_dict=best_model_dict, + epoch=epoch, + global_step=global_step, + ) + # Restore training weights + if _ema_train_state_latest is not None: + model.set_state_dict(_ema_train_state_latest) + + if log_writer is not None: + log_writer.log_model(is_best=False, prefix="latest") + + if dist.get_rank() == 0 and epoch > 0 and epoch % save_epoch_step == 0: + prefix = "iter_epoch_{}".format(epoch) + # Apply EMA weights for save + _ema_train_state_iter = None + if ema is not None: + _ema_train_state_iter = copy.deepcopy(model.state_dict()) + model.set_state_dict(ema.apply()) + if uniform_output_enabled: + export(config, model, os.path.join(save_model_dir, prefix, "inference")) + gc.collect() + model_info = {"epoch": epoch, "metric": best_model_dict} + else: + model_info = None + save_model( + model, + optimizer, + ( + os.path.join(save_model_dir, prefix) + if uniform_output_enabled + else save_model_dir + ), + logger, + config, + is_best=False, + prefix=prefix, + ema=ema, + train_state=_ema_train_state_iter, + save_model_info=model_info, + best_model_dict=best_model_dict, + epoch=epoch, + global_step=global_step, + done_flag=epoch == config["Global"]["epoch_num"], + ) + # Restore training weights + if _ema_train_state_iter is not None: + model.set_state_dict(_ema_train_state_iter) + if log_writer is not None: + log_writer.log_model( + is_best=False, prefix="iter_epoch_{}".format(epoch) + ) + + # Reset reader_start so next epoch's first batch doesn't include + # save_model time in avg_reader_cost + reader_start = time.time() + + best_str = "best metric, {}".format( + ", ".join(["{}: {}".format(k, v) for k, v in best_model_dict.items()]) + ) + logger.info(best_str) + if dist.get_rank() == 0 and log_writer is not None: + log_writer.close() + return + + +def eval( + model, + valid_dataloader, + post_process_class, + eval_class, + model_type=None, + extra_input=False, + scaler=None, + amp_level="O2", + amp_custom_black_list=[], + amp_custom_white_list=[], + amp_dtype="float16", +): + model.eval() + with paddle.no_grad(): + total_frame = 0.0 + total_time = 0.0 + pbar = tqdm( + total=len(valid_dataloader), desc="eval model:", position=0, leave=True + ) + max_iter = ( + len(valid_dataloader) - 1 + if platform.system() == "Windows" + else len(valid_dataloader) + ) + sum_images = 0 + for idx, batch in enumerate(valid_dataloader): + if idx >= max_iter: + break + images = batch[0] + start = time.time() + + # use amp + if scaler: + with paddle.amp.auto_cast( + level=amp_level, + custom_black_list=amp_custom_black_list, + dtype=amp_dtype, + ): + if model_type == "table" or extra_input: + preds = model(images, data=batch[1:]) + elif model_type in ["kie"]: + preds = model(batch) + elif model_type in ["can"]: + preds = model(batch[:3]) + elif model_type in ["latexocr"]: + preds = model(batch) + elif model_type in ["sr"]: + preds = model(batch) + sr_img = preds["sr_img"] + lr_img = preds["lr_img"] + else: + preds = model(images) + preds = to_float32(preds) + else: + if model_type == "table" or extra_input: + preds = model(images, data=batch[1:]) + elif model_type in ["kie"]: + preds = model(batch) + elif model_type in ["can"]: + preds = model(batch[:3]) + elif model_type in ["latexocr", "unimernet", "pp_formulanet"]: + preds = model(batch) + elif model_type in ["sr"]: + preds = model(batch) + sr_img = preds["sr_img"] + lr_img = preds["lr_img"] + else: + preds = model(images) + + batch_numpy = [] + for item in batch: + if isinstance(item, paddle.Tensor): + batch_numpy.append(item.numpy()) + else: + batch_numpy.append(item) + # Obtain usable results from post-processing methods + total_time += time.time() - start + # Evaluate the results of the current batch + if model_type in ["table", "kie"]: + if post_process_class is None: + eval_class(preds, batch_numpy) + else: + post_result = post_process_class(preds, batch_numpy) + eval_class(post_result, batch_numpy) + elif model_type in ["sr"]: + eval_class(preds, batch_numpy) + elif model_type in ["can"]: + eval_class(preds[0], batch_numpy[2:], epoch_reset=(idx == 0)) + elif model_type in ["latexocr", "unimernet", "pp_formulanet"]: + post_result = post_process_class(preds, batch[1], "eval") + eval_class(post_result[0], post_result[1], epoch_reset=(idx == 0)) + else: + post_result = post_process_class(preds, batch_numpy[1]) + eval_class(post_result, batch_numpy) + + pbar.update(1) + total_frame += len(images) + sum_images += 1 + # Get final metric,eg. acc or hmean + metric = eval_class.get_metric() + + pbar.close() + model.train() + # Avoid ZeroDivisionError + if total_time > 0: + metric["fps"] = total_frame / total_time + else: + metric["fps"] = 0 # or set to a fallback value + return metric + + +def update_center(char_center, post_result, preds): + result, label = post_result + feats, logits = preds + logits = paddle.argmax(logits, axis=-1) + feats = feats.numpy() + logits = logits.numpy() + + for idx_sample in range(len(label)): + if result[idx_sample][0] == label[idx_sample][0]: + feat = feats[idx_sample] + logit = logits[idx_sample] + for idx_time in range(len(logit)): + index = logit[idx_time] + if index in char_center.keys(): + char_center[index][0] = ( + char_center[index][0] * char_center[index][1] + feat[idx_time] + ) / (char_center[index][1] + 1) + char_center[index][1] += 1 + else: + char_center[index] = [feat[idx_time], 1] + return char_center + + +def get_center(model, eval_dataloader, post_process_class): + pbar = tqdm(total=len(eval_dataloader), desc="get center:") + max_iter = ( + len(eval_dataloader) - 1 + if platform.system() == "Windows" + else len(eval_dataloader) + ) + char_center = dict() + for idx, batch in enumerate(eval_dataloader): + if idx >= max_iter: + break + images = batch[0] + start = time.time() + preds = model(images) + + batch = [item.numpy() for item in batch] + # Obtain usable results from post-processing methods + post_result = post_process_class(preds, batch[1]) + + # update char_center + char_center = update_center(char_center, post_result, preds) + pbar.update(1) + + pbar.close() + for key in char_center.keys(): + char_center[key] = char_center[key][0] + return char_center + + +def preprocess(is_train=False): + FLAGS = ArgsParser().parse_args() + profiler_options = FLAGS.profiler_options + config = load_config(FLAGS.config) + config = merge_config(config, FLAGS.opt) + profile_dic = {"profiler_options": FLAGS.profiler_options} + config = merge_config(config, profile_dic) + + if is_train: + # save_config + save_model_dir = config["Global"]["save_model_dir"] + os.makedirs(save_model_dir, exist_ok=True) + with open(os.path.join(save_model_dir, "config.yml"), "w") as f: + yaml.dump(dict(config), f, default_flow_style=False, sort_keys=False) + log_file = "{}/train.log".format(save_model_dir) + else: + log_file = None + + log_ranks = config["Global"].get("log_ranks", "0") + logger = get_logger(log_file=log_file, log_ranks=log_ranks) + + # check if set use_gpu=True in paddlepaddle cpu version + use_gpu = config["Global"].get("use_gpu", False) + use_xpu = config["Global"].get("use_xpu", False) + use_npu = config["Global"].get("use_npu", False) + use_mlu = config["Global"].get("use_mlu", False) + use_gcu = config["Global"].get("use_gcu", False) + use_metax_gpu = config["Global"].get("use_metax_gpu", False) + + use_iluvatar_gpu = config["Global"].get("use_iluvatar_gpu", False) + + alg = config["Architecture"]["algorithm"] + assert alg in [ + "EAST", + "DB", + "SAST", + "Rosetta", + "CRNN", + "STARNet", + "RARE", + "SRN", + "CLS", + "PGNet", + "Distillation", + "NRTR", + "TableAttn", + "SAR", + "PSE", + "SEED", + "SDMGR", + "LayoutXLM", + "LayoutLM", + "LayoutLMv2", + "PREN", + "FCE", + "SVTR", + "SVTR_LCNet", + "ViTSTR", + "ABINet", + "DB++", + "TableMaster", + "SPIN", + "VisionLAN", + "Gestalt", + "SLANet", + "RobustScanner", + "CT", + "RFL", + "DRRG", + "CAN", + "Telescope", + "SATRN", + "SVTR_HGNet", + "ParseQ", + "CPPD", + "LaTeXOCR", + "UniMERNet", + "SLANeXt", + "PP-FormulaNet-S", + "PP-FormulaNet-L", + "PP-FormulaNet_plus-S", + "PP-FormulaNet_plus-M", + "PP-FormulaNet_plus-L", + ] + + if use_xpu: + device = "xpu:{0}".format(os.getenv("FLAGS_selected_xpus", 0)) + elif use_npu: + device = "npu:{0}".format(os.getenv("FLAGS_selected_npus", 0)) + elif use_mlu: + device = "mlu:{0}".format(os.getenv("FLAGS_selected_mlus", 0)) + elif use_gcu: # Use Enflame GCU(General Compute Unit) + device = "gcu:{0}".format(os.getenv("FLAGS_selected_gcus", 0)) + elif use_metax_gpu: # Use Enflame GCU(General Compute Unit) + device = "metax:{0}".format(dist.ParallelEnv().dev_id) + elif use_iluvatar_gpu: + device = "iluvatar_gpu:{0}".format(dist.ParallelEnv().dev_id) + else: + device = "gpu:{}".format(dist.ParallelEnv().dev_id) if use_gpu else "cpu" + check_device( + use_gpu, use_xpu, use_npu, use_mlu, use_gcu, use_iluvatar_gpu, use_metax_gpu + ) + + device = paddle.set_device(device) + + config["Global"]["distributed"] = dist.get_world_size() != 1 + + loggers = [] + + if "use_visualdl" in config["Global"] and config["Global"]["use_visualdl"]: + logger.warning( + "You are using VisualDL, the VisualDL is deprecated and " + "removed in ppocr!" + ) + log_writer = None + if ( + "use_wandb" in config["Global"] and config["Global"]["use_wandb"] + ) or "wandb" in config: + save_dir = config["Global"]["save_model_dir"] + wandb_writer_path = "{}/wandb".format(save_dir) + if "wandb" in config: + wandb_params = config["wandb"] + else: + wandb_params = dict() + wandb_params.update({"save_dir": save_dir}) + log_writer = WandbLogger(**wandb_params, config=config) + loggers.append(log_writer) + else: + log_writer = None + print_dict(config, logger) + + if loggers: + log_writer = Loggers(loggers) + else: + log_writer = None + + logger.info("train with paddle {} and device {}".format(paddle.__version__, device)) + return config, device, logger, log_writer diff --git a/tools/resolve_doc_github_refs.py b/tools/resolve_doc_github_refs.py new file mode 100644 index 0000000..3bde8d9 --- /dev/null +++ b/tools/resolve_doc_github_refs.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +import argparse +from pathlib import Path + + +TEXT_SUFFIXES = {".md", ".yml", ".yaml"} + + +def resolve_placeholders(root, placeholder, source_ref): + if ( + not source_ref + or source_ref.strip() != source_ref + or any(c.isspace() for c in source_ref) + ): + raise ValueError("source_ref must be a non-empty ref without whitespace") + + root = Path(root) + changed = [] + for path in sorted(root.rglob("*")): + if not path.is_file() or path.suffix not in TEXT_SUFFIXES: + continue + content = path.read_text(encoding="utf-8") + if placeholder not in content: + continue + path.write_text(content.replace(placeholder, source_ref), encoding="utf-8") + changed.append(path) + return changed + + +def main(argv=None): + parser = argparse.ArgumentParser( + description="Resolve docs GitHub source-ref placeholders before building docs." + ) + parser.add_argument("--root", default="docs", help="Directory to rewrite.") + parser.add_argument("--placeholder", required=True) + parser.add_argument("--source-ref", required=True) + args = parser.parse_args(argv) + + changed = resolve_placeholders( + args.root, + placeholder=args.placeholder, + source_ref=args.source_ref, + ) + print(f"Resolved {len(changed)} file(s) under {args.root}.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/test_hubserving.py b/tools/test_hubserving.py new file mode 100755 index 0000000..5480107 --- /dev/null +++ b/tools/test_hubserving.py @@ -0,0 +1,162 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.append(os.path.abspath(os.path.join(__dir__, ".."))) + +from ppocr.utils.logging import get_logger + +logger = get_logger() + +import cv2 +import numpy as np +import time +from PIL import Image +from ppocr.utils.utility import get_image_file_list +from tools.infer.utility import draw_ocr, draw_boxes, str2bool +from ppstructure.utility import draw_structure_result +from ppstructure.predict_system import to_excel + +import requests +import json +import base64 + + +def cv2_to_base64(image): + return base64.b64encode(image).decode("utf8") + + +def draw_server_result(image_file, res): + img = cv2.imread(image_file) + image = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) + if len(res) == 0: + return np.array(image) + keys = res[0].keys() + if "text_region" not in keys: # for ocr_rec, draw function is invalid + logger.info("draw function is invalid for ocr_rec!") + return None + elif "text" not in keys: # for ocr_det + logger.info("draw text boxes only!") + boxes = [] + for dno in range(len(res)): + boxes.append(res[dno]["text_region"]) + boxes = np.array(boxes) + draw_img = draw_boxes(image, boxes) + return draw_img + else: # for ocr_system + logger.info("draw boxes and texts!") + boxes = [] + texts = [] + scores = [] + for dno in range(len(res)): + boxes.append(res[dno]["text_region"]) + texts.append(res[dno]["text"]) + scores.append(res[dno]["confidence"]) + boxes = np.array(boxes) + scores = np.array(scores) + draw_img = draw_ocr(image, boxes, texts, scores, draw_txt=True, drop_score=0.5) + return draw_img + + +def save_structure_res(res, save_folder, image_file): + img = cv2.imread(image_file) + excel_save_folder = os.path.join(save_folder, os.path.basename(image_file)) + os.makedirs(excel_save_folder, exist_ok=True) + # save res + with open(os.path.join(excel_save_folder, "res.txt"), "w", encoding="utf8") as f: + for region in res: + if region["type"] == "Table": + excel_path = os.path.join( + excel_save_folder, "{}.xlsx".format(region["bbox"]) + ) + to_excel(region["res"], excel_path) + elif region["type"] == "Figure": + x1, y1, x2, y2 = region["bbox"] + print(region["bbox"]) + roi_img = img[y1:y2, x1:x2, :] + img_path = os.path.join( + excel_save_folder, "{}.jpg".format(region["bbox"]) + ) + cv2.imwrite(img_path, roi_img) + else: + for text_result in region["res"]: + f.write("{}\n".format(json.dumps(text_result))) + + +def main(args): + image_file_list = get_image_file_list(args.image_dir) + is_visualize = False + headers = {"Content-type": "application/json"} + cnt = 0 + total_time = 0 + for image_file in image_file_list: + img = open(image_file, "rb").read() + if img is None: + logger.info("error in loading image:{}".format(image_file)) + continue + img_name = os.path.basename(image_file) + # seed http request + starttime = time.time() + data = {"images": [cv2_to_base64(img)]} + r = requests.post(url=args.server_url, headers=headers, data=json.dumps(data)) + elapse = time.time() - starttime + total_time += elapse + logger.info("Predict time of %s: %.3fs" % (image_file, elapse)) + res = r.json()["results"][0] + logger.info(res) + + if args.visualize: + draw_img = None + if "structure_table" in args.server_url: + to_excel(res["html"], "./{}.xlsx".format(img_name)) + elif "structure_system" in args.server_url: + save_structure_res(res["regions"], args.output, image_file) + else: + draw_img = draw_server_result(image_file, res) + if draw_img is not None: + if not os.path.exists(args.output): + os.makedirs(args.output) + cv2.imwrite( + os.path.join(args.output, os.path.basename(image_file)), + draw_img[:, :, ::-1], + ) + logger.info( + "The visualized image saved in {}".format( + os.path.join(args.output, os.path.basename(image_file)) + ) + ) + cnt += 1 + if cnt % 100 == 0: + logger.info("{} processed".format(cnt)) + logger.info("avg time cost: {}".format(float(total_time) / cnt)) + + +def parse_args(): + import argparse + + parser = argparse.ArgumentParser(description="args for hub serving") + parser.add_argument("--server_url", type=str, required=True) + parser.add_argument("--image_dir", type=str, required=True) + parser.add_argument("--visualize", type=str2bool, default=False) + parser.add_argument("--output", type=str, default="./hubserving_result") + args = parser.parse_args() + return args + + +if __name__ == "__main__": + args = parse_args() + main(args) diff --git a/tools/train.py b/tools/train.py new file mode 100755 index 0000000..fc2b49b --- /dev/null +++ b/tools/train.py @@ -0,0 +1,301 @@ +# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys + +__dir__ = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(__dir__) +sys.path.insert(0, os.path.abspath(os.path.join(__dir__, ".."))) + +import yaml +import paddle +import paddle.distributed as dist + +from ppocr.data import build_dataloader, set_signal_handlers +from ppocr.modeling.architectures import build_model +from ppocr.losses import build_loss +from ppocr.optimizer import build_optimizer +from ppocr.postprocess import build_post_process +from ppocr.metrics import build_metric +from ppocr.utils.save_load import load_model +from ppocr.utils.utility import set_seed +from ppocr.modeling.architectures import apply_to_static +import tools.program as program +import tools.naive_sync_bn as naive_sync_bn + +dist.get_world_size() + + +def main(config, device, logger, vdl_writer): + # init dist environment + if config["Global"]["distributed"]: + dist.init_parallel_env() + + global_config = config["Global"] + + # build dataloader + # NOTE: Do NOT pass seed here. The seed parameter in build_dataloader is used + # as epoch number by set_epoch_as_seed (for adaptive shrink_ratio), not as + # random seed. First construction should use epoch=0 (i.e., seed=None). + # The epoch loop in program.train() handles subsequent updates via + # reset_data_lines(seed=epoch). + set_signal_handlers() + train_dataloader = build_dataloader(config, "Train", device, logger) + if len(train_dataloader) == 0: + logger.error( + "No Images in train dataset, please ensure\n" + + "\t1. The images num in the train label_file_list should be larger than or equal with batch size.\n" + + "\t2. The annotation file and path in the configuration file are provided normally." + ) + return + + if config["Eval"]: + valid_dataloader = build_dataloader(config, "Eval", device, logger) + else: + valid_dataloader = None + step_pre_epoch = len(train_dataloader) + + # build post process + post_process_class = build_post_process(config["PostProcess"], global_config) + + # build model + # for rec algorithm + if hasattr(post_process_class, "character"): + char_num = len(getattr(post_process_class, "character")) + if config["Architecture"]["algorithm"] in [ + "Distillation", + ]: # distillation model + for key in config["Architecture"]["Models"]: + if ( + config["Architecture"]["Models"][key]["Head"]["name"] == "MultiHead" + ): # for multi head + if config["PostProcess"]["name"] == "DistillationSARLabelDecode": + char_num = char_num - 2 + if config["PostProcess"]["name"] == "DistillationNRTRLabelDecode": + char_num = char_num - 3 + out_channels_list = {} + out_channels_list["CTCLabelDecode"] = char_num + # update SARLoss params + if ( + list(config["Loss"]["loss_config_list"][-1].keys())[0] + == "DistillationSARLoss" + ): + config["Loss"]["loss_config_list"][-1]["DistillationSARLoss"][ + "ignore_index" + ] = (char_num + 1) + out_channels_list["SARLabelDecode"] = char_num + 2 + elif any( + "DistillationNRTRLoss" in d + for d in config["Loss"]["loss_config_list"] + ): + out_channels_list["NRTRLabelDecode"] = char_num + 3 + + config["Architecture"]["Models"][key]["Head"][ + "out_channels_list" + ] = out_channels_list + else: + config["Architecture"]["Models"][key]["Head"][ + "out_channels" + ] = char_num + elif config["Architecture"]["Head"]["name"] == "MultiHead": # for multi head + if config["PostProcess"]["name"] == "SARLabelDecode": + char_num = char_num - 2 + if config["PostProcess"]["name"] == "NRTRLabelDecode": + char_num = char_num - 3 + out_channels_list = {} + out_channels_list["CTCLabelDecode"] = char_num + # update SARLoss params + if list(config["Loss"]["loss_config_list"][1].keys())[0] == "SARLoss": + if config["Loss"]["loss_config_list"][1]["SARLoss"] is None: + config["Loss"]["loss_config_list"][1]["SARLoss"] = { + "ignore_index": char_num + 1 + } + else: + config["Loss"]["loss_config_list"][1]["SARLoss"]["ignore_index"] = ( + char_num + 1 + ) + out_channels_list["SARLabelDecode"] = char_num + 2 + elif list(config["Loss"]["loss_config_list"][1].keys())[0] == "NRTRLoss": + out_channels_list["NRTRLabelDecode"] = char_num + 3 + config["Architecture"]["Head"]["out_channels_list"] = out_channels_list + else: # base rec model + config["Architecture"]["Head"]["out_channels"] = char_num + + if config["PostProcess"]["name"] == "SARLabelDecode": # for SAR model + config["Loss"]["ignore_index"] = char_num - 1 + + model = build_model(config["Architecture"]) + + use_sync_bn = config["Global"].get("use_sync_bn", False) + if use_sync_bn: + if config["Global"].get("use_npu", False) or config["Global"].get( + "use_xpu", False + ): + naive_sync_bn.convert_syncbn(model) + else: + model = paddle.nn.SyncBatchNorm.convert_sync_batchnorm(model) + logger.info("convert_sync_batchnorm") + + model = apply_to_static(model, config, logger) + + # build loss + loss_class = build_loss(config["Loss"]) + + # build optim + optimizer, lr_scheduler, wd_scheduler = build_optimizer( + config["Optimizer"], + epochs=config["Global"]["epoch_num"], + step_each_epoch=len(train_dataloader), + model=model, + ) + + # build metric + eval_class = build_metric(config["Metric"]) + + logger.info("train dataloader has {} iters".format(len(train_dataloader))) + if valid_dataloader is not None: + logger.info("valid dataloader has {} iters".format(len(valid_dataloader))) + + use_amp = config["Global"].get("use_amp", False) + amp_level = config["Global"].get("amp_level", "O2") + amp_dtype = config["Global"].get("amp_dtype", "float16") + amp_custom_black_list = config["Global"].get("amp_custom_black_list", []) + amp_custom_white_list = config["Global"].get("amp_custom_white_list", []) + if os.path.exists( + os.path.join(config["Global"]["save_model_dir"], "train_result.json") + ): + try: + os.remove( + os.path.join(config["Global"]["save_model_dir"], "train_result.json") + ) + except: + pass + if use_amp: + AMP_RELATED_FLAGS_SETTING = {} + if paddle.is_compiled_with_cuda(): + AMP_RELATED_FLAGS_SETTING.update( + { + "FLAGS_cudnn_batchnorm_spatial_persistent": 1, + "FLAGS_gemm_use_half_precision_compute_type": 0, + } + ) + paddle.set_flags(AMP_RELATED_FLAGS_SETTING) + scale_loss = config["Global"].get("scale_loss", 1.0) + use_dynamic_loss_scaling = config["Global"].get( + "use_dynamic_loss_scaling", False + ) + scaler = paddle.amp.GradScaler( + init_loss_scaling=scale_loss, + use_dynamic_loss_scaling=use_dynamic_loss_scaling, + ) + if amp_level == "O2": + model, optimizer = paddle.amp.decorate( + models=model, + optimizers=optimizer, + level=amp_level, + master_weight=True, + dtype=amp_dtype, + ) + else: + scaler = None + + # build EMA (after AMP decorate, before load_model) + ema = None + if config["Global"].get("use_ema", False): + from ppocr.utils.ema import ModelEMA + + ema = ModelEMA( + model, + decay=config["Global"].get("ema_decay", 0.9998), + gamma=config["Global"].get("ema_gamma", 2000), + ema_decay_type=config["Global"].get("ema_decay_type", "threshold"), + ema_filter_no_grad=config["Global"].get("ema_filter_no_grad", False), + ) + logger.info( + "EMA enabled: decay={}, gamma={}, type={}, filter_no_grad={}".format( + ema.decay, + ema.gamma, + ema.ema_decay_type, + config["Global"].get("ema_filter_no_grad", False), + ) + ) + + # load pretrain model + pre_best_model_dict = load_model( + config, model, optimizer, config["Architecture"]["model_type"], ema=ema + ) + + if config["Global"]["distributed"]: + find_unused_parameters = config["Global"].get("find_unused_parameters", False) + model = paddle.DataParallel( + model, find_unused_parameters=find_unused_parameters + ) + # start train + program.train( + config, + train_dataloader, + valid_dataloader, + device, + model, + loss_class, + optimizer, + lr_scheduler, + post_process_class, + eval_class, + pre_best_model_dict, + logger, + step_pre_epoch, + vdl_writer, + scaler, + amp_level, + amp_custom_black_list, + amp_custom_white_list, + amp_dtype, + wd_scheduler=wd_scheduler, + ema=ema, + ) + + +def test_reader(config, device, logger): + loader = build_dataloader(config, "Train", device, logger) + import time + + starttime = time.time() + count = 0 + try: + for data in loader(): + count += 1 + if count % 1 == 0: + batch_time = time.time() - starttime + starttime = time.time() + logger.info( + "reader: {}, {}, {}".format(count, len(data[0]), batch_time) + ) + except Exception as e: + logger.info(e) + logger.info("finish reader: {}, Success!".format(count)) + + +if __name__ == "__main__": + config, device, logger, vdl_writer = program.preprocess(is_train=True) + seed = config["Global"]["seed"] if "seed" in config["Global"] else 1024 + set_seed(seed) + main(config, device, logger, vdl_writer) + # test_reader(config, device, logger) diff --git a/train.sh b/train.sh new file mode 100644 index 0000000..0a64150 --- /dev/null +++ b/train.sh @@ -0,0 +1,2 @@ +# recommended paddle.__version__ == 3.0.0 +python3 -m paddle.distributed.launch --log_dir=./log/ --gpus '0,1,2,3,4,5,6,7' tools/train.py -c configs/rec/PP-OCRv5/PP-OCRv5_server_rec.yml