Przeglądaj źródła

Merge branch 'fix/todo'

Michael Pitidis 13 lat temu
rodzic
commit
e6577a18a8
5 zmienionych plików z 100 dodań i 46 usunięć
  1. BIN
      icons/copy.png
  2. BIN
      icons/edit.png
  3. 59 46
      labelme.py
  4. 2 0
      resources.qrc
  5. 39 0
      simpleLabelDialog.py

BIN
icons/copy.png


BIN
icons/edit.png


+ 59 - 46
labelme.py

@@ -18,6 +18,7 @@ from shape import Shape, DEFAULT_LINE_COLOR, DEFAULT_FILL_COLOR
 from canvas import Canvas
 from zoomWidget import ZoomWidget
 from labelDialog import LabelDialog
+from simpleLabelDialog import SimpleLabelDialog
 from colorDialog import ColorDialog
 from labelFile import LabelFile, LabelFileError
 from toolBar import ToolBar
@@ -29,19 +30,16 @@ __appname__ = 'labelme'
 # - [medium] Set max zoom value to something big enough for FitWidth/Window
 # - [medium] Disabling the save button prevents the user from saving to
 #   alternate files. Either keep enabled, or add "Save As" button.
-# - [low] Label validation/postprocessing breaks with TAB.
 
 # TODO:
 # - [medium] Zoom should keep the image centered.
-# - [high] Context menu over label list.
+# - [high] Label dialog options are vague.
 # - [high] Add recently opened files list in File menu.
 # - [high] Escape should cancel editing mode if no point in canvas.
 # - [medium] Maybe have separate colors for different shapes, and
 #   color the background in the label list accordingly (kostas).
-# - [medium] Highlight label list on shape selection and vice-verca.
 # - [medium] Add undo button for vertex addition.
 # - [medium,maybe] Support vertex moving.
-# - [low,easy] Add button to Hide/Show all labels.
 # - [low,maybe] Open images with drag & drop.
 # - [low,maybe] Preview images on file dialogs.
 # - [low,maybe] Sortable label list.
@@ -88,14 +86,15 @@ class MainWindow(QMainWindow, WindowMixin):
         self.items = {}
         self.highlighted = None
         self.labelList = QListWidget()
-        self.dock = QDockWidget(u'Labels', self)
+        self.dock = QDockWidget(u'Polygon Labels', self)
         self.dock.setObjectName(u'Labels')
         self.dock.setWidget(self.labelList)
         self.zoomWidget = ZoomWidget()
         self.colorDialog = ColorDialog(parent=self)
+        self.simpleLabelDialog = SimpleLabelDialog(parent=self)
 
-        self.labelList.setItemDelegate(LabelDelegate())
         self.labelList.itemActivated.connect(self.highlightLabel)
+        self.labelList.itemDoubleClicked.connect(self.editLabel)
         # Connect to itemChanged to detect checkbox changes.
         self.labelList.itemChanged.connect(self.labelItemChanged)
         self.labelList.itemSelectionChanged.connect(self.labelSelectionChanged)
@@ -122,25 +121,25 @@ class MainWindow(QMainWindow, WindowMixin):
         # Actions
         action = partial(newAction, self)
         quit = action('&Quit', self.close,
-                'Ctrl+Q', 'quit', u'Exit application')
+                'Ctrl+Q', 'quit', u'Quit application')
         open = action('&Open', self.openFile,
-                'Ctrl+O', 'open', u'Open file')
+                'Ctrl+O', 'open', u'Open image or label file')
         save = action('&Save', self.saveFile,
-                'Ctrl+S', 'save', u'Save file')
+                'Ctrl+S', 'save', u'Save labels to file', enabled=False)
         close = action('&Close', self.closeFile,
                 'Ctrl+K', 'close', u'Close current file')
