zyc 3 anni fa
commit
15810d45f9
100 ha cambiato i file con 17959 aggiunte e 0 eliminazioni
  1. 8 0
      .idea/.gitignore
  2. 6 0
      .idea/inspectionProfiles/profiles_settings.xml
  3. 27 0
      .idea/invoice_ocr.iml
  4. 7 0
      .idea/misc.xml
  5. 8 0
      .idea/modules.xml
  6. 6 0
      .idea/vcs.xml
  7. 0 0
      db.sqlite3
  8. 0 0
      invoice_ocr/__init__.py
  9. BIN
      invoice_ocr/__pycache__/__init__.cpython-37.pyc
  10. BIN
      invoice_ocr/__pycache__/settings.cpython-37.pyc
  11. BIN
      invoice_ocr/__pycache__/urls.cpython-37.pyc
  12. BIN
      invoice_ocr/__pycache__/wsgi.cpython-37.pyc
  13. 16 0
      invoice_ocr/asgi.py
  14. 228 0
      invoice_ocr/settings.py
  15. 22 0
      invoice_ocr/urls.py
  16. 16 0
      invoice_ocr/wsgi.py
  17. 197 0
      logs/all-2021-03-11.log
  18. 240 0
      logs/all-2021-03-12.log
  19. 0 0
      logs/error-2021-03-11.log
  20. 0 0
      logs/error-2021-03-12.log
  21. 0 0
      logs/info-2021-03-11.log
  22. 0 0
      logs/info-2021-03-12.log
  23. 0 0
      logs/invoice_ocr.log
  24. 22 0
      manage.py
  25. 3 0
      ocr/__init__.py
  26. BIN
      ocr/__pycache__/__init__.cpython-37.pyc
  27. BIN
      ocr/__pycache__/admin.cpython-37.pyc
  28. BIN
      ocr/__pycache__/models.cpython-37.pyc
  29. BIN
      ocr/__pycache__/urls.cpython-37.pyc
  30. BIN
      ocr/__pycache__/views.cpython-37.pyc
  31. 3 0
      ocr/admin.py
  32. 5 0
      ocr/apps.py
  33. 0 0
      ocr/migrations/__init__.py
  34. BIN
      ocr/migrations/__pycache__/__init__.cpython-37.pyc
  35. 3 0
      ocr/models.py
  36. 3 0
      ocr/tests.py
  37. 7 0
      ocr/urls.py
  38. 23 0
      ocr/views.py
  39. 27 0
      utils/PaddleOCR/.gitignore
  40. 35 0
      utils/PaddleOCR/.pre-commit-config.yaml
  41. 3 0
      utils/PaddleOCR/.style.yapf
  42. 201 0
      utils/PaddleOCR/LICENSE
  43. 7 0
      utils/PaddleOCR/MANIFEST.in
  44. 35 0
      utils/PaddleOCR/PPOCRLabel/Makefile
  45. 2005 0
      utils/PaddleOCR/PPOCRLabel/PPOCRLabel.py
  46. 170 0
      utils/PaddleOCR/PPOCRLabel/README.md
  47. 155 0
      utils/PaddleOCR/PPOCRLabel/README_ch.md
  48. 0 0
      utils/PaddleOCR/PPOCRLabel/__init__.py
  49. 46 0
      utils/PaddleOCR/PPOCRLabel/combobox.py
  50. BIN
      utils/PaddleOCR/PPOCRLabel/data/gif/steps.gif
  51. BIN
      utils/PaddleOCR/PPOCRLabel/data/gif/steps_en.gif
  52. BIN
      utils/PaddleOCR/PPOCRLabel/data/paddle.png
  53. 0 0
      utils/PaddleOCR/PPOCRLabel/data/predefined_classes.txt
  54. 2 0
      utils/PaddleOCR/PPOCRLabel/libs/__init__.py
  55. 162 0
      utils/PaddleOCR/PPOCRLabel/libs/autoDialog.py
  56. 798 0
      utils/PaddleOCR/PPOCRLabel/libs/canvas.py
  57. 49 0
      utils/PaddleOCR/PPOCRLabel/libs/colorDialog.py
  58. 31 0
      utils/PaddleOCR/PPOCRLabel/libs/constants.py
  59. 143 0
      utils/PaddleOCR/PPOCRLabel/libs/create_ml_io.py
  60. 31 0
      utils/PaddleOCR/PPOCRLabel/libs/editinlist.py
  61. 40 0
      utils/PaddleOCR/PPOCRLabel/libs/hashableQListWidgetItem.py
  62. 107 0
      utils/PaddleOCR/PPOCRLabel/libs/labelDialog.py
  63. 11114 0
      utils/PaddleOCR/PPOCRLabel/libs/resources.py
  64. 60 0
      utils/PaddleOCR/PPOCRLabel/libs/settings.py
  65. 217 0
      utils/PaddleOCR/PPOCRLabel/libs/shape.py
  66. 86 0
      utils/PaddleOCR/PPOCRLabel/libs/stringBundle.py
  67. 51 0
      utils/PaddleOCR/PPOCRLabel/libs/toolBar.py
  68. 29 0
      utils/PaddleOCR/PPOCRLabel/libs/ustr.py
  69. 182 0
      utils/PaddleOCR/PPOCRLabel/libs/utils.py
  70. 38 0
      utils/PaddleOCR/PPOCRLabel/libs/zoomWidget.py
  71. 39 0
      utils/PaddleOCR/PPOCRLabel/resources.qrc
  72. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/Auto.png
  73. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/app.icns
  74. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/app.png
  75. 27 0
      utils/PaddleOCR/PPOCRLabel/resources/icons/app.svg
  76. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/cancel.png
  77. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/close.png
  78. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/color.png
  79. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/color_line.png
  80. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/copy.png
  81. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/delete.png
  82. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/done.png
  83. 400 0
      utils/PaddleOCR/PPOCRLabel/resources/icons/done.svg
  84. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/edit.png
  85. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/expert1.png
  86. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/expert2.png
  87. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/eye.png
  88. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/feBlend-icon.png
  89. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/file.png
  90. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/fit-width.png
  91. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/fit-window.png
  92. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/fit.png
  93. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/format_createml.png
  94. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/format_voc.png
  95. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/format_yolo.png
  96. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/help.png
  97. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/labels.png
  98. 819 0
      utils/PaddleOCR/PPOCRLabel/resources/icons/labels.svg
  99. BIN
      utils/PaddleOCR/PPOCRLabel/resources/icons/new.png
  100. 0 0
      utils/PaddleOCR/PPOCRLabel/resources/icons/next.png

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
+# Editor-based HTTP Client requests
+/httpRequests/

+ 6 - 0
.idea/inspectionProfiles/profiles_settings.xml

@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>

+ 27 - 0
.idea/invoice_ocr.iml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="FacetManager">
+    <facet type="django" name="Django">
+      <configuration>
+        <option name="rootFolder" value="$MODULE_DIR$" />
+        <option name="settingsModule" value="invoice_ocr/settings.py" />
+        <option name="manageScript" value="$MODULE_DIR$/manage.py" />
+        <option name="environment" value="&lt;map/&gt;" />
+        <option name="doNotUseTestRunner" value="false" />
+        <option name="trackFilePattern" value="migrations" />
+      </configuration>
+    </facet>
+  </component>
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="jdk" jdkName="Pipenv (invoice_ocr)" jdkType="Python SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+  <component name="PyDocumentationSettings">
+    <option name="format" value="PLAIN" />
+    <option name="myDocStringFormat" value="Plain" />
+  </component>
+  <component name="TemplatesService">
+    <option name="TEMPLATE_CONFIGURATION" value="Django" />
+  </component>
+</module>

+ 7 - 0
.idea/misc.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptSettings">
+    <option name="languageLevel" value="ES6" />
+  </component>
+  <component name="ProjectRootManager" version="2" project-jdk-name="Pipenv (invoice_ocr)" project-jdk-type="Python SDK" />
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/invoice_ocr.iml" filepath="$PROJECT_DIR$/.idea/invoice_ocr.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 0 - 0
db.sqlite3


+ 0 - 0
invoice_ocr/__init__.py


BIN
invoice_ocr/__pycache__/__init__.cpython-37.pyc


BIN
invoice_ocr/__pycache__/settings.cpython-37.pyc


BIN
invoice_ocr/__pycache__/urls.cpython-37.pyc


BIN
invoice_ocr/__pycache__/wsgi.cpython-37.pyc


+ 16 - 0
invoice_ocr/asgi.py

@@ -0,0 +1,16 @@
+"""
+ASGI config for invoice_ocr project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'invoice_ocr.settings')
+
+application = get_asgi_application()

+ 228 - 0
invoice_ocr/settings.py

@@ -0,0 +1,228 @@
+"""
+Django settings for invoice_ocr project.
+
+Generated by 'django-admin startproject' using Django 3.1.4.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.1/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/3.1/ref/settings/
+"""
+import os
+import time
+from pathlib import Path
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'l56929t$rpl&(75-g20nnoe*@q7gxp9c)i*1lh2fi=4+p-4(mj'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'corsheaders',
+    'ocr',
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'corsheaders.middleware.CorsMiddleware',  # 跨域资源
+    'django.middleware.common.CommonMiddleware',
+    # 'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'invoice_ocr.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'invoice_ocr.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': BASE_DIR / 'db.sqlite3',
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/3.1/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+# 日志:当运行出错时,记录在日志中,方便后续修改
+cur_path = os.path.dirname(os.path.realpath(__file__))  # log_path是存放日志的路径
+log_path = os.path.join(os.path.dirname(cur_path), 'logs')
+if not os.path.exists(log_path):os.mkdir(log_path)  # 如果不存在这个logs文件夹,就自动创建一个
+
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': True,
+    'formatters': {
+        # 日志格式
+        'standard': {
+            'format': '[%(asctime)s] [%(filename)s:%(lineno)d] [%(module)s:%(funcName)s] '
+                      '[%(levelname)s]- %(message)s'},
+        'simple': {  # 简单格式
+            'format': '%(levelname)s %(message)s'
+        },
+    },
+    # 过滤
+    'filters': {
+    },
+    # 定义具体处理日志的方式
+    'handlers': {
+        # 默认记录所有日志
+        'default': {
+            'level': 'INFO',
+            'class': 'logging.handlers.RotatingFileHandler',
+            'filename': os.path.join(log_path, 'all-{}.log'.format(time.strftime('%Y-%m-%d'))),
+            'maxBytes': 1024 * 1024 * 5,  # 文件大小
+            'backupCount': 5,  # 备份数
+            'formatter': 'standard',  # 输出格式
+            'encoding': 'utf-8',  # 设置默认编码,否则打印出来汉字乱码
+        },
+        # 输出错误日志
+        'error': {
+            'level': 'ERROR',
+            'class': 'logging.handlers.RotatingFileHandler',
+            'filename': os.path.join(log_path, 'error-{}.log'.format(time.strftime('%Y-%m-%d'))),
+            'maxBytes': 1024 * 1024 * 5,  # 文件大小
+            'backupCount': 5,  # 备份数
+            'formatter': 'standard',  # 输出格式
+            'encoding': 'utf-8',  # 设置默认编码
+        },
+        # 控制台输出
+        'console': {
+            'level': 'DEBUG',
+            'class': 'logging.StreamHandler',
+            'formatter': 'standard'
+        },
+        # 输出info日志
+        'info': {
+            'level': 'INFO',
+            'class': 'logging.handlers.RotatingFileHandler',
+            'filename': os.path.join(log_path, 'info-{}.log'.format(time.strftime('%Y-%m-%d'))),
+            'maxBytes': 1024 * 1024 * 5,
+            'backupCount': 5,
+            'formatter': 'standard',
+            'encoding': 'utf-8',  # 设置默认编码
+        },
+    },
+    # 配置用哪几种 handlers 来处理日志
+    'loggers': {
+        # 类型 为 django 处理所有类型的日志, 默认调用
+        'django': {
+            'handlers': ['default', 'console'],
+            'level': 'INFO',
+            'propagate': False
+        },
+        # log 调用时需要当作参数传入
+        'log': {
+            'handlers': ['error', 'info', 'console', 'default'],
+            'level': 'INFO',
+            'propagate': True
+        },
+    }
+}
+
+# 跨域增加忽略
+CORS_ALLOW_CREDENTIALS = True
+CORS_ORIGIN_ALLOW_ALL = True
+
+CORS_ORIGIN_WHITELIST = (
+    'http://*.*.*.*:*',
+)
+
+CORS_ALLOW_METHODS = (
+    'DELETE',
+    'GET',
+    'OPTIONS',
+    'PATCH',
+    'POST',
+    'PUT',
+    'VIEW',
+)
+
+CORS_ALLOW_HEADERS = (
+    'XMLHttpRequest',
+    'X_FILENAME',
+    'accept-encoding',
+    'authorization',
+    'content-type',
+    'dnt',
+    'origin',
+    'user-agent',
+    'x-csrftoken',
+    'x-requested-with',
+)
+
+STATIC_URL = '/static/'
+
+

+ 22 - 0
invoice_ocr/urls.py

@@ -0,0 +1,22 @@
+"""invoice_ocr URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/3.1/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path, include
+
+urlpatterns = [
+    path('admin/', admin.site.urls),
+    path('ocr/', include('ocr.urls'))
+]

+ 16 - 0
invoice_ocr/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for invoice_ocr project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'invoice_ocr.settings')
+
+application = get_wsgi_application()

+ 197 - 0
logs/all-2021-03-11.log

