diff --git a/PPOCRLabel/PPOCRLabel.py b/PPOCRLabel/PPOCRLabel.py index 6477ea07025c09303e255ba0118f1b9a4d7fbb8a..98e2b1ef77f6fb63b49cd8d32ca87d3d783daf50 100644 --- a/PPOCRLabel/PPOCRLabel.py +++ b/PPOCRLabel/PPOCRLabel.py @@ -61,7 +61,7 @@ from combobox import ComboBox from libs.constants import * from libs.utils import * from libs.settings import Settings -from libs.shape import Shape, DEFAULT_LINE_COLOR, DEFAULT_FILL_COLOR +from libs.shape import Shape, DEFAULT_LINE_COLOR, DEFAULT_FILL_COLOR,DEFAULT_LOCK_COLOR from libs.stringBundle import StringBundle from libs.canvas import Canvas from libs.zoomWidget import ZoomWidget @@ -126,7 +126,7 @@ class MainWindow(QMainWindow, WindowMixin): self.labelHist = [] self.lastOpenDir = None self.result_dic = [] - + self.result_dic_locked = [] self.changeFileFolder = False self.haveAutoReced = False self.labelFile = None @@ -395,6 +395,7 @@ class MainWindow(QMainWindow, WindowMixin): delete = action(getStr('delBox'), self.deleteSelectedShape, 'backspace', 'delete', getStr('delBoxDetail'), enabled=False) + copy = action(getStr('dupBox'), self.copySelectedShape, 'Ctrl+C', 'copy', getStr('dupBoxDetail'), enabled=False) @@ -405,6 +406,7 @@ class MainWindow(QMainWindow, WindowMixin): showAll = action(getStr('showBox'), partial(self.togglePolygons, True), 'Ctrl+A', 'hide', getStr('showAllBoxDetail'), enabled=False) + help = action(getStr('tutorial'), self.showTutorialDialog, None, 'help', getStr('tutorialDetail')) showInfo = action(getStr('info'), self.showInfoDialog, None, 'help', getStr('info')) @@ -476,6 +478,10 @@ class MainWindow(QMainWindow, WindowMixin): undo = action(getStr("undo"), self.undoShapeEdit, 'Ctrl+Z', "undo", getStr("undo"), enabled=False) + + lock = action(getStr("lockBox"), self.lockSelectedShape, + None, "lock", getStr("lockBoxDetail"), + enabled=False) self.editButton.setDefaultAction(edit) self.newButton.setDefaultAction(create) @@ -538,13 +544,13 @@ class MainWindow(QMainWindow, WindowMixin): fitWindow=fitWindow, fitWidth=fitWidth, zoomActions=zoomActions, saveLabel=saveLabel, undo=undo, undoLastPoint=undoLastPoint,open_dataset_dir=open_dataset_dir, - rotateLeft=rotateLeft,rotateRight=rotateRight, + rotateLeft=rotateLeft,rotateRight=rotateRight,lock=lock, fileMenuActions=( opendir, open_dataset_dir, saveLabel, resetAll, quit), beginner=(), advanced=(), editMenu=(createpoly, edit, copy, delete,singleRere,None, undo, undoLastPoint, - None, rotateLeft, rotateRight, None, color1, self.drawSquaresOption), - beginnerContext=(create, edit, copy, delete, singleRere, rotateLeft, rotateRight,), + None, rotateLeft, rotateRight, None, color1, self.drawSquaresOption,lock), + beginnerContext=(create, edit, copy, delete, singleRere, rotateLeft, rotateRight,lock), advancedContext=(createMode, editMode, edit, copy, delete, shapeLineColor, shapeFillColor), onLoadActive=( @@ -998,6 +1004,7 @@ class MainWindow(QMainWindow, WindowMixin): self.actions.delete.setEnabled(n_selected) self.actions.copy.setEnabled(n_selected) self.actions.edit.setEnabled(n_selected == 1) + self.actions.lock.setEnabled(n_selected) def addLabel(self, shape): shape.paintLabel = self.displayLabelOption.isChecked() @@ -1041,7 +1048,7 @@ class MainWindow(QMainWindow, WindowMixin): def loadLabels(self, shapes): s = [] for label, points, line_color, fill_color, difficult in shapes: - shape = Shape(label=label) + shape = Shape(label=label,line_color=line_color) for x, y in points: # Ensure the labels are within the bounds of the image. If not, fix them. @@ -1051,6 +1058,7 @@ class MainWindow(QMainWindow, WindowMixin): shape.addPoint(QPointF(x, y)) shape.difficult = difficult + #shape.locked = False shape.close() s.append(shape) @@ -1063,10 +1071,12 @@ class MainWindow(QMainWindow, WindowMixin): # shape.fill_color = QColor(*fill_color) # else: # shape.fill_color = generateColorByText(label) - + self.addLabel(shape) + self.updateComboBox() self.canvas.loadShapes(s) + def singleLabel(self, shape): if shape is None: @@ -1106,10 +1116,9 @@ class MainWindow(QMainWindow, WindowMixin): difficult=s.difficult) # bool shapes = [] if mode == 'Auto' else \ - [format_shape(shape) for shape in self.canvas.shapes] + [format_shape(shape) for shape in self.canvas.shapes if shape.line_color != DEFAULT_LOCK_COLOR] # Can add differrent annotation formats here - - for box in self.result_dic: + for box in self.result_dic : trans_dic = {"label": box[1][0], "points": box[0], 'difficult': False} if trans_dic["label"] == "" and mode == 'Auto': continue @@ -1120,7 +1129,6 @@ class MainWindow(QMainWindow, WindowMixin): for box in shapes: trans_dic.append({"transcription": box['label'], "points": box['points'], 'difficult': box['difficult']}) self.PPlabel[annotationFilePath] = trans_dic - if mode == 'Auto': self.Cachelabel[annotationFilePath] = trans_dic @@ -1313,6 +1321,7 @@ class MainWindow(QMainWindow, WindowMixin): # unicodeFilePath = os.path.abspath(unicodeFilePath) # Tzutalin 20160906 : Add file list and dock to move faster # Highlight the file item + if unicodeFilePath and self.fileListWidget.count() > 0: if unicodeFilePath in self.mImgList: index = self.mImgList.index(unicodeFilePath) @@ -1322,6 +1331,7 @@ class MainWindow(QMainWindow, WindowMixin): ### self.iconlist.clear() self.additems5(None) + for i in range(5): item_tooltip = self.iconlist.item(i).toolTip() # print(i,"---",item_tooltip) @@ -1340,7 +1350,6 @@ class MainWindow(QMainWindow, WindowMixin): if unicodeFilePath and os.path.exists(unicodeFilePath): self.canvas.verified = False - cvimg = cv2.imdecode(np.fromfile(unicodeFilePath, dtype=np.uint8), 1) height, width, depth = cvimg.shape cvimg = cv2.cvtColor(cvimg, cv2.COLOR_BGR2RGB) @@ -1361,16 +1370,19 @@ class MainWindow(QMainWindow, WindowMixin): else: self.dirty = False self.actions.save.setEnabled(True) - + if len(self.canvas.lockedShapes) != 0: + self.actions.save.setEnabled(True) + self.setDirty() self.canvas.setEnabled(True) self.adjustScale(initial=True) self.paintCanvas() self.addRecentFile(self.filePath) self.toggleActions(True) + self.showBoundingBoxFromPPlabel(filePath) self.setWindowTitle(__appname__ + ' ' + filePath) - + # Default : select last item if there is at least one item if self.labelList.count(): self.labelList.setCurrentItem(self.labelList.item(self.labelList.count() - 1)) @@ -1380,15 +1392,23 @@ class MainWindow(QMainWindow, WindowMixin): return True return False - def showBoundingBoxFromPPlabel(self, filePath): + width, height = self.image.width(), self.image.height() imgidx = self.getImglabelidx(filePath) - if imgidx not in self.PPlabel.keys(): - return - shapes = [] - for box in self.PPlabel[imgidx]: - shapes.append((box['transcription'], box['points'], None, None, box['difficult'])) - + shapes =[] + #box['ratio'] of the shapes saved in lockedShapes contains the ratio of the + # four corner coordinates of the shapes to the height and width of the image + for box in self.canvas.lockedShapes: + if self.canvas.isInTheSameImage: + shapes.append((box['transcription'], [[s[0]*width,s[1]*height]for s in box['ratio']], + DEFAULT_LOCK_COLOR, None, box['difficult'])) + else: + shapes.append(('锁定框:待检测', [[s[0]*width,s[1]*height]for s in box['ratio']], + DEFAULT_LOCK_COLOR, None, box['difficult'])) + if imgidx in self.PPlabel.keys(): + for box in self.PPlabel[imgidx]: + shapes.append((box['transcription'], box['points'], None, None, box['difficult'])) + self.loadLabels(shapes) self.canvas.verified = False @@ -1646,9 +1666,37 @@ class MainWindow(QMainWindow, WindowMixin): else: return fullFilePath return '' - + + + def saveLockedShapes(self): + self.canvas.lockedShapes = [] + self.canvas.selectedShapes = [] + for s in self.canvas.shapes: + if s.line_color == DEFAULT_LOCK_COLOR: + self.canvas.selectedShapes.append(s) + self.lockSelectedShape() + for s in self.canvas.shapes: + if s.line_color == DEFAULT_LOCK_COLOR: + self.canvas.selectedShapes.remove(s) + self.canvas.shapes.remove(s) + + def _saveFile(self, annotationFilePath, mode='Manual'): + if len(self.canvas.lockedShapes) != 0: + self.saveLockedShapes() + if mode == 'Manual': + self.result_dic_locked = [] + img = cv2.imread(self.filePath) + width, height = self.image.width(), self.image.height() + for shape in self.canvas.lockedShapes: + box = [[int(p[0]*width), int(p[1]*height)] for p in shape['ratio']] + assert len(box) == 4 + result = [(shape['transcription'],1)] + result.insert(0, box) + self.result_dic_locked.append(result) + self.result_dic += self.result_dic_locked + self.result_dic_locked = [] if annotationFilePath and self.saveLabels(annotationFilePath, mode=mode): self.setClean() self.statusBar().showMessage('Saved to %s' % annotationFilePath) @@ -1663,13 +1711,13 @@ class MainWindow(QMainWindow, WindowMixin): self.savePPlabel(mode='Auto') self.fileListWidget.insertItem(int(currIndex), item) - self.openNextImg() + if not self.canvas.isInTheSameImage: + self.openNextImg() self.actions.saveRec.setEnabled(True) self.actions.saveLabel.setEnabled(True) elif mode == 'Auto': if annotationFilePath and self.saveLabels(annotationFilePath, mode=mode): - self.setClean() self.statusBar().showMessage('Saved to %s' % annotationFilePath) self.statusBar().show() @@ -1733,7 +1781,9 @@ class MainWindow(QMainWindow, WindowMixin): if discardChanges == QMessageBox.No: return True elif discardChanges == QMessageBox.Yes: + self.canvas.isInTheSameImage = True self.saveFile() + self.canvas.isInTheSameImage = False return True else: return False @@ -1872,6 +1922,7 @@ class MainWindow(QMainWindow, WindowMixin): # org_box = [dic['points'] for dic in self.PPlabel[self.getImglabelidx(self.filePath)]] if self.canvas.shapes: self.result_dic = [] + self.result_dic_locked = [] # result_dic_locked stores the ocr result of self.canvas.lockedShapes rec_flag = 0 for shape in self.canvas.shapes: box = [[int(p.x()), int(p.y())] for p in shape.points] @@ -1883,21 +1934,32 @@ class MainWindow(QMainWindow, WindowMixin): return result = self.ocr.ocr(img_crop, cls=True, det=False) if result[0][0] != '': - result.insert(0, box) - print('result in reRec is ', result) - self.result_dic.append(result) + if shape.line_color == DEFAULT_LOCK_COLOR: + shape.label = result[0][0] + result.insert(0, box) + self.result_dic_locked.append(result) + else: + result.insert(0, box) + self.result_dic.append(result) else: print('Can not recognise the box') - self.result_dic.append([box,(self.noLabelText,0)]) - - if self.noLabelText == shape.label or result[1][0] == shape.label: - print('label no change') - else: - rec_flag += 1 - - if len(self.result_dic) > 0 and rec_flag > 0: + if shape.line_color == DEFAULT_LOCK_COLOR: + shape.label = result[0][0] + self.result_dic_locked.append([box,(self.noLabelText,0)]) + else: + self.result_dic.append([box,(self.noLabelText,0)]) + try: + if self.noLabelText == shape.label or result[1][0] == shape.label: + print('label no change') + else: + rec_flag += 1 + except IndexError as e: + print('Can not recognise the box') + if (len(self.result_dic) > 0 and rec_flag > 0)or self.canvas.lockedShapes: + self.canvas.isInTheSameImage = True self.saveFile(mode='Auto') self.loadFile(self.filePath) + self.canvas.isInTheSameImage = False self.setDirty() elif len(self.result_dic) == len(self.canvas.shapes) and rec_flag == 0: QMessageBox.information(self, "Information", "The recognition result remains unchanged!") @@ -2107,6 +2169,44 @@ class MainWindow(QMainWindow, WindowMixin): self.labelList.clearSelection() self._noSelectionSlot = False self.canvas.loadShapes(shapes, replace=replace) + print("loadShapes")#1 + + + def lockSelectedShape(self): + """lock the selsected shapes. + + Add self.selectedShapes to lock self.canvas.lockedShapes, + which holds the ratio of the four coordinates of the locked shapes + to the width and height of the image + """ + width, height = self.image.width(), self.image.height() + def format_shape(s): + return dict(label=s.label, # str + line_color=s.line_color.getRgb(), + fill_color=s.fill_color.getRgb(), + ratio=[[int(p.x())/width, int(p.y())/height] for p in s.points], # QPonitF + # add chris + difficult=s.difficult) # bool + #lock + if len(self.canvas.lockedShapes) == 0: + for s in self.canvas.selectedShapes: + s.line_color = DEFAULT_LOCK_COLOR + s.locked = True + shapes = [format_shape(shape) for shape in self.canvas.selectedShapes] + trans_dic = [] + for box in shapes: + trans_dic.append({"transcription": box['label'], "ratio": box['ratio'], 'difficult': box['difficult']}) + self.canvas.lockedShapes = trans_dic + self.actions.save.setEnabled(True) + + #unlock + else: + for s in self.canvas.shapes: + s.line_color = DEFAULT_LINE_COLOR + self.canvas.lockedShapes = [] + self.result_dic_locked = [] + self.setDirty() + self.actions.save.setEnabled(True) def inverted(color): diff --git a/PPOCRLabel/libs/canvas.py b/PPOCRLabel/libs/canvas.py index 6ac1f28b85e65c3776d310136352b70c45628db6..6116f357d6efb91a5a9d9cdc6ba757fbd06df60e 100644 --- a/PPOCRLabel/libs/canvas.py +++ b/PPOCRLabel/libs/canvas.py @@ -87,6 +87,10 @@ class Canvas(QWidget): #initialisation for panning self.pan_initial_pos = QPoint() + #lockedshapes related + self.lockedShapes = [] + self.isInTheSameImage = False + def setDrawingColor(self, qColor): self.drawingLineColor = qColor self.drawingRectColor = qColor diff --git a/PPOCRLabel/libs/shape.py b/PPOCRLabel/libs/shape.py index ef8e09be061927d39403cc0cdc0727fff69854a7..e2cdcb322790c9b6edd3c504405ad65097a7bc49 100644 --- a/PPOCRLabel/libs/shape.py +++ b/PPOCRLabel/libs/shape.py @@ -30,6 +30,7 @@ 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) +DEFAULT_LOCK_COLOR = QColor(255, 0, 255) MIN_Y_LABEL = 10 @@ -57,7 +58,7 @@ class Shape(object): self.selected = False self.difficult = difficult self.paintLabel = paintLabel - + self.locked = False self._highlightIndex = None self._highlightMode = self.NEAR_VERTEX self._highlightSettings = { diff --git a/PPOCRLabel/resources/icons/lock.png b/PPOCRLabel/resources/icons/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..f4d50d70b43ae91cdb60ddc73ffa5385e6253ea1 Binary files /dev/null and b/PPOCRLabel/resources/icons/lock.png differ diff --git a/PPOCRLabel/resources/strings/strings-en.properties b/PPOCRLabel/resources/strings/strings-en.properties index 70036e6560c53bd35e2c5f29b6912092701fe4ae..f59e43aa92ff9ccd04686e9c16db181983b57b2c 100644 --- a/PPOCRLabel/resources/strings/strings-en.properties +++ b/PPOCRLabel/resources/strings/strings-en.properties @@ -104,4 +104,6 @@ singleRe=Re-recognition RectBox labelDialogOption=Pop-up Label Input Dialog undo=Undo undoLastPoint=Undo Last Point -autoSaveMode=Auto Export Label Mode \ No newline at end of file +autoSaveMode=Auto Export Label Mode +lockBox=Lock selected box/Unlock all box +lockBoxDetail=Lock selected box/Unlock all box \ No newline at end of file diff --git a/PPOCRLabel/resources/strings/strings-zh-CN.properties b/PPOCRLabel/resources/strings/strings-zh-CN.properties index 1cd4da7611c72cf37f9c3febe57522f2e38c7f9b..d8bd9d4bff02748397d7a57a6205e67ff69779c2 100644 --- a/PPOCRLabel/resources/strings/strings-zh-CN.properties +++ b/PPOCRLabel/resources/strings/strings-zh-CN.properties @@ -104,4 +104,6 @@ singleRe=重识别此区块 labelDialogOption=弹出标记输入框 undo=撤销 undoLastPoint=撤销上个点 -autoSaveMode=自动导出标记结果 \ No newline at end of file +autoSaveMode=自动导出标记结果 +lockBox=锁定框/解除锁定框 +lockBoxDetail=若当前没有框处于锁定状态则锁定选中的框,若存在锁定框则解除所有锁定框的锁定状态