mirror of
				https://github.com/PaddlePaddle/PaddleOCR.git
				synced 2025-10-30 17:29:13 +00:00 
			
		
		
		
	
						commit
						2acde6c503
					
				| @ -21,12 +21,13 @@ import os.path | ||||
| import platform | ||||
| import subprocess | ||||
| import sys | ||||
| import xlrd | ||||
| from functools import partial | ||||
| 
 | ||||
| from PyQt5.QtCore import QSize, Qt, QPoint, QByteArray, QTimer, QFileInfo, QPointF, QProcess | ||||
| from PyQt5.QtGui import QImage, QCursor, QPixmap, QImageReader | ||||
| from PyQt5.QtWidgets import QMainWindow, QListWidget, QVBoxLayout, QToolButton, QHBoxLayout, QDockWidget, QWidget, \ | ||||
|     QSlider, QGraphicsOpacityEffect, QMessageBox, QListView, QScrollArea, QWidgetAction, QApplication, QLabel, \ | ||||
|     QSlider, QGraphicsOpacityEffect, QMessageBox, QListView, QScrollArea, QWidgetAction, QApplication, QLabel, QGridLayout, \ | ||||
|     QFileDialog, QListWidgetItem, QComboBox, QDialog | ||||
| 
 | ||||
| __dir__ = os.path.dirname(os.path.abspath(__file__)) | ||||
| @ -36,7 +37,7 @@ sys.path.append(os.path.abspath(os.path.join(__dir__, '../..'))) | ||||
| sys.path.append(os.path.abspath(os.path.join(__dir__, '../PaddleOCR'))) | ||||
| sys.path.append("..") | ||||
| 
 | ||||
| from paddleocr import PaddleOCR | ||||
| from paddleocr import PaddleOCR, PPStructure | ||||
| from libs.constants import * | ||||
| from libs.utils import * | ||||
| from libs.labelColor import label_colormap | ||||
| @ -100,9 +101,15 @@ class MainWindow(QMainWindow): | ||||
|                              use_gpu=gpu, | ||||
|                              lang=lang, | ||||
|                              show_log=False) | ||||
|         self.table_ocr = PPStructure(use_pdserving=False, | ||||
|                                      use_gpu=gpu, | ||||
|                                      lang=lang, | ||||
|                                      layout=False, | ||||
|                                      show_log=False) | ||||
| 
 | ||||
|         if os.path.exists('./data/paddle.png'): | ||||
|             result = self.ocr.ocr('./data/paddle.png', cls=True, det=True) | ||||
|             result = self.table_ocr('./data/paddle.png', return_ocr_result_in_table=True) | ||||
| 
 | ||||
|         # For loading all image under a directory | ||||
|         self.mImgList = [] | ||||
| @ -196,16 +203,25 @@ class MainWindow(QMainWindow): | ||||
|         self.reRecogButton.setIcon(newIcon('reRec', 30)) | ||||
|         self.reRecogButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) | ||||
| 
 | ||||
|         self.tableRecButton = QToolButton() | ||||
|         self.tableRecButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) | ||||
| 
 | ||||
|         self.newButton = QToolButton() | ||||
|         self.newButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) | ||||
|         self.createpolyButton = QToolButton() | ||||
|         self.createpolyButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) | ||||
| 
 | ||||
|         self.SaveButton = QToolButton() | ||||
|         self.SaveButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) | ||||
|         self.DelButton = QToolButton() | ||||
|         self.DelButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) | ||||
| 
 | ||||
|         leftTopToolBox = QHBoxLayout() | ||||
|         leftTopToolBox.addWidget(self.newButton) | ||||
|         leftTopToolBox.addWidget(self.reRecogButton) | ||||
|         leftTopToolBox = QGridLayout() | ||||
|         leftTopToolBox.addWidget(self.newButton, 0, 0, 1, 1) | ||||
|         leftTopToolBox.addWidget(self.createpolyButton, 0, 1, 1, 1) | ||||
|         leftTopToolBox.addWidget(self.reRecogButton, 1, 0, 1, 1) | ||||
|         leftTopToolBox.addWidget(self.tableRecButton, 1, 1, 1, 1) | ||||
| 
 | ||||
|         leftTopToolBoxContainer = QWidget() | ||||
|         leftTopToolBoxContainer.setLayout(leftTopToolBox) | ||||
|         listLayout.addWidget(leftTopToolBoxContainer) | ||||
| @ -446,7 +462,13 @@ class MainWindow(QMainWindow): | ||||
|                             'Ctrl+R', 'reRec', getStr('singleRe'), enabled=False) | ||||
| 
 | ||||
|         createpoly = action(getStr('creatPolygon'), self.createPolygon, | ||||
|                             'q', 'new', getStr('creatPolygon'), enabled=True) | ||||
|                             'q', 'new', getStr('creatPolygon'), enabled=False) | ||||
|          | ||||
|         tableRec = action(getStr('TableRecognition'), self.TableRecognition, | ||||
|                         '', 'Auto', getStr('TableRecognition'), enabled=False) | ||||
| 
 | ||||
