Implement dict-like interface for model items

This commit is contained in:
Mika Fischer
2011-06-16 15:43:34 +02:00
parent c88c4788ad
commit 6a8be5f0b7
4 changed files with 87 additions and 78 deletions
+64 -56
View File
@@ -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:
+2 -1
View File
@@ -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()
+1 -1
View File
@@ -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())
+20 -20
View File
@@ -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())