Переглянути джерело

Add detailed zoom actions

The user can now zoom to fit window or width and also original size. The
logic is slightly different now as well, the user can just change the
zoom level to go out of a mode such as fit window.

Add more icons and change the one for drawing to something more
relevant.
Michael Pitidis 13 роки тому
батько
коміт
2abdd7c2bd
10 змінених файлів з 84 додано та 30 видалено
  1. BIN
      icons/eye.png
  2. BIN
      icons/fit-width.png
  3. BIN
      icons/fit-window.png
  4. BIN
      icons/objects.png
  5. BIN
      icons/zoom-in.png
  6. BIN
      icons/zoom-out.png
  7. BIN
      icons/zoom.png
  8. 69 26
      labelme.py
  9. 6 2
      resources.qrc
  10. 9 2
      zoomWidget.py

BIN
icons/fit-width.png


BIN
icons/fit-window.png


BIN
icons/objects.png


BIN
icons/zoom-in.png


BIN
icons/zoom-out.png


BIN
icons/zoom.png


+ 69 - 26
labelme.py

@@ -26,6 +26,7 @@ __appname__ = 'labelme'
 
 
 # FIXME
 # FIXME
 # - [low] Label validation/postprocessing breaks with TAB.
 # - [low] Label validation/postprocessing breaks with TAB.
+# - Set max zoom value to something big enough for FitWidth/Window
 
 
 # TODO:
 # TODO:
 # - [easy] Add button to Hide/Show all labels.
 # - [easy] Add button to Hide/Show all labels.
@@ -54,6 +55,8 @@ class WindowMixin(object):
 
 
 
 
 class MainWindow(QMainWindow, WindowMixin):
 class MainWindow(QMainWindow, WindowMixin):
+    FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = range(3)
+
     def __init__(self, filename=None):
     def __init__(self, filename=None):
         super(MainWindow, self).__init__()
         super(MainWindow, self).__init__()
         self.setWindowTitle(__appname__)
         self.setWindowTitle(__appname__)
@@ -70,7 +73,7 @@ class MainWindow(QMainWindow, WindowMixin):
         self.dock = QDockWidget(u'Labels', self)
         self.dock = QDockWidget(u'Labels', self)
         self.dock.setObjectName(u'Labels')
         self.dock.setObjectName(u'Labels')
         self.dock.setWidget(self.labelList)
         self.dock.setWidget(self.labelList)
-        self.zoom_widget = ZoomWidget()
+        self.zoomWidget = ZoomWidget()
 
 
         self.labelList.setItemDelegate(LabelDelegate())
         self.labelList.setItemDelegate(LabelDelegate())
         self.labelList.itemActivated.connect(self.highlightLabel)
         self.labelList.itemActivated.connect(self.highlightLabel)
@@ -115,6 +118,26 @@ class MainWindow(QMainWindow, WindowMixin):
                 'Ctrl+H', 'hide', u'Hide background labels when drawing',
                 'Ctrl+H', 'hide', u'Hide background labels when drawing',
                 checkable=True)
                 checkable=True)
 
 