|         cellreRec = action(getStr('cellreRecognition'), self.cellreRecognition, | ||||
|                         '', 'reRec', getStr('cellreRecognition'), enabled=False) | ||||
| 
 | ||||
|         saveRec = action(getStr('saveRec'), self.saveRecResult, | ||||
|                          '', 'save', getStr('saveRec'), enabled=False) | ||||
| @ -454,6 +476,9 @@ class MainWindow(QMainWindow): | ||||
|         saveLabel = action(getStr('saveLabel'), self.saveLabelFile,  # | ||||
|                            'Ctrl+S', 'save', getStr('saveLabel'), enabled=False) | ||||
|          | ||||
|         exportJSON = action(getStr('exportJSON'), self.exportJSON, | ||||
|                             '', 'save', getStr('exportJSON'), enabled=False) | ||||
| 
 | ||||
|         undoLastPoint = action(getStr("undoLastPoint"), self.canvas.undoLastPoint, | ||||
|                                'Ctrl+Z', "undo", getStr("undoLastPoint"), enabled=False) | ||||
| 
 | ||||
| @ -474,10 +499,12 @@ class MainWindow(QMainWindow): | ||||
| 
 | ||||
|         self.editButton.setDefaultAction(edit) | ||||
|         self.newButton.setDefaultAction(create) | ||||
|         self.createpolyButton.setDefaultAction(createpoly) | ||||
|         self.DelButton.setDefaultAction(deleteImg) | ||||
|         self.SaveButton.setDefaultAction(save) | ||||
|         self.AutoRecognition.setDefaultAction(AutoRec) | ||||
|         self.reRecogButton.setDefaultAction(reRec) | ||||
|         self.tableRecButton.setDefaultAction(tableRec) | ||||
|         # self.preButton.setDefaultAction(openPrevImg) | ||||
|         # self.nextButton.setDefaultAction(openNextImg) | ||||
| 
 | ||||
| @ -523,25 +550,25 @@ class MainWindow(QMainWindow): | ||||
| 
 | ||||
|         # Store actions for further handling. | ||||
|         self.actions = struct(save=save, resetAll=resetAll, deleteImg=deleteImg, | ||||
|                               lineColor=color1, create=create, delete=delete, edit=edit, copy=copy, | ||||
|                               saveRec=saveRec, singleRere=singleRere, AutoRec=AutoRec, reRec=reRec, | ||||
|                               lineColor=color1, create=create, createpoly=createpoly, tableRec=tableRec, delete=delete, edit=edit, copy=copy, | ||||
|                               saveRec=saveRec, singleRere=singleRere, AutoRec=AutoRec, reRec=reRec, cellreRec=cellreRec, | ||||
|                               createMode=createMode, editMode=editMode, | ||||
|                               shapeLineColor=shapeLineColor, shapeFillColor=shapeFillColor, | ||||
|                               zoom=zoom, zoomIn=zoomIn, zoomOut=zoomOut, zoomOrg=zoomOrg, | ||||
|                               fitWindow=fitWindow, fitWidth=fitWidth, | ||||
|                               zoomActions=zoomActions, saveLabel=saveLabel, change_cls=change_cls, | ||||
|                               undo=undo, undoLastPoint=undoLastPoint, open_dataset_dir=open_dataset_dir, | ||||
|                               rotateLeft=rotateLeft, rotateRight=rotateRight, lock=lock, | ||||
|                               fileMenuActions=(opendir, open_dataset_dir, saveLabel, resetAll, quit), | ||||
|                               rotateLeft=rotateLeft, rotateRight=rotateRight, lock=lock, exportJSON=exportJSON, | ||||
|                               fileMenuActions=(opendir, open_dataset_dir, saveLabel, exportJSON, resetAll, quit), | ||||
|                               beginner=(), advanced=(), | ||||
|                               editMenu=(createpoly, edit, copy, delete, singleRere, None, undo, undoLastPoint, | ||||
|                               editMenu=(createpoly, edit, copy, delete, singleRere, cellreRec, None, undo, undoLastPoint, | ||||
|                                         None, rotateLeft, rotateRight, None, color1, self.drawSquaresOption, lock, | ||||
|                                         None, change_cls), | ||||
|                               beginnerContext=( | ||||
|                                   create, edit, copy, delete, singleRere, rotateLeft, rotateRight, lock, change_cls), | ||||
|                                   create, createpoly, edit, copy, delete, singleRere, cellreRec, rotateLeft, rotateRight, lock, change_cls), | ||||
|                               advancedContext=(createMode, editMode, edit, copy, | ||||
|                                                delete, shapeLineColor, shapeFillColor), | ||||
|                               onLoadActive=(create, createMode, editMode), | ||||
|                               onLoadActive=(create, createpoly, createMode, editMode), | ||||
|                               onShapesPresent=(hideAll, showAll)) | ||||
| 
 | ||||
|         # menus | ||||
| @ -574,7 +601,7 @@ class MainWindow(QMainWindow): | ||||
|         self.autoSaveOption.triggered.connect(self.autoSaveFunc) | ||||
| 
 | ||||
