win11 labelme 汉化菜单
替换 app.py,再重启
#labelme 汉化菜单# -*- coding: utf-8 -*-import functools
import os
import os.path as osp
import re
import webbrowserimport imgviz
from qtpy import QtCore
from qtpy.QtCore import Qt
from qtpy import QtGui
from qtpy import QtWidgetsfrom labelme import __appname__
from labelme import PY2
from labelme import QT5from . import utils
from labelme.config import get_config
from labelme.label_file import LabelFile
from labelme.label_file import LabelFileError
from labelme.logger import logger
from labelme.shape import Shape
from labelme.widgets import BrightnessContrastDialog
from labelme.widgets import Canvas
from labelme.widgets import LabelDialog
from labelme.widgets import LabelListWidget
from labelme.widgets import LabelListWidgetItem
from labelme.widgets import ToolBar
from labelme.widgets import UniqueLabelQListWidget
from labelme.widgets import ZoomWidget# FIXME
# - [medium] Set max zoom value to something big enough for FitWidth/Window# TODO(unknown):
# - [high] Add polygon movement with arrow keys
# - [high] Deselect shape when clicking and already selected(?)
# - [low,maybe] Preview images on file dialogs.
# - Zoom is too "steppy".LABEL_COLORMAP = imgviz.label_colormap(value=200)class MainWindow(QtWidgets.QMainWindow):FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = 0, 1, 2def __init__(self,config=None,filename=None,output=None,output_file=None,output_dir=None,):if output is not None:logger.warning("argument output is deprecated, use output_file instead")if output_file is None:output_file = output# see labelme/config/default_config.yaml for valid configurationif config is None:config = get_config()self._config = config# set default shape colorsShape.line_color = QtGui.QColor(*self._config["shape"]["line_color"])Shape.fill_color = QtGui.QColor(*self._config["shape"]["fill_color"])Shape.select_line_color = QtGui.QColor(*self._config["shape"]["select_line_color"])Shape.select_fill_color = QtGui.QColor(*self._config["shape"]["select_fill_color"])Shape.vertex_fill_color = QtGui.QColor(*self._config["shape"]["vertex_fill_color"])Shape.hvertex_fill_color = QtGui.QColor(*self._config["shape"]["hvertex_fill_color"])super(MainWindow, self).__init__()self.setWindowTitle(__appname__)# Whether we need to save or not.self.dirty = Falseself._noSelectionSlot = False# Main widgets and related state.self.labelDialog = LabelDialog(parent=self,labels=self._config["labels"],sort_labels=self._config["sort_labels"],show_text_field=self._config["show_label_text_field"],completion=self._config["label_completion"],fit_to_content=self._config["fit_to_content"],flags=self._config["label_flags"],)self.labelList = LabelListWidget()self.lastOpenDir = Noneself.flag_dock = self.flag_widget = Noneself.flag_dock = QtWidgets.QDockWidget(self.tr("标记"), self)self.flag_dock.setObjectName("Flags")self.flag_widget = QtWidgets.QListWidget()if config["flags"]:self.loadFlags({k: False for k in config["flags"]})self.flag_dock.setWidget(self.flag_widget)self.flag_widget.itemChanged.connect(self.setDirty)self.labelList.itemSelectionChanged.connect(self.labelSelectionChanged)self.labelList.itemDoubleClicked.connect(self.editLabel)self.labelList.itemChanged.connect(self.labelItemChanged)self.labelList.itemDropped.connect(self.labelOrderChanged)self.shape_dock = QtWidgets.QDockWidget(self.tr("多边形标注"), self)self.shape_dock.setObjectName("Labels")self.shape_dock.setWidget(self.labelList)self.uniqLabelList = UniqueLabelQListWidget()self.uniqLabelList.setToolTip(self.tr("选择标签开始标注. ""按'Esc'取消选择."))if self._config["labels"]:for label in self._config["labels"]:item = self.uniqLabelList.createItemFromLabel(label)self.uniqLabelList.addItem(item)rgb = self._get_rgb_by_label(label)self.uniqLabelList.setItemLabel(item, label, rgb)self.label_dock = QtWidgets.QDockWidget(self.tr(u"标签列表"), self)self.label_dock.setObjectName(u"Label List")self.label_dock.setWidget(self.uniqLabelList)self.fileSearch = QtWidgets.QLineEdit()self.fileSearch.setPlaceholderText(self.tr("搜索文件名"))self.fileSearch.textChanged.connect(self.fileSearchChanged)self.fileListWidget = QtWidgets.QListWidget()self.fileListWidget.itemSelectionChanged.connect(self.fileSelectionChanged)fileListLayout = QtWidgets.QVBoxLayout()fileListLayout.setContentsMargins(0, 0, 0, 0)fileListLayout.setSpacing(0)fileListLayout.addWidget(self.fileSearch)fileListLayout.addWidget(self.fileListWidget)self.file_dock = QtWidgets.QDockWidget(self.tr(u"文件列表"), self)self.file_dock.setObjectName(u"Files")fileListWidget = QtWidgets.QWidget()fileListWidget.setLayout(fileListLayout)self.file_dock.setWidget(fileListWidget)self.zoomWidget = ZoomWidget()self.setAcceptDrops(True)self.canvas = self.labelList.canvas = Canvas(epsilon=self._config["epsilon"],double_click=self._config["canvas"]["double_click"],)self.canvas.zoomRequest.connect(self.zoomRequest)scrollArea = QtWidgets.QScrollArea()scrollArea.setWidget(self.canvas)scrollArea.setWidgetResizable(True)self.scrollBars = {Qt.Vertical: scrollArea.verticalScrollBar(),Qt.Horizontal: scrollArea.horizontalScrollBar(),}self.canvas.scrollRequest.connect(self.scrollRequest)self.canvas.newShape.connect(self.newShape)self.canvas.shapeMoved.connect(self.setDirty)self.canvas.selectionChanged.connect(self.shapeSelectionChanged)self.canvas.drawingPolygon.connect(self.toggleDrawingSensitive)self.setCentralWidget(scrollArea)features = QtWidgets.QDockWidget.DockWidgetFeatures()for dock in ["flag_dock", "label_dock", "shape_dock", "file_dock"]:if self._config[dock]["closable"]:features = features | QtWidgets.QDockWidget.DockWidgetClosableif self._config[dock]["floatable"]:features = features | QtWidgets.QDockWidget.DockWidgetFloatableif self._config[dock]["movable"]:features = features | QtWidgets.QDockWidget.DockWidgetMovablegetattr(self, dock).setFeatures(features)if self._config[dock]["show"] is False:getattr(self, dock).setVisible(False)self.addDockWidget(Qt.RightDockWidgetArea, self.flag_dock)self.addDockWidget(Qt.RightDockWidgetArea, self.label_dock)self.addDockWidget(Qt.RightDockWidgetArea, self.shape_dock)self.addDockWidget(Qt.RightDockWidgetArea, self.file_dock)# Actionsaction = functools.partial(utils.newAction, self)shortcuts = self._config["shortcuts"]# 修改动作文本quit = action(self.tr("退出(&Q)"),self.close,shortcuts["quit"],"quit", self.tr("退出程序"),)open_ = action(self.tr("打开(&O)"),self.openFile,shortcuts["open"],"open",self.tr("打开图片或标注文件"),)opendir = action(self.tr("打开目录(&D)"),self.openDirDialog,shortcuts["open_dir"],"open",self.tr("打开目录"),)openNextImg = action(self.tr("下一张(&N)"),self.openNextImg,shortcuts["open_next"],"next",self.tr("打开下一张(按住Ctrl+Shift复制标注)"),enabled=False,)openPrevImg = action(self.tr("上一张(&P)"),self.openPrevImg, shortcuts["open_prev"],"prev",self.tr("打开上一张(按住Ctrl+Shift复制标注)"),enabled=False,)save = action(self.tr("保存(&S)"),self.saveFile,shortcuts["save"],"save",self.tr("保存标注到文件"),enabled=False,)saveAs = action(self.tr("另存为(&A)"),self.saveFileAs,shortcuts["save_as"],"save-as", self.tr("保存标注到其他文件"),enabled=False,)deleteFile = action(self.tr("删除文件(&D)"),self.deleteFile,shortcuts["delete_file"],"delete",self.tr("删除当前标注文件"),enabled=False,)changeOutputDir = action(self.tr("更改输出目录(&C)"),slot=self.changeOutputDirDialog,shortcut=shortcuts["save_to"],icon="open",tip=self.tr("更改标注文件的加载/保存位置"),)saveAuto = action(text=self.tr("自动保存(&A)"),slot=lambda x: self.actions.saveAuto.setChecked(x),icon="save",tip=self.tr("自动保存"),checkable=True,enabled=True,)saveAuto.setChecked(self._config["auto_save"])# 9. 按钮文本saveWithImageData = action(text="Save With Image Data", # 需要汉化: "保存图像数据"slot=self.enableSaveImageWithData,tip="Save image data in label file", # 需要汉化: "在标签文件中保存图像数据"checkable=True,checked=self._config["store_data"],)close = action("关闭(&C)", # 需要汉化: "关闭(&C)"self.closeFile,shortcuts["close"],"close","关闭当前文件", # 需要汉化: "关闭当前文件")toggle_keep_prev_mode = action(self.tr("保持上一个标注"),self.toggleKeepPrevMode,shortcuts["toggle_keep_prev_mode"],None,self.tr('保持上一个标注"模式'),checkable=True,)toggle_keep_prev_mode.setChecked(self._config["keep_prev"])# 10. 其他操作按钮文本createMode = action(self.tr("创建多边形"), # 需要汉化: "创建多边形"lambda: self.toggleDrawMode(False, createMode="polygon"),shortcuts["create_polygon"],"objects",self.tr("开始绘制多边形"), # 需要汉化: "开始绘制多边形"enabled=False,)createRectangleMode = action(self.tr("创建矩形"),lambda: self.toggleDrawMode(False, createMode="rectangle"),shortcuts["create_rectangle"],"objects",self.tr("开始绘制矩形"),enabled=False,)createCircleMode = action(self.tr("创建圆形"),lambda: self.toggleDrawMode(False, createMode="circle"),shortcuts["create_circle"],"objects",self.tr("开始绘制圆形"),enabled=False,)createLineMode = action(self.tr("创建直线"),lambda: self.toggleDrawMode(False, createMode="line"),shortcuts["create_line"],"objects",self.tr("开始绘制直线"),enabled=False,)createPointMode = action(self.tr("创建点"),lambda: self.toggleDrawMode(False, createMode="point"),shortcuts["create_point"],"objects",self.tr("开始绘制点"),enabled=False,)createLineStripMode = action(self.tr("创建折线"),lambda: self.toggleDrawMode(False, createMode="linestrip"),shortcuts["create_linestrip"],"objects",self.tr("开始绘制折线。按住Ctrl+左键结束创建"),enabled=False,)editMode = action(self.tr("编辑多边形"),self.setEditMode,shortcuts["edit_polygon"],"edit",self.tr("移动和编辑选中的多边形"),enabled=False,)delete = action(self.tr("删除多边形"),self.deleteSelectedShape,shortcuts["delete_polygon"],"cancel",self.tr("删除选中的多边形"),enabled=False,)copy = action(self.tr("复制多边形"),self.copySelectedShape,shortcuts["duplicate_polygon"],"copy",self.tr("创建选中多边形的副本"),enabled=False,)undoLastPoint = action(self.tr("撤销上一个点"),self.canvas.undoLastPoint,shortcuts["undo_last_point"],"undo",self.tr("撤销上一个绘制的点"),enabled=False,)addPointToEdge = action(text=self.tr("添加边缘点"),slot=self.canvas.addPointToEdge,shortcut=shortcuts["add_point_to_edge"],icon="edit",tip=self.tr("在最近的边缘添加点"),enabled=False,)removePoint = action(text=self.tr("删除选中点"),slot=self.canvas.removeSelectedPoint,icon="edit",tip=self.tr("从多边形中删除选中的点"),enabled=False,)undo = action(self.tr("撤销"),self.undoShapeEdit,shortcuts["undo"],"undo",self.tr("撤销上一次添加和编辑形状"),enabled=False,)hideAll = action(self.tr("隐藏\n多边形"),functools.partial(self.togglePolygons, False),icon="eye",tip=self.tr("隐藏所有多边形"),enabled=False,)showAll = action(self.tr("显示\n多边形"),functools.partial(self.togglePolygons, True),icon="eye",tip=self.tr("显示所有多边形"),enabled=False,)help = action(self.tr("教程(&T)"),self.tutorial,icon="help",tip=self.tr("显示教程页面"),)zoom = QtWidgets.QWidgetAction(self)zoom.setDefaultWidget(self.zoomWidget)self.zoomWidget.setWhatsThis(self.tr("放大或缩小图像。也可以使用 ""画布上的 {} 和 {} 进行缩放。").format(utils.fmtShortcut("{},{}".format(shortcuts["zoom_in"], shortcuts["zoom_out"])),utils.fmtShortcut(self.tr("Ctrl+滚轮")),))self.zoomWidget.setEnabled(False)zoomIn = action(self.tr("放大"), # 需要汉化: "放大"functools.partial(self.addZoom, 1.1),shortcuts["zoom_in"],"zoom-in",self.tr("增加缩放级别"), # 需要汉化: "增加缩放级别"enabled=False,)zoomOut = action(self.tr("&Zoom Out"),functools.partial(self.addZoom, 0.9),shortcuts["zoom_out"],"zoom-out",self.tr("Decrease zoom level"),enabled=False,)zoomOrg = action(self.tr("&Original size"),functools.partial(self.setZoom, 100),shortcuts["zoom_to_original"],"zoom",self.tr("Zoom to original size"),enabled=False,)fitWindow = action(self.tr("&Fit Window"),self.setFitWindow,shortcuts["fit_window"],"fit-window",self.tr("Zoom follows window size"),checkable=True,enabled=False,)fitWidth = action(self.tr("Fit &Width"),self.setFitWidth,shortcuts["fit_width"],"fit-width",self.tr("Zoom follows window width"),checkable=True,enabled=False,)brightnessContrast = action("亮度对比度(&B)",self.brightnessContrast,None,"color","调整亮度和对比度",enabled=False,)# Group zoom controls into a list for easier toggling.zoomActions = (self.zoomWidget,zoomIn,zoomOut,zoomOrg,fitWindow,fitWidth,)self.zoomMode = self.FIT_WINDOWfitWindow.setChecked(Qt.Checked)self.scalers = {self.FIT_WINDOW: self.scaleFitWindow,self.FIT_WIDTH: self.scaleFitWidth,# Set to one to scale to 100% when loading files.self.MANUAL_ZOOM: lambda: 1,}edit = action(self.tr("&Edit Label"),self.editLabel,shortcuts["edit_label"],"edit",self.tr("Modify the label of the selected polygon"),enabled=False,)fill_drawing = action(self.tr("Fill Drawing Polygon"),self.canvas.setFillDrawing,None,"color",self.tr("Fill polygon while drawing"),checkable=True,enabled=True,)fill_drawing.trigger()# Lavel list context menu.labelMenu = QtWidgets.QMenu()utils.addActions(labelMenu, (edit, delete))self.labelList.setContextMenuPolicy(Qt.CustomContextMenu)self.labelList.customContextMenuRequested.connect(self.popLabelListMenu)# Store actions for further handling.self.actions = utils.struct(saveAuto=saveAuto,saveWithImageData=saveWithImageData,changeOutputDir=changeOutputDir,save=save,saveAs=saveAs,open=open_,close=close,deleteFile=deleteFile,toggleKeepPrevMode=toggle_keep_prev_mode,delete=delete,edit=edit,copy=copy,undoLastPoint=undoLastPoint,undo=undo,addPointToEdge=addPointToEdge,removePoint=removePoint,createMode=createMode,editMode=editMode,createRectangleMode=createRectangleMode,createCircleMode=createCircleMode,createLineMode=createLineMode,createPointMode=createPointMode,createLineStripMode=createLineStripMode,zoom=zoom,zoomIn=zoomIn,zoomOut=zoomOut,zoomOrg=zoomOrg,fitWindow=fitWindow,fitWidth=fitWidth,brightnessContrast=brightnessContrast,zoomActions=zoomActions,openNextImg=openNextImg,openPrevImg=openPrevImg,fileMenuActions=(open_, opendir, save, saveAs, close, quit),tool=(),# XXX: need to add some actions here to activate the shortcuteditMenu=(edit,copy,delete,None,undo,undoLastPoint,None,addPointToEdge,None,toggle_keep_prev_mode,),# menu shown at right clickmenu=(createMode,createRectangleMode,createCircleMode,createLineMode,createPointMode,createLineStripMode,editMode,edit,copy,delete,undo,undoLastPoint,addPointToEdge,removePoint,),onLoadActive=(close,createMode,createRectangleMode,createCircleMode,createLineMode,createPointMode,createLineStripMode,editMode,brightnessContrast,),onShapesPresent=(saveAs, hideAll, showAll),)self.canvas.edgeSelected.connect(self.canvasShapeEdgeSelected)self.canvas.vertexSelected.connect(self.actions.removePoint.setEnabled)# 修改菜单栏文本self.menus = utils.struct(file=self.menu(self.tr("文件(&F)")),edit=self.menu(self.tr("编辑(&E)")), view=self.menu(self.tr("视图(&V)")),help=self.menu(self.tr("帮助(&H)")),recentFiles=QtWidgets.QMenu(self.tr("最近打开(&R)")),labelList=labelMenu,)utils.addActions(self.menus.file,(open_,openNextImg,openPrevImg,opendir,self.menus.recentFiles,save,saveAs,saveAuto,changeOutputDir,saveWithImageData,close,deleteFile,None,quit,),)utils.addActions(self.menus.help, (help,))utils.addActions(self.menus.view,(self.flag_dock.toggleViewAction(),self.label_dock.toggleViewAction(),self.shape_dock.toggleViewAction(),self.file_dock.toggleViewAction(),None,fill_drawing,None,hideAll,showAll,None,zoomIn,zoomOut,zoomOrg,None,fitWindow,fitWidth,None,brightnessContrast,),)self.menus.file.aboutToShow.connect(self.updateFileMenu)# Custom context menu for the canvas widget:utils.addActions(self.canvas.menus[0], self.actions.menu)utils.addActions(self.canvas.menus[1],(action("复制到此处(&C)", self.copyShape),action("移动到此处(&M)", self.moveShape),),)self.tools = self.toolbar("Tools")# Menu buttons on Leftself.actions.tool = (open_,opendir,openNextImg,openPrevImg,save,deleteFile,None,createMode,editMode,copy,delete,undo,brightnessContrast,None,zoom,fitWidth,)self.statusBar().showMessage(self.tr("%s 已启动.") % __appname__)self.statusBar().show()if output_file is not None and self._config["auto_save"]:logger.warn("如果`auto_save`参数为True,`output_file`参数 ""将被忽略,输出文件名将自动""设置为IMAGE_BASENAME.json。")self.output_file = output_fileself.output_dir = output_dir# Application state.self.image = QtGui.QImage()self.imagePath = Noneself.recentFiles = []self.maxRecent = 7self.otherData = Noneself.zoom_level = 100self.fit_window = Falseself.zoom_values = {} # key=filename, value=(zoom_mode, zoom_value)self.brightnessContrast_values = {}self.scroll_values = {Qt.Horizontal: {},Qt.Vertical: {},} # key=filename, value=scroll_valueif filename is not None and osp.isdir(filename):self.importDirImages(filename, load=False)else:self.filename = filenameif config["file_search"]:self.fileSearch.setText(config["file_search"])self.fileSearchChanged()# XXX: Could be completely declarative.# Restore application settings.self.settings = QtCore.QSettings("labelme", "labelme")# FIXME: QSettings.value can return None on PyQt4self.recentFiles = self.settings.value("recentFiles", []) or []size = self.settings.value("window/size", QtCore.QSize(600, 500))position = self.settings.value("window/position", QtCore.QPoint(0, 0))self.resize(size)self.move(position)# or simply:# self.restoreGeometry(settings['window/geometry']self.restoreState(self.settings.value("window/state", QtCore.QByteArray()))# Populate the File menu dynamically.self.updateFileMenu()# Since loading the file may take some time,# make sure it runs in the background.if self.filename is not None:self.queueEvent(functools.partial(self.loadFile, self.filename))# Callbacks:self.zoomWidget.valueChanged.connect(self.paintCanvas)self.populateModeActions()# self.firstStart = True# if self.firstStart:# QWhatsThis.enterWhatsThisMode()def menu(self, title, actions=None):menu = self.menuBar().addMenu(title)if actions:utils.addActions(menu, actions)return menudef toolbar(self, title, actions=None):toolbar = ToolBar(title)toolbar.setObjectName("%sToolBar" % title)# toolbar.setOrientation(Qt.Vertical)toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)if actions:utils.addActions(toolbar, actions)self.addToolBar(Qt.LeftToolBarArea, toolbar)return toolbar# Support Functionsdef noShapes(self):return not len(self.labelList)def populateModeActions(self):tool, menu = self.actions.tool, self.actions.menuself.tools.clear()utils.addActions(self.tools, tool)self.canvas.menus[0].clear()utils.addActions(self.canvas.menus[0], menu)self.menus.edit.clear()actions = (self.actions.createMode,self.actions.createRectangleMode,self.actions.createCircleMode,self.actions.createLineMode,self.actions.createPointMode,self.actions.createLineStripMode,self.actions.editMode,)utils.addActions(self.menus.edit, actions + self.actions.editMenu)def setDirty(self):if self._config["auto_save"] or self.actions.saveAuto.isChecked():label_file = osp.splitext(self.imagePath)[0] + ".json"if self.output_dir:label_file_without_path = osp.basename(label_file)label_file = osp.join(self.output_dir, label_file_without_path)self.saveLabels(label_file)returnself.dirty = Trueself.actions.save.setEnabled(True)self.actions.undo.setEnabled(self.canvas.isShapeRestorable)title = __appname__if self.filename is not None:title = "{} - {}*".format(title, self.filename)self.setWindowTitle(title)def setClean(self):self.dirty = Falseself.actions.save.setEnabled(False)self.actions.createMode.setEnabled(True)self.actions.createRectangleMode.setEnabled(True)self.actions.createCircleMode.setEnabled(True)self.actions.createLineMode.setEnabled(True)self.actions.createPointMode.setEnabled(True)self.actions.createLineStripMode.setEnabled(True)title = __appname__if self.filename is not None:title = "{} - {}".format(title, self.filename)self.setWindowTitle(title)if self.hasLabelFile():self.actions.deleteFile.setEnabled(True)else:self.actions.deleteFile.setEnabled(False)def toggleActions(self, value=True):"""Enable/Disable widgets which depend on an opened image."""for z in self.actions.zoomActions:z.setEnabled(value)for action in self.actions.onLoadActive:action.setEnabled(value)def canvasShapeEdgeSelected(self, selected, shape):self.actions.addPointToEdge.setEnabled(selected and shape and shape.canAddPoint())def queueEvent(self, function):QtCore.QTimer.singleShot(0, function)def status(self, message, delay=5000):self.statusBar().showMessage(message, delay)def resetState(self):self.labelList.clear()self.filename = Noneself.imagePath = Noneself.imageData = Noneself.labelFile = Noneself.otherData = Noneself.canvas.resetState()def currentItem(self):items = self.labelList.selectedItems()if items:return items[0]return Nonedef addRecentFile(self, filename):if filename in self.recentFiles:self.recentFiles.remove(filename)elif len(self.recentFiles) >= self.maxRecent:self.recentFiles.pop()self.recentFiles.insert(0, filename)# Callbacksdef undoShapeEdit(self):self.canvas.restoreShape()self.labelList.clear()self.loadShapes(self.canvas.shapes)self.actions.undo.setEnabled(self.canvas.isShapeRestorable)def tutorial(self):url = "https://github.com/wkentaro/labelme/tree/master/examples/tutorial" # NOQAwebbrowser.open(url)def toggleDrawingSensitive(self, drawing=True):"""Toggle drawing sensitive.In the middle of drawing, toggling between modes should be disabled."""self.actions.editMode.setEnabled(not drawing)self.actions.undoLastPoint.setEnabled(drawing)self.actions.undo.setEnabled(not drawing)self.actions.delete.setEnabled(not drawing)def toggleDrawMode(self, edit=True, createMode="polygon"):self.canvas.setEditing(edit)self.canvas.createMode = createModeif edit:self.actions.createMode.setEnabled(True)self.actions.createRectangleMode.setEnabled(True)self.actions.createCircleMode.setEnabled(True)self.actions.createLineMode.setEnabled(True)self.actions.createPointMode.setEnabled(True)self.actions.createLineStripMode.setEnabled(True)else:if createMode == "polygon":self.actions.createMode.setEnabled(False)self.actions.createRectangleMode.setEnabled(True)self.actions.createCircleMode.setEnabled(True)self.actions.createLineMode.setEnabled(True)self.actions.createPointMode.setEnabled(True)self.actions.createLineStripMode.setEnabled(True)elif createMode == "rectangle":self.actions.createMode.setEnabled(True)self.actions.createRectangleMode.setEnabled(False)self.actions.createCircleMode.setEnabled(True)self.actions.createLineMode.setEnabled(True)self.actions.createPointMode.setEnabled(True)self.actions.createLineStripMode.setEnabled(True)elif createMode == "line":self.actions.createMode.setEnabled(True)self.actions.createRectangleMode.setEnabled(True)self.actions.createCircleMode.setEnabled(True)self.actions.createLineMode.setEnabled(False)self.actions.createPointMode.setEnabled(True)self.actions.createLineStripMode.setEnabled(True)elif createMode == "point":self.actions.createMode.setEnabled(True)self.actions.createRectangleMode.setEnabled(True)self.actions.createCircleMode.setEnabled(True)self.actions.createLineMode.setEnabled(True)self.actions.createPointMode.setEnabled(False)self.actions.createLineStripMode.setEnabled(True)elif createMode == "circle":self.actions.createMode.setEnabled(True)self.actions.createRectangleMode.setEnabled(True)self.actions.createCircleMode.setEnabled(False)self.actions.createLineMode.setEnabled(True)self.actions.createPointMode.setEnabled(True)self.actions.createLineStripMode.setEnabled(True)elif createMode == "linestrip":self.actions.createMode.setEnabled(True)self.actions.createRectangleMode.setEnabled(True)self.actions.createCircleMode.setEnabled(True)self.actions.createLineMode.setEnabled(True)self.actions.createPointMode.setEnabled(True)self.actions.createLineStripMode.setEnabled(False)else:raise ValueError("Unsupported createMode: %s" % createMode)self.actions.editMode.setEnabled(not edit)def setEditMode(self):self.toggleDrawMode(True)def updateFileMenu(self):current = self.filenamedef exists(filename):return osp.exists(str(filename))menu = self.menus.recentFilesmenu.clear()files = [f for f in self.recentFiles if f != current and exists(f)]for i, f in enumerate(files):icon = utils.newIcon("labels")action = QtWidgets.QAction(icon, "&%d %s" % (i + 1, QtCore.QFileInfo(f).fileName()), self)action.triggered.connect(functools.partial(self.loadRecent, f))menu.addAction(action)def popLabelListMenu(self, point):self.menus.labelList.exec_(self.labelList.mapToGlobal(point))def validateLabel(self, label):# no validationif self._config["validate_label"] is None:return Truefor i in range(self.uniqLabelList.count()):label_i = self.uniqLabelList.item(i).data(Qt.UserRole)if self._config["validate_label"] in ["exact"]:if label_i == label:return Truereturn Falsedef editLabel(self, item=None):if item and not isinstance(item, LabelListWidgetItem):raise TypeError("item must be LabelListWidgetItem type")if not self.canvas.editing():returnif not item:item = self.currentItem()if item is None:returnshape = item.shape()if shape is None:returntext, flags, group_id = self.labelDialog.popUp(text=shape.label, flags=shape.flags, group_id=shape.group_id,)if text is None:returnif not self.validateLabel(text):self.errorMessage(self.tr("无效标签"),self.tr("标签'{}'验证类型'{}'无效").format(text, self._config["validate_label"]),)returnshape.label = textshape.flags = flagsshape.group_id = group_idif shape.group_id is None:item.setText(shape.label)else:item.setText("{} ({})".format(shape.label, shape.group_id))self.setDirty()if not self.uniqLabelList.findItemsByLabel(shape.label):item = QtWidgets.QListWidgetItem()item.setData(Qt.UserRole, shape.label)self.uniqLabelList.addItem(item)def fileSearchChanged(self):self.importDirImages(self.lastOpenDir, pattern=self.fileSearch.text(), load=False,)def fileSelectionChanged(self):items = self.fileListWidget.selectedItems()if not items:returnitem = items[0]if not self.mayContinue():returncurrIndex = self.imageList.index(str(item.text()))if currIndex < len(self.imageList):filename = self.imageList[currIndex]if filename:self.loadFile(filename)# React to canvas signals.def shapeSelectionChanged(self, selected_shapes):self._noSelectionSlot = Truefor shape in self.canvas.selectedShapes:shape.selected = Falseself.labelList.clearSelection()self.canvas.selectedShapes = selected_shapesfor shape in self.canvas.selectedShapes:shape.selected = Trueitem = self.labelList.findItemByShape(shape)self.labelList.selectItem(item)self.labelList.scrollToItem(item)self._noSelectionSlot = Falsen_selected = len(selected_shapes)self.actions.delete.setEnabled(n_selected)self.actions.copy.setEnabled(n_selected)self.actions.edit.setEnabled(n_selected == 1)def addLabel(self, shape):if shape.group_id is None:text = shape.labelelse:text = "{} ({})".format(shape.label, shape.group_id)label_list_item = LabelListWidgetItem(text, shape)self.labelList.addItem(label_list_item)if not self.uniqLabelList.findItemsByLabel(shape.label):item = self.uniqLabelList.createItemFromLabel(shape.label)self.uniqLabelList.addItem(item)rgb = self._get_rgb_by_label(shape.label)self.uniqLabelList.setItemLabel(item, shape.label, rgb)self.labelDialog.addLabelHistory(shape.label)for action in self.actions.onShapesPresent:action.setEnabled(True)rgb = self._get_rgb_by_label(shape.label)r, g, b = rgblabel_list_item.setText('{} <font color="#{:02x}{:02x}{:02x}">●</font>'.format(text, r, g, b))shape.line_color = QtGui.QColor(r, g, b)shape.vertex_fill_color = QtGui.QColor(r, g, b)shape.hvertex_fill_color = QtGui.QColor(255, 255, 255)shape.fill_color = QtGui.QColor(r, g, b, 128)shape.select_line_color = QtGui.QColor(255, 255, 255)shape.select_fill_color = QtGui.QColor(r, g, b, 155)def _get_rgb_by_label(self, label):if self._config["shape_color"] == "auto":item = self.uniqLabelList.findItemsByLabel(label)[0]label_id = self.uniqLabelList.indexFromItem(item).row() + 1label_id += self._config["shift_auto_shape_color"]return LABEL_COLORMAP[label_id % len(LABEL_COLORMAP)]elif (self._config["shape_color"] == "manual"and self._config["label_colors"]and label in self._config["label_colors"]):return self._config["label_colors"][label]elif self._config["default_shape_color"]:return self._config["default_shape_color"]def remLabels(self, shapes):for shape in shapes:item = self.labelList.findItemByShape(shape)self.labelList.removeItem(item)def loadShapes(self, shapes, replace=True):self._noSelectionSlot = Truefor shape in shapes:self.addLabel(shape)self.labelList.clearSelection()self._noSelectionSlot = Falseself.canvas.loadShapes(shapes, replace=replace)def loadLabels(self, shapes):s = []for shape in shapes:label = shape["label"]points = shape["points"]shape_type = shape["shape_type"]flags = shape["flags"]group_id = shape["group_id"]other_data = shape["other_data"]shape = Shape(label=label, shape_type=shape_type, group_id=group_id,)for x, y in points:shape.addPoint(QtCore.QPointF(x, y))shape.close()default_flags = {}if self._config["label_flags"]:for pattern, keys in self._config["label_flags"].items():if re.match(pattern, label):for key in keys:default_flags[key] = Falseshape.flags = default_flagsshape.flags.update(flags)shape.other_data = other_datas.append(shape)self.loadShapes(s)def loadFlags(self, flags):self.flag_widget.clear()for key, flag in flags.items():item = QtWidgets.QListWidgetItem(key)item.setFlags(item.flags() | Qt.ItemIsUserCheckable)item.setCheckState(Qt.Checked if flag else Qt.Unchecked)self.flag_widget.addItem(item)def saveLabels(self, filename):lf = LabelFile()def format_shape(s):data = s.other_data.copy()data.update(dict(label=s.label.encode("utf-8") if PY2 else s.label,points=[(p.x(), p.y()) for p in s.points],group_id=s.group_id,shape_type=s.shape_type,flags=s.flags,))return datashapes = [format_shape(item.shape()) for item in self.labelList]flags = {}for i in range(self.flag_widget.count()):item = self.flag_widget.item(i)key = item.text()flag = item.checkState() == Qt.Checkedflags[key] = flagtry:imagePath = osp.relpath(self.imagePath, osp.dirname(filename))imageData = self.imageData if self._config["store_data"] else Noneif osp.dirname(filename) and not osp.exists(osp.dirname(filename)):os.makedirs(osp.dirname(filename))lf.save(filename=filename,shapes=shapes,imagePath=imagePath,imageData=imageData,imageHeight=self.image.height(),imageWidth=self.image.width(),otherData=self.otherData,flags=flags,)self.labelFile = lfitems = self.fileListWidget.findItems(self.imagePath, Qt.MatchExactly)if len(items) > 0:if len(items) != 1:raise RuntimeError("There are duplicate files.")items[0].setCheckState(Qt.Checked)# disable allows next and previous image to proceed# self.filename = filenamereturn Trueexcept LabelFileError as e:self.errorMessage(self.tr("Error saving label data"), self.tr("<b>%s</b>") % e)return Falsedef copySelectedShape(self):added_shapes = self.canvas.copySelectedShapes()self.labelList.clearSelection()for shape in added_shapes:self.addLabel(shape)self.setDirty()def labelSelectionChanged(self):if self._noSelectionSlot:returnif self.canvas.editing():selected_shapes = []for item in self.labelList.selectedItems():selected_shapes.append(item.shape())if selected_shapes:self.canvas.selectShapes(selected_shapes)else:self.canvas.deSelectShape()def labelItemChanged(self, item):shape = item.shape()self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked)def labelOrderChanged(self):self.setDirty()self.canvas.loadShapes([item.shape() for item in self.labelList])# Callback functions:def newShape(self):"""Pop-up and give focus to the label editor.position MUST be in global coordinates."""items = self.uniqLabelList.selectedItems()text = Noneif items:text = items[0].data(Qt.UserRole)flags = {}group_id = Noneif self._config["display_label_popup"] or not text:previous_text = self.labelDialog.edit.text()text, flags, group_id = self.labelDialog.popUp(text)if not text:self.labelDialog.edit.setText(previous_text)if text and not self.validateLabel(text):self.errorMessage(self.tr("无效标签"),self.tr("标签'{}'验证类型'{}'无效").format(text, self._config["validate_label"]),)text = ""if text:self.labelList.clearSelection()shape = self.canvas.setLastLabel(text, flags)shape.group_id = group_idself.addLabel(shape)self.actions.editMode.setEnabled(True)self.actions.undoLastPoint.setEnabled(False)self.actions.undo.setEnabled(True)self.setDirty()else:self.canvas.undoLastLine()self.canvas.shapesBackups.pop()def scrollRequest(self, delta, orientation):units = -delta * 0.1 # natural scrollbar = self.scrollBars[orientation]value = bar.value() + bar.singleStep() * unitsself.setScroll(orientation, value)def setScroll(self, orientation, value):self.scrollBars[orientation].setValue(value)self.scroll_values[orientation][self.filename] = valuedef setZoom(self, value):self.actions.fitWidth.setChecked(False)self.actions.fitWindow.setChecked(False)self.zoomMode = self.MANUAL_ZOOMself.zoomWidget.setValue(value)self.zoom_values[self.filename] = (self.zoomMode, value)def addZoom(self, increment=1.1):self.setZoom(self.zoomWidget.value() * increment)def zoomRequest(self, delta, pos):canvas_width_old = self.canvas.width()units = 1.1if delta < 0:units = 0.9self.addZoom(units)canvas_width_new = self.canvas.width()if canvas_width_old != canvas_width_new:canvas_scale_factor = canvas_width_new / canvas_width_oldx_shift = round(pos.x() * canvas_scale_factor) - pos.x()y_shift = round(pos.y() * canvas_scale_factor) - pos.y()self.setScroll(Qt.Horizontal,self.scrollBars[Qt.Horizontal].value() + x_shift,)self.setScroll(Qt.Vertical, self.scrollBars[Qt.Vertical].value() + y_shift,)def setFitWindow(self, value=True):if value:self.actions.fitWidth.setChecked(False)self.zoomMode = self.FIT_WINDOW if value else self.MANUAL_ZOOMself.adjustScale()def setFitWidth(self, value=True):if value:self.actions.fitWindow.setChecked(False)self.zoomMode = self.FIT_WIDTH if value else self.MANUAL_ZOOMself.adjustScale()def onNewBrightnessContrast(self, qimage):self.canvas.loadPixmap(QtGui.QPixmap.fromImage(qimage), clear_shapes=False)def brightnessContrast(self, value):dialog = BrightnessContrastDialog(utils.img_data_to_pil(self.imageData),self.onNewBrightnessContrast,parent=self,)brightness, contrast = self.brightnessContrast_values.get(self.filename, (None, None))if brightness is not None:dialog.slider_brightness.setValue(brightness)if contrast is not None:dialog.slider_contrast.setValue(contrast)dialog.exec_()brightness = dialog.slider_brightness.value()contrast = dialog.slider_contrast.value()self.brightnessContrast_values[self.filename] = (brightness, contrast)def togglePolygons(self, value):for item in self.labelList:item.setCheckState(Qt.Checked if value else Qt.Unchecked)def loadFile(self, filename=None):"""Load the specified file, or the last opened file if None."""# changing fileListWidget loads fileif filename in self.imageList and (self.fileListWidget.currentRow() != self.imageList.index(filename)):self.fileListWidget.setCurrentRow(self.imageList.index(filename))self.fileListWidget.repaint()returnself.resetState()self.canvas.setEnabled(False)if filename is None:filename = self.settings.value("filename", "")filename = str(filename)if not QtCore.QFile.exists(filename):self.errorMessage(self.tr("打开文件错误"),self.tr("文件不存在: <b>%s</b>") % filename,)return False# assumes same name, but json extensionself.status(self.tr("Loading %s...") % osp.basename(str(filename)))label_file = osp.splitext(filename)[0] + ".json"if self.output_dir:label_file_without_path = osp.basename(label_file)label_file = osp.join(self.output_dir, label_file_without_path)if QtCore.QFile.exists(label_file) and LabelFile.is_label_file(label_file):try:self.labelFile = LabelFile(label_file)except LabelFileError as e:self.errorMessage(self.tr("打开文件错误"),self.tr("<p><b>%s</b></p>""<p>Make sure <i>%s</i> is a valid label file.")% (e, label_file),)self.status(self.tr("Error reading %s") % label_file)return Falseself.imageData = self.labelFile.imageDataself.imagePath = osp.join(osp.dirname(label_file), self.labelFile.imagePath,)self.otherData = self.labelFile.otherDataelse:self.imageData = LabelFile.load_image_file(filename)if self.imageData:self.imagePath = filenameself.labelFile = Noneimage = QtGui.QImage.fromData(self.imageData)if image.isNull():formats = ["*.{}".format(fmt.data().decode())for fmt in QtGui.QImageReader.supportedImageFormats()]self.errorMessage(self.tr("打开文件错误"),self.tr("<p>请确保 <i>{0}</i> 是有效的图像文件。<br/>""支持的图像格式: {1}</p>").format(filename, ",".join(formats)),)self.status(self.tr("Error reading %s") % filename)return Falseself.image = imageself.filename = filenameif self._config["keep_prev"]:prev_shapes = self.canvas.shapesself.canvas.loadPixmap(QtGui.QPixmap.fromImage(image))flags = {k: False for k in self._config["flags"] or []}if self.labelFile:self.loadLabels(self.labelFile.shapes)if self.labelFile.flags is not None:flags.update(self.labelFile.flags)self.loadFlags(flags)if self._config["keep_prev"] and self.noShapes():self.loadShapes(prev_shapes, replace=False)self.setDirty()else:self.setClean()self.canvas.setEnabled(True)# set zoom valuesis_initial_load = not self.zoom_valuesif self.filename in self.zoom_values:self.zoomMode = self.zoom_values[self.filename][0]self.setZoom(self.zoom_values[self.filename][1])elif is_initial_load or not self._config["keep_prev_scale"]:self.adjustScale(initial=True)# set scroll valuesfor orientation in self.scroll_values:if self.filename in self.scroll_values[orientation]:self.setScroll(orientation, self.scroll_values[orientation][self.filename])# set brightness constrast valuesdialog = BrightnessContrastDialog(utils.img_data_to_pil(self.imageData),self.onNewBrightnessContrast,parent=self,)brightness, contrast = self.brightnessContrast_values.get(self.filename, (None, None))if self._config["keep_prev_brightness"] and self.recentFiles:brightness, _ = self.brightnessContrast_values.get(self.recentFiles[0], (None, None))if self._config["keep_prev_contrast"] and self.recentFiles:_, contrast = self.brightnessContrast_values.get(self.recentFiles[0], (None, None))if brightness is not None:dialog.slider_brightness.setValue(brightness)if contrast is not None:dialog.slider_contrast.setValue(contrast)self.brightnessContrast_values[self.filename] = (brightness, contrast)if brightness is not None or contrast is not None:dialog.onNewValue(None)self.paintCanvas()self.addRecentFile(self.filename)self.toggleActions(True)self.status(self.tr("Loaded %s") % osp.basename(str(filename)))return Truedef resizeEvent(self, event):if (self.canvasand not self.image.isNull()and self.zoomMode != self.MANUAL_ZOOM):self.adjustScale()super(MainWindow, self).resizeEvent(event)def paintCanvas(self):assert not self.image.isNull(), "cannot paint null image"self.canvas.scale = 0.01 * self.zoomWidget.value()self.canvas.adjustSize()self.canvas.update()def adjustScale(self, initial=False):value = self.scalers[self.FIT_WINDOW if initial else self.zoomMode]()value = int(100 * value)self.zoomWidget.setValue(value)self.zoom_values[self.filename] = (self.zoomMode, value)def scaleFitWindow(self):"""Figure out the size of the pixmap to fit the main widget."""e = 2.0 # So that no scrollbars are generated.w1 = self.centralWidget().width() - eh1 = self.centralWidget().height() - ea1 = w1 / h1# Calculate a new scale value based on the pixmap's aspect ratio.w2 = self.canvas.pixmap.width() - 0.0h2 = self.canvas.pixmap.height() - 0.0a2 = w2 / h2return w1 / w2 if a2 >= a1 else h1 / h2def scaleFitWidth(self):# The epsilon does not seem to work too well here.w = self.centralWidget().width() - 2.0return w / self.canvas.pixmap.width()def enableSaveImageWithData(self, enabled):self._config["store_data"] = enabledself.actions.saveWithImageData.setChecked(enabled)def closeEvent(self, event):if not self.mayContinue():event.ignore()self.settings.setValue("filename", self.filename if self.filename else "")self.settings.setValue("window/size", self.size())self.settings.setValue("window/position", self.pos())self.settings.setValue("window/state", self.saveState())self.settings.setValue("recentFiles", self.recentFiles)# ask the use for where to save the labels# self.settings.setValue('window/geometry', self.saveGeometry())def dragEnterEvent(self, event):extensions = [".%s" % fmt.data().decode().lower()for fmt in QtGui.QImageReader.supportedImageFormats()]if event.mimeData().hasUrls():items = [i.toLocalFile() for i in event.mimeData().urls()]if any([i.lower().endswith(tuple(extensions)) for i in items]):event.accept()else:event.ignore()def dropEvent(self, event):if not self.mayContinue():event.ignore()returnitems = [i.toLocalFile() for i in event.mimeData().urls()]self.importDroppedImageFiles(items)# User Dialogs #def loadRecent(self, filename):if self.mayContinue():self.loadFile(filename)def openPrevImg(self, _value=False):keep_prev = self._config["keep_prev"]if Qt.KeyboardModifiers() == (Qt.ControlModifier | Qt.ShiftModifier):self._config["keep_prev"] = Trueif not self.mayContinue():returnif len(self.imageList) <= 0:returnif self.filename is None:returncurrIndex = self.imageList.index(self.filename)if currIndex - 1 >= 0:filename = self.imageList[currIndex - 1]if filename:self.loadFile(filename)self._config["keep_prev"] = keep_prevdef openNextImg(self, _value=False, load=True):keep_prev = self._config["keep_prev"]if Qt.KeyboardModifiers() == (Qt.ControlModifier | Qt.ShiftModifier):self._config["keep_prev"] = Trueif not self.mayContinue():returnif len(self.imageList) <= 0:returnfilename = Noneif self.filename is None:filename = self.imageList[0]else:currIndex = self.imageList.index(self.filename)if currIndex + 1 < len(self.imageList):filename = self.imageList[currIndex + 1]else:filename = self.imageList[-1]self.filename = filenameif self.filename and load:self.loadFile(self.filename)self._config["keep_prev"] = keep_prevdef openFile(self, _value=False):if not self.mayContinue():returnpath = osp.dirname(str(self.filename)) if self.filename else "."formats = ["*.{}".format(fmt.data().decode())for fmt in QtGui.QImageReader.supportedImageFormats()]filters = self.tr("图像和标签文件 (%s)") % " ".join(formats + ["*%s" % LabelFile.suffix])filename = QtWidgets.QFileDialog.getOpenFileName(self,self.tr("%s - 选择图像或标签文件") % __appname__,path,filters,)if QT5:filename, _ = filenamefilename = str(filename)if filename:self.loadFile(filename)def changeOutputDirDialog(self, _value=False):default_output_dir = self.output_dirif default_output_dir is None and self.filename:default_output_dir = osp.dirname(self.filename)if default_output_dir is None:default_output_dir = self.currentPath()output_dir = QtWidgets.QFileDialog.getExistingDirectory(self,self.tr("%s - 在目录中保存/加载标注") % __appname__,default_output_dir,QtWidgets.QFileDialog.ShowDirsOnly| QtWidgets.QFileDialog.DontResolveSymlinks,)output_dir = str(output_dir)if not output_dir:returnself.output_dir = output_dirself.statusBar().showMessage(self.tr("%s . 标注将保存/加载在 %s")% ("更改标注目录", self.output_dir))self.statusBar().show()current_filename = self.filenameself.importDirImages(self.lastOpenDir, load=False)if current_filename in self.imageList:# retain currently selected fileself.fileListWidget.setCurrentRow(self.imageList.index(current_filename))self.fileListWidget.repaint()def saveFile(self, _value=False):assert not self.image.isNull(), "cannot save empty image"if self.labelFile:# DL20180323 - overwrite when in directoryself._saveFile(self.labelFile.filename)elif self.output_file:self._saveFile(self.output_file)self.close()else:self._saveFile(self.saveFileDialog())def saveFileAs(self, _value=False):assert not self.image.isNull(), "cannot save empty image"self._saveFile(self.saveFileDialog())def saveFileDialog(self):caption = self.tr("%s - 选择文件") % __appname__filters = self.tr("标签文件 (*%s)") % LabelFile.suffixif self.output_dir:dlg = QtWidgets.QFileDialog(self, caption, self.output_dir, filters)else:dlg = QtWidgets.QFileDialog(self, caption, self.currentPath(), filters)dlg.setDefaultSuffix(LabelFile.suffix[1:])dlg.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)dlg.setOption(QtWidgets.QFileDialog.DontConfirmOverwrite, False)dlg.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, False)basename = osp.basename(osp.splitext(self.filename)[0])if self.output_dir:default_labelfile_name = osp.join(self.output_dir, basename + LabelFile.suffix)else:default_labelfile_name = osp.join(self.currentPath(), basename + LabelFile.suffix)filename = dlg.getSaveFileName(self,self.tr("选择文件"),default_labelfile_name,self.tr("Label files (*%s)") % LabelFile.suffix,)if isinstance(filename, tuple):filename, _ = filenamereturn filenamedef _saveFile(self, filename):if filename and self.saveLabels(filename):self.addRecentFile(filename)self.setClean()def closeFile(self, _value=False):if not self.mayContinue():returnself.resetState()self.setClean()self.toggleActions(False)self.canvas.setEnabled(False)self.actions.saveAs.setEnabled(False)def getLabelFile(self):if self.filename.lower().endswith(".json"):label_file = self.filenameelse:label_file = osp.splitext(self.filename)[0] + ".json"return label_filedef deleteFile(self):mb = QtWidgets.QMessageBoxmsg = self.tr("你即将永久删除此标签文件 ""是否继续?")answer = mb.warning(self, self.tr("注意"), msg, mb.Yes | mb.No)if answer != mb.Yes:returnlabel_file = self.getLabelFile()if osp.exists(label_file):os.remove(label_file)logger.info("Label file is removed: {}".format(label_file))item = self.fileListWidget.currentItem()item.setCheckState(Qt.Unchecked)self.resetState()# Message Dialogs. #def hasLabels(self):if self.noShapes():self.errorMessage("无标注对象","保存文件前必须至少标注一个对象.",)return Falsereturn Truedef hasLabelFile(self):if self.filename is None:return Falselabel_file = self.getLabelFile()return osp.exists(label_file)def mayContinue(self):if not self.dirty:return Truemb = QtWidgets.QMessageBoxmsg = self.tr('关闭前是否保存标注到"{}"?').format(self.filename)answer = mb.question(self,self.tr("Save annotations?"),msg,mb.Save | mb.Discard | mb.Cancel,mb.Save,)if answer == mb.Discard:return Trueelif answer == mb.Save:self.saveFile()return Trueelse: # answer == mb.Cancelreturn Falsedef errorMessage(self, title, message):return QtWidgets.QMessageBox.critical(self, title, "<p><b>%s</b></p>%s" % (title, message))def currentPath(self):return osp.dirname(str(self.filename)) if self.filename else "."def toggleKeepPrevMode(self):self._config["keep_prev"] = not self._config["keep_prev"]def deleteSelectedShape(self):yes, no = QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.Nomsg = self.tr("您即将永久删除 {} 个多边形, ""是否继续?").format(len(self.canvas.selectedShapes))if yes == QtWidgets.QMessageBox.warning(self, self.tr("Attention"), msg, yes | no, yes):self.remLabels(self.canvas.deleteSelected())self.setDirty()if self.noShapes():for action in self.actions.onShapesPresent:action.setEnabled(False)def copyShape(self):self.canvas.endMove(copy=True)self.labelList.clearSelection()for shape in self.canvas.selectedShapes:self.addLabel(shape)self.setDirty()def moveShape(self):self.canvas.endMove(copy=False)self.setDirty()def openDirDialog(self, _value=False, dirpath=None):if not self.mayContinue():returndefaultOpenDirPath = dirpath if dirpath else "."if self.lastOpenDir and osp.exists(self.lastOpenDir):defaultOpenDirPath = self.lastOpenDirelse:defaultOpenDirPath = (osp.dirname(self.filename) if self.filename else ".")targetDirPath = str(QtWidgets.QFileDialog.getExistingDirectory(self,self.tr("%s - 打开目录") % __appname__,defaultOpenDirPath,QtWidgets.QFileDialog.ShowDirsOnly| QtWidgets.QFileDialog.DontResolveSymlinks,))self.importDirImages(targetDirPath)@propertydef imageList(self):lst = []for i in range(self.fileListWidget.count()):item = self.fileListWidget.item(i)lst.append(item.text())return lstdef importDroppedImageFiles(self, imageFiles):extensions = [".%s" % fmt.data().decode().lower()for fmt in QtGui.QImageReader.supportedImageFormats()]self.filename = Nonefor file in imageFiles:if file in self.imageList or not file.lower().endswith(tuple(extensions)):continuelabel_file = osp.splitext(file)[0] + ".json"if self.output_dir:label_file_without_path = osp.basename(label_file)label_file = osp.join(self.output_dir, label_file_without_path)item = QtWidgets.QListWidgetItem(file)item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)if QtCore.QFile.exists(label_file) and LabelFile.is_label_file(label_file):item.setCheckState(Qt.Checked)else:item.setCheckState(Qt.Unchecked)self.fileListWidget.addItem(item)if len(self.imageList) > 1:self.actions.openNextImg.setEnabled(True)self.actions.openPrevImg.setEnabled(True)self.openNextImg()def importDirImages(self, dirpath, pattern=None, load=True):self.actions.openNextImg.setEnabled(True)self.actions.openPrevImg.setEnabled(True)if not self.mayContinue() or not dirpath:returnself.lastOpenDir = dirpathself.filename = Noneself.fileListWidget.clear()for filename in self.scanAllImages(dirpath):if pattern and pattern not in filename:continuelabel_file = osp.splitext(filename)[0] + ".json"if self.output_dir:label_file_without_path = osp.basename(label_file)label_file = osp.join(self.output_dir, label_file_without_path)item = QtWidgets.QListWidgetItem(filename)item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)if QtCore.QFile.exists(label_file) and LabelFile.is_label_file(label_file):item.setCheckState(Qt.Checked)else:item.setCheckState(Qt.Unchecked)self.fileListWidget.addItem(item)self.openNextImg(load=load)def scanAllImages(self, folderPath):extensions = [".%s" % fmt.data().decode().lower()for fmt in QtGui.QImageReader.supportedImageFormats()]images = []for root, dirs, files in os.walk(folderPath):for file in files:if file.lower().endswith(tuple(extensions)):relativePath = osp.join(root, file)images.append(relativePath)images.sort(key=lambda x: x.lower())return images相关文章:
win11 labelme 汉化菜单
替换 app.py,再重启 #labelme 汉化菜单# -*- coding: utf-8 -*-import functools import os import os.path as osp import re import webbrowserimport imgviz from qtpy import QtCore from qtpy.QtCore import Qt from qtpy import QtGui from qtpy import QtWidgetsfrom l…...
Linux的基础指令和环境部署,项目部署实战(下)
目录 上一篇:Linxu的基础指令和环境部署,项目部署实战(上)-CSDN博客 1. 搭建Java部署环境 1.1 apt apt常用命令 列出所有的软件包 更新软件包数据库 安装软件包 移除软件包 1.2 JDK 1.2.1. 更新 1.2.2. 安装openjdk&am…...
利用Java爬虫精准获取商品SKU详细信息:实战案例指南
在电商领域,SKU(Stock Keeping Unit,库存单位)详细信息是电商运营的核心数据之一。它不仅包含了商品的规格、价格、库存等关键信息,还直接影响到库存管理、价格策略和市场分析等多个方面。本文将详细介绍如何利用Java爬…...
数值积分:通过复合梯形法计算
在物理学和工程学中,很多问题都可以通过数值积分来求解,特别是当我们无法得到解析解时。数值积分是通过计算积分区间内离散点的函数值来近似积分的结果。在这篇博客中,我将讨论如何使用 复合梯形法 来进行数值积分,并以一个简单的…...
【Java计算机毕业设计】基于SSM+VUE保险公司管理系统数据库源代码+LW文档+开题报告+答辩稿+部署教程+代码讲解
源代码数据库LW文档(1万字以上)开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统:Window操作系统 2、开发工具:IntelliJ IDEA或者Eclipse 3、数据库存储:…...
C#之上位机开发---------C#通信库及WPF的简单实践
〇、上位机,分层架构 界面层 要实现的功能: 展示数据 获取数据 发送数据 数据层 要实现的功能: 转换数据 打包数据 存取数据 通信层 要实现的功能: 打开连接 关闭连接 读取数据 写入数据 实体类 作用: 封装数据…...
Pytorch论文实现之GAN-C约束鉴别器训练自己的数据集
简介 简介:这次介绍复现的论文主要是约束判别器的函数空间,作者认为原来的损失函数在优化判别器关于真样本和假样本的相对输出缺乏显式约束,因为在实践中,在优化生成器时,鉴别器对生成样本的输出会增加,但对真实数据保持不变,而优化鉴别器会导致其对真实数据的输出增加…...
vue3.x 的shallowReactive 与 shallowRef 详细解读
在 Vue 3.x 中,shallowReactive 和 shallowRef 是两个用于创建浅层响应式数据的 API。它们与 reactive 和 ref 类似,但在处理嵌套对象时的行为有所不同。以下是它们的详细解读和示例。 1. shallowReactive 作用 shallowReactive 创建一个浅层响应式对…...
MongoDB 常用命令速查表
以下是一份 MongoDB 常用命令速查表,涵盖数据库、集合、文档的增删改查、索引管理、聚合操作等场景: 1. 数据库操作 命令说明show dbs查看所有数据库use <db-name>切换/创建数据库(需插入数据后才会显示)db.dropDatabase()…...
DeepSeek崛起的本质分析:AI变局中的中国机会
DeepSeek崛起的本质分析:AI变局中的中国机会 1. 中国AI发展的大背景 近年来,全球AI技术竞争日趋白热化,而中国作为全球第二大经济体,在AI领域的投入和政策支持力度不断加大。大模型是AI产业的制高点,而美国对中国的高…...
Autojs: 使用 SQLite
例子 let db new SQLiteUtil("/sdcard/A_My_DB/sqlite.db");db.fastCreateTable("user_table",{name: "",online: false,},["name"] // 设置 name 为唯一, 重复项 不会添加成功 );// 新增数据的 ID let row_id db.insert("use…...
读书笔记 - 修改代码的艺术
读书笔记 - 修改代码的艺术 第 1 章 修改软件第 2 章 带着反馈工作系统变更方式反馈方式遗留代码修改方法 第 3 章 感知和分离伪协作程序模拟对象 第 4 章 接缝模型接缝 第 5 章 工具自动化重构工具单元测试用具 第 6 章 时间紧迫,但必须修改新生方法(Sp…...
element-plus树形数据与懒加载的实现
环境 vue版本: 2.6.14 需求 树形表格,默认返回当前登录人拥有权限的一个层级的数据,通过点击load懒加载获取下一层的数据,要求有新增、编辑、删除操作。 树类型的懒加载: 当row中包含children字段时,被…...
仿 Sora 之形,借物理模拟之技绘视频之彩
来自麻省理工学院、斯坦福大学、哥伦比亚大学以及康奈尔大学的研究人员携手开源了一款创新的3D交互视频模型——PhysDreamer(以下简称“PD”)。PD与OpenAI旗下的Sora相似,能够借助物理模拟技术来生成视频,这意味着PD所生成的视频蕴…...
【算法】快排
题目 快排 思路 如果输入为0或1直接返回;否则取一个基准值,可以取中间位置,如果输入是有序的可以避免时间过长,然后移动指针,先让i指针右移,如果小于基准值就继续右移,j指针左移同理。如果指…...
RedisTemplate存储含有特殊字符解决
ERROR信息: 案发时间: 2025-02-18 01:01 案发现场: UserServiceImpl.java 嫌疑人: stringRedisTemplate.opsForValue().set(SystemConstants.LOGIN_CODE_PREFIX phone, code, Duration.ofMinutes(3L)); // 3分钟过期作案动机: stringRedisTemplate继承了Redistemplate 使用的…...
Django REST Framework (DRF) 中用于构建 API 视图类解析
Django REST Framework (DRF) 提供了丰富的视图类,用于构建 API 视图。这些视图类可以分为以下几类: 1. 基础视图类 这些是 DRF 中最基础的视图类,通常用于实现自定义逻辑。 常用类 APIView: 最基本的视图类,所有其…...
Zotero PDF Translate插件配置百度翻译api
Zotero PDF Translate插件可以使用几种翻译api,虽然谷歌最好用,但是由于众所周知的原因,不稳定。而cnki有字数限制,有道有时也不行。其他的翻译需要申请密钥。本文以百度为例,进行申请 官方有申请教程: Zot…...
Redis离线安装
Linux系统Centos安装部署Redis缓存插件 参考:Redis中文网: https://www.redis.net.cn/ 参考:RPM软件包下载地址: https://rpmfind.net/linux/RPM/index.html http://rpm.pbone.net/ https://mirrors.aliyun.com/centos/7/os…...
五、k8s:容忍 存储卷
容忍: 即使节点上有污点,依然可以部署pod。 tolerations: operator: "Exists" 不指定key,表示容忍所有的污点 cordon和drain cordon: 直接标记节点为不可用,pod不能部署到该节点。新建的pod不会再部署到该节点&#…...
零售顶流三只松鼠如何重塑品牌营销新生态,寻找新的增长点?
在零售行业的变革浪潮中,三只松鼠作为休闲零食领域的代表品牌,面临着前所未有的机遇与挑战。在竞争激烈的零售市场中,三只松鼠以其突出的表现成为行业焦点。2024 年前三季度,营收 71.69 亿元,同比增长 56.46%ÿ…...
USC 安防平台之移动侦测
随着第四次科技革命的开启,AI技术获取了突飞猛进的发展,视频监控对应的视频分析技术也获取了巨大的发展。 还记得15年前采用人工提取特征做前景背景分离和提取,大部分依赖CPU,最多使用一下TI的DM642 DSP加速,开发难度…...
MySQL智障离谱问题,删了库确还存在、也不能再创建同名库
1、问题 今天跟后端朋友接毕设单子的时候,后端穿过来的【weather.sql】这个文件没弄好,导致这个【weather】数据库的数据是错的,因此我用datagrip的GUI界面直接右键删除,结果就是tmd删不掉,ok,我只能在那新…...
IIS asp.net权限不足
检查应用程序池的权限 IIS 应用程序池默认使用一个低权限账户(如 IIS_IUSRS),这可能导致无法删除某些文件或目录。可以通过以下方式提升权限: 方法 1:修改应用程序池的标识 打开 IIS 管理器。 在左侧导航树中&#x…...
pptx文档提取信息
目录 一、前言二、python-pptx提取核心代码三、LibreOffice 转换pdf再提取的核心代码一、前言 pptx文档提取解析常用的库。 如果只需要解析 .pptx 的文本、表格、图片,推荐使用 python-pptx(开源,轻量级)。 如果需要高性能、支持 .ppt、动画、格式转换,推荐 Aspose.Slid…...
深入理解Python字典(Dictionary):从基础操作到高级应用
深入理解Python字典(Dictionary):从基础操作到高级应用 flyfish 一、Python 字典(Dictionary)基本概念 1. 定义 Python 字典是一种可变、无序的数据结构,用于存储键值对(key - value pairs&…...
@RestController和@RequestBody注解含义
一、RestController (一)含义 RestController 是 Spring Framework 中的一个组合注解,主要用于简化创建 RESTful Web 服务的过程。 它结合了 Controller 和 ResponseBody 注解的功能,使得开发者可以更简洁地编写处理 HTTP 请求的…...
用deepseek学大模型04-模型可视化与数据可视化
deepseek.com: pytorch可视化工具 生成神经网络图 在 PyTorch 中,可视化神经网络结构的常用工具和方法有以下几种,以下将详细介绍它们的用法: 1. TensorBoard (PyTorch 官方集成) PyTorch 通过 torch.utils.tensorboard 支持 TensorBoard&a…...
C++ 设计模式-外观模式
外观模式的定义 外观模式是一种 结构型设计模式,它通过提供一个简化的接口来隐藏系统的复杂性。外观模式的核心思想是: 封装复杂子系统:将多个复杂的子系统或组件封装在一个统一的接口后面。提供简单接口:为客户端提供一个更简单、更易用的接口,而不需要客户端直接与复杂…...
设计模式-结构型-享元模式
1. 享元模式概述 享元模式(Flyweight Pattern)是一种结构型设计模式,主要用于减少对象的数量,以降低内存占用和提高性能。它通过共享相似对象来避免创建大量相同的实例,适用于需要大量创建重复对象的场景,…...