+        zoom = QWidgetAction(self)
+        zoom.setDefaultWidget(self.zoomWidget)
+        zoomIn = action('Zoom &In', partial(self.addZoom, 10),
+                'Ctrl++', 'zoom-in', u'Increase zoom level')
+        zoomOut = action('&Zoom Out', partial(self.addZoom, -10),
+                'Ctrl+-', 'zoom-out', u'Decrease zoom level')
+        zoomOrg = action('&Original size', partial(self.setZoom, 100),
+                'Ctrl+=', 'zoom', u'Zoom to original size')
+        fitWindow = action('&Fit Window', self.setFitWindow,
+                'Ctrl+F', 'fit-window', u'Zoom follows window size',
+                checkable=True)
+        fitWidth = action('Fit &Width', self.setFitWidth,
+                'Ctrl+W', 'fit-width', u'Zoom follows window width',
+                checkable=True)
+        self.zoomMode = self.MANUAL_ZOOM
+        self.scalers = {
+            self.FIT_WINDOW: self.scaleFitWindow,
+            self.FIT_WIDTH: self.scaleFitWidth,
+        }
+
         # Custom context menu for the canvas widget:
         # Custom context menu for the canvas widget:
         addActions(self.canvas.menus[0], (label, copy, delete))
         addActions(self.canvas.menus[0], (label, copy, delete))
         addActions(self.canvas.menus[1], (
         addActions(self.canvas.menus[1], (
@@ -124,30 +147,29 @@ class MainWindow(QMainWindow, WindowMixin):
         labels = self.dock.toggleViewAction()
         labels = self.dock.toggleViewAction()
         labels.setShortcut('Ctrl+L')
         labels.setShortcut('Ctrl+L')
 
 
-        zoom = QWidgetAction(self)
-        zoom.setDefaultWidget(self.zoom_widget)
-
         # Store actions for further handling.
         # Store actions for further handling.
         self.actions = struct(save=save, open=open, color=color,
         self.actions = struct(save=save, open=open, color=color,
-                label=label, delete=delete, zoom=zoom, copy=copy)
+                label=label, delete=delete, zoom=zoom, copy=copy,
+                zoomIn=zoomIn, zoomOut=zoomOut, zoomOrg=zoomOrg,
+                fitWindow=fitWindow, fitWidth=fitWidth)
         save.setEnabled(False)
         save.setEnabled(False)
 
 
-        fit_window = action('&Fit Window', self.setFitWindow,
-                'Ctrl+F', 'fit',  u'Fit image to window', checkable=True)
-
         self.menus = struct(
         self.menus = struct(
                 file=self.menu('&File'),
                 file=self.menu('&File'),
                 edit=self.menu('&Image'),
                 edit=self.menu('&Image'),
                 view=self.menu('&View'))
                 view=self.menu('&View'))
         addActions(self.menus.file, (open, save, quit))
         addActions(self.menus.file, (open, save, quit))
-        addActions(self.menus.edit, (label, color, fit_window))
-        addActions(self.menus.view, (labels,))
+        addActions(self.menus.edit, (label, color, fitWindow, fitWidth))
+        addActions(self.menus.view, (
+            labels, None,
+            zoomIn, zoomOut, zoomOrg, None,
+            fitWindow, fitWidth))
 
 
         self.tools = self.toolbar('Tools')
         self.tools = self.toolbar('Tools')
         addActions(self.tools, (
         addActions(self.tools, (
             open, save, None,
             open, save, None,
             label, delete, color, hide, None,
             label, delete, color, hide, None,
-            zoom, fit_window, None,
+            zoomIn, zoom, zoomOut, fitWindow, fitWidth, None,
             quit))
             quit))
 
 
         self.statusBar().showMessage('%s started.' % __appname__)
         self.statusBar().showMessage('%s started.' % __appname__)
@@ -189,7 +211,7 @@ class MainWindow(QMainWindow, WindowMixin):
         self.queueEvent(partial(self.loadFile, self.filename))
         self.queueEvent(partial(self.loadFile, self.filename))
 
 
         # Callbacks:
         # Callbacks:
-        self.zoom_widget.editingFinished.connect(self.paintCanvas)
+        self.zoomWidget.valueChanged.connect(self.paintCanvas)
 
 
     def shapeSelectionChanged(self, selected=False):
     def shapeSelectionChanged(self, selected=False):
         self.actions.delete.setEnabled(selected)
         self.actions.delete.setEnabled(selected)
@@ -273,17 +295,31 @@ class MainWindow(QMainWindow, WindowMixin):
         bar = self.scrollBars[orientation]
         bar = self.scrollBars[orientation]
         bar.setValue(bar.value() + bar.singleStep() * units)
         bar.setValue(bar.value() + bar.singleStep() * units)
 
 
+    def setZoom(self, value):
+        self.actions.fitWidth.setChecked(False)
+        self.actions.fitWindow.setChecked(False)
+        self.zoomMode = self.MANUAL_ZOOM
+        self.zoomWidget.setValue(value)
+
+    def addZoom(self, increment=10):
+        self.setZoom(self.zoomWidget.value() + increment)
+
     def zoomRequest(self, delta):
     def zoomRequest(self, delta):
-        if not self.fit_window:
-            units = delta / (8 * 15)
-            scale = 10
-            self.zoom_widget.setValue(self.zoom_widget.value() + scale * units)
-            self.zoom_widget.editingFinished.emit()
+        units = delta / (8 * 15)
+        scale = 10
+        self.addZoom(scale * units)
 
 
     def setFitWindow(self, value=True):
     def setFitWindow(self, value=True):
-        self.zoom_widget.setEnabled(not value)
-        self.fit_window = value
-        self.paintCanvas()
+        if value:
+            self.actions.fitWidth.setChecked(False)
+        self.zoomMode = self.FIT_WINDOW if value else self.MANUAL_ZOOM
+        self.adjustScale()
+
+    def setFitWidth(self, value=True):
+        if value:
+            self.actions.fitWindow.setChecked(False)
+        self.zoomMode = self.FIT_WIDTH if value else self.MANUAL_ZOOM
+        self.adjustScale()
 
 
     def queueEvent(self, function):
     def queueEvent(self, function):
         QTimer.singleShot(0, function)
         QTimer.singleShot(0, function)
@@ -323,18 +359,21 @@ class MainWindow(QMainWindow, WindowMixin):
             self.statusBar().showMessage(message)
             self.statusBar().showMessage(message)
 
 
     def resizeEvent(self, event):
     def resizeEvent(self, event):
-        if self.fit_window and self.canvas and not self.image.isNull():
-            self.paintCanvas()
+        if self.canvas and not self.image.isNull()\
+           and self.zoomMode != self.MANUAL_ZOOM:
+            self.adjustScale()
         super(MainWindow, self).resizeEvent(event)
         super(MainWindow, self).resizeEvent(event)
 
 
     def paintCanvas(self):
     def paintCanvas(self):
         assert not self.image.isNull(), "cannot paint null image"
         assert not self.image.isNull(), "cannot paint null image"
-        self.canvas.scale = self.fitSize() if self.fit_window\
-                            else 0.01 * self.zoom_widget.value()
+        self.canvas.scale = 0.01 * self.zoomWidget.value()
         self.canvas.adjustSize()
         self.canvas.adjustSize()
         self.canvas.repaint()
         self.canvas.repaint()
 
 
-    def fitSize(self):
+    def adjustScale(self):
+        self.zoomWidget.setValue(int(100 * self.scalers[self.zoomMode]()))
+
+    def scaleFitWindow(self):
         """Figure out the size of the pixmap in order to fit the main widget."""
         """Figure out the size of the pixmap in order to fit the main widget."""
         e = 2.0 # So that no scrollbars are generated.
         e = 2.0 # So that no scrollbars are generated.
         w1 = self.centralWidget().width() - e
         w1 = self.centralWidget().width() - e
@@ -346,6 +385,10 @@ class MainWindow(QMainWindow, WindowMixin):
         a2 = w2 / h2
         a2 = w2 / h2
         return w1 / w2 if a2 >= a1 else h1 / h2
         return w1 / w2 if a2 >= a1 else h1 / h2
 
 
+    def scaleFitWidth(self):
+        # The epsilon does not seem to work too well here.
+        w = self.centralWidget().width() - 2.0
+        return w / self.canvas.pixmap.width()
 
 
     def closeEvent(self, event):
     def closeEvent(self, event):
         # TODO: Make sure changes are saved.
         # TODO: Make sure changes are saved.
@@ -356,8 +399,8 @@ class MainWindow(QMainWindow, WindowMixin):
         s['window/state'] = self.saveState()
         s['window/state'] = self.saveState()
         s['line/color'] = self.color
         s['line/color'] = self.color
         # ask the use for where to save the labels
         # ask the use for where to save the labels
-
         #s['window/geometry'] = self.saveGeometry()
         #s['window/geometry'] = self.saveGeometry()
+
     def updateFileMenu(self):
     def updateFileMenu(self):
         """Populate menu with recent files."""
         """Populate menu with recent files."""
 
 

+ 6 - 2
resources.qrc

@@ -1,13 +1,17 @@
 <!DOCTYPE RCC><RCC version="1.0">
 <!DOCTYPE RCC><RCC version="1.0">
 <qresource>
 <qresource>
 
 
-<file alias="new">icons/new.png</file>
-<file alias="fit">icons/fit.png</file>
+<file alias="new">icons/objects.png</file>
+<file alias="fit-width">icons/fit-width.png</file>
+<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="quit">icons/quit.png</file>
 <file alias="open">icons/open.png</file>
 <file alias="open">icons/open.png</file>
 <file alias="save">icons/open.png</file>
 <file alias="save">icons/open.png</file>
 <file alias="color">icons/color.png</file>
 <file alias="color">icons/color.png</file>
 <file alias="zoom">icons/zoom.png</file>
 <file alias="zoom">icons/zoom.png</file>
+<file alias="zoom-in">icons/zoom-in.png</file>
+<file alias="zoom-out">icons/zoom-out.png</file>
 <file alias="delete">icons/delete.png</file>
 <file alias="delete">icons/delete.png</file>
 
 
 </qresource>
 </qresource>

+ 9 - 2
zoomWidget.py

@@ -5,10 +5,17 @@ from PyQt4.QtCore import *
 class ZoomWidget(QSpinBox):
 class ZoomWidget(QSpinBox):
     def __init__(self, value=100):
     def __init__(self, value=100):
         super(ZoomWidget, self).__init__()
         super(ZoomWidget, self).__init__()
-        self.setButtonSymbols(QAbstractSpinBox.PlusMinus)
+        self.setButtonSymbols(QAbstractSpinBox.NoButtons)
         self.setRange(1, 500)
         self.setRange(1, 500)
         self.setSuffix(' %')
         self.setSuffix(' %')
         self.setValue(value)
         self.setValue(value)
-        self.setToolTip(u'Image zoom')
+        self.setToolTip(u'Zoom Level')
         self.setStatusTip(self.toolTip())
         self.setStatusTip(self.toolTip())
+        self.setAlignment(Qt.AlignCenter)
+
+    def minimumSizeHint(self):
+        height = super(ZoomWidget, self).minimumSizeHint().height()
+        fm = QFontMetrics(self.font())
+        width = fm.width(str(self.maximum()))
+        return QSize(width, height)