From 6a8be5f0b71c486ae84d70e40572a30fbd41b49b Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Thu, 16 Jun 2011 15:43:34 +0200 Subject: [PATCH] Implement dict-like interface for model items --- sloth/annotations/model.py | 120 ++++++++++++++++++++----------------- sloth/core/labeltool.py | 3 +- sloth/gui/labeltool.py | 2 +- sloth/items/items.py | 40 ++++++------- 4 files changed, 87 insertions(+), 78 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index 1d31506..3e1555f 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -5,16 +5,54 @@ from PyQt4.QtGui import QTreeView, QSortFilterProxyModel, QAbstractItemView from PyQt4.QtCore import QModelIndex, QPersistentModelIndex, QAbstractItemModel, QVariant, Qt, pyqtSignal import os.path import copy +from collections import MutableMapping ItemRole, TypeRole, DataRole, ImageRole = [Qt.UserRole + i + 1 for i in range(4)] -class ModelItem: +class ModelItem(MutableMapping): def __init__(self): self._children = [] self._pindex = [] self._model = None self._parent = None self._columns = 1 + if not hasattr(self, "_dict"): + self._dict = {} + + # Methods for MutableMapping + def __len__(self): + return len(self._dict) + + def __iter__(self): + return self._dict.iterkeys() + + def __getitem__(self, key): + return self._dict[key] + + def __setitem__(self, key, value): + if key not in self._dict or self._dict[key] != value: + self._dict[key] = value + self._valueChanged() + + def __delitem__(self, key): + del self._dict[key] + self._valueChanged() + + def has_key(self, key): + return self.__contains__(key) + + def clear(self): + if len(self) > 0: + MutableMapping.clear(self) + self._valueChanged() + + def update(self, other=None, **kwargs): + MutableMapping.update(self, other, **kwargs) + # TODO: Only call _valueChanged, if anything actually changed... + self._valueChanged() + + def _valueChanged(self): + pass def children(self): return self._children @@ -23,7 +61,7 @@ class ModelItem: return self._model def parent(self): - assert self._parent != self + assert self._parent is not self return self._parent def data(self, role=Qt.DisplayRole, column=0): @@ -183,14 +221,12 @@ class RootModelItem(ModelItem): class FileModelItem(ModelItem): def __init__(self, fileinfo): ModelItem.__init__(self) - self._fileinfo = fileinfo - - def filename(self): - return self._fileinfo['filename'] + self.update(fileinfo) + print self['filename'] def data(self, role=Qt.DisplayRole, column=0): if role == Qt.DisplayRole and column == 0: - return os.path.basename(self.filename()) + return os.path.basename(self['filename']) return ModelItem.data(self, role, column) @staticmethod @@ -221,7 +257,7 @@ class ImageModelItem(ModelItem): def updateAnnotation(self, ann): for child in self._children: if child.type() == ann['type']: - if (child.has_key('id') and ann.has_key('id') and child.value('id') == ann['id']) or (not child.has_key('id') and not ann.has_key('id')): + if (child.has_key('id') and ann.has_key('id') and child['id'] == ann['id']) or (not child.has_key('id') and not ann.has_key('id')): ann[None] = None child.setData(QVariant(ann), DataRole, 1) return @@ -265,13 +301,13 @@ class FrameModelItem(ImageModelItem): if frameinfo.has_key("annotations"): ImageModelItem.__init__(self, frameinfo["annotations"]) del frameinfo["annotations"] - self._frameinfo = frameinfo + self.update(frameinfo) def framenum(self): - return int(self._frameinfo.get('num', -1)) + return int(self.get('num', -1)) def timestamp(self): - return float(self._frameinfo.get('timestamp', -1)) + return float(self.get('timestamp', -1)) def data(self, role=Qt.DisplayRole, column=0): if role == Qt.DisplayRole and column == 0: @@ -288,58 +324,30 @@ class AnnotationModelItem(ModelItem): ModelItem.__init__(self) # dummy key/value so that pyqt does not convert the dict # into a QVariantMap while communicating with the Views - self._annotation = {None: None} - self.setAnnotation(annotation) + self._items = {} + self[None] = None + self.update(annotation) - def type(self): - return self._annotation['type'] + def _valueChanged(self): + # Keep self._dict and self._items in sync + for key, val in self._items.iteritems(): + if not key in self: + self.deleteChild(val) - def getAnnotations(self): - ann = copy.deepcopy(self._annotation) - if None in ann: - del ann[None] - # TODO: Maybe it would be nice to enforce a deterministic ordering? - return ann + for key, val in self.iteritems(): + if not key in self._items and key is not None: + self._items[key] = KeyValueModelItem(key) + self.appendChild(self._items[key]) - def annotation(self): - return self._annotation - - def setAnnotation(self, ann): - for key, val in ann.iteritems(): - # print key, val - if not key in self._annotation: - # print "not in annotation: ", key - self._annotation[key] = val - self.appendChild(KeyValueModelItem(key)) - - for key in self._annotation.keys(): - if not key in ann: - for child in [e for e in self.children() if e.key() == key]: - self.deleteChild(child) - del self._annotation[key] - else: - self.setValue(key, ann[key]) - - def setValue(self, key, value): - if key not in self._annotation or self._annotation[key] != value: - self._annotation[key] = value - if self.model() is not None: - # TODO: This should only emit dataChanged for the value that - # was actually changed, not for the whole annotation - self.model().dataChanged.emit(self.index(), self.index()) - - def value(self, key): - return self._annotation[key] - - def has_key(self, key): - return self._annotation.has_key(key) + if self.model() is not None: + self.model().dataChanged.emit(self.index(), self.index()) # Delegated from QAbstractItemModel def data(self, role=Qt.DisplayRole, column=0): if role == Qt.DisplayRole and column == 0: - return self.type() + return self['type'] elif role == TypeRole: - return self.type() + return self['type'] elif role == DataRole: return self._annotation return ModelItem.data(self, role, column) @@ -358,7 +366,7 @@ class KeyValueModelItem(ModelItem): if column == 0: return self._key elif column == 1: - return self.parent().value(self._key) + return QVariant(self.parent()[self._key]) else: return QVariant() else: diff --git a/sloth/core/labeltool.py b/sloth/core/labeltool.py index b29dd25..6338bcf 100755 --- a/sloth/core/labeltool.py +++ b/sloth/core/labeltool.py @@ -145,6 +145,7 @@ class LabelTool(QObject): self.loadAnnotations(args[1], handleErrors=False) except Exception, e: print "Error loading annotations:", e + raise sys.exit(1) else: self.clearAnnotations() @@ -295,7 +296,7 @@ class LabelTool(QObject): def getImage(self, item): # TODO: Also handle video frames - return self.container_.loadImage(item.filename()) + return self.container_.loadImage(item['filename']) def getAnnotationFilePatterns(self): return self.container_factory_.patterns() diff --git a/sloth/gui/labeltool.py b/sloth/gui/labeltool.py index 82ead60..ad3931c 100755 --- a/sloth/gui/labeltool.py +++ b/sloth/gui/labeltool.py @@ -58,7 +58,7 @@ class MainWindow(QMainWindow): if isinstance(new_image, FrameModelItem): self.controls.setFrameNumAndTimestamp(item.framenum(), item.timestamp()) elif isinstance(new_image, ImageFileModelItem): - self.controls.setFilename(os.path.basename(new_image.filename())) + self.controls.setFilename(os.path.basename(new_image['filename'])) if new_image.index() != self.treeview.currentIndex(): self.treeview.setCurrentIndex(new_image.index()) diff --git a/sloth/items/items.py b/sloth/items/items.py index 7f5960c..0432ace 100644 --- a/sloth/items/items.py +++ b/sloth/items/items.py @@ -32,11 +32,11 @@ class BaseItem(QAbstractGraphicsShapeItem): self.text_bg_brush_ = None self.auto_text_keys_ = [] - def annotation(self): + def modelItem(self): """ - Returns the annotation of this items. + Returns the model item of this items. """ - return self._model_item.annotation() + return self._model_item def index(self): """ @@ -98,8 +98,8 @@ class BaseItem(QAbstractGraphicsShapeItem): pass def updateModel(self, ann=None): - if data is not None: - self._model_item.setAnnotation(ann) + if ann is not None: + self._model_item.update(ann) def paint(self, painter, option, widget=None): painter.save() @@ -162,15 +162,15 @@ class PointItem(BaseItem): self.updatePoint() def updateModel(self): - self._model_item.setValue('x', self.scenePos().x()) - self._model_item.setValue('y', self.scenePos().y()) + self._model_item['x'] = self.scenePos().x() + self._model_item['y'] = self.scenePos().y() def updatePoint(self): if self._model_item is None: return - point = QPointF(float(self._model_item.value('x')), - float(self._model_item.value('y'))) + point = QPointF(float(self._model_item['x']), + float(self._model_item['y'])) if point == self.point_: return @@ -222,14 +222,14 @@ class RectItem(BaseItem): if model_item is None: return QRectF() if model_item.has_key('w'): - w = model_item.value('w') + w = model_item['w'] if model_item.has_key('width'): - w = model_item.value('width') + w = model_item['width'] if model_item.has_key('h'): - h = model_item.value('h') + h = model_item['h'] if model_item.has_key('height'): - h = model_item.value('height') - return QRectF(float(model_item.value('x')), float(model_item.value('y')), + h = model_item['height'] + return QRectF(float(model_item['x']), float(model_item['y']), float(w), float (h)) def _updateRect(self, rect): @@ -244,16 +244,16 @@ class RectItem(BaseItem): def updateModel(self): self.rect_ = QRectF(self.scenePos(), self.rect_.size()) - self._model_item.setValue('x', self.rect_.topLeft().x()) - self._model_item.setValue('y', self.rect_.topLeft().y()) + self._model_item['x'] = self.rect_.topLeft().x() + self._model_item['y'] = self.rect_.topLeft().y() if self._model_item.has_key('width'): - self._model_item.setValue('width', float(self.rect_.width())) + self._model_item['width'] = float(self.rect_.width()) if self._model_item.has_key('w'): - self._model_item.setValue('w', float(self.rect_.width())) + self._model_item['w'] = float(self.rect_.width()) if self._model_item.has_key('height'): - self._model_item.setValue('height', float(self.rect_.height())) + self._model_item['height'] = float(self.rect_.height()) if self._model_item.has_key('h'): - self._model_item.setValue('h', float(self.rect_.height())) + self._model_item['h'] = float(self.rect_.height()) def boundingRect(self): return QRectF(QPointF(0, 0), self.rect_.size())