mirror of
https://github.com/wassname/sloth.git
synced 2026-07-08 09:51:28 +08:00
Implement dict-like interface for model items
This commit is contained in:
+64
-56
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user