|         addActions(self.menus.file, | ||||
|                    (opendir, open_dataset_dir, None, saveLabel, saveRec, self.autoSaveOption, None, resetAll, deleteImg, | ||||
|                    (opendir, open_dataset_dir, None, saveLabel, saveRec, exportJSON, self.autoSaveOption, None, resetAll, deleteImg, | ||||
|                     quit)) | ||||
| 
 | ||||
|         addActions(self.menus.help, (showKeys, showSteps, showInfo)) | ||||
| @ -585,7 +612,7 @@ class MainWindow(QMainWindow): | ||||
|             zoomIn, zoomOut, zoomOrg, None, | ||||
|             fitWindow, fitWidth)) | ||||
| 
 | ||||
|         addActions(self.menus.autolabel, (AutoRec, reRec, alcm, None, help)) | ||||
|         addActions(self.menus.autolabel, (AutoRec, reRec, cellreRec, alcm, None, help)) | ||||
| 
 | ||||
|         self.menus.file.aboutToShow.connect(self.updateFileMenu) | ||||
| 
 | ||||
| @ -695,6 +722,7 @@ class MainWindow(QMainWindow): | ||||
|         self.dirty = False | ||||
|         self.actions.save.setEnabled(False) | ||||
|         self.actions.create.setEnabled(True) | ||||
|         self.actions.createpoly.setEnabled(True) | ||||
| 
 | ||||
|     def toggleActions(self, value=True): | ||||
|         """Enable/Disable widgets which depend on an opened image.""" | ||||
| @ -780,6 +808,7 @@ class MainWindow(QMainWindow): | ||||
|         assert self.beginner() | ||||
|         self.canvas.setEditing(False) | ||||
|         self.actions.create.setEnabled(False) | ||||
|         self.actions.createpoly.setEnabled(False) | ||||
|         self.canvas.fourpoint = False | ||||
| 
 | ||||
|     def createPolygon(self): | ||||
| @ -787,10 +816,10 @@ class MainWindow(QMainWindow): | ||||
|         self.canvas.setEditing(False) | ||||
|         self.canvas.fourpoint = True | ||||
|         self.actions.create.setEnabled(False) | ||||
|         self.actions.createpoly.setEnabled(False) | ||||
|         self.actions.undoLastPoint.setEnabled(True) | ||||
| 
 | ||||
|     def rotateImg(self, filename, k, _value): | ||||
| 
 | ||||
|         self.actions.rotateRight.setEnabled(_value) | ||||
|         pix = cv2.imread(filename) | ||||
|         pix = np.rot90(pix, k) | ||||
| @ -831,6 +860,7 @@ class MainWindow(QMainWindow): | ||||
|             self.canvas.setEditing(True) | ||||
|             self.canvas.restoreCursor() | ||||
|             self.actions.create.setEnabled(True) | ||||
|             self.actions.createpoly.setEnabled(True) | ||||
| 
 | ||||
|     def toggleDrawMode(self, edit=True): | ||||
|         self.canvas.setEditing(edit) | ||||
| @ -1001,6 +1031,7 @@ class MainWindow(QMainWindow): | ||||
|         self._noSelectionSlot = False | ||||
|         n_selected = len(selected_shapes) | ||||
|         self.actions.singleRere.setEnabled(n_selected) | ||||
|         self.actions.cellreRec.setEnabled(n_selected) | ||||
|         self.actions.delete.setEnabled(n_selected) | ||||
|         self.actions.copy.setEnabled(n_selected) | ||||
|         self.actions.edit.setEnabled(n_selected == 1) | ||||
| @ -1225,6 +1256,7 @@ class MainWindow(QMainWindow): | ||||
|             if self.beginner():  # Switch to edit mode. | ||||
|                 self.canvas.setEditing(True) | ||||
|                 self.actions.create.setEnabled(True) | ||||
|                 self.actions.createpoly.setEnabled(True) | ||||
|                 self.actions.undoLastPoint.setEnabled(False) | ||||
|                 self.actions.undo.setEnabled(True) | ||||
|             else: | ||||
| @ -1663,8 +1695,10 @@ class MainWindow(QMainWindow): | ||||
|         self.haveAutoReced = False | ||||
|         self.AutoRecognition.setEnabled(True) | ||||
|         self.reRecogButton.setEnabled(True) | ||||
|         self.tableRecButton.setEnabled(True) | ||||
|         self.actions.AutoRec.setEnabled(True) | ||||
|         self.actions.reRec.setEnabled(True) | ||||
|         self.actions.tableRec.setEnabled(True) | ||||
|         self.actions.open_dataset_dir.setEnabled(True) | ||||
|         self.actions.rotateLeft.setEnabled(True) | ||||
|         self.actions.rotateRight.setEnabled(True) | ||||
| @ -1764,6 +1798,7 @@ class MainWindow(QMainWindow): | ||||
|                     self.openNextImg() | ||||
|                 self.actions.saveRec.setEnabled(True) | ||||
|                 self.actions.saveLabel.setEnabled(True) | ||||
|                 self.actions.exportJSON.setEnabled(True)  | ||||
| 
 | ||||
