class PropertyEditor(QWidget): # Signals insertionModeStarted = pyqtSignal(str) insertionModeEnded = pyqtSignal() insertionPropertiesChanged = pyqtSignal(object) editPropertiesChanged = pyqtSignal(object) def __init__(self, config, parent=None): QWidget.__init__(self, parent) self._class_config = {} self._class_items = {} self._class_prototypes = {} self._attribute_handlers = {} self._handler_factory = AttributeHandlerFactory() self._setupGUI() self._parea.setGeometry(0, 0, 200, 0) # (快捷键,button) self.shortcut2button = {} # (button,快捷键) self.label2shortcut = {} # Add label classes from config for label in config: self.addLabelClass(label) self.image_path = None def addLabelClassByPath(self, configs_path): # 读配置文件 with open(configs_path, 'r') as f: configs = json5.load(f) # 写入当前配置文件的路径 direct = os.path.dirname(sys.argv[0]) with open(os.path.join(direct, 'sloth.txt'), 'w') as f: f.write(configs_path) self._parea.setGeometry(0, 0, 200, 0) for temp_json in configs: self.addLabelClass(temp_json) # 注册 self._register('inserter', temp_json['attributes']['class'], temp_json['inserter']) self._register('item', temp_json['attributes']['class'], temp_json['item']) # add_txt的下拉框里也要添加 self.combo_box.addItem(temp_json['attributes']['class']) self.items.append(temp_json['attributes']['class']) cf.LABELS.append(temp_json) def onModelChanged(self, new_model): attrs = set([ k for k, v in self._attribute_handlers.items() if v.autoAddEnabled() ]) if len(attrs) > 0: start = time.time() attr2vals = {} for item in new_model.iterator(AnnotationModelItem): for attr in attrs: if attr in item: if attr not in attr2vals: attr2vals[attr] = set((item[attr], )) else: attr2vals[attr] |= set((item[attr], )) diff = time.time() - start LOG.info("Extracted annotation values from model in %.2fs" % diff) for attr, vals in attr2vals.items(): h = self._attribute_handlers[attr] for val in vals: h.addValue(val, True) # 设置右键菜单所在位置 def showContextMenu(self, label_class): self.label_menu[label_class].exec_(QCursor.pos()) # 删除所有的item def remove_all_item(self): for v in self.shortcut2button.values(): v.setShortcut(QKeySequence()) self.shortcut2button.clear() self.label2shortcut.clear() self.label_menu.clear() self.label_action.clear() self._class_config.clear() self.combo_box.clear() self.items.clear() temp_dict = copy.copy(self._class_buttons) for k, v in temp_dict.items(): self._classbox_layout.removeWidget(v) # 下面这句很重要,不然相当于没删 self._class_buttons[k].deleteLater() self._class_buttons.clear() cf.LABELS.clear() self._parea.setGeometry(0, 0, 200, 60) # 删除标签 def remove_item(self, label_class): """ 删除标签 :param label_class: 删除的标签名字 """ try: # 删除快捷键 self.endInsertionMode() shortcut = self.label2shortcut[label_class] if shortcut in self.shortcut2button and \ self.shortcut2button[shortcut] is not None and \ self.shortcut2button[shortcut] == self._class_buttons[label_class]: self.shortcut2button[shortcut].setShortcut(QKeySequence()) del self.shortcut2button[shortcut] # 删除菜单 del self.label_menu[label_class] # 删除菜单的动作 del self.label_action[label_class] # 删除视图中的按钮 self._classbox_layout.removeWidget( self._class_buttons[label_class]) self._class_buttons[label_class].deleteLater() self._class_buttons[label_class] = None del self._class_config[label_class] # 写回json direct = os.path.dirname(sys.argv[0]) with open(os.path.join(direct, 'sloth.txt'), 'r') as f: label_path = f.read() try: with open(label_path, 'r') as f: temp = json5.load(f) for i, current_json in enumerate(temp): if current_json['attributes']['class'] == label_class: temp.remove(current_json) # 遍历combo box 找到要删的 for i in range(len(self.combo_box)): current_label = self.combo_box.itemText(i) if current_label == label_class: print('removed', label_class) self.combo_box.removeItem(i) self.items.pop(i) break self._class_buttons.pop(label_class) break with open(label_path, 'w') as f: json5.dump(temp, f, quote_keys=True, trailing_commas=False, indent=4, separators=(',', ': '), sort_keys=True, ensure_ascii=False) self._parea.setGeometry( 0, 0, 200, max(self._parea.geometry().height() - 40, 60)) except Exception as e: print(e) except Exception as e: print(e) # 添加标签 def addLabelClass(self, label_config): """ 添加标签 :param label_config: 标签的json """ # Check label configuration if 'attributes' not in label_config: raise ImproperlyConfigured("Label with no 'attributes' dict found") attrs = label_config['attributes'] if 'class' not in attrs: raise ImproperlyConfigured("Labels must have an attribute 'class'") label_class = attrs['class'] if label_class in self._class_config: raise ImproperlyConfigured( "Label with class '%s' defined more than once" % label_class) # Store config self._class_config[label_class] = label_config # Parse configuration and create handlers and item self.parseConfiguration(label_class, label_config) # Add label class button button_text = label_config['text'] button = QPushButton(button_text) button.setCheckable(True) button.setFlat(True) button.clicked.connect(bind(self.onClassButtonPressed, label_class)) self._class_buttons[label_class] = button self._parea.setGeometry(0, 0, 200, self._parea.geometry().height() + 40) self._classbox_layout.addWidget(button) # 添加右键菜单 self.label_menu[label_class] = QtGui.QMenu(self) self.label_action[label_class] = self.label_menu[ label_class].addAction('删除') self.label_action[label_class].triggered.connect( bind(self.remove_item, label_class)) self._class_buttons[label_class].setContextMenuPolicy( QtCore.Qt.CustomContextMenu) self._class_buttons[label_class].customContextMenuRequested.connect( bind(self.showContextMenu, label_class)) # Add hotkey if 'hotkey' in label_config: # 快捷键 hotkey = label_config['hotkey'] # 快捷键已经存在,那就去掉原来的 if hotkey in self.shortcut2button and self.shortcut2button[ hotkey] is not None: self.shortcut2button[hotkey].setShortcut(QKeySequence()) # 设置快捷键 button.setShortcut(QKeySequence(hotkey)) self.shortcut2button[hotkey] = button self.label2shortcut[label_class] = hotkey def parseConfiguration(self, label_class, label_config): attrs = label_config['attributes'] # Add prototype item for insertion self._class_items[label_class] = AnnotationModelItem( {'class': label_class}) # Create attribute handler widgets or update their values for attr, vals in attrs.items(): if attr in self._attribute_handlers: self._attribute_handlers[attr].updateValues(vals) else: handler = self._handler_factory.create(attr, vals) if handler is None: self._class_items[label_class][attr] = vals else: self._attribute_handlers[attr] = handler for attr in attrs: if attr in self._attribute_handlers: self._class_items[label_class].update( self._attribute_handlers[attr].defaults()) def getHandler(self, attribute): if attribute in self._attribute_handlers: return self._attribute_handlers[attribute] else: return None def getLabelClassAttributes(self, label_class): return self._class_config[label_class]['attributes'].keys() def onClassButtonPressed(self, label_class): if self._class_buttons[label_class].isChecked(): self.startInsertionMode(label_class) else: self.endInsertionMode() def startInsertionMode(self, label_class): self.endInsertionMode(False) for lc, button in self._class_buttons.items(): button.setChecked(lc == label_class) LOG.debug("Starting insertion mode for %s" % label_class) self._label_editor = LabelEditor([self._class_items[label_class]], self, True) # self._layout.insertWidget(1, self._label_editor, 0) self.insertionModeStarted.emit(label_class) def endInsertionMode(self, uncheck_buttons=True): if self._label_editor is not None: LOG.debug("Ending insertion/edit mode") self._label_editor.hide() # self._layout.removeWidget(self._label_editor) self._label_editor = None if uncheck_buttons: self.uncheckAllButtons() self.insertionModeEnded.emit() def uncheckAllButtons(self): for lc, button in self._class_buttons.items(): button.setChecked(False) def markEditButtons(self, label_classes): for lc, button in self._class_buttons.items(): button.setFlat(lc not in label_classes) def currentEditorProperties(self): if self._label_editor is None: return None else: return self._label_editor.currentProperties() def startEditMode(self, model_items): # If we're in insertion mode, ignore empty edit requests if self._label_editor is not None and self._label_editor.insertionMode() \ and len(model_items) == 0: return self.endInsertionMode() LOG.debug("Starting edit mode for items: %s" % model_items) self._label_editor = LabelEditor(model_items, self) self.markEditButtons(self._label_editor.labelClasses()) self._layout.insertWidget(1, self._label_editor, 0) # 添加txt def add_txt(self): if not Main.isConfig(Main.get_json()): QMessageBox.warning(self, "Warning", '当前的配置文件错误或者为空,无法添加txt', QMessageBox.Ok) defect = self.combo_box.currentText() if defect is None or defect == '': return dir_path = QFileDialog.getExistingDirectory(self) Main.write_txt(dir_path, {defect}, 'defect') # 选择图片 def select_image(self): image_types = [ '*.jpg', '*.bmp', '*.png', '*.pgm', '*.ppm', '*.tiff', '*.tif', '*.gif' ] format_str = ' '.join(image_types) fname = QFileDialog.getOpenFileName( self, "select training source", '.', "Media files (%s)" % (format_str, )) if fname is None or fname == '': return self.image_path = os.path.abspath(fname) self._image_label.setText(os.path.basename(self.image_path)) # 获得图片路径对应的json路径 def image2json(self, path): temp = path.split('.') return ''.join(temp[:-1]) + '.json' # 获得图片路径转成的训练图片路径 def image2cpimage(self, path, id, length): length = max(length, 5) temp = path.split('.') return ''.join(temp[:-1]) + str(id).zfill(length) + '.' + temp[-1] # 判断是否包含瑕疵 def contains_defect(self, annotations, defect_type): defects = set() for annotation in annotations: if 'class' in annotation: defects.add(annotation['class']) return defect_type.issubset(defects) # 生成训练数据 def generate(self): if not Main.isConfig(Main.get_json()): QMessageBox.warning(self, "Warning", '配置文件错误或者为空,无法生成训练数据', QMessageBox.Ok) return a = TrainDialog(self) a.exec_() # 从labeltool中设置搜索按钮 def setFunction(self, func): self._search_btn.clicked.connect(func) # 获得关键字 def get_key_word(self): key_word = self._key_word.text() if key_word is None or key_word == '': key_word = self._key_word.placeholderText() return key_word # 获得文件类型 def get_extension(self): extension = self._extension.text() # 为空则用默认的,否则用输入的 if extension is None or extension == '': extension = self._extension.placeholderText() return extension # 返回一个含有权限类型的list def get_attributes_type(self): ''' 'Rect':('sloth.items.RectItem','sloth.items.RectItemInserter'), 'Point':('sloth.items.PointItem','sloth.items.PointItemInserter'), 'Polygon':('sloth.items.PolygonItem','sloth.items.PolygonItemInserter') ''' return ['Rect', 'Point', 'Polygon'] # 写回json def rewrite_json(self, temp_json): # json所在的txt direct = os.path.dirname(sys.argv[0]) with open(os.path.join(direct, 'sloth.txt'), 'r') as f: label_path = f.read() try: # 读取旧json with open(label_path, 'r') as f: temp = json5.load(f) # 追加我们要写入的json temp.append(temp_json) # 写入 with open(label_path, 'w') as f: json5.dump(temp, f, quote_keys=True, trailing_commas=False, indent=4, separators=(',', ': '), sort_keys=True, ensure_ascii=False) except Exception as e: print(e) # 添加标签 def add_attributes(self): if not Main.isConfig(Main.get_json()): QMessageBox.warning(self, "Warning", '当前配置文件错误或者为空,不能添加标签', QMessageBox.Ok) return # 转换dict type_dict = { 'Rect': ('sloth.items.RectItem', 'sloth.items.RectItemInserter'), 'Point': ('sloth.items.PointItem', 'sloth.items.PointItemInserter'), 'Polygon': ('sloth.items.PolygonItem', 'sloth.items.PolygonItemInserter') } # 获取添加的标签信息 attributes = {'class': self.attributes_LineEdit.text()} attributes_item, attributes_inserter = type_dict[ self.attributes_type.currentText()] attributes_hotkey = self.hotkey.text() attributes_text = self.text_LineEdit.text() global brush2idx brush_idx = str(brush2idx[self.brush_combo_box.currentText()]) temp_json = { 'attributes': attributes, 'inserter': attributes_inserter, 'item': attributes_item, 'color': ','.join(map(str, self.color_info)), 'brush': brush_idx, 'text': attributes_text } # 快捷键 if attributes_hotkey is not None and attributes_hotkey != '': temp_json['hotkey'] = attributes_hotkey print(temp_json) try: # 加入标签 self.addLabelClass(temp_json) print(self._class_buttons.keys()) # 注册 self._register('inserter', temp_json['attributes']['class'], temp_json['inserter']) self._register('item', temp_json['attributes']['class'], temp_json['item']) # add_txt的下拉框里也要添加 self.combo_box.addItem(temp_json['attributes']['class']) self.items.append(temp_json['attributes']['class']) cf.LABELS.append(temp_json) # 写回json self.rewrite_json(temp_json) except Exception as e: print(e) # 颜色对话框 def color_dialog(self): col = QtGui.QColorDialog.getColor() if col.isValid(): self.color_label.setStyleSheet("QWidget { background-color: %s }" % col.name()) self.color_info = col.getRgb()[:-1] # 设置控件的隐藏状态 def component_visible(self, component_name, state): if component_name == '添加标签': self._group_box_add_label.setVisible(state) elif component_name == 'add_txt': self._group_box_add_txt.setVisible(state) elif component_name == 'add_files': self._group_box_add_files.setVisible(state) def _setupGUI(self): self._class_buttons = {} self.label_menu = {} self.label_action = {} self._label_editor = None # Label class buttons self._parea = QGroupBox("Labels") self._classbox = QScrollArea() self._classbox_layout = FloatingLayout() self._parea.setLayout(self._classbox_layout) self._parea.setGeometry(0, 0, 200, 200) self._classbox.setWidget(self._parea) self._classbox.setGeometry(0, 0, 100, 100) # 添加txt模块 self.combo_box = QComboBox() self._group_box_add_txt = QGroupBox('add_txt', self) self._group_box_add_txt_layout = QVBoxLayout() self._group_box_add_txt.setLayout(self._group_box_add_txt_layout) temp = cf.LABELS self.items = [] # 获取所有的标签 for i in temp: self.items.append(i['attributes']['class']) # 假如下拉框 self.combo_box.addItems(self.items) self.add_txt_btn = QPushButton('add txt') self.add_txt_btn.clicked.connect(self.add_txt) # 加入下拉框和按钮 self._group_box_add_txt_layout.addWidget(self.combo_box, 0) self._group_box_add_txt_layout.addWidget(self.add_txt_btn, 1) # 根据关键字搜索图片模块 self._group_box_add_files = QGroupBox('add files', self) # 文件名包含的 self._key_word = QLineEdit('') self._key_word.setPlaceholderText('Second') # 文件类型 self._extension = QLineEdit('') self._extension.setPlaceholderText('bmp') self._search_btn = QPushButton('search files') self._group_box_add_files_layout = QVBoxLayout() # 加入控件 self._group_box_add_files_layout.addWidget(self._key_word, 0) self._group_box_add_files_layout.addWidget(self._extension, 1) self._group_box_add_files_layout.addWidget(self._search_btn, 2) self._group_box_add_files.setLayout(self._group_box_add_files_layout) # 添加标签模块 self._group_box_add_label = QGroupBox("添加标签", self) self._add_label_group_layout = QVBoxLayout() self._group_box_add_label.setLayout(self._add_label_group_layout) # 标签的class self.attributes_LineEdit = QLineEdit('') self.attributes_LineEdit.setPlaceholderText('attributes') # 标签画出来的类型 self.attributes_type = QComboBox() self.attributes_type.addItems(self.get_attributes_type()) # 快捷键,目前设置了只允许一个键 self.hotkey = QLineEdit('') self.hotkey.setPlaceholderText('hotkey') self.regx = QRegExp("[a-z0-9]$") self.validator = QRegExpValidator(self.regx, self.hotkey) self.hotkey.setValidator(self.validator) # 标签显示 self.text_LineEdit = QLineEdit('') self.text_LineEdit.setPlaceholderText('text') # 颜色 color = QtGui.QColor(0, 0, 0) self.color_label = QtGui.QWidget() self.color_label.setStyleSheet("QWidget { background-color: %s }" % color.name()) self.color_info = [0, 0, 0] self.color_layout = QtGui.QHBoxLayout() self.color_btn = QPushButton('选择颜色') self.color_btn.clicked.connect(self.color_dialog) self.color_layout.addWidget(self.color_label) self.color_layout.addWidget(self.color_btn) # 笔刷 global brush2idx self.brush_combo_box = QComboBox() self.brush_combo_box.addItems(list(brush2idx.keys())) # 按钮 self.attributes_add_btn = QPushButton('添加标签') self.attributes_add_btn.clicked.connect(self.add_attributes) # 加入控件 self._add_label_group_layout.addWidget(self.attributes_LineEdit, 0) self._add_label_group_layout.addWidget(self.attributes_type, 1) self._add_label_group_layout.addWidget(self.hotkey, 2) self._add_label_group_layout.addWidget(self.text_LineEdit, 3) self._label_widget = QWidget() self._label_widget.setLayout(self.color_layout) self._add_label_group_layout.addWidget(self._label_widget, 4) self._add_label_group_layout.addWidget(self.brush_combo_box, 5) self._add_label_group_layout.addWidget(self.attributes_add_btn, 6) # 生成训练数据按钮 self._file_button = QPushButton('生成训练数据') self._file_button.clicked.connect(self.generate) # Global widget self._layout = MyVBoxLayout() self.setLayout(self._layout) self._layout.addWidget(self._classbox, 1) self._layout.insertWidget(-1, self._group_box_add_label, 1) self._layout.insertWidget(-1, self._group_box_add_txt, 1) self._layout.insertWidget(-1, self._group_box_add_files, 1) self._layout.insertWidget(-1, self._file_button, 1)