@@ -0,0 +1,197 @@
+[2021-03-11 15:45:11,090] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 15:45:18,176] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 36
+[2021-03-11 16:48:41,324] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 16:55:01,145] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 16:56:00,609] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 16:56:39,716] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 16:56:44,906] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 14, in post
+    res = ocr.predict(paths=image_path)
+  File "D:\pythonProject\invoice_ocr\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 72, in predict
+    predicted_data = self.read_images(paths)
+  File "D:\pythonProject\invoice_ocr\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 51, in read_images
+    img_path), "The {} isn't a valid file.".format(img_path)
+AssertionError: The .PaddleOCR/doc/imgs/11.jpg isn't a valid file.
+[2021-03-11 16:56:44,907] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 83254
+[2021-03-11 16:57:26,616] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 16:57:30,898] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 14, in post
+    res = ocr.predict(paths=image_path)
+  File "D:\pythonProject\invoice_ocr\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 85, in predict
+    dt_boxes, rec_res = self.text_sys(img)
+AttributeError: 'OCRSystem' object has no attribute 'text_sys'
+[2021-03-11 16:57:30,899] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 81632
+[2021-03-11 17:09:26,941] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 17:09:30,716] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 14, in post
+    res = ocr.predict(paths=image_path)
+  File "D:\pythonProject\invoice_ocr\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 85, in predict
+    dt_boxes, rec_res = self.text_sys(img)
+AttributeError: 'OCRSystem' object has no attribute 'text_sys'
+[2021-03-11 17:09:30,717] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 82459
+[2021-03-11 17:10:55,703] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 17:11:02,591] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 14, in post
+    res = ocr.predict(paths=image_path)
+  File "D:\pythonProject\invoice_ocr\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 85, in predict
+    dt_boxes, rec_res = self.text_sys(img)
+AttributeError: 'OCRSystem' object has no attribute 'text_sys'
+[2021-03-11 17:11:02,593] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 81741
+[2021-03-11 17:35:31,917] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 17:35:41,540] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 17, in post
+    res = ocr.predict(paths=image_path)
+  File "D:\pythonProject\invoice_ocr\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 85, in predict
+    dt_boxes, rec_res = self.text_sys(img)
+AttributeError: 'OCRSystem' object has no attribute 'text_sys'
+[2021-03-11 17:35:41,542] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 82573
+[2021-03-11 17:36:43,172] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 17:36:46,332] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 16, in post
+    res = ocr.predict(paths=image_path)
+  File "D:\pythonProject\invoice_ocr\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 85, in predict
+    dt_boxes, rec_res = self.text_sys(img)
+AttributeError: 'OCRSystem' object has no attribute 'text_sys'
+[2021-03-11 17:36:46,333] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 82334
+[2021-03-11 17:38:20,368] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 17:38:26,483] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 15, in post
+    res = ocr.predict(paths=image_path)
+  File "D:\pythonProject\invoice_ocr\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 85, in predict
+    dt_boxes, rec_res = self.text_sys(img)
+AttributeError: 'OCRSystem' object has no attribute 'text_sys'
+[2021-03-11 17:38:26,485] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 82333
+[2021-03-11 17:42:13,675] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 17:42:46,110] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 15, in post
+    res = ocr.predict(paths=image_path)
+  File "D:\pythonProject\invoice_ocr\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 85, in predict
+    dt_boxes, rec_res = self.text_sys(img)
+AttributeError: 'OCRSystem' object has no attribute 'text_sys'
+[2021-03-11 17:42:46,115] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 84339
+[2021-03-11 17:44:59,338] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 17:46:38,923] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 17:46:49,402] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 15, in post
+    res = ocr.predict(paths=image_path)
+  File "D:\pythonProject\invoice_ocr\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 85, in predict
+    dt_boxes, rec_res = self.text_sys(img)
+AttributeError: 'OCRSystem' object has no attribute 'text_sys'
+[2021-03-11 17:46:49,403] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 82334
+[2021-03-11 17:47:33,531] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 17:47:43,431] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 2809
+[2021-03-11 17:49:15,419] [autoreload.py:240] [autoreload:trigger_reload] [INFO]- D:\pythonProject\invoice_ocr\PaddleOCR\deploy\hubserving\ocr_system\module.py changed, reloading.
+[2021-03-11 17:49:18,917] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 18:04:57,114] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 18:07:58,829] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 18:08:09,947] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 9, in post
+    image = images.read()
+AttributeError: 'list' object has no attribute 'read'
+[2021-03-11 18:08:09,950] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 78453
+[2021-03-11 18:09:34,381] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 18:14:45,668] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-11 18:14:48,585] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 12, in post
+    img = cv2.imdecode(image.read(), cv2.IMREAD_ANYCOLOR)
+TypeError: Expected Ptr<cv::UMat> for argument 'buf'
+[2021-03-11 18:14:48,587] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 78852

+ 240 - 0
logs/all-2021-03-12.log

@@ -0,0 +1,240 @@
+[2021-03-12 08:42:46,731] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 08:43:00,571] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 21, in post
+    res = ocr.predict(paths=image_path, invoice_type=1)
+  File "D:\pythonProject\invoice_ocr\utils\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 73, in predict
+    predicted_data = self.read_images(paths)
+  File "D:\pythonProject\invoice_ocr\utils\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 52, in read_images
+    img_path), "The {} isn't a valid file.".format(img_path)
+AssertionError: The ./PaddleOCR/doc/imgs/test2.jpg isn't a valid file.
+[2021-03-12 08:43:00,574] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 84812
+[2021-03-12 08:43:22,651] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 08:43:33,537] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 444
+[2021-03-12 08:44:13,453] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 08:45:54,862] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 444
+[2021-03-12 08:47:56,159] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 08:48:05,749] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 444
+[2021-03-12 08:48:44,838] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 08:48:51,624] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 456
+[2021-03-12 09:03:29,840] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 09:08:12,134] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 09:08:16,377] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 10, in post
+    invoice_type = request.post.get('invoice_type', 1)
+AttributeError: 'WSGIRequest' object has no attribute 'post'
+[2021-03-12 09:08:16,381] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 78950
+[2021-03-12 09:09:49,649] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 09:10:09,069] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 456
+[2021-03-12 09:10:22,290] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 449
+[2021-03-12 09:12:18,278] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 456
+[2021-03-12 09:26:08,702] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 09:26:33,841] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 09:28:00,729] [autoreload.py:240] [autoreload:trigger_reload] [INFO]- D:\pythonProject\invoice_ocr\invoice_ocr\settings.py changed, reloading.
+[2021-03-12 09:28:04,137] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 09:28:08,209] [autoreload.py:240] [autoreload:trigger_reload] [INFO]- D:\pythonProject\invoice_ocr\invoice_ocr\settings.py changed, reloading.
+[2021-03-12 09:28:11,355] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 09:29:26,093] [autoreload.py:240] [autoreload:trigger_reload] [INFO]- D:\pythonProject\invoice_ocr\invoice_ocr\settings.py changed, reloading.
+[2021-03-12 09:29:29,345] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 09:29:34,327] [autoreload.py:240] [autoreload:trigger_reload] [INFO]- D:\pythonProject\invoice_ocr\invoice_ocr\settings.py changed, reloading.
+[2021-03-12 09:29:37,492] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 09:29:53,724] [autoreload.py:240] [autoreload:trigger_reload] [INFO]- D:\pythonProject\invoice_ocr\invoice_ocr\settings.py changed, reloading.
+[2021-03-12 09:34:51,274] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 09:55:49,284] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 10:17:38,892] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 10:17:47,253] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 456
+[2021-03-12 10:20:03,567] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 10:21:00,587] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 456
+[2021-03-12 10:21:30,968] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 10:21:37,317] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 456
+[2021-03-12 10:22:53,991] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 10:23:01,293] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 454
+[2021-03-12 10:24:09,370] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 17, in post
+    res = ocr.predict(images=image_list, invoice_type=invoice_type)
+  File "D:\pythonProject\invoice_ocr\utils\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 75, in predict
+    raise TypeError("The input data is inconsistent with expectations.")
+TypeError: The input data is inconsistent with expectations.
+[2021-03-12 10:24:09,371] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 82610
+[2021-03-12 10:27:17,559] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 17, in post
+    res = ocr.predict(images=image_list, invoice_type=invoice_type)
+  File "D:\pythonProject\invoice_ocr\utils\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 75, in predict
+    raise TypeError("The input data is inconsistent with expectations.")
+TypeError: The input data is inconsistent with expectations.
+[2021-03-12 10:27:17,560] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 82610
+[2021-03-12 10:30:17,393] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 17, in post
+    res = ocr.predict(images=image_list, invoice_type=invoice_type)
+  File "D:\pythonProject\invoice_ocr\utils\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 75, in predict
+    raise TypeError("The input data is inconsistent with expectations.")
+TypeError: The input data is inconsistent with expectations.
+[2021-03-12 10:30:17,394] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 82628
+[2021-03-12 10:36:51,451] [log.py:230] [log:log_response] [WARNING]- Bad request (Unable to parse request body): /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 9, in post
+    images = request.FILES.getlist('images')
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\wsgi.py", line 116, in FILES
+    self._load_post_and_files()
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\http\request.py", line 350, in _load_post_and_files
+    self._post, self._files = self.parse_file_upload(self.META, data)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\http\request.py", line 309, in parse_file_upload
+    parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\http\multipartparser.py", line 77, in __init__
+    raise MultiPartParserError('Invalid boundary in multipart: %s' % force_str(boundary))
+django.http.multipartparser.MultiPartParserError: Invalid boundary in multipart: None
+[2021-03-12 10:36:51,461] [basehttp.py:157] [basehttp:log_message] [WARNING]- "POST /ocr/invoice_ocr/ HTTP/1.1" 400 143
+[2021-03-12 10:39:24,107] [log.py:230] [log:log_response] [WARNING]- Bad request (Unable to parse request body): /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 9, in post
+    images = request.FILES.getlist('images')
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\wsgi.py", line 116, in FILES
+    self._load_post_and_files()
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\http\request.py", line 350, in _load_post_and_files
+    self._post, self._files = self.parse_file_upload(self.META, data)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\http\request.py", line 309, in parse_file_upload
+    parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\http\multipartparser.py", line 77, in __init__
+    raise MultiPartParserError('Invalid boundary in multipart: %s' % force_str(boundary))
+django.http.multipartparser.MultiPartParserError: Invalid boundary in multipart: None
+[2021-03-12 10:39:24,108] [basehttp.py:157] [basehttp:log_message] [WARNING]- "POST /ocr/invoice_ocr/ HTTP/1.1" 400 143
+[2021-03-12 10:53:30,675] [log.py:230] [log:log_response] [WARNING]- Bad request (Unable to parse request body): /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 9, in post
+    images = request.FILES.getlist('images')
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\wsgi.py", line 116, in FILES
+    self._load_post_and_files()
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\http\request.py", line 350, in _load_post_and_files
+    self._post, self._files = self.parse_file_upload(self.META, data)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\http\request.py", line 309, in parse_file_upload
+    parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\http\multipartparser.py", line 77, in __init__
+    raise MultiPartParserError('Invalid boundary in multipart: %s' % force_str(boundary))
+django.http.multipartparser.MultiPartParserError: Invalid boundary in multipart: None
+[2021-03-12 10:53:30,676] [basehttp.py:157] [basehttp:log_message] [WARNING]- "POST /ocr/invoice_ocr/ HTTP/1.1" 400 143
+[2021-03-12 10:54:10,303] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 17, in post
+    res = ocr.predict(images=image_list, invoice_type=invoice_type)
+  File "D:\pythonProject\invoice_ocr\utils\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 75, in predict
+    raise TypeError("The input data is inconsistent with expectations.")
+TypeError: The input data is inconsistent with expectations.
+[2021-03-12 10:54:10,304] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 82628
+[2021-03-12 11:13:17,809] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 11:14:31,507] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 17, in post
+    res = ocr.predict(images=image_list, invoice_type=invoice_type)
+  File "D:\pythonProject\invoice_ocr\utils\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 75, in predict
+    raise TypeError("The input data is inconsistent with expectations.")
+TypeError: The input data is inconsistent with expectations.
+[2021-03-12 11:14:31,509] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 84633
+[2021-03-12 11:18:02,722] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 454
+[2021-03-12 11:22:41,428] [autoreload.py:240] [autoreload:trigger_reload] [INFO]- D:\pythonProject\invoice_ocr\ocr\views.py changed, reloading.
+[2021-03-12 11:22:46,928] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 11:23:01,554] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 11:23:10,062] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 454
+[2021-03-12 11:23:23,249] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 442
+[2021-03-12 11:23:28,630] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 73
+[2021-03-12 11:23:33,706] [log.py:230] [log:log_response] [ERROR]- Internal Server Error: /ocr/invoice_ocr/
+Traceback (most recent call last):
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
+    response = get_response(request)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 70, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "C:\Users\t\.virtualenvs\invoice_ocr-XlsmZNoZ\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
+    return handler(request, *args, **kwargs)
+  File "D:\pythonProject\invoice_ocr\ocr\views.py", line 21, in post
+    res = ocr.predict(images=image_list, invoice_type=invoice_type)
+  File "D:\pythonProject\invoice_ocr\utils\PaddleOCR\deploy\hubserving\ocr_system\module.py", line 75, in predict
+    raise TypeError("The input data is inconsistent with expectations.")
+TypeError: The input data is inconsistent with expectations.
+[2021-03-12 11:23:33,707] [basehttp.py:157] [basehttp:log_message] [ERROR]- "POST /ocr/invoice_ocr/ HTTP/1.1" 500 82466
+[2021-03-12 11:24:25,767] [autoreload.py:617] [autoreload:run_with_reloader] [INFO]- Watching for file changes with StatReloader
+[2021-03-12 11:24:28,224] [basehttp.py:157] [basehttp:log_message] [INFO]- "POST /ocr/invoice_ocr/ HTTP/1.1" 200 61

+ 0 - 0
logs/error-2021-03-11.log


+ 0 - 0
logs/error-2021-03-12.log


+ 0 - 0
logs/info-2021-03-11.log


+ 0 - 0
logs/info-2021-03-12.log


+ 0 - 0
logs/invoice_ocr.log


+ 22 - 0
manage.py

@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    """Run administrative tasks."""
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'invoice_ocr.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()

+ 3 - 0
ocr/__init__.py

@@ -0,0 +1,3 @@
+from utils.PaddleOCR.deploy.hubserving.ocr_system.module import OCRSystem
+
+ocrModel = OCRSystem()

BIN
ocr/__pycache__/__init__.cpython-37.pyc


BIN
ocr/__pycache__/admin.cpython-37.pyc


BIN
ocr/__pycache__/models.cpython-37.pyc


BIN
ocr/__pycache__/urls.cpython-37.pyc


BIN
ocr/__pycache__/views.cpython-37.pyc


+ 3 - 0
ocr/admin.py

@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.

+ 5 - 0
ocr/apps.py

@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class OcrConfig(AppConfig):
+    name = 'ocr'

+ 0 - 0
ocr/migrations/__init__.py


BIN
ocr/migrations/__pycache__/__init__.cpython-37.pyc


+ 3 - 0
ocr/models.py

@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.

+ 3 - 0
ocr/tests.py

@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.

+ 7 - 0
ocr/urls.py

@@ -0,0 +1,7 @@
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+    path('invoice_ocr/', views.InvoiceOCRView.as_view()),
+]

+ 23 - 0
ocr/views.py

@@ -0,0 +1,23 @@
+from cv2 import cv2
+from django.views import View
+from django import http
+from . import ocrModel
+import numpy as np
+
+class InvoiceOCRView(View):
+    def post(self, request):
+        images = request.FILES.getlist('images', '')
+        if images == '':
+            return http.JsonResponse({'code': 404, 'results': '请上传图片!!!'})
+        invoice_type = int(request.POST.get('invoice_type', 1))
+        if invoice_type not in (1, 2):
+            return http.JsonResponse({'code': 404, 'results': '输入正确的参数!!!'})
+        image_list = []
+        for image in images:
+            img = np.fromstring(image.read(), np.uint8)
+            img = cv2.imdecode(img, cv2.IMREAD_COLOR)
+            image_list.append(img)
+        ocr = ocrModel
+        res = ocr.predict(images=image_list, invoice_type=invoice_type)
+        # print(res)
+        return http.JsonResponse({'code': 200, 'results': res})

+ 27 - 0
utils/PaddleOCR/.gitignore

@@ -0,0 +1,27 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+.ipynb_checkpoints/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+inference/
+inference_results/
+output/
+
+*.DS_Store
+*.vs
+*.user
+*~
+*.vscode
+*.idea
+
+*.log
+.clang-format
+.clang_format.hook
+
+build/
+dist/
+paddleocr.egg-info/

+ 35 - 0
utils/PaddleOCR/.pre-commit-config.yaml