-        color1 = action('&Line Color', self.chooseColor1,
-                'Ctrl+C', 'color', u'Choose line color')
-        color2 = action('&Fill Color', self.chooseColor2,
-                'Ctrl+Shift+C', 'color', u'Choose fill color')
-        label = action('&New Item', self.newLabel,
-                'Ctrl+N', 'new', u'Add new label', enabled=False)
-        copy = action('&Copy', self.copySelectedShape,
-                'Ctrl+C', 'copy', u'Copy', enabled=False)
-        delete = action('&Delete', self.deleteSelectedShape,
+        color1 = action('Polygon &Line Color', self.chooseColor1,
+                'Ctrl+C', 'color', u'Choose polygon line color')
+        color2 = action('Polygon &Fill Color', self.chooseColor2,
+                'Ctrl+Shift+C', 'color', u'Choose polygon fill color')
+        label = action('&New Polygon', self.newLabel,
+                'Ctrl+N', 'new', u'Start a new polygon', enabled=False)
+        copy = action('&Copy Polygon', self.copySelectedShape,
+                'Ctrl+C', 'copy', u'Copy selected polygon', enabled=False)
+        delete = action('&Delete Polygon', self.deleteSelectedShape,
                 ['Ctrl+D', 'Delete'], 'delete', u'Delete', enabled=False)
-        hide = action('&Hide labels', self.hideLabelsToggle,
-                'Ctrl+H', 'hide', u'Hide background labels when drawing',
+        hide = action('&Hide Polygons', self.hideLabelsToggle,
+                'Ctrl+H', 'hide', u'Hide all polygons',
                 checkable=True)
 
         zoom = QWidgetAction(self)
@@ -163,8 +162,13 @@ class MainWindow(QMainWindow, WindowMixin):
             self.FIT_WIDTH: self.scaleFitWidth,
         }
 
+        edit = action('&Edit Label', self.editLabel,
+                'Ctrl+E', 'edit', u'Modify the label of the selected polygon',
+                enabled=False)
+
+
         # Custom context menu for the canvas widget:
-        addActions(self.canvas.menus[0], (label, copy, delete))
+        addActions(self.canvas.menus[0], (label, edit, copy, delete))
         addActions(self.canvas.menus[1], (
             action('&Copy here', self.copyShape),
             action('&Move here', self.moveShape)))
@@ -172,18 +176,26 @@ class MainWindow(QMainWindow, WindowMixin):
         labels = self.dock.toggleViewAction()
         labels.setShortcut('Ctrl+L')
 
+        # Lavel list context menu.
+        labelMenu = QMenu()
+        addActions(labelMenu, (edit, delete))
+        self.labelList.setContextMenuPolicy(Qt.CustomContextMenu)
+        self.labelList.customContextMenuRequested.connect(self.popLabelListMenu)
+        # Add the action to the main window, so that its shortcut is global.
+        self.addAction(edit)
+
         # Store actions for further handling.
         self.actions = struct(save=save, open=open, close=close,
                 lineColor=color1, fillColor=color2,
-                label=label, delete=delete, zoom=zoom, copy=copy,
-                zoomIn=zoomIn, zoomOut=zoomOut, zoomOrg=zoomOrg,
+                label=label, delete=delete, edit=edit, copy=copy,
+                zoom=zoom, zoomIn=zoomIn, zoomOut=zoomOut, zoomOrg=zoomOrg,
                 fitWindow=fitWindow, fitWidth=fitWidth)
-        save.setEnabled(False)
 
         self.menus = struct(
                 file=self.menu('&File'),
-                edit=self.menu('&Image'),
-                view=self.menu('&View'))
+                edit=self.menu('&Polygons'),
+                view=self.menu('&View'),
+                labelList=labelMenu)
         addActions(self.menus.file, (open, save, close, quit))
         addActions(self.menus.edit, (label, color1, color2))
         addActions(self.menus.view, (
@@ -269,8 +281,23 @@ class MainWindow(QMainWindow, WindowMixin):
         self.labelFile = None
         self.canvas.resetState()
 
+    def currentItem(self):
+        items = self.labelList.selectedItems()
+        if items:
+            return items[0]
+        return None
+
     ## Callbacks ##
 
+    def popLabelListMenu(self, point):
+        self.menus.labelList.exec_(self.labelList.mapToGlobal(point))
+
+    def editLabel(self, item=None):
+        item = item if item else self.currentItem()
+        text = self.simpleLabelDialog.popUp(item.text())
+        if text is not None:
+            item.setText(text)
+
     # React to canvas signals.
     def shapeSelectionChanged(self, selected=False):
         if self._noSelectionSlot:
@@ -283,10 +310,11 @@ class MainWindow(QMainWindow, WindowMixin):
                 self.labelList.clearSelection()
         self.actions.delete.setEnabled(selected)
         self.actions.copy.setEnabled(selected)
+        self.actions.edit.setEnabled(selected)
 
     def addLabel(self, shape):
         item = QListWidgetItem(shape.label)
-        item.setFlags(item.flags() | Qt.ItemIsUserCheckable | Qt.ItemIsEditable)
+        item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
         item.setCheckState(Qt.Checked)
         self.labels[item] = shape
         self.items[shape] = item
@@ -400,7 +428,10 @@ class MainWindow(QMainWindow, WindowMixin):
         self.adjustScale()
 
     def hideLabelsToggle(self, value):
-        self.canvas.hideBackroundShapes(value)
+        #self.canvas.hideBackroundShapes(value)
+        for item, shape in self.labels.iteritems():
+            item.setCheckState(Qt.Unchecked if value and not shape.selected\
+                               else Qt.Checked)
 
     def loadFile(self, filename=None):
         """Load the specified file, or the last opened file if None."""
@@ -579,24 +610,6 @@ class MainWindow(QMainWindow, WindowMixin):
         self.setDirty()
 
 
-class LabelDelegate(QItemDelegate):
-    def __init__(self, parent=None):
-        super(LabelDelegate, self).__init__(parent)
-        self.validator = labelValidator()
-
-    # FIXME: Validation and trimming are completely broken if the
-    # user navigates away from the editor with something like TAB.
-    def createEditor(self, parent, option, index):
-        """Make sure the user cannot enter empty labels.
-        Also remove trailing whitespace."""
-        edit = super(LabelDelegate, self).createEditor(parent, option, index)
-        if isinstance(edit, QLineEdit):
-            edit.setValidator(self.validator)
-            def strip():
-                edit.setText(edit.text().trimmed())
-            edit.editingFinished.connect(strip)
-        return edit
-
 class Settings(object):
     """Convenience dict-like wrapper around QSettings."""
     def __init__(self, types=None):

+ 2 - 0
resources.qrc

@@ -7,6 +7,8 @@
 <file alias="fit-window">icons/fit-window.png</file>
 <file alias="hide">icons/eye.png</file>
 <file alias="quit">icons/quit.png</file>
+<file alias="copy">icons/copy.png</file>
+<file alias="edit">icons/edit.png</file>
 <file alias="open">icons/open.png</file>
 <file alias="save">icons/open.png</file>
 <file alias="color">icons/color.png</file>

+ 39 - 0
simpleLabelDialog.py

@@ -0,0 +1,39 @@
+
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+
+from lib import newButton, labelValidator
+
+BB = QDialogButtonBox
+
+class SimpleLabelDialog(QDialog):
+
+    def __init__(self, text='', parent=None):
+        super(SimpleLabelDialog, self).__init__(parent)
+        self.edit = QLineEdit()
+        self.edit.setText(text)
+        self.edit.setValidator(labelValidator())
+        self.edit.editingFinished.connect(self.postProcess)
+        layout = QVBoxLayout()
+        layout.addWidget(self.edit)
+        bb = BB(BB.Ok | BB.Cancel, Qt.Horizontal, self)
+        bb.accepted.connect(self.validate)
+        bb.rejected.connect(self.reject)
+        layout.addWidget(bb)
+        self.setLayout(layout)
+
+    def validate(self):
+        if self.edit.text().trimmed():
+            self.accept()
+
+    def postProcess(self):
+        self.edit.setText(self.edit.text().trimmed())
+
+    def popUp(self, text='', pos=None):
+        self.edit.setText(text)
+        self.edit.setSelection(0, len(text))
+        self.edit.setFocus(Qt.PopupFocusReason)
+        if pos is not None:
+            self.move(pos)
+        return self.edit.text() if self.exec_() else None
+