Skip to content

Commit a0ac413

Browse files
GreatVCopilot
andauthored
Refactor and enhance OCR functionality with PaddleOCR 3.0 support (#161)
* Refactor and enhance OCR functionality with PaddleOCR 3.0 support - Updated PaddleOCR dependency to version 3.0 for improved performance and features. - Refactored OCR-related functions to utilize the new PaddleOCR API, including changes in model initialization and prediction methods. - Enhanced error handling and logging throughout the OCR process for better debugging and user feedback. - Updated documentation strings for clarity and consistency. - Removed deprecated code and unnecessary imports to streamline the codebase. - Added new table recognition capabilities using the updated PaddleOCR features. This commit aims to improve the overall functionality and maintainability of the OCR system. * Refactor code for improved readability and consistency - Cleaned up formatting in multiple files for better readability, including adjustments to line breaks and indentation. * Update tablepyxl/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update tablepyxl/LICENSE Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update libs/shape.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update libs/shape.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update version information to 3.0.0 in __init__.py --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 07facd5 commit a0ac413

23 files changed

+1021
-497
lines changed

PPOCRLabel.py

Lines changed: 347 additions & 312 deletions
Large diffs are not rendered by default.

gen_ocr_train_val_test.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import argparse
66

77

8-
# 删除划分的训练集、验证集、测试集文件夹,重新创建一个空的文件夹
8+
# Delete the divided train, val, and test folders and create a new empty folder
99
def isCreateOrDeleteFolder(path, flag):
1010
flagPath = os.path.join(path, flag)
1111

@@ -66,7 +66,7 @@ def splitTrainVal(
6666
test_txt.write("{}\t{}".format(image_copy_path, image_label))
6767

6868

69-
# 删掉存在的文件
69+
# Remove the file if it exists
7070
def removeFile(path):
7171
if os.path.exists(path):
7272
os.remove(path)
@@ -128,9 +128,13 @@ def genDetRecTrainVal(args):
128128

129129

130130
if __name__ == "__main__":
131-
# 功能描述:分别划分检测和识别的训练集、验证集、测试集
132-
# 说明:可以根据自己的路径和需求调整参数,图像数据往往多人合作分批标注,每一批图像数据放在一个文件夹内用PPOCRLabel进行标注,
133-
# 如此会有多个标注好的图像文件夹汇总并划分训练集、验证集、测试集的需求
131+
"""
132+
Function description: Split detection and recognition datasets into training, validation, and test sets
133+
Note: You can adjust parameters according to your own path and needs. Image data is often annotated
134+
in batches by multiple people collaborating. Each batch of image data is placed in a folder and
135+
annotated using PPOCRLabel. This creates a need to aggregate multiple annotated image folders
136+
and split them into training, validation, and test sets.
137+
"""
134138
parser = argparse.ArgumentParser()
135139
parser.add_argument(
136140
"--trainValTestRatio",

libs/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__version_info__ = ("2", "1", "12")
1+
__version_info__ = ("3", "0", "0")
22
__version__ = ".".join(__version_info__)

libs/autoDialog.py

Lines changed: 60 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,71 @@
1-
try:
2-
from PyQt5.QtGui import *
3-
from PyQt5.QtCore import *
4-
from PyQt5.QtWidgets import *
5-
except ImportError:
6-
from PyQt4.QtGui import *
7-
from PyQt4.QtCore import *
8-
9-
import time
101
import datetime
112
import json
3+
import logging
4+
import time
5+
126
import cv2
137
import numpy as np
8+
from PyQt5.QtCore import QThread, pyqtSignal, Qt
9+
from PyQt5.QtWidgets import (
10+
QDialog,
11+
QDialogButtonBox as BB,
12+
QProgressBar,
13+
QVBoxLayout,
14+
QListWidget,
15+
)
1416

1517
from libs.utils import newIcon
1618

17-
BB = QDialogButtonBox
19+
logger = logging.getLogger("PPOCRLabel")
1820

1921

2022
class Worker(QThread):
2123
progressBarValue = pyqtSignal(int)
2224
listValue = pyqtSignal(str)
23-
endsignal = pyqtSignal(int, str)
25+
end_signal = pyqtSignal(int, str)
2426
handle = 0
2527

26-
def __init__(self, ocr, mImgList, mainThread, model):
28+
def __init__(self, ocr, img_list, main_thread, model):
2729
super(Worker, self).__init__()
30+
self.result_dic = None
2831
self.ocr = ocr
29-
self.mImgList = mImgList
30-
self.mainThread = mainThread
32+
self.img_list = img_list
33+
self.mainThread = main_thread
3134
self.model = model
3235
self.setStackSize(1024 * 1024)
3336

3437
def run(self):
3538
try:
3639
findex = 0
37-
for Imgpath in self.mImgList:
40+
for img_path in self.img_list:
3841
if self.handle == 0:
39-
self.listValue.emit(Imgpath)
42+
self.listValue.emit(img_path)
4043
if self.model == "paddle":
4144
h, w, _ = cv2.imdecode(
42-
np.fromfile(Imgpath, dtype=np.uint8), 1
45+
np.fromfile(img_path, dtype=np.uint8), 1
4346
).shape
4447
if h > 32 and w > 32:
45-
self.result_dic = self.ocr.ocr(Imgpath, cls=True, det=True)[
46-
0
47-
]
48+
result = self.ocr.predict(img_path)[0]
49+
self.result_dic = []
50+
for poly, text, score in zip(
51+
result["rec_polys"],
52+
result["rec_texts"],
53+
result["rec_scores"],
54+
):
55+
# Convert numpy array to list for JSON serialization
56+
poly_list = (
57+
poly.tolist() if hasattr(poly, "tolist") else poly
58+
)
59+
self.result_dic.append([poly_list, (text, score)])
4860
else:
49-
print(
50-
"The size of", Imgpath, "is too small to be recognised"
61+
logger.warning(
62+
"The size of %s is too small to be recognised", img_path
5163
)
5264
self.result_dic = None
5365

5466
# 结果保存
5567
if self.result_dic is None or len(self.result_dic) == 0:
56-
print("Can not recognise file", Imgpath)
68+
logger.warning("Can not recognise file %s", img_path)
5769
pass
5870
else:
5971
strs = ""
@@ -73,32 +85,37 @@ def run(self):
7385
# Sending large amounts of data repeatedly through pyqtSignal may affect the program efficiency
7486
self.listValue.emit(strs)
7587
self.mainThread.result_dic = self.result_dic
76-
self.mainThread.filePath = Imgpath
88+
self.mainThread.filePath = img_path
7789
# 保存
7890
self.mainThread.saveFile(mode="Auto")
7991
findex += 1
8092
self.progressBarValue.emit(findex)
8193
else:
8294
break
83-
self.endsignal.emit(0, "readAll")
95+
self.end_signal.emit(0, "readAll")
8496
self.exec()
8597
except Exception as e:
86-
print(e)
98+
logger.error("Error in worker thread: %s", e)
8799
raise
88100

89101

90102
class AutoDialog(QDialog):
91103
def __init__(
92-
self, text="Enter object label", parent=None, ocr=None, mImgList=None, lenbar=0
104+
self,
105+
text="Enter object label",
106+
parent=None,
107+
ocr=None,
108+
image_list=None,
109+
len_bar=0,
93110
):
94111
super(AutoDialog, self).__init__(parent)
95112
self.setFixedWidth(1000)
96113
self.parent = parent
97114
self.ocr = ocr
98-
self.mImgList = mImgList
99-
self.lender = lenbar
100-
self.pb = QProgressBar()
101-
self.pb.setRange(0, self.lender)
115+
self.img_list = image_list
116+
self.len_bar = len_bar
117+
self.pb = QProgressBar(parent)
118+
self.pb.setRange(0, self.len_bar)
102119
self.pb.setValue(0)
103120

104121
layout = QVBoxLayout()
@@ -121,25 +138,25 @@ def __init__(
121138

122139
# self.setWindowFlags(Qt.WindowCloseButtonHint)
123140

124-
self.thread_1 = Worker(self.ocr, self.mImgList, self.parent, "paddle")
141+
self.thread_1 = Worker(self.ocr, self.img_list, self.parent, "paddle")
125142
self.thread_1.progressBarValue.connect(self.handleProgressBarSingal)
126143
self.thread_1.listValue.connect(self.handleListWidgetSingal)
127-
self.thread_1.endsignal.connect(self.handleEndsignalSignal)
144+
self.thread_1.end_signal.connect(self.handleEndsignalSignal)
128145
self.time_start = time.time() # save start time
129146

130147
def handleProgressBarSingal(self, i):
131148
self.pb.setValue(i)
132149

133150
# calculate time left of auto labeling
134-
avg_time = (
135-
time.time() - self.time_start
136-
) / i # Use average time to prevent time fluctuations
137-
time_left = str(datetime.timedelta(seconds=avg_time * (self.lender - i))).split(
138-
"."
139-
)[
151+
# Use average time to prevent time fluctuations
152+
avg_time = (time.time() - self.time_start) / i
153+
time_left = str(
154+
datetime.timedelta(seconds=avg_time * (self.len_bar - i))
155+
).split(".")[
140156
0
141157
] # Remove microseconds
142-
self.setWindowTitle("PPOCRLabel -- " + f"Time Left: {time_left}") # show
158+
# show
159+
self.setWindowTitle("PPOCRLabel -- " + f"Time Left: {time_left}")
143160

144161
def handleListWidgetSingal(self, i):
145162
self.listWidget.addItem(i)
@@ -152,14 +169,9 @@ def handleEndsignalSignal(self, i, str):
152169
self.buttonBox.button(BB.Cancel).setEnabled(False)
153170

154171
def reject(self):
155-
print("reject")
172+
logger.debug("Auto recognition dialog rejected")
156173
self.thread_1.handle = -1
157174
self.thread_1.quit()
158-
# del self.thread_1
159-
# if self.thread_1.isRunning():
160-
# self.thread_1.terminate()
161-
# self.thread_1.quit()
162-
# super(AutoDialog,self).reject()
163175
while not self.thread_1.isFinished():
164176
pass
165177
self.accept()
@@ -170,22 +182,13 @@ def validate(self):
170182
def postProcess(self):
171183
try:
172184
self.edit.setText(self.edit.text().trimmed())
173-
# print(self.edit.text())
174185
except AttributeError:
175-
# PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
176186
self.edit.setText(self.edit.text())
177-
print(self.edit.text())
187+
logger.debug("Auto dialog text: %s", self.edit.text())
178188

179189
def popUp(self):
180190
self.thread_1.start()
181191
return 1 if self.exec_() else None
182192

183-
def closeEvent(self, event):
184-
print("???")
185-
# if self.thread_1.isRunning():
186-
# self.thread_1.quit()
187-
#
188-
# # self._thread.terminate()
189-
# # del self.thread_1
190-
# super(AutoDialog, self).closeEvent(event)
193+
def closeEvent(self, event, **kwargs):
191194
self.reject()

libs/canvas.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
# THE SOFTWARE.
1313

1414
import copy
15+
import logging
1516

1617
from PyQt5.QtCore import Qt, pyqtSignal, QPointF, QPoint
1718
from PyQt5.QtGui import QPainter, QBrush, QColor, QPixmap
1819
from PyQt5.QtWidgets import QWidget, QMenu, QApplication
1920
from libs.shape import Shape
2021
from libs.utils import distance
2122

23+
logger = logging.getLogger("PPOCRLabel")
24+
2225
CURSOR_DEFAULT = Qt.ArrowCursor
2326
CURSOR_POINT = Qt.PointingHandCursor
2427
CURSOR_DRAW = Qt.CrossCursor
@@ -350,14 +353,16 @@ def handleDrawing(self, pos):
350353
if self.fourpoint:
351354
targetPos = self.line[self.pointnum]
352355
self.current.addPoint(targetPos)
353-
print("current points in handleDrawing is ", self.line[self.pointnum])
356+
logger.debug(
357+
"current points in handleDrawing is %s", self.line[self.pointnum]
358+
)
354359
self.update()
355360
if self.pointnum == 3:
356361
self.finalise()
357362

358363
else:
359364
initPos = self.current[0]
360-
print("initPos", self.current[0])
365+
logger.debug("initPos %s", self.current[0])
361366
minX = initPos.x()
362367
minY = initPos.y()
363368
targetPos = self.line[1]
@@ -369,7 +374,7 @@ def handleDrawing(self, pos):
369374
self.finalise()
370375

371376
elif not self.outOfPixmap(pos):
372-
print("release")
377+
logger.debug("release")
373378
self.current = Shape()
374379
self.current.addPoint(pos)
375380
self.line.points = [pos, pos]
@@ -610,7 +615,7 @@ def paintEvent(self, event):
610615
and self.current is not None
611616
and len(self.current.points) >= 2
612617
):
613-
print("paint event")
618+
logger.debug("paint event")
614619
drawing_shape = self.current.copy()
615620
drawing_shape.addPoint(self.line[1])
616621
drawing_shape.fill = True
@@ -737,7 +742,7 @@ def keyPressEvent(self, ev):
737742
self.shapesBackups.pop()
738743
self.shapesBackups.append(shapesBackup)
739744
if key == Qt.Key_Escape and self.current:
740-
print("ESC press")
745+
logger.debug("ESC press")
741746
self.current = None
742747
self.drawingPolygon.emit(False)
743748
self.update()

libs/colorDialog.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,8 @@
1010
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
1111
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1212
# THE SOFTWARE.
13-
try:
14-
from PyQt5.QtGui import *
15-
from PyQt5.QtCore import *
16-
from PyQt5.QtWidgets import QColorDialog, QDialogButtonBox
17-
except ImportError:
18-
from PyQt4.QtGui import *
19-
from PyQt4.QtCore import *
2013

21-
BB = QDialogButtonBox
14+
from PyQt5.QtWidgets import QColorDialog, QDialogButtonBox as BB
2215

2316

2417
class ColorDialog(QColorDialog):

libs/create_ml_io.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313
#!/usr/bin/env python
1414
# -*- coding: utf8 -*-
1515
import json
16+
import logging
1617
from pathlib import Path
17-
1818
from libs.constants import DEFAULT_ENCODING
1919
import os
2020

21+
logger = logging.getLogger("PPOCRLabel")
22+
2123
JSON_EXT = ".json"
2224
ENCODE_METHOD = DEFAULT_ENCODING
2325

@@ -114,7 +116,7 @@ def __init__(self, jsonpath, filepath):
114116
try:
115117
self.parse_json()
116118
except ValueError:
117-
print("JSON decoding failed")
119+
logger.error("JSON decoding failed")
118120

119121
def parse_json(self):
120122
with open(self.jsonpath, "r") as file:

libs/editinlist.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def item_clicked(self, modelindex: QModelIndex):
1313
try:
1414
if self.edited_item is not None:
1515
self.closePersistentEditor(self.edited_item)
16-
except:
16+
except Exception:
1717
self.edited_item = self.currentItem()
1818

1919
self.edited_item = self.item(modelindex.row())

libs/hashableQListWidgetItem.py

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,7 @@
1212
# THE SOFTWARE.
1313
#!/usr/bin/env python
1414
# -*- coding: utf-8 -*-
15-
import sys
16-
17-
try:
18-
from PyQt5.QtGui import *
19-
from PyQt5.QtCore import *
20-
from PyQt5.QtWidgets import *
21-
except ImportError:
22-
# needed for py3+qt4
23-
# Ref:
24-
# http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
25-
# http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string
26-
if sys.version_info.major >= 3:
27-
import sip
28-
29-
sip.setapi("QVariant", 2)
30-
from PyQt4.QtGui import *
31-
from PyQt4.QtCore import *
32-
33-
# PyQt5: TypeError: unhashable type: 'QListWidgetItem'
15+
from PyQt5.QtWidgets import QListWidgetItem
3416

3517

3618
class HashableQListWidgetItem(QListWidgetItem):

0 commit comments

Comments
 (0)