@@ -0,0 +1,35 @@
+-   repo: https://github.com/PaddlePaddle/mirrors-yapf.git
+    sha: 0d79c0c469bab64f7229c9aca2b1186ef47f0e37
+    hooks:
+    -   id: yapf
+        files: \.py$
+-   repo: https://github.com/pre-commit/pre-commit-hooks
+    sha: a11d9314b22d8f8c7556443875b731ef05965464
+    hooks:
+    -   id: check-merge-conflict
+    -   id: check-symlinks
+    -   id: detect-private-key
+        files: (?!.*paddle)^.*$
+    -   id: end-of-file-fixer
+        files: \.md$
+    -   id: trailing-whitespace
+        files: \.md$
+-   repo: https://github.com/Lucas-C/pre-commit-hooks
+    sha: v1.0.1
+    hooks:
+    -   id: forbid-crlf
+        files: \.md$
+    -   id: remove-crlf
+        files: \.md$
+    -   id: forbid-tabs
+        files: \.md$
+    -   id: remove-tabs
+        files: \.md$
+-   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)$

+ 3 - 0
utils/PaddleOCR/.style.yapf

@@ -0,0 +1,3 @@
+[style]
+based_on_style = pep8
+column_limit = 80

+ 201 - 0
utils/PaddleOCR/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 [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.

+ 7 - 0
utils/PaddleOCR/MANIFEST.in

@@ -0,0 +1,7 @@
+include LICENSE.txt
+include README.md
+
+recursive-include ppocr/utils *.txt utility.py logging.py
+recursive-include ppocr/data/ *.py
+recursive-include ppocr/postprocess *.py
+recursive-include tools/infer *.py

+ 35 - 0
utils/PaddleOCR/PPOCRLabel/Makefile

@@ -0,0 +1,35 @@
+# ex: set ts=8 noet:
+
+all: qt5 test
+
+test: testpy3
+
+testpy2:
+	python -m unittest discover tests
+
+testpy3:
+	python3 -m unittest discover tests
+
+qt4: qt4py2
+
+qt5: qt5py3
+
+qt4py2:
+	pyrcc4 -py2 -o libs/resources.py resources.qrc
+
+qt4py3:
+	pyrcc4 -py3 -o libs/resources.py resources.qrc
+
+qt5py3:
+	pyrcc5 -o libs/resources.py resources.qrc
+
+clean:
+	rm -rf ~/.labelImgSettings.pkl *.pyc dist labelImg.egg-info __pycache__ build
+
+pip_upload:
+	python3 setup.py upload
+
+long_description:
+	restview --long-description
+
+.PHONY: all

File diff suppressed because it is too large
+ 2005 - 0
utils/PaddleOCR/PPOCRLabel/PPOCRLabel.py


+ 170 - 0
utils/PaddleOCR/PPOCRLabel/README.md

@@ -0,0 +1,170 @@
+English | [简体中文](README_ch.md)
+
+# PPOCRLabel
+
+PPOCRLabel is a semi-automatic graphic annotation tool suitable for OCR field, with built-in PPOCR model to automatically detect and re-recognize data. It is written in python3 and pyqt5, supporting rectangular box annotation and four-point annotation modes. Annotations can be directly used for the training of PPOCR detection and recognition models.
+
+<img src="./data/gif/steps_en.gif" width="100%"/>
+
+### Recent Update
+
+- 2021.1.11: Optimize the labeling experience (by [edencfc](https://github.com/edencfc)),
+  - Users can choose whether to pop up the label input dialog after drawing the detection box in "View - Pop-up Label Input Dialog".
+  - The recognition result scrolls synchronously when users click related detection box.
+  - Click to modify the recognition result.(If you can't change the result, please switch to the system default input method, or switch back to the original input method again)
+- 2020.12.18: Support re-recognition of a single label box (by [ninetailskim](https://github.com/ninetailskim) ), perfect shortcut keys.
+
+### TODO:
+- Lock box mode: For the same scene data, the size and position of the locked detection box can be transferred between different pictures.
+- Experience optimization: Add undo, batch operation include move, copy, delete and so on, optimize the annotation process.
+
+## Installation
+
+### 1. Install PaddleOCR
+
+PaddleOCR models has been built in PPOCRLabel, please refer to [PaddleOCR installation document](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_ch/installation.md) to prepare PaddleOCR and make sure it works.
+
+### 2. Install PPOCRLabel
+
+#### Windows + Anaconda
+
+Download and install [Anaconda](https://www.anaconda.com/download/#download) (Python 3+)
+
+```
+pip install pyqt5
+cd ./PPOCRLabel # Change the directory to the PPOCRLabel folder
+python PPOCRLabel.py
+```
+
+#### Ubuntu Linux
+
+```
+pip3 install pyqt5
+pip3 install trash-cli
+cd ./PPOCRLabel # Change the directory to the PPOCRLabel folder
+python3 PPOCRLabel.py
+```
+
+#### macOS
+```
+pip3 install pyqt5
+pip3 uninstall opencv-python # Uninstall opencv manually as it conflicts with pyqt
+pip3 install opencv-contrib-python-headless==4.2.0.32 # Install the headless version of opencv
+cd ./PPOCRLabel # Change the directory to the PPOCRLabel folder
+python3 PPOCRLabel.py
+```
+
+## Usage
+
+### Steps
+
+1. Build and launch using the instructions above.
+
+2. Click 'Open Dir' in Menu/File to select the folder of the picture.<sup>[1]</sup>
+
+3. Click 'Auto recognition', use PPOCR model to automatically annotate images which marked with 'X' <sup>[2]</sup>before the file name.
+
+4. Create Box:
+
+   4.1 Click 'Create RectBox' or press 'W' in English keyboard mode to draw a new rectangle detection box. Click and release left mouse to select a region to annotate the text area.
+
+   4.2 Press 'Q' to enter four-point labeling mode which enables you to create any four-point shape by clicking four points with the left mouse button in succession and DOUBLE CLICK the left mouse as the signal of labeling completion.
+
+5. After the marking frame is drawn, the user clicks "OK", and the detection frame will be pre-assigned a "TEMPORARY" label.
+
+6. Click 're-Recognition', model will rewrite ALL recognition results in ALL detection box<sup>[3]</sup>.
+
+7. Double click the result in 'recognition result' list to manually change inaccurate recognition results.
+
+8. Click "Check", the image status will switch to "√",then the program automatically jump to the next(The results will not be written directly to the file at this time).
+
+9. Click "Delete Image" and the image will be deleted to the recycle bin.
+
+10. Labeling result: the user can save manually through the menu "File - Save Label", while the program will also save automatically after every 5 images confirmed by the user.the manually checked label will be stored in *Label.txt* under the opened picture folder.
+    Click "PaddleOCR"-"Save Recognition Results" in the menu bar, the recognition training data of such pictures will be saved in the *crop_img* folder, and the recognition label will be saved in *rec_gt.txt*<sup>[4]</sup>.
+
+### Note
+
+[1] PPOCRLabel uses the opened folder as the project. After opening the image folder, the picture will not be displayed in the dialog. Instead, the pictures under the folder will be directly imported into the program after clicking "Open Dir".
+
+[2] The image status indicates whether the user has saved the image manually. If it has not been saved manually it is "X", otherwise it is "√", PPOCRLabel will not relabel pictures with a status of "√".
+
+[3] After clicking "Re-recognize", the model will overwrite ALL recognition results in the picture.
+Therefore, if the recognition result has been manually changed before, it may change after re-recognition.
+
+[4] The files produced by PPOCRLabel can be found under the opened picture folder including the following, please do not manually change the contents, otherwise it will cause the program to be abnormal.
+
+|   File name   |                         Description                          |
+| :-----------: | :----------------------------------------------------------: |
+|   Label.txt   | The detection label file can be directly used for PPOCR detection model training. After the user saves 5 label results, the file will be automatically saved. It will also be written when the user closes the application or changes the file folder. |
+| fileState.txt | The picture status file save the image in the current folder that has been manually confirmed by the user. |
+|  Cache.cach   |    Cache files to save the results of model recognition.     |
+|  rec_gt.txt   | The recognition label file, which can be directly used for PPOCR identification model training, is generated after the user clicks on the menu bar "File"-"Save recognition result". |
+|   crop_img    | The recognition data, generated at the same time with *rec_gt.txt* |
+
+## Explanation
+
+### Shortcut keys
+
+| Shortcut keys    | Description                                      |
+| ---------------- | ------------------------------------------------ |
+| Ctrl + shift + A | Automatically label all unchecked images         |
+| Ctrl + shift + R | Re-recognize all the labels of the current image |
+| W                | Create a rect box                                |
+| Q                | Create a four-points box                         |
+| Ctrl + E         | Edit label of the selected box                   |
+| Ctrl + R         | Re-recognize the selected box                    |
+| Backspace        | Delete the selected box                          |
+| Ctrl + V         | Check image                                      |
+| Ctrl + Shift + d | Delete image                                     |
+| D                | Next image                                       |
+| A                | Previous image                                   |
+| Ctrl++           | Zoom in                                          |
+| Ctrl--           | Zoom out                                         |
+| ↑→↓←             | Move selected box                                |
+
+### Built-in Model
+
+- Default model: PPOCRLabel uses the Chinese and English ultra-lightweight OCR model in PaddleOCR by default, supports Chinese, English and number recognition, and multiple language detection.
+
+- Model language switching: Changing the built-in model language is supportable by clicking "PaddleOCR"-"Choose OCR Model" in the menu bar. Currently supported languages​include French, German, Korean, and Japanese.
+  For specific model download links, please refer to [PaddleOCR Model List](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_en/models_list_en.md#multilingual-recognition-modelupdating)
+
+- Custom model: The model trained by users can be replaced by modifying PPOCRLabel.py in [PaddleOCR class instantiation](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/PPOCRLabel/PPOCRLabel.py#L110) referring [Custom Model Code](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_en/whl_en.md#use-custom-model)
+
+### Save
+
+PPOCRLabel supports three ways to save Label.txt
+
+- Automatically save: When it detects that the user has manually checked 5 pictures, the program automatically writes the annotations into Label.txt. The user can change the value of ``self.autoSaveNum`` in ``PPOCRLabel.py`` to set the number of images to be automatically saved after confirmation.
+- Manual save: Click "File-Save Marking Results" to manually save the label.
+- Close application save
+
+
+### Export partial recognition results
+
+For some data that are difficult to recognize, the recognition results will not be exported by **unchecking** the corresponding tags in the recognition results checkbox.
+
+*Note: The status of the checkboxes in the recognition results still needs to be saved manually by clicking Save Button.*
+
+### Error message
+
+- If paddleocr is installed with whl, it has a higher priority than calling PaddleOCR class with paddleocr.py, which may cause an exception if whl package is not updated.
+
+- For Linux users, if you get an error starting with **objc[XXXXX]** when opening the software, it proves that your opencv version is too high. It is recommended to install version 4.2:
+
+    ```
+    pip install opencv-python==4.2.0.32
+    ```
+- If you get an error starting with **Missing string id **,you need to recompile resources:
+    ```
+    pyrcc5 -o libs/resources.py resources.qrc
+    ```
+- If you get an error ``` module 'cv2' has no attribute 'INTER_NEAREST'```, you need to delete all opencv related packages first, and then reinstall the 4.2.0.32 version of headless opencv
+    ```
+    pip install opencv-contrib-python-headless==4.2.0.32
+    ```
+
+### Related
+
+1.[Tzutalin. LabelImg. Git code (2015)](https://github.com/tzutalin/labelImg)

+ 155 - 0
utils/PaddleOCR/PPOCRLabel/README_ch.md

@@ -0,0 +1,155 @@
+[English](README.md) | 简体中文
+
+# PPOCRLabel
+
+PPOCRLabel是一款适用于OCR领域的半自动化图形标注工具,内置PPOCR模型对数据自动标注和重新识别。使用python3和pyqt5编写,支持矩形框标注和四点标注模式,导出格式可直接用于PPOCR检测和识别模型的训练。
+
+<img src="./data/gif/steps.gif" width="100%"/>
+
+#### 近期更新
+
+- 2021.1.11:优化标注体验(by [edencfc](https://github.com/edencfc)):
+  - 用户可在“视图 - 弹出标记输入框”选择在画完检测框后标记输入框是否弹出。
+  - 识别结果与检测框同步滚动。
+  - 识别结果更改为单击修改。(如果无法修改,请切换为系统自带输入法,或再次切回原输入法)
+- 2020.12.18: 支持对单个标记框进行重新识别(by [ninetailskim](https://github.com/ninetailskim)),完善快捷键。
+
+#### 尽请期待
+
+- 锁定框模式:针对同一场景数据,被锁定的检测框的大小与位置能在不同图片之间传递。
+- 体验优化:增加撤销操作,批量移动、复制、删除等功能。优化标注流程。
+
+如果您对以上内容感兴趣或对完善工具有不一样的想法,欢迎加入我们的队伍与我们共同开发
+
+
+## 安装
+
+### 1. 安装PaddleOCR
+PPOCRLabel内置PaddleOCR模型,故请参考[PaddleOCR安装文档](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_ch/installation.md)准备好PaddleOCR,并确保PaddleOCR安装成功。
+
+### 2. 安装PPOCRLabel
+#### Windows + Anaconda
+
+```
+pip install pyqt5
+cd ./PPOCRLabel # 将目录切换到PPOCRLabel文件夹下
+python PPOCRLabel.py --lang ch
+```
+
+#### Ubuntu Linux
+
+```
+pip3 install pyqt5
+pip3 install trash-cli
+cd ./PPOCRLabel # 将目录切换到PPOCRLabel文件夹下
+python3 PPOCRLabel.py --lang ch
+```
+
+#### macOS
+```
+pip3 install pyqt5
+pip3 uninstall opencv-python # 由于mac版本的opencv与pyqt有冲突,需先手动卸载opencv
+pip3 install opencv-contrib-python-headless==4.2.0.32 # 安装headless版本的open-cv
+cd ./PPOCRLabel # 将目录切换到PPOCRLabel文件夹下
+python3 PPOCRLabel.py --lang ch
+```
+
+## 使用
+
+### 操作步骤
+
+1. 安装与运行:使用上述命令安装与运行程序。
+2. 打开文件夹:在菜单栏点击 “文件” - "打开目录" 选择待标记图片的文件夹<sup>[1]</sup>.
+3. 自动标注:点击 ”自动标注“,使用PPOCR超轻量模型对图片文件名前图片状态<sup>[2]</sup>为 “X” 的图片进行自动标注。
+4. 手动标注:点击 “矩形标注”(推荐直接在英文模式下点击键盘中的 “W”),用户可对当前图片中模型未检出的部分进行手动绘制标记框。点击键盘Q,则使用四点标注模式(或点击“编辑” - “四点标注”),用户依次点击4个点后,双击左键表示标注完成。
+5. 标记框绘制完成后,用户点击 “确认”,检测框会先被预分配一个 “待识别” 标签。
+6. 重新识别:将图片中的所有检测画绘制/调整完成后,点击 “重新识别”,PPOCR模型会对当前图片中的**所有检测框**重新识别<sup>[3]</sup>。
+7. 内容更改:双击识别结果,对不准确的识别结果进行手动更改。
+8. 确认标记:点击 “确认”,图片状态切换为 “√”,跳转至下一张(此时不会直接将结果写入文件)。
+9. 删除:点击 “删除图像”,图片将会被删除至回收站。
+10. 保存结果:用户可以通过菜单中“文件-保存标记结果”手动保存,同时程序也会在用户每确认5张图片后自动保存一次。手动确认过的标记将会被存放在所打开图片文件夹下的*Label.txt*中。在菜单栏点击 “文件” - "保存识别结果"后,会将此类图片的识别训练数据保存在*crop_img*文件夹下,识别标签保存在*rec_gt.txt*中<sup>[4]</sup>。
+
+### 注意
+
+[1] PPOCRLabel以文件夹为基本标记单位,打开待标记的图片文件夹后,不会在窗口栏中显示图片,而是在点击 "选择文件夹" 之后直接将文件夹下的图片导入到程序中。
+
+[2] 图片状态表示本张图片用户是否手动保存过,未手动保存过即为 “X”,手动保存过为 “√”。点击 “自动标注”按钮后,PPOCRLabel不会对状态为 “√” 的图片重新标注。
+
+[3] 点击“重新识别”后,模型会对图片中的识别结果进行覆盖。因此如果在此之前手动更改过识别结果,有可能在重新识别后产生变动。
+
+[4] PPOCRLabel产生的文件放置于标记图片文件夹下,包括一下几种,请勿手动更改其中内容,否则会引起程序出现异常。
+
+|    文件名     |                             说明                             |
+| :-----------: | :----------------------------------------------------------: |
+|   Label.txt   | 检测标签,可直接用于PPOCR检测模型训练。用户每保存5张检测结果后,程序会进行自动写入。当用户关闭应用程序或切换文件路径后同样会进行写入。 |
+| fileState.txt | 图片状态标记文件,保存当前文件夹下已经被用户手动确认过的图片名称。 |
+|  Cache.cach   |              缓存文件,保存模型自动识别的结果。              |
+|  rec_gt.txt   | 识别标签。可直接用于PPOCR识别模型训练。需用户手动点击菜单栏“文件” - "保存识别结果"后产生。 |
+|   crop_img    |   识别数据。按照检测框切割后的图片。与rec_gt.txt同时产生。   |
+
+## 说明
+
+### 快捷键
+
+| 快捷键           | 说明                         |
+| ---------------- | ---------------------------- |
+| Ctrl + shift + A | 自动标注所有未确认过的图片   |
+| Ctrl + shift + R | 对当前图片的所有标记重新识别 |
+| W                | 新建矩形框                   |
+| Q                | 新建四点框                   |
+| Ctrl + E         | 编辑所选框标签               |
+| Ctrl + R         | 重新识别所选标记             |
+| Backspace        | 删除所选框                   |
+| Ctrl + V         | 确认本张图片标记             |
+| Ctrl + Shift + d | 删除本张图片                 |
+| D                | 下一张图片                   |
+| A                | 上一张图片                   |
+| Ctrl++           | 缩小                         |
+| Ctrl--           | 放大                         |
+| ↑→↓←             | 移动标记框                   |
+
+### 内置模型
+
+ - 默认模型:PPOCRLabel默认使用PaddleOCR中的中英文超轻量OCR模型,支持中英文与数字识别,多种语言检测。
+
+ - 模型语言切换:用户可通过菜单栏中 "PaddleOCR" - "选择模型" 切换内置模型语言,目前支持的语言包括法文、德文、韩文、日文。具体模型下载链接可参考[PaddleOCR模型列表](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_ch/models_list.md).
+
+ - 自定义模型:用户可根据[自定义模型代码使用](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/doc/doc_ch/whl.md#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A8%A1%E5%9E%8B),通过修改PPOCRLabel.py中针对[PaddleOCR类的实例化](https://github.com/PaddlePaddle/PaddleOCR/blob/develop/PPOCRLabel/PPOCRLabel.py#L110)替换成自己训练的模型。
+
+### 保存方式
+
+PPOCRLabel支持三种保存方式:
+
+- 程序自动保存:当检测到用户手动确认过5张图片后,程序自动将标记结果写入Label.txt中。其中用户可通过更改```PPOCRLabel.py```中的```self.autoSaveNum```的数值设置确认几张图片后进行自动保存。
+- 手动保存:点击“文件 - 保存标记结果”手动保存标记。
+- 关闭应用程序保存
+
+### 导出部分识别结果
+
+针对部分难以识别的数据,通过在识别结果的复选框中**取消勾选**相应的标记,其识别结果不会被导出。
+
+*注意:识别结果中的复选框状态仍需用户手动点击保存后才能保留*
+
+### 错误提示
+- 如果同时使用whl包安装了paddleocr,其优先级大于通过paddleocr.py调用PaddleOCR类,whl包未更新时会导致程序异常。
+
+- PPOCRLabel**不支持对中文文件名**的图片进行自动标注。
+
+- 针对Linux用户:如果您在打开软件过程中出现**objc[XXXXX]**开头的错误,证明您的opencv版本太高,建议安装4.2版本:
+    ```
+    pip install opencv-python==4.2.0.32
+    ```
+
+- 如果出现 ```Missing string id``` 开头的错误,需要重新编译资源:
+    ```
+    pyrcc5 -o libs/resources.py resources.qrc
+    ```
+
+- 如果出现``` module 'cv2' has no attribute 'INTER_NEAREST'```错误,需要首先删除所有opencv相关包,然后重新安装4.2.0.32版本的headless opencv
+    ```
+    pip install opencv-contrib-python-headless==4.2.0.32
+    ```
+
+### 参考资料
+
+1.[Tzutalin. LabelImg. Git code (2015)](https://github.com/tzutalin/labelImg)

+ 0 - 0
utils/PaddleOCR/PPOCRLabel/__init__.py


+ 46 - 0
utils/PaddleOCR/PPOCRLabel/combobox.py

@@ -0,0 +1,46 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+import sys
+try:
+    from PyQt5.QtWidgets import QWidget, QHBoxLayout, QComboBox
+except ImportError:
+    # needed for py3+qt4
+    # Ref:
+    # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
+    # http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string
+    if sys.version_info.major >= 3:
+        import sip
+        sip.setapi('QVariant', 2)
+    from PyQt4.QtGui import QWidget, QHBoxLayout, QComboBox
+
+
+class ComboBox(QWidget):
+    def __init__(self, parent=None, items=[]):
+        super(ComboBox, self).__init__(parent)
+
+        layout = QHBoxLayout()
+        self.cb = QComboBox()
+        self.items = items
+        self.cb.addItems(self.items)
+
+        self.cb.currentIndexChanged.connect(parent.comboSelectionChanged)
+
+        layout.addWidget(self.cb)
+        self.setLayout(layout)
+
+    def update_items(self, items):
+        self.items = items
+
+        self.cb.clear()
+        self.cb.addItems(self.items)

BIN
utils/PaddleOCR/PPOCRLabel/data/gif/steps.gif


BIN
utils/PaddleOCR/PPOCRLabel/data/gif/steps_en.gif


BIN
utils/PaddleOCR/PPOCRLabel/data/paddle.png


+ 0 - 0
utils/PaddleOCR/PPOCRLabel/data/predefined_classes.txt


+ 2 - 0
utils/PaddleOCR/PPOCRLabel/libs/__init__.py

@@ -0,0 +1,2 @@
+__version_info__ = ('1', '0', '0')
+__version__ = '.'.join(__version_info__)

+ 162 - 0
utils/PaddleOCR/PPOCRLabel/libs/autoDialog.py

@@ -0,0 +1,162 @@
+try:
+    from PyQt5.QtGui import *
+    from PyQt5.QtCore import *
+    from PyQt5.QtWidgets import *
+except ImportError:
+    from PyQt4.QtGui import *
+    from PyQt4.QtCore import *
+
+import json
+import cv2
+import numpy as np
+
+from libs.utils import newIcon
+
+BB = QDialogButtonBox
+
+
+class Worker(QThread):
+    progressBarValue = pyqtSignal(int)
+    listValue = pyqtSignal(str)
+    endsignal = pyqtSignal(int, str)
+    handle = 0
+
+    def __init__(self, ocr, mImgList, mainThread, model):
+        super(Worker, self).__init__()
+        self.ocr = ocr
+        self.mImgList = mImgList
+        self.mainThread = mainThread
+        self.model = model
+        self.setStackSize(1024*1024)
+
+    def run(self):
+        try:
+            findex = 0
+            for Imgpath in self.mImgList:
+                if self.handle == 0:
+                    self.listValue.emit(Imgpath)
+                    if self.model == 'paddle':
+                        h, w, _ = cv2.imdecode(np.fromfile(Imgpath, dtype=np.uint8), 1).shape
+                        if h > 32 and w > 32:
+                            self.result_dic = self.ocr.ocr(Imgpath, cls=True, det=True)
+                        else:
+                            print('The size of', Imgpath, 'is too small to be recognised')
+                            self.result_dic = None
+
+                    # 结果保存
+                    if self.result_dic is None or len(self.result_dic) == 0:
+                        print('Can not recognise file', Imgpath)
+                        pass
+                    else:
+                        strs = ''
+                        for res in self.result_dic:
+                            chars = res[1][0]
+                            cond = res[1][1]
+                            posi = res[0]
+                            strs += "Transcription: " + chars + " Probability: " + str(cond) + \
+                                    " Location: " + json.dumps(posi) +'\n'
+                        # Sending large amounts of data repeatedly through pyqtSignal may affect the program efficiency
+                        self.listValue.emit(strs)
+                        self.mainThread.result_dic = self.result_dic
+                        self.mainThread.filePath = Imgpath
+                        # 保存
+                        self.mainThread.saveFile(mode='Auto')
+                    findex += 1
+                    self.progressBarValue.emit(findex)
+                else:
+                    break
+            self.endsignal.emit(0, "readAll")
+            self.exec()
+        except Exception as e:
+            print(e)
+            raise
+
+
+class AutoDialog(QDialog):
+
+    def __init__(self, text="Enter object label", parent=None, ocr=None, mImgList=None, lenbar=0):
+        super(AutoDialog, self).__init__(parent)
+        self.setFixedWidth(1000)
+        self.parent = parent
+        self.ocr = ocr
+        self.mImgList = mImgList
+        self.pb = QProgressBar()
+        self.pb.setRange(0, lenbar)
+        self.pb.setValue(0)
+
+        layout = QVBoxLayout()
+        layout.addWidget(self.pb)
+        self.model = 'paddle'
+        self.listWidget = QListWidget(self)
+        layout.addWidget(self.listWidget)
+
+        self.buttonBox = bb = BB(BB.Ok | BB.Cancel, Qt.Horizontal, self)
+        bb.button(BB.Ok).setIcon(newIcon('done'))
+        bb.button(BB.Cancel).setIcon(newIcon('undo'))
+        bb.accepted.connect(self.validate)
+        bb.rejected.connect(self.reject)
+        layout.addWidget(bb)
+        bb.button(BB.Ok).setEnabled(False)
+
+        self.setLayout(layout)
+        # self.setWindowTitle("自动标注中")
+        self.setWindowModality(Qt.ApplicationModal)
+
+        # self.setWindowFlags(Qt.WindowCloseButtonHint)
+
+        self.thread_1 = Worker(self.ocr, self.mImgList, self.parent, 'paddle')
+        self.thread_1.progressBarValue.connect(self.handleProgressBarSingal)
+        self.thread_1.listValue.connect(self.handleListWidgetSingal)
+        self.thread_1.endsignal.connect(self.handleEndsignalSignal)
+
+    def handleProgressBarSingal(self, i):
+        self.pb.setValue(i)
+
+    def handleListWidgetSingal(self, i):
+        self.listWidget.addItem(i)
+        titem = self.listWidget.item(self.listWidget.count() - 1)
+        self.listWidget.scrollToItem(titem)
+
+    def handleEndsignalSignal(self, i, str):
+        if i == 0 and str == "readAll":
+            self.buttonBox.button(BB.Ok).setEnabled(True)
+            self.buttonBox.button(BB.Cancel).setEnabled(False)
+
+    def reject(self):
+        print("reject")
+        self.thread_1.handle = -1
+        self.thread_1.quit()
+        # del self.thread_1
+        # if self.thread_1.isRunning():
+        #     self.thread_1.terminate()
+        # self.thread_1.quit()
+        # super(AutoDialog,self).reject()
+        while not self.thread_1.isFinished():
+            pass
+        self.accept()
+
+    def validate(self):
+        self.accept()
+
+    def postProcess(self):
+        try:
+            self.edit.setText(self.edit.text().trimmed())
+            # print(self.edit.text())
+        except AttributeError:
+            # PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
+            self.edit.setText(self.edit.text())
+            print(self.edit.text())
+
+    def popUp(self):
+        self.thread_1.start()
+        return 1 if self.exec_() else None
+
+    def closeEvent(self, event):
+        print("???")
+        # if self.thread_1.isRunning():
+        #     self.thread_1.quit()
+        #
+        #     # self._thread.terminate()
+        # # del self.thread_1
+        # super(AutoDialog, self).closeEvent(event)
+        self.reject()

+ 798 - 0
utils/PaddleOCR/PPOCRLabel/libs/canvas.py

@@ -0,0 +1,798 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+try:
+    from PyQt5.QtGui import *
+    from PyQt5.QtCore import *
+    from PyQt5.QtWidgets import *
+except ImportError:
+    from PyQt4.QtGui import *
+    from PyQt4.QtCore import *
+
+#from PyQt4.QtOpenGL import *
+
+from libs.shape import Shape
+from libs.utils import distance
+
+CURSOR_DEFAULT = Qt.ArrowCursor
+CURSOR_POINT = Qt.PointingHandCursor
+CURSOR_DRAW = Qt.CrossCursor
+CURSOR_MOVE = Qt.ClosedHandCursor
+CURSOR_GRAB = Qt.OpenHandCursor
+
+# class Canvas(QGLWidget):
+
+
+class Canvas(QWidget):
+    zoomRequest = pyqtSignal(int)
+    scrollRequest = pyqtSignal(int, int)
+    newShape = pyqtSignal()
+    selectionChanged = pyqtSignal(bool)
+    shapeMoved = pyqtSignal()
+    drawingPolygon = pyqtSignal(bool)
+
+    CREATE, EDIT = list(range(2))
+    _fill_drawing = False # draw shadows
+
+    epsilon = 11.0
+
+    def __init__(self, *args, **kwargs):
+        super(Canvas, self).__init__(*args, **kwargs)
+        # Initialise local state.
+        self.mode = self.EDIT
+        self.shapes = []
+        self.current = None
+        self.selectedShape = None  # save the selected shape here
+        self.selectedShapeCopy = None
+        self.drawingLineColor = QColor(0, 0, 255)
+        self.drawingRectColor = QColor(0, 0, 255)
+        self.line = Shape(line_color=self.drawingLineColor)
+        self.prevPoint = QPointF()
+        self.offsets = QPointF(), QPointF()
+        self.scale = 1.0
+        self.pixmap = QPixmap()
+        self.visible = {}
+        self._hideBackround = False
+        self.hideBackround = False
+        self.hShape = None
+        self.hVertex = None
+        self._painter = QPainter()
+        self._cursor = CURSOR_DEFAULT
+        # Menus:
+        self.menus = (QMenu(), QMenu())
+        # Set widget options.
+        self.setMouseTracking(True)
+        self.setFocusPolicy(Qt.WheelFocus)
+        self.verified = False
+        self.drawSquare = False
+        self.fourpoint = True # ADD
+        self.pointnum = 0
+
+        #initialisation for panning
+        self.pan_initial_pos = QPoint()
+
+    def setDrawingColor(self, qColor):
+        self.drawingLineColor = qColor
+        self.drawingRectColor = qColor
+
+    def enterEvent(self, ev):
+        self.overrideCursor(self._cursor)
+
+    def leaveEvent(self, ev):
+        self.restoreCursor()
+
+    def focusOutEvent(self, ev):
+        self.restoreCursor()
+
+    def isVisible(self, shape):
+        return self.visible.get(shape, True)
+
+    def drawing(self):
+        return self.mode == self.CREATE
+
+    def editing(self):
+        return self.mode == self.EDIT
+
+    def setEditing(self, value=True):
+        self.mode = self.EDIT if value else self.CREATE
+        if not value:  # Create
+            self.unHighlight()
+            self.deSelectShape()
+        self.prevPoint = QPointF()
+        self.repaint()
+
+    def unHighlight(self):
+        if self.hShape:
+            self.hShape.highlightClear()
+        self.hVertex = self.hShape = None
+
+    def selectedVertex(self):
+        return self.hVertex is not None
+
+
+    def mouseMoveEvent(self, ev):
+        """Update line with last point and current coordinates."""
+        pos = self.transformPos(ev.pos())
+
+        # Update coordinates in status bar if image is opened
+        window = self.parent().window()
+        if window.filePath is not None:
+            self.parent().window().labelCoordinates.setText(
+                'X: %d; Y: %d' % (pos.x(), pos.y()))
+
+        # Polygon drawing.
+        if self.drawing():
+            self.overrideCursor(CURSOR_DRAW) # ?
+            if self.current:
+                # Display annotation width and height while drawing
+                currentWidth = abs(self.current[0].x() - pos.x())
+                currentHeight = abs(self.current[0].y() - pos.y())
+                self.parent().window().labelCoordinates.setText(
+                        'Width: %d, Height: %d / X: %d; Y: %d' % (currentWidth, currentHeight, pos.x(), pos.y()))
+
+                color = self.drawingLineColor
+                if self.outOfPixmap(pos):
+                    # Don't allow the user to draw outside the pixmap.
+                    # Clip the coordinates to 0 or max,
+                    # if they are outside the range [0, max]
+                    size = self.pixmap.size()
+                    clipped_x = min(max(0, pos.x()), size.width())
+                    clipped_y = min(max(0, pos.y()), size.height())
+                    pos = QPointF(clipped_x, clipped_y)
+                elif len(self.current) > 1 and self.closeEnough(pos, self.current[0]) and not self.fourpoint:
+                    # Attract line to starting point and colorise to alert the
+                    # user:
+                    pos = self.current[0]
+                    color = self.current.line_color
+                    self.overrideCursor(CURSOR_POINT)
+                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
+                elif ( # ADD
+                        len(self.current) > 1
+                        and self.fourpoint
+                        and self.closeEnough(pos, self.current[0])
+                ):
+                    # Attract line to starting point and
+                    # colorise to alert the user.
+                    pos = self.current[0]
+                    self.overrideCursor(CURSOR_POINT)
+                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
+
+
+                if self.drawSquare:
+                    initPos = self.current[0]
+                    minX = initPos.x()
+                    minY = initPos.y()
+                    min_size = min(abs(pos.x() - minX), abs(pos.y() - minY))
+                    directionX = -1 if pos.x() - minX < 0 else 1
+                    directionY = -1 if pos.y() - minY < 0 else 1
+                    self.line[1] = QPointF(minX + directionX * min_size, minY + directionY * min_size)
+
+                elif self.fourpoint:
+                    # self.line[self.pointnum] = pos # OLD
+
+                    self.line[0] = self.current[-1]
+                    self.line[1] = pos
+
+                else:
+                    self.line[1] = pos # pos is the mouse's current position
+
+                self.line.line_color = color
+                self.prevPoint = QPointF() # ?
+                self.current.highlightClear()
+            else:
+                self.prevPoint = pos
+            self.repaint()
+            return
+
+        # Polygon copy moving.
+        if Qt.RightButton & ev.buttons():
+            if self.selectedShapeCopy and self.prevPoint:
+                self.overrideCursor(CURSOR_MOVE)
+                self.boundedMoveShape(self.selectedShapeCopy, pos)
+                self.repaint()
+            elif self.selectedShape:
+                self.selectedShapeCopy = self.selectedShape.copy()
+                self.repaint()
+            return
+
+        # Polygon/Vertex moving.
+        if Qt.LeftButton & ev.buttons():
+            if self.selectedVertex():
+                self.boundedMoveVertex(pos)
+                self.shapeMoved.emit()
+                self.repaint()
+            elif self.selectedShape and self.prevPoint:
+                self.overrideCursor(CURSOR_MOVE)
+                self.boundedMoveShape(self.selectedShape, pos)
+                self.shapeMoved.emit()
+                self.repaint()
+            else:
+                #pan
+                delta_x = pos.x() - self.pan_initial_pos.x()
+                delta_y = pos.y() - self.pan_initial_pos.y()
+                self.scrollRequest.emit(delta_x, Qt.Horizontal)
+                self.scrollRequest.emit(delta_y, Qt.Vertical)
+                self.update()
+            return
+
+        # Just hovering over the canvas, 2 posibilities:
+        # - Highlight shapes
+        # - Highlight vertex
+        # Update shape/vertex fill and tooltip value accordingly.
+        self.setToolTip("Image")
+        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
+            # Look for a nearby vertex to highlight. If that fails,
+            # check if we happen to be inside a shape.
+            index = shape.nearestVertex(pos, self.epsilon)
+            if index is not None:
+                if self.selectedVertex():
+                    self.hShape.highlightClear()
+                self.hVertex, self.hShape = index, shape
+                shape.highlightVertex(index, shape.MOVE_VERTEX)
+                self.overrideCursor(CURSOR_POINT)
+                self.setToolTip("Click & drag to move point")
+                self.setStatusTip(self.toolTip())
+                self.update()
+                break
+            elif shape.containsPoint(pos):
+                if self.selectedVertex():
+                    self.hShape.highlightClear()
+                self.hVertex, self.hShape = None, shape
+                self.setToolTip(
+                    "Click & drag to move shape '%s'" % shape.label)
+                self.setStatusTip(self.toolTip())
+                self.overrideCursor(CURSOR_GRAB)
+                self.update()
+                break
+        else:  # Nothing found, clear highlights, reset state.
+            if self.hShape:
+                self.hShape.highlightClear()
+                self.update()
+            self.hVertex, self.hShape = None, None
+            self.overrideCursor(CURSOR_DEFAULT)
+
+    def mousePressEvent(self, ev):
+        pos = self.transformPos(ev.pos())
+
+        if ev.button() == Qt.LeftButton:
+            if self.drawing():
+                # self.handleDrawing(pos) # OLD
+
+
+                if self.current and self.fourpoint: # ADD IF
+                    # Add point to existing shape.
+                    print('Adding points in mousePressEvent is ', self.line[1])
+                    self.current.addPoint(self.line[1])
+                    self.line[0] = self.current[-1]
+                    if self.current.isClosed():
+                        # print('1111')
+                        self.finalise()
+                elif not self.outOfPixmap(pos):
+                    # Create new shape.
+                    self.current = Shape()# self.current = Shape(shape_type=self.createMode)
+                    self.current.addPoint(pos)
+                    # if self.createMode == "point":
+                    #     self.finalise()
+                    # else:
+                    #     if self.createMode == "circle":
+                    #         self.current.shape_type = "circle"
+                    self.line.points = [pos, pos]
+                    self.setHiding()
+                    self.drawingPolygon.emit(True)
+                    self.update()
+
+
+            else:
+                selection = self.selectShapePoint(pos)
+                self.prevPoint = pos
+
+                if selection is None:
+                    #pan
+                    QApplication.setOverrideCursor(QCursor(Qt.OpenHandCursor))
+                    self.pan_initial_pos = pos
+
+        elif ev.button() == Qt.RightButton and self.editing():
+            self.selectShapePoint(pos)
+            self.prevPoint = pos
+        self.update()
+
+    def mouseReleaseEvent(self, ev):
+        if ev.button() == Qt.RightButton:
+            menu = self.menus[bool(self.selectedShapeCopy)]
+            self.restoreCursor()
+            if not menu.exec_(self.mapToGlobal(ev.pos()))\
+               and self.selectedShapeCopy:
+                # Cancel the move by deleting the shadow copy.
+                self.selectedShapeCopy = None
+                self.repaint()
+        elif ev.button() == Qt.LeftButton and self.selectedShape: # OLD
+            if self.selectedVertex():
+                self.overrideCursor(CURSOR_POINT)
+            else:
+                self.overrideCursor(CURSOR_GRAB)
+
+
+        elif ev.button() == Qt.LeftButton and not self.fourpoint:
+            pos = self.transformPos(ev.pos())
+            if self.drawing():
+                self.handleDrawing(pos)
+            else:
+                #pan
+                QApplication.restoreOverrideCursor() # ?
+
+
+    def endMove(self, copy=False):
+        assert self.selectedShape and self.selectedShapeCopy
+        shape = self.selectedShapeCopy
+        #del shape.fill_color
+        #del shape.line_color
+        if copy:
+            self.shapes.append(shape)
+            self.selectedShape.selected = False
+            self.selectedShape = shape
+            self.repaint()
+        else:
+            self.selectedShape.points = [p for p in shape.points]
+        self.selectedShapeCopy = None
+
+    def hideBackroundShapes(self, value):
+        self.hideBackround = value
+        if self.selectedShape:
+            # Only hide other shapes if there is a current selection.
+            # Otherwise the user will not be able to select a shape.
+            self.setHiding(True)
+            self.repaint()
+
+    def handleDrawing(self, pos):
+        if self.current and self.current.reachMaxPoints() is False:
+            if self.fourpoint:
+                targetPos = self.line[self.pointnum]
+                self.current.addPoint(targetPos)
+                print('current points in handleDrawing is ', self.line[self.pointnum])
+                self.update()
+                if self.pointnum == 3:
+                    self.finalise()
+
+            else: # 按住送掉后跳到这里
+                initPos = self.current[0]
+                print('initPos', self.current[0])
+                minX = initPos.x()
+                minY = initPos.y()
+                targetPos = self.line[1]
+                maxX = targetPos.x()
+                maxY = targetPos.y()
+                self.current.addPoint(QPointF(maxX, minY))
+                self.current.addPoint(targetPos)
+                self.current.addPoint(QPointF(minX, maxY))
+                self.finalise()
+
+        elif not self.outOfPixmap(pos):
+            print('release')
+            self.current = Shape()
+            self.current.addPoint(pos)
+            self.line.points = [pos, pos]
+            self.setHiding()
+            self.drawingPolygon.emit(True)
+            self.update()
+
+    def setHiding(self, enable=True):
+        self._hideBackround = self.hideBackround if enable else False
+
+    def canCloseShape(self):
+        return self.drawing() and self.current and len(self.current) > 2
+
+    def mouseDoubleClickEvent(self, ev):
+        # We need at least 4 points here, since the mousePress handler
+        # adds an extra one before this handler is called.
+        if self.canCloseShape() and len(self.current) > 3:
+            if not self.fourpoint:
+                self.current.popPoint()
+            self.finalise()
+
+    def selectShape(self, shape):
+        self.deSelectShape()
+        shape.selected = True
+        self.selectedShape = shape
+        self.setHiding()
+        self.selectionChanged.emit(True)
+        self.update()
+
+    def selectShapePoint(self, point):
+        """Select the first shape created which contains this point."""
+        self.deSelectShape()
+        if self.selectedVertex():  # A vertex is marked for selection.
+            index, shape = self.hVertex, self.hShape
+            shape.highlightVertex(index, shape.MOVE_VERTEX)
+            self.selectShape(shape)
+            return self.hVertex
+        for shape in reversed(self.shapes):
+            if self.isVisible(shape) and shape.containsPoint(point):
+                self.selectShape(shape)
+                self.calculateOffsets(shape, point)
+                return self.selectedShape
+        return None
+
+    def calculateOffsets(self, shape, point):
+        rect = shape.boundingRect()
+        x1 = rect.x() - point.x()
+        y1 = rect.y() - point.y()
+        x2 = (rect.x() + rect.width()) - point.x()
+        y2 = (rect.y() + rect.height()) - point.y()
+        self.offsets = QPointF(x1, y1), QPointF(x2, y2)
+
+    def snapPointToCanvas(self, x, y):
+        """
+        Moves a point x,y to within the boundaries of the canvas.
+        :return: (x,y,snapped) where snapped is True if x or y were changed, False if not.
+        """
+        if x < 0 or x > self.pixmap.width() or y < 0 or y > self.pixmap.height():
+            x = max(x, 0)
+            y = max(y, 0)
+            x = min(x, self.pixmap.width())
+            y = min(y, self.pixmap.height())
+            return x, y, True
+
+        return x, y, False
+
+    def boundedMoveVertex(self, pos):
+        index, shape = self.hVertex, self.hShape
+        point = shape[index]
+        if self.outOfPixmap(pos):
+            size = self.pixmap.size()
+            clipped_x = min(max(0, pos.x()), size.width())
+            clipped_y = min(max(0, pos.y()), size.height())
+            pos = QPointF(clipped_x, clipped_y)
+
+        if self.drawSquare:
+            opposite_point_index = (index + 2) % 4
+            opposite_point = shape[opposite_point_index]
+
+            min_size = min(abs(pos.x() - opposite_point.x()), abs(pos.y() - opposite_point.y()))
+            directionX = -1 if pos.x() - opposite_point.x() < 0 else 1
+            directionY = -1 if pos.y() - opposite_point.y() < 0 else 1
+            shiftPos = QPointF(opposite_point.x() + directionX * min_size - point.x(),
+                               opposite_point.y() + directionY * min_size - point.y())
+        else:
+            shiftPos = pos - point
+
+        shape.moveVertexBy(index, shiftPos)
+
+        lindex = (index + 1) % 4
+        rindex = (index + 3) % 4
+        lshift = None
+        rshift = None
+        if index % 2 == 0:
+            rshift = QPointF(shiftPos.x(), 0)
+            lshift = QPointF(0, shiftPos.y())
+        else:
+            lshift = QPointF(shiftPos.x(), 0)
+            rshift = QPointF(0, shiftPos.y())
+        shape.moveVertexBy(rindex, rshift)
+        shape.moveVertexBy(lindex, lshift)
+
+    def boundedMoveShape(self, shape, pos):
+        if self.outOfPixmap(pos):
+            return False  # No need to move
+        o1 = pos + self.offsets[0]
+        if self.outOfPixmap(o1):
+            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
+        o2 = pos + self.offsets[1]
+        if self.outOfPixmap(o2):
+            pos += QPointF(min(0, self.pixmap.width() - o2.x()),
+                           min(0, self.pixmap.height() - o2.y()))
+        # The next line tracks the new position of the cursor
+        # relative to the shape, but also results in making it
+        # a bit "shaky" when nearing the border and allows it to
+        # go outside of the shape's area for some reason. XXX
+        #self.calculateOffsets(self.selectedShape, pos)
+        dp = pos - self.prevPoint
+        if dp:
+            shape.moveBy(dp)
+            self.prevPoint = pos
+            return True
+        return False
+
+    def deSelectShape(self):
+        if self.selectedShape:
+            self.selectedShape.selected = False
+            self.selectedShape = None
+            self.setHiding(False)
+            self.selectionChanged.emit(False)
+            self.update()
+
+    def deleteSelected(self):
+        if self.selectedShape:
+            shape = self.selectedShape
+            self.shapes.remove(self.selectedShape)
+            self.selectedShape = None
+            self.update()
+            return shape
+
+    def copySelectedShape(self):
+        if self.selectedShape:
+            shape = self.selectedShape.copy()
+            self.deSelectShape()
+            self.shapes.append(shape)
+            shape.selected = True
+            self.selectedShape = shape
+            self.boundedShiftShape(shape)
+            return shape
+
+    def boundedShiftShape(self, shape):
+        # Try to move in one direction, and if it fails in another.
+        # Give up if both fail.
+        point = shape[0]
+        offset = QPointF(2.0, 2.0)
+        self.calculateOffsets(shape, point)
+        self.prevPoint = point
+        if not self.boundedMoveShape(shape, point - offset):
+            self.boundedMoveShape(shape, point + offset)
+
+    def paintEvent(self, event):
+        if not self.pixmap:
+            return super(Canvas, self).paintEvent(event)
+
+        p = self._painter
+        p.begin(self)
+        p.setRenderHint(QPainter.Antialiasing)
+        p.setRenderHint(QPainter.HighQualityAntialiasing)
+        p.setRenderHint(QPainter.SmoothPixmapTransform)
+
+        p.scale(self.scale, self.scale)
+        p.translate(self.offsetToCenter())
+
+        p.drawPixmap(0, 0, self.pixmap)
+        Shape.scale = self.scale
+        for shape in self.shapes:
+            if (shape.selected or not self._hideBackround) and self.isVisible(shape):
+                shape.fill = shape.selected or shape == self.hShape
+                shape.paint(p)
+        if self.current:
+            self.current.paint(p)
+            self.line.paint(p)
+        if self.selectedShapeCopy:
+            self.selectedShapeCopy.paint(p)
+
+        # Paint rect
+        if self.current is not None and len(self.line) == 2 and not self.fourpoint:
+            # print('Drawing rect')
+            leftTop = self.line[0]
+            rightBottom = self.line[1]
+            rectWidth = rightBottom.x() - leftTop.x()
+            rectHeight = rightBottom.y() - leftTop.y()
+            p.setPen(self.drawingRectColor)
+            brush = QBrush(Qt.BDiagPattern)
+            p.setBrush(brush)
+            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)
+
+
+        # ADD:
+        if (
+                self.fillDrawing()
+                and self.fourpoint
+                and self.current is not None
+                and len(self.current.points) >= 2
+        ):
+            print('paint event')
+            drawing_shape = self.current.copy()
+            drawing_shape.addPoint(self.line[1])
+            drawing_shape.fill = True
+            drawing_shape.paint(p)
+
+        if self.drawing() and not self.prevPoint.isNull() and not self.outOfPixmap(self.prevPoint):
+            p.setPen(QColor(0, 0, 0))
+            p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(), self.pixmap.height())
+            p.drawLine(0, self.prevPoint.y(), self.pixmap.width(), self.prevPoint.y())
+
+        self.setAutoFillBackground(True)
+        if self.verified:
+            pal = self.palette()
+            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
+            self.setPalette(pal)
+        else:
+            pal = self.palette()
+            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
+            self.setPalette(pal)
+
+        p.end()
+
+    def fillDrawing(self):
+        return self._fill_drawing
+
+    def transformPos(self, point):
+        """Convert from widget-logical coordinates to painter-logical coordinates."""
+        return point / self.scale - self.offsetToCenter()
+
+    def offsetToCenter(self):
+        s = self.scale
+        area = super(Canvas, self).size()
+        w, h = self.pixmap.width() * s, self.pixmap.height() * s
+        aw, ah = area.width(), area.height()
+        x = (aw - w) / (2 * s) if aw > w else 0
+        y = (ah - h) / (2 * s) if ah > h else 0
+        return QPointF(x, y)
+
+    def outOfPixmap(self, p):
+        w, h = self.pixmap.width(), self.pixmap.height()
+        return not (0 <= p.x() <= w and 0 <= p.y() <= h)
+
+    def finalise(self):
+        assert self.current
+        if self.current.points[0] == self.current.points[-1]:
+            # print('finalse')
+            self.current = None
+            self.drawingPolygon.emit(False)
+            self.update()
+            return
+
+        self.current.close()
+        self.shapes.append(self.current)
+        self.current = None
+        self.setHiding(False)
+        self.newShape.emit()
+        self.update()
+
+    def closeEnough(self, p1, p2):
+        #d = distance(p1 - p2)
+        #m = (p1-p2).manhattanLength()
+        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
+        return distance(p1 - p2) < self.epsilon
+
+    # These two, along with a call to adjustSize are required for the
+    # scroll area.
+    def sizeHint(self):
+        return self.minimumSizeHint()
+
+    def minimumSizeHint(self):
+        if self.pixmap:
+            return self.scale * self.pixmap.size()
+        return super(Canvas, self).minimumSizeHint()
+
+    def wheelEvent(self, ev):
+        qt_version = 4 if hasattr(ev, "delta") else 5
+        if qt_version == 4:
+            if ev.orientation() == Qt.Vertical:
+                v_delta = ev.delta()
+                h_delta = 0
+            else:
+                h_delta = ev.delta()
+                v_delta = 0
+        else:
+            delta = ev.angleDelta()
+            h_delta = delta.x()
+            v_delta = delta.y()
+
+        mods = ev.modifiers()
+        if Qt.ControlModifier == int(mods) and v_delta:
+            self.zoomRequest.emit(v_delta)
+        else:
+            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
+            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
+        ev.accept()
+
+    def keyPressEvent(self, ev):
+        key = ev.key()
+        if key == Qt.Key_Escape and self.current:
+            print('ESC press')
+            self.current = None
+            self.drawingPolygon.emit(False)
+            self.update()
+        elif key == Qt.Key_Return and self.canCloseShape():
+            self.finalise()
+        elif key == Qt.Key_Left and self.selectedShape:
+            self.moveOnePixel('Left')
+        elif key == Qt.Key_Right and self.selectedShape:
+            self.moveOnePixel('Right')
+        elif key == Qt.Key_Up and self.selectedShape:
+            self.moveOnePixel('Up')
+        elif key == Qt.Key_Down and self.selectedShape:
+            self.moveOnePixel('Down')
+
+    def moveOnePixel(self, direction):
+        # print(self.selectedShape.points)
+        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
+            # print("move Left one pixel")
+            self.selectedShape.points[0] += QPointF(-1.0, 0)
+            self.selectedShape.points[1] += QPointF(-1.0, 0)
+            self.selectedShape.points[2] += QPointF(-1.0, 0)
+            self.selectedShape.points[3] += QPointF(-1.0, 0)
+        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
+            # print("move Right one pixel")
+            self.selectedShape.points[0] += QPointF(1.0, 0)
+            self.selectedShape.points[1] += QPointF(1.0, 0)
+            self.selectedShape.points[2] += QPointF(1.0, 0)
+            self.selectedShape.points[3] += QPointF(1.0, 0)
+        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
+            # print("move Up one pixel")
+            self.selectedShape.points[0] += QPointF(0, -1.0)
+            self.selectedShape.points[1] += QPointF(0, -1.0)
+            self.selectedShape.points[2] += QPointF(0, -1.0)
+            self.selectedShape.points[3] += QPointF(0, -1.0)
+        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
+            # print("move Down one pixel")
+            self.selectedShape.points[0] += QPointF(0, 1.0)
+            self.selectedShape.points[1] += QPointF(0, 1.0)
+            self.selectedShape.points[2] += QPointF(0, 1.0)
+            self.selectedShape.points[3] += QPointF(0, 1.0)
+        self.shapeMoved.emit()
+        self.repaint()
+
+    def moveOutOfBound(self, step):
+        points = [p1+p2 for p1, p2 in zip(self.selectedShape.points, [step]*4)]
+        return True in map(self.outOfPixmap, points)
+
+    def setLastLabel(self, text, line_color  = None, fill_color = None):
+        assert text
+        self.shapes[-1].label = text
+        if line_color:
+            self.shapes[-1].line_color = line_color
+
+        if fill_color:
+            self.shapes[-1].fill_color = fill_color
+
+        return self.shapes[-1]
+
+    def undoLastLine(self):
+        assert self.shapes
+        self.current = self.shapes.pop()
+        self.current.setOpen()
+        self.line.points = [self.current[-1], self.current[0]]
+        self.drawingPolygon.emit(True)
+
+    def resetAllLines(self):
+        assert self.shapes
+        self.current = self.shapes.pop()
+        self.current.setOpen()
+        self.line.points = [self.current[-1], self.current[0]]
+        self.drawingPolygon.emit(True)
+        self.current = None
+        self.drawingPolygon.emit(False)
+        self.update()
+
+    def loadPixmap(self, pixmap):
+        self.pixmap = pixmap
+        self.shapes = []
+        self.repaint() # 这函数在哪
+
+    def loadShapes(self, shapes):
+        self.shapes = list(shapes)
+        self.current = None
+        self.repaint()
+
+    def setShapeVisible(self, shape, value):
+        self.visible[shape] = value
+        self.repaint()
+
+    def currentCursor(self):
+        cursor = QApplication.overrideCursor()
+        if cursor is not None:
+            cursor = cursor.shape()
+        return cursor
+
+    def overrideCursor(self, cursor):
+        self._cursor = cursor
+        if self.currentCursor() is None:
+            QApplication.setOverrideCursor(cursor)
+        else:
+            QApplication.changeOverrideCursor(cursor)
+
+    def restoreCursor(self):
+        QApplication.restoreOverrideCursor()
+
+    def resetState(self):
+        self.restoreCursor()
+        self.pixmap = None
+        self.update()
+
+    def setDrawingShapeToSquare(self, status):
+        self.drawSquare = status

+ 49 - 0
utils/PaddleOCR/PPOCRLabel/libs/colorDialog.py

@@ -0,0 +1,49 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+try:
+    from PyQt5.QtGui import *
+    from PyQt5.QtCore import *
+    from PyQt5.QtWidgets import QColorDialog, QDialogButtonBox
+except ImportError:
+    from PyQt4.QtGui import *
+    from PyQt4.QtCore import *
+
+BB = QDialogButtonBox
+
+
+class ColorDialog(QColorDialog):
+
+    def __init__(self, parent=None):
+        super(ColorDialog, self).__init__(parent)
+        self.setOption(QColorDialog.ShowAlphaChannel)
+        # The Mac native dialog does not support our restore button.
+        self.setOption(QColorDialog.DontUseNativeDialog)
+        # Add a restore defaults button.
+        # The default is set at invocation time, so that it
+        # works across dialogs for different elements.
+        self.default = None
+        self.bb = self.layout().itemAt(1).widget()
+        self.bb.addButton(BB.RestoreDefaults)
+        self.bb.clicked.connect(self.checkRestore)
+
+    def getColor(self, value=None, title=None, default=None):
+        self.default = default
+        if title:
+            self.setWindowTitle(title)
+        if value:
+            self.setCurrentColor(value)
+        return self.currentColor() if self.exec_() else None
+
+    def checkRestore(self, button):
+        if self.bb.buttonRole(button) & BB.ResetRole and self.default:
+            self.setCurrentColor(self.default)

+ 31 - 0
utils/PaddleOCR/PPOCRLabel/libs/constants.py

@@ -0,0 +1,31 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+SETTING_FILENAME = 'filename'
+SETTING_RECENT_FILES = 'recentFiles'
+SETTING_WIN_SIZE = 'window/size'
+SETTING_WIN_POSE = 'window/position'
+SETTING_WIN_GEOMETRY = 'window/geometry'
+SETTING_LINE_COLOR = 'line/color'
+SETTING_FILL_COLOR = 'fill/color'
+SETTING_ADVANCE_MODE = 'advanced'
+SETTING_WIN_STATE = 'window/state'
+SETTING_SAVE_DIR = 'savedir'
+SETTING_PAINT_LABEL = 'paintlabel'
+SETTING_LAST_OPEN_DIR = 'lastOpenDir'
+SETTING_AUTO_SAVE = 'autosave'
+SETTING_SINGLE_CLASS = 'singleclass'
+FORMAT_PASCALVOC='PascalVOC'
+FORMAT_YOLO='YOLO'
+SETTING_DRAW_SQUARE = 'draw/square'
+SETTING_LABEL_FILE_FORMAT= 'labelFileFormat'
+DEFAULT_ENCODING = 'utf-8'

+ 143 - 0
utils/PaddleOCR/PPOCRLabel/libs/create_ml_io.py

@@ -0,0 +1,143 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#!/usr/bin/env python
+# -*- coding: utf8 -*-
+import json
+from pathlib import Path
+
+from libs.constants import DEFAULT_ENCODING
+import os
+
+JSON_EXT = '.json'
+ENCODE_METHOD = DEFAULT_ENCODING
+
+
+class CreateMLWriter:
+    def __init__(self, foldername, filename, imgsize, shapes, outputfile, databasesrc='Unknown', localimgpath=None):
+        self.foldername = foldername
+        self.filename = filename
+        self.databasesrc = databasesrc
+        self.imgsize = imgsize
+        self.boxlist = []
+        self.localimgpath = localimgpath
+        self.verified = False
+        self.shapes = shapes
+        self.outputfile = outputfile
+
+    def write(self):
+        if os.path.isfile(self.outputfile):
+            with open(self.outputfile, "r") as file:
+                input_data = file.read()
+                outputdict = json.loads(input_data)
+        else:
+            outputdict = []
+
+        outputimagedict = {
+            "image": self.filename,
+            "annotations": []
+        }
+
+        for shape in self.shapes:
+            points = shape["points"]
+
+            x1 = points[0][0]
+            y1 = points[0][1]
+            x2 = points[1][0]
+            y2 = points[2][1]
+
+            height, width, x, y = self.calculate_coordinates(x1, x2, y1, y2)
+
+            shapedict = {
+                "label": shape["label"],
+                "coordinates": {
+                    "x": x,
+                    "y": y,
+                    "width": width,
+                    "height": height
+                }
+            }
+            outputimagedict["annotations"].append(shapedict)
+
+        # check if image already in output
+        exists = False
+        for i in range(0, len(outputdict)):
+            if outputdict[i]["image"] == outputimagedict["image"]:
+                exists = True
+                outputdict[i] = outputimagedict
+                break
+
+        if not exists:
+            outputdict.append(outputimagedict)
+
+        Path(self.outputfile).write_text(json.dumps(outputdict), ENCODE_METHOD)
+
+    def calculate_coordinates(self, x1, x2, y1, y2):
+        if x1 < x2:
+            xmin = x1
+            xmax = x2
+        else:
+            xmin = x2
+            xmax = x1
+        if y1 < y2:
+            ymin = y1
+            ymax = y2
+        else:
+            ymin = y2
+            ymax = y1
+        width = xmax - xmin
+        if width < 0:
+            width = width * -1
+        height = ymax - ymin
+        # x and y from center of rect
+        x = xmin + width / 2
+        y = ymin + height / 2
+        return height, width, x, y
+
+
+class CreateMLReader:
+    def __init__(self, jsonpath, filepath):
+        self.jsonpath = jsonpath
+        self.shapes = []
+        self.verified = False
+        self.filename = filepath.split("/")[-1:][0]
+        try:
+            self.parse_json()
+        except ValueError:
+            print("JSON decoding failed")
+
+    def parse_json(self):
+        with open(self.jsonpath, "r") as file:
+            inputdata = file.read()
+
+        outputdict = json.loads(inputdata)
+        self.verified = True
+
+        if len(self.shapes) > 0:
+            self.shapes = []
+        for image in outputdict:
+            if image["image"] == self.filename:
+                for shape in image["annotations"]:
+                    self.add_shape(shape["label"], shape["coordinates"])
+
+    def add_shape(self, label, bndbox):
+        xmin = bndbox["x"] - (bndbox["width"] / 2)
+        ymin = bndbox["y"] - (bndbox["height"] / 2)
+
+        xmax = bndbox["x"] + (bndbox["width"] / 2)
+        ymax = bndbox["y"] + (bndbox["height"] / 2)
+
+        points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
+        self.shapes.append((label, points, None, None, True))
+
+    def get_shapes(self):
+        return self.shapes

+ 31 - 0
utils/PaddleOCR/PPOCRLabel/libs/editinlist.py

@@ -0,0 +1,31 @@
+import sys, time
+from PyQt5 import QtWidgets
+from PyQt5.QtGui import *
+from PyQt5.QtCore import *
+from PyQt5.QtWidgets import *
+
+class EditInList(QListWidget):
+    def __init__(self):
+        super(EditInList,self).__init__()
+        # click to edit
+        self.clicked.connect(self.item_clicked)  
+
+    def item_clicked(self, modelindex: QModelIndex) -> None:
+        self.edited_item = self.currentItem()
+        self.closePersistentEditor(self.edited_item)
+        item = self.item(modelindex.row())
+        # time.sleep(0.2)
+        self.edited_item = item
+        self.openPersistentEditor(item)
+        # time.sleep(0.2)
+        self.editItem(item)
+
+    def mouseDoubleClickEvent(self, event):
+        # close edit
+        for i in range(self.count()):
+            self.closePersistentEditor(self.item(i))
+
+    def leaveEvent(self, event):
+        # close edit
+        for i in range(self.count()):
+            self.closePersistentEditor(self.item(i))

+ 40 - 0
utils/PaddleOCR/PPOCRLabel/libs/hashableQListWidgetItem.py

@@ -0,0 +1,40 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import sys
+try:
+    from PyQt5.QtGui import *
+    from PyQt5.QtCore import *
+    from PyQt5.QtWidgets import *
+except ImportError:
+    # needed for py3+qt4
+    # Ref:
+    # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
+    # http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string
+    if sys.version_info.major >= 3:
+        import sip
+        sip.setapi('QVariant', 2)
+    from PyQt4.QtGui import *
+    from PyQt4.QtCore import *
+
+# PyQt5: TypeError: unhashable type: 'QListWidgetItem'
+
+
+class HashableQListWidgetItem(QListWidgetItem):
+
+    def __init__(self, *args):
+        super(HashableQListWidgetItem, self).__init__(*args)
+
+    def __hash__(self):
+        return hash(id(self))

+ 107 - 0
utils/PaddleOCR/PPOCRLabel/libs/labelDialog.py

@@ -0,0 +1,107 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+try:
+    from PyQt5.QtGui import *
+    from PyQt5.QtCore import *
+    from PyQt5.QtWidgets import *
+except ImportError:
+    from PyQt4.QtGui import *
+    from PyQt4.QtCore import *
+
+from libs.utils import newIcon, labelValidator
+
+BB = QDialogButtonBox
+
+
+class LabelDialog(QDialog):
+
+    def __init__(self, text="Enter object label", parent=None, listItem=None):
+        super(LabelDialog, self).__init__(parent)
+
+        self.edit = QLineEdit()  # OLD
+        # self.edit = QTextEdit()
+        self.edit.setText(text)
+        # self.edit.setValidator(labelValidator()) # 验证有效性
+        self.edit.editingFinished.connect(self.postProcess)
+
+        model = QStringListModel()
+        model.setStringList(listItem)
+        completer = QCompleter()
+        completer.setModel(model)
+        self.edit.setCompleter(completer)
+
+        layout = QVBoxLayout()
+        layout.addWidget(self.edit)
+        self.buttonBox = bb = BB(BB.Ok | BB.Cancel, Qt.Horizontal, self)
+        bb.button(BB.Ok).setIcon(newIcon('done'))
+        bb.button(BB.Cancel).setIcon(newIcon('undo'))
+        bb.accepted.connect(self.validate)
+        bb.rejected.connect(self.reject)
+        layout.addWidget(bb)
+
+        # if listItem is not None and len(listItem) > 0:
+        #     self.listWidget = QListWidget(self)
+        #     for item in listItem:
+        #         self.listWidget.addItem(item)
+        #     self.listWidget.itemClicked.connect(self.listItemClick)
+        #     self.listWidget.itemDoubleClicked.connect(self.listItemDoubleClick)
+        #     layout.addWidget(self.listWidget)
+
+        self.setLayout(layout)
+
+    def validate(self):
+        try:
+            if self.edit.text().trimmed():
+                self.accept()
+        except AttributeError:
+            # PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
+            if self.edit.text().strip():
+                self.accept()
+
+    def postProcess(self):
+        try:
+            self.edit.setText(self.edit.text().trimmed())
+            # print(self.edit.text())
+        except AttributeError:
+            # PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
+            self.edit.setText(self.edit.text())
+            print(self.edit.text())
+
+    def popUp(self, text='', move=True):
+        self.edit.setText(text)
+        self.edit.setSelection(0, len(text))
+        self.edit.setFocus(Qt.PopupFocusReason)
+        if move:
+            cursor_pos = QCursor.pos()
+            parent_bottomRight = self.parentWidget().geometry()
+            max_x = parent_bottomRight.x() + parent_bottomRight.width() - self.sizeHint().width()
+            max_y = parent_bottomRight.y() + parent_bottomRight.height() - self.sizeHint().height()
+            max_global = self.parentWidget().mapToGlobal(QPoint(max_x, max_y))
+            if cursor_pos.x() > max_global.x():
+                cursor_pos.setX(max_global.x())
+            if cursor_pos.y() > max_global.y():
+                cursor_pos.setY(max_global.y())
+            self.move(cursor_pos)
+        return self.edit.text() if self.exec_() else None
+
+    def listItemClick(self, tQListWidgetItem):
+        try:
+            text = tQListWidgetItem.text().trimmed()
+        except AttributeError:
+            # PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
+            text = tQListWidgetItem.text().strip()
+        self.edit.setText(text)
+
+    def listItemDoubleClick(self, tQListWidgetItem):
+        self.listItemClick(tQListWidgetItem)
+        self.validate()

File diff suppressed because it is too large
+ 11114 - 0
utils/PaddleOCR/PPOCRLabel/libs/resources.py


+ 60 - 0
utils/PaddleOCR/PPOCRLabel/libs/settings.py

@@ -0,0 +1,60 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+import pickle
+import os
+import sys
+
+
+class Settings(object):
+    def __init__(self):
+        # Be default, the home will be in the same folder as labelImg
+        home = os.path.expanduser("~")
+        self.data = {}
+        # self.path = os.path.join(home, '.labelImgSettings.pkl')
+        self.path = os.path.join(home, '.autoOCRSettings.pkl')
+
+    def __setitem__(self, key, value):
+        self.data[key] = value
+
+    def __getitem__(self, key):
+        return self.data[key]
+
+    def get(self, key, default=None):
+        if key in self.data:
+            return self.data[key]
+        return default
+
+    def save(self):
+        if self.path:
+            with open(self.path, 'wb') as f:
+                pickle.dump(self.data, f, pickle.HIGHEST_PROTOCOL)
+                return True
+        return False
+
+    def load(self):
+        try:
+            if os.path.exists(self.path):
+                with open(self.path, 'rb') as f:
+                    self.data = pickle.load(f)
+                    return True
+        except:
+            print('Loading setting failed')
+        return False
+
+    def reset(self):
+        if os.path.exists(self.path):
+            os.remove(self.path)
+            print('Remove setting pkl file ${0}'.format(self.path))
+        self.data = {}
+        self.path = None

+ 217 - 0
utils/PaddleOCR/PPOCRLabel/libs/shape.py

@@ -0,0 +1,217 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+
+try:
+    from PyQt5.QtGui import *
+    from PyQt5.QtCore import *
+except ImportError:
+    from PyQt4.QtGui import *
+    from PyQt4.QtCore import *
+
+from libs.utils import distance
+import sys
+
+DEFAULT_LINE_COLOR = QColor(0, 255, 0, 128)
+DEFAULT_FILL_COLOR = QColor(255, 0, 0, 128)
+DEFAULT_SELECT_LINE_COLOR = QColor(255, 255, 255)
+DEFAULT_SELECT_FILL_COLOR = QColor(0, 128, 255, 155)
+DEFAULT_VERTEX_FILL_COLOR = QColor(0, 255, 0, 255)
+DEFAULT_HVERTEX_FILL_COLOR = QColor(255, 0, 0)
+MIN_Y_LABEL = 10
+
+
+class Shape(object):
+    P_SQUARE, P_ROUND = range(2)
+
+    MOVE_VERTEX, NEAR_VERTEX = range(2)
+
+    # The following class variables influence the drawing
+    # of _all_ shape objects.
+    line_color = DEFAULT_LINE_COLOR
+    fill_color = DEFAULT_FILL_COLOR
+    select_line_color = DEFAULT_SELECT_LINE_COLOR
+    select_fill_color = DEFAULT_SELECT_FILL_COLOR
+    vertex_fill_color = DEFAULT_VERTEX_FILL_COLOR
+    hvertex_fill_color = DEFAULT_HVERTEX_FILL_COLOR
+    point_type = P_ROUND
+    point_size = 8
+    scale = 1.0
+
+    def __init__(self, label=None, line_color=None, difficult=False, paintLabel=False):
+        self.label = label
+        self.points = []
+        self.fill = False
+        self.selected = False
+        self.difficult = difficult
+        self.paintLabel = paintLabel
+
+        self._highlightIndex = None
+        self._highlightMode = self.NEAR_VERTEX
+        self._highlightSettings = {
+            self.NEAR_VERTEX: (4, self.P_ROUND),
+            self.MOVE_VERTEX: (1.5, self.P_SQUARE),
+        }
+
+        self._closed = False
+
+        if line_color is not None:
+            # Override the class line_color attribute
+            # with an object attribute. Currently this
+            # is used for drawing the pending line a different color.
+            self.line_color = line_color
+
+    def close(self):
+        self._closed = True
+
+    def reachMaxPoints(self):
+        if len(self.points) >= 4:
+            return True
+        return False
+
+    def addPoint(self, point):
+        if not self.reachMaxPoints():
+            self.points.append(point)
+
+    def popPoint(self):
+        if self.points:
+            return self.points.pop()
+        return None
+
+    def isClosed(self):
+        return self._closed
+
+    def setOpen(self):
+        self._closed = False
+
+    def paint(self, painter):
+        if self.points:
+            color = self.select_line_color if self.selected else self.line_color
+            pen = QPen(color)
+            # Try using integer sizes for smoother drawing(?)
+            pen.setWidth(max(1, int(round(2.0 / self.scale))))
+            painter.setPen(pen)
+
+            line_path = QPainterPath()
+            vrtx_path = QPainterPath()
+
+            line_path.moveTo(self.points[0])
+            # Uncommenting the following line will draw 2 paths
+            # for the 1st vertex, and make it non-filled, which
+            # may be desirable.
+            #self.drawVertex(vrtx_path, 0)
+
+            for i, p in enumerate(self.points):
+                line_path.lineTo(p)
+                self.drawVertex(vrtx_path, i)
+            if self.isClosed():
+                line_path.lineTo(self.points[0])
+
+            painter.drawPath(line_path)
+            painter.drawPath(vrtx_path)
+            painter.fillPath(vrtx_path, self.vertex_fill_color)
+
+            # Draw text at the top-left
+            if self.paintLabel:
+                min_x = sys.maxsize
+                min_y = sys.maxsize
+                for point in self.points:
+                    min_x = min(min_x, point.x())
+                    min_y = min(min_y, point.y())
+                if min_x != sys.maxsize and min_y != sys.maxsize:
+                    font = QFont()
+                    font.setPointSize(8)
+                    font.setBold(True)
+                    painter.setFont(font)
+                    if(self.label == None):
+                        self.label = ""
+                    if(min_y < MIN_Y_LABEL):
+                        min_y += MIN_Y_LABEL
+                    painter.drawText(min_x, min_y, self.label)
+
+            if self.fill:
+                color = self.select_fill_color if self.selected else self.fill_color
+                painter.fillPath(line_path, color)
+
+    def drawVertex(self, path, i):
+        d = self.point_size / self.scale
+        shape = self.point_type
+        point = self.points[i]
+        if i == self._highlightIndex:
+            size, shape = self._highlightSettings[self._highlightMode]
+            d *= size
+        if self._highlightIndex is not None:
+            self.vertex_fill_color = self.hvertex_fill_color
+        else:
+            self.vertex_fill_color = Shape.vertex_fill_color
+        if shape == self.P_SQUARE:
+            path.addRect(point.x() - d / 2, point.y() - d / 2, d, d)
+        elif shape == self.P_ROUND:
+            path.addEllipse(point, d / 2.0, d / 2.0)
+        else:
+            assert False, "unsupported vertex shape"
+
+    def nearestVertex(self, point, epsilon):
+        for i, p in enumerate(self.points):
+            if distance(p - point) <= epsilon:
+                return i
+        return None
+
+    def containsPoint(self, point):
+        return self.makePath().contains(point)
+
+    def makePath(self):
+        path = QPainterPath(self.points[0])
+        for p in self.points[1:]:
+            path.lineTo(p)
+        return path
+
+    def boundingRect(self):
+        return self.makePath().boundingRect()
+
+    def moveBy(self, offset):
+        self.points = [p + offset for p in self.points]
+
+    def moveVertexBy(self, i, offset):
+        self.points[i] = self.points[i] + offset
+
+    def highlightVertex(self, i, action):
+        self._highlightIndex = i
+        self._highlightMode = action
+
+    def highlightClear(self):
+        self._highlightIndex = None
+
+    def copy(self):
+        shape = Shape("%s" % self.label)
+        shape.points = [p for p in self.points]
+        shape.fill = self.fill
+        shape.selected = self.selected
+        shape._closed = self._closed
+        if self.line_color != Shape.line_color:
+            shape.line_color = self.line_color
+        if self.fill_color != Shape.fill_color:
+            shape.fill_color = self.fill_color
+        shape.difficult = self.difficult
+        return shape
+
+    def __len__(self):
+        return len(self.points)
+
+    def __getitem__(self, key):
+        return self.points[key]
+
+    def __setitem__(self, key, value):
+        self.points[key] = value

+ 86 - 0
utils/PaddleOCR/PPOCRLabel/libs/stringBundle.py

@@ -0,0 +1,86 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import re
+import os
+import sys
+import locale
+from libs.ustr import ustr
+
+try:
+    from PyQt5.QtCore import *
+except ImportError:
+    if sys.version_info.major >= 3:
+        import sip
+        sip.setapi('QVariant', 2)
+    from PyQt4.QtCore import *
+
+
+class StringBundle:
+
+    __create_key = object()
+
+    def __init__(self, create_key, localeStr):
+        assert(create_key == StringBundle.__create_key), "StringBundle must be created using StringBundle.getBundle"
+        self.idToMessage = {}
+        paths = self.__createLookupFallbackList(localeStr)
+        for path in paths:
+            self.__loadBundle(path)
+
+    @classmethod
+    def getBundle(cls, localeStr=None):
+        if localeStr is None:
+            try:
+                localeStr = locale.getlocale()[0] if locale.getlocale() and len(
+                    locale.getlocale()) > 0 else os.getenv('LANG')
+            except:
+                print('Invalid locale')
+                localeStr = 'en'
+
+        return StringBundle(cls.__create_key, localeStr)
+
+    def getString(self, stringId):
+        assert(stringId in self.idToMessage), "Missing string id : " + stringId
+        return self.idToMessage[stringId]
+
+    def __createLookupFallbackList(self, localeStr):
+        resultPaths = []
+        basePath = ":/strings"
+        resultPaths.append(basePath)
+        if localeStr is not None:
+            # Don't follow standard BCP47. Simple fallback
+            tags = re.split('[^a-zA-Z]', localeStr)
+            for tag in tags:
+                lastPath = resultPaths[-1]
+                resultPaths.append(lastPath + '-' + tag)
+
+        return resultPaths
+
+    def __loadBundle(self, path):
+        PROP_SEPERATOR = '='
+        f = QFile(path)
+        if f.exists():
+            if f.open(QIODevice.ReadOnly | QFile.Text):
+                text = QTextStream(f)
+                text.setCodec("UTF-8")
+
+            while not text.atEnd():
+                line = ustr(text.readLine())
+                key_value = line.split(PROP_SEPERATOR)
+                key = key_value[0].strip()
+                value = PROP_SEPERATOR.join(key_value[1:]).strip().strip('"')
+                self.idToMessage[key] = value
+
+            f.close()

+ 51 - 0
utils/PaddleOCR/PPOCRLabel/libs/toolBar.py

@@ -0,0 +1,51 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+try:
+    from PyQt5.QtGui import *
+    from PyQt5.QtCore import *
+    from PyQt5.QtWidgets import *
+except ImportError:
+    from PyQt4.QtGui import *
+    from PyQt4.QtCore import *
+
+
+class ToolBar(QToolBar):
+
+    def __init__(self, title):
+        super(ToolBar, self).__init__(title)
+        layout = self.layout()
+        m = (0, 0, 0, 0)
+        layout.setSpacing(0)
+        layout.setContentsMargins(*m)
+        self.setContentsMargins(*m)
+        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
+
+    def addAction(self, action):
+        if isinstance(action, QWidgetAction):
+            return super(ToolBar, self).addAction(action)
+        btn = ToolButton()
+        btn.setDefaultAction(action)
+        btn.setToolButtonStyle(self.toolButtonStyle())
+        self.addWidget(btn)
+
+
+class ToolButton(QToolButton):
+    """ToolBar companion class which ensures all buttons have the same size."""
+    minSize = (60, 60)
+
+    def minimumSizeHint(self):
+        ms = super(ToolButton, self).minimumSizeHint()
+        w1, h1 = ms.width(), ms.height()
+        w2, h2 = self.minSize
+        ToolButton.minSize = max(w1, w2), max(h1, h2)
+        return QSize(*ToolButton.minSize)

+ 29 - 0
utils/PaddleOCR/PPOCRLabel/libs/ustr.py

@@ -0,0 +1,29 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+import sys
+from libs.constants import DEFAULT_ENCODING
+
+def ustr(x):
+    '''py2/py3 unicode helper'''
+
+    if sys.version_info < (3, 0, 0):
+        from PyQt4.QtCore import QString
+        if type(x) == str:
+            return x.decode(DEFAULT_ENCODING)
+        if type(x) == QString:
+            #https://blog.csdn.net/friendan/article/details/51088476
+            #https://blog.csdn.net/xxm524/article/details/74937308
+            return unicode(x.toUtf8(), DEFAULT_ENCODING, 'ignore')
+        return x
+    else:
+        return x

+ 182 - 0
utils/PaddleOCR/PPOCRLabel/libs/utils.py

@@ -0,0 +1,182 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+from math import sqrt
+from libs.ustr import ustr
+import hashlib
+import re
+import sys
+import cv2
+import numpy as np
+
+try:
+    from PyQt5.QtGui import *
+    from PyQt5.QtCore import *
+    from PyQt5.QtWidgets import *
+except ImportError:
+    from PyQt4.QtGui import *
+    from PyQt4.QtCore import *
+
+
+def newIcon(icon, iconSize=None):
+    if iconSize is not None:
+        return QIcon(QIcon(':/' + icon).pixmap(iconSize,iconSize))
+    else:
+        return QIcon(':/' + icon)
+
+
+def newButton(text, icon=None, slot=None):
+    b = QPushButton(text)
+    if icon is not None:
+        b.setIcon(newIcon(icon))
+    if slot is not None:
+        b.clicked.connect(slot)
+    return b
+
+
+def newAction(parent, text, slot=None, shortcut=None, icon=None,
+              tip=None, checkable=False, enabled=True, iconSize=None):
+    """Create a new action and assign callbacks, shortcuts, etc."""
+    a = QAction(text, parent)
+    if icon is not None:
+        if iconSize is not None:
+            a.setIcon(newIcon(icon, iconSize))
+        else:
+            a.setIcon(newIcon(icon))
+    if shortcut is not None:
+        if isinstance(shortcut, (list, tuple)):
+            a.setShortcuts(shortcut)
+        else:
+            a.setShortcut(shortcut)
+    if tip is not None:
+        a.setToolTip(tip)
+        a.setStatusTip(tip)
+    if slot is not None:
+        a.triggered.connect(slot)
+    if checkable:
+        a.setCheckable(True)
+    a.setEnabled(enabled)
+    return a
+
+
+def addActions(widget, actions):
+    for action in actions:
+        if action is None:
+            widget.addSeparator()
+        elif isinstance(action, QMenu):
+            widget.addMenu(action)
+        else:
+            widget.addAction(action)
+
+
+def labelValidator():
+    return QRegExpValidator(QRegExp(r'^[^ \t].+'), None)
+
+
+class struct(object):
+
+    def __init__(self, **kwargs):
+        self.__dict__.update(kwargs)
+
+
+def distance(p):
+    return sqrt(p.x() * p.x() + p.y() * p.y())
+
+
+def fmtShortcut(text):
+    mod, key = text.split('+', 1)
+    return '<b>%s</b>+<b>%s</b>' % (mod, key)
+
+
+def generateColorByText(text):
+    s = ustr(text)
+    hashCode = int(hashlib.sha256(s.encode('utf-8')).hexdigest(), 16)
+    r = int((hashCode / 255) % 255)
+    g = int((hashCode / 65025)  % 255)
+    b = int((hashCode / 16581375)  % 255)
+    return QColor(r, g, b, 100)
+
+def have_qstring():
+    '''p3/qt5 get rid of QString wrapper as py3 has native unicode str type'''
+    return not (sys.version_info.major >= 3 or QT_VERSION_STR.startswith('5.'))
+
+def util_qt_strlistclass():
+    return QStringList if have_qstring() else list
+
+def natural_sort(list, key=lambda s:s):
+    """
+    Sort the list into natural alphanumeric order.
+    """
+    def get_alphanum_key_func(key):
+        convert = lambda text: int(text) if text.isdigit() else text
+        return lambda s: [convert(c) for c in re.split('([0-9]+)', key(s))]
+    sort_key = get_alphanum_key_func(key)
+    list.sort(key=sort_key)
+
+
+def get_rotate_crop_image(img, points):
+
+    try:
+        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
+    except Exception as e:
+        print(e)
+
+def stepsInfo(lang='en'):
+    if lang == 'ch':
+        msg = "1. 安装与运行:使用上述命令安装与运行程序。\n" \
+              "2. 打开文件夹:在菜单栏点击 “文件” - 打开目录 选择待标记图片的文件夹.\n"\
+              "3. 自动标注:点击 ”自动标注“,使用PPOCR超轻量模型对图片文件名前图片状态为 “X” 的图片进行自动标注。\n" \
+              "4. 手动标注:点击 “矩形标注”(推荐直接在英文模式下点击键盘中的 “W”),用户可对当前图片中模型未检出的部分进行手动" \
+              "绘制标记框。点击键盘P,则使用四点标注模式(或点击“编辑” - “四点标注”),用户依次点击4个点后,双击左键表示标注完成。\n" \
+              "5. 标记框绘制完成后,用户点击 “确认”,检测框会先被预分配一个 “待识别” 标签。\n" \
+              "6. 重新识别:将图片中的所有检测画绘制/调整完成后,点击 “重新识别”,PPOCR模型会对当前图片中的**所有检测框**重新识别。\n" \
+              "7. 内容更改:双击识别结果,对不准确的识别结果进行手动更改。\n" \
+              "8. 保存:点击 “保存”,图片状态切换为 “√”,跳转至下一张。\n" \
+              "9. 删除:点击 “删除图像”,图片将会被删除至回收站。\n" \
+              "10. 标注结果:关闭应用程序或切换文件路径后,手动保存过的标签将会被存放在所打开图片文件夹下的" \
+              "*Label.txt*中。在菜单栏点击 “PaddleOCR” - 保存识别结果后,会将此类图片的识别训练数据保存在*crop_img*文件夹下," \
+              "识别标签保存在*rec_gt.txt*中。\n"
+    else:
+        msg = "1. Build and launch using the instructions above.\n" \
+              "2. Click 'Open Dir' in Menu/File to select the folder of the picture.\n"\
+              "3. Click 'Auto recognition', use PPOCR model to automatically annotate images which marked with 'X' before the file name."\
+              "4. Create Box:\n"\
+                   "4.1 Click 'Create RectBox' or press 'W' in English keyboard mode to draw a new rectangle detection box. Click and release left mouse to select a region to annotate the text area.\n"\
+                   "4.2 Press 'P' to enter four-point labeling mode which enables you to create any four-point shape by clicking four points with the left mouse button in succession and DOUBLE CLICK the left mouse as the signal of labeling completion.\n"\
+              "5. After the marking frame is drawn, the user clicks 'OK', and the detection frame will be pre-assigned a TEMPORARY label.\n"\
+              "6. Click re-Recognition, model will rewrite ALL recognition results in ALL detection box.\n"\
+              "7. Double click the result in 'recognition result' list to manually change inaccurate recognition results.\n"\
+              "8. Click 'Save', the image status will switch to '√',then the program automatically jump to the next.\n"\
+              "9. Click 'Delete Image' and the image will be deleted to the recycle bin.\n"\
+              "10. Labeling result: After closing the application or switching the file path, the manually saved label will be stored in *Label.txt* under the opened picture folder.\n"\
+              "    Click PaddleOCR-Save Recognition Results in the menu bar, the recognition training data of such pictures will be saved in the *crop_img* folder, and the recognition label will be saved in *rec_gt.txt*.\n"
+    return msg

+ 38 - 0
utils/PaddleOCR/PPOCRLabel/libs/zoomWidget.py

@@ -0,0 +1,38 @@
+# Copyright (c) <2015-Present> Tzutalin
+# Copyright (C) 2013  MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
+# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+# associated documentation files (the "Software"), to deal in the Software without restriction, including without
+# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
+# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+try:
+    from PyQt5.QtGui import *
+    from PyQt5.QtCore import *
+    from PyQt5.QtWidgets import *
+except ImportError:
+    from PyQt4.QtGui import *
+    from PyQt4.QtCore import *
+
+
+class ZoomWidget(QSpinBox):
+
+    def __init__(self, value=100):
+        super(ZoomWidget, self).__init__()
+        self.setButtonSymbols(QAbstractSpinBox.NoButtons)
+        self.setRange(1, 500)
+        self.setSuffix(' %')
+        self.setValue(value)
+        self.setToolTip(u'Zoom Level')
+        self.setStatusTip(self.toolTip())
+        self.setAlignment(Qt.AlignCenter)
+
+    def minimumSizeHint(self):
+        height = super(ZoomWidget, self).minimumSizeHint().height()
+        fm = QFontMetrics(self.font())
+        width = fm.width(str(self.maximum()))
+        return QSize(width, height)

+ 39 - 0
utils/PaddleOCR/PPOCRLabel/resources.qrc

@@ -0,0 +1,39 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource>
+
+<file alias="help">resources/icons/help.png</file>
+<file alias="app">resources/icons/app.png</file>
+<file alias="Auto">resources/icons/Auto.png</file>
+<file alias="reRec">resources/icons/reRec.png</file>
+<file alias="expert">resources/icons/expert2.png</file>
+<file alias="done">resources/icons/done.png</file>
+<file alias="file">resources/icons/file.png</file>
+<file alias="labels">resources/icons/labels.png</file>
+<file alias="new">resources/icons/objects.png</file>
+<file alias="close">resources/icons/close.png</file>
+<file alias="fit-width">resources/icons/fit-width.png</file>
+<file alias="fit-window">resources/icons/fit-window.png</file>
+<file alias="undo">resources/icons/undo.png</file>
+<file alias="hide">resources/icons/eye.png</file>
+<file alias="quit">resources/icons/quit.png</file>
+<file alias="copy">resources/icons/copy.png</file>
+<file alias="edit">resources/icons/edit.png</file>
+<file alias="open">resources/icons/open.png</file>
+<file alias="save">resources/icons/save.png</file>
+<file alias="format_voc">resources/icons/format_voc.png</file>
+<file alias="format_yolo">resources/icons/format_yolo.png</file>
+<file alias="save-as">resources/icons/save-as.png</file>
+<file alias="color">resources/icons/color.png</file>
+<file alias="color_line">resources/icons/color_line.png</file>
+<file alias="zoom">resources/icons/zoom.png</file>
+<file alias="zoom-in">resources/icons/zoom-in.png</file>
+<file alias="zoom-out">resources/icons/zoom-out.png</file>
+<file alias="delete">resources/icons/cancel.png</file>
+<file alias="next">resources/icons/next.png</file>
+<file alias="prev">resources/icons/prev.png</file>
+<file alias="resetall">resources/icons/resetall.png</file>
+<file alias="verify">resources/icons/verify.png</file>
+<file alias="strings">resources/strings/strings.properties</file>
+<file alias="strings-zh-CN">resources/strings/strings-zh-CN.properties</file>
+</qresource>
+</RCC>

BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/Auto.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/app.icns


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/app.png


File diff suppressed because it is too large
+ 27 - 0
utils/PaddleOCR/PPOCRLabel/resources/icons/app.svg


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/cancel.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/close.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/color.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/color_line.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/copy.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/delete.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/done.png


File diff suppressed because it is too large
+ 400 - 0
utils/PaddleOCR/PPOCRLabel/resources/icons/done.svg


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/edit.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/expert1.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/expert2.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/eye.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/feBlend-icon.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/file.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/fit-width.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/fit-window.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/fit.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/format_createml.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/format_voc.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/format_yolo.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/help.png


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/labels.png


File diff suppressed because it is too large
+ 819 - 0
utils/PaddleOCR/PPOCRLabel/resources/icons/labels.svg


BIN
utils/PaddleOCR/PPOCRLabel/resources/icons/new.png


+ 0 - 0
utils/PaddleOCR/PPOCRLabel/resources/icons/next.png


Some files were not shown because too many files changed in this diff