Add new PropertyEditor to replace ButtonArea

Basic functionality working, some things are left to do.

This also fixes the Layout issues with the ButtonArea
This commit is contained in:
Mika Fischer
2011-06-28 10:48:38 +02:00
parent 148865a5dc
commit 20ad715411
7 changed files with 163 additions and 150 deletions
+4 -2
View File
@@ -7,14 +7,16 @@
"width": 46.0,
"y": 105.0,
"x": 346.0,
"type": "rect"
"type": "rect",
"class": "Face"
},
{
"height": 58.0,
"width": 56.0,
"y": 119.0,
"x": 636.0,
"type": "rect"
"type": "rect",
"class": "Face"
}
],
"filename": "image1.jpg"
+4 -6
View File
@@ -10,7 +10,7 @@ import time
import logging
LOG = logging.getLogger(__name__)
ItemRole, TypeRole, DataRole, ImageRole = [Qt.UserRole + i + 1 for i in range(4)]
ItemRole, DataRole, ImageRole = [Qt.UserRole + i + 1 for i in range(3)]
class ModelItem:
def __init__(self):
@@ -44,21 +44,21 @@ class ModelItem:
def setData(self, value, role=Qt.DisplayRole, column=0):
return False
def getChildAt(self, pos):
def childAt(self, pos):
return self._children[pos]
def getPreviousSibling(self):
p = self.parent()
if p is not None:
if self._row > 0:
return p.getChildAt(self._row-1)
return p.childAt(self._row-1)
return None
def getNextSibling(self):
p = self.parent()
if p is not None:
if self._row < len(p.children()) - 1:
return p.getChildAt(self._row+1)
return p.childAt(self._row+1)
return None
def _attachToModel(self, model):
@@ -348,8 +348,6 @@ class AnnotationModelItem(KeyValueModelItem):
return self['type']
else:
return ""
elif role == TypeRole:
return self['type']
elif role == DataRole:
return self._annotation
return ModelItem.data(self, role, column)
+12 -3
View File
@@ -46,7 +46,7 @@ class LabelTool(QObject):
# This still emits a QModelIndex, because Qt cannot handle emiting
# a derived class instead of a base class, i.e. ImageFileModelItem
# instead of ModelItem
currentImageChanged = pyqtSignal(QModelIndex)
currentImageChanged = pyqtSignal()
# TODO clean up --> prefix all members with _
def __init__(self, parent=None):
@@ -299,7 +299,7 @@ class LabelTool(QObject):
raise RuntimeError("Tried to set current image to item that has no Image or Frame as parent!")
if image != self._current_image:
self._current_image = image
self.currentImageChanged.emit(self._current_image.index())
self.currentImageChanged.emit()
def getImage(self, item):
# TODO: Also handle video frames
@@ -339,6 +339,15 @@ class LabelTool(QObject):
self._model._root.appendFileItem(fileitem)
###
### PropertyEditor functions
###___________________________________________________________________________________________
def propertyeditor(self):
if self._mainwindow is None:
return None
else:
return self._mainwindow.property_editor
###
### Scene functions
###___________________________________________________________________________________________
@@ -363,7 +372,7 @@ class LabelTool(QObject):
def exitInsertMode(self):
if self._mainwindow is not None:
return self._mainwindow.buttonarea.exitInsertMode()
return self._mainwindow.property_editor.endInsertionMode()
###
### TreeView functions
+65 -72
View File
@@ -2,42 +2,30 @@
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from sloth.items import *
from sloth.annotations.model import TypeRole
from sloth.core.exceptions import InvalidArgumentException
import okapy
import logging
LOG = logging.getLogger(__name__)
class AnnotationScene(QGraphicsScene):
"""Dies ist ein Test"""
# TODO signal itemadded
def __init__(self, labeltool, items=None, inserters=None, parent=None):
super(AnnotationScene, self).__init__(parent)
self.model_ = None
self.mode_ = None
self.inserter_ = None
self.debug_ = True
self.message_ = ""
self.last_key_ = None
self.labeltool_ = labeltool
self.model_ = None
self.image_item_ = None
self.inserter_ = None
self.message_ = ""
self.labeltool_ = labeltool
self.itemfactory_ = Factory(items)
self.inserterfactory_ = Factory(inserters)
self.setBackgroundBrush(Qt.darkGray)
self.setMode(None)
self.reset()
#
# getters/setters
#______________________________________________________________________________________________________
def model(self):
return self.model_
def setModel(self, model):
if model == self.model_:
# same model as the current one
@@ -66,79 +54,78 @@ class AnnotationScene(QGraphicsScene):
# reset caches, invalidate root
self.reset()
def root(self):
return self.root_
def setRoot(self, root):
def setCurrentImage(self, current_image):
"""
Set the index of the model which denotes the current image to be
displayed by the scene. This can be either the index to a frame in a
video, or to an image.
"""
self.image_item_ = None
self.image_ = None
self.pixmap_ = None
self.root_ = root
self.clear()
if not root.isValid():
if current_image == self.image_item_:
return
elif current_image is None:
self.clear()
self.image_item_ = None
self.image_ = None
self.pixmap_ = None
else:
self.clear()
self.image_item_ = current_image
assert self.image_item_.model() == self.model_
self.image_ = self.labeltool_.getImage(self.image_item_)
self.pixmap_ = QPixmap(okapy.guiqt.toQImage(self.image_))
item = QGraphicsPixmapItem(self.pixmap_)
item.setZValue(-1)
self.setSceneRect(0, 0, self.pixmap_.width(), self.pixmap_.height())
self.addItem(item)
assert self.root_.model() == self.model_
self.image_item_ = self.model_.itemFromIndex(root)
self.image_ = self.labeltool_.getImage(self.image_item_)
self.pixmap_ = QPixmap(okapy.guiqt.toQImage(self.image_))
item = QGraphicsPixmapItem(self.pixmap_)
item.setZValue(-1)
self.setSceneRect(0, 0, self.pixmap_.width(), self.pixmap_.height())
self.addItem(item)
num_items = self.model_.rowCount(self.root_)
self.insertItems(0, num_items)
self.update()
self.insertItems(0, len(self.image_item_.children())-1)
self.update()
def insertItems(self, first, last):
if not self.root_.isValid():
if self.image_item_ is None:
return
assert self.model_ is not None
# create a graphics item for each model index
for row in range(first, last+1):
child = self.root_.child(row, 0) # get index
t = child.data(TypeRole) # get type from index
if isinstance(t, QVariant):
t = t.toPyObject()
_type = str(t)
item = self.itemfactory_.create(_type, self.model_.itemFromIndex(child)) # create graphics item from factory
child = self.image_item_.childAt(row)
label_class = child['class']
item = self.itemfactory_.create(label_class, child)
if item is not None:
self.addItem(item)
else:
LOG.warn("Could not find item for annotation with class '%s'" % label_class)
def onInserterFinished(self):
self.sender().inserterFinished.disconnect(self.onInserterFinished)
self.inserter_ = None
def setMode(self, mode):
LOG.debug("setMode : %s" % mode)
def onInsertionModeStarted(self, label_class):
# Abort current inserter
if self.inserter_ is not None:
self.inserter_.abort()
self.deselectAllItems()
# Add new inserter
if mode is not None:
inserter = self.inserterfactory_.create(mode['type'], self.labeltool_, self, mode)
if inserter is None:
raise InvalidArgumentException("Invalid mode")
inserter.inserterFinished.connect(self.onInserterFinished)
self.inserter_ = inserter
default_properties = self.labeltool_.propertyeditor().currentEditorProperties()
inserter = self.inserterfactory_.create(label_class, self.labeltool_, self, default_properties)
if inserter is None:
raise InvalidArgumentException("Invalid mode")
inserter.inserterFinished.connect(self.onInserterFinished)
self.inserter_ = inserter
def onInsertionModeEnded(self):
if self.inserter_ is not None:
self.inserter_.abort()
#
# common methods
#______________________________________________________________________________________________________
def reset(self):
self.clear()
self.setRoot(QModelIndex())
self.setCurrentImage(None)
self.clearMessage()
def addItem(self, item):
@@ -149,8 +136,7 @@ class AnnotationScene(QGraphicsScene):
# mouse event handlers
#______________________________________________________________________________________________________
def mousePressEvent(self, event):
if self.debug_:
LOG.debug("mousePressEvent %s %s" % (self.sceneRect().contains(event.scenePos()), event.scenePos()))
LOG.debug("mousePressEvent %s %s" % (self.sceneRect().contains(event.scenePos()), event.scenePos()))
if self.inserter_ is not None:
if not self.sceneRect().contains(event.scenePos()) and \
not self.inserter_.allowOutOfSceneEvents():
@@ -163,8 +149,7 @@ class AnnotationScene(QGraphicsScene):
QGraphicsScene.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
if self.debug_:
LOG.debug("mouseReleaseEvent %s %s" % (self.sceneRect().contains(event.scenePos()), event.scenePos()))
LOG.debug("mouseReleaseEvent %s %s" % (self.sceneRect().contains(event.scenePos()), event.scenePos()))
if self.inserter_ is not None:
# insert mode
self.inserter_.mouseReleaseEvent(event, self.image_item_)
@@ -173,8 +158,7 @@ class AnnotationScene(QGraphicsScene):
QGraphicsScene.mouseReleaseEvent(self, event)
def mouseMoveEvent(self, event):
#if self.debug_:
# print "mouseMoveEvent", self.sceneRect().contains(event.scenePos()), event.scenePos()
# print "mouseMoveEvent", self.sceneRect().contains(event.scenePos()), event.scenePos()
if self.inserter_ is not None:
# insert mode
self.inserter_.mouseMoveEvent(event, self.image_item_)
@@ -182,10 +166,14 @@ class AnnotationScene(QGraphicsScene):
# selection mode
QGraphicsScene.mouseMoveEvent(self, event)
def deselectAllItems(self):
for item in self.items():
item.setSelected(False)
def onSelectionChanged(self):
model_items = [item.modelItem() for item in self.selectedItems()]
self.labeltool_.treeview().setSelectedItems(model_items)
self.editSelectedItems()
def onSelectionChangedInTreeView(self, items):
block = self.blockSignals(True)
@@ -196,6 +184,13 @@ class AnnotationScene(QGraphicsScene):
if item is not None:
item.setSelected(True)
self.blockSignals(block)
self.editSelectedItems()
def editSelectedItems(self):
scene_items = self.selectedItems()
if self.inserter_ is None or len(scene_items) > 0:
items = [item.modelItem() for item in scene_items]
self.labeltool_.propertyeditor().startEditMode(items)
#
# key event handlers
@@ -232,10 +227,9 @@ class AnnotationScene(QGraphicsScene):
break
def keyPressEvent(self, event):
if self.debug_:
LOG.debug("keyPressEvent %s" % event)
LOG.debug("keyPressEvent %s" % event)
if self.model_ is None or not self.root_.isValid():
if self.model_ is None or self.image_item_ is None:
event.ignore()
return
@@ -266,12 +260,12 @@ class AnnotationScene(QGraphicsScene):
# this is the implemenation of the scene as a view of the model
#______________________________________________________________________________________________________
def dataChanged(self, indexFrom, indexTo):
if not self.root_.isValid():
if self.image_item_ is None:
return
annotation_item_index= indexFrom.parent()
annotation_item_index = indexFrom.parent()
if self.root_ != annotation_item_index.parent():
if self.image_item_.index() != annotation_item_index.parent():
return
item = self.itemFromIndex(annotation_item_index)
@@ -279,14 +273,13 @@ class AnnotationScene(QGraphicsScene):
item.dataChanged()
def rowsInserted(self, index, first, last):
if self.root_ != index:
if self.image_item_.index() != index:
return
self.insertItems(first, last)
def rowsAboutToBeRemoved(self, index, first, last):
if self.root_ != index:
if self.image_item_.index() != index:
return
for row in range(first, last+1):
+59 -52
View File
@@ -1,72 +1,35 @@
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4.QtCore import Qt, QRect, QSize, QPoint
from PyQt4.QtGui import QLayout, QSizePolicy
class FloatingLayout(QLayout):
def __init__(self, parent=None):
QLayout.__init__(self, parent)
self._items = []
self._last_min_size = self.minimumSize()
self._updateMinimumSize()
def addItem(self, item):
self._items.append(item)
def count(self):
return len(self._items)
def itemAt(self, index):
if index < 0 or index >= len(self._items):
return None
return self._items[index]
def takeAt(self, index):
if index < 0 or index >= len(self._items):
return None
else:
item = self._items[index]
del self._items[index]
return item
def sizeHint(self):
return self.minimumSize()
def setGeometry(self, r):
QLayout.setGeometry(self, r)
self.layoutChildren(r)
min_size = self.minimumSize()
if self._last_min_size != min_size:
self._last_min_size = min_size
self.parentWidget().updateGeometry()
def minimumSize(self):
w = 0
h = 0
def _updateMinimumSize(self, height=None):
w, h = 0, 0
for item in self._items:
w = max(w, item.minimumSize().width())
h = max(h, item.minimumSize().height())
left, top, right, bottom = self.getContentsMargins()
current_width = self.contentsRect().width() - left - right
if current_width > 0:
h = self.heightForWidth(current_width)
w += left + right
h += top + bottom
return QSize(w, h)
if height is None:
current_width = self.contentsRect().width()
if current_width > 0:
height = self.heightForWidth(current_width + left + right)
if height is not None:
h = max(h, height)
def hasHeightForWidth(self):
return True
self._min_w, self._min_h = w, h
def heightForWidth(self, width):
height = self.layoutChildren(QRect(0, 0, width, 0), False)
left, top, right, bottom = self.getContentsMargins()
return height + top + bottom
def layoutChildren(self, rect, appl=True):
def _layoutChildren(self, rect, appl=True):
left, top, right, bottom = self.getContentsMargins()
r = rect.adjusted(+left, +top, -right, -bottom)
x = r.x();
y = r.y();
x, y = r.x(), r.y()
lineHeight = 0
for item in self._items:
@@ -86,4 +49,48 @@ class FloatingLayout(QLayout):
x += sz_hint.width() + spaceX
lineHeight = max(lineHeight, sz_hint.height())
return y + lineHeight - r.y()
return y + lineHeight - r.y() + top + bottom
def heightForWidth(self, width):
return self._layoutChildren(QRect(0, 0, width, 0), False)
def setGeometry(self, r):
QLayout.setGeometry(self, r)
new_height = self._layoutChildren(r)
if new_height != self._min_h:
self._updateMinimumSize(new_height)
i = 0
wid = self.parentWidget()
while wid is not None:
wid.updateGeometry()
wid = wid.parentWidget()
i += 1
def addItem(self, item):
self._items.append(item)
def count(self):
return len(self._items)
def hasHeightForWidth(self):
return True
def itemAt(self, index):
if index < 0 or index >= len(self._items):
return None
return self._items[index]
def minimumSize(self):
return QSize(self._min_w, self._min_h)
def takeAt(self, index):
if index < 0 or index >= len(self._items):
return None
else:
item = self._items[index]
del self._items[index]
return item
def sizeHint(self):
return self.minimumSize()
+18 -13
View File
@@ -6,7 +6,6 @@ from PyQt4.QtGui import QMainWindow, QSizePolicy, QWidget, QVBoxLayout, QAction,
QKeySequence, QLabel, QItemSelectionModel, QMessageBox, QFileDialog
from PyQt4.QtCore import SIGNAL, QSettings, QSize, QPoint, QVariant, QFileInfo
import PyQt4.uic as uic
from sloth.gui.buttonarea import ButtonArea
from sloth.gui.propertyeditor import PropertyEditor
from sloth.gui.annotationscene import AnnotationScene
from sloth.gui.frameviewer import GraphicsView
@@ -54,10 +53,10 @@ class MainWindow(QMainWindow):
self.treeview.setSelectionModel(self.selectionmodel)
self.treeview.selectionModel().currentChanged.connect(self.labeltool.setCurrentImage)
def onCurrentImageChanged(self, index):
self.scene.setRoot(index)
def onCurrentImageChanged(self):
new_image = self.labeltool.currentImage()
self.scene.setCurrentImage(new_image)
img = self.labeltool.getImage(new_image)
h = img.shape[0]
@@ -70,7 +69,7 @@ class MainWindow(QMainWindow):
elif isinstance(new_image, ImageFileModelItem):
self.controls.setFilename(os.path.basename(new_image['filename']))
self.selectionmodel.setCurrentIndex(index, QItemSelectionModel.ClearAndSelect|QItemSelectionModel.Rows)
self.selectionmodel.setCurrentIndex(new_image.index(), QItemSelectionModel.ClearAndSelect|QItemSelectionModel.Rows)
def onScaleChanged(self, scale):
self.zoominfo.setText("%.2f%%" % (100 * scale, ))
@@ -103,17 +102,27 @@ class MainWindow(QMainWindow):
# get inserters and items from labels
# FIXME for handling the new-style config correctly
inserters = dict([(label['attributes']['type'], label['inserter'])
inserters = dict([(label['attributes']['class'], label['inserter'])
for label in config.LABELS
if 'type' in label.get('attributes', {}) and 'inserter' in label])
items = dict([(label['attributes']['type'], label['item'])
if 'class' in label.get('attributes', {}) and 'inserter' in label])
items = dict([(label['attributes']['class'], label['item'])
for label in config.LABELS
if 'type' in label.get('attributes', {}) and 'item' in label])
if 'class' in label.get('attributes', {}) and 'item' in label])
# Property Editor
self.property_editor = PropertyEditor(config.LABELS)
self.ui.dockAnnotationButtons.setWidget(self.property_editor)
# Scene
self.scene = AnnotationScene(self.labeltool, items=items, inserters=inserters)
self.property_editor.insertionModeStarted.connect(self.scene.onInsertionModeStarted)
self.property_editor.insertionModeEnded.connect(self.scene.onInsertionModeEnded)
# SceneView
self.view = GraphicsView(self)
self.view.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
self.view.setScene(self.scene)
self.central_widget = QWidget()
self.central_layout = QVBoxLayout()
self.controls = ControlButtonWidget()
@@ -127,10 +136,6 @@ class MainWindow(QMainWindow):
self.initShortcuts(config.HOTKEYS)
self.buttonarea = ButtonArea(config.LABELS)
self.ui.dockAnnotationButtons.setWidget(self.buttonarea)
self.buttonarea.stateChanged.connect(self.scene.setMode)
self.treeview = AnnotationTreeView()
self.treeview.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
self.ui.dockInformation.setWidget(self.treeview)
+1 -2
View File
@@ -1,4 +1,3 @@
from sloth.core import exceptions
from sloth.core.utils import import_callable
class Factory:
@@ -77,7 +76,7 @@ class Factory:
Newly created object. If for the given type no mapping exists, this
function returns ``None``.
"""
_type = _type.lower()
_type = str(_type).lower()
if _type not in self.items_:
return None