Skip to content

Commit 8958d83

Browse files
UserWangZzadiel
authored and
adiel
committed
Fix the error of token_list formatting only taking the first digit when the number of cells across rows or columns is greater than 10 (PaddlePaddle#11839)
1 parent f6373fa commit 8958d83

File tree

1 file changed

+326
-0
lines changed

1 file changed

+326
-0
lines changed

Diff for: PPOCRLabel/libs/utils.py

+326
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
# Copyright (c) <2015-Present> Tzutalin
2+
# Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
3+
# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
4+
# associated documentation files (the "Software"), to deal in the Software without restriction, including without
5+
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
6+
# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
8+
# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
9+
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
10+
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
11+
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
12+
# THE SOFTWARE.
13+
import hashlib
14+
import os
15+
import re
16+
import sys
17+
from math import sqrt
18+
19+
import cv2
20+
import numpy as np
21+
from PyQt5.QtCore import QRegExp, QT_VERSION_STR
22+
from PyQt5.QtGui import QIcon, QRegExpValidator, QColor
23+
from PyQt5.QtWidgets import QPushButton, QAction, QMenu
24+
from libs.ustr import ustr
25+
26+
__dir__ = os.path.dirname(os.path.abspath(__file__)) # 获取本程序文件路径
27+
__iconpath__ = os.path.abspath(os.path.join(__dir__, '../resources/icons'))
28+
29+
30+
def newIcon(icon, iconSize=None):
31+
if iconSize is not None:
32+
return QIcon(QIcon(__iconpath__ + "/" + icon + ".png").pixmap(iconSize, iconSize))
33+
else:
34+
return QIcon(__iconpath__ + "/" + icon + ".png")
35+
36+
37+
def newButton(text, icon=None, slot=None):
38+
b = QPushButton(text)
39+
if icon is not None:
40+
b.setIcon(newIcon(icon))
41+
if slot is not None:
42+
b.clicked.connect(slot)
43+
return b
44+
45+
46+
def newAction(parent, text, slot=None, shortcut=None, icon=None,
47+
tip=None, checkable=False, enabled=True, iconSize=None):
48+
"""Create a new action and assign callbacks, shortcuts, etc."""
49+
a = QAction(text, parent)
50+
if icon is not None:
51+
if iconSize is not None:
52+
a.setIcon(newIcon(icon, iconSize))
53+
else:
54+
a.setIcon(newIcon(icon))
55+
if shortcut is not None:
56+
if isinstance(shortcut, (list, tuple)):
57+
a.setShortcuts(shortcut)
58+
else:
59+
a.setShortcut(shortcut)
60+
if tip is not None:
61+
a.setToolTip(tip)
62+
a.setStatusTip(tip)
63+
if slot is not None:
64+
a.triggered.connect(slot)
65+
if checkable:
66+
a.setCheckable(True)
67+
a.setEnabled(enabled)
68+
return a
69+
70+
71+
def addActions(widget, actions):
72+
for action in actions:
73+
if action is None:
74+
widget.addSeparator()
75+
elif isinstance(action, QMenu):
76+
widget.addMenu(action)
77+
else:
78+
widget.addAction(action)
79+
80+
81+
def labelValidator():
82+
return QRegExpValidator(QRegExp(r'^[^ \t].+'), None)
83+
84+
85+
class struct(object):
86+
87+
def __init__(self, **kwargs):
88+
self.__dict__.update(kwargs)
89+
90+
91+
def distance(p):
92+
return sqrt(p.x() * p.x() + p.y() * p.y())
93+
94+
95+
def fmtShortcut(text):
96+
mod, key = text.split('+', 1)
97+
return '<b>%s</b>+<b>%s</b>' % (mod, key)
98+
99+
100+
def generateColorByText(text):
101+
s = ustr(text)
102+
hashCode = int(hashlib.sha256(s.encode('utf-8')).hexdigest(), 16)
103+
r = int((hashCode / 255) % 255)
104+
g = int((hashCode / 65025) % 255)
105+
b = int((hashCode / 16581375) % 255)
106+
return QColor(r, g, b, 100)
107+
108+
109+
def have_qstring():
110+
'''p3/qt5 get rid of QString wrapper as py3 has native unicode str type'''
111+
return not (sys.version_info.major >= 3 or QT_VERSION_STR.startswith('5.'))
112+
113+
114+
def natural_sort(list, key=lambda s: s):
115+
"""
116+
Sort the list into natural alphanumeric order.
117+
"""
118+
119+
def get_alphanum_key_func(key):
120+
convert = lambda text: int(text) if text.isdigit() else text
121+
return lambda s: [convert(c) for c in re.split('([0-9]+)', key(s))]
122+
123+
sort_key = get_alphanum_key_func(key)
124+
list.sort(key=sort_key)
125+
126+
127+
def get_rotate_crop_image(img, points):
128+
# Use Green's theory to judge clockwise or counterclockwise
129+
# author: biyanhua
130+
d = 0.0
131+
for index in range(-1, 3):
132+
d += -0.5 * (points[index + 1][1] + points[index][1]) * (
133+
points[index + 1][0] - points[index][0])
134+
if d < 0: # counterclockwise
135+
tmp = np.array(points)
136+
points[1], points[3] = tmp[3], tmp[1]
137+
138+
try:
139+
img_crop_width = int(
140+
max(
141+
np.linalg.norm(points[0] - points[1]),
142+
np.linalg.norm(points[2] - points[3])))
143+
img_crop_height = int(
144+
max(
145+
np.linalg.norm(points[0] - points[3]),
146+
np.linalg.norm(points[1] - points[2])))
147+
pts_std = np.float32([[0, 0], [img_crop_width, 0],
148+
[img_crop_width, img_crop_height],
149+
[0, img_crop_height]])
150+
M = cv2.getPerspectiveTransform(points, pts_std)
151+
dst_img = cv2.warpPerspective(
152+
img,
153+
M, (img_crop_width, img_crop_height),
154+
borderMode=cv2.BORDER_REPLICATE,
155+
flags=cv2.INTER_CUBIC)
156+
dst_img_height, dst_img_width = dst_img.shape[0:2]
157+
if dst_img_height * 1.0 / dst_img_width >= 1.5:
158+
dst_img = np.rot90(dst_img)
159+
return dst_img
160+
except Exception as e:
161+
print(e)
162+
163+
164+
def boxPad(box, imgShape, pad : int) -> np.array:
165+
"""
166+
Pad a box with [pad] pixels on each side.
167+
"""
168+
box = np.array(box, dtype=np.int32)
169+
box[0][0], box[0][1] = box[0][0] - pad, box[0][1] - pad
170+
box[1][0], box[1][1] = box[1][0] + pad, box[1][1] - pad
171+
box[2][0], box[2][1] = box[2][0] + pad, box[2][1] + pad
172+
box[3][0], box[3][1] = box[3][0] - pad, box[3][1] + pad
173+
h, w, _ = imgShape
174+
box[:,0] = np.clip(box[:,0], 0, w)
175+
box[:,1] = np.clip(box[:,1], 0, h)
176+
return box
177+
178+
179+
def expand_list(merged, html_list):
180+
'''
181+
Fill blanks according to merged cells
182+
'''
183+
sr, er, sc, ec = merged
184+
for i in range(sr, er):
185+
for j in range(sc, ec):
186+
html_list[i][j] = None
187+
html_list[sr][sc] = ''
188+
if ec - sc > 1:
189+
html_list[sr][sc] += " colspan={}".format(ec - sc)
190+
if er - sr > 1:
191+
html_list[sr][sc] += " rowspan={}".format(er - sr)
192+
return html_list
193+
194+
195+
def convert_token(html_list):
196+
'''
197+
Convert raw html to label format
198+
'''
199+
token_list = ["<tbody>"]
200+
# final html list:
201+
for row in html_list:
202+
token_list.append("<tr>")
203+
for col in row:
204+
if col == None:
205+
continue
206+
elif col == 'td':
207+
token_list.extend(["<td>", "</td>"])
208+
else:
209+
token_list.append("<td")
210+
if 'colspan' in col:
211+
_, n = col.split('colspan=')
212+
token_list.append(" colspan=\"{}\"".format(str(int(n))))
213+
if 'rowspan' in col:
214+
_, n = col.split('rowspan=')
215+
token_list.append(" rowspan=\"{}\"".format(str(int(n))))
216+
token_list.extend([">", "</td>"])
217+
token_list.append("</tr>")
218+
token_list.append("</tbody>")
219+
220+
return token_list
221+
222+
223+
def rebuild_html_from_ppstructure_label(label_info):
224+
from html import escape
225+
html_code = label_info['html']['structure']['tokens'].copy()
226+
to_insert = [
227+
i for i, tag in enumerate(html_code) if tag in ('<td>', '>')
228+
]
229+
for i, cell in zip(to_insert[::-1], label_info['html']['cells'][::-1]):
230+
if cell['tokens']:
231+
cell = [
232+
escape(token) if len(token) == 1 else token
233+
for token in cell['tokens']
234+
]
235+
cell = ''.join(cell)
236+
html_code.insert(i + 1, cell)
237+
html_code = ''.join(html_code)
238+
html_code = '<html><body><table>{}</table></body></html>'.format(
239+
html_code)
240+
return html_code
241+
242+
243+
def stepsInfo(lang='en'):
244+
if lang == 'ch':
245+
msg = "1. 安装与运行:使用上述命令安装与运行程序。\n" \
246+
"2. 打开文件夹:在菜单栏点击 “文件” - 打开目录 选择待标记图片的文件夹.\n" \
247+
"3. 自动标注:点击 ”自动标注“,使用PPOCR超轻量模型对图片文件名前图片状态为 “X” 的图片进行自动标注。\n" \
248+
"4. 手动标注:点击 “矩形标注”(推荐直接在英文模式下点击键盘中的 “W”),用户可对当前图片中模型未检出的部分进行手动" \
249+
"绘制标记框。点击键盘P,则使用四点标注模式(或点击“编辑” - “四点标注”),用户依次点击4个点后,双击左键表示标注完成。\n" \
250+
"5. 标记框绘制完成后,用户点击 “确认”,检测框会先被预分配一个 “待识别” 标签。\n" \
251+
"6. 重新识别:将图片中的所有检测画绘制/调整完成后,点击 “重新识别”,PPOCR模型会对当前图片中的**所有检测框**重新识别。\n" \
252+
"7. 内容更改:双击识别结果,对不准确的识别结果进行手动更改。\n" \
253+
"8. 保存:点击 “保存”,图片状态切换为 “√”,跳转至下一张。\n" \
254+
"9. 删除:点击 “删除图像”,图片将会被删除至回收站。\n" \
255+
"10. 标注结果:关闭应用程序或切换文件路径后,手动保存过的标签将会被存放在所打开图片文件夹下的" \
256+
"*Label.txt*中。在菜单栏点击 “PaddleOCR” - 保存识别结果后,会将此类图片的识别训练数据保存在*crop_img*文件夹下," \
257+
"识别标签保存在*rec_gt.txt*中。\n"
258+
259+
else:
260+
msg = "1. Build and launch using the instructions above.\n" \
261+
"2. Click 'Open Dir' in Menu/File to select the folder of the picture.\n" \
262+
"3. Click 'Auto recognition', use PPOCR model to automatically annotate images which marked with 'X' before the file name." \
263+
"4. Create Box:\n" \
264+
"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" \
265+
"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" \
266+
"5. After the marking frame is drawn, the user clicks 'OK', and the detection frame will be pre-assigned a TEMPORARY label.\n" \
267+
"6. Click re-Recognition, model will rewrite ALL recognition results in ALL detection box.\n" \
268+
"7. Double click the result in 'recognition result' list to manually change inaccurate recognition results.\n" \
269+
"8. Click 'Save', the image status will switch to '√',then the program automatically jump to the next.\n" \
270+
"9. Click 'Delete Image' and the image will be deleted to the recycle bin.\n" \
271+
"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" \
272+
" 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"
273+
274+
return msg
275+
276+
277+
def keysInfo(lang='en'):
278+
if lang == 'ch':
279+
msg = "快捷键\t\t\t说明\n" \
280+
"———————————————————————\n" \
281+
"Ctrl + shift + R\t\t对当前图片的所有标记重新识别\n" \
282+
"W\t\t\t新建矩形框\n" \
283+
"Q\t\t\t新建四点框\n" \
284+
"Ctrl + E\t\t编辑所选框标签\n" \
285+
"Ctrl + R\t\t重新识别所选标记\n" \
286+
"Ctrl + C\t\t复制并粘贴选中的标记框\n" \
287+
"Ctrl + 鼠标左键\t\t多选标记框\n" \
288+
"Backspace\t\t删除所选框\n" \
289+
"Ctrl + V\t\t确认本张图片标记\n" \
290+
"Ctrl + Shift + d\t删除本张图片\n" \
291+
"D\t\t\t下一张图片\n" \
292+
"A\t\t\t上一张图片\n" \
293+
"Ctrl++\t\t\t缩小\n" \
294+
"Ctrl--\t\t\t放大\n" \
295+
"↑→↓←\t\t\t移动标记框\n" \
296+
"———————————————————————\n" \
297+
"注:Mac用户Command键替换上述Ctrl键"
298+
299+
else:
300+
msg = "Shortcut Keys\t\tDescription\n" \
301+
"———————————————————————\n" \
302+
"Ctrl + shift + R\t\tRe-recognize all the labels\n" \
303+
"\t\t\tof the current image\n" \
304+
"\n" \
305+
"W\t\t\tCreate a rect box\n" \
306+
"Q\t\t\tCreate a four-points box\n" \
307+
"Ctrl + E\t\tEdit label of the selected box\n" \
308+
"Ctrl + R\t\tRe-recognize the selected box\n" \
309+
"Ctrl + C\t\tCopy and paste the selected\n" \
310+
"\t\t\tbox\n" \
311+
"\n" \
312+
"Ctrl + Left Mouse\tMulti select the label\n" \
313+
"Button\t\t\tbox\n" \
314+
"\n" \
315+
"Backspace\t\tDelete the selected box\n" \
316+
"Ctrl + V\t\tCheck image\n" \
317+
"Ctrl + Shift + d\tDelete image\n" \
318+
"D\t\t\tNext image\n" \
319+
"A\t\t\tPrevious image\n" \
320+
"Ctrl++\t\t\tZoom in\n" \
321+
"Ctrl--\t\t\tZoom out\n" \
322+
"↑→↓←\t\t\tMove selected box" \
323+
"———————————————————————\n" \
324+
"Notice:For Mac users, use the 'Command' key instead of the 'Ctrl' key"
325+
326+
return msg

0 commit comments

Comments
 (0)