|         elif mode == 'Auto': | ||||
|             if annotationFilePath and self.saveLabels(annotationFilePath, mode=mode): | ||||
| @ -2090,6 +2125,280 @@ class MainWindow(QMainWindow): | ||||
|             self.singleLabel(shape) | ||||
|             self.setDirty() | ||||
| 
 | ||||
|     def TableRecognition(self): | ||||
|         ''' | ||||
|             Table Recegnition | ||||
|         ''' | ||||
|         from paddleocr.ppstructure.table.predict_table import to_excel | ||||
| 
 | ||||
|         import time | ||||
| 
 | ||||
|         start = time.time() | ||||
|         img = cv2.imread(self.filePath) | ||||
|         res = self.table_ocr(img, return_ocr_result_in_table=True) | ||||
| 
 | ||||
|         TableRec_excel_dir = self.lastOpenDir + '/tableRec_excel_output/' | ||||
|         os.makedirs(TableRec_excel_dir, exist_ok=True) | ||||
|         filename, _ = os.path.splitext(os.path.basename(self.filePath)) | ||||
| 
 | ||||
|         excel_path = TableRec_excel_dir + '{}.xlsx'.format(filename) | ||||
|          | ||||
|         if res is None: | ||||
|             msg = 'Can not recognise the table in ' + self.filePath + '. Please change manually' | ||||
|             QMessageBox.information(self, "Information", msg) | ||||
|             to_excel('', excel_path) # create an empty excel | ||||
|             return | ||||
|          | ||||
|         # save res | ||||
|         # ONLY SUPPORT ONE TABLE in one image | ||||
|         hasTable = False | ||||
|         for region in res: | ||||
|             if region['type'] == 'Table': | ||||
|                 if region['res']['boxes'] is None: | ||||
|                     msg = 'Can not recognise the detection box in ' + self.filePath + '. Please change manually' | ||||
|                     QMessageBox.information(self, "Information", msg) | ||||
|                     to_excel('', excel_path) # create an empty excel | ||||
|                     return | ||||
|                 hasTable = True | ||||
|                 # save table ocr result on PPOCRLabel | ||||
|                 # clear all old annotaions before saving result | ||||
|                 self.itemsToShapes.clear() | ||||
|                 self.shapesToItems.clear() | ||||
|                 self.itemsToShapesbox.clear()  # ADD | ||||
|                 self.shapesToItemsbox.clear() | ||||
|                 self.labelList.clear() | ||||
|                 self.BoxList.clear() | ||||
|                 self.result_dic = [] | ||||
|                 self.result_dic_locked = [] | ||||
| 
 | ||||
|                 shapes = [] | ||||
|                 result_len = len(region['res']['boxes']) | ||||
|                 for i in range(result_len): | ||||
|                     bbox = np.array(region['res']['boxes'][i]) | ||||
|                     rec_text = region['res']['rec_res'][i][0] | ||||
| 
 | ||||
|                     # polys to rectangles | ||||
|                     x1, y1 = np.min(bbox[:, 0]), np.min(bbox[:, 1]) | ||||
|                     x2, y2 = np.max(bbox[:, 0]), np.max(bbox[:, 1]) | ||||
|                     rext_bbox = [[x1, y1], [x2, y1], [x2, y2], [x1, y2]] | ||||
| 
 | ||||
|                     # save bbox to shape | ||||
|                     shape = Shape(label=rec_text, line_color=DEFAULT_LINE_COLOR, key_cls=None) | ||||
|                     for point in rext_bbox: | ||||
|                         x, y = point | ||||
|                         # Ensure the labels are within the bounds of the image.  | ||||
|                         # If not, fix them. | ||||
|                         x, y, snapped = self.canvas.snapPointToCanvas(x, y) | ||||
|                         shape.addPoint(QPointF(x, y)) | ||||
|                     shape.difficult = False | ||||
|                     # shape.locked = False | ||||
|                     shape.close() | ||||
|                     self.addLabel(shape) | ||||
|                     shapes.append(shape) | ||||
|                 self.setDirty() | ||||
|                 self.canvas.loadShapes(shapes) | ||||
|                  | ||||
|                 # save HTML result to excel | ||||
|                 try: | ||||
|                     to_excel(region['res']['html'], excel_path) | ||||
|                 except: | ||||
|                     print('Can not save excel file, maybe Permission denied (.xlsx is being occupied)') | ||||
|                 break | ||||
|          | ||||
|         if not hasTable: | ||||
|             msg = 'Can not recognise the table in ' + self.filePath + '. Please change manually' | ||||
|             QMessageBox.information(self, "Information", msg) | ||||
|             to_excel('', excel_path) # create an empty excel | ||||
|             return | ||||
| 
 | ||||
|         # automatically open excel annotation file | ||||
|         if platform.system() == 'Windows': | ||||
|             try: | ||||
|                 import win32com.client | ||||
|             except: | ||||
|                 print("CANNOT OPEN .xlsx. It could be one of the following reasons: " \ | ||||
|                     "Only support Windows | No python win32com") | ||||
| 
 | ||||
|             try: | ||||
|                 xl = win32com.client.Dispatch("Excel.Application") | ||||
|                 xl.Visible = True | ||||
|                 xl.Workbooks.Open(excel_path) | ||||
|                 # excelEx = "You need to show the excel executable at this point" | ||||
|                 # subprocess.Popen([excelEx, excel_path]) | ||||
| 
 | ||||
