From f560c3a7ee49986b0ef2b783d9eda283388bcd48 Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Fri, 10 Jun 2011 19:10:28 +0200 Subject: [PATCH 01/21] Changed ModelItems to new API Will NOT work, so don't use it! --- sloth/annotations/model.py | 254 ++++++++++++++++++++----------------- 1 file changed, 136 insertions(+), 118 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index 63c2926..3c0cc2c 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -11,17 +11,14 @@ import okapy.videoio as okv TypeRole, DataRole, ImageRole = [Qt.UserRole + i + 1 for i in range(3)] class ModelItem: - def __init__(self, model, parent=None): - self.model_ = model - self.parent_ = parent + def __init__(self): self.children_ = [] + self._pindex = None + self.model_ = None + self.parent_ = None - def children(self, index=None): - if index is None: - return self.children_ - else: - # return tuple child, index of the child - return [(child, index.child(row, 0)) for row, child in enumerate(self.children_)] + def children(self): + return self.children_ def model(self): return self.model_ @@ -35,65 +32,109 @@ class ModelItem: except: return -1 - def data(self, index, role): + def data(self, role=Qt.DisplayRole, column=0): return QVariant() + def setParent(self, parent): + assert self.parent_ is None + self.parent_ = parent + + def setIndex(self, index): + assert self._pindex is None + self._pindex = QPersistentModelIndex(index) + if index.isValid(): + self.model_ = index.model() + + def pindex(self): + assert self._pindex is not None + return self._pindex + + def index(self): + assert self._pindex is not None + return QModelIndex(self._pindex) + + def parentIndex(self): + if self.parent_ is not None: + return self.parent_.index() + else: + return QModelIndex() + + def appendChild(self, item): + next_row = len(self.children_) + index = self.index() + self.model_.beginInsertRows(index, next_row, next_row) + self.children_.append(item) + item.setParent(self) + self.model_.endInsertRows() + item.setIndex(self.model_.index(next_row, 0, index)) + + def deleteAllChildren(self): + for child in self.children_: + child.deleteAllChildren() + + self.model_.beginRemoveRows(self.index(), 0, len(self.children_) - 1) + self.children_ = [] + self.model_.endRemoveRows() + + def deleteChild(self, arg): + if arg isinstance ModelItem: + self.deleteChild(self.children_.index(item)) + else: + if pos < 0 or pos >= len(self.children_): + raise IndexError("child index out of range") + self.children_[pos].deleteAllChildren() + self.model_.beginRemoveRows(self.index(), pos, pos) + del self.children_[pos] + self.model_.endRemoveRows() + class RootModelItem(ModelItem): - def __init__(self, model, files): - ModelItem.__init__(self, model, None) - self.files_ = files + def __init__(self, model, fileinfos): + ModelItem.__init__(self) + self.model_ = model + self.setIndex(QModelIndex()) - for file in files: - fmi = FileModelItem.create(self.model(), file, self) - self.children_.append(fmi) + for fileinfo in fileinfos: + appendFileItem(fileinfo) - def addFile(self, file): - fmi = FileModelItem.create(self.model(), file, self) - next = len(self.children_) - index = QModelIndex() - self.model().beginInsertRows(index, next, next) - self.children_.append(fmi) - self.files_.append(file) - self.model().endInsertRows() - self.model().emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), index, index) + def appendFileItem(self, fileinfo): + item = FileModelItem.create(fileinfo, self) + self.appendChild(item) class FileModelItem(ModelItem): - def __init__(self, model, file, parent): - ModelItem.__init__(self, model, parent) - self.file_ = file + def __init__(self, fileinfo): + ModelItem.__init__(self) + self._fileinfo = fileinfo def filename(self): - return self.file_['filename'] + return self._fileinfo['filename'] - def fullpath(self): - return os.path.join(self.model().basedir(), self.filename()) - - def data(self, index, role): + def data(self, role=Qt.DisplayRole, column=0): if role == Qt.DisplayRole and index.column() == 0: return os.path.basename(self.filename()) - return ModelItem.data(self, index, role) + return ModelItem.data(self, role, column) @staticmethod - def create(model, file, parent): - if file['type'] == 'image': - return ImageFileModelItem(model, file, parent) - elif file['type'] == 'video': - return VideoFileModelItem(model, file, parent) + def create(fileinfo, parent): + if fileinfo['type'] == 'image': + return ImageFileModelItem(fileinfo, parent) + elif fileinfo['type'] == 'video': + return VideoFileModelItem(fileinfo, parent) class ImageFileModelItem(FileModelItem): - def __init__(self, model, file, parent): - FileModelItem.__init__(self, model, file, parent) + def __init__(self, fileinfo): + FileModelItem.__init__(self, fileinfo) - for ann in file['annotations']: - ami = AnnotationModelItem(self.model(), ann, self) - self.children_.append(ami) + for ann in fileinfo['annotations']: + item = AnnotationModelItem(ann) + self.appendChild(item) def addAnnotation(self, ann): - self.file_['annotations'].append(ann) - ami = AnnotationModelItem(self.model(), ann, self) - self.children_.append(ami) + self.fileinfo_['annotations'].append(ann) + item = AnnotationModelItem(ann) + self.appendChild(item) - def updateAnnotation(self, index, ann): + # TODO + def updateAnnotation(self, ann): child_found = False for child in self.children_: if child.type() == ann['type']: @@ -106,62 +147,44 @@ class ImageFileModelItem(FileModelItem): raise Exception("No ImageFileModelItem found that could be updated!") def removeAnnotation(self, pos): - del self.file_['annotations'][pos] - del self.children_[pos] + del self.fileinfo_['annotations'][pos] + self.deleteChild(pos) - def data(self, index, role): - if role == ImageRole: - return okapy.loadImage(self.fullpath()) + def data(self, role=Qt.DisplayRole, column=0): elif role == DataRole: - return self.file_ - return FileModelItem.data(self, index, role) + return self.fileinfo_ + return FileModelItem.data(self, role) class VideoFileModelItem(FileModelItem): - _cached_vs_filename = None - _cached_vs = None + def __init__(self, fileinfo): + FileModelItem.__init__(self, fileinfo) - def __init__(self, model, file, parent): - FileModelItem.__init__(self, model, file, parent) - - for frame in file['frames']: - fmi = FrameModelItem(self.model(), frame, self) - self.children_.append(fmi) - - def updateCachedVideoSource(self): - # have only one cached video source at a time for now - # TODO: for labeling multiple synchronized videos this should - # be modified, otherwise it might be awfully slow - VideoFileModelItem._cached_vs = okv.FFMPEGIndexedVideoSource(self.fullpath()) - VideoFileModelItem._cached_vs_filename = self.fullpath() - - def getFrame(self, frame): - if VideoFileModelItem._cached_vs_filename != self.fullpath(): - self.updateCachedVideoSource() - - VideoFileModelItem._cached_vs.getFrame(frame) - return VideoFileModelItem._cached_vs.getImage() + for frameinfo in fileinfo['frames']: + item = FrameModelItem(frameinfo) + self.appendChild(item) class FrameModelItem(ModelItem): - def __init__(self, model, frame, parent): - ModelItem.__init__(self, model, parent) - self.frame_ = frame + def __init__(self, frameinfo): + ModelItem.__init__(self) + self.frameinfo_ = frameinfo - for ann in frame['annotations']: - ami = AnnotationModelItem(ann, self) - self.children_.append(ami) + for ann in frameinfo['annotations']: + item = AnnotationModelItem(ann) + self.appendChild(item) def framenum(self): - return int(self.frame_.get('num', -1)) + return int(self.frameinfo_.get('num', -1)) def timestamp(self): - return float(self.frame_.get('timestamp', -1)) + return float(self.frameinfo_.get('timestamp', -1)) def addAnnotation(self, ann): - self.frame_['annotations'].append(ann) - ami = AnnotationModelItem(ann, self) - self.children_.append(ami) + self.frameinfo_['annotations'].append(ann) + item = AnnotationModelItem(ann) + self.appendChild(item) - def updateAnnotation(self, index, ann): + # TODO + def updateAnnotation(self, ann): child_found = False for child in self.children_: if child.type() == ann['type']: @@ -174,19 +197,17 @@ class FrameModelItem(ModelItem): raise Exception("No FrameModelItem found that could be updated!") def removeAnnotation(self, pos): - del self.frame_['annotations'][pos] - del self.children_[pos] + del self.frameinfo_['annotations'][pos] + self.deleteChild(pos) - def data(self, index, role): + def data(self, index, role=Qt.DisplayRole, column=0): if role == Qt.DisplayRole and index.column() == 0: return "%d / %.3f" % (self.framenum(), self.timestamp()) - elif role == ImageRole: - return self.parent().getFrame(self.frame_['num']) return QVariant() class AnnotationModelItem(ModelItem): - def __init__(self, model, annotation, parent): - ModelItem.__init__(self, model, parent) + def __init__(self, annotation): + ModelItem.__init__(self) self.annotation_ = annotation # dummy key/value so that pyqt does not convert the dict # into a QVariantMap while communicating with the Views @@ -195,7 +216,7 @@ class AnnotationModelItem(ModelItem): for key, value in annotation.iteritems(): if key == None: continue - self.children_.append(KeyValueModelItem(model, key, self)) + self.addChild(KeyValueModelItem(key)) def type(self): return self.annotation_['type'] @@ -210,38 +231,35 @@ class AnnotationModelItem(ModelItem): print key, value if not key in self.annotation_: print "not in annotation: ", key - next = len(self.children_) - index.model().beginInsertRows(index, next, next) - self.children_.append(KeyValueModelItem(key, self)) - index.model().endInsertRows() - index.model().emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), index, index) + self.addChild(KeyValueModelItem(key)) self.annotation_[key] = data[key] for key in self.annotation_.keys(): if not key in data: - #TODO beginRemoveRows, delete child, etc. + # TODO + self.deleteChild(???) del self.annotation_[key] else: self.annotation_[key] = data[key] + # TODO: Emit data changed signal + print "new annotation:", self.annotation_ - index.model().dataChanged.emit(index, index.sibling(index.row(), 0)) + # TODO: Emit data changed signal return True return False - def data(self, index, role): - if role == Qt.DisplayRole and index.column() == 0: + def data(self, index, role=Qt.DisplayRole, column=0): + if role == Qt.DisplayRole and column == 0: return self.type() elif role == TypeRole: return self.type() elif role == DataRole: - #print "data():", self.annotation_ return self.annotation_ - return QVariant() - def setValue(self, key, value, index): + def setValue(self, key, value): self.annotation_[key] = value - index.model().dataChanged.emit(index, index.sibling(index.row(), 0)) + # TODO: Emit data changed signal def value(self, key): return self.annotation_[key] @@ -250,16 +268,16 @@ class AnnotationModelItem(ModelItem): return self.annotation_.has_key(key) class KeyValueModelItem(ModelItem): - def __init__(self, model, key, parent): - ModelItem.__init__(self, model, parent) - self.key_ = key + def __init__(self, key): + ModelItem.__init__(self) + self._key = key - def data(self, index, role): + def data(self, role=Qt.DisplayRole, column=0): if role == Qt.DisplayRole: - if index.column() == 0: - return self.key_ - elif index.column() == 1: - return self.parent().value(self.key_) + if column == 0: + return self._key + elif column == 1: + return self.parent().value(self._key) else: return QVariant() From 390c899bb865967c9553b9b9baa3e273eb61ed7a Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Mon, 13 Jun 2011 12:18:42 +0200 Subject: [PATCH 02/21] Fix bugs discovered by pyflakes --- sloth/annotations/model.py | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index 3c0cc2c..5485b58 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -1,12 +1,9 @@ """ The annotationmodel module contains the classes for the AnnotationModel. """ -from PyQt4.QtGui import * -from PyQt4.QtCore import * -from functools import partial +from PyQt4.QtGui import QAbstractItemModel, QModelIndex, QPersistentModelIndex, QSortFilterProxyModel, QTreeView, QAbstractItemView, QApplication +from PyQt4.QtCore import QVariant, Qt, pyqtSignal, SIGNAL import os.path -import okapy -import okapy.videoio as okv TypeRole, DataRole, ImageRole = [Qt.UserRole + i + 1 for i in range(3)] @@ -77,14 +74,14 @@ class ModelItem: self.model_.endRemoveRows() def deleteChild(self, arg): - if arg isinstance ModelItem: - self.deleteChild(self.children_.index(item)) + if isinstance(arg, ModelItem): + self.deleteChild(self.children_.index(arg)) else: - if pos < 0 or pos >= len(self.children_): + if arg < 0 or arg >= len(self.children_): raise IndexError("child index out of range") - self.children_[pos].deleteAllChildren() - self.model_.beginRemoveRows(self.index(), pos, pos) - del self.children_[pos] + self.children_[arg].deleteAllChildren() + self.model_.beginRemoveRows(self.index(), arg, arg) + del self.children_[arg] self.model_.endRemoveRows() class RootModelItem(ModelItem): @@ -94,7 +91,7 @@ class RootModelItem(ModelItem): self.setIndex(QModelIndex()) for fileinfo in fileinfos: - appendFileItem(fileinfo) + self.appendFileItem(fileinfo) def appendFileItem(self, fileinfo): item = FileModelItem.create(fileinfo, self) @@ -109,7 +106,7 @@ class FileModelItem(ModelItem): return self._fileinfo['filename'] def data(self, role=Qt.DisplayRole, column=0): - if role == Qt.DisplayRole and index.column() == 0: + if role == Qt.DisplayRole and column == 0: return os.path.basename(self.filename()) return ModelItem.data(self, role, column) @@ -151,7 +148,7 @@ class ImageFileModelItem(FileModelItem): self.deleteChild(pos) def data(self, role=Qt.DisplayRole, column=0): - elif role == DataRole: + if role == DataRole: return self.fileinfo_ return FileModelItem.data(self, role) @@ -495,14 +492,6 @@ class AnnotationModel(QAbstractItemModel): return index - def asDictList(self): - """return annotations as python list of dictionary""" - # TODO - annotations = [] - if self.root_ is not None: - for child in self.root_.children_: - pass - ####################################################################################### From ea4314c9c01c62e089d395d40df9032073a205ed Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Mon, 13 Jun 2011 17:19:12 +0200 Subject: [PATCH 03/21] Continue model refactoring Still NOT working! --- sloth/annotations/model.py | 364 ++++++++----------------------------- sloth/core/labeltool.py | 53 +++--- sloth/gui/labeltool.py | 20 +- 3 files changed, 119 insertions(+), 318 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index 5485b58..f120c48 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -1,13 +1,13 @@ """ The annotationmodel module contains the classes for the AnnotationModel. """ -from PyQt4.QtGui import QAbstractItemModel, QModelIndex, QPersistentModelIndex, QSortFilterProxyModel, QTreeView, QAbstractItemView, QApplication -from PyQt4.QtCore import QVariant, Qt, pyqtSignal, SIGNAL +from PyQt4.QtGui import QTreeView, QSortFilterProxyModel, QAbstractItemView +from PyQt4.QtCore import QObject, QModelIndex, QPersistentModelIndex, QAbstractItemModel, QVariant, Qt, pyqtSignal import os.path -TypeRole, DataRole, ImageRole = [Qt.UserRole + i + 1 for i in range(3)] +ItemRole, TypeRole, DataRole, ImageRole = [Qt.UserRole + i + 1 for i in range(4)] -class ModelItem: +class ModelItem(QObject): def __init__(self): self.children_ = [] self._pindex = None @@ -21,16 +21,14 @@ class ModelItem: return self.model_ def parent(self): + assert self.parent_ != self return self.parent_ - def rowOfChild(self, item): - try: - return self.children_.index(item) - except: - return -1 - def data(self, role=Qt.DisplayRole, column=0): - return QVariant() + if role == ItemRole: + return QVariant(self) + else: + return QVariant() def setParent(self, parent): assert self.parent_ is None @@ -46,8 +44,13 @@ class ModelItem: assert self._pindex is not None return self._pindex - def index(self): + def index(self, column=0): + # TODO: The two columns can probably be handled better... + # Maybe use a list of indices, with the second being QModelIndex() + # for everything except the KeyValueModelItems? assert self._pindex is not None + if column != 0: + return QModelIndex() return QModelIndex(self._pindex) def parentIndex(self): @@ -63,7 +66,8 @@ class ModelItem: self.children_.append(item) item.setParent(self) self.model_.endInsertRows() - item.setIndex(self.model_.index(next_row, 0, index)) + item_index = self.model().createIndex(next_row, 0, item) + item.setIndex(item_index) def deleteAllChildren(self): for child in self.children_: @@ -73,6 +77,12 @@ class ModelItem: self.children_ = [] self.model_.endRemoveRows() + def delete(self): + if self.parent() is None: + raise RuntimeError("Trying to delete orphan") + else: + self.parent().deleteChild(self) + def deleteChild(self, arg): if isinstance(arg, ModelItem): self.deleteChild(self.children_.index(arg)) @@ -85,18 +95,25 @@ class ModelItem: self.model_.endRemoveRows() class RootModelItem(ModelItem): - def __init__(self, model, fileinfos): + def __init__(self, model): ModelItem.__init__(self) self.model_ = model self.setIndex(QModelIndex()) - for fileinfo in fileinfos: - self.appendFileItem(fileinfo) + def appendChild(self, item): + if isinstance(item, FileModelItem): + ModelItem.appendChild(self, item) + else: + raise TypeError("Only FileModelItems can be attached to RootModelItem") def appendFileItem(self, fileinfo): - item = FileModelItem.create(fileinfo, self) + item = FileModelItem.create(fileinfo) self.appendChild(item) + def appendFileItems(self, fileinfos): + for fileinfo in fileinfos: + self.appendFileItem(fileinfo) + class FileModelItem(ModelItem): def __init__(self, fileinfo): ModelItem.__init__(self) @@ -111,13 +128,16 @@ class FileModelItem(ModelItem): return ModelItem.data(self, role, column) @staticmethod - def create(fileinfo, parent): + def create(fileinfo): if fileinfo['type'] == 'image': - return ImageFileModelItem(fileinfo, parent) + return ImageFileModelItem(fileinfo) elif fileinfo['type'] == 'video': - return VideoFileModelItem(fileinfo, parent) + return VideoFileModelItem(fileinfo) -class ImageFileModelItem(FileModelItem): +class ImageModelItem(ModelItem): + pass + +class ImageFileModelItem(FileModelItem, ImageModelItem): def __init__(self, fileinfo): FileModelItem.__init__(self, fileinfo) @@ -137,7 +157,7 @@ class ImageFileModelItem(FileModelItem): 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')): ann[None] = None - child.setData(index, QVariant(ann), DataRole) + #child.setData(index, QVariant(ann), DataRole) child_found = True break if not child_found: @@ -160,7 +180,7 @@ class VideoFileModelItem(FileModelItem): item = FrameModelItem(frameinfo) self.appendChild(item) -class FrameModelItem(ModelItem): +class FrameModelItem(ImageModelItem): def __init__(self, frameinfo): ModelItem.__init__(self) self.frameinfo_ = frameinfo @@ -187,7 +207,7 @@ class FrameModelItem(ModelItem): 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')): ann[None] = None - child.setData(index, QVariant(ann), DataRole) + #child.setData(index, QVariant(ann), DataRole) child_found = True break if not child_found: @@ -234,7 +254,7 @@ class AnnotationModelItem(ModelItem): for key in self.annotation_.keys(): if not key in data: # TODO - self.deleteChild(???) + self.deleteChild() # TODO del self.annotation_[key] else: self.annotation_[key] = data[key] @@ -285,72 +305,11 @@ class AnnotationModel(QAbstractItemModel): def __init__(self, annotations, parent=None): QAbstractItemModel.__init__(self, parent) self.annotations_ = annotations - self.root_ = RootModelItem(self, self.annotations_) self.dirty_ = False - self.basedir_ = "" - - def dirty(self): - return self.dirty_ - - def setDirty(self, dirty=True): - previous = self.dirty_ - self.dirty_ = dirty - if previous != dirty: - self.dirtyChanged.emit(dirty) - - def basedir(self): - return self.basedir_ - - def setBasedir(self, dir): - print "setBasedir: \"" + dir + "\"" - self.basedir_ = dir - - def itemFromIndex(self, index): - index = QModelIndex(index) # explicitly convert from QPersistentModelIndex - if index.isValid(): - return index.internalPointer() - return self.root_ - - def index(self, row, column, parent_idx=QModelIndex()): - parent_item = self.itemFromIndex(parent_idx) - if row >= len(parent_item.children()): - return QModelIndex() - child_item = parent_item.children()[row] - return self.createIndex(row, column, child_item) - - def imageIndex(self, index): - """return index that points to the (maybe parental) image/frame object""" - if not index.isValid(): - return QModelIndex() - - index = QModelIndex(index) # explicitly convert from QPersistentModelIndex - item = self.itemFromIndex(index) - if isinstance(item, ImageFileModelItem) or \ - isinstance(item, FrameModelItem): - return index - - # try with next hierarchy up - return self.imageIndex(index.parent()) - - def data(self, index, role=Qt.DisplayRole): - if not index.isValid(): - return QVariant() - index = QModelIndex(index) # explicitly convert from QPersistentModelIndex - - #if role == Qt.CheckStateRole: - #item = self.itemFromIndex(index) - #if item.isCheckable(index.column()): - #return QVariant(Qt.Checked if item.visible() else Qt.Unchecked) - #return QVariant() - - #if role != Qt.DisplayRole and role != GraphicsItemRole and role != DataRole: - #return QVariant() - - ## non decorational behaviour - - item = self.itemFromIndex(index) - return item.data(index, role) + self.root_ = RootModelItem(self) + self.root_.appendFileItems(self.annotations_) + # QAbstractItemModel overloads def columnCount(self, index=QModelIndex()): return 2 @@ -359,108 +318,34 @@ class AnnotationModel(QAbstractItemModel): return len(item.children()) def parent(self, index): + if index is None: + return QModelIndex() item = self.itemFromIndex(index) parent = item.parent() if parent is None: return QModelIndex() - grandparent = parent.parent() - if grandparent is None: + return parent.index() + + def index(self, row, column, parent_idx=QModelIndex()): + parent = self.itemFromIndex(parent_idx) + if row >= len(parent.children()): return QModelIndex() - row = grandparent.rowOfChild(parent) - assert row != -1 - return self.createIndex(row, 0, parent) + return parent.children()[row].index(column) - def mapToSource(self, index): - return index - - def flags(self, index): - return Qt.ItemIsEnabled | Qt.ItemIsSelectable + def data(self, index, role=Qt.DisplayRole): if not index.isValid(): - return Qt.ItemIsEnabled - index = QModelIndex(index) # explicitly convert from QPersistentModelIndex + return QVariant() item = self.itemFromIndex(index) - return item.flags(index) + return item.data(role, index.column()) def setData(self, index, value, role=Qt.EditRole): if not index.isValid(): return False - index = QModelIndex(index) # explicitly convert from QPersistentModelIndex + item = self.itemFromIndex(index) + return item.setData(index, value, role) - #if role == Qt.EditRole: - #item = self.itemFromIndex(index) - #item.data_ = value - #self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), index, index) - #return True - - if role == Qt.CheckStateRole: - item = self.itemFromIndex(index) - checked = (value.toInt()[0] == Qt.Checked) - item.set_visible(checked) - self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), index, index) - return True - - if role == Qt.EditRole: - item = self.itemFromIndex(index) - return item.setData(index, value, role) - - if role == DataRole: - item = self.itemFromIndex(index) - print "setData", value.toPyObject() - if item.setData(index, value, role): - self.setDirty(True) - # TODO check why this is needed (should be done by item.setData() anyway) - self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), index, index.sibling(index.row(), 1)) - return True - - return False - - def addAnnotation(self, imageidx, ann={}, **kwargs): - ann.update(kwargs) - print "addAnnotation", ann - imageidx = QModelIndex(imageidx) # explicitly convert from QPersistentModelIndex - item = self.itemFromIndex(imageidx) - assert isinstance(item, FrameModelItem) or isinstance(item, ImageFileModelItem) - - next = len(item.children()) - self.beginInsertRows(imageidx, next, next) - item.addAnnotation(ann) - self.endInsertRows() - self.setDirty(True) - - self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), imageidx, imageidx) - - return True - - def updateAnnotation(self, imageidx, ann={}, **kwargs): - ann.update(kwargs) - print "updateAnnotation", ann - imageidx = QModelIndex(imageidx) # explicitly convert from QPersistentModelIndex - item = self.itemFromIndex(imageidx) - assert isinstance(item, FrameModelItem) or isinstance(item, ImageFileModelItem) - - item.updateAnnotation(imageidx, ann) - self.setDirty(True) - - self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"), imageidx, imageidx) - - return True - - def removeAnnotation(self, annidx): - annidx = QModelIndex(annidx) # explicitly convert from QPersistentModelIndex - item = self.itemFromIndex(annidx) - assert isinstance(item, AnnotationModelItem) - - parent = item.parent_ - parentidx = annidx.parent() - assert isinstance(parent, FrameModelItem) or isinstance(parent, ImageFileModelItem) - - pos = parent.rowOfChild(item) - self.beginRemoveRows(parentidx, pos, pos) - parent.removeAnnotation(pos) - self.endRemoveRows() - self.setDirty(True) - - return True + def flags(self, index): + return Qt.ItemIsEnabled | Qt.ItemIsSelectable def headerData(self, section, orientation, role): if orientation == Qt.Horizontal and role == Qt.DisplayRole: @@ -468,30 +353,20 @@ class AnnotationModel(QAbstractItemModel): elif section == 1: return QVariant("Value") return QVariant() - def getNextIndex(self, index): - """returns index of next *image* or *frame*""" - if not index.isValid(): - return QModelIndex() + # Own methods + def dirty(self): + return self.dirty_ - assert index == self.imageIndex(index) - num_images = self.rowCount(index.parent()) - if index.row() < num_images - 1: - return index.sibling(index.row()+1, 0) - - return index - - def getPreviousIndex(self, index): - # TODO bool parameter to disable wrap around - """returns index of previous *image* or *frame*""" - if not index.isValid(): - return QModelIndex() - - assert index == self.imageIndex(index) - if index.row() > 0: - return index.sibling(index.row()-1, 0) - - return index + def setDirty(self, dirty=True): + if dirty != self.dirty_: + self.dirty_ = dirty + self.dirtyChanged.emit(self.dirty_) + def itemFromIndex(self, index): + index = QModelIndex(index) # explicitly convert from QPersistentModelIndex + if index.isValid(): + return index.internalPointer() + return self.root_ ####################################################################################### @@ -527,6 +402,7 @@ class AnnotationSortFilterProxyModel(QSortFilterProxyModel): def insertFile(self, filename): return self.sourceModel().insertFile(filename) + ####################################################################################### # view ####################################################################################### @@ -542,18 +418,13 @@ class AnnotationTreeView(QTreeView): self.setAlternatingRowColors(True) self.setEditTriggers(QAbstractItemView.SelectedClicked) self.setSortingEnabled(True) -# self.setStyleSheet(""" -# QTreeView { selection-color: blue; show-decoration-selected: 1; } -# QTreeView::item:alternate { background-color: #EEEEEE; } -# """) - - self.connect(self, SIGNAL("expanded(QModelIndex)"), self.expanded) + self.expanded.connect(self.onExpanded) def resizeColumns(self): for column in range(self.model().columnCount(QModelIndex())): self.resizeColumnToContents(column) - def expanded(self): + def onExpanded(self): self.resizeColumns() def setModel(self, model): @@ -563,11 +434,7 @@ class AnnotationTreeView(QTreeView): def keyPressEvent(self, event): ## handle deletions of items if event.key() == Qt.Key_Delete: - index = self.currentIndex() - if not index.isValid(): - return - parent = self.model().parent(index) - self.model().removeRow(index.row(), parent) + self.model().itemFromIndex(self.currentindex()).delete() ## it is important to use the keyPressEvent of QAbstractItemView, not QTreeView QAbstractItemView.keyPressEvent(self, event) @@ -576,76 +443,3 @@ class AnnotationTreeView(QTreeView): QTreeView.rowsInserted(self, index, start, end) self.resizeColumns() # self.setCurrentIndex(index.child(end, 0)) - - -def someAnnotations(): - annotations = [] - annotations.append({'type': 'rect', - 'x': '10', - 'y': '20', - 'w': '40', - 'h': '60'}) - annotations.append({'type': 'rect', - 'x': '80', - 'y': '20', - 'w': '40', - 'h': '60'}) - annotations.append({'type': 'point', - 'x': '30', - 'y': '30'}) - annotations.append({'type': 'point', - 'x': '100', - 'y': '100'}) - return annotations - -def defaultAnnotations(): - annotations = [] - import os, glob - if os.path.exists('/cvhci/data/multimedia/bigbangtheory/still_images/s1e1/'): - images = glob.glob('/cvhci/data/multimedia/bigbangtheory/still_images/s1e1/*.png') - images.sort() - for fname in images: - file = { - 'filename': fname, - 'type': 'image', - 'annotations': someAnnotations() - } - annotations.append(file) - - for i in range(5): - file = { - 'filename': 'file%d.png' % i, - 'type': 'image', - 'annotations': someAnnotations() - } - annotations.append(file) - for i in range(5): - file = { - 'filename': 'file%d.avi' % i, - 'type': 'video', - 'frames': [], - } - for j in range(5): - frame = { - 'num': '%d' % j, - 'timestamp': '123456.789', - 'annotations': someAnnotations() - } - file['frames'].append(frame) - annotations.append(file) - return annotations - - -if __name__ == '__main__': - import sys - app = QApplication(sys.argv) - annotations = defaultAnnotations() - - model = AnnotationModel(annotations) - - wnd = AnnotationTreeView() - wnd.setModel(model) - wnd.show() - - sys.exit(app.exec_()) - diff --git a/sloth/core/labeltool.py b/sloth/core/labeltool.py index c470278..2e8d549 100755 --- a/sloth/core/labeltool.py +++ b/sloth/core/labeltool.py @@ -17,7 +17,10 @@ class LabelTool(QObject): statusMessage = pyqtSignal(QString) annotationsLoaded = pyqtSignal() pluginLoaded = pyqtSignal(QAction) - currentIndexChanged = pyqtSignal(QModelIndex) + # 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) def __init__(self, argv, parent=None): QObject.__init__(self, parent) @@ -32,7 +35,7 @@ class LabelTool(QObject): # Instatiate container factory self.container_factory_ = AnnotationContainerFactory(config.CONTAINERS) self.container_ = AnnotationContainer() - self.current_index_ = None + self._current_image = None # Load annotation file if len(args) > 0: @@ -68,18 +71,18 @@ class LabelTool(QObject): ###___________________________________________________________________________________________ def loadAnnotations(self, fname): fname = str(fname) # convert from QString - try: - self.container_ = self.container_factory_.create(fname) - self.container_.load(fname) - msg = "Successfully loaded %s (%d files, %d annotations)" % \ - (fname, self.container_.numFiles(), self.container_.numAnnotations()) - self._model = AnnotationModel(self.container_.annotations()) - if self.container_.filename() is not None: - self._model.setBasedir(os.path.dirname(self.container_.filename())) - else: - self._model.setBasedir("") - except Exception, e: - msg = "Error: Loading failed (%s)" % str(e) + #try: + self.container_ = self.container_factory_.create(fname) + self.container_.load(fname) + msg = "Successfully loaded %s (%d files, %d annotations)" % \ + (fname, self.container_.numFiles(), self.container_.numAnnotations()) + self._model = AnnotationModel(self.container_.annotations()) + #if self.container_.filename() is not None: + #self._model.setBasedir(os.path.dirname(self.container_.filename())) + #else: + #self._model.setBasedir("") + #except Exception, e: + #msg = "Error: Loading failed (%s)" % str(e) self.statusMessage.emit(msg) self.annotationsLoaded.emit() @@ -120,7 +123,7 @@ class LabelTool(QObject): def clearAnnotations(self): self.container_.clear() self._model = AnnotationModel(self.container_.annotations()) - self._model.setBasedir("") + #self._model.setBasedir("") self.statusMessage.emit('') self.annotationsLoaded.emit() @@ -155,15 +158,19 @@ class LabelTool(QObject): #self.setWindowModified(self.annotations.dirty()) pass - def currentIndex(self): - return self.current_index_ + def currentImage(self): + return self._current_image - def setCurrentIndex(self, index): - assert index.isValid() - newindex = index.model().imageIndex(index) - if newindex.isValid() and newindex != self.current_index_: - self.current_index_ = newindex - self.currentIndexChanged.emit(self.current_index_) + def setCurrentImage(self, image): + if isinstance(image, QModelIndex): + image = self._model.itemFromIndex(image) + while (image is not None) and (not isinstance(image, ImageModelItem)): + image = image.parent() + if image is None: + 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()) def getAnnotationFilePatterns(self): return self.container_factory_.patterns() diff --git a/sloth/gui/labeltool.py b/sloth/gui/labeltool.py index 5b53584..c7f3a25 100755 --- a/sloth/gui/labeltool.py +++ b/sloth/gui/labeltool.py @@ -46,19 +46,19 @@ class MainWindow(QMainWindow): self.setWindowTitle("%s - Unnamed[*]" % APP_NAME) self.treeview.setModel(self.labeltool.model()) self.scene.setModel(self.labeltool.model()) - self.treeview.selectionModel().currentChanged.connect(self.labeltool.setCurrentIndex) + self.treeview.selectionModel().currentChanged.connect(self.labeltool.setCurrentImage) - def onCurrentIndexChanged(self, new_index): - self.scene.setRoot(new_index) + def onCurrentImageChanged(self, index): + self.scene.setRoot(index) + new_image = self.labeltool.currentImage() # TODO: This info should be obtained from AnnotationModel or LabelTool - item = self.labeltool.model().itemFromIndex(new_index) - if isinstance(item, FrameModelItem): + if isinstance(new_image, FrameModelItem): self.controls.setFrameNumAndTimestamp(item.framenum(), item.timestamp()) - elif isinstance(item, ImageFileModelItem): - self.controls.setFilename(os.path.basename(item.filename())) - if new_index != self.treeview.currentIndex(): - self.treeview.setCurrentIndex(new_index) + elif isinstance(new_image, ImageFileModelItem): + self.controls.setFilename(os.path.basename(new_image.filename())) + if new_image.index() != self.treeview.currentIndex(): + self.treeview.setCurrentIndex(new_image.index()) def initShortcuts(self): # TODO clean up, make configurable @@ -141,7 +141,7 @@ class MainWindow(QMainWindow): self.labeltool.pluginLoaded. connect(self.onPluginLoaded) self.labeltool.statusMessage. connect(self.onStatusMessage) self.labeltool.annotationsLoaded. connect(self.onAnnotationsLoaded) - self.labeltool.currentIndexChanged.connect(self.onCurrentIndexChanged) + self.labeltool.currentImageChanged.connect(self.onCurrentImageChanged) def loadApplicationSettings(self): settings = QSettings() From d463adaece39c1797d8d372790b5f2beff063224 Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 09:23:57 +0200 Subject: [PATCH 04/21] Load images via LabelTool/Container --- sloth/annotations/container.py | 2 +- sloth/core/labeltool.py | 4 ++++ sloth/gui/annotationscene.py | 6 ++++-- sloth/gui/labeltool.py | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/sloth/annotations/container.py b/sloth/annotations/container.py index 8f5e09a..577f355 100644 --- a/sloth/annotations/container.py +++ b/sloth/annotations/container.py @@ -14,7 +14,7 @@ try: import yaml except: pass - +import okapy class AnnotationContainerFactory: def __init__(self, containers): diff --git a/sloth/core/labeltool.py b/sloth/core/labeltool.py index 2e8d549..763a854 100755 --- a/sloth/core/labeltool.py +++ b/sloth/core/labeltool.py @@ -172,6 +172,10 @@ class LabelTool(QObject): self._current_image = image self.currentImageChanged.emit(self._current_image.index()) + def getImage(self, item): + # TODO: Also handle video frames + self.container_.loadImage(item.filename()) + def getAnnotationFilePatterns(self): return self.container_factory_.patterns() diff --git a/sloth/gui/annotationscene.py b/sloth/gui/annotationscene.py index 5941198..234e4c7 100644 --- a/sloth/gui/annotationscene.py +++ b/sloth/gui/annotationscene.py @@ -13,7 +13,7 @@ class AnnotationScene(QGraphicsScene): # TODO signal itemadded - def __init__(self, items=None, inserters=None, parent=None): + def __init__(self, labeltool, items=None, inserters=None, parent=None): super(AnnotationScene, self).__init__(parent) self.model_ = None @@ -22,6 +22,7 @@ class AnnotationScene(QGraphicsScene): self.debug_ = True self.message_ = "" self.last_key_ = None + self.labeltool_ = labeltool self.itemfactory_ = Factory(items) self.inserterfactory_ = Factory(inserters) @@ -79,7 +80,8 @@ class AnnotationScene(QGraphicsScene): return assert self.root_.model() == self.model_ - self.image_ = self.root_.data(ImageRole).toPyObject() + item = self.model_.itemFromIndex(root) + self.image_ = self.labeltool_.getImage(item) self.pixmap_ = QPixmap(okapy.guiqt.toQImage(self.image_)) item = QGraphicsPixmapItem(self.pixmap_) item.setZValue(-1) diff --git a/sloth/gui/labeltool.py b/sloth/gui/labeltool.py index c7f3a25..0a8d25b 100755 --- a/sloth/gui/labeltool.py +++ b/sloth/gui/labeltool.py @@ -91,7 +91,7 @@ class MainWindow(QMainWindow): def setupGui(self): self.ui = uic.loadUi(os.path.join(GUIDIR, "labeltool.ui"), self) - self.scene = AnnotationScene(items=config.ITEMS, inserters=config.INSERTERS) + self.scene = AnnotationScene(self.labeltool, items=config.ITEMS, inserters=config.INSERTERS) self.view = GraphicsView(self) self.view.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.view.setScene(self.scene) From f7a38da38365a90bc4dddaf31a63b4e635095c1e Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 10:24:16 +0200 Subject: [PATCH 05/21] Fix going to next/previous image --- sloth/annotations/model.py | 16 ++++++++++++++++ sloth/core/labeltool.py | 12 ++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index f120c48..ebce7eb 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -34,6 +34,22 @@ class ModelItem(QObject): assert self.parent_ is None self.parent_ = parent + def getPreviousSibling(self): + if self.parent_ is not None: + c = self.parent().children() + row = c.index(self) + if row > 0: + return c[row-1] + return None + + def getNextSibling(self): + if self.parent_ is not None: + c = self.parent().children() + row = c.index(self) + if row < len(c) - 2: + return c[row+1] + return None + def setIndex(self, index): assert self._pindex is None self._pindex = QPersistentModelIndex(index) diff --git a/sloth/core/labeltool.py b/sloth/core/labeltool.py index 763a854..8c5415c 100755 --- a/sloth/core/labeltool.py +++ b/sloth/core/labeltool.py @@ -139,15 +139,15 @@ class LabelTool(QObject): def gotoNext(self): # TODO move this to the scene - if self._model is not None and self.current_index_ is not None: - next_index = self._model.getNextIndex(self.current_index_) - self.setCurrentIndex(next_index) + if self._model is not None and self._current_image is not None: + next_image = self._current_image.getNextSibling() + self.setCurrentImage(next_image) def gotoPrevious(self): # TODO move this to the scene - if self._model is not None and self.current_index_ is not None: - prev_index = self._model.getPreviousIndex(self.current_index_) - self.setCurrentIndex(prev_index) + if self._model is not None and self._current_image is not None: + prev_image = self._current_image.getPreviousSibling() + self.setCurrentImage(prev_image) def updateModified(self): """update all GUI elements which depend on the state of the model, From 092b9673dcc2450ad4be1590fd20ff71a5897c52 Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 12:15:28 +0200 Subject: [PATCH 06/21] Fix bug with image loading --- sloth/core/labeltool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sloth/core/labeltool.py b/sloth/core/labeltool.py index 8c5415c..2147e40 100755 --- a/sloth/core/labeltool.py +++ b/sloth/core/labeltool.py @@ -174,7 +174,7 @@ class LabelTool(QObject): def getImage(self, item): # TODO: Also handle video frames - self.container_.loadImage(item.filename()) + return self.container_.loadImage(item.filename()) def getAnnotationFilePatterns(self): return self.container_factory_.patterns() From d5dbd9140227803fc56082fd393687af3d78c951 Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 12:15:58 +0200 Subject: [PATCH 07/21] Don't derive ModelItem from QObject as it causes problems --- sloth/annotations/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index ebce7eb..42f56aa 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -2,12 +2,12 @@ The annotationmodel module contains the classes for the AnnotationModel. """ from PyQt4.QtGui import QTreeView, QSortFilterProxyModel, QAbstractItemView -from PyQt4.QtCore import QObject, QModelIndex, QPersistentModelIndex, QAbstractItemModel, QVariant, Qt, pyqtSignal +from PyQt4.QtCore import QModelIndex, QPersistentModelIndex, QAbstractItemModel, QVariant, Qt, pyqtSignal import os.path ItemRole, TypeRole, DataRole, ImageRole = [Qt.UserRole + i + 1 for i in range(4)] -class ModelItem(QObject): +class ModelItem: def __init__(self): self.children_ = [] self._pindex = None From 741c31b8fbc2aa16ba7ddb5d4f1cf6652be51e3f Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 13:32:04 +0200 Subject: [PATCH 08/21] Continue with model refactoring Workable now, but some cleanup left to do --- sloth/annotations/model.py | 101 ++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 41 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index 42f56aa..d554377 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -10,7 +10,7 @@ ItemRole, TypeRole, DataRole, ImageRole = [Qt.UserRole + i + 1 for i in range(4) class ModelItem: def __init__(self): self.children_ = [] - self._pindex = None + self._pindex = [] self.model_ = None self.parent_ = None @@ -30,44 +30,51 @@ class ModelItem: else: return QVariant() - def setParent(self, parent): - assert self.parent_ is None - self.parent_ = parent + def getPosOfChild(self, item): + return self.children_.index(item) + + def getChildAt(self, pos): + return self.children_[pos] def getPreviousSibling(self): - if self.parent_ is not None: - c = self.parent().children() - row = c.index(self) + p = self.parent() + if p is not None: + row = p.getPosOfChild(self) if row > 0: - return c[row-1] + return p.getChildAt(row-1) return None def getNextSibling(self): - if self.parent_ is not None: - c = self.parent().children() - row = c.index(self) - if row < len(c) - 2: - return c[row+1] + p = self.parent() + if p is not None: + row = p.getPosOfChild(self) + if row < len(p.children()) - 2: + return p.getChildAt(row+1) return None - def setIndex(self, index): - assert self._pindex is None - self._pindex = QPersistentModelIndex(index) - if index.isValid(): - self.model_ = index.model() + def _attachToModel(self, model): + assert self.model() is None + assert self.parent() is not None + assert self.parent().model() is not None - def pindex(self): - assert self._pindex is not None - return self._pindex + self.model_ = model + p = self.parent() + + # Find out own index + index = self.model().createIndex(p.getPosOfChild(self), 0, self) + self._pindex = [QPersistentModelIndex(index), QPersistentModelIndex()] + + # Recurse + for item in self.children(): + item._attachToModel(model) + + def pindex(self, column=0): + assert self._pindex + return self._pindex[column] def index(self, column=0): - # TODO: The two columns can probably be handled better... - # Maybe use a list of indices, with the second being QModelIndex() - # for everything except the KeyValueModelItems? - assert self._pindex is not None - if column != 0: - return QModelIndex() - return QModelIndex(self._pindex) + assert self._pindex + return QModelIndex(self._pindex[column]) def parentIndex(self): if self.parent_ is not None: @@ -75,15 +82,27 @@ class ModelItem: else: return QModelIndex() + def getNDescendants(self): + n = 1 + for item in self.children_: + n += item.getNDescendants() + return n + def appendChild(self, item): - next_row = len(self.children_) - index = self.index() - self.model_.beginInsertRows(index, next_row, next_row) - self.children_.append(item) - item.setParent(self) - self.model_.endInsertRows() - item_index = self.model().createIndex(next_row, 0, item) - item.setIndex(item_index) + assert isinstance(item, ModelItem) + assert item.model() is None + assert item.parent() is None + + if self.model() is not None: + next_row = len(self.children_) + self.model().beginInsertRows(self.index(), next_row, next_row) + + item.parent_ = self + self.children().append(item) + + if self.model() is not None: + item._attachToModel(self.model()) + self.model().endInsertRows() def deleteAllChildren(self): for child in self.children_: @@ -114,7 +133,7 @@ class RootModelItem(ModelItem): def __init__(self, model): ModelItem.__init__(self) self.model_ = model - self.setIndex(QModelIndex()) + self._pindex = [QPersistentModelIndex(), QPersistentModelIndex()] def appendChild(self, item): if isinstance(item, FileModelItem): @@ -233,8 +252,8 @@ class FrameModelItem(ImageModelItem): del self.frameinfo_['annotations'][pos] self.deleteChild(pos) - def data(self, index, role=Qt.DisplayRole, column=0): - if role == Qt.DisplayRole and index.column() == 0: + def data(self, role=Qt.DisplayRole, column=0): + if role == Qt.DisplayRole and column == 0: return "%d / %.3f" % (self.framenum(), self.timestamp()) return QVariant() @@ -249,7 +268,7 @@ class AnnotationModelItem(ModelItem): for key, value in annotation.iteritems(): if key == None: continue - self.addChild(KeyValueModelItem(key)) + self.appendChild(KeyValueModelItem(key)) def type(self): return self.annotation_['type'] @@ -281,7 +300,7 @@ class AnnotationModelItem(ModelItem): return True return False - def data(self, index, role=Qt.DisplayRole, column=0): + def data(self, role=Qt.DisplayRole, column=0): if role == Qt.DisplayRole and column == 0: return self.type() elif role == TypeRole: From 09abec135eff8bb7d27293c2f21ada2375a0164b Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 13:56:05 +0200 Subject: [PATCH 09/21] Remove unneeded functions --- sloth/annotations/model.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index d554377..66570f7 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -76,18 +76,6 @@ class ModelItem: assert self._pindex return QModelIndex(self._pindex[column]) - def parentIndex(self): - if self.parent_ is not None: - return self.parent_.index() - else: - return QModelIndex() - - def getNDescendants(self): - n = 1 - for item in self.children_: - n += item.getNDescendants() - return n - def appendChild(self, item): assert isinstance(item, ModelItem) assert item.model() is None From fd113e727e4f67c4094b0eecd6dd9821106144f3 Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 14:14:54 +0200 Subject: [PATCH 10/21] Remove index from delegated setData calls --- sloth/annotations/model.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index 66570f7..92fef05 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -30,6 +30,9 @@ class ModelItem: else: return QVariant() + def setData(self, value, role=Qt.DisplayRole, column=0): + pass + def getPosOfChild(self, item): return self.children_.index(item) @@ -180,7 +183,7 @@ class ImageFileModelItem(FileModelItem, ImageModelItem): 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')): ann[None] = None - #child.setData(index, QVariant(ann), DataRole) + #child.setData(QVariant(ann), DataRole) child_found = True break if not child_found: @@ -261,30 +264,33 @@ class AnnotationModelItem(ModelItem): def type(self): return self.annotation_['type'] - def setData(self, index, data, role): + def setData(self, value, role, column=0): if role == DataRole: print self.annotation_ - data = data.toPyObject() - print data, type(data) + value = value.toPyObject() + print value, type(value) print self.annotation_ - for key, value in data.iteritems(): - print key, value + for key, val in value.iteritems(): + print key, val if not key in self.annotation_: print "not in annotation: ", key - self.addChild(KeyValueModelItem(key)) - self.annotation_[key] = data[key] + self.appendChild(KeyValueModelItem(key)) + self.annotation_[key] = val for key in self.annotation_.keys(): - if not key in data: + if not key in value: # TODO - self.deleteChild() # TODO + self.deleteChild() del self.annotation_[key] else: - self.annotation_[key] = data[key] - # TODO: Emit data changed signal + self.annotation_[key] = value[key] + if self.model() is not None: + # TODO: Emit for child with changed key, not for self + self.model().dataChanged.emit(self.index(), self.index()) print "new annotation:", self.annotation_ - # TODO: Emit data changed signal + if self.model() is not None: + self.model().dataChanged.emit(self.index(), self.index()) return True return False @@ -365,7 +371,7 @@ class AnnotationModel(QAbstractItemModel): if not index.isValid(): return False item = self.itemFromIndex(index) - return item.setData(index, value, role) + return item.setData(value, role, index.column()) def flags(self, index): return Qt.ItemIsEnabled | Qt.ItemIsSelectable From 70146d3e0e6042d1f05660a79e19857e68722564 Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 14:15:36 +0200 Subject: [PATCH 11/21] Always call base class data function if not handled by derived class --- sloth/annotations/model.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index 92fef05..8c3414d 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -246,7 +246,7 @@ class FrameModelItem(ImageModelItem): def data(self, role=Qt.DisplayRole, column=0): if role == Qt.DisplayRole and column == 0: return "%d / %.3f" % (self.framenum(), self.timestamp()) - return QVariant() + return ImageModelItem.data(self, role, column) class AnnotationModelItem(ModelItem): def __init__(self, annotation): @@ -301,7 +301,7 @@ class AnnotationModelItem(ModelItem): return self.type() elif role == DataRole: return self.annotation_ - return QVariant() + return ModelItem.data(self, role, column) def setValue(self, key, value): self.annotation_[key] = value @@ -326,6 +326,8 @@ class KeyValueModelItem(ModelItem): return self.parent().value(self._key) else: return QVariant() + else: + return ModelItem.data(self, role, column) class AnnotationModel(QAbstractItemModel): # signals From 1e40eafa338aa3d3d45ece644acaa26114625608 Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 14:15:55 +0200 Subject: [PATCH 12/21] Emit dataChanged signal --- sloth/annotations/model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index 8c3414d..f4101a6 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -305,7 +305,8 @@ class AnnotationModelItem(ModelItem): def setValue(self, key, value): self.annotation_[key] = value - # TODO: Emit data changed signal + if self.model() is not None: + self.model().dataChanged.emit(self.index(), self.index()) def value(self, key): return self.annotation_[key] From 76e538d8ce14d182ac7708bf5dacc04f0c967c4e Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 14:16:08 +0200 Subject: [PATCH 13/21] Remove unneeded call --- sloth/gui/annotationscene.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sloth/gui/annotationscene.py b/sloth/gui/annotationscene.py index 234e4c7..7068719 100644 --- a/sloth/gui/annotationscene.py +++ b/sloth/gui/annotationscene.py @@ -274,7 +274,6 @@ class AnnotationScene(QGraphicsScene): pass def itemFromIndex(self, index): - index = index.model().mapToSource(index) # TODO: solve this somehow else for item in self.items(): # some graphics items will not have an index method, # we just skip these From 03d00da1b3c4fba6ab8de12dd7b3753e13e5cab4 Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 14:30:49 +0200 Subject: [PATCH 14/21] Fix display of attribute values --- sloth/annotations/model.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index f4101a6..4bd3d23 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -13,6 +13,7 @@ class ModelItem: self._pindex = [] self.model_ = None self.parent_ = None + self._columns = 1 def children(self): return self.children_ @@ -57,6 +58,7 @@ class ModelItem: def _attachToModel(self, model): assert self.model() is None + assert not self._pindex assert self.parent() is not None assert self.parent().model() is not None @@ -64,8 +66,12 @@ class ModelItem: p = self.parent() # Find out own index - index = self.model().createIndex(p.getPosOfChild(self), 0, self) - self._pindex = [QPersistentModelIndex(index), QPersistentModelIndex()] + for i in range(self.model().columnCount()): + if i < self._columns: + index = self.model().createIndex(p.getPosOfChild(self), i, self) + else: + index = QModelIndex() + self._pindex.append(QPersistentModelIndex(index)) # Recurse for item in self.children(): @@ -318,6 +324,7 @@ class KeyValueModelItem(ModelItem): def __init__(self, key): ModelItem.__init__(self) self._key = key + self._columns = 2 def data(self, role=Qt.DisplayRole, column=0): if role == Qt.DisplayRole: From 351ced184dc0196122c0a8e9bb3d5e7af5fcf06a Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 15:02:21 +0200 Subject: [PATCH 15/21] Consistent naming of "protected" members --- sloth/annotations/model.py | 100 ++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index 4bd3d23..a58958c 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -9,21 +9,21 @@ ItemRole, TypeRole, DataRole, ImageRole = [Qt.UserRole + i + 1 for i in range(4) class ModelItem: def __init__(self): - self.children_ = [] + self._children = [] self._pindex = [] - self.model_ = None - self.parent_ = None + self._model = None + self._parent = None self._columns = 1 def children(self): - return self.children_ + return self._children def model(self): - return self.model_ + return self._model def parent(self): - assert self.parent_ != self - return self.parent_ + assert self._parent != self + return self._parent def data(self, role=Qt.DisplayRole, column=0): if role == ItemRole: @@ -35,10 +35,10 @@ class ModelItem: pass def getPosOfChild(self, item): - return self.children_.index(item) + return self._children.index(item) def getChildAt(self, pos): - return self.children_[pos] + return self._children[pos] def getPreviousSibling(self): p = self.parent() @@ -62,7 +62,7 @@ class ModelItem: assert self.parent() is not None assert self.parent().model() is not None - self.model_ = model + self._model = model p = self.parent() # Find out own index @@ -91,10 +91,10 @@ class ModelItem: assert item.parent() is None if self.model() is not None: - next_row = len(self.children_) + next_row = len(self._children) self.model().beginInsertRows(self.index(), next_row, next_row) - item.parent_ = self + item._parent = self self.children().append(item) if self.model() is not None: @@ -102,12 +102,12 @@ class ModelItem: self.model().endInsertRows() def deleteAllChildren(self): - for child in self.children_: + for child in self._children: child.deleteAllChildren() - self.model_.beginRemoveRows(self.index(), 0, len(self.children_) - 1) - self.children_ = [] - self.model_.endRemoveRows() + self._model.beginRemoveRows(self.index(), 0, len(self._children) - 1) + self._children = [] + self._model.endRemoveRows() def delete(self): if self.parent() is None: @@ -117,20 +117,20 @@ class ModelItem: def deleteChild(self, arg): if isinstance(arg, ModelItem): - self.deleteChild(self.children_.index(arg)) + self.deleteChild(self._children.index(arg)) else: - if arg < 0 or arg >= len(self.children_): + if arg < 0 or arg >= len(self._children): raise IndexError("child index out of range") - self.children_[arg].deleteAllChildren() - self.model_.beginRemoveRows(self.index(), arg, arg) - del self.children_[arg] - self.model_.endRemoveRows() + self._children[arg].deleteAllChildren() + self._model.beginRemoveRows(self.index(), arg, arg) + del self._children[arg] + self._model.endRemoveRows() class RootModelItem(ModelItem): def __init__(self, model): ModelItem.__init__(self) - self.model_ = model - self._pindex = [QPersistentModelIndex(), QPersistentModelIndex()] + self._model = model + self._pindex = [QPersistentModelIndex() for i in range(model.columnCount())] def appendChild(self, item): if isinstance(item, FileModelItem): @@ -185,7 +185,7 @@ class ImageFileModelItem(FileModelItem, ImageModelItem): # TODO def updateAnnotation(self, ann): child_found = False - for child in self.children_: + 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')): ann[None] = None @@ -235,7 +235,7 @@ class FrameModelItem(ImageModelItem): # TODO def updateAnnotation(self, ann): child_found = False - for child in self.children_: + 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')): ann[None] = None @@ -257,10 +257,10 @@ class FrameModelItem(ImageModelItem): class AnnotationModelItem(ModelItem): def __init__(self, annotation): ModelItem.__init__(self) - self.annotation_ = annotation + self._annotation = annotation # dummy key/value so that pyqt does not convert the dict # into a QVariantMap while communicating with the Views - self.annotation_[None] = None + self._annotation[None] = None for key, value in annotation.iteritems(): if key == None: @@ -268,33 +268,33 @@ class AnnotationModelItem(ModelItem): self.appendChild(KeyValueModelItem(key)) def type(self): - return self.annotation_['type'] + return self._annotation['type'] def setData(self, value, role, column=0): if role == DataRole: - print self.annotation_ + print self._annotation value = value.toPyObject() print value, type(value) - print self.annotation_ + print self._annotation for key, val in value.iteritems(): print key, val - if not key in self.annotation_: + if not key in self._annotation: print "not in annotation: ", key self.appendChild(KeyValueModelItem(key)) - self.annotation_[key] = val + self._annotation[key] = val - for key in self.annotation_.keys(): + for key in self._annotation.keys(): if not key in value: # TODO self.deleteChild() - del self.annotation_[key] + del self._annotation[key] else: - self.annotation_[key] = value[key] + self._annotation[key] = value[key] if self.model() is not None: # TODO: Emit for child with changed key, not for self self.model().dataChanged.emit(self.index(), self.index()) - print "new annotation:", self.annotation_ + print "new annotation:", self._annotation if self.model() is not None: self.model().dataChanged.emit(self.index(), self.index()) return True @@ -306,19 +306,19 @@ class AnnotationModelItem(ModelItem): elif role == TypeRole: return self.type() elif role == DataRole: - return self.annotation_ + return self._annotation return ModelItem.data(self, role, column) def setValue(self, key, value): - self.annotation_[key] = value + self._annotation[key] = value if self.model() is not None: self.model().dataChanged.emit(self.index(), self.index()) def value(self, key): - return self.annotation_[key] + return self._annotation[key] def has_key(self, key): - return self.annotation_.has_key(key) + return self._annotation.has_key(key) class KeyValueModelItem(ModelItem): def __init__(self, key): @@ -343,10 +343,10 @@ class AnnotationModel(QAbstractItemModel): def __init__(self, annotations, parent=None): QAbstractItemModel.__init__(self, parent) - self.annotations_ = annotations - self.dirty_ = False - self.root_ = RootModelItem(self) - self.root_.appendFileItems(self.annotations_) + self._annotations = annotations + self._dirty = False + self._root = RootModelItem(self) + self._root.appendFileItems(annotations) # QAbstractItemModel overloads def columnCount(self, index=QModelIndex()): @@ -394,18 +394,18 @@ class AnnotationModel(QAbstractItemModel): # Own methods def dirty(self): - return self.dirty_ + return self._dirty def setDirty(self, dirty=True): - if dirty != self.dirty_: - self.dirty_ = dirty - self.dirtyChanged.emit(self.dirty_) + if dirty != self._dirty: + self._dirty = dirty + self.dirtyChanged.emit(self._dirty) def itemFromIndex(self, index): index = QModelIndex(index) # explicitly convert from QPersistentModelIndex if index.isValid(): return index.internalPointer() - return self.root_ + return self._root ####################################################################################### From a30ede687433e6093ac0664a88661829107aa2e9 Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 16:11:44 +0200 Subject: [PATCH 16/21] Initialize model of LabelTool --- sloth/core/labeltool.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sloth/core/labeltool.py b/sloth/core/labeltool.py index 2147e40..c3f8577 100755 --- a/sloth/core/labeltool.py +++ b/sloth/core/labeltool.py @@ -36,6 +36,7 @@ class LabelTool(QObject): self.container_factory_ = AnnotationContainerFactory(config.CONTAINERS) self.container_ = AnnotationContainer() self._current_image = None + self._model = None # Load annotation file if len(args) > 0: From 72f373bcdff816cf5f30142c9e51fea45ac88c9a Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 16:13:06 +0200 Subject: [PATCH 17/21] Finish model refactoring Annotation attributes are not displayed for some reason... --- sloth/annotations/model.py | 106 +++++++++++++++---------------------- 1 file changed, 43 insertions(+), 63 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index a58958c..6358bfb 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -32,7 +32,7 @@ class ModelItem: return QVariant() def setData(self, value, role=Qt.DisplayRole, column=0): - pass + return False def getPosOfChild(self, item): return self._children.index(item) @@ -167,87 +167,66 @@ class FileModelItem(ModelItem): return VideoFileModelItem(fileinfo) class ImageModelItem(ModelItem): - pass + def __init__(self, annotations): + ModelItem.__init__(self) + for ann in annotations: + self.addAnnotation(ann) -class ImageFileModelItem(FileModelItem, ImageModelItem): - def __init__(self, fileinfo): - FileModelItem.__init__(self, fileinfo) - - for ann in fileinfo['annotations']: - item = AnnotationModelItem(ann) - self.appendChild(item) + def appendChild(self, item): + if isinstance(item, AnnotationModelItem): + ModelItem.appendChild(self, item) + else: + raise TypeError("Only AnnotationModelItems can be attached to ImageModelItem") def addAnnotation(self, ann): - self.fileinfo_['annotations'].append(ann) - item = AnnotationModelItem(ann) - self.appendChild(item) + self.appendChild(AnnotationModelItem(ann)) + + def removeAnnotation(self, pos): + self.deleteChild(pos) - # TODO def updateAnnotation(self, ann): - child_found = False 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')): ann[None] = None - #child.setData(QVariant(ann), DataRole) - child_found = True - break - if not child_found: - raise Exception("No ImageFileModelItem found that could be updated!") + child.setData(QVariant(ann), DataRole, 1) + return + raise Exception("No AnnotationModelItem found that could be updated!") - def removeAnnotation(self, pos): - del self.fileinfo_['annotations'][pos] - self.deleteChild(pos) +class ImageFileModelItem(FileModelItem, ImageModelItem): + def __init__(self, fileinfo): + if fileinfo.has_key("annotations"): + ImageModelItem.__init__(self, fileinfo["annotations"]) + del fileinfo["annotations"] + FileModelItem.__init__(self, fileinfo) def data(self, role=Qt.DisplayRole, column=0): if role == DataRole: - return self.fileinfo_ + return self._fileinfo return FileModelItem.data(self, role) class VideoFileModelItem(FileModelItem): def __init__(self, fileinfo): + frameinfos = fileinfo.get("frames", []) + if fileinfo.has_key("frames"): + del fileinfo["frames"] FileModelItem.__init__(self, fileinfo) - for frameinfo in fileinfo['frames']: - item = FrameModelItem(frameinfo) - self.appendChild(item) + for frameinfo in frameinfos: + self.appendChild(FrameModelItem(frameinfo)) class FrameModelItem(ImageModelItem): def __init__(self, frameinfo): - ModelItem.__init__(self) - self.frameinfo_ = frameinfo - - for ann in frameinfo['annotations']: - item = AnnotationModelItem(ann) - self.appendChild(item) + if frameinfo.has_key("annotations"): + ImageModelItem.__init__(self, frameinfo["annotations"]) + del frameinfo["annotations"] + self._frameinfo = frameinfo def framenum(self): - return int(self.frameinfo_.get('num', -1)) + return int(self._frameinfo.get('num', -1)) def timestamp(self): - return float(self.frameinfo_.get('timestamp', -1)) - - def addAnnotation(self, ann): - self.frameinfo_['annotations'].append(ann) - item = AnnotationModelItem(ann) - self.appendChild(item) - - # TODO - def updateAnnotation(self, ann): - child_found = False - 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')): - ann[None] = None - #child.setData(index, QVariant(ann), DataRole) - child_found = True - break - if not child_found: - raise Exception("No FrameModelItem found that could be updated!") - - def removeAnnotation(self, pos): - del self.frameinfo_['annotations'][pos] - self.deleteChild(pos) + return float(self._frameinfo.get('timestamp', -1)) def data(self, role=Qt.DisplayRole, column=0): if role == Qt.DisplayRole and column == 0: @@ -280,23 +259,21 @@ class AnnotationModelItem(ModelItem): print key, val if not key in self._annotation: print "not in annotation: ", key - self.appendChild(KeyValueModelItem(key)) self._annotation[key] = val + self.appendChild(KeyValueModelItem(key)) for key in self._annotation.keys(): if not key in value: - # TODO - self.deleteChild() + for child in [e for e in self.children() if e.key() == key]: + self.deleteChild(child) del self._annotation[key] else: self._annotation[key] = value[key] if self.model() is not None: - # TODO: Emit for child with changed key, not for self - self.model().dataChanged.emit(self.index(), self.index()) + for child in [e for e in self.children() if e.key() == key]: + self.model().dataChanged.emit(child.index(1), child.index(1)) print "new annotation:", self._annotation - if self.model() is not None: - self.model().dataChanged.emit(self.index(), self.index()) return True return False @@ -326,6 +303,9 @@ class KeyValueModelItem(ModelItem): self._key = key self._columns = 2 + def key(self): + return self._key + def data(self, role=Qt.DisplayRole, column=0): if role == Qt.DisplayRole: if column == 0: From 1bd1b808a3c91199df18f51305c0ea40d1aae658 Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 16:15:23 +0200 Subject: [PATCH 18/21] Add a TODO --- sloth/annotations/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index 6358bfb..e9e8077 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -376,6 +376,7 @@ class AnnotationModel(QAbstractItemModel): def dirty(self): return self._dirty + # TODO: This might need to be updated from within the ModelItems when they change def setDirty(self, dirty=True): if dirty != self._dirty: self._dirty = dirty From 1f7bdb098b8468f706536758f2c375901ce7d13d Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 16:40:27 +0200 Subject: [PATCH 19/21] Fix problem with constructor ordering --- sloth/annotations/model.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index e9e8077..de126b9 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -195,10 +195,11 @@ class ImageModelItem(ModelItem): class ImageFileModelItem(FileModelItem, ImageModelItem): def __init__(self, fileinfo): + annotations = fileinfo.get("annotations", []) if fileinfo.has_key("annotations"): - ImageModelItem.__init__(self, fileinfo["annotations"]) del fileinfo["annotations"] FileModelItem.__init__(self, fileinfo) + ImageModelItem.__init__(self, annotations) def data(self, role=Qt.DisplayRole, column=0): if role == DataRole: From c1b8ee7568f6891c9e1019d00a8db2fadd13693f Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 16:44:26 +0200 Subject: [PATCH 20/21] Add multiple children at once in RootModelItem --- sloth/annotations/model.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index de126b9..d3b85ef 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -101,6 +101,26 @@ class ModelItem: item._attachToModel(self.model()) self.model().endInsertRows() + def appendChildren(self, items): + for item in items: + assert isinstance(item, ModelItem) + assert item.model() is None + assert item.parent() is None + + if self.model() is not None: + next_row = len(self._children) + self.model().beginInsertRows(self.index(), next_row, next_row + len(items) - 1) + + for item in items: + item._parent = self + self.children().append(item) + + if self.model() is not None: + for item in items: + item._attachToModel(self.model()) + + self.model().endInsertRows() + def deleteAllChildren(self): for child in self._children: child.deleteAllChildren() @@ -143,8 +163,8 @@ class RootModelItem(ModelItem): self.appendChild(item) def appendFileItems(self, fileinfos): - for fileinfo in fileinfos: - self.appendFileItem(fileinfo) + items = [FileModelItem.create(fi) for fi in fileinfos] + self.appendChildren(items) class FileModelItem(ModelItem): def __init__(self, fileinfo): From 8d71af7f6506f2d0110aaf21b0a7283250f411d4 Mon Sep 17 00:00:00 2001 From: Mika Fischer Date: Tue, 14 Jun 2011 17:34:20 +0200 Subject: [PATCH 21/21] Speed up generation of indices --- sloth/annotations/model.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/sloth/annotations/model.py b/sloth/annotations/model.py index d3b85ef..023c539 100644 --- a/sloth/annotations/model.py +++ b/sloth/annotations/model.py @@ -56,26 +56,26 @@ class ModelItem: return p.getChildAt(row+1) return None - def _attachToModel(self, model): + def _attachToModel(self, model, indices): assert self.model() is None assert not self._pindex assert self.parent() is not None assert self.parent().model() is not None self._model = model - p = self.parent() - # Find out own index for i in range(self.model().columnCount()): if i < self._columns: - index = self.model().createIndex(p.getPosOfChild(self), i, self) + ind = indices[i] else: - index = QModelIndex() - self._pindex.append(QPersistentModelIndex(index)) + ind = QModelIndex() + self._pindex.append(QPersistentModelIndex(ind)) # Recurse - for item in self.children(): - item._attachToModel(model) + for i in range(len(self.children())): + item = self.children()[i] + cindices = [self.model().createIndex(i, j, item) for j in range(item._columns)] + item._attachToModel(model, cindices) def pindex(self, column=0): assert self._pindex @@ -98,7 +98,8 @@ class ModelItem: self.children().append(item) if self.model() is not None: - item._attachToModel(self.model()) + indices = [self.model().createIndex(next_row, i, item) for i in range(item._columns)] + item._attachToModel(self.model(), indices) self.model().endInsertRows() def appendChildren(self, items): @@ -116,8 +117,10 @@ class ModelItem: self.children().append(item) if self.model() is not None: - for item in items: - item._attachToModel(self.model()) + for i in range(len(items)): + item = items[i] + indices = [self.model().createIndex(next_row+i, j, item) for j in range(item._columns)] + item._attachToModel(self.model(), indices) self.model().endInsertRows() @@ -299,6 +302,7 @@ class AnnotationModelItem(ModelItem): return False def data(self, role=Qt.DisplayRole, column=0): + print "Annotation:", self._annotation if role == Qt.DisplayRole and column == 0: return self.type() elif role == TypeRole: @@ -328,6 +332,7 @@ class KeyValueModelItem(ModelItem): return self._key def data(self, role=Qt.DisplayRole, column=0): + print "KeyValue:", self._key if role == Qt.DisplayRole: if column == 0: return self._key