From 858abe3415d59ff02f1798d3272ef38ffbdb7154 Mon Sep 17 00:00:00 2001 From: 283375 Date: Fri, 13 Oct 2023 20:15:16 +0800 Subject: [PATCH] refactor: TabOcr_B30 --- ui/designer/tabs/tabOcr/tabOcr_B30.ui | 137 ++++++++++++--------- ui/designer/tabs/tabOcr/tabOcr_B30_ui.py | 112 +++++++++-------- ui/extends/ocr/dependencies.py | 28 +++++ ui/extends/shared/cv2_utils.py | 28 ----- ui/implements/tabs/tabOcr/tabOcr_B30.py | 146 +++++++++++++---------- ui/resources/lang/en_US.ts | 22 ++-- ui/resources/lang/zh_CN.ts | 22 ++-- 7 files changed, 282 insertions(+), 213 deletions(-) create mode 100644 ui/extends/ocr/dependencies.py delete mode 100644 ui/extends/shared/cv2_utils.py diff --git a/ui/designer/tabs/tabOcr/tabOcr_B30.ui b/ui/designer/tabs/tabOcr/tabOcr_B30.ui index 5965a79..6a2df22 100644 --- a/ui/designer/tabs/tabOcr/tabOcr_B30.ui +++ b/ui/designer/tabs/tabOcr/tabOcr_B30.ui @@ -13,7 +13,7 @@ TabOcr_B30 - + @@ -27,60 +27,87 @@ - - - - - knnModelSelector.title - - - - - - - - - - - - b30KnnModelSelector.title - - - - - - - - - - - - - - - - phashDatabaseSelector.title - - - - - - - - - - - - imageSelector.title - - - - - - - - - + + + dependencies.title + + + + + + dependencies.knnModel + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ... + + + + + + + Qt::Vertical + + + + + + + dependencies.b30KnnModel + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ... + + + + + + + Qt::Vertical + + + + + + + dependencies.phashDatabase + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ... + + + + + + + + + + + + + + diff --git a/ui/designer/tabs/tabOcr/tabOcr_B30_ui.py b/ui/designer/tabs/tabOcr/tabOcr_B30_ui.py index be0f290..ba0343c 100644 --- a/ui/designer/tabs/tabOcr/tabOcr_B30_ui.py +++ b/ui/designer/tabs/tabOcr/tabOcr_B30_ui.py @@ -15,8 +15,9 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont, QFontDatabase, QGradient, QIcon, QImage, QKeySequence, QLinearGradient, QPainter, QPalette, QPixmap, QRadialGradient, QTransform) -from PySide6.QtWidgets import (QApplication, QComboBox, QGroupBox, QHBoxLayout, - QSizePolicy, QVBoxLayout, QWidget) +from PySide6.QtWidgets import (QApplication, QComboBox, QFrame, QGridLayout, + QGroupBox, QLabel, QSizePolicy, QVBoxLayout, + QWidget) from ui.implements.components.fileSelector import FileSelector from ui.implements.components.ocrQueue import OcrQueue @@ -27,8 +28,8 @@ class Ui_TabOcr_B30(object): TabOcr_B30.setObjectName(u"TabOcr_B30") TabOcr_B30.resize(555, 461) TabOcr_B30.setWindowTitle(u"TabOcr_B30") - self.verticalLayout_3 = QVBoxLayout(TabOcr_B30) - self.verticalLayout_3.setObjectName(u"verticalLayout_3") + self.verticalLayout_2 = QVBoxLayout(TabOcr_B30) + self.verticalLayout_2.setObjectName(u"verticalLayout_2") self.groupBox = QGroupBox(TabOcr_B30) self.groupBox.setObjectName(u"groupBox") self.verticalLayout = QVBoxLayout(self.groupBox) @@ -39,65 +40,80 @@ class Ui_TabOcr_B30(object): self.verticalLayout.addWidget(self.b30TypeComboBox) - self.verticalLayout_3.addWidget(self.groupBox) + self.verticalLayout_2.addWidget(self.groupBox) - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setObjectName(u"horizontalLayout") - self.groupBox_3 = QGroupBox(TabOcr_B30) - self.groupBox_3.setObjectName(u"groupBox_3") - self.verticalLayout_4 = QVBoxLayout(self.groupBox_3) - self.verticalLayout_4.setObjectName(u"verticalLayout_4") - self.knnModelSelector = FileSelector(self.groupBox_3) - self.knnModelSelector.setObjectName(u"knnModelSelector") + self.groupBox_6 = QGroupBox(TabOcr_B30) + self.groupBox_6.setObjectName(u"groupBox_6") + self.gridLayout = QGridLayout(self.groupBox_6) + self.gridLayout.setObjectName(u"gridLayout") + self.label = QLabel(self.groupBox_6) + self.label.setObjectName(u"label") + self.label.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) - self.verticalLayout_4.addWidget(self.knnModelSelector) + self.gridLayout.addWidget(self.label, 0, 0, 1, 1) + self.dependencies_knnModelStatusLabel = QLabel(self.groupBox_6) + self.dependencies_knnModelStatusLabel.setObjectName(u"dependencies_knnModelStatusLabel") + self.dependencies_knnModelStatusLabel.setText(u"...") - self.horizontalLayout.addWidget(self.groupBox_3) + self.gridLayout.addWidget(self.dependencies_knnModelStatusLabel, 0, 2, 1, 1) - self.groupBox_5 = QGroupBox(TabOcr_B30) - self.groupBox_5.setObjectName(u"groupBox_5") - self.verticalLayout_6 = QVBoxLayout(self.groupBox_5) - self.verticalLayout_6.setObjectName(u"verticalLayout_6") - self.b30KnnModelSelector = FileSelector(self.groupBox_5) - self.b30KnnModelSelector.setObjectName(u"b30KnnModelSelector") + self.line_2 = QFrame(self.groupBox_6) + self.line_2.setObjectName(u"line_2") + self.line_2.setFrameShape(QFrame.VLine) + self.line_2.setFrameShadow(QFrame.Sunken) - self.verticalLayout_6.addWidget(self.b30KnnModelSelector) + self.gridLayout.addWidget(self.line_2, 0, 3, 3, 1) + self.label_2 = QLabel(self.groupBox_6) + self.label_2.setObjectName(u"label_2") + self.label_2.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) - self.horizontalLayout.addWidget(self.groupBox_5) + self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1) + self.dependencies_phashDatabaseStatusLabel = QLabel(self.groupBox_6) + self.dependencies_phashDatabaseStatusLabel.setObjectName(u"dependencies_phashDatabaseStatusLabel") + self.dependencies_phashDatabaseStatusLabel.setText(u"...") - self.verticalLayout_3.addLayout(self.horizontalLayout) + self.gridLayout.addWidget(self.dependencies_phashDatabaseStatusLabel, 2, 2, 1, 1) - self.horizontalLayout_3 = QHBoxLayout() - self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") - self.groupBox_4 = QGroupBox(TabOcr_B30) - self.groupBox_4.setObjectName(u"groupBox_4") - self.verticalLayout_5 = QVBoxLayout(self.groupBox_4) - self.verticalLayout_5.setObjectName(u"verticalLayout_5") - self.phashDatabaseSelector = FileSelector(self.groupBox_4) - self.phashDatabaseSelector.setObjectName(u"phashDatabaseSelector") + self.line = QFrame(self.groupBox_6) + self.line.setObjectName(u"line") + self.line.setFrameShape(QFrame.VLine) + self.line.setFrameShadow(QFrame.Sunken) - self.verticalLayout_5.addWidget(self.phashDatabaseSelector) + self.gridLayout.addWidget(self.line, 0, 1, 3, 1) + self.label_3 = QLabel(self.groupBox_6) + self.label_3.setObjectName(u"label_3") + self.label_3.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) - self.horizontalLayout_3.addWidget(self.groupBox_4) + self.gridLayout.addWidget(self.label_3, 2, 0, 1, 1) - self.groupBox_2 = QGroupBox(TabOcr_B30) - self.groupBox_2.setObjectName(u"groupBox_2") - self.verticalLayout_2 = QVBoxLayout(self.groupBox_2) - self.verticalLayout_2.setObjectName(u"verticalLayout_2") - self.imageSelector = FileSelector(self.groupBox_2) - self.imageSelector.setObjectName(u"imageSelector") + self.dependencies_b30KnnModelStatusLabel = QLabel(self.groupBox_6) + self.dependencies_b30KnnModelStatusLabel.setObjectName(u"dependencies_b30KnnModelStatusLabel") + self.dependencies_b30KnnModelStatusLabel.setText(u"...") - self.verticalLayout_2.addWidget(self.imageSelector) + self.gridLayout.addWidget(self.dependencies_b30KnnModelStatusLabel, 1, 2, 1, 1) + self.dependencies_knnModelSelector = FileSelector(self.groupBox_6) + self.dependencies_knnModelSelector.setObjectName(u"dependencies_knnModelSelector") - self.horizontalLayout_3.addWidget(self.groupBox_2) + self.gridLayout.addWidget(self.dependencies_knnModelSelector, 0, 4, 1, 1) + self.dependencies_b30KnnModelSelector = FileSelector(self.groupBox_6) + self.dependencies_b30KnnModelSelector.setObjectName(u"dependencies_b30KnnModelSelector") - self.verticalLayout_3.addLayout(self.horizontalLayout_3) + self.gridLayout.addWidget(self.dependencies_b30KnnModelSelector, 1, 4, 1, 1) + + self.dependencies_phashDatabaseSelector = FileSelector(self.groupBox_6) + self.dependencies_phashDatabaseSelector.setObjectName(u"dependencies_phashDatabaseSelector") + + self.gridLayout.addWidget(self.dependencies_phashDatabaseSelector, 2, 4, 1, 1) + + self.gridLayout.setColumnStretch(4, 1) + + self.verticalLayout_2.addWidget(self.groupBox_6) self.ocrQueue = OcrQueue(TabOcr_B30) self.ocrQueue.setObjectName(u"ocrQueue") @@ -107,7 +123,7 @@ class Ui_TabOcr_B30(object): sizePolicy.setHeightForWidth(self.ocrQueue.sizePolicy().hasHeightForWidth()) self.ocrQueue.setSizePolicy(sizePolicy) - self.verticalLayout_3.addWidget(self.ocrQueue) + self.verticalLayout_2.addWidget(self.ocrQueue) self.retranslateUi(TabOcr_B30) @@ -117,10 +133,10 @@ class Ui_TabOcr_B30(object): def retranslateUi(self, TabOcr_B30): self.groupBox.setTitle(QCoreApplication.translate("TabOcr_B30", u"b30type", None)) - self.groupBox_3.setTitle(QCoreApplication.translate("TabOcr_B30", u"knnModelSelector.title", None)) - self.groupBox_5.setTitle(QCoreApplication.translate("TabOcr_B30", u"b30KnnModelSelector.title", None)) - self.groupBox_4.setTitle(QCoreApplication.translate("TabOcr_B30", u"phashDatabaseSelector.title", None)) - self.groupBox_2.setTitle(QCoreApplication.translate("TabOcr_B30", u"imageSelector.title", None)) + self.groupBox_6.setTitle(QCoreApplication.translate("TabOcr_B30", u"dependencies.title", None)) + self.label.setText(QCoreApplication.translate("TabOcr_B30", u"dependencies.knnModel", None)) + self.label_2.setText(QCoreApplication.translate("TabOcr_B30", u"dependencies.b30KnnModel", None)) + self.label_3.setText(QCoreApplication.translate("TabOcr_B30", u"dependencies.phashDatabase", None)) pass # retranslateUi diff --git a/ui/extends/ocr/dependencies.py b/ui/extends/ocr/dependencies.py new file mode 100644 index 0000000..6fffbbb --- /dev/null +++ b/ui/extends/ocr/dependencies.py @@ -0,0 +1,28 @@ +import cv2 +from arcaea_offline_ocr.phash_db import ImagePhashDatabase + + +def getCv2StatModelStatusText(model: cv2.ml.StatModel): + if not isinstance(model, cv2.ml.StatModel): + return 'ERROR' + + varCount = model.getVarCount() + if varCount != 81: + return f'WARN, varCount {varCount}' + else: + return f'OK, varCount {varCount}' + + +def getPhashDatabaseStatusText(db: ImagePhashDatabase): + if not isinstance(db, ImagePhashDatabase): + return 'ERROR' + + jacketCount = len(db.jacket_hashes) + partnerIconCount = len(db.partner_icon_hashes) + + statusText = f"J{jacketCount} PI{partnerIconCount}" + + if partnerIconCount <= 0: + return f'WARN, {statusText}' + else: + return f'OK, {statusText}' diff --git a/ui/extends/shared/cv2_utils.py b/ui/extends/shared/cv2_utils.py deleted file mode 100644 index c587a4d..0000000 --- a/ui/extends/shared/cv2_utils.py +++ /dev/null @@ -1,28 +0,0 @@ -import cv2 -import numpy as np -from PySide6.QtGui import QImage - - -def cv2BgrMatToQImage(mat) -> QImage: - arr = np.ascontiguousarray(mat) - return QImage( - arr.data, - arr.shape[1], - arr.shape[0], - arr.strides[0], - QImage.Format.Format_RGB888, - ).rgbSwapped() - - -def qImageToCvMatBgr(qImg: QImage): - # from Bing AI, references - # 1: https://stackoverflow.com/q/384759/16484891 | CC BY-SA 4.0 - # 2: https://stackoverflow.com/q/37552924/16484891 | CC BY-SA 3.0 - qImg = qImg.convertToFormat(QImage.Format.Format_RGB888) - qImg = qImg.copy().rgbSwapped() - return np.ndarray( - (qImg.height(), qImg.width(), 3), - buffer=qImg.constBits(), - strides=[qImg.bytesPerLine(), 3, 1], - dtype=np.uint8, - ) diff --git a/ui/implements/tabs/tabOcr/tabOcr_B30.py b/ui/implements/tabs/tabOcr/tabOcr_B30.py index 37ff12b..0473c7b 100644 --- a/ui/implements/tabs/tabOcr/tabOcr_B30.py +++ b/ui/implements/tabs/tabOcr/tabOcr_B30.py @@ -1,16 +1,20 @@ import logging import cv2 +import numpy as np from arcaea_offline_ocr.b30.chieri.v4.ocr import ChieriBotV4Ocr -from arcaea_offline_ocr.phash_db import ImagePHashDatabase -from arcaea_offline_ocr.sift_db import SIFTDatabase +from arcaea_offline_ocr.phash_db import ImagePhashDatabase from arcaea_offline_ocr.utils import imread_unicode +from PIL import Image from PySide6.QtCore import Signal, Slot -from PySide6.QtWidgets import QWidget +from PySide6.QtWidgets import QFileDialog, QMessageBox, QWidget from ui.designer.tabs.tabOcr.tabOcr_B30_ui import Ui_TabOcr_B30 from ui.extends.components.ocrQueue import OcrQueueModel -from ui.extends.shared.cv2_utils import cv2BgrMatToQImage, qImageToCvMatBgr +from ui.extends.ocr.dependencies import ( + getCv2StatModelStatusText, + getPhashDatabaseStatusText, +) from ui.extends.shared.language import LanguageChangeEventFilter from ui.extends.shared.settings import ( B30_KNN_MODEL_FILE, @@ -36,97 +40,119 @@ class TabOcr_B30(Ui_TabOcr_B30, QWidget): self.b30TypeComboBox.setCurrentIndex(0) self.b30TypeComboBox.setEnabled(False) - self.imageSelector.filesSelected.connect(self.imageSelected) - self.knnModelSelector.filesSelected.connect(self.knnModelSelected) - self.b30KnnModelSelector.filesSelected.connect(self.b30KnnModelSelected) - self.phashDatabaseSelector.filesSelected.connect(self.phashDatabaseSelected) + self.dependencies_knnModelSelector.filesSelected.connect(self.knnModelSelected) + self.dependencies_b30KnnModelSelector.filesSelected.connect( + self.b30KnnModelSelected + ) + self.dependencies_phashDatabaseSelector.filesSelected.connect( + self.phashDatabaseSelected + ) - self.imagePath = None # for checking only - self.img = None - self.paddleFolder = None - self.paddle = None self.knnModel = None self.b30KnnModel = None - # self.siftDatabase = None self.phashDatabase = None self.ocr = None - self.tryPrepareOcr.connect(self.prepareOcr) - logger.info("Applying settings...") - self.knnModelSelector.connectSettings(KNN_MODEL_FILE) - self.b30KnnModelSelector.connectSettings(B30_KNN_MODEL_FILE) - self.phashDatabaseSelector.connectSettings(PHASH_DATABASE_FILE) + self.dependencies_knnModelSelector.connectSettings(KNN_MODEL_FILE) + self.dependencies_b30KnnModelSelector.connectSettings(B30_KNN_MODEL_FILE) + self.dependencies_phashDatabaseSelector.connectSettings(PHASH_DATABASE_FILE) self.ocrQueueModel = OcrQueueModel(self) self.ocrQueue.setModel(self.ocrQueueModel) - def imageSelected(self): - if selectedFiles := self.imageSelector.selectedFiles(): - imagePath = selectedFiles[0] - self.imagePath = imagePath - self.img = imread_unicode(imagePath) - self.tryPrepareOcr.emit() + # def imageSelected(self): + # if selectedFiles := self.imageSelector.selectedFiles(): + # imagePath = selectedFiles[0] + # self.imagePath = imagePath + # self.img = imread_unicode(imagePath) + # self.tryPrepareOcr.emit() def knnModelSelected(self): - if selectedFiles := self.knnModelSelector.selectedFiles(): - knnModelPath = selectedFiles[0] - self.knnModel = cv2.ml.KNearest.load(knnModelPath) - self.tryPrepareOcr.emit() + try: + filePath = self.dependencies_knnModelSelector.selectedFiles()[0] + self.knnModel = cv2.ml.KNearest.load(filePath) + except Exception: + self.knnModel = None + logger.exception("Error loading knn model:") + finally: + self.dependencies_knnModelStatusLabel.setText( + getCv2StatModelStatusText(self.knnModel) + ) def b30KnnModelSelected(self): - if selectedFiles := self.b30KnnModelSelector.selectedFiles(): - b30KnnModelPath = selectedFiles[0] - self.b30KnnModel = cv2.ml.KNearest.load(b30KnnModelPath) - self.tryPrepareOcr.emit() + try: + filePath = self.dependencies_b30KnnModelSelector.selectedFiles()[0] + self.b30KnnModel = cv2.ml.KNearest.load(filePath) + except Exception: + self.b30KnnModel = None + logger.exception("Error loading b30 knn model:") + finally: + self.dependencies_b30KnnModelStatusLabel.setText( + getCv2StatModelStatusText(self.b30KnnModel) + ) def phashDatabaseSelected(self): - if selectedFiles := self.phashDatabaseSelector.selectedFiles(): - phashDatabasePath = selectedFiles[0] - self.phashDatabase = ImagePHashDatabase(phashDatabasePath) - self.tryPrepareOcr.emit() + try: + filePath = self.dependencies_phashDatabaseSelector.selectedFiles()[0] + self.phashDatabase = ImagePhashDatabase(filePath) + except Exception: + self.phashDatabase = None + logger.exception("Error loading phash database:") + finally: + self.dependencies_phashDatabaseStatusLabel.setText( + getPhashDatabaseStatusText(self.phashDatabase) + ) - def prepareOcr(self): + def checkDependencies(self): b30Type = self.b30TypeComboBox.currentData() if not b30Type: + return False + elif b30Type == "chieri_v4": + return ( + self.knnModel is not None + and self.b30KnnModel is not None + and self.phashDatabase is not None + ) + else: + return False + + @Slot() + def on_ocr_addImageButton_clicked(self): + if not self.checkDependencies(): + QMessageBox.critical(self, None, "Dependencies not configured.") return - if b30Type == "chieri_v4": - if ( - not self.imagePath - or not self.knnModel - or not self.b30KnnModel - or not self.phashDatabase - ): - return + imagePath, _ = QFileDialog.getOpenFileName( + self, None, "", "Image Files (*.png *.jpg *.jpeg *.bmp *.webp);;*" + ) - self.ocrQueueModel.clear() + if not imagePath: + return - ocr = ChieriBotV4Ocr(self.knnModel, self.b30KnnModel, self.phashDatabase) - ocr.set_factor(self.img) - self.ocr = ocr + self.ocrQueueModel.clear() - roi = ocr.rois - for component in roi.components(self.img): - qImage = cv2BgrMatToQImage(component.copy()) - self.ocrQueueModel.addItem(qImage) + img = imread_unicode(imagePath, cv2.IMREAD_COLOR) + ocr = ChieriBotV4Ocr(self.knnModel, self.b30KnnModel, self.phashDatabase) + ocr.set_factor(img) + self.ocr = ocr + + roi = ocr.rois + for component in roi.components(img): + qImage = Image.fromarray(component.copy()).toqimage() + self.ocrQueueModel.addItem(qImage) self.ocrQueue.resizeTableView() @Slot() def on_ocr_startButton_clicked(self): - if ( - not self.imagePath - or not self.knnModel - or not self.b30KnnModel - or not self.phashDatabase - ): + if not self.ocr: return for row in range(self.ocrQueueModel.rowCount()): index = self.ocrQueueModel.index(row, 0) qImage = index.data(OcrQueueModel.ImageQImageRole) - cv2Mat = qImageToCvMatBgr(qImage) + cv2Mat = np.array(Image.fromqimage(qImage)) runnable = ChieriV4OcrRunnable(self.ocr, cv2Mat) self.ocrQueueModel.setData(index, runnable, OcrQueueModel.OcrRunnableRole) self.ocrQueueModel.setData( diff --git a/ui/resources/lang/en_US.ts b/ui/resources/lang/en_US.ts index 60cd6b7..973c9a2 100644 --- a/ui/resources/lang/en_US.ts +++ b/ui/resources/lang/en_US.ts @@ -692,24 +692,24 @@ validation B30 Image Type - - knnModelSelector.title - Select KNearest Model + + dependencies.title + OCR Dependencies - - b30KnnModelSelector.title - Select B30 Specialized KNearest Model + + dependencies.knnModel + KNearest model - phashDatabaseSelector.title - Select Image PHash Database + dependencies.b30KnnModel + B30 KNearest model - - imageSelector.title - Select Image + + dependencies.phashDatabase + Image pHash database diff --git a/ui/resources/lang/zh_CN.ts b/ui/resources/lang/zh_CN.ts index 47eb270..0064c23 100644 --- a/ui/resources/lang/zh_CN.ts +++ b/ui/resources/lang/zh_CN.ts @@ -691,24 +691,24 @@ B30 图片类型 - - knnModelSelector.title - 选择 KNearest 模型 + + dependencies.title + OCR 依赖 - - b30KnnModelSelector.title - 选择 B30 特别版 KNearest 模型 + + dependencies.knnModel + KNearest 模型 - phashDatabaseSelector.title - 选择图像 PHash 数据库 + dependencies.b30KnnModel + B30 KNearest 模型 - - imageSelector.title - 选择图片 + + dependencies.phashDatabase + 图像 pHash 数据库