Browse Source

Merge branch '8-label-dialog' into 'master'

Resolve "label dialog"

Closes #8

See merge request troebs/pycs!7
Eric Tröbs 4 years ago
parent
commit
b4b0cb0ccc

+ 3 - 5
pycs/ui/AnnotatedImageView.py

@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
 """AnnotatedImageView:  Displays images with bounding boxes etc."""
 """AnnotatedImageView:  Displays images with bounding boxes etc."""
 
 
 from PyQt5 import QtWidgets, QtGui, QtCore
 from PyQt5 import QtWidgets, QtGui, QtCore
@@ -21,7 +19,7 @@ class AnnotatedImageView(QtWidgets.QGraphicsView):
         self._image = None
         self._image = None
         self._current_video = None
         self._current_video = None
 
 
-    def display(self, prediction):
+    def display(self, prediction, mode='prediction'):
         self._scene.clear()
         self._scene.clear()
         if prediction['filetype'] == 'image':
         if prediction['filetype'] == 'image':
             self._image = QtGui.QImage(prediction['filename'])
             self._image = QtGui.QImage(prediction['filename'])
@@ -30,8 +28,8 @@ class AnnotatedImageView(QtWidgets.QGraphicsView):
             self._scene.setSceneRect(item.boundingRect())
             self._scene.setSceneRect(item.boundingRect())
             self._rect = item.boundingRect()
             self._rect = item.boundingRect()
 
 
-            if 'prediction' in prediction.keys():
-                self._overlay(prediction['prediction'])
+            if mode in prediction.keys():
+                self._overlay(prediction[mode])
 
 
         elif prediction['filetype'] == 'video':
         elif prediction['filetype'] == 'video':
             if 'cap' in prediction.keys():
             if 'cap' in prediction.keys():

+ 52 - 0
pycs/ui/AnnotatedInteractiveImageView.py

@@ -0,0 +1,52 @@
+from pycs.ui.AnnotatedImageView import AnnotatedImageView
+
+
+class AnnotatedInteractiveImageView(AnnotatedImageView):
+    def __init__(self, parent, click_handler=None):
+        super().__init__(parent)
+        self.__click_handler = click_handler
+
+    def mousePressEvent(self, event):
+        """
+        handle click event, get point and emit to parent node
+        :param event:
+        :return:
+        """
+
+        # do not execute if no click handler is given
+        if self.__click_handler is None:
+            return
+
+        # get clicked point
+        pos = event.pos()
+        point_x, point_y = pos.x() / self.width(), pos.y() / self.height()
+
+        # get image and box dimensions
+        image_width, image_height = self._image.width(), self._image.height()
+        box_width, box_height = self.width(), self.height()
+
+        maximum_width, maximum_height = max(image_width, box_width), max(image_height, box_height)
+
+        # determine orientation
+        # horizontal
+        if image_width / image_height >= box_width / box_height:
+            # calculate top margin
+            image_height, box_height = maximum_width / image_width * image_height, maximum_width / box_width * box_height
+            top = (box_height - image_height) / 2
+
+            # click is inside the image
+            if top <= point_y * box_height <= box_height - top:
+                off = top / box_height
+                rat = 1 - 2 * off
+                self.__click_handler(point_x, (point_y - off) / rat, event)
+
+        # vertical
+        else:
+            # calculate left margin
+            image_width, box_width = maximum_height / image_height * image_width, maximum_height / box_height * box_width
+            left = (box_width - image_width) / 2
+
+            if left <= point_x * box_width <= box_width - left:
+                off = left / box_width
+                rat = 1 - 2 * off
+                self.__click_handler((point_x - off) / rat, point_y, event)

+ 97 - 0
pycs/ui/LabelDialog.py

@@ -0,0 +1,97 @@
+import os
+
+from PyQt5 import uic, QtWidgets, QtCore
+
+from pycs.ui.AnnotatedInteractiveImageView import AnnotatedInteractiveImageView
+
+
+class LabelDialog(QtWidgets.QDialog):
+    def __init__(self, predictions, **kwargs):
+        # call parent constructor
+        super(LabelDialog, self).__init__(**kwargs)
+
+        # handle properties
+        self.__predictions = predictions
+        self.__current = -1
+
+        # load ui
+        spath = os.path.dirname(__file__)
+        uic.loadUi(os.path.join(spath, 'LabelDialog.ui'), self)
+
+        self.button_next.clicked.connect(self._next)
+        self.button_previous.clicked.connect(self._prev)
+
+        # add annotated image view
+        self.__image = AnnotatedInteractiveImageView(self, self.click)
+        self.gridLayout.addWidget(self.__image)
+
+        # call next image function to display first item
+        self._next()
+
+    def _next(self):
+        """
+        displays next item
+        """
+        if len(self.__predictions) > 0:
+            self.__current += 1
+            if self.__current >= len(self.__predictions):
+                self.__current = 0
+
+            self.__image.display(self.__predictions[self.__current])
+
+    def _prev(self):
+        """
+        displays previous item
+        """
+        if len(self.__predictions) > 0:
+            self.__current -= 1
+            if self.__current <= 0:
+                self.__current = len(self.__predictions) - 1
+
+            self.__image.display(self.__predictions[self.__current])
+
+    def click(self, x, y, event):
+        """
+        handles clicks on images
+        :param x: x position in [0, 1]
+        :param y: y position in [0, 1]
+        :param event: actual QMouseEvent object
+        """
+
+        # return if there is no prediction
+        if len(self.__predictions) == 0:
+            return
+
+        # add label object to current prediction
+        if 'label' not in self.__predictions[self.__current].keys():
+            self.__predictions[self.__current]['label'] = {
+                'faces': [{
+                    'x1': None,
+                    'y1': None,
+                    'x2': None,
+                    'y2': None
+                }]
+            }
+
+        face = self.__predictions[self.__current]['label']['faces'][0]
+
+        # set data depending on click
+        if event.button() == QtCore.Qt.LeftButton:
+            face['x1'] = x
+            face['y1'] = y
+        elif event.button() == QtCore.Qt.RightButton:
+            face['x2'] = x
+            face['y2'] = y
+
+        # return if not at least two points selected
+        if face['x1'] is None or face['y1'] is None or face['x2'] is None or face['y2'] is None:
+            return
+
+        # detect upper left point and calculate width and height
+        face['x'] = min(face['x1'], face['x2'])
+        face['y'] = min(face['y1'], face['y2'])
+        face['w'] = max(face['x1'], face['x2']) - face['x']
+        face['h'] = max(face['y1'], face['y2']) - face['y']
+
+        # display rectangle
+        self.__image.display(self.__predictions[self.__current], mode='label')