|                 # os.startfile(excel_path) | ||||
|             except: | ||||
|                 print("CANNOT OPEN .xlsx. It could be the following reasons: " \ | ||||
|                     ".xlsx is not existed") | ||||
|         else: | ||||
|             os.system('open ' + os.path.normpath(excel_path)) | ||||
|                  | ||||
|         print('time cost: ', time.time() - start) | ||||
| 
 | ||||
|     def cellreRecognition(self): | ||||
|         ''' | ||||
|             re-recognise text in a cell | ||||
|         ''' | ||||
|         img = cv2.imread(self.filePath) | ||||
|         for shape in self.canvas.selectedShapes: | ||||
|             box = [[int(p.x()), int(p.y())] for p in shape.points] | ||||
| 
 | ||||
|             if len(box) > 4: | ||||
|                 box = self.gen_quad_from_poly(np.array(box)) | ||||
|             assert len(box) == 4 | ||||
| 
 | ||||
|             # pad around bbox for better text recognition accuracy | ||||
|             _box = boxPad(box, img.shape, 6) | ||||
|             img_crop = get_rotate_crop_image(img, np.array(_box, np.float32)) | ||||
|             if img_crop is None: | ||||
|                 msg = 'Can not recognise the detection box in ' + self.filePath + '. Please change manually' | ||||
|                 QMessageBox.information(self, "Information", msg) | ||||
|                 return | ||||
| 
 | ||||
|             # merge the text result in the cell | ||||
|             texts = '' | ||||
|             probs = 0. # the probability of the cell is avgerage prob of every text box in the cell | ||||
|             bboxes = self.ocr.ocr(img_crop, det=True, rec=False, cls=False) | ||||
|             if len(bboxes) > 0: | ||||
|                 bboxes.reverse() # top row text at first | ||||
|                 for _bbox in bboxes: | ||||
|                     patch = get_rotate_crop_image(img_crop, np.array(_bbox, np.float32)) | ||||
|                     rec_res = self.ocr.ocr(patch, det=False, rec=True, cls=False) | ||||
|                     text = rec_res[0][0] | ||||
|                     if text != '': | ||||
|                         texts += text + (' ' if text[0].isalpha() else '') # add space between english word | ||||
|                         probs += rec_res[0][1] | ||||
|                 probs = probs / len(bboxes) | ||||
|             result = [(texts.strip(), probs)] | ||||
| 
 | ||||
|             if result[0][0] != '': | ||||
|                 result.insert(0, box) | ||||
|                 print('result in reRec is ', result) | ||||
|                 if result[1][0] == shape.label: | ||||
|                     print('label no change') | ||||
|                 else: | ||||
|                     shape.label = result[1][0] | ||||
|             else: | ||||
|                 print('Can not recognise the box') | ||||
|                 if self.noLabelText == shape.label: | ||||
|                     print('label no change') | ||||
|                 else: | ||||
|                     shape.label = self.noLabelText | ||||
|             self.singleLabel(shape) | ||||
|             self.setDirty() | ||||
| 
 | ||||
|     def exportJSON(self): | ||||
|         ''' | ||||
|             export PPLabel and CSV to JSON (PubTabNet) | ||||
|         ''' | ||||
|         import pandas as pd | ||||
|         from libs.dataPartitionDialog import DataPartitionDialog | ||||
| 
 | ||||
|         # data partition user input | ||||
|         partitionDialog = DataPartitionDialog(parent=self) | ||||
|         partitionDialog.exec() | ||||
|         if partitionDialog.getStatus() == False: | ||||
|             return | ||||
| 
 | ||||
|         # automatically save annotations | ||||
|         self.saveFilestate() | ||||
|         self.savePPlabel(mode='auto') | ||||
| 
 | ||||
|         # load box annotations | ||||
|         labeldict = {} | ||||
|         if not os.path.exists(self.PPlabelpath): | ||||
|             msg = 'ERROR, Can not find Label.txt' | ||||
|             QMessageBox.information(self, "Information", msg) | ||||
|             return | ||||
|         else: | ||||
|             with open(self.PPlabelpath, 'r', encoding='utf-8') as f: | ||||
|                 data = f.readlines() | ||||
|                 for each in data: | ||||
|                     file, label = each.split('\t') | ||||
|                     if label: | ||||
|                         label = label.replace('false', 'False') | ||||
|                         label = label.replace('true', 'True') | ||||
|                         labeldict[file] = eval(label) | ||||
|                     else: | ||||
|                         labeldict[file] = [] | ||||
| 
 | ||||
|         # if len(labeldict) != len(csv_paths): | ||||
|         #     msg = 'ERROR, box label and excel label are not in the same number\n' + \ | ||||
|         #           'box label: ' + str(len(labeldict)) + '\n' + \ | ||||
|         #           'excel label: ' + str(len(csv_paths)) + '\n' + \ | ||||
|         #           'Please check the label.txt and tableRec_excel_output\n' | ||||
|         #     QMessageBox.information(self, "Information", msg) | ||||
|         #     return | ||||
|         train_split, val_split, test_split = partitionDialog.getDataPartition() | ||||
|         # check validate | ||||
|         if train_split + val_split + test_split > 100: | ||||
|             msg = "The sum of training, validation and testing data should be less than 100%" | ||||
|             QMessageBox.information(self, "Information", msg) | ||||
|             return | ||||
|         print(train_split, val_split, test_split) | ||||
|         train_split, val_split, test_split = float(train_split) / 100., float(val_split) / 100., float(test_split) / 100. | ||||
|         train_id = int(len(labeldict) * train_split) | ||||
|         val_id = int(len(labeldict) * (train_split + val_split)) | ||||
|         print('Data partition: train:', train_id,  | ||||
|               'validation:',  val_id - train_id, | ||||
|               'test:', len(labeldict) - val_id) | ||||
| 
 | ||||
|         TableRec_excel_dir = os.path.join(self.lastOpenDir, 'tableRec_excel_output') | ||||
|         json_results = [] | ||||
|         imgid = 0 | ||||
|         for image_path in labeldict.keys(): | ||||
|             # load csv annotations | ||||
|             filename, _ = os.path.splitext(os.path.basename(image_path)) | ||||
|             csv_path = os.path.join(TableRec_excel_dir, filename + '.xlsx') | ||||
|             if not os.path.exists(csv_path): | ||||
|                 msg = 'ERROR, Can not find ' + csv_path | ||||
|                 QMessageBox.information(self, "Information", msg) | ||||
|                 return | ||||
| 
 | ||||
|             # read xlsx file, convert to HTML | ||||
|             # xd = pd.ExcelFile(csv_path) | ||||
|             # df = xd.parse() | ||||
|             # structure = df.to_html(index = False) | ||||
|             excel = xlrd.open_workbook(csv_path) | ||||
|             sheet0 = excel.sheet_by_index(0)  # only sheet 0 | ||||
|             merged_cells = sheet0.merged_cells # (0,1,1,3) start row, end row, start col, end col | ||||
| 
 | ||||
|             html_list = [['td'] * sheet0.ncols for i in range(sheet0.nrows)] | ||||
| 
 | ||||
|             for merged in merged_cells: | ||||
|                 html_list = expand_list(merged, html_list) | ||||
| 
 | ||||
|             token_list = convert_token(html_list) | ||||
| 
 | ||||
| 
 | ||||
|             # load box annotations | ||||
|             cells = [] | ||||
|             for anno in labeldict[image_path]: | ||||
|                 tokens = list(anno['transcription']) | ||||
|                 obb = anno['points'] | ||||
|                 hbb = OBB2HBB(np.array(obb)).tolist() | ||||
|                 cells.append({'tokens': tokens, 'bbox': hbb}) | ||||
|              | ||||
|             # data split | ||||
|             if imgid < train_id: | ||||
|                 split = 'train' | ||||
|             elif imgid < val_id: | ||||
|                 split = 'val' | ||||
|             else: | ||||
|                 split = 'test' | ||||
| 
 | ||||
|             #  save dict | ||||
|             html = {'structure': {'tokens': token_list}, 'cell': cells} | ||||
|             json_results.append({'filename': os.path.basename(image_path), 'split': split, 'imgid': imgid, 'html': html}) | ||||
|             imgid += 1 | ||||
| 
 | ||||
|         # save json | ||||
|         with open("{}/annotation.json".format(self.lastOpenDir), "w", encoding='utf-8') as fid: | ||||
|             fid.write(json.dumps(json_results, ensure_ascii=False)) | ||||
|          | ||||
|         msg = 'JSON sucessfully saved in {}/annotation.json'.format(self.lastOpenDir) | ||||
|         QMessageBox.information(self, "Information", msg) | ||||
| 
 | ||||
|     def autolcm(self): | ||||
|         vbox = QVBoxLayout() | ||||
|         hbox = QHBoxLayout() | ||||
| @ -2129,6 +2438,12 @@ class MainWindow(QMainWindow): | ||||
|         del self.ocr | ||||
|         self.ocr = PaddleOCR(use_pdserving=False, use_angle_cls=True, det=True, cls=True, use_gpu=False, | ||||
|                              lang=lg_idx[self.comboBox.currentText()]) | ||||
|         del self.table_ocr | ||||
|         self.table_ocr = PPStructure(use_pdserving=False, | ||||
|                                      use_gpu=False, | ||||
|                                      lang=lg_idx[self.comboBox.currentText()], | ||||
|                                      layout=False, | ||||
|                                      show_log=False) | ||||
|         self.dialog.close() | ||||
| 
 | ||||
|     def cancel(self): | ||||
| @ -2147,6 +2462,7 @@ class MainWindow(QMainWindow): | ||||
|                     self.fileStatedict[file] = 1 | ||||
|                 self.actions.saveLabel.setEnabled(True) | ||||
|                 self.actions.saveRec.setEnabled(True) | ||||
|                 self.actions.exportJSON.setEnabled(True) | ||||
| 
 | ||||
|     def saveFilestate(self): | ||||
|         with open(self.fileStatepath, 'w', encoding='utf-8') as f: | ||||
|  | ||||
							
								
								
									
										113
									
								
								PPOCRLabel/libs/dataPartitionDialog.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								PPOCRLabel/libs/dataPartitionDialog.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,113 @@ | ||||
| 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 | ||||
| 
 | ||||
| import time | ||||
| import datetime | ||||
| import json | ||||
| import cv2 | ||||
| import numpy as np | ||||
| 
 | ||||
| 
 | ||||
| BB = QDialogButtonBox | ||||
| 
 | ||||
| class DataPartitionDialog(QDialog): | ||||
|     def __init__(self, parent=None): | ||||
|         super().__init__() | ||||
|         self.parnet = parent | ||||
|         self.title = 'DATA PARTITION' | ||||
| 
 | ||||
|         self.train_ratio = 70 | ||||
|         self.val_ratio = 15 | ||||
|         self.test_ratio = 15 | ||||
|          | ||||
|         self.initUI() | ||||
| 
 | ||||
|     def initUI(self): | ||||
|         self.setWindowTitle(self.title) | ||||
|         self.setWindowModality(Qt.ApplicationModal) | ||||
| 
 | ||||
|         self.flag_accept = True | ||||
| 
 | ||||
|         if self.parnet.lang == 'ch': | ||||
|             msg = "导出JSON前请保存所有图像的标注且关闭EXCEL!" | ||||
|         else: | ||||
|             msg = "Please save all the annotations and close the EXCEL before exporting JSON!" | ||||
| 
 | ||||
|         info_msg = QLabel(msg, self) | ||||
|         info_msg.setWordWrap(True) | ||||
|         info_msg.setStyleSheet("color: red") | ||||
|         info_msg.setFont(QFont('Arial', 12)) | ||||
| 
 | ||||
|         train_lbl = QLabel('Train split: ', self) | ||||
|         train_lbl.setFont(QFont('Arial', 15)) | ||||
|         val_lbl = QLabel('Valid split: ', self) | ||||
|         val_lbl.setFont(QFont('Arial', 15)) | ||||
|         test_lbl = QLabel('Test split: ', self) | ||||
|         test_lbl.setFont(QFont('Arial', 15)) | ||||
| 
 | ||||
|         self.train_input = QLineEdit(self) | ||||
|         self.train_input.setFont(QFont('Arial', 15)) | ||||
|         self.val_input = QLineEdit(self) | ||||
|         self.val_input.setFont(QFont('Arial', 15)) | ||||
|         self.test_input = QLineEdit(self) | ||||
|         self.test_input.setFont(QFont('Arial', 15)) | ||||
| 
 | ||||
|         self.train_input.setText(str(self.train_ratio)) | ||||
|         self.val_input.setText(str(self.val_ratio)) | ||||
|         self.test_input.setText(str(self.test_ratio)) | ||||
| 
 | ||||
|         validator = QIntValidator(0, 100) | ||||
|         self.train_input.setValidator(validator) | ||||
|         self.val_input.setValidator(validator) | ||||
|         self.test_input.setValidator(validator) | ||||
| 
 | ||||
|         gridlayout = QGridLayout() | ||||
|         gridlayout.addWidget(info_msg, 0, 0, 1, 2) | ||||
|         gridlayout.addWidget(train_lbl, 1, 0) | ||||
|         gridlayout.addWidget(val_lbl, 2, 0) | ||||
|         gridlayout.addWidget(test_lbl, 3, 0) | ||||
|         gridlayout.addWidget(self.train_input, 1, 1) | ||||
|         gridlayout.addWidget(self.val_input, 2, 1) | ||||
|         gridlayout.addWidget(self.test_input, 3, 1) | ||||
| 
 | ||||
|         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.cancel) | ||||
|         gridlayout.addWidget(bb, 4, 0, 1, 2) | ||||
| 
 | ||||
|         self.setLayout(gridlayout) | ||||
|          | ||||
|         self.show() | ||||
| 
 | ||||
|     def validate(self): | ||||
|         self.flag_accept = True | ||||
|         self.accept() | ||||
| 
 | ||||
|     def cancel(self): | ||||
|         self.flag_accept = False | ||||
|         self.reject() | ||||
|      | ||||
|     def getStatus(self): | ||||
|         return self.flag_accept | ||||
| 
 | ||||
|     def getDataPartition(self): | ||||
|         self.train_ratio = int(self.train_input.text()) | ||||
|         self.val_ratio = int(self.val_input.text()) | ||||
|         self.test_ratio = int(self.test_input.text()) | ||||
| 
 | ||||
|         return self.train_ratio, self.val_ratio, self.test_ratio | ||||
| 
 | ||||
|     def closeEvent(self, event): | ||||
|         self.flag_accept = False | ||||
|         self.reject() | ||||
| 
 | ||||
| 
 | ||||
| @ -161,6 +161,77 @@ def get_rotate_crop_image(img, points): | ||||
|         print(e) | ||||
| 
 | ||||
| 
 | ||||