+ 56 - 0
pycs/ui/LabelDialog.ui

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+    <class>AboutDialog</class>
+    <widget class="QDialog" name="AboutDialog">
+        <property name="geometry">
+            <rect>
+                <x>0</x>
+                <y>0</y>
+                <width>582</width>
+                <height>703</height>
+            </rect>
+        </property>
+        <property name="windowTitle">
+            <string>Labeling Session</string>
+        </property>
+        <layout class="QVBoxLayout" name="verticalLayout" stretch="1,0">
+            <item>
+                <layout class="QGridLayout" name="gridLayout"/>
+            </item>
+            <item>
+                <layout class="QHBoxLayout" name="horizontalLayout">
+                    <item>
+                        <widget class="QPushButton" name="button_previous">
+                            <property name="text">
+                                <string>Voriges</string>
+                            </property>
+                        </widget>
+                    </item>
+                    <item>
+                        <widget class="QPushButton" name="button_ok">
+                            <property name="text">
+                                <string>OK</string>
+                            </property>
+                        </widget>
+                    </item>
+                    <item>
+                        <widget class="QPushButton" name="button_cancel">
+                            <property name="text">
+                                <string>Abbrechen</string>
+                            </property>
+                        </widget>
+                    </item>
+                    <item>
+                        <widget class="QPushButton" name="button_next">
+                            <property name="text">
+                                <string>Nächstes</string>
+                            </property>
+                        </widget>
+                    </item>
+                </layout>
+            </item>
+        </layout>
+    </widget>
+    <resources/>
+    <connections/>
+</ui>

+ 10 - 2
pycs/ui/MainWindow.py

@@ -1,10 +1,10 @@
-import copy
 import json
 import json
 import os
 import os
 
 
 from PyQt5 import uic, QtWidgets, QtCore, QtGui
 from PyQt5 import uic, QtWidgets, QtCore, QtGui
 
 
 from . import AboutDialog
 from . import AboutDialog
+from .LabelDialog import LabelDialog
 from .NewProjectWizard import NewProjectWizard
 from .NewProjectWizard import NewProjectWizard
 from ..project import Project
 from ..project import Project
 from ..utils import Video
 from ..utils import Video
@@ -72,6 +72,10 @@ class MainWindow:
         self.ui.actionSave.triggered.connect(self._project_save)
         self.ui.actionSave.triggered.connect(self._project_save)
         self.ui.actionQuit.triggered.connect(self._file_quit)
         self.ui.actionQuit.triggered.connect(self._file_quit)
 
 
+        # Labeling
+        self.ui.actionStart_Labeling_Session.triggered.connect(self._start_labeling_session)
+        self.ui.startLabelingSessionButton.clicked.connect(self._start_labeling_session)
+
         # Help
         # Help
         self.ui.actionAbout.triggered.connect(self._help_about)
         self.ui.actionAbout.triggered.connect(self._help_about)
 
 
@@ -204,6 +208,10 @@ class MainWindow:
         # TODO: warning if there are any unsaved changes
         # TODO: warning if there are any unsaved changes
         self.close()
         self.close()
 
 
+    def _start_labeling_session(self):
+        labeling = LabelDialog(self._predictions)
+        labeling.exec_()
+
     def _help_about(self):
     def _help_about(self):
         about = AboutDialog()
         about = AboutDialog()
         about.exec_()
         about.exec_()
@@ -254,7 +262,7 @@ class MainWindow:
 
 
         '''
         '''
         TODO: remove PredictionDialog.py
         TODO: remove PredictionDialog.py
-        
+
         prediction_dialog = PredictionDialog(parent=self.ui)
         prediction_dialog = PredictionDialog(parent=self.ui)
         if prediction_dialog.exec_():
         if prediction_dialog.exec_():