| def boxPad(box, imgShape, pad : int) -> np.array: | ||||
|     """ | ||||
|     Pad a box with [pad] pixels on each side. | ||||
|     """ | ||||
|     box = np.array(box, dtype=np.int32) | ||||
|     box[0][0], box[0][1] = box[0][0] - pad, box[0][1] - pad | ||||
|     box[1][0], box[1][1] = box[1][0] + pad, box[1][1] - pad | ||||
|     box[2][0], box[2][1] = box[2][0] + pad, box[2][1] + pad | ||||
|     box[3][0], box[3][1] = box[3][0] - pad, box[3][1] + pad | ||||
|     h, w, _ = imgShape | ||||
|     box[:,0] = np.clip(box[:,0], 0, w) | ||||
|     box[:,1] = np.clip(box[:,1], 0, h) | ||||
|     return box | ||||
| 
 | ||||
| 
 | ||||
| def OBB2HBB(obb) -> np.array: | ||||
|     """ | ||||
|     Convert Oriented Bounding Box to Horizontal Bounding Box. | ||||
|     """ | ||||
|     hbb = np.zeros(4, dtype=np.int32) | ||||
|     hbb[0] = min(obb[:, 0]) | ||||
|     hbb[1] = min(obb[:, 1]) | ||||
|     hbb[2] = max(obb[:, 0]) | ||||
|     hbb[3] = max(obb[:, 1]) | ||||
|     return hbb | ||||
| 
 | ||||
| 
 | ||||
| def expand_list(merged, html_list): | ||||
|     ''' | ||||
|     Fill blanks according to merged cells | ||||
|     ''' | ||||
|     sr, er, sc, ec = merged | ||||
|     for i in range(sr, er): | ||||
|         for j in range(sc, ec): | ||||
|             html_list[i][j] = None | ||||
|     html_list[sr][sc] = '' | ||||
|     if ec - sc > 1: | ||||
|         html_list[sr][sc] += " colspan={}".format(ec - sc) | ||||
|     if er - sr > 1: | ||||
|         html_list[sr][sc] += " rowspan={}".format(er - sr) | ||||
|     return html_list | ||||
| 
 | ||||
| 
 | ||||
| def convert_token(html_list): | ||||
|     ''' | ||||
|     Convert raw html to label format | ||||
|     ''' | ||||
|     token_list = ["<tbody>"] | ||||
|     # final html list: | ||||
|     for row in html_list: | ||||
|         token_list.append("<tr>") | ||||
|         for col in row: | ||||
|             if col == None: | ||||
|                 continue | ||||
|             elif col == 'td': | ||||
|                 token_list.extend(["<td>", "</td>"]) | ||||
|             else: | ||||
|                 token_list.append("<td") | ||||
|                 if 'colspan' in col: | ||||
|                     _, n = col.split('colspan=') | ||||
|                     token_list.append(" colspan=\"{}\"".format(n)) | ||||
|                 if 'rowspan' in col: | ||||
|                     _, n = col.split('rowspan=') | ||||
|                     token_list.append(" rowspan=\"{}\"".format(n)) | ||||
|                 token_list.extend([">", "</td>"]) | ||||
|         token_list.append("</tr>") | ||||
|     token_list.append("</tbody>") | ||||
| 
 | ||||
|     return token_list | ||||
| 
 | ||||
| 
 | ||||
| def stepsInfo(lang='en'): | ||||
|     if lang == 'ch': | ||||
|         msg = "1. 安装与运行:使用上述命令安装与运行程序。\n" \ | ||||
|  | ||||
| @ -84,7 +84,7 @@ mhelp=Help | ||||
| iconList=Icon List | ||||
| detectionBoxposition=Detection box position | ||||
| recognitionResult=Recognition result | ||||
| creatPolygon=Create Quadrilateral | ||||
| creatPolygon=Create PolygonBox | ||||
| rotateLeft=Left turn 90 degrees | ||||
| rotateRight=Right turn 90 degrees | ||||
| drawSquares=Draw Squares | ||||
| @ -110,3 +110,6 @@ lockBoxDetail=Lock selected box/Unlock all box | ||||
| keyListTitle=Key List | ||||
| keyDialogTip=Enter object label | ||||
| keyChange=Change Box Key | ||||
| TableRecognition=Table Recognition | ||||
| cellreRecognition=Cell Re-Recognition | ||||
| exportJSON=export JSON(PubTabNet) | ||||
|  | ||||
| @ -84,7 +84,7 @@ mhelp=帮助 | ||||
| iconList=缩略图 | ||||
| detectionBoxposition=检测框位置 | ||||
| recognitionResult=识别结果 | ||||
| creatPolygon=四点标注 | ||||
| creatPolygon=多边形标注 | ||||
| drawSquares=正方形标注 | ||||
| rotateLeft=图片左旋转90度 | ||||
| rotateRight=图片右旋转90度 | ||||
| @ -110,3 +110,6 @@ lockBoxDetail=若当前没有框处于锁定状态则锁定选中的框,若存 | ||||
| keyListTitle=关键词列表 | ||||
| keyDialogTip=请输入类型名称 | ||||
| keyChange=更改Box关键字类别 | ||||
| TableRecognition=表格识别 | ||||
| cellreRecognition=单元格重识别 | ||||
| exportJSON=导出表格JSON标注 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Evezerest
						